2008-09-20 17 views
7

¿Cómo se dibuja un botón personalizado al lado de los botones minimizar, maximizar y cerrar dentro de la barra de título del formulario?Cómo dibujar un botón personalizado en Window Titlebar con Windows Forms?

Sé que necesita utilizar las llamadas a la API de Win32 y anular el procedimiento de WndProc, pero no he podido encontrar una solución que funcione correctamente.

¿Alguien sabe cómo hacer esto? Más específicamente, ¿alguien sabe cómo hacer esto que funciona en Vista?

Respuesta

6

Lo siguiente funcionará en XP, no tengo la máquina de Vista a mano para probarlo, pero creo que sus problemas provienen de un hWnd incorrecto de alguna manera. De todos modos, sigue con el código mal comentado.

// 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

Esto todavía no aparece gráficamente en Vista. De todas formas, gracias por la ayuda. –

+0

Rectángulo rojo que se muestra debajo de la barra de título (dentro del marco de la ventana) en Windows 8 y superior (incluidas las previsualizaciones de tecnología e información privilegiada de Win10). No esperaba que esto funcionara, pero pensé que comentaría de todos modos para avisarle. – NDEIGU

1

dibujo parece ser la parte fácil, lo siguiente será hacer eso:

[Editar: Código retirado, ver mi otra respuesta]

El problema real está cambiando el estado y la detección de clics en el botón ... para eso tendrá que enganchar en el manejador de mensaje global para el programa, .NET parece ocultar los eventos del mouse para un formulario mientras no está en las áreas del contenedor real (es decir, el mouse se mueve y hace clic en la barra de título) . Estoy buscando información sobre eso, lo encontré ahora, estoy trabajando en ello, no debería ser demasiado difícil ... Si podemos averiguar lo que estos mensajes realmente están pasando.

+0

Este código no parece funcionar en Vista. –

+0

¿Puedes definir "no funciona"? No está reconociendo los mensajes, ¿no está dibujando? Ni siquiera compilará? –

+0

lo siento, se ejecuta pero el rectángulo dibujado no se muestra visiblemente. –

2

Yo sé que ha pasado mucho tiempo desde la última respuesta, pero esto realmente me ayudó hace poco y me gustaría actualizar el código proporcionado por Chris con mis comentarios y modificaciones. La versión funciona perfectamente en Win XP y Win 2003. En Win 2008 ot tiene un pequeño error que no pude identificar al cambiar el tamaño de Windows. También funciona en Vista (sin Aero), pero tenga en cuenta que los botones de la barra de título no son cuadrados y las dimensiones de los botones deben tener esto en cuenta.

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

El código muestra los mensajes que deben tratarse y cómo tratarlos. El código usa una colección de objetos personalizados TitleBarButton. Esa clase es demasiado grande para ser incluida aquí pero puedo proporcionarla si es necesario junto con un ejemplo.

+0

Estoy muy interesado en eso. ¿Ofrecerías el ejemplo para la demostración? Gracias. – Gnought

+0

Los comentarios que tienen "WM_PAINT" etc. no se correlacionan correctamente con los valores numéricos en los casos de cambio. Por ejemplo, 0x0A es WM_ENABLE, no WM_PAINT. Y hay bastantes otros. Supongo que los valores numéricos son los correctos, y los comentarios son los que necesitan corrección, pero no quiero sugerir una edición sin estar seguro. –

Cuestiones relacionadas