文章目录
- 文章合集
- 数据绑定
- 数据绑定实战
- 事件通知型
- 数据驱动,双向绑定
- 资源绑定
- 数据源绑定
- 全局数据源
- 后端和前端绑定问题
- 继承事件通知,刷新数据
- 事件通知强制刷新(无效)
- 结论:
文章合集
WPF基础知识博客专栏
WPF微软文档
WPF控件文档
B站对应WPF数据绑定视频教程
数据绑定
我们在之前的文章中,详细解释了数据模版和控件模板。简单来说数据模板和控件模板就是为了解决代码重复的问题。我们可以回顾一下之前的所有内容。
- 为了不写重复的样式,WPF提供了样式设置
- 为了减少业务代码和界面之间的沟通,WPF将简单的交互逻辑设计到了触发器
- 为了重复使用复杂控件,WPF开发了控件模板
- 控件模板又分为
- 多标签复合
- 标签元素拆分
- 标签子项重写
这个时候,为了数据的高效绑定,为了实现事件驱动到数据驱动的转变,我们要进行数据绑定。
数据绑定实战
基础样式
<StackPanel>
<Slider x:Name="sd" Width="200" />
<TextBox x:Name="txt" HorizontalAlignment="Center" Text="50"/>
</StackPanel >
事件通知型
xml代码
<StackPanel>
<Slider x:Name="sd" Width="200" ValueChanged="sd_ValueChanged"/>
<TextBox x:Name="txt" HorizontalAlignment="Center" Text="50"/>
</StackPanel >
cs代码
private void sd_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
txt.Text = e.NewValue.ToString();
}
实现效果
数据驱动,双向绑定
我们这里还是事件驱动,就是一个滑块移动事件来设置属性。WPF为了将这种简单的交互脱离业务逻辑,我们可以直接页面元素之间进行数据绑定。
<StackPanel>
<Slider x:Name="sd" Width="200" />
<TextBox x:Name="txt" HorizontalAlignment="Center" Text="{Binding ElementName=sd, Path=Value}"/>
</StackPanel >
这个代码实现了双向绑定,而且没有在CS代码中写逻辑。这个是纯数据驱动事件。让我们的业务代码专心于业务。
功能 | |
---|---|
Default | 同TowWay。双向绑定 |
OneTime | 只响应一次 |
OneWay | 单向正绑定 |
OneWayToSource | 单向负绑定 |
TwoWay | 双向绑定 |
这里推荐大家用双向绑定,而且大部分业务也是双向绑定的业务。如果有特殊需求再更改。
资源绑定
我们还可以绑定静态资源
<Window.Resources>
<TextBox x:Key="txt">Hello WPF!</TextBox>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox FontSize="50" Text="{Binding Source={StaticResource txt},Path=Text}"/>
</StackPanel >
</Grid>
数据源绑定
我们之前都是前端的数据交互,我们希望直接绑定后端数据。通过使用dataContext来实现
<StackPanel>
<TextBox x:Name="txt" FontSize="50" Text="{Binding Name}"/>
</StackPanel >
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
///获取前端名为txt的数据,传入数据源
txt.DataContext = new Person()
{
Name = "Hello C#"
};
}
}
public class Person
{
public string Name { get; set; }
}
注意,这里一定是传入一个类,这样才能绑定这个类的某个值。而且不能用已经存在的关键字命名。
全局数据源
我们一个一个绑定数据源太麻烦,所以我们可以设置全局数据源,把整个窗体的数据源都绑定上去。
新建一个MainViewModel类
public class MainViewModel
{
public MainViewModel() {
Name = "Hello WPF!";
}
public string Name { get; set; }
}
在MainWindow.xmal.cs中引入MainViewModel
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//给整个窗口设置数据源
this.DataContext = new MainViewModel();
}
}
直接绑定
<Grid>
<StackPanel>
<TextBox FontSize="50"
Text="{Binding Name}" />
<TextBox FontSize="50"
Text="{Binding Name}" />
<TextBox FontSize="50"
Text="{Binding Name}" />
<TextBox FontSize="50"
Text="{Binding Name}" />
</StackPanel >
</Grid>
后端和前端绑定问题
这里是单向绑定,后端代码更新是不影响前端的,要主动渲染。前端代码更新影响后端。
前端不重新渲染示例代码
<Grid>
<StackPanel>
<TextBox FontSize="50"
Text="{Binding Name}" />
<TextBox FontSize="50"
Text="{Binding Name}" />
<TextBox FontSize="50"
Text="{Binding Name}" />
<TextBox FontSize="50"
Text="{Binding Name}" />
<Button Click="Button_Click" Height="100" FontSize="50" Content="按钮"/>
</StackPanel >
</Grid>
public partial class MainWindow : Window
{
MainViewModel data = new MainViewModel();
public MainWindow()
{
InitializeComponent();
//给整个窗口设置数据源
this.DataContext = data;
}
//我们添加按钮事件同时打印值
private void Button_Click(object sender, RoutedEventArgs e)
{
data.Name = "data change!";
MessageBox.Show(data.Name);
}
}
继承事件通知,刷新数据
在原来的基础上
/// <summary>
/// 继承INotifyPropertyChanged
/// </summary>
public class MainViewModel: INotifyPropertyChanged
{
public MainViewModel() {
Name = "Hello WPF!";
}
/// <summary>
/// 使用public类,不然无法双向绑定
/// </summary>
public string Name { get;set; }
/// <summary>
/// 重载事件类,不然会报错
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
}
成功改变
我们将MainWindow.xmal.cs的点击按钮改造一下,看看多次点击会不会改变
public partial class MainWindow : Window
{
MainViewModel data = new MainViewModel();
public MainWindow()
{
InitializeComponent();
//给整个窗口设置数据源
this.DataContext = data;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
data.Name += "!";
MessageBox.Show(data.Name);
}
}
事件通知强制刷新(无效)
我把代码修改了一下,发现并不是强制通知,而是触发了事件,或者有点点击事件之后才会触发通知
MainViewModel代码
public MainViewModel() {
Name = "Hello WPF!";
Task.Run(async () =>
{
await Task.Delay(3000);
Name = "I have been changed!";
MessageBox.Show(Name);
});
}
/// <summary>
/// 使用public类,不然无法双向绑定
/// </summary>
public string Name { get;set; }
/// <summary>
/// 重载事件类,不然会报错
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
如果你鼠标不动,则最多停留10s钟才会刷新,如果鼠标动一动就立刻刷新
public class MainViewModel: INotifyPropertyChanged
{
public MainViewModel() {
Name = "Hello WPF!";
//Name = "I have been changed!";
Task.Run(async () =>
{
await Task.Delay(3000);
Name = "I have been changed!";
//MessageBox.Show(Name);
});
}
/// <summary>
/// 使用public类,不然无法双向绑定
/// </summary>
private string _name;
/// <summary>
/// 当修改时触发
/// </summary>
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
/// <summary>
/// 重载事件类,不然会报错
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// 强制事件通知
/// </summary>
/// <param name="propertyName"></param>
protected void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
结果还是一样。我估计是懒重载吧,就是WPF认为这个不是活动窗口,所以刷新频率低一点,这个其实是个优化吧,多窗口默认优化。
结论:
继承INotifyPropertyChanged,扩不扩展public属性,感觉效果差不多。出于简洁的目的,我们在WPF项目中就不用扩展了。