WPF(数据绑定)

数据绑定

定义:数据绑定是在应用 UI 与其显示的数据之间建立连接的过程。

DataContext数据上下文

**定义:**一个属性继承自FrameworkElement,因此所有控件都有,类型为object,一般放viewmodel对象。

性质:

  1. DataContext具有​继承性​:子控件会自动继承父容器的DataContext。
  2. DataContext​ 是WPF中​数据绑定的默认源​

例:

XML 复制代码
<Grid> <!-- 继承Window的DataContext -->
    <StackPanel> <!-- 继承Grid的DataContext -->
        <Slider Value="{Binding SliderValue}"/> <!-- 仍然绑定到DataModel -->
    </StackPanel>
</Grid>

Viewmodel属性要在View中显示,要将ViewModel对象赋值给View的DataContext(数据上下文)属性

Binding(绑定)

查看Binding的定义中构造函数,WPF中一切都是类,而Binding也是一个类

cs 复制代码
public Binding();
    public Binding(string path);

Binding的4种数据源

总结:4种方式的path都是属性,绑定就是目标属性和被赋值的属性关联,只是数据源不同和写法不同

(1)DataContext(隐式源)

  • 源:当前元素沿逻辑树继承下来的 DataContext。
  • 场景:MVVM 的主方式 。整个页面/整块区域共享一个 ViewModel,所有绑定都只写 Path。
cs 复制代码
Text="{Binding Person.Name}"

上面代码:实例化了一个Binding对象,Person.Name赋值给Path,等于当前的DataContext(默认数据源)中那个ViewModel对象的Person.Name属性

(2)Source(显式对象)

是Binding对象的一个object类型属性

  • 源:你明确指定的某个对象,比如资源、单例、静态对象、x:Static。
  • 场景:这个值不在 DataContext 里 ,或者你想让绑定不受 DataContext 影响。
    • 绑定到画刷资源的 Color;
    • 绑定到全局配置单例;
    • 绑定到静态字段/系统参数。
cs 复制代码
Text="{Binding Source={StaticResource RedBrush},Path=Color}"

(3)ElementName(另一个控件)

是Binding对象的一个string类型属性

  • 源:同一个 XAML 里另一个有 x:Name 的控件。
  • 场景:纯 UI 联动 ,不需要 ViewModel 参与。
    • 一个 TextBlock 显示 Slider 的当前值;
    • 一个预览区显示 TextBox 的输入;
    • 一个按钮的 IsEnabled 绑到某个 CheckBox;
    • 一个进度条绑到 Slider。
cs 复制代码
Text="{Binding ElementName=panel, Path=Margin}"

(4) RelativeSource(相对源)

Mode 源是谁 典型场景
Self 绑定目标自己 控件自己的两个属性联动,比如 ToolTip 显示自己的值、TextBlock 的 Text 绑自己的 Tag
FindAncestor 父链上某个类型的控件 子控件拿父容器/父控件的数据;DataGrid 单元格里的按钮找 DataGrid/Window
TemplatedParent 应用模板的那个控件 ControlTemplate 内部,模板子元素绑控件的属性
PreviousData 数据项列表里的上一项 ItemsControl 的项模板里,当前项和上一项比较

自己的前景色显示到Text属性上:

XML 复制代码
Text="{Binding RelativeSource={RelativeSource Mode=Self},Path=Foreground}"

从当前控件开始找上级,一个类型为StackPanel的控件,并把这个StackPanel控件的Margin显示到当前控件的Text属性上:

XML 复制代码
Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=StackPanel},Path=Margin}"

AncestorLevel:获取或设置要查找的上级级别。 使用 1 指示最靠近绑定目标元素的项。

绑定的四种模式(mode属性)

相对源的mode是RelativeSource自己的属性,不冲突。

|----------------|------------------------------------|
| 枚举值 | 说明 |
| TwoWay | 双向绑定,即导致更改源属性或目标属性时自动更新另一方。 |
| OneWay | 单向绑定,在更改绑定源(源)时更新绑定目标(目标)。 |
| OneTime | 一次绑定,在应用程序启动或数据上下文更改时,更新绑定目标。 |
| OneWayToSource | 在目标属性更改时,更新源属性。 |
| Default | 默认绑定,文本框的默认绑定是双向的,而其他大多数属性默认为单向绑定。 |

改变时机

什么是改变时机?其实就是如果前端的值发生改变,后端的值在什么时候跟着改变。它由Binding类的UpdateSourceTrigger属性的值决定 。这个属性也是一个枚举类型

|-----------------|-------------------------------------------------------------------|
| 枚举值 | 说明 |
| Default | 采用控件各自的UpdateSourceTrigger默认值。 |
| PropertyChanged | 每当绑定目标属性发生更改时,都会更新绑定源。 |
| LostFocus | 每当绑定目标元素失去焦点时,都会更新绑定源。 |
| Explicit | 仅在调用 System.Windows.Data.BindingExpression.UpdateSource 方法时更新绑定源。 |

双向绑定(属性通知)

引入:

前端属性值发生改变后,由UpdateSourceTrigger决定什么时候更新后端的Model属性值,但是,后端那些Model的属性值发生改变后,前端什么时候跟着改变?需不需要做特殊的处理?

答案是需要!如果Model中的那些属性没有实现属性通知,就算前后端成功的建立了绑定关系,后端Model属性值改变时,前端的显示是没有变化的,如果要实时跟着变化,则需要掌握WPF的INotifyPropertyChanged接口以及属性通知的相关知识点。

当不实现通知时,只是前端改变使得后端改变

属性通知

自己实现通知

要实现属性通知要继承INotifyPropertyChanged接口

cs 复制代码
public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler PropertyChanged;
}

定义一个特别的类,来实现这个功能,要通知的话要继承这个类

委托:一组方法的引用列表

事件:委托的包装器,使得这个类以外的类只能对委托进行+=(订阅),-=(退订)操作

cs 复制代码
public class ObservableObject : INotifyPropertyChanged
{
   //定义一个事件
    public event PropertyChangedEventHandler PropertyChanged;
 //[CallerMemberName]属性中使用这个方法,自动填写属性名称给到propertyName
    public void RaisePropertyChanged([CallerMemberName] string propertyName = "")
    {
       //委托调用,同步、按订阅顺序依次调用这个委托中装的方法
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
使用CommunityToolkit.Mvvm工具包实现

ObservableCollection泛型集合

转换器

IValueConverter转换器

定义:Binding类的属性,是一个IValueConverter接口,前后端建立绑定时,定义一套自定义逻辑,让前端显示的数据与后端获取的数据建立一定的对应关系。

下方代码为一个bool类型属性对应textblock是否可见的实例

定义转换规则类

cs 复制代码
 //必须继承IValueConverter接口
 public class BooltoVisiableConverst : IValueConverter
  {
      //源到页面
      public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
      {
         return value is true? Visibility.Visible : Visibility.Collapsed;
      }

      public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
      {
          return value is Visibility.Visible;
      }
  }

资源中实例化转换规则类

XML 复制代码
    <local:BooltoVisiableConverst x:Key="BooltoVisiableConverst"></local:BooltoVisiableConverst>

属性绑定函数计算结果

XML 复制代码
 <TextBox Visibility="{Binding Isbusy,Converter={StaticResource BooltoVisiableConverst}}"></TextBox>

IMultiValueConverter多值转换器

使用场景:

当你要给某个控件属性赋值时,它的值取决于"多个来源",而且其中至少一个来源 ViewModel 拿不到

来源 例子 ViewModel 能拿到吗
① ViewModel 里的数据 Ratio、Price、IsEdit ✔ 拿得到
② 控件自己的尺寸/位置 ActualWidth、ActualHeight、ActualTop、RenderSize ✘ 拿不到
③ 其它控件的状态 另一个控件的 IsSelected、Value、Text、IsChecked ✘ 拿不到

示例:一个占父控件百分之65宽带的色块

定义转换规则类

cs 复制代码
 //多值转换
 public class RatioToWidth : IMultiValueConverter
 {
     public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
     {
         double hostWidth = (double)values[0];   // 容器宽度
         double ratio = (double)values[1];   // 比例 0~1

         return hostWidth * ratio;
     }

     public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
     {
         throw new NotImplementedException();
     }
 }

资源中实例化

XML 复制代码
 <local:RatioToWidth x:Key="RatioToWidth"></local:RatioToWidth>

控件使用

XML 复制代码
 <Grid x:Name="BarHost" Height="40" Margin="20" Background="#EEEEEE" >
     <Rectangle Fill="DodgerBlue" HorizontalAlignment="Left">
         <Rectangle.Width>
             <MultiBinding Converter="{StaticResource RatioToWidth}">
                 <Binding Path="ActualWidth" ElementName="BarHost"/>
                 <Binding Path="Ratio"/>
             </MultiBinding>
         </Rectangle.Width>
     </Rectangle>
 </Grid>

ValidationRule验证规则

在"界面输入 → ViewModel"这一步拦截非法值。

**原始效果:**当规则验证为不通过时,框会变红

定义验证类

必须继承抽象类ValidationRule

必须重写Validate方法

返回类型ValidationResult构造函数如下:

cs 复制代码
 public ValidationResult(bool isValid, object errorContent)
 {
     _isValid = isValid;
     _errorContent = errorContent;
 }
cs 复制代码
 internal class AgeRule : ValidationRule
 {
     public override ValidationResult Validate(object value, CultureInfo cultureInfo)
     {
         if(value == null)
         {
             return new ValidationResult(false,"为空");
         }
         
         else if (int.Parse((string)value) >= 150|| int.Parse((string)value) < 0)
         {
             return new ValidationResult(false, "我一眼就看出你不是人");
         }
         else {
             return new ValidationResult(true, "对的对的");
         }
         
     }
 }

控件绑定中指定验证规则

绑定Binding有一个ValidationRule类型的集合属性,可以放多条规则

XML 复制代码
 <TextBox Margin="10">
     <TextBox.Text>
         <Binding Path="Text" UpdateSourceTrigger="LostFocus">
             <Binding.ValidationRules>
                 <ValidationSpace:AgeRule />
             </Binding.ValidationRules>
         </Binding>
     </TextBox.Text>
 </TextBox>

修改当验证为失败时的样式

(Validation.Errors)0.ErrorContent"

Validation.Errors是附加属性,是一个数组,(Validation.Errors)是附加属性的写法,ErrorContent是继承了ValidationRule的类的返回值类型构造函数的第二次参数

Validation.HasError = true

有错误的时候被置为true

绑定表达式内部链路

  1. 用户输入 → TextBox.Text 变化

  2. Binding 执行转换

  3. 依次调用 ValidationRules 里的每个 Validate

  4. 结果:

├─ 全部通过 → 写入源属性,清空 Errors

└─ 某条失败 → 往 TextBox 上写 Validation.Errors (附加属性)

并设置 Validation.HasError = true

源属性不更新

代码实现

XML 复制代码
<TextBox x:Name="aaa" Margin="10">
    <TextBox.Text>
        <Binding Path="Text" UpdateSourceTrigger="LostFocus">
            <Binding.ValidationRules>
                <ValidationSpace:AgeRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
    <TextBox.Style>
        <Style TargetType="TextBox">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="BorderBrush" Value="Red" />
                    <Setter Property="BorderThickness" Value="2" />
                    <Setter Property="Background" Value="#FFF0F0" />
                </Trigger>
            </Style.Triggers>
        </Style>

    </TextBox.Style>
</TextBox>
<TextBlock>
    <TextBlock.Text>
        <Binding
            ElementName="aaa"
            Mode="OneWay"
            Path="(Validation.Errors)[0].ErrorContent" />
    </TextBlock.Text>
</TextBlock>
相关推荐
samble1 天前
从零搭建半导体设备上位机(四):单实例、启动流程与配置文件,App 里那 526 行到底在干什么
wpf·配置文件·单实例
浪扼飞舟1 天前
WPF(控件的继承树、内容控件、布局控件)
wpf
avi91114 天前
电影开源Jevdo - 源码介绍(vue版)没有android 和 没有wpf
android·wpf·框架·数据爬虫·meego·电影管理
吾与谁归in5 天前
C#脚本工具,支持智能提示自动补全(WPF + Roslyn + AvalonEdit)
开发语言·c#·wpf
昂好的day5 天前
WPF 从零入门(上):WPF 是什么、XAML 语法、布局与样式全解
开发语言·c#·wpf
影寂ldy6 天前
WPF导航窗体+Page页面跳转 知识点整理
wpf
He BianGu6 天前
【WPF-Control教程】IOC 依赖注入
c#·wpf·wpf-control
He BianGu6 天前
【WPF-Control教程】H.Extensions.Behvaiors 开发文档
c#·wpf·wpf-control
He BianGu6 天前
【WPF-Control教程】H.Style 开发文档
c#·wpf·wpf-control