Window サイズの変更に追随して MaxSize を変更するビヘイビア

情報の表示と一覧の表示をそれぞれのボタンクリックで行い、情報の表示領域と一覧の表示領域をそれぞれのボタンがクリックされたときに切り替え、さらに、ウィンドウサイズの変更に追随するものを作ってみました。ウィンドウサイズの変更への追随部分はビヘイビアで作成しています。

このビヘイビアは、サイズ変更に追随させたいコントロールの MaxHeight プロパティおよび MaxWidth プロパティの値を増減させることで、大きさを変えています。したがって、拡大方向に追随するのは、当該コントロールのデータ表示で必要となる大きさまでとなります。

(2016/08/03 追記 最大値が最小値より小さくなる場合、最小値を書き換えるように変更しました。)

DocPanel を利用すればウィンドウサイズ変更への追随は可能ですが、表示領域の切り替えと両立させることができそうにないことから、DocPanel の利用はあきらめました。
また、DocPanel を利用したウィンドウサイズ変更への追随は、Dock 添付プロパティで設定する DocPanel 内の結合方向と DocPanel 内でのコントロールの定義順に依存することになるため、ちょっと分かりづらい点も難点です。

最初にプログラムの表示例を示しておきます。プログラムは Visual Studio 2015 で作成しています。
まず最初は、切り替え機能なしの一覧表示のみのもの。
起動時の画面。「ほげ」ボタンはダミーです。

「切り替え機能なし・起動時」の画面例
切り替え機能なし・起動時

続いて、「読み込み」ボタンをクリックしたときの画面。

「切り替え無し・データ読み込み時」の画面
切り替え無し・データ読み込み時

続いて、ウィンドウサイズを変更したときの画面。

「切り替え無し・ウィンドウサイズ変更時」の画面
切り替え無し・ウィンドウサイズ変更時

次は、切り替え機能ありの起動時の画面。

「切り替えあり・起動時」の画面
切り替えあり・起動時

続いて、「情報表示」ボタンをクリックしたときの画面。

「切り替えあり・情報表示ボタンクリック時」の画面
切り替えあり・情報表示ボタンクリック時

続いて、ウィンドウサイズを変更したときの画面。

「切り替えあり・情報表示でウィンドウサイズを変更時」の画面
切り替えあり・情報表示でウィンドウサイズを変更時

続いて、上の画面から「一覧表示」ボタンをクリックしたときの画面。

「切り替えあり・一覧表示ボタンクリック時」の画面
切り替えあり・一覧表示ボタンクリック時

続いて、ウィンドウサイズを変更したときの画面。

「切り替えあり・ウィンドウサイズ変更時」の画面
切り替えあり・ウィンドウサイズ変更時

続いて、上の画面から「情報表示」ボタンをクリックたときの画面。

「切り替えあり・再度情報表示ボタンをクリック時」の画面
切り替えあり・再度情報表示ボタンをクリック時

ひとつ前の画面で、ウィンドウのサイズが拡大されて、情報表示画面の表示に必要となるサイズより大きくなっているため、「ほげ」ボタンの下に空白のエリアが広がっていますが、まぁこれはしょうがないということで。

続いて、プログラムを示しますが、上記で画面例を2つ出しているので、MainWindow.xaml は二通り示します。ビューモデルは1つで、切り替えができる機能付きで実装し、ビュー側では必要となる機能のみを利用します。
 なお、コマンド機能やプロパティ変更通知機能を提供する ViewModelBase クラスは、拙策の MakViewModelBase を NuGet からインストールする形式で書いています。

それでは、WPF プロジェクトを作成します。プロジェクト名は、FollowdMaxSize としています。

プロジェクト作成後、「参照の追加」で拡張にある System.Windows.Interactivity への参照を追加します。

続いて、MakViewModelBase を NuGet からインストールします。

続いて、プロジェクトに Behaviors フォルダを作成し、FollowedMaxSizeBehavior クラスを作成します。

using System;
using System.Windows;
using System.Windows.Interactivity;

namespace FollowdMaxSize.Behaviors
{
    public class FollowedMaxSizeBehavior : Behavior<FrameworkElement>
    {
        public static readonly DependencyProperty TargetMaxHeightProperty = DependencyProperty.Register(
            "TargetMaxHeight", typeof(Double), typeof(FollowedMaxSizeBehavior),
            new FrameworkPropertyMetadata(0.0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        public Double TargetMaxHeight
        {
            get { return (double)GetValue(TargetMaxHeightProperty); }
            set { SetValue(TargetMaxHeightProperty, value); }
        }

        public static readonly DependencyProperty TargetMaxWidthProperty = DependencyProperty.Register(
            "TargetMaxWidth", typeof(Double), typeof(FollowedMaxSizeBehavior),
            new FrameworkPropertyMetadata(0.0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        public double TargetMaxWidth
        {
            get { return (double)GetValue(TargetMaxWidthProperty); }
            set { SetValue(TargetMaxWidthProperty, value); }
        }

        public static readonly DependencyProperty TargetMinHeightProperty = DependencyProperty.Register(
            "TargetMinHeight", typeof(Double), typeof(FollowedMaxSizeBehavior),
            new FrameworkPropertyMetadata(0.0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        public Double TargetMinHeight
        {
            get { return (double)GetValue(TargetMinHeightProperty); }
            set { SetValue(TargetMinHeightProperty, value); }
        }

        public static readonly DependencyProperty TargetMinWidthProperty = DependencyProperty.Register(
            "TargetMinWidth", typeof(Double), typeof(FollowedMaxSizeBehavior),
            new FrameworkPropertyMetadata(0.0d, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
        public Double TargetMinWidth
        {
            get { return (double)GetValue(TargetMinWidthProperty); }
            set { SetValue(TargetMinWidthProperty, value); }
        }

        /// <summary>
        /// ビヘイビアーが AssociatedObject にアタッチされた後で呼び出されます。 
        /// </summary>
        protected override void OnAttached()
        {
            base.OnAttached();

            this.AssociatedObject.SizeChanged += OnWindowSizeChanged;
        }

        /// <summary>
        /// ビヘイビアーが AssociatedObject からデタッチされるとき、その前に呼び出されます。
        /// </summary>
        protected override void OnDetaching()
        {
            base.OnDetaching();

            this.AssociatedObject.SizeChanged -= OnWindowSizeChanged;
        }

        private void OnWindowSizeChanged(object sender, SizeChangedEventArgs e)
        {
            TargetMaxHeight += e.NewSize.Height - e.PreviousSize.Height;
            TargetMaxWidth += e.NewSize.Width - e.PreviousSize.Width;

            // 最大サイズが最小サイズより小さくなったら、最小サイズを変更する
            if (TargetMinHeight > TargetMaxHeight)
            {
                TargetMinHeight = TargetMaxHeight;
            }
            if (TargetMinWidth > TargetMaxWidth)
            {
                TargetMinWidth = TargetMaxWidth;
            }
        }
    }
}

(2016/08/03 追記 最大値が最小値より小さくなる場合、最小値を書き換えるように変更しました。TargetMinHeightProperty, TargetMinWidthProperty を追加し、OnWindowSizeChanged メソッドを変更。)

Window のサイズが変更されたら、TargetMaxHeight プロパティと TargetMaxWidth プロパティにバインドされた値を Window の変更されたサイズ分増減させます。

続いて、プロジェクトに ViewModels フォルダを作成し、MainViewModel クラスを作成します。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;

using MakCraft.ViewModels;

using FollowdMaxSize.Models;

namespace FollowdMaxSize.ViewModels
{
    class MainViewModel : ViewModelBase
    {
        public MainViewModel() { }

        // 情報表示部分の表示・非表示の切り替え指定
        public Visibility InfoVisibility
        {
            get { return _infoVisibility; }
            set { base.SetProperty(ref _infoVisibility, value); }
        }
        private Visibility _infoVisibility = Visibility.Collapsed;

        // 情報表示の内容
        public string InfoString
        {
            get { return _infoString; }
            set { base.SetProperty(ref _infoString, value); }
        }
        private string _infoString;

        // 一覧表示部分の表示・非表示の切り替え指定
        public Visibility CollectionVisibility
        {
            get { return _collectionVisibility; }
            set { SetProperty(ref _collectionVisibility, value); }
        }
        private Visibility _collectionVisibility = Visibility.Collapsed;

        // 一覧表示のソース
        public IReadOnlyCollection<TestModel> TestCollection
        {
            get { return _testCollection; }
            set { base.SetProperty(ref _testCollection, value); }
        }
        private IReadOnlyCollection<TestModel> _testCollection;

        // 情報表示のコマンドの設定
        private void getInfoExecute()
        {
            CollectionVisibility = Visibility.Collapsed;
            InfoVisibility = Visibility.Visible;
            InfoString = getInfo();
        }
        private ICommand _getInfoCommand;
        public ICommand GetInfoCommand
        {
            get
            {
                if (_getInfoCommand == null)
                {
                    _getInfoCommand = new RelayCommand(getInfoExecute);
                }
                return _getInfoCommand;
            }
        }

        // 一覧表示のコマンドの設定
        private void readExecute()
        {
            InfoVisibility = Visibility.Collapsed;
            CollectionVisibility = Visibility.Visible;
            TestCollection = createCollection();
        }
        private ICommand _readCommand;
        public ICommand ReadCommand
        {
            get
            {
                if (_readCommand == null)
                {
                    _readCommand = new RelayCommand(readExecute);
                }
                return _readCommand;
            }
        }

        private IReadOnlyCollection<TestModel> createCollection()
        {
            var result = new List<TestModel>();

            for (int i = 1; i < 100; ++i)
            {
                var name = string.Format("苗字{0:00000} 名前{0:00000}", i);
                var memo1 = 
                    string.Format("メモ1-0 {0:00000}, メモ1-1 {1:0000000},  メモ1-2 {2:000000000}",
                    i, i * 2, i * 3);
                var memo2 =
                    string.Format("メモ2-0 {0:0000000}, メモ2-1 {1:000000000},  メモ2-2 {2:00000000000}",
                    i * 4, i * 5, i * 6);
                result.Add(new TestModel { Id = i, Name = name, Memo1 = memo1, Memo2 = memo2 });
            }

            return result;
        }

        private string getInfo()
        {
            var infoList = new string[]
            {
                "abcdefghijklmnopqrstuvwxyz",
                "0123456789",
                "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん",
                "アイウエオ",
                "カキクケコ",
                "サシスセソ",
                "タチツテト",
                "ナニヌネノ",
                "ハヒフヘホ",
                "マミムメモ",
                "ヤユヨ",
                "ラリルレロ",
                "わを",
                "ん"
            };
            return string.Join(Environment.NewLine, infoList);
        }
    }
}

データグリッドに表示するためにバインドさせるプロパティの型ですが、この例では表示させるだけ & 簡便に済ますために単純に IReadOnlyCollection<TestModel> としています。

続いて、MainWindow.xaml ですが、最初に切り替え機能なしの例からです。

<Window x:Class="FollowdMaxSize.MainWindow"
        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:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:b="clr-namespace:FollowdMaxSize.Behaviors"
        xmlns:vm="clr-namespace:FollowdMaxSize.ViewModels"
        xmlns:local="clr-namespace:FollowdMaxSize"
        mc:Ignorable="d"
        Title="MainWindow" SizeToContent="WidthAndHeight">
    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>
    <i:Interaction.Behaviors>
        <b:FollowedMaxSizeBehavior
                    TargetMaxHeight="{Binding ElementName=dataGrid, Path=MaxHeight}"
                    TargetMaxWidth="{Binding ElementName=dataGrid, Path=MaxWidth}"
                    TargetMinHeight="{Binding ElementName=dataGrid, Path=MinHeight}"
                    TargetMinWidth="{Binding ElementName=dataGrid, Path=MinWidth}" />
    </i:Interaction.Behaviors>

    <StackPanel>
        <TextBlock
            Margin="10" FontSize="20" HorizontalAlignment="Center"
            Text="Window のサイズ変更に追随" />

        <StackPanel Margin="4" HorizontalAlignment="Right">
            <Button
                FontSize="14" Padding="14 6" Content="読み込み"
                Command="{Binding ReadCommand}"/>
        </StackPanel>

        <StackPanel Orientation="Horizontal" Background="Aqua">
            <DataGrid
                Name="dataGrid" AutoGenerateColumns="False" 
                MinHeight="90" MaxHeight="90"
                MinWidth="300" MaxWidth="300"
                IsReadOnly="True"
                ItemsSource="{Binding TestCollection}">
                <DataGrid.Resources>
                    <Style x:Key="wrap" TargetType="TextBlock">
                        <Setter Property="TextWrapping" Value="Wrap" />
                    </Style>
                </DataGrid.Resources>
                <DataGrid.Columns>
                    <DataGridTextColumn
                        Header="ID" Binding="{Binding Id}" />
                    <DataGridTextColumn
                        Header="名前" Binding="{Binding Name}" />
                    <DataGridTextColumn
                        Header="メモ1" Binding="{Binding Memo1}" Width="*"
                        ElementStyle="{StaticResource wrap}" />
                    <DataGridTextColumn
                        Header="メモ2" Binding="{Binding Memo2}" Width="*"
                        ElementStyle="{StaticResource wrap}" />
                </DataGrid.Columns>
            </DataGrid>

            <StackPanel>
                <TextBlock
                    Margin="10 2" FontSize="14" Text="acbdefg" />
                <TextBlock
                    Margin="10 2" FontSize="14" Text="0123456" />
            </StackPanel>
        </StackPanel>

        <StackPanel Margin="4" HorizontalAlignment="Right">
            <Button
                FontSize="14" Padding="14 6" Content="ほげ" />
        </StackPanel>

    </StackPanel>
</Window>

(2016/08/03 追記 最大値が最小値より小さくなる場合、最小値を書き換えるように変更しました。TargetMinHeight, TargetMinWidth のバインディング設定を追加。)

ウィンドウサイズの変更に追随するコントロール(上記例では DataGrid)に設定する横幅あるいは縦幅の最小値と最大値には同じ値を設定しておきます(ウィンドウのサイズ変更による対象コントロールの幅の増減は最大値を基に計算しているため、初期表示の幅を最大値と同じにしておく必要がある)。

次に、切り替え機能付きの MainWindow.xaml の例です。

<Window x:Class="FollowdMaxSize.MainWindow"
        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:i="http://schemas.microsoft.com/expression/2010/interactivity"
        xmlns:b="clr-namespace:FollowdMaxSize.Behaviors"
        xmlns:vm="clr-namespace:FollowdMaxSize.ViewModels"
        xmlns:local="clr-namespace:FollowdMaxSize"
        mc:Ignorable="d"
        Title="MainWindow" SizeToContent="WidthAndHeight">
    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>
    <i:Interaction.Behaviors>
        <b:FollowedMaxSizeBehavior
                    TargetMaxHeight="{Binding ElementName=dataGrid, Path=MaxHeight}"
                    TargetMaxWidth="{Binding ElementName=dataGrid, Path=MaxWidth}"
                    TargetMinHeight="{Binding ElementName=dataGrid, Path=MinHeight}"
                    TargetMinWidth="{Binding ElementName=dataGrid, Path=MinWidth}" />
        <b:FollowedMaxSizeBehavior
                    TargetMaxHeight="{Binding ElementName=infoText, Path=MaxHeight}"
                    TargetMaxWidth="{Binding ElementName=infoText, Path=MaxWidth}"
                    TargetMinHeight="{Binding ElementName=infoText, Path=MinHeight}"
                    TargetMinWidth="{Binding ElementName=infoText, Path=MinWidth}" />
    </i:Interaction.Behaviors>

    <StackPanel>
        <TextBlock
            Margin="10" FontSize="20" HorizontalAlignment="Center"
            Text="Window のサイズ変更に追随" />

        <StackPanel
            Margin="4" Orientation="Horizontal" HorizontalAlignment="Right">
            <Button
                FontSize="14" Padding="14 6" Content="情報表示"
                Command="{Binding GetInfoCommand}"/>
            <Button
                FontSize="14" Padding="14 6" Content="一覧表示"
                Command="{Binding ReadCommand}"/>
        </StackPanel>

        <StackPanel Visibility="{Binding InfoVisibility}">
            <TextBox
                Name="infoText" IsReadOnly="True" 
                MinHeight="90" MaxHeight="0"
                MinWidth="260" MaxWidth="260"
                TextWrapping="Wrap" VerticalScrollBarVisibility="Auto"
                Text="{Binding InfoString}" />
        </StackPanel>

        <StackPanel
            Visibility="{Binding CollectionVisibility}"
            Orientation="Horizontal" Background="Aqua">
            <DataGrid
                Name="dataGrid" AutoGenerateColumns="False" 
                MinHeight="90" MaxHeight="0"
                MinWidth="100" MaxWidth="120"
                IsReadOnly="True"
                ItemsSource="{Binding TestCollection}">
                <DataGrid.Resources>
                    <Style x:Key="wrap" TargetType="TextBlock">
                        <Setter Property="TextWrapping" Value="Wrap" />
                    </Style>
                </DataGrid.Resources>
                <DataGrid.Columns>
                    <DataGridTextColumn
                        Header="ID" Binding="{Binding Id}" />
                    <DataGridTextColumn
                        Header="名前" Binding="{Binding Name}" />
                    <DataGridTextColumn
                        Header="メモ1" Binding="{Binding Memo1}" Width="*"
                        ElementStyle="{StaticResource wrap}" />
                    <DataGridTextColumn
                        Header="メモ2" Binding="{Binding Memo2}" Width="*"
                        ElementStyle="{StaticResource wrap}" />
                </DataGrid.Columns>
            </DataGrid>

            <StackPanel>
                <TextBlock
                    Margin="10 2" FontSize="14" Text="acbdefg" />
                <TextBlock
                    Margin="10 2" FontSize="14" Text="0123456" />
            </StackPanel>
        </StackPanel>

        <StackPanel Margin="4" HorizontalAlignment="Right">
            <Button
                FontSize="14" Padding="14 6" Content="ほげ" />
        </StackPanel>

    </StackPanel>
</Window>

(2016/08/03 追記 最大値が最小値より小さくなる場合、最小値を書き換えるように変更しました。TargetMinHeight, TargetMinWidth のバインディング設定を追加。)

Visibility プロパティを Collapsed から Visible に変更して表示させる場合には、MaxHeight プロパティの値を 0 とし、 MaxWidth プロパティの値は、当該コントロールの位置の横幅が Window の Width プロパティの値と同じか小さくなるようにしておきます。


コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です