From 4f5383dccadc789ef643e9a98d144cd332ee5689 Mon Sep 17 00:00:00 2001 From: Peter Wagner Date: Fri, 3 Jul 2026 08:55:46 +0100 Subject: [PATCH] * Expand `KryptonCheckBox` to allow more text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # PR: `KryptonCheckBoxExtended` — word-wrapped checkbox with subtext (#3833) Closes [#3833](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3833) ## Summary Adds **`KryptonCheckBoxExtended`** to **`Krypton.Toolkit.Utilities`**: a checkbox control with Krypton styling that supports word-wrapped main text, optional secondary subtext, configurable spacing, optional clickable subtext links, and accessibility support. The base **`KryptonCheckBox`** is unchanged. This follows the same placement pattern as other extended controls (for example `KryptonCommandLinkButton`) and is modelled after the word-wrap / subtext behaviour of [dahall/groupcontrols `CheckBoxEx`](https://github.com/dahall/groupcontrols/blob/master/GroupControls/CheckBoxEx.cs). ## What's included ### New control: `KryptonCheckBoxExtended` - Word-wrapped main text (no ellipsis truncation at typical widths) - Optional subtext below the main text with separate font and colour - Top-aligned checkbox glyph (multi-line friendly) - Full parity with `KryptonCheckBox` for checked state, three-state, `KryptonCommand`, orientation, check position, palette overrides, and image values - Palette content padding for spacing between glyph and text (plus optional extra `TextGap`) ### Expandable designer property groups | Property | Contents | |----------|----------| | **`Values`** | Main text, subtext, subtext font/colour, images | | **`LayoutValues`** | `SubtextSeparatorHeight` (gap between text and subtext, default 5px), `TextGap` (extra glyph-to-text spacing) | | **`SubtextLinkValues`** | `LinkArea`, `LinkColor` for clickable subtext regions | Top-level **`Text`** and **`Subtext`** remain available for binding; layout and link settings are also forward-compatible via hidden designer-hidden properties. ### Subtext links - Set `SubtextLinkValues.LinkArea` to define a character range as a link - Handle **`SubtextLinkClicked`** without toggling the checkbox - Clicks on non-link subtext still toggle the checkbox - Hand cursor over link regions ### Accessibility - Custom accessible object: main text as name, subtext as description, check state and default action exposed to screen readers ### TestForm demo - **`CheckBoxExtendedDemo`** — agreement-style checkbox with wrapped text, subtext, linked phrase ("full agreement"), and comparison with a standard `KryptonCheckBox` at the same width - Entry points: **Start Screen** → "CheckBox Extended", and **Main** → "CheckBox Extended" ## Implementation notes - **`ViewDrawCheckBoxExtendedContent`** — custom view drawer; standard `ViewDrawContent` ellipsizes single-line text, so word wrap uses `TextRenderer` with `WordBreak` for measure and paint - **`SubtextLinkPresenter`** — internal `KryptonLinkWrapLabel` overlay for link hit-testing and rendering when `LinkArea` is set - **`ViewLayoutSeparator`** — implements configurable `TextGap` between glyph column and text - Projects auto-include new `.cs` files under `Krypton.Toolkit.Utilities`; no project file changes required ## Files changed **New** - `Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/` (control, values, view, designers, accessibility, link presenter) - `Source/Krypton Components/TestForm/CheckBoxExtendedDemo.cs` - `Source/Krypton Components/TestForm/CheckBoxExtendedDemo.Designer.cs` **Modified** - `Documents/Changelog/Changelog.md` — V110 entry for #3833 - `Source/Krypton Components/TestForm/Main.cs` / `Main.Designer.cs` — demo button - `Source/Krypton Components/TestForm/StartScreen.cs` — start screen entry ## Test plan - [ ] Build `Krypton.Toolkit.Utilities` and `TestForm` (Debug, at least one .NET Framework TFM e.g. `net48`) - [ ] Run **TestForm** → **CheckBox Extended** - [ ] Confirm main text wraps when the form is narrowed; subtext remains visible below - [ ] Confirm spacing between checkbox glyph and text matches standard `KryptonCheckBox` (palette padding + optional `TextGap`) - [ ] Adjust **`LayoutValues.SubtextSeparatorHeight`** and confirm gap between main text and subtext changes - [ ] Click **"full agreement"** link — status updates; checkbox does not toggle - [ ] Click subtext outside the link — checkbox toggles - [ ] Click main text / checkbox — checkbox toggles - [ ] Toggle checked, disabled, and focus states; try another palette/theme - [ ] Verify control appears in the toolbox under `Krypton.Toolkit.Utilities` (designer) ## Breaking changes None. This is a new control in **Utilities**; existing `KryptonCheckBox` consumers are unaffected. ## NuGet / deployment note Consumers need the **`Krypton.Standard.Toolkit`** (or equivalent) package that includes **`Krypton.Toolkit.Utilities`**, as documented in the changelog entry. --- Documents/Changelog/Changelog.md | 14 +- .../KryptonCheckBoxExtended.cs | 975 ++++++++++++++++++ .../KryptonCheckBoxExtendedActionList.cs | 234 +++++ .../KryptonCheckBoxExtendedDesigner.cs | 40 + ...KryptonCheckBoxExtendedAccessibleObject.cs | 117 +++ .../General/SubtextLinkPresenter.cs | 106 ++ .../Values/CheckBoxExtendedLayoutValues.cs | 115 +++ .../CheckBoxExtendedSubtextLinkValues.cs | 115 +++ .../Values/CheckBoxExtendedTextValues.cs | 145 +++ .../ViewDrawCheckBoxExtendedContent.cs | 358 +++++++ .../TestForm/CheckBoxExtendedDemo.Designer.cs | 129 +++ .../TestForm/CheckBoxExtendedDemo.cs | 29 + .../TestForm/Main.Designer.cs | 13 + Source/Krypton Components/TestForm/Main.cs | 9 +- .../TestForm/StartScreen.cs | 7 +- 15 files changed, 2395 insertions(+), 11 deletions(-) create mode 100644 Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Controls Toolkit/KryptonCheckBoxExtended.cs create mode 100644 Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Designers/Action Lists/KryptonCheckBoxExtendedActionList.cs create mode 100644 Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Designers/Designers/KryptonCheckBoxExtendedDesigner.cs create mode 100644 Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/General/KryptonCheckBoxExtendedAccessibleObject.cs create mode 100644 Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/General/SubtextLinkPresenter.cs create mode 100644 Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedLayoutValues.cs create mode 100644 Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedSubtextLinkValues.cs create mode 100644 Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedTextValues.cs create mode 100644 Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/View Draw/ViewDrawCheckBoxExtendedContent.cs create mode 100644 Source/Krypton Components/TestForm/CheckBoxExtendedDemo.Designer.cs create mode 100644 Source/Krypton Components/TestForm/CheckBoxExtendedDemo.cs diff --git a/Documents/Changelog/Changelog.md b/Documents/Changelog/Changelog.md index 43b889d541..eb4a30c637 100644 --- a/Documents/Changelog/Changelog.md +++ b/Documents/Changelog/Changelog.md @@ -1,4 +1,4 @@ -# ![Krypton logo](https://github.com/Krypton-Suite/Standard-Toolkit/blob/master/Krypton.png?raw=true) Standard Toolkit - ChangeLog +# ![Krypton logo](https://github.com/Krypton-Suite/Standard-Toolkit/blob/master/Krypton.png?raw=true) Standard Toolkit - ChangeLog ======= @@ -45,6 +45,8 @@ ## 2026-11-xx - Build 2611 (V110 Nightly) - November 2026 +* Implemented [#3833](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3833), Expand `KryptonCheckBox` to allow more text + * To use, you will need to download the `Krypton.Standard.Toolkit` NuGet package, as this control is part of the `Krypton.Toolkit.Utilities` assembly. * Implemented [#3829](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3829), Showing Tab ToolTips for Docking Pages * Resolved [#3826](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3826), Null reference in `KryptonToggleSwitch` when the global palette changes * **[Breaking Change]**: The `Checked` property has been moved from `KryptonToggleSwitch` to `KryptonToggleSwitchValues`. Please update your code accordingly. @@ -89,7 +91,7 @@ * Resolved [#397](https://github.com/Krypton-Suite/Standard-Toolkit/issues/397), normal context menus now use the same palette colours as `KryptonContextMenu` * Resolved [#3545](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3545), `KryptonComboBoxUserControl` uses `VisualPopup` as a general `UserControl` host (e.g. `KryptonTreeView`, `DataGridView`). Replaced `VisualKryptonDropDownPopup` (`VisualPopup` + view layout over HWND children) with `KryptonDropDownHostForm`, a borderless top-level form that hosts `DropContent` on a plain `Panel` (not `KryptonPanel`), reuses the host between opens, and enables composited/double-buffered painting for the host and its descendants. * Resolved [#3616](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3616), Alpha Backup Synchronisation workflow fails to run -* Implemented [#3591](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3591), Implement a automated backup system. Manual **Repository Restore from Mirror** workflow (`.github/workflows/repo-restore-from-mirror.yml`). Restores branches—and optionally tags—from the configured mirror back into this repository via `workflow_dispatch`. Supports dry-run preview (default), safe `new_branch` restore (e.g. `restore/alpha-2025-06-01`), guarded `force_push` (requires typing `RESTORE`), and point-in-time recovery using `restore_date` or `commit_sha`. Kill switch: `REPO_RESTORE_DISABLED=true`. Optional Discord: `DISCORD_WEBHOOK_RESTORE`. +* Implemented [#3591](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3591), Implement a automated backup system. Manual **Repository Restore from Mirror** workflow (`.github/workflows/repo-restore-from-mirror.yml`). Restores branches�and optionally tags�from the configured mirror back into this repository via `workflow_dispatch`. Supports dry-run preview (default), safe `new_branch` restore (e.g. `restore/alpha-2025-06-01`), guarded `force_push` (requires typing `RESTORE`), and point-in-time recovery using `restore_date` or `commit_sha`. Kill switch: `REPO_RESTORE_DISABLED=true`. Optional Discord: `DISCORD_WEBHOOK_RESTORE`. * Implemented [#3611](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3611), Badges for workflows * Resolved [#3598](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3598), Fix KryptonContextMenu disposal leaks * Implemented [#3514](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3514), Include `README.md` in NuGet Packages @@ -144,7 +146,7 @@ * Implemented [#3305](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3305), QR Code Generation/Viewer * To use, you will need to download the `Krypton.Standard.Toolkit` NuGet package, as this control is part of the `Krypton.Toolkit.Utilities` assembly. * `KryptonQRCode` optional palette integration for `CenterImage`: `CenterImageUsePaletteColors`, `CenterImagePaletteStyle`, `CenterImageColorMap`, `CenterImageColorTo`, `CenterImageTransparentColor`, and `CenterImageEffect` (empty/inherit values resolve from the active palette; template glyphs using the Krypton transparency key remap to the effective dark module color). `GetCenterImagePalette()` and `QRCodeCenterImagePalette` support `GetBitmap()` / `GenerateBitmap()` export with the same drawing rules. - * Added `PaletteImageDrawing` in `Krypton.Toolkit` — shared palette image effect and color-remap drawing used by `KryptonQRCode` and `RenderBase.DrawImageHelper`. + * Added `PaletteImageDrawing` in `Krypton.Toolkit` � shared palette image effect and color-remap drawing used by `KryptonQRCode` and `RenderBase.DrawImageHelper`. * Resolved [#3018](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3018), `KryptonToast` no longer works properly * Resolved [#3227](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3227), `KryptonDockingManager.LoadConfigFromArray` throws exception * Resolved [#3225](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3225), Ribbon large button image-to-text separator not DPI-scaled @@ -188,10 +190,10 @@ * Implemented [#1827](https://github.com/Krypton-Suite/Standard-Toolkit/issues/1827), `KryptonDateTimePicker`'s 'MonthCalendar' with custom background color * Implemented [#776](https://github.com/Krypton-Suite/Standard-Toolkit/issues/776), Ability to set a number of custom colours for `KryptonColorButton` * Implemented [#922](https://github.com/Krypton-Suite/Standard-Toolkit/issues/922), Can external themes have names bundled with them -* Resolved [#3025](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3025), KryptonLabel with AutoSize not working in the Designer – when drawing a KryptonLabel by click-drag on the form, the control now resizes to fit its text (when `AutoSize = true`), matching standard WinForms Label behavior. `KryptonLabel` overrides `SetBoundsCore` to enforce preferred size. +* Resolved [#3025](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3025), KryptonLabel with AutoSize not working in the Designer � when drawing a KryptonLabel by click-drag on the form, the control now resizes to fit its text (when `AutoSize = true`), matching standard WinForms Label behavior. `KryptonLabel` overrides `SetBoundsCore` to enforce preferred size. * Implemented [#1326](https://github.com/Krypton-Suite/Standard-Toolkit/issues/1326), Button Text Tracking - Alternate text color for tracking (hover) state on buttons. Added comprehensive example in TestForm (`ButtonTextTrackingExample`). Wired up `SchemeExtraColors` enum: new `SetSchemeExtraColor`/`GetSchemeExtraColor`/`UpdateSchemeExtraColors` API in `PaletteBase`; `SchemeExtraColorChanged` event; `ButtonTextTracking` resolvable from both `SchemeBaseColors` and `SchemeExtraColors`. -* Resolved [#3012](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3012), Space between form close button and right edge of the form – introduced `PaletteMetricInt.HeaderButtonEdgeInsetFormRight` (returns 0) so the close button aligns with the form edge; top-right corner is now clickable for easy closing -* Resolved [#972](https://github.com/Krypton-Suite/Standard-Toolkit/issues/972), Office 2013 & Microsoft 365 control box items are not 'flat' – control box buttons (minimize, maximize, close) now use solid flat fills instead of gradients to match the official Office 2013 appearance +* Resolved [#3012](https://github.com/Krypton-Suite/Standard-Toolkit/issues/3012), Space between form close button and right edge of the form � introduced `PaletteMetricInt.HeaderButtonEdgeInsetFormRight` (returns 0) so the close button aligns with the form edge; top-right corner is now clickable for easy closing +* Resolved [#972](https://github.com/Krypton-Suite/Standard-Toolkit/issues/972), Office 2013 & Microsoft 365 control box items are not 'flat' � control box buttons (minimize, maximize, close) now use solid flat fills instead of gradients to match the official Office 2013 appearance * Implemented [#1326](https://github.com/Krypton-Suite/Standard-Toolkit/issues/1326), Is it possible to have an alternate colour for `##Tracking` - Alternate text color for tracking (hover) state on buttons for improved readability in dark themes. Added comprehensive example in TestForm (`ButtonTextTrackingExample`) * Implemented [#2129](https://github.com/Krypton-Suite/Standard-Toolkit/issues/2129), Drop-down arrows smaller and DPI aware; base size reduced from 16 to 10 logical pixels with DPI scaling; size configurable via theme (`PaletteMetricInt.DropDownArrowBaseSize`, `KryptonPalette.Navigator.StateCommon.Bar.DropDownArrowBaseSize`) * Implemented [#2968](https://github.com/Krypton-Suite/Standard-Toolkit/issues/2968), Move **all** RTL specific dialogs to use the feature fully diff --git a/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Controls Toolkit/KryptonCheckBoxExtended.cs b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Controls Toolkit/KryptonCheckBoxExtended.cs new file mode 100644 index 0000000000..0b851767f7 --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Controls Toolkit/KryptonCheckBoxExtended.cs @@ -0,0 +1,975 @@ +#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), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avilés (aka mcpbcs) et al. 2026 - 2026. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit.Utilities; + +/// +/// Displays a check box with word-wrapped main text and optional subtext, using Krypton styling. +/// +[ToolboxItem(true)] +[ToolboxBitmap(typeof(KryptonCheckBox), @"ToolboxBitmaps.KryptonCheckBox.bmp")] +[DefaultEvent(nameof(CheckedChanged))] +[DefaultProperty(nameof(Text))] +[DefaultBindingProperty(nameof(CheckState))] +[Designer(typeof(KryptonCheckBoxExtendedDesigner))] +[DesignerCategory(@"code")] +[DisplayName(@"Krypton CheckBox Extended")] +[Description(@"Displays a check box with word-wrapped text and optional subtext.")] +public class KryptonCheckBoxExtended : VisualSimpleBase, IContentValues +{ + #region Instance Fields + + private LabelStyle _style; + private VisualOrientation _orientation; + private readonly CheckBoxController _controller; + private readonly ViewLayoutDocker _layoutDocker; + private readonly ViewLayoutDocker _layoutCheckBoxGlyph; + private readonly ViewDrawCheckBox _drawCheckBox; + private readonly ViewDrawCheckBoxExtendedContent _drawContent; + private readonly PaletteContentInheritRedirect _paletteCommonRedirect; + private readonly PaletteRedirectCheckBox? _paletteCheckBoxImages; + private readonly PaletteContentInheritOverride _overrideNormal; + private KryptonCommand? _command; + private VisualOrientation _checkPosition; + private CheckState _checkState; + private CheckState _wasCheckState; + private bool _wasEnabled; + private bool _checked; + private bool _threeState; + private bool _useMnemonic; + private readonly CheckBoxExtendedLayoutValues _layoutValues; + private readonly CheckBoxExtendedSubtextLinkValues _subtextLinkValues; + private readonly ViewLayoutSeparator _textGapSeparator; + private readonly SubtextLinkPresenter _subtextLinkPresenter; + private Cursor? _savedCursor; + + #endregion + + #region Events + + /// + /// Occurs when the control is double clicked with the mouse. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new event EventHandler? DoubleClick; + + /// + /// Occurs when the control is mouse double clicked with the mouse. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new event EventHandler? MouseDoubleClick; + + /// + /// Occurs when the value of the ImeMode property is changed. + /// + [Browsable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new event EventHandler? ImeModeChanged; + + /// + /// Occurs when the value of the Checked property has changed. + /// + [Category(@"Misc")] + [Description(@"Occurs whenever the Checked property has changed.")] + public event EventHandler? CheckedChanged; + + /// + /// Occurs when the value of the CheckState property has changed. + /// + [Category(@"Misc")] + [Description(@"Occurs whenever the CheckState property has changed.")] + public event EventHandler? CheckStateChanged; + + /// + /// Occurs when the value of the KryptonCommand property changes. + /// + [Category(@"Property Changed")] + [Description(@"Occurs when the value of the KryptonCommand property changes.")] + public event EventHandler? KryptonCommandChanged; + + /// + /// Occurs when the user clicks a link within the subtext. + /// + [Category(@"Action")] + [Description(@"Occurs when the user clicks a link within the subtext.")] + public event LinkLabelLinkClickedEventHandler? SubtextLinkClicked; + + #endregion + + #region Identity + + /// + /// Initialize a new instance of the class. + /// + public KryptonCheckBoxExtended() + { + SetStyle(ControlStyles.StandardClick | + ControlStyles.StandardDoubleClick, false); + + _style = LabelStyle.NormalPanel; + _orientation = VisualOrientation.Top; + _checkPosition = VisualOrientation.Left; + _checked = false; + _threeState = false; + _checkState = CheckState.Unchecked; + _useMnemonic = true; + AutoCheck = true; + + Values = new CheckBoxExtendedTextValues(NeedPaintDelegate); + Values.TextChanged += OnCheckBoxTextChanged; + _layoutValues = new CheckBoxExtendedLayoutValues(NeedPaintDelegate); + _layoutValues.SetOwner(this); + _subtextLinkValues = new CheckBoxExtendedSubtextLinkValues(NeedPaintDelegate); + _subtextLinkValues.SetOwner(this); + Images = new CheckBoxImages(NeedPaintDelegate); + + _paletteCommonRedirect = new PaletteContentInheritRedirect(Redirector, PaletteContentStyle.LabelNormalPanel); + _paletteCheckBoxImages = new PaletteRedirectCheckBox(Redirector, Images); + + StateCommon = new PaletteContent(_paletteCommonRedirect, NeedPaintDelegate); + StateDisabled = new PaletteContent(StateCommon, NeedPaintDelegate); + StateNormal = new PaletteContent(StateCommon, NeedPaintDelegate); + OverrideFocus = new PaletteContent(_paletteCommonRedirect, NeedPaintDelegate); + + ConfigureWrapTextDefaults(); + + _overrideNormal = new PaletteContentInheritOverride(OverrideFocus, StateNormal, PaletteState.FocusOverride, false); + + _drawContent = new ViewDrawCheckBoxExtendedContent(_overrideNormal, this, VisualOrientation.Top) + { + UseMnemonic = _useMnemonic, + TestForFocusCues = true, + SubtextSeparatorHeight = _layoutValues.SubtextSeparatorHeight + }; + + _drawCheckBox = new ViewDrawCheckBox(_paletteCheckBoxImages) + { + CheckState = _checkState + }; + + _layoutCheckBoxGlyph = new ViewLayoutDocker + { + { _drawCheckBox, ViewDockStyle.Top }, + { new ViewLayoutFill(), ViewDockStyle.Fill } + }; + + _textGapSeparator = new ViewLayoutSeparator(0, 0); + + _layoutDocker = new ViewLayoutDocker + { + { _layoutCheckBoxGlyph, ViewDockStyle.Left }, + { _textGapSeparator, ViewDockStyle.Left }, + { _drawContent, ViewDockStyle.Fill } + }; + + _subtextLinkPresenter = new SubtextLinkPresenter(); + _subtextLinkPresenter.LinkClicked += OnSubtextLinkPresenterLinkClicked; + _subtextLinkPresenter.NonLinkClick += OnSubtextLinkPresenterNonLinkClick; + Controls.Add(_subtextLinkPresenter); + + _controller = new CheckBoxController(_drawCheckBox, _layoutDocker, NeedPaintDelegate); + _controller.Click += OnControllerClick; + _controller.Enabled = true; + _layoutDocker.MouseController = _controller; + _layoutDocker.KeyController = _controller; + + UpdateForOrientation(); + ApplyTextGap(); + + ViewManager = new ViewManager(this, _layoutDocker); + + AutoSize = true; + AutoSizeMode = AutoSizeMode.GrowAndShrink; + ApplySubtextAppearance(); + } + + #endregion + + #region Public + + /// + [Browsable(true)] + [Localizable(true)] + [EditorBrowsable(EditorBrowsableState.Always)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + [RefreshProperties(RefreshProperties.All)] + [DefaultValue(true)] + public override bool AutoSize + { + get => base.AutoSize; + set => base.AutoSize = value; + } + + /// + [Browsable(false)] + [Localizable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + [DefaultValue(AutoSizeMode.GrowAndShrink)] + public new AutoSizeMode AutoSizeMode + { + get => base.AutoSizeMode; + set => base.AutoSizeMode = value; + } + + /// + [Browsable(false)] + [Localizable(false)] + [EditorBrowsable(EditorBrowsableState.Never)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public new Padding Padding + { + get => base.Padding; + set => base.Padding = value; + } + + /// + [Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] + [AllowNull] + public override string Text + { + get => Values.Text; + set => Values.Text = value; + } + + private bool ShouldSerializeText() => false; + + /// + public override void ResetText() => Values.ResetText(); + /// + /// Gets access to layout spacing values. + /// + [Category(@"CheckBox Extended")] + [Description(@"Groups layout spacing properties for the check box extended control.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public CheckBoxExtendedLayoutValues LayoutValues => _layoutValues; + + private bool ShouldSerializeLayoutValues() => !_layoutValues.IsDefault; + + /// + /// Gets access to subtext link values. + /// + [Category(@"CheckBox Extended")] + [Description(@"Groups subtext link properties for the check box extended control.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public CheckBoxExtendedSubtextLinkValues SubtextLinkValues => _subtextLinkValues; + + private bool ShouldSerializeSubtextLinkValues() => !_subtextLinkValues.IsDefault; + + /// + /// Gets and sets the visual orientation of the control. + /// + [Category(@"Visuals")] + [Description(@"Visual orientation of the control.")] + [DefaultValue(VisualOrientation.Top)] + public virtual VisualOrientation Orientation + { + get => _orientation; + + set + { + if (_orientation != value) + { + _orientation = value; + _drawContent.Orientation = value; + UpdateForOrientation(); + PerformNeedPaint(true); + } + } + } + + /// + /// Gets and sets the position of the check box. + /// + [Category(@"Visuals")] + [Description(@"Visual position of the check box.")] + [DefaultValue(VisualOrientation.Left)] + [Localizable(true)] + public virtual VisualOrientation CheckPosition + { + get => _checkPosition; + + set + { + if (_checkPosition != value) + { + _checkPosition = value; + UpdateForOrientation(); + PerformNeedPaint(true); + } + } + } + + /// + /// Gets and sets the label style. + /// + [Category(@"Visuals")] + [Description(@"Label style.")] + public LabelStyle LabelStyle + { + get => _style; + + set + { + if (_style != value) + { + _style = value; + SetLabelStyle(_style); + PerformNeedPaint(true); + } + } + } + + private void ResetLabelStyle() => LabelStyle = LabelStyle.NormalPanel; + + private bool ShouldSerializeLabelStyle() => LabelStyle != LabelStyle.NormalPanel; + + /// + /// Gets access to the label content. + /// + [Category(@"CheckBox Extended")] + [Description(@"Groups main and subtext content properties.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public CheckBoxExtendedTextValues Values { get; } + + private bool ShouldSerializeValues() => !Values.IsDefault; + + /// + /// Gets access to the image value overrides. + /// + [Category(@"Visuals")] + [Description(@"Image value overrides.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public CheckBoxImages Images { get; } + + private bool ShouldSerializeImages() => !Images.IsDefault; + + /// + /// Gets access to the common label appearance that other states can override. + /// + [Category(@"Visuals")] + [Description(@"Overrides for defining common label appearance that other states can override.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public PaletteContent StateCommon { get; } + + private bool ShouldSerializeStateCommon() => !StateCommon.IsDefault; + + /// + /// Gets access to the disabled label appearance entries. + /// + [Category(@"Visuals")] + [Description(@"Overrides for defining disabled label appearance.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public PaletteContent StateDisabled { get; } + + private bool ShouldSerializeStateDisabled() => !StateDisabled.IsDefault; + + /// + /// Gets access to the normal label appearance entries. + /// + [Category(@"Visuals")] + [Description(@"Overrides for defining normal label appearance.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public PaletteContent StateNormal { get; } + + private bool ShouldSerializeStateNormal() => !StateNormal.IsDefault; + + /// + /// Gets access to the label appearance when it has focus. + /// + [Category(@"Visuals")] + [Description(@"Overrides for defining label appearance when it has focus.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public PaletteContent OverrideFocus { get; } + + private bool ShouldSerializeOverrideFocus() => !OverrideFocus.IsDefault; + + /// + /// 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 => _useMnemonic; + + set + { + if (_useMnemonic != value) + { + _useMnemonic = value; + _drawContent.UseMnemonic = value; + PerformNeedPaint(true); + } + } + } + + /// + /// Gets or sets a value indicating if the component is in the checked state. + /// + [Category(@"Appearance")] + [Description(@"Indicates if the component is in the checked state.")] + [DefaultValue(false)] + [Bindable(true)] + public bool Checked + { + get => _checked; + + set + { + if (_checked != value) + { + _checked = value; + _checkState = _checked ? CheckState.Checked : CheckState.Unchecked; + OnCheckedChanged(EventArgs.Empty); + OnCheckStateChanged(EventArgs.Empty); + PerformNeedPaint(true); + } + } + } + + /// + /// Gets or sets a value indicating if the check box is automatically changed state when clicked. + /// + [Category(@"Behavior")] + [Description(@"Causes the check box to automatically change state when clicked.")] + [DefaultValue(true)] + public bool AutoCheck { get; set; } + + /// + /// Gets or sets a value indicating if the component allows three states instead of two. + /// + [Category(@"Behavior")] + [Description(@"Indicates if the component allows three states instead of two.")] + [DefaultValue(false)] + public bool ThreeState + { + get => _threeState; + + set + { + if (_threeState != value) + { + _threeState = value; + PerformNeedPaint(true); + } + } + } + + /// + /// Gets or sets a value indicating the checked state of the component. + /// + [Category(@"Appearance")] + [Description(@"Indicates the checked state of the component.")] + [DefaultValue(CheckState.Unchecked)] + [Bindable(true)] + public CheckState CheckState + { + get => _checkState; + + set + { + if (_checkState != value) + { + _checkState = value; + var newChecked = _checkState != CheckState.Unchecked; + var checkedChanged = _checked != newChecked; + _checked = newChecked; + + if (checkedChanged) + { + OnCheckedChanged(EventArgs.Empty); + } + + OnCheckStateChanged(EventArgs.Empty); + PerformNeedPaint(true); + } + } + } + + /// + /// Gets and sets the associated KryptonCommand. + /// + [Category(@"Behavior")] + [Description(@"Command associated with the check button.")] + [DefaultValue(null)] + public virtual KryptonCommand? KryptonCommand + { + get => _command; + + set + { + if (_command != value) + { + if (_command != null) + { + _command.PropertyChanged -= OnCommandPropertyChanged; + } + else + { + _wasEnabled = Enabled; + _wasCheckState = CheckState; + } + + _command = value; + OnKryptonCommandChanged(EventArgs.Empty); + + if (_command != null) + { + _command.PropertyChanged += OnCommandPropertyChanged; + } + else + { + Enabled = _wasEnabled; + CheckState = _wasCheckState; + } + } + } + } + + /// + /// Fix the control to a particular palette state. + /// + /// Focus state for display. + /// Enabled state for display. + /// Tracking state for display. + /// Pressed state for display. + public virtual void SetFixedState(bool focus, + bool enabled, + bool tracking, + bool pressed) + { + _controller.Enabled = false; + _overrideNormal.Apply = focus; + _drawContent.FixedState = enabled ? PaletteState.Normal : PaletteState.Disabled; + _drawCheckBox.Enabled = enabled; + _drawCheckBox.Tracking = tracking; + _drawCheckBox.Pressed = pressed; + } + + #endregion + + #region IContentValues + + /// + public string GetShortText() => KryptonCommand?.Text ?? Values.GetShortText(); + + /// + public string GetLongText() => KryptonCommand?.ExtraText ?? Values.GetLongText(); + + /// + public Image? GetImage(PaletteState state) => KryptonCommand?.ImageSmall ?? Values.GetImage(state); + + /// + public Color GetImageTransparentColor(PaletteState state) => + KryptonCommand?.ImageTransparentColor ?? Values.GetImageTransparentColor(state); + + /// + public Image? GetOverlayImage(PaletteState state) => Values.GetOverlayImage(state); + + /// + public Color GetOverlayImageTransparentColor(PaletteState state) => Values.GetOverlayImageTransparentColor(state); + + /// + public OverlayImagePosition GetOverlayImagePosition(PaletteState state) => Values.GetOverlayImagePosition(state); + + /// + public OverlayImageScaleMode GetOverlayImageScaleMode(PaletteState state) => Values.GetOverlayImageScaleMode(state); + + /// + public float GetOverlayImageScaleFactor(PaletteState state) => Values.GetOverlayImageScaleFactor(state); + + /// + public Size GetOverlayImageFixedSize(PaletteState state) => Values.GetOverlayImageFixedSize(state); + + #endregion + + #region Protected + + /// + protected override void OnDoubleClick(EventArgs e) => DoubleClick?.Invoke(this, e); + + /// + protected virtual void OnMouseDoubleClick(EventArgs e) => MouseDoubleClick?.Invoke(this, e); + + /// + protected virtual void OnMouseImeModeChanged(EventArgs e) => ImeModeChanged?.Invoke(this, e); + + /// + protected virtual void OnCheckedChanged(EventArgs e) => CheckedChanged?.Invoke(this, e); + + /// + protected virtual void OnCheckStateChanged(EventArgs e) + { + _drawCheckBox.CheckState = _checkState; + CheckStateChanged?.Invoke(this, e); + + if (KryptonCommand != null) + { + KryptonCommand.CheckState = CheckState; + } + } + + /// + protected override void OnGotFocus(EventArgs e) + { + if (!_drawContent.IsFixed) + { + _overrideNormal.Apply = true; + PerformNeedPaint(false); + } + + base.OnGotFocus(e); + } + + /// + protected virtual void OnKryptonCommandChanged(EventArgs e) + { + KryptonCommandChanged?.Invoke(this, e); + + if (KryptonCommand != null) + { + Enabled = KryptonCommand.Enabled; + CheckState = KryptonCommand.CheckState; + } + + PerformNeedPaint(true); + } + + /// + protected virtual void OnCommandPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + switch (e.PropertyName) + { + case nameof(Enabled): + Enabled = KryptonCommand!.Enabled; + break; + case nameof(CheckState): + CheckState = KryptonCommand!.CheckState; + break; + case nameof(Text): + case @"ExtraText": + case @"ImageSmall": + case @"ImageTransparentColor": + PerformNeedPaint(true); + break; + } + } + + /// + protected override void OnLostFocus(EventArgs e) + { + if (!_drawContent.IsFixed) + { + _overrideNormal.Apply = false; + PerformNeedPaint(false); + } + + base.OnLostFocus(e); + } + + /// + protected override void OnClick(EventArgs e) + { + if (AutoCheck) + { + CheckState = CheckState switch + { + CheckState.Unchecked => CheckState.Checked, + CheckState.Checked => ThreeState ? CheckState.Indeterminate : CheckState.Unchecked, + CheckState.Indeterminate => CheckState.Unchecked, + _ => CheckState + }; + } + + base.OnClick(e); + KryptonCommand?.PerformExecute(); + } + + /// + protected override void OnMouseMove(MouseEventArgs e) + { + UpdateSubtextLinkCursor(e.Location); + base.OnMouseMove(e); + } + + /// + protected override void OnMouseLeave(EventArgs e) + { + RestoreSubtextLinkCursor(); + base.OnMouseLeave(e); + } + + /// + protected override AccessibleObject CreateAccessibilityInstance() => new KryptonCheckBoxExtendedAccessibleObject(this); + + /// + protected override void OnPaint(PaintEventArgs? e) + { + ApplySubtextAppearance(); + UpdateSubtextLinkPresenter(); + base.OnPaint(e); + } + + /// + protected virtual void SetLabelStyle(LabelStyle style) => + _paletteCommonRedirect.Style = CommonHelper.ContentStyleFromLabelStyle(style); + + /// + protected override bool ProcessMnemonic(char charCode) + { + if (UseMnemonic && AutoCheck && CanProcessMnemonic()) + { + if (IsMnemonic(charCode, Values.Text)) + { + if (!ContainsFocus) + { + Focus(); + } + + OnClick(EventArgs.Empty); + return true; + } + } + + return base.ProcessMnemonic(charCode); + } + + /// + protected override void OnEnabledChanged(EventArgs e) + { + if (Enabled) + { + _drawContent.SetPalette(_overrideNormal); + } + else + { + _drawContent.SetPalette(StateDisabled); + } + + _drawContent.Enabled = Enabled; + _drawCheckBox.Enabled = Enabled; + MarkLayoutDirty(); + base.OnEnabledChanged(e); + } + + /// + protected override void OnRightToLeftChanged(EventArgs e) + { + UpdateForOrientation(); + base.OnRightToLeftChanged(e); + } + + /// + protected override Size DefaultSize => new Size(300, 60); + + /// + protected override bool EvalTransparentPaint() => true; + + /// + /// Performs the default accessibility action for the control. + /// + internal void PerformAccessibilityClick() + { + if (!CanSelect) + { + return; + } + + if (!ContainsFocus) + { + Focus(); + } + + OnClick(EventArgs.Empty); + } + + #endregion + + #region Implementation + + private void ConfigureWrapTextDefaults() + { + PaletteContentText shortText = StateCommon.ShortText; + shortText.MultiLine = InheritBool.True; + shortText.MultiLineH = PaletteRelativeAlign.Near; + shortText.TextH = PaletteRelativeAlign.Near; + shortText.TextV = PaletteRelativeAlign.Near; + shortText.Trim = PaletteTextTrim.Word; + + PaletteContentText longText = StateCommon.LongText; + longText.MultiLine = InheritBool.True; + longText.MultiLineH = PaletteRelativeAlign.Near; + longText.TextH = PaletteRelativeAlign.Near; + longText.TextV = PaletteRelativeAlign.Near; + longText.Trim = PaletteTextTrim.Word; + } + + private void ApplySubtextAppearance() + { + StateCommon.AdjacentGap = _layoutValues.SubtextSeparatorHeight; + _drawContent.SubtextSeparatorHeight = _layoutValues.SubtextSeparatorHeight; + _drawContent.SubtextFont = Values.SubtextFont; + _drawContent.SubtextForeColor = Values.SubtextForeColor; + + if (Values.SubtextFont != null) + { + StateCommon.LongText.Font = Values.SubtextFont; + } + + if (!Values.SubtextForeColor.IsEmpty) + { + StateCommon.LongText.Color1 = Values.SubtextForeColor; + StateCommon.LongText.Color2 = Values.SubtextForeColor; + } + + UpdateSubtextLinkPresenter(); + } + + internal void OnLayoutValuesChanged() + { + _drawContent.SubtextSeparatorHeight = _layoutValues.SubtextSeparatorHeight; + StateCommon.AdjacentGap = _layoutValues.SubtextSeparatorHeight; + ApplyTextGap(); + MarkLayoutDirty(); + } + + internal void OnSubtextLinkValuesChanged() => UpdateSubtextLinkPresenter(); + + private void ApplyTextGap() + { + bool horizontalGap = _checkPosition is VisualOrientation.Left or VisualOrientation.Right; + _textGapSeparator.SeparatorSize = horizontalGap + ? new Size(_layoutValues.TextGap, 0) + : new Size(0, _layoutValues.TextGap); + } + + private void UpdateSubtextLinkPresenter() + { + bool useLinkPresenter = _subtextLinkValues.LinkArea.Length > 0 && !string.IsNullOrEmpty(Values.Subtext); + _drawContent.SkipSubtextDrawing = useLinkPresenter; + + if (!useLinkPresenter) + { + _subtextLinkPresenter.Visible = false; + return; + } + + Rectangle subtextRect = _drawContent.SubtextLayoutRect; + if (subtextRect.Width <= 0 || subtextRect.Height <= 0) + { + _subtextLinkPresenter.Visible = false; + return; + } + + Font subtextFont = Values.SubtextFont ?? Font; + Color subtextColor = Values.SubtextForeColor.IsEmpty ? ForeColor : Values.SubtextForeColor; + Color linkColor = _subtextLinkValues.LinkColor.IsEmpty ? SystemColors.HotTrack : _subtextLinkValues.LinkColor; + + _subtextLinkPresenter.SetBounds(subtextRect.X, subtextRect.Y, subtextRect.Width, subtextRect.Height, BoundsSpecified.All); + _subtextLinkPresenter.Text = Values.Subtext; + _subtextLinkPresenter.LinkArea = _subtextLinkValues.LinkArea; + _subtextLinkPresenter.Font = subtextFont; + _subtextLinkPresenter.ForeColor = subtextColor; + _subtextLinkPresenter.LinkColor = linkColor; + _subtextLinkPresenter.VisitedLinkColor = linkColor; + _subtextLinkPresenter.Enabled = Enabled; + _subtextLinkPresenter.RightToLeft = RightToLeft; + _subtextLinkPresenter.Visible = true; + _subtextLinkPresenter.BringToFront(); + } + + private void UpdateSubtextLinkCursor(Point clientLocation) + { + if (!Enabled + || !_subtextLinkPresenter.Visible + || !_subtextLinkPresenter.ContainsLinkPoint(clientLocation)) + { + RestoreSubtextLinkCursor(); + return; + } + + if (_savedCursor == null) + { + _savedCursor = Cursor; + Cursor = Cursors.Hand; + } + } + + private void RestoreSubtextLinkCursor() + { + if (_savedCursor != null) + { + Cursor = _savedCursor; + _savedCursor = null; + } + } + + private void OnSubtextLinkPresenterLinkClicked(object? sender, LinkLabelLinkClickedEventArgs e) => OnSubtextLinkClicked(e); + + private void OnSubtextLinkPresenterNonLinkClick(object? sender, EventArgs e) => OnClick(EventArgs.Empty); + + private void OnSubtextLinkClicked(LinkLabelLinkClickedEventArgs e) => SubtextLinkClicked?.Invoke(this, e); + + private void OnCheckBoxTextChanged(object? sender, EventArgs e) + { + MarkLayoutDirty(); + OnTextChanged(EventArgs.Empty); + } + + private void OnControllerClick(object? sender, EventArgs e) => OnClick(e); + + private void UpdateForOrientation() + { + ViewDockStyle dockStyle = _checkPosition switch + { + VisualOrientation.Right => _orientation switch + { + VisualOrientation.Bottom => RightToLeft == RightToLeft.Yes ? ViewDockStyle.Right : ViewDockStyle.Left, + VisualOrientation.Left => ViewDockStyle.Top, + VisualOrientation.Right => ViewDockStyle.Bottom, + _ => RightToLeft == RightToLeft.Yes ? ViewDockStyle.Left : ViewDockStyle.Right + }, + VisualOrientation.Top => _orientation switch + { + VisualOrientation.Bottom => ViewDockStyle.Bottom, + VisualOrientation.Left => ViewDockStyle.Left, + VisualOrientation.Right => ViewDockStyle.Right, + _ => ViewDockStyle.Top + }, + VisualOrientation.Bottom => _orientation switch + { + VisualOrientation.Bottom => ViewDockStyle.Top, + VisualOrientation.Left => ViewDockStyle.Right, + VisualOrientation.Right => ViewDockStyle.Left, + _ => ViewDockStyle.Bottom + }, + _ => _orientation switch + { + VisualOrientation.Bottom => RightToLeft == RightToLeft.Yes ? ViewDockStyle.Left : ViewDockStyle.Right, + VisualOrientation.Left => ViewDockStyle.Bottom, + VisualOrientation.Right => ViewDockStyle.Top, + _ => RightToLeft == RightToLeft.Yes ? ViewDockStyle.Right : ViewDockStyle.Left + } + }; + + ViewDockStyle separatorDock = dockStyle; + _layoutDocker.SetDock(_layoutCheckBoxGlyph, dockStyle); + _layoutDocker.SetDock(_textGapSeparator, separatorDock); + ApplyTextGap(); + } + + #endregion +} diff --git a/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Designers/Action Lists/KryptonCheckBoxExtendedActionList.cs b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Designers/Action Lists/KryptonCheckBoxExtendedActionList.cs new file mode 100644 index 0000000000..f8a0eccba7 --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Designers/Action Lists/KryptonCheckBoxExtendedActionList.cs @@ -0,0 +1,234 @@ +#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), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avilés (aka mcpbcs) et al. 2026 - 2026. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit.Utilities; + +internal class KryptonCheckBoxExtendedActionList : DesignerActionList +{ + #region Instance Fields + + private readonly KryptonCheckBoxExtended _checkBox; + private readonly IComponentChangeService? _service; + + #endregion + + #region Identity + + public KryptonCheckBoxExtendedActionList(KryptonCheckBoxExtendedDesigner owner) + : base(owner.Component) + { + _checkBox = (owner.Component as KryptonCheckBoxExtended)!; + _service = GetService(typeof(IComponentChangeService)) as IComponentChangeService; + } + + #endregion + + #region Public + + public bool Checked + { + get => _checkBox.Checked; + set + { + if (_checkBox.Checked != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.Checked, value); + _checkBox.Checked = value; + } + } + } + + public CheckState CheckState + { + get => _checkBox.CheckState; + set + { + if (_checkBox.CheckState != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.CheckState, value); + _checkBox.CheckState = value; + } + } + } + + public bool ThreeState + { + get => _checkBox.ThreeState; + set + { + if (_checkBox.ThreeState != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.ThreeState, value); + _checkBox.ThreeState = value; + } + } + } + + public VisualOrientation Orientation + { + get => _checkBox.Orientation; + set + { + if (_checkBox.Orientation != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.Orientation, value); + _checkBox.Orientation = value; + } + } + } + + public VisualOrientation CheckPosition + { + get => _checkBox.CheckPosition; + set + { + if (_checkBox.CheckPosition != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.CheckPosition, value); + _checkBox.CheckPosition = value; + } + } + } + + public string Text + { + get => _checkBox.Text; + set + { + if (_checkBox.Text != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.Text, value); + _checkBox.Text = value; + } + } + } + + public string Subtext + { + get => _checkBox.Values.Subtext; + set + { + if (_checkBox.Values.Subtext != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.Values.Subtext, value); + _checkBox.Values.Subtext = value; + } + } + } + + public Font? SubtextFont + { + get => _checkBox.Values.SubtextFont; + set + { + if (_checkBox.Values.SubtextFont != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.Values.SubtextFont, value); + _checkBox.Values.SubtextFont = value; + } + } + } + + public Color SubtextForeColor + { + get => _checkBox.Values.SubtextForeColor; + set + { + if (_checkBox.Values.SubtextForeColor != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.Values.SubtextForeColor, value); + _checkBox.Values.SubtextForeColor = value; + } + } + } + + public int SubtextSeparatorHeight + { + get => _checkBox.LayoutValues.SubtextSeparatorHeight; + set + { + if (_checkBox.LayoutValues.SubtextSeparatorHeight != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.LayoutValues.SubtextSeparatorHeight, value); + _checkBox.LayoutValues.SubtextSeparatorHeight = value; + } + } + } + + public int TextGap + { + get => _checkBox.LayoutValues.TextGap; + set + { + if (_checkBox.LayoutValues.TextGap != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.LayoutValues.TextGap, value); + _checkBox.LayoutValues.TextGap = value; + } + } + } + + public LabelStyle LabelStyle + { + get => _checkBox.LabelStyle; + set + { + if (_checkBox.LabelStyle != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.LabelStyle, value); + _checkBox.LabelStyle = value; + } + } + } + + public PaletteMode PaletteMode + { + get => _checkBox.PaletteMode; + set + { + if (_checkBox.PaletteMode != value) + { + _service?.OnComponentChanged(_checkBox, null, _checkBox.PaletteMode, value); + _checkBox.PaletteMode = value; + } + } + } + + #endregion + + #region Public Override + + public override DesignerActionItemCollection GetSortedActionItems() + { + var actions = new DesignerActionItemCollection(); + + if (_checkBox != null) + { + actions.Add(new DesignerActionHeaderItem(@"Appearance")); + actions.Add(new DesignerActionPropertyItem(nameof(Text), @"Text", @"Appearance", @"Main check box text")); + actions.Add(new DesignerActionPropertyItem(nameof(Subtext), @"Subtext", @"Appearance", @"Secondary descriptive text")); + actions.Add(new DesignerActionPropertyItem(nameof(SubtextFont), @"Subtext Font", @"Appearance", @"Font used for the subtext")); + actions.Add(new DesignerActionPropertyItem(nameof(SubtextForeColor), @"Subtext Color", @"Appearance", @"Foreground color of the subtext")); + actions.Add(new DesignerActionPropertyItem(nameof(SubtextSeparatorHeight), @"Subtext Spacing", @"Appearance", @"Pixels between main text and subtext")); + actions.Add(new DesignerActionPropertyItem(nameof(TextGap), @"Text Gap", @"Appearance", @"Extra spacing between the check box glyph and the text")); + actions.Add(new DesignerActionPropertyItem(nameof(Checked), @"Checked", @"Appearance", @"Checked state")); + actions.Add(new DesignerActionPropertyItem(nameof(CheckState), @"Check State", @"Appearance", @"Three state check value")); + actions.Add(new DesignerActionPropertyItem(nameof(ThreeState), @"Three State", @"Appearance", @"Allow indeterminate state")); + actions.Add(new DesignerActionHeaderItem(@"Visuals")); + actions.Add(new DesignerActionPropertyItem(nameof(Orientation), @"Orientation", @"Visuals", @"Control orientation")); + actions.Add(new DesignerActionPropertyItem(nameof(CheckPosition), @"Check Position", @"Visuals", @"Check box position")); + actions.Add(new DesignerActionPropertyItem(nameof(LabelStyle), @"Label Style", @"Visuals", @"Label style")); + actions.Add(new DesignerActionPropertyItem(nameof(PaletteMode), @"Palette", @"Visuals", @"Palette applied to drawing")); + } + + return actions; + } + + #endregion +} \ No newline at end of file diff --git a/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Designers/Designers/KryptonCheckBoxExtendedDesigner.cs b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Designers/Designers/KryptonCheckBoxExtendedDesigner.cs new file mode 100644 index 0000000000..a2b472a5cd --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Designers/Designers/KryptonCheckBoxExtendedDesigner.cs @@ -0,0 +1,40 @@ +#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), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avilés (aka mcpbcs) et al. 2026 - 2026. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit.Utilities; + +internal class KryptonCheckBoxExtendedDesigner : ControlDesigner +{ + #region Identity + + /// + /// Initialize a new instance of the class. + /// + public KryptonCheckBoxExtendedDesigner() => AutoResizeHandles = true; + + #endregion + + #region Public Overrides + + /// + public override DesignerActionListCollection ActionLists + { + get + { + var actionLists = new DesignerActionListCollection + { + new KryptonCheckBoxExtendedActionList(this) + }; + + return actionLists; + } + } + + #endregion +} diff --git a/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/General/KryptonCheckBoxExtendedAccessibleObject.cs b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/General/KryptonCheckBoxExtendedAccessibleObject.cs new file mode 100644 index 0000000000..a44080b9eb --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/General/KryptonCheckBoxExtendedAccessibleObject.cs @@ -0,0 +1,117 @@ +#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), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avilés (aka mcpbcs) et al. 2026 - 2026. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit.Utilities; + +/// +/// Provides accessibility information for controls. +/// +internal sealed class KryptonCheckBoxExtendedAccessibleObject : Control.ControlAccessibleObject +{ + #region Instance Fields + + private readonly KryptonCheckBoxExtended _owner; + + #endregion + + #region Identity + + /// + /// Initialize a new instance of the class. + /// + /// The owning control. + public KryptonCheckBoxExtendedAccessibleObject(KryptonCheckBoxExtended owner) + : base(owner) + { + _owner = owner; + } + + #endregion + + #region Public Overrides + + /// + public override string? Name + { + get + { + if (!string.IsNullOrEmpty(_owner.AccessibleName)) + { + return _owner.AccessibleName; + } + + string text = _owner.Text; + return string.IsNullOrEmpty(text) ? base.Name ?? _owner.Name : text.Replace(@"&", string.Empty); + } + } + + /// + public override string? Description + { + get + { + if (!string.IsNullOrEmpty(_owner.AccessibleDescription)) + { + return _owner.AccessibleDescription; + } + + return string.IsNullOrEmpty(_owner.Values.Subtext) ? base.Description : _owner.Values.Subtext; + } + } + + /// + public override AccessibleRole Role => _owner.AccessibleRole != AccessibleRole.Default + ? _owner.AccessibleRole + : AccessibleRole.CheckButton; + + /// + public override AccessibleStates State + { + get + { + AccessibleStates state = AccessibleStates.Focusable; + + if (_owner.Focused) + { + state |= AccessibleStates.Focused; + } + + if (_owner.Checked) + { + state |= AccessibleStates.Checked; + } + + if (!_owner.Visible) + { + state |= AccessibleStates.Invisible; + } + + if (!_owner.Enabled) + { + state |= AccessibleStates.Unavailable; + } + + return state; + } + } + + /// + public override string DefaultAction => @"Check"; + + /// + public override void DoDefaultAction() + { + if (_owner.Enabled && _owner.Visible) + { + _owner.PerformAccessibilityClick(); + } + } + + #endregion +} diff --git a/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/General/SubtextLinkPresenter.cs b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/General/SubtextLinkPresenter.cs new file mode 100644 index 0000000000..45ccce4355 --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/General/SubtextLinkPresenter.cs @@ -0,0 +1,106 @@ +#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), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avilés (aka mcpbcs) et al. 2026 - 2026. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit.Utilities; + +/// +/// Presents word-wrapped subtext with link styling and hit testing for . +/// +internal sealed class SubtextLinkPresenter : KryptonLinkWrapLabel +{ + #region Events + + /// + /// Occurs when the user clicks the subtext outside of a link region. + /// + public event EventHandler? NonLinkClick; + + #endregion + + #region Identity + + /// + /// Initialize a new instance of the class. + /// + public SubtextLinkPresenter() + { + SetStyle(ControlStyles.SupportsTransparentBackColor, true); + AutoSize = false; + TabStop = false; + BackColor = Color.Transparent; + LinkBehavior = LinkBehavior.HoverUnderline; + } + + #endregion + + #region Implementation + + private int GetCharIndexFromPoint(Point localPoint) + { + MethodInfo? method = typeof(LinkLabel).GetMethod( + "PointToCharIndex", + BindingFlags.Instance | BindingFlags.NonPublic, + null, + new[] { typeof(Point) }, + null); + + if (method is null) + { + return -1; + } + + return (int)method.Invoke(this, new object[] { localPoint })!; + } + + private bool IsLinkCharIndex(int charIndex) => + charIndex >= LinkArea.Start && charIndex < LinkArea.Start + LinkArea.Length; + + #endregion + + #region Public + + /// + /// Gets a value indicating whether the specified point is within a link region. + /// + /// Point in the parent control's client coordinates. + /// True if the point is over a link; otherwise false. + public bool ContainsLinkPoint(Point parentClientPoint) + { + if (!Visible || LinkArea.Length <= 0) + { + return false; + } + + Point localPoint = new Point(parentClientPoint.X - Left, parentClientPoint.Y - Top); + int charIndex = GetCharIndexFromPoint(localPoint); + return charIndex >= 0 && IsLinkCharIndex(charIndex); + } + + #endregion + + #region Protected + + /// + protected override void OnMouseUp(MouseEventArgs e) + { + if (e.Button == MouseButtons.Left) + { + int charIndex = GetCharIndexFromPoint(new Point(e.X, e.Y)); + if (!IsLinkCharIndex(charIndex)) + { + NonLinkClick?.Invoke(this, EventArgs.Empty); + return; + } + } + + base.OnMouseUp(e); + } + + #endregion +} diff --git a/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedLayoutValues.cs b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedLayoutValues.cs new file mode 100644 index 0000000000..07b07c7240 --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedLayoutValues.cs @@ -0,0 +1,115 @@ +#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), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avilés (aka mcpbcs) et al. 2026 - 2026. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit.Utilities; + +/// +/// Storage for layout spacing. +/// +[TypeConverter(typeof(ExpandableObjectConverter))] +public class CheckBoxExtendedLayoutValues : Storage +{ + #region Instance Fields + + private KryptonCheckBoxExtended? _owner; + private int _subtextSeparatorHeight; + private int _textGap; + + #endregion + + #region Identity + + /// + /// Initialize a new instance of the class. + /// + /// Delegate for notifying paint requests. + public CheckBoxExtendedLayoutValues(NeedPaintHandler needPaint) + { + NeedPaint = needPaint; + _subtextSeparatorHeight = 5; + _textGap = 0; + } + + #endregion + + #region Public + + /// + public override bool IsDefault => _subtextSeparatorHeight == 5 && _textGap == 0; + + /// + /// Gets or sets the number of pixels separating the main text from the subtext. + /// + [Category(@"Layout")] + [Description(@"Number of pixels separating the main text from the subtext.")] + [DefaultValue(5)] + public int SubtextSeparatorHeight + { + get => _subtextSeparatorHeight; + + set + { + if (_subtextSeparatorHeight != value) + { + _subtextSeparatorHeight = value; + _owner?.OnLayoutValuesChanged(); + PerformNeedPaint(true); + } + } + } + + /// + /// Resets the property to its default value. + /// + public void ResetSubtextSeparatorHeight() => SubtextSeparatorHeight = 5; + + private bool ShouldSerializeSubtextSeparatorHeight() => SubtextSeparatorHeight != 5; + + /// + /// Gets or sets the number of pixels between the check box glyph and the text. + /// + [Category(@"Layout")] + [Description(@"Additional spacing between the check box glyph and the text.")] + [DefaultValue(0)] + public int TextGap + { + get => _textGap; + + set + { + if (_textGap != value) + { + _textGap = Math.Max(0, value); + _owner?.OnLayoutValuesChanged(); + PerformNeedPaint(true); + } + } + } + + /// + /// Resets the property to its default value. + /// + public void ResetTextGap() => TextGap = 0; + + private bool ShouldSerializeTextGap() => TextGap != 0; + + #endregion + + #region Implementation + + internal void SetOwner(KryptonCheckBoxExtended owner) => _owner = owner; + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() => IsDefault ? string.Empty : "Modified"; + + #endregion +} diff --git a/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedSubtextLinkValues.cs b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedSubtextLinkValues.cs new file mode 100644 index 0000000000..501712f693 --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedSubtextLinkValues.cs @@ -0,0 +1,115 @@ +#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), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avilés (aka mcpbcs) et al. 2026 - 2026. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit.Utilities; + +/// +/// Storage for linked subtext settings on . +/// +[TypeConverter(typeof(ExpandableObjectConverter))] +public class CheckBoxExtendedSubtextLinkValues : Storage +{ + #region Instance Fields + + private KryptonCheckBoxExtended? _owner; + private LinkArea _linkArea; + private Color _linkColor; + + #endregion + + #region Identity + + /// + /// Initialize a new instance of the class. + /// + /// Delegate for notifying paint requests. + public CheckBoxExtendedSubtextLinkValues(NeedPaintHandler needPaint) + { + NeedPaint = needPaint; + _linkArea = new LinkArea(0, 0); + _linkColor = GlobalStaticVariables.EMPTY_COLOR; + } + + #endregion + + #region Public + + /// + public override bool IsDefault => _linkArea.Length == 0 && _linkColor.IsEmpty; + + /// + /// Gets or sets the portion of the subtext that behaves as a link. + /// + [Category(@"Link")] + [Description(@"The portion of the subtext that behaves as a link.")] + [DefaultValue(typeof(LinkArea), "0, 0")] + public LinkArea LinkArea + { + get => _linkArea; + + set + { + if (_linkArea.Start != value.Start || _linkArea.Length != value.Length) + { + _linkArea = value; + _owner?.OnSubtextLinkValuesChanged(); + PerformNeedPaint(true); + } + } + } + + /// + /// Resets the property to its default value. + /// + public void ResetLinkArea() => LinkArea = new LinkArea(0, 0); + + private bool ShouldSerializeLinkArea() => LinkArea.Length != 0; + + /// + /// Gets or sets the color used to render linked subtext. + /// + [Category(@"Link")] + [Description(@"The color used to render linked subtext.")] + [DefaultValue(typeof(Color), "Empty")] + public Color LinkColor + { + get => _linkColor; + + set + { + if (_linkColor != value) + { + _linkColor = value; + _owner?.OnSubtextLinkValuesChanged(); + PerformNeedPaint(false); + } + } + } + + /// + /// Resets the property to its default value. + /// + public void ResetLinkColor() => LinkColor = GlobalStaticVariables.EMPTY_COLOR; + + private bool ShouldSerializeLinkColor() => !LinkColor.IsEmpty; + + #endregion + + #region Implementation + + internal void SetOwner(KryptonCheckBoxExtended owner) => _owner = owner; + + /// + /// Returns a string representation of the current object. + /// + /// A that represents this instance. + public override string ToString() => IsDefault ? string.Empty : "Modified"; + + #endregion +} diff --git a/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedTextValues.cs b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedTextValues.cs new file mode 100644 index 0000000000..9e73402d03 --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/Values/CheckBoxExtendedTextValues.cs @@ -0,0 +1,145 @@ +#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), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avilés (aka mcpbcs) et al. 2026 - 2026. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit.Utilities; + +/// +/// Storage for text content. +/// +[TypeConverter(typeof(ExpandableObjectConverter))] +public class CheckBoxExtendedTextValues : LabelValues +{ + #region Static Fields + + private const string DEFAULT_TEXT = @"Krypton CheckBox Extended"; + private const string DEFAULT_SUBTEXT = @""; + + #endregion + + #region Instance Fields + + private Font? _subtextFont; + private Color _subtextForeColor; + + #endregion + + #region Identity + + /// + /// Initialize a new instance of the class. + /// + /// Delegate for notifying paint requests. + public CheckBoxExtendedTextValues(NeedPaintHandler needPaint) + : base(needPaint) + { + Text = DEFAULT_TEXT; + ExtraText = DEFAULT_SUBTEXT; + _subtextForeColor = GlobalStaticVariables.EMPTY_COLOR; + } + + #endregion + + #region Public + + /// + public override bool IsDefault => base.IsDefault + && !ShouldSerializeSubtextFont() + && !ShouldSerializeSubtextForeColor(); + + /// + /// Gets or sets the secondary descriptive text displayed below the main text. + /// + [Category(@"Text")] + [Description(@"Secondary descriptive text displayed below the main text.")] + [Editor(typeof(MultilineStringEditor), typeof(UITypeEditor))] + [DefaultValue(DEFAULT_SUBTEXT)] + public string Subtext + { + get => ExtraText; + set => ExtraText = value; + } + + /// + /// Resets the property to its default value. + /// + public void ResetSubtext() => Subtext = DEFAULT_SUBTEXT; + + internal bool ShouldSerializeSubtext() => Subtext != DEFAULT_SUBTEXT; + + /// + /// Gets or sets the font used to render the subtext. + /// + [Category(@"Text")] + [Description(@"The font used to display the subtext.")] + [DefaultValue(null)] + public Font? SubtextFont + { + get => _subtextFont; + set + { + if (_subtextFont != value) + { + _subtextFont = value; + PerformNeedPaint(true); + } + } + } + + /// + /// Resets the property to its default value. + /// + public void ResetSubtextFont() => SubtextFont = null; + + internal bool ShouldSerializeSubtextFont() => SubtextFont != null; + + /// + /// Gets or sets the foreground color of the subtext. + /// + [Category(@"Text")] + [Description(@"The color used to display the subtext.")] + [DefaultValue(typeof(Color), "Empty")] + public Color SubtextForeColor + { + get => _subtextForeColor; + set + { + if (_subtextForeColor != value) + { + _subtextForeColor = value; + PerformNeedPaint(true); + } + } + } + + /// + /// Resets the property to its default value. + /// + public void ResetSubtextForeColor() => SubtextForeColor = GlobalStaticVariables.EMPTY_COLOR; + + internal bool ShouldSerializeSubtextForeColor() => SubtextForeColor != GlobalStaticVariables.EMPTY_COLOR; + + /// + public new void ResetText() + { + Text = DEFAULT_TEXT; + Subtext = DEFAULT_SUBTEXT; + } + + #endregion + + #region Implementation + + /// + /// Returns a that represents this instance. + /// + /// A that represents this instance. + public override string ToString() => IsDefault ? string.Empty : "Modified"; + + #endregion +} diff --git a/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/View Draw/ViewDrawCheckBoxExtendedContent.cs b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/View Draw/ViewDrawCheckBoxExtendedContent.cs new file mode 100644 index 0000000000..b6ea20dc8f --- /dev/null +++ b/Source/Krypton Components/Krypton.Toolkit.Utilities/Components/KryptonCheckBoxExtended/View Draw/ViewDrawCheckBoxExtendedContent.cs @@ -0,0 +1,358 @@ +#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), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avilés (aka mcpbcs) et al. 2026 - 2026. All rights reserved. + * + */ +#endregion + +namespace Krypton.Toolkit.Utilities; + +/// +/// Draws word-wrapped short and long text for . +/// +internal class ViewDrawCheckBoxExtendedContent : ViewDrawContent +{ + #region Instance Fields + + private Rectangle _shortTextRect; + private Rectangle _longTextRect; + private int _subtextSeparatorHeight; + private Font? _subtextFont; + private Color _subtextForeColor; + private bool _skipSubtextDrawing; + + #endregion + + #region Identity + + /// + /// Initialize a new instance of the class. + /// + /// Palette source for the content. + /// Reference to actual content values. + /// Visual orientation of the content. + public ViewDrawCheckBoxExtendedContent(IPaletteContent paletteContent, + IContentValues values, + VisualOrientation orientation) + : base(paletteContent, values, orientation) + { + _subtextForeColor = GlobalStaticVariables.EMPTY_COLOR; + } + + #endregion + + #region Public + + /// + /// Gets or sets the number of pixels separating the main text from the subtext. + /// + public int SubtextSeparatorHeight + { + get => _subtextSeparatorHeight; + set => _subtextSeparatorHeight = value; + } + + /// + /// Gets or sets the font used to render the subtext. + /// + public Font? SubtextFont + { + get => _subtextFont; + set => _subtextFont = value; + } + + /// + /// Gets or sets the foreground color of the subtext. + /// + public Color SubtextForeColor + { + get => _subtextForeColor; + set => _subtextForeColor = value; + } + + /// + /// Gets or sets a value indicating whether subtext rendering is handled externally. + /// + public bool SkipSubtextDrawing + { + get => _skipSubtextDrawing; + set => _skipSubtextDrawing = value; + } + + /// + /// Gets the layout rectangle used for subtext. + /// + public Rectangle SubtextLayoutRect => _longTextRect; + + #endregion + + #region Layout + + /// + public override Size GetPreferredSize(ViewLayoutContext context) + { + if (context.Renderer is null + || GetPalette() is not IPaletteContent paletteContent + || Values is null + || paletteContent.GetContentDraw(State) != InheritBool.True) + { + return Size.Empty; + } + + Padding borderPadding = GetBorderContentPadding(context, paletteContent); + + var availableWidth = context.DisplayRectangle.Width; + if (availableWidth <= 0) + { + availableWidth = 200; + } + else + { + availableWidth = Math.Max(0, availableWidth - borderPadding.Horizontal); + } + + using var g = context.Control?.CreateGraphics(); + if (g is null) + { + return Size.Empty; + } + + Size contentSize = MeasureWrappedContent(g, availableWidth, context.Control!.RightToLeft); + return new Size( + Math.Max(0, contentSize.Width + borderPadding.Horizontal), + Math.Max(0, contentSize.Height + borderPadding.Vertical)); + } + + /// + public override void Layout(ViewLayoutContext context) + { + ClientRectangle = context.DisplayRectangle; + + if (GetPalette() is not IPaletteContent paletteContent + || Values is null + || paletteContent.GetContentDraw(State) != InheritBool.True) + { + _shortTextRect = Rectangle.Empty; + _longTextRect = Rectangle.Empty; + return; + } + + Padding borderPadding = GetBorderContentPadding(context, paletteContent); + Rectangle textArea = ApplyPadding(ClientRectangle, borderPadding); + + using var g = context.Control?.CreateGraphics(); + if (g is null) + { + _shortTextRect = Rectangle.Empty; + _longTextRect = Rectangle.Empty; + return; + } + + RightToLeft rtl = context.Control!.RightToLeft; + string shortText = Values.GetShortText() ?? string.Empty; + string longText = Values.GetLongText() ?? string.Empty; + Font shortFont = paletteContent.GetContentShortTextFont(State)!; + Font longFont = _subtextFont ?? paletteContent.GetContentLongTextFont(State)!; + + var offsetY = textArea.Top; + var textWidth = Math.Max(0, textArea.Width); + + if (shortText.Length > 0) + { + Size shortSize = MeasureWrappedText(g, shortText, shortFont, textWidth, rtl, UseMnemonic); + _shortTextRect = new Rectangle(textArea.Left, offsetY, textWidth, shortSize.Height); + offsetY += shortSize.Height; + } + else + { + _shortTextRect = Rectangle.Empty; + } + + if (longText.Length > 0) + { + if (_shortTextRect.Height > 0) + { + offsetY += _subtextSeparatorHeight; + } + + Size longSize = MeasureWrappedText(g, longText, longFont, textWidth, rtl, false); + _longTextRect = new Rectangle(textArea.Left, offsetY, textWidth, longSize.Height); + } + else + { + _longTextRect = Rectangle.Empty; + } + } + + #endregion + + #region Paint + + /// + public override void RenderBefore(RenderContext context) + { + if (context.Renderer is null + || GetPalette() is not IPaletteContent paletteContent + || Values is null + || paletteContent.GetContentDraw(State) != InheritBool.True) + { + return; + } + + Graphics g = context.Graphics; + RightToLeft rtl = context.Control!.RightToLeft; + + string shortText = Values.GetShortText() ?? string.Empty; + if (shortText.Length > 0 && _shortTextRect.Width > 0 && _shortTextRect.Height > 0) + { + Color shortColor = paletteContent.GetContentShortTextColor1(State); + Font shortFont = paletteContent.GetContentShortTextFont(State)!; + DrawWrappedText(g, shortText, shortFont, shortColor, _shortTextRect, rtl, UseMnemonic); + } + + if (!_skipSubtextDrawing) + { + string longText = Values.GetLongText() ?? string.Empty; + if (longText.Length > 0 && _longTextRect.Width > 0 && _longTextRect.Height > 0) + { + Color longColor = _subtextForeColor.IsEmpty + ? paletteContent.GetContentLongTextColor1(State) + : _subtextForeColor; + Font longFont = _subtextFont ?? paletteContent.GetContentLongTextFont(State)!; + DrawWrappedText(g, longText, longFont, longColor, _longTextRect, rtl, false); + } + } + + if (TestForFocusCues && ShouldDrawFocusCues(context.Control)) + { + Rectangle focusRect = _skipSubtextDrawing && _shortTextRect.Width > 0 + ? _shortTextRect + : Rectangle.Union(_shortTextRect, _longTextRect); + if (focusRect.Width > 0 && focusRect.Height > 0) + { + ControlPaint.DrawFocusRectangle(g, focusRect); + } + } + } + + #endregion + + #region Implementation + + private Padding GetBorderContentPadding(ViewLayoutContext context, IPaletteContent paletteContent) + { + KryptonForm? ownerForm = context.Control as KryptonForm; + return paletteContent.GetBorderContentPadding(ownerForm, State); + } + + private static Rectangle ApplyPadding(Rectangle rect, Padding padding) + { + rect.X += padding.Left; + rect.Y += padding.Top; + rect.Width = Math.Max(0, rect.Width - padding.Horizontal); + rect.Height = Math.Max(0, rect.Height - padding.Vertical); + return rect; + } + + private Size MeasureWrappedContent(Graphics g, int availableWidth, RightToLeft rtl) + { + if (GetPalette() is not IPaletteContent paletteContent) + { + return Size.Empty; + } + + var totalHeight = 0; + var maxWidth = 0; + + string shortText = Values!.GetShortText() ?? string.Empty; + if (shortText.Length > 0) + { + Font shortFont = paletteContent.GetContentShortTextFont(State)!; + Size shortSize = MeasureWrappedText(g, shortText, shortFont, availableWidth, rtl, UseMnemonic); + totalHeight += shortSize.Height; + maxWidth = Math.Max(maxWidth, shortSize.Width); + } + + string longText = Values.GetLongText() ?? string.Empty; + if (longText.Length > 0) + { + if (totalHeight > 0) + { + totalHeight += _subtextSeparatorHeight; + } + + Font longFont = _subtextFont ?? paletteContent.GetContentLongTextFont(State)!; + Size longSize = MeasureWrappedText(g, longText, longFont, availableWidth, rtl, false); + totalHeight += longSize.Height; + maxWidth = Math.Max(maxWidth, longSize.Width); + } + + return new Size(maxWidth, totalHeight); + } + + private static TextFormatFlags CreateTextFormatFlags(RightToLeft rtl, bool useMnemonic) + { + TextFormatFlags flags = TextFormatFlags.WordBreak + | TextFormatFlags.TextBoxControl + | TextFormatFlags.NoPadding + | TextFormatFlags.Top + | TextFormatFlags.Left; + + if (rtl == RightToLeft.Yes) + { + flags |= TextFormatFlags.RightToLeft | TextFormatFlags.Right; + } + + if (!useMnemonic) + { + flags |= TextFormatFlags.NoPrefix; + } + + return flags; + } + + private static Size MeasureWrappedText(Graphics g, + string text, + Font font, + int width, + RightToLeft rtl, + bool useMnemonic) + { + if (width <= 0 || string.IsNullOrEmpty(text)) + { + return Size.Empty; + } + + TextFormatFlags flags = CreateTextFormatFlags(rtl, useMnemonic); + return TextRenderer.MeasureText(g, text, font, new Size(width, int.MaxValue), flags); + } + + private static void DrawWrappedText(Graphics g, + string text, + Font font, + Color color, + Rectangle bounds, + RightToLeft rtl, + bool useMnemonic) + { + if (bounds.Width <= 0 || bounds.Height <= 0 || string.IsNullOrEmpty(text)) + { + return; + } + + TextFormatFlags flags = CreateTextFormatFlags(rtl, useMnemonic); + TextRenderer.DrawText(g, text, font, bounds, color, flags); + } + + private static bool ShouldDrawFocusCues(Control control) + { + PropertyInfo? propertyInfo = typeof(Control).GetProperty("ShowFocusCues", + BindingFlags.Instance | BindingFlags.GetProperty | BindingFlags.NonPublic); + + return propertyInfo is not null && (bool)propertyInfo.GetValue(control, null)!; + } + + #endregion +} diff --git a/Source/Krypton Components/TestForm/CheckBoxExtendedDemo.Designer.cs b/Source/Krypton Components/TestForm/CheckBoxExtendedDemo.Designer.cs new file mode 100644 index 0000000000..23e4c8be36 --- /dev/null +++ b/Source/Krypton Components/TestForm/CheckBoxExtendedDemo.Designer.cs @@ -0,0 +1,129 @@ +namespace TestForm +{ + partial class CheckBoxExtendedDemo + { + private System.ComponentModel.IContainer components = null; + + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + private void InitializeComponent() + { + this.kryptonPanel1 = new Krypton.Toolkit.KryptonPanel(); + this.kwlblInfo = new Krypton.Toolkit.KryptonWrapLabel(); + this.kcbxAgreement = new Krypton.Toolkit.Utilities.KryptonCheckBoxExtended(); + this.kcbxStandard = new Krypton.Toolkit.KryptonCheckBox(); + this.kwlblStatus = new Krypton.Toolkit.KryptonWrapLabel(); + this.kbtnClose = new Krypton.Toolkit.KryptonButton(); + ((System.ComponentModel.ISupportInitialize)(this.kryptonPanel1)).BeginInit(); + this.kryptonPanel1.SuspendLayout(); + this.SuspendLayout(); + // + // kryptonPanel1 + // + this.kryptonPanel1.Controls.Add(this.kwlblInfo); + this.kryptonPanel1.Controls.Add(this.kcbxAgreement); + this.kryptonPanel1.Controls.Add(this.kcbxStandard); + this.kryptonPanel1.Controls.Add(this.kwlblStatus); + this.kryptonPanel1.Controls.Add(this.kbtnClose); + this.kryptonPanel1.Dock = System.Windows.Forms.DockStyle.Fill; + this.kryptonPanel1.Location = new System.Drawing.Point(0, 0); + this.kryptonPanel1.Name = "kryptonPanel1"; + this.kryptonPanel1.Padding = new System.Windows.Forms.Padding(20); + this.kryptonPanel1.Size = new System.Drawing.Size(684, 361); + this.kryptonPanel1.TabIndex = 0; + // + // kwlblInfo + // + this.kwlblInfo.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.kwlblInfo.AutoSize = false; + this.kwlblInfo.Location = new System.Drawing.Point(23, 23); + this.kwlblInfo.Name = "kwlblInfo"; + this.kwlblInfo.Size = new System.Drawing.Size(638, 40); + this.kwlblInfo.Text = "Compare Krypton CheckBox Extended (word-wrapped text, subtext, and optional subtext links) with a standard KryptonCheckBox at the same width."; + // + // kcbxAgreement + // + this.kcbxAgreement.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.kcbxAgreement.AutoSize = true; + this.kcbxAgreement.Location = new System.Drawing.Point(23, 79); + this.kcbxAgreement.MaximumSize = new System.Drawing.Size(0, 0); + this.kcbxAgreement.Name = "kcbxAgreement"; + this.kcbxAgreement.Size = new System.Drawing.Size(638, 0); + this.kcbxAgreement.Values.Subtext = "Please read the full agreement before continuing. Subtext uses a separate font and color."; + this.kcbxAgreement.TabIndex = 0; + this.kcbxAgreement.Text = "I agree to the terms and conditions that govern use of this software product and any associated services."; + this.kcbxAgreement.CheckedChanged += new System.EventHandler(this.kcbxAgreement_CheckedChanged); + // + // kcbxStandard + // + this.kcbxStandard.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.kcbxStandard.AutoSize = false; + this.kcbxStandard.Location = new System.Drawing.Point(23, 200); + this.kcbxStandard.Name = "kcbxStandard"; + this.kcbxStandard.Size = new System.Drawing.Size(638, 25); + this.kcbxStandard.TabIndex = 1; + this.kcbxStandard.Values.Text = "Standard KryptonCheckBox with the same main text (single line)."; + // + // kwlblStatus + // + this.kwlblStatus.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.kwlblStatus.AutoSize = false; + this.kwlblStatus.Location = new System.Drawing.Point(23, 241); + this.kwlblStatus.Name = "kwlblStatus"; + this.kwlblStatus.Size = new System.Drawing.Size(638, 23); + this.kwlblStatus.Text = "Agreement accepted: False"; + // + // kbtnClose + // + this.kbtnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.kbtnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.kbtnClose.Location = new System.Drawing.Point(561, 316); + this.kbtnClose.Name = "kbtnClose"; + this.kbtnClose.Size = new System.Drawing.Size(100, 25); + this.kbtnClose.TabIndex = 2; + this.kbtnClose.Values.DropDownArrowColor = System.Drawing.Color.Empty; + this.kbtnClose.Values.Text = "Close"; + // + // CheckBoxExtendedDemo + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.CancelButton = this.kbtnClose; + this.ClientSize = new System.Drawing.Size(684, 361); + this.Controls.Add(this.kryptonPanel1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "CheckBoxExtendedDemo"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Krypton CheckBox Extended Demo"; + this.Load += new System.EventHandler(this.CheckBoxExtendedDemo_Load); + ((System.ComponentModel.ISupportInitialize)(this.kryptonPanel1)).EndInit(); + this.kryptonPanel1.ResumeLayout(false); + this.ResumeLayout(false); + } + + #endregion + + private Krypton.Toolkit.KryptonPanel kryptonPanel1; + private Krypton.Toolkit.KryptonWrapLabel kwlblInfo; + private Krypton.Toolkit.Utilities.KryptonCheckBoxExtended kcbxAgreement; + private Krypton.Toolkit.KryptonCheckBox kcbxStandard; + private Krypton.Toolkit.KryptonWrapLabel kwlblStatus; + private Krypton.Toolkit.KryptonButton kbtnClose; + } +} diff --git a/Source/Krypton Components/TestForm/CheckBoxExtendedDemo.cs b/Source/Krypton Components/TestForm/CheckBoxExtendedDemo.cs new file mode 100644 index 0000000000..abbe80946d --- /dev/null +++ b/Source/Krypton Components/TestForm/CheckBoxExtendedDemo.cs @@ -0,0 +1,29 @@ +namespace TestForm; + +public partial class CheckBoxExtendedDemo : KryptonForm +{ + public CheckBoxExtendedDemo() + { + InitializeComponent(); + } + + private void CheckBoxExtendedDemo_Load(object sender, EventArgs e) + { + kcbxAgreement.Values.SubtextFont = new Font(Font.FontFamily, Font.SizeInPoints - 1F); + kcbxAgreement.SubtextLinkValues.LinkArea = new LinkArea(18, 14); + kcbxAgreement.SubtextLinkClicked += kcbxAgreement_SubtextLinkClicked; + UpdateStatus(); + } + + private void kcbxAgreement_SubtextLinkClicked(object? sender, LinkLabelLinkClickedEventArgs e) + { + kwlblStatus.Text = @"Agreement link clicked. Open your terms document here."; + } + + private void kcbxAgreement_CheckedChanged(object sender, EventArgs e) => UpdateStatus(); + + private void UpdateStatus() + { + kwlblStatus.Text = $@"Agreement accepted: {kcbxAgreement.Checked}"; + } +} diff --git a/Source/Krypton Components/TestForm/Main.Designer.cs b/Source/Krypton Components/TestForm/Main.Designer.cs index 606f8aef58..dd086dea8a 100644 --- a/Source/Krypton Components/TestForm/Main.Designer.cs +++ b/Source/Krypton Components/TestForm/Main.Designer.cs @@ -65,6 +65,7 @@ private void InitializeComponent() this.progressBar1 = new System.Windows.Forms.ProgressBar(); this.kryptonProgressBar2 = new Krypton.Toolkit.KryptonProgressBar(); this.kryptonButton14 = new Krypton.Toolkit.KryptonButton(); + this.kbtnCheckBoxExtendedDemo = new Krypton.Toolkit.KryptonButton(); this.kryptonButton13 = new Krypton.Toolkit.KryptonButton(); this.kbtnDialogs = new Krypton.Toolkit.KryptonButton(); this.kryptonThemeComboBox1 = new Krypton.Toolkit.KryptonThemeComboBox(); @@ -166,6 +167,7 @@ private void InitializeComponent() this.kryptonPanel1.Controls.Add(this.progressBar1); this.kryptonPanel1.Controls.Add(this.kryptonProgressBar2); this.kryptonPanel1.Controls.Add(this.kryptonButton14); + this.kryptonPanel1.Controls.Add(this.kbtnCheckBoxExtendedDemo); this.kryptonPanel1.Controls.Add(this.kryptonButton13); this.kryptonPanel1.Controls.Add(this.kbtnDialogs); this.kryptonPanel1.Controls.Add(this.kryptonThemeComboBox1); @@ -466,6 +468,16 @@ private void InitializeComponent() this.kryptonButton14.Values.Text = "kryptonButton14"; this.kryptonButton14.Click += new System.EventHandler(this.kryptonButton14_Click); // + // kbtnCheckBoxExtendedDemo + // + this.kbtnCheckBoxExtendedDemo.Location = new System.Drawing.Point(153, 434); + this.kbtnCheckBoxExtendedDemo.Name = "kbtnCheckBoxExtendedDemo"; + this.kbtnCheckBoxExtendedDemo.Size = new System.Drawing.Size(134, 25); + this.kbtnCheckBoxExtendedDemo.TabIndex = 86; + this.kbtnCheckBoxExtendedDemo.Values.DropDownArrowColor = System.Drawing.Color.Empty; + this.kbtnCheckBoxExtendedDemo.Values.Text = "CheckBox Extended"; + this.kbtnCheckBoxExtendedDemo.Click += new System.EventHandler(this.kbtnCheckBoxExtendedDemo_Click); + // // kryptonButton13 // this.kryptonButton13.Location = new System.Drawing.Point(13, 434); @@ -1050,6 +1062,7 @@ private void InitializeComponent() private Krypton.Toolkit.KryptonThemeComboBox kryptonThemeComboBox1; private KryptonButton kbtnDialogs; private KryptonButton kryptonButton13; + private KryptonButton kbtnCheckBoxExtendedDemo; private KryptonButton kryptonButton14; private ProgressBar progressBar1; private KryptonProgressBar kryptonProgressBar2; diff --git a/Source/Krypton Components/TestForm/Main.cs b/Source/Krypton Components/TestForm/Main.cs index 971dafd7ac..aee407e715 100644 --- a/Source/Krypton Components/TestForm/Main.cs +++ b/Source/Krypton Components/TestForm/Main.cs @@ -1,8 +1,8 @@ -#region BSD License +#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), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avilés (aka mcpbcs) et al. 2024 - 2026. All rights reserved. + * Modifications by Peter Wagner (aka Wagnerp), Simon Coghlan (aka Smurf-IV), Giduac, Ahmed Abdelhameed, tobitege, KamaniAR, Lesandro Gotardo (aka lesandrog), Jorge A. Avil�s (aka mcpbcs) et al. 2024 - 2026. All rights reserved. * */ #endregion @@ -365,6 +365,11 @@ private void kryptonButton13_Click(object sender, EventArgs e) new CheckBoxStyleExamples().Show(); } + private void kbtnCheckBoxExtendedDemo_Click(object sender, EventArgs e) + { + new CheckBoxExtendedDemo().ShowDialog(this); + } + private void kryptonButton14_Click(object sender, EventArgs e) { KryptonMessageBox.Show("question?", "title", KryptonMessageBoxButtons.YesNo, diff --git a/Source/Krypton Components/TestForm/StartScreen.cs b/Source/Krypton Components/TestForm/StartScreen.cs index b86ad24814..edf87ee101 100644 --- a/Source/Krypton Components/TestForm/StartScreen.cs +++ b/Source/Krypton Components/TestForm/StartScreen.cs @@ -1,4 +1,4 @@ -#region BSD License +#region BSD License /* * * New BSD 3-Clause License (https://github.com/Krypton-Suite/Standard-Toolkit/blob/master/LICENSE) @@ -64,6 +64,7 @@ private void AddButtons() CreateButton("Button Text Tracking", "Demonstrates alternate text color for tracking (hover) state on KryptonButton, KryptonCheckButton, KryptonColorButton and other controls (Issue #1326). Improves readability in dark themes."); CreateButton("Text List Item", "Demonstrates independent list and tree item text color via SchemeBaseColors.TextListItem (Issue #880). TextLabelControl, TextListItem, and TextButtonNormal each drive labels, tree/list controls, and buttons separately. Contrast preset, per-slot color pickers, live scheme readout, and theme switching."); CreateButton("Buttons Test", "All the buttons you want to test."); + CreateButton("CheckBox Extended", "Issue #3833: KryptonCheckBoxExtended in Krypton.Toolkit.Utilities for word-wrapped check box text and optional subtext. Compare with standard KryptonCheckBox at the same width."); CreateButton("KryptonColorButton Custom Colours", "Comprehensive demo of KryptonColorButton custom colours (Issue #776): CustomColors, MaxCustomColors, and visibility. Only 10 colours, or custom + theme + standard, or cap display count."); CreateButton("KryptonComboBoxUserControl", "Demo for Issue #3443: a ComboBox-style control whose drop-down hosts any UserControl. Shows tree-picker, grid-picker and a plain (non-contract) UserControl scenario."); CreateButton("KryptonTreeComboBox", "Demo for Issue #3444: ComboBox-style control with a grouped tree drop-down (leaf/full path, breadcrumb, and parent-node selection)."); @@ -76,8 +77,8 @@ private void AddButtons() CreateButton("Bug 3342 Multiline TextBox Flicker", "Demo for issue #3342: multiline KryptonTextBox text flicker while resizing. Includes manual resize steps and an automated stress-resize toggle."); CreateButton("Bug 3786 Control Box Order", "Issue #3786: comprehensive demo for KryptonForm control box order (LTR/RTL), macOS traffic lights (red-yellow-green), RTL left-side placement, FormTrafficLightEdge, and live PASS/FAIL diagnostics."); CreateButton("Bug 3367 TextBox ButtonSpec Hover", "Demo for issue #3367: ButtonSpec hover flicker on KryptonTextBox/KryptonMaskedTextBox, including ImageStates.ImageNormal without the Image property."); - CreateButton("Bug 3382 CueHint line artifacts", "Demo for issue #3382: KryptonTextBox CueHint with TextH Near and mixed cue/content fonts — verify no stray top/left lines; cue remains vertically centered."); - CreateButton("Bug 3383 KryptonButton hover rounding vs OverrideFocus", "Demo for issue #3383: large StateCommon rounding with different StateTracking rounding and OverrideFocus rounding — Tab to focus, then hover (left repro vs right matched control). Corner fill and stroke should align after the palette merge fix."); + CreateButton("Bug 3382 CueHint line artifacts", "Demo for issue #3382: KryptonTextBox CueHint with TextH Near and mixed cue/content fonts � verify no stray top/left lines; cue remains vertically centered."); + CreateButton("Bug 3383 KryptonButton hover rounding vs OverrideFocus", "Demo for issue #3383: large StateCommon rounding with different StateTracking rounding and OverrideFocus rounding � Tab to focus, then hover (left repro vs right matched control). Corner fill and stroke should align after the palette merge fix."); CreateButton("Bug 3451 HeaderGroup panel parenting", "Demo for issue #3451: add child controls to KryptonHeaderGroup, KryptonGroup, and KryptonGroupBox via the internal Panel at design time. Open in the WinForms Designer and drag controls into each content area; verify no ReadOnly controls collection error."); CreateButton("Bug 3661 Context Menu Overflow", "Issue #3661: KryptonContextMenu with more items than fit on screen shows Scroll Up/Scroll Down rows, mouse-wheel scrolling, and keyboard navigation past visible items. Long list, mixed content, programmatic placement, and keyboard-open scenarios."); CreateButton("Bug 3381 KryptonButton Rounded Text Centering", "Demo for issue #3381: vertical and horizontal text centering inside heavily rounded KryptonButton (wide pill, Cyrillic, font metrics). Includes side-by-side stress, tall narrow capsule, low-rounding baseline, and live rounding / TextV / font / height controls.");