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;