かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Universal Windows Platform appでレスポンシブ対応

Windows Insider Preview 10071 + VS2015 RC時点の情報です

悪夢のようなレスポンシブ対応。UWP appでは色々な画面サイズに対応するのが望ましいです。UWP appのレスポンシブ対応ですが大きく分けて2つのポイントがあると思います。

  • SplitViewの表示切替
  • コンテンツの並び替え

SplitViewの表示切替はSplitView使ってる場合に限るので、基本はコンテンツの並び替えですね。

骨組の作成

とりあえず、以下のような骨組を作ります。SplitViewに適当なメニューを表示するようにして、ToggleButtonでPaneが開閉するようにしています。今回はコンテンツを150×150のBorder4個にしました。現段階では、RelativePanelに置いただけなので重なってます。

<Page
    x:Class="App23.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App23"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">

    <RelativePanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <!-- ヘッダー -->
        <ToggleButton x:Name="ToggleButtonSplitView"
                      Content="&#xE700;"
                      FontFamily="{ThemeResource SymbolThemeFontFamily}"
                      Background="Transparent"
                      Width="48"
                      Height="40" />
        <TextBlock x:Name="TextBlockTitle"
                   Text="My App"
                   Style="{ThemeResource TitleTextBlockStyle}"
                   Margin="5,0,0,0"
                   RelativePanel.RightOf="ToggleButtonSplitView"
                   RelativePanel.AlignVerticalCenterWith="ToggleButtonSplitView" />

        <SplitView x:Name="SplitView"
                   IsPaneOpen="{Binding IsChecked, ElementName=ToggleButtonSplitView, Mode=TwoWay}"
                   RelativePanel.Below="ToggleButtonSplitView"
                   RelativePanel.AlignLeftWithPanel="True"
                   RelativePanel.AlignRightWithPanel="True"
                   RelativePanel.AlignBottomWithPanel="True">
            <SplitView.Pane>
                <!-- メニュー -->
                <ListView x:Name="ListViewPane" 
                          SelectionMode="None">
                    <ListView.ItemContainerStyle>
                        <Style TargetType="ListViewItem">
                            <Setter Property="Margin" Value="0" />
                            <Setter Property="Padding" Value="0" />
                        </Style>
                    </ListView.ItemContainerStyle>
                    <ListViewItem>
                        <StackPanel Orientation="Horizontal">
                            <SymbolIcon Symbol="Import" Width="48" />
                            <TextBlock Text="AAAAAAAAAA" Style="{ThemeResource BodyTextBlockStyle}" />
                        </StackPanel>
                    </ListViewItem>
                    <ListViewItem>
                        <StackPanel Orientation="Horizontal">
                            <SymbolIcon Symbol="Delete" Width="48" />
                            <TextBlock Text="AAAAAAAAAA" Style="{ThemeResource BodyTextBlockStyle}" />
                        </StackPanel>
                    </ListViewItem>
                    <ListViewItem>
                        <StackPanel Orientation="Horizontal">
                            <SymbolIcon Symbol="Save" Width="48" />
                            <TextBlock Text="AAAAAAAAAA" Style="{ThemeResource BodyTextBlockStyle}" />
                        </StackPanel>
                    </ListViewItem>
                </ListView>
            </SplitView.Pane>
            <!-- コンテンツ部 -->
            <RelativePanel>
                <Border x:Name="Border1"
                        Width="150"
                        Height="150"
                        Background="Red" />
                <Border x:Name="Border2"
                        Width="150"
                        Height="150"
                        Background="Beige" />
                <Border x:Name="Border3"
                        Width="150"
                        Height="150"
                        Background="Black" />
                <Border x:Name="Border4"
                        Width="150"
                        Height="150"
                        Background="Cyan" />
            </RelativePanel>
        </SplitView>
    </RelativePanel>
</Page>

f:id:okazuki:20150510122346p:plain

レスポンシブ対応

これをレスポンシブ対応にします。レスポンシブ対応にするにはVisualStateManagerを使います。RelativePanelの設定とSplitViewの表示を幅ごとに設定していきます。

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState>
            <!-- 一番幅が広いとき -->
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="1025" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="SplitView.DisplayMode" Value="CompactInline" />
                <Setter Target="SplitView.IsPaneOpen" Value="True" />
                <Setter Target="Border2.(RelativePanel.RightOf)" Value="Border1" />
                <Setter Target="Border3.(RelativePanel.RightOf)" Value="Border2" />
                <Setter Target="Border4.(RelativePanel.RightOf)" Value="Border3" />
            </VisualState.Setters>
        </VisualState>
        <!-- 中ぐらい -->
        <VisualState>
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="721" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="SplitView.DisplayMode" Value="CompactOverlay" />
                <Setter Target="SplitView.IsPaneOpen" Value="False" />
                <Setter Target="Border2.(RelativePanel.RightOf)" Value="Border1" />
                <Setter Target="Border3.(RelativePanel.RightOf)" Value="Border2" />
                <Setter Target="Border4.(RelativePanel.Below)" Value="Border1" />
            </VisualState.Setters>
        </VisualState>
        <!-- 狭い -->
        <VisualState>
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="321" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="SplitView.DisplayMode" Value="CompactOverlay" />
                <Setter Target="SplitView.IsPaneOpen" Value="False" />
                <Setter Target="Border2.(RelativePanel.RightOf)" Value="Border1" />
                <Setter Target="Border3.(RelativePanel.Below)" Value="Border1" />
                <Setter Target="Border4.(RelativePanel.Below)" Value="Border1" />
                <Setter Target="Border4.(RelativePanel.RightOf)" Value="Border3" />
            </VisualState.Setters>
        </VisualState>
        <!-- 超狭い -->
        <VisualState>
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="0" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="Border2.(RelativePanel.Below)" Value="Border1" />
                <Setter Target="Border3.(RelativePanel.Below)" Value="Border2" />
                <Setter Target="Border4.(RelativePanel.Below)" Value="Border3" />
            </VisualState.Setters>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

これで実行すると以下のようになります。

幅が広いとき

f:id:okazuki:20150510122612p:plain

普通のとき

f:id:okazuki:20150510122700p:plain f:id:okazuki:20150510122728p:plain

ちょっと狭いとき

f:id:okazuki:20150510122817p:plain

超狭いとき

f:id:okazuki:20150510122906p:plain

まとめ

RelativePanelにレスポンシブ対応したいコンテンツをとりあえず置いておいて、VSMからRelativePanel.****プロパティを設定するという方法でした。