かずきのBlog@hatena

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

Universal Windows appを見てみた

先日、//build/のキーノートで個人的には一番ヒットだったUniversal Windows appが気になったので、早速Windows 8.1 Updateを入れてVisual Studio 2013 Update 2 RCを入れて試してみました。既に拡張機能からUniversal Windows appのサンプルコードもインストール可能なのでC#のサンプルを入れてみました。

実行してみると、XAMLとコードビハインド以外共通で同じ動きをするアプリが作られてます。

f:id:okazuki:20140403211256j:plain

どんな構造?

プロジェクトの新規作成のストアアプリの中にユニバーサルアプリというのがあるので、そこから作ってどんな構造なのか見てみました。

f:id:okazuki:20140403211945j:plain

ソリューションエクスプローラで重要そうだと思ったところを展開したのが以下の画像です。

f:id:okazuki:20140403212121j:plain

  • App12.Windows
    • Windows 8.1固有のコード
  • App12.WindowsPhone
    • Windows Phone 8.1固有のコード
  • App12.Shared
    • 共通コード

このSharedの部分がWindows store appとWindows Phone app共通のコード置き場みたいです。App.xamlというアプリケーションのエントリポイントが共通だなんて、結構大胆な作りになってるなと思いました。

足し算してみよう

ということで、足し算アプリを作って動きを見てみようと思います。

App12.Shared

Modelsというフォルダを作ってその中に以下のクラスを作りました。

  • BindableBaseクラス
    • INotifyPropertyChangedの実装
  • CalcModelクラス
    • 足し算するクラス

CalcModelは、足し算するだけの簡単なクラスです。

using System;
using System.Collections.Generic;
using System.Text;

namespace App12.Models
{
    public class CalcModel : BindableBase
    {
        private double lhs;

        public double Lhs
        {
            get { return this.lhs; }
            set { this.SetProperty(ref this.lhs, value); }
        }

        private double rhs;

        public double Rhs
        {
            get { return this.rhs; }
            set { this.SetProperty(ref this.rhs, value); }
        }

        private double answer;

        public double Answer
        {
            get { return this.answer; }
            set { this.SetProperty(ref this.answer, value); }
        }

        public void ExecuteSum()
        {
            this.Answer = this.Lhs + this.Rhs;
        }
    }
}

ここからが面白いところで、UserControlやテンプレートコントロールもSharedの中に作れるんです。なので、CalcViewという名前のUserControlをViewsフォルダのなかに作ってみました。

f:id:okazuki:20140403214052j:plain

中身は、左辺値右辺値入れて計算ボタンがあって答えを表示するTextBlockがあるだけのシンプルなものですね。先ほどのCalcModelをDataContextにXAMLから設定しちゃいました。

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App12.Views"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:Models="using:App12.Models"
    x:Class="App12.Views.CalcView"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400" 
    d:DataContext="{d:DesignInstance Models:CalcModel, IsDesignTimeCreatable=True}">
    <UserControl.DataContext>
        <Models:CalcModel />
    </UserControl.DataContext>
    <UserControl.Resources>
        <Style x:Key="TitleStyle" TargetType="TextBlock">
            <Setter Property="FontSize" Value="18" />
            <Setter Property="HorizontalAlignment" Value="Right" />
            <Setter Property="VerticalAlignment" Value="Center" />
        </Style>
        <Style x:Key="AnswerStyle" TargetType="TextBlock">
            <Setter Property="FontSize" Value="24" />
        </Style>
    </UserControl.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="10"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="10"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="10"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="10"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Button Grid.ColumnSpan="3" Content="計算" Grid.Row="4" HorizontalAlignment="Stretch" FontFamily="Global User Interface" Click="Button_Click"/>
        <TextBox Grid.Column="2" TextWrapping="Wrap" Text="{Binding Lhs, Mode=TwoWay}" HorizontalContentAlignment="Left" TextAlignment="Right"/>
        <TextBox Grid.Column="2" Grid.Row="2" TextWrapping="Wrap" Text="{Binding Rhs, Mode=TwoWay}" FontFamily="Global User Interface" TextAlignment="Right"/>
        <TextBlock TextWrapping="Wrap" Text="左辺値:" Style="{StaticResource TitleStyle}"/>
        <TextBlock Grid.Row="2" TextWrapping="Wrap" Text="右辺値:" Style="{StaticResource TitleStyle}" />
        <TextBlock Grid.ColumnSpan="3" Grid.Row="6" TextWrapping="Wrap" Text="{Binding Answer}" HorizontalAlignment="Center" FontSize="24" Style="{StaticResource TitleStyle}"/>
    </Grid>
</UserControl>

注意すべきは、Windows store appにあるTitleTextBlockStyleとかみたいな便利なやつは電話では使えないので使わないようにしましょう。 使うと、電話のデザイナに、このUserControlを置いた瞬間に死にます。

個々のプロジェクトに貼る

あとは、電話とストアの両方に置きました。

実行

動くのか?と思いながら両方のプロジェクトを起動してみました。

f:id:okazuki:20140403215649j:plain

f:id:okazuki:20140403215702j:plain

ちゃんと動いてます。現実問題として、電話の画面とパソコンの画面とで大きさが異なるため同じ見た目のUserControlを置くのか?と言われると微妙かもですが、そこはWindowのサイズをハンドリングしてVisualStateで適切な見た目に切り替えるUserControlとかを作れば、同一のやつでもいけるかもしれません。

まとめ

PCLでロジックの共有はできてたけど、このUniversal Windows appは、Windows Phone 8.1がWindows Phone RuntimeになってWindows Runtimeと、ほぼほぼ互換になったため、画面部品まで共有できるという恐ろしい事態になりました。これはあつい!!!

おまけ

従来のWindows PhoneのSLベースのやつはWindows Phone Silverlight 8.1みたいな名前でレガシー的な扱いになりました。

ソースコード

見る人がいるかわかりませんが、UPしておきます。