【问题标题】:Custom C# button's DialogResult doesn't work correctly on modal form自定义 C# 按钮的 DialogResult 在模态表单上无法正常工作
【发布时间】:2014-05-21 00:05:30
【问题描述】:

在制作 Winforms ImageButton 类 (C#) 时遇到了一点麻烦。我的 ImageButton 类实现了 IButtonControl 接口,但是当我将它添加到表单并将按钮的 DialogResult 设置为 'OK' ,然后在表单上调用 ShowDialog() 时,按下按钮不会关闭表单并返回 DialogResult,如正常的 Winforms Button 控件。

这是我对 ImageButton 的实现,你可以随意使用它。

/// <summary>
/// A control that displays an image and responds to mouse clicks on the image.
/// </summary>
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
[Designer(typeof(ImageButtonDesigner))]
[ToolboxItem(typeof(ImageButtonToolboxItem))]
public class ImageButton : PictureBox, IButtonControl, INotifyPropertyChanged
{
    #region Constructors

    /// <summary>
    /// Initializes a new instance of the ImageButton class using the default initial values.
    /// </summary>
    public ImageButton()
    {
        DoubleBuffered = true;
        BackColor = Color.Transparent;
        SizeMode = PictureBoxSizeMode.AutoSize;
    }

    #endregion

    #region Properties

    /// <summary>
    /// Backing field for the DialogResult property.
    /// </summary>
    private DialogResult dialogResult;

    /// <summary>
    /// Gets or sets a value that is returned to the parent form when the button is clicked.
    /// </summary>
    [Category("Behavior")]
    [Description("The dialog-box result produced in a modal form by clicking the button")]
    public DialogResult DialogResult
    {
        get
        {
            return dialogResult;
        }

        set
        {
            if (Enum.IsDefined(typeof(DialogResult), value))
                dialogResult = value;
            else
                throw new InvalidEnumArgumentException("value", (int)value, typeof(DialogResult));
        }
    }

    /// <summary>
    /// Backing field for the Idle property.
    /// </summary>
    private Image idle;

    /// <summary>
    /// The image that will be displayed on the control when the mouse is not over a visible part of it.
    /// </summary>
    [Category("Appearance")]
    [Description("The image that will be displayed on the Control when the mouse is not over a visible part of it.")]
    public Image Idle
    {
        get
        {
            return idle;
        }

        set
        {
            idle = value;

            NotifyPropertyChanged();
        }
    }

    /// <summary>
    /// Backing field for the Mouseover property
    /// </summary>
    private Image mouseover;

    /// <summary>
    /// The image that will be displayed on the control when the mouse is over a visible part of it.
    /// </summary>
    [Category("Appearance")]
    [Description("The image that will be displayed on the control when the mouse is over a visible part of it.")]
    public Image Mouseover
    {
        get { return mouseover; }

        set
        {
            mouseover = value;

            NotifyPropertyChanged();
        }
    }

    /// <summary>
    /// Backing field for the Mousedown property
    /// </summary>
    private Image mousedown;

    /// <summary>
    /// The image that will be displayed on the control when the left mouse button is held down and the mouse is over a visible part of it.
    /// </summary>
    [Category("Appearance")]
    [Description("The image that will be displayed on the control when the left mouse button is held down and the mouse is over a visible part of it.")]
    public Image Mousedown
    {
        get { return mousedown; }

        set
        {
            mousedown = value;

            NotifyPropertyChanged();
        }
    }

    /// <summary>
    /// Gets or sets the text associated with the control.
    /// </summary>
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    [Category("Appearance")]
    [Description("The text associated with the control.")]
    public override string Text
    {
        get
        {
            return base.Text;
        }
        set
        {
            base.Text = value;
        }
    }

    /// <summary>
    /// Gets or sets the font of the text displayed by the control.
    /// </summary>
    [Browsable(true)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
    [Category("Appearance")]
    [Description("The font used to display text in the control.")]
    public override Font Font
    {
        get
        {
            return base.Font;
        }
        set
        {
            base.Font = value;
        }
    }

    /// <summary>
    /// Gets or sets the image that is displayed by the PictureBox.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    [Browsable(false)]
    [Category("Appearance")]
    [Description("The image displayed in the PictureBox.")]
    new public Image Image
    {
        get
        {
            return base.Image;
        }

        set
        {
            base.Image = value;
        }
    }

    /// <summary>
    /// Backing field for the ButtonState property.
    /// </summary>
    private ButtonStates buttonState = ButtonStates.None;

    /// <summary>
    /// The current state of the button.
    /// </summary>
    private ButtonStates ButtonState
    {
        get { return buttonState; }

        set
        {
            buttonState = value;

            NotifyPropertyChanged();
        }
    }

    /// <summary>
    /// Gets the default size of the control.
    /// </summary>
    protected override Size DefaultSize
    {
        get
        {
            return new Size(75, 23);
        }
    }

    #endregion

    #region Enums

    /// <summary>
    /// Specifies the current state of a button.
    /// </summary>
    [Flags]
    private enum ButtonStates : byte
    {
        /// <summary>
        /// 
        /// </summary>
        [Description("")]
        None = 0,

        /// <summary>
        /// 
        /// </summary>
        [Description("")]
        Default = 1 << 0,

        /// <summary>
        /// 
        /// </summary>
        [Description("")]
        Mouseover = 1 << 1,

        /// <summary>
        /// 
        /// </summary>
        [Description("")]
        Mousedown = 1 << 2
    }

    #endregion

    #region Events

    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    [Category("Property Changed")]
    [Description("Occurs when a property value changes.")]
    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    #region Methods

    /// <summary>
    /// Raises the System.ComponentModel.PropertyChanged event.
    /// </summary>
    /// <param name="propertyName">the name of the property that changed.</param>
    protected virtual void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        if (propertyName == "Idle")
            Image = Idle;
    }

    /// <summary>
    /// Notifies the button whether it is the default button so that it can adjust its appearance accordingly.
    /// </summary>
    /// <param name="value">true if the button is to have the appearance of the default button; otherwise, false.</param>
    public void NotifyDefault(bool value)
    {
        ButtonState = value ? ButtonState | ButtonStates.Default : ButtonState & ~ButtonStates.Default;
    }

    /// <summary>
    /// Generates a Click event for a button.
    /// </summary>
    public void PerformClick()
    {
        if (CanSelect)
        {
            OnClick(EventArgs.Empty);
        }
    }

    /// <summary>
    /// Raises the System.Windows.Control.TextChanged event.
    /// </summary>
    /// <param name="e">A System.EventArgs that contains the event data.</param>
    protected override void OnTextChanged(EventArgs e)
    {
        base.OnTextChanged(e);

        Refresh();
    }

    /// <summary>
    /// Raises the System.Windows.Forms.Paint event.
    /// </summary>
    /// <param name="pe">A PaintEventArgs that contains the event data.</param>
    protected override void OnPaint(PaintEventArgs pe)
    {
        base.OnPaint(pe);

        if ((!string.IsNullOrEmpty(Text)) && (pe != null) && (base.Font != null))
        {
            SolidBrush drawBrush = new SolidBrush(base.ForeColor);

            // Calculate the size of the text that will be drawn onto the control.
            SizeF drawStringSize = pe.Graphics.MeasureString(base.Text, base.Font);

            // The registration point used to draw the text.
            PointF drawPoint;

            if (base.Image != null)
                drawPoint = new PointF(base.Image.Width / 2 - drawStringSize.Width / 2, base.Image.Height / 2 - drawStringSize.Height / 2);
            else
                drawPoint = new PointF(base.Width / 2 - drawStringSize.Width / 2, base.Height / 2 - drawStringSize.Height / 2);

            pe.Graphics.DrawString(base.Text, base.Font, drawBrush, drawPoint);
        }
    }

    /// <summary>
    /// Raises the System.Windows.Forms.MouseEnter event.
    /// </summary>
    /// <param name="e">A System.EventArgs that contains the event data.</param>
    protected override void OnMouseEnter(EventArgs e)
    {
        base.OnMouseEnter(e);

        ButtonState |= ButtonStates.Mouseover;
        Image = Mouseover;
    }

    /// <summary>
    /// Raises the System.Windows.Forms.MouseLeave event.
    /// </summary>
    /// <param name="e">A System.EventArgs that contains the event data.</param>
    protected override void OnMouseLeave(EventArgs e)
    {
        base.OnMouseLeave(e);

        ButtonState &= ~ButtonStates.Mouseover;
        Image = Idle;
    }

    /// <summary>
    /// Raises the System.Windows.Forms.MouseDown event.
    /// </summary>
    /// <param name="e">A System.Windows.Forms.MouseEventArgs that contains the event data.</param>
    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);

        ButtonState |= ButtonStates.Mousedown;
        Image = Mousedown;
    }

    /// <summary>
    /// Raises the System.Windows.Forms.MouseUp event.
    /// </summary>
    /// <param name="e">A System.Windows.Forms.MouseEventArgs that contains the event data.</param>
    protected override void OnMouseUp(MouseEventArgs e)
    {
        base.OnMouseUp(e);

        ButtonState &= ~ButtonStates.Mousedown;
        Image = ((ButtonState & ButtonStates.Mouseover) != 0) ? Mouseover : idle;
    }

    #endregion
}

[Serializable]
internal class ImageButtonToolboxItem : ToolboxItem
{
    public ImageButtonToolboxItem() : base(typeof(ImageButton)) { }

    protected ImageButtonToolboxItem(SerializationInfo info, StreamingContext context)
    {
        Deserialize(info, context);
    }

    protected override IComponent[] CreateComponentsCore(IDesignerHost host)
    {
        ImageButton imageButton = (ImageButton)host.CreateComponent(typeof(ImageButton));

        Assembly assembly = Assembly.GetAssembly(typeof(ImageButton));

        using (Stream streamMouseover = assembly.GetManifestResourceStream("Mvc.Mouseover.png"))
        using (Stream streamMousedown = assembly.GetManifestResourceStream("Mvc.Mousedown.png"))
        using (Stream streamIdle = assembly.GetManifestResourceStream("Mvc.Idle.png"))
        {
            imageButton.Idle = Image.FromStream(streamIdle);
            imageButton.Mouseover = Image.FromStream(streamMouseover);
            imageButton.Mousedown = Image.FromStream(streamMousedown);
        }

        return new IComponent[] { imageButton };
    }
}

internal class ImageButtonDesigner : ControlDesigner
{
    protected override void PostFilterAttributes(System.Collections.IDictionary attributes)
    {
        base.PostFilterAttributes(attributes);

        Attribute dockingBehaviour = new DockingAttribute(DockingBehavior.Never);

        attributes[typeof(DockingAttribute)] = dockingBehaviour;
    }

    public override SelectionRules SelectionRules
    {
        get
        {
            return SelectionRules.Moveable;
        }
    }
}

抱歉工具箱项目和设计器代码凌乱..

我的问题是,是否需要任何特殊的实现方式才能使按钮控件在模态表单上工作(与普通按钮一样) (注意,Form 的 BorderStyle 属性设置为 None,不知道是否重要)

谢谢,提前!

【问题讨论】:

    标签: c# winforms modal-dialog imagebutton dialogresult


    【解决方案1】:

    您需要实际应用分配的 DialogResult 属性,它不是自动的。通过覆盖 OnClick() 方法来做到这一点:

    protected override void OnClick(EventArgs e) {
        var form = this.FindForm();
        if (form != null) form.DialogResult = dialogResult;
        base.OnClick(e);
    }
    

    从技术上讲,您还应该将状态更改通知可访问性客户端,完全不清楚您是否关心这一点。对于自定义控件,它往往会被跳过,但您通常应该这样做。在 base.OnClick() 调用之前插入这些语句:

        base.AccessibilityNotifyClients(AccessibleEvents.StateChange, -1);
        base.AccessibilityNotifyClients(AccessibleEvents.NameChange, -1);
    

    【讨论】:

    • 微软这句话怎么样:If the DialogResult for this property is set to anything other than None, and if the parent form was displayed through the ShowDialog method, clicking the button closes the parent form without your having to hook up any events. The form's DialogResult property is then set to the DialogResult of the button when the button is clicked.见:docs.microsoft.com/en-us/dotnet/api/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-02-07
    • 2023-03-03
    • 2021-01-17
    • 2013-11-11
    • 2021-11-21
    相关资源
    最近更新 更多