【WPF】Expander開閉時にGridSplitterで広げた領域を戻す

C#

こんにちは、働くC#プログラマーのさんさめです。

WPFで領域をドラッグで広げられるようにするには
Gridと、GridSplitterを使います。

ただこれ、コンテンツを折り畳める
Expanderと併用すると、
なんとも残念なことになります。

というのも、
Expanderを閉じても
広げた領域がそのまま残ってしまうのです。

Expanderを閉じても残りっぱなし

こういう時にやりたいのは、
「開いている時だけ領域サイズが調整できて、
閉じたらヘッダー部分だけ残して縮むこと」
です。

というわけで本記事では、
この挙動を改善するために必要な実装を解説し、
最後にxamlにちょっと書き足すだけで、
良い感じの挙動になる添付プロパティを紹介します。

スポンサーリンク

Expanderの開閉に合わせてGridの幅を再設定すると良い

改善の話に進む前に、
どうして領域が残る挙動になってしまうのか、
その原因を確認します。

GridSplitterは、
Gridに配置した
ColumnDefinitionのWidth ( / RowDefinitionのHeight)
を変更するコントロールです。

つまり、そこに置いたコントロール
(この場合はExpander)の高さや幅がいくつか、
など知ったこっちゃないのです。

これを改善するためには、
Expanderの開閉に合わせて、
Expanderが配置されている
ColumnDefinitionのWidth( / RowDefinitionのHeight)
をAutoに戻す必要があります。

コードビハインドに書くとしたら、
以下のようなコードになります。

private GridLength lastRowHeight = GridLength.Auto;
private void MyExpander_Expanded(object sender, RoutedEventArgs e)
{
    // 前に閉じたときの高さ値が残っていたらそれを復元
    MyRowDefinition.Height = lastRowHeight;

    // GridSplitterを可視化
    MyGridSplitter.Visibility = Visibility.Visible;
}

private void MyExpander_Collapsed(object sender, RoutedEventArgs e)
{
    // 閉じる前の高さを保存し
    // 高さをAutoに戻す
    lastRowHeight = MyRowDefinition.Height;
    MyRowDefinition.Height = GridLength.Auto;

    // GridSplitterを非表示に
    MyGridSplitter.Visibility = Visibility.Collapsed;
}

Expanderが閉じているときは、
GridSplitterも非表示にして、
ドラッグできないようにしておくと、
気が利いていますね。

念のため、xaml側も載せておきます。

<Window x:Class="GridSnapExpander.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="250" Width="300">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="Auto" x:Name="MyRowDefinition"/>
        </Grid.RowDefinitions>
        <TextBox Text="メイン画面のつもり"/>
        <GridSplitter Height="4"
                      Background="Gray"
                      x:Name="MyGridSplitter"
                      HorizontalAlignment="Stretch"
                      VerticalAlignment="Bottom"
                      Visibility="Collapsed"/>
        <Expander Header="サブ画面"
                  x:Name="MyExpander"
                  Expanded="MyExpander_Expanded"
                  Collapsed="MyExpander_Collapsed"
                  Grid.Row="1">
            <TextBox Text="サブ画面のつもり"/>
        </Expander>
    </Grid>
</Window>

実行結果を見てみましょう。

Expanderを閉じたときに、
領域が引っ込むようになりました。

また、Expanderが展開しているときだけ、
GridSplitterが現れています。

相性を改善する添付プロパティを作ってみた

しかし、
この挙動を実現させるために、
上記のようなコードを毎回書くのは
はっかり言って面倒です。

というわけで、
上記のような振る舞いをExpanderにさせるための
添付プロパティを作ってみました。

コードは以下の通りです。
…と、言いつつ長いので閉じておきます。
興味のある方は実装を眺めてみてください。
WPFのプロジェクトなら、
全文コピペするだけでビルド通ります。

xamlで使うときは、
以下のように設定します。

簡単ですね。
コードビハインドや各所のx:Nameが消えて
だいぶすっきりしました。

Autoを設定しておけば、
自身の配置されているRowDefinitionを、
自動的に検知して高さを調整してくれます。
また、GridSplitterの表示も切り替えてくれます。

実行結果を見てみましょう。

ちゃんとExpanderを閉じたときに、
Gridが縮んでいますね。

ExpandDirectionが横向きの時も、
ちゃんと動作しますのでご安心を。
(両対応のせいでコードが長くなってます…)

GridSplitterを複数置きたい場合はExplicitモードを使う

ちなみに、
もしGridSplitterが複数置かれている場合は、
Autoでは表示切替がうまくいかない可能性があります。

その場合は、モードをExplicitに変更して、
TargetGridSplitterを以下のようにBindingで指定します。

これを使えば、
指定したGridSplitterの表示状態が
変更されるようになります。

実行結果は次のようになります。

指定したGridSplitterの表示状態のみが、
Expanderの開閉に連動していますね。

まとめ

まとめです。

  • ExpanderとGridSplitterを併用すると、
    閉じたときに残念な見た目になる
  • Expanderの開閉に合わせて、
    幅や高さをAutoに再設定すると改善する
  • xamlの設定1つで相性を改善する
    添付プロパティを紹介

最後までお読みいただき、ありがとうございました。

関連記事

添付プロパティの中では、
ビューの祖先や子孫から、
特定のコントロールを取得するコードを使用しています。

それぞれ、取得するコード例を紹介しています。

コメント

タイトルとURLをコピーしました