4

我试图在FileSystemWatcher通知更改时更新我的​​ObservableCollection。我知道这是不可能的,因为跨线程操作。
因此,我想在触发事件时获取创建/删除/重命名的文件的名称,并在事件完成后在UI线程中更新它,就像我们在BackgroundWorker中做的那样。谁能告诉我如何做到这一点?根据FileSystemWatcher更改通知更改ObservableCollection

也告诉我在哪里我应该定义和启动这个FileSystemWatcher。目前我已经在MainViewModel中定义了它。

PS:我已经看到了这样类似的问题,但并没有得到清晰的图像

由于事先
德维尔

回答

2

我认为主要的视图模型是正确的位置来定义FileSystemWatcher。而对于线程问题,这是最简单的方法:

_watcher = new FileSystemWatcher(path); 
_watcher.Created += (obj, e) => 
    Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() => 
    { 
    // Code to handle Created event 
    }; 
_watcher.Changed += (obj, e) => 
    Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() => 
    { 
    // Code to handle Changed event 
    }; 
_watcher.Renamed += (obj, e) => 
    Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() => 
    { 
    // Code to handle Renamed event 
    }; 
_watcher.Deleted += (obj, e) => 
    Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() => 
    { 
    // Code to handle Deleted event 
    }; 
// ... 
_watcher.EnableRaisingEvents = true; 

各的“代码来处理”,将在UI线程中执行,因此它可以更新ObservableCollection。请注意,此代码中提供了FileSystemEventArgs“e”。

如果你喜欢使用单独的事件处理方法,你可以从上面的代码中调用它们或用这个方便的快捷键:

var switchThread = 
    (FileSystemEventHandler handler) => 
    (object obj, FileSystemEventArgs e) => 
     Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() => 
     handler(obj, e)) 

_watcher = new FileSystemWatcher(path); 
_watcher.Created += switchThread(OnCreated); 
_watcher.Changed += switchThread(OnChanged); 
_watcher.Deleted += switchThread(OnDeleted); 
_watcher.Renamed += switchThread(OnRenamed); 
_watcher.EnableRaisingEvents = true; 

其中OnCreatedOnChangedOnDeletedOnRenamed是正常的事件处理方法与正常的签名,例如:

void OnChanged(object sender, FileSystemEventArgs e) 
{ 
    // Code to handle Changed event 
} 

个人而言,我更喜欢做它的第一种方式,因为我不喜欢创建四个额外的1线的方法。

请注意,您的视图模型将需要知道回拨哪个Dispatcher。如上所述,最简单的方法是从DispatcherObject派生视图模型。另一种方式是视图模型的构造函数或注册FileSystemWatcher事件的方法在本地字段或局部变量中存储Dispatcher.Current的副本,然后将其用于.BeginInvoke调用。

另请注意,如果您愿意,您可以在视图代码隐藏中使用完全相同的代码,而不是视图模型中的代码。

+0

很酷。 我想监视我的整个计算机,并为每个驱动器创建单独的FileSystemWatcher对象。这可以吗?或者有没有办法用一个监视器对象来监视所有的驱动器? – Amsakanna 2010-03-04 05:36:13

+0

我不认为有任何方法可以用一个'FileSystemWatcher'来监视所有的驱动器,但是可以为每个驱动器创建一个'FileSystemWatcher'。您可以使用FileSystemWatcher.IncludeSubdirectories属性来监视整个驱动器。我没有测试过这个表现。另请注意,如果FileSystemWatcher的内部缓冲区溢出,您将收到一个重置事件,此时您必须重新扫描该树以查找更改的内容。 – 2010-03-04 06:12:11

+0

目前我已经为每个驱动器创建了一个观察器。但正如你所说,已经检查了缓冲区溢出...... – Amsakanna 2010-03-04 09:24:22

2
public void SomeActionToBeInvokedOnTheMainThread() 
{ 
    if (someControl.Dispatcher.CheckAccess()) 
    { 
     // you can modify the control 
    } 
    else 
    { 
     someControl.Dispatcher.Invoke(
      System.Windows.Threading.DispatcherPriority.Normal, 
      new Action(SomeActionToBeInvokedOnTheMainThread) 
     ); 
    } 
} 
+0

对我的第二个问题有任何建议吗? – Amsakanna 2010-03-03 15:02:28

+0

此代码是**反模式**。与简单得多相比,它的效率明显较低,可读性也较差:'someControl.Dispatcher.Invoke(DispatcherPriority.Send,new Action(()=> {...修改控件的代码在这里...}) )'。 – 2010-03-03 18:32:57

+0

效率不高的原因是CheckAccess被调用三次:一次在调用中,一次在Dispatcher.Invoke中,一次在递归调用中。调用Dispatcher.Invoke直接绕过所有这些。 **唯一一次'CheckAccess'完全有意义**是,如果99%以上的调用将来自同一个线程*,并且*调用来自同一个线程时,性能绝对至关重要(性能差异在此case小于1ns,所以你最好有一个很好的理由,用一些奇怪的递归来混淆你的代码!)。 – 2010-03-03 18:36:53

1

我使用了Ray B.的方法,但不得不稍微修改一些东西,并认为我会在此处发布更新以便可能为其他人节省一些时间。

我的VS2010/.NET 4.0的WPF项目被扔的错误:

Cannot assign lambda expression to an implicitly-typed local variable 

一些调整,我想出了以下后。请注意定义的另一个变量来处理更名事件:

var switchThreadForFsEvent = (Func<FileSystemEventHandler, FileSystemEventHandler>)(
     (FileSystemEventHandler handler) => 
       (object obj, FileSystemEventArgs e) => 
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() => 
         handler(obj, e)))); 

var switchThreadForFsRenameEvent = (Func<RenamedEventHandler, RenamedEventHandler>)(
      (RenamedEventHandler handler) => 
       (object obj, RenamedEventArgs e) => 
        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action(() => 
         handler(obj, e)))); 

_fileSystemWatcher = new FileSystemWatcher(documentCollectionPath); 
_fileSystemWatcher.Created += switchThreadForFsEvent(OnFileCreated); 
_fileSystemWatcher.Deleted += switchThreadForFsEvent(OnFileDeleted); 
_fileSystemWatcher.Renamed += switchThreadForFsRenameEvent(OnFileRenamed); 
_fileSystemWatcher.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.FileName; 
_fileSystemWatcher.IncludeSubdirectories = true; 
_fileSystemWatcher.EnableRaisingEvents = true;