Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

[C#/WPF] ユーザーコントロールのひな型

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

やりたいこと

とりあえず、自前でユーザーコントロールを作れるようにひな型と、作り方の流れを自分の中に持っておきたい。

前提

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

ひな型

お試しアプリのざっくり仕様

画面

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

ひな型サンプルの動作

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

ユーザーコントロール側のひな型サンプル

見た目(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="350" Height="50">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="100"/>
            <ColumnDefinition Width="250"/>
        </Grid.ColumnDefinitions>

        <!-- タイトル行 -->
        <TextBox Grid.Row="0" Grid.Column="0" Text="value" />

        <!-- 数字を表示する行 -->
        <TextBox Grid.Row="1" Grid.Column="0" Text="{Binding MyDoubleValue1, 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 { SetValue(MyDoubleValue1Property, value); }
        }
        public static readonly DependencyProperty MyDoubleValue1Property = DependencyProperty.Register(nameof(MyDoubleValue1), typeof(double), typeof(SimpleUserControl), new PropertyMetadata(0.0));

        public SimpleUserControl()
        {
            InitializeComponent();
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            MyDoubleValue1 += 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="550"
        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=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 MainWindow()
        {
            InitializeComponent();
        }
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            DoubleValue1++;
        }
    }
}

ひな型の説明

全体のイメージ

基本的に、下記のようなイメージで処理を書けばいい。

  • 画面側でプロパティの値を変更すると、その変更が依存関係プロパティを通じてユーザーコントロールのプロパティに伝わる。
  • ユーザーコントロール側でプロパティの値を変更すると、その変更が依存関係プロパティを通じて画面のプロパティに伝わる。
  • ただし、これはバインディングのModeがTwoWayの場合のみ。OneWayToSourceOneWayだと、片道一方通行になるので注意。(別途メモ書きまとめたい)

image.png

ユーザーコントロール側

依存関係プロパティの作成

依存関係プロパティをつくる。

  • ユーザーコントロールが、使う側のプロパティとバインドするためには、依存関係プロパティというものを持つ必要がある。
  • コードビハインドでDependencyProperty.Registerを使って、自分はこういうものを持ってますよ、というのを文字通りRegister(登録)する。
  • 依存関係プロパティは、外部(ここでは画面側)に、「自分はこんなプロパティをバインドできますよ」とお知らせするためのもの。(門番的な)
  • 外部(画面)側は、そのお知らせされたプロパティに、自分のプロパティをバインドして、連動させて使う。

具体的には下記の部分。

public static readonly DependencyProperty MyDoubleValue1Property = DependencyProperty.Register(nameof(MyDoubleValue1), typeof(double), typeof(SimpleUserControl), new PropertyMetadata(0.0));

DependencyProperty.Register()に色々引数を指定して、DependencyPropertyを作る。
引数は下記のようにする。

  1. 名前
  2. プロパティに付けたい型を指定
  3. ユーザーコントロールの型を指定
  4. ユーザーコントロールの情報を指定するためのPropertyMetaDataを載せる
    (引数1個のPropertyMetaDataの引数で、初期値を指定できる。ほかにもいろいろ指定できるが、簡単のため今はこれだけにする)

image.png

依存関係プロパティと対になるプロパティ作成

依存関係プロパティには、対になるプロパティが必要。それが下記の部分。

public double MyDoubleValue1
{
    get => (double)GetValue(MyDoubleValue1Property);
    set { SetValue(MyDoubleValue1Property, value); }
}
  • getter
    • 依存関係プロパティの値をとってきてくれるGetValueメソッドを呼ぶ。
    • GetValueメソッドの引数は、先ほど作成したDependencyPropertyを渡す。
    • GetValueメソッドはobjectを返すので、DependencyProperty.Registerの第二引数で指定した型でキャストする。
  • setter
    • 依存関係プロパティの値をセットしてくれるSetValueメソッドを呼ぶ。
    • SetValueメソッドの第一引数は、先ほど作成したDependencyPropertyを渡す。
    • SetValueメソッドの第二引数は、valueを渡す。

このプロパティの中では、GetValueSetValueを呼ぶこと以外には何もしない方がよい。
こちら参照(CLRプロパティでは、setvalue,getvalue以外のことをしないほうがよい)

内部処理を作成

必要に応じて、ユーザーコントロール内部で処理を行う。
上のひな型では、下記の部分で、プロパティの値を++している。

private void Button_Click(object sender, RoutedEventArgs e)
{
    MyDoubleValue1 += 1.0;
}

この例だと、プロパティは画面に下記のようにバインドしている。

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

これにより、++した値は画面に表示される。
※特にInotifyPropertyChangedを継承して実装したりしなくても、プロパティの値が変化したら、それが画面に反映される。

画面側(ユーザーコントロールを使う側)

画面側では、UserControlではない普通のコントロール(例えばTextBlock)にプロパティをバインドする要領で、バインドして使えばいい。

コードビハインド(やビューモデル)でプロパティを作成して、

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

画面(xaml)でそれをバインドする。

<local:SimpleUserControl Grid.Column="2" Grid.Row="1" MyDoubleValue1="{Binding DoubleValue1, ElementName=root, Mode=TwoWay}"/>

クセが強い

ユーザーコントロール、触ってみた感じ、扱い方のクセが強い気がする。
ひな型のように、基本的なことしかしないとそれほどでもないが、「プロパティの値が変わった時に〇〇したい」とか「プロパティの値が100を超えたら100に丸めたい」とかをやろうと思うと、かなりハマった。
丸めるとか、変化検出とかは、それ用の仕組みがあるのだが、そいつらがまたよくわからない動きをする。(個人の感覚)

コード置き場

https://github.com/tera1707/WPF-/tree/master/037_BindingWithUserControlBasic

tera1707
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away