WPF:如何使窗口不可调整大小,但保留框架?

8
我有一个没有标题栏的窗口(WindowStyle == WindowStyle.None),整个窗口使用Aero玻璃效果。当我将其设置为不可调整大小(ResizeMode == ResizeMode.NoResize)时,玻璃效果消失了,我的控件就悬浮在空中(实质上,窗口本身消失了,但保留了其内容)。
有没有办法让窗口不可调整大小而不会去掉窗口边框呢?
我已经阅读了问题Enable Vista glass effect on a borderless WPF window,但那不是我想要的——我想要保留窗口边框。如果您想看看我希望我的窗口看起来像什么,请使用启用Aero后按Alt+Tab。
澄清一下,我不希望调整大小光标在悬停窗口边框时出现。这基本上就是我想让我的窗口看起来像什么:

Projector

解决方案不一定只限于WPF —— 我也可以通过使用Win32 API来实现这一点。
4个回答

13

你可以挂钩wndproc并拦截WM_WINDOWPOSCHANGING消息。虽然不是严格的WPF,但这可能是你最好的选择。

如果你想隐藏调整大小的光标,则最好的方法是拦截WM_NCHITTEST。调用DefWindowProc(以获取默认行为),并测试返回值;如果它是HTBOTTOM、HTBOTTOMLEFT、HTBOTTOMRIGHT、HTTOP、HTTOPLEFT或HTTOPRIGHT,则将返回值更改为HTBORDER。


您仍然会有调整大小的光标,而OP在另一条评论中表示这些光标是不可取的。 - Joe White
太棒了!谢谢!再加油,还有275个声望等着你。 :) - Sasha Chedygov

11

基于 Eric 的答案。

示例图片

public partial class MainWindow : Window
{
    [DllImport("DwmApi.dll")]
    public static extern int DwmExtendFrameIntoClientArea(
        IntPtr hwnd,
        ref MARGINS pMarInset);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr DefWindowProc(
        IntPtr hWnd,
        int msg,
        IntPtr wParam,
        IntPtr lParam);

    private const int WM_NCHITTEST = 0x0084;
    private const int HTBORDER = 18;
    private const int HTBOTTOM = 15;
    private const int HTBOTTOMLEFT = 16;
    private const int HTBOTTOMRIGHT = 17;
    private const int HTLEFT = 10;
    private const int HTRIGHT = 11;
    private const int HTTOP = 12;
    private const int HTTOPLEFT = 13;
    private const int HTTOPRIGHT = 14;

    public MainWindow()
    {
        InitializeComponent();

        this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
    }

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        try
        {
            // Obtain the window handle for WPF application
            IntPtr mainWindowPtr = new WindowInteropHelper(this).Handle;
            HwndSource mainWindowSrc = HwndSource.FromHwnd(mainWindowPtr);
            mainWindowSrc.CompositionTarget.BackgroundColor = Color.FromArgb(0, 0, 0, 0);
            mainWindowSrc.AddHook(WndProc);

            // Set Margins
            MARGINS margins = new MARGINS();
            margins.cxLeftWidth = 10;
            margins.cxRightWidth = 10;
            margins.cyBottomHeight = 10;
            margins.cyTopHeight = 10;

            int hr = DwmExtendFrameIntoClientArea(mainWindowSrc.Handle, ref margins);
            //
            if (hr < 0)
            {
                //DwmExtendFrameIntoClientArea Failed
            }
        }
        // If not Vista, paint background white.
        catch (DllNotFoundException)
        {
            Application.Current.MainWindow.Background = Brushes.White;
        }
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        // Override the window hit test
        // and if the cursor is over a resize border,
        // return a standard border result instead.
        if (msg == WM_NCHITTEST)
        {
            handled = true;
            var htLocation = DefWindowProc(hwnd, msg, wParam, lParam).ToInt32();
            switch (htLocation)
            {
                case HTBOTTOM:
                case HTBOTTOMLEFT:
                case HTBOTTOMRIGHT:
                case HTLEFT:
                case HTRIGHT:
                case HTTOP:
                case HTTOPLEFT:
                case HTTOPRIGHT:
                    htLocation = HTBORDER;
                    break;
            }

            return new IntPtr(htLocation);
        }

        return IntPtr.Zero;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.Close();
    }
}

[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
    public int cxLeftWidth;      // width of left border that retains its size
    public int cxRightWidth;     // width of right border that retains its size
    public int cyTopHeight;      // height of top border that retains its size
    public int cyBottomHeight;   // height of bottom border that retains its size
};

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="150" Width="200" 
    Background="Transparent"
    WindowStyle="None"
    ResizeMode="CanResize"
>
    <Grid Background="White" Margin="10,10,10,10">
        <Button Content="Go Away" Click="Button_Click" Height="20" Width="100" />
    </Grid>
</Window>

我会公平地把答案给Eric,因为他首先发布了这个想法,但是感谢你提供的可行示例。 :) - Sasha Chedygov
1
这就是我提到它的原因。我只是想看看能否让它工作。 - Cameron MacFarland
1
但是不应该在所有情况下都调用DefWindowProc吗? 在此代码中,仅当msg为WM_NCHITTEST时才调用原始函数。 我想要正确地执行覆盖,任何其他消息都应返回原始函数给出的结果。 - Nyerguds
这个不太行。窗口内框架添加了一个白色边框。如果您使用的是白色背景,那么这很好,但否则它将无法正常工作。 - Ed S.
1
@Ed S. 在MainWindow_Loaded事件处理程序中,将所有边缘的边距从10更改为12将移动边框到WPF窗口区域下方,有效地隐藏它。 - Cameron MacFarland

1

一种hackish的方法是将MinWidth/MaxWidth和MinHeight/MaxHeight属性设置为有效地使其不可调整大小。当然,问题在于你仍然会在边框上得到调整大小的光标。


整个重点是要摆脱调整大小光标,所以这不会有帮助,不幸的是。 - Sasha Chedygov

1
为什么不为窗口创建这个窗口边框呢? 它使用偏移量来设置窗口的颜色。 因此,一个简单的方法就是在你的窗口周围包裹整个边框,然后在其上面获得自己的颜色!

当我说将其包装在窗口周围时,我的意思是基本上包装<边框> <网格/> </边框> - Kevin
但是我无法使其与用户当前的主题相匹配。我可以制作一个自定义边框,看起来与默认的Aero主题完全相同,但如果用户已经自定义了它(或正在使用不同的主题),窗口将无法匹配。 - Sasha Chedygov
嗯...如果你想要定制颜色,为什么不绑定偏移量并让用户选择自己的颜色呢?如果你正在使用MVVM方法,在你的视图模型中设置一个带有颜色代码的字符串,并将其绑定到偏移量上,这样行不行? - Kevin
那不是我的重点。我想让窗口框架与用户当前的主题相匹配。因此,如果他们正在使用一些自定义的黑色Aero主题,如这个,我也希望窗口看起来像那样。让用户费力地创建自己的窗口主题,而且甚至不能完全匹配所选主题,这是不可接受的;而将窗口硬编码为默认的Aero样式(作为妥协)则是一个次优解决方案(尽管我可能不得不求助于此)。 - Sasha Chedygov
为什么不使用WPF的内置支持呢?例如,使用DynamicResource来引用SystemColors?(http://msdn.microsoft.com/en-us/library/system.windows.systemcolors.aspx) - Greg D
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接