この記事では、「プログラミング Windows 第6版」を使って WPF XAML の学習を支援することを目的にしています。この目的から、書籍と併せて読まれることをお勧めします。
最初に、第2章の記事で Visual Studio より Blend を簡単に起動するための設定を説明しました。この時は、Visual Studio 2013 のエディションを記載していませんでしたが、有償のエディションを前提としていました。私は確認していませんが、無償の Express エディションでも可能になると考えられる組み合わせがあります。それは、Visual Studio Express 2013 for Windowsと Visual Studio Express for Window Desktopの両方をインストールした環境になります。この理由は、Visual Studio Express 2013 for Windows という Windows ストア アプリ用の開発ツールに Blend for Visual Studio が含まれているからです。この関係で、インストールできるオペレーティング システムが Windows 8/8.1 という制限がありますが、WPF XAML の編集などに Blend を利用できるので環境を構築する価値があると私は考えています。
第4章 パネルを使った表示
書籍では、「WinRT アプリケーションは、Page クラスを継承する 1つ以上のクラスで構成されています(引用)」で始まります。が、WPF アプリケーションは、Windows クラスを継承する 1つ以上のクラスか、NavigationWindow クラスを継承する 1つ以上のクラスで構成されています。と、読み替えることができます(NavigationWindow クラスを継承した場合に、コンテンツとして Page クラスを使用します)。そして、Panel の説明にと続きます。Panel の派生クラスの例として、VariableSizedWrapGridの説明がありますが、VariableSizedWrapGrid は WinRT XAML に固有のクラスであり、WPF XAML には含まれていませんので、ご注意ください。VariableSizedWrapGrid に相当するパネルとして、WPF には WrapPanelがあります。これ以外にも、Panel を継承するクラスには WinRT XAML と WPF XAML には違いがありますが、総じて WPF XAML の方が高機能だと私は感じています。多分、WinRT XAML のようなサブセットは、その目的に応じて拡張すべき機能と実装すべき機能を絞っているためだと考えられます。このように考えた方が、あれができないと悩むよりも、ではこうしようという風に割り切って使うことができるのではないでしょうか。
4.1(P108) Border
この節では、パネルを使って表示がどのようになるかを説明しています。最初に、NativeBorderText プロジェクトの MainWindow.xaml の抜粋を示します。
<Window ... ><Grid><Border BorderBrush="Red" BorderThickness="12" CornerRadius="24" Background="Yellow"><TextBlock Text="Hello Windows 8!" FontSize="96" Foreground="Blue" HorizontalAlignment="Center" VerticalAlignment="Center" /></Border></Grid></Window>
HorizontalAlignment と VeryicalAlignment プロパティが TextBlock 要素に設定されています。この NativeBorderText の実行結果を示します。
Border 要素が、Window 一杯に広がって、Border 要素の中で TextBlock に設定した文字列が HorizontalAlignment と VerticalAlignment プロパティ によってセンタリングされています。今度は、BetterBorderedText プロジェクトの MainWindow.xaml の抜粋を示します。
<Grid><Border BorderBrush="Red" BorderThickness="12" CornerRadius="24" Background="Yellow" HorizontalAlignment="Center" VerticalAlignment="Center"><TextBlock Text="Hello Windows 8!" FontSize="96" Foreground="Blue" Margin="24" /></Border></Grid>
NativeBorder との違いは、HorizontalAlignment と VerticalAlignment プロパティを Border 要素へ移動したことです。実行結果を示します。
今度は、Border 要素が Window の中心に配置されて、TextBlock 要素を取り囲んでいます。これが、パネル(この例では、Grid)に要素を配置する場合の HorizontalAlignment と VerticalAlignment プロパティの挙動になります。そして書籍では、Margin プロパティの説明が行われます。ここでは、レイアウトの配置に使用する Margin プロパティと Padding プロパティの考え方を少しだけ説明します。
Marign プロパティは、余白を設定するもので Windows Forms にもあったプロパティになります。XAML のコントロールには、Windows Forms のコントロールと違い Location プロパティがありません。このために良く使われるのが、Margin プロパティとなります。設定できる値は、コントロールの左上の角座標と右下の角座標になります(Margin は、オーバーロードされていることから X1 のみや、X1,Y1 なども設定できます)。もう 1つの Padding プロパティは、一部のコントロールが持つプロパティになりますが、Margin プロパティとは基準点が異なる点に注意が必要です。それぞれの基準点を示します。
- Margin: コントロールを配置するパネルなどを基準座標とします。
- Padding:コントロールが配置される座標を基準とします。つまり、本来の配置位置から Content の表示位置を指定する用途に使用します。
XAML のコントロールを配置する場合に、Margin と Padding プロパティは、最初に悩むプロパティの 1つでもあります。Margin プロパティは、FrameworkElement で定義されており、全ての要素で使用することができます。Padding プロパティは、コントロールの Content (コントロールのコンテンツ モデル)が、Padding プロパティをサポートしている場合にだけ使用できるようになっています。この理由から、全てのコントロールが Padding プロパティをサポートしていないのです。よって、通常は Margin プロパティを配置に使うと考えれば良いということになります。
4.2(P101) Rectangle と Ellipse
この節では、Shape クラス(WPF XAML では System.Windows.Shapes 名前空間)から派生したクラスを使って図形を描画することを説明しています。最初に円弧をレンダリングする SimpleEllipse プロジェクトの MainWindow.xaml の抜粋を示します。
<Grid><Ellipse Stroke="Red" StrokeThickness="24" Fill="Blue" /></Grid>
実行結果を示します。WinRT XAML と同じように、コンテナー(Grid)一杯にレンダリングされています。
Height や Width プロパティ、Stretch プロパティによってどのように描画が変化するかは、書籍を参照してください。
4.3(P113) StackPanel
本節では、StackPanel を説明しています。説明の通り、StackPanel は使うことが多いパネル コントロールです(これ以外は、Window に配置する Grid や Canvas なども使う頻度が高いパネル コントロールです)。StackPanel の子要素にどのように高さが割り当てるかを確認するために SimpleVerticalStack プロジェクトの MainWindow.xaml の抜粋を示します。
<Grid><StackPanel><TextBlock Text="Right-Aligned Text" FontSize="48" HorizontalAlignment="Right" /><Image Source="http://www.charlespetzold.com/pw6/PetzoldJersey.jpg" Stretch="None" /><TextBlock Text="Figure 1. Petzold heading to the basketball court" FontSize="24" HorizontalAlignment="Center" /><Ellipse Stroke="Red" StrokeThickness="12" Fill="Blue" /><TextBlock Text="Left-Aligned Text" FontSize="36" HorizontalAlignment="Left" /></StackPanel></Grid>
書籍の実行結果と比べると、大きな違いは Ellipse の描画状態でしょう。これは、書籍に記述されているように Height プロパティを設定していない(つまり、ゼロ)ためです。StackPanel の子要素の高さは、その子要素が必要とする高さが割り当てられていることが理解できます。また、Orientation プロパティを指定していない StackPanel は子要素を縦方向に配置することも理解できます。書籍に従って、様々なプロパティを変更してレンダリング結果を確かめてみることをお勧めします。
4.4(P117) 横方向への積み重ね
本節では、StackPanel の Orientation プロパティを使って横方向へ子要素を配置することを説明しています。このために、SimpleHorizontalStack プロジェクトの MainWindow.xaml の抜粋を示します。
<Grid><StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center"><TextBlock Text="Rectangle: " VerticalAlignment="Center" /><Rectangle Stroke="Blue" Fill="Red" Width="72" Height="72" Margin="12 0" VerticalAlignment="Center" /><TextBlock Text="Ellipse: " VerticalAlignment="Center" /><Ellipse Stroke="Red" Fill="Blue" Width="72" Height="72" Margin="12 0" VerticalAlignment="Center" /><TextBlock Text="Petzold: " VerticalAlignment="Center" /><Image Source="http://www.charlespetzold.com/pw6/PetzoldJersey.jpg" Stretch="Uniform" Width="72" Margin="12 0" VerticalAlignment="Center" /></StackPanel></Grid>
書籍で説明しているように、各種のプロパティを変更して子要素の配置がどのように変化するかを確認することをお勧めします。
4.5(P119) WhatSize とバインディング
本節では、第2章で使用した WhatSize プロジェクトを StackPanel とバインディングを使って説明しています。最初に、WhatSizeBinding プロジェクトの MainWindow.xaml の抜粋を示します。
<Window x:Name="page" ... ><Grid><StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top"><TextBlock Text="↤ " /><TextBlock Text="{Binding ElementName=page, Path=ActualWidth}" /><TextBlock Text=" pixels ↦" /></StackPanel><StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"><TextBlock Text="↥" TextAlignment="Center" /><StackPanel Orientation="Horizontal" HorizontalAlignment="Center"><TextBlock Text="{Binding ElementName=page, Path=ActualHeight}" /><TextBlock Text=" pixels" /></StackPanel><TextBlock Text="↧" TextAlignment="Center" /></StackPanel></Grid></Window>
実行結果を示します。
書籍では、画面の向きを変えたりスナップにしたりすると値が変更されない(WinRT XAML)とありますが、WPF 版ではウィンドウ サイズを変更することによって値が更新されていきます。この点も、WinRT XAML と WPF XAML の違いとなります。この両者の違いを正確に説明するのは難しいですが、あえて説明するとすれば、「WinRT XAML は WPF XAML のサブセットであり、WPF XAML がフルセットであり高機能のため」となります。
書籍では、この問題を解決するためにコンバーターを作成します。コンバーターは、WPF XAML でも必要になることから、WhatSizeWithBindingConverter プロジェクトの FormattedStringConverter.cs を示します。
using System; using System.Globalization; using System.Windows.Data; namespace WhatSizeWithBindingConverter { public class FormattedStringConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is IFormattable && parameter is string && !String.IsNullOrEmpty(parameter as string) && targetType == typeof(string)) { if (culture == null) return (value as IFormattable).ToString(parameter as string, null); return (value as IFormattable).ToString(parameter as string, culture); } return value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value; } } }
WinRT XAML との違いは、IValueConverter インターフェースのメンバーである Convert メソッドと ConvertBack メソッドの引数になります。WinRT XAML では、最後の引数が文字列(string)型ですが、WPF XAML では CultureInfo 型となっています。この点に注意して書き換えれば、WPF でも問題なく動作します。 作成した FormattedStringConverter クラスを使用するようにした MainWindow.xaml の抜粋を示します。
<Window ... x:Name="page"><Window.Resources><local:FormattedStringConverter x:Key="stringConverter" /></Window.Resources> <Grid><StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top"><TextBlock Text="↤ " /><TextBlock Text="{Binding ElementName=page, Path=ActualWidth, Converter={StaticResource stringConverter}, ConverterParameter=N0}" /><TextBlock Text=" pixels ↦" /></StackPanel><StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"><TextBlock Text="↥" TextAlignment="Center" /><StackPanel Orientation="Horizontal" HorizontalAlignment="Center"><TextBlock Text="{Binding ElementName=page, Path=ActualHeight, Converter={StaticResource stringConverter}, ConverterParameter=N0}" /><TextBlock Text=" pixels" /></StackPanel><TextBlock Text="↧" TextAlignment="Center" /></StackPanel></Grid></Window>
最初にリソースとして、FormattedStringConverter を定義(xmlns:local を定義しています)し、バインディング構文でコンバーター(Converter)とコンバーター パラメーター(この例では、文字列の「NO」)を指定しています。実行しても、WPF XAML では結果は同じになりますが、WinRT XAML では画面の向きの変化などによって値が更新されるようになります。
4.6(p124) ScrollViewer
本節では、StackPanel などの子要素が多い場合に表示されない子要素をどうするかという観点で ScrollViewer コントロールを説明しています。ScrollViewer コントロールは、Windows Forms に存在しないコントロールとなります。Windows Forms のコントロールとしては、HScrollBar と VScrollBar コントロールが合体したイメージのコントロールが ScrollViewer になります。この説明で理解できると思いますが、HScrollBar と VScrollBar コントロールは XAML のコントロールにはありません。理由は、ScrollViewer コントロールが、水平スクロールバーと垂直スクロールバーを兼ね備えているからです。それでは、StackPanelWithScrolling プロジェクトの MainWindow.xaml の抜粋を示します。
<Window x:Class="StackPanelWithScrolling.MainWindow" ... ><Grid><ScrollViewer><StackPanel Name="stackPanel" /></ScrollViewer></Grid></Window>
そして、StackPanel の子要素を生成する MainWindows.xaml.cs の抜粋を示します。
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); IEnumerable properties = typeof(Colors).GetTypeInfo().DeclaredProperties; foreach (PropertyInfo property in properties) { Color clr = (Color)property.GetValue(null); TextBlock txtblk = new TextBlock(); txtblk.Text = String.Format("{0} \x2014 {1:X2}-{2:X2}-{3:X2}-{4:X2}", property.Name, clr.A, clr.R, clr.G, clr.B); stackPanel.Children.Add(txtblk); } } }
コードを見れば、Colors 列挙を使って作成した TextBlock オブジェクトを StackPanel に追加しています。実行結果を示します。
実行して操作すれば理解ができますが、スクロールバーをマウスか指でタッチすることでスクロールさせることができます。つまり、WinRT XAML と違ってScrollViewer コントロールは完全なタッチ対応ではないことになります。タッチ対応の WPF コントロールについては、入力の概要ドキュメントのタッチに応答するコントロールを参照してください。
今度は、DependencyObject の階層構造を出力する DependencyObjectClassHierarchy プロジェクトの MainWindow.xaml を示します。
<Window ... ><Grid><ScrollViewer><StackPanel Name="stackPanel" /></ScrollViewer></Grid></Window>
WinRT XAMLとの違いは、FontSize を指定していないことです。理由は、組み込みスタイルがないためです。そして、ClassAndSubclasses.cs は同じコードを使用することができます。WPF XAML へ移植するために必要な変更は、MainWindow.xaml.cs のコードだけなので抜粋を示します。
public partial class MainWindow : Window { Type rootType = typeof(DependencyObject); TypeInfo rootTypeInfo = typeof(DependencyObject).GetTypeInfo(); List<Type> classes = new List<Type>(); Brush highlightBrush; public MainWindow() { InitializeComponent(); //highlightBrush = new SolidColorBrush(new UISettings().UIElementColor(UIElementType.Highlight)); highlightBrush = new SolidColorBrush(SystemColors.HighlightColor); // Accumulate all the classes that derive from DependencyObject AddToClassList(typeof(System.Windows.DependencyObject)); AddToClassList(typeof(System.Windows.UIElement)); // 追加 AddToClassList(typeof(System.Windows.Window)); // 追加 // Sort them alphabetically by name classes.Sort((t1, t2) => { return String.Compare(t1.GetTypeInfo().Name, t2.GetTypeInfo().Name); }); // Put all these sorted classes into a tree structure ClassAndSubclasses rootClass = new ClassAndSubclasses(rootType); AddToTree(rootClass, classes); // Display the tree using TextBlock's added to StackPanel Display(rootClass, 0); } void AddToClassList(Type sampleType) { Assembly assembly = sampleType.GetTypeInfo().Assembly; var types = assembly.GetTypes(); foreach (Type type in assembly.ExportedTypes) // ExportedTypes { TypeInfo typeInfo = type.GetTypeInfo(); System.Diagnostics.Debug.WriteLine(typeInfo.Name); if (typeInfo.IsPublic && rootTypeInfo.IsAssignableFrom(typeInfo)) classes.Add(type); } } void AddToTree(ClassAndSubclasses parentClass, List<Type> classes) { foreach (Type type in classes) { Type baseType = type.GetTypeInfo().BaseType; if (baseType == parentClass.Type) { ClassAndSubclasses subClass = new ClassAndSubclasses(type); parentClass.Subclasses.Add(subClass); AddToTree(subClass, classes); } } } void Display(ClassAndSubclasses parentClass, int indent) { TypeInfo typeInfo = parentClass.Type.GetTypeInfo(); // Create TextBlock with type name TextBlock txtblk = new TextBlock(); txtblk.Inlines.Add(new Run { Text = new string(' ', 8 * indent) }); txtblk.Inlines.Add(new Run { Text = typeInfo.Name }); // Indicate if the class is sealed if (typeInfo.IsSealed) txtblk.Inlines.Add(new Run { Text = " (sealed)", Foreground = highlightBrush }); // Indicate if the class can't be instantiated IEnumerable<ConstructorInfo> constructorInfos = typeInfo.DeclaredConstructors; int publicConstructorCount = 0; foreach (ConstructorInfo constructorInfo in constructorInfos) if (constructorInfo.IsPublic) publicConstructorCount += 1; if (publicConstructorCount == 0) txtblk.Inlines.Add(new Run { Text = " (non-instantiable)", Foreground = highlightBrush }); // Add to the StackPanel stackPanel.Children.Add(txtblk); // Call this method recursively for all subclasses foreach (ClassAndSubclasses subclass in parentClass.Subclasses) Display(subclass, indent + 1); } }
コードで変更した箇所は、数か所があります。最初に、highlightBrush に設定するシステムが用意するハイライト用のブラシ定義を WPF 用に変更しています。続いて、変更しているのが AddToClassList メソッドの呼び出しになります。 WinRT XAMLでは、DependencyObject だけの呼び出しですが、WPF XAML では UIElement と Window の呼び出しを追加しています。これは、WinRT XAML が使用するアセンブリ(メタデータ) と WPF XAML が使用するアセンブリ(PresentationCore、PresentationFramework、WindowsBase)の違いによるものです。このアセンブリの違いも、WinRT XAML が WPF XAML のサブセットであることから 1つのアセンブリだけでメタデータを定義していることを表しています。それでは、実行結果を示します。
WinRT XAML と同じように DependecyObject の階層構造を表示することができます。
4.7(P130) レイアウトの是非
この節では、レイアウト メカニズムを理解するために StackPanelWithScrollong プロジェクトを書き換えてレイアウトへの影響を説明しています。考え方は、WPF XAML も同じなので自分で試してみてください。
4.8(P132) 電子書籍の作成
本節では、「The Tale of Tom Kitten」という童話を題材に電子書籍ビューワーのようなアプリを作ることを説明しています。この TheTaleOfTomKitten プロジェクトを WPF XAML へ移植するのは簡単です。具体的には、ここまでに説明してきた内容(Page を Window へ、組み込みスタイルを変更)を反映するだけなので、MainWindow.xaml のリソース定義を示します。
<Window.Resources><Style x:Key="commonTextStyle" TargetType="TextBlock"><Setter Property="FontFamily" Value="Century Schoolbook" /><Setter Property="FontSize" Value="36" /><Setter Property="Foreground" Value="Black" /><Setter Property="Margin" Value="0 12" /></Style><Style x:Key="paragraphTextStyle" TargetType="TextBlock" BasedOn="{StaticResource commonTextStyle}"><Setter Property="TextWrapping" Value="Wrap" /></Style><Style x:Key="frontMatterTextStyle" TargetType="TextBlock" BasedOn="{StaticResource commonTextStyle}"><Setter Property="TextAlignment" Value="Center" /></Style><Style x:Key="imageStyle" TargetType="Image"><Setter Property="Stretch" Value="None" /><Setter Property="HorizontalAlignment" Value="Center" /></Style></Window.Resources>
次に TextBlock 要素と Image 要素を交互に配置した XAML を示します(MainWindow.xamlより)。
<Grid Background="White"><ScrollViewer><StackPanel MaxWidth="640" HorizontalAlignment="Center"> ... <!-- pg. 38 --><TextBlock Style='{StaticResource paragraphTextStyle}'> Mittens laughed so that she fell off the wall. Moppet and Tom descended after her; the pinafores and all the rest of Tom's clothes came off on the way down.</TextBlock><TextBlock Style='{StaticResource paragraphTextStyle}'> “Come! Mr. Drake Puddle-Duck,” said Moppet — “Come and help us to dress him! Come and button up Tom!”</TextBlock><Image Source='Images/tom39.jpg' Style='{StaticResource imageStyle}' /><!-- pg. 41 --><TextBlock Style='{StaticResource paragraphTextStyle}'> Mr. Drake Puddle-Duck advanced in a slow sideways manner, and picked up the various articles.</TextBlock><Image Source='Images/tom40.jpg' Style='{StaticResource imageStyle}' /> ...</StackPanel></ScrollViewer></Grid>
WinRT XAML との違いは、Grid 要素の Background プロパティだけになります。それでは、実行結果を示します。
4.9(P135) StackPanel の子要素のカスタマイズ
この節では、ScrollViewer 要素内に配置した StackPanel の子要素を順序を追ってカスタマイズしていく方法を説明しています。最初に、WinRT で利用可能な 141 種類の色を表示することから始めています。WPF XAML へ移植するのは簡単で、ここまでに説明してきた内容と使用している各クラスの名前空間を変更(WPF アプリケーション プロジェクトで作業する限りは、意識する必要はありません)するだけです。それでは、ColorList1 プロジェクトの MainWindow.xaml の抜粋を示します。
<Grid><ScrollViewer><StackPanel Name="stackPanel" HorizontalAlignment="Center" /></ScrollViewer></Grid>
今度は、MainWindow.xaml.cs の抜粋を示します。
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); IEnumerable<PropertyInfo> properties = typeof(Colors).GetTypeInfo().DeclaredProperties; foreach (PropertyInfo property in properties) { Color clr = (Color)property.GetValue(null); StackPanel vertStackPanel = new StackPanel { VerticalAlignment = VerticalAlignment.Center }; TextBlock txtblkName = new TextBlock { Text = property.Name, FontSize = 24 }; vertStackPanel.Children.Add(txtblkName); TextBlock txtblkRgb = new TextBlock { Text = String.Format("{0:X2}-{1:X2}-{2:X2}-{3:X2}", clr.A, clr.R, clr.G, clr.B), FontSize = 18 }; vertStackPanel.Children.Add(txtblkRgb); StackPanel horzStackPanel = new StackPanel { Orientation = Orientation.Horizontal }; Rectangle rectangle = new Rectangle { Width = 72, Height = 72, Fill = new SolidColorBrush(clr), Margin = new Thickness(6) }; horzStackPanel.Children.Add(rectangle); horzStackPanel.Children.Add(vertStackPanel); stackPanel.Children.Add(horzStackPanel); } } }
4.10(P138) UserControl の継承
この節では、ColorList1 プロジェクトの StackPanel の子要素を UserControl 化することを説明しています。WPF XAML でも WinRT XAML と同じ操作で ユーザー コントロールを作成することができます。それでは、ColorList2 プロジェクトの ColorItem.xaml の抜粋を示します。
<UserControl x:Class="ColorList2.ColorItem" ... d:DesignHeight="300" d:DesignWidth="300"><Grid><StackPanel Orientation="Horizontal"><Rectangle Name="rectangle" Width="72" Height="72" Margin="6" /><StackPanel VerticalAlignment="Center"><TextBlock Name="txtblkName" FontSize="24" /><TextBlock Name="txtblkRgb" FontSize="18" /></StackPanel></StackPanel></Grid></UserControl>
次に ColorItem.xaml.cs の抜粋を示します。
public partial class ColorItem : UserControl { public ColorItem(string name, Color clr) { this.InitializeComponent(); rectangle.Fill = new SolidColorBrush(clr); txtblkName.Text = name; txtblkRgb.Text = String.Format("{0:X2}-{1:X2}-{2:X2}-{3:X2}", clr.A, clr.R, clr.G, clr.B); } }
WinRT XAML と同じコードになっていることが理解できると思います。それでは、MainWindow.xaml.cs の抜粋を示します。
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); IEnumerable<PropertyInfo> properties = typeof(Colors).GetTypeInfo().DeclaredProperties; foreach (PropertyInfo property in properties) { Color clr = (Color)property.GetValue(null); ColorItem clrItem = new ColorItem(property.Name, clr); stackPanel.Children.Add(clrItem); } } }
コードは、ColorList1 プロジェクトよりも単純になっています。これは、ユーザーコントロール化したことによって記述するコードが減少したためです。もちろん、実行しても ColorList1 と同じ結果が得られます。
4.11(P141) WinRT ライブラリの作成
この節では、WinRT ライブラリの作成と題していますが、クラス ライブラリ プロジェクトの作成方法を説明しています。タイトルに「WinRT ライブラリ」とある理由は、ユーザー コントロールを含むライブラリを作成するためです。このため、WPF でも同じ方法を利用することがでいます。違いがあるとすれば、Windows ストア アプリのプロジェクトでは必要な参照設定が事前に行われていることに対して、デスクトップのクラス ライブラリ プロジェクトでは自分で参照設定を行う必要があることです(WPF のユーザー コントロールを作成しますので、PresentationCore、PresentationFramework、WindowsBase アセンブリへ参照を設定してください)。それでは、PetzoldWindows8Controls プロジェクトの ColorItem.xaml の抜粋を示します。
<UserControl x:Class="PetzoldWindows8Controls.ColorItem" ... d:DesignHeight="300" d:DesignWidth="300"><Grid><Border BorderBrush="Black" BorderThickness="1" Width="336" Margin="6"><StackPanel Orientation="Horizontal"><Rectangle Name="rectangle" Width="72" Height="72" Margin="6" /><StackPanel VerticalAlignment="Center"><TextBlock Name="txtblkName" FontSize="24" /><TextBlock Name="txtblkRgb" FontSize="18" /></StackPanel></StackPanel></Border></Grid></UserControl>
組み込みのスタイルを変更しただけで、WinRT XAML と同じになっています。ColorItem.xaml.cs は、ColorList2 プロジェクトの ColorItem.xaml.cs と同じになります。次に、ColorList3 プロジェクトの MainWindow.xaml.cs の抜粋を示します。
using PetzoldWindows8Controls; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace ColorList3 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); IEnumerable<PropertyInfo> properties = typeof(Colors).GetTypeInfo().DeclaredProperties; foreach (PropertyInfo property in properties) { Color clr = (Color)property.GetValue(null); ColorItem clrItem = new ColorItem(property.Name, clr); stackPanel.Children.Add(clrItem); } } } }
コードは WinRT XAML と同じになります。もちろん、実行結果も同じになります。
書籍では、Windows ランタイム コンポーネントに対する言及がありますが、デスクトップ アプリの世界である WPF の世界には Windows ランタイム コンポーネントはありませんので、気にする必要はありません。WPF で作成したクラス ライブラリも .NET Framework のアセンブリですから、Windows Forms と同じようにクラス ライブラリとして使用することができます。作成したライブラリに対する使用方法の制限は、Windows Forms と WPF で相互に GUI コンポーネントを利用する場合になります。相互に利用するには、移行と相互運用のドキュメントを参照してください。
4.12(P144) スクロールの向きの変更
この節では、PetzoldWindows8Controls プロジェクトで作成したクラス ライブラリの利用方法を様々な角度から説明してから、ScrollViewer クラスで水平方向にスクロールさせる方法を説明しています。この説明の中で、VariableSizedWrapGrid パネルという WinRT XAML 固有のパネルを使用します。つまり、WPF で動作させるには、VariableSizedWrapGrid パネルに相当するパネルへ書き換える必要があることになります。このために、WrapPanel を使用して書き換えた ColorWrap プロジェクトの MainWindow.xaml の抜粋を示します。
<Grid><ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled"><WrapPanel Name="wrapPanel" Orientation="Vertical"/></ScrollViewer></Grid>
ScrollViewer の HorizontalScrollBarVisibility と VerticalScrollBarVisibility プロパティを設定しています。このプロパティによって、水平スクロール バーが表示されるようになります。このことが、Windows Forms の VScrollBar と HScrollBar を統合したような機能を持つコントロールとして ScrollViewer があると説明した理由になります。また、WrapPanel の Orientation プロパティを「Vertical」に設定することで、子要素を縦に並べて、あふれた子要素を右側の列へ並べるようになります。結果として、画面をあふれる子要素を表示するために水平スクロールバーが機能するということになります。今度は、ColorWrap プロジェクトの MainWindow.xaml.cs の抜粋を示します。
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); IEnumerable<PropertyInfo> properties = typeof(Colors).GetTypeInfo().DeclaredProperties; foreach (PropertyInfo property in properties) { Color clr = (Color)property.GetValue(null); ColorItem clrItem = new ColorItem(property.Name, clr); wrapPanel.Children.Add(clrItem); } } }
スクロールが横方向になっていることが、確認できると思います。
4.13(P147) Canvas と添付プロパティ
本節で説明するのは、Canvas パネルになります。Canvas パネルを使用すると、Windows Forms の Location プロパティに似た方法で絶対座標をでコントロールを配置できるようになります(絶対座標といっても、XAML という UI 技術が論理座標になっていることを忘れないでください)。この場合に利用するプロパティが、添付プロパティ(Attached Property)と呼ばれるものになります。これらの説明も、書籍で行っています。それでは、TextOnCanvas プロジェクトの MainWindow.xaml の抜粋を示します。
<Window x:Class="TextOnCanvas.MainWindow" ... FontSize="48"><Grid><Canvas><TextBlock Text="Text on Canvas at (0, 0)" Canvas.Left="0" Canvas.Top="0" /><TextBlock Text="Text on Canvas at (200, 100)" Canvas.Left="200" Canvas.Top="100" /><TextBlock Text="Text on Canvas at (400, 200)" Canvas.Left="400" Canvas.Top="200" /></Canvas></Grid></Window>
TextBlock 要素に Canvas.Top と Canvas.Left プロパティを設定しています。この二つのプロパティが、Windows Forms の Location.X と Location.Y に相当します。そして、Canvas パネル内にコントロールを配置した場合に、Visual Studio のデザイナー上のプロパティ ウィンドウにも Left と Top プロパティが設定できるようになります。
今度は、Canvas の添付プロパティの使い方を確認するために TapAndShowPoint プロジェクトの MainWindow.xaml の抜粋を示します。
<Grid><Canvas Name="canvas" /></Grid>
そして、コードで添付プロパティを操作する MainWindow.xaml.cs の抜粋を示します。
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } protected override void OnTouchDown(TouchEventArgs e) { //Point pt = e.GetPosition(this); // 変更 Point pt = e.GetTouchPoint(this).Position; // Create dot Ellipse ellipse = new Ellipse { Width = 3, Height = 3, Fill = this.Foreground }; Canvas.SetLeft(ellipse, pt.X); Canvas.SetTop(ellipse, pt.Y); canvas.Children.Add(ellipse); // Create text TextBlock txtblk = new TextBlock { Text = String.Format("({0})", pt), FontSize = 24, }; Canvas.SetLeft(txtblk, pt.X); Canvas.SetTop(txtblk, pt.Y); canvas.Children.Add(txtblk); e.Handled = true; base.OnTouchDown(e); } }
WPF XAML で動作させるために変更したのは、 OnTapped イベントを OnTouchDown イベントにしたことです。XAML に記述していた Canvas.Top などの添付プロパティは、コード上では Canvas オブジェクトの SetTop 静的メソッドと SetLeft 静的メソッドになっていることを確認できることでしょう。また、WinRT XAML の OnTapped イベントを OnTouchDown イベントに変更したことで、座標の取得方法も変更しています。具体的には、TouchEventArgs の GetTouchPoint メソッドを使用することでタッチされた座標を取得しています。実行結果を示します。
残りの記述も WPF XAML と WinRT XAML では同じですから、書籍を読みことで使い方を理解できることでしょう。
4.14(P152) Z インデックス
本節では、Z インデックス(もしくは、Z オーダー)の使い方を説明しています。基本的に XAML では、記述した順序で Z インデックスが決定されます。つまり、最初が Z インデックスがゼロとなり、次の要素が上になります。要は、記述した順序で要素が上側に配置されるのです。この順序を変更するのが、Z インデックスになります。個人的な考えですが、何らかのアクションで Z インデックスを変更する必要がない限りは、XAML の記述順序を Z インデックスに合わせておく方がトラブルが少ないように考えます(あくまでも、個人の考えです)。書籍では、Z インデックスの使い方を Canvas.ZIndex 添付プロパティを使って説明しています。
4.15(P153) Canvas の注意点
本節では、他のパネルと違って Canvas パネルを使用する上での違いを踏まえて説明していますので、書籍に従って自分で試してみることをお勧めします。
Canvas をどのような場合に使用するかに関しては、書籍にはガイドのような記述はありません。が、私がお勧めするのは自分でグラフィックスなどを描画する場合です。たとえば、ペンを使って描画するアプリなどです。このような描画アプリでは、描画した図形を相対座標より絶対座標で固定する方が利用者の期待に応えることになるからです。つまり、アプリがユーザーに提供する機能によってパネルを選択すべきだと言うことです。その中で、基本的には フレキシブル レイアウトを基本にしながら、必要な箇所には固定レイアウトを組み合わせるということを意識して UI をデザインした方が良いことは、言うまでもありません。
パネルに関して WinRT XAML と WPF XAML には、説明していない違いがあります。具体的には、提供されるパネル コントロールの数は WPF XAML の方が多くある点です。たとえば、DocPanelや UniformGrid、TabPanelなどがあります。一方で、WinRT XAML にしかない WrapGridパネルなどもあります。これらのパネルは、ドキュメントを読んだり自分で試して使い方を学習すると良いでしょう。
ここまで説明してた違いを意識しながら、第4章を読むことで WPF にも書籍の内容を応用することができるようになることでしょう。