.NET WPF中如何使用MVVM模式实现特定文件变更跟踪?
解决方案:将文件变更事件传递至MVVM层
针对你的场景,有几种简洁的方式可以把文件变更事件传递到MainViewModel,同时保持MVVM的分离原则,以下是具体实现方案:
方案1:直接通过DataContext调用ViewModel方法
这是最直接的实现方式,利用View的DataContext获取ViewModel实例,调用其方法传递状态数据。
步骤1:在MainViewModel中添加状态更新方法
public class MainViewModel : INotifyPropertyChanged { private int _currentAppState; public int CurrentAppState { get => _currentAppState; set { _currentAppState = value; OnPropertyChanged(); // 这里可以添加状态变更后的业务逻辑 } } public void UpdateAppState(int newState) { // FileSystemWatcher事件在非UI线程触发,需切换到UI线程更新属性 Application.Current.Dispatcher.Invoke(() => { CurrentAppState = newState; }); } // 实现INotifyPropertyChanged接口 public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
步骤2:在MainWindow.xaml.cs的OnChanged方法中调用ViewModel方法
修改现有OnChanged方法,添加ViewModel调用逻辑:
private void OnChanged(object source, FileSystemEventArgs e) { Thread.Sleep(1000); var appState = CheckAppState(); AppState = appState; // 获取ViewModel实例并传递状态 var viewModel = DataContext as MainViewModel; viewModel?.UpdateAppState(appState); // 原有的托盘图标更新逻辑保持不变 string workingDirectory = Environment.CurrentDirectory; string projectDirectory = Directory.GetParent(workingDirectory).Parent.FullName; var path = Path.Combine(projectDirectory, "Assets"); switch (appState) { case 1: m_notifyIcon.Icon = new System.Drawing.Icon(Path.Combine(path, "48x48_active.ico")); break; case 2: m_notifyIcon.Icon = new System.Drawing.Icon(Path.Combine(path, "48x48_inactive.ico")); break; case 4: m_notifyIcon.Icon = new System.Drawing.Icon(Path.Combine(path, "48x48_running.ico")); break; default: break; } }
方案2:使用事件委托实现解耦
如果想让View和ViewModel进一步解耦,可以通过自定义事件传递状态,避免直接依赖ViewModel类型。
步骤1:在MainWindow中定义事件
public partial class MainWindow : Window { // 定义状态变更事件 public event Action<int> AppStateUpdated; // 原有的CreateFileWatcher、CheckAppState等方法保持不变,仅修改OnChanged中的触发逻辑 private void OnChanged(object source, FileSystemEventArgs e) { Thread.Sleep(1000); var appState = CheckAppState(); AppState = appState; // 触发事件传递状态 AppStateUpdated?.Invoke(appState); // 原有的托盘图标更新逻辑... } }
步骤2:在MainViewModel中订阅事件
在ViewModel初始化时订阅View的事件:
public class MainViewModel : INotifyPropertyChanged { private int _currentAppState; public int CurrentAppState { get => _currentAppState; set { _currentAppState = value; OnPropertyChanged(); } } public MainViewModel() { // 获取主窗口实例并订阅事件 var mainWindow = Application.Current.MainWindow as MainWindow; mainWindow?.AppStateUpdated += OnAppStateUpdated; } private void OnAppStateUpdated(int newState) { Application.Current.Dispatcher.Invoke(() => { CurrentAppState = newState; // 处理状态变更的业务逻辑 }); } // INotifyPropertyChanged实现... }
方案3:将文件监听逻辑移至ViewModel(更符合MVVM原则)
如果希望完全遵循MVVM的职责分离,可以把FileSystemWatcher的逻辑移到ViewModel中,再通过事件通知View更新托盘图标。
步骤1:ViewModel中实现文件监听
public class MainViewModel : INotifyPropertyChanged { private FileSystemWatcher _watcher; private int _currentAppState; public int CurrentAppState { get => _currentAppState; set { _currentAppState = value; OnPropertyChanged(); // 触发托盘图标更新事件 AppStateChangedForUI?.Invoke(value); } } // 通知View更新UI的事件 public event Action<int> AppStateChangedForUI; public void CreateFileWatcher(string path) { _watcher = new FileSystemWatcher(); _watcher.Path = path; _watcher.NotifyFilter = NotifyFilters.LastWrite; _watcher.Filter = nameof(AppStateModel); _watcher.Changed += OnFileChanged; _watcher.EnableRaisingEvents = true; } private void OnFileChanged(object source, FileSystemEventArgs e) { Thread.Sleep(1000); var appState = CheckAppState(); Application.Current.Dispatcher.Invoke(() => { CurrentAppState = appState; }); } private int CheckAppState() { // 复制原有的CheckAppState逻辑到这里 try { var tempFile = Path.Combine(AppWizard.Default.FolderPath, nameof(AppStateModel)); if (!File.Exists(tempFile)) return -1; using (var stream = new FileStream(tempFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { var appStateModel = JsonConvert.DeserializeObject<AppStateModel>(File.ReadAllText(tempFile)); if (appStateModel != null) { if (appStateModel.CurrentState == AppStates.Ready) return 1; else if (appStateModel.CurrentState == AppStates.Dead || appStateModel.CurrentState == AppStates.Expired) return 2; else if (appStateModel.CurrentState == AppStates.Busy) return 4; } } } catch (Exception ex) { FileLogger.Default.WriteExceptionLog(ex); } return -1; } // INotifyPropertyChanged实现... }
步骤2:View中订阅ViewModel的事件更新托盘
在MainWindow.xaml.cs中订阅ViewModel的事件:
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var viewModel = new MainViewModel(); DataContext = viewModel; // 订阅ViewModel的UI更新事件 viewModel.AppStateChangedForUI += UpdateTrayIcon; // 初始化文件监听 viewModel.CreateFileWatcher("你的监听路径"); } private void UpdateTrayIcon(int appState) { string workingDirectory = Environment.CurrentDirectory; string projectDirectory = Directory.GetParent(workingDirectory).Parent.FullName; var path = Path.Combine(projectDirectory, "Assets"); switch (appState) { case 1: m_notifyIcon.Icon = new System.Drawing.Icon(Path.Combine(path, "48x48_active.ico")); break; case 2: m_notifyIcon.Icon = new System.Drawing.Icon(Path.Combine(path, "48x48_inactive.ico")); break; case 4: m_notifyIcon.Icon = new System.Drawing.Icon(Path.Combine(path, "48x48_running.ico")); break; default: break; } } }
注意事项
- 线程安全:
FileSystemWatcher的事件在后台线程触发,更新UI或ViewModel属性时必须切换到UI线程(使用Dispatcher.Invoke)。 - 资源释放:窗口关闭或ViewModel销毁时,记得停止
FileSystemWatcher并释放资源,避免内存泄漏。
内容的提问来源于stack exchange,提问作者Kaan Çağhan
相关产品推荐
相关产品推荐

