Introducerea datelor numerice în WPF

Cum gestionați introducerea valorilor numerice în aplicațiile WPF?

Fără un control NumericUpDown, folosesc un TextBox și gestionez evenimentul PreviewKeyDown cu codul de mai jos, dar e destul de urât.

A găsit cineva o modalitate mai grațioasă de a obține date numerice de la utilizator fără a se baza pe un control de la o terță parte?

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;
}
0
fr hi bn

16 răspunsuri

Sunați-mă nebun, dar de ce nu puneți butoanele plus și minus de pe ambele părți ale butonului TextBox și pur și simplu împiedicați TextBox-ul să primească focalizarea cursorului, creând astfel propriul dvs. control numeric NumericUpDown?

0
adăugat
Nu-mi vine să cred că am apărat acest răspuns peste 2 ani, dar viteza de intrare nu are nimic de-a face cu acesta - posterul original dorea un TextBox NumericUpDown și acest lucru ar funcționa pentru acest scenariu. Nu a spus nimic despre viteză. Dacă doriți 8080, ați apăsat pe tasta și tastați 8080 pe tastatură; nu este într-adevăr o afacere mare. Îmi fac griji pentru creierul oricui crede cu adevărat că spun că apăsarea butonului plus de mii de ori a fost o soluție bună.
adăugat autor tags2k, sursa
Viteza intrării datelor. Avem operatori de introducere a datelor care ciocnesc datele folosind tastatura (adesea doar tastatura numerică), astfel încât este imposibil pentru ei să ajungă pentru mouse-ul pe jumătate printr-un ecran de intrare.
adăugat autor Matt Hamilton, sursa
@Greg, sunt de acord cu comentariile lui tags2k aici, controalele NUD au o utilizare eficientă atunci când sunt utilizate corespunzător, cum ar fi intrarea comenzii unde cantitatea implicită ar putea fi 1, dar un utilizator poate alege să comande mai mult. Două întrebări apar atunci când se decide să se utilizeze un NUD, 1) va fi cel mai probabil utilizatorul are mâna pe mouse-ul, 2) în cele mai multe cazuri este probabil să nu fie nici o schimbare sau doar câteva incrememnts / decrements.
adăugat autor Brett Ryan, sursa
În acest caz, scrieți-vă controlul NUD reutilizabil și îl utilizați în întreaga aplicație!
adăugat autor RQDQ, sursa
Cât timp va dura pentru a introduce un PIN de 4 cifre? Sau transferați o sumă mare între conturi? Sau adăugați două numere mari? Sau setați numărul portului la 8080? Rău.
adăugat autor Greg Sansom, sursa
Esti nebun. :)
adăugat autor F.D.Castel, sursa

De asemenea, puteți încerca să utilizați validarea datelor dacă utilizatorii comit date înainte de al utiliza. Făcând asta, mi-am dat seama că era destul de simplă și mai curată decât să arunce cu cheile.

În caz contrar, puteți dezactiva întotdeauna și Lipirea!

0
adăugat
Da, cu siguranță o să fiu validată oricum. Îmi place să împiedic utilizatorul să facă greșelile cât mai mult posibil, astfel că nu există nicio șansă să vadă un pop-up de eroare.
adăugat autor Matt Hamilton, sursa

Ce zici:

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
adăugat
Tocmai mi-am dat seama că nici o abordare nu va împiedica utilizatorul să folosească caractere non-numerice în control, dar asta nu este o problemă imensă acum. Voi marca răspunsul dvs. ca fiind răspunsul, că este cât de aproape putem obține, cred. Mulțumiri!
adăugat autor Matt Hamilton, sursa
Asta e mai bine decât abordarea mea prin faptul că este mai puțin cod și încă permite cheile de control, cum ar fi tastele săgeată și backspace etc.
adăugat autor Matt Hamilton, sursa
De ce nu Double.TryParse?
adăugat autor Louis Rhys, sursa
Aceasta va eșua pe zecimale. Dacă utilizatorul introduce 5.3. De asemenea, va eșua pe numere negative, deoarece "-" nu este un număr. E ușor de rezolvat, dar doar m-am gândit să arunc acea prudență acolo.
adăugat autor Kelly, sursa
Nu este la fel de bun ca utilizarea Validării obligatorii.
adăugat autor Eric, sursa
e.Handled =! e.Text.ToCharArray() Toate (c => Char.IsNumber (c));
adăugat autor si618, sursa
Vă recomandăm răspunsul lui juanagui, deoarece se ocupă cu lipirea în date non-numerice stackoverflow.com/a/2673131/755404
adăugat autor Darren, sursa
e.Handled =! e.Text.All (Char.IsNumber);
adăugat autor Olson.dev, sursa

Așa fac eu. Utilizează o expresie regulată pentru a verifica dacă textul care va fi în casetă este numeric sau nu.

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

Acum există o modalitate mult mai bună de a face acest lucru în wpf și Silverlight. Dacă controlul dvs. este legat de o proprietate, tot ce trebuie să faceți este să vă modificați puțin declarația obligatorie. Utilizați următoarele pentru legarea:


Rețineți că puteți folosi acest lucru și pe proprietăți personalizate, tot ce trebuie să faceți este să aruncați o excepție dacă valoarea din casetă este nevalidă și controlul va fi evidențiat cu o margine roșie. Dacă faceți clic pe partea dreaptă sus a marginiului roșu, mesajul de excepție va apărea.

0
adăugat
+1 pentru a apela metoda de legare.
adăugat autor Justin R., sursa
Îmi place soluția Regex. Dar problema aici este că nu pot adăuga un minus la un număr existent, deoarece presupune că noul text este mereu atașat textului vechi. Vreo idee?
adăugat autor newman, sursa
soluția Regex nu este la fel de bună ca metoda BindingValidation.
adăugat autor Eric, sursa

Combinând ideile din câteva dintre aceste răspunsuri, am creat un număr care conține NumericTextBox

  • Mânerele zecimalelor
  • Există unele validări de bază pentru a vă asigura că ați introdus "-" sau "." este valabil
  • Manipulează valori lipite

Vă rugăm să nu ezitați să actualizați dacă vă puteți gândi la orice altă logică care ar trebui inclusă.

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);
    }
}
0
adăugat

Am folosit o proprietate atașată pentru a permite utilizatorului să utilizeze tastele sus și jos pentru a modifica valorile din caseta de text. Pentru ao folosi, pur și simplu folosești

100

Acest lucru nu se referă, de fapt, la problemele de validare la care se face referire în această întrebare, dar se referă la ceea ce fac pentru a nu avea un control numeric sus / jos. Folosind-o pentru un pic, cred că aș putea să-mi plac mai bine decât vechiul control numeric sus / jos.

Codul nu este perfect, dar se ocupă de cazurile de care aveam nevoie să o gestioneze:

  • Up arrow, Down arrow
  • Shift + Up arrow, Shift + Down arrow
  • Page Up, Page Down
  • Binding Converter on the text property

Cod în spatele

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
adăugat
E o bucată mare de cod. Diferitele controale NUD pe care le-am încercat au fost întotdeauna buggy. Lucrul tău este farmec, mulțumesc. :)
adăugat autor Echilon, sursa
Și se potrivește cu MVVM. +1
adăugat autor Ignacio Soler Garcia, sursa

Nu poți folosi ceva de genul următor?

int numericValue = 0;

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

A lucrat pentru mine.

0
adăugat
... atâta timp cât utilizatorul nu introduce un - sau.
adăugat autor Greg Sansom, sursa
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;
        }
    }
}
0
adăugat
Acest lucru nu va funcționa deoarece e.Text conține numai textul adăugat, nu conținutul întregului TextBox. Valoarea ar putea fi doar "."
adăugat autor Greg Sansom, sursa
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);
        }
    }
}

În plus, puteți personaliza comportamentul de parsing oferind proprietăți de dependență adecvate.

0
adăugat
Lucrează perfect pentru mine
adăugat autor Darren, sursa
Acest lucru nu va funcționa deoarece e.Text conține numai textul adăugat, nu conținutul întregului TextBox. Valoarea ar putea fi doar "." .
adăugat autor Greg Sansom, sursa

Adăugați acest lucru la soluția principală pentru a vă asigura că legarea este actualizată la zero când caseta de text este șters.

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
adăugat
Interesant. Trebuie să fie înfășurat într-un apel IsDataBound?
adăugat autor Matt Hamilton, sursa

Am decis să simplifică răspunsul marcat ca răspuns aici, în principiu cu 2 linii folosind o expresie LINQ.

e.Handled = !e.Text.All(Char.IsNumber);
base.OnPreviewTextInput(e);
0
adăugat
Acesta este frumos și îngrijit. Dar cum o accept "." și "-"?
adăugat autor newman, sursa
e.Text.All (cc => Char.IsNumber (cc) || ​​cc == '.' || cc == '-') ?
adăugat autor user7116, sursa
Din păcate, OnPreviewTextInput nu este apelat când utilizatorul introduce un spațiu alb. Acest lucru ar putea fi tratat separat, de asemenea, prin suprimarea OnPreviewKeyDown, oricum, utilizatorul ar putea încă să lipsească caractere non-numerice. PS: puteți să vă îmbunătățiți răspunsul scriind metoda completă: protect override void OnPreviewTextInput (TextCompositionEventArgs e) {...}
adăugat autor Gobe, sursa

Versiunea mea de răspuns Arcturus poate schimba metoda de conversie folosită pentru a lucra cu int / uint / zecimal / octet (pentru culori) sau orice alt format numeric pe care aveți grijă să îl utilizați, funcționează și cu copiere / lipire

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

N.B. Codul netestat.

0
adăugat
nu funcționează cu copierea / lipirea
adăugat autor Kluyg, sursa

De ce nu încercați să utilizați evenimentul KeyDown decât evenimentul PreviewKeyDown. Puteți opri caracterele nevalide acolo, dar toate caracterele de control sunt acceptate. Acest lucru pare să funcționeze pentru mine:

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);
}
0
adăugat

Eu folosesc un ValidationRule personalizat pentru a verifica dacă textul este numeric.

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

Atunci când legăm un TextBox la o proprietate numerică, adaug noua clasă particulară colecției Binding.ValidationRules . În exemplul de mai jos, regula de validare este verificată de fiecare dată când TextBox.Text se modifică.


    
        
            
                
            
        
    

0
adăugat

De asemenea, puteți utiliza un convertor cum ar fi:

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;
    }
}
0
adăugat
Nu aveți nevoie de un convertor - wpf va efectua conversia în mod implicit. Întrebarea mea a fost mai mult să mă asigur că utilizatorul nu vede o eroare deoarece a introdus o valoare non-numerică. Un convertor nu le împiedică să facă asta.
adăugat autor Matt Hamilton, sursa