Skip to content

fix: apply the colour picked in the property grid Color/TabColor fields - #156

Open
jafin wants to merge 2 commits into
robertpopa22:mainfrom
jafin:fix/color-picker-string-property
Open

fix: apply the colour picked in the property grid Color/TabColor fields#156
jafin wants to merge 2 commits into
robertpopa22:mainfrom
jafin:fix/color-picker-string-property

Conversation

@jafin

@jafin jafin commented Aug 5, 2026

Copy link
Copy Markdown

Problem

Setting Color or Tab Color on a connection via the property-grid picker fails with Property value is not valid / Object of type 'System.Drawing.Color' cannot be converted to type 'System.String', and the colour is never applied.

Root cause

Both properties are string, but they were annotated with the stock System.Drawing.Design.ColorEditor, which returns a System.Drawing.Color. The PropertyGrid commits an editor's return value straight through PropertyDescriptor.SetValue and never runs the TypeConverter on that path, so the assignment throws.

MiscTools.TabColorConverter already handled Colorstring, but only the text-entry path goes through it — which is why the error survived that earlier fix.

Changes

  • ColorStringEditor (new) — wraps ColorEditor for string-backed colour properties: feeds the editor a Color, translates the picked colour back into the stored string form. Also restores the swatch preview, which was blank because ColorEditor cannot paint a string value.
  • AbstractConnectionRecord.Color / .TabColor now use the new editor.
  • TabColorConverter.GetStandardValues returns strings instead of Color objects — picking an entry from the dropdown list failed in exactly the same way.
  • TabColorConverter.ConvertFrom(Color.Empty) now yields "" instead of "#00000000", so clearing a colour leaves the property unset.

Verification

Full MSBuild build succeeds.

Note for reviewers: the NUnit suite could not be executed on my machine — every group reports NUnit couldn't run the N discovered tests: Only supported on Windows10.0.26100.0, because mRemoteNGTests.csproj sets SupportedOSPlatformVersion 10.0.26100.0 while the host is Windows 10.0.22631. That block is pre-existing and assembly-wide (it hits untouched groups such as Config and Security identically) and unrelated to this change, so I left the test project's target platform alone. CI should run the suite normally.

To verify behaviour regardless, I ran the equivalent assertions against the built mRemoteNG.dll from a standalone harness — all pass:

  • string round-trip through the editor for "Red", "#804020", "" and null
  • both properties resolve to ColorStringEditor
  • SetValue with the editor's output stores "Purple" instead of throwing
  • standard values are all strings

New tests added: mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs (non-interactive — no IWindowsFormsEditorService is supplied, so no dialog is shown) plus an empty-colour case in TabColorConverterTests.

Picking a colour for a connection's Color or TabColor failed with
"Property value is not valid" / "Object of type 'System.Drawing.Color'
cannot be converted to type 'System.String'", and the colour was not set.

Both properties are strings but were annotated with the stock ColorEditor,
which returns a System.Drawing.Color. The PropertyGrid commits an editor's
return value straight through PropertyDescriptor.SetValue and never runs the
TypeConverter on that path, so the assignment threw. TabColorConverter only
covered the text-entry path, which is why the error survived it.

- Add ColorStringEditor, wrapping ColorEditor for string-backed colour
  properties: it feeds the editor a Color and translates the picked colour
  back to the stored string form. It also restores the swatch preview, which
  was blank because ColorEditor cannot paint a string.
- Point Color and TabColor at the new editor.
- Return the standard-values dropdown entries as strings; picking one from
  the list failed in exactly the same way.
- Convert Color.Empty to an empty string instead of "#00000000" so clearing
  a colour leaves the property unset.
Copilot AI lite review requested due to automatic review settings August 5, 2026 11:43
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Fix PropertyGrid color picker for string-backed Color/TabColor properties

🐞 Bug fix 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Fix PropertyGrid color picking for string-backed connection Color/TabColor fields
• Ensure dropdown standard colors commit as strings and clearing color unsets the value
• Add unit tests covering editor round-trips and converter empty-color behavior
Diagram

graph TD
  PG(["WinForms PropertyGrid"]) --> CSE(["ColorStringEditor (UITypeEditor)"]) --> TCC(["TabColorConverter"]) --> CONN["Connection Color/TabColor (string)"]
  TESTS["NUnit tests"] --> CSE(["ColorStringEditor (UITypeEditor)"]) --> TCC(["TabColorConverter"])
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Change Color/TabColor property types to System.Drawing.Color
  • ➕ Avoids string/editor mismatch entirely
  • ➕ PropertyGrid and ColorEditor work naturally with Color values
  • ➖ Potentially breaking change for persistence/serialization and existing config formats
  • ➖ May require wider migration code and compatibility handling
2. Write a dedicated UITypeEditor (not inheriting ColorEditor)
  • ➕ Full control over editing/painting and return type (string)
  • ➕ Can reduce dependency on ColorEditor quirks
  • ➖ More code and higher maintenance than leveraging ColorEditor
  • ➖ Must re-implement or re-host standard color picker UX
3. Use a proxy property (Color) mapped to backing string
  • ➕ Keeps storage as string while exposing a Color-typed surface to the PropertyGrid
  • ➕ Reuses stock ColorEditor without wrapping
  • ➖ Adds extra API surface area and indirection
  • ➖ Requires careful hiding/browsability to avoid duplicate properties

Recommendation: The chosen approach (ColorStringEditor wrapping ColorEditor and translating through the existing TabColorConverter) is the best balance of minimal surface-area change and correct PropertyGrid behavior. It preserves the string storage contract, fixes both dialog and dropdown commit paths, and restores swatch painting without a broader serialization or API migration.

Files changed (5) +131 / -4

Enhancement (1) +46 / -0
ColorStringEditor.csAdd string-aware ColorEditor wrapper for PropertyGrid +46/-0

Add string-aware ColorEditor wrapper for PropertyGrid

• Introduces ColorStringEditor, inheriting from ColorEditor, to translate string values to Color for editing/painting and translate the edited Color back to the stored string format via TabColorConverter. Handles unsupported values by falling back to Color.Empty.

mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs

Bug fix (2) +12 / -4
AbstractConnectionRecord.csSwitch Color/TabColor properties to use ColorStringEditor +2/-2

Switch Color/TabColor properties to use ColorStringEditor

• Replaces the stock ColorEditor attribute on the string-backed Color and TabColor properties with the new ColorStringEditor. This ensures the PropertyGrid editor returns a string value that can be assigned without conversion exceptions.

mRemoteNG/Connection/AbstractConnectionRecord.cs

MiscTools.csFix TabColorConverter empty-color handling and standard values typing +10/-2

Fix TabColorConverter empty-color handling and standard values typing

• Updates conversion so Color.Empty maps to an empty string to represent 'unset'. Adjusts GetStandardValues to return string values (color names) rather than Color instances so PropertyGrid dropdown selection can be committed to string properties.

mRemoteNG/Tools/MiscTools.cs

Tests (2) +73 / -0
TabColorConverterTests.csAdd test for converting Color.Empty to empty string +7/-0

Add test for converting Color.Empty to empty string

• Adds coverage ensuring ConvertFrom(Color.Empty) yields string.Empty, matching the 'unset color' semantics used by the UI/editor flow.

mRemoteNGTests/Tools/TabColorConverterTests.cs

ColorStringEditorTests.csAdd unit tests for ColorStringEditor and standard values contract +66/-0

Add unit tests for ColorStringEditor and standard values contract

• Adds non-interactive STA tests that validate EditValue round-trips for common string inputs and null, verifies the Color/TabColor properties resolve to ColorStringEditor, and asserts TabColorConverter standard values are strings to support dropdown commit.

mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes PropertyGrid color editing for string-backed connection color fields in mRemoteNG by ensuring the UI editor and standard-value dropdowns commit string values (not System.Drawing.Color), preventing assignment exceptions and allowing the selected color to be applied correctly.

Changes:

  • Added ColorStringEditor to bridge between ColorEditor (Color) and string-backed properties (string).
  • Updated AbstractConnectionRecord.Color and .TabColor to use the new editor.
  • Adjusted TabColorConverter to return string standard values and to map Color.Empty to ""; added NUnit coverage.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs New UITypeEditor wrapper converting between string storage and ColorEditor behavior.
mRemoteNG/Connection/AbstractConnectionRecord.cs Swapped PropertyGrid editor for Color / TabColor to the new string-aware editor.
mRemoteNG/Tools/MiscTools.cs Updated TabColorConverter empty-color handling and standard values to be strings.
mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs Added tests covering editor usage and dropdown standard value typing.
mRemoteNGTests/Tools/TabColorConverterTests.cs Added regression test for Color.Empty"".

Comment on lines +20 to +24
public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value)
{
object? editedValue = base.EditValue(context, provider, ToColor(value));
return editedValue is Color color ? Converter.ConvertFrom(color) : value;
}
@qodo-code-review

qodo-code-review Bot commented Aug 5, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (3) 📜 Skill insights (0)

Grey Divider


Remediation recommended

1. Cancel rewrites color string ✓ Resolved 🐞 Bug ≡ Correctness
Description
ColorStringEditor.EditValue always serializes the ColorEditor result back to a string, so a
cancel/no-op can still change the stored value (e.g., normalize casing/hex or even clear invalid
existing strings to ""). This can silently modify persisted connection settings when the user didn’t
intend to change the color.
Code

mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs[R20-24]

+        public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value)
+        {
+            object? editedValue = base.EditValue(context, provider, ToColor(value));
+            return editedValue is Color color ? Converter.ConvertFrom(color) : value;
+        }
Evidence
The editor always converts the base ColorEditor result back into a string, even when the returned
Color is just the original Color value. Because ToColor routes string->Color through
TabColorConverter, invalid strings become Color.Empty, so a cancel/no-op can be committed as an
empty string, overwriting the original stored value.

mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs[20-24]
mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs[31-44]
mRemoteNG/Tools/MiscTools.cs[430-447]

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

### Issue description
`ColorStringEditor.EditValue` converts the incoming string to a `Color`, calls the base `ColorEditor`, then converts any returned `Color` back into a string. If the user cancels (or makes no change), `ColorEditor` typically returns the original `Color` value that was passed in; because the editor always re-serializes that `Color`, the property grid can commit a modified string anyway.

This is particularly harmful for previously-invalid stored strings: `ToColor` ends up mapping them to `Color.Empty` (via `TabColorConverter.ConvertTo` returning `Color.Empty` on parse failures), so canceling can overwrite the original invalid string with `""`.

### Issue Context
- The new behavior is introduced by the PR’s `ColorStringEditor`.
- `TabColorConverter.ConvertTo(..., typeof(Color))` returns `Color.Empty` on any parse error, so the editor cannot distinguish “invalid original string” vs “unset color” unless it preserves the original value when no effective edit occurred.

### Fix Focus Areas
- mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs[20-24]

### Suggested fix approach
- Compute `originalColor = (Color)ToColor(value)`.
- Call `base.EditValue(..., originalColor)`.
- If the returned `editedValue` is a `Color editedColor` and `editedColor.Equals(originalColor)`, return the original `value` (or `string.Empty` when `value` is null) to avoid normalization/clearing on cancel/no-op.
- Otherwise, return `Converter.ConvertFrom(editedColor)`.
- Add/extend a unit test to cover an invalid input string (e.g. `"NotAColor"`) and assert that `EditValue` returns the original string when the base editor echoes the value (no UI service provider).

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



Informational

2. ColorStringEditorTests names lack pattern 📘 Rule violation ▣ Testability
Description
New NUnit tests use method names that do not follow the required
MethodName_Scenario_ExpectedBehavior underscore pattern, which makes test intent less consistent
and violates the test naming convention rule. This includes methods in ColorStringEditorTests as
well as the newly added ConvertFromEmptyColorReturnsEmptyString test.
Code

mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs[R37-40]

+        public void EditValueReturnsAString(string value)
+        {
+            var result = _editor.EditValue(null, new EmptyServiceProvider(), value);
+            Assert.That(result, Is.EqualTo(value));
Evidence
The rule requires test method names to consist of three segments separated by underscores (i.e.,
exactly two underscores). The added test methods cited, such as EditValueReturnsAString and
ConvertFromEmptyColorReturnsEmptyString, contain zero underscores, so their names do not match the
required MethodName_Scenario_ExpectedBehavior format and therefore violate the convention.

Rule 1563791: Test method names must follow MethodName_Scenario_ExpectedBehavior pattern
mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs[37-64]
mRemoteNGTests/Tools/TabColorConverterTests.cs[58-63]

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

## Issue description
Several newly-added NUnit test method names do not follow the required `MethodName_Scenario_ExpectedBehavior` naming convention (three segments separated by exactly two underscores).

## Issue Context
The naming rule expects three underscore-delimited segments to make test intent consistent. Current examples that violate the rule include methods like `EditValueReturnsAString`, `EditValueOfNullDoesNotThrow`, and the added `ConvertFromEmptyColorReturnsEmptyString`, all of which have no underscores and therefore do not satisfy the convention.

## Fix Focus Areas
- mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs[37-64]
- mRemoteNGTests/Tools/TabColorConverterTests.cs[58-63]

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


3. ColorStringEditor overrides missing XMLdocs 📘 Rule violation ⚙ Maintainability
Description
ColorStringEditor adds public override members (EditValue, PaintValue) without required XML
documentation comments. This reduces API documentation quality and violates the project’s public-API
documentation requirement.
Code

mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs[R20-23]

+        public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value)
+        {
+            object? editedValue = base.EditValue(context, provider, ToColor(value));
+            return editedValue is Color color ? Converter.ConvertFrom(color) : value;
Evidence
The compliance rule requires XML documentation comments for all public API members. In
ColorStringEditor, the public overrides EditValue and PaintValue are introduced without any
preceding /// XML doc blocks.

Rule 1563561: Require XML documentation comments on all public API members
mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs[20-29]

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

## Issue description
`ColorStringEditor` introduces public override members without XML documentation comments (`/// <summary>...</summary>`), which violates the requirement to document all public API members.

## Issue Context
The class has a `<summary>`, but the public methods `EditValue` and `PaintValue` do not.

## Fix Focus Areas
- mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs[20-29]

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


4. ColorStringEditorTests path not mirrored 📘 Rule violation ▣ Testability
Description
ColorStringEditor production code is added under
mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/, but its test is placed under
mRemoteNGTests/UI/Controls/ without the matching subdirectory. This violates the requirement to
mirror production directory structure in the test project.
Code

mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs[R11-14]

+namespace mRemoteNGTests.UI.Controls
+{
+    [NUnit.Framework.Apartment(System.Threading.ApartmentState.STA)]
+    public class ColorStringEditorTests
Evidence
The rule requires tests for production files to live in a matching folder structure under
mRemoteNGTests. The production file is under UI/Controls/ConnectionInfoPropertyGrid, but the
added test file is under UI/Controls (missing ConnectionInfoPropertyGrid).

Rule 1563834: Mirror production directory structure in test project
mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs[7-17]
mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs[11-15]

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

## Issue description
The new test file location does not mirror the production folder hierarchy for `ColorStringEditor`.

## Issue Context
Production file path: `mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs`.
Test file path currently: `mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs`.

## Fix Focus Areas
- mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs[11-15]

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


Grey Divider

Context used
✅ Compliance rules (platform): 52 rules

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +20 to +23
public override object? EditValue(ITypeDescriptorContext? context, IServiceProvider provider, object? value)
{
object? editedValue = base.EditValue(context, provider, ToColor(value));
return editedValue is Color color ? Converter.ConvertFrom(color) : value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

1. Colorstringeditor overrides missing xmldocs 📘 Rule violation ⚙ Maintainability

ColorStringEditor adds public override members (EditValue, PaintValue) without required XML
documentation comments. This reduces API documentation quality and violates the project’s public-API
documentation requirement.
Agent Prompt
## Issue description
`ColorStringEditor` introduces public override members without XML documentation comments (`/// <summary>...</summary>`), which violates the requirement to document all public API members.

## Issue Context
The class has a `<summary>`, but the public methods `EditValue` and `PaintValue` do not.

## Fix Focus Areas
- mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs[20-29]

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

Comment on lines +37 to +40
public void EditValueReturnsAString(string value)
{
var result = _editor.EditValue(null, new EmptyServiceProvider(), value);
Assert.That(result, Is.EqualTo(value));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

2. Colorstringeditortests names lack pattern 📘 Rule violation ▣ Testability

New NUnit tests use method names that do not follow the required
MethodName_Scenario_ExpectedBehavior underscore pattern, which makes test intent less consistent
and violates the test naming convention rule. This includes methods in ColorStringEditorTests as
well as the newly added ConvertFromEmptyColorReturnsEmptyString test.
Agent Prompt
## Issue description
Several newly-added NUnit test method names do not follow the required `MethodName_Scenario_ExpectedBehavior` naming convention (three segments separated by exactly two underscores).

## Issue Context
The naming rule expects three underscore-delimited segments to make test intent consistent. Current examples that violate the rule include methods like `EditValueReturnsAString`, `EditValueOfNullDoesNotThrow`, and the added `ConvertFromEmptyColorReturnsEmptyString`, all of which have no underscores and therefore do not satisfy the convention.

## Fix Focus Areas
- mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs[37-64]
- mRemoteNGTests/Tools/TabColorConverterTests.cs[58-63]

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

Comment on lines +11 to +14
namespace mRemoteNGTests.UI.Controls
{
[NUnit.Framework.Apartment(System.Threading.ApartmentState.STA)]
public class ColorStringEditorTests

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

3. Colorstringeditortests path not mirrored 📘 Rule violation ▣ Testability

ColorStringEditor production code is added under
mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/, but its test is placed under
mRemoteNGTests/UI/Controls/ without the matching subdirectory. This violates the requirement to
mirror production directory structure in the test project.
Agent Prompt
## Issue description
The new test file location does not mirror the production folder hierarchy for `ColorStringEditor`.

## Issue Context
Production file path: `mRemoteNG/UI/Controls/ConnectionInfoPropertyGrid/ColorStringEditor.cs`.
Test file path currently: `mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs`.

## Fix Focus Areas
- mRemoteNGTests/UI/Controls/ColorStringEditorTests.cs[11-15]

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

TabColorConverter maps text it cannot parse to Color.Empty rather than
throwing, so a legacy or hand-edited entry such as "not-a-color" reached the
wrapped ColorEditor as an empty colour. Dismissing the picker echoed that
empty colour straight back, and it was then stored as an empty string,
silently discarding the original value.

Track whether the stored value actually describes a colour, and keep the
value untouched when it does not and no colour was picked. A genuinely
unset value - null or blank - still round-trips to an empty string.
@jafin

jafin commented Aug 5, 2026

Copy link
Copy Markdown
Author

Follow-up pushed: TabColorConverter maps text it cannot parse to Color.Empty rather than throwing, so a legacy or hand-edited value such as not-a-color reached the wrapped ColorEditor as an empty colour. Dismissing the picker echoed that empty colour back and it was stored as an empty string, silently discarding the original value.

ColorStringEditor now tracks whether the stored value actually describes a colour (TryConvertToColor) and leaves the value untouched when it does not and no colour was picked. A genuinely unset value — null or blank — still round-trips to an empty string, and picking a real colour still stores its string form.

Covered by new cases in ColorStringEditorTests.EditValueKeepsAValueItCannotParse (not-a-color, #nothex).

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.

2 participants