2008-09-20 21 views
7

Làm thế nào để bạn vẽ một nút tùy chỉnh bên cạnh các nút thu nhỏ, phóng to và đóng trong thanh tiêu đề của biểu mẫu?Làm thế nào để vẽ nút tùy chỉnh trong Window Titlebar với Windows Forms?

Tôi biết bạn cần sử dụng các cuộc gọi API Win32 và ghi đè thủ tục WndProc, nhưng tôi đã không thể tìm ra giải pháp hoạt động đúng.

Có ai biết cách thực hiện việc này không? Cụ thể hơn, có ai biết cách làm điều này hoạt động trong Vista không?

Trả lời

6

Sau đây sẽ làm việc trong XP, tôi không có máy Vista tiện dụng để kiểm tra nó, nhưng tôi nghĩ rằng vấn đề của bạn bắt nguồn từ một hWnd không chính xác bằng cách nào đó. Dù sao, với mã bình luận kém.

// 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; 
} 
+0

Điều này vẫn không hiển thị đồ họa trong Vista. Cảm ơn sự giúp đỡ. –

+0

Hình chữ nhật màu đỏ hiển thị bên dưới thanh tiêu đề (bên trong khung cửa sổ) trên Windows 8 trở lên (bao gồm cả bản xem trước công nghệ và nội bộ của Win10). Tôi đã không mong đợi điều này để làm việc, nhưng nghĩ rằng tôi muốn bình luận anyway để cho bạn biết. – NDEIGU

1

Vẽ dường như là một phần dễ dàng, sau đây sẽ làm điều đó:

[Chỉnh sửa: Mã loại bỏ, xem câu trả lời khác tôi]

Vấn đề thực sự đang thay đổi nhà nước và phát hiện nhấp chuột vào nút ... cho rằng bạn sẽ cần phải móc vào trình xử lý tin nhắn toàn cầu cho chương trình. .NET dường như ẩn các sự kiện chuột cho biểu mẫu trong khi không nằm trong vùng chứa thực tế (ví dụ: di chuyển chuột và nhấp chuột trên thanh tiêu đề) . Tôi đang tìm kiếm thông tin về điều đó, tìm thấy nó ngay bây giờ, tôi đang làm việc trên nó, không nên quá khó ... Nếu chúng ta có thể tìm ra những thông điệp này thực sự đi qua.

+0

Mã này dường như không hoạt động trong Vista. –

+0

bạn có thể xác định "không hoạt động" không? Nó không nhận ra các tin nhắn, nó không phải là bản vẽ? Nó thậm chí sẽ không biên dịch? –

+0

xin lỗi, nó chạy nhưng hình chữ nhật được vẽ không hiển thị rõ ràng. –

2

Tôi biết nó được dài kể từ khi câu trả lời cuối cùng nhưng điều này thực sự giúp tôi thời gian gần đây và tôi muốn cập nhật mã được cung cấp bởi Chris với ý kiến ​​và sửa đổi của tôi. Phiên bản chạy hoàn toàn trên Win XP và Win 2003. Trên Win 2008 ot có một lỗi nhỏ mà tôi không thể xác định, khi thay đổi kích thước cửa sổ. Hoạt động trên Vista quá (no-Aero) nhưng lưu ý rằng các nút thanh tiêu đề không phải là kích thước hình vuông và nút nên tính đến điều đó.

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; 
      } 

Mã chứng minh các thông báo mà anh ấy được đối xử và cách xử lý chúng. Mã sử ​​dụng bộ sưu tập các tiêu đề tùy chỉnh TitleBarButton. Lớp đó quá lớn để được đưa vào đây nhưng tôi có thể cung cấp nó nếu cần thiết cùng với một ví dụ.

+0

Tôi rất quan tâm đến nó. Bạn có cung cấp ví dụ cho cuộc biểu tình không? Cảm ơn. – Gnought

+0

Các nhận xét có "WM_PAINT" vv không tương quan chính xác với các giá trị số trong các trường hợp chuyển đổi. Ví dụ 0x0A là WM_ENABLE, không phải WM_PAINT. Và có khá nhiều người khác. Tôi giả sử các giá trị số là những giá trị đúng và các nhận xét là những gì cần chỉnh sửa, nhưng tôi không muốn đề xuất chỉnh sửa mà không chắc chắn. –