RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1030195
Accepted
4per
4per
Asked:2020-10-02 08:30:17 +0000 UTC2020-10-02 08:30:17 +0000 UTC 2020-10-02 08:30:17 +0000 UTC

如何将 ToolStrip 菜单放在窗口的标题栏中?在 Visual Studio 2019 中是如何完成的

  • 772

在 Visual Studio 2019 中,主菜单已移至历史上称为标题栏的位置。我怎样才能在 WinForms 中做同样的事情?

视觉工作室: Visual Studio 2019 主菜单左侧截图

Visual Studio 2019 主菜单右侧截图

我想我已经明白它是如何完成的了。这根本不是一个标题,而是它的模仿。提供非本地窗口控制按钮。但在谷歌浏览器中,按钮是原生的..

谷歌浏览器:

“窗口标题”谷歌浏览器右侧截图

因此,使用了不同的方法来解决这个问题。不过也有开发平台,可能不是.net

然而,如何以一种或另一种方式实现标题菜单?

c#
  • 2 2 个回答
  • 10 Views

2 个回答

  • Voted
  1. Best Answer
    4per
    2020-10-14T15:43:28Z2020-10-14T15:43:28Z

    让我们画一个形状,放下面板Dock = Top。它有两个 ToolStripEx。

    /// <summary>
    /// Вылечена часть глюков ToolStrip с фокусом
    /// </summary>
    public class ToolStripEx : ToolStrip
    {
        protected override void WndProc(ref Message m)
        {
            base.WndProc(ref m);
    
            if (m.Msg == WinApi.WM_MOUSEACTIVATE &&
                m.Result == (IntPtr)WinApi.MA_ACTIVATEANDEAT)
            {
                m.Result = (IntPtr)WinApi.MA_ACTIVATE;
            }            
        }
    }
    

    属性设置:

    剩下GripStyle = Hidden, RenderMode = System, Dock = Fill

    正确的GripStyle = Hidden, RenderMode = System, Dock = Fill, LayoutStyle = Flow

    在左侧,我们将放置:

    • 按钮模仿窗口的系统菜单,
    • 我们感兴趣的菜单本身的按钮,
    • ToolStripLabel,它将作为窗口的标题

    在右侧,我们抛出将模仿常规 ControlBox 的按钮。结果是这样的。

    设计时申请表的截图 表单的“文档结构”屏幕截图

    现在我们实际上需要删除标题,并为模拟器提供类似的行为。

    最简单的方法是按下 ControlBox 并双击“虚拟标题”。

    private void toolStripTitle_DoubleClick(object sender, EventArgs e)
    {
        WindowState = WindowState == FormWindowState.Maximized ?
                       FormWindowState.Normal
                       : FormWindowState.Maximized;
        UpdateControlBox();
    }
    
    private void toolStripButtonClose_Click(object sender, EventArgs e)
    {
        Close();
    }
    
    private void toolStripButtonMaximize_Click(object sender, EventArgs e)
    {
        WindowState = FormWindowState.Maximized;
        UpdateControlBox();
    }
    
    private void toolStripButtonMinimize_Click(object sender, EventArgs e)
    {
        this.WindowState = FormWindowState.Minimized;
    }
    
    private void toolStripButtonRestore_Click(object sender, EventArgs e)
    {
        WindowState = FormWindowState.Normal;
        UpdateControlBox();
    }
    private void UpdateControlBox()
    {
        toolStripButtonMaximize.Visible = WindowState != FormWindowState.Maximized;
        toolStripButtonRestore.Visible = WindowState != FormWindowState.Normal;
    }       
    

    隐藏本机标头的方法有三四种。但是除了一个之外,其他的都有一个缺点:我们失去了在任务栏中显示文本的能力,或者我们失去了调用“窗口系统菜单”的能力。

    我的方法基于以下:在设计器中我们保存窗口的标准视图,然后在 Form.Load 处理程序中我们捕获 Handle“窗口系统菜单”,然后才在帮助下SetWindowLongPtr消除常规标题。

    private IntPtr sysMenuHandle;
    private void Form1_Load(object sender, EventArgs e)
    {
        //Захватываем меню
        sysMenuHandle = WinApi.GetSystemMenu(this.Handle, false);
    
        //Ликвидируем штатный заголовок
        int style = WinApi.GetWindowLongPtr(this.Handle, WinApi.GWL_STYLE).ToInt32();
        WinApi.SetWindowLongPtr(new HandleRef(this, this.Handle), WinApi.GWL_STYLE,
            (IntPtr)(style & ~WinApi.WS_CAPTION));
    
        //Сворачиваем окно и возвращаем как-было
        //Это костыль, чтобы нормально перерисовалось окно после предыдущей операции
        //Не помешало бы найти менее корявый способ
        var winstate = this.WindowState; 
        this.WindowState = FormWindowState.Minimized;
        this.WindowState = winstate;
    
        //Обновляем внешний вид ControlBox
        UpdateControlBox();            
    }
    

    现在我们可以调用“窗口系统菜单”

    private void toolStripTitle_MouseDown(object sender, MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Right)
        {
            IntPtr hWnd = this.Handle;
            WinApi.GetWindowRect(hWnd, out WinApi.RECT pos);                
            int cmd = WinApi.TrackPopupMenu(sysMenuHandle, 0x100,  
                this.Left + e.X, this.Top + e.Y, 0, hWnd, IntPtr.Zero);
            if (cmd > 0) WinApi.SendMessage(hWnd, 0x112, (IntPtr)cmd, IntPtr.Zero);
        }
    }
    
    private void toolStripIcon_MouseDown(object sender, MouseEventArgs e)
    {            
        toolStripTitle_MouseDown(sender, e);
    }
    

    它仍然返回按标题拖动窗口的能力

    public partial class Form1 : Form, IMessageFilter
    {
        private HashSet<Control> controlsToMove = new HashSet<Control>();        
    
        public Form1()
        {
            InitializeComponent();
            Application.AddMessageFilter(this);
            controlsToMove.Add(this.toolStripTitle);           
        }
        
        public bool PreFilterMessage(ref Message m)
        {            
            if (m.Msg == WinApi.WM_LBUTTONDOWN &&
                 controlsToMove.Contains(Control.FromHandle(m.HWnd)))
            {                
                WinApi.SendMessage(this.Handle, WinApi.WM_NCLBUTTONDOWN,
                                    (IntPtr)WinApi.HT_CAPTION, (IntPtr)0);                
            }
            return false;
        }
    

    此解决方案中使用的一些答案: https ://stackoverflow.com/questions/23966253/moving-form-without-title-bar https://stackoverflow.com/questions/472301/toolstrip-sometimes-not-responding-to-鼠标单击 https://stackoverflow.com/questions/5245498/application-title-in-taskbar-but-not-titlebar https://stackoverflow.com/questions/21825352/how-to-open-window-右键单击系统菜单 https://stackoverflow.com/questions/16695154/winapi-getsystemmenu-without-ws-sysmenu-in-style

    • 9
  2. MSDN.WhiteKnight
    2020-10-15T14:36:27Z2020-10-15T14:36:27Z

    最有可能的是,Chrome 使用此处描述的 DWM API 方法:使用 DWM 的自定义窗口框架。底线是通过 DwmExtendFrameIntoClientArea 将窗口的客户区扩展到其框架,并完成消息处理,使标准的最小化-关闭按钮继续工作。在 WinForms 中,可以这样应用:

    using System;
    using System.IO;
    using System.Collections.Generic;
    using System.Text;
    using System.Drawing;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;
    
    namespace WindowsFormsApp1
    {
        public partial class Form1 : Form
        {      
            //размеры отступов клиентской области
            const int TOPEXTENDWIDTH = 1;
            const int BOTTOMEXTENDWIDTH = 30;
            const int LEFTEXTENDWIDTH = 1;
            const int RIGHTEXTENDWIDTH = 1;
    
            //WinAPI
            [StructLayout(LayoutKind.Sequential)]
            public struct MARGINS
            {
                public int cxLeftWidth;
                public int cxRightWidth;
                public int cyBottomHeight;
                public int cyTopHeight;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct RECT
            {
                public int left, top, right, bottom;
            }
    
            [DllImport("dwmapi.dll")]
            static extern int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins);
    
            [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);
    
            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 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;
    
            //обработка координат мыши для неклиентской области
            static 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 + BOTTOMEXTENDWIDTH)
                {
                    fOnResizeBorder = (ptMouse.Y < (rcWindow.top - rcFrame.top));
                    uRow = 0;
                }
                else if (ptMouse.Y < rcWindow.bottom && ptMouse.Y >= rcWindow.bottom - TOPEXTENDWIDTH)
                {
                    uRow = 2;
                }
    
                // Determine if the point is at the left or right of the window.
                if (ptMouse.X >= rcWindow.left && ptMouse.X < rcWindow.left + LEFTEXTENDWIDTH)
                {
                    uCol = 0; // left side
                }
                else if (ptMouse.X < rcWindow.right && ptMouse.X >= rcWindow.right - RIGHTEXTENDWIDTH)
                {
                    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];
            }
    
            public Form1()
            {
                InitializeComponent();
    
                foreach (ToolStripMenuItem item in menuStrip1.Items)
                {
                    item.Paint += Item_Paint;
                }
            }
    
            private void Item_Paint(object sender, PaintEventArgs e)
            {
                var item = sender as ToolStripMenuItem;
                if (item == null) return;
    
                //для элементов, которые лежат на бывшей рамке окна, нужна нестандартная отрисовка
                e.Graphics.FillRectangle(SystemBrushes.Control, 2, 2, item.Width - 4,item.Height - 4);
                e.Graphics.DrawString(item.Text, SystemFonts.DefaultFont, SystemBrushes.ControlText, 2, 2);
            }
    
            //при первой активации окна расширим клиентскую область на рамку окна
            bool dwminit = false;
            private void Form1_Activated(object sender, EventArgs e)
            {
                if (dwminit == false)
                {
                    // Extend the frame into the client area.
                    MARGINS margins = new MARGINS();
    
                    margins.cxLeftWidth = LEFTEXTENDWIDTH;
                    margins.cxRightWidth = RIGHTEXTENDWIDTH;
                    margins.cyBottomHeight = BOTTOMEXTENDWIDTH;
                    margins.cyTopHeight = TOPEXTENDWIDTH;
    
                    int hr = DwmExtendFrameIntoClientArea(this.Handle, ref margins);
                    dwminit = true;
    
                    if (hr != 0)
                    {
                        throw Marshal.GetExceptionForHR(hr);
                    }
                }
            }
    
            protected override void WndProc(ref Message m)
            {
                bool fCallDWP = true;
                IntPtr lRet = IntPtr.Zero;
                fCallDWP = !DwmDefWindowProc(m.HWnd, m.Msg, m.WParam, m.LParam, ref lRet);
    
                if (m.Msg == WM_NCCALCSIZE)
                {
                    if (m.WParam != (IntPtr)0)
                    {
                        //убираем страндартную рамку
                        lRet = IntPtr.Zero;                    
                        fCallDWP = false;
                    }
                }
    
                if (m.Msg == WM_NCHITTEST && lRet==IntPtr.Zero)
                {
                    //обработка нажатий мыши
                    lRet = HitTestNCA(m.HWnd, m.WParam, m.LParam);
    
                    if (lRet != (IntPtr)HTNOWHERE)
                    {
                        fCallDWP = false;
                    }
                }
    
                m.Result = lRet;
    
                //если сообщение не обработано, передаем в базовый класс
                if (fCallDWP) base.WndProc(ref m);
            }             
    
            private void Form1_Paint(object sender, PaintEventArgs e)
            {
                //это нужно, чтобы были видны рамка и кнопки свернуть-закрыть в Windows 10, 
                //так как рамку система принудительно заливает белым
    
                e.Graphics.FillRectangle(Brushes.Black, 0, 0, this.Width, BOTTOMEXTENDWIDTH);
                e.Graphics.FillRectangle(Brushes.Black, this.Width - RIGHTEXTENDWIDTH, 0, RIGHTEXTENDWIDTH, this.Height);
                e.Graphics.FillRectangle(Brushes.Black, 0, 0, LEFTEXTENDWIDTH, this.Height);
                e.Graphics.FillRectangle(Brushes.Black, 0, this.Height- TOPEXTENDWIDTH, this.Width, TOPEXTENDWIDTH);
            }
    
            private void Form1_Resize(object sender, EventArgs e)
            {
                //необходимо для корректной перерисовки рамки при изменении размера
                this.Invalidate();
            }
    
            //другие обработчики событий...        
        }
    }
    

    在表单设计器中查看:

    设计师

    启动视图:

    运行

    当然,这是一个相当粗略的示例,针对标准的 Windows 10 主题量身定制 - 为了使其无论主题如何都能正常工作,建议不要设置常量缩进,而是通过GetSystemMetrics获取窗口参数。但我认为这个想法很明确。

    在实践中,我认为WinForms的这种方法不是很方便,因为需要手动绘制所有落入之前非客户区的元素。

    警告:在 Vista/7 上,此方法仅适用于启用 DWM 组合。在以前的版本中,它不会。

    • 6

相关问题

Sidebar

Stats

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

    根据浏览器窗口的大小调整背景图案的大小

    • 2 个回答
  • Marko Smith

    理解for循环的执行逻辑

    • 1 个回答
  • Marko Smith

    复制动态数组时出错(C++)

    • 1 个回答
  • Marko Smith

    Or and If,elif,else 构造[重复]

    • 1 个回答
  • Marko Smith

    如何构建支持 x64 的 APK

    • 1 个回答
  • Marko Smith

    如何使按钮的输入宽度?

    • 2 个回答
  • Marko Smith

    如何显示对象变量的名称?

    • 3 个回答
  • Marko Smith

    如何循环一个函数?

    • 1 个回答
  • Marko Smith

    LOWORD 宏有什么作用?

    • 2 个回答
  • Marko Smith

    从字符串的开头删除直到并包括一个字符

    • 2 个回答
  • 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