2008-08-08 80 views
61

Bạn xử lý việc nhập các giá trị số trong các ứng dụng WPF như thế nào?Nhập dữ liệu số trong WPF

Nếu không có điều khiển NumericUpDown, tôi đã sử dụng một TextBox và xử lý sự kiện PreviewKeyDown của nó bằng mã bên dưới, nhưng nó khá xấu xí.

Có ai tìm thấy một cách duyên dáng hơn để lấy dữ liệu số từ người dùng mà không dựa vào quyền kiểm soát của bên thứ ba không?

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; 
} 

Trả lời

54

Làm thế nào về:

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; 
} 
+0

Đó là tốt hơn so với cách tiếp cận của tôi ở chỗ nó ít mã hơn và vẫn cho phép các phím điều khiển như các phím mũi tên và backspace vv –

+4

Tôi vừa nhận ra rằng không cách tiếp cận nào sẽ ngăn người dùng dán các ký tự không phải số vào điều khiển, nhưng đó không phải là vấn đề ngay bây giờ. Tôi sẽ đánh dấu phản ứng của bạn là câu trả lời 'coz nó gần như chúng ta có thể nhận được, tôi nghĩ. Cảm ơn! –

+8

Điều này sẽ không thành công trên số thập phân. Nếu người dùng nhập 5.3. Nó cũng sẽ thất bại về số âm vì "-" không phải là số. Dễ sửa nhưng chỉ nghĩ rằng tôi sẽ ném cảnh cáo đó vào đó. – Kelly

0

Hãy gọi cho tôi điên, nhưng tại sao không đặt dấu cộng và trừ nút ở hai bên của các TextBox kiểm soát và chỉ đơn giản là ngăn chặn sự TextBox từ nhận con trỏ tập trung, từ đó tạo riêng kiểm soát NumericUpDown rẻ của bạn?

+5

Bạn bị điên. :) –

+1

Tốc độ nhập dữ liệu. Chúng ta có các toán tử nhập dữ liệu, người búa dữ liệu bằng cách sử dụng bàn phím (và thường chỉ là bàn phím số) nên không thực tế để chúng tiếp cận được với chuột một nửa thông qua màn hình nhập. –

+0

Trong trường hợp đó, hãy viết kiểm soát NUD có thể sử dụng lại của riêng bạn và sử dụng nó trong suốt ứng dụng của bạn! – RQDQ

1

Bạn cũng có thể thử sử dụng xác nhận dữ liệu nếu người dùng cam kết dữ liệu trước khi bạn sử dụng nó. Làm điều đó tôi thấy khá đơn giản và sạch hơn là không quan trọng với các phím.

Nếu không, bạn luôn có thể vô hiệu hóa Dán quá!

+0

Vâng, tôi chắc chắn sẽ xác thực. Tôi chỉ muốn ngăn người dùng có thể phạm sai lầm càng nhiều càng tốt, để bên cạnh không có cơ hội nào họ sẽ thấy cửa sổ bật lên lỗi. –

9

Tôi đã sử dụng thuộc tính được đính kèm để cho phép người dùng sử dụng các phím lên và xuống để thay đổi các giá trị trong hộp văn bản. Để sử dụng nó, bạn chỉ cần sử dụng

<TextBox local:TextBoxNumbers.SingleDelta="1">100</TextBox> 

Điều này không thực sự giải quyết các vấn đề xác thực được đề cập trong câu hỏi này, nhưng nó giải quyết những gì tôi làm về việc không có điều khiển tăng/giảm số. Sử dụng nó cho một chút, tôi nghĩ rằng tôi thực sự có thể thích nó tốt hơn so với điều khiển lên/xuống số cũ.

Mã này không phải là hoàn hảo, nhưng nó xử lý các trường hợp tôi cần nó để xử lý:

  • Up mũi tên, mũi tên Down
  • Shift + Up mũi tên, mũi tên Shift + Down
  • Page Up, Page Down
  • Ràng buộc Converter trên thuộc tính văn bản

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(); 
     } 
    } 
} 
+0

Đó là một đoạn mã tuyệt vời. Các điều khiển NUD khác nhau mà tôi đã thử luôn luôn là lỗi. Yours làm việc như say mê, cảm ơn. :) – Echilon

+0

Và nó phù hợp với MVVM. +1 –

0

Bạn có thể không chỉ cần sử dụng một cái gì đó như sau?

int numericValue = 0; 

if (false == int.TryParse(yourInput, out numericValue)) 
{ 
    // handle non-numeric input 
} 
0
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; 
    } 
} 

Đây là kỹ thuật đơn giản nhất tôi đã tìm thấy để thực hiện điều này. Phía dưới là menu ngữ cảnh của TextBox vẫn cho phép số không qua Paste. Để giải quyết điều này một cách nhanh chóng, tôi chỉ cần thêm thuộc tính/thuộc tính: ContextMenu = "{x: Null}" vào TextBox do đó vô hiệu hóa nó. Không lý tưởng nhưng đối với kịch bản của tôi nó sẽ đủ.

Rõ ràng bạn có thể thêm một vài chi tiết phím/chars trong các thử nghiệm để đưa giá trị bổ sung có thể chấp nhận (ví dụ '', '$' vv ...)

+0

Đáng buồn thay, giải pháp của tôi có lỗ vì các ký tự Oem không được chuyển đổi thành chuỗi có ký tự đơn (ví dụ: '.' = OemPeriod). –

13

Đây là cách tôi làm điều đó. Nó sử dụng một biểu thức chính quy để kiểm tra xem văn bản trong hộp có phải là số hay không.

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"); 
} 

Hiện có cách tốt hơn để thực hiện điều này trong WPF và Silverlight. Nếu điều khiển của bạn bị ràng buộc vào một thuộc tính, tất cả những gì bạn phải làm là thay đổi câu lệnh ràng buộc của bạn một chút. Sử dụng sau đây để ràng buộc của bạn:

<TextBox Text="{Binding Number, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnExceptions=True}"/> 

Lưu ý rằng bạn có thể sử dụng trên các thuộc tính tùy chỉnh quá, tất cả các bạn phải làm là ném một ngoại lệ nếu giá trị trong hộp là không hợp lệ và sự kiểm soát sẽ được đánh dấu bằng một đường viền màu đỏ. Nếu bạn nhấp vào phía trên bên phải của đường viền màu đỏ thì thông báo ngoại lệ sẽ bật lên.

+2

+1 để gọi phương thức ràng buộc. –

+2

Tôi thích giải pháp Regex. Nhưng vấn đề ở đây là tôi không thể thêm một trừ vào một số hiện có, bởi vì nó giả định văn bản mới luôn được nối thêm vào văn bản cũ. Bất kì lời đề nghị nào? – newman

+0

Giải pháp Regex không gần bằng phương pháp BindingValidation. – Eric

0
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 

Làm việc cho tôi.

+0

... miễn là người dùng không nhập - hoặc. –

0
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; 
     } 
    } 
} 
+1

Điều này sẽ không hoạt động vì e.Text chỉ chứa văn bản được thêm vào, không phải toàn bộ nội dung của Hộp văn bản. Giá trị chỉ có thể là "." –

3
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); 
     } 
    } 
} 

Ngoài ra bạn có thể tùy chỉnh các hành vi phân tích cú pháp bằng cách cung cấp các thuộc tính phụ thuộc thích hợp.

+1

Điều này sẽ không hoạt động vì e.Text chỉ chứa văn bản được thêm vào, không phải toàn bộ nội dung của Hộp văn bản. Giá trị chỉ có thể là '". "'. –

+0

Hoạt động hoàn hảo cho tôi – Darren

1

Thêm giải pháp này vào giải pháp chính để đảm bảo ràng buộc được cập nhật về 0 khi hộp văn bản bị xóa.

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(); 
     } 
    } 
} 
+0

Thú vị. Nó có cần phải được bọc trong một cuộc gọi IsDataBound không? –

10

Tôi đã quyết định đơn giản hóa câu trả lời được đánh dấu là câu trả lời ở đây về cơ bản là 2 dòng sử dụng biểu thức LINQ.

e.Handled = !e.Text.All(Char.IsNumber); 
base.OnPreviewTextInput(e); 
+1

Cái này đẹp và gọn gàng. Nhưng làm cách nào để tôi chấp nhận "." và "-"? – newman

+1

'e.Text.All (cc => Char.IsNumber (cc) || ​​cc == '.' || cc == '-')'? – user7116

+0

Thật không may, OnPreviewTextInput không được gọi khi người dùng nhập vào khoảng trắng.Điều này có thể được xử lý riêng bằng cách ghi đè OnPreviewKeyDown, dù sao người dùng vẫn có thể dán các ký tự không phải là số. PS .: bạn có thể cải thiện câu trả lời bằng cách viết phương thức đầy đủ: 'void override void OnPreviewTextInput (TextCompositionEventArgs e) {...}' – Gobe

1

Version tôi về Arcturus câu trả lời, có thể thay đổi phương pháp chuyển đổi sử dụng để làm việc với int/uint/thập phân/byte (ví màu) hoặc bất kỳ định dạng số khác mà bạn quan tâm để sử dụng, cũng làm việc với copy/paste

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); 
} 

NB Mã chưa được kiểm tra.

+0

không hoạt động với bản sao/dán – Kluyg

2

Tại sao bạn không thử sử dụng sự kiện KeyDown thay vì Sự kiện PreviewKeyDown. Bạn có thể dừng các ký tự không hợp lệ ở đó, nhưng tất cả các ký tự điều khiển đều được chấp nhận. Điều này có vẻ phù hợp với tôi:

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); 
} 
3

Tôi sử dụng tùy chỉnh ValidationRule để kiểm tra xem văn bản có phải là số hay không.

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; 
    } 

Sau đó, khi tôi gắn một TextBox để sở hữu một số, tôi thêm các lớp tùy chỉnh mới cho bộ sưu tập Binding.ValidationRules. Trong ví dụ bên dưới, quy tắc xác thực được kiểm tra mọi lúc TextBox.Text thay đổi.

<TextBox> 
    <TextBox.Text> 
     <Binding Path="MyNumericProperty" UpdateSourceTrigger="PropertyChanged"> 
      <Binding.ValidationRules> 
       <local:DoubleValidation/> 
      </Binding.ValidationRules> 
     </Binding> 
    </TextBox.Text> 
</TextBox> 
0

cũng có thể sử dụng một bộ chuyển đổi như:

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; 
    } 
} 
+1

Không cần trình chuyển đổi - WPF sẽ thực hiện chuyển đổi theo mặc định. Câu hỏi của tôi là nhiều hơn về việc đảm bảo rằng người dùng không bao giờ thấy lỗi vì họ đã nhập giá trị không phải là số. Một công cụ chuyển đổi không ngăn họ làm điều đó. –

2

Kết hợp các ý tưởng từ một vài trong số những câu trả lời, tôi đã tạo ra một NumericTextBox rằng

  • Xử lý số thập phân
  • Có một số xác nhận cơ bản để đảm bảo bất kỳ nhập '-' hoặc '.'Có giá trị
  • Handles giá trị dán

Xin vui lòng cập nhật nếu bạn có thể nghĩ ra bất kỳ logic khác mà nên được bao gồm.

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); 
    } 
} 
Các vấn đề liên quan