かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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パターンでさくっと出来そう。