|
// 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.Runtime.InteropServices;
using System.Windows.Forms.VisualStyles;
using Microsoft.Win32;
namespace System.Windows.Forms;
/// <summary>
/// Implements the basic functionality required by an up-down control.
/// </summary>
[Designer($"System.Windows.Forms.Design.UpDownBaseDesigner, {AssemblyRef.SystemDesign}")]
public abstract partial class UpDownBase : ContainerControl
{
private const int DefaultWheelScrollLinesPerPage = 1;
private const int DefaultButtonsWidth = 16;
private const int DefaultControlWidth = 120;
private const int ThemedBorderWidth = 1; // width of custom border we draw when themed
private const BorderStyle DefaultBorderStyle = BorderStyle.Fixed3D;
private const LeftRightAlignment DefaultUpDownAlign = LeftRightAlignment.Right;
private const int DefaultTimerInterval = 500;
// Child controls
internal UpDownEdit _upDownEdit;
internal UpDownButtons _upDownButtons;
private LeftRightAlignment _upDownAlign = DefaultUpDownAlign;
/// <summary>
/// The current border for this edit control.
/// </summary>
private BorderStyle _borderStyle = DefaultBorderStyle;
// Mouse wheel movement
private int _wheelDelta;
private bool _doubleClickFired;
internal int _defaultButtonsWidth = DefaultButtonsWidth;
/// <summary>
/// Initializes a new instance of the <see cref="UpDownBase"/> class.
/// </summary>
public UpDownBase()
{
_defaultButtonsWidth = LogicalToDeviceUnits(DefaultButtonsWidth);
_upDownButtons = new UpDownButtons(this);
_upDownEdit = new UpDownEdit(this)
{
BorderStyle = BorderStyle.None,
AutoSize = false
};
_upDownEdit.KeyDown += OnTextBoxKeyDown;
_upDownEdit.KeyPress += OnTextBoxKeyPress;
_upDownEdit.TextChanged += OnTextBoxTextChanged;
_upDownEdit.LostFocus += OnTextBoxLostFocus;
_upDownEdit.Resize += OnTextBoxResize;
_upDownButtons.TabStop = false;
_upDownButtons.Size = new Size(_defaultButtonsWidth, PreferredHeight);
_upDownButtons.UpDown += OnUpDown;
Controls.AddRange([_upDownButtons, _upDownEdit]);
SetStyle(ControlStyles.Opaque | ControlStyles.FixedHeight | ControlStyles.ResizeRedraw, true);
SetStyle(ControlStyles.StandardClick, false);
SetStyle(ControlStyles.UseTextForAccessibility, false);
#pragma warning disable WFO5001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
SetStyle(ControlStyles.ApplyThemingImplicitly, true);
#pragma warning restore WFO5001
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override bool AutoScroll
{
get => false;
set
{
// Don't allow AutoScroll to be set to anything
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new Size AutoScrollMargin
{
get => base.AutoScrollMargin;
set => base.AutoScrollMargin = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new Size AutoScrollMinSize
{
get => base.AutoScrollMinSize;
set => base.AutoScrollMinSize = value;
}
/// <summary>
/// Override to re-expose AutoSize.
/// </summary>
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public override bool AutoSize
{
get => base.AutoSize;
set => base.AutoSize = value;
}
[SRCategory(nameof(SR.CatPropertyChanged))]
[SRDescription(nameof(SR.ControlOnAutoSizeChangedDescr))]
[Browsable(true)]
[EditorBrowsable(EditorBrowsableState.Always)]
public new event EventHandler? AutoSizeChanged
{
add => base.AutoSizeChanged += value;
remove => base.AutoSizeChanged -= value;
}
/// <summary>
/// Gets or sets the background color for the
/// text box portion of the up-down control.
/// </summary>
public override Color BackColor
{
get => _upDownEdit.BackColor;
set
{
base.BackColor = value; // Don't remove this or you will break serialization.
_upDownEdit.BackColor = value;
Invalidate();
}
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override Image? BackgroundImage
{
get => base.BackgroundImage;
set => base.BackgroundImage = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? BackgroundImageChanged
{
add => base.BackgroundImageChanged += value;
remove => base.BackgroundImageChanged -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public override ImageLayout BackgroundImageLayout
{
get => base.BackgroundImageLayout;
set => base.BackgroundImageLayout = value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? BackgroundImageLayoutChanged
{
add => base.BackgroundImageLayoutChanged += value;
remove => base.BackgroundImageLayoutChanged -= value;
}
/// <summary>
/// Gets or sets the border style for the up-down control.
/// </summary>
[SRCategory(nameof(SR.CatAppearance))]
[DefaultValue(BorderStyle.Fixed3D)]
[DispId(PInvokeCore.DISPID_BORDERSTYLE)]
[SRDescription(nameof(SR.UpDownBaseBorderStyleDescr))]
public BorderStyle BorderStyle
{
get => _borderStyle;
set
{
SourceGenerated.EnumValidator.Validate(value);
if (_borderStyle != value)
{
_borderStyle = value;
RecreateHandle();
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the text property is being
/// changed internally by its parent class.
/// </summary>
protected bool ChangingText { get; set; }
public override ContextMenuStrip? ContextMenuStrip
{
get => base.ContextMenuStrip;
set
{
base.ContextMenuStrip = value;
_upDownEdit.ContextMenuStrip = value;
}
}
protected override AccessibleObject CreateAccessibilityInstance()
=> new UpDownBaseAccessibleObject(this);
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.Style &= ~(int)WINDOW_STYLE.WS_BORDER;
if (!Application.RenderWithVisualStyles)
{
switch (_borderStyle)
{
case BorderStyle.Fixed3D:
cp.ExStyle |= (int)WINDOW_EX_STYLE.WS_EX_CLIENTEDGE;
break;
case BorderStyle.FixedSingle:
cp.Style |= (int)WINDOW_STYLE.WS_BORDER;
break;
}
}
return cp;
}
}
/// <summary>
/// Deriving classes can override this to configure a default size for their control.
/// This is more efficient than setting the size in the control's constructor.
/// </summary>
protected override Size DefaultSize => new(DefaultControlWidth, PreferredHeight);
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public new DockPaddingEdges DockPadding => base.DockPadding;
/// <summary>
/// Returns true if this control has focus.
/// </summary>
[Browsable(false)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[SRDescription(nameof(SR.ControlFocusedDescr))]
public override bool Focused => _upDownEdit.Focused;
/// <summary>
/// Indicates the foreground color for the control.
/// </summary>
public override Color ForeColor
{
get => _upDownEdit.ForeColor;
set
{
base.ForeColor = value;
_upDownEdit.ForeColor = value;
}
}
/// <summary>
/// Gets or sets a value indicating whether the user can use the UP ARROW and
/// DOWN ARROW keys to select values.
/// </summary>
[SRCategory(nameof(SR.CatBehavior))]
[DefaultValue(true)]
[SRDescription(nameof(SR.UpDownBaseInterceptArrowKeysDescr))]
public bool InterceptArrowKeys { get; set; } = true;
public override Size MaximumSize
{
get => base.MaximumSize;
set => base.MaximumSize = new Size(value.Width, 0);
}
public override Size MinimumSize
{
get => base.MinimumSize;
set => base.MinimumSize = new Size(value.Width, 0);
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? MouseEnter
{
add => base.MouseEnter += value;
remove => base.MouseEnter -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? MouseLeave
{
add => base.MouseLeave += value;
remove => base.MouseLeave -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event EventHandler? MouseHover
{
add => base.MouseHover += value;
remove => base.MouseHover -= value;
}
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public new event MouseEventHandler? MouseMove
{
add => base.MouseMove += value;
remove => base.MouseMove -= value;
}
/// <summary>
/// Gets the height of the up-down control.
/// </summary>
[SRCategory(nameof(SR.CatLayout))]
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Advanced)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[SRDescription(nameof(SR.UpDownBasePreferredHeightDescr))]
public int PreferredHeight
{
get
{
int height = FontHeight;
// Adjust for the border style
if (_borderStyle != BorderStyle.None)
{
height += SystemInformation.BorderSize.Height * 4 + 3;
}
else
{
height += 3;
}
return height;
}
}
/// <summary>
/// Gets or sets a value indicating whether the text may only be changed by
/// the use of the up or down buttons.
/// </summary>
[SRCategory(nameof(SR.CatBehavior))]
[DefaultValue(false)]
[SRDescription(nameof(SR.UpDownBaseReadOnlyDescr))]
public bool ReadOnly
{
get => _upDownEdit.ReadOnly;
set => _upDownEdit.ReadOnly = value;
}
/// <summary>
/// Gets or sets the text displayed in the up-down control.
/// </summary>
[Localizable(true)]
[AllowNull]
public override string Text
{
get => _upDownEdit.Text;
set
{
// The text changed event will at this point be triggered. After returning, the value of UserEdit will
// reflect whether or not the current upDownEditbox text is in sync with any internally stored values.
// If UserEdit is true, we must validate the text the user typed or set.
_upDownEdit.Text = value;
// Usually, the code in the Text changed event handler sets ChangingText back to false. If the text
// hasn't actually changed though, the event handler never fires. ChangingText should always be false
// on exit from this property.
ChangingText = false;
if (UserEdit)
{
ValidateEditText();
}
}
}
/// <summary>
/// Gets or sets the alignment of the text in the up-down control.
/// </summary>
[Localizable(true)]
[SRCategory(nameof(SR.CatAppearance))]
[DefaultValue(HorizontalAlignment.Left)]
[SRDescription(nameof(SR.UpDownBaseTextAlignDescr))]
public HorizontalAlignment TextAlign
{
get => _upDownEdit.TextAlign;
set
{
SourceGenerated.EnumValidator.Validate(value);
_upDownEdit.TextAlign = value;
}
}
internal TextBox TextBox => _upDownEdit;
/// <summary>
/// Gets or sets the alignment of the up and down buttons on the up-down control.
/// </summary>
[Localizable(true)]
[SRCategory(nameof(SR.CatAppearance))]
[DefaultValue(LeftRightAlignment.Right)]
[SRDescription(nameof(SR.UpDownBaseAlignmentDescr))]
public LeftRightAlignment UpDownAlign
{
get => _upDownAlign;
set
{
SourceGenerated.EnumValidator.Validate(value);
if (_upDownAlign != value)
{
_upDownAlign = value;
PositionControls();
Invalidate();
}
}
}
internal UpDownButtons UpDownButtonsInternal => _upDownButtons;
/// <summary>
/// Gets or sets a value indicating whether a value has been entered by the user.
/// </summary>
protected bool UserEdit { get; set; }
/// <summary>
/// When overridden in a derived class, handles the pressing of the down button
/// on the up-down control.
/// </summary>
public abstract void DownButton();
internal override Rectangle ApplyBoundsConstraints(int suggestedX, int suggestedY, int proposedWidth, int proposedHeight)
{
return base.ApplyBoundsConstraints(suggestedX, suggestedY, proposedWidth, PreferredHeight);
}
internal override void ReleaseUiaProvider(HWND handle)
{
// UpDownEdit as TextBox is a control, that should disconnect its accessible object itself,
// but if it supports Uia providers. If no, force disconnecting for UpDownEdit accessible object
// as a part of UIA tree of Domain/NumericUpDown controls.
if (!_upDownEdit.SupportsUiaProviders)
{
_upDownEdit.ReleaseUiaProvider(_upDownEdit.HWNDInternal);
}
base.ReleaseUiaProvider(handle);
}
/// <summary>
/// When overridden in a derived class, handles rescaling of any magic numbers used in control painting.
/// For UpDown controls, scale the width of the up/down buttons.
/// Must call the base class method to get the current DPI values. This method is invoked only when
/// Application opts-in into the Per-monitor V2 support, targets .NETFX 4.7 and has
/// EnableDpiChangedMessageHandling config switch turned on.
/// </summary>
protected override void RescaleConstantsForDpi(int deviceDpiOld, int deviceDpiNew)
{
base.RescaleConstantsForDpi(deviceDpiOld, deviceDpiNew);
_defaultButtonsWidth = LogicalToDeviceUnits(DefaultButtonsWidth);
_upDownButtons.Width = _defaultButtonsWidth;
}
/// <summary>
/// When overridden in a derived class, raises the Changed event.
/// </summary>
protected virtual void OnChanged(object? source, EventArgs e)
{
}
/// <summary>
/// Initialize the updown. Adds the upDownEdit and updown buttons.
/// </summary>
protected override void OnHandleCreated(EventArgs e)
{
base.OnHandleCreated(e);
PositionControls();
SystemEvents.UserPreferenceChanged += UserPreferenceChanged;
}
/// <summary>
/// Tear down the updown.
/// </summary>
protected override void OnHandleDestroyed(EventArgs e)
{
SystemEvents.UserPreferenceChanged -= UserPreferenceChanged;
base.OnHandleDestroyed(e);
}
/// <summary>
/// Handles painting the buttons on the control.
/// </summary>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Rectangle editBounds = _upDownEdit.Bounds;
Color backColor = BackColor;
if (Application.RenderWithVisualStyles)
{
if (_borderStyle != BorderStyle.None)
{
Rectangle bounds = ClientRectangle;
Rectangle clipBounds = e.ClipRectangle;
// Draw a themed textbox-like border, which is what the spin control does
VisualStyleRenderer vsr = new(VisualStyleElement.TextBox.TextEdit.Normal);
int border = ThemedBorderWidth;
Rectangle clipLeft = new(bounds.Left, bounds.Top, border, bounds.Height);
Rectangle clipTop = new(bounds.Left, bounds.Top, bounds.Width, border);
Rectangle clipRight = new(bounds.Right - border, bounds.Top, border, bounds.Height);
Rectangle clipBottom = new(bounds.Left, bounds.Bottom - border, bounds.Width, border);
clipLeft.Intersect(clipBounds);
clipTop.Intersect(clipBounds);
clipRight.Intersect(clipBounds);
clipBottom.Intersect(clipBounds);
using DeviceContextHdcScope hdc = new(e);
vsr.DrawBackground(hdc, bounds, clipLeft, HWNDInternal);
vsr.DrawBackground(hdc, bounds, clipTop, HWNDInternal);
vsr.DrawBackground(hdc, bounds, clipRight, HWNDInternal);
vsr.DrawBackground(hdc, bounds, clipBottom, HWNDInternal);
// Draw a rectangle around edit control with the background color.
Rectangle backRect = editBounds;
backRect.X--;
backRect.Y--;
backRect.Width += 2;
backRect.Height += 2;
using CreatePenScope hpen = new(backColor);
hdc.DrawRectangle(backRect, hpen);
}
}
else
{
// Draw a rectangle around edit control with the background color.
Rectangle backRect = editBounds;
backRect.Inflate(1, 1);
if (!Enabled)
{
backRect.X--;
backRect.Y--;
backRect.Width++;
backRect.Height++;
}
int width = Enabled ? 2 : 1;
backRect.Width++;
backRect.Height++;
using DeviceContextHdcScope hdc = new(e);
using CreatePenScope hpen = new(backColor, width);
hdc.DrawRectangle(backRect, hpen);
}
if (!Enabled && BorderStyle != BorderStyle.None && !_upDownEdit.ShouldSerializeBackColor())
{
// Draws a grayed rectangle around the upDownEdit, since otherwise we will have a white
// border around the upDownEdit, which is inconsistent with Windows' behavior
// we only want to do this when BackColor is not serialized, since otherwise
// we should display the BackColor instead of the usual grayed textbox.
editBounds.Inflate(1, 1);
ControlPaint.DrawBorderSimple(e, editBounds, SystemColors.Control);
}
}
/// <summary>
/// Raises the <see cref="Control.KeyDown"/> event.
/// </summary>
protected virtual void OnTextBoxKeyDown(object? source, KeyEventArgs e)
{
OnKeyDown(e);
if (InterceptArrowKeys)
{
// Intercept up arrow
if (e.KeyData == Keys.Up)
{
UpButton();
e.Handled = true;
}
// Intercept down arrow
else if (e.KeyData == Keys.Down)
{
DownButton();
e.Handled = true;
}
}
// Perform text validation if ENTER is pressed
if (e.KeyCode == Keys.Return && UserEdit)
{
ValidateEditText();
}
}
/// <summary>
/// Raises the <see cref="Control.KeyPress"/> event.
/// </summary>
protected virtual void OnTextBoxKeyPress(object? source, KeyPressEventArgs e)
=> OnKeyPress(e);
/// <summary>
/// Raises the <see cref="Control.LostFocus"/> event.
/// </summary>
protected virtual void OnTextBoxLostFocus(object? source, EventArgs e)
{
if (UserEdit)
{
ValidateEditText();
}
}
/// <summary>
/// Raises the <see cref="Control.Resize"/> event.
/// </summary>
protected virtual void OnTextBoxResize(object? source, EventArgs e)
{
Height = PreferredHeight;
PositionControls();
}
/// <summary>
/// Raises the TextBoxTextChanged event.
/// event.
/// </summary>
protected virtual void OnTextBoxTextChanged(object? source, EventArgs e)
{
if (ChangingText)
{
Debug.Assert(!UserEdit, "OnTextBoxTextChanged() - UserEdit == true");
ChangingText = false;
}
else
{
UserEdit = true;
}
OnTextChanged(e);
OnChanged(source, EventArgs.Empty);
}
/// <summary>
/// Called from the UpDownButtons member. Provided for derived controls to have a finer way to handle the event.
/// </summary>
internal virtual void OnStartTimer()
{
}
internal virtual void OnStopTimer()
{
}
/// <summary>
/// Raises the <see cref="Control.OnMouseDown"/> event.
/// </summary>
protected override void OnMouseDown(MouseEventArgs e)
{
if (e.Clicks == 2 && e.Button == MouseButtons.Left)
{
_doubleClickFired = true;
}
base.OnMouseDown(e);
}
/// <summary>
/// Raises the <see cref="Control.OnMouseUp"/> event.
/// </summary>
protected override void OnMouseUp(MouseEventArgs mevent)
{
if (mevent.Button == MouseButtons.Left)
{
if (PInvoke.WindowFromPoint(PointToScreen(mevent.Location)) == HWND && !ValidationCancelled)
{
if (!_doubleClickFired)
{
OnClick(mevent);
OnMouseClick(mevent);
}
else
{
_doubleClickFired = false;
OnDoubleClick(mevent);
OnMouseDoubleClick(mevent);
}
}
_doubleClickFired = false;
}
base.OnMouseUp(mevent);
}
/// <summary>
/// Raises the <see cref="Control.OnMouseWheel"/> event.
/// </summary>
protected override void OnMouseWheel(MouseEventArgs e)
{
base.OnMouseWheel(e);
if (e is HandledMouseEventArgs hme)
{
if (hme.Handled)
{
return;
}
hme.Handled = true;
}
if ((ModifierKeys & (Keys.Shift | Keys.Alt)) != 0 || MouseButtons != MouseButtons.None)
{
// Do not scroll when Shift or Alt key is down, or when a mouse button is down.
return;
}
int wheelScrollLines = SystemInformation.MouseWheelScrollLines;
if (wheelScrollLines == 0)
{
// Do not scroll when the user system setting is 0 lines per notch
return;
}
Debug.Assert(_wheelDelta > -PInvoke.WHEEL_DELTA, "wheelDelta is too small");
Debug.Assert(_wheelDelta < PInvoke.WHEEL_DELTA, "wheelDelta is too big");
_wheelDelta += e.Delta;
float partialNotches;
partialNotches = _wheelDelta / (float)PInvoke.WHEEL_DELTA;
if (wheelScrollLines == -1)
{
wheelScrollLines = DefaultWheelScrollLinesPerPage;
}
// Evaluate number of bands to scroll
int scrollBands = (int)(wheelScrollLines * partialNotches);
if (scrollBands != 0)
{
int absScrollBands;
if (scrollBands > 0)
{
absScrollBands = scrollBands;
while (absScrollBands > 0)
{
UpButton();
absScrollBands--;
}
_wheelDelta -= (int)(scrollBands * (PInvoke.WHEEL_DELTA / (float)wheelScrollLines));
}
else
{
absScrollBands = -scrollBands;
while (absScrollBands > 0)
{
DownButton();
absScrollBands--;
}
_wheelDelta -= (int)(scrollBands * (PInvoke.WHEEL_DELTA / (float)wheelScrollLines));
}
}
}
/// <summary>
/// Handle the layout event. The size of the upDownEdit control, and the
/// position of the UpDown control must be modified.
/// </summary>
protected override void OnLayout(LayoutEventArgs e)
{
PositionControls();
base.OnLayout(e);
}
/// <summary>
/// Raises the FontChanged event.
/// </summary>
protected override void OnFontChanged(EventArgs e)
{
// Clear the font height cache
FontHeight = -1;
Height = PreferredHeight;
PositionControls();
base.OnFontChanged(e);
}
/// <summary>
/// Handles UpDown events, which are generated by clicking on the updown
/// buttons in the child updown control.
/// </summary>
private void OnUpDown(object? source, UpDownEventArgs e)
{
// Modify the value
if (e.ButtonID == (int)ButtonID.Up)
{
UpButton();
}
else if (e.ButtonID == (int)ButtonID.Down)
{
DownButton();
}
}
/// <summary>
/// Calculates the size and position of the upDownEdit control and the updown buttons.
/// </summary>
private void PositionControls()
{
Rectangle upDownEditBounds = Rectangle.Empty;
Rectangle upDownButtonsBounds = Rectangle.Empty;
Rectangle clientArea = new(Point.Empty, ClientSize);
int totalClientWidth = clientArea.Width;
bool themed = Application.RenderWithVisualStyles;
BorderStyle borderStyle = BorderStyle;
// Determine how much to squish in - Fixed3D and FixedSingle have 2PX border
int borderWidth = (borderStyle == BorderStyle.None) ? 0 : 2;
clientArea.Inflate(-borderWidth, -borderWidth);
// Reposition and resize the upDownEdit control
if (_upDownEdit is not null)
{
upDownEditBounds = clientArea;
upDownEditBounds.Size = new Size(clientArea.Width - _defaultButtonsWidth, clientArea.Height);
}
// Reposition and resize the updown buttons
if (_upDownButtons is not null)
{
int borderFixup = (themed) ? 1 : 2;
if (borderStyle == BorderStyle.None)
{
borderFixup = 0;
}
upDownButtonsBounds = new Rectangle(
clientArea.Right - _defaultButtonsWidth + borderFixup,
clientArea.Top - borderFixup,
_defaultButtonsWidth,
clientArea.Height + (borderFixup * 2));
}
// Right to left translation
LeftRightAlignment updownAlign = UpDownAlign;
updownAlign = RtlTranslateLeftRight(updownAlign);
// Left/right updown align translation
if (updownAlign == LeftRightAlignment.Left)
{
// If the buttons are aligned to the left, swap position of text box/buttons
upDownButtonsBounds.X = totalClientWidth - upDownButtonsBounds.Right;
upDownEditBounds.X = totalClientWidth - upDownEditBounds.Right;
}
// Apply locations
if (_upDownEdit is not null)
{
_upDownEdit.Bounds = upDownEditBounds;
}
if (_upDownButtons is not null)
{
_upDownButtons.Bounds = upDownButtonsBounds;
_upDownButtons.Invalidate();
}
}
/// <summary>
/// Selects a range of text in the up-down control.
/// </summary>
public void Select(int start, int length) => _upDownEdit.Select(start, length);
/// <summary>
/// Create a new <see cref="MouseEventArgs"/> with the points translated from the <paramref name="child"/>
/// coordinates to this control's.
/// </summary>
private MouseEventArgs TranslateMouseEvent(Control child, MouseEventArgs e)
{
if (child is not null && IsHandleCreated)
{
// Same control as PointToClient or PointToScreen, just
// with two specific controls in mind.
Point point = e.Location;
point = WindowsFormsUtils.TranslatePoint(point, child, this);
return new MouseEventArgs(e.Button, e.Clicks, point.X, point.Y, e.Delta);
}
return e;
}
/// <summary>
/// When overridden in a derived class, handles the pressing of the up button on the up-down control.
/// </summary>
public abstract void UpButton();
/// <summary>
/// When overridden in a derived class, updates the text displayed in the up-down control.
/// </summary>
protected abstract void UpdateEditText();
private void UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs pref)
{
if (pref.Category == UserPreferenceCategory.Locale)
{
UpdateEditText();
}
}
/// <summary>
/// When overridden in a derived class, validates the text displayed in the up-down control.
/// </summary>
protected virtual void ValidateEditText()
{
}
[EditorBrowsable(EditorBrowsableState.Advanced)]
protected override void WndProc(ref Message m)
{
switch (m.MsgInternal)
{
case PInvokeCore.WM_SETFOCUS:
if (!HostedInWin32DialogManager)
{
if (ActiveControl is null)
{
SetActiveControl(TextBox);
}
else
{
FocusActiveControlInternal();
}
}
else
{
if (TextBox.CanFocus)
{
PInvoke.SetFocus(TextBox);
}
base.WndProc(ref m);
}
break;
case PInvokeCore.WM_KILLFOCUS:
DefWndProc(ref m);
break;
default:
base.WndProc(ref m);
break;
}
}
internal override void SetToolTip(ToolTip toolTip)
{
if (toolTip is null)
{
return;
}
string? caption = toolTip.GetToolTip(this);
toolTip.SetToolTip(_upDownEdit, caption);
toolTip.SetToolTip(_upDownButtons, caption);
}
internal override void RemoveToolTip(ToolTip toolTip)
{
if (toolTip is null)
{
return;
}
string? caption = toolTip.GetToolTip(this);
toolTip.SetToolTip(_upDownEdit, caption);
toolTip.SetToolTip(_upDownButtons, caption);
}
}
|