かずきのBlog@hatena

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

Windowsストアみたいなメニューの出し方(未完全)

ちょっと未完全ですが、それっぽい感じに近づいてきたのでここらへんで一度放流。

Windows ストアのメニュー

新しいWindowsストアが、アプリバーを無くしてきました…。まるでメニューみたいなものが画面上部についてます。初期のWindowsストアアプリの審査では、絶対に//reject/されてたような感じです。

f:id:okazuki:20140519220717p:plain

出し方

Flyoutで出すのが一番楽です。Flyoutは、FlyoutPresenterStyleを設定することで、Flyoutの見た目をカスタマイズできます。デフォルトでは最大の幅とかが設定されてそうなので、設定を解除するのと、Templateを差し換えて枠とかをとっぱらいます。

<Style x:Key="FlyoutPresenterStyle" TargetType="FlyoutPresenter">
    <Setter Property="MaxWidth" Value="NaN" />
    <Setter Property="MaxHeight" Value="NaN" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="FlyoutPresenter">
                <ContentPresenter />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

このFlyoutPresenterのStyleを設定したFlyoutを作ります。こいつがメニューっぽい感じになります。ポイントは中身にFlipViewを置くことです。こいつは可能な限りひろがろうとするので、何もしないと横幅いっぱい、縦幅いっぱいになります。今回は、これに高さを制限することで、いい感じの高さで、幅が画面いっぱいになるようにしました。 FlipViewの中には、GridViewを置いてメニューっぽくアイテムを並べています。

<Flyout 
    x:Key="Menu1Flyout" 
    FlyoutPresenterStyle="{StaticResource FlyoutPresenterStyle}" 
    Placement="Bottom">
    <Grid Height="175" Background="White">
        <FlipView>
            <GridView>
                <GridViewItem Content="Menu1" />
                <GridViewItem Content="Menu2" />
                <GridViewItem Content="Menu3" />
                <GridViewItem Content="Menu4" />
                <GridViewItem Content="Menu5" />
                <GridViewItem Content="Menu6" />
                <GridViewItem Content="Menu7" />
                <GridViewItem Content="Menu8" />
                <GridViewItem Content="Menu9" />
                <GridViewItem Content="Menu10" />
            </GridView>
        </FlipView>
    </Grid>
</Flyout>

ボタン

メニューっぽい見た目のボタンはTextBlockButtonStyleに少し手を入れて、Contentを真ん中に配置できるようにしました。

<Style x:Key="StoreMenuLikeButtonStyle" TargetType="Button">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="ButtonBase">
                <Border>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="Normal"/>
                            <VisualState x:Name="PointerOver">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ApplicationPointerOverForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Pressed">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ApplicationPressedForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Disabled">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ApplicationPressedForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="FocusStates">
                            <VisualState x:Name="Focused">
                                <Storyboard>
                                    <DoubleAnimation Duration="0" To="1" Storyboard.TargetName="FocusVisualWhite" Storyboard.TargetProperty="Opacity"/>
                                    <DoubleAnimation Duration="0" To="1" Storyboard.TargetName="FocusVisualBlack" Storyboard.TargetProperty="Opacity"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unfocused"/>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="CheckStates">
                            <VisualState x:Name="Checked"/>
                            <VisualState x:Name="Unchecked">
                                <Storyboard>
                                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Text" Storyboard.TargetProperty="Foreground">
                                        <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ApplicationSecondaryForegroundThemeBrush}"/>
                                    </ObjectAnimationUsingKeyFrames>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Indeterminate"/>
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <Grid Background="Transparent">
                        <ContentPresenter 
                            x:Name="Text" 
                            Content="{TemplateBinding Content}" 
                            HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                            VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
                        <Rectangle
                            x:Name="FocusVisualWhite"
                            IsHitTestVisible="False"
                            Stroke="{ThemeResource FocusVisualWhiteStrokeThemeBrush}"
                            StrokeEndLineCap="Square"
                            StrokeDashArray="1,1"
                            Opacity="0"
                            StrokeDashOffset="1.5"/>
                        <Rectangle
                            x:Name="FocusVisualBlack"
                            IsHitTestVisible="False"
                            Stroke="{ThemeResource FocusVisualBlackStrokeThemeBrush}"
                            StrokeEndLineCap="Square"
                            StrokeDashArray="1,1"
                            Opacity="0"
                            StrokeDashOffset="0.5"/>
                    </Grid>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

ページ

ページの上側にStackPanelで横並びにButtonを置きます。そしてStyleに先ほど作ったStoreMenuLikeButtonStyleを適用して、FlyoutにMenu1Flyoutを適用します。

<StackPanel Orientation="Horizontal" Background="#FF008706">
    <Button 
        Content="Menu1" 
        Style="{StaticResource StoreMenuLikeButtonStyle}" 
        Width="150" 
        VerticalAlignment="Stretch" Flyout="{StaticResource Menu1Flyout}"/>
    <Button 
        Content="Menu2" 
        Style="{StaticResource StoreMenuLikeButtonStyle}" 
        Width="150" 
        VerticalAlignment="Stretch"/>
    <Button 
        Content="Menu3" 
        Style="{StaticResource StoreMenuLikeButtonStyle}" 
        Width="150" 
        VerticalAlignment="Stretch"/>
</StackPanel>

これで実行してMenu1をタップすると、それっぽいメニューが出てきます。

f:id:okazuki:20140519222344p:plain

f:id:okazuki:20140519222603p:plain

未完成部分

ストアは、マウスオーバーしたときと、メニューが開いたときにボタンにしるしがつきます。こいつを楽につける方法が思いつかないのが今の課題です。あとFlyoutでできる微妙な余白も取り除きたい…。

ボタン風カスタムコントロール+Popupでやると本物により近づけそうだけど、ちょっと辛い…。