Imagine the following construction in WinForms .NET. A WinForms form contains a custom control with several buttons, which are instances of the traditional Button class. One of these buttons is the default button for the form. The custom control executes the action associated with the default button when ENTER is pressed. This is done in the redefined ProcessCmdKey method:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    if (keyData == Keys.Return)
    {
        buttonOK_Click(null, EventArgs.Empty);
        return true;
    }
    return base.ProcessCmdKey(ref msg, keyData);
}

The default button must have an additional visual cue telling the user that this is the default button (an extra border inside the button). If we did this in a normal form, we would set its AcceptButton property. However, this approach is not applicable here. Even if we find the parent form using the Control.FindForm method or with an expression like (this.Parent as Form), we cannot set the AcceptButton property of the host form and then clear it the right way without resource leak or similar problems (a lot of technical details to place here and to bloat the question).

The first possible way to solve this task is to redefine or enhance the drawing of the button. Is there a relatively easy way to draw a button as the default button with the corresponding visual cue without implementing full custom painting? In my understanding, we might write a special class for our default button based on the following core:

internal class DefaultButton : Button
{
    protected override void OnPaint(PaintEventArgs pevent)
    {
        Rectangle rc = new Rectangle(0, 0, this.Width, this.Height);
        ButtonRenderer.DrawButton(pevent.Graphics, rc, System.Windows.Forms.VisualStyles.PushButtonState.Default);
    }
}

However, it should take into account the focused state, whether another button on a form is focused (in this case the default button is not drawn with the visual cue), and the like. I could not find a good example of this to use as a basis for my development.

Another possible way to solve my problem could be setting the protected IsDefault property or/and specifying the BS_DEFPUSHBUTTON flag in the overridden CreateParams method in a class inherited from the Button class, for example:

internal class DefaultButton : Button
{
    public DefaultButton() : base()
    {
        IsDefault = true;
    }

    protected override CreateParams CreateParams
    {
        get
        {
            const int BS_DEFPUSHBUTTON = 1;
            CreateParams cp = base.CreateParams;
            cp.Style |= BS_DEFPUSHBUTTON;
            return cp;
        }
    }
}

But I could not make this code work. Buttons based on this class are always drawn as normal push buttons without the default button visual cue.

1

There are 1 best solutions below

6
Reza Aghaei On

I'm not sure about the original requirement; for example I don't have any idea why a UserControl itself should set the AcceptButton of a Form, or what is the expected behavior if there are multiple instances of such controls on the form. It doesn't seem to be responsibility of the UserControl to set the AcceptButton of the Form and there might be better solutions, like relying on events and setting the AcceptButton.

Anyways, the following code example shows you how to set the AcceptButton of a Form; maybe it helps you to find a solutions. The highlights of the code:

  • The code uses dispose to set the AcceptButton to null.
  • The code implements ISupportInitialize to set the accept button after initialization of the control is done. If you create the control instance at run-time with code, don't forget to call EndInit, like this: ((System.ComponentModel.ISupportInitialize)(userControl11)).EndInit(); after adding it to the Form, but if you use designer, the designer will take care of that.
  • The code calls NotifyDefault(true) just for visual effect in design time when it's hosted on a form.

Here's the example:

using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
    public class UserControl1 : UserControl, ISupportInitialize
    {
        /// <summary> 
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        #region Component Designer generated code

        /// <summary> 
        /// Required method for Designer support - do not modify 
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.button2 = new System.Windows.Forms.Button();
            this.textBox1 = new System.Windows.Forms.TextBox();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(15, 57);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            this.button1.Click += new System.EventHandler(this.button1_Click);
            // 
            // button2
            // 
            this.button2.Location = new System.Drawing.Point(96, 57);
            this.button2.Name = "button2";
            this.button2.Size = new System.Drawing.Size(75, 23);
            this.button2.TabIndex = 1;
            this.button2.Text = "button2";
            this.button2.UseVisualStyleBackColor = true;
            this.button2.Click += new System.EventHandler(this.button2_Click);
            // 
            // textBox1
            // 
            this.textBox1.Location = new System.Drawing.Point(15, 17);
            this.textBox1.Name = "textBox1";
            this.textBox1.Size = new System.Drawing.Size(100, 20);
            this.textBox1.TabIndex = 2;
            // 
            // UserControl1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.Controls.Add(this.textBox1);
            this.Controls.Add(this.button2);
            this.Controls.Add(this.button1);
            this.Name = "UserControl1";
            this.Size = new System.Drawing.Size(236, 106);
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion
        private System.Windows.Forms.TextBox textBox1;
        public System.Windows.Forms.Button button1;
        public System.Windows.Forms.Button button2;
        public UserControl1()
        {
            InitializeComponent();
            //Just for visual effect in design time when it's hosted on a form
            button2.NotifyDefault(true); 
        }
        private void button1_Click(object sender, EventArgs e)
        {
            MessageBox.Show("1");
        }
        private void button2_Click(object sender, EventArgs e)
        {
            MessageBox.Show("2");
        }
        public void BeginInit()
        {
        }
        public void EndInit()
        {
            var f = this.FindForm();
            if (f != null)
                f.AcceptButton = button2;
        }
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            if (disposing)
            {
                var f = this.FindForm();
                if (f != null)
                    f.AcceptButton = null;
            }
            base.Dispose(disposing);
        }
    }
}