InputMan for WPF コントロールの背景色を変更する。
昨日「サードパーティ製 WPF 数値コントロールのカスタマイズ」というエントリを書いたわけですが、実は昨日書いてなかった話が一つあって、InputMan for WPF のコントロールが編集中だと背景色が変更できないという問題が起きてました。ホントは以下の図のとおりに実行したいのですが、これが出来ない(汗
私が携わっている Forms のシステムの方では InputMan の各コントロールに対してサブクラスを設け、ReadOnly なら Focus 時は LemonChiffon になるよう設定してますが、これを InputMan for WPF で実現するなら以下のようなスタイルになると思います。しかし IsReadOnly = true の場合はコントロールに反映されても、IsFocused = true の場合、Background に XAML で設定したブラシは反映されません。
<Style TargetType="im:GcTextBox"> <Style.Triggers> <Trigger Property="IsFocused" Value="true" > <Setter Property="Background" Value="LemonChiffon" /> </Trigger> <Trigger Property="IsReadOnly" Value="true" > <Setter Property="Background" Value="AliceBlue" /> </Trigger> </Style.Triggers> </Style> <Style TargetType="im:GcNumber"> <Style.Triggers> <Trigger Property="IsFocused" Value="true" > <Setter Property="Background" Value="LemonChiffon" /> </Trigger> <Trigger Property="IsReadOnly" Value="true" > <Setter Property="Background" Value="AliceBlue" /> </Trigger> </Style.Triggers> </Style> <Style TargetType="im:GcDateTime"> <Style.Triggers> <Trigger Property="IsFocused" Value="true" > <Setter Property="Background" Value="LemonChiffon" /> </Trigger> <Trigger Property="IsReadOnly" Value="true" > <Setter Property="Background" Value="AliceBlue" /> <Setter Property="DropDownButtonVisibility" Value="NotShown" /> </Trigger> </Style.Triggers> </Style>
で、色々調べてみたところ、まず IsFocused プロパティですが、コントロールに Focus が当たっても True になりません。GotFocus イベントで拾うと標準の WPF コントロールなら IsFocused プロパティが True になるのですが、InputMan のコントロールは False のままです。この挙動、どうもバグっぽいような気がします。
Private Sub TextBox1_GotFocus(sender As Object, e As RoutedEventArgs) Handles TextBox1.GotFocus If TextBox1.IsFocused Then MessageBox.Show("フォーカスがあるよー") ' これは実行されるが・・・ End If End Sub Private Sub GcTextBox1_GotFocus(sender As Object, e As RoutedEventArgs) Handles GcTextBox1.GotFocus If GcTextBox1.IsFocused Then MessageBox.Show("フォーカスがあるよー") ' こちらは実行されない・・・ End If End Sub
対処方法(2011/04/22 追記)
有難いことに GrapeCity さんから直接回答を頂きました。
InputMan for WPFでは、入力をフィールドという単位で構成している仕様上、コントロール内部に別のエディターが存在し、そのエディタがフォーカスを受けとります。そのため、コントロールのIsFocusedプロパティが意図した値を返さない場合がございます。恐れ入りますが、現時点ではこの動作は仕様となっております。
InputMan for WPFでキーボードフォーカスを持つコントロールの背景色を指定する場合には、以下のサンプルのように、IsFocused プロパティではなく IsKeyboardFocusWithin プロパティを使用ください。
このプロパティはキーボード フォーカスが要素内またはビジュアルツリーの子要素内に設定されている場合にtrueを返すため、InputMan for WPFの内部のエディタがキーボードフォーカスを受け取っていた場合でも判定することが可能です。
なるほど。以下のように IsKeyboardFocusWithin を使うと、こちらの意図したとおりの挙動になります。
<Style TargetType="im:GcTextBox"> <Style.Triggers> <Trigger Property="IsKeyboardFocusWithin" Value="true" > <Setter Property="Background" Value="LemonChiffon" /> </Trigger> <Trigger Property="IsReadOnly" Value="true" > <Setter Property="Background" Value="AliceBlue" /> </Trigger> </Style.Triggers> </Style> <Style TargetType="im:GcNumber"> <Style.Triggers> <Trigger Property="IsKeyboardFocusWithin" Value="true" > <Setter Property="Background" Value="LemonChiffon" /> </Trigger> <Trigger Property="IsReadOnly" Value="true" > <Setter Property="Background" Value="AliceBlue" /> </Trigger> </Style.Triggers> </Style> <Style TargetType="im:GcDateTime"> <Style.Triggers> <Trigger Property="IsKeyboardFocusWithin" Value="true" > <Setter Property="Background" Value="LemonChiffon" /> </Trigger> <Trigger Property="IsReadOnly" Value="true" > <Setter Property="Background" Value="AliceBlue" /> <Setter Property="DropDownButtonVisibility" Value="NotShown" /> </Trigger> </Style.Triggers> </Style>
この話、標準の ComboBox も同様で、IsEditable プロパティを True にした ComboBox の場合、IsFocused だと Background の設定がビューに反映されません。
<Style TargetType="ComboBox" > <Setter Property="IsEditable" Value="true" /> <Style.Triggers> <Trigger Property="IsFocused" Value="true"> <Setter Property="Background" Value="LemonChiffon" /> <Setter Property="InputMethod.IsInputMethodEnabled" Value="False" /> </Trigger> </Style.Triggers> </Style>
ただし、以下のように IsKeyboardFocusWithin プロパティを使ったら変更が反映されました。
<Style TargetType="ComboBox" > <Setter Property="IsEditable" Value="true" /> <Style.Triggers> <Trigger Property="IsKeyboardFocusWithin" Value="true"> <Setter Property="Background" Value="LemonChiffon" /> <Setter Property="InputMethod.IsInputMethodEnabled" Value="False" /> </Trigger> </Style.Triggers> </Style>
この辺り、まだまだ情報が少ないのが難点ですが、当ブログでは今後もこういう細かい TIPS をどんどん紹介していきたいと思います。
回避方法
(注:以下は当初の記事なので、一応参考のため残しておきますが、対応は上記 IsKeyboardFocusWithin を使ってください)
で、散々調べた結果、回避方法を見つけました。
まず、コントロールの要素にアクセスするクラスを用意します。使っているクラスは以前の記事で用意したものですが、さらに改良を加えてます。このクラスの拡張メソッド「FindChild」 はコントロールの要素を検索して返します。
using System; using System.Windows; using System.Windows.Data; using System.Windows.Media; /// <summary> /// UIElement 子要素検索ヘルパークラス /// </summary> public static class UIChildFinder { /// <summary> /// 子要素検索の拡張メソッドです。 /// </summary> public static DependencyObject FindChild (this DependencyObject reference, Type childType, string childName) { DependencyObject foundChild = null; if (reference != null) { int childrenCount = VisualTreeHelper.GetChildrenCount(reference); for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(reference, i); // 子要素が検索する方でなければ if (child.GetType() != childType) { // 再帰的に階層をたどります。 foundChild = FindChild(child, childType, childName); if (foundChild != null) break; } else if (!string.IsNullOrEmpty(childName)) { var frameworkElement = child as FrameworkElement; // 子要素の名前が検索条件に設定されてるなら if (frameworkElement != null && frameworkElement.Name == childName) { // 子要素がリクエストした名前にヒットした foundChild = child; break; } } else { // 子要素が見つかった foundChild = child; break; } } } return foundChild; } /// <summary> /// 子要素検索の拡張メソッドです。 /// </summary> public static DependencyObject FindChild (this DependencyObject reference, string childTypeName, string childName) { DependencyObject foundChild = null; if (reference != null) { int childrenCount = VisualTreeHelper.GetChildrenCount(reference); for (int i = 0; i < childrenCount; i++) { var child = VisualTreeHelper.GetChild(reference, i); // 子要素が検索する方でなければ if (child.GetType().Name != childTypeName) { // 再帰的に階層をたどります。 foundChild = FindChild(child, childType, childName); if (foundChild != null) break; } else if (!string.IsNullOrEmpty(childName)) { var frameworkElement = child as FrameworkElement; // 子要素の名前が検索条件に設定されてるなら if (frameworkElement != null && frameworkElement.Name == childName) { // 子要素がリクエストした名前にヒットした foundChild = child; break; } } else { // 子要素が見つかった foundChild = child; break; } } } return foundChild; } }
私の環境で調べたところ、 InputMan の Control を編集中は Background プロパティを設定しても View に反映されません。なぜかは判りませんが コントロールの要素で BD という名前を持つ Border を変更しなければなりません。そこで以下のように、GotFocus イベントを使ってコントロールのインスタンスにアクセスし、以下のように子要素「BD」にアクセスして、BackGround を変更します。(なぜかこっちのコードは VB だが気にしないように・・・)
Option Explicit On Option Strict On Imports System.Windows Imports GrapeCity.Windows.InputMan Imports ClassLibrary1 Public Class MainWindow Private Sub GcTextBox1_GotFocus(sender As Object, e As RoutedEventArgs) Handles GcTextBox1.GotFocus Dim border = GcTextBox1.FindChild(GetType(Border), "Bd") CType(border, Border).Background = New SolidColorBrush(Colors.LemonChiffon) End Sub Private Sub GcTextBox1_LostFocus(sender As Object, e As RoutedEventArgs) Handles GcTextBox1.LostFocus Dim border = GcTextBox1.FindChild(GetType(Border), "Bd") CType(border, Border).Background = GcTextBox1.Background End Sub Private Sub GcDateTime_GotFocus(sender As Object, e As RoutedEventArgs) Handles GcDateTime1.GotFocus Dim border = GcDateTime1.FindChild(GetType(Border), "Bd") CType(border, Border).Background = New SolidColorBrush(Colors.LemonChiffon) End Sub Private Sub GcDateTime_LostFocus(sender As Object, e As RoutedEventArgs) Handles GcDateTime1.LostFocus Dim border = GcDateTime1.FindChild(GetType(Border), "Bd") CType(border, Border).Background = GcDateTime1.Background End Sub Private Sub GcNumber_GotFocus(sender As Object, e As RoutedEventArgs) Handles GcNumber1.GotFocus Dim border = GcNumber1.FindChild(GetType(Border), "Bd") CType(border, Border).Background = New SolidColorBrush(Colors.LemonChiffon) End Sub Private Sub GcNumber_LostFocus(sender As Object, e As RoutedEventArgs) Handles GcNumber1.LostFocus Dim border = GcNumber1.FindChild(GetType(Border), "Bd") CType(border, Border).Background = GcNumber1.Background End Sub End Class
実行すると、こうなります。
ちと手間食いましたが、やっと仕様を実現できました。実コードでは添付ビヘイビアにして対応するつもりです。でも疲れた・・・