質問
WPF アプリケーションで数値の入力をどのように処理していますか?
NumericUpDown コントロールがない場合、TextBox を使用し、その PreviewKeyDown イベントを以下のコードで処理していますが、かなり見苦しいです。
サードパーティのコントロールに依存せずにユーザーから数値データを取得する、より適切な方法を見つけた人はいるでしょうか?
private void NumericEditPreviewKeyDown(object sender, KeyEventArgs e)
{
bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9) || e.Key == Key.Decimal;
bool isNumeric = (e.Key >= Key.D0 && e.Key <= Key.D9) || e.Key == Key.OemPeriod;
if ((isNumeric || isNumPadNumeric) && Keyboard.Modifiers != ModifierKeys.None)
{
e.Handled = true;
return;
}
bool isControl = ((Keyboard.Modifiers != ModifierKeys.None && Keyboard.Modifiers != ModifierKeys.Shift)
|| e.Key == Key.Back || e.Key == Key.Delete || e.Key == Key.Insert
|| e.Key == Key.Down || e.Key == Key.Left || e.Key == Key.Right || e.Key == Key.Up
|| e.Key == Key.Tab
|| e.Key == Key.PageDown || e.Key == Key.PageUp
|| e.Key == Key.Enter || e.Key == Key.Return || e.Key == Key.Escape
|| e.Key == Key.Home || e.Key == Key.End);
e.Handled = !isControl && !isNumeric && !isNumPadNumeric;
}
解決
どうでしょうか:
protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
e.Handled = !AreAllValidNumericChars(e.Text);
base.OnPreviewTextInput(e);
}
private bool AreAllValidNumericChars(string str)
{
foreach(char c in str)
{
if(!Char.IsNumber(c)) return false;
}
return true;
}
他のヒント
これが私のやり方です。正規表現を使用して、ボックスに表示されるテキストが数値かどうかをチェックします。
Regex NumEx = new Regex(@"^-?\d*\.?\d*$");
private void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (sender is TextBox)
{
string text = (sender as TextBox).Text + e.Text;
e.Handled = !NumEx.IsMatch(text);
}
else
throw new NotImplementedException("TextBox_PreviewTextInput Can only Handle TextBoxes");
}
WPF と Silverlight には、これを行うためのはるかに優れた方法が存在します。コントロールがプロパティにバインドされている場合は、バインディング ステートメントを少し変更するだけで済みます。バインディングには以下を使用します。
<TextBox Text="{Binding Number, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"/>
これはカスタム プロパティでも使用できることに注意してください。ボックス内の値が無効な場合は例外をスローするだけで、コントロールが赤い枠線で強調表示されます。赤い枠の右上をクリックすると、例外メッセージがポップアップ表示されます。
添付プロパティを使用して、ユーザーが上下キーを使用してテキスト ボックス内の値を変更できるようにしました。それを使用するには、単に使用します
<TextBox local:TextBoxNumbers.SingleDelta="1">100</TextBox>
これは実際には、この質問で言及されている検証の問題には対処していませんが、数値の上下コントロールがないことについて私が行っていることに対処しています。少し使ってみると、古い数値の上下コントロールよりもむしろ気に入っているかもしれません。
コードは完璧ではありませんが、処理する必要があるケースは処理できます。
Up
矢印、Down
矢印Shift + Up
矢印、Shift + Down
矢印Page Up
,Page Down
- バインディング
Converter
テキストプロパティで
Code behind
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace Helpers
{
public class TextBoxNumbers
{
public static Decimal GetSingleDelta(DependencyObject obj)
{
return (Decimal)obj.GetValue(SingleDeltaProperty);
}
public static void SetSingleDelta(DependencyObject obj, Decimal value)
{
obj.SetValue(SingleDeltaProperty, value);
}
// Using a DependencyProperty as the backing store for SingleValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SingleDeltaProperty =
DependencyProperty.RegisterAttached("SingleDelta", typeof(Decimal), typeof(TextBoxNumbers), new UIPropertyMetadata(0.0m, new PropertyChangedCallback(f)));
public static void f(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
TextBox t = o as TextBox;
if (t == null)
return;
t.PreviewKeyDown += new System.Windows.Input.KeyEventHandler(t_PreviewKeyDown);
}
private static Decimal GetSingleValue(DependencyObject obj)
{
return GetSingleDelta(obj);
}
private static Decimal GetDoubleValue(DependencyObject obj)
{
return GetSingleValue(obj) * 10;
}
private static Decimal GetTripleValue(DependencyObject obj)
{
return GetSingleValue(obj) * 100;
}
static void t_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
TextBox t = sender as TextBox;
Decimal i;
if (t == null)
return;
if (!Decimal.TryParse(t.Text, out i))
return;
switch (e.Key)
{
case System.Windows.Input.Key.Up:
if (Keyboard.Modifiers == ModifierKeys.Shift)
i += GetDoubleValue(t);
else
i += GetSingleValue(t);
break;
case System.Windows.Input.Key.Down:
if (Keyboard.Modifiers == ModifierKeys.Shift)
i -= GetDoubleValue(t);
else
i -= GetSingleValue(t);
break;
case System.Windows.Input.Key.PageUp:
i += GetTripleValue(t);
break;
case System.Windows.Input.Key.PageDown:
i -= GetTripleValue(t);
break;
default:
return;
}
if (BindingOperations.IsDataBound(t, TextBox.TextProperty))
{
try
{
Binding binding = BindingOperations.GetBinding(t, TextBox.TextProperty);
t.Text = (string)binding.Converter.Convert(i, null, binding.ConverterParameter, binding.ConverterCulture);
}
catch
{
t.Text = i.ToString();
}
}
else
t.Text = i.ToString();
}
}
}
ここで回答としてマークされた返信を、LINQ 式を使用して基本的に 2 行に簡略化することにしました。
e.Handled = !e.Text.All(Char.IsNumber);
base.OnPreviewTextInput(e);
カスタムを使用しています ValidationRule
テキストが数値かどうかを確認します。
public class DoubleValidation : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value is string)
{
double number;
if (!Double.TryParse((value as string), out number))
return new ValidationResult(false, "Please enter a valid number");
}
return ValidationResult.ValidResult;
}
次に、バインドすると、 TextBox
数値プロパティに新しいカスタム クラスを追加します。 Binding.ValidationRules
コレクション。以下の例では、検証ルールは毎回チェックされます。 TextBox.Text
変化します。
<TextBox>
<TextBox.Text>
<Binding Path="MyNumericProperty" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:DoubleValidation/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
PreviewKeyDown イベントではなく KeyDown イベントを使用してみてはいかがでしょうか。そこで無効な文字を停止できますが、制御文字はすべて受け入れられます。これは私にとってはうまくいくようです:
private void NumericKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
bool isNumPadNumeric = (e.Key >= Key.NumPad0 && e.Key <= Key.NumPad9);
bool isNumeric =((e.Key >= Key.D0 && e.Key <= Key.D9) && (e.KeyboardDevice.Modifiers == ModifierKeys.None));
bool isDecimal = ((e.Key == Key.OemPeriod || e.Key == Key.Decimal) && (((TextBox)sender).Text.IndexOf('.') < 0));
e.Handled = !(isNumPadNumeric || isNumeric || isDecimal);
}
public class NumericTextBox : TextBox
{
public NumericTextBox()
: base()
{
DataObject.AddPastingHandler(this, new DataObjectPastingEventHandler(CheckPasteFormat));
}
private Boolean CheckFormat(string text)
{
short val;
return Int16.TryParse(text, out val);
}
private void CheckPasteFormat(object sender, DataObjectPastingEventArgs e)
{
var isText = e.SourceDataObject.GetDataPresent(System.Windows.DataFormats.Text, true);
if (isText)
{
var text = e.SourceDataObject.GetData(DataFormats.Text) as string;
if (CheckFormat(text))
{
return;
}
}
e.CancelCommand();
}
protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
if (!CheckFormat(e.Text))
{
e.Handled = true;
}
else
{
base.OnPreviewTextInput(e);
}
}
}
さらに、適切な依存関係プロパティを提供することで、解析動作をカスタマイズできます。
これらの回答のいくつかから得たアイデアを組み合わせて、次の NumericTextBox を作成しました。
- 小数を処理します
- 入力された「 - 」または「」を確保するための基本的な検証を行います。有効です
- 貼り付けられた値を処理します
他に含めるべきロジックを思いついた場合は、お気軽に更新してください。
public class NumericTextBox : TextBox
{
public NumericTextBox()
{
DataObject.AddPastingHandler(this, OnPaste);
}
private void OnPaste(object sender, DataObjectPastingEventArgs dataObjectPastingEventArgs)
{
var isText = dataObjectPastingEventArgs.SourceDataObject.GetDataPresent(System.Windows.DataFormats.Text, true);
if (isText)
{
var text = dataObjectPastingEventArgs.SourceDataObject.GetData(DataFormats.Text) as string;
if (IsTextValid(text))
{
return;
}
}
dataObjectPastingEventArgs.CancelCommand();
}
private bool IsTextValid(string enteredText)
{
if (!enteredText.All(c => Char.IsNumber(c) || c == '.' || c == '-'))
{
return false;
}
//We only validation against unselected text since the selected text will be replaced by the entered text
var unselectedText = this.Text.Remove(SelectionStart, SelectionLength);
if (enteredText == "." && unselectedText.Contains("."))
{
return false;
}
if (enteredText == "-" && unselectedText.Length > 0)
{
return false;
}
return true;
}
protected override void OnPreviewTextInput(System.Windows.Input.TextCompositionEventArgs e)
{
e.Handled = !IsTextValid(e.Text);
base.OnPreviewTextInput(e);
}
}
ユーザーがデータを使用する前にコミットした場合は、データ検証を使用してみることもできます。これを行うことは、キーをいじるよりもかなり簡単でクリーンであることがわかりました。
それ以外の場合は、いつでも貼り付けを無効にすることもできます。
私のバージョン アルクトゥルス 答えは、int / uint / 10 進数 / byte (色の場合) またはその他の使用したい数値形式で使用する変換メソッドを変更できます。また、コピー / ペーストでも機能します。
protected override void OnPreviewTextInput( System.Windows.Input.TextCompositionEventArgs e )
{
try
{
if ( String.IsNullOrEmpty( SelectedText ) )
{
Convert.ToDecimal( this.Text.Insert( this.CaretIndex, e.Text ) );
}
else
{
Convert.ToDecimal( this.Text.Remove( this.SelectionStart, this.SelectionLength ).Insert( this.SelectionStart, e.Text ) );
}
}
catch
{
// mark as handled if cannot convert string to decimal
e.Handled = true;
}
base.OnPreviewTextInput( e );
}
注:未テストのコード。
これをメイン ソリューションに追加して、テキスト ボックスがクリアされたときにバインディングがゼロに更新されるようにします。
protected override void OnPreviewKeyUp(System.Windows.Input.KeyEventArgs e)
{
base.OnPreviewKeyUp(e);
if (BindingOperations.IsDataBound(this, TextBox.TextProperty))
{
if (this.Text.Length == 0)
{
this.SetValue(TextBox.TextProperty, "0");
this.SelectAll();
}
}
}
気が狂っていると言われるかもしれませんが、TextBox コントロールの両側にプラスとマイナスのボタンを配置して、単純に TextBox がカーソル フォーカスを受け取らないようにして、独自の安価な NumericUpDown コントロールを作成してはどうでしょうか?
private void txtNumericValue_PreviewKeyDown(object sender, KeyEventArgs e)
{
KeyConverter converter = new KeyConverter();
string key = converter.ConvertToString(e.Key);
if (key != null && key.Length == 1)
{
e.Handled = Char.IsDigit(key[0]) == false;
}
}
これは、これを達成するために私が見つけた最も簡単なテクニックです。欠点は、TextBox のコンテキスト メニューで、貼り付けによる数値以外の入力が依然として許可されていることです。これを迅速に解決するために、単に属性/プロパティを追加しました。ContextMenu="{x:Null}" を TextBox に追加して無効にします。理想的ではありませんが、私のシナリオでは十分です。
明らかに、テストにさらにいくつかのキー/文字を追加して、追加の許容値を含めることもできます (例:「.」、「$」など...)
Private Sub Value1TextBox_PreviewTextInput(ByVal sender As Object, ByVal e As TextCompositionEventArgs) Handles Value1TextBox.PreviewTextInput
Try
If Not IsNumeric(e.Text) Then
e.Handled = True
End If
Catch ex As Exception
End Try
End Sub
私のために働きました。
次のようなものを使用することはできませんか?
int numericValue = 0;
if (false == int.TryParse(yourInput, out numericValue))
{
// handle non-numeric input
}
void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
{
string sVal = e.Text;
int val = 0;
if (sVal != null && sVal.Length > 0)
{
if (int.TryParse(sVal, out val))
{
e.Handled = false;
}
else
{
e.Handled = true;
}
}
}
次のようなコンバータを使用することもできます。
public class IntegerFormatConverter : IValueConverter
{
public object Convert(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int result;
int.TryParse(value.ToString(), out result);
return result;
}
public object ConvertBack(object value, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
int result;
int.TryParse(value.ToString(), out result);
return result;
}
}