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

本文关键字:响应 UI WPF 数据绑定 使用 | 更新日期: 2023-09-27 18:08:28

我是WPF新手。我试图理解MVVM模式使用WPF绑定。我有以下两个类

  1. 主窗口。xamal
  2. ViewModel

    我有三个控件

    1. 显示ViewModel的Name属性的文本框
    2. 文本框,显示ViewModel的'Status'依赖属性
    3. 调用'ViewModel'类的' execute '方法的按钮。

    现在,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;
    }
}
有人能帮我一下吗?

问候,赫曼特

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

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. DependencyProperty为…有依赖属性的类。对于视图模型,实现INotifyPropertyChangedDependencyObject现在绑定了你的继承,这不是它的预期用途。

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

所以,话虽如此,你的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.
}