かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

WPFでFormの継承(Windowの継承 or 見た目の継承?)

ネタ元1:http://bbs.wankuma.com/index.cgi?mode=al2&namber=42894
ネタ元2:http://social.msdn.microsoft.com/Forums/ja-JP/vbgeneralja/thread/dd3810a9-e3aa-4706-89ee-3f836408e709



Windows Formの頃には一般的?に行われていたFormの継承を、WPFでやろうとすると躓きます。これは、ネタ元を見ていただければわかると思います。
じゃぁどうやるの?ということで強引にしてみました。

Windowをベースにしたカスタムコントロールを作る

まず、WPFアプリケーションのプロジェクトでカスタムコントロールを作成します。
ここでは、MyWindowという名前にしました。そして、基本クラスをControlからWindowに変更します。

using System.Windows;
using System.Windows.Media;

namespace WpfBaseWindow
{
    // 基本クラスはWindowにする
    public class MyWindow : Window
    {
        static MyWindow()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MyWindow), new FrameworkPropertyMetadata(typeof(MyWindow)));
            // 背景色をWindowBrushにしておく
            BackgroundProperty.OverrideMetadata(typeof(MyWindow), 
                new FrameworkPropertyMetadata(SystemColors.WindowBrush));
        }
    }
}

そして、Generic.xamlに、見た目を作っていきます。

<?xml version="1.0" encoding="Shift_JIS"?>
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfBaseWindow">


    <Style TargetType="{x:Type local:MyWindow}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MyWindow}">
                    <!-- ここに見た目を作っていく -->
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}">
                        <!-- 今回は、ヘッダーとフッターに文字を出すだけにします -->
                        <DockPanel>
                            <TextBlock DockPanel.Dock="Top" Text="へっだー" />
                            <TextBlock DockPanel.Dock="Bottom" Text="ふったー" />
                            <!-- 
                            センターにコンテンツ(つまり継承先のWindowで追加されるコントロール)を
                            表示するようにします
                            -->
                            <ContentPresenter />
                        </DockPanel>
                    </Border>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

Generic.xamlの頭にShift_JISであることを宣言しておかないと、日本語が使えないので要注意です。(ここらへんはXMLのお話だけど)

Windowの親クラスを差し替える

ついにWindowの親クラスを差し替えます。最初にWindow1.xamlのルートのタグをWindowからlocal:MyWindowに変更します。localは、MyWindowの所属する名前空間とマッピングするようにxmlnsを定義しておきます。

<local:MyWindow x:Class="WpfBaseWindow.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WpfBaseWindow"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <Button Content="aaaaa" />
    </Grid>
</local:MyWindow>

ついでにボタンも1つ置いておきました。

この状態でビルドすると、

'WpfBaseWindow.Window1' の partial 宣言では、異なる基本クラスを指定してはいけません。

というエラーで怒られてしまいます。
これは、Window1.xaml.csでWindow1 : Windowと明示的に継承が書かれているせいです。これをMyWindowに変えてしまいましょう。

using System.Windows;

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

以上で、完成です。実行してみると・・・。

ちゃんと見た目が継承されています。

感想

やる必要があるのかというと、そうでもないかな〜という感じがします。
個人的には、Windowを1つ作っておいて、固有の部分をUserControlで作っていって差し替えるといった感じでやっていくと思います。
それにModel-View-ViewModelパターンでさくっと出来そう。