Como: Um botão by Using nativo Callbacks subclasse

Este tópico é um exercício para demonstrar como fazer subclassing de um controle Windows Forms para receber callbacks de código nativo usando um procedimento gerenciado de Windows (WndProc).Este programa exemplo é descrito detalhadamente no Subclassificação dos controles com um procedimento de janela gerenciado.

Este programa de exemplo mostra como exibir um preenchimento gradiente com um controle em uma subclasse de Button. Ele exige tratamento de mensagens do Windows.Uma maneira mais fácil exibir um preenchimento gradiente em um botão é criar um controle personalizado derivado de Control. Para um exemplo, consulte Como: Exibir um preenchimento gradual.

Para fazer subclassing de um controle Button para exibir um preenchimento gradiente

  1. In Microsoft Visual Studio 2005, criar um projeto dispositivo inteligente Pocket PC.

  2. Adicionar o Gradientfill classe para seu projeto.

    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
        }
    }
    
  3. Adicionar o GradientFilledButton classe para seu projeto.

    // 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();
        }
    }
    
  4. Adicionar o Win32 classe auxiliar para seu projeto. Este código está disponível em Como: Use a auxiliar classe para plataforma Invokes.

  5. Adicionar o WinProcHooker classe ao nosso projeto. Este código está disponível em Como: Usar uma classe de interceptação de procedimentos do Windows.

  6. Declare uma variável de formulário chamada buttonGF do tipo GradientFilledButton.

    private GradientFilledButton buttonGF;
    
  7. Adicione o código a seguir, que inicializa controle de botão em que foi feito subclassing, para o construtor do Form1 classe. Este código deve seguir a telefonar para o InitializeComponent método. Você pode especificar o início e fim cores de preenchimento gradiente e qualquer um TopToBottom ou LeftToRight direção de preenchimento.

    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);
    
    
  8. Adicione o código de manipulação de eventos do botão Click evento para o Form1 classe.

    // 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");
    }
    
    
  9. Opcionalmente, substitua o OnPaint método para pintar o segundo plano do formulário com o padrão de preenchimento gradiente.

    // 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();
    }
    
  10. Compile e execute o aplicativo.

Consulte também

Tarefas

Como: Usar uma classe de interceptação de procedimentos do Windows

Como: Use a auxiliar classe para plataforma Invokes

Como: Subclasse a TreeView by Using nativo Callbacks

Conceitos

Tópicos "como" do .NET compact estrutura

Outros recursos

Interoperabilidade no .NET Compact Framework