LoginSignup
9
7

More than 3 years have passed since last update.

[C#/WPF] 自作ユーザーコントロールにバインディングするプロパティのMode(OneWay/OneWayToSource/TwoWay)によって、値変化時の動作がどう変わるか実験

Last updated at Posted at 2020-06-20

もくじ
https://qiita.com/tera1707/items/4fda73d86eded283ec4f

やりたいこと

自分で作ったユーザーコントロールには依存関係プロパティがあり、そいつに画面側のプロパティをバインドして使う、というのは何となくわかったが、下記の動きの理解がいまひとつ足りてないせいで、作れなくはないがなんとなくしっくり来てない。

  • 画面側のプロパティをユーザーコントロールにバインドしてるが、
    • 画面側のプロパティが変化したら、ユーザーコントロール側のプロパティはどう変化するのか?(同時に変化するのか?)
    • ユーザーコントロール側のプロパティが変化したら、画面側のプロパティはどう変化するのか?
    • バインディングには「モード」が3つあり、どれを使うかによって動きが異なる。それぞれどう違うのか?

⇒MSのドキュメントとか見てもこれ以上理解できそうにないので、実際に動かしてみて、動きを目で見てどういう動きをするのか確かめたい。

前提

.NET Framework 4.7.2を使用して下記を作成した。

実験コード

ざっくり仕様

画面

画面は下図のようになる。赤で囲ったところがユーザーコントロール。
image.png
ログのイメージ
image.png

動作

  • ユーザーコントロール側でやっていること
    • 「UserControl側で数字を++」ボタンを押すと、ユーザーコントロールの中に持っている3つのプロパティの値を++する。
    • ユーザーコントロールの中のプロパティは3つのテキストボックスにバインドしている。
    • それにより、3つのプロパティの値が変化時、値を3つのテキストボックスに表示する。
  • 画面側でやっていること
    • 「画面側で数字を++」を押すと、ユーザーコントロールにバインドしている画面側のプロパティの数字を++する。
    • その際、ユーザーコントロール側に++したことが伝われば、ユーザーコントロール側のプロパティの変化により、テキストボックスの数字が更新される。
    • 画面側のプロパティは3つあり、ユーザーコントロールにそれぞれModeを「OneWay」「OneWayToSource」「TwoWay」でバインドしている。
    • 画面側のプロパティの値の変化を、デバッグログに出力する。

目的

画面側プロパティのバインディングのModeの違いで、画面側プロパティとユーザーコントロール側プロパティの値の変化時に、お互いがどのように変化するかを試す。

ユーザーコントロール側の実験用コード

見た目(xaml)

SimpleUserControl.xaml
<UserControl x:Class="WpfApp1.SimpleUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d" 
             x:Name="root">
    <Grid Width="550" Height="50">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="250"/>
        </Grid.ColumnDefinitions>

        <!-- タイトル行 -->
        <TextBox Grid.Row="0" Grid.Column="0" Text="OneWay" />
        <TextBox Grid.Row="0" Grid.Column="1" Text="OneWayToSource" />
        <TextBox Grid.Row="0" Grid.Column="2" Text="TwoWay" />

        <!-- 数字を表示する行 -->
        <TextBox Grid.Row="1" Grid.Column="0" Text="{Binding MyDoubleValue1, ElementName=root}" />
        <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding MyDoubleValue2, ElementName=root}" />
        <TextBox Grid.Row="1" Grid.Column="2" Text="{Binding MyDoubleValue3, ElementName=root}" />

        <!-- 数字を++するボタン -->
        <Button Grid.Row="0" Grid.Column="3" Grid.RowSpan="2" Content="UserControl側で数字を++" Click="Button_Click"/>
    </Grid>
</UserControl>

コードビハインド

SimpleUserControl.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;

namespace WpfApp1
{
    public partial class SimpleUserControl : UserControl
    {
        public double MyDoubleValue1
        {
            get => (double)GetValue(MyDoubleValue1Property);
            set { Console.WriteLine(" - UserControl DoubleValue1 = {0} ", value); SetValue(MyDoubleValue1Property, value); }
        }
        public static readonly DependencyProperty MyDoubleValue1Property = DependencyProperty.Register(nameof(MyDoubleValue1), typeof(double), typeof(SimpleUserControl), new PropertyMetadata(0.0));
        //---------------------------------------------------------------------------
        public double MyDoubleValue2
        {
            get => (double)GetValue(MyDoubleValue2Property);
            set { Console.WriteLine(" - UserControl DoubleValue2 = {0} ", value); SetValue(MyDoubleValue2Property, value); }
        }
        public static readonly DependencyProperty MyDoubleValue2Property = DependencyProperty.Register(nameof(MyDoubleValue2), typeof(double), typeof(SimpleUserControl), new PropertyMetadata(0.0));
        //---------------------------------------------------------------------------
        public double MyDoubleValue3
        {
            get => (double)GetValue(MyDoubleValue3Property);
            set { Console.WriteLine(" - UserControl DoubleValue3 = {0} ", value); SetValue(MyDoubleValue3Property, value); }
        }
        public static readonly DependencyProperty MyDoubleValue3Property = DependencyProperty.Register(nameof(MyDoubleValue3), typeof(double), typeof(SimpleUserControl), new PropertyMetadata(0.0));
        //---------------------------------------------------------------------------
        // コンストラクタ
        public SimpleUserControl()
        {
            InitializeComponent();
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MyDoubleValue1 += 1.0;
            MyDoubleValue2 += 1.0;
            MyDoubleValue3 += 1.0;
        }
    }
}

画面側の実験用コード

見た目(xaml)

MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="800"
        x:Name="root">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="2*"/>
            <ColumnDefinition Width="5*"/>
        </Grid.ColumnDefinitions>

        <Button Grid.Column="0" Content="画面側で数字を++" Click="Button_Click"/>

        <TextBlock Grid.Column="1"  Text="UseControlのProperty" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <local:SimpleUserControl Grid.Column="2" Grid.Row="1" 
                                 MyDoubleValue1="{Binding DoubleValue1, ElementName=root, Mode=OneWay}"
                                 MyDoubleValue2="{Binding DoubleValue2, ElementName=root, Mode=OneWayToSource}"
                                 MyDoubleValue3="{Binding DoubleValue3, ElementName=root, Mode=TwoWay}"
                                 />
    </Grid>
</Window>

コードビハインド

MainWindow.xaml.cs
using System;
using System.ComponentModel;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string propertyName) => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        public double DoubleValue1
        {
            get { return _doubleValue1; }
            set { Console.WriteLine("* MainWindow DoubleValue1 = {0} ", value); _doubleValue1 = value; OnPropertyChanged(nameof(DoubleValue1)); }
        }
        private double _doubleValue1 = 0.0;
        public double DoubleValue2
        {
            get { return _doubleValue2; }
            set { Console.WriteLine("* MainWindow DoubleValue2 = {0} ", value); _doubleValue2 = value; OnPropertyChanged(nameof(DoubleValue2)); }
        }
        private double _doubleValue2 = 0.0;
        public double DoubleValue3
        {
            get { return _doubleValue3; }
            set { Console.WriteLine("* MainWindow DoubleValue3 = {0} ", value); _doubleValue3 = value; OnPropertyChanged(nameof(DoubleValue3)); }
        }
        private double _doubleValue3 = 0.0;

        public MainWindow()
        {
            InitializeComponent();
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            DoubleValue1++;
            DoubleValue2++;
            DoubleValue3++;
        }
    }
}

実験結果

当たり前の結果だが、
画面側とユーザーコントロール側の両方のプロパティの値の変化がお互いにリンクするのは、ModeがTwoWayの時のみ。
(リンク=画面側プロパティが変化したら、ユーザーコントロール側のプロパティも一緒に変化(++)する、ということ)

  • ModeがOneWayのときは、
    • 画面側のプロパティが変化したら、ユーザーコントロール側のプロパティも一緒に変化(++)する。
    • ユーザーコントロール側のプロパティが変化しても、画面側のプロパティは変化しない。
  • ModeがOneWayToSourceのときは、
    • 画面側のプロパティが変化しても、ユーザーコントロール側のプロパティは変化しない。
    • ユーザーコントロール側のプロパティが変化すると、画面側のプロパティは一緒に変化(++)する。
  • ModeがTwoWayのときは、
    • 画面側のプロパティが変化したら、ユーザーコントロール側のプロパティも一緒に変化する。
    • ユーザーコントロール側のプロパティが変化すると、画面側のプロパティも一緒に変化する。

TwoWayにしておくと、画面とユーザーコントロールで、バインドしたお互いのプロパティを通して、同じ値を共有できるイメージになる。

image.png

クセが強い

自分でいろいろ試してみた結果、下記のようなクセがあった。

①画面側のプロパティ変更時、ユーザーコントロール側のプロパティのsetterを通らない

画面側のプロパティに値を代入すると、画面側のsetterを通るので、ユーザーコントロール側のプロパティのsetterも通るのかなと思っていたら、これが通らない。
(参考)

画面側でプロパティに入れた値を、ユーザーコントロール側のプロパティで、下記のように値のチェックとか丸めとかをしようと思ってもできない。(setter通らない。上の参考サイトによると「直接SetValueを呼んでいるのでsetterは通らない」とのこと。)

ユーザーコントロール側プロパティでこう書いていても、画面側のプロパティの値変化時にはここ通ってくれない.cs
public double MyDoubleValue1
{
    get => (double)GetValue(MyDoubleValue1Property);
    set
    {
        var ret = value;
        if (ret > 10)
        {
            ret = 10;
        }
        SetValue(MyDoubleValue1Property, ret);
    }
}

image.png

②OneWayでバインド時、一回でもユーザーコントロール側のプロパティの値をユーザーコントロール側で変化させると、その後画面側プロパティを画面側で変化させても、ユーザーコントロール側に伝わらなくなる

なぜか、今回のサンプル実験アプリだとそういう風になる。(なにか、実装方法がまずいのかも?)

具体的には、下記の画面で、
image.png

「画面側で数字を++」を5回押した後に、
image.png

「UserControl側で数字を++する」を1回押すと、
image.png

そのあと「画面側で数字を++」を5回押しても、TwoWayでバインドした数字は上がるのに、OneWayでバインドした数字は上がらなくなる。
image.png

なぜ??逆流?のようなことをすると、バインドが何かおかしくなるのか??

コード置き場

9
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
7