かずきのBlog@hatena

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

画面のステートによってGridViewとかのItemTemplateの幅を変えてみたい

Windows 8.1になったらサヨウナラしてしまうApplicationViewのValueプロパティにひと手間かければできますね。
こんなクラスを用意します。

using Microsoft.Practices.Prism.StoreApps;
using Windows.UI.Core;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml;

namespace PrismApp2.Common
{
    public class ApplicationViewHelper : BindableBase
    {
        public ApplicationViewHelper()
        {
        }

        // どっかUIスレッドから一度呼んでほしい
        public void Initialize()
        {
            Window.Current.SizeChanged += this.WindowSizeChanged;
        }

        // Windowのサイズが変わったらApplicationView.Valueを取得
        private void WindowSizeChanged(object sender, WindowSizeChangedEventArgs e)
        {
            this.Value = ApplicationView.Value;
        }

        private ApplicationViewState value;

        // 公開用プロパティ
        public ApplicationViewState Value
        {
            get { return this.value; }
            set { this.SetProperty(ref this.value, value); }
        }
    }
}

これをApp.xamlあたりのResourceに突っ込んでおきます。

<prism:MvvmAppBase 
    x:Class="PrismApp2.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:PrismApp2"
    xmlns:prism="using:Microsoft.Practices.Prism.StoreApps"
    xmlns:common="using:PrismApp2.Common">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>

                <!-- 
                    Styles that define common aspects of the platform look and feel
                    Required by Visual Studio project and item templates
                 -->
                <ResourceDictionary Source="Common/StandardStyles.xaml" />
            </ResourceDictionary.MergedDictionaries>

            <!-- Application-specific resources -->

            <x:String x:Key="AppName">PrismApp2</x:String>
            <common:ApplicationViewHelper x:Key="ApplicationView" />
        </ResourceDictionary>
    </Application.Resources>
</prism:MvvmAppBase>

つっこんだら、適当なタイミングでInitializeメソッドを呼び出します。ここではPrismのテンプレートを使っているので、OnInitializeメソッド内で呼び出しました。普通のテンプレート使うときは画面遷移する前あたりでいいかも。

using Microsoft.Practices.Prism.StoreApps;
using PrismApp2.Common;
using PrismApp2.ViewModels;
using PrismApp2.Views;
using Windows.ApplicationModel.Activation;

namespace PrismApp2
{
    sealed partial class App : MvvmAppBase
    {
        public App()
        {
            this.InitializeComponent();
        }

        protected override void OnLaunchApplication(LaunchActivatedEventArgs args)
        {
            NavigationService.Navigate("Main", null);
        }

        protected override void OnInitialize(IActivatedEventArgs args)
        {
            // 一度きりの初期化
            var helper = (ApplicationViewHelper)this.Resources["ApplicationView"];
            helper.Initialize();
            ViewModelLocator.Register(typeof(MainPage).ToString(), () => new MainPageViewModel(NavigationService));
        }
    }
}

あとはGridViewに適当なテンプレート(ここでは250x250のテンプレートをコピーして使いました)のWidthあたりにバインドして、以下のようなコンバータをかませれば状況に応じてサイズ変更ができます。

using System;
using System.Linq;
using Windows.UI.ViewManagement;
using Windows.UI.Xaml.Data;

namespace PrismApp2.Common
{
    public class ViewStateToValueConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            // カンマ区切りで横のとき縦のときのサイズを指定してる
            var size = parameter as string;
            var sizeList = size.Split(',').Select(s => int.Parse(s.Trim())).ToArray();

            // ApplicationViewStateに応じてサイズを返す
            var state = (ApplicationViewState)value;
            switch (state)
            {
                // 横画面系
                case ApplicationViewState.FullScreenLandscape:
                case ApplicationViewState.Filled:
                    return sizeList[0];
                // 縦画面系
                case ApplicationViewState.Snapped:
                case ApplicationViewState.FullScreenPortrait:
                    return sizeList[1];
                default:
                    return sizeList[0];
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotSupportedException();
        }
    }
}

あとはWidthにBindingすればOKです。

<DataTemplate x:Key="DataTemplate1">
    <Grid HorizontalAlignment="Left" Width="{Binding Value, ConverterParameter=250\,150, Converter={StaticResource ViewStateToValueConverter}, Source={StaticResource ApplicationView}}" Height="250">
        <Border Background="{StaticResource ListViewItemPlaceholderBackgroundThemeBrush}">
        	<Image Source="{Binding Image}" Stretch="UniformToFill" AutomationProperties.Name="{Binding Title}"/>
        </Border>
        <StackPanel VerticalAlignment="Bottom" Background="{StaticResource ListViewItemOverlayBackgroundThemeBrush}">
        	<TextBlock Text="{Binding Title}" Foreground="{StaticResource ListViewItemOverlayForegroundThemeBrush}" Style="{StaticResource TitleTextStyle}" Height="60" Margin="15,0,15,0"/>
        	<TextBlock Text="{Binding Subtitle}" Foreground="{StaticResource ListViewItemOverlaySecondaryForegroundThemeBrush}" Style="{StaticResource CaptionTextStyle}" TextWrapping="NoWrap" Margin="15,0,15,10"/>
        </StackPanel>
    </Grid>
</DataTemplate>

重要なのはDataTemplate直下のWidthへのBindingです。あとは無視しましょう。

実行結果

実行するとこんな感じになります。デフォルトでは250x250の正方形です。

f:id:okazuki:20130826002005p:plain

縦にくるんと回転させると縦長になります。
f:id:okazuki:20130826002120p:plain

どうもVSMでこのテンプレートの幅を変えるのができないのというご意見を頂いたので代替手段をば。