使用WPF数据绑定设计响应式UI

时间:2013-04-10 06:39:37

标签: c# wpf multithreading data-binding

我是WPF的新手。我试图使用WPF绑定来理解MVVM模式。我有2个以下的课程

  1. MainWindow.xamal
  2. 视图模型

    我有三个控件

    1. 文本框,显示ViewModel的“名称”属性
    2. 文本框,显示ViewModel的“状态”依赖项属性
    3. 调用'ViewModel'类的'Execute'方法的按钮。
    4. 现在,Execute()方法有点笨重,所以我创建了一个委托并异步调用它。但是我的UI仍然是阻止,并且它没有更新“状态”依赖属性

      的值

      请参阅以下课程。

      App.xaml.cs

      namespace bindingDemo
      {
          /// <summary>
          /// Interaction logic for App.xaml
          /// </summary>
          public partial class App : Application
          {
              protected override void OnStartup(StartupEventArgs e)
              {
                  base.OnStartup(e);
                  MainWindow mw = new MainWindow();
                  ViewModel vm = new ViewModel();
      
                  ///Set data context property of main windows.
                  mw.DataContext = vm;
                  mw.Show();
              }
          }
      }
      

      MainWindow.xaml

      <Window x:Class="bindingDemo.MainWindow"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          Title="MainWindow" Height="350" Width="525">
      <Grid>
          <TextBox Text="{Binding Name, Mode=TwoWay}"   Height="23" HorizontalAlignment="Left" Margin="76,26,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
          <Button Command="{Binding Path=MyCommand}" Content="Button" Height="23" HorizontalAlignment="Left" Margin="76,127,0,0" Name="button1" VerticalAlignment="Top" Width="120" />
          <TextBox Text="{Binding Path=Status}"  Height="23" HorizontalAlignment="Left" Margin="76,55,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" />
      </Grid>
      

      ViewModel.cs

          namespace bindingDemo
      {
          public class ViewModel : DependencyObject , ICommand
          {
              public string Status
              {
                  get { return (string)GetValue(StatusProperty); }
                  set { SetValue(StatusProperty, value); }
              }
      
              // Using a DependencyProperty as the backing store for Status.  This enables animation, styling, binding, etc...
              public static readonly DependencyProperty StatusProperty =
                  DependencyProperty.Register("Status", typeof(string), typeof(ViewModel), new UIPropertyMetadata("In Progress..."));       
      
              private ICommand _command = null;
      
              public ViewModel()
              {
                  Name = "Default Name";
              }
      
      
              public void Execute(object parameter)
              {            
                  Action a = new Action(() =>
                  {
                      ///While this code is being executed, UI gets blocked.
                      Console.WriteLine(Name);
                      Name = "OK";
                      Status = "Connecting to database....";
                      Thread.Sleep(2000);
                      Status = "Connected to database....";
                      Thread.Sleep(2000);
                      Status = "Performing validations....";
                      Thread.Sleep(2000);
                      Status = "Data saved.";
      
                  });
      
                  /// Even if I have invoked operation asynchronously, UI is not getting updated
                  /// UI is freezing for 6 seconds and can directly see last 'Status' message on UI
                  Dispatcher.BeginInvoke(a, null);            
              }
      
              public string Name { get; set; }
      
              public ICommand MyCommand
              {
                  get
                  {
                      return this;
                  }
              }
      
              public bool CanExecute(object parameter)
              {
                  return true;
              }
      
              public event EventHandler CanExecuteChanged;
          }
      }
      

      有人可以帮我吗?

      此致 与Hemant

2 个答案:

答案 0 :(得分:4)

ViewModel通常不包含依赖项属性。为了能够通过数据绑定更新UI,它必须实现INotifyPropertyChanged接口 尝试像这样实现您的ViewModel:

public class ViewModel : INotifyPropertyChanged
{
    private string _status;

    public string Status
    {
        get { return _status; }
        set
        {
            if(_status == value)
                return;
            _status = value;

            OnPropertyChanged("Status");
        }
    }

    public event EventHandler<PropertyChangedEventArgs> PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if(handler != null)
            handler(new PropertyChangedEventArgs(propertyName));
    }

    // ...
}

在您的视图模型上实现ICommand似乎也很奇怪。

答案 1 :(得分:2)

这里几件事:

  1. DependencyProperty适用于......具有依赖属性的类。对于视图模型,请实现INotifyPropertyChangedDependencyObject现在绑定您的继承,而不是它的预期用途。

  2. 您正在调用Dispatcher上的操作,Dispatcher应该用于在调度程序线程上运行函数,在这种情况下,它将是UI线程。难怪它被阻止了,你在UI线程上调用一个方法。如果要从后台任务更改UI绑定值(例如,报告某种进度),Dispatcher非常有用。你必须分离你的逻辑,在后台进行处理,然后报告结果。

  3. 那就是说,你的Execute应该看起来像这样(使用C#5):

    private async Task DoStuff()
    {
         await Task.Delay(5000);
        //or drop the async modifier and 'return Task.Delay(5000);'
    }
    
    public async void Execute(object parameter)
    {
        await DoStuff();
        //Add some checks if it really was 'OK', catch exceptions etc
        Name = "OK";
    }
    

    使用C#4(未经测试):

    private Task DoStuff()
    {
        return Task.Factory.StartNew(() => Thread.Sleep(5000));
    }
    
    public void Execute(object parameter)
    {
       DoStuff().ContinueWith(result => Name = "OK", TaskScheduler.FromCurrentSynchronizationContext());
       //Same as above, probably should specify appropriate TaskOptions to run the continuation
       //only when the task was completed successfully.
    }