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.Sizers" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" />
<PackageReference Include="CommunityToolkit.Mvvm" />
<PackageReference Include="Microsoft.Windows.CsWin32">
<PrivateAssets>all</PrivateAssets>
</PackageReference>

View File

@@ -9,12 +9,12 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Windows.Input;
using System.Threading.Tasks;
using CommunityToolkit.Mvvm.Input;
using ManagedCommon;
using Microsoft.PowerToys.Settings.UI.Library;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using PowerDisplay.Commands;
using PowerDisplay.Common.Models;
using PowerDisplay.Common.Services;
using PowerDisplay.Helpers;
@@ -168,17 +168,20 @@ public partial class MainViewModel : INotifyPropertyChanged, IDisposable
/// </summary>
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)
{
await SetAllBrightnessAsync(brightness.Value);
}
});
}
public ICommand IdentifyMonitorsCommand => new RelayCommand(() =>
[RelayCommand]
private void IdentifyMonitors()
{
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($"Stack trace: {ex.StackTrace}");
}
});
}
public ICommand ApplyProfileCommand => new RelayCommand<PowerDisplayProfile>(async profile =>
[RelayCommand]
private async Task ApplyProfile(PowerDisplayProfile? profile)
{
if (profile != null && profile.IsValid())
{
Logger.LogInfo($"[Profile] Applying profile '{profile.Name}' from quick apply");
await ApplyProfileAsync(profile.Name, profile.MonitorSettings);
}
});
}
public event PropertyChangedEventHandler? PropertyChanged;

View File

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