| File: System\Windows\Forms\Controls\Buttons\Button.cs | Web Access |
| Project: src\winforms\src\System.Windows.Forms\System.Windows.Forms.csproj (System.Windows.Forms) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel; using System.Drawing; using System.Windows.Forms.ButtonInternal; using System.Windows.Forms.Layout; using Windows.Win32.System.Variant; using Windows.Win32.UI.Accessibility; namespace System.Windows.Forms; /// <summary> /// Represents a Windows button. /// </summary> [SRDescription(nameof(SR.DescriptionButton))] [Designer($"System.Windows.Forms.Design.ButtonBaseDesigner, {Assemblies.SystemDesign}")] public partial class Button : ButtonBase, IButtonControl { /// <summary> /// The dialog result that will be sent to the parent dialog form when we are clicked. /// </summary> private DialogResult _dialogResult; private const int InvalidDimensionValue = int.MinValue; /// <summary> /// For buttons whose style is <see cref="FlatStyle.Flat"/>, this property specifies the size, in pixels /// of the border around the button. /// </summary> private Size _systemSize = new(InvalidDimensionValue, InvalidDimensionValue); /// <summary> /// Initializes a new instance of the <see cref="Button"/> class. /// </summary> public Button() : base() { // Buttons shouldn't respond to right clicks, so we need to do all our own click logic SetStyle(ControlStyles.StandardClick | ControlStyles.StandardDoubleClick, false); } /// <summary> /// Allows the control to optionally shrink when <see cref="Control.AutoSize"/> is <see langword="true"/>. /// </summary> [SRCategory(nameof(SR.CatLayout))] [Browsable(true)] [DefaultValue(AutoSizeMode.GrowOnly)] [Localizable(true)] [SRDescription(nameof(SR.ControlAutoSizeModeDescr))] public AutoSizeMode AutoSizeMode { get => GetAutoSizeMode(); set { SourceGenerated.EnumValidator.Validate(value); if (GetAutoSizeMode() != value) { SetAutoSizeMode(value); if (ParentInternal is not null) { // DefaultLayout does not keep anchor information until it needs to. When // AutoSize became a common property, we could no longer blindly call into // DefaultLayout, so now we do a special InitLayout just for DefaultLayout. if (ParentInternal.LayoutEngine == DefaultLayout.Instance) { ParentInternal.LayoutEngine.InitLayout(this, BoundsSpecified.Size); } LayoutTransaction.DoLayout(ParentInternal, this, PropertyNames.AutoSize); } } } } protected override AccessibleObject CreateAccessibilityInstance() => new ButtonAccessibleObject(this); internal override ButtonBaseAdapter CreateFlatAdapter() => DarkModeAdapterFactory.CreateFlatAdapter(this); internal override ButtonBaseAdapter CreatePopupAdapter() => DarkModeAdapterFactory.CreatePopupAdapter(this); internal override ButtonBaseAdapter CreateStandardAdapter() => DarkModeAdapterFactory.CreateStandardAdapter(this); internal override Size GetPreferredSizeCore(Size proposedConstraints) { if (FlatStyle != FlatStyle.System) { Size preferredSize = base.GetPreferredSizeCore(proposedConstraints); return AutoSizeMode == AutoSizeMode.GrowAndShrink ? preferredSize : LayoutUtils.UnionSizes(preferredSize, Size); } if (_systemSize.Width == InvalidDimensionValue) { // Note: The result from the BCM_GETIDEALSIZE message isn't accurate if the font has been // changed, because this method is called before the font is set into the device context. Size requiredSize = TextRenderer.MeasureText(Text, Font); requiredSize = SizeFromClientSize(requiredSize); // This padding makes FlatStyle.System about the same size as FlatStyle.Standard // with an 8px font. requiredSize.Width += 14; requiredSize.Height += 9; _systemSize = requiredSize; } Size paddedSize = _systemSize + Padding.Size; return AutoSizeMode == AutoSizeMode.GrowAndShrink ? paddedSize : LayoutUtils.UnionSizes(paddedSize, Size); } protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.ClassName = PInvoke.WC_BUTTON; if (GetStyle(ControlStyles.UserPaint)) { cp.Style |= PInvoke.BS_OWNERDRAW; } else { cp.Style |= PInvoke.BS_PUSHBUTTON; if (IsDefault) { cp.Style |= PInvoke.BS_DEFPUSHBUTTON; } } return cp; } } /// <summary> /// Gets or sets a value that is returned to the parent form when the button is clicked. /// </summary> [SRCategory(nameof(SR.CatBehavior))] [DefaultValue(DialogResult.None)] [SRDescription(nameof(SR.ButtonDialogResultDescr))] public virtual DialogResult DialogResult { get => _dialogResult; set { SourceGenerated.EnumValidator.Validate(value); _dialogResult = value; } } /// <summary> /// Defines, whether the control is owner-drawn. Based on this, /// the UserPaint flags get set, which in turn makes it later /// a Win32 controls, which we wrap (OwnerDraw == false) or not, and then /// we draw ourselves. If the user wants to opt out of DarkMode, we can no /// longer force (wrapping) System-Painting for FlatStyle.Standard, and we /// need this then also here and now, before CreateParams is called. /// </summary> private protected override bool OwnerDraw { get { if (Application.IsDarkModeEnabled // The SystemRenderer cannot render images. So, we flip to our // own DarkMode renderer, if we need to render images, except if... && Image is null // ...or a BackgroundImage, except if... && BackgroundImage is null // ...the user wants to opt out of implicit DarkMode rendering. && DarkModeRequestState is true // And all of this only counts for FlatStyle.Standard. For the // rest, we're using specific renderers anyway, which check // themselves on demand, if they need to apply Light- or DarkMode. && FlatStyle == FlatStyle.Standard) { return false; } return base.OwnerDraw; } } internal override bool SupportsUiaProviders => true; /// <summary> /// Raises the <see cref="Control.OnMouseEnter"/> event. /// </summary> protected override void OnMouseEnter(EventArgs e) => base.OnMouseEnter(e); /// <summary> /// Raises the <see cref="Control.OnMouseLeave"/> event. /// </summary> protected override void OnMouseLeave(EventArgs e) => base.OnMouseLeave(e); /// <hideinheritance/> [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Advanced)] public new event EventHandler? DoubleClick { add => base.DoubleClick += value; remove => base.DoubleClick -= value; } /// <hideinheritance/> [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Advanced)] public new event MouseEventHandler? MouseDoubleClick { add => base.MouseDoubleClick += value; remove => base.MouseDoubleClick -= value; } /// <summary> /// Notifies the <see cref="Button"/> whether it is the default button so that it can adjust its appearance /// accordingly. /// </summary> public virtual void NotifyDefault(bool value) { if (IsDefault != value) { IsDefault = value; } } protected override void OnClick(EventArgs e) { if (FindForm() is { } form) { form.DialogResult = _dialogResult; } AccessibilityNotifyClients(AccessibleEvents.StateChange, -1); AccessibilityNotifyClients(AccessibleEvents.NameChange, -1); // UIA events: if (IsAccessibilityObjectCreated) { using var nameVariant = (VARIANT)Name; AccessibilityObject.RaiseAutomationPropertyChangedEvent(UIA_PROPERTY_ID.UIA_NamePropertyId, nameVariant, nameVariant); AccessibilityObject.RaiseAutomationEvent(UIA_EVENT_ID.UIA_AutomationPropertyChangedEventId); } base.OnClick(e); } protected override void OnFontChanged(EventArgs e) { _systemSize = new Size(InvalidDimensionValue, InvalidDimensionValue); base.OnFontChanged(e); } protected override void OnMouseUp(MouseEventArgs mevent) { if (mevent.Button == MouseButtons.Left && MouseIsPressed) { bool isMouseDown = MouseIsDown; if (GetStyle(ControlStyles.UserPaint)) { // Paint in raised state. ResetFlagsandPaint(); } if (isMouseDown) { if (PInvoke.WindowFromPoint(PointToScreen(mevent.Location)) == HWND && !ValidationCancelled) { if (GetStyle(ControlStyles.UserPaint)) { OnClick(mevent); } OnMouseClick(mevent); } } } base.OnMouseUp(mevent); } protected override void OnTextChanged(EventArgs e) { _systemSize = new Size(InvalidDimensionValue, InvalidDimensionValue); base.OnTextChanged(e); } protected override void RescaleConstantsForDpi(int deviceDpiOld, int deviceDpiNew) { base.RescaleConstantsForDpi(deviceDpiOld, deviceDpiNew); if (ScaleHelper.IsScalingRequirementMet) { // Reset cached boundary size - it needs to be recalculated for new DPI _systemSize = new Size(InvalidDimensionValue, InvalidDimensionValue); } } /// <summary> /// Generates a <see cref="Control.Click"/> event for a button. /// </summary> public void PerformClick() { if (CanSelect) { bool validate = ValidateActiveControl(out bool validatedControlAllowsFocusChange); if (!ValidationCancelled && (validate || validatedControlAllowsFocusChange)) { // Paint in raised state. ResetFlagsandPaint(); OnClick(EventArgs.Empty); } } } protected internal override bool ProcessMnemonic(char charCode) { if (UseMnemonic && CanProcessMnemonic() && IsMnemonic(charCode, Text)) { PerformClick(); return true; } return base.ProcessMnemonic(charCode); } public override string ToString() => $"{base.ToString()}, Text: {Text}"; protected override void WndProc(ref Message m) { switch (m.MsgInternal) { case MessageId.WM_REFLECT_COMMAND: if (m.WParamInternal.HIWORD == PInvoke.BN_CLICKED) { if (!ValidationCancelled) { OnClick(EventArgs.Empty); } } break; case PInvokeCore.WM_ERASEBKGND: DefWndProc(ref m); break; default: base.WndProc(ref m); break; } } }