2025-10-16 16:26:26 +08:00
// 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.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Collections.Specialized ;
using System.ComponentModel ;
using System.IO ;
2025-10-16 17:45:49 +08:00
using System.Linq ;
2025-10-16 16:26:26 +08:00
using System.Runtime.CompilerServices ;
using System.Threading ;
using System.Threading.Tasks ;
using System.Windows.Input ;
using ManagedCommon ;
using Microsoft.PowerToys.Settings.UI.Library ;
2025-10-16 17:45:49 +08:00
using Microsoft.UI.Dispatching ;
using Microsoft.UI.Xaml ;
2025-10-16 16:26:26 +08:00
using PowerDisplay.Core ;
using PowerDisplay.Core.Interfaces ;
using PowerDisplay.Core.Models ;
using PowerDisplay.Helpers ;
using Monitor = PowerDisplay . Core . Models . Monitor ;
namespace PowerDisplay.ViewModels ;
/// <summary>
/// Main ViewModel for the PowerDisplay application
/// </summary>
public class MainViewModel : INotifyPropertyChanged , IDisposable
{
private readonly MonitorManager _monitorManager ;
private readonly DispatcherQueue _dispatcherQueue ;
private readonly CancellationTokenSource _cancellationTokenSource ;
private readonly ISettingsUtils _settingsUtils ;
2025-10-17 16:08:54 +08:00
private readonly MonitorStateManager _stateManager ;
2025-10-16 16:26:26 +08:00
private FileSystemWatcher ? _settingsWatcher ;
private ObservableCollection < MonitorViewModel > _monitors ;
private string _statusText ;
private bool _isScanning ;
private bool _isInitialized ;
2025-10-17 16:59:26 +08:00
private bool _isLoading ;
2025-10-17 17:27:24 +08:00
private bool _isReloadingSettings ; // 防止重复加载
2025-10-16 16:26:26 +08:00
/// <summary>
/// Event triggered when UI refresh is requested due to settings changes
/// </summary>
public event EventHandler ? UIRefreshRequested ;
/// <summary>
/// Event triggered when theme change is requested
/// </summary>
public event EventHandler < ElementTheme > ? ThemeChangeRequested ;
public MainViewModel ( )
{
_dispatcherQueue = DispatcherQueue . GetForCurrentThread ( ) ;
_cancellationTokenSource = new CancellationTokenSource ( ) ;
_monitors = new ObservableCollection < MonitorViewModel > ( ) ;
_statusText = "Initializing..." ;
_isScanning = true ;
// Initialize settings utils
_settingsUtils = new SettingsUtils ( ) ;
2025-10-17 16:08:54 +08:00
_stateManager = new MonitorStateManager ( ) ;
2025-10-16 16:26:26 +08:00
// Initialize the monitor manager
_monitorManager = new MonitorManager ( ) ;
// Subscribe to events
_monitorManager . MonitorsChanged + = OnMonitorsChanged ;
_monitorManager . MonitorStatusChanged + = OnMonitorStatusChanged ;
// Setup settings file monitoring
SetupSettingsFileWatcher ( ) ;
// Start initial discovery
_ = InitializeAsync ( ) ;
}
public ObservableCollection < MonitorViewModel > Monitors
{
get = > _monitors ;
set
{
_monitors = value ;
OnPropertyChanged ( ) ;
}
}
public string StatusText
{
get = > _statusText ;
set
{
_statusText = value ;
OnPropertyChanged ( ) ;
}
}
public bool IsScanning
{
get = > _isScanning ;
set
{
_isScanning = value ;
OnPropertyChanged ( ) ;
OnPropertyChanged ( nameof ( HasMonitors ) ) ;
OnPropertyChanged ( nameof ( ShowNoMonitorsMessage ) ) ;
}
}
public bool HasMonitors = > ! IsScanning & & Monitors . Count > 0 ;
public bool ShowNoMonitorsMessage = > ! IsScanning & & Monitors . Count = = 0 ;
public bool IsInitialized
{
get = > _isInitialized ;
private set
{
_isInitialized = value ;
OnPropertyChanged ( ) ;
}
}
2025-10-17 16:59:26 +08:00
public bool IsLoading
{
get = > _isLoading ;
private set
{
_isLoading = value ;
OnPropertyChanged ( ) ;
// Update all monitors' interaction state
foreach ( var monitor in Monitors )
{
monitor . IsInteractionEnabled = ! value ;
}
}
}
2025-10-16 16:26:26 +08:00
public ICommand RefreshCommand = > new RelayCommand ( async ( ) = > await RefreshMonitorsAsync ( ) ) ;
public ICommand SetAllBrightnessCommand = > new RelayCommand < int? > ( async ( brightness ) = >
{
if ( brightness . HasValue )
{
await SetAllBrightnessAsync ( brightness . Value ) ;
}
} ) ;
private async Task InitializeAsync ( )
{
try
{
StatusText = "Scanning monitors..." ;
IsScanning = true ;
// Discover monitors
var monitors = await _monitorManager . DiscoverMonitorsAsync ( _cancellationTokenSource . Token ) ;
// Update UI on the dispatcher thread
_dispatcherQueue . TryEnqueue ( ( ) = >
{
UpdateMonitorList ( monitors ) ;
IsScanning = false ;
IsInitialized = true ;
if ( monitors . Count > 0 )
{
StatusText = $"Found {monitors.Count} monitors" ;
}
else
{
StatusText = "No controllable monitors found" ;
}
} ) ;
}
catch ( Exception ex )
{
_dispatcherQueue . TryEnqueue ( ( ) = >
{
StatusText = $"Scan failed: {ex.Message}" ;
IsScanning = false ;
} ) ;
}
}
public async Task RefreshMonitorsAsync ( )
{
if ( IsScanning )
{
return ;
}
try
{
StatusText = "Refreshing monitor list..." ;
IsScanning = true ;
var monitors = await _monitorManager . DiscoverMonitorsAsync ( _cancellationTokenSource . Token ) ;
_dispatcherQueue . TryEnqueue ( ( ) = >
{
UpdateMonitorList ( monitors ) ;
IsScanning = false ;
StatusText = $"Found {monitors.Count} monitors" ;
} ) ;
}
catch ( Exception ex )
{
_dispatcherQueue . TryEnqueue ( ( ) = >
{
StatusText = $"Refresh failed: {ex.Message}" ;
IsScanning = false ;
} ) ;
}
}
private void UpdateMonitorList ( IReadOnlyList < Monitor > monitors )
{
Monitors . Clear ( ) ;
foreach ( var monitor in monitors )
{
var vm = new MonitorViewModel ( monitor , _monitorManager , this ) ;
Monitors . Add ( vm ) ;
// Asynchronously initialize color temperature for DDC/CI monitors
if ( monitor . SupportsColorTemperature & & monitor . Type = = MonitorType . External )
{
_ = InitializeColorTemperatureSafeAsync ( monitor . Id , vm ) ;
}
}
OnPropertyChanged ( nameof ( HasMonitors ) ) ;
OnPropertyChanged ( nameof ( ShowNoMonitorsMessage ) ) ;
2025-10-17 16:59:26 +08:00
// Restore saved settings if enabled (async, don't block)
_ = ReloadMonitorSettingsAsync ( ) ;
2025-10-16 16:26:26 +08:00
}
public async Task SetAllBrightnessAsync ( int brightness )
{
try
{
StatusText = $"Setting all monitors brightness to {brightness}%..." ;
await _monitorManager . SetAllBrightnessAsync ( brightness , _cancellationTokenSource . Token ) ;
StatusText = $"All monitors brightness set to {brightness}%" ;
}
catch ( Exception ex )
{
StatusText = $"Failed to set brightness: {ex.Message}" ;
}
}
private void OnMonitorsChanged ( object? sender , MonitorListChangedEventArgs e )
{
_dispatcherQueue . TryEnqueue ( ( ) = >
{
// Handle monitors being added or removed
if ( e . AddedMonitors . Count > 0 )
{
foreach ( var monitor in e . AddedMonitors )
{
var existingVm = GetMonitorViewModel ( monitor . Id ) ;
if ( existingVm = = null )
{
var vm = new MonitorViewModel ( monitor , _monitorManager , this ) ;
Monitors . Add ( vm ) ;
}
}
}
if ( e . RemovedMonitors . Count > 0 )
{
foreach ( var monitor in e . RemovedMonitors )
{
var vm = GetMonitorViewModel ( monitor . Id ) ;
if ( vm ! = null )
{
Monitors . Remove ( vm ) ;
vm . Dispose ( ) ;
}
}
}
StatusText = $"Monitor list updated ({Monitors.Count} total)" ;
} ) ;
}
private void OnMonitorStatusChanged ( object? sender , MonitorStatusChangedEventArgs e )
{
_dispatcherQueue . TryEnqueue ( ( ) = >
{
var vm = GetMonitorViewModel ( e . Monitor . Id ) ;
vm ? . UpdateFromModel ( e . Monitor ) ;
} ) ;
}
private MonitorViewModel ? GetMonitorViewModel ( string monitorId )
{
foreach ( var vm in Monitors )
{
if ( vm . Id = = monitorId )
{
return vm ;
}
}
return null ;
}
/// <summary>
/// Setup settings file watcher
/// </summary>
private void SetupSettingsFileWatcher ( )
{
try
{
var settingsPath = _settingsUtils . GetSettingsFilePath ( "PowerDisplay" ) ;
var directory = Path . GetDirectoryName ( settingsPath ) ;
var fileName = Path . GetFileName ( settingsPath ) ;
if ( ! string . IsNullOrEmpty ( directory ) )
{
// Ensure directory exists
if ( ! Directory . Exists ( directory ) )
{
Directory . CreateDirectory ( directory ) ;
}
_settingsWatcher = new FileSystemWatcher ( directory , fileName )
{
NotifyFilter = NotifyFilters . LastWrite | NotifyFilters . CreationTime ,
EnableRaisingEvents = true
} ;
_settingsWatcher . Changed + = OnSettingsFileChanged ;
_settingsWatcher . Created + = OnSettingsFileChanged ;
Logger . LogInfo ( $"Settings file watcher setup for: {settingsPath}" ) ;
}
}
catch ( Exception ex )
{
Logger . LogError ( $"Failed to setup settings file watcher: {ex.Message}" ) ;
}
}
/// <summary>
2025-10-17 16:08:54 +08:00
/// Handle settings file changes - only monitors UI configuration changes from Settings UI
/// (monitor_state.json is managed separately and doesn't trigger this)
2025-10-16 16:26:26 +08:00
/// </summary>
private void OnSettingsFileChanged ( object sender , FileSystemEventArgs e )
{
try
{
2025-10-17 16:08:54 +08:00
Logger . LogInfo ( $"Settings file changed by Settings UI: {e.FullPath}" ) ;
2025-10-16 16:26:26 +08:00
// Add small delay to ensure file write completion
Task . Delay ( 500 ) . ContinueWith ( _ = >
{
try
{
// Read updated settings
var settings = _settingsUtils . GetSettingsOrDefault < PowerDisplaySettings > ( "PowerDisplay" ) ;
_dispatcherQueue . TryEnqueue ( ( ) = >
{
// Update feature visibility for each monitor (UI configuration only)
foreach ( var monitorVm in Monitors )
{
// Use converted internal name for lookup
var internalName = GetInternalName ( monitorVm ) ;
Logger . LogInfo ( $"[Settings Update] Looking for monitor settings with internal name: '{internalName}', Hardware ID: '{monitorVm.HardwareId}'" ) ;
var monitorSettings = settings . Properties . Monitors . FirstOrDefault ( m = >
m . InternalName = = internalName | | m . HardwareId = = monitorVm . HardwareId ) ;
if ( monitorSettings ! = null )
{
Logger . LogInfo ( $"[Settings Update] Found monitor settings for '{internalName}': ColorTemp={monitorSettings.EnableColorTemperature}, Contrast={monitorSettings.EnableContrast}, Volume={monitorSettings.EnableVolume}" ) ;
// Update visibility flags based on Settings UI toggles
monitorVm . ShowColorTemperature = monitorSettings . EnableColorTemperature ;
monitorVm . ShowContrast = monitorSettings . EnableContrast ;
monitorVm . ShowVolume = monitorSettings . EnableVolume ;
}
else
{
Logger . LogWarning ( $"[Settings Update] No monitor settings found for '{internalName}' with Hardware ID '{monitorVm.HardwareId}'" ) ;
Logger . LogInfo ( $"[Settings Update] Available monitors in settings:" ) ;
foreach ( var availableMonitor in settings . Properties . Monitors )
{
Logger . LogInfo ( $" - Internal: '{availableMonitor.InternalName}', Hardware: '{availableMonitor.HardwareId}', Name: '{availableMonitor.Name}'" ) ;
}
}
}
// Check for theme changes and apply them
var newTheme = PowerDisplay . Helpers . ThemeManager . GetThemeFromPowerToysSettings ( ) ;
if ( newTheme ! = ElementTheme . Default )
{
ThemeChangeRequested ? . Invoke ( this , newTheme ) ;
Logger . LogInfo ( $"Theme change requested: {newTheme}" ) ;
}
// Trigger UI refresh for configuration changes
UIRefreshRequested ? . Invoke ( this , EventArgs . Empty ) ;
} ) ;
Logger . LogInfo ( $"Settings UI configuration reloaded, monitor count: {settings.Properties.Monitors.Count}" ) ;
}
catch ( Exception ex )
{
Logger . LogError ( $"Failed to reload settings: {ex.Message}" ) ;
}
} ) ;
}
catch ( Exception ex )
{
Logger . LogError ( $"Error handling settings file change: {ex.Message}" ) ;
}
}
/// <summary>
/// Safe wrapper for initializing color temperature asynchronously
/// </summary>
private async Task InitializeColorTemperatureSafeAsync ( string monitorId , MonitorViewModel vm )
{
try
{
await _monitorManager . InitializeColorTemperatureAsync ( monitorId ) ;
// Update UI on dispatcher thread - get the monitor from manager
var monitor = _monitorManager . GetMonitor ( monitorId ) ;
if ( monitor ! = null )
{
_dispatcherQueue . TryEnqueue ( ( ) = > vm . UpdateFromModel ( monitor ) ) ;
}
}
catch ( Exception ex )
{
Logger . LogWarning ( $"Failed to initialize color temperature for {monitorId}: {ex.Message}" ) ;
}
}
// INotifyPropertyChanged
public event PropertyChangedEventHandler ? PropertyChanged ;
protected virtual void OnPropertyChanged ( [ CallerMemberName ] string? propertyName = null )
{
PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( propertyName ) ) ;
}
/// <summary>
/// Reload monitor settings from configuration
/// </summary>
2025-10-17 16:59:26 +08:00
public async Task ReloadMonitorSettingsAsync ( )
2025-10-16 16:26:26 +08:00
{
2025-10-17 17:27:24 +08:00
// 防止重复调用
if ( _isReloadingSettings )
{
Logger . LogInfo ( "[Startup] ReloadMonitorSettingsAsync already in progress, skipping" ) ;
return ;
}
2025-10-16 16:26:26 +08:00
try
{
2025-10-17 17:27:24 +08:00
_isReloadingSettings = true ;
2025-10-17 16:59:26 +08:00
// Set loading state to block UI interactions
IsLoading = true ;
StatusText = "Loading settings..." ;
2025-10-16 16:26:26 +08:00
// Read current settings
var settings = _settingsUtils . GetSettingsOrDefault < PowerDisplaySettings > ( "PowerDisplay" ) ;
if ( settings . Properties . RestoreSettingsOnStartup )
{
// Restore saved settings from configuration file
Logger . LogInfo ( "[Startup] RestoreSettingsOnStartup enabled - applying saved settings" ) ;
foreach ( var monitorVm in Monitors )
{
2025-10-17 18:03:13 +08:00
var hardwareId = monitorVm . HardwareId ;
Logger . LogInfo ( $"[Startup] Processing monitor: '{monitorVm.Name}', HardwareId: '{hardwareId}'" ) ;
2025-10-16 16:26:26 +08:00
2025-10-17 18:03:13 +08:00
// Find and apply corresponding saved settings from state file using stable HardwareId
var savedState = _stateManager . GetMonitorParameters ( hardwareId ) ;
2025-10-17 16:08:54 +08:00
if ( savedState . HasValue )
2025-10-16 16:26:26 +08:00
{
2025-10-17 18:03:13 +08:00
Logger . LogInfo ( $"[Startup] Restoring state for HardwareId '{hardwareId}': Brightness={savedState.Value.Brightness}, ColorTemp={savedState.Value.ColorTemperature}" ) ;
2025-10-16 16:26:26 +08:00
2025-10-17 17:27:24 +08:00
// 验证并应用保存的值(跳过无效值)
if ( savedState . Value . Brightness > = monitorVm . MinBrightness & & savedState . Value . Brightness < = monitorVm . MaxBrightness )
{
monitorVm . Brightness = savedState . Value . Brightness ;
}
else
{
2025-10-17 18:03:13 +08:00
Logger . LogWarning ( $"[Startup] Invalid brightness value {savedState.Value.Brightness} for HardwareId '{hardwareId}', skipping" ) ;
2025-10-17 17:27:24 +08:00
}
// 色温值必须有效且在范围内
if ( savedState . Value . ColorTemperature > 0 & &
savedState . Value . ColorTemperature > = monitorVm . MinColorTemperature & &
savedState . Value . ColorTemperature < = monitorVm . MaxColorTemperature )
{
monitorVm . ColorTemperature = savedState . Value . ColorTemperature ;
}
else
{
2025-10-17 18:03:13 +08:00
Logger . LogWarning ( $"[Startup] Invalid color temperature value {savedState.Value.ColorTemperature} for HardwareId '{hardwareId}', skipping" ) ;
2025-10-17 17:27:24 +08:00
}
2025-10-17 17:34:01 +08:00
// 对比度值验证 - 只在硬件支持的情况下才应用
if ( monitorVm . ShowContrast & &
savedState . Value . Contrast > = monitorVm . MinContrast & &
savedState . Value . Contrast < = monitorVm . MaxContrast )
2025-10-17 17:27:24 +08:00
{
monitorVm . Contrast = savedState . Value . Contrast ;
}
2025-10-17 17:34:01 +08:00
else if ( ! monitorVm . ShowContrast )
{
2025-10-17 18:03:13 +08:00
Logger . LogInfo ( $"[Startup] Contrast not supported on HardwareId '{hardwareId}', skipping" ) ;
2025-10-17 17:34:01 +08:00
}
2025-10-17 17:27:24 +08:00
2025-10-17 17:34:01 +08:00
// 音量值验证 - 只在硬件支持的情况下才应用
if ( monitorVm . ShowVolume & &
savedState . Value . Volume > = monitorVm . MinVolume & &
savedState . Value . Volume < = monitorVm . MaxVolume )
2025-10-17 17:27:24 +08:00
{
monitorVm . Volume = savedState . Value . Volume ;
}
2025-10-17 17:34:01 +08:00
else if ( ! monitorVm . ShowVolume )
{
2025-10-17 18:03:13 +08:00
Logger . LogInfo ( $"[Startup] Volume not supported on HardwareId '{hardwareId}', skipping" ) ;
2025-10-17 17:34:01 +08:00
}
2025-10-16 16:26:26 +08:00
}
else
{
2025-10-17 18:03:13 +08:00
Logger . LogInfo ( $"[Startup] No saved state for HardwareId '{hardwareId}' - keeping current hardware values" ) ;
2025-10-16 16:26:26 +08:00
}
2025-10-17 18:03:13 +08:00
// Apply feature visibility settings (still need InternalName for Settings UI matching)
var internalName = GetInternalName ( monitorVm ) ;
2025-10-16 16:26:26 +08:00
ApplyFeatureVisibility ( monitorVm , settings , internalName ) ;
}
2025-10-17 16:59:26 +08:00
StatusText = "Applying settings..." ;
// Wait for all hardware updates to complete
2025-10-17 17:27:24 +08:00
try
{
await Task . WhenAll ( Monitors . Select ( m = > m . FlushAllUpdatesAsync ( ) ) ) ;
StatusText = "Saved settings restored successfully" ;
}
catch ( Exception ex )
{
Logger . LogError ( $"[Startup] Error waiting for updates: {ex.Message}" ) ;
StatusText = "Settings applied with errors" ;
}
2025-10-16 16:26:26 +08:00
}
else
{
// Save current hardware values to configuration file
Logger . LogInfo ( "[Startup] RestoreSettingsOnStartup disabled - saving current hardware values" ) ;
foreach ( var monitorVm in Monitors )
{
var internalName = GetInternalName ( monitorVm ) ;
// Save current hardware values to settings
SaveMonitorSetting ( monitorVm . Id , "Brightness" , monitorVm . Brightness ) ;
SaveMonitorSetting ( monitorVm . Id , "ColorTemperature" , monitorVm . ColorTemperature ) ;
SaveMonitorSetting ( monitorVm . Id , "Contrast" , monitorVm . Contrast ) ;
SaveMonitorSetting ( monitorVm . Id , "Volume" , monitorVm . Volume ) ;
Logger . LogInfo ( $"[Startup] Saved current values for '{internalName}': Brightness={monitorVm.Brightness}, ColorTemp={monitorVm.ColorTemperature}" ) ;
// Apply feature visibility settings
ApplyFeatureVisibility ( monitorVm , settings , internalName ) ;
}
// Flush pending changes immediately
2025-10-17 16:59:26 +08:00
await _stateManager . FlushAsync ( ) ;
2025-10-16 16:26:26 +08:00
2025-10-17 16:08:54 +08:00
StatusText = "Current monitor values saved to state file" ;
2025-10-16 16:26:26 +08:00
}
}
catch ( Exception ex )
{
Logger . LogError ( $"Failed to reload/save settings: {ex.Message}" ) ;
StatusText = $"Failed to process settings: {ex.Message}" ;
}
2025-10-17 16:59:26 +08:00
finally
{
// Clear loading state to enable UI interactions
IsLoading = false ;
2025-10-17 17:27:24 +08:00
_isReloadingSettings = false ;
2025-10-17 16:59:26 +08:00
}
2025-10-16 16:26:26 +08:00
}
/// <summary>
/// Apply feature visibility settings to a monitor ViewModel
/// </summary>
private void ApplyFeatureVisibility ( MonitorViewModel monitorVm , PowerDisplaySettings settings , string internalName )
{
var monitorSettings = settings . Properties . Monitors . FirstOrDefault ( m = >
m . InternalName = = internalName | | m . HardwareId = = monitorVm . HardwareId ) ;
if ( monitorSettings ! = null )
{
Logger . LogInfo ( $"[Startup] Applying feature visibility for '{internalName}': ColorTemp={monitorSettings.EnableColorTemperature}, Contrast={monitorSettings.EnableContrast}, Volume={monitorSettings.EnableVolume}" ) ;
monitorVm . ShowColorTemperature = monitorSettings . EnableColorTemperature ;
monitorVm . ShowContrast = monitorSettings . EnableContrast ;
monitorVm . ShowVolume = monitorSettings . EnableVolume ;
}
else
{
Logger . LogWarning ( $"[Startup] No feature settings found for '{internalName}' - using defaults" ) ;
}
}
/// <summary>
/// Save monitor settings to configuration file (one-way: save only, no read during runtime)
/// </summary>
public void SaveMonitorSetting ( string monitorId , string property , int value )
{
try
{
2025-10-17 18:03:13 +08:00
// Find the monitor VM to get the stable HardwareId
2025-10-16 16:26:26 +08:00
var monitorVm = GetMonitorViewModel ( monitorId ) ;
if ( monitorVm = = null )
{
Logger . LogError ( $"Monitor not found for ID: {monitorId}" ) ;
return ;
}
2025-10-17 18:03:13 +08:00
// Use stable HardwareId as the key for state persistence
var hardwareId = monitorVm . HardwareId ;
2025-10-16 16:26:26 +08:00
2025-10-17 16:08:54 +08:00
// Update parameter in state file (lock-free, non-blocking)
2025-10-17 18:03:13 +08:00
_stateManager . UpdateMonitorParameter ( hardwareId , property , value ) ;
2025-10-16 16:26:26 +08:00
2025-10-17 18:03:13 +08:00
Logger . LogTrace ( $"[State] Queued setting change for HardwareId '{hardwareId}': {property}={value}" ) ;
2025-10-16 16:26:26 +08:00
}
catch ( Exception ex )
{
// Log error but don't interrupt user operation
Logger . LogError ( $"Failed to queue setting save: {ex.Message}" ) ;
StatusText = $"Warning: Failed to save settings: {ex.Message}" ;
}
}
/// <summary>
/// Reset a monitor to default values
/// </summary>
public void ResetMonitor ( string monitorId )
{
try
{
var monitorVm = GetMonitorViewModel ( monitorId ) ;
if ( monitorVm ! = null )
{
// Apply default values
monitorVm . Brightness = 30 ;
monitorVm . ColorTemperature = 6500 ;
monitorVm . Contrast = 50 ;
monitorVm . Volume = 50 ;
StatusText = $"Monitor {monitorVm.Name} reset to default values" ;
}
}
catch ( Exception ex )
{
StatusText = $"Failed to reset monitor: {ex.Message}" ;
}
}
/// <summary>
/// Convert monitor ID to the internal name format used by Settings UI
/// </summary>
public string GetInternalName ( MonitorViewModel monitor )
{
// For internal displays, use "Internal Display"
if ( monitor . Type = = MonitorType . Internal )
{
return "Internal Display" ;
}
// For external monitors, remove technical prefix to match SettingsManager logic
var id = monitor . Id ;
2025-10-16 17:45:49 +08:00
if ( ! string . IsNullOrEmpty ( id ) & & id . StartsWith ( "DDC_" , StringComparison . Ordinal ) )
2025-10-16 16:26:26 +08:00
{
return id . Substring ( 4 ) ; // Remove "DDC_" prefix to match SettingsManager.GetInternalName
}
// Use the full ID if no prefix found
if ( ! string . IsNullOrEmpty ( id ) )
{
return id ;
}
// Use hardware ID as secondary option if unique ID is not available
if ( ! string . IsNullOrEmpty ( monitor . HardwareId ) )
{
return monitor . HardwareId ;
}
// For external monitors, try to use a clean identifier
if ( ! string . IsNullOrEmpty ( monitor . Name ) )
{
return monitor . Name ;
}
// Fall back to a default identifier if nothing else works
return "Unknown Monitor" ;
}
// IDisposable
public void Dispose ( )
{
try
{
// 首先取消所有异步操作
_cancellationTokenSource ? . Cancel ( ) ;
// 立即停止文件监控
_settingsWatcher ? . Dispose ( ) ;
_settingsWatcher = null ;
2025-10-17 16:08:54 +08:00
// Flush any unsaved state immediately (synchronously wait)
2025-10-16 16:26:26 +08:00
try
{
// Use Task.Run to avoid deadlock and wait with timeout
2025-10-17 16:08:54 +08:00
if ( _stateManager ! = null )
2025-10-16 16:26:26 +08:00
{
2025-10-17 16:08:54 +08:00
var flushTask = _stateManager . FlushAsync ( ) ;
2025-10-16 16:48:51 +08:00
if ( ! flushTask . Wait ( TimeSpan . FromSeconds ( 2 ) ) )
{
2025-10-17 16:08:54 +08:00
Logger . LogWarning ( "State flush timed out during dispose" ) ;
2025-10-16 16:48:51 +08:00
}
2025-10-16 16:26:26 +08:00
}
}
catch ( Exception ex )
{
2025-10-17 16:08:54 +08:00
Logger . LogError ( $"Failed to flush state during dispose: {ex.Message}" ) ;
2025-10-16 16:26:26 +08:00
}
// 快速清理监控器视图模型
try
{
foreach ( var vm in Monitors )
{
vm ? . Dispose ( ) ;
}
Monitors . Clear ( ) ;
}
2025-10-16 17:45:49 +08:00
catch
{
/* 忽略清理错误 */
}
2025-10-16 16:26:26 +08:00
// 释放监控器管理器
try
{
_monitorManager ? . Dispose ( ) ;
}
2025-10-16 17:45:49 +08:00
catch
{
/* 忽略清理错误 */
}
2025-10-16 16:26:26 +08:00
2025-10-17 16:08:54 +08:00
// 释放状态管理器
2025-10-16 16:26:26 +08:00
try
{
2025-10-17 16:08:54 +08:00
_stateManager ? . Dispose ( ) ;
2025-10-16 16:26:26 +08:00
}
2025-10-16 17:45:49 +08:00
catch
{
/* 忽略清理错误 */
}
2025-10-16 16:26:26 +08:00
// 最后释放取消令牌
try
{
_cancellationTokenSource ? . Dispose ( ) ;
}
2025-10-16 17:45:49 +08:00
catch
{
/* 忽略清理错误 */
}
2025-10-16 16:26:26 +08:00
}
catch
{
// 确保 Dispose 不会抛出异常
}
}
/// <summary>
/// ViewModel for individual monitor
/// </summary>
public class MonitorViewModel : INotifyPropertyChanged , IDisposable
{
private readonly Monitor _monitor ;
private readonly MonitorManager _monitorManager ;
private readonly MainViewModel _mainViewModel ;
2025-10-16 17:45:49 +08:00
// Property managers for preventing race conditions
private readonly MonitorPropertyManager _brightnessManager ;
private readonly MonitorPropertyManager _colorTemperatureManager ;
private readonly MonitorPropertyManager _contrastManager ;
private readonly MonitorPropertyManager _volumeManager ;
2025-10-16 16:26:26 +08:00
private int _brightness ;
private int _colorTemperature ;
private int _contrast ;
private int _volume ;
private bool _isAvailable ;
private bool _isUpdating ;
2025-10-17 16:59:26 +08:00
private bool _isInteractionEnabled = true ;
2025-10-16 16:26:26 +08:00
// Visibility settings (controlled by Settings UI)
private bool _showColorTemperature ;
private bool _showContrast ;
private bool _showVolume ;
// User intent tracking for smooth slider operation
private int _targetBrightness = - 1 ;
private int _targetColorTemperature = - 1 ;
private int _targetContrast = - 1 ;
private int _targetVolume = - 1 ;
private DateTime _lastUserInteraction = DateTime . MinValue ;
public MonitorViewModel ( Monitor monitor , MonitorManager monitorManager , MainViewModel mainViewModel )
{
_monitor = monitor ;
_monitorManager = monitorManager ;
_mainViewModel = mainViewModel ;
// Initialize property managers
2025-10-16 17:45:49 +08:00
_brightnessManager = new MonitorPropertyManager ( monitor . Id , nameof ( Brightness ) ) ;
_colorTemperatureManager = new MonitorPropertyManager ( monitor . Id , nameof ( ColorTemperature ) ) ;
_contrastManager = new MonitorPropertyManager ( monitor . Id , nameof ( Contrast ) ) ;
_volumeManager = new MonitorPropertyManager ( monitor . Id , nameof ( Volume ) ) ;
2025-10-16 16:26:26 +08:00
// Initialize Show properties based on hardware capabilities
_showColorTemperature = monitor . SupportsColorTemperature ; // Only show for DDC/CI monitors that support it
_showContrast = monitor . SupportsContrast ;
_showVolume = monitor . SupportsVolume ;
// Try to get current color temperature via DDC/CI, use default if failed
try
{
// For DDC/CI monitors that support color temperature, use 6500K as default
// The actual temperature will be loaded asynchronously after construction
if ( monitor . SupportsColorTemperature )
{
_colorTemperature = 6500 ; // Default neutral temperature for DDC monitors
}
else
{
_colorTemperature = 6500 ; // Default for unsupported monitors
}
monitor . CurrentColorTemperature = _colorTemperature ;
Logger . LogDebug ( $"Initialized {monitor.Id} with default color temperature {_colorTemperature}K" ) ;
}
catch ( Exception ex )
{
Logger . LogWarning ( $"Failed to initialize color temperature for {monitor.Id}: {ex.Message}" ) ;
_colorTemperature = 6500 ; // Default neutral temperature
monitor . CurrentColorTemperature = 6500 ;
}
UpdateFromModel ( monitor ) ;
}
public string Id = > _monitor . Id ;
public string HardwareId = > _monitor . HardwareId ;
public string Name = > _monitor . Name ;
public string Manufacturer = > _monitor . Manufacturer ;
public MonitorType Type = > _monitor . Type ;
public string TypeDisplay = > Type = = MonitorType . Internal ? "Internal" : "External" ;
// Monitor property ranges
public int MinBrightness = > _monitor . MinBrightness ;
public int MaxBrightness = > _monitor . MaxBrightness ;
public int MinColorTemperature = > _monitor . MinColorTemperature ;
public int MaxColorTemperature = > _monitor . MaxColorTemperature ;
public int MinContrast = > _monitor . MinContrast ;
public int MaxContrast = > _monitor . MaxContrast ;
public int MinVolume = > _monitor . MinVolume ;
public int MaxVolume = > _monitor . MaxVolume ;
// Advanced control display logic
public bool HasAdvancedControls = > ShowColorTemperature | | ShowContrast | | ShowVolume ;
public bool ShowColorTemperature
{
get = > _showColorTemperature ;
set
{
if ( _showColorTemperature ! = value )
{
_showColorTemperature = value ;
OnPropertyChanged ( ) ;
OnPropertyChanged ( nameof ( HasAdvancedControls ) ) ;
}
}
}
public bool ShowContrast
{
get = > _showContrast ;
set
{
if ( _showContrast ! = value )
{
_showContrast = value ;
OnPropertyChanged ( ) ;
OnPropertyChanged ( nameof ( HasAdvancedControls ) ) ;
}
}
}
public bool ShowVolume
{
get = > _showVolume ;
set
{
if ( _showVolume ! = value )
{
_showVolume = value ;
OnPropertyChanged ( ) ;
OnPropertyChanged ( nameof ( HasAdvancedControls ) ) ;
}
}
}
public int Brightness
{
get = > _brightness ;
set
{
if ( _brightness ! = value )
{
// 立即更新UI状态 - 保持滑块流畅
_brightness = value ;
_targetBrightness = value ; // Record user intent
_lastUserInteraction = DateTime . Now ;
OnPropertyChanged ( ) ; // UI立即响应
2025-10-17 15:20:29 +08:00
// 队列硬件更新 - 智能错误处理:只在队列最后失败时回滚
2025-10-16 16:26:26 +08:00
_brightnessManager . QueueUpdate ( value , async ( brightness , cancellationToken ) = >
{
try
{
IsUpdating = true ;
await _monitorManager . SetBrightnessAsync ( Id , brightness , cancellationToken ) ;
2025-10-17 15:20:29 +08:00
// 硬件更新成功后保存配置( 异步, 不阻塞UI)
_mainViewModel ? . _dispatcherQueue . TryEnqueue ( ( ) = >
{
_mainViewModel . SaveMonitorSetting ( Id , "Brightness" , brightness ) ;
} ) ;
return true ; // 成功
2025-10-16 16:26:26 +08:00
}
catch ( Exception ex )
{
Logger . LogError ( $"Failed to set brightness for {Id}: {ex.Message}" ) ;
2025-10-17 15:20:29 +08:00
return false ; // 失败
2025-10-16 16:26:26 +08:00
}
finally
{
IsUpdating = false ;
}
} ) ;
}
}
}
public int ColorTemperature
{
get = > _colorTemperature ;
set
{
if ( _colorTemperature ! = value )
{
_colorTemperature = value ;
_targetColorTemperature = value ; // Record user intent
_lastUserInteraction = DateTime . Now ;
OnPropertyChanged ( ) ;
2025-10-17 15:20:29 +08:00
// 队列硬件更新 - 智能错误处理:只在队列最后失败时回滚
2025-10-16 16:26:26 +08:00
_colorTemperatureManager . QueueUpdate ( value , async ( temperature , cancellationToken ) = >
{
try
{
IsUpdating = true ;
Logger . LogDebug ( $"[{Id}] Setting color temperature to {temperature}K via DDC/CI" ) ;
// 直接使用MonitorManager的DDC/CI色温控制
var result = await _monitorManager . SetColorTemperatureAsync ( Id , temperature , cancellationToken ) ;
if ( result . IsSuccess )
{
_monitor . CurrentColorTemperature = temperature ;
Logger . LogInfo ( $"[{Id}] Successfully set color temperature to {temperature}K via DDC/CI" ) ;
2025-10-17 15:20:29 +08:00
// 硬件更新成功后保存配置( 异步, 不阻塞UI)
_mainViewModel ? . _dispatcherQueue . TryEnqueue ( ( ) = >
{
_mainViewModel . SaveMonitorSetting ( Id , "ColorTemperature" , temperature ) ;
} ) ;
return true ; // 成功
2025-10-16 16:26:26 +08:00
}
else
{
Logger . LogError ( $"[{Id}] Failed to set color temperature via DDC/CI: {result.ErrorMessage}" ) ;
2025-10-17 15:20:29 +08:00
return false ; // 失败
2025-10-16 16:26:26 +08:00
}
}
catch ( Exception ex )
{
Logger . LogError ( $"Failed to set color temperature for {Id}: {ex.Message}" ) ;
2025-10-17 15:20:29 +08:00
return false ; // 失败
2025-10-16 16:26:26 +08:00
}
finally
{
IsUpdating = false ;
}
} ) ;
}
}
}
public int Contrast
{
get = > _contrast ;
set
{
if ( _contrast ! = value )
{
_contrast = value ;
_targetContrast = value ; // Record user intent
_lastUserInteraction = DateTime . Now ;
OnPropertyChanged ( ) ;
2025-10-17 15:20:29 +08:00
// 队列硬件更新 - 智能错误处理:只在队列最后失败时回滚
2025-10-16 16:26:26 +08:00
_contrastManager . QueueUpdate ( value , async ( contrast , cancellationToken ) = >
{
try
{
IsUpdating = true ;
await _monitorManager . SetContrastAsync ( Id , contrast , cancellationToken ) ;
2025-10-17 15:20:29 +08:00
// 硬件更新成功后保存配置( 异步, 不阻塞UI)
_mainViewModel ? . _dispatcherQueue . TryEnqueue ( ( ) = >
{
_mainViewModel . SaveMonitorSetting ( Id , "Contrast" , contrast ) ;
} ) ;
return true ; // 成功
2025-10-16 16:26:26 +08:00
}
catch ( Exception ex )
{
Logger . LogError ( $"Failed to set contrast for {Id}: {ex.Message}" ) ;
2025-10-17 15:20:29 +08:00
return false ; // 失败
2025-10-16 16:26:26 +08:00
}
finally
{
IsUpdating = false ;
}
} ) ;
}
}
}
public int Volume
{
get = > _volume ;
set
{
if ( _volume ! = value )
{
_volume = value ;
_targetVolume = value ; // Record user intent
_lastUserInteraction = DateTime . Now ;
OnPropertyChanged ( ) ;
2025-10-17 15:20:29 +08:00
// 队列硬件更新 - 智能错误处理:只在队列最后失败时回滚
2025-10-16 16:26:26 +08:00
_volumeManager . QueueUpdate ( value , async ( volume , cancellationToken ) = >
{
try
{
IsUpdating = true ;
await _monitorManager . SetVolumeAsync ( Id , volume , cancellationToken ) ;
2025-10-17 15:20:29 +08:00
// 硬件更新成功后保存配置( 异步, 不阻塞UI)
_mainViewModel ? . _dispatcherQueue . TryEnqueue ( ( ) = >
{
_mainViewModel . SaveMonitorSetting ( Id , "Volume" , volume ) ;
} ) ;
return true ; // 成功
2025-10-16 16:26:26 +08:00
}
catch ( Exception ex )
{
Logger . LogError ( $"Failed to set volume for {Id}: {ex.Message}" ) ;
2025-10-17 15:20:29 +08:00
return false ; // 失败
2025-10-16 16:26:26 +08:00
}
finally
{
IsUpdating = false ;
}
} ) ;
}
}
}
public bool IsAvailable
{
get = > _isAvailable ;
set
{
_isAvailable = value ;
OnPropertyChanged ( ) ;
}
}
public bool IsUpdating
{
get = > _isUpdating ;
set
{
_isUpdating = value ;
OnPropertyChanged ( ) ;
}
}
2025-10-17 16:59:26 +08:00
/// <summary>
/// Gets or sets whether user interaction is enabled (not loading)
/// </summary>
public bool IsInteractionEnabled
{
get = > _isInteractionEnabled ;
set
{
if ( _isInteractionEnabled ! = value )
{
_isInteractionEnabled = value ;
OnPropertyChanged ( ) ;
}
}
}
2025-10-16 16:26:26 +08:00
public ICommand SetBrightnessCommand = > new RelayCommand < int? > ( ( brightness ) = >
{
if ( brightness . HasValue )
{
Brightness = brightness . Value ;
}
} ) ;
public ICommand SetColorTemperatureCommand = > new RelayCommand < int? > ( ( temperature ) = >
{
if ( temperature . HasValue & & _monitor . SupportsColorTemperature )
{
Logger . LogDebug ( $"[{Id}] Color temperature command: {temperature.Value}K (DDC/CI)" ) ;
ColorTemperature = temperature . Value ;
}
else if ( temperature . HasValue & & ! _monitor . SupportsColorTemperature )
{
Logger . LogWarning ( $"[{Id}] Color temperature not supported on this monitor" ) ;
}
} ) ;
public ICommand SetContrastCommand = > new RelayCommand < int? > ( ( contrast ) = >
{
if ( contrast . HasValue )
{
Contrast = contrast . Value ;
}
} ) ;
public ICommand SetVolumeCommand = > new RelayCommand < int? > ( ( volume ) = >
{
if ( volume . HasValue )
{
Volume = volume . Value ;
}
} ) ;
public void UpdateFromModel ( Monitor monitor )
{
bool brightnessChanged = false ;
bool colorTemperatureChanged = false ;
bool contrastChanged = false ;
bool volumeChanged = false ;
// Smart update: only update if necessary to prevent slider bouncing
if ( ShouldUpdateValue ( _brightness , monitor . CurrentBrightness , ref _targetBrightness , nameof ( Brightness ) ) )
{
_brightness = monitor . CurrentBrightness ;
brightnessChanged = true ;
}
if ( ShouldUpdateValue ( _colorTemperature , monitor . CurrentColorTemperature , ref _targetColorTemperature , nameof ( ColorTemperature ) ) )
{
_colorTemperature = monitor . CurrentColorTemperature ;
colorTemperatureChanged = true ;
}
if ( ShouldUpdateValue ( _contrast , monitor . CurrentContrast , ref _targetContrast , nameof ( Contrast ) ) )
{
_contrast = monitor . CurrentContrast ;
contrastChanged = true ;
}
if ( ShouldUpdateValue ( _volume , monitor . CurrentVolume , ref _targetVolume , nameof ( Volume ) ) )
{
_volume = monitor . CurrentVolume ;
volumeChanged = true ;
}
// Always update availability
if ( _isAvailable ! = monitor . IsAvailable )
{
_isAvailable = monitor . IsAvailable ;
OnPropertyChanged ( nameof ( IsAvailable ) ) ;
}
// Notify property changes only for values that actually changed
if ( brightnessChanged ) OnPropertyChanged ( nameof ( Brightness ) ) ;
if ( colorTemperatureChanged ) OnPropertyChanged ( nameof ( ColorTemperature ) ) ;
if ( contrastChanged ) OnPropertyChanged ( nameof ( Contrast ) ) ;
if ( volumeChanged ) OnPropertyChanged ( nameof ( Volume ) ) ;
}
private async Task UpdateBrightnessAsync ( int brightness )
{
if ( IsUpdating )
{
return ;
}
try
{
IsUpdating = true ;
await _monitorManager . SetBrightnessAsync ( Id , brightness ) ;
}
catch
{
// Revert on error
_brightness = _monitor . CurrentBrightness ;
OnPropertyChanged ( nameof ( Brightness ) ) ;
}
finally
{
IsUpdating = false ;
}
}
private async Task UpdateColorTemperatureAsync ( int temperature )
{
if ( IsUpdating )
{
return ;
}
try
{
IsUpdating = true ;
Logger . LogDebug ( $"[{Id}] Updating color temperature to {temperature}K via DDC/CI" ) ;
var result = await _monitorManager . SetColorTemperatureAsync ( Id , temperature ) ;
if ( result . IsSuccess )
{
_monitor . CurrentColorTemperature = temperature ;
Logger . LogDebug ( $"[{Id}] Successfully updated color temperature to {temperature}K" ) ;
}
else
{
Logger . LogError ( $"[{Id}] Failed to update color temperature: {result.ErrorMessage}" ) ;
// Revert on error
_colorTemperature = _monitor . CurrentColorTemperature ;
OnPropertyChanged ( nameof ( ColorTemperature ) ) ;
}
}
catch ( Exception ex )
{
Logger . LogError ( $"[{Id}] Exception updating color temperature: {ex.Message}" ) ;
// Revert on error
_colorTemperature = _monitor . CurrentColorTemperature ;
OnPropertyChanged ( nameof ( ColorTemperature ) ) ;
}
finally
{
IsUpdating = false ;
}
}
private async Task UpdateContrastAsync ( int contrast )
{
if ( IsUpdating )
{
return ;
}
try
{
IsUpdating = true ;
await _monitorManager . SetContrastAsync ( _monitor . Id , contrast ) ;
}
catch
{
// Revert on error
_contrast = _monitor . CurrentContrast ;
OnPropertyChanged ( nameof ( Contrast ) ) ;
}
finally
{
IsUpdating = false ;
}
}
private async Task UpdateVolumeAsync ( int volume )
{
if ( IsUpdating )
{
return ;
}
try
{
IsUpdating = true ;
await _monitorManager . SetVolumeAsync ( _monitor . Id , volume ) ;
}
catch
{
// Revert on error
_volume = _monitor . CurrentVolume ;
OnPropertyChanged ( nameof ( Volume ) ) ;
}
finally
{
IsUpdating = false ;
}
}
/// <summary>
/// Determines if a UI value should be updated based on hardware feedback
/// to prevent slider bouncing during user interaction
/// </summary>
private bool ShouldUpdateValue ( int currentValue , int hardwareValue , ref int targetValue , string propertyName )
{
// 1. If user just interacted (800ms), don't update to preserve smooth dragging
// 增加了时间窗口,因为现在有渐进式更新需要更多时间
if ( ( DateTime . Now - _lastUserInteraction ) . TotalMilliseconds < 800 )
{
return false ;
}
// 2. If hardware value reached target, reset target and don't update UI
if ( targetValue ! = - 1 & & Math . Abs ( hardwareValue - targetValue ) < = 2 )
{
targetValue = - 1 ; // Reset target
return false ;
}
// 3. Only update if there's a significant difference and user isn't actively dragging
if ( Math . Abs ( currentValue - hardwareValue ) > 3 )
{
return true ;
}
return false ;
}
// Percentage-based properties for uniform slider behavior
public int ColorTemperaturePercent
{
get = > MapToPercent ( _colorTemperature , MinColorTemperature , MaxColorTemperature ) ;
set
{
var actualValue = MapFromPercent ( value , MinColorTemperature , MaxColorTemperature ) ;
ColorTemperature = actualValue ;
}
}
public int ContrastPercent
{
get = > MapToPercent ( _contrast , MinContrast , MaxContrast ) ;
set
{
var actualValue = MapFromPercent ( value , MinContrast , MaxContrast ) ;
Contrast = actualValue ;
}
}
// Mapping functions for percentage conversion
private int MapToPercent ( int value , int min , int max )
{
if ( max < = min ) return 0 ;
return ( int ) Math . Round ( ( value - min ) * 100.0 / ( max - min ) ) ;
}
private int MapFromPercent ( int percent , int min , int max )
{
if ( max < = min ) return min ;
percent = Math . Clamp ( percent , 0 , 100 ) ;
return min + ( int ) Math . Round ( percent * ( max - min ) / 100.0 ) ;
}
public event PropertyChangedEventHandler ? PropertyChanged ;
protected virtual void OnPropertyChanged ( [ CallerMemberName ] string? propertyName = null )
{
PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( propertyName ) ) ;
// Notify percentage properties when actual values change
if ( propertyName = = nameof ( ColorTemperature ) )
{
PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( nameof ( ColorTemperaturePercent ) ) ) ;
}
else if ( propertyName = = nameof ( Contrast ) )
{
PropertyChanged ? . Invoke ( this , new PropertyChangedEventArgs ( nameof ( ContrastPercent ) ) ) ;
}
}
2025-10-17 16:59:26 +08:00
/// <summary>
/// Wait for all pending property updates to complete
/// </summary>
public async Task FlushAllUpdatesAsync ( )
{
await Task . WhenAll (
_brightnessManager . FlushAsync ( ) ,
_colorTemperatureManager . FlushAsync ( ) ,
_contrastManager . FlushAsync ( ) ,
_volumeManager . FlushAsync ( ) ) ;
}
2025-10-16 16:26:26 +08:00
public void Dispose ( )
{
// Dispose property managers
_brightnessManager ? . Dispose ( ) ;
_colorTemperatureManager ? . Dispose ( ) ;
_contrastManager ? . Dispose ( ) ;
_volumeManager ? . Dispose ( ) ;
}
}
public class RelayCommand : ICommand
{
private readonly Action _execute ;
private readonly Func < bool > ? _canExecute ;
public RelayCommand ( Action execute , Func < bool > ? canExecute = null )
{
_execute = execute ;
_canExecute = canExecute ;
}
public event EventHandler ? CanExecuteChanged ;
public bool CanExecute ( object? parameter ) = > _canExecute ? . Invoke ( ) ? ? true ;
public void Execute ( object? parameter ) = > _execute ( ) ;
public void RaiseCanExecuteChanged ( ) = > CanExecuteChanged ? . Invoke ( this , EventArgs . Empty ) ;
}
/// <summary>
/// Generic relay command implementation
/// </summary>
public 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 ;
_canExecute = canExecute ;
}
public event EventHandler ? CanExecuteChanged ;
public bool CanExecute ( object? parameter ) = > _canExecute ? . Invoke ( ( T ? ) parameter ) ? ? true ;
public void Execute ( object? parameter ) = > _execute ( ( T ? ) parameter ) ;
public void RaiseCanExecuteChanged ( ) = > CanExecuteChanged ? . Invoke ( this , EventArgs . Empty ) ;
}
}