Windows Forms를 사용하여 창 제목 표시줄에 사용자 정의 버튼을 그리는 방법은 무엇입니까?
-
01-07-2019 - |
문제
양식의 제목 표시줄에 있는 최소화, 최대화 및 닫기 버튼 옆에 사용자 정의 버튼을 어떻게 그리나요?
Win32 API 호출을 사용하고 WndProc 프로시저를 재정의해야 한다는 것을 알고 있지만 제대로 작동하는 솔루션을 찾지 못했습니다.
이 작업을 수행하는 방법을 아는 사람이 있습니까?좀 더 구체적으로 말하자면, Vista에서 작동하는 방법을 아는 사람이 있나요?
해결책
다음은 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에서는 창 크기를 조정할 때 확인할 수 없는 작은 버그가 있습니다.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 개체 컬렉션을 사용합니다.해당 클래스는 여기에 포함하기에는 너무 크지만 필요한 경우 예제와 함께 제공할 수 있습니다.
그리기는 쉬운 부분인 것 같습니다. 다음을 수행하면 됩니다.
[편집하다:코드가 제거되었습니다. 다른 답변을 참조하세요.]
진짜 문제는 상태를 변경하고 버튼 클릭을 감지하는 것입니다.이를 위해 프로그램의 전역 메시지 처리기에 연결해야 합니다. .NET은 실제 컨테이너 영역이 아닌 동안 양식에 대한 마우스 이벤트를 숨기는 것 같습니다(예:마우스가 움직이고 제목 표시줄을 클릭합니다.)나는 그것에 대한 정보를 찾고 있는데 지금 찾았습니다. 작업 중입니다. 너무 어렵지는 않을 것입니다...이 메시지가 실제로 전달되는 것이 무엇인지 알아낼 수 있다면.