はじめに
前回の続きです。
前回は、目標に対して、UserControlのインスタンス公開と、その利用のところまでいかず、
コードによるバインディングの確認に終わりました。
今回で、目標まで達成し、終了です。
サンプルの仕様
そもそも、このサンプルを作った動機は、
Visual Studio WPFで、任意のclientから利用可能なコンポーネントを作りたかったのですが、
そこには、少し特殊な要求があったのです。
画面を持っていないコンポーネントなら良かったのですが、画面が欲しいと。
しかも、画面を持っているのだけれど、Windowではなくて、UserControlの画面のインスタンスを提供したい。
Clientは、コンポーネントからUserControlのUI部品インスタンスを拾って、自分(Client)側のWindowに、はめ込んで使える。
というサンプルを作りたかったのです。
サンプルの画面としては、↓の感じです。
MainWindowは、ClientのMainWindowです。
[Push]ボタンは、MainWindow側で直接実装したものです。
[オリジナル画面という表記とその下の数字]は、上の図のUserControl1で、ClassLibrary1が提供するものです。
これを、MainWindowは直接実装するのではなく、ClassLibrary1から取得して、MainWindow内に組み込む。
というのが、やりたい画面のつくりです。
プロジェクトの構造
独自UserControlを持っていて、利用者側に公開する側のプロジェクトが、上の「ClassLibrary1」。
ClassLibrary1のFactoryクラスを使って、ClassLibrary1の中を生成し、公開されたUserControlを利用して、
画面に取り込んで表示する側のプロジェクトが、下の「WpfApp14」。
「WpfApp14」の参照に、「ClassLibrary1」を入れてね。
コード
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;
}
}
}
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 = 非公開の内部オブジェクト;
}
}
}
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();
}
}
<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>
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;
}
}
}
}
<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>
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();
}
}
}


