Comment : sous-classer un contrôle Button à l'aide de rappels natifs
Mise à jour : novembre 2007
Cette rubrique est un exercice visant à montrer comment sous-classer un contrôle Windows Forms de sorte qu'il reçoive des rappels de code natif avec une procédure Windows managée (WndProc). Cet exemple de programme est décrit en détail dans Sous-classement de contrôles avec une procédure de fenêtre managée.
Ce programme d'exemple indique comment afficher un remplissage dégradé avec un contrôle sous-classé de Button. Il requiert la gestion des messages Windows. Une façon plus simple d'afficher un remplissage dégradé dans un contrôle Button consiste à créer un contrôle personnalisé dérivé de Control. Pour obtenir un exemple, consultez Comment : afficher un remplissage dégradé.
Pour sous-classer un contrôle Button de sorte qu'il affiche un remplissage dégradé
Dans Microsoft Visual Studio 2005, créez un projet Smart Device Pocket PC.
Ajoutez la classe Gradientfill à votre projet.
public sealed class GradientFill { // This method wraps the PInvoke to GradientFill. // Parameters: // gr - The Graphics object we are filling // rc - The rectangle to fill // startColor - The starting color for the fill // endColor - The ending color for the fill // fillDir - The direction to fill // // Returns true if the call to GradientFill succeeded; false // otherwise. public static bool Fill( Graphics gr, Rectangle rc, Color startColor, Color endColor, FillDirection fillDir) { // Initialize the data to be used in the call to GradientFill. Win32.TRIVERTEX[] tva = new Win32.TRIVERTEX[2]; tva[0] = new Win32.TRIVERTEX(rc.X, rc.Y, startColor); tva[1] = new Win32.TRIVERTEX(rc.Right, rc.Bottom, endColor); Win32.GRADIENT_RECT[] gra = new Win32.GRADIENT_RECT[] { new Win32.GRADIENT_RECT(0, 1)}; // Get the hDC from the Graphics object. IntPtr hdc = gr.GetHdc(); // PInvoke to GradientFill. bool b; b = Win32.GradientFill( hdc, tva, (uint)tva.Length, gra, (uint)gra.Length, (uint)fillDir); System.Diagnostics.Debug.Assert(b, string.Format( "GradientFill failed: {0}", System.Runtime.InteropServices.Marshal.GetLastWin32Error())); // Release the hDC from the Graphics object. gr.ReleaseHdc(hdc); return b; } // The direction to the GradientFill will follow public enum FillDirection { // // The fill goes horizontally // LeftToRight = Win32.GRADIENT_FILL_RECT_H, // // The fill goes vertically // TopToBottom = Win32.GRADIENT_FILL_RECT_V } }
Ajoutez la classe GradientFilledButton à votre projet.
// Extends the standard button control and does some custom // drawing with a GradientFill background public class GradientFilledButton : Button { // Creates a new instance of the object public GradientFilledButton() { // Messages required to override // in this control's window procedure. WndProcHooker.HookWndProc(this, new WndProcHooker.WndProcCallback(this.WM_Paint_Handler), Win32.WM_PAINT); WndProcHooker.HookWndProc(this, new WndProcHooker.WndProcCallback(this.WM_LButtonDown_Handler), Win32.WM_LBUTTONDOWN); WndProcHooker.HookWndProc(this, new WndProcHooker.WndProcCallback(this.WM_LButtonUp_Handler), Win32.WM_LBUTTONUP); WndProcHooker.HookWndProc(this, new WndProcHooker.WndProcCallback(this.WM_MouseMove_Handler), Win32.WM_MOUSEMOVE); WndProcHooker.HookWndProc(this, new WndProcHooker.WndProcCallback(this.WM_KeyDown_Handler), Win32.WM_KEYDOWN); WndProcHooker.HookWndProc(this, new WndProcHooker.WndProcCallback(this.WM_KeyUp_Handler), Win32.WM_KEYUP); } // Controls the direction in which the button is filled public GradientFill.FillDirection FillDirection { get { return fillDirectionValue; } set { fillDirectionValue = value; Invalidate(); } } private GradientFill.FillDirection fillDirectionValue; // The start color for the GradientFill. This is the color // at the left or top of the control depending on the value // of the FillDirection property. public Color StartColor { get { return startColorValue; } set { startColorValue = value; Invalidate(); } } private Color startColorValue = Color.Red; // The end color for the GradientFill. This is the color // at the right or bottom of the control depending on the // value of the FillDirection property public Color EndColor { get { return endColorValue; } set { endColorValue = value; Invalidate(); } } private Color endColorValue = Color.Blue; // This is the offset from the left or top edge of the button // to start the gradient fill. public int StartOffset { get { return startOffsetValue; } set { startOffsetValue = value; Invalidate(); } } private int startOffsetValue; // This is the offset from the right or bottom edge // of the button to end the gradient fill. public int EndOffset { get { return endOffsetValue; } set { endOffsetValue = value; Invalidate(); } } private int endOffsetValue; // Used to double-buffer our drawing to avoid flicker between // painting the background, border, focus-rect and the // text of the control. private Bitmap DoubleBufferImage { get { if (bmDoubleBuffer == null) bmDoubleBuffer = new Bitmap( this.ClientSize.Width, this.ClientSize.Height); return bmDoubleBuffer; } set { if (bmDoubleBuffer != null) bmDoubleBuffer.Dispose(); bmDoubleBuffer = value; } } private Bitmap bmDoubleBuffer; // Called when the control is resized. When that happens, we need to // recreate the bitmap we use for double-buffering. // e - The arguments for this event protected override void OnResize(EventArgs e) { DoubleBufferImage = new Bitmap( this.ClientSize.Width, this.ClientSize.Height); base.OnResize(e); } // Called when the control gets focus. We need to repaint the control // to ensure the focus rectangle is drawn correctly. // e - The arguments for this control protected override void OnGotFocus(EventArgs e) { base.OnGotFocus(e); this.Invalidate(); } // Called when the control loses focus. We need to repaint the control // to ensure the focus rectangle is removed. // e - The arguments for this control protected override void OnLostFocus(EventArgs e) { base.OnLostFocus(e); this.Invalidate(); } // This is set to true when we get a MouseDown event. It is used // to determine if we should fire the Click event when we get // a MouseUp bool gotMouseDown = false; bool gotKeyDown = false; // Called when a mouse button is pressed while the cursor is // in the control. // e - The arguments for this event. protected override void OnMouseDown(MouseEventArgs e) { if (e.Button == MouseButtons.Left) gotMouseDown = true; base.OnMouseDown(e); } // Called when a mouse button is released while the cursor is // in the control // e - The arguments for this event protected override void OnMouseUp(MouseEventArgs e) { base.OnMouseUp(e); // If the MouseDown event was fired before this event then // that constitutes a Click. if ((e.Button == MouseButtons.Left) && gotMouseDown) { base.OnClick(EventArgs.Empty); gotMouseDown = false; } } // The callback called when the window receives a WM_MOUSEMOVE // message. If we have the mouse captured (the user had previously // clicked down on the button), we redraw the button. // hwnd - The handle to the window that received the // message. // wParam - Indicates whether various virtual keys are // down. // lParam - The coordinates of the cursor // handled - Set to true if we don't want to pass this // message on to the original window procedure. // Returns zero if we process this message. int WM_MouseMove_Handler( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) { if (this.Capture) { Point coord = Win32.LParamToPoint(lParam); if (this.ClientRectangle.Contains(coord) != this.ClientRectangle.Contains(lastCursorCoordinates)) { DrawButton(hwnd, this.ClientRectangle.Contains(coord)); } lastCursorCoordinates = coord; } return -1; } // The coordinates of the cursor the last time we saw a WM_MOUSEMOVE, // WM_LBUTTONDOWN or WM_LBUTTONUP message. Point lastCursorCoordinates; // The callback called when the window receives a WM_LBUTTONDOWN // message. We capture the mouse and draw the button in the "pushed" // state. // hwnd - The handle to the window that received the // message. // wParam - Indicates whether various virtual keys are // down. // lParam - The coordinates of the cursor. // handled - Set to true if we don't want to pass this // message on to the original window procedure. // Returns zero if we process this message. int WM_LButtonDown_Handler( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) { // Start capturing the mouse input. this.Capture = true; // someone clicked on us so grab the focus this.Focus(); // draw the button DrawButton(hwnd, true); // Fire the MouseDown event lastCursorCoordinates = Win32.LParamToPoint(lParam); OnMouseDown(new MouseEventArgs(MouseButtons.Left, 1, lastCursorCoordinates.X, lastCursorCoordinates.Y, 0)); // We have handled this windows message and we don't want the // sub-classed window to do anything else. handled = true; return 0; } // The callback called when the window receives a WM_KEYDOWN message. // If the key was the spacebar, We draw the button in the "pushed" // state. // hwnd - The handle to the window that received the // message // wParam - Specifies the virtual-key code of the // non system key. // lParam - Specifies various attributes about the key // that is down. // handled - Set to true if we don't want to pass this // message on to the original window procedure // Returns>Zero if we process this message. int WM_KeyDown_Handler( IntPtr hwnd, uint msg, uint wParam, int lPAram, ref bool handled) { if ((wParam == Win32.VK_SPACE) || (wParam == Win32.VK_RETURN)) { DrawButton(hwnd, true); handled = true; gotKeyDown = true; } return handled ? 0 : -1; } // The callback called when the window receives a WM_KEYUP message. // If the key was the spacebar, We draw the button in the "un-pushed" // state and fire the Click event. // hwnd - The handle to the window that received the // message // wParam - Specifies the virtual-key code of the non- // system key. // lParam - Specifies various attributes about the key // that is down. // handled - Set to true if we don't want to pass this // message // on to the original window procedure // Returns zero if we process this message. int WM_KeyUp_Handler( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) { if (gotKeyDown && ((wParam == Win32.VK_SPACE) || (wParam == Win32.VK_RETURN))) { DrawButton(hwnd, false); OnClick(EventArgs.Empty); handled = true; gotKeyDown = false; } return handled ? 0 : -1; } // The callback called when the window receives a WM_LBUTTONUP // message. We release capture on the mouse, draw the button in the // "un-pushed" state and fire the OnMouseUp event if the cursor was // let go of inside our client area. // hwnd - The handle to the window that received the // message // wParam - Indicates whether various virtual keys are // down. // lParam - The coordinates of the cursor // handled - Set to true if we don't want to pass this // message // on to the original window procedure // Returns zero if we process this message. int WM_LButtonUp_Handler( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) { this.Capture = false; DrawButton(hwnd, false); lastCursorCoordinates = Win32.LParamToPoint(lParam); if (this.ClientRectangle.Contains(lastCursorCoordinates)) OnMouseUp(new MouseEventArgs(MouseButtons.Left, 1, lastCursorCoordinates.X, lastCursorCoordinates.Y, 0)); handled = true; return 0; } // The callback called when the window receives a WM_PAINT message. // We draw the button in the appropriate state. // hwnd - The handle to the window that received the // message // wParam - Indicates whether various virtual keys are // down. // lParam - The coordinates of the cursor // handled - Set to true if we don't want to pass this // message on to the original window procedure // Returns zero if we process this message. int WM_Paint_Handler( IntPtr hwnd, uint msg, uint wParam, int lParam, ref bool handled) { Win32.PAINTSTRUCT ps = new Win32.PAINTSTRUCT(); Graphics gr = Graphics.FromHdc(Win32.BeginPaint(hwnd, ref ps)); DrawButton(gr, this.Capture && (this.ClientRectangle.Contains(lastCursorCoordinates))); gr.Dispose(); Win32.EndPaint(hwnd, ref ps); handled = true; return 0; } // Gets a Graphics object for the provided window handle and then // calls DrawButton(Graphics, bool). // hwnd - The handle to the window to draw as a // button // pressed - If true, the button is draw in the // depressed state void DrawButton(IntPtr hwnd, bool pressed) { IntPtr hdc = Win32.GetDC(hwnd); Graphics gr = Graphics.FromHdc(hdc); DrawButton(gr, pressed); gr.Dispose(); Win32.ReleaseDC(hwnd, hdc); } // Draws the button on the specified Graphics in the specified // state. // gr - The Graphics object on which to draw the // button // pressed - If true, the button is draw in the // depressed state void DrawButton(Graphics gr, bool pressed) { // get a Graphics object from our background image Graphics gr2 = Graphics.FromImage(DoubleBufferImage); // fill solid up until where the gradient fill starts if (startOffsetValue > 0) { if (fillDirectionValue == GradientFill.FillDirection.LeftToRight) { gr2.FillRectangle( new SolidBrush(pressed ? EndColor : StartColor), 0, 0, startOffsetValue, Height); } else { gr2.FillRectangle( new SolidBrush(pressed ? EndColor : StartColor), 0, 0, Width, startOffsetValue); } } // draw the gradient fill Rectangle rc = this.ClientRectangle; if (fillDirectionValue == GradientFill.FillDirection.LeftToRight) { rc.X = startOffsetValue; rc.Width = rc.Width - startOffsetValue - endOffsetValue; } else { rc.Y = startOffsetValue; rc.Height = rc.Height - startOffsetValue - endOffsetValue; } GradientFill.Fill( gr2, rc, pressed ? endColorValue : startColorValue, pressed ? startColorValue : endColorValue, fillDirectionValue); // fill solid from the end of the gradient fill to the edge of the // button if (endOffsetValue > 0) { if (fillDirectionValue == GradientFill.FillDirection.LeftToRight) { gr2.FillRectangle( new SolidBrush(pressed ? StartColor : EndColor), rc.X + rc.Width, 0, endOffsetValue, Height); } else { gr2.FillRectangle( new SolidBrush(pressed ? StartColor : EndColor), 0, rc.Y + rc.Height, Width, endOffsetValue); } } // draw the text StringFormat sf = new StringFormat(); sf.Alignment = StringAlignment.Center; sf.LineAlignment = StringAlignment.Center; gr2.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), this.ClientRectangle, sf); // draw the border. // we need to shrink the width and height by 1 otherwise we // won't get any border on the right or bottom. rc = this.ClientRectangle; rc.Width--; rc.Height--; Pen pen = new Pen(SystemColors.WindowFrame); // focused buttons have a thicker border on device if (this.Focused) pen = new Pen(SystemColors.WindowFrame, 3f); gr2.DrawRectangle(pen, rc); // draw from the background image onto the screen gr.DrawImage(DoubleBufferImage, 0, 0); gr2.Dispose(); } }
Ajoutez la classe d'assistance Win32 à votre projet. Ce code est disponible dans Comment : utiliser une classe d'assistance pour les appels de plates-formes.
Ajoutez la classe WinProcHooker à votre projet. Ce code est disponible dans Comment : utiliser une classe pour raccorder des procédures Windows.
Déclarez une variable de formulaire nommée buttonGF de type GradientFilledButton.
private GradientFilledButton buttonGF;
Ajoutez le code suivant, qui permet d'initialiser le contrôle bouton sous-classé, au constructeur de la classe Form1. Ce code doit suivre l'appel à la méthode InitializeComponent. Vous pouvez spécifier la couleur de début et de fin du remplissage dégradé et une direction de remplissage TopToBottom ou LeftToRight.
InitializeComponent(); this.buttonGF = new GradientFilledButton(); this.buttonGF.EndColor = System.Drawing.Color.White; this.buttonGF.Location = new System.Drawing.Point(71, 24); this.buttonGF.Name = "button1"; this.buttonGF.Size = new System.Drawing.Size(100, 23); this.buttonGF.StartColor = System.Drawing.Color.Turquoise; this.buttonGF.TabIndex = 1; this.buttonGF.Text = "button1"; this.buttonGF.Click += new System.EventHandler(this.button_Click); this.Controls.Add(buttonGF);
Ajoutez le code de gestion des événements pour l'événement Click du bouton à la classe Form1.
// The event handler called when a button is clicked // sender - The object that raised this event. // e - The arguments for this event. void button_Click(object sender, System.EventArgs e) { MessageBox.Show("Clicked", "Click event handler"); }
Substituez éventuellement la méthode OnPaint pour peindre l'arrière-plan du formulaire avec le motif de remplissage dégradé.
// Paints the background of the form with a GradientFill pattern. protected override void OnPaintBackground(PaintEventArgs e) { // On Windows Mobile Pocket PC 2003, the call to GradientFill // fails with GetLastError() returning 87 (ERROR_INVALID_PARAMETER) // when e.Graphics is used. // Instead, fill into a bitmap and then draw that onto e.Graphics. Bitmap bm = new Bitmap(Width, Height); Graphics gr = System.Drawing.Graphics.FromImage(bm); GradientFill.Fill( gr, this.ClientRectangle, Color.LightCyan, Color.SlateBlue, GradientFill.FillDirection.TopToBottom); e.Graphics.DrawImage(bm, 0, 0); gr.Dispose(); bm.Dispose(); }
Compilez et exécutez l'application.
Voir aussi
Tâches
Comment : utiliser une classe pour raccorder des procédures Windows
Comment : utiliser une classe d'assistance pour les appels de plates-formes
Comment : sous-classer un contrôle TreeView à l'aide de rappels natifs
Concepts
.Rubriques Comment relatives au .NET Compact Framework