SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

UWP Community Toolkitを使ってみた

先日MSが、こんなライブラリを公開してました。

https://blogs.windows.com/buildingapps/2016/08/17/introducing-the-uwp-community-toolkit/#ezOozvSa1jfh3Er2.97

てことで、さっそく使ってみました。

概要・UWP Community Toolkitとは

MS製のUWP向けライブラリです。
アプリ作成の上でのフレームワークなどではなく、純粋なコントロール類や便利な各種ヘルパークラスなどが主な内容となっています。 各種機能も、数行のコードを書くだけで簡単に利用できるように作られていて、お気軽に使えます。
また、このライブラリを使うことで、アプリ全体をこう書かなきゃみたいな強制が無いので、その点でも非常に取り回しのよいライブラリだと思います。

ちなみに、MSはこのライブラリへのフィードバックを今後のWin10のSDKへと反映していくつもり、とのことです。
このライブラリで特に有用で安定してきた機能は、将来SDKに標準で取り込まれるかもしれません。

公式のサンプルアプリ

このライブラリでできることを簡単に体験できるよう、サンプルアプリが公開されてます。
https://www.microsoft.com/ja-jp/store/p/uwp-community-toolkit-sample-app/9nblggh4tlcq

まずは、ストアから以下のアプリをインストールして試してみるとよいかと。
f:id:minami_SC:20160924123730p:plain

UWP Community Toolkitでできることが一通りサンプルとして提供されています。
f:id:minami_SC:20160924123739p:plain

このアプリからコードのコピペなどもできるようになっています。
ライブラリの動作を見てみるだけでなく、開発時にコードスニペットの取得元、みたいな位置づけとしても便利かもしれません。
f:id:minami_SC:20160924123755p:plain

ロードマップ

今後のアップデートについては、以下のように書かれています。
https://github.com/Microsoft/UWPCommunityToolkit/issues?q=is%3Aopen+is%3Aissue+milestone%3Av1.1
https://github.com/Microsoft/UWPCommunityToolkit/milestones
月に一回くらいのアップデートを計画しているようですね。

準備

UWP Community Toolkitを使うには、以下の環境が必要です。

VS2015をUpdate3にしてない場合は、まずアップデートしておきましょう。

ライブラリのインストール

インストールはNugetから行えるようになっています。

UWP Community Toolkitでは、機能ごとに複数のアセンブリに分かれています。
アセンブリ一覧は↓のページにまとまっています。
https://developer.microsoft.com/en-us/windows/uwp-community-toolkit/nugetpackages

自分が必要な機能のアセンブリを適宜取得していく、という感じで使います。

これらのアセンブリは、NugetのGUIからMicrosoft.Toolkit.UWPで検索すると出てきます。
f:id:minami_SC:20160924123807p:plain

ここから探せば、目的のものをすぐ見つけられるかと。

使ってみる

ということで、さっそく使ってみます。

細かいことは、公式リポジトリのリンクからリファレンスを読むとよいですが、 ここでは特に便利そうと感じた、主な機能/コントロールの使い方をまとめてみます。

Control類

まずは、各種コントロール類から。
ハンバーガーメニューを作るコントロールをはじめ、sdkに標準で入れておいてほしいような便利なコントロールが多数用意されています。

この辺のコントロール類を使うときは、Microsoft.Toolkit.Uwp.UI.Controlsというパッケージをインストールします。
Nugetパッケージマネージャ コンソールから、以下のコマンドでインストールできます。

Install-Package Microsoft.Toolkit.Uwp.UI.Controls
HeaderedTextBlock

項目のタイトルを表示することができるようになっているテキストブロックです。 これはコードだけ見ればなんとなく使えるかと。

        <StackPanel>
            <controls:HeaderedTextBlock Margin="5"
                                        Header="Name"
                                        Text="UWP Toolkit" />
            <controls:HeaderedTextBlock Margin="5"
                                        Header="Title"
                                        Orientation="Horizontal"
                                        Text="ここに内容を書く" />
        </StackPanel>

f:id:minami_SC:20160924124333p:plain

プロパティ名 内容
Header 項目のヘッダーを設定
Text 項目の本文を設定
Orientation ヘッダーと本文の並ぶ方向を設定
RangeSelector

普通のスライダーと違い、下限値/上限値の二つを設定できるようになっているものです。
こういうコントロール、確かに微妙にほしくなる時ありますよね。

            <controls:RangeSelector Maximum="100"
                                    Minimum="10"
                                    RangeMax="50"
                                    RangeMin="30" />

f:id:minami_SC:20160924124341p:plain

プロパティ名 内容
Maximum 現在の設定値(上限側)を設定
Minimum 現在の設定値(下限側)を設定
RangeMax 設定可能な値の最大値を設定(スライダーの右端の値)
RangeMin 設定可能な値の最小値を設定(スライダーの左端の値)
AdaptiveGrid

これは結構使いどころの多そうなコントロール
画像のサムネイル一覧とか表示するときに便利そうです。

こんな風に、各グリッドの幅がDesiredWidth以上になるように保った上で、入るだけ列を作ってAdaptiveGridの幅いっぱいに埋める、って感じの動作です。

f:id:minami_SC:20160924124404p:plain:w300
f:id:minami_SC:20160924124420p:plain:w600

MainPage.xaml

    <Page.Resources>
        <DataTemplate x:Key="PhotosTemplate">
            <Grid Background="White"
                  BorderBrush="Black"
                  BorderThickness="1">
                <Image HorizontalAlignment="Center"
                       VerticalAlignment="Center"
                       Source="{Binding}"
                       Stretch="UniformToFill" />
            </Grid>
        </DataTemplate>
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:AdaptiveGridView Margin="5"
                                   DesiredWidth="300"
                                   ItemHeight="200"
                                   ItemTemplate="{StaticResource PhotosTemplate}"
                                   ItemsSource="{x:Bind ImageList}" />

    </Grid>

MainPage.xaml.cs

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            this.ImageList = new List<string>()
            {
                "Images/1.JPG",
                "Images/2.JPG",
                "Images/3.JPG",
                "Images/4.JPG",
                "Images/5.JPG",
                "Images/6.JPG",
            };
        }

        public IList<string> ImageList { get; set; }
    }
プロパティ名 内容
DesiredWidth 各グリッドの最小幅のサイズを設定
ItemHeight 各グリッドの高さを設定
OneRowModeEnabled trueにすると、1行だけ表示するようになります。
ItemsSource グリッドに表示する要素のリストを設定
ItemTemplate グリッドに表示する各アイテムのテンプレートを設定
ItemClickCommand 各要素をクリックした際に呼び出されるコマンドを設定
イベント名 内容
ItemClick 各要素をクリックした際に発生するイベント
SlidableListItem

横フリックで要素の選択などができるコントロールです。
ListBoxやListViewなどのItemTemplateとして使い、リストの各要素のテンプレートとして使うことを想定されたコントロールです。
スマホ向けのUIとかで、結構便利に使えそうですね。

MainPage.xaml

    <Page.Resources>
        <DataTemplate x:Key="EmailsItemTemplate">
            <controls:SlidableListItem MinWidth="300"
                                       MaxWidth="800"
                                       HorizontalAlignment="Stretch"
                                       LeftIcon="Message"
                                       LeftLabel="ダイアログを表示"
                                       LeftCommandRequested="SlidableListItem_LeftCommandActivated"
                                       RightIcon="Delete"
                                       RightLabel="削除します"
                                       RightCommandRequested="SlidableListItem_RightCommandActivated"
                                       MouseSlidingEnabled="True">
                <Grid Height="110" Background="WhiteSmoke">
                    <TextBlock Grid.Column="1"
                               Margin="12"
                               VerticalAlignment="Center"
                               FontSize="16"
                               FontWeight="Light"
                               Text="{Binding}"
                               TextWrapping="NoWrap" />
                </Grid>
            </controls:SlidableListItem>
        </DataTemplate>
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <ListView x:Name="listView"
                  HorizontalAlignment="Center"
                  IsItemClickEnabled="False"
                  ItemTemplate="{StaticResource EmailsItemTemplate}"
                  ItemsSource="{x:Bind ImageList, Mode=OneWay}"
                  SelectionMode="None">
            <ListView.ItemContainerStyle>
                <Style TargetType="ListViewItem">
                    <Setter Property="HorizontalContentAlignment" Value="Stretch" />
                    <Setter Property="Margin" Value="0,1" />
                </Style>
            </ListView.ItemContainerStyle>
        </ListView>
    </Grid>

MainPage.xaml.cs

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            this.ImageList = new ObservableCollection<string>()
            {
                "Item1",
                "Item2",
                "Item3",
                "Item4",
                "Item5",
                "Item6",
            };
        }

        public ObservableCollection<string> ImageList { get; set; }

        private async void SlidableListItem_LeftCommandActivated(object sender, EventArgs e)
        {
            var item = (sender as SlidableListItem).DataContext as string;

            var dlg = new Windows.UI.Popups.MessageDialog(item);
            await dlg.ShowAsync();
        }

        private void SlidableListItem_RightCommandActivated(object sender, EventArgs e)
        {
            ImageList.Remove((sender as SlidableListItem).DataContext as string);
        }
    }

f:id:minami_SC:20160924124459p:plain:w400

HamburgerMenu

名前の通り、ハンバーガーメニューを作るためのコントロール。 このコントロールを使うと、簡単にハンバーガーメニューを伴ったUIを実装できます。

ここでのサンプルでは、選択した画像を表示しているだけですが、HamburgerMenu内部にFrameコントロールを配置してページ切替できるようにすると、よく見かける感じのUIになるかと思います。

MainPage.xaml

    <Page.Resources>
        <DataTemplate x:Key="DefaultTemplate" x:DataType="local:MenuItem">
            <Grid Width="240" Height="48">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="48" />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <Image Margin="3"
                       Source="{x:Bind ImagePath}"
                       Stretch="UniformToFill" />
                <TextBlock Grid.Column="1"
                           VerticalAlignment="Center"
                           FontSize="16"
                           Foreground="White"
                           Text="{x:Bind Title, Mode=OneWay}" />
            </Grid>
        </DataTemplate>
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <controls:HamburgerMenu x:Name="hamburgerMenuControl"
                                Foreground="White"
                                ItemClick="OnMenuItemClick"
                                ItemTemplate="{StaticResource DefaultTemplate}"
                                ItemsSource="{x:Bind List}"
                                OptionsItemTemplate="{StaticResource DefaultTemplate}"
                                PaneBackground="Black">
            <Image x:Name="imgSelected" Margin="10" />
        </controls:HamburgerMenu>
    </Grid>

MainPage.xaml.cs

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            this.List = new ObservableCollection<MenuItem>()
            {
                new MenuItem("Sample1", "Images/1.JPG"),
                new MenuItem("Sample2", "Images/2.JPG"),
                new MenuItem("Sample3", "Images/3.JPG"),
                new MenuItem("Sample4", "Images/4.JPG"),
                new MenuItem("Sample5", "Images/5.JPG"),
                new MenuItem("Sample6", "Images/6.JPG"),
            };
        }

        public ObservableCollection<MenuItem> List { get; set; }

        private void OnMenuItemClick(object sender, ItemClickEventArgs e)
        {
            var clicked = e.ClickedItem as MenuItem;
            this.imgSelected.Source = new BitmapImage(new Uri($"ms-appx://UwpToolkitTest/{clicked.ImagePath}"));
        }
    }

    public class MenuItem
    {
        public string Title { get; set; }
        public string ImagePath { get; set; }

        public MenuItem(string title, string path)
        {
            this.Title = title;
            this.ImagePath = path;
        }
    }

f:id:minami_SC:20160924124511p:plain

RadialGauge

これは、使いどころを選びそうなコントロールですね。
車の速度計みたいな表示のコントロールです。

        <StackPanel>
            <controls:RadialGauge
                  Grid.Column="1"
                  Value="{Binding ElementName=slider, Path=Value}"
                  Minimum="0"
                  Maximum="180"
                  TickSpacing="20"
                  ScaleWidth="26"
                  Unit="Units"
                  TickBrush="Gainsboro"
                  ScaleTickBrush="{ThemeResource ApplicationPageBackgroundThemeBrush}"
                  UnitBrush="Black"
                  ValueBrush="Black" 
                  NeedleWidth="5" 
                  TickLength="18" />
            <Slider x:Name="slider" Margin="10" Minimum="0" Maximum="180"/>
        </StackPanel>

f:id:minami_SC:20160924124529p:plain:w250

Animations

アニメーション効果を簡単に加えるためのクラス類です。
コードビハインドから呼ぶことも、xaml上からビヘイビアとして設定することもできます。

それぞれの使い方を順番に見ていきます。

コードビハインドからのアニメーション

BlurOffsetFadeRotateScaleといったアニメーション用の拡張メソッドが用意されています。

using Microsoft.Toolkit.Uwp.UI.Animations;という名前空間の設定を加えると、 以下のように、FrameworkElementやUIElementのようなUI要素に対して、これらのアニメーション用の拡張メソッドが呼び出せるようになります。

MainPage.xaml

        <Border x:Name="border"
                Margin="10"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Background="LightGray"
                BorderBrush="Black"
                BorderThickness="1"
                Padding="10">
            <FontIcon FontFamily="Segoe MDL2 Assets"
                      FontSize="128"
                      Glyph="&#xE170;" />
        </Border>
        <Button Margin="10,165,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Content="Button" Click="Button_Click" />

MainPage.xaml.cs

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.border.Blur(value: 10, duration: 100, delay: 100)
                       .StartAsync();
        }
メソッドチェーンでつなぐ

以下のようにメソッドチェーンで複数のアニメーションをつなぎ、順次実行することもできます。

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.border.Blur(value: 10, duration: 500)
                       .Offset(offsetX: 50, offsetY: 100, delay: 500)
                       .Rotate(value: 60, duration: 500, delay: 1000)
                       .StartAsync();
        }
ビヘイビアを用いたアニメーション

以下のように、ビヘイビアとしてこれらのアニメーションを定義することもできます。

        <Border Margin="10"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Background="LightGray"
                BorderBrush="Black"
                BorderThickness="1"
                Padding="10">
            <interactivity:Interaction.Behaviors>
                <behaviors:Blur x:Name="BlurBehavior"
                                AutomaticallyStart="False"
                                Delay="100"
                                Duration="1000"
                                Value="10" />
            </interactivity:Interaction.Behaviors>
            <FontIcon FontFamily="Segoe MDL2 Assets"
                      FontSize="128"
                      Glyph="&#xE170;" />
        </Border>
        <Button Margin="10,165,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Content="Button">
            <interactivity:Interaction.Behaviors>
                <core:EventTriggerBehavior EventName="Click">
                    <core:CallMethodAction MethodName="StartAnimation" TargetObject="{Binding ElementName=BlurBehavior}" />
                </core:EventTriggerBehavior>
            </interactivity:Interaction.Behaviors>
        </Button>

各種Helperなど

コンバーターやちょっとしたユーティリティクラスなども用意されています。

color

RGBではなく、HSV表色系で色を表す、HsvColor構造体などが用意されています。

また、HSV値や16進数文字列のRGB値などからColor構造体を作るためのヘルパークラスなどなど、色を扱う上で便利な機能が用意されてます。

            var hsv = new HsvColor();
            hsv.H = 180;

            var col1 = ColorHelper.FromHsv(180, 0.5, 0.5);
            var col2 = ColorHelper.ToColor("Red");
            var col3 = ColorHelper.ToColor("#FF00FF");
コンバーター

以下のようなコンバーターが用意されています。

使用する場合は、xamlで以下のような名前空間の設定をしておきます。

xmlns:Converters="using:Microsoft.Toolkit.Uwp.UI.Converters"
名前 内容
BoolToVisibilityConverter bool値をVisibilityに変換
CollectionVisibilityConverter コレクションの要素が0個の場合、Visibility.Collapsedに変換するコンバーター
StringFormatConverter ConverterParameterで指定した書式で文字列をフォーマットして返すコンバーター
StringVisibilityConverter 文字列がnullまたは空文字の場合に、Visibility.Collapsedに変換するコンバーター

UWPではBooleanToVisibilityConverterが標準ではなくて何かと不便ですが、そんな時にはここで用意されているBoolToVisibilityConverterが使えます。

外部サービスとの連携

BingやTwitter, Facebookといった、定番どころのWebServiceとの連携を簡単に行うための各種ヘルパークラスも用意されています。

こんな風に簡単にBing検索結果を利用したりすることができます。 Microsoft.Toolkit.Uwp.ServicesというパッケージをNugetでインストールして、以下のように使います。

<Page x:Class="UwpToolkitTest.MainPage"
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:bing="using:Microsoft.Toolkit.Uwp.Services.Bing"
      xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
      xmlns:core="using:Microsoft.Xaml.Interactions.Core"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:local="using:UwpToolkitTest"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      mc:Ignorable="d">
    <Page.Resources>
        <DataTemplate x:Key="SearchResultTemplate" x:DataType="bing:BingResult">
            <Grid Margin="0,5,10,5">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition Width="Auto" />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <TextBlock Grid.Row="0"
                           Grid.Column="0"
                           FontWeight="Bold"
                           Text="{x:Bind Title}"
                           TextTrimming="CharacterEllipsis" />
                <TextBlock Grid.Row="0"
                           Grid.Column="1"
                           HorizontalAlignment="Right"
                           Text="{x:Bind Published}" />
                <TextBlock Grid.Row="1"
                           Grid.Column="0"
                           Grid.ColumnSpan="2"
                           Text="{x:Bind Summary}"
                           TextWrapping="Wrap" />
                <HyperlinkButton Grid.Row="2"
                                 Grid.Column="0"
                                 Grid.ColumnSpan="2"
                                 Content="{x:Bind Link}"
                                 NavigateUri="{x:Bind Link}" />
            </Grid>
        </DataTemplate>
    </Page.Resources>
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition />
        </Grid.RowDefinitions>

        <TextBox x:Name="txtKerword"
                 Width="250"
                 Margin="10"
                 HorizontalAlignment="Left"
                 VerticalAlignment="Top"
                 Text="Windows 10"
                 TextWrapping="Wrap" />
        <Button Margin="265,10,0,0"
                HorizontalAlignment="Left"
                VerticalAlignment="Top"
                Click="Button_Click"
                Content="検索" />
        <ListView x:Name="lstResult"
                  Grid.Row="1"
                  Margin="10"
                  ItemTemplate="{StaticResource SearchResultTemplate}" />
    </Grid>
</Page>
        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var searchConfig = new BingSearchConfig
            {
                Country = BingCountry.Japan,
                Query = this.txtKerword.Text
            };

            this.lstResult.ItemsSource = await BingService.Instance.RequestAsync(searchConfig, 50);
        }

f:id:minami_SC:20160924124543p:plain