diff --git a/Documents/Changelog/Changelog.md b/Documents/Changelog/Changelog.md index 6654f05a61..f6b5bf3cb5 100644 --- a/Documents/Changelog/Changelog.md +++ b/Documents/Changelog/Changelog.md @@ -3,6 +3,7 @@ ==== ## 2025-11-xx - Build 2511 (V10 - alpha) - November 2025 +* Resolved [#2461](https://github.com/Krypton-Suite/Standard-Toolkit/issues/2461), (feat) Add `KryptonCalcInput` edit+dropdown control. * Resolved [#2480](https://github.com/Krypton-Suite/Standard-Toolkit/issues/2480). `KryptonForm`'s 'InternalPanel' designer issues * Resolved [#2512](https://github.com/Krypton-Suite/Standard-Toolkit/issues/2512), Added borders with straight corners (`LinearBorder2`) and adjusted the colors in the `KryptonRibbon` in the `Microsoft365` themes. Adjusted the design of the `RibbonQATButton` * Implemented [#2503](https://github.com/Krypton-Suite/Standard-Toolkit/issues/2503), Add the ability to create zip files for binaries diff --git a/Scripts/make_calc_icon.py b/Scripts/make_calc_icon.py new file mode 100644 index 0000000000..1b02e6ef1f --- /dev/null +++ b/Scripts/make_calc_icon.py @@ -0,0 +1,160 @@ +# Python 3.10+ +# Creates a 16x16 24-bit BMP depicting a simple calculator. +# Default output: Source\Krypton Components\Krypton.Toolkit\Resources\KryptonCalcInput.bmp + +from __future__ import annotations + +from pathlib import Path +from typing import List, Tuple +import sys + +# Image size +WIDTH = 16 +HEIGHT = 16 + +# Colors (B, G, R) for BMP storage convenience +BGR = Tuple[int, int, int] + +MAGENTA: BGR = (255, 0, 255) # transparent key background for legacy use +GRAY: BGR = (200, 200, 200) # body +DARK: BGR = (30, 30, 30) # outline +DISPLAY: BGR = (170, 220, 180) # display area +BTN: BGR = (230, 230, 230) # buttons +BTN_EDGE: BGR = (60, 60, 60) # button outline +EQUALS: BGR = (200, 210, 250) # equals button + + +def clamp8(v: int) -> int: + if v < 0: + return 0 + if v > 255: + return 255 + return v + + +def new_canvas(color: BGR) -> List[List[BGR]]: + return [[color for _ in range(WIDTH)] for _ in range(HEIGHT)] + + +def set_px(img: List[List[BGR]], x: int, y: int, color: BGR) -> None: + if 0 <= x < WIDTH and 0 <= y < HEIGHT: + b, g, r = color + img[y][x] = (clamp8(b), clamp8(g), clamp8(r)) + + +def fill_rect(img: List[List[BGR]], x0: int, y0: int, x1: int, y1: int, color: BGR) -> None: + if x1 < x0: + x0, x1 = x1, x0 + if y1 < y0: + y0, y1 = y1, y0 + for y in range(max(0, y0), min(HEIGHT - 1, y1) + 1): + for x in range(max(0, x0), min(WIDTH - 1, x1) + 1): + set_px(img, x, y, color) + + +def draw_rect(img: List[List[BGR]], x0: int, y0: int, x1: int, y1: int, color: BGR) -> None: + if x1 < x0: + x0, x1 = x1, x0 + if y1 < y0: + y0, y1 = y1, y0 + for x in range(x0, x1 + 1): + set_px(img, x, y0, color) + set_px(img, x, y1, color) + for y in range(y0, y1 + 1): + set_px(img, x0, y, color) + set_px(img, x1, y, color) + + +def draw_calculator() -> List[List[BGR]]: + img = new_canvas(MAGENTA) + + # Body + fill_rect(img, 1, 1, 14, 14, GRAY) + draw_rect(img, 1, 1, 14, 14, DARK) + + # Display + fill_rect(img, 3, 3, 12, 6, DISPLAY) + draw_rect(img, 3, 3, 12, 6, DARK) + + # Buttons 2 rows of 3 + wide equals bottom + buttons = [ + (3, 7, 5, 9), (7, 7, 9, 9), (11, 7, 13, 9), + (3, 10, 5, 12), (7, 10, 9, 12), (11, 10, 13, 12), + ] + for (x0, y0, x1, y1) in buttons: + fill_rect(img, x0, y0, x1, y1, BTN) + draw_rect(img, x0, y0, x1, y1, BTN_EDGE) + + # Equals wide + fill_rect(img, 3, 13, 13, 14, EQUALS) + draw_rect(img, 3, 13, 13, 14, BTN_EDGE) + + return img + + +def to_24bit_bottom_up(img: List[List[BGR]]) -> bytes: + # Each row is WIDTH*3 bytes; for width=16 -> 48 bytes (already multiple of 4, so no padding). + out = bytearray() + for y in range(HEIGHT - 1, -1, -1): + for x in range(WIDTH): + b, g, r = img[y][x] + out.extend((b, g, r)) + return bytes(out) + + +def write_bmp_24(path: Path, img: List[List[BGR]]) -> None: + pixel_data = to_24bit_bottom_up(img) + file_header_size = 14 + info_header_size = 40 + off_bits = file_header_size + info_header_size + size_image = len(pixel_data) + file_size = off_bits + size_image + + # BITMAPFILEHEADER + bfType = b"BM" + bfSize = file_size.to_bytes(4, "little") + bfReserved1 = (0).to_bytes(2, "little") + bfReserved2 = (0).to_bytes(2, "little") + bfOffBits = off_bits.to_bytes(4, "little") + file_header = bfType + bfSize + bfReserved1 + bfReserved2 + bfOffBits + + # BITMAPINFOHEADER + biSize = info_header_size.to_bytes(4, "little") + biWidth = WIDTH.to_bytes(4, "little", signed=True) + biHeight = HEIGHT.to_bytes(4, "little", signed=True) + biPlanes = (1).to_bytes(2, "little") + biBitCount = (24).to_bytes(2, "little") + biCompression = (0).to_bytes(4, "little") # BI_RGB + biSizeImage = size_image.to_bytes(4, "little") + biXPelsPerMeter = (0).to_bytes(4, "little") + biYPelsPerMeter = (0).to_bytes(4, "little") + biClrUsed = (0).to_bytes(4, "little") + biClrImportant = (0).to_bytes(4, "little") + info_header = ( + biSize + biWidth + biHeight + biPlanes + biBitCount + biCompression + + biSizeImage + biXPelsPerMeter + biYPelsPerMeter + biClrUsed + biClrImportant + ) + + path.parent.mkdir(parents=True, exist_ok=True) + with path.open("wb") as f: + f.write(file_header) + f.write(info_header) + f.write(pixel_data) + + +def default_output_path() -> Path: + # Scripts/make_calc_icon.py -> repo root -> Source/.../Resources/KryptonCalcInput.bmp + repo_root = Path(__file__).resolve().parents[1] + return repo_root / "Source" / "Krypton Components" / "Krypton.Toolkit" / "Resources" / "KryptonCalcInput.bmp" + + +def main(argv: list[str]) -> int: + out_path = Path(argv[1]) if len(argv) > 1 else default_output_path() + img = draw_calculator() + write_bmp_24(out_path, img) + print(f"Wrote 16x16 BMP to {out_path}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv)) diff --git a/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCalcInput.cs b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCalcInput.cs new file mode 100644 index 0000000000..e29793531b --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit/Controls Toolkit/KryptonCalcInput.cs @@ -0,0 +1,1715 @@ +#region BSD License +/* + * + * New BSD 3-Clause License (https://github.com/Krypton-Suite/Standard-Toolkit/blob/master/LICENSE) + * Modifications by Peter Wagner (aka Wagnerp), Simon Coghlan (aka Smurf-IV), tobitege et al. 2025 - 2025. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit; + +/// +/// Provides a numeric input control with a calculator dropdown. +/// +[ToolboxItem(true)] +[ToolboxBitmap(typeof(KryptonCalcInput), "ToolboxBitmaps.KryptonCalcInput.bmp")] +[DefaultEvent(nameof(ValueChanged))] +[DefaultProperty(nameof(Value))] +[DefaultBindingProperty(nameof(Value))] +[Designer(typeof(KryptonCalcInputDesigner))] +[DesignerCategory(@"code")] +[Description(@"Represents a numeric input control with integrated calculator dropdown.")] +public class KryptonCalcInput : VisualControlBase, IContainedInputControl +{ + #region Instance Fields + private VisualPopupToolTip? _visualPopupToolTip; + private readonly ButtonSpecManagerLayout? _buttonManager; + private readonly ViewLayoutDocker _drawDockerInner; + private readonly ViewDrawDocker _drawDockerOuter; + private readonly ViewLayoutFill _layoutFill; + private readonly KryptonTextBox _textBox; + private readonly ViewDrawDropDownButton _dropDownGlyph; + private readonly ButtonController _buttonController; + private InputControlStyle _inputControlStyle; + private decimal _value; + private bool? _fixedActive; + private bool _alwaysActive; + private bool _mouseOver; + private bool _trackingMouseEnter; + private bool _allowDecimals; + private int _decimalPlaces; + private bool _trailingZeroes; + private bool _autoSize; + private bool _forcedLayout; + private bool _thousandsSeparator; + private Padding _contentPadding; + private int _dropDownWidth; + private VisualOrientation _popupSide; + #endregion + + #region Events + /// + /// Occurs when the value of the Value property changes. + /// + [Description(@"Occurs when the value of the Value property changes.")] + [Category(@"Action")] + public event EventHandler? ValueChanged; + + /// + /// Occurs when the value of the TextChanged property changes. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler? TextChanged; + + /// + /// Occurs when the mouse enters the control. + /// + [Description(@"Raises the TrackMouseEnter event in the wrapped control.")] + [Category(@"Mouse")] + [EditorBrowsable(EditorBrowsableState.Advanced)] + public event EventHandler? TrackMouseEnter; + + /// + /// Occurs when the mouse leaves the control. + /// + [Description(@"Raises the TrackMouseLeave event in the wrapped control.")] + [Category(@"Mouse")] + [EditorBrowsable(EditorBrowsableState.Advanced)] + public event EventHandler? TrackMouseLeave; + + /// + /// Occurs when the value of the BackColor property changes. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler? BackColorChanged; + + /// + /// Occurs when the value of the BackgroundImage property changes. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler? BackgroundImageChanged; + + /// + /// Occurs when the value of the BackgroundImageLayout property changes. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler? BackgroundImageLayoutChanged; + + /// + /// Occurs when the value of the ForeColor property changes. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler? ForeColorChanged; + + /// + /// Occurs when the value of the PaddingChanged property changes. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public new event EventHandler? PaddingChanged; + + /// + /// Occurs when any ButtonSpec in is clicked. + /// + [Category(@"Action")] + [Description(@"Raised when any ButtonSpec is clicked.")] + public event EventHandler? ButtonSpecClicked; + #endregion + + #region Identity + /// + /// Initialize a new instance of the KryptonCalcInput class. + /// + public KryptonCalcInput() + { + // Contains another control and needs marking as such for validation to work + SetStyle(ControlStyles.ContainerControl, true); + + // By default we are not multiline and so the height is fixed + SetStyle(ControlStyles.FixedHeight, true); + + // Cannot select this control, only the child TextBox + SetStyle(ControlStyles.Selectable, false); + + // Defaults + _inputControlStyle = InputControlStyle.Standalone; + _alwaysActive = true; + _decimalPlaces = 0; + _allowDecimals = false; + _trailingZeroes = true; + _autoSize = false; + _contentPadding = Padding.Empty; + _dropDownWidth = 0; + _popupSide = VisualOrientation.Bottom; + + // Create storage properties + ButtonSpecs = new CalcButtonSpecCollection(this); + + // Monitor ButtonSpec additions/removals so we can surface click events + ButtonSpecs.Inserted += OnButtonSpecInserted; + ButtonSpecs.Removed += OnButtonSpecRemoved; + + // Create the palette storage + StateCommon = new PaletteInputControlTripleRedirect(Redirector, PaletteBackStyle.InputControlStandalone, PaletteBorderStyle.InputControlStandalone, PaletteContentStyle.InputControlStandalone, NeedPaintDelegate); + StateDisabled = new PaletteInputControlTripleStates(StateCommon, NeedPaintDelegate); + StateNormal = new PaletteInputControlTripleStates(StateCommon, NeedPaintDelegate); + StateActive = new PaletteInputControlTripleStates(StateCommon, NeedPaintDelegate); + + // Create the internal textbox used for containing content + _textBox = new KryptonTextBox(); + _textBox.StateCommon.Border.Draw = InheritBool.False; + _textBox.StateCommon.Border.Width = 0; + _textBox.TextChanged += OnTextBoxTextChanged; + _textBox.GotFocus += OnTextBoxGotFocus; + _textBox.LostFocus += OnTextBoxLostFocus; + _textBox.KeyDown += OnTextBoxKeyDown; + _textBox.KeyUp += OnTextBoxKeyUp; + _textBox.KeyPress += OnTextBoxKeyPress; + _textBox.PreviewKeyDown += OnTextBoxPreviewKeyDown; + _textBox.Validating += OnTextBoxValidating; + _textBox.Validated += OnTextBoxValidated; + _textBox.MouseEnter += OnTextBoxMouseEnter; + _textBox.MouseLeave += OnTextBoxMouseLeave; + + // Create the element that fills the remainder space and remembers fill rectangle + _layoutFill = new ViewLayoutFill(_textBox) + { + DisplayPadding = new Padding(1, 1, 1, 0) + }; + + // Create inner view for placing inside the drawing docker + _drawDockerInner = new ViewLayoutDocker + { + { _layoutFill, ViewDockStyle.Fill } + }; + + // Add dropdown glyph holder (inserted after construction below) + + // Create view for the control border and background + _drawDockerOuter = new ViewDrawDocker(StateNormal.Back, StateNormal.Border) + { + { _drawDockerInner, ViewDockStyle.Fill } + }; + + // Create the view manager instance + ViewManager = new ViewManager(this, _drawDockerOuter); + + // Create button specification collection manager + _buttonManager = new ButtonSpecManagerLayout(this, Redirector, ButtonSpecs, null, + [_drawDockerInner], + [StateCommon], + [PaletteMetricInt.HeaderButtonEdgeInsetInputControl], + [PaletteMetricPadding.HeaderButtonPaddingInputControl], + CreateToolStripRenderer, + NeedPaintDelegate); + + // Create the manager for handling tooltips + ToolTipManager = new ToolTipManager(ToolTipValues); + ToolTipManager.ShowToolTip += OnShowToolTip; + ToolTipManager.CancelToolTip += OnCancelToolTip; + _buttonManager.ToolTipManager = ToolTipManager; + + // Create the dropdown glyph view (renderer draws an arrow or custom glyph) + _dropDownGlyph = new ViewDrawDropDownButton(StateCommon.Content) + { + Orientation = VisualOrientation.Top + }; + + // Set calculator icon for dropdown glyph + try + { + _dropDownGlyph.CustomGlyph = ResourceFiles.Generic.GenericKryptonImageResources.KryptonCalcInput; + } + catch + { + } + + // Constrain glyph size using a fixed-size decorator and center it + var glyphFixed = new ViewDecoratorFixedSize(_dropDownGlyph, new Size(20, 20)); + var glyphCenter = new ViewLayoutCenter(1); + glyphCenter.Add(glyphFixed); + + // Add the glyph to the right side of the inner docker + _drawDockerInner.Add(glyphCenter, ViewDockStyle.Right); + + // Create dropdown controller + _buttonController = new ButtonController(glyphCenter, NeedPaintDelegate) + { + BecomesFixed = true, + ClickOnDown = false + }; + + // Assign the controller to the view element + glyphCenter.MouseController = _buttonController; + glyphCenter.KeyController = _buttonController; + glyphCenter.SourceController = _buttonController; + + // Handle dropdown button click + _buttonController.Click += OnDropDownClick; + + // Add textbox to the controls collection + ((KryptonReadOnlyControls)Controls).AddInternal(_textBox); + + // Defaults for calculator value + _value = 0m; + UpdateTextBoxValue(); + } + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing) + { + // Remove any showing tooltip + OnCancelToolTip(this, EventArgs.Empty); + + // Tell the buttons class to cleanup resources + _buttonManager?.Destruct(); + } + + base.Dispose(disposing); + } + #endregion + + #region Type Definitions + /// + /// Collection for managing ButtonSpecAny instances. + /// + public class CalcButtonSpecCollection : ButtonSpecCollection + { + #region Identity + /// + /// Initialize a new instance of the CalcButtonSpecCollection class. + /// + /// Reference to owning object. + public CalcButtonSpecCollection(KryptonCalcInput owner) + : base(owner) + { + } + #endregion + } + #endregion + + #region Public + /// + /// Gets and sets the automatic resize of the control to fit contents. + /// + [Browsable(true)] + [DefaultValue(false)] + [Description("Autosizes the control based on the maximum value possible.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + public new bool AutoSize + { + get => _autoSize; + set + { + if (_autoSize != value) + { + _autoSize = value; + UpdateAutoSizing(); + } + } + } + + /// + /// Gets and sets the internal padding space. + /// + [Browsable(false)] + [Localizable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new Padding Padding + { + get => base.Padding; + set => base.Padding = value; + } + + /// + /// Gets or sets extra padding inside the text area. When empty, palette padding is used. + /// + [Category(@"Layout")] + [Description(@"Extra padding inside the text area. When empty, palette padding is used.")] + [DefaultValue(typeof(Padding), "0,0,0,0")] + public Padding ContentPadding + { + get => _contentPadding; + set + { + if (_contentPadding != value) + { + _contentPadding = value; + if (_contentPadding != Padding.Empty) + { + _layoutFill.DisplayPadding = _contentPadding; + } + PerformNeedPaint(true); + } + } + } + + /// + /// Gets or sets the text associated with this control. + /// + [Browsable(false)] + [Localizable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [Bindable(false)] + [AllowNull] + public override string Text + { + get => _textBox.Text; + set => _textBox.Text = value; + } + + /// + /// Gets or sets horizontal alignment of the text in the edit area. + /// + [Category(@"Appearance")] + [Description(@"Horizontal alignment of the text in the edit area.")] + [DefaultValue(HorizontalAlignment.Left)] + public HorizontalAlignment TextAlign + { + get => _textBox.TextAlign; + set + { + if (_textBox.TextAlign != value) + { + _textBox.TextAlign = value; + PerformNeedPaint(true); + } + } + } + + /// + /// Gets and sets the associated context menu strip. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + public override ContextMenuStrip? ContextMenuStrip + { + get => base.ContextMenuStrip; + set + { + base.ContextMenuStrip = value; + _textBox.ContextMenuStrip = value; + } + } + + /// + /// Gets or sets the number of decimal places to display. + /// + [Category(@"Data")] + [Description(@"Indicates the number of decimal places to display.")] + [DefaultValue(0)] + [Browsable(true)] + public int DecimalPlaces + { + get => _decimalPlaces; + set + { + if (_decimalPlaces != value) + { + _decimalPlaces = value; + UpdateTextBoxValue(); + } + } + } + + /// + /// Gets or sets whether the control accepts decimal values. + /// + [Category(@"Behavior")] + [Description(@"Indicates whether the control can accept decimal values, rather than integer values only.")] + [DefaultValue(false)] + public bool AllowDecimals + { + get => _allowDecimals; + set + { + if (_allowDecimals != value) + { + _allowDecimals = value; + UpdateTextBoxValue(); + } + } + } + + /// + /// Gets or sets whether the control displays trailing zeroes. + /// + [Category(@"Behavior")] + [Description(@"Indicates whether the control will display trailing zeroes, when decimals are in play")] + [DefaultValue(true)] + public bool TrailingZeroes + { + get => _trailingZeroes; + set + { + if (_trailingZeroes != value) + { + _trailingZeroes = value; + UpdateTextBoxValue(); + } + } + } + + /// + /// Gets or sets whether the thousands separator will be inserted between each three decimal digits. + /// + [Category(@"Data")] + [Description(@"Indicates whether the thousands separator will be inserted between each three decimal digits.")] + [DefaultValue(false)] + [Localizable(true)] + public bool ThousandsSeparator + { + get => _thousandsSeparator; + set + { + if (_thousandsSeparator != value) + { + _thousandsSeparator = value; + UpdateTextBoxValue(); + } + } + } + + /// + /// Gets or sets the default width of the calculator dropdown. At runtime a sensible minimum is enforced. + /// + [Category(@"Layout")] + [Description(@"Default width of the calculator popup. A sensible minimum is enforced at runtime.")] + [DefaultValue(0)] + [Localizable(true)] + public int DropDownWidth + { + get => _dropDownWidth; + set + { + if (_dropDownWidth != value) + { + _dropDownWidth = value; + } + } + } + + /// + /// Gets or sets the side of the control where the calculator popup appears. + /// + [Category(@"Visuals")] + [Description(@"Side of the control where the calculator popup appears (Top/Bottom/Left/Right).")] + [DefaultValue(VisualOrientation.Bottom)] + public VisualOrientation PopupSide + { + get => _popupSide; + set + { + if (_popupSide != value) + { + _popupSide = value; + PerformNeedPaint(true); + } + } + } + + /// + /// Gets or sets the current value of the numeric up-down control. + /// + [Category(@"Appearance")] + [Description(@"The current value of the numeric up-down control.")] + [DefaultValue(0.0d)] + [Bindable(true)] + public decimal Value + { + get => _value; + set + { + if (_value != value) + { + _value = value; + UpdateTextBoxValue(); + OnValueChanged(EventArgs.Empty); + PerformNeedPaint(true); + } + } + } + + /// + /// Gets and sets Determines if the control is always active or only when the mouse is over the control or has focus. + /// + [Category(@"Visuals")] + [Description(@"Determines if the control is always active or only when the mouse is over the control or has focus.")] + [DefaultValue(true)] + public bool AlwaysActive + { + get => _alwaysActive; + set + { + if (_alwaysActive != value) + { + _alwaysActive = value; + PerformNeedPaint(true); + } + } + } + + /// + /// Gets and sets the input control style. + /// + [Category(@"Visuals")] + [Description(@"Input control style.")] + public InputControlStyle InputControlStyle + { + get => _inputControlStyle; + set + { + if (_inputControlStyle != value) + { + _inputControlStyle = value; + StateCommon.SetStyles(value); + PerformNeedPaint(true); + } + } + } + + private void ResetInputControlStyle() => InputControlStyle = InputControlStyle.Standalone; + + private bool ShouldSerializeInputControlStyle() => InputControlStyle != InputControlStyle.Standalone; + + /// + /// Gets and sets a value indicating if tooltips should be displayed for button specs. + /// + [Category(@"Visuals")] + [Description(@"Should tooltips be displayed for button specs.")] + [DefaultValue(false)] + public bool AllowButtonSpecToolTips { get; set; } + + /// + /// Gets and sets a value indicating if button spec tooltips should remove the parent tooltip. + /// + [Category(@"Visuals")] + [Description(@"Should button spec tooltips should remove the parent tooltip")] + [DefaultValue(false)] + public bool AllowButtonSpecToolTipPriority { get; set; } + + /// + /// Gets the collection of button specifications. + /// + [Category(@"Visuals")] + [Description(@"Collection of button specifications.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public CalcButtonSpecCollection ButtonSpecs { get; } + + /// + /// Gets access to the common textbox appearance entries that other states can override. + /// + [Category(@"Visuals")] + [Description(@"Overrides for defining common textbox appearance that other states can override.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public PaletteInputControlTripleRedirect StateCommon { get; } + + private bool ShouldSerializeStateCommon() => !StateCommon.IsDefault; + + /// + /// Gets access to the disabled textbox appearance entries. + /// + [Category(@"Visuals")] + [Description(@"Overrides for defining disabled textbox appearance.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public PaletteInputControlTripleStates StateDisabled { get; } + + private bool ShouldSerializeStateDisabled() => !StateDisabled.IsDefault; + + /// + /// Gets access to the normal textbox appearance entries. + /// + [Category(@"Visuals")] + [Description(@"Overrides for defining normal textbox appearance.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public PaletteInputControlTripleStates StateNormal { get; } + + private bool ShouldSerializeStateNormal() => !StateNormal.IsDefault; + + /// + /// Gets access to the active textbox appearance entries. + /// + [Category(@"Visuals")] + [Description(@"Overrides for defining active textbox appearance.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public PaletteInputControlTripleStates StateActive { get; } + + private bool ShouldSerializeStateActive() => !StateActive.IsDefault; + + /// + /// Gets access to the contained input control. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [EditorBrowsable(EditorBrowsableState.Always)] + [Browsable(false)] + public Control ContainedControl => _textBox; + + /// + /// Gets a value indicating whether the control has input focus. + /// + [Browsable(false)] + public override bool Focused => _textBox.Focused; + + /// + /// Selects a range of text in the control. + /// + /// The position of the first character in the current text selection within the text box. + /// The number of characters to select. + public void Select(int start, int length) => _textBox?.Select(start, length); + + /// + /// Sets the fixed state of the control. + /// + /// Should the control be fixed as active. + public void SetFixedState(bool active) => _fixedActive = active; + + /// + /// Gets access to the ToolTipManager used for displaying tool tips. + /// + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ToolTipManager ToolTipManager { get; } + + /// + /// Gets a value indicating if the input control is active. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsActive => + _fixedActive ?? DesignMode || AlwaysActive || + ContainsFocus || _mouseOver; + + /// + /// Sets input focus to the control. + /// + /// true if the input focus request was successful; otherwise, false. + public new bool Focus() => _textBox != null && _textBox.Focus(); + + /// + /// Activates the control. + /// + public new void Select() => _textBox?.Select(); + + /// + /// Get the preferred size of the control based on a proposed size. + /// + /// Starting size proposed by the caller. + /// Calculated preferred size. + public override Size GetPreferredSize(Size proposedSize) + { + // Do we have a manager to ask for a preferred size? + if (ViewManager != null) + { + // Ask the view to perform a layout + Size retSize = ViewManager.GetPreferredSize(Renderer, proposedSize); + + // Apply the maximum sizing + if (MaximumSize.Width > 0) + { + retSize.Width = Math.Min(MaximumSize.Width, retSize.Width); + } + + if (MaximumSize.Height > 0) + { + retSize.Height = Math.Min(MaximumSize.Height, retSize.Height); + } + + // Apply the minimum sizing + if (MinimumSize.Width > 0) + { + retSize.Width = Math.Max(MinimumSize.Width, retSize.Width); + } + + if (MinimumSize.Height > 0) + { + retSize.Height = Math.Max(MinimumSize.Height, retSize.Height); + } + + return retSize; + } + + // Fall back on default control processing + return base.GetPreferredSize(proposedSize); + } + + /// + /// Gets or sets a value indicating whether an ampersand is included in the text of the control. + /// + [Category(@"Appearance")] + [Description(@"When true the first character after an ampersand will be used as a mnemonic.")] + [DefaultValue(true)] + public bool UseMnemonic + { + get => _buttonManager!.UseMnemonic; + + set + { + if (_buttonManager!.UseMnemonic != value) + { + _buttonManager.UseMnemonic = value; + PerformNeedPaint(true); + } + } + } + + /// + /// Gets and sets if the control is in the ribbon design mode. + /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [EditorBrowsable(EditorBrowsableState.Never)] + [Browsable(false)] + public bool InRibbonDesignMode { get; set; } + #endregion + + #region Protected + /// + /// Force the layout logic to size and position the controls. + /// + protected void ForceControlLayout() + { + if (!IsHandleCreated) + { + _forcedLayout = true; + OnLayout(new LayoutEventArgs(null, null)); + _forcedLayout = false; + } + } + + /// + /// Gets or sets a value indicating whether a value has been entered by the user. + /// + protected bool UserEdit { get; set; } + #endregion + + #region Protected Virtual + /// + /// Raises the ValueChanged event. + /// + /// An EventArgs containing the event data. + protected virtual void OnValueChanged(EventArgs e) => ValueChanged?.Invoke(this, e); + + /// + /// Raises the TrackMouseEnter event. + /// + /// An EventArgs containing the event data. + protected virtual void OnTrackMouseEnter(EventArgs e) => TrackMouseEnter?.Invoke(this, e); + + /// + /// Raises the TrackMouseLeave event. + /// + /// An EventArgs containing the event data. + protected virtual void OnTrackMouseLeave(EventArgs e) => TrackMouseLeave?.Invoke(this, e); + #endregion + + #region Protected Overrides + /// + /// Creates a new instance of the control collection for the KryptonTextBox. + /// + /// A new instance of Control.ControlCollection assigned to the control. + [EditorBrowsable(EditorBrowsableState.Advanced)] + protected override ControlCollection CreateControlsInstance() => new KryptonReadOnlyControls(this); + + /// + /// Raises the HandleCreated event. + /// + /// An EventArgs containing the event data. + protected override void OnHandleCreated(EventArgs e) + { + // Let base class do standard stuff + base.OnHandleCreated(e); + + // Ensure the child textbox reflects any explicitly set Font immediately + _textBox.Font = Font; + PerformNeedPaint(false); + + // We need a layout to occur before any painting + InvokeLayout(); + + // We need to recalculate the correct height + Height = PreferredHeight; + } + + /// + /// Raises the EnabledChanged event. + /// + /// An EventArgs that contains the event data. + protected override void OnEnabledChanged(EventArgs e) + { + // Change in enabled state requires a layout and repaint + UpdateStateAndPalettes(); + + // Update view elements + _drawDockerInner.Enabled = Enabled; + _drawDockerOuter.Enabled = Enabled; + + // Update state to reflect change in enabled state + _buttonManager?.RefreshButtons(); + + PerformNeedPaint(true); + + // Let base class fire standard event + base.OnEnabledChanged(e); + } + + /// + /// Raises the PaletteChanged event. + /// + /// An EventArgs containing the event data. + protected override void OnPaletteChanged(EventArgs e) + { + InvalidateChildren(); + // If no explicit font set, update textbox font from palette + var fontProp = System.ComponentModel.TypeDescriptor.GetProperties(this)[nameof(Font)]; + bool explicitFont = fontProp?.ShouldSerializeValue(this) == true; + if (!explicitFont) + { + IPaletteTriple triple = GetTripleState(); + Font? font = triple.PaletteContent!.GetContentShortTextFont(_drawDockerOuter.State); + if ((_textBox.Handle != IntPtr.Zero) && font != null && !_textBox.Font.Equals(font)) + { + _textBox.Font = font; + } + } + base.OnPaletteChanged(e); + } + + /// + /// Processes a notification from palette of a paint and optional layout required. + /// + /// Source of notification. + /// An NeedLayoutEventArgs containing event data. + protected override void OnPaletteNeedPaint(object? sender, NeedLayoutEventArgs e) + { + InvalidateChildren(); + base.OnPaletteChanged(e); + } + + /// + /// Raises the BackColorChanged event. + /// + /// An EventArgs that contains the event data. + protected override void OnBackColorChanged(EventArgs e) => BackColorChanged?.Invoke(this, e); + + /// + /// Raises the BackgroundImageChanged event. + /// + /// An EventArgs that contains the event data. + protected override void OnBackgroundImageChanged(EventArgs e) => BackgroundImageChanged?.Invoke(this, e); + + /// + /// Raises the BackgroundImageLayoutChanged event. + /// + /// An EventArgs that contains the event data. + protected override void OnBackgroundImageLayoutChanged(EventArgs e) => BackgroundImageLayoutChanged?.Invoke(this, e); + + /// + /// Raises the ForeColorChanged event. + /// + /// An EventArgs that contains the event data. + protected override void OnForeColorChanged(EventArgs e) => ForeColorChanged?.Invoke(this, e); + + /// + /// Raises the Resize event. + /// + /// An EventArgs that contains the event data. + protected override void OnResize(EventArgs e) + { + // Let base class raise events + base.OnResize(e); + + // We must have a layout calculation + ForceControlLayout(); + } + + /// + /// Raises the Layout event. + /// + /// An EventArgs that contains the event data. + protected override void OnLayout(LayoutEventArgs levent) + { + if (!IsDisposed && !Disposing) + { + // Update to match the new palette settings + Height = PreferredHeight; + + // Apply current content padding visually by adjusting our fill padding + if (_contentPadding != Padding.Empty) + { + _layoutFill.DisplayPadding = _contentPadding; + } + + // Let base class calculate fill rectangle + base.OnLayout(levent); + + // Only use layout logic if control is fully initialized or if being forced + // to allow a relayout or if in design mode. + if (IsHandleCreated || _forcedLayout || (DesignMode && (_textBox != null))) + { + Rectangle fillRect = _layoutFill.FillRect; + _textBox?.SetBounds(fillRect.X, fillRect.Y, fillRect.Width, fillRect.Height); + + // In the designer, ensure the control Width reflects the full visual width + // (not just the inner textbox width), so the property grid reports the true size. + if (DesignMode && ViewManager != null) + { + Size pref = ViewManager.GetPreferredSize(Renderer, new Size(int.MaxValue, int.MaxValue)); + if (Width < pref.Width) + { + Width = pref.Width; + } + } + } + } + } + + /// + /// Raises the MouseEnter event. + /// + /// An EventArgs that contains the event data. + protected override void OnMouseEnter(EventArgs e) + { + _mouseOver = true; + PerformNeedPaint(true); + InvalidateChildren(); + base.OnMouseEnter(e); + } + + /// + /// Raises the MouseLeave event. + /// + /// An EventArgs that contains the event data. + protected override void OnMouseLeave(EventArgs e) + { + _mouseOver = false; + PerformNeedPaint(true); + InvalidateChildren(); + base.OnMouseLeave(e); + } + + /// + /// Raises the GotFocus event. + /// + /// An EventArgs that contains the event data. + protected override void OnGotFocus(EventArgs e) + { + base.OnGotFocus(e); + _textBox?.Focus(); + } + + /// + /// Performs the work of setting the specified bounds of this control. + /// + /// The new Left property value of the control. + /// The new Top property value of the control. + /// The new Width property value of the control. + /// The new Height property value of the control. + /// A bitwise combination of the BoundsSpecified values. + protected override void SetBoundsCore(int x, int y, + int width, int height, + BoundsSpecified specified) + { + // Get the preferred size of the entire control + Size preferredSize = GetPreferredSize(new Size(int.MaxValue, int.MaxValue)); + + // If setting the actual height + if (specified.HasFlag(BoundsSpecified.Height)) + { + // Override the actual height used + height = preferredSize.Height; + } + + base.SetBoundsCore(x, y, width, height, specified); + } + + /// + /// Gets the default size of the control. + /// + protected override Size DefaultSize => new Size(120, PreferredHeight); + + /// + /// Processes a notification from palette storage of a paint and optional layout required. + /// + /// Source of notification. + /// An NeedLayoutEventArgs containing event data. + protected override void OnNeedPaint(object? sender, NeedLayoutEventArgs e) + { + if (IsHandleCreated && !e.NeedLayout) + { + InvalidateChildren(); + } + else + { + ForceControlLayout(); + } + + if (!IsDisposed && !Disposing) + { + // Update the back/fore/font from the palette settings + UpdateStateAndPalettes(); + IPaletteTriple triple = GetTripleState(); + _textBox.BackColor = triple.PaletteBack.GetBackColor1(_drawDockerOuter.State); + _textBox.ForeColor = triple.PaletteContent!.GetContentShortTextColor1(_drawDockerOuter.State); + + // Apply font: honor an explicit Font override set on this control; otherwise use palette font + var fontProp = System.ComponentModel.TypeDescriptor.GetProperties(this)[nameof(Font)]; + bool explicitFont = fontProp?.ShouldSerializeValue(this) == true; + Font? desiredFont = explicitFont + ? Font + : triple.PaletteContent.GetContentShortTextFont(_drawDockerOuter.State); + if ((_textBox.Handle != IntPtr.Zero) && desiredFont != null && !_textBox.Font.Equals(desiredFont)) + { + _textBox.Font = desiredFont; + } + + // Alignment mapping from palette + var hAlign = triple.PaletteContent.GetContentShortTextH(_drawDockerOuter.State); + _textBox.TextAlign = hAlign switch + { + PaletteRelativeAlign.Center => HorizontalAlignment.Center, + PaletteRelativeAlign.Far => HorizontalAlignment.Right, + _ => HorizontalAlignment.Left + }; + } + + base.OnNeedPaint(sender, e); + } + + /// + /// Raises the PaddingChanged event. + /// + /// An NeedLayoutEventArgs containing event data. + protected override void OnPaddingChanged(EventArgs e) => PaddingChanged?.Invoke(this, e); + + /// + /// Raises the TabStop event. + /// + /// An EventArgs that contains the event data. + protected override void OnTabStopChanged(EventArgs e) + { + _textBox.TabStop = TabStop; + base.OnTabStopChanged(e); + } + + /// + /// Raises the CausesValidationChanged event. + /// + /// An EventArgs that contains the event data. + protected override void OnCausesValidationChanged(EventArgs e) + { + _textBox.CausesValidation = CausesValidation; + base.OnCausesValidationChanged(e); + } + + /// + /// Raises the TextChanged event. + /// + /// An NeedLayoutEventArgs containing event data. + protected override void OnTextChanged(EventArgs e) => TextChanged?.Invoke(this, e); + + /// + /// Process Windows-based messages. + /// + /// A Windows-based message. + protected override void WndProc(ref Message m) + { + switch (m.Msg) + { + case PI.WM_.NCHITTEST: + // Treat clicks as client area to avoid click-through on transparent pixels + base.WndProc(ref m); + m.Result = (IntPtr)PI.HT.CLIENT; + return; + default: + base.WndProc(ref m); + return; + } + } + #endregion + + #region Internal + internal bool InTransparentDesignMode => InRibbonDesignMode; + + internal bool IsFixedActive => _fixedActive != null; + #endregion + + #region Implementation + private void UpdateAutoSizing() + { + if (AutoSize) + { + var graphics = Graphics.FromHwnd(Handle); + var newWidth = (int)Math.Ceiling(graphics.MeasureString(Value.ToString(), Font).Width); + newWidth += 60; // Add space for dropdown button and padding + newWidth = Math.Max(newWidth, MinimumSize.Width); + newWidth = Math.Min(newWidth, MaximumSize.Width); + + if (newWidth > 0) + { + Width = newWidth; + PerformNeedPaint(true); + } + } + } + + private void InvalidateChildren() + { + if (_textBox != null) + { + _textBox.Invalidate(); + + if (!IsDisposed && !Disposing) + { + PI.RedrawWindow(Handle, IntPtr.Zero, IntPtr.Zero, 0x85); + } + } + } + + private void UpdateStateAndPalettes() + { + // Get the correct palette settings to use + IPaletteTriple tripleState = GetTripleState(); + _drawDockerOuter.SetPalettes(tripleState.PaletteBack, tripleState.PaletteBorder!); + + // Update enabled state + _drawDockerOuter.Enabled = Enabled; + + // Find the new state of the main view element + PaletteState state = Enabled ? (IsActive ? PaletteState.Tracking : PaletteState.Normal) : PaletteState.Disabled; + + _drawDockerOuter.ElementState = state; + } + + internal PaletteInputControlTripleStates GetTripleState() => Enabled ? (IsActive ? StateActive : StateNormal) : StateDisabled; + + private int PreferredHeight + { + get + { + // Get the preferred size of the entire control + Size preferredSize = GetPreferredSize(new Size(int.MaxValue, int.MaxValue)); + + // We only need the height + return preferredSize.Height; + } + } + + private void UpdateTextBoxValue() + { + if (_textBox != null) + { + var numberFormat = (System.Globalization.NumberFormatInfo)System.Globalization.CultureInfo.CurrentCulture.NumberFormat.Clone(); + + if (ThousandsSeparator) + { + // Respect current culture separators and digits + numberFormat.NumberDecimalDigits = _decimalPlaces; + } + + string formatString = AllowDecimals + ? $"F{_decimalPlaces.ToString(System.Globalization.CultureInfo.InvariantCulture)}" + : "F0"; + + if (TrailingZeroes || !AllowDecimals) + { + // Simple culture-aware format when zero trimming is not requested + _textBox.Text = _value.ToString(formatString, System.Globalization.CultureInfo.CurrentCulture); + } + else + { + // Mirror KryptonNumericUpDown: compute a culture formatted string with requested places, + // and an invariant trimmed string; remove the delta to keep grouping while trimming zeros. + var culture = System.Globalization.CultureInfo.CurrentCulture; + string textAsRequested = _value.ToString(formatString, culture); + string textTrimmedInvariant = _value.ToString(@"0.#########################", + System.Globalization.CultureInfo.InvariantCulture); + int lengthToRemove = textAsRequested.Length - textTrimmedInvariant.Length; + if (lengthToRemove > 0 && lengthToRemove <= textAsRequested.Length) + { + _textBox.Text = textAsRequested.Substring(0, textAsRequested.Length - lengthToRemove); + } + else + { + _textBox.Text = textAsRequested; + } + } + } + } + + private void OnTextBoxTextChanged(object? sender, EventArgs e) + { + if (_textBox != null && decimal.TryParse(_textBox.Text, out decimal newValue)) + { + if (_value != newValue) + { + _value = newValue; + OnValueChanged(EventArgs.Empty); + } + } + OnTextChanged(e); + } + + private void OnTextBoxGotFocus(object? sender, EventArgs e) + { + UpdateStateAndPalettes(); + PerformNeedPaint(true); + InvalidateChildren(); + base.OnGotFocus(e); + } + + private void OnTextBoxLostFocus(object? sender, EventArgs e) + { + UpdateStateAndPalettes(); + PerformNeedPaint(true); + InvalidateChildren(); + base.OnLostFocus(e); + } + + private void OnTextBoxKeyDown(object? sender, KeyEventArgs e) => OnKeyDown(e); + + private void OnTextBoxKeyUp(object? sender, KeyEventArgs e) => OnKeyUp(e); + + private void OnTextBoxKeyPress(object? sender, KeyPressEventArgs e) + { + // Handle numeric input validation + if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar)) + { + if (AllowDecimals && (e.KeyChar == '.' || e.KeyChar == ',')) + { + // Allow decimal separator only if not already present + if (_textBox.Text.Contains('.') || _textBox.Text.Contains(',')) + { + e.Handled = true; + } + } + else if (e.KeyChar == '-') + { + // Allow negative sign only at the beginning + if (_textBox.SelectionStart != 0 || _textBox.Text.Contains('-')) + { + e.Handled = true; + } + } + else + { + e.Handled = true; + } + } + + if (!e.Handled) + { + OnKeyPress(e); + } + } + + private void OnTextBoxPreviewKeyDown(object? sender, PreviewKeyDownEventArgs e) => OnPreviewKeyDown(e); + + private void OnTextBoxValidating(object? sender, CancelEventArgs e) => OnValidating(e); + + private void OnTextBoxValidated(object? sender, EventArgs e) => OnValidated(e); + + private void OnTextBoxMouseEnter(object? sender, EventArgs e) + { + if (!_trackingMouseEnter) + { + _trackingMouseEnter = true; + InvalidateChildren(); + OnTrackMouseEnter(EventArgs.Empty); + OnMouseEnter(e); + } + } + + private void OnTextBoxMouseLeave(object? sender, EventArgs e) + { + if (_trackingMouseEnter) + { + _trackingMouseEnter = false; + InvalidateChildren(); + OnTrackMouseLeave(EventArgs.Empty); + OnMouseLeave(e); + } + } + + private void OnDropDownClick(object? sender, EventArgs e) + { + ShowCalculator(); + } + + private void ShowCalculator() + { + // Ensure owner form is active and focus is correct + var ownerForm = FindForm(); + ownerForm?.Activate(); + Focus(); + _textBox.Focus(); + + // Create a new KryptonContextMenu each time + Rectangle dropScreenRect = RectangleToScreen(ClientRectangle); + var kcm = new CalcContextMenu(dropScreenRect); + + // Honor palette settings + if (PaletteMode != PaletteMode.Custom) + { + kcm.PaletteMode = PaletteMode; + } + else + { + kcm.LocalCustomPalette = LocalCustomPalette; + } + + // Preview row using a disabled menu item (ensures reliable repaint on item clicks) + var culture = System.Globalization.CultureInfo.CurrentCulture; + string expr = Value.ToString(culture); + bool selectAllPrimed = true; + var displayItem = new KryptonContextMenuItem(expr) + { + AutoClose = false, + Enabled = false + }; + displayItem.StateDisabled.ItemTextStandard.ShortText.Font = Font; + var palette = KryptonManager.CurrentGlobalPalette; + if (palette != null) + { + // Use a palette-derived text color that contrasts with context menu surfaces + var c = palette.GetContentShortTextColor1(PaletteContentStyle.ContextMenuItemTextStandard, PaletteState.Normal); + if (c != GlobalStaticValues.EMPTY_COLOR && !c.IsEmpty) + { + displayItem.StateDisabled.ItemTextStandard.ShortText.Color1 = c; + displayItem.StateDisabled.ItemTextStandard.ShortText.Color2 = c; + } + } + kcm.Items.Add(displayItem); + + // Build a 5x4 grid as a single items block with 4 columns + var items = new KryptonContextMenuItems + { + ImageColumn = false + }; + + // Column definitions (top to bottom) + string[][] columns = + [ + ["7","4","1","0","<-"] , + ["8","5","2",".","(" ] , + ["9","6","3","C",")" ] , + ["/","*","-","+","=" ] + ]; + + for (int c = 0; c < columns.Length; c++) + { + if (c > 0) + { + items.Items.Add(new KryptonContextMenuSeparator { Horizontal = false }); + } + + for (int r = 0; r < columns[c].Length; r++) + { + string token = columns[c][r]; + var mi = new KryptonContextMenuItem(token, (_, __) => + { + switch (token) + { + case "C": + expr = string.Empty; + selectAllPrimed = false; + displayItem.Text = expr; + break; + case "<-": + if (!string.IsNullOrEmpty(expr)) + { + expr = expr.Substring(0, expr.Length - 1); + } + selectAllPrimed = false; + displayItem.Text = expr; + break; + case "=": + { + try + { + var dt = new System.Data.DataTable(); + string normalized = NormalizeExpressionForCompute(expr, culture); + object? v = dt.Compute(normalized, null); + if (v != null) + { + decimal dec = Convert.ToDecimal(v, System.Globalization.CultureInfo.InvariantCulture); + Value = dec; + } + } + catch { } + break; + } + default: + { + if (selectAllPrimed) + { + if (IsDigitToken(token)) + { + expr = token; + } + else if (IsOperatorToken(token)) + { + expr += token; + } + else + { + expr = token; + } + selectAllPrimed = false; + } + else + { + expr += token; + } + displayItem.Text = expr; + break; + } + } + }); + // Keep the menu open for all inputs except '=' which should compute and close + mi.AutoClose = token == "="; + items.Items.Add(mi); + } + } + + kcm.Items.Add(items); + + // Show relative to control with side preference and 1px gap like DTP + Rectangle screenRect = RectangleToScreen(ClientRectangle); + var posH = KryptonContextMenuPositionH.Left; + var posV = KryptonContextMenuPositionV.Below; + switch (_popupSide) + { + case VisualOrientation.Top: + posV = KryptonContextMenuPositionV.Above; + break; + case VisualOrientation.Left: + posH = KryptonContextMenuPositionH.Before; + break; + case VisualOrientation.Right: + posH = KryptonContextMenuPositionH.After; + break; + } + + if (posV == KryptonContextMenuPositionV.Above) + { + screenRect.Y -= 1; + } + else if (posV == KryptonContextMenuPositionV.Below) + { + screenRect.Height += 1; + } + + if (posH == KryptonContextMenuPositionH.Before) + { + screenRect.X -= 1; + } + else if (posH == KryptonContextMenuPositionH.After) + { + screenRect.Width += 1; + } + + // Respect DropDownWidth as a minimum width + if (_dropDownWidth > 0) + { + // KryptonContextMenu measures itself; we nudge via an invisible padding item if needed + // Add a spacer heading with non-breaking spaces to reach desired width + int target = _dropDownWidth; + using (var g = CreateGraphics()) + { + string pad = "\u00A0\u00A0\u00A0\u00A0"; + while (TextRenderer.MeasureText(g, displayItem.Text + pad, Font).Width < target) + { + pad += "\u00A0\u00A0"; + } + // Temporarily extend heading to influence width + displayItem.Text = expr + pad; + } + } + + kcm.Closed += OnKryptonCalcContextMenuClosed; + kcm.Show(this, screenRect, posH, posV); + } + + private void OnShowToolTip(object? sender, ToolTipEventArgs e) + { + if (!IsDisposed && !Disposing) + { + // Do not show tooltips when the form we are in does not have focus + Form? topForm = FindForm(); + if (topForm is { ContainsFocus: false }) + { + return; + } + + // Never show tooltips at design time + if (!DesignMode) + { + IContentValues? sourceContent = null; + var toolTipStyle = LabelStyle.ToolTip; + + var shadow = true; + + // Find the button spec associated with the tooltip request + ButtonSpec? buttonSpec = _buttonManager?.ButtonSpecFromView(e.Target); + + // If the tooltip is for a button spec + if (buttonSpec != null) + { + // Are we allowed to show page related tooltips + if (AllowButtonSpecToolTips) + { + // Create a helper object to provide tooltip values + var buttonSpecMapping = new ButtonSpecToContent(Redirector, buttonSpec); + + // Is there actually anything to show for the tooltip + if (buttonSpecMapping.HasContent) + { + sourceContent = buttonSpecMapping; + toolTipStyle = buttonSpec.ToolTipStyle; + shadow = buttonSpec.ToolTipShadow; + } + } + } + + if (sourceContent != null) + { + // Remove any currently showing tooltip + _visualPopupToolTip?.Dispose(); + + // Create the actual tooltip popup object + _visualPopupToolTip = new VisualPopupToolTip(Redirector, + sourceContent, + Renderer, + PaletteBackStyle.ControlToolTip, + PaletteBorderStyle.ControlToolTip, + CommonHelper.ContentStyleFromLabelStyle(toolTipStyle), + shadow); + + _visualPopupToolTip.Disposed += OnVisualPopupToolTipDisposed; + _visualPopupToolTip.ShowRelativeTo(e.Target, e.ControlMousePosition); + } + } + } + } + + private void OnCancelToolTip(object? sender, EventArgs e) => + // Remove any currently showing tooltip + _visualPopupToolTip?.Dispose(); + + private void OnVisualPopupToolTipDisposed(object? sender, EventArgs e) + { + // Unhook events from the specific instance that generated event + var popupToolTip = sender as VisualPopupToolTip ?? throw new ArgumentNullException(nameof(sender)); + popupToolTip.Disposed -= OnVisualPopupToolTipDisposed; + + // Not showing a popup page anymore + _visualPopupToolTip = null; + } + + private void OnKryptonCalcContextMenuClosed(object? sender, EventArgs e) + { + if (sender is KryptonContextMenu kcm) + { + kcm.Closed -= OnKryptonCalcContextMenuClosed; + _buttonController.RemoveFixed(); + kcm.Dispose(); + } + } + + private static string NormalizeExpressionForCompute(string expr, System.Globalization.CultureInfo culture) + { + var nf = culture.NumberFormat; + string decimalSep = nf.NumberDecimalSeparator; + string groupSep = nf.NumberGroupSeparator; + + string s = expr.Replace("\u00A0", string.Empty).Replace(" ", string.Empty); + + if (!string.IsNullOrEmpty(groupSep)) + { + s = s.Replace(groupSep, string.Empty); + } + + if (!string.IsNullOrEmpty(decimalSep) && decimalSep != ".") + { + s = s.Replace(decimalSep, "."); + } + + return s; + } + + private static bool IsDigitToken(string t) => t.Length == 1 && (char.IsDigit(t[0]) || t[0] == '.'); + + private static bool IsOperatorToken(string t) + { + if (t.Length != 1) + { + return false; + } + char ch = t[0]; + return ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '(' || ch == ')'; + } + #endregion + + [ToolboxItem(false)] + [DesignerCategory(@"code")] + private sealed class CalcContextMenu : KryptonContextMenu + { + private readonly Rectangle _dropScreenRect; + + public CalcContextMenu(Rectangle dropScreenRect) => _dropScreenRect = dropScreenRect; + + protected override VisualContextMenu CreateContextMenu(KryptonContextMenu kcm, + PaletteBase? palette, + PaletteMode paletteMode, + PaletteRedirect redirector, + PaletteRedirectContextMenu redirectorImages, + KryptonContextMenuCollection items, + bool enabled, + bool keyboardActivated) => + new VisualContextMenuDTP(kcm, palette, paletteMode, redirector, redirectorImages, items, enabled, keyboardActivated, _dropScreenRect); + } + + #region ButtonSpec wiring + private void OnButtonSpecInserted(object? sender, ButtonSpecEventArgs e) + { + if (e.ButtonSpec != null) + { + e.ButtonSpec.Click += OnAnyButtonSpecClick; + } + } + + private void OnButtonSpecRemoved(object? sender, ButtonSpecEventArgs e) + { + if (e.ButtonSpec != null) + { + e.ButtonSpec.Click -= OnAnyButtonSpecClick; + } + } + + private void OnAnyButtonSpecClick(object? sender, EventArgs e) + { + if (sender is ButtonSpec spec) + { + var index = ButtonSpecs.IndexOf(spec); + OnButtonSpecClicked(new ButtonSpecEventArgs(spec, index)); + } + } + + /// + /// Raises the ButtonSpecClicked event. + /// + /// Event args containing the clicked spec. + protected virtual void OnButtonSpecClicked(ButtonSpecEventArgs e) => ButtonSpecClicked?.Invoke(this, e); + #endregion +} \ No newline at end of file diff --git a/Source/Krypton Components/Krypton.Toolkit/Designers/Action Lists/KryptonCalcButtonActionList.cs b/Source/Krypton Components/Krypton.Toolkit/Designers/Action Lists/KryptonCalcButtonActionList.cs new file mode 100644 index 0000000000..7af1f94cc2 --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit/Designers/Action Lists/KryptonCalcButtonActionList.cs @@ -0,0 +1,170 @@ +#region BSD License +/* + * + * New BSD 3-Clause License (https://github.com/Krypton-Suite/Standard-Toolkit/blob/master/LICENSE) + * Modifications by Peter Wagner (aka Wagnerp), Simon Coghlan (aka Smurf-IV), tobitege 2025 - 2025. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit; + +internal class KryptonCalcInputActionList : DesignerActionList +{ + #region Instance Fields + private readonly KryptonCalcInput _calcButton; + private readonly IComponentChangeService? _service; + #endregion + + #region Identity + /// + /// Initialize a new instance of the KryptonCalcInputActionList class. + /// + /// Designer that owns this action list instance. + public KryptonCalcInputActionList(KryptonCalcInputDesigner owner) + : base(owner.Component) + { + _calcButton = (owner.Component as KryptonCalcInput)!; + _service = GetService(typeof(IComponentChangeService)) as IComponentChangeService; + } + #endregion + + #region Public + public InputControlStyle InputControlStyle + { + get => _calcButton.InputControlStyle; + + set + { + if (_calcButton.InputControlStyle != value) + { + _service?.OnComponentChanged(_calcButton, null, _calcButton.InputControlStyle, value); + _calcButton.InputControlStyle = value; + } + } + } + + public decimal Value + { + get => _calcButton.Value; + + set + { + if (_calcButton.Value != value) + { + _service?.OnComponentChanged(_calcButton, null, _calcButton.Value, value); + _calcButton.Value = value; + } + } + } + + public int DecimalPlaces + { + get => _calcButton.DecimalPlaces; + + set + { + if (_calcButton.DecimalPlaces != value) + { + _service?.OnComponentChanged(_calcButton, null, _calcButton.DecimalPlaces, value); + _calcButton.DecimalPlaces = value; + } + } + } + + public bool AllowDecimals + { + get => _calcButton.AllowDecimals; + + set + { + if (_calcButton.AllowDecimals != value) + { + _service?.OnComponentChanged(_calcButton, null, _calcButton.AllowDecimals, value); + _calcButton.AllowDecimals = value; + } + } + } + + public int DropDownWidth + { + get => _calcButton.DropDownWidth; + + set + { + if (_calcButton.DropDownWidth != value) + { + _service?.OnComponentChanged(_calcButton, null, _calcButton.DropDownWidth, value); + _calcButton.DropDownWidth = value; + } + } + } + + public VisualOrientation PopupSide + { + get => _calcButton.PopupSide; + + set + { + if (_calcButton.PopupSide != value) + { + _service?.OnComponentChanged(_calcButton, null, _calcButton.PopupSide, value); + _calcButton.PopupSide = value; + } + } + } + + public string Text + { + get => _calcButton.Text; + + set + { + if (_calcButton.Text != value) + { + _service?.OnComponentChanged(_calcButton, null, _calcButton.Text, value); + _calcButton.Text = value; + } + } + } + + public PaletteMode PaletteMode + { + get => _calcButton.PaletteMode; + + set + { + if (_calcButton.PaletteMode != value) + { + _service?.OnComponentChanged(_calcButton, null, _calcButton.PaletteMode, value); + _calcButton.PaletteMode = value; + } + } + } + #endregion + + #region Public Override + public override DesignerActionItemCollection GetSortedActionItems() + { + var actions = new DesignerActionItemCollection(); + + if (_calcButton != null) + { + actions.Add(new DesignerActionHeaderItem(nameof(Appearance))); + actions.Add(new DesignerActionPropertyItem(nameof(InputControlStyle), nameof(InputControlStyle), nameof(Appearance), @"Input control style")); + actions.Add(new DesignerActionPropertyItem(nameof(DecimalPlaces), nameof(DecimalPlaces), nameof(Appearance), @"Number of decimal places")); + actions.Add(new DesignerActionPropertyItem(nameof(AllowDecimals), nameof(AllowDecimals), nameof(Appearance), @"Allow decimal values")); + actions.Add(new DesignerActionHeaderItem(@"Layout")); + actions.Add(new DesignerActionPropertyItem(nameof(DropDownWidth), nameof(DropDownWidth), @"Layout", @"Default width of popup")); + actions.Add(new DesignerActionPropertyItem(nameof(PopupSide), nameof(PopupSide), @"Layout", @"Side where popup appears")); + actions.Add(new DesignerActionHeaderItem(@"Values")); + actions.Add(new DesignerActionPropertyItem(nameof(Text), nameof(Text), @"Values", @"Input text")); + actions.Add(new DesignerActionPropertyItem(nameof(Value), nameof(Value), @"Values", @"Numeric value")); + actions.Add(new DesignerActionHeaderItem(@"Visuals")); + actions.Add(new DesignerActionPropertyItem(nameof(PaletteMode), @"Palette", @"Visuals", @"Palette applied to drawing")); + } + + return actions; + } + #endregion +} diff --git a/Source/Krypton Components/Krypton.Toolkit/Designers/Designers/KryptonCalcButtonDesigner.cs b/Source/Krypton Components/Krypton.Toolkit/Designers/Designers/KryptonCalcButtonDesigner.cs new file mode 100644 index 0000000000..473738ef30 --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit/Designers/Designers/KryptonCalcButtonDesigner.cs @@ -0,0 +1,45 @@ +#region BSD License +/* + * + * New BSD 3-Clause License (https://github.com/Krypton-Suite/Standard-Toolkit/blob/master/LICENSE) + * Modifications by Peter Wagner (aka Wagnerp), Simon Coghlan (aka Smurf-IV), tobitege 2025 - 2025. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit; + +internal class KryptonCalcInputDesigner : ControlDesigner +{ + #region Identity + /// + /// Initialize a new instance of the KryptonCalcInputDesigner class. + /// + public KryptonCalcInputDesigner() => + // The resizing handles around the control need to change depending on the + // value of the AutoSize and AutoSizeMode properties. When in AutoSize you + // do not get the resizing handles, otherwise you do. + AutoResizeHandles = true; + + #endregion + + #region Public Overrides + /// + /// Gets the design-time action lists supported by the component associated with the designer. + /// + public override DesignerActionListCollection ActionLists + { + get + { + // Create a collection of action lists + var actionLists = new DesignerActionListCollection + { + // Add the calculator button specific list + new KryptonCalcInputActionList(this) + }; + + return actionLists; + } + } + #endregion +} diff --git a/Source/Krypton Components/Krypton.Toolkit/Krypton.Toolkit 2022.csproj b/Source/Krypton Components/Krypton.Toolkit/Krypton.Toolkit 2022.csproj index 70a40caf48..c64288fdaf 100644 --- a/Source/Krypton Components/Krypton.Toolkit/Krypton.Toolkit 2022.csproj +++ b/Source/Krypton Components/Krypton.Toolkit/Krypton.Toolkit 2022.csproj @@ -533,6 +533,7 @@ + diff --git a/Source/Krypton Components/Krypton.Toolkit/ResourceFiles/Generic/GenericKryptonImageResources.Designer.cs b/Source/Krypton Components/Krypton.Toolkit/ResourceFiles/Generic/GenericKryptonImageResources.Designer.cs index b56af09704..5d36d0dec3 100644 --- a/Source/Krypton Components/Krypton.Toolkit/ResourceFiles/Generic/GenericKryptonImageResources.Designer.cs +++ b/Source/Krypton Components/Krypton.Toolkit/ResourceFiles/Generic/GenericKryptonImageResources.Designer.cs @@ -110,6 +110,16 @@ internal static System.Drawing.Bitmap KryptonButtonSpec { } } + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap KryptonCalcInput { + get { + object obj = ResourceManager.GetObject("KryptonCalcInput", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + /// /// Looks up a localized resource of type System.Drawing.Bitmap. /// diff --git a/Source/Krypton Components/Krypton.Toolkit/ResourceFiles/Generic/GenericKryptonImageResources.resx b/Source/Krypton Components/Krypton.Toolkit/ResourceFiles/Generic/GenericKryptonImageResources.resx index 1969f73937..fc49884d02 100644 --- a/Source/Krypton Components/Krypton.Toolkit/ResourceFiles/Generic/GenericKryptonImageResources.resx +++ b/Source/Krypton Components/Krypton.Toolkit/ResourceFiles/Generic/GenericKryptonImageResources.resx @@ -187,6 +187,9 @@ ..\..\Resources\KryptonDropButton.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + ..\..\Resources\KryptonCalcInput.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + ..\..\Resources\KryptonForm.bmp;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a diff --git a/Source/Krypton Components/Krypton.Toolkit/Resources/KryptonCalcInput.bmp b/Source/Krypton Components/Krypton.Toolkit/Resources/KryptonCalcInput.bmp new file mode 100644 index 0000000000..2e2cba20a5 Binary files /dev/null and b/Source/Krypton Components/Krypton.Toolkit/Resources/KryptonCalcInput.bmp differ diff --git a/Source/Krypton Components/Krypton.Toolkit/ToolboxBitmaps/KryptonCalcInput.bmp b/Source/Krypton Components/Krypton.Toolkit/ToolboxBitmaps/KryptonCalcInput.bmp new file mode 100644 index 0000000000..2e2cba20a5 Binary files /dev/null and b/Source/Krypton Components/Krypton.Toolkit/ToolboxBitmaps/KryptonCalcInput.bmp differ diff --git a/Source/Krypton Components/Krypton.Toolkit/View Draw/ViewDrawDropDownButton.cs b/Source/Krypton Components/Krypton.Toolkit/View Draw/ViewDrawDropDownButton.cs index 999ec23f14..f39b9ab7e8 100644 --- a/Source/Krypton Components/Krypton.Toolkit/View Draw/ViewDrawDropDownButton.cs +++ b/Source/Krypton Components/Krypton.Toolkit/View Draw/ViewDrawDropDownButton.cs @@ -5,7 +5,7 @@ * © Component Factory Pty Ltd, 2006 - 2016, (Version 4.5.0.0) All rights reserved. * * New BSD 3-Clause License (https://github.com/Krypton-Suite/Standard-Toolkit/blob/master/LICENSE) - * Modifications by Peter Wagner (aka Wagnerp), Simon Coghlan (aka Smurf-IV), Giduac & Ahmed Abdelhameed et al. 2017 - 2025. All rights reserved. + * Modifications by Peter Wagner (aka Wagnerp), Simon Coghlan (aka Smurf-IV), Giduac, Ahmed Abdelhameed, tobitege et al. 2017 - 2025. All rights reserved. * */ #endregion @@ -17,6 +17,10 @@ namespace Krypton.Toolkit; /// public class ViewDrawDropDownButton : ViewLeaf { + #region Instance Fields + private Image? _customGlyph; + #endregion + #region Identity /// /// Initialize a new instance of the ViewDrawDropDownButton class. @@ -51,6 +55,16 @@ public override string ToString() => /// public VisualOrientation Orientation { get; set; } + /// + /// Gets or sets a custom image to render instead of the default arrow glyph. + /// When null, the renderer-drawn arrow is used. + /// + public Image? CustomGlyph + { + get => _customGlyph; + set => _customGlyph = value; + } + #endregion #region Layout @@ -72,6 +86,12 @@ public override Size GetPreferredSize([DisallowNull] ViewLayoutContext context) throw new ArgumentNullException(nameof(context.Renderer)); } + // If a custom glyph is provided, prefer its size + if (_customGlyph != null) + { + return _customGlyph.Size; + } + // Ask the renderer for the required size of the drop-down button return context.Renderer.RenderGlyph.GetDropDownButtonPreferredSize(context, Palette, State, Orientation); } @@ -99,6 +119,7 @@ public override void Layout([DisallowNull] ViewLayoutContext context) #region Paint /// /// Perform rendering before child elements are rendered. + /// Allows to render a custom glyph, if set. /// /// Rendering context. public override void RenderBefore( [DisallowNull] RenderContext context) @@ -108,11 +129,21 @@ public override void RenderBefore( [DisallowNull] RenderContext context) throw new ArgumentNullException(nameof(context.Renderer)); } - context.Renderer.RenderGlyph.DrawDropDownButton(context, - ClientRectangle, - Palette, - State, - Orientation); + if (_customGlyph != null) + { + Size gsz = _customGlyph.Size; + int x = ClientRectangle.X + (ClientRectangle.Width - gsz.Width) / 2; + int y = ClientRectangle.Y + (ClientRectangle.Height - gsz.Height) / 2; + context.Graphics.DrawImage(_customGlyph, new Rectangle(new Point(x, y), gsz)); + } + else + { + context.Renderer.RenderGlyph.DrawDropDownButton(context, + ClientRectangle, + Palette, + State, + Orientation); + } } #endregion diff --git a/Source/Krypton Components/TestForm/ButtonsTest.Designer.cs b/Source/Krypton Components/TestForm/ButtonsTest.Designer.cs index 1f998fdfd8..5196bc31f9 100644 --- a/Source/Krypton Components/TestForm/ButtonsTest.Designer.cs +++ b/Source/Krypton Components/TestForm/ButtonsTest.Designer.cs @@ -39,6 +39,8 @@ private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ButtonsTest)); this.kryptonPanel1 = new Krypton.Toolkit.KryptonPanel(); + this.KryptonCalcInput1 = new Krypton.Toolkit.KryptonCalcInput(); + this.buttonSpecAny2 = new Krypton.Toolkit.ButtonSpecAny(); this.kryptonLabel2 = new Krypton.Toolkit.KryptonLabel(); this.kcbColorScheme = new Krypton.Toolkit.KryptonComboBox(); this.kryptonLabel1 = new Krypton.Toolkit.KryptonLabel(); @@ -72,6 +74,7 @@ private void InitializeComponent() // // kryptonPanel1 // + this.kryptonPanel1.Controls.Add(this.KryptonCalcInput1); this.kryptonPanel1.Controls.Add(this.kryptonLabel2); this.kryptonPanel1.Controls.Add(this.kcbColorScheme); this.kryptonPanel1.Controls.Add(this.kryptonLabel1); @@ -91,9 +94,33 @@ private void InitializeComponent() this.kryptonPanel1.Dock = System.Windows.Forms.DockStyle.Fill; this.kryptonPanel1.Location = new System.Drawing.Point(0, 0); this.kryptonPanel1.Name = "kryptonPanel1"; - this.kryptonPanel1.Size = new System.Drawing.Size(559, 305); + this.kryptonPanel1.Size = new System.Drawing.Size(562, 318); this.kryptonPanel1.TabIndex = 0; // + // KryptonCalcInput1 + // + this.KryptonCalcInput1.AllowDecimals = true; + this.KryptonCalcInput1.ButtonSpecs.Add(this.buttonSpecAny2); + this.KryptonCalcInput1.DecimalPlaces = 2; + this.KryptonCalcInput1.DropDownWidth = 0; + this.KryptonCalcInput1.Font = new System.Drawing.Font("Microsoft Sans Serif", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.KryptonCalcInput1.Location = new System.Drawing.Point(120, 270); + this.KryptonCalcInput1.Name = "KryptonCalcInput1"; + this.KryptonCalcInput1.Size = new System.Drawing.Size(141, 24); + this.KryptonCalcInput1.TabIndex = 16; + this.KryptonCalcInput1.Value = new decimal(new int[] { + 0, + 0, + 0, + 0}); + this.KryptonCalcInput1.ButtonSpecClicked += new System.EventHandler(this.KryptonCalcInput1_ButtonSpecClicked); + // + // buttonSpecAny2 + // + this.buttonSpecAny2.Image = ((System.Drawing.Image)(resources.GetObject("buttonSpecAny2.Image"))); + this.buttonSpecAny2.Type = Krypton.Toolkit.PaletteButtonSpecStyle.Close; + this.buttonSpecAny2.UniqueName = "f71d42b1cecb4ded8a08ee132c851ffe"; + // // kryptonLabel2 // this.kryptonLabel2.Location = new System.Drawing.Point(18, 173); @@ -149,7 +176,7 @@ private void InitializeComponent() // this.kbtnButtonStyles.Location = new System.Drawing.Point(278, 270); this.kbtnButtonStyles.Name = "kbtnButtonStyles"; - this.kbtnButtonStyles.Size = new System.Drawing.Size(243, 25); + this.kbtnButtonStyles.Size = new System.Drawing.Size(243, 24); this.kbtnButtonStyles.TabIndex = 10; this.kbtnButtonStyles.Values.DropDownArrowColor = System.Drawing.Color.Empty; this.kbtnButtonStyles.Values.Text = "Button Styles"; @@ -332,7 +359,7 @@ private void InitializeComponent() this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ButtonSpecs.Add(this.buttonSpecAny1); - this.ClientSize = new System.Drawing.Size(559, 305); + this.ClientSize = new System.Drawing.Size(562, 318); this.Controls.Add(this.kryptonPanel1); this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.Fixed3D; this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); @@ -379,5 +406,7 @@ private void InitializeComponent() private Krypton.Toolkit.KryptonLabel kryptonLabel1; private Krypton.Toolkit.KryptonComboBox kcbColorScheme; private Krypton.Toolkit.KryptonLabel kryptonLabel2; + private KryptonCalcInput KryptonCalcInput1; + private ButtonSpecAny buttonSpecAny2; } } diff --git a/Source/Krypton Components/TestForm/ButtonsTest.cs b/Source/Krypton Components/TestForm/ButtonsTest.cs index 127c39b422..5d5dfcde23 100644 --- a/Source/Krypton Components/TestForm/ButtonsTest.cs +++ b/Source/Krypton Components/TestForm/ButtonsTest.cs @@ -64,4 +64,12 @@ private void kcbColorScheme_SelectedIndexChanged(object sender, EventArgs e) kcbtnDropDown.SchemeThemes = scheme; kcbSortMode.Enabled = scheme == Krypton.Toolkit.ColorScheme.PaletteColors; } + + private void KryptonCalcInput1_ButtonSpecClicked(object sender, ButtonSpecEventArgs e) + { + if (e.ButtonSpec is ButtonSpecAny any && any.Type == PaletteButtonSpecStyle.Close) + { + KryptonCalcInput1.Value = 0m; + } + } } \ No newline at end of file diff --git a/Source/Krypton Components/TestForm/ButtonsTest.resx b/Source/Krypton Components/TestForm/ButtonsTest.resx index 21eb1fe54c..f36669bdc6 100644 --- a/Source/Krypton Components/TestForm/ButtonsTest.resx +++ b/Source/Krypton Components/TestForm/ButtonsTest.resx @@ -118,6 +118,24 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL + DQAACw0B7QfALAAAAAd0SU1FB9gEDw0NJWkouy0AAAK4SURBVDhPdVNbSFRRFN3jYxwHxbcovt9+KOiH + KAgiZlg6VpqO3pEczBgbRdR8IBGk0EOs7z6DCOrDyNBSC7FmHEctfGQPK4iIKKuv7CMwwdW6M02g5IbD + 3Nlnr7XXWfsc8cRBkeoDIpuHRBr4V+PO7o4qkVqDyK9jIvV/U+4oE6lTdLqd6b4+NAcEoFrEzPQuErIq + LVrtzp2SErT4+aFOpNG1oXau1+t3ZgcGsDI0BEd/P6x6PdjiHwk/lHaCJsrLMV5aipHCQpz29UUt80LZ + Px62tWFlcBALPT1wdnTAbrGgS6eDQqwK7tZqMV1WhgfFxRjNy8NITg5upaaqBNuqfGMj5dusVji47E1N + cJrNmDMa0csuPQQ72NVG6Y/y8zGWnY3RlBT0enurDZpUhXKcXSwsfKIocJpMmK+pwbOqKhdwrqgI87m5 + mImLw0RwMO75++OcCE6KWAj1chGowTMr7ezoqKjAUmUlVnneJUp9GhXlWguRkbCT4IKXF6x7wZ5Qne70 + 8YGzoADLSUl4EROD9fh4rCckYCk8HMMEt+4HVuMECc5Q3nRgIF5R8ofkZHxOT8entDSskeCKRoPu/4zY + FarbHSyYJHg1NhbvCf6WkYGfNG0zKwsb/H5Nkqus6dtLQjeVVm7c5fznIiKwRoKPdPpNdDQ2EhOxRRO/ + Z2a6SJdDQnCJtV0eEl5L4ymRnZu8KJNBQZhll+ckWCXRRZ55mMVf6MNXKnhHX1boi411g8zTjAY5IrJ1 + jTO9zTGO8xo/DguDLTQUA27DzPTEoMp+SzUvSbTIicxw/z7VsvG2HBUxNdO46xzhKJNjJOl3j8pzTg3P + bLjMJotUZ+eaok9nSUqcSbVA1IfRwsQNqujkL6/XXqc1PLPhPImn2EAFU537MXmihmy8279p6H7PWUPJ + h7m2uNydReQPiLFB/uRDeL0AAAAASUVORK5CYII= + + iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAL