WPF (2) WPF コアの高度な

1. 組み合わせたコントロール

  • コントロール モデル: WPF には、コントロールを作成するための 3 つの一般的なモデルが用意されており、それぞれが異なる機能セットと柔軟性を提供します。3 つのモデルの基本クラスは、UserControl、Control、および FrameworkElement です。このうちUserControlはContentControlを継承したUser Controlと呼ばれるもので、Windowウィンドウと同様の簡単なレイアウトコントロール作成方法(複合制御を実現するため)を提供します。Control および FrameworkElement はカスタム コントロールと呼ばれます。カスタム コントロールはユーザー コントロールよりも下位レベルであり、より多くのコントロールを取得するほど、継承する機能は少なくなります。ユーザー コントロールとカスタム コントロールの主な違いの 1 つは、カスタム コントロールではスタイル/テンプレート (DataTemplate/ContentTemplate) を設定できるのに対し、ユーザー コントロールでは設定できないことです。

  • UserControl から派生する利点:次のすべてに当てはまる場合は、 UserControlから派生することを検討してください(例として UserControl を使用します)。

    • アプリケーションを構築するのと同じような方法でコントロールを構築したいと考えています。

    • コントロールには既存のコンポーネント (複合コントロール) のみが含まれます。

    • 複雑なカスタマイズをサポートする必要はありません。

  • ユーザーの組み合わせコントロールの実践: UserControl の使用方法は、ユーザー コントロールの .xaml UI ファイルと郵便コードの .xaml.cs ファイルを含む、WPF ウィンドウ ウィンドウに似ています。UserControl コントロールには、他のコントロールをレイアウトするための WPF ウィンドウと同様に動作する Content オブジェクト プロパティが含まれています。ユーザー コントロールは、マークアップとコードを再利用可能なコンテナにグループ化する概念です。実際には、TextBox 内のテキストの量を特定の文字数に制限しながら、使用された文字数と使用可能な文字数をユーザーに表示できる、シンプルで再利用可能なユーザー コントロールを作成します。

1. UserControl コントロール定義

<UserControl x:Class="WPF_Demo.LimitedInputUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:WPF_Demo"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <Label Content="{Binding Title}" />
        <Label Grid.Column="1">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding ElementName=txtLimitedInput,Path=Text.Length}"/>
                <TextBlock Text="/" />
                <TextBlock Text="{Binding MaxLength}"/>
            </StackPanel>
        </Label>
        <TextBox Name="txtLimitedInput" Grid.Row="1" Grid.ColumnSpan="2" MaxLength="{Binding MaxLength}" TextWrapping="Wrap" ScrollViewer.VerticalScrollBarVisibility="Auto"/>
    </Grid>
</UserControl>
namespace WPF_Demo
{
    
    
    /// <summary>
    /// LimitedInputUserControl.xaml 的交互逻辑
    /// </summary>
    public partial class LimitedInputUserControl : UserControl
    {
    
    
        public LimitedInputUserControl()
        {
    
    
            InitializeComponent();
            this.DataContext = this;
        }

        public string Title {
    
     set; get; }
        public int MaxLength {
    
     set; get; }
    }
}

2. UserControl コントロールの使用

<Window x:Class="WPF_Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        xmlns:cv="clr-namespace:WPF_Demo.Converts"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="300">

    <Grid Margin="10">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <local:LimitedInputUserControl Title="Enter title:" MaxLength="30" Height="50"/>
        <local:LimitedInputUserControl Title="Enter description:" MaxLength="140" Grid.Row="1"/>
    </Grid>
</Window>

画像

2. 依存関係のプロパティ

上記の例では、TextOptions や ToolTipService などのプロパティと、データ バインディングで宣言された Binding オブジェクトがコントロールの基本プロパティではないことがわかりますが、これらのプロパティが現在のコントロール オブジェクトに直接影響を与えるのはなぜでしょうか。これは実際には、WPF で機能する依存関係プロパティです。依存関係プロパティの目的は、次のように、他の入力の値に基づいてプロパティの値を計算する方法を提供することです。

1 依存関係プロパティの概要

WPF では、依存関係プロパティと呼ばれる新しいプロパティ メカニズムが導入されており、依存関係プロパティは通常のプロパティによってパッケージ化できるため、通常のプロパティ操作と何ら変わりなく使用できます。その目的は、他の入力の値に基づいてプロパティの値を計算する方法を提供することです。つまり、依存関係プロパティは、それ自体には値を持たず、Binding メカニズムを使用して (他のオブジェクトに応じて) データ ソースから値を取得できるプロパティです。依存関係プロパティを持つオブジェクトは「依存オブジェクト」と呼ばれます。依存関係プロパティの利点は次のとおりです。

  • インスタンスのメモリ オーバーヘッドを節約する: 現在 100 個の標準属性を持つオブジェクトがあり、その背後に 4 バイトのフィールドが定義されているとします。10,000 個のそのようなオブジェクトを初期化すると、これらのフィールドは 100×4× 10000= 3.81M のメモリを占有することになります。しかし実際には、すべての属性を使用するわけではないため、メモリの大部分が無駄になります。しかし、依存関係プロパティを使用すると、さまざまなプロパティを統一された方法で提供でき、プロパティ値が明確に設定/使用されている場合にのみスペースを空けることができるため、メモリ消費を削減できます。
  • バインディングを通じて属性値を他のオブジェクトに依存させることができ、オブジェクト属性間の計算関係を自動的に確立して「データ バインディング」の効果を完成させることができます

2 依存関係プロパティの動作メカニズム

WPF システムでは、依存オブジェクトの概念は、DependencyObject クラスによって実現され、依存関係プロパティの概念は、DependencyProperty クラスによって実現されます。dependencyObject には GetValue と SetValue という 2 つのメソッドがあり、どちらもDependencyProperty オブジェクトをパラメータとして受け取ります。GetValue メソッドは、DependencyProperty オブジェクトを通じて依存データ ソースのデータを取得します。DependencyObject とDependencyProperty は密接に結合されています。WPF の開発では、依存オブジェクトを依存プロパティのホストとして使用する必要があり、この 2 つの組み合わせにより、データによって駆動される完全なバインディング ターゲットを形成できます。つまり、依存関係プロパティを使用するには、DependencyObject クラスを継承する必要があります。DependencyObject が提供する GetValue メソッドと SetValue メソッドを、DependencyProperty と組み合わせることでのみ、依存関係プロパティの役割を果たすことができます。具体的な理由は、ソース コードの分析で説明します。

WPF では、WPF のすべての UI コントロールは依存オブジェクトです。WPF のクラス ライブラリは、設計時に依存関係プロパティの利点を最大限に活用しており、幅、高さ、テキストなど、UI コントロールのほとんどのプロパティはすでに依存しています。私たちが通常使用するこれらのプロパティは、内部依存関係プロパティのラッパーにすぎませんが、依存関係プロパティの存在にさえ気づきません。それがフィールドであろうと依存関係プロパティであろうと、外部から見える操作はすべてそのプロパティであるためです。

ここに画像の説明を挿入

3 依存関係プロパティの使用

public class MyClass : DependencyObject //1.继承DependencyObject类
{
    
    
    public string MyName //3.依赖属性包装器
    {
    
    
        get => (string)GetValue(MyNameProperty);  //使用DependencyObject类提供的GetValue方法取值
        set => SetValue(MyNameProperty, value); 
    }
 
 
    public static readonly DependencyProperty MyNameProperty =
        DependencyProperty.Register("MyName", typeof(string), typeof(MyClass)); //2.初始化DependencyProperty依赖属性对象
}
- 要点一 : 使用依赖属性必须要继承DependencyObject类,目的的是结合其提供的Set/GetValue方法以及内部提供的一些数据结构来操作DependencyProperty
- 要点二 : 依赖属性声明需要使用public static readonly三个修饰符修饰,实例依赖属性也不是通过new操作符,而是通过DependencyProperty的Register方法来获取。
	- 修饰符目的 : 通过static将依赖属性对象声明为静态的属于类的而不是每个实例,减少内存开销;通过readonly是为了保证依赖属性对象内部一些属性值计算的稳定性(GlobaIndex)
- 要点三 : 依赖属性对象的命名约定 : 以Property为后缀,以表明它是一个依赖属性
- 要点四 : Register方法有三个重载,此处用的是其三个参数的重载,它还有四个参数和五个参数的重载。我们这里介绍一下它的主要参数
	- 第一个参数(string) : 指定依赖属性的包装器名称是什么(包装器是通过一个属性来包装依赖属性供外部使用的)
	- 第二个参数(type) : 指定依赖属性要存储的值的类型是什么
	- 第三个参数(type) : 指定依赖属性属于哪个类的,或者说是为哪个类定义依赖属性(依赖属性的宿主类型)
- 要点五 : 我们可以通过声明依赖属性包装器将依赖属性暴露出去,内部将依赖属性的SetValue/GetValue封装起来。对依赖属性的这层包装,使得我们在外部操作依赖属性变得简单,这也是为什么我们在正常使用中感觉不到依赖属性的存在
public MainWindowBase()
{
    
    
    InitializeComponent();
    this.DataContext = this;
    Data = "我是皮卡丘";
    MyClass myclass = new MyClass(); 
    //使用Binding操作类将MyClass对象的名字依赖属性关联到Data上
    BindingOperations.SetBinding(myclass,MyClass.MyNameProperty, new Binding("Data") {
    
     Source = this });
    //将按钮的Content依赖属性绑定到皮卡丘的皮卡丘名字包装器上
    btn_show.SetBinding(Button.ContentProperty, new Binding(nameof(myclass.MyNameProperty)) {
    
     Source = myclass });
}

この例のロジックは、Data という名前のプロパティをデータ ソースとして使用することです。まず、ピカチュウ オブジェクトの依存関係プロパティを Data データ ソースにバインドし、次に Button の Content 依存関係プロパティを、ボタンの依存関係プロパティ ラッパーにバインドします。ピカチュウ オブジェクト。これによりバインド チェーンが形成されます。プロセス全体で、「I am Pikachu」のデータを格納するフィールドでサポートされているのは Data 属性だけです。Pikachu オブジェクトと Button オブジェクトは両方とも依存属性であり、メモリ領域を占有しません。バインディングはそれらの間で使用されます。データ チャネルを使用して、複数の用途に使用できるメモリを実現します。以前のプログラミング モデルによれば、ピカチュウとボタンはそれぞれ Data からのデータを保存するためのスペースを開く必要がありましたが、これで 3 つのメモリが 1 つのメモリに保存されます。これはメモリの節約に対する依存関係プロパティの効果です。

4. 依存関係プロパティのソースコード解析

(1)DependencyPropertyクラス

 public sealed class DependencyProperty
 {
    
    
     ...
     //关键1 全局Hashtable : PropertyFromName是DependencyProperty类中的一个全局静态的Hashtable,它类似于Dictionary,是一个键值对集合。这个全局Hashtable就是用来注册保存DependencyProperty实例对象的
     private static Hashtable PropertyFromName = new Hashtable();
     
     ...
     //关键2 GlobalIndex唯一标识 : 该属性是一个只读的实例属性,通过算法生成每个DependencyProperty实例对象的全局且唯一的标识ID
     public int GlobalIndex
     {
    
    
         get {
    
     return (int) (_packedData & Flags.GlobalIndexMask); }
     }
     
     ...
     //关键3 : RegisterCommon 注册 : DependencyProperty的Register方法最终会调用RegisterCommon来进行全局依赖属性对象的注册(生成key存入Hashtable)
      private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata, ValidateValueCallback validateValueCallback)
        {
    
    
         	//根据DependencyProperty注册时的 包装器name和所属类型ownerType参数,通过FromNameKey类提供的异或运算方式生成对应的唯一key
            FromNameKey key = new FromNameKey(name, ownerType);
            lock (Synchronized)
            {
    
    
                //如果有重复值key:则抛出异常(也就是说每对 “宿主类型-包装器属性名称” 只能对应生成一个全局DependencyProperty依赖属性对象)
                if (PropertyFromName.Contains(key))
                {
    
    
                    throw new ArgumentException(SR.Get(SRID.PropertyAlreadyRegistered, name, ownerType.Name));
                }
            }
 
            //做一些校验逻辑、默认值计算等操作
            ...
 
            // new DependencyProperty对象,经过层层把关,依赖属性终于new出来
            DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
 
            ...
 
            //PropertyFromName是存储依赖属性对象的一个全局key-value集合,所有new出来的依赖对象都存储在这里,它的key就是之前通过FromNameKey类通过异或出的。
            lock (Synchronized)
            {
    
    
                PropertyFromName[key] = dp;
            }
            //返回生成的DependencyProperty全局对象
            return dp;
        }
 
 }

(2)DependencyObjectクラス

public class DependencyObject : DispatcherObject
{
    
    
    ...
    //关键一 : EffectiveValueEntry数组,用于存储EffectiveValueEntry类型,EffectiveValues采用索引——值的形式,保存的是本对象实例中所有用到的依赖属性所对应设置的值。要注意的是该数组非静态全局,是属于每个对象实例的。因此我们可以得出,依赖属性是全局static保存的,但依赖属性对应的值在每个实例对象中都是不一样的,因此保存在每个实例对象的EffectiveValueEntry数组中(用到才保存,用不到不存储节约空间),值和属性分开存储,建立映射。
    private EffectiveValueEntry[] _effectiveValues;
    
    ...
     //关键二 : 获取DependencyProperty依赖属性在本对象实例中对应的值,根据dp.GlobalIndex唯一标识来在本地EffectiveValueEntry数组中获取索引值
     public object GetValue(DependencyProperty dp)
     {
    
    
            // Do not allow foreign threads access.
            // (This is a noop if this object is not assigned to a Dispatcher.)
            //
            this.VerifyAccess();
 
            if (dp == null)
            {
    
    
                throw new ArgumentNullException("dp");
            }
 
            // Call Forwarded
            return GetValueEntry(
                    LookupEntry(dp.GlobalIndex),
                    dp,
                    null,
                    RequestFlags.FullyResolved).Value;
     }
    
    ...
    //关键三 : 设置DependencyProperty依赖属性的值。检查EffectiveValucEntry数组中是否已经存在相应依赖属性的位置,如果有则把旧值改写为新值,如果没有则新建EffectiveValueEntry对象并存储新值。这样,只有被用到的值才会被放进这个列表,借此,WPF系统用算法(时间)换取了对内存(空间)的节省。
    public void SetValue(DependencyProperty dp, object value)
    {
    
    
            // Do not allow foreign threads access.
            // (This is a noop if this object is not assigned to a Dispatcher.)
            //
            this.VerifyAccess();
 
            // Cache the metadata object this method needed to get anyway.
            PropertyMetadata metadata = SetupPropertyChange(dp);
 
            // Do standard property set
            SetValueCommon(dp, value, metadata, false /* coerceWithDeferredReference */, false /* coerceWithCurrentValue */, OperationType.Unknown, false /* isInternal */);
   }
}

したがって、依存関係プロパティは次のように機能します。

  • プログラムの実行中に、DependencyProperty クラスがメモリにロードされ、初期化されてグローバル静的 PropertyFromName オブジェクト (HashTable キーと値のコレクション、DependencyProperty クラスのグローバル静的共有! 最初は空) が作成されます。
  • すべての依存関係プロパティ (すべてのDependencyObjects のグローバルおよび静的) を作成して登録し、一意のグローバル キーを生成して、各依存関係プロパティ オブジェクトのインスタンスを Hashtable に保存します。このとき、DependencyProperty の静的フィールド PropertyFromName には、すべてのグローバル依存関係プロパティの元の値 (デフォルト値) が保存されます。
  • dependencyObject オブジェクト (コントロール、要素など) を初期化し、空の配列 EffectsValueEntry を初期化して作成します (配列はグローバルではなく、オブジェクトによって保持されます。つまり、各 dependencyObject オブジェクトには独自の EffectsValueEntry 配列があり、これが使用されます)このオブジェクトで使用されるものを格納します。DependencyProperty依存プロパティの値。ただし、異なるオブジェクト インスタンス間では、同じラッパー プロパティが同じグローバルの dependencyProperty クラスを共有します)。
  • プロパティ値を読み取ります (DependencyObject によって提供される GetValue() メソッドを使用して、このオブジェクトのEffectiveValueEntry 配列に格納されているDependencyProperty 依存関係プロパティに対応する値を取得します)。優先順位は、最初にローカルのEffectiveValueEntry 配列を見つけることです。そうでない場合は、見つけます。継承、アニメーション、スタイルなどが見つからないため、PropertyFromName に移動してデフォルト値を見つけます。
  • 値を設定します (DependencyObject の SetValue() メソッドを使用して、オブジェクト インスタンスのEffectiveValueEntry 配列に値を保存します)。

5 つの追加属性

付加属性とは、オブジェクトに付加される属性であり、本来はその属性を持たないが、このオブジェクトに付加されることでこの属性が付加される、このような属性を追加属性と呼びます。
: 添付プロパティは依存関係プロパティでもありますが、登録方法と表現方法が少し異なるだけです。

6つのショートカット

- prop+tab : 自动创建 属性
- propdp+tab : 自动创建 依赖属性及其包装器
- propfull+tab : 自动创建 属性及其字段
- propa+tab : 自动创建 附加属性

3. データバインディング

1 データバインディングの概念

データ バインディングの中核は Binding オブジェクトと依存関係プロパティです。依存関係プロパティについては以前に説明したので、ここで Binding オブジェクトの概念を紹介しましょう。WPF のデータ バインディング機能は、アプリケーションを提示してデータを操作するための簡単かつ一貫した方法を提供します。データ バインディングを通じて、2 つの異なるオブジェクトの属性の値を同期できます。Binding オブジェクトはデータ バインディングのデータ チャネルです。

つまり、バインディングの橋の上に高速道路が敷設されていると想像できますが、その高速道路がソースとターゲットの間の双方向交通であるか、特定の方向への一方通行であるかを制御できるだけではありません(モード) ) だけでなく、データのリリースも制御します。タイミング (トリガー) によって、ブリッジ上にいくつかの「ゲート」を設定して、データ型を変換したり (変換)、データの正確さを検証したりすることもできます。

  • 各バインディングは通常、バインディング ターゲット オブジェクト、ターゲット プロパティ、バインディング ソース、およびパス (使用するバインディング ソース内の値) の 4 つのコンポーネントで構成されます。
  • ターゲット プロパティは依存関係プロパティである必要があります。これは、フィールドをバインドできないことも意味します。つまり、ターゲット属性自体には値がなく、バインドされたデータ オブジェクトに依存することによってのみ計算できます。
  • バインディング ソース オブジェクトはカスタム CLR オブジェクトに限定されません。バインディング ソースには、UIElement、任意のリスト オブジェクトなどを指定できます。
  • Binding オブジェクトはデータ チャネル/パスの機能のみを提供し、データ変更通知メカニズムは提供しません。データ フローの開始方向は、 Modeプロパティを使用して指定できます一方向または双方向バインディングでソースの変更を検出するには、ソースはINotifyPropertyChangedなどの適切なプロパティ変更通知メカニズムを実装する必要があります具体的には、 OneWayまたはTwoWayバインディングをサポートして、バインディング ターゲットのプロパティがバインディング ソースの動的な変更を自動的に反映できるようにするには (たとえば、ユーザーがフォームを編集した後、プレビュー ペインが自動的に更新されます)、クラスは対応するプロパティの変更が通知されます。

ここに画像の説明を挿入

簡単に言えば、データ バインディングは、ソース ターゲット オブジェクトから情報を抽出し、その情報を使用してターゲット オブジェクトのプロパティを設定するように WPF に指示する関係です。ターゲット プロパティは常に依存関係プロパティであり、通常は WPF 要素内にあります。ただし、ソース オブジェクトは、ランダムに生成されたオブジェクト、データベースからのデータ オブジェクト、手動で作成されたオブジェクトなど、あらゆるものにすることができます。

2 バインドオブジェクト

public class Binding : System.Windows.Data.BindingBase

- Binding(String) : 使用初始路径Path 初始化 Binding 类的新实例。
- Converter(IValueConverter) : 获取或设置要使用的转换器。
- ElementName(string) : 获取或设置要用作绑定源对象的元素的名称(相关元素的 Name 属性或 x:Name的值)
- Mode(BindingMode) : 获取或设置一个值,该值指示绑定的数据流方向(开启数据通道的方向,但并不监听数据改变)。
- Path(PropertyPath) : 获取或设置绑定源-属性的路径。默认值为 null。
	- 在最简单的情况下,Path 属性值是要用于绑定的源对象的属性名称,支持多级路径等(一路.下去)。
	- 注意典型的string、int等基本类型的实例本身就是数据,我们无法指出通过它的哪个属性来访问这个数据,这时我们只需将Path的值设置为“.”就可以了。在XAML代码里这个“.”可以省略不写,但在C#代码里却不能省略。
	- 若要绑定到整个对象/本身数据,也可无需指定 Path 属性。{Binding} = {Binding Path="."}
- Source(Object) : 获取或设置要用作绑定源的对象。没有Source时,从当前节点沿着UI元素树一路向树的根部找过去
- StringFormat(string) : 获取或设置一个字符串,该字符串指定如果绑定值显示为字符串,应如何设置该绑定的格式。
- UpdateSourceTrigger : 获取或设置一个值,它可确定绑定源更新的时机。绑定的UpdateSourceTrigger属性控制将更改的值由UI发送回源的方式和时间。侦听目标属性中的更改并将其传播回源,这称为更新源

3つのデータバインディング方法

3.1 ElementName バインディング UI 要素

ElementName は、さまざまな UI 要素間の関係を確立するためによく使用され、その入力データはバインドされた要素の Name または x:Name 文字列です。

(1) XAMLバインディング形式

<Window x:Class="WPF_Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="110" Width="300">
	<!--将该TextBox对象的Text依赖属性 绑定到 ‘slider’元素的‘Value’属性上,默认单向绑定 -->
    <StackPanel>
        <TextBox x:Name="textBox" Text="{Binding Path=Value,ElementName=slider}" BorderBrush="Black" Margin="5"/>
        <Slider x:Name="slider" Maximum="100" Minimum="0" Margin="5"/>
    </StackPanel>
</Window>

(2) コードバインディング形式

//代码绑定声明格式
namespace WPF_Demo
{
    
    
    public partial class MainWindow : Window
    {
    
    
        public MainWindow()
        {
    
    
            InitializeComponent();

            //1.新建Binding对象
            Binding binding = new Binding("Value");
            //2.关联绑定的属性
            //binding.Path = new PropertyPath("Value");
            //3.指定源对象
            //binding.Source = this.slider;
            binding.ElementName = "slider";
            //4.绑定对象 目标对象.SetBinding(目标全局依赖属性,绑定对象)
            this.textBox.SetBinding(TextBox.TextProperty, binding);
            //5.C#3.0 初始化语法 四合一操作
            //this.textBox.SetBinding(TextBox.TextProperty, new Binding("Value") { Source = this.slider });
        }
    }
}

3.2 ソースバインディングカスタムオブジェクト

Source のタイプは Object であり、任意のオブジェクト データをバインドできます。ただし、カスタム オブジェクトはバインディング UI にデータを更新するように自動的に通知することはできません (リアルタイムで同期することはできません)。バインディング オブジェクトはデータ チャネルを提供するだけであり、データの初期値をバインドして表示するだけですカスタム オブジェクトのプロパティ更新通知を実装するには、ソースで INotifyPropertyChanged インターフェイスを実装する必要があります。データ ソースが Binding 用に設定されている場合、Binding はこのインターフェイスからの PropertyChanged イベントを自動的にリッスンします。

 public class Person : INotifyPropertyChanged
    {
    
    
        private string name;

        public string Name
        {
    
    
            get {
    
     return name; }
            set 
            {
    
    
                if(value != null)
                {
    
    
               		//属性值改变时,通知绑定事件
                    this.name = value;
                    this.NotifyPropertyChanged("Name");
                }
                name = value;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string propName)
        {
    
    
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }

(1) コードバインディング形式

namespace WPF_Demo
{
    
    
    public partial class MainWindow : Window
    {
    
    
        Person per;
        public MainWindow()
        {
    
    
            InitializeComponent();
            //初始化自定义对象
            per = new Person() {
    
     Name = "Init" };

            //准备Binding
            //1.新建Binding对象
            Binding binding = new Binding();
            //2.关联绑定的属性
            binding.Path = new PropertyPath("Name");
            //3.指定源数据
            binding.Source = per;
            //4.绑定对象 BindingOperations.SetBinding(目标对象,目标全局依赖属性,绑定对象)
            BindingOperations.SetBinding(this.textBox, TextBox.TextProperty, binding);
            //注意:上面等价于下面的操作,只不过为了方便快捷,WPF UI控件将BindingOperations.SetBinding封装进了自身SetBinding方法里
            //this.textBox.SetBinding(TextBox.TextProperty, binding);

        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
    
    
            per.Name += " add...";
        }
    }
}

(2) XAMLバインディング形式

XAML コードで宣言された変数には C# コードでアクセスできますが、C# コードで宣言された変数には XAML コードで直接アクセスできないことに注意してください。したがって、XAML で UI 要素と論理層オブジェクトのバインディングを確立するには、リソース (Resource) を使用する必要があります。

<Window x:Class="WPF_Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="110" Width="300">
    <Window.Resources>
        <local:Person x:Key="per" Name="Init"/>
    </Window.Resources>

    <StackPanel>
        <TextBox x:Name="textBox" BorderBrush="Black" Margin="5" Text="{Binding Path=Name,Source={StaticResource per}}"/>
        <Button Content="Add" Margin="5" Click="Button_Click"/>
    </StackPanel>
</Window>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            ((Person)this.FindResource("per")).Name += "add...";
        }
    }

3.3 DataContext コンテキスト バインディング

DataContext プロパティは、データ バインディング動作のデフォルトのソースです。DataContext プロパティは、WPF コントロールの基本クラスである FrameworkElement クラスで定義されます。つまり、すべての WPF コントロール (コンテナー コントロールを含む) がこのプロパティを持つことになります。前述したように、WPF の UI レイアウトはツリー構造であり、このツリーの各ノードはコントロールです。つまり、UI 要素ツリーの各ノードには DataContext プロパティがあります。視覚的に言えば、DataContext はデータの「指令高さ」に相当します。

Binding オブジェクトがパスだけを知っていてソースを知らない場合、現在のノードから UI 要素ツリーに沿ってツリーのルートまで検索し、ノードを通過するたびにこのノードの DataContext を調べます。パスで指定された名前。存在する場合は、このオブジェクトを独自のソースとして使用します。存在しない場合は、検索を続けます。ツリーのルートで見つからない場合、このバインディングにはソースがないため、データは取得されません (ただし、エラーが報告されますが、値はありません)。DataContext の使用シナリオは次のとおりです。

  • UI 上の複数のコントロールがバインドを使用して同じオブジェクトにフォーカスする場合
  • Sourceとして使用されるオブジェクトに直接アクセスできない場合。さらに、DataContext 自体も依存関係プロパティであり、バインディングを通じて他のデータ ソースに関連付けてバインディング チェーンを形成できます。
<Window x:Class="WPF_Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="110" Width="300">

    <StackPanel>
        <TextBox x:Name="textBox" BorderBrush="Black" Margin="5" Text="{Binding Name}"/>
        <Button Content="Add" Margin="5" Click="Button_Click"/>
    </StackPanel>
</Window>

namespace WPF_Demo
{
    
    

    public partial class MainWindow : Window
    {
    
    
        Person per;
        public MainWindow()
        {
    
    
            InitializeComponent();
            //初始化自定义对象
            per = new Person() {
    
     Name = "Init" };
			//声明本Window的上下文DataContext
            this.DataContext = per;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
    
    
            this.per.Name += " add...";
        }
    }

    public class Person : INotifyPropertyChanged
    {
    
    
        private string name;

        public string Name
        {
    
    
            get {
    
     return name; }
            set 
            {
    
    
                if(value != null)
                {
    
    
                    this.name = value;
                    this.NotifyPropertyChanged("Name");
                }
                name = value;
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public void NotifyPropertyChanged(string propName)
        {
    
    
            if (this.PropertyChanged != null)
                this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
        }
    }
}

3.4 リスト コントロールのデータ バインディング

リスト コントロールはすべて ItemsControl クラスから派生し、データ バインディングでリスト コントロールのいくつかのプロパティを継承します。原理は次のとおりです。 ItemsControl の ItemsSource がコレクションをバインドすると、各データ要素に対して同量の項目コンテナー xxxItem (ContentControl から継承) が自動的に作成されます。これは、リスト コントロールの各データ項目によって表示される「データ コート」です。そして、自動的に Binding を使用して、各データ項目コンテナーとデータ要素の間の関連付けとバインディングを確立します (各リスト コレクション データを各項目の DataContext として使用します)したがって、リスト コントロールのデータ バインディングには、リスト コレクション全体の外部バインディングと各データ要素の内部バインディングが含まれます。

- Items : [获取]用于生成 ItemsControl 的内容的集合,可以是不同类型数据。 默认值为空集合。
- ItemsSource : [获取或设置]用于生成 ItemsControl 的内容的集合。接收一个 IEnumerable 接口派生类实例作为数据源(所有可迭代的集合都实现了该接口),默认值为 null。设置ItemsSource后集合Items是只读且固定大小的(无法通过Items添加修改数据)。
- DisplayMemberPath : 获取或设置[源对象上的值的路径]。默认值为空字符串("")。若未指定DisplayMemberPath以及DataTemplate,则 ListBox 显示基础集合中每个对象的字符串表示形式(toString)

ListBox:
	- SelectedItem(Object) : 获取或设置当前选择中的第一项,如果选择为空,则返回 null。
	- SelectedItems(IList)	: 获取当前选定的项。

(1) コード例

<Window x:Class="WPF_Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="270" Width="300">

    <StackPanel x:Name="stackPanel" Background="LightBlue">
        <TextBlock Text="Student ID:" FontWeight="Bold" Margin="5"/>
        <TextBox x:Name="textBoxId" BorderBrush="Black" Margin="5"/>
        <TextBlock Text="Student List:" FontWeight="Bold" Margin="5"/>
        <ListBox x:Name="listBoxStudents" Height="110" Margin="5"/>
        <Button Content="Add" Margin="5" Click="Button_Click"/>
    </StackPanel>
</Window>
namespace WPF_Demo
{
    
    
    public partial class MainWindow : Window
    {
    
    
        List<Student> stuList;
        public MainWindow()
        {
    
    
            InitializeComponent();
            //列表数据源
            stuList = new List<Student>()
            {
    
    
                new Student(){
    
    Id=202201,Name="Tim",Age=19},
                new Student(){
    
    Id=202202,Name="Kitty",Age=20},
                new Student(){
    
    Id=202203,Name="Tom",Age=18},
                new Student(){
    
    Id=202204,Name="Mike",Age=22},
            };
            //为ListBox设置Binding(ItemSource依赖属性)
            this.listBoxStudents.ItemsSource = stuList;
            //设置源数据的值路径(如果不设置,则默认显示对象的toString)
            this.listBoxStudents.DisplayMemberPath = "Name";
            //上述等价于
            //Binding bind1 = new Binding() { Source = this.stuList };
            //this.listBoxStudents.SetBinding(ListBox.ItemsSourceProperty, bind1);
            //this.listBoxStudents.DisplayMemberPath = "Name";

            //为TextBox设置Binding
            Binding binding = new Binding("SelectedItem.Id") {
    
     Source = this.listBoxStudents };
            this.textBoxId.SetBinding(TextBox.TextProperty, binding);

        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
    
    
        	//测试数据变化能否自动推送到UI : 显然否
            MessageBox.Show(this.listBoxStudents.Items.Count.ToString());
            this.stuList.Add(new Student() {
    
     Id = 202205, Name = "Wx", Age = 23 });
        }
    }

    public class Student
    {
    
    
        public int Id {
    
     get; set; }
        public string Name {
    
     get; set; }
        public int Age {
    
     get; set; }
    }
}

注:通常のリストはソースとして使用されます。ロジック層がデータを変更した後、すぐに応答して UI 更新にプッシュすることはできません。初期データをバインドすることしかできません。INotifyCollectionChanged 通知インターフェイスの支援が必要です。

(2) XAMLの例

<Window x:Class="WPF_Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        mc:Ignorable="d"
        Title="MainWindow" Height="270" Width="300">

    <StackPanel x:Name="stackPanel" Background="LightBlue">
        <TextBlock Text="Student ID:" FontWeight="Bold" Margin="5"/>
        <TextBox x:Name="textBoxId" BorderBrush="Black" Margin="5" Text="{Binding ElementName=listBoxStudents,Path=SelectedItem.Id}"/>
        <TextBlock Text="Student List:" FontWeight="Bold" Margin="5"/>
        <ListBox x:Name="listBoxStudents" Height="110" Margin="5" DisplayMemberPath="Name" ItemsSource="{Binding}" />
        <Button Content="Add" Margin="5" Click="Button_Click"/>
    </StackPanel>
</Window>
namespace WPF_Demo
{
    
    
    public partial class MainWindow : Window
    {
    
    
        List<Student> stuList;
        public MainWindow()
        {
    
    
            InitializeComponent();
            //列表数据源
            stuList = new List<Student>()
            {
    
    
                new Student(){
    
    Id=202201,Name="Tim",Age=19},
                new Student(){
    
    Id=202202,Name="Kitty",Age=20},
                new Student(){
    
    Id=202203,Name="Tom",Age=18},
                new Student(){
    
    Id=202204,Name="Mike",Age=22},
            };
             //设置上下文,为ListBox设置Binding
            this.DataContext = stuList;
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
    
    
            MessageBox.Show(this.listBoxStudents.Items.Count.ToString());
            this.stuList.Add(new Student() {
    
     Id = 202205, Name = "Wx", Age = 23 });
        }
    }
}

注: {Binding} の空のバインディング構文が表示されます。まず、構文で Source 属性が省略されている場合、データ ソースは現在のノードから DataContext を検索します。その後、構文では Path 属性が省略されており、これはデータ オブジェクトのバインドを意味します。それ自体 (toString) の場合、最初に見つかった DataContext がバインディング オブジェクトです。

3.4 RelativeSource の相対バインディング

データ バインディングでは、データ ソースをバインドする主な方法が 4 つあります。

  • Binding.ElementName : バインディング名 Name、x:Name UI 要素 (文字列型)。名前が明確にわかっている場合のフロントエンド UI 要素のバインディングに適しています。
  • Binding.Source: バインドするソース オブジェクト (オブジェクト タイプ) を明示的に指定します。オブジェクト インスタンスが明示的に知られている場合のフロントエンド リソース バインディングに適しています
  • DataContext : データ バインディングのデフォルトの動作。データ バインディング ソース (オブジェクト タイプ) としてコンテキストを検索します。バックグラウンドのグローバル ロジック オブジェクト バインディングに適しています
  • Binding.RelativeSource: バインディング ターゲットの位置を基準とした相対的な位置 (相対バインディング) を指定することにより、バインディング ソースを取得または設定します。(RelativeSource オブジェクト)。バインドが必要なデータソースの名前が明確ではなく、位置関係が曖昧な場合のバインドに適しています。
RelativeSource 属性
- AncestorLevel: 以 FindAncestor 模式获取或设置要查找的上级级别。 使用 1 指示最靠近绑定目标元素的项。
- AncestorType: 获取或设置要查找的上级节点的类型。
- Mode(enum RelativeSourceMode): 获取或设置 RelativeSourceMode 值,该值描述相对于绑定目标的位置的绑定源的位置关系。
	- FindAncestor: 引用数据绑定元素的父链中的上级。 这可用于绑定到特定类型的上级或其子类。 如果您要指定 AncestorType 和/或 AncestorLevel,可以使用此模式。
	- PreviousData: 允许在当前显示的数据项列表中绑定上一个数据项(不是包含数据项的控件)。
	- Self: 引用正在其上设置绑定的元素,并允许你将该元素的一个属性绑定到同一元素的其他属性上。
	- TemplatedParent: 引用应用了模板的元素,其中此模板中存在数据绑定元素。 这类似于设置 TemplateBindingExtension,并仅当 Binding 在模板中时适用。
- PreviousData: 获取一个[静态值],该值用于返回为 RelativeSource 模式构造的 PreviousData。
- Self: 获取一个[静态值],该值用于返回为 RelativeSource 模式构造的 Self。
- TemplatedParent: 获取一个[静态值],该值用于返回为 RelativeSource 模式构造的 TemplatedParent。

(1) モード 自己関連付け制御そのもの

<!--传统Binding写法:控件Width关联自身Height-->
<TextBlock FontSize="18" Text="Simple" 
		Name="Box1"
		FontWeight="Bold"
        Width="80" 
        Height="{Binding ElementName=Box1,Path=Width}"/>
                    
<!--相对Binding写法:控件Width关联自身Height-->
<TextBlock FontSize="18" Text="Simple" 
		FontWeight="Bold"
        Width="80" 
        Height="{Binding RelativeSource={RelativeSource Mode=self},Path=Width}"/>
//代码形式
Binding myBinding = new Binding("Height");
myBinding.RelativeSource = new RelativeSource(RelativeSourceMode.Self);
this.Box1.SetBinding(Height.HeightProperty, myBinding);

(2) モード FindAncestor 関連の親コンテナ

FindAncestor モードは、現在の要素の親コンテナー内の上位要素をバインドし、AncestorType および AncestorLevel とともに使用する必要があります。ルールは次のとおりです。

  • AncestorLevel: Bingding ターゲット コントロールから始まる上向きのレベル オフセットを指します。たとえば、S1 のオフセットは 1、G2 のオフセットは 2、G1 のオフセットは 3 です。
  • AncestorType: 検索対象のターゲット オブジェクトのタイプを指します。
  • 注: AncestorLevel は、AncestorType への参照に基づいて有効になります。たとえば、ここで AncestorType={x:Type Grid} が設定されている場合、Bingding は検索時に非 Grid コントロールを無視し、G2 のオフセットは 1、G1 のオフセットは 2 になり、StackPanel は無視されます。
<Window x:Class="RelativeSource.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 Name="G1">
        <Grid Name="G2">
            <StackPanel Name="S1">
                <TextBox Height="30" Width="60" 
                Name="Box1" 
                Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Grid}, AncestorLevel=2},Path=Name }"/>
            </StackPanel>
        </Grid>
    </Grid>
</Window>
Binding myBinding = new Binding();
// Returns the second ItemsControl encountered on the upward path
// starting at the target element of the binding
myBinding.RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor, typeof(ItemsControl), 2);

(3) Mode TempatedParent 関連テンプレート

このモードは、TemplateBinding と同様に、ControlTemplate テンプレートのコントロール プロパティをアプリケーション テンプレートのコントロール プロパティにバインドし、特定のコントロールのプロパティをそのコントロール テンプレートに適用するために使用されます。{TemplateBinding X} は、{Binding X, RelativeSource={RelativeSource TempulatedParent}} を記述するためのショートカットにすぎず、この 2 つは基本的に同等です。

<!--应用Button的背景色到模板中椭圆的背景色相同,在这个例子中 TemplateParent 指的是应用该模板的Button-->
<Window.Resources>
    <Style TargetType="{x:Type Button}">
        <Setter Property="Background" Value="Green"/>
            <Setter Property="Template">              
                <Setter.Value>
                <ControlTemplate TargetType="{x:Type Button}">
                    <Grid>
                        <Ellipse>
                            <Ellipse.Fill>
                                <SolidColorBrush Color="{Binding Path=Background.Color,RelativeSource={RelativeSource TemplatedParent}}"/>
                            </Ellipse.Fill>
                        </Ellipse>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

4 テンプレートバインディング

TemplateBinding は Binding の軽量バージョンであり、成熟したバージョンの Binding の多くの機能を簡素化します。TemplateBinding の主な目的は、テンプレート ターゲット コントロールまたはテンプレートに組み込まれたテンプレート コントロールのプロパティをバインドすることであり、この場合、成熟した Binding よりもはるかに効率的です。たとえば、ControlTemplate のコントロールは、TemplateBinding を使用して、それ自体の特定のプロパティ値をテンプレート コントロールの特定のプロパティ値に関連付けることができ、必要に応じてコンバータを追加できます。テンプレートの特徴は以下の通りです。

  • TemplateBinding のデータ バインディングは一方向であり、データ ソースからターゲット (つまり、テンプレートを適用するコントロールからテンプレート内のコントロール要素) へのみです。
  • TemplateBinding はデータ オブジェクトを自動的に変換できません。データ ソースとターゲットのデータ型が異なる場合は、コンバーターを自分で記述する必要があります
<!--两种绑定效果等价-->
<TextBlock Text="{TemplateBinding MyText}"/>
<TextBlock Text="{Binding Path=MyText, Mode=OneWay, RelativeSource={RelativeSource TemplatedParent}}"/>

4.1 TemplateBinding と TemplateParent の違い

(1) TemplateBinding はコンパイル時に評価され、RelativeSource TempulatedParent は実行時に評価されます。そのため、テンプレートTemplate内のトリガーは動作時に判定され、属性バインディングはTemplateParentの形式を使用する必要があり、TemplateBindingを使用した場合はエラーが報告されます。

<Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type local:ButtonEx}">
                            <Border x:Name="border" Background="{TemplateBinding Background}" 
                                    CornerRadius="{TemplateBinding CornerRadius}" 
                                    BorderBrush="{TemplateBinding BorderBrush}" 
                                    BorderThickness="{TemplateBinding BorderThickness}" 
                                    Width="{TemplateBinding Width}" 
                                    Height="{TemplateBinding Height}" 
                                    SnapsToDevicePixels="True">
                                <TextBlock x:Name="txt" Text="{TemplateBinding Content}" Foreground="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center"/>
                            </Border>
                            <ControlTemplate.Triggers>
                                <Trigger Property="IsMouseOver" Value="True">
                                    <Setter TargetName="border" Property="Background" Value="{Binding MouseOverBackground,RelativeSource= {RelativeSource Mode=TemplatedParent}}"/>
                                    <Setter TargetName="txt" Property="Foreground" Value="{Binding MouseOverForeground,RelativeSource= {RelativeSource Mode=TemplatedParent}}"/>
                                    <Setter TargetName="border" Property="BorderBrush" Value="{Binding MouseOverBorderbrush,RelativeSource= {RelativeSource Mode=TemplatedParent}}"/>
                                </Trigger>
                                <Trigger Property="IsPressed" Value="True">
                                    <Setter TargetName="border" Property="Background" Value="{Binding MousePressedBackground,RelativeSource={RelativeSource TemplatedParent}}"/>
                                    <Setter TargetName="txt" Property="Foreground" Value="{Binding MousePressedForeground,RelativeSource={RelativeSource TemplatedParent}}"/>

                                </Trigger>
                            </ControlTemplate.Triggers>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>

(2) TemplateBinding のデータ バインディングは、データ ソースからターゲットへ (つまり、テンプレートを適用するコントロールからテンプレートへ) という一方向です。Binding のデータ バインド方法は、一方向、双方向などのモードを通じて設定できます。

(3) TemplateBinding ではデータオブジェクトの自動変換ができないため、データソースとターゲットのデータ型が異なる場合、コンバータを自分で記述する必要があります。バインディングにより、データ ソースとターゲットのデータ型が自動的に変換されます。

(4) TemplateBinding の高速化(コンパイル時評価)

5 変更および通知への対応

5.1 通知の使用方法

バインディング ソースとしてのオブジェクトに、そのオブジェクト自体のプロパティ値が変更されたことを自動的にバインディングに通知する機能を持たせ、UI を即座に更新する効果を実現したい場合。次に、データ ソースに変更通知インターフェイスを実装させ、変更中に Changed イベントをトリガーする必要があります。データ ソースがバインディング用に設定されている場合、バインディングはこのインターフェイスからの Changed イベントを自動的にリッスンします。

(1) 通常のオブジェクト:カスタム クラスはINotifyPropertyChanged インターフェイスを実装し、プロパティの set ステートメントで PropertyChanged イベントをトリガーして、バインディング チャネル Binding と UI に通知してプロパティ データ表示を更新する必要があります。

	public class User : INotifyPropertyChanged
	{
    
    
		private string name;
		public string Name {
    
    
			get {
    
     return this.name; }
			set
			{
    
    
				if(this.name != value)
				{
    
    
					this.name = value;
					this.NotifyPropertyChanged("Name");
				}
			}
		}

		public event PropertyChangedEventHandler PropertyChanged;

		public void NotifyPropertyChanged(string propName)
		{
    
    
			if(this.PropertyChanged != null)
				this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
		}
	}

(2) リスト コレクション: WPF は動的コレクション リスト ObservableCollection を提供します。これはINotifyCollectionChanged インターフェイスと INotifyPropertyChanged インターフェイスを実装し、リストが変更されたときにバインディング チャネルのバインディングと UI 更新データ表示を自動的に通知できます。使用時には、ObservableCollection と List に違いはありません。包括的なテンプレート通知クラスを自分で実装することもできます

namespace ClientNew.ViewModel
{
    
    
    public class ViewModelBase : INotifyPropertyChanged, INotifyCollectionChanged
    {
    
    
        public event PropertyChangedEventHandler PropertyChanged;

        public void RaisePropertyChanged(string propertyName)
        {
    
    
            if (PropertyChanged != null)
            {
    
    
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        public event NotifyCollectionChangedEventHandler CollectionChanged;

        public void RaiseCollectionChanged(ICollection collection)
        {
    
    
            if (CollectionChanged != null)
            {
    
    
                CollectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
            }
        }

        public void RaiseCollectionAdd(ICollection collection, object item)
        {
    
    
            if (CollectionChanged != null)
            {
    
    
                if (PropertyChanged != null)
                {
    
    
                    PropertyChanged(collection, new PropertyChangedEventArgs("Count"));
                    PropertyChanged(collection, new PropertyChangedEventArgs("Item[]"));
                }
                CollectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
                //CollectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
            }
        }
    }
}

注:コレクション内のデータのプロパティが変更された場合、コレクション データ項目オブジェクトは INotifyPropertyChanged インターフェイスも実装して、UI に自動的に通知し、データ要素にバインドされている項目コンテナー xxxItem を更新する必要があります。そうしないと、コレクション リストのみが更新されます。

5.2 応答通知の原理分析

ここでは Textbox の Binding 通知を例に挙げます。データ バインディングが実現されると、UI 要素とバックエンド データ バインディング、フロントエンド データの変更をリアルタイムでバックエンド データ モデルに更新できます。バックエンド データ モデルは、更新をプッシュするために INotifyPropertyChanged インターフェイスを実装する必要があります。フロントエンドに接続し、その動作原理を分析します。

(1) View -> ViewModel 通知
ここでは主に、フロントエンドのテキスト ボックス TextBox でデータが直接変更された場合に、Binding がバックエンド データ モデル (View -> ViewModel) への通知をどのように更新するかを説明します。

  • バインディングオブジェクトを作成します。SetBinding時は、コントロールの依存関係プロパティとBindingするデータの関係を作成し、バインディング式CreateBindingExpression()を作成し、BindingExpressionBaseオブジェクトを返します。BindingExpressionBase は文字通り Binding の式として理解できます。これは実際には Binding の値を格納する関係 (誰が誰をバインドするか) であり、依存関係プロパティをコントロールおよびバインディング オブジェクトに関連付けます。

  • テキストボックスに手動で入力したコンテンツは、コントロールの OnPreviewTextInput イベントによってキャプチャされ、最後に、BindingExpressionBase.OnPreviewTextInput によって Drity メソッドがトリガーされます。Drity メソッドは、データに変更があるかどうかを検出し、変更がない場合は更新メカニズムを終了します。バインディング式で Delay プロパティが使用されている場合、BindingExpressionBase の DispatcherTimer がトリガーされて、遅延データ更新の効果が実現されます。

  • データの変更が検出された場合、次に BindingExpressionBase.UpdateValue() メソッドが呼び出され、依存関係プロパティ Text の内容にアクセスし、主にいくつかの部分で構成される ViewModel にバインドされているプロパティを変更します。

    • 値が正当かどうか、検証ルールを通過できるかどうかを判断します。
    • バインディング式に Convert コンバータが記述されている場合、値の変換が実行されます。
// transfer a value from target to source
internal bool UpdateValue()
{
    
    
            ValidationError oldValidationError = BaseValidationError;
 
            if (StatusInternal == BindingStatusInternal.UpdateSourceError)
                SetStatus(BindingStatusInternal.Active);
    		//获取依赖属性数值
            object value = GetRawProposedValue();
            if (!Validate(value, ValidationStep.RawProposedValue))
                return false;
            //转换器转换
            value = ConvertProposedValue(value);
            if (!Validate(value, ValidationStep.ConvertedProposedValue))
                return false;
 			//最终触发更新
            value = UpdateSource(value);
            if (!Validate(value, ValidationStep.UpdatedValue))
                return false;
 
            value = CommitSource(value);
            if (!Validate(value, ValidationStep.CommittedValue))
                return false;
 
            if (BaseValidationError == oldValidationError)
            {
    
    
                // the binding is now valid - remove the old error
                UpdateValidationError(null);
            }
 
            EndSourceUpdate();
            NotifyCommitManager();
 
            return !HasValue(Feature.ValidationError);
}

最後に、値の変更は、依存関係プロパティの PropertyMetadata によって登録された PropertyChangedCallback によって実装されます。これを見れば、デザイナーがデフォルトで ViewModel の各フィールドのデータ通知メカニズムを統合しない理由を誰もが理解できるはずです。私の個人的な理解では、データ通知は一定のパフォーマンスの低下をもたらすため、開発者は通知メンバーを追加できます。」オンデマンド」。

(2) ViewModel -> View フロントエンドに
プッシュ更新を通知する INotifyPropertyChanged インターフェースをバックエンドに実装するよう通知する 本質は依然として従来のイベントのパブリッシュ・サブスクライブ・モデルですが、イベントのパブリッシュとサブスクライブの宣言コードはまだ見ていないようです。サブスクライブ プロセスでは、Binding はどのようにイベントを実装するのでしょうか?

// System.ComponentModel.PropertyChangedEventManager
protected override void StartListening(object source)
{
    
    
	INotifyPropertyChanged notifyPropertyChanged = (INotifyPropertyChanged)source;
	notifyPropertyChanged.PropertyChanged += OnPropertyChanged;
}

実際、Binding データ ソースを初期化するときに、BindingExpression を作成して UpdateTarget を呼び出すと、最終的に PropertyChangedEventManager の StartListening メソッドが呼び出され、INotifyPropertyChanged インターフェイス実装クラスの PropertyChanged イベントにサブスクライブされます。これまでのところ、2 つのバインドされたプロパティは接続されています。
Binding データ ソースが StartListening() のリスニングを開始すると、受信した DataContext データ ソース クラス オブジェクトに従って、上位層の参照から ViewModel 参照を取得します (参照は Binding クラス レベルから層ごとに渡されます)。これ ViewModel が INotifyPropertyChanged を継承するかどうか。継承する場合は、そのイベント PropertyChangedEventHandler PropertyChanged の参照を検索し、それを管理するために追加し、サブスクリプション イベントを追加します。Set プロパティが設定されると後続のイベントがトリガーされ、OnPropertyChanged メソッドが呼び出されてインターフェイス値をリアルタイムで変更します。

6 マルチウェイバインディング MultiBinding

場合によっては、UI によって表示される情報を複数のデータ ソースによって共同で決定する必要がある場合、その場合はマルチウェイ バインディングを使用する必要があります。マルチウェイ バインディング MultiBinding クラスは、Binding と同様、BindingBase クラスを継承します。つまり、Binding を使用するすべてのシナリオを MultiBinding に置き換えることができます。MultiBinding オブジェクトは、多くの場合、これらのバインドされた値に従ってバインディング ターゲットの最終値を生成するコンバーターと一緒に使用されますが、コンバーター クラスはIMultiValueConverter インターフェイスを継承する必要があります。

MultiBinding 属性:
- Bindings: 获取此 MultiBinding 实例中的 Binding 对象的集合。Collection<BindingBase> 通过该属性 MultiBinding 将一组 Binding 对象聚合起来
- Converter: 获取或设置用于在源值和目标值之间来回转换的转换器。
- Mode: 获取或设置一个值,该值指示此绑定的数据流的方向。
- StringFormat: 获取或设置一个字符串,该字符串指定如果绑定值显示为字符串,应如何设置该绑定的格式。

ここに画像の説明を挿入

(1) MultiBinding XAML実装

必要:

  1. 最初と 2 番目の TextBox にユーザー名を入力します。内容は一貫している必要があります。
  2. 3 番目と 4 番目の TextBox はメールボックスを入力し、内容は一貫している必要があります。
  3. 上記 2 つの条件が満たされ、コンテンツが空でない場合、ボタン表示が可能になります。クリックすると成功プロンプトが表示されます
  • コンバータの実装
namespace WPF_Demo.Convert
{
    
    
    class MultiInfoConverter : IMultiValueConverter
    {
    
    
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
    
    
            //values存储了绑定值的集合(按照绑定顺序传入)
            if(values.Length >= 4)
            {
    
    
                string username_1 = values[0].ToString();
                string username_2 = values[1].ToString();
                string email_1 = values[2].ToString();
                string email_2 = values[3].ToString();
                if (!string.IsNullOrEmpty(username_1) && !string.IsNullOrEmpty(username_2) && !string.IsNullOrEmpty(email_1) && !string.IsNullOrEmpty(email_2))
                {
    
    
                    if (username_1 == username_2 && email_1 == email_2)
                    {
    
    
                        return true;
                    }
                }
            }
            return false;
        }

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

  • XAMLの実装
<Window x:Class="WPF_Demo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPF_Demo"
        xmlns:vm="clr-namespace:WPF_Demo.Status" 
        xmlns:cv="clr-namespace:WPF_Demo.Convert"
        xmlns:control="clr-namespace:WPF_Demo.Style"
        mc:Ignorable="d" 
        DataContext="{x:Static vm:MainWindowStatus.Instance}"
        Title="MainWindow" Height="185" Width="300">
    <Window.Resources>
        <cv:MultiInfoConverter x:Key="mfCV"/>
    </Window.Resources>
    
    <StackPanel Background="LightBlue">
        <TextBox x:Name="textBox1" Height="23" Margin="5"/>
        <TextBox x:Name="textBox2" Height="23" Margin="5,0"/>
        <TextBox x:Name="textBox3" Height="23" Margin="5"/>
        <TextBox x:Name="textBox4" Height="23" Margin="5,0"/>
        <Button x:Name="button1" Content="Submit" Width="80" Margin="5">
            <Button.IsEnabled>
                <!--MultiBinding 对子级 Binding 的添加顺序是敏感的,因为这个顺序决定了汇集到 Converter 里数据的顺序-->
                <MultiBinding Mode="OneWay" Converter="{StaticResource mfCV}">
                    <Binding ElementName="textBox1" Path="Text"/>
                    <Binding ElementName="textBox2" Path="Text"/>
                    <Binding ElementName="textBox3" Path="Text"/>
                    <Binding ElementName="textBox4" Path="Text"/>
                </MultiBinding>
            </Button.IsEnabled>
        </Button>
    </StackPanel>
</Window>

(2) MultiBinding C#の実装

    public partial class MainWindow : Window
    {
    
    
        public MainWindow()
        {
    
    
            InitializeComponent();

            //准备基础Binding
            Binding Binding1 = new Binding("Text") {
    
     Source = this.textBox1 };
            Binding Binding2 = new Binding("Text") {
    
     Source = this.textBox2 };
            Binding Binding3 = new Binding("Text") {
    
     Source = this.textBox3 };
            Binding Binding4 = new Binding("Text") {
    
     Source = this.textBox4 };
            //准备MultiBinding(子Binding添加顺序敏感)
            MultiBinding multiBinding = new MultiBinding() {
    
     Mode = BindingMode.OneWay };
            multiBinding.Bindings.Add(Binding1);
            multiBinding.Bindings.Add(Binding2);
            multiBinding.Bindings.Add(Binding3);
            multiBinding.Bindings.Add(Binding4);
            multiBinding.Converter = new MultiInfoConverter();//添加转换器
            //关联控件与MultiBinding对象
            this.button1.SetBinding( Button.IsEnabledProperty, multiBinding);
        }
    }

おすすめ

転載: blog.csdn.net/qq_40772692/article/details/126428033