RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1128387
Accepted
D .Stark
D .Stark
Asked:2020-05-20 08:23:24 +0000 UTC2020-05-20 08:23:24 +0000 UTC 2020-05-20 08:23:24 +0000 UTC

创建具有标准行为的自定义窗口样式

  • 772

要移除标准窗框,请将属性设置Window.WindowStyle为None。我们得到这个窗口:

在此处输入图像描述

我们看到一个 1 像素的灰色边框,顶部有一个白色(我猜是窗口背景颜色)条纹和一个投影。窗口是可调整大小的,并且调整大小的框架位于可见窗口框架之外(顶部边框除外,Microsoft 决定它应该正好在可见框架内 - 而不是在阴影上)。

我想让这个顶部边框不可见,在顶部留下调整窗口大小的选项。我很高兴有一个解决方案,它可以让我自己的按钮简单地放在这个框架顶部的边缘。

您可以制作一个窗口AllowsTransparency,但随后阴影、窗口大小调整框架将消失(显然,整个非客户区都消失了,窗口完全由我们支配)并最小化/最大化动画。当然,您可以尝试自己完成所有操作:只需使用一些元素(例如 )创建自己的窗口大小调整框架Rectangle,在其下绘制阴影,忘记最小化/最大化动画。

但是有一个非常令人不快的效果 - 当窗口调整大小超出这种非标准框架时,它会抽搐很多。那些。一个正常的窗口调用SetWindowPos会AllowsTransparency导致这样的问题。使用标志SWP_NOCOPYBITS并不能解决问题(这意味着您不需要提供创建处理程序 on WM_WINDOWPOSCHANGING(和 on WM_NCCALCSIZE,据我所知,类似地)以及在那里做什么)。通常这种行为是由于给应用程序更新窗口内容的临时配额过期了,Windows 本身复制了左上角的内容,并用背景颜色填充了剩余的窗口空间(然后应用程序本身必须“赶上”)。在禁用 Aero 的 Windows 7 上,问题仍然存在。

所以问题是如何将自定义元素放置在标准窗口大小调整边框之上,或者消除调用SetWindowPos这种窗口样式的效果。

为什么WindowChrome它不起作用:是的,使用这种WindowChrome抽搐确实消失了,但条件是它GlassFrameThickness不是 0 和NonClientFrameEdgesnot None。后者在指示的一侧添加相同的条带。AllowsTransparency不能与此类属性值一起使用(我只是没有从任务栏最大化窗口)。

该项目正在.NET 4.5 下构建。

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    MSDN.WhiteKnight
    2020-05-25T12:00:42Z2020-05-25T12:00:42Z

    由于新版本 Windows 中的DWM 限制,您将无法在调整大小时完全消除闪烁。事实上,最知名的 WPF 应用程序 Visual Studio 也受到此问题的影响,至少在 VS 2017 中是这样。但在我的测试中,最好的结果是通过处理WM_NCCALCSIZE消息并通过自定义 WM_NCHITTEST 处理程序实现调整大小:

    <Window x:Class="WpfTest.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Height="250" Width="400" FontSize="14" Loaded="Window_Loaded" WindowStyle="SingleBorderWindow">
    
        <Grid Background="Black" x:Name="grid">
    
            <Button Content="Button1" HorizontalAlignment="Left" Height="50" Margin="64,0,0,0" VerticalAlignment="Top" 
                   Background="Green" Width="150" Click="Button1_Click" Foreground="White"/>
            <Button x:Name="bMin" Content="_" HorizontalAlignment="Right" Height="40" Margin="0,0,80,00" VerticalAlignment="Top" 
                   Background="LightBlue" Width="40" Click="bMin_Click" />
            <Button x:Name="bMax" Content="□" HorizontalAlignment="Right" Height="40" Margin="0,0,40,00" VerticalAlignment="Top" 
                   Background="LightBlue" Width="40" Click="bMax_Click" />
            <Button Content="X" HorizontalAlignment="Right" Height="40" Margin="0,0,0,0" VerticalAlignment="Top" 
                   Background="Red" Width="40" Click="ButtonClose_Click" />
            <Label  Content="Label" HorizontalAlignment="Left"  Margin="0,0,0,0" Foreground="White"
                       VerticalAlignment="Top" Width="50" />
    
        </Grid>
    </Window>
    

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interop;
    using System.Runtime.InteropServices;
    
    namespace WpfTest
    {
        public partial class MainWindow : Window
        {
            IntPtr Handle;
            int xborder;
            int yborder;
    
            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int left, top, right, bottom;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            struct NCCALCSIZE_PARAMS
            {
                [MarshalAs(UnmanagedType.ByValArray, SizeConst = 3)]
                public RECT[] rgrc;
                public IntPtr lppos;
            }
    
            [DllImport("user32.dll", SetLastError = true)]
            static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
    
            [DllImport("user32.dll")]
            static extern bool AdjustWindowRectEx(ref RECT lpRect, uint dwStyle,
            bool bMenu, uint dwExStyle);
    
            [DllImport("dwmapi.dll")]
            static extern bool DwmDefWindowProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref IntPtr plResult);
    
            [DllImport("user32.dll")]
            static extern int GetSystemMetrics(uint smIndex);
    
            static int GET_X_LPARAM(IntPtr lp)
            {
                short loword = (short)((ulong)lp & 0xffff);
                return loword;
            }
    
            static int GET_Y_LPARAM(IntPtr lp)
            {
                short hiword = (short)((((ulong)lp) >> 16) & 0xffff);
                return hiword;
            }
    
            const uint WM_NCCALCSIZE = 0x0083;
            const uint WM_NCHITTEST = 0x0084;
            const uint WM_ACTIVATE = 0x0006;
            const uint WM_NCACTIVATE = 0x0086;
            const uint WM_NCPAINT = 0x85;
    
            const uint WS_OVERLAPPED = 0x00000000;
            const uint WS_CAPTION = 0x00C00000;
            const uint WS_SYSMENU = 0x00080000;
            const uint WS_THICKFRAME = 0x00040000;
            const uint WS_MINIMIZEBOX = 0x00020000;
            const uint WS_MAXIMIZEBOX = 0x00010000;
            const uint WS_OVERLAPPEDWINDOW = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU |
                  WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
    
            const uint HTTOPLEFT = 13;
            const uint HTTOPRIGHT = 14;
            const uint HTTOP = 12;
            const uint HTCAPTION = 2;
            const uint HTLEFT = 10;
            const uint HTNOWHERE = 0;
            const uint HTRIGHT = 11;
            const uint HTBOTTOM = 15;
            const uint HTBOTTOMLEFT = 16;
            const uint HTBOTTOMRIGHT = 17;
    
            const uint SM_CXSIZEFRAME = 32;
            const uint SM_CYSIZEFRAME = 33;
    
            public MainWindow()
            {
                InitializeComponent();
            }
    
            //обработка координат мыши для неклиентской области
            IntPtr HitTestNCA(IntPtr hWnd, IntPtr wParam, IntPtr lParam)
            {
                // Get the point coordinates for the hit test.
                var ptMouse = new Point(GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
    
                // Get the window rectangle.
                RECT rcWindow;
                GetWindowRect(hWnd, out rcWindow);
    
                // Get the frame rectangle, adjusted for the style without a caption.
                RECT rcFrame = new RECT();
                AdjustWindowRectEx(ref rcFrame, WS_OVERLAPPEDWINDOW & ~WS_CAPTION, false, 0);
    
                // Determine if the hit test is for resizing. Default middle (1,1).
                ushort uRow = 1;
                ushort uCol = 1;
                bool fOnResizeBorder = false;
    
                // Determine if the point is at the top or bottom of the window.
                if (ptMouse.Y >= rcWindow.top && ptMouse.Y < rcWindow.top + yborder)
                {
                    fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top));
                    uRow = 0;
                }
                else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - yborder)
                {
                    uRow = 2;
                }
    
                // Determine if the point is at the left or right of the window.
                if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + xborder)
                {
                    uCol = 0; // left side
                }
                else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - xborder)
                {
                    uCol = 2; // right side
                }
    
                // Hit test (HTTOPLEFT, ... HTBOTTOMRIGHT)
                IntPtr[,] hitTests = new IntPtr[,]
                {
                    { (IntPtr)HTTOPLEFT, fOnResizeBorder? (IntPtr)HTTOP : (IntPtr)HTCAPTION, (IntPtr)HTTOPRIGHT },
                    { (IntPtr)HTLEFT,  (IntPtr)HTNOWHERE, (IntPtr)HTRIGHT},
                    { (IntPtr)HTBOTTOMLEFT, (IntPtr)HTBOTTOM, (IntPtr)HTBOTTOMRIGHT },
                };
    
                return hitTests[uRow, uCol];
            }
    
            //обработчик сообщений для окна
            private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
            {
                bool fCallDWP = true;
                IntPtr lRet = IntPtr.Zero;
    
                if (msg == WM_NCCALCSIZE)
                {
                    if (wParam != (IntPtr)0)
                    {
                        //убираем стандартную рамку сверху
                        lRet = IntPtr.Zero;
    
                        NCCALCSIZE_PARAMS pars = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(lParam, typeof(NCCALCSIZE_PARAMS));
    
                        pars.rgrc[0].top = pars.rgrc[0].top;
                        pars.rgrc[0].left = pars.rgrc[0].left + xborder;
                        pars.rgrc[0].right = pars.rgrc[0].right - xborder * 2;
                        pars.rgrc[0].bottom = pars.rgrc[0].bottom - yborder;
                        
                        Marshal.StructureToPtr(pars, lParam, false);
    
                        handled = true;
                        return lRet;
                    }
                }
    
                if (msg == WM_NCACTIVATE)
                {
                    lRet = (IntPtr)1;
                    handled = true;
                    return lRet;
                }                        
    
                fCallDWP = !DwmDefWindowProc(hwnd, msg, wParam, lParam, ref lRet);
    
                if (msg == WM_NCHITTEST && lRet == IntPtr.Zero)
                {
                    //обработка нажатий мыши
                    lRet = HitTestNCA(hwnd, wParam, lParam);
    
                    if (lRet != (IntPtr)HTNOWHERE)
                    {
                        fCallDWP = false;
                    }
                }
    
                //если сообщение не обработано, передаем базовой процедуре
                if (fCallDWP) handled = false;
                else handled = true;
    
                return lRet;
            }
    
            private void Window_Loaded(object sender, RoutedEventArgs e)
            {
                WindowInteropHelper h = new WindowInteropHelper(this);
                HwndSource source = HwndSource.FromHwnd(h.Handle);
                Handle = h.Handle;
                source.AddHook(new HwndSourceHook(WndProc));//регистрируем обработчик сообщений
                xborder = GetSystemMetrics(SM_CXSIZEFRAME);
                yborder = GetSystemMetrics(SM_CYSIZEFRAME);
            }
    
            private void Button1_Click(object sender, RoutedEventArgs e)
            {
                MessageBox.Show("Button 1");
            }
    
            private void bMin_Click(object sender, RoutedEventArgs e)
            {
                this.WindowState = WindowState.Minimized;
            }
    
            private void bMax_Click(object sender, RoutedEventArgs e)
            {
                if (this.WindowState == System.Windows.WindowState.Normal) this.WindowState = System.Windows.WindowState.Maximized;
                else this.WindowState = System.Windows.WindowState.Normal;
            }
    
            private void ButtonClose_Click(object sender, RoutedEventArgs e)
            {
                this.Close();
            }
        }
    }
    

    看起来像这样:

    自定义窗口

    为 Windows 10 量身定做。在 Windows 7 中,该方法也有效并且效果很好,但窗口样式必须为WindowStyle="None". 对于其他样式,相反边缘的闪烁会返回。在 Windows 10 中,当使用 None 样式时,折叠-展开动画会消失,因此对于不同版本的 Windows,您需要设置不同的样式。

    • 5

相关问题

  • 使用嵌套类导出 xml 文件

  • 分层数据模板 [WPF]

  • 如何在 WPF 中为 ListView 手动创建列?

  • 在 2D 空间中,Collider 2D 挂在玩家身上,它对敌人的重量相同,我需要它这样当它们碰撞时,它们不会飞向不同的方向。统一

  • 如何在 c# 中使用 python 神经网络来创建语音合成?

  • 如何知道类中的方法是否属于接口?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    如何从列表中打印最大元素(str 类型)的长度?

    • 2 个回答
  • Marko Smith

    如何在 PyQT5 中清除 QFrame 的内容

    • 1 个回答
  • Marko Smith

    如何将具有特定字符的字符串拆分为两个不同的列表?

    • 2 个回答
  • Marko Smith

    导航栏活动元素

    • 1 个回答
  • Marko Smith

    是否可以将文本放入数组中?[关闭]

    • 1 个回答
  • Marko Smith

    如何一次用多个分隔符拆分字符串?

    • 1 个回答
  • Marko Smith

    如何通过 ClassPath 创建 InputStream?

    • 2 个回答
  • Marko Smith

    在一个查询中连接多个表

    • 1 个回答
  • Marko Smith

    对列表列表中的所有值求和

    • 3 个回答
  • Marko Smith

    如何对齐 string.Format 中的列?

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5