かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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.****プロパティを設定するという方法でした。