2009-11-18 531 views
9

我在我的第一個WPF應用程序中使用MVVM模式,並且出現了一些我認爲很基本​​的問題。從WPF中的ViewModel類(MVVM模式)更新用戶界面

當用戶點擊我的視圖上的「保存」按鈕時,會執行一個命令,在我的ViewModel中調用private void Save()。

問題是「Save()」中的代碼需要一些時間來執行,所以我想在執行大塊代碼之前隱藏UI視圖中的「Save」按鈕。

問題是視圖不會更新,直到視圖模型中執行所有代碼。 如何在執行Save()代碼之前強制視圖重繪和處理PropertyChanged事件?

此外,我想要一個可重複使用的方式,以便我可以很容易地在其他頁面做同樣的事情..任何人都做了這樣的事情呢? 「正在加載...」消息?

回答

11

如果需要很長時間,請考慮使用單獨的線程,例如使用BackgroundWorker,以便在執行操作時UI線程可以保持響應(即更新UI)。

在你Save方法,你會

  • 更改UI(即修改一些INotifyPropertyChanged的或DependencyProperty的IsBusySaving布爾這勢必將你的用戶界面,隱藏保存按鈕,也許顯示了IsIndeterminate = True一些的進度條)
  • 開始BackgroundWorker

在BackgroundWorker的DoWork事件處理程序中,您執行冗長的保存操作。

在UI線程中執行的RunWorkerCompleted事件處理程序中,您將IsBusySaving設置爲false,並可能更改UI中的其他內容以顯示您已完成。

代碼示例(未經測試):

BackgroundWorker bwSave; 
DependencyProperty IsBusySavingProperty = ...; 

private MyViewModel() { 
    bwSave = new BackgroundWorker(); 

    bwSave.DoWork += (sender, args) => { 
     // do your lengthy save stuff here -- this happens in a separate thread 
    } 

    bwSave.RunWorkerCompleted += (sender, args) => { 
     IsBusySaving = false; 
     if (args.Error != null) // if an exception occurred during DoWork, 
      MessageBox.Show(args.Error.ToString()); // do your error handling here 
    } 
} 

private void Save() { 
    if (IsBusySaving) { 
     throw new Exception("Save in progress -- this should be prevented by the UI"); 
    } 
    IsBusySaving = true; 
    bwSave.RunWorkerAsync(); 
} 
+0

謝謝,我會試試看。 – 2009-11-18 10:00:35

+0

對不起,我是線程的總小笨蛋。在保存代碼中,我(有時)嘗試導航到另一個頁面。但是因爲我在另一個線程中,所以會產生運行時錯誤。我想我必須對原始線程進行回調並從那裏導航到其他頁面。但我會自己嘗試,我相信與原始線程溝通並不困難。 – 2009-11-18 10:06:47

+0

「調用線程不能訪問此對象,因爲不同的線程擁有它。」是我得到的消息。如果你知道我需要什麼,請告訴我:-) – 2009-11-18 10:07:59

0

你總是可以做這樣的事情:

public class SaveDemo : INotifyPropertyChanged 
{ 
    public event PropertyChangedEventHandler PropertyChanged; 
    private bool _canSave; 

    public bool CanSave 
    { 
    get { return _canSave; } 
    set 
    { 
     if (_canSave != value) 
     { 
     _canSave = value; 
     OnChange("CanSave"); 
     } 
    } 
    } 

    public void Save() 
    { 
    _canSave = false; 

    // Do the lengthy operation 
    _canSave = true; 
    } 

    private void OnChange(string p) 
    { 
    PropertyChangedEventHandler handler = PropertyChanged; 
    if (handler != null) 
    { 
     handler(this, new PropertyChangedEventArgs(p)); 
    } 
    } 
} 

然後,你可以的按鈕CanSave屬性的IsEnabled屬性綁定,它會自動啓用/禁用。另一種方法,我會去使用Command CanExecute來排序,但這個想法足夠類似於你的工作。

+1

(1)如果您設置_canSave而不是CanSave,則不會引發OnChange。(2)我認爲它不會起作用,因爲Save在UI線程中運行,所以在Save完成之前,WPF UI將不會更新。 – Heinzi 2009-11-18 10:08:11

+0

的確的,我appriciate的答案,但我認爲它不能解決我的問題。海因茨的建議修正了它。 – 2009-11-18 10:11:47

+0

@Heinzi - CanSave的好處,是的,它會工作,因爲通知更改是在保存操作開始時提出的 - 因此,UI在此時更新。 – 2009-11-18 12:06:16

3

您正在使用MVVM模式,因此您的保存按鈕的命令被設置爲RoutedCommand對象的一個​​實例,該對象以聲明方式或命令方式添加到Window的CommandBindings集合中。

假設您以聲明方式進行。像

<Window.CommandBindings> 
    <CommandBinding 
     Command="{x:Static namespace:ClassName.StaticRoutedCommandObj}" 
     CanExecute="Save_CanExecute" 
     Executed="Save" 
    /> 
</Window.CommandBindings> 

東西執行的路由事件的處理程序,您的Save()方法,入境,你的變量設置爲false,在返回將其重新設置爲true。就像是。

void Save(object sender, ExecutedRoutedEventArgs e) 
{ 
    _canExecute = false; 
    // do work 
    _canExecute = true; 
} 

對於CanExecute的處理路由事件,所述Save_CanExecute()方法,可以使用變量作爲條件之一。

void ShowSelectedXray_CanExecute(object sender, CanExecuteRoutedEventArgs e) 
{ 
    e.CanExecute = _canExecute && _others; 
} 

我希望我很清楚。 :)

0

您可以通過下面的代碼實現這一點..

Thread workerThread = null; 
void Save(object sender, ExecutedRoutedEventArgs e) 
{ 
workerThread = new Thread(new ThreadStart(doWork)); 
SaveButton.isEnable = false; 
workerThread.start(); 
} 

把你所有的漫長過程中的DoWork()方法

在其他一些方法.. 。

workerThread.join(); 
SaveButtton.isEnable = true; 

這將導致運行保存在另一個線程冗長的過程,並不會阻止你的用戶界面,如果你想顯示一個動畫,而用戶點擊保存按鈕,然後顯示一些像iPhone等進度條...給我反饋我會盡力幫助你更多。

0

最新回答,但我覺得輸入一點也不錯。

而不是創建您自己的新線程,它可能會更好地將它留給線程池來運行保存。它並不強制它像創建自己的線程那樣立即運行,但它確實允許您保存線程資源。

做到這一點的方法是:

ThreadPool.QueueUserWorkItem(Save); 

使用這種方法,還有的問題是,你必須有你的「保存()」方法,採取一個對象,將採取行動作爲一個國家。我遇到了類似的問題,並決定走這條路線,因爲我工作的地方非常需要資源。

+0

感謝您的回答。我在我的應用程序中使用了接受的答案,它工作正常。我不知道資源使用情況與解決方案相比如何,但背景工作人員使用起來非常方便。 – 2010-07-15 11:17:04

+0

Keith:在.Net 3.5及更高版本中,如果您不需要它,請考慮使用lambda表達式修剪狀態對象。 ThreadPool.QueueUserWorkItem(state => Save()); 也許在這裏稍微多一些開銷,但代碼通常變得更簡單,少用委託放置方法。您還可以使用lambda將狀態對象轉換爲任何您需要的內容並提取特定的屬性。 ThreadPool.QueueUserWorkItem(state => Save(state as SaveArgs).Length,state as SaveArgs).TimeStamp); – Gusdor 2011-07-27 09:12:07