「C#製コンポーネントで、WPF UserControlのインスタンスを公開したい」のサンプルコード

#はじめに
前回の続きです。
前回は、目標に対して、UserControlのインスタンス公開と、その利用のところまでいかず、
コードによるバインディングの確認に終わりました。
今回で、目標まで達成し、終了です。

#サンプルの仕様

そもそも、このサンプルを作った動機は、
Visual Studio WPFで、任意のclientから利用可能なコンポーネントを作りたかったのですが、
そこには、少し特殊な要求があったのです。
画面を持っていないコンポーネントなら良かったのですが、画面が欲しいと。
しかも、画面を持っているのだけれど、Windowではなくて、UserControlの画面のインスタンスを提供したい。
Clientは、コンポーネントからUserControlのUI部品インスタンスを拾って、自分（Client）側のWindowに、はめ込んで使える。
というサンプルを作りたかったのです。

図で描くと、↓の感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197297/62ac1ea0-766f-23aa-9a5c-fdf1227ba48f.png)

サンプルの画面としては、↓の感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197297/2e1f0349-db58-7ec4-dc3d-1bc21affa8cf.png)

MainWindowは、ClientのMainWindowです。

[Push]ボタンは、MainWindow側で直接実装したものです。

[オリジナル画面という表記とその下の数字]は、上の図のUserControl1で、ClassLibrary1が提供するものです。
これを、MainWindowは直接実装するのではなく、ClassLibrary1から取得して、MainWindow内に組み込む。
というのが、やりたい画面のつくりです。

#プロジェクトの構造
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197297/2e27a3c6-1f58-cb9c-69f5-19c0b5c784b3.png)

独自UserControlを持っていて、利用者側に公開する側のプロジェクトが、上の「ClassLibrary1」。

ClassLibrary1のFactoryクラスを使って、ClassLibrary1の中を生成し、公開されたUserControlを利用して、
画面に取り込んで表示する側のプロジェクトが、下の「WpfApp14」。

「WpfApp14」の参照に、「ClassLibrary1」を入れてね。

#コード

```csharp:Class1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ClassLibrary1
{
    // 外部に提供したくないクラスという設定で、internalにしている
    internal class Class1 : INotifyPropertyChanged  // バインディングさせるソースは、INotifyPropertyChangedが必要
    {
        private int _val1;
        public int val1
        {
            get { return _val1; }
            set
            {
                if (value != _val1)
                {
                    _val1 = value;
                    OnPropertyChanged(nameof(val1));
                }
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
        public Class1(int x)
        {
            val1 = x;
        }
    }
}
```

```csharp:ClassLibrary1Factory.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ClassLibrary1
{
    public class ClassLibrary1Factory
    {
        private static ClassLibrary1Factory factory = new ClassLibrary1Factory();
        public static ClassLibrary1Factory GetInstance()
        {
            return factory;
        }

        private Class1 非公開の内部オブジェクト = null;

        public publicInterface 公開するものだけを持つクラス = null;

        private ClassLibrary1Factory()
        {
            非公開の内部オブジェクト = new Class1(1);
            公開するものだけを持つクラス = new publicInterface(非公開の内部オブジェクト);

            公開するものだけを持つクラス.obj = 非公開の内部オブジェクト;
        }
    }
}
```

```csharp:publicInterface.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ClassLibrary1
{
    public class publicInterface
    {
        // オリジナルなUserControlのデータソースになるクラスのポインタ
        // Factoryによって、コンストラクタ引数で設定している
        // このClassLibrary1を利用する側 WpfApp14 には公開したくないので、internalになっている
        internal Class1 obj { set; get; } = null;

        // コンストラクタ 上と同じく公開したくないので、internalになっている
        internal publicInterface(Class1 sorce)
        {
            obj = sorce;
            // ★オリジナルなUserControlにデータソースを設定
            Control1.DataContext = (object)obj;
            // ★UserControl内の依存関係プロパティとデータソースのプロパティをバインドする
            Control1.SetBinding(UserControl1.値2Property, nameof(Class1.val1));
        }

        // 内部を動かす処理関数
        public void Start()
        {
            obj.val1 += 1;
        }

        // 公開するユーザコントロール
        public UserControl1 Control1 { private set; get; } = new UserControl1();
    }
}
```

```xml:UserControl1.xaml
<UserControl x:Class="ClassLibrary1.UserControl1"
             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:ClassLibrary1"
             mc:Ignorable="d" 
             d:DesignHeight="100" d:DesignWidth="100">
    <StackPanel>
        <Label Content="オリジナル画面" />
        <Label x:Name="label2" Height="40" />
    </StackPanel>
</UserControl>
```

```csharp:UserControl1.xaml.cs
using System.Windows;
using System.Windows.Controls;

namespace ClassLibrary1
{
    /// <summary>
    /// UserControl1.xaml の相互作用ロジック
    /// </summary>
    public partial class UserControl1 : UserControl
    {
        public UserControl1()
        {
            InitializeComponent();
        }

        // オリジナルのUserControl1に表示される値とつながる依存関係プロパティ

        public static readonly DependencyProperty 値2Property = DependencyProperty.Register("値2", typeof(int), typeof(UserControl1),
                new FrameworkPropertyMetadata(default(int), new PropertyChangedCallback(On値2Changed)));
        public int 値2
        {
            get { return (int)GetValue(値2Property); }
            set { SetValue(値2Property, value); }
        }
        private static void On値2Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            // 値が変化したときに動く処理。値が設定されたときに動く訳ではないので、注意。
            UserControl1 ctrl = obj as UserControl1;
            if (ctrl != null)
            {
                // ここで画面更新
                ctrl.label2.Content = ctrl.値2;
            }
        }
    }
}
```

```xml:MainWindow.xaml
<Window x:Class="WpfApp14.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:WpfApp14"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel x:Name="stackPanel1">
        <Button Click="Button_Click" Content="Push" />
    </StackPanel>
</Window>
```

```csharp:MainWindow.xaml.cs
using ClassLibrary1;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApp14
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        // このサンプルでは、WpfApp14プロジェクトが、ClassLibrary1プロジェクトを使って、機能実装するサンプル
        // ClassLibrary1プロジェクトは、Factoryクラスを持っていて、内部的にインスタンスを生成する

        // このWpfApp14のMainWindowは、ClassLibrary1が提供する機能を利用し、公開されているUserControlも利用するというサンプル
        ClassLibrary1Factory factory = ClassLibrary1Factory.GetInstance();

        public MainWindow()
        {
            InitializeComponent();

            // 公開されているUserControlを取得
            UserControl ClassLibrary1から取得した実体のUserControl = factory.公開するものだけを持つクラス.Control1;

            // 取得したUserControlをMainWindowに差し込む
            stackPanel1.Children.Add(ClassLibrary1から取得した実体のUserControl);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            // 公開されている機能を実行すると、その機能の実行結果として、公開されているUserControlの値が更新されるサンプル
            factory.公開するものだけを持つクラス.Start();
        }
    }
}
```