TextBox と Material Design In XAML Toolkit【case: 1-3 WPF UI Gallery】
前回は MahApps.Metro の MetroWindow と MetroWindow を制御するための Behavior を紹介しました。今回から MahApps.Metro と Material Design In XAML Toolkit をインストールした場合の WPF 標準コントロールについて数回に分けて紹介していく予定です。
尚、この連載は Visual Studio 2019 Community Edition で .NET Core 3.1 以上 と C# + Prism 7.2 以降 + ReactiveProperty + CalcBinding を使用して 、WPF アプリケーションを MVVM パターンで作成するのが目的で、C# での基本的なコーディング知識を持っている人が対象です。
又、このエントリで紹介しているサンプルコードは MahApps.Metro と MahApps.Metro.IconPacks、Material Design In XAML Toolkit がインストールされている必要があります。
MahApps.Metro と Material Design In XAML Toolkit のインストールについては WPF Prism episode: 19 を見てください。
又、MahApps.Metro.IconPacks のインストールについては WPF UI Gallery case: 1-1 を見てください。
目次
MahApps.Metro と Material Design In XAML Toolkit をインストールした場合の WPF 標準コントロール
MahApps.Metro や Material Design In XAML Toolkit をインストールすると、WPF 標準コントロールはそれぞれのフレームワークで定義している Style に置き換わります。
MahApps.Metro と Material Design In XAML Toolkit を冒頭で紹介した WPF Prism episode: 19 の内容に沿ってインストールした場合、WPF 標準コントロールは Material Design In XAML Toolkit の Style が適用されるようになります。Material Design In XAML Toolkit はデザインフレームワークなので、Extended WPF Toolkit™ 等のコントロールライブラリとは違い、標準コントロールを継承した入力系拡張コントロール等は基本的に含まれません。
ですが、MahApps.Metro、Material Design In XAML Toolkit 共に標準コントロールを拡張する仕組みは用意されていて、今回のエントリは TextBox について紹介します。
尚、このエントリ執筆時点で MahApps.Metro の .NET Core 対応版がリリースされていないため、本エントリでは MahApps.Metro Ver. 1.6.5(.NET Framework 版)を使用しています。
TextBox コントロール
Material Design In XAML Toolkit には TextBox 用にいくつかの Style が用意されていて、fig. 1 は TextBox 用の全 Style です。
マウスカーソルホバー時、フォーカス受け取り時、フォーカス喪失時にそれぞれ効果が追加されている事が確認できます。
TextBox の Style
TextBox の Style は src. 1 のように指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | <UserControl x:Class="MaterialDesignControls.TextBoxes.TextBoxPanel" ~ 略 ~ xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes" ~ 略 ~ prism:ViewModelLocator.AutoWireViewModel="True"> ~ 略 ~ <Grid Panel.ZIndex="1"> <Grid.RowDefinitions> <RowDefinition Height="0.35*"/> <RowDefinition Height="0.65*"/> </Grid.RowDefinitions> <Grid Grid.Row="0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.33*"/> <ColumnDefinition Width="0.33*"/> <ColumnDefinition Width="0.34*"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" Orientation="Vertical" Margin="20, 10, 0, 0"> <TextBox Text="Style指定なし" Margin="0, 18, 0, 0" /> <TextBox Margin="0, 30, 0, 0" TextWrapping="Wrap" Style="{StaticResource MaterialDesignOutlinedTextFieldTextBox}" Text="MaterialDesignOutlinedTextFieldTextBox指定" /> </StackPanel> <StackPanel Grid.Column="1" Orientation="Vertical" Margin="20, 10, 0, 0"> <TextBox Style="{StaticResource MaterialDesignTextBox}" Margin="0, 18, 0, 0" Text="MaterialDesignTextBox指定"/> <TextBox Margin="0, 25, 0, 0" TextWrapping="Wrap" Style="{StaticResource MaterialDesignFilledTextFieldTextBox}" Text="MaterialDesignFilledTextFieldTextBox指定"/> </StackPanel> <StackPanel Grid.Column="2" Orientation="Vertical" Margin="20, 10, 0, 0"> <TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}" Text="MaterialDesignFloatingHintTextBox指定" /> </StackPanel> </Grid> ~ 略 ~ </Grid> </Grid> </UserControl> |
XAML を記述する際はコピペしても構いませんが、『StaticResource』まで入力すると IntelliSense で候補が表示される(はず)なのでそこから選ぶこともできます。
fig. 1 最上段左の TextBox には Style を指定していませんが、他の TextBox はそれぞれ以下の Style を指定しています。
Style | 内容 |
---|---|
MaterialDesignTextBox | TextBox にデフォルトで適用される Style。 Style を指定しない場合、この Style が自動的に適用されます。 |
MaterialDesignFloatingHintTextBox | 見た目は MaterialDesignTextBox と全く同じですが、後で紹介する HintAssist を組み合わせるとウォーターマークが Float 表示されます。 |
MaterialDesignOutlinedTextFieldTextBox | TextBox の四方に境界線が描画される Style。 |
MaterialDesignFilledTextFieldTextBox | 背景を塗りつぶした Style。 |
Material Design In XAML Toolkit には大半の WPF 標準コントロールに対してデフォルトの Style が定義されているので、個々に Style を指定しなくても App.xaml へ ResourceDictionary をマージするだけで自動的にデフォルト Style が適用されます。
IsEnabled プロパティの設定値による Style の変化
fig. 2 は IsEnabled、IsReadOnly を false に設定した場合の Style です。
IsEnabled プロパティを false に変更するとグレーアウトしますが、MaterialDesignTextBox Style と MaterialDesignFloatingHintTextBox Style は下枠線が点線で描画されます。
そして IsReadOnly プロパティを true に変更するとマウスホバー時やフォーカスイン / アウト時の効果は変わりませんが、何故か MaterialDesignFloatingHintTextBox(fig. 2 上段右端)以外の Style ではキャレットが表示されなくなっています。
ウォーターマークを表示するための HintAssist
最近のスマホアプリ等でもよく見かけるウォーターマークを Material Design In XAML Toolkit で表示するには HintAssist と呼ばれる添付プロパティを使用します。fig. 3 は HintAssist を TextBox に設定した場合の動作です。
※ 以降のスクショとソースコード内に出て来る『Floting』は全て『Floating』のタイポです。
fig. 3 のように左上以外の TextBox ではフォーカス取得にウォーターマークが Floating し、フォーカス喪失時に元に位置に戻る効果が追加されます。ウォーターマークは以下のプロパティを変更して表示をカスタイマイズできます。
HintAssist プロパティ | 内容 |
---|---|
Hint | ウォーターマークとして表示する文字列を指定します。 |
HelperText | 『入力上の注意』的な文章をコントロールの下位置に追加します。(表示位置は変更不可) fig. 3 では Style 名を表示しています。 但し、fig. 3 左上のように長文を設定しても改行等はされずコントロール幅までしか表示されません。(ウィンドウ幅を広げれば切れた部分も見えるようになります) |
HintOpacity | Hint や HelperText プロパティに設定した文字列の透明度を double 型で指定します。 デフォルト値は 0.56。最大値は 1.0。(1.0 以上も指定できますが設定しても 1.0 に丸められるようです) |
FloatingScale | ウォーターマークが Floating した場合の文字サイズ比率を double 型で指定します。 デフォルト値は 0.74。(コントロールに設定したフォントサイズの 74% で表示される) HintOpacity と異なり 1.0 以上の値も設定可能。 |
FloatingOffset | ウォーターマークが Floating する位置を TextBox 左上座標からのオフセット値で指定します。デフォルト値は Point(1, -16)。 |
IsFloating | Floating する / しないを設定します。 MaterialDesignTextBox Style(デフォルトスタイル)のみ規定値は false。 |
そして、fig. 3 の HintAssist は TextBox ごとに各プロパティへ以下のように異なる値を設定しています。
Style | HintOpacity | FloatingScale | FloatingOffset |
---|---|---|---|
MaterialDesignTextBox(デフォルト Style) | 0.5 | – | – |
MaterialDesignFloatingHintTextBox | 0.9 | 1.0 | Point(10, 40) |
MaterialDesignOutlinedTextFieldTextBox | 0.7 | 0.7 | – |
MaterialDesignFilledTextFieldTextBox | 1.0 | 1.2 | Point(1, -25) |
MaterialDesignOutlinedTextFieldTextBox の場合、FloatingOffset が無視されるようなので『-(設定値無し)』にしています。
そして、fig. 4 のように HintAssist.IsFloating プロパティの値を反転すると左上の TextBox 以外は Floating しない一般的なウォーターマークとして動作するようになり、逆に左上の TextBox は Floating するようになります。
※ fig. 4 右の赤字 Floating プロパティ ⇒ IsFloating プロパティの間違いです
fig. 4 では IsFloating プロパティを反転すると Floating Hint 用に確保されているマージンが増減する事も確認できます。
前回の WPF UI Gallery case: 1-2 ではプロパティを操作するコントロールと値を反映したいコントロールが UserControl と Window に分かれていたため Service クラスを仲介して値をやり取りしましたが、fig. 4 のようにプロパティを操作するコントロール(ToggleButton)と値を反映したいコントロール(TextBox 達)が同一 XAML 上に配置されている場合は XAML だけでバインドできます。
fig. 4 のように 1 つの ToggleButton を 4 つの TextBox(数に制限があるわけではありません)とバインドする事は可能ですが、バインドする値を反転したい場合は Binding に Converter を指定する必要があります。
(IsFloating プロパティは左上の TextBox に設定した Style のみ false で、他の Style は true が規定値なので左上のみ反転した値をバインドしたい)
Converter で変換してバインドする
bool 値を反転してバインドする Converter 自体は大して難しい訳ではなく『bool型の変数値を反転させて、コントロールにバインディングしたいとき – ポンコツXAMLer奮闘記』等に書かれているように IValueConverter を継承した Converter を作成して添付するだけです。
WPF が発表されて 10 年以上経つので経験が長い人なら既にこのような Converter は作成済みかもしれませんが、今から WPF を始めようとする人には結構面倒に思えるかもしれません。bool を反転するような汎用的な Conver は 1 度作成すれば他の機能やプロジェクトへ使い回せますが、Converter 自体を Resource へ追加する等、少し面倒な手間も必要になるので他の方法を調べると【QuickConverter】と言うライブラリを何度か見かけました。
QuickConverter は元々 Codeplex で発表されていたライブラリで、現在は GitHub で公開されていますが 2018 年 8 月以降リポジトリの更新も止まっているようで .NET Core 版もリリースされていません。(2020 年 4 月現在)
.NET 5 の Preview 版がリリースされているような時期なのに今更 .NET Framework しかサポートしていないライブラリを好んで選択する必要も無いと思って他の選択肢を探してみると【CalcBinding】を見つけました。
CalcBinding で bool を反転バインディング
【CalcBinding】はバインディングに式を記述できる XAML 拡張ライブラリで .NET Core 3.0 にも対応済みで NuGet からインストールできます。
CalcBinding、QuickConverter 共に日本語情報はあまり多くありませんが NuGet からのダウンロード数は既に CalcBinding の方が多いようなので今後のことを考えると CalcBinding を使用する方が良いと思っています。
(あくまでも管理人の主観です)
CalcBinding のインストール
CalcBinding をインストールするには [ソリューションの NuGet パッケージの管理] 画面で fig. 5 のように『calc』で検索すると見つかります。
管理人はとりあえず導入してみましたが、まだ ReadMe すらほとんど読んでいませんし、今の所は bool を反転する程度しか適用箇所が思い付かないので、反転した bool 値とバインドする方法は紹介しますが、それ以外の機能はこのエントリでは詳しく紹介しません。
HintAssist.IsFloating(bool)を CalcBinding で反転してバインドする
src. 2 は HintAssist.IsFloating プロパティを同一 XAML 上の ToggleButton とバインドする例です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | <UserControl x:Class="MaterialDesignControls.TextBoxes.TextBoxPanel" ~ 略 ~ xmlns:calc="clr-namespace:CalcBinding;assembly=CalcBinding" ~ 略 ~ xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes" ~ 略 ~ prism:ViewModelLocator.AutoWireViewModel="True"> <Grid> ~ 略 ~ <Grid Panel.ZIndex="1"> <Grid.RowDefinitions> <RowDefinition Height="0.35*"/> <RowDefinition Height="0.65*"/> </Grid.RowDefinitions> ~ 略 ~ <Grid Grid.Row="1"> ~ 略 ~ <Grid Grid.Row="0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.5*"/> <ColumnDefinition Width="0.5*"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" Orientation="Vertical" Margin="20, 0, 0, 0"> <TextBox md:HintAssist.Hint="ヒント" md:HintAssist.HintOpacity="0.5" md:HintAssist.HelperText="Style指定無し 長い文字列を指定すると改行はされず TextBox の幅で切れます。" md:HintAssist.IsFloating="{calc:Binding ElementName=IsFlotingToggle, Path=!IsChecked, Mode=OneWay}" Style="{StaticResource MaterialDesignTextBox}" Text="{Binding NormalText.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" /> <TextBox Margin="0, 70, 0, 0" TextWrapping="Wrap" md:HintAssist.Hint="ヒントプロパティ" md:HintAssist.HelperText="Style:MaterialDesignOutlinedTextFieldTextBox" md:HintAssist.FloatingScale="0.7" md:HintAssist.HintOpacity="0.7" md:HintAssist.IsFloating="{Binding ElementName=IsFlotingToggle, Path=IsChecked, Mode=OneWay}" Style="{StaticResource MaterialDesignOutlinedTextFieldTextBox}" Text="{Binding OutlinedText.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" /> ~ 略 ~ </StackPanel> ~ 略 ~ </Grid> </Grid> <StackPanel Grid.Column="1" Orientation="Vertical" Margin="20, 0, 0, 0"> <StackPanel Orientation="Horizontal" Margin="0, 20, 0, 0" HorizontalAlignment="Center"> <TextBlock Text="Floting ヒント反転 / 既定" /> <ToggleButton Name="IsFlotingToggle" IsChecked="True" Margin="13, 0, 0, 0" /> </StackPanel> </StackPanel> </Grid> </Grid> </Grid> </UserControl> |
プロパティを同一 XAML 上のコントロールとバインドするには src. 2 の 38 行目のように【ElementName パラメータ】に対象のコントロール名、【Path パラメータ】にバインドするプロパティ名を指定するだけです。
ここで注目して欲しいのは 29 行目の『calc:Binding ElementName=IsFlotingToggle, Path=!IsChecked』の部分で、通常の【Binding】ではなく 3 行目に名前空間を追加した CalcBinding に含まれる【calc:Binding】を利用して反転した(『!』)プロパティ値(bool)とバインドしている箇所です。
CalcBinding で bool を反転してバインドするのは既に Converter が作成済みであっても比較にならない程簡単な記述になるのは分かって頂けると思います。そして、当然のことながら CalcBinding は同一 XAML 上のコントロール間バインディングだけでなく VM とバインドする場合でも同様に使用できます。
ただ、CalcBinding には fig. 6 のように Binding 部を入力する場合に不満点もあります。
fig. 6 の通り、通常の Binding を入力する際は『ElementName』や『Path』の入力時にインテリセンスで候補が表示されますが、CalcBinding では表示されない(ElementName はペーストしています)事は不満です。
ですが、実際のアプリでは VM とバインドする事が大半なので ElementName 入力時に候補が表示されなくても、大して困る事は無いかもしれません。ただ、fig. 6 のような場合は先に通常の『Binding』で書いてから『calc: プレフィックス』を追加する方が楽そうだとは思いました。
CalcBinding は bool の反転以外にも色々使えるとは思いますが、管理人的に現時点ではそれ以外の用途が思い付かないので、又の機会にでも紹介できたら…と思っています。
Material Design In XAML Toolkit の TextBox Style を更にカスタマイズする
Material Design In XAML Toolkit に含まれる TextBox 用の Assist は HintAssist だけではなく TextFieldAssist と呼ばれる添付プロパティも用意されています。TextFieldAssist を紹介している記事等は海外含めほとんど見当たりませんが特に難しい事はありません。
fig. 7 は TextFieldAssist を設定した場合の動作です。
fig. 7 では設定可能なプロパティの内とりあえず半分だけ紹介していて残りは後で紹介します。
以下は fig. 7 で操作しているプロパティの一覧です。
TextFieldAssist プロパティ | 内容 |
---|---|
HasOutlinedTextField | TextBox に境界線を表示する / しないを設定します。 Style に MaterialDesignOutlinedTextFieldTextBox を設定した場合と同じ外観になります。 |
HasFilledTextField | Material Design In XAML Toolkit の Style を適用した TextBox の背景は透明ですが、このプロパティを true に設定すると背景を塗りつぶすことができます。 Style に MaterialDesignFilledTextFieldTextBox を設定した場合と同じ外観になります。 |
NewSpecHighlightingEnabled | プロパティ名を見てももどのような効果になるかピンと来ないと思うので fig. 7 で確認してもらうのが一番分かり易いと思います。 Mouse_Hover、Focus_Enter、Focus_Leave 時の効果が変更され、標準と比べて少し地味な効果(?)になります。(業務系システム向きかもしれません) |
RippleOnFocusEnabled | Focus_Enter 時に背景に波紋が広がるような描画効果を有効にする / しないを設定します。このプロパティも fig. 7 で確認してもらうのが一番分かり易いと思います。 |
fig. 8 は残りのプロパティを変更した場合の効果です。
fig. 7 で紹介したプロパティは見た目の変更に関係するものが多かったと思いますが、fig. 8 はどちらかと言うと機能を追加する系の設定が多いと思います。
TextFieldAssist プロパティ | 内容 |
---|---|
HasClearButton | スマホアプリ等でもよく見かける入力テキストをクリアするためのボタンの表示する / しない を設定します。 fig. 8 ではマウスで操作していますが Tab キーでもフォーカスがクリアボタンへ移動します。 |
SuffixText | Suffix として表示する文字列を設定します。単位等を表示したい時には良いと思います。 設定した文字列は HintOpacity に設定した透明度で描画されるようです。 |
DecorationVisibility | このプロパティも文章では説明しずらいので fig 8 を確認してください。 既定では Focus_Enter 時に枠線が強調表示されますが、Hidden に設定すると強調表示されなくなります。(見た目的に地味になるので業務系システム向きかもしれません) |
TextBoxViewMargin | fig. 7、8 で対象にしていませんが、TextBox 入力エリアのマージンを設定します。 |
TextFieldCornerRadius | fig. 7、8 で対象にしていませんが、境界線を表示した場合のコーナー半径を設定します。 |
UnderlineBrush | 下線部のブラシを設定します。 |
UnderlineCornerRadius | 未確認。 |
又、fig. 8 には含めていませんが SuffixText を設定してコントロール幅を超える文字列を入力した場合、fig. 9 のように SuffixText の部分は保護され入力テキストで上書きされるようなことはありません。又、SuffixText 自体にマージなどは設定できないため、入力文字列と間隔を空けたい場合は SuffixText の先頭へスペースなどを設定して調整する必要があります。
fig. 7、8 ではプロパティを個別に設定していますが、全て同時に設定することもできます。fig. 9 では SuffixText と HasClearButton を同時に設定した場合の表示位置も確認できます。
TextFieldAssist は XAML を src. 3 のように指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | <UserControl x:Class="MaterialDesignControls.TextBoxes.TextBoxPanel" ~ 略 ~ xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes" ~ 略 ~ prism:ViewModelLocator.AutoWireViewModel="True"> <Grid> ~ 略 ~ <Grid Panel.ZIndex="1"> <Grid.RowDefinitions> <RowDefinition Height="0.35*"/> <RowDefinition Height="0.65*"/> </Grid.RowDefinitions> ~ 略 ~ <Grid Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="0.33*"/> <ColumnDefinition Width="0.33*"/> <ColumnDefinition Width="0.34*"/> </Grid.ColumnDefinitions> <StackPanel Grid.Column="0" Orientation="Vertical" Margin="20, 0, 0, 0"> ~ 略 ~ <TextBox Margin="0, 70, 0, 0" TextWrapping="Wrap" md:HintAssist.Hint="ヒントプロパティ" md:HintAssist.HelperText="Style:MaterialDesignOutlinedTextFieldTextBox" md:HintAssist.FloatingScale="0.7" md:HintAssist.HintOpacity="0.7" md:HintAssist.IsFloating="{Binding ElementName=IsFlotingToggle, Path=IsChecked, Mode=OneWay}" md:TextFieldAssist.DecorationVisibility="{Binding ElementName=visibilityCombo, Path=SelectedValue, Mode=OneWay}" md:TextFieldAssist.HasClearButton="{Binding ElementName=HasClearButtonToggle, Path=IsChecked, Mode=OneWay}" md:TextFieldAssist.HasFilledTextField="{Binding ElementName=HasFilledToggle, Path=IsChecked, Mode=OneWay}" md:TextFieldAssist.HasOutlinedTextField="{Binding ElementName=HasOutlinedToggle, Path=IsChecked, Mode=OneWay}" md:TextFieldAssist.NewSpecHighlightingEnabled="{Binding ElementName=NewSpecHighlightingEnabledToggle, Path=IsChecked, Mode=OneWay}" md:TextFieldAssist.RippleOnFocusEnabled="{Binding ElementName=RippleOnFocusEnabledToggle, Path=IsChecked, Mode=OneWay}" md:TextFieldAssist.SuffixText="{Binding ElementName=SuffixText, Path=Text, Mode=OneWay}" Style="{StaticResource MaterialDesignOutlinedTextFieldTextBox}" /> </StackPanel> ~ 略 ~ <StackPanel Grid.Column="2" Orientation="Vertical" Margin="20, 0, 0, 0"> <StackPanel Orientation="Horizontal" Margin="0, 20, 0, 0" HorizontalAlignment="Center"> <TextBlock Text="Floting ヒント有効/無効" /> <ToggleButton Name="IsFlotingToggle" IsChecked="True" Margin="13, 0, 0, 0" /> </StackPanel> <GroupBox Header="TextFieldAssist" Margin="0, 20, 10, 0"> <StackPanel Orientation="Vertical"> <StackPanel Orientation="Horizontal"> <TextBlock Text="HasOutlinedTextField" /> <ToggleButton Name="HasOutlinedToggle" IsChecked="False" Margin="61, 0, 0, 0" /> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0, 8, 0, 0"> <TextBlock Text="HasFilledTextField" /> <ToggleButton Name="HasFilledToggle" IsChecked="False" Margin="78, 0, 0, 0" /> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0, 8, 0, 0"> <TextBlock Text="NewSpecHighlightingEnabled" /> <ToggleButton Name="NewSpecHighlightingEnabledToggle" IsChecked="False" Margin="15, 0, 0, 0" /> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0, 8, 0, 0"> <TextBlock Text="RippleOnFocusEnabled" /> <ToggleButton Name="RippleOnFocusEnabledToggle" IsChecked="False" Margin="51, 0, 0, 0" /> </StackPanel> <StackPanel Orientation="Horizontal" Margin="0, 8, 0, 0"> <TextBlock Text="HasClearButton" /> <ToggleButton Name="HasClearButtonToggle" IsChecked="False" Margin="90, 0, 0, 0" /> </StackPanel> <TextBox Name="SuffixText" md:HintAssist.Hint="SuffixText" md:HintAssist.HintOpacity="0.8" Style="{StaticResource MaterialDesignFloatingHintTextBox}"/> <ComboBox Name="visibilityCombo" Margin="0, 10, 0, 0" md:HintAssist.Hint="DecorationVisibility" md:HintAssist.HintOpacity="0.8" ItemsSource="{Binding Source={hg:EnumBindingSource EnumType={x:Type Visibility}}}" SelectedIndex="0" Style="{StaticResource MaterialDesignFloatingHintComboBox}"/> </StackPanel> </GroupBox> </StackPanel> </Grid> </Grid> </Grid> </UserControl> |
TextFieldAssist の使用は必須ではありませんが、HasClearButton や SuffixText 等は UX を高めるのに良いと思います。
Material Design In XAML Toolkit の ErrorTemplate
Material Design In XAML Toolkit の Style には標準のスタイルだけでなく fig. 7 のような Validation 用の ErrorTemplate も含まれています。
Error Template はコントロールの Style にデフォルトで組み込まれているので、src. 4 のように バインディング先の VM へ Validation を設定するだけで良く XAML 側の変更は不要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | using System.ComponentModel.DataAnnotations; using HalationGhost.WinApps; using Reactive.Bindings; using Reactive.Bindings.Extensions; namespace MaterialDesignControls.TextBoxes { /// <summary>TextBoxパネルのViewModelを表します。</summary> public class TextBoxPanelViewModel : HalationGhostViewModelBase { /// <summary>MaterialDesignTextBoxスタイル(TextBoxデフォルトスタイル)の入力文字列を取得・設定します。</summary> [Required(ErrorMessage = "必須入力です")] public ReactiveProperty<string> NormalText { get; } ~ 略 ~ /// <summary>コンストラクタ。</summary> public TextBoxPanelViewModel() { var mode = ReactivePropertyMode.Default | ReactivePropertyMode.IgnoreInitialValidationError; this.NormalText = new ReactiveProperty<string>(string.Empty, mode: mode) .SetValidateAttribute(() => this.NormalText) .AddTo(this.disposable); ~ 略 ~ } } } |
src. 4 では MaterialDesignTextBox Style(デフォルトスタイル)の TextBox にバインドするプロパティのみ残していますが、他の TextBox も同じ Validation を設定しています。
WPF の Validation については WPF Prism episode: 9 で紹介しています。
又、fig. 7 の ErrorTemplate の表示をカスタイマイズするため Material Design In XAML Toolkit には ValidationAssist 添付プロパティが含まれています。
ValidationAssist を使用した ErrorTemplate のカスタマイズ
Material Design In XAML Toolkit の ValidationAssist を添付すると fig. 8 のように Validation の ErrorTemplate をカスタマイズできます。
fig. 8 は以下の ValidationAssist プロパティを変更しています。
ValidationAssist プロパティ | 内容 |
---|---|
Background | エラーメッセージの背景を塗りつぶすブラシを指定します。 左上の TextBox のみ『Yellow』を設定しています。(他の TextBox は未設定) |
FontSize | エラーメッセージのフォントサイズを指定します。 左上の TextBox のみ『15』を設定しています。(他の TextBox は未設定) |
OnlyShowOnFocus | true を設定するとエラーのあるコントロールがフォーカスを受け取った場合のみエラーメッセージが表示されるようになります。フォーカスを喪失すると fig. 8 の通りエラーメッセージが消え、元の HelperText が表示されます。 |
Suppress | true に設定するとフォーカスの有無にかかわらずエラーメッセージが表示されなくなります。 |
HorizontalAlignment | エラーメッセージの水平位置を『Center、Left、Right』から選択します。 デフォルト値は『Left』です。又、選択肢は『Stretch』も存在しますが、選択しても『Left』と同じ位置に表示されます。 |
【OnlyShowOnFocus】、【Suppress】共に true を設定した場合はエラーメッセージが表示されないタイミングはありますが、下枠線等が赤色のままなのでエラーであることは認識できると思います。
又、fig. 8 で紹介したプロパティに加えてエラーメッセージの表示場所を fig. 9 のように変更することもできます。
UsePopup プロパティが false(デフォルト値)の場合、Validation のエラーメッセージは HintAssist.HelperText の位置に表示されますが、true に設定すると PopupPlacement で指定した場所に表示することができます。
ValidationAssist プロパティ | 内容 |
---|---|
UsePopup | true に設定すると Validation のエラーメッセージを PopupPlacement で指定した位置に表示できるため HintAssist.HelperText も同時に確認できるようになります。 デフォルト値(false)では HintAssist.HelperText を上書いて描画するため、エラー時は HelperText が確認できなくなります。 |
PopupPlacement | fig. 9 のようにエラーメッセージの表示場所を以下から選択します。 Absolute:絶対座標指定(詳細不明) AbsolutePoint:絶対座標指定(詳細不明) Bottom:添付コントロールの下に表示 Center:添付垂直・水平方向中央 Custom:詳細不明 Left:添付コントロールの左に表示 Mouse:マウスの位置に表示 MousePoint:マウスの位置に表示(詳細不明) Relative:添付コントロールの左上座標に合わせて表示 RelativePoint:添付コントロールの左上座標に合わせて表示(詳細不明) Right:添付コントロールの右に表示 Top:添付コントロールの上に表示 |
fig. 9 で Absolute、AbsolutePoint を選択するとエラーメッセージが表示されていないように見えますが、実は fig. 10 のように画面左上を起点に表示されています。
PopupPlacement に Absolute 等を設定する場合、本来であれば座標を指定する必要があると思いますが、ValidationAssist にそのようなプロパティは見当たりません。この PopupPlacement は WPF 標準の System.Windows.Controls.Primitives.PlacementMode 列挙型を流用しているため、Material Design In XAML Toolkit 的に選択肢としては存在するが選択対象外項目にされていると考えられます。
ValidationAssist も他の Assist と同じく XAML は src. 5 のように指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | <UserControl x:Class="MaterialDesignControls.TextBoxes.TextBoxPanel" ~ 略 ~ xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes" ~ 略 ~ prism:ViewModelLocator.AutoWireViewModel="True"> <Grid> ~ 略 ~ <Grid Panel.ZIndex="1"> ~ 略 ~ <Grid Grid.Row="1"> ~ 略 ~ <StackPanel Grid.Column="0" Orientation="Vertical" Margin="20, 0, 0, 0"> <TextBox md:HintAssist.Hint="ヒント" ~ 略 ~ md:ValidationAssist.Background="Yellow" md:ValidationAssist.FontSize="15" md:ValidationAssist.HorizontalAlignment="{Binding ElementName=cmbHorizontalAlign, Path=SelectedValue, Mode=OneWay}" md:ValidationAssist.UsePopup="{Binding ElementName=tglUsePopup, Path=IsChecked, Mode=OneWay}" md:ValidationAssist.PopupPlacement="{Binding ElementName=cmbPopupPlacement, Path=SelectedValue, Mode=OneWay}" md:ValidationAssist.OnlyShowOnFocus="{Binding ElementName=tglOnlyShowOnFocus, Path=IsChecked, Mode=OneWay}" md:ValidationAssist.Suppress="{Binding ElementName=tglSuppress, Path=IsChecked, Mode=OneWay}" Style="{StaticResource MaterialDesignTextBox}" Text="{Binding NormalText.Value, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" /> ~ 略 ~ <GroupBox Grid.Row="1" Header="ValidationAssist" Margin="20, 20, 0, 18"> ~ 略 ~ <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center"> <TextBlock Text="OnlyShowOnFocus" VerticalAlignment="Center"/> <ToggleButton Name="tglOnlyShowOnFocus" Margin="10, 0, 0, 0" IsChecked="False" /> </StackPanel> <StackPanel Orientation="Horizontal" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Center"> <TextBlock Text="Suppress" VerticalAlignment="Center"/> <ToggleButton Name="tglSuppress" Margin="18, 0, 0, 0" IsChecked="False" /> </StackPanel> <ComboBox Name="cmbHorizontalAlign" Grid.Column="0" Grid.Row="1" md:HintAssist.Hint="HorizontalAlignment" md:HintAssist.HintOpacity="0.8" ItemsSource="{Binding Source={hg:EnumBindingSource EnumType={x:Type HorizontalAlignment}}}" SelectedIndex="0" Style="{StaticResource MaterialDesignFloatingHintComboBox}" /> <StackPanel Orientation="Horizontal" Grid.Row="1" Grid.Column="1" HorizontalAlignment="Center"> <TextBlock Text="UsePopup" VerticalAlignment="Center"/> <ToggleButton Name="tglUsePopup" Margin="10, 0, 0, 0" IsChecked="False" /> </StackPanel> <ComboBox Name="cmbPopupPlacement" Grid.Column="2" Grid.Row="1" IsEnabled="{Binding ElementName=tglUsePopup, Path=IsChecked, Mode=OneWay}" md:HintAssist.Hint="PopupPlacemen" md:HintAssist.HintOpacity="0.8" ItemsSource="{Binding Source={hg:EnumBindingSource EnumType={x:Type PlacementMode}}}" SelectedIndex="2" Style="{StaticResource MaterialDesignFloatingHintComboBox}" /> </Grid> </GroupBox> </Grid> ~ 略 ~ </Grid> </Grid> </Grid> </UserControl> |
本来、ErrorTemplate は WPF Prism episode: 10 で紹介したように ErrorTemplate 自体を上書きするのが WPF 標準の方法ですが、Material Design In XAML Toolkit や MahApps.Metro はデザインフレームワークと言う位置付けのため、ErrorTemplate を差し替えるのではなくフレームワークが用意している方法を選択すべきだと思います。
Livet の生みの親である尾上さんがツイート されていましたが、フレームワークと言う位置付けのプロダクトであれば、用意されているシナリオに沿った方法を選択する方が良いと管理人は考えています。
フレームワークとライブラリはそれっぽいもの作っている時意識しないと詰む。フレームワークはただ便利機能が生えてればいい話じゃなくてシナリオに沿った機能提供するべきだし、ライブラリは様々な用途に使えるべきだし。
— 尾上 雅則 (@ugaya40) April 27, 2020
おそらく ErrorTemplate を丸ごと差し替える事も可能だとは思いますが、結構大変な作業になりそうな気はする(管理人は試していません)のでやはり ValidationAssist で変更できる範囲だけカスタマイズする方法を選択すべきだと思います。
Material Design In XAML Toolkit で TextBox へ設定できる項目の紹介は以上で、以降は MahApps.Metro の設定項目を紹介します。
MahApps.Metro の TextBox
冒頭で紹介した通り、WPF Prism episode: 19 の通りにインストールした場合、WPF 標準コントロールは Material Design In XAML Toolkit の Style が適用されるようになりますが、Style を指定すれば fig. 11 のように MahApps.Metro の Style も併用できます。(併用することの是非は置いておきます)
fig. 11 は MahApps.Metro に含まれる 3 つの TextBox Style を設定しています。見た目の違いを分かり易くするため Material Design In XAML Toolkit の MaterialDesignFloatingHintTextBox Style を設定した TextBox も右下に配置していて、全ての TextBox は MahApps.Metro の MetroHeader コントロールと一緒に配置しています。
MahApps.Metro の TextBox Style
MahApps.Metro には 以下の TextBox Style が含まれていて、fig. 11 では IsEnabled と IsReadOnly を変更した場合の Style も同時に確認できます。
MahApps.Metro TextBox Style | 内容 |
---|---|
MetroTextBox | MahApps.Metro の標準 TextBox Style。 |
ButtonCommandMetroTextBox | バインド可能なボタンを表示する TextBox Style。 |
SearchMetroTextBox | ButtonCommandMetroTextBox と同じだが、検索用のアイコンが設定済み。 |
MahApps.Metro は元々 Metro(Windows 8 スタイル)の見た目を再現するデザインフレームワークなので、コントロールの外観は標準コントロールとほとんど変わらないデザインになっています。
但し、上で紹介している Style 名には不思議な点があって GitHub のソースコード に書かれているリソースキー名は『MahApps.Styles.TextBox』ですが実際に指定するスタイル名や IntelliSense の候補に出て来るのは『MetroTextBox』です。どこかで『MahApps.Styles.』を『Metro』に置換するような処理があるのかと検索してみても見当たりませんし、海外のサイトでも普通に『MetroTextBox』と書かれているので当たり前のことなのかもしれません… 現時点ではよく分かりませんが、何か分かれば又追記したいと思います。
そして全 TextBox を囲んでいる MetroHeader コントロールは GroupBox を継承したコントロールなので、Header プロパティへ文字列を設定すれば fig. 11 のようにコントロールの上位置にキャプションを表示したのと同じような見た目になります。
MahApps.Metro にも Material Design In XAML Toolkit の Assist と同じく Helper と呼ばれる添付プロパティが含まれています。
TextBox の機能を拡張する MahApps.Metro の TextBoxHelper
MahApps.Metro の TextBoxHelper を添付すると fig. 12 のような動作が可能になります。
基本的に TextBoxHelper は MahApps.Metro に含まれる TextBox 用の Style を前提にしているため、Material Design In XAML Toolkit 等の別ライブラリ Style を指定した場合は無視されるようなので Material Design In XAML Toolkit の Style を適用した TextBox は Material Design In XAML Toolkit の Assist を添付しています。
TextBoxHelper プロパティ | 内容 |
---|---|
Watermark | ウォーターマーク用の文字列を指定します。 |
AutoWatermark | true を設定しても変化がありませんし、ウォーターマークは Watermark プロパティを設定すれば表示されるので何のために用意されているのか調べてもイマイチ分かりませんでした。とりあえず設定する必要は無いようです。 |
SelectAllOnFocus | true を設定するとフォーカス取得時にテキストが全選択されます。 但し、fig. 12 の通り IsMonitoring が true に設定されている必要があります。 このプロパティだけは Material Design In XAML Toolkit の Style を設定していても反映されます。 |
IsMonitoring | true に設定した場合のみ HasText、SelectAllOnFocus プロパティが有効になります。 |
ClearTextButton | 入力テキストをクリアするためのボタンを表示します。 |
残りのプロパティを変更した場合の動作が fig. 13 です。
fig. 13 では以下のプロパティを操作しています。
TextBoxHelper プロパティ | 内容 |
---|---|
UseFloatingWatermark | Material Design In XAML Toolkit の HintAssist にもあった Floating するウォーターマークですが、表示位置などのカスタマイズはできません。 |
TextButton | TextBox 内にボタンを表示する / しないを設定します。 但し、実際にボタンが表示されるのは Style に ButtonCommandMetroTextBox か SearchMetroTextBox が設定されている場合だけのようです。 残念なのは ClearTextButton とは併用できず、fig. 13 のように ClearTextButton に true を設定するとボタンはテキストをクリアするためのボタンになり任意のコマンド等は設定できなくなります。 |
TextBoxHelper を添付した XAML が src. 6 です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | <UserControl x:Class="MaterialDesignControls.MetroTextBoxes.MetroTextBoxPanel" ~ 略 ~ xmlns:metro="http://metro.mahapps.com/winfx/xaml/controls" ~ 略 ~ prism:ViewModelLocator.AutoWireViewModel="True"> <Grid> ~ 略 ~ <Grid Panel.ZIndex="1"> ~ 略 ~ <StackPanel Orientation="Vertical" Grid.Column="0" Margin="10, 20, 10, 0"> <metro:MetroHeader Header="MetroTextBox"> <TextBox metro:TextBoxHelper.AutoWatermark="{Binding ElementName=tglAutoWatermark, Path=IsChecked, Mode=OneWay}" metro:TextBoxHelper.Watermark="ウォーターマーク" metro:TextBoxHelper.ClearTextButton="{Binding ElementName=tglClearTextButton, Path=IsChecked, Mode=OneWay}" metro:TextBoxHelper.IsWaitingForData="{Binding ElementName=tglIsWaitingForData, Path=IsChecked, Mode=OneWay}" metro:TextBoxHelper.IsMonitoring="{Binding ElementName=tglIsMonitoring, Path=IsChecked, Mode=OneWay}" metro:TextBoxHelper.SelectAllOnFocus="{Binding ElementName=tglSelectAll, Path=IsChecked, Mode=OneWay}" metro:TextBoxHelper.TextButton="{Binding ElementName=tglTextButton, Path=IsChecked, Mode=OneWay}" metro:TextBoxHelper.UseFloatingWatermark="{Binding ElementName=tglUseFloatingWatermark, Path=IsChecked, Mode=OneWay}" metro:TextBoxHelper.WatermarkAlignment="Center" IsEnabled="{Binding ElementName=tglIsEnabled, Path=IsChecked, Mode=OneWay}" IsReadOnly="{Binding ElementName=tglIsReadOnly, Path=IsChecked, Mode=OneWay}" Style="{StaticResource MetroTextBox}" Text="{Binding NormalText.Value, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> </metro:MetroHeader> ~ 略 ~ </Grid> </Grid> </UserControl> |
一覧で紹介していませんが、TextBoxHelper には『Button』から始まるプロパティがいくつか定義されていて、その中の ButtonCommand プロパティをバインドすると TextButton を Click した際の処理を記述することができます。
又、ButtonContent プロパティで TextButton のアイコンを変更することができ、画像を表示したい場合は Image コントロールを指定するようです。詳しくは MahApps.Metro の Issue #849 を見てください。
ButtonContent プロパティに文字列を指定した場合は Marlett フォントが割り当てられている文字を指定しないと無視されるようなので、Marlett フォントの一覧 等を見て指定してください。
元は TextBoxHelper の全プロパティを紹介するつもりでしたが、設定しても反映されない項目があったりで調べる気が薄れてしまったのと、一応重要そうなプロパティは紹介したつもりなのでここまでにします。
MahApps.Metro の ErrorTemplate
最後に MahApps.Metro の Style に含まれる ErrorTemplate も紹介します。
Material Design In XAML Toolkit の ErrorTemplate を紹介した時と同じく DataAnnotations の Required を設定すると fig. 14 のような ErrorTemplate で描画されます。
MahApps.Metro には Material Design In XAML Toolkit の ValidationAssist のようなものは現時点では確認できていないため、おそらく fig. 14 の表示をカスタマイズするには ErrorTemplate を差し替える事しか無い気がします…
どうしても MahApps.Metro の Style を使いたい人には「頑張ってください」としか言えませんが、もし簡単にカスタマイズできる方法が分かれば記事にするかもしれません。
そして今回のソースコードもいつものように GitHub リポジトリ にコミットしています。
かなり長い記事になってしまいましたが、MahApps.Metro と Material Design In XAML Toolkit で TextBox を使用する場合の設定項目の紹介はここまでになります。
今回のエントリはコロナ騒動の真っ只中で在宅勤務かつ GW を間に挟んだにもかかわらず予想以上に本業が忙しく休日勤務等も多かったので 1 か月以上空いてしまいましたが、今後はしばらく今回のエントリのように対象のコントロールを 1 つ(複数の場合もあると思います)取り上げて紹介していく予定なのでお付き合い頂けるとありがたいです。