Skip to content

Enhance focus rectangle support and rendering for buttons#3256

Open
julesbobb wants to merge 4 commits intomRemoteNG:v1.78.2-devfrom
julesbobb:enhancement/3265-Add-visual-focus-indication-to-CTaskDialog-buttons
Open

Enhance focus rectangle support and rendering for buttons#3256
julesbobb wants to merge 4 commits intomRemoteNG:v1.78.2-devfrom
julesbobb:enhancement/3265-Add-visual-focus-indication-to-CTaskDialog-buttons

Conversation

@julesbobb
Copy link
Copy Markdown
Contributor

This pull request introduces improvements to the focus handling and visual feedback for custom button controls in the UI, ensuring that buttons display a focus rectangle when selected and that focus assignment is more robust in dialog forms. Additionally, it updates the build number and version information for the application.

UI/UX Improvements:

  • Added logic to both mrngButton and CommandButton controls to visually indicate when the button has focus by drawing a focus rectangle, and implemented proper focus state management through overrides of OnGotFocus and OnLostFocus. [1] [2] [3] [4] [5] [6]

  • Improved focus assignment in frmTaskDialog.cs by ensuring that the default button receives focus if no other control is set, across all dialog button configurations. [1] [2] [3] [4]

Versioning:

  • Updated the application's build number and version information in AssemblyInfo.cs to reflect the latest nightly build. [1] [2]

Jules Bobb added 2 commits April 6, 2026 11:53
mrngButton and CommandButton now visually indicate focus by drawing a focus rectangle when focused and enabled. Added OnGotFocus/OnLostFocus handlers to manage focus state. Also, set the default focus control in frmTaskDialog for each button configuration. Updated build number to 3468.
The focus rectangle is now drawn slightly inset from the button's border, rather than covering the entire ClientRectangle. This adjustment enhances the visual appearance by preventing overlap with the button's border.
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add visual focus indication to custom button controls

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add focus rectangle rendering to mrngButton and CommandButton controls
• Implement OnGotFocus/OnLostFocus handlers for focus state management
• Ensure default button receives focus in all dialog configurations
• Update build number from 3465 to 3468
Diagram
flowchart LR
  A["Button Controls<br/>mrngButton & CommandButton"] -->|"Add focus state tracking"| B["_hasFocus property"]
  B -->|"OnGotFocus/OnLostFocus"| C["Focus state management"]
  C -->|"OnPaint override"| D["Draw focus rectangle"]
  E["frmTaskDialog"] -->|"Set default focus"| F["Button configurations"]
  F -->|"All button types"| G["Improved focus assignment"]
  H["AssemblyInfo.cs"] -->|"Update version"| I["Build 3468"]
Loading

Grey Divider

File Changes

1. mRemoteNG/Properties/AssemblyInfo.cs ⚙️ Configuration changes +4/-4

Update version and build number

• Updated build number from 3465 to 3468
• Updated AssemblyVersion from 1.78.2.3465 to 1.78.2.3468
• Updated AssemblyFileVersion from 1.78.2.3465 to 1.78.2.3468
• Updated AssemblyInformationalVersion to reflect nightly build 3468

mRemoteNG/Properties/AssemblyInfo.cs


2. mRemoteNG/UI/Controls/mrngButton.cs ✨ Enhancement +31/-1

Add focus rectangle support to mrngButton

• Added _hasFocus boolean field to track focus state
• Added focus rectangle drawing in OnPaint method with 2-pixel inset from edges
• Implemented OnGotFocus override to set focus state and invalidate control
• Implemented OnLostFocus override to clear focus state and invalidate control
• Added System namespace import for EventArgs

mRemoteNG/UI/Controls/mrngButton.cs


3. mRemoteNG/UI/TaskDialog/CommandButton.cs ✨ Enhancement +24/-0

Add focus rectangle support to CommandButton

• Added _hasFocus boolean field to track focus state
• Added focus rectangle drawing in OnPaint method with 2-pixel inset from button rectangle
• Implemented OnGotFocus override to set focus state and invalidate control
• Implemented OnLostFocus override to clear focus state and invalidate control

mRemoteNG/UI/TaskDialog/CommandButton.cs


View more (1)
4. mRemoteNG/UI/TaskDialog/frmTaskDialog.cs ✨ Enhancement +12/-0

Ensure default button receives focus in dialogs

• Added focus assignment logic to YesNo button configuration to set default focus to bt2
• Added focus assignment logic to YesNoCancel button configuration to set default focus to bt1
• Added focus assignment logic to OkCancel button configuration to set default focus to bt2
• Added focus assignment logic to Ok button configuration to set default focus to bt3
• Added focus assignment logic to Close button configuration to set default focus to bt3
• Added focus assignment logic to Cancel button configuration to set default focus to bt3

mRemoteNG/UI/TaskDialog/frmTaskDialog.cs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 6, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. Stale focus target kept🐞 Bug ≡ Correctness
Description
frmTaskDialog.BuildForm() rebuilds (including disposing dynamic CommandButtons) but never resets
_focusControl, while the PR change only assigns _focusControl when it is null. This can keep
_focusControl pointing at a removed/disposed control or at a control that is no longer the
appropriate default for the current layout, so frmTaskDialog_Shown may not focus a valid default
control.
Code

mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[R300-301]

+                    if (_focusControl == null)
+                        _focusControl = bt2;
Evidence
BuildForm explicitly supports idempotent rebuilds and disposes previously created dynamic controls,
but it does not clear _focusControl during that rebuild. The PR’s new guard (`if (_focusControl ==
null) _focusControl = ...`) means a non-null (potentially stale) _focusControl will prevent
reselecting a valid default, yet the dialog later attempts to focus whatever _focusControl
references.

mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[150-171]
mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[259-287]
mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[289-358]
mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[495-500]
mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[547-571]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`frmTaskDialog.BuildForm()` rebuilds the dialog UI (including disposing and recreating dynamic command buttons), but `_focusControl` is not reset. With the newly-added `if (_focusControl == null)` guards, a previously-assigned `_focusControl` can block selecting a valid default focus target for the current layout, and `_focusControl?.Focus()` can end up focusing nothing.
### Issue Context
- `BuildForm()` is called more than once (e.g., via `OnDpiChanged`).
- `BuildForm()` disposes dynamic controls and recreates them.
- `_focusControl` is used later in `frmTaskDialog_Shown` to apply initial focus.
### Fix approach
1. At the start of `BuildForm()`, reset `_focusControl = null;`.
2. After creating dynamic controls and configuring `Buttons`, set `_focusControl` deterministically to a valid, focusable control:
- Prefer a valid default CommandButton when applicable.
- Otherwise fall back to `AcceptButton` (cast to `Control`) or the first visible+enabled button.
3. Before focusing in `frmTaskDialog_Shown`, optionally guard with `if (_focusControl != null && !_focusControl.IsDisposed && _focusControl.CanFocus)`.
### Fix Focus Areas
- mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[150-171]
- mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[259-287]
- mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[289-358]
- mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[547-571]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

2. Focus state duplicated🐞 Bug ⚙ Maintainability
Description
MrngButton and CommandButton store focus in a private _hasFocus field and depend on focus events
to keep it in sync with real control focus. This adds state that can drift and is harder to maintain
than using built-in Focused (and optionally ShowFocusCues) directly in OnPaint.
Code

mRemoteNG/UI/Controls/mrngButton.cs[R159-174]

+        protected override void OnGotFocus(EventArgs e)
+        {
+            _hasFocus = true;
+            Invalidate();
+            base.OnGotFocus(e);
+        }
+
+        ///<summary>
+        /// Called when the button loses focus
+        /// </summary>
+        protected override void OnLostFocus(EventArgs e)
+        {
+            _hasFocus = false;
+            Invalidate();
+            base.OnLostFocus(e);
+        }
Evidence
Both controls introduced a _hasFocus field, set it in OnGotFocus/OnLostFocus, and then consult
it during painting to decide whether to render the focus rectangle. This duplicates information
already tracked by WinForms and increases the surface area for focus/paint bugs.

mRemoteNG/UI/Controls/mrngButton.cs[28-36]
mRemoteNG/UI/Controls/mrngButton.cs[80-143]
mRemoteNG/UI/Controls/mrngButton.cs[156-174]
mRemoteNG/UI/TaskDialog/CommandButton.cs[33-35]
mRemoteNG/UI/TaskDialog/CommandButton.cs[168-257]
mRemoteNG/UI/TaskDialog/CommandButton.cs[307-321]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Both `MrngButton` and `CommandButton` maintain a separate `_hasFocus` field to decide whether to draw the focus rectangle. This duplicates the WinForms focus state and adds extra code paths to keep consistent.
### Issue Context
- `_hasFocus` is updated in `OnGotFocus`/`OnLostFocus`.
- `_hasFocus` is read in `OnPaint` to draw focus rectangle.
### Suggested change
- Remove `_hasFocus` and the overrides that only toggle it.
- In `OnPaint`, replace `_hasFocus` with `Focused` (and consider `ShowFocusCues` if you want OS-consistent focus-cue behavior).
### Fix Focus Areas
- mRemoteNG/UI/Controls/mrngButton.cs[34-36]
- mRemoteNG/UI/Controls/mrngButton.cs[137-142]
- mRemoteNG/UI/Controls/mrngButton.cs[156-174]
- mRemoteNG/UI/TaskDialog/CommandButton.cs[33-35]
- mRemoteNG/UI/TaskDialog/CommandButton.cs[251-256]
- mRemoteNG/UI/TaskDialog/CommandButton.cs[307-321]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread mRemoteNG/UI/TaskDialog/frmTaskDialog.cs Outdated
Jules Bobb added 2 commits April 6, 2026 15:26
Reset and reassign _focusControl on dialog rebuilds to avoid stale references. Add safety checks before focusing to prevent exceptions and ensure the correct button receives focus.
Removed private _hasFocus fields and related focus event overrides from mrngButton and CommandButton. Now use the built-in Focused property to determine focus state when drawing focus rectangles, simplifying the code and relying on standard control behavior.
@julesbobb
Copy link
Copy Markdown
Contributor Author

Removed the duplicate _hasFocus field tracking from both button classes and replaced it with the built-in Focused property provided by the WinForms Button base class

@julesbobb julesbobb closed this Apr 13, 2026
@julesbobb julesbobb reopened this Apr 13, 2026
@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add visual focus indication to custom button controls

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add focus rectangle rendering to custom button controls
• Improve focus assignment in TaskDialog for all button configurations
• Add safety checks before focusing to prevent exceptions
• Update build number to 3468
Diagram
flowchart LR
  A["mrngButton & CommandButton"] -->|"Draw focus rectangle"| B["OnPaint override"]
  C["frmTaskDialog"] -->|"Reset _focusControl"| D["BuildForm method"]
  D -->|"Assign default button"| E["Each button configuration"]
  E -->|"Focus with safety checks"| F["frmTaskDialog_Shown"]
Loading

Grey Divider

File Changes

1. mRemoteNG/Properties/AssemblyInfo.cs ⚙️ Configuration changes +4/-4

Update version and build number

• Updated build number from 3465 to 3468
• Updated AssemblyVersion to 1.78.2.3468
• Updated AssemblyFileVersion to 1.78.2.3468
• Updated AssemblyInformationalVersion to reflect nightly build 3468

mRemoteNG/Properties/AssemblyInfo.cs


2. mRemoteNG/UI/Controls/mrngButton.cs ✨ Enhancement +9/-1

Add focus rectangle rendering to mrngButton

• Added System namespace import
• Added focus rectangle drawing in OnPaint method when button has focus and is enabled
• Focus rectangle is inset 2 pixels from button edges for better visual appearance

mRemoteNG/UI/Controls/mrngButton.cs


3. mRemoteNG/UI/TaskDialog/CommandButton.cs ✨ Enhancement +7/-0

Add focus rectangle rendering to CommandButton

• Added focus rectangle drawing in OnPaint method when button has focus and is enabled
• Focus rectangle is inset 2 pixels from the button's content rectangle

mRemoteNG/UI/TaskDialog/CommandButton.cs


View more (1)
4. mRemoteNG/UI/TaskDialog/frmTaskDialog.cs ✨ Enhancement +13/-1

Improve default focus handling in TaskDialog

• Reset _focusControl to null at start of BuildForm to prevent stale references
• Assign _focusControl to the default button for each button configuration (YesNo, YesNoCancel,
 OkCancel, Ok, Close, Cancel)
• Add safety checks in frmTaskDialog_Shown before focusing to verify control is not disposed and can
 receive focus

mRemoteNG/UI/TaskDialog/frmTaskDialog.cs


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Apr 13, 2026

Code Review by Qodo

🐞 Bugs (1)   📘 Rule violations (0)   📎 Requirement gaps (0)   🖥 UI issues (0)   🎨 UX Issues (0)
🐞\ ≡ Correctness (1) ⭐ New (1)

Grey Divider


Action required

1. Stale focus target kept🐞
Description
frmTaskDialog.BuildForm() rebuilds (including disposing dynamic CommandButtons) but never resets
_focusControl, while the PR change only assigns _focusControl when it is null. This can keep
_focusControl pointing at a removed/disposed control or at a control that is no longer the
appropriate default for the current layout, so frmTaskDialog_Shown may not focus a valid default
control.
Code

mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[R300-301]

+                    if (_focusControl == null)
+                        _focusControl = bt2;
Evidence
BuildForm explicitly supports idempotent rebuilds and disposes previously created dynamic controls,
but it does not clear _focusControl during that rebuild. The PR’s new guard (`if (_focusControl ==
null) _focusControl = ...`) means a non-null (potentially stale) _focusControl will prevent
reselecting a valid default, yet the dialog later attempts to focus whatever _focusControl
references.

mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[150-171]
mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[259-287]
mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[289-358]
mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[495-500]
mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[547-571]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`frmTaskDialog.BuildForm()` rebuilds the dialog UI (including disposing and recreating dynamic command buttons), but `_focusControl` is not reset. With the newly-added `if (_focusControl == null)` guards, a previously-assigned `_focusControl` can block selecting a valid default focus target for the current layout, and `_focusControl?.Focus()` can end up focusing nothing.
### Issue Context
- `BuildForm()` is called more than once (e.g., via `OnDpiChanged`).
- `BuildForm()` disposes dynamic controls and recreates them.
- `_focusControl` is used later in `frmTaskDialog_Shown` to apply initial focus.
### Fix approach
1. At the start of `BuildForm()`, reset `_focusControl = null;`.
2. After creating dynamic controls and configuring `Buttons`, set `_focusControl` deterministically to a valid, focusable control:
- Prefer a valid default CommandButton when applicable.
- Otherwise fall back to `AcceptButton` (cast to `Control`) or the first visible+enabled button.
3. Before focusing in `frmTaskDialog_Shown`, optionally guard with `if (_focusControl != null && !_focusControl.IsDisposed && _focusControl.CanFocus)`.
### Fix Focus Areas
- mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[150-171]
- mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[259-287]
- mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[289-358]
- mRemoteNG/UI/TaskDialog/frmTaskDialog.cs[547-571]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. Focus cue can be stale 🐞
Description
MrngButton draws the focus rectangle only during OnPaint, but it never invalidates on focus
gain/loss, so keyboard/programmatic focus changes may not update the visual focus indicator. This
can leave the focus rectangle missing or not cleared until another repaint happens.
Code

mRemoteNG/UI/Controls/mrngButton.cs[R135-140]

+            // Draw focus rectangle if button has focus
+            if (Focused && Enabled)
+            {
+                Rectangle focusRect = new(2, 2, Width - 4, Height - 4);
+                ControlPaint.DrawFocusRectangle(e.Graphics, focusRect);
+            }
Evidence
The focus indicator is conditional on Focused inside OnPaint, but the control only calls Invalidate
from mouse handlers; there are no focus-event invalidations in the class, so focus transitions may
not trigger a repaint of the new focus rectangle.

mRemoteNG/UI/Controls/mrngButton.cs[78-141]
mRemoteNG/UI/Controls/mrngButton.cs[45-68]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`MrngButton` draws a focus rectangle in `OnPaint` when `Focused` is true, but it doesn't trigger a repaint when focus is gained/lost. This can cause the focus rectangle to appear/disappear only after unrelated repaints.

### Issue Context
The control already calls `Invalidate()` for mouse state changes; focus state changes should also invalidate.

### Fix Focus Areas
- mRemoteNG/UI/Controls/mrngButton.cs[28-141]

### Suggested fix
- Override `OnGotFocus` and `OnLostFocus`.
- Call `base.OnGotFocus(e)` / `base.OnLostFocus(e)` and then `Invalidate()`.
- (Optional hardening) build the focus rect from `ClientRectangle` and clamp/guard against very small sizes before drawing.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

3. Focus state duplicated🐞
Description
MrngButton and CommandButton store focus in a private _hasFocus field and depend on focus events
to keep it in sync with real control focus. This adds state that can drift and is harder to maintain
than using built-in Focused (and optionally ShowFocusCues) directly in OnPaint.
Code

mRemoteNG/UI/Controls/mrngButton.cs[R159-174]

+        protected override void OnGotFocus(EventArgs e)
+        {
+            _hasFocus = true;
+            Invalidate();
+            base.OnGotFocus(e);
+        }
+
+        ///<summary>
+        /// Called when the button loses focus
+        /// </summary>
+        protected override void OnLostFocus(EventArgs e)
+        {
+            _hasFocus = false;
+            Invalidate();
+            base.OnLostFocus(e);
+        }
Evidence
Both controls introduced a _hasFocus field, set it in OnGotFocus/OnLostFocus, and then consult
it during painting to decide whether to render the focus rectangle. This duplicates information
already tracked by WinForms and increases the surface area for focus/paint bugs.

mRemoteNG/UI/Controls/mrngButton.cs[28-36]
mRemoteNG/UI/Controls/mrngButton.cs[80-143]
mRemoteNG/UI/Controls/mrngButton.cs[156-174]
mRemoteNG/UI/TaskDialog/CommandButton.cs[33-35]
mRemoteNG/UI/TaskDialog/CommandButton.cs[168-257]
mRemoteNG/UI/TaskDialog/CommandButton.cs[307-321]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Both `MrngButton` and `CommandButton` maintain a separate `_hasFocus` field to decide whether to draw the focus rectangle. This duplicates the WinForms focus state and adds extra code paths to keep consistent.
### Issue Context
- `_hasFocus` is updated in `OnGotFocus`/`OnLostFocus`.
- `_hasFocus` is read in `OnPaint` to draw focus rectangle.
### Suggested change
- Remove `_hasFocus` and the overrides that only toggle it.
- In `OnPaint`, replace `_hasFocus` with `Focused` (and consider `ShowFocusCues` if you want OS-consistent focus-cue behavior).
### Fix Focus Areas
- mRemoteNG/UI/Controls/mrngButton.cs[34-36]
- mRemoteNG/UI/Controls/mrngButton.cs[137-142]
- mRemoteNG/UI/Controls/mrngButton.cs[156-174]
- mRemoteNG/UI/TaskDialog/CommandButton.cs[33-35]
- mRemoteNG/UI/TaskDialog/CommandButton.cs[251-256]
- mRemoteNG/UI/TaskDialog/CommandButton.cs[307-321]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant