在代码完成执行之前动态更新WPF窗口

时间:2013-12-22 23:30:21

标签: c# wpf mvvm

这可能是一个非常基本的问题,请耐心等待,我对WPF / C#的世界还很陌生。

我有一个WPF应用程序,如果单击一个按钮,我打开一个新窗口。

该窗口称为Sync,它所做的只是实例化一个viewmodel类,它包含一些绑定到我视图的公共属性。

viewmodel还实例化一个包含大量业务逻辑的类,这会更新ViewModel的绑定属性,目的是更新窗口的内容。

这种方法有效,但只有当所有(有时非常冗长的)处理完成时,窗口加载和视图才会填充ViewModel属性的最后一个值。

我想我在这里遗漏了一些非常基本的东西。如何让我的窗口立即加载,然后在任何属性发生变化时更新视图?我应该监听PropertyChanged事件然后更新视图吗?我在哪里这样做?在视图模型的setter中?

这是一些简化的代码:

从主窗口的View Model

中调用我的窗口
public void SyncAction()
    {
        Sync syncWindow = new Sync();
        syncWindow.Show();
        syncWindow.Activate();
    }

窗口

public partial class Sync : Window
{
    public Sync()
    {
        InitializeComponent();

        var viewModel = new SyncViewModel();
    }
}

视图模型

class SyncViewModel
{

    private string _miscStatus = "";

    public SyncViewModel()
    {
        var sync = new SyncLogic();
        sync.SyncAll(this);
    }

    public string MiscStatus
    {
        get
        {
            return _miscStatus;
        }
        set
        {
            _miscStatus += value;
        }
    }
}

一些商业逻辑

class SyncLogic
{
    private ViewModel.SyncViewModel _syncViewModel;

    public void SyncAll(ViewModel.SyncViewModel syncViewModel)
    {
        _syncViewModel = syncViewModel;

        // lock our synctime
        var syncTime = DateTools.getNow();

        _syncViewModel.MiscStatus = "Sync starting at " + syncTime.ToString();

        // Do lots of other stuff

        _syncViewModel.MiscStatus = String.Format("Sync finished at at {0}, total time taken {1}", 
            DateTools.getNow().ToString(), (DateTools.getNow() - syncTime).ToString());
    }
}

奖金问题:我从业务逻辑中更新视图的方式(通过传递对viewmodel的引用并从那里更新其属性)似乎有些愚蠢。我当然希望保持业务逻辑分离,但我不确定如何将任何输出传递回viewmodel。请问这是一个更好的方法吗?

5 个答案:

答案 0 :(得分:2)

为什么关心在代码完成执行之前或之后更新是否具有视觉效果?内部属性立即更新;任何查询UI的代码都会看到新值。

用户能够在执行期间感知更新与执行之后之间的区别时,唯一一次是在UI线程上有长时间运行的计算。不要那样做。

相反,与UI异步运行计算,以便同时处理重绘消息。您可以使用后台线程执行此操作,但C#4及更高版本的新更简单方法是async。因为async是使用UI线程的延续消息实现的,所以您不需要在线程之间同步数据访问或编组UI访问。它只是有效,而且非常好。您唯一需要做的就是将代码分成足够小的块,每个块都以async方法实现,不会导致明显的延迟。

答案 1 :(得分:1)

首先,确保将viewmodel设置为视图的DataContext:

public partial class Sync : Window
{
    public Sync()
    {
        InitializeComponent();

        var viewModel = new SyncViewModel();
        DataContext = viewModel;
    }
}

其次,你必须在后台线程上运行“sync”内容。使用.Net 4.5中的async + await关键字最简单:

public async void SyncAll(ViewModel.SyncViewModel syncViewModel)
{
    _syncViewModel = syncViewModel;

    // lock our synctime
    var syncTime = DateTools.getNow();

    _syncViewModel.MiscStatus = "Sync starting at " + syncTime.ToString();

    await Task.Factory.StartNew(() => {
        // Do lots of other stuff
    });

    _syncViewModel.MiscStatus = String.Format("Sync finished at at {0}, total time taken {1}", 
        DateTools.getNow().ToString(), (DateTools.getNow() - syncTime).ToString());
}

答案 2 :(得分:1)

使用数据绑定,只要窗口通知它绑定的属性已更改,Window就会自动更新。因此,您需要在viewmodel中实现INotifyPropertyChanged,并在绑定源属性值更改时引发属性更改事件。例如:

public class SyncViewModel : INotifyPropertyChanged
{
    private string _miscStatus = "";
    public string MiscStatus
    {
        get{ return _miscStatus; }
        set
        {
            _miscStatus += value;
            OnPropertyChanged("MiscStatus");
        }
    }

    #region INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    #endregion
}

答案 3 :(得分:1)

我会做什么:

不要在ViewModel构造函数中执行任何繁重的逻辑。构造函数应该只初始化对象而不执行任何其他操作。在您的示例中,构造函数应为空。

public SyncViewModel()
{       
}

SyncLogic不应该知道ViewModel。引入一些其他类来传递输入参数和同步结果。我们说SyncArgumentsSyncResult

class SyncLogic
{
   public SyncResult SyncAll(SyncArguments syncArgs)
   {
      var syncResult = new SyncResult();

      // Do lots of other stuff
      // populate syncResult
      return syncResult;
  }  
}

在viewmodel中引入一个方法,该方法应该被调用以执行“sync”逻辑,并使该方法async。这样就可以很容易地在后台完成繁重的工作并让UI线程完成它应该做的工作,绘制UI。

public async Task Sync()
{
    // lock our synctime
    var syncTime = DateTools.getNow();
    MiscStatus = "Sync starting at " + syncTime.ToString();


    var sync = new SyncLogic();
    var syncArgs = new SyncArguments();
    //populate syncArgs from ViewModel data

    //call the SyncAll as new Task so it will be executed as background operation
    //and "await" the result
    var syncResults = await Task.Factory.StartNew(()=>sync.SyncAll(syncArgs));

    //when the Task completes your execution will continue here and you can populate the   
    //ViewModel with results

     MiscStatus = String.Format("Sync finished at at {0}, total time taken {1}", 
        DateTools.getNow().ToString(), (DateTools.getNow() - syncTime).ToString());

}

创建按钮点击事件处理程序,创建并显示窗口async,以便您可以在ViewModel上调用Sync方法

private void async Button_click(object sender, EventArgs e)
{
    Sync syncWindow = new Sync(); 
    var viewModel = new SyncViewModel();
    syncWindow.DataContext = viewModel;
    syncWindow.Show();
    syncWindow.Activate();
    await viewModel.Sync();
}

这将绘制窗口而不等待Sync方法。当Sync taks完成时,将从SyncResult填充viewmodel属性,Bindings将在屏幕上绘制它们。

希望你明白这个想法,对不起,如果我的代码中有一些错误,不确定它是否全部编译。

答案 4 :(得分:1)

如果其他人在WPF中遇到此问题,那么here描述的解决方案非常简单,对我来说工作正常。它使用扩展方法强制呈现UIElement:

public static class ExtensionMethods
{

   private static Action EmptyDelegate = delegate() { };


   public static void Refresh(this UIElement uiElement)
   {
      uiElement.Dispatcher.Invoke(DispatcherPriority.Render, EmptyDelegate);
   }
}

然后,只需使用:

    private void SomeLongOperation()
{
   // long operations...

   // UI update
      label1.Content = someValue;
      label1.Refresh();

   // continue long operations   
   }
}

引用原作者:

  

Refresh方法是一种扩展方法,它接受任何UI元素,然后调用UIElement的Dispatcher的Invoke方法。 诀窍是使用DisdercherPriority为Render或更低版本来调用Invoke方法。由于我们不想做任何事情,我创建了一个空委托。那么为什么这会实现刷新功能?

     

当DispatcherPriority设置为Render(或更低)时,代码将执行具有该优先级或更高优先级的所有操作。在该示例中,代码已经将label1.Content设置为其他内容,这将导致渲染操作。 因此,通过调用Dispatcher.Invoke,代码实质上要求系统执行Render或更高优先级的所有操作,因此控件将自行呈现(绘制新内容)。然后,它将执行提供的委托(这是我们的空方法)。