Refactor: Replace custom RelayCommand with CommunityToolkit

Replaced the custom RelayCommand implementation with the
[RelayCommand] attribute from the CommunityToolkit.Mvvm library
to simplify command creation and reduce boilerplate code.

- Removed RelayCommand and RelayCommand<T> classes.
- Added CommunityToolkit.Mvvm package to the project.
- Updated MainViewModel and MonitorViewModel to use
  [RelayCommand] for command generation.
- Cleaned up unused imports related to the removed RelayCommand.

This change improves maintainability and aligns the project
with modern MVVM practices.
This commit is contained in:
Yu Leng
2025-12-08 14:01:18 +08:00
parent 0655497762
commit ce9bd1e67e
4 changed files with 28 additions and 128 deletions

View File

@@ -1,109 +0,0 @@
// Copyright (c) Microsoft Corporation
// The Microsoft Corporation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Windows.Input;
using ManagedCommon;
namespace PowerDisplay.Commands
{
/// <summary>
/// Basic relay command implementation for parameterless actions
/// </summary>
public partial class RelayCommand : ICommand
{
private readonly Action _execute;
private readonly Func<bool>? _canExecute;
public RelayCommand(Action execute, Func<bool>? canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
#pragma warning disable CS0067 // Event is never used - required by ICommand interface
public event EventHandler? CanExecuteChanged;
#pragma warning restore CS0067
public bool CanExecute(object? parameter)
{
if (_canExecute == null)
{
return true;
}
try
{
return _canExecute.Invoke();
}
catch (Exception ex)
{
Logger.LogError($"CanExecute failed: {ex.Message}");
return false;
}
}
public void Execute(object? parameter)
{
try
{
_execute();
}
catch (Exception ex)
{
Logger.LogError($"Command execution failed: {ex.Message}");
}
}
}
/// <summary>
/// Generic relay command implementation for parameterized actions
/// </summary>
/// <typeparam name="T">Type of the command parameter</typeparam>
public partial class RelayCommand<T> : ICommand
{
private readonly Action<T?> _execute;
private readonly Func<T?, bool>? _canExecute;
public RelayCommand(Action<T?> execute, Func<T?, bool>? canExecute = null)
{
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
_canExecute = canExecute;
}
#pragma warning disable CS0067 // Event is never used - required by ICommand interface
public event EventHandler? CanExecuteChanged;
#pragma warning restore CS0067
public bool CanExecute(object? parameter)
{
if (_canExecute == null)
{
return true;
}
try
{
return _canExecute.Invoke((T?)parameter);
}
catch (Exception ex)
{
Logger.LogError($"CanExecute<T> failed: {ex.Message}");
return false;
}
}
public void Execute(object? parameter)
{
try
{
_execute((T?)parameter);
}
catch (Exception ex)
{
Logger.LogError($"Command<T> execution failed: {ex.Message}");
}
}
}
}

View File

@@ -59,6 +59,7 @@
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" /> <PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" /> <PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Microsoft.Windows.CsWin32"> <PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
</PackageReference> </PackageReference>

View File

@@ -9,12 +9,12 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Windows.Input; using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using ManagedCommon; using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library; using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Dispatching; using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing; using Microsoft.UI.Windowing;
using PowerDisplay.Commands;
using PowerDisplay.Common.Models; using PowerDisplay.Common.Models;
using PowerDisplay.Common.Services; using PowerDisplay.Common.Services;
using PowerDisplay.Helpers; using PowerDisplay.Helpers;
@@ -168,17 +168,20 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
/// </summary> /// </summary>
public bool IsInteractionEnabled => !IsLoading && !IsScanning; public bool IsInteractionEnabled => !IsLoading && !IsScanning;
public ICommand RefreshCommand => new RelayCommand(async () => await RefreshMonitorsAsync()); [RelayCommand]
private async Task RefreshAsync() => await RefreshMonitorsAsync();
public ICommand SetAllBrightnessCommand => new RelayCommand<int?>(async (brightness) => [RelayCommand]
private async Task SetAllBrightness(int? brightness)
{ {
if (brightness.HasValue) if (brightness.HasValue)
{ {
await SetAllBrightnessAsync(brightness.Value); await SetAllBrightnessAsync(brightness.Value);
} }
}); }
public ICommand IdentifyMonitorsCommand => new RelayCommand(() => [RelayCommand]
private void IdentifyMonitors()
{ {
Logger.LogInfo("Identify monitors feature triggered"); Logger.LogInfo("Identify monitors feature triggered");
@@ -217,16 +220,17 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
Logger.LogError($"Failed to identify monitors: {ex.Message}"); Logger.LogError($"Failed to identify monitors: {ex.Message}");
Logger.LogError($"Stack trace: {ex.StackTrace}"); Logger.LogError($"Stack trace: {ex.StackTrace}");
} }
}); }
public ICommand ApplyProfileCommand => new RelayCommand<PowerDisplayProfile>(async profile => [RelayCommand]
private async Task ApplyProfile(PowerDisplayProfile? profile)
{ {
if (profile != null && profile.IsValid()) if (profile != null && profile.IsValid())
{ {
Logger.LogInfo($"[Profile] Applying profile '{profile.Name}' from quick apply"); Logger.LogInfo($"[Profile] Applying profile '{profile.Name}' from quick apply");
await ApplyProfileAsync(profile.Name, profile.MonitorSettings); await ApplyProfileAsync(profile.Name, profile.MonitorSettings);
} }
}); }
public event PropertyChangedEventHandler? PropertyChanged; public event PropertyChangedEventHandler? PropertyChanged;

View File

@@ -9,10 +9,10 @@ using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input; using CommunityToolkit.Mvvm.Input;
using ManagedCommon; using ManagedCommon;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using PowerDisplay.Commands;
using PowerDisplay.Common.Models; using PowerDisplay.Common.Models;
using PowerDisplay.Common.Utils; using PowerDisplay.Common.Utils;
using PowerDisplay.Configuration; using PowerDisplay.Configuration;
@@ -574,13 +574,14 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
/// <summary> /// <summary>
/// Command to set input source /// Command to set input source
/// </summary> /// </summary>
public ICommand SetInputSourceCommand => new RelayCommand<int?>(async (source) => [RelayCommand]
private async Task SetInputSource(int? source)
{ {
if (source.HasValue) if (source.HasValue)
{ {
await SetInputSourceAsync(source.Value); await SetInputSourceAsync(source.Value);
} }
}); }
public int Contrast public int Contrast
{ {
@@ -618,29 +619,32 @@ public partial class MonitorViewModel : INotifyPropertyChanged, IDisposable
} }
} }
public ICommand SetBrightnessCommand => new RelayCommand<int?>((brightness) => [RelayCommand]
private void SetBrightness(int? brightness)
{ {
if (brightness.HasValue) if (brightness.HasValue)
{ {
Brightness = brightness.Value; Brightness = brightness.Value;
} }
}); }
public ICommand SetContrastCommand => new RelayCommand<int?>((contrast) => [RelayCommand]
private void SetContrast(int? contrast)
{ {
if (contrast.HasValue) if (contrast.HasValue)
{ {
Contrast = contrast.Value; Contrast = contrast.Value;
} }
}); }
public ICommand SetVolumeCommand => new RelayCommand<int?>((volume) => [RelayCommand]
private void SetVolume(int? volume)
{ {
if (volume.HasValue) if (volume.HasValue)
{ {
Volume = volume.Value; Volume = volume.Value;
} }
}); }
public int ContrastPercent public int ContrastPercent
{ {