如何在Windows窗体的窗口标题栏中绘制自定义按钮?
时间:2020-03-06 14:28:32 来源:igfitidea点击:
我们如何在表单标题栏中的最小化,最大化和关闭按钮旁边绘制自定义按钮?
我知道我们需要使用Win32 API调用并覆盖WndProc过程,但是我一直无法找出一种可行的解决方案。
有谁知道如何做到这一点?更具体地说,有人知道在Vista中可以做到这一点的方法吗?
解决方案
绘图似乎是最简单的部分,以下将实现此目的:
[编辑:代码已删除,请参阅其他答案]
真正的问题是更改状态并检测对按钮的单击……为此,我们需要挂钩到程序的全局消息处理程序,.NET似乎在不在实际容器中时隐藏了表单的鼠标事件。区域(即,鼠标移动并单击标题栏)。我正在寻找有关的信息,现在找到它,我正在研究它,应该不会太难...如果我们可以弄清楚这些消息实际上正在传递什么。
以下内容将在XP中运行,我没有方便的Vista机器进行测试,但是我认为问题是由于某种原因导致的hWnd错误。无论如何,用评论不佳的代码。
// The state of our little button ButtonState _buttState = ButtonState.Normal; Rectangle _buttPosition = new Rectangle(); [DllImport("user32.dll")] private static extern IntPtr GetWindowDC(IntPtr hWnd); [DllImport("user32.dll")] private static extern int GetWindowRect(IntPtr hWnd, ref Rectangle lpRect); [DllImport("user32.dll")] private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); protected override void WndProc(ref Message m) { int x, y; Rectangle windowRect = new Rectangle(); GetWindowRect(m.HWnd, ref windowRect); switch (m.Msg) { // WM_NCPAINT case 0x85: // WM_PAINT case 0x0A: base.WndProc(ref m); DrawButton(m.HWnd); m.Result = IntPtr.Zero; break; // WM_ACTIVATE case 0x86: base.WndProc(ref m); DrawButton(m.HWnd); break; // WM_NCMOUSEMOVE case 0xA0: // Extract the least significant 16 bits x = ((int)m.LParam << 16) >> 16; // Extract the most significant 16 bits y = (int)m.LParam >> 16; x -= windowRect.Left; y -= windowRect.Top; base.WndProc(ref m); if (!_buttPosition.Contains(new Point(x, y)) && _buttState == ButtonState.Pushed) { _buttState = ButtonState.Normal; DrawButton(m.HWnd); } break; // WM_NCLBUTTONDOWN case 0xA1: // Extract the least significant 16 bits x = ((int)m.LParam << 16) >> 16; // Extract the most significant 16 bits y = (int)m.LParam >> 16; x -= windowRect.Left; y -= windowRect.Top; if (_buttPosition.Contains(new Point(x, y))) { _buttState = ButtonState.Pushed; DrawButton(m.HWnd); } else base.WndProc(ref m); break; // WM_NCLBUTTONUP case 0xA2: // Extract the least significant 16 bits x = ((int)m.LParam << 16) >> 16; // Extract the most significant 16 bits y = (int)m.LParam >> 16; x -= windowRect.Left; y -= windowRect.Top; if (_buttPosition.Contains(new Point(x, y)) && _buttState == ButtonState.Pushed) { _buttState = ButtonState.Normal; // [[TODO]]: Fire a click event for your button // however you want to do it. DrawButton(m.HWnd); } else base.WndProc(ref m); break; // WM_NCHITTEST case 0x84: // Extract the least significant 16 bits x = ((int)m.LParam << 16) >> 16; // Extract the most significant 16 bits y = (int)m.LParam >> 16; x -= windowRect.Left; y -= windowRect.Top; if (_buttPosition.Contains(new Point(x, y))) m.Result = (IntPtr)18; // HTBORDER else base.WndProc(ref m); break; default: base.WndProc(ref m); break; } } private void DrawButton(IntPtr hwnd) { IntPtr hDC = GetWindowDC(hwnd); int x, y; using (Graphics g = Graphics.FromHdc(hDC)) { // Work out size and positioning int CaptionHeight = Bounds.Height - ClientRectangle.Height; Size ButtonSize = SystemInformation.CaptionButtonSize; x = Bounds.Width - 4 * ButtonSize.Width; y = (CaptionHeight - ButtonSize.Height) / 2; _buttPosition.Location = new Point(x, y); // Work out color Brush color; if (_buttState == ButtonState.Pushed) color = Brushes.LightGreen; else color = Brushes.Red; // Draw our "button" g.FillRectangle(color, x, y, ButtonSize.Width, ButtonSize.Height); } ReleaseDC(hwnd, hDC); } private void Form1_Load(object sender, EventArgs e) { _buttPosition.Size = SystemInformation.CaptionButtonSize; }
我知道距最后一个答案已经很久了,但这最近确实对我有所帮助,我想用我的评论和修改来更新Chris提供的代码。
该版本可以在Win XP和Win 2003上完美运行。在Win 2008上,ot的一个小错误在调整Windows大小时无法识别。也可以在Vista上运行(无Aero),但请注意,标题栏按钮不是方形的,按钮尺寸应考虑在内。
switch (m.Msg) { // WM_NCPAINT / WM_PAINT case 0x85: case 0x0A: //Call base method base.WndProc(ref m); //we have 3 buttons in the corner of the window. So first's new button left coord is offseted by 4 widths int crt = 4; //navigate trough all titlebar buttons on the form foreach (TitleBarImageButton crtBtn in titleBarButtons.Values) { //Calculate button coordinates p.X = (Bounds.Width - crt * crtBtn.Size.Width); p.Y = (Bounds.Height - ClientRectangle.Height - crtBtn.Size.Height) / 2; //Initialize button and draw crtBtn.Location = p; crtBtn.ButtonState = ImageButtonState.NORMAL; crtBtn.DrawButton(m.HWnd); //increment button left coord location offset crt++; } m.Result = IntPtr.Zero; break; // WM_ACTIVATE case 0x86: //Call base method base.WndProc(ref m); //Draw each button foreach (TitleBarImageButton crtBtn in titleBarButtons.Values) { crtBtn.ButtonState = ImageButtonState.NORMAL; crtBtn.DrawButton(m.HWnd); } break; // WM_NCMOUSEMOVE case 0xA0: //Get current mouse position p.X = ((int)m.LParam << 16) >> 16;// Extract the least significant 16 bits p.Y = (int)m.LParam >> 16; // Extract the most significant 16 bits p.X -= windowRect.Left; p.Y -= windowRect.Top; //Call base method base.WndProc(ref m); ImageButtonState newButtonState; foreach (TitleBarImageButton crtBtn in titleBarButtons.Values) { if (crtBtn.HitTest(p)) {//mouse is over the current button if (crtBtn.MouseButtonState == MouseButtonState.PRESSED) //button is pressed - set pressed state newButtonState = ImageButtonState.PRESSED; else //button not pressed - set hoover state newButtonState = ImageButtonState.HOOVER; } else { //mouse not over the current button - set normal state newButtonState = ImageButtonState.NORMAL; } //if button state not modified, do not repaint it. if (newButtonState != crtBtn.ButtonState) { crtBtn.ButtonState = newButtonState; crtBtn.DrawButton(m.HWnd); } } break; // WM_NCLBUTTONDOWN case 0xA1: //Get current mouse position p.X = ((int)m.LParam << 16) >> 16;// Extract the least significant 16 bits p.Y = (int)m.LParam >> 16; // Extract the most significant 16 bits p.X -= windowRect.Left; p.Y -= windowRect.Top; //Call base method base.WndProc(ref m); foreach (TitleBarImageButton crtBtn in titleBarButtons.Values) { if (crtBtn.HitTest(p)) { crtBtn.MouseButtonState = MouseButtonState.PRESSED; crtBtn.ButtonState = ImageButtonState.PRESSED; crtBtn.DrawButton(m.HWnd); } } break; // WM_NCLBUTTONUP case 0xA2: case 0x202: //Get current mouse position p.X = ((int)m.LParam << 16) >> 16;// Extract the least significant 16 bits p.Y = (int)m.LParam >> 16; // Extract the most significant 16 bits p.X -= windowRect.Left; p.Y -= windowRect.Top; //Call base method base.WndProc(ref m); foreach (TitleBarImageButton crtBtn in titleBarButtons.Values) { //if button is press if (crtBtn.ButtonState == ImageButtonState.PRESSED) { //Rasie button's click event crtBtn.OnClick(EventArgs.Empty); if (crtBtn.HitTest(p)) crtBtn.ButtonState = ImageButtonState.HOOVER; else crtBtn.ButtonState = ImageButtonState.NORMAL; } crtBtn.MouseButtonState = MouseButtonState.NOTPESSED; crtBtn.DrawButton(m.HWnd); } break; // WM_NCHITTEST case 0x84: //Get current mouse position p.X = ((int)m.LParam << 16) >> 16;// Extract the least significant 16 bits p.Y = (int)m.LParam >> 16; // Extract the most significant 16 bits p.X -= windowRect.Left; p.Y -= windowRect.Top; bool isAnyButtonHit = false; foreach (TitleBarImageButton crtBtn in titleBarButtons.Values) { //if mouse is over the button, or mouse is pressed //(do not process messages when mouse was pressed on a button) if (crtBtn.HitTest(p) || crtBtn.MouseButtonState == MouseButtonState.PRESSED) { //return 18 (do not process further) m.Result = (IntPtr)18; //we have a hit isAnyButtonHit = true; //return break; } else {//mouse is not pressed and not over the button, redraw button if needed if (crtBtn.ButtonState != ImageButtonState.NORMAL) { crtBtn.ButtonState = ImageButtonState.NORMAL; crtBtn.DrawButton(m.HWnd); } } } //if we have a hit, do not process further if (!isAnyButtonHit) //Call base method base.WndProc(ref m); break; default: //Call base method base.WndProc(ref m); //Console.WriteLine(m.Msg + "(0x" + m.Msg.ToString("x") + ")"); break; }
该代码演示了必须处理的消息以及如何处理它们。
该代码使用自定义TitleBarButton objets的集合。该类太大了,无法在此处包括,但是如果需要,我可以提供它和一个示例。