2009-11-18 57 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

相关问题