1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

1
Last updated at Posted at 2020-01-27

はじめに

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

サンプルの仕様

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

図で描くと、↓の感じ。
image.png

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

image.png

MainWindowは、ClientのMainWindowです。

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

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

プロジェクトの構造

image.png

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

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

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

コード

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;
        }
    }
}
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 = 非公開の内部オブジェクト;
        }
    }
}
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();
    }
}
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>
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(On2Changed)));
        public int 2
        {
            get { return (int)GetValue(2Property); }
            set { SetValue(2Property, value); }
        }
        private static void On2Changed(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            // 値が変化したときに動く処理。値が設定されたときに動く訳ではないので、注意。
            UserControl1 ctrl = obj as UserControl1;
            if (ctrl != null)
            {
                // ここで画面更新
                ctrl.label2.Content = ctrl.2;
            }
        }
    }
}
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>
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();
        }
    }
}
1
1
1

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
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?