需求:
滑动条拖动,文本框值变化
文本框值变,滑动条值也变化
实际上是一种控件双方绑定的需求。
靠事件的实现:
/// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { txt1.Text = slider.Value.ToString(); txt2.Text = slider.Value.ToString(); txt3.Text = slider.Value.ToString(); } private void txt1_TextChanged(object sender, TextChangedEventArgs e) { slider.Value = double.Parse(txt1.Text); } }
<Grid> <StackPanel> <Slider x:Name="slider" Margin="5" ValueChanged="Slider_ValueChanged"/> <TextBox x:Name="txt1" Margin="5" Height="30" TextChanged="txt1_TextChanged"/> <TextBox x:Name="txt2" Margin="5" Height="30"/> <TextBox x:Name="txt3" Margin="5" Height="30"/> </StackPanel> </Grid>
靠事件的方式的缺点:
靠事件代码维持这种双向绑定的效果,实际上后台代码应该是处理工作逻辑,而不是进行这种控件事件的处理。
控件与控件的绑定
<Grid> <StackPanel> <Slider x:Name="slider" Margin="5"/> <TextBox Text="{Binding ElementName=slider,Path=Value}" Margin="5" Height="30"/> <TextBox Text="{Binding ElementName=slider,Path=Value}" Margin="5" Height="30"/> <TextBox Text="{Binding ElementName=slider,Path=Value}" Margin="5" Height="30"/> </StackPanel> </Grid>
此模式下就完成了双向绑定,有几个特点:
1。 注意此时后台不需要任何代码的,我们也删除了事件的方式,这种绑定完全是在xmal端完成的
2。 此时3个textbox不需要命名的。
3。 你也可以指定绑定模式
Default 默认的双向绑定
OneTime 绑定变化只反映出第一个值的变化
OneWay 单方面模式,对于本例,只允许slider改变textbox,而反过来不允许
OneWayToSource 就是OneWay倒过来,只允许Textbox改变slider
TwoWay 双向绑定
控件与属性的绑定
指的是后台的某个类的属性做绑定关系。
<Grid> <StackPanel> <TextBox Text="{Binding Name}" Margin="5" Height="30"/> </StackPanel> </Grid>
注意DataContext这个是数据上下文。
有了它,上面xmal中的{Binding Name} 才真正有的意义。(没上下文就找不到数据源Name)
namespace WpfApp1 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new MyData() { Name = "张三" }; } } public class MyData { public string Name { get; set; } } }
控件的Command
对一个按钮来说,虽然可以有一个点击事件然后后台书写代码。
但是可以使用高级的Command机制做响应点击的动作。
注意两者各有应用场合,并不是谁替代谁。
xmal代码:
<StackPanel> <Button Command="{Binding ShowCommand}" Height="30" Width="30"/> </StackPanel>
C#代码:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace WpfApp1 { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new MainViewModel(); } } public class MainViewModel { public MainViewModel() { ShowCommand = new MyCommand(Show); } public MyCommand ShowCommand { get; set; } public void Show() { MessageBox.Show("Hello world"); } } public class MyCommand : ICommand { Action _act; public event EventHandler CanExecuteChanged; public MyCommand(Action act) { this._act = act; } public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { this._act.Invoke(); } } }
控件更新通知
解决在Command代码中,更新Name属性,无法更新到控件上的问题。
只要继承 INotifyPropertyChanged接口,使用它提供的PropertyChanged事件即可完成更新。
Xmal代码:
注意{Binding Name} 这个绑定实际上就是做了订阅通知事件。
<Grid> <StackPanel> <TextBlock Text="{Binding Name}"/> <Button Command="{Binding ShowCommand}" Height="30" Width="30"/> </StackPanel> </Grid>
后台代码:
public class MainViewModel: INotifyPropertyChanged { public MainViewModel() { ShowCommand = new MyCommand(Show); name = "刘备"; } private string name; public string Name { get { return name; } set { name = value; PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); } } public MyCommand ShowCommand { get; set; } public event PropertyChangedEventHandler PropertyChanged; public void Show() { Name = "刘备2"; MessageBox.Show("Hello world"); } }
优化代码,弄个基类,简化多属性情况下的使用麻烦问题。
几点说明:
1。 由基类NodifyBase做事件通知
2。 基类中的[CallerMemberName]特性,可以取得传入参数的值。这样我们就不用写传入值了。(写了也可以)
public class MainViewModel: NodifyBase { public MainViewModel() { ShowCommand = new MyCommand(Show); name = "刘备"; } private string name; public string Name { get { return name; } set { name = value; //PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); OnPropertyChange(); } } private string title; public string Title { get { return title; } set { title = value; //PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Title")); OnPropertyChange(); } } public MyCommand ShowCommand { get; set; } // public event PropertyChangedEventHandler PropertyChanged; public void Show() { Name = "刘备2"; MessageBox.Show("Hello world"); } } public class NodifyBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; public void OnPropertyChange([CallerMemberName]string propertyName="") { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
MvvmLight框架
nuget安装 MvvmLight。
修改MainViewModel代码:
我们可以看到这个框架也实现上面Command和控件更新通知的效果。
public class MainViewModel:ViewModelBase { public RelayCommand ShowCommand { get; } public MainViewModel() { ShowCommand = new RelayCommand(Show); name = "刘备"; title = "windows"; } private string name; public string Name { get { return name; } set { name = value; RaisePropertyChanged(); } } private string title; public string Title { get { return title; } set { title = value; RaisePropertyChanged(); } } public void Show() { Name = "刘备2"; Title = "windows12"; MessageBox.Show("Hello world"); } }

