【WPF(XAML)C#7】MVVMのModel側で画面作成

セクション5までで画面の大半を使うクラス周りが終わりましたのでセクション6からはC#側も含めたXAMLの応用をお送りできればと思います。

モデル側で画面を作る?

本来はMVVMパターンに合わせてXAML側で画面を作るのですがWPFはあくまでフレームワークであり、XAMLの実体もC#なので画面側もC#で作る事ができます。ただMVVMパターンに違反するものとなっておりますのでプロジェクトによっては禁止している所もあるかもしれません。

とはいえ、以下のパターンの場合、C#で組んだ方がよりよい画面が作れることも多いです。

  • DataGridの表を作りたいが種類によって使わない列がある場合はC#で汎用的に作った方がいい
  • 例えば東京の天気をカレンダーのように見たい場合、月によって日数曜日によって描画位置が異なるのでカレンダーを作るにはC#で組んだ方がよい

まずはこの画像を見てください。

セクション4で見た画像と同じになっていますが、セクション4と違う所はこれはXAML側で作ったDataGridではないということです。XAMLのソースは以下です。

XAMLのコード

<Window x:Class="WpfApp_Core.main.DataGridWindow"
        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:WpfApp_Core.main"
        mc:Ignorable="d"
        Title="DataGridWindow" Height="400" Width="500"
        Loaded="DataGridWindow_Loaded">
    <Window.Resources>
        <Style x:Key="WeatherIconStyle" TargetType="{x:Type Image}">
            <Style.Triggers>
                <DataTrigger Binding="{Binding weather}" Value="1">
                    <Setter Property="Source" Value="../Image/16_2_19.png"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding weather}" Value="2">
                    <Setter Property="Source" Value="../Image/cloudy.png"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding weather}" Value="3">
                    <Setter Property="Source" Value="../Image/rainy.png"/>
                </DataTrigger>
                <DataTrigger Binding="{Binding weather}" Value="4">
                    <Setter Property="Source" Value="../Image/sunder.png"/>
                </DataTrigger>
            </Style.Triggers>
        </Style>
        <DataTemplate x:Key="WeatherTemplate">
            <StackPanel>
                <Image Style="{DynamicResource WeatherIconStyle}" Width="25"/>
            </StackPanel>
        </DataTemplate>

        <Style TargetType="{x:Type DataGridColumnHeader}">
            <Setter Property="Background" Value="Aqua"/>
            <Setter Property="BorderThickness" Value="0"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="HorizontalContentAlignment" Value="Center"/>
        </Style>
        <Style TargetType="{x:Type DataGridRowHeader}">
            <Setter Property="FontSize" Value="12"/>
        </Style>
        <Style TargetType="{x:Type DataGridRow}">
            <Setter Property="Height" Value="30"/>
        </Style>
        <Style TargetType="{x:Type DataGridCell}">
            <Setter Property="HorizontalAlignment" Value="Center"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
        </Style>

    </Window.Resources>
    <DockPanel>
        <StackPanel DockPanel.Dock="Top" Height="30" Background="Yellow">
            <Label Content="詳細" HorizontalAlignment="Center" FontSize="20" FontWeight="Bold"/>
        </StackPanel>

        <DockPanel DockPanel.Dock="Bottom" Height="30" Background="Aqua">
            <Button Content="閉じる"
                    HorizontalContentAlignment="Center"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Center"
                    Height="20"
                    Width="100"
                    Margin="0,0,5,0"
                    Click="WindowClose_Click"/>
        </DockPanel>

        <Grid x:Name="DataGridGrid">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="100"/>
                <ColumnDefinition Width="auto"/>
                <ColumnDefinition Width="100"/>
            </Grid.ColumnDefinitions>

セクション4から変わった所は3つです。

  • DataGrid内で定義したスタイルを共通部分に移動。
  • XAML内のDataGridは削除しているのでGridの定義でコードが終わっている。
  • Gridの名前にDataGridGridとつけている。

DataGrid内で定義したスタイルを共通部分に移動

前述したとおり、基本的には生成する列を汎用的にする所だけC#で組むので固定のスタイル部分はC#に入れずにXAMLで定義した方がC#でDataGridを作る時も非常に楽になります。

Gridの名前にDataGridGridとつけている。

この名前をつける操作がとても重要になります。名前をつけるとC#側でその名前をつけたクラスが参照できるようになるのでその下にDataGridを入れていくイメージです。

C#のコード

/* C#でDataGridを作る */
var editGrid = new DataGrid()
{
    Name = "WeatherGrid",
    AutoGenerateColumns = false,
    CanUserAddRows = false,
    CanUserReorderColumns = false,
    GridLinesVisibility = DataGridGridLinesVisibility.None,
    IsReadOnly = true,
    HeadersVisibility = DataGridHeadersVisibility.Column,
    AlternatingRowBackground = Brushes.Beige,
    AlternationCount = 2,
    BorderThickness = new Thickness(0),
    Margin = new Thickness(0, 10, 0, 10),
    ItemsSource = MyVM.gridSource
};

// Grid.Column="1"
Grid.SetColumn(editGrid, 1);

// 都道府県列生成
var textColumn = new DataGridTextColumn()
{
    Header = "都道府県",
    Width = 60,
    Binding = new Binding("prefLabelPref")
};

editGrid.Columns.Add(textColumn);

// 市区町村列生成
textColumn = new DataGridTextColumn()
{
    Header = "市区町村",
    Width = 90,
    Binding = new Binding("municipalityLabel")
};

editGrid.Columns.Add(textColumn);

// 天気列生成
var templateColumn = new DataGridTemplateColumn()
{
    Header = "天気",
    Width = 50,
    CellTemplate = FindResource("WeatherTemplate") as DataTemplate
};

editGrid.Columns.Add(templateColumn);

// 気温列生成
textColumn = new DataGridTextColumn()
{
    Header = "気温",
    Width = 40,
    Binding = new Binding("temperature")
};

// TextのBinding属性を定義
textColumn.Binding.StringFormat = @"#0" + "℃";
textColumn.Binding.TargetNullValue = "不明";

editGrid.Columns.Add(textColumn);

// 降水確率列生成
textColumn = new DataGridTextColumn()
{
    Header = "降水確率",
    Width = 50,
    Binding = new Binding("rainyPercent")
};

// TextのBinding属性を定義
textColumn.Binding.StringFormat = @"#0" + "%";
textColumn.Binding.TargetNullValue = "不明";

editGrid.Columns.Add(textColumn);

// 名前をつけたGridエリアにeditGridを入れる
DataGridGrid.Children.Add(editGrid);

セクション4のコードにXAMLでやっていた事をC#コード化すると上記のようになります。

Grid.SetColumn(editGrid, 1);等、所々同じように書けない箇所が存在します。ちなみにDockPanel.Dockみたいな設定も直接はできずにDockPanel.SetDockに名前をつけたPanel等に入れて設定します。外のTemplateやStyleを参照させたい時もFindResource(“WeatherTemplate”) as DataTemplate や Styleとつければ参照が可能です。

コードを見て頂くとわかりますが、XAMLで定義したDataGridDataGridTextColumnStackPanel等もそうですが、C#のクラスだったんだ。という事をC#側から作ると実感しますね。

今回は解説のためにDataGridもC#で作ってしまいましたが本来の用途である列数を可変にするだけならNameをつけたDataGridをXAMLで定義してColumnsの下だけをC#で作っAdd方が良いです。

まとめ

本来はXAMLで作る画面側ですが、C#側で作ると画面側で作るより、より汎用性の高い画面になっていきます。もちろんMVVMパターンからすればViewをModelで定義しているということになるので違反しています。どうしてもでない限りはC#側で作らない方が良いでしょう。

StackPanelやDockPanelも同じように new StackPanel();等でインスタンス化できますのでこの技術を使いこなせばだいぶXAMLのスキルも上達すると思いますよ。

以上でセクション6を終わります。セクション7はConverterをやりたいと思います。

お知らせ:セクション8以降は実際に実務で作った応用的なコードを共有したいと思いますが、現時点でそういったコードを公開すると丸コピーではないにしても身バレしてしまう危険性がありますのでセクション7で一旦XAML記事はストップして別の技術記事をアップしたいと思います。