Minus is red.

仕事上でよく扱うのが数値(Only)の項目。なので、数値だけのTextBoxだってやれるはずだ(四)みたいなのを考える訳なんだけども。
この数値系でよくあるのが、「マイナス値は赤文字で表示したい」というやつ。仕事上、マイナスになるような数値はあまりよろしくないことが多い。赤字だったり返品だったりする。且つあまり頻度の高いものではない。なので、通常の正の数とは明確に違うように視覚的に見えるようにしておきたい。
こういうネタは、視覚的(ビュー)な話なので、WPFでやるなら、コードビハインド(やViewModel)ではなく、XAML内で片付ける(片付けたい)話になると思った。
そこで、Style.Triggerの出番となるわけだ。わけなんだけど。今回の動作を言葉にすると、

「Textプロパティの値が、マイナスの(0より小さい)時、文字色(Foreground)を赤色にする」

となる。この「0より小さい」というのが曲者であった。普通、Style.Triggerは、

「対象のプロパティ値が、指定した値に等しい時、○○する」

という動作になるので、この「0より小さい時」というのを「0より小さいという事が真(true)の時」みたいな扱いにする必要がある。まあ、こういう時に頼りになるのが、Converterだ。Converterは、ある値(バインディングソース)を別の値に変換する(ターゲットに渡す)のに使われる。今回は、「数値(decimal)→bool値(マイナスならTrue)」となるConverterクラスを作る事が目的なんだけど、どうせなら、他の不等号式にも応用できるようにする。
using System;
using System.Windows.Data;
using System.Text.RegularExpressions;

namespace TawamureDays.Converters {

/// <summary>
/// 不等号による比較をbool値に変換する為のコンバータクラス
/// </summary>
[ValueConversion(typeof(object), typeof(bool))]
public class ExpressionToBoolConverter : IValueConverter {

#region コンストラクタ

/// <summary>
/// コンストラクタ
/// </summary>
public ExpressionToBoolConverter() {
}

#endregion

#region IValueConverter メンバー

/// <summary>
/// 値を変換します。
/// </summary>
/// <param name="value">バインディング ソースによって生成された値。</param>
/// <param name="targetType">バインディング ターゲット プロパティの型。</param>
/// <param name="parameter">使用するコンバーター パラメーター</param>
/// <param name="culture">コンバーターで使用するカルチャ。</param>
/// <returns>変換された値</returns>
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture) {

if (parameter == null || value == null) {
//比較される値、条件となる式がない時はfalseを返します。
return false;
}

//パラメータに不等号表現(<0等)が設定される前提(仕様)。
var param = Utils.ToString(parameter);

if (Utils.IsEmptyOrWhiteSpace(param)) {
//判断(比較する)情報がない
return false;
}

var targetValue = decimal.Zero;

if (!decimal.TryParse(Utils.ToString(value), out targetValue)) {
//バインディングソースの値を数値に変換できないときはfalseを返す
//開発時は例外をスローする方が良いかもしれないが、デザインビューを壊す可能性もある。
return false;
}

//正規表現を使って、不等号式を分解します。
var match = ExpressionToBoolConverter.INEQUALITY_SIGN_REGEX.Match(param);

if (match != null && match.Groups.Count >= 3) {
//不等号部分(>や<, !=, etc.)
var expression = match.Groups[1].Value.Trim();
//比較値
var compareValue = decimal.Zero;

if (!decimal.TryParse(
match.Groups[match.Groups.Count - 1].Value.Trim(),
out compareValue)) {
//比較値をdecimalに変換できない時もfalseを返す
//開発時は例外をスローしても良いけど、以下略
return false;
}

try {
//四則演算+数値でバインドした値と計算します。
switch (expression) {
case GREATER_EQUALS:
return targetValue >= compareValue;
case GREATER_THAN:
return targetValue > compareValue;
case LESS_EQUALS:
return targetValue <= compareValue;
case LESS_THAN:
return targetValue < compareValue;
case NOT_EQUALITY1:
case NOT_EQUALITY2:
return targetValue != compareValue;
default:
//不明な不等式
//throw new NotSupportedException("");
return false;
}

} catch (Exception) {
//本来(開発)なら、ログを出力するか、例外を発生させる
return false;
}
}

//コンバータパラメータを想定どおりに分解できない。
//開発中は例外でも良いかも
//throw new ArgumentException("");
return false;
}

/// <summary>
/// 値を変換します。
/// </summary>
/// <param name="value">バインディング ターゲットによって生成される値。</param>
/// <param name="targetType">変換後の型。</param>
/// <param name="parameter">使用するコンバーター パラメーター</param>
/// <param name="culture">コンバーターで使用するカルチャ。</param>
/// <returns>変換された値</returns>
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture) {
//何もしない
return Binding.DoNothing;
}

#endregion

#region 内部定数

/// <summary>正規表現インスタンス</summary>
private static readonly Regex INEQUALITY_SIGN_REGEX =
new Regex(@"(>=|<=|<|>|!=)\s{0,}(.{1,})");

/// <summary>不等号(≧)</summary>
private const string GREATER_EQUALS = ">=";

/// <summary>不等号(>)</summary>
private const string GREATER_THAN = ">";

/// <summary>不等号(≦)</summary>
private const string LESS_EQUALS = "<=";

/// <summary>不等号(<)</summary>
private const string LESS_THAN = "<";

/// <summary>不等式(!=)</summary>
private const string NOT_EQUALITY1 = "!=";

/// <summary>不等式(x<>a)</summary>
private const string NOT_EQUALITY2 = "<>";

#endregion
}
}

このコンバータを使って、「マイナスなら文字色を赤色に」なるように組み立てる。
<StackPanel>
<TextBox x:Name="TextA" MaxLength="10">
<TextBox.Resources>
<tw:ExpressionToBoolConverter x:Key="ExpressionToBoolConverter"/>
<Style TargetType="TextBox">
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=TextA,
Converter={StaticResource ExpressionToBoolConverter},
ConverterParameter=&lt;0}"
Value="True">
<Setter Property="Foreground" Value="Red"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Resources>
</TextBox>
</StackPanel>
ConverterParameter内でも、不等式(<や>)をそのまま使うとエラーになる。エスケープしてやる必要がある。
実行結果:
20130305_1
20130305_2
20130305_3
まあ、こんなもんだろう。
メモ:
・decimalだけでなく、文字列やDateTime等で比較したいときはもう一工夫が必要となる。
・Bindingが必要になるので、DataTriggerで使えても、Triggerでは使えない。
・ConverterParameterにBindingを設定できないので、比較値を動的に指定できない。
スポンサーサイト
当サイトは基本をすっ飛ばしてます。基本文法等は、@ITをどうぞ
カテゴリー: WPF4 | コメント: 2 | トラックバック: 0


この記事へのコメント

No title
ConverterParameterにBindingを設定できないとのことですが、コードビハインドとかでも無理でしょうか?通貨毎に小数桁数の異なる外貨額をDataGridに表示したいのですが
Re: No title
通貨毎に小数桁数の異なる外貨額をDataGridに表示したいのですが
手段としては、MultiBindingがあるでしょうか。外貨額、通貨(コード?)をバインディングして、MultiConverterで変換させるといったところでしょうか。
<DataGridTextColumn>
<DataGridTextColumn.Binding>
<MultiBinding Converter="{StaticResource PriceToFormatConverter}">
<Binding Path="ForeignPrice"/>
<Binding Path="Currency"/>
</MultiBinding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
MultiConverterは自作する必要があります。

コメントの投稿

非公開コメント


サイドバー背後固定表示サンプル

当ブログに書かれたソースコードは流用自由です。

バグ、スペルミス等はありうる事です。

ご利用の際は自己責任でお願いしますm(_ _)m