読者です 読者をやめる 読者になる 読者になる

SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

WPF/UWP向けに、グリッドレイアウト補助ライブラリを作ってみました~GridExtra~

この記事はXAMLアドベントカレンダー 2016 18日目の記事です。

WPF/UWP向けに、グリッドレイアウトに役立つクラス類を提供するライブラリを作りました。

今のところ、この二つのクラスだけですが、今後少しずつ色々なパネルを追加していきたいと思っています。

  • ResponsiveGrid
  • GridEx
    • Gridの行/列定義を簡単にするための各種添付プロパティ類

目次

準備

インストール方法

Nugetから以下のパッケージをインストールすればOK!!
https://www.nuget.org/packages/GridExtra/

Nugetパッケージ管理のGUIで、GridExtraで検索するか、
f:id:minami_SC:20161218155955p:plain

Nugetのパッケージマネージャー コンソールから、↓のコマンドでインストールできます。

Install-Package GridExtra
名前空間の定義

XAMLに以下のような名前空間の定義を追加します。
WPFとUWPで若干指定方法が違うので注意。

WPFの場合

xmlns:ge="clr-namespace:SourceChord.GridExtra;assembly=GridExtra.Wpf"

UWPの場合

xmlns:ge="using:SourceChord.GridExtra"

使い方

ResponsiveGrid

f:id:minami_SC:20160702010414g:plain

こんな風にResponsiveGrid.XS, ResponsiveGrid.MDなどのプロパティを指定すると、ウィンドウ幅の変更に応じて自動でレイアウトが切り替わります。
CSSの定番フレームワークbootstrapのレイアウトシステムを模倣したものです。

    <Grid>
        <Grid.Resources>
            <Style TargetType="{x:Type Border}">
                <Setter Property="BorderBrush" Value="Black" />
                <Setter Property="BorderThickness" Value="1" />
                <Setter Property="Background" Value="LightGray" />
                <Setter Property="Height" Value="60" />
                <Setter Property="TextBlock.FontSize" Value="10" />
            </Style>
        </Grid.Resources>
        <ge:ResponsiveGrid Margin="10" BreakPoints="345, 567, 789">
            <Border ge:ResponsiveGrid.XS="12">
                <TextBlock Text="[Header]&#xa;XS=12"/>
            </Border>
            <Border ge:ResponsiveGrid.XS="6" ge:ResponsiveGrid.SM="3" ge:ResponsiveGrid.MD="2">
                <TextBlock Text="[A]&#xa;XS=6&#xa;SM=3&#xa;MD=2"/>
            </Border>
            <Border ge:ResponsiveGrid.XS="6" ge:ResponsiveGrid.SM="3" ge:ResponsiveGrid.MD="2">
                <TextBlock Text="[B]&#xa;XS=6&#xa;SM=3&#xa;MD=2"/>
            </Border>
            <Border ge:ResponsiveGrid.XS="6" ge:ResponsiveGrid.SM="3" ge:ResponsiveGrid.MD="2" ge:ResponsiveGrid.SM_Push="3" ge:ResponsiveGrid.MD_Push="2">
                <TextBlock Text="[C]&#xa;XS=6&#xa;SM=3&#xa;MD=2"/>
            </Border>
            <Border ge:ResponsiveGrid.XS="6" ge:ResponsiveGrid.SM="3" ge:ResponsiveGrid.MD="2" ge:ResponsiveGrid.SM_Pull="3" ge:ResponsiveGrid.MD_Pull="2">
                <TextBlock Text="[D]&#xa;XS=6&#xa;SM=3&#xa;MD=2"/>
            </Border>
            <Border ge:ResponsiveGrid.XS="12" ge:ResponsiveGrid.SM="6" ge:ResponsiveGrid.MD="2">
                <TextBlock Text="[E]&#xa;XS=12&#xa;SM=6&#xa;MD=2"/>
            </Border>
            <Border ge:ResponsiveGrid.XS="12" ge:ResponsiveGrid.SM="6" ge:ResponsiveGrid.MD="2">
                <TextBlock Text="[F]&#xa;XS=12&#xa;SM=6&#xa;MD=2"/>
            </Border>
            <Border ge:ResponsiveGrid.XS="12">
                <TextBlock Text="[Footer]&#xa;XS=12"/>
            </Border>
        </ge:ResponsiveGrid>
    </Grid>

以前、ResponsiveGridというライブラリを作って個別にGitHubに上げてたものですが、今回作ったGridExtraの中に移動しました。
ResponsiveGridの詳細な使い方は、↓の記事を参照してください。

GridEx

Gridパネルでのグリッド定義を便利にする、各種添付プロパティを定義したクラスです。

行×列の定義

普通にGridの行×列を定義をする場合には、以下のようなXAMLを書きます。

    <Grid ShowGridLines="True">
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="*" />
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="2*" />
            <ColumnDefinition Width="100" />
        </Grid.ColumnDefinitions>
        <Button Grid.Row="0"
                Grid.Column="1"
                Margin="5"
                Content="Button" />
    </Grid>

f:id:minami_SC:20161218155608p:plain

これって、結構冗長ですよね。
インテリセンスでコード補間されるとはいえ、何回</>を書けばいいんだ・・・と。

GridExの添付プロパティを使うと、以下のように単純に書くことができます。

    <Grid ge:GridEx.ColumnDefinition="*, 2*, 100"
          ge:GridEx.RowDefinition="50, *, 50"
          ShowGridLines="True">
        <Button Grid.Row="0"
                Grid.Column="1"
                Margin="5"
                Content="Button" />
    </Grid>

MaxWidth, MinWidthなどの設定には対応してませんが、多くのケースはこれだけでも便利に利用できるのでは、と思います。

MaxWidthなどのプロパティは、今後のアップデートで対応しようと思います。

グリッドの子要素の位置定義

こちらも、先ほどと同じように、まずは普通にGridクラスのプロパティを使って定義してみます。

    <Grid ge:GridEx.ColumnDefinition="*, *, *, *"
          ge:GridEx.RowDefinition="*, *, *, *"
          ShowGridLines="True">
        <Button Grid.Row="1"
                Grid.Column="2"
                Grid.RowSpan="3"
                Grid.ColumnSpan="2"
                Margin="5"
                Content="Button" />
    </Grid>

f:id:minami_SC:20161218155617p:plain

子要素の位置定義では、Gridクラスの添付プロパティを複数書かなければならず、少々面倒です。

そこで、Row/Column/RowSpan/ColumnSpanを一度に設定する添付プロパティを作成しました。
GridExクラスのArea添付プロパティを使うと、以下のようにこれらを一度に定義できます。

    <Grid ge:GridEx.ColumnDefinition="*, *, *, *"
          ge:GridEx.RowDefinition="*, *, *, *"
          ShowGridLines="True">
        <Button ge:GridEx.Area="1, 2, 3, 2"
                Margin="5"
                Content="Button" />
    </Grid>

GridEx.Areaプロパティには、「Row, Column, RowSpan, ColumnSpan」の順番で、カンマ区切りで値を指定します。

ASCIIアート風のグリッド定義

HTML&CSSでのグリッドレイアウト用の仕様として、CSS Grid Layout Module Level1という仕様の策定が進んでいます。

この中で、グリッドをアスキーアートのように文字列で定義して各領域に名前を付け、グリッド内の子要素はこの領域名を用いて配置できる機能があります。

参考リンク
CSS Grid Layout Module Level 1
ElectronでCSS Grid Layout Moduleを使ってちょっと未来のレイアウト方法を先取りしてみる - SourceChord

TemplateAreaは、グリッドの各領域に対し名前を付けながら、行×列の定義を行います。
各領域はスペース区切りの文字列で領域名を定義していきます。
また、改行コードで行の終わりを定義します。

隣接している箇所に同じ名前を付けておくと、複数の行×列にまたがる領域を定義できます。

    <Grid ge:GridEx.TemplateArea="
            Header Header Header &#10;
            Menu Content SubMenu &#10;
            Footer Footer Footer &#10;
          "
          ShowGridLines="True">
        <Button Margin="5"
                ge:GridEx.AreaName="Header"
                Content="Header" />
        <Button Margin="5"
                ge:GridEx.AreaName="Menu"
                Content="Menu" />
        <Button Margin="5"
                ge:GridEx.AreaName="Content"
                Content="Content" />
        <Button Margin="5"
                ge:GridEx.AreaName="SubMenu"
                Content="SubMenu" />
        <Button Margin="5"
                ge:GridEx.AreaName="Footer"
                Content="Footer" />
    </Grid>

f:id:minami_SC:20161218155632p:plain

行の終わりの定義
xmlでは複数行にまたがる文字列を定義しても、行末の改行は無視されてしまいます。
そこで、&#10というエスケープ文字列を使って改行コード書くと、xmlでも改行を扱うことができます。

また、毎回このエスケープ文字列を書くのは面倒なので、/という文字列でも行の終わりを定義できるようにしています。

    <Grid ge:GridEx.TemplateArea="
            Header Header Header/
            Menu Content SubMenu/
            Footer Footer Footer/
          ">

RowDefinition/ColumnDefinitionとの併用
TemplateAreaは、GridEx.RowDefinitionなどの定義と併用できます。
GridEx.RowDefinition, GridEx.ColumnDefinitionと組み合わせることで、柔軟なグリッド定義ができます。

    <Grid ge:GridEx.RowDefinition="50, *, 30"
          ge:GridEx.ColumnDefinition="*, 2*, 100"
          ge:GridEx.TemplateArea="
            Header Header Header/
            Menu Content SubMenu/
            Footer Footer Footer/
          "
          ShowGridLines="True">
        <Button Margin="5"
                ge:GridEx.AreaName="Header"
                Content="Header" />
        <Button Margin="5"
                ge:GridEx.AreaName="Menu"
                Content="Menu" />
        <Button Margin="5"
                ge:GridEx.AreaName="Content"
                Content="Content" />
        <Button Margin="5"
                ge:GridEx.AreaName="SubMenu"
                Content="SubMenu" />
        <Button Margin="5"
                ge:GridEx.AreaName="Footer"
                Content="Footer" />
    </Grid>

f:id:minami_SC:20161218155641p:plain

このようにTemplateAreaの編集にXAMLデザイナも追従するので、直感的にグリッドを定義できるのでは、と思います。
f:id:minami_SC:20161218155440g:plain