diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5a1c0903..8df6bc3e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,11 +7,23 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Fixed
+- **ProcessManagerViewModel** — resolved CodeQL `cs/complex-condition` alert (#302)
+ by replacing chained null-conditional `||` expression with a `ReadOnlySpan` loop
+ in `MatchesDescription`.
+- **PerformanceView** — eliminated MVVM violation: removed `PropertyChanged`
+ subscription and `Checked` event handler from code-behind; radio buttons now use
+ two-way `EqualityConverter` binding to `SelectedPlan` (pure XAML, no code-behind
+ logic).
- **OperationLockServiceTests** — replaced flaky `Barrier` + `Thread.Sleep`
thread-safety test with deterministic `CountdownEvent` + `ManualResetEventSlim`
synchronization; asserts exactly 1 acquisition instead of `>= 1`.
### Added
+- **EqualityConverter** — reusable two-way `IValueConverter` that compares a bound
+ value to `ConverterParameter`; ideal for radio button groups bound to a string
+ property.
+- **EqualityConverterTests** — 10 unit tests covering Convert/ConvertBack, null
+ handling, and case sensitivity.
- **FormatHelperTests** — 14 unit tests covering `FormatSize` at all boundaries
(bytes, KB, MB, GB) with exact boundary and mid-range values.
diff --git a/SysManager/SysManager.Tests/EqualityConverterTests.cs b/SysManager/SysManager.Tests/EqualityConverterTests.cs
new file mode 100644
index 00000000..1dcd6bcd
--- /dev/null
+++ b/SysManager/SysManager.Tests/EqualityConverterTests.cs
@@ -0,0 +1,82 @@
+// SysManager · EqualityConverterTests
+// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager
+// License: MIT
+
+using System.Globalization;
+using System.Windows.Data;
+using SysManager.Helpers;
+
+namespace SysManager.Tests;
+
+///
+/// Tests for .
+///
+public class EqualityConverterTests
+{
+ private readonly EqualityConverter _sut = new();
+
+ [Theory]
+ [InlineData("balanced", "balanced", true)]
+ [InlineData("high", "high", true)]
+ [InlineData("ultimate", "ultimate", true)]
+ [InlineData("balanced", "high", false)]
+ [InlineData("high", "ultimate", false)]
+ [InlineData("", "balanced", false)]
+ public void Convert_ComparesValueToParameter(string value, string parameter, bool expected)
+ {
+ var result = _sut.Convert(value, typeof(bool), parameter, CultureInfo.InvariantCulture);
+ Assert.Equal(expected, result);
+ }
+
+ [Fact]
+ public void Convert_NullValue_ReturnsFalse()
+ {
+ var result = _sut.Convert(null, typeof(bool), "balanced", CultureInfo.InvariantCulture);
+ Assert.Equal(false, result);
+ }
+
+ [Fact]
+ public void Convert_NullParameter_ReturnsFalse()
+ {
+ var result = _sut.Convert("balanced", typeof(bool), null, CultureInfo.InvariantCulture);
+ Assert.Equal(false, result);
+ }
+
+ [Fact]
+ public void Convert_BothNull_ReturnsFalse()
+ {
+ var result = _sut.Convert(null, typeof(bool), null, CultureInfo.InvariantCulture);
+ Assert.Equal(false, result);
+ }
+
+ [Theory]
+ [InlineData("balanced")]
+ [InlineData("high")]
+ [InlineData("ultimate")]
+ public void ConvertBack_WhenTrue_ReturnsParameter(string parameter)
+ {
+ var result = _sut.ConvertBack(true, typeof(string), parameter, CultureInfo.InvariantCulture);
+ Assert.Equal(parameter, result);
+ }
+
+ [Fact]
+ public void ConvertBack_WhenFalse_ReturnsDoNothing()
+ {
+ var result = _sut.ConvertBack(false, typeof(string), "balanced", CultureInfo.InvariantCulture);
+ Assert.Equal(Binding.DoNothing, result);
+ }
+
+ [Fact]
+ public void ConvertBack_WhenNull_ReturnsDoNothing()
+ {
+ var result = _sut.ConvertBack(null, typeof(string), "balanced", CultureInfo.InvariantCulture);
+ Assert.Equal(Binding.DoNothing, result);
+ }
+
+ [Fact]
+ public void Convert_IsCaseSensitive()
+ {
+ var result = _sut.Convert("Balanced", typeof(bool), "balanced", CultureInfo.InvariantCulture);
+ Assert.Equal(false, result);
+ }
+}
diff --git a/SysManager/SysManager/App.xaml b/SysManager/SysManager/App.xaml
index 652c4b09..15ce1254 100644
--- a/SysManager/SysManager/App.xaml
+++ b/SysManager/SysManager/App.xaml
@@ -22,6 +22,7 @@
+
True
False
diff --git a/SysManager/SysManager/Helpers/EqualityConverter.cs b/SysManager/SysManager/Helpers/EqualityConverter.cs
new file mode 100644
index 00000000..fb2f6f7b
--- /dev/null
+++ b/SysManager/SysManager/Helpers/EqualityConverter.cs
@@ -0,0 +1,31 @@
+// SysManager · EqualityConverter — two-way value equality for radio buttons
+// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager
+// License: MIT
+
+using System.Globalization;
+using System.Windows.Data;
+
+namespace SysManager.Helpers;
+
+///
+/// Two-way converter that compares a bound value to ConverterParameter.
+/// Convert: returns true when value equals parameter (for IsChecked).
+/// ConvertBack: returns the parameter when IsChecked becomes true.
+/// Usage: IsChecked="{Binding SelectedPlan, Converter={StaticResource IsEqual}, ConverterParameter=balanced}"
+///
+public sealed class EqualityConverter : IValueConverter
+{
+ public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value == null || parameter == null)
+ return false;
+ return string.Equals(value.ToString(), parameter.ToString(), StringComparison.Ordinal);
+ }
+
+ public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
+ {
+ if (value is true)
+ return parameter?.ToString();
+ return Binding.DoNothing;
+ }
+}
diff --git a/SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs b/SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs
index 8e0d5774..3f69366a 100644
--- a/SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs
+++ b/SysManager/SysManager/ViewModels/ProcessManagerViewModel.cs
@@ -165,10 +165,16 @@ private static bool MatchesFilter(ProcessEntry p, string filter) =>
private static bool MatchesName(ProcessEntry p, string filter) =>
p.Name.Contains(filter, StringComparison.OrdinalIgnoreCase);
- private static bool MatchesDescription(ProcessEntry p, string filter) =>
- (p.Description?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false) ||
- (p.PlainDescription?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false) ||
- (p.Category?.Contains(filter, StringComparison.OrdinalIgnoreCase) ?? false);
+ private static bool MatchesDescription(ProcessEntry p, string filter)
+ {
+ ReadOnlySpan fields = [p.Description, p.PlainDescription, p.Category];
+ foreach (var field in fields)
+ {
+ if (field?.Contains(filter, StringComparison.OrdinalIgnoreCase) == true)
+ return true;
+ }
+ return false;
+ }
private static bool MatchesPid(ProcessEntry p, string filter) =>
p.Pid.ToString().Contains(filter);
diff --git a/SysManager/SysManager/Views/PerformanceView.xaml b/SysManager/SysManager/Views/PerformanceView.xaml
index 870dc489..0d179135 100644
--- a/SysManager/SysManager/Views/PerformanceView.xaml
+++ b/SysManager/SysManager/Views/PerformanceView.xaml
@@ -61,15 +61,15 @@
+ IsChecked="{Binding SelectedPlan, Converter={StaticResource IsEqual}, ConverterParameter=balanced}"/>
+ IsChecked="{Binding SelectedPlan, Converter={StaticResource IsEqual}, ConverterParameter=high}"/>
+ IsChecked="{Binding SelectedPlan, Converter={StaticResource IsEqual}, ConverterParameter=ultimate}"/>
diff --git a/SysManager/SysManager/Views/PerformanceView.xaml.cs b/SysManager/SysManager/Views/PerformanceView.xaml.cs
index 572c48c4..1c02b6ce 100644
--- a/SysManager/SysManager/Views/PerformanceView.xaml.cs
+++ b/SysManager/SysManager/Views/PerformanceView.xaml.cs
@@ -2,55 +2,14 @@
// Author: laurentiu021 · https://github.com/laurentiu021/SystemManager
// License: MIT
-using System.Windows;
using System.Windows.Controls;
-using SysManager.ViewModels;
namespace SysManager.Views;
public partial class PerformanceView : UserControl
{
- private System.ComponentModel.PropertyChangedEventHandler? _propertyHandler;
-
public PerformanceView()
{
InitializeComponent();
- DataContextChanged += OnDataContextChanged;
- }
-
- private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
- {
- // MEM-004: unsubscribe from previous VM to prevent leak
- if (e.OldValue is PerformanceViewModel oldVm && _propertyHandler != null)
- oldVm.PropertyChanged -= _propertyHandler;
-
- if (e.NewValue is PerformanceViewModel vm)
- {
- _propertyHandler = (_, args) =>
- {
- if (args.PropertyName == nameof(PerformanceViewModel.SelectedPlan))
- SyncRadioButtons(vm.SelectedPlan);
- };
- vm.PropertyChanged += _propertyHandler;
- SyncRadioButtons(vm.SelectedPlan);
- }
- }
-
- private void SyncRadioButtons(string plan)
- {
- if (!Dispatcher.CheckAccess())
- {
- Dispatcher.BeginInvoke(() => SyncRadioButtons(plan));
- return;
- }
- RbBalanced.IsChecked = plan == "balanced";
- RbHigh.IsChecked = plan == "high";
- RbUltimate.IsChecked = plan == "ultimate";
- }
-
- private void PowerPlan_Checked(object sender, RoutedEventArgs e)
- {
- if (sender is RadioButton rb && DataContext is PerformanceViewModel vm)
- vm.SelectedPlan = rb.Tag?.ToString() ?? "balanced";
}
}