Jak sprawić, by TextBox akceptował tylko dane numeryczne w WPF?

335

Chcę zaakceptować cyfry i kropkę dziesiętną, ale bez znaku.

Patrzyłem na próbki przy użyciu kontrolki NumericUpDown dla formularzy Windows Forms i tej próbki niestandardowej kontrolki NumericUpDown firmy Microsoft . Ale do tej pory wydaje się, że NumericUpDown (obsługiwany przez WPF lub nie) nie zapewni funkcjonalności, której chcę. Sposób, w jaki zaprojektowano moją aplikację, nikt przy zdrowych zmysłach nie będzie chciał zadzierać ze strzałkami. W kontekście mojej aplikacji nie mają one praktycznego sensu.

Szukam więc prostego sposobu, aby standardowy WPF TextBox akceptował tylko te znaki, które chcę. czy to możliwe? Czy to jest praktyczne?

Giffyguy
źródło

Odpowiedzi:

418

Dodaj podgląd zdarzenia wprowadzania tekstu. Tak: <TextBox PreviewTextInput="PreviewTextInput" />.

Następnie wewnątrz tego zestawu, e.Handledjeśli tekst nie jest dozwolony.e.Handled = !IsTextAllowed(e.Text);

Używam prostej wyrażenia regularnego w IsTextAllowedmetodzie, aby sprawdzić, czy powinienem zezwolić na to, co wpisali. W moim przypadku chcę tylko dopuścić liczby, kropki i myślniki.

private static readonly Regex _regex = new Regex("[^0-9.-]+"); //regex that matches disallowed text
private static bool IsTextAllowed(string text)
{
    return !_regex.IsMatch(text);
}

Jeśli chcesz zapobiec wklejaniu niepoprawnych danych, podłącz DataObject.Pastingzdarzenie, DataObject.Pasting="TextBoxPasting"jak pokazano tutaj (fragment fragmentu):

// Use the DataObject.Pasting Handler 
private void TextBoxPasting(object sender, DataObjectPastingEventArgs e)
{
    if (e.DataObject.GetDataPresent(typeof(String)))
    {
        String text = (String)e.DataObject.GetData(typeof(String));
        if (!IsTextAllowed(text))
        {
            e.CancelCommand();
        }
    }
    else
    {
        e.CancelCommand();
    }
}
Promień
źródło
5
Twoje wyrażenie regularne nie pozwala na notację naukową (1e5), jeśli jest to ważne.
Ron Warholic,
14
Zauważ, że ta odpowiedź sprawdza tylko to, co piszesz, więc możesz wpisać 3 -3
David Sykes
153
Celem odpowiedzi nie było określenie idealnego Regeksu, ale pokazanie, jak używać WPF do filtrowania tego, co ktoś pisze.
Ray
27
[Space] nie uruchamia zdarzenia PreviewTextInput.
peterG
5
Coś takiego double.TryParse()prawdopodobnie zostałoby zaimplementowane z taką samą liczbą linii i byłoby bardziej elastyczne.
Thomas Weller,
190

Moduł obsługi zdarzeń wyświetla podgląd tekstu. W tym przypadku wyrażenie regularne pasuje do wpisywanego tekstu tylko wtedy, gdy nie jest liczbą, a następnie nie jest wprowadzane do pola tekstowego wprowadzania.

Jeśli chcesz tylko litery, zamień wyrażenie regularne na [^a-zA-Z].

XAML

<TextBox Name="NumberTextBox" PreviewTextInput="NumberValidationTextBox"/>

PLIK XAML.CS

using System.Text.RegularExpressions;
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
{
    Regex regex = new Regex("[^0-9]+");
    e.Handled = regex.IsMatch(e.Text);
}
Kishor
źródło
1
Moduł obsługi zdarzeń jest podglądem tekstu. W tym przypadku wyrażenie regularne pasuje do wpisywanego tekstu tylko wtedy, gdy nie jest liczbą, nie jest wprowadzane do pola tekstowego wprowadzania. Jeśli chcesz tylko alfabetu, zamień wyrażenie regularne na [^ a-zA-Z].
Kishor
Co z liczbami, liczbami dziesiętnymi i operatorami?
Jason Ebersey
Daj mi znać, jak go używać, gdy deklaruje w innej klasie STATIC i ma zastosowanie do pola tekstowego?
SHEKHAR SHETE
3
Podoba mi się ta bardziej niż faktyczna odpowiedź, krótka i prosta. Rzeczywista odpowiedź wymaga dwóch metod, aby uzyskać ten sam wynik.
Sliver,
1
@Jagd Sugerowana odpowiedź to lepsze rozdzielenie obaw. Możesz jednak ustawić tak wiele pól tekstowych w tym sprawdzaniu poprawności. wystarczy dodać PreviewTextInput = "NumberValidationTextBox". (podobnie jak inna odpowiedź!)
Sliver
84

Wykorzystałem część tego, co już tu było i nadałem temu swój własny styl, dzięki czemu nie muszę propagować tego kodu w całej masie wyświetleń ...

public class AllowableCharactersTextBoxBehavior : Behavior<TextBox>
{
    public static readonly DependencyProperty RegularExpressionProperty =
         DependencyProperty.Register("RegularExpression", typeof(string), typeof(AllowableCharactersTextBoxBehavior),
         new FrameworkPropertyMetadata(".*"));
    public string RegularExpression
    {
        get
        {
            return (string)base.GetValue(RegularExpressionProperty);
        }
        set
        {
            base.SetValue(RegularExpressionProperty, value);
        }
    }

    public static readonly DependencyProperty MaxLengthProperty =
        DependencyProperty.Register("MaxLength", typeof(int), typeof(AllowableCharactersTextBoxBehavior),
        new FrameworkPropertyMetadata(int.MinValue));
    public int MaxLength
    {
        get
        {
            return (int)base.GetValue(MaxLengthProperty);
        }
        set
        {
            base.SetValue(MaxLengthProperty, value);
        }
    }

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.PreviewTextInput += OnPreviewTextInput;
        DataObject.AddPastingHandler(AssociatedObject, OnPaste);
    }

    private void OnPaste(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(DataFormats.Text))
        {
            string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));

            if (!IsValid(text, true))
            {
                e.CancelCommand();
            }
        }
        else
        {
            e.CancelCommand();
        }
    }

    void OnPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
    {
        e.Handled = !IsValid(e.Text, false);
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        AssociatedObject.PreviewTextInput -= OnPreviewTextInput;
        DataObject.RemovePastingHandler(AssociatedObject, OnPaste);
    }

    private bool IsValid(string newText, bool paste)
    {
        return !ExceedsMaxLength(newText, paste) && Regex.IsMatch(newText, RegularExpression);
    }

    private bool ExceedsMaxLength(string newText, bool paste)
    {
        if (MaxLength == 0) return false;

        return LengthOfModifiedText(newText, paste) > MaxLength;
    }

    private int LengthOfModifiedText(string newText, bool paste)
    {
        var countOfSelectedChars = this.AssociatedObject.SelectedText.Length;
        var caretIndex = this.AssociatedObject.CaretIndex;
        string text = this.AssociatedObject.Text;

        if (countOfSelectedChars > 0 || paste)
        {
            text = text.Remove(caretIndex, countOfSelectedChars);
            return text.Length + newText.Length;
        }
        else
        {
            var insert = Keyboard.IsKeyToggled(Key.Insert);

            return insert && caretIndex < text.Length ? text.Length : text.Length + newText.Length;
        }
    }
}

Oto odpowiedni kod widoku:

<TextBox MaxLength="50" TextWrapping="Wrap" MaxWidth="150" Margin="4"
 Text="{Binding Path=FileNameToPublish}" >
     <interactivity:Interaction.Behaviors>
         <v:AllowableCharactersTextBoxBehavior RegularExpression="^[0-9.\-]+$" MaxLength="50" />
     </interactivity:Interaction.Behaviors>
</TextBox>
Wil P.
źródło
1
Zainspirowany tym niesamowitym rozwiązaniem wdrożyłem kilka ulepszeń. Sprawdź to poniżej w wątku.
Alex Klaus,
2
Cześć. Wiem, że to trochę za późno, ale próbuję to zaimplementować, ale wciąż pojawiają się błędy. Chyba brakuje mi niektórych referencji. Czy są jakieś, które należy wpisać inaczej niż domyślne po utworzeniu klasy?
Oferta
1
@Offer Tak, pamiętaj o dołączeniu xmlns: interactivity = " schemas.microsoft.com/expression/2010/interactivity " w górnej części okna xaml.
WiteCastle
Wyrażenie jest teraz przestarzałe. Chociaż takie podejście jest czyste, wykorzystuje kod, który nie jest już utrzymywany.
Robert Baker
1
Więc jeśli edytujesz funkcję IsValid, aby zwrócić! ExceedsMaxLength (newText, paste) && Regex.IsMatch (String.Concat (this.AssociatedObject.Text, newText), RegularExpression); to oceni cały ciąg. Btw - Uwielbiam tę opcję z zachowaniami !!
Rogala
59

To ulepszone rozwiązanie odpowiedzi WilP . Moje ulepszenia to:

  • Poprawione zachowanie przycisków Del i Backspace
  • Dodano EmptyValuewłaściwość, jeśli pusty ciąg jest nieodpowiedni
  • Naprawiono kilka drobnych literówek
/// <summary>
///     Regular expression for Textbox with properties: 
///         <see cref="RegularExpression"/>, 
///         <see cref="MaxLength"/>,
///         <see cref="EmptyValue"/>.
/// </summary>
public class TextBoxInputRegExBehaviour : Behavior<TextBox>
{
    #region DependencyProperties
    public static readonly DependencyProperty RegularExpressionProperty =
        DependencyProperty.Register("RegularExpression", typeof(string), typeof(TextBoxInputRegExBehaviour), new FrameworkPropertyMetadata(".*"));

    public string RegularExpression
    {
        get { return (string)GetValue(RegularExpressionProperty); }
        set { SetValue(RegularExpressionProperty, value); }
    }

    public static readonly DependencyProperty MaxLengthProperty =
        DependencyProperty.Register("MaxLength", typeof(int), typeof(TextBoxInputRegExBehaviour),
                                        new FrameworkPropertyMetadata(int.MinValue));

    public int MaxLength
    {
        get { return (int)GetValue(MaxLengthProperty); }
        set { SetValue(MaxLengthProperty, value); }
    }

    public static readonly DependencyProperty EmptyValueProperty =
        DependencyProperty.Register("EmptyValue", typeof(string), typeof(TextBoxInputRegExBehaviour), null);

    public string EmptyValue
    {
        get { return (string)GetValue(EmptyValueProperty); }
        set { SetValue(EmptyValueProperty, value); }
    }
    #endregion

    /// <summary>
    ///     Attach our behaviour. Add event handlers
    /// </summary>
    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.PreviewTextInput += PreviewTextInputHandler;
        AssociatedObject.PreviewKeyDown += PreviewKeyDownHandler;
        DataObject.AddPastingHandler(AssociatedObject, PastingHandler);
    }

    /// <summary>
    ///     Deattach our behaviour. remove event handlers
    /// </summary>
    protected override void OnDetaching()
    {
        base.OnDetaching();

        AssociatedObject.PreviewTextInput -= PreviewTextInputHandler;
        AssociatedObject.PreviewKeyDown -= PreviewKeyDownHandler;
        DataObject.RemovePastingHandler(AssociatedObject, PastingHandler);
    }

    #region Event handlers [PRIVATE] --------------------------------------

    void PreviewTextInputHandler(object sender, TextCompositionEventArgs e)
    {
        string text;
        if (this.AssociatedObject.Text.Length < this.AssociatedObject.CaretIndex)
            text = this.AssociatedObject.Text;
        else
        {
            //  Remaining text after removing selected text.
            string remainingTextAfterRemoveSelection;

            text = TreatSelectedText(out remainingTextAfterRemoveSelection)
                ? remainingTextAfterRemoveSelection.Insert(AssociatedObject.SelectionStart, e.Text)
                : AssociatedObject.Text.Insert(this.AssociatedObject.CaretIndex, e.Text);
        }

        e.Handled = !ValidateText(text);
    }

    /// <summary>
    ///     PreviewKeyDown event handler
    /// </summary>
    void PreviewKeyDownHandler(object sender, KeyEventArgs e)
    {
        if (string.IsNullOrEmpty(this.EmptyValue))
            return;

        string text = null;

        // Handle the Backspace key
        if (e.Key == Key.Back)
        {
            if (!this.TreatSelectedText(out text))
            {
                if (AssociatedObject.SelectionStart > 0)
                    text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart - 1, 1);
            }
        }
        // Handle the Delete key
        else if (e.Key == Key.Delete)
        {
            // If text was selected, delete it
            if (!this.TreatSelectedText(out text) && this.AssociatedObject.Text.Length > AssociatedObject.SelectionStart)
            {
                // Otherwise delete next symbol
                text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, 1);
            }
        }

        if (text == string.Empty)
        {
            this.AssociatedObject.Text = this.EmptyValue;
            if (e.Key == Key.Back)
                AssociatedObject.SelectionStart++;
            e.Handled = true;
        }
    }

    private void PastingHandler(object sender, DataObjectPastingEventArgs e)
    {
        if (e.DataObject.GetDataPresent(DataFormats.Text))
        {
            string text = Convert.ToString(e.DataObject.GetData(DataFormats.Text));

            if (!ValidateText(text))
                e.CancelCommand();
        }
        else
            e.CancelCommand();
    }
    #endregion Event handlers [PRIVATE] -----------------------------------

    #region Auxiliary methods [PRIVATE] -----------------------------------

    /// <summary>
    ///     Validate certain text by our regular expression and text length conditions
    /// </summary>
    /// <param name="text"> Text for validation </param>
    /// <returns> True - valid, False - invalid </returns>
    private bool ValidateText(string text)
    {
        return (new Regex(this.RegularExpression, RegexOptions.IgnoreCase)).IsMatch(text) && (MaxLength == int.MinValue || text.Length <= MaxLength);
    }

    /// <summary>
    ///     Handle text selection
    /// </summary>
    /// <returns>true if the character was successfully removed; otherwise, false. </returns>
    private bool TreatSelectedText(out string text)
    {
        text = null;
        if (AssociatedObject.SelectionLength <= 0) 
            return false;

        var length = this.AssociatedObject.Text.Length;
        if (AssociatedObject.SelectionStart >= length)
            return true;

        if (AssociatedObject.SelectionStart + AssociatedObject.SelectionLength >= length)
            AssociatedObject.SelectionLength = length - AssociatedObject.SelectionStart;

        text = this.AssociatedObject.Text.Remove(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength);
        return true;
    }
    #endregion Auxiliary methods [PRIVATE] --------------------------------
}

Użycie jest dość proste:

<i:Interaction.Behaviors>
    <behaviours:TextBoxInputRegExBehaviour RegularExpression="^\d+$" MaxLength="9" EmptyValue="0" />
</i:Interaction.Behaviors>
Alex Klaus
źródło
1
To rozwiązanie jest o wiele lepsze. Ale popełniłeś mały błąd: gdy nie MaxLengthustawisz warunku, (this.MaxLength == 0 || text.Length <= this.MaxLength)zawsze wraca falsepodczas testowania nowego tekstu. Powinno to być lepsze, (this.MaxLength == int.MinValue || text.Length <= this.MaxLength)ponieważ ustawiłeś int.MinValuewartość domyślną dla MaxLength.
Christoph Meißner
1
Dziękuję @Christoph, tak, masz rację. Zmodyfikowałem swoją odpowiedź.
Alex Klaus,
@AlexKlaus działa to świetnie, dopóki nie dodam UpdateSourceTrigger=PropertyChangeddo wiązania. Masz pomysł, jak uruchomić ten kod, gdy zmienisz go UpdateSourceTriggerna PropertyChanged? Dziękujemy za udostępnienie tego kodu.
Junior
32

Oto bardzo prosty i łatwy sposób, aby to zrobić za pomocą MVVM.

Powiąż swój textBox z wartością całkowitą w modelu widoku, a to będzie działać jak klejnot ... wyświetli nawet sprawdzenie poprawności, gdy w polu tekstowym zostanie wprowadzona liczba całkowita.

Kod XAML:

<TextBox x:Name="contactNoTxtBox"  Text="{Binding contactNo}" />

Wyświetl kod modelu:

private long _contactNo;
public long contactNo
{
    get { return _contactNo; }
    set
    {
        if (value == _contactNo)
            return;
        _contactNo = value;
        OnPropertyChanged();
    }
}
Snziv Gupta
źródło
Ale pytanie zawierało: „Chcę zaakceptować cyfry i kropkę dziesiętną” . Czy kropka dziesiętna jest akceptowana dla tej odpowiedzi?
Peter Mortensen
Próbowałem zmienić longna float, ale nie działało to dobrze z natychmiastową weryfikacją. Dodałem UpdateSourceTrigger="PropertyChanged"do powiązania, aby sprawdzanie poprawności odbywało się, gdy każdy znak był wpisywany, i nie mógł już wpisać „.” w TextBox, chyba że pojawił się niedozwolony znak (musiał wpisać „1x.234”, a następnie usunąć „x”). W tym trybie wydaje się też nieco powolny. Wydaje się, że jest to przydatne System.Number.ParseSingle()do wykonywania pracy, więc akceptuje różne notacje.
fadden
@wolle prawdopodobnie nie otrzyma większego głosu, ponieważ nie wyjaśnia, jak działa sprawdzanie poprawności.
Paul McCarthy,
26

Dodaj ZASADĘ WALIDACJI, aby przy zmianie tekstu sprawdzić, czy dane są liczbowe, a jeśli tak, pozwala na kontynuowanie przetwarzania, a jeśli nie, monituje użytkownika, że ​​w tym polu akceptowane są tylko dane liczbowe.

Przeczytaj więcej w Sprawdzanie poprawności w Windows Presentation Foundation

Stephen Wrighton
źródło
6
To nie jest tak naprawdę odpowiedź na standardy SO.
Robert Baker
Wygląda na to, że jest to metoda .net.
Telemat,
1
Jest poprawna odpowiedź: walidacja powinna wynosić co Viene modelu lub poziomie modelu. Co więcej, możesz po prostu powiązać z typem numerycznym, doublektóry już zapewnia standardową weryfikację.
24

Rozszerzony zestaw narzędzi WPF ma jeden: NumericUpDown wprowadź opis zdjęcia tutaj

Brian Lagunas
źródło
Próbowałem tej kontroli, ale daje problemy podczas używania pokrętła z UpdateSourceTrigger = PropertyChanged i ogólnie trudno jest użytkownikowi wprowadzić notację naukową.
Menno Deij - van Rijswijk
5
Proszę zauważyć, że NumericUpDownjest już przestarzały. możesz skorzystać DecimalUpDownze zaktualizowanego zestawu narzędzi Rozszerzona
jego
20

Można również po prostu zaimplementować regułę sprawdzania poprawności i zastosować ją do TextBox:

  <TextBox>
    <TextBox.Text>
      <Binding Path="OnyDigitInput" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
        <Binding.ValidationRules>
          <conv:OnlyDigitsValidationRule />
        </Binding.ValidationRules>
      </Binding>
    </TextBox.Text>

Po wdrożeniu reguły w następujący sposób (przy użyciu tego samego Regex, co zaproponowano w innych odpowiedziach):

public class OnlyDigitsValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        var validationResult = new ValidationResult(true, null);

        if(value != null)
        {
            if (!string.IsNullOrEmpty(value.ToString()))
            {
                var regex = new Regex("[^0-9.-]+"); //regex that matches disallowed text
                var parsingOk = !regex.IsMatch(value.ToString());
                if (!parsingOk)
                {
                    validationResult = new ValidationResult(false, "Illegal Characters, Please Enter Numeric Value");
                }
            }
        }

        return validationResult;
    }
}
goul
źródło
Jeśli chcesz wprowadzić cyfry dziesiętne, nie zwracaj „ważny”, gdy tekst kończy się na „.” Proszę odnieść się do stackoverflow.com/a/27838893/417939
YantingChen
14

Tutaj mam proste rozwiązanie zainspirowane odpowiedzią Raya . Powinno to wystarczyć do zidentyfikowania dowolnej liczby.

To rozwiązanie można również łatwo zmodyfikować, jeśli chcesz mieć tylko liczby dodatnie, wartości całkowite lub wartości dokładne do maksymalnej liczby miejsc po przecinku itp.


Jak sugeruje odpowiedź Raya , musisz najpierw dodać PreviewTextInputwydarzenie:

<TextBox PreviewTextInput="TextBox_OnPreviewTextInput"/>

Następnie wstaw następujący kod do kodu:

private void TextBox_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
    var textBox = sender as TextBox;
    // Use SelectionStart property to find the caret position.
    // Insert the previewed text into the existing text in the textbox.
    var fullText = textBox.Text.Insert(textBox.SelectionStart, e.Text);

    double val;
    // If parsing is successful, set Handled to false
    e.Handled = !double.TryParse(fullText, out val);
}
Anthony
źródło
4
podoba mi się ta odpowiedź, prosta i skuteczna +
Pulle
Bóg i prosty, ale brzydki, że pozwala na spacje
Momo
2
To wciąż pozwala komuś po prostu wkleić ciąg do pola tekstowego
FCin
8

Dopuszczałem numery klawiatury numerycznej i backspace:

    private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
    {
        int key = (int)e.Key;

        e.Handled = !(key >= 34 && key <= 43 || 
                      key >= 74 && key <= 83 || 
                      key == 2);
    }
Hamzeh Soboh
źródło
8
Polecam użycie wartości wyliczeniowych zamiast liczb magicznych :var keyEnum = (System.Windows.Input.Key) e.Key; e.Handled = !(keyEnum >= System.Windows.Input.Key.D0 && keyEnum <= System.Windows.Input.Key.D9 || keyEnum >= System.Windows.Input.Key.NumPad0 && keyEnum <= System.Windows.Input.Key.NumPad9 || keyEnum == System.Windows.Input.Key.Back);
itho
7

Zakładam, że:

  1. W TextBox, dla którego chcesz zezwalać na wprowadzanie numeryczne, właściwość Text początkowo jest ustawiona na pewną prawidłową wartość liczbową (na przykład 2.7172).

  2. Twoje pole tekstowe jest dzieckiem twojego głównego okna

  3. Twoje główne okno należy do klasy Window1

  4. Twoja nazwa TextBox to numeryczna TB

Podstawowy pomysł:

  1. Dodaj: private string previousText;do głównej klasy okna (Window1)

  2. Dodaj: previousText = numericTB.Text;do głównego konstruktora okna

  3. Utwórz moduł obsługi zdarzenia numericTB.TextChanged, aby wyglądał mniej więcej tak:

    private void numericTB_TextChanged(object sender, TextChangedEventArgs e)
    {
        double num = 0;
        bool success = double.TryParse(((TextBox)sender).Text, out num);
        if (success & num >= 0)
            previousText = ((TextBox)sender).Text;
        else
            ((TextBox)sender).Text = previousText;
    }

Spowoduje to ustawianie previousText na numericTB.Text tak długo, jak będzie to ważne, i ustawi numericTB.Text na ostatnią prawidłową wartość, jeśli użytkownik napisze coś, co ci się nie podoba. Oczywiście jest to tylko podstawowy pomysł i jest on „idiotoodporny”, a nie „idiotyczny”. Na przykład nie obsługuje przypadku, w którym użytkownik miesza spacje. Oto kompletne rozwiązanie, które moim zdaniem jest „idiotyczne”, a jeśli się mylę, proszę mi powiedzieć:

  1. Zawartość pliku Window1.xaml:

    <Window x:Class="IdiotProofNumericTextBox.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
        <Grid>
            <TextBox Height="30" Width="100" Name="numericTB" TextChanged="numericTB_TextChanged"/>
        </Grid>
    </Window>
  2. Zawartość pliku Window.xaml.cs:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    namespace IdiotProofNumericTextBox
    {
        public partial class Window1 : Window
        {
            private string previousText;
    
            public Window1()
            {
                InitializeComponent();
                previousText = numericTB.Text;
            }
    
            private void numericTB_TextChanged(object sender, TextChangedEventArgs e)
            {
                if (string.IsNullOrEmpty(((TextBox)sender).Text))
                    previousText = "";
                else
                {
                    double num = 0;
                    bool success = double.TryParse(((TextBox)sender).Text, out num);
                    if (success & num >= 0)
                    {
                        ((TextBox)sender).Text.Trim();
                        previousText = ((TextBox)sender).Text;
                    }
                    else
                    {
                        ((TextBox)sender).Text = previousText;
                        ((TextBox)sender).SelectionStart = ((TextBox)sender).Text.Length;
                    }
                }
            }
        }
    }

I to wszystko. Jeśli masz wiele TextBoxów, polecam utworzenie CustomControl, który dziedziczy z TextBox, abyś mógł zawinąć previousText i numericTB_TextChanged w osobny plik.

użytkownik666535
źródło
Wow, to świetnie! Jak mogę jednak dopuścić symbol ujemny z przodu?
theNoobGuy
6

Jest to jedyny potrzebny kod:

void MyTextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    e.Handled = new Regex("[^0-9]+").IsMatch(e.Text);
}

Umożliwia to wprowadzanie liczb tylko w polu tekstowym.

Aby umożliwić znak dziesiętny lub znak minus, możesz zmienić wyrażenie regularne na [^0-9.-]+.

Danny Beckett
źródło
1
Bardzo dobre rozwiązanie, z wyjątkiem jednego drobnego nitpick: Nie powstrzyma cię przed wprowadzaniem białych znaków, ponieważ nie wyzwalają zdarzenia PreviewTextInput.
Tim Pohlmann
Backspace również go nie odpala.
Winger Sendon
6

Jeśli nie chcesz pisać dużo kodu, aby wykonać podstawową funkcję (nie wiem, dlaczego ludzie robią długie metody), możesz po prostu to zrobić:

  1. Dodaj przestrzeń nazw:

    using System.Text.RegularExpressions;
  2. W XAML ustaw właściwość TextChanged:

    <TextBox x:Name="txt1" TextChanged="txt1_TextChanged"/>
  3. W WPF w metodzie txt1_TextChanged dodaj Regex.Replace:

    private void txt1_TextChanged(object sender, TextChangedEventArgs e)
    {
        txt1.Text = Regex.Replace(txt1.Text, "[^0-9]+", "");
    }
iato
źródło
2
O wiele łatwiej jest skorzystać z zachowania lub właściwości AttachedProperty. Żadnych skrzypków związanych z kodowaniem w każdym widoku / dla każdego pola tekstowego
Sir Rufo
Może działać, ale jest brzydki, ponieważ marchewka skoczy na pierwsze miejsce pola tekstowego
Momo
6

Innym podejściem będzie użycie załączonego zachowania, zaimplementowałem mój niestandardowy TextBoxHelper klasę , której można używać w w całym projekcie. Ponieważ pomyślałem, że subskrybowanie wydarzeń dla każdego pola tekstowego i każdego pliku XAML w tym celu może być czasochłonne.

Zaimplementowana klasa TextBoxHelper ma następujące funkcje:

  • Filtrowanie i akceptowanie tylko liczb w Double , Int , Uint i Natural formacie
  • Filtrowanie i akceptowanie tylko parzystych lub nieparzystych numery
  • Obsługa modułu obsługi zdarzeń wklejania aby zapobiec wklejaniu nieprawidłowego tekstu do naszych numerycznych pól tekstowych
  • Może ustawić wartość domyślną, która będzie używana, aby zapobiec nieprawidłowym danym jako ostatnim strzałem, subskrybując pole tekstowe Zdarzenie TextChanged

Oto implementacja klasy TextBoxHelper:

public static class TextBoxHelper
{
    #region Enum Declarations

    public enum NumericFormat
    {
        Double,
        Int,
        Uint,
        Natural
    }

    public enum EvenOddConstraint
    {
        All,
        OnlyEven,
        OnlyOdd
    }

    #endregion

    #region Dependency Properties & CLR Wrappers

    public static readonly DependencyProperty OnlyNumericProperty =
        DependencyProperty.RegisterAttached("OnlyNumeric", typeof(NumericFormat?), typeof(TextBoxHelper),
            new PropertyMetadata(null, DependencyPropertiesChanged));
    public static void SetOnlyNumeric(TextBox element, NumericFormat value) =>
        element.SetValue(OnlyNumericProperty, value);
    public static NumericFormat GetOnlyNumeric(TextBox element) =>
        (NumericFormat) element.GetValue(OnlyNumericProperty);


    public static readonly DependencyProperty DefaultValueProperty =
        DependencyProperty.RegisterAttached("DefaultValue", typeof(string), typeof(TextBoxHelper),
            new PropertyMetadata(null, DependencyPropertiesChanged));
    public static void SetDefaultValue(TextBox element, string value) =>
        element.SetValue(DefaultValueProperty, value);
    public static string GetDefaultValue(TextBox element) => (string) element.GetValue(DefaultValueProperty);


    public static readonly DependencyProperty EvenOddConstraintProperty =
        DependencyProperty.RegisterAttached("EvenOddConstraint", typeof(EvenOddConstraint), typeof(TextBoxHelper),
            new PropertyMetadata(EvenOddConstraint.All, DependencyPropertiesChanged));
    public static void SetEvenOddConstraint(TextBox element, EvenOddConstraint value) =>
        element.SetValue(EvenOddConstraintProperty, value);
    public static EvenOddConstraint GetEvenOddConstraint(TextBox element) =>
        (EvenOddConstraint)element.GetValue(EvenOddConstraintProperty);

    #endregion

    #region Dependency Properties Methods

    private static void DependencyPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (!(d is TextBox textBox))
            throw new Exception("Attached property must be used with TextBox.");

        switch (e.Property.Name)
        {
            case "OnlyNumeric":
            {
                var castedValue = (NumericFormat?) e.NewValue;

                if (castedValue.HasValue)
                {
                    textBox.PreviewTextInput += TextBox_PreviewTextInput;
                    DataObject.AddPastingHandler(textBox, TextBox_PasteEventHandler);
                }
                else
                {
                    textBox.PreviewTextInput -= TextBox_PreviewTextInput;
                    DataObject.RemovePastingHandler(textBox, TextBox_PasteEventHandler);
                }

                break;
            }

            case "DefaultValue":
            {
                var castedValue = (string) e.NewValue;

                if (castedValue != null)
                {
                    textBox.TextChanged += TextBox_TextChanged;
                }
                else
                {
                    textBox.TextChanged -= TextBox_TextChanged;
                }

                break;
            }
        }
    }

    #endregion

    private static void TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
    {
        var textBox = (TextBox)sender;

        string newText;

        if (textBox.SelectionLength == 0)
        {
            newText = textBox.Text.Insert(textBox.SelectionStart, e.Text);
        }
        else
        {
            var textAfterDelete = textBox.Text.Remove(textBox.SelectionStart, textBox.SelectionLength);

            newText = textAfterDelete.Insert(textBox.SelectionStart, e.Text);
        }

        var evenOddConstraint = GetEvenOddConstraint(textBox);

        switch (GetOnlyNumeric(textBox))
        {
            case NumericFormat.Double:
            {
                if (double.TryParse(newText, out double number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;
                    }
                }
                else
                    e.Handled = true;

                break;
            }

            case NumericFormat.Int:
            {
                if (int.TryParse(newText, out int number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;
                    }
                }
                else
                    e.Handled = true;

                break;
            }

            case NumericFormat.Uint:
            {
                if (uint.TryParse(newText, out uint number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                e.Handled = true;
                            else
                                e.Handled = false;

                            break;
                    }
                }
                else
                    e.Handled = true;

                break;
            }

            case NumericFormat.Natural:
            {
                if (uint.TryParse(newText, out uint number))
                {
                    if (number == 0)
                        e.Handled = true;
                    else
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.Handled = true;
                                else
                                    e.Handled = false;

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.Handled = true;
                                else
                                    e.Handled = false;

                                break;
                        }
                    }
                }
                else
                    e.Handled = true;

                break;
            }
        }
    }

    private static void TextBox_PasteEventHandler(object sender, DataObjectPastingEventArgs e)
    {
        var textBox = (TextBox)sender;

        if (e.DataObject.GetDataPresent(typeof(string)))
        {
            var clipboardText = (string) e.DataObject.GetData(typeof(string));

            var newText = textBox.Text.Insert(textBox.SelectionStart, clipboardText);

            var evenOddConstraint = GetEvenOddConstraint(textBox);

            switch (GetOnlyNumeric(textBox))
            {
                case NumericFormat.Double:
                {
                    if (double.TryParse(newText, out double number))
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.CancelCommand();

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.CancelCommand();

                                break;
                        }
                    }
                    else
                        e.CancelCommand();

                    break;
                }

                case NumericFormat.Int:
                {
                    if (int.TryParse(newText, out int number))
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.CancelCommand();

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.CancelCommand();


                                break;
                        }
                    }
                    else
                        e.CancelCommand();

                    break;
                }

                case NumericFormat.Uint:
                {
                    if (uint.TryParse(newText, out uint number))
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    e.CancelCommand();

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    e.CancelCommand();


                                break;
                        }
                    }
                    else
                        e.CancelCommand();

                    break;
                }

                case NumericFormat.Natural:
                {
                    if (uint.TryParse(newText, out uint number))
                    {
                        if (number == 0)
                            e.CancelCommand();
                        else
                        {
                            switch (evenOddConstraint)
                            {
                                case EvenOddConstraint.OnlyEven:

                                    if (number % 2 != 0)
                                        e.CancelCommand();

                                    break;

                                case EvenOddConstraint.OnlyOdd:

                                    if (number % 2 == 0)
                                        e.CancelCommand();

                                    break;
                            }
                        }
                    }
                    else
                    {
                        e.CancelCommand();
                    }

                    break;
                }
            }
        }
        else
        {
            e.CancelCommand();
        }
    }

    private static void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        var textBox = (TextBox)sender;

        var defaultValue = GetDefaultValue(textBox);

        var evenOddConstraint = GetEvenOddConstraint(textBox);

        switch (GetOnlyNumeric(textBox))
        {
            case NumericFormat.Double:
            {
                if (double.TryParse(textBox.Text, out double number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                textBox.Text = defaultValue;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                textBox.Text = defaultValue;

                            break;
                    }
                }
                else
                    textBox.Text = defaultValue;

                break;
            }

            case NumericFormat.Int:
            {
                if (int.TryParse(textBox.Text, out int number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                textBox.Text = defaultValue;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                textBox.Text = defaultValue;

                            break;
                    }
                }
                else
                    textBox.Text = defaultValue;

                break;
            }

            case NumericFormat.Uint:
            {
                if (uint.TryParse(textBox.Text, out uint number))
                {
                    switch (evenOddConstraint)
                    {
                        case EvenOddConstraint.OnlyEven:

                            if (number % 2 != 0)
                                textBox.Text = defaultValue;

                            break;

                        case EvenOddConstraint.OnlyOdd:

                            if (number % 2 == 0)
                                textBox.Text = defaultValue;

                            break;
                    }
                }
                else
                    textBox.Text = defaultValue;

                break;
            }

            case NumericFormat.Natural:
            {
                if (uint.TryParse(textBox.Text, out uint number))
                {
                    if(number == 0)
                        textBox.Text = defaultValue;
                    else
                    {
                        switch (evenOddConstraint)
                        {
                            case EvenOddConstraint.OnlyEven:

                                if (number % 2 != 0)
                                    textBox.Text = defaultValue;

                                break;

                            case EvenOddConstraint.OnlyOdd:

                                if (number % 2 == 0)
                                    textBox.Text = defaultValue;

                                break;
                        }
                    }
                }
                else
                {
                    textBox.Text = defaultValue;
                }

                break;
            }
        }
    }
}

A oto przykład jego łatwego użycia:

<TextBox viewHelpers:TextBoxHelper.OnlyNumeric="Double"
         viewHelpers:TextBoxHelper.DefaultValue="1"/>

Lub

<TextBox viewHelpers:TextBoxHelper.OnlyNumeric="Natural"
         viewHelpers:TextBoxHelper.DefaultValue="3"
         viewHelpers:TextBoxHelper.EvenOddConstraint="OnlyOdd"/>

Zauważ, że mój TextBoxHelper znajduje się w aliasie viewHelpers xmlns.

Mam nadzieję, że ta implementacja ułatwia pracę innym :)

Amir Mahdi Nassiri
źródło
1
To świetnie, gdy używasz pola tekstowego wewnątrz DataTemplate, dzięki!
NucS
3
Świetna odpowiedź, ale twoje metody są trudne do odczytania. Prawdopodobnie powinieneś podzielić je na mniejsze. Zobacz, jaka jest dla Ciebie idealna długość metody?
Anthony
Dziękujemy za konstruktywną informację zwrotną @Anthony
Amir Mahdi Nassiri,
4
e.Handled = (int)e.Key >= 43 || (int)e.Key <= 34;

w podglądzie zdarzenie keydown pola tekstowego.

Nowicjusz
źródło
3
Nie pozwala jednak na cofanie się.
sventevit
2
Backspace to 2, tab to 3
Daniel
6
-1, ponieważ z mojego doświadczenia wynika, że ​​takie inteligentne sztuczki w końcu gryzą cię w tyłek, jak zauważyli inni komentatorzy.
DonkeyMaster
Lewa strzałka to 23, prawa strzałka to 25.
Aaron
4
PreviewTextInput += (s, e) =>
{
    e.Handled = !e.Text.All(char.IsDigit);
};
Julian Kowalczuk
źródło
2
to również nie akceptuje kropki ., ponieważ e.Textzwraca tylko ostatni wprowadzony znak, a kropka nie powiedzie się podczas IsDigitsprawdzania.
Anthony
4

Dla tych, którzy szukają szybkiej i bardzo prostej implementacji tego typu problemu, używając tylko liczb całkowitych i dziesiętnych, w pliku XAML dodaj PreviewTextInputwłaściwość do pliku, TextBoxa następnie do pliku xaml.cs:

private void Text_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    e.Handled = !char.IsDigit(e.Text.Last()) && !e.Text.Last() == '.';
}

Nadmiarowe jest sprawdzanie całego łańcucha za każdym razem, chyba że, jak wspomnieli inni, robisz coś z notacją naukową (aczkolwiek, jeśli dodajesz pewne znaki, takie jak „e”, proste wyrażenie regularne dodające symbole / znaki to bardzo proste i zilustrowane w innych odpowiedziach). Ale dla prostych wartości zmiennoprzecinkowych to rozwiązanie wystarczy.

Napisane jako jednowarstwowe z wyrażeniem lambda:

private void Text_PreviewTextInput(object sender, TextCompositionEventArgs e) => e.Handled = !char.IsDigit(e.Text.Last() && !e.Text.Last() == '.');
Chris
źródło
3

Możemy przeprowadzić weryfikację zdarzenia zmienionego w polu tekstowym. Poniższa implementacja uniemożliwia wprowadzanie naciśnięć klawiszy innych niż numeryczne i jeden przecinek dziesiętny.

private void textBoxNumeric_TextChanged(object sender, TextChangedEventArgs e) 
{         
      TextBox textBox = sender as TextBox;         
      Int32 selectionStart = textBox.SelectionStart;         
      Int32 selectionLength = textBox.SelectionLength;         
      String newText = String.Empty;         
      int count = 0;         
      foreach (Char c in textBox.Text.ToCharArray())         
      {             
         if (Char.IsDigit(c) || Char.IsControl(c) || (c == '.' && count == 0))             
         {                 
            newText += c;                 
            if (c == '.')                     
              count += 1;             
         }         
     }         
     textBox.Text = newText;         
     textBox.SelectionStart = selectionStart <= textBox.Text.Length ? selectionStart :        textBox.Text.Length;     
} 
Kumar Gouraw
źródło
3

Co powiesz na to? Działa dla mnie dobrze. Mam nadzieję, że nie przegapiłem żadnych skrzyń ...

MyTextBox.PreviewTextInput += (sender, args) =>
{
    if (!int.TryParse(args.Text, out _))
    {
        args.Handled = true;
    }
};

DataObject.AddPastingHandler(MyTextBox, (sender, args) =>
{
    var isUnicodeText = args.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true);
    if (!isUnicodeText)
    {
        args.CancelCommand();
    }

    var data = args.SourceDataObject.GetData(DataFormats.UnicodeText) as string;
    if (!int.TryParse(data, out _))
    {
        args.CancelCommand();
    }
});
Shahin Dohan
źródło
2

W Windows Forms było to łatwe; możesz dodać wydarzenie do KeyPress i wszystko działa łatwo. Jednak w WPF tego zdarzenia nie ma. Ale jest na to znacznie łatwiejszy sposób.

WPF TextBox ma zdarzenie TextChanged, które jest ogólne dla wszystkiego. Obejmuje to wklejanie, pisanie na klawiaturze i wszystko, co może przyjść ci do głowy.

Możesz więc zrobić coś takiego:

XAML:

<TextBox name="txtBox1" ... TextChanged="TextBox_TextChanged"/>

KOD ZA:

private void TextBox_TextChanged(object sender, TextChangedEventArgs e) {
    string s = Regex.Replace(((TextBox)sender).Text, @"[^\d.]", "");
    ((TextBox)sender).Text = s;
}

Akceptuje to również ., jeśli nie chcesz, po prostu usuń go z regexinstrukcji@[^\d] .

Uwaga : To zdarzenie może być użyte na wielu obiektach TextBox, ponieważ wykorzystuje ono sendertekst obiektu. Zapisujesz zdarzenie tylko raz i możesz go użyć dla wielu TextBoxów.

Wszyscy
źródło
2

Teraz wiem, że to pytanie ma zaakceptowaną odpowiedź , ale osobiście uważam, że jest to trochę mylące i uważam, że powinno być łatwiejsze. Spróbuję więc wykazać, w jaki sposób sprawiłem, by działał jak najlepiej:

W Windows Forms istnieje zdarzenie o nazwie KeyPressdoskonale nadające się do tego rodzaju zadania. Ale to nie istnieje w WPF , więc zamiast tego będziemy używać PreviewTextInputzdarzenia. Ponadto do walidacji uważam, że można użyć foreachpętli do textbox.Textsprawdzenia i sprawdzenia, czy pasuje ;) warunek, ale szczerze mówiąc, to właśnie wyrażenia regularne służą .

Jeszcze jedna rzecz, zanim zanurzymy się w świętym kodeksie . Aby zdarzenie zostało odpalone, można zrobić dwie rzeczy:

  1. Użyj XAML, aby powiedzieć programowi, którą funkcję wywołać: <PreviewTextInput="textBox_PreviewTextInput/>
  2. Zrób to w Loadedprzypadku formularza (w którym znajduje się textBox): textBox.PreviewTextInput += onlyNumeric;

Myślę, że druga metoda jest lepsza, ponieważ w sytuacjach takich jak ten, najczęściej będziesz musiał zastosować ten sam warunek ( regex) do więcej niż jednego TextBoxi nie chcesz się powtarzać! .

Wreszcie, oto jak to zrobić:

private void onlyNumeric(object sender, TextCompositionEventArgs e)
{
    string onlyNumeric = @"^([0-9]+(.[0-9]+)?)$";
    Regex regex = new Regex(onlyNumeric);
    e.Handled = !regex.IsMatch(e.Text);
}
Amir A. Shabani
źródło
2

Oto moja wersja. Opiera się na ValidatingTextBoxklasie bazowej, która po prostu cofa to, co zostało zrobione, jeśli nie jest „prawidłowe”. Obsługuje wklejanie, wycinanie, usuwanie, cofanie, +, - itp.

Dla 32-bitowej liczby całkowitej istnieje klasa Int32TextBox, która po prostu porównuje się z liczbą całkowitą. Dodałem także klasy sprawdzania zmiennoprzecinkowego.

public class ValidatingTextBox : TextBox
{
    private bool _inEvents;
    private string _textBefore;
    private int _selectionStart;
    private int _selectionLength;

    public event EventHandler<ValidateTextEventArgs> ValidateText;

    protected override void OnPreviewKeyDown(KeyEventArgs e)
    {
        if (_inEvents)
            return;

        _selectionStart = SelectionStart;
        _selectionLength = SelectionLength;
        _textBefore = Text;
    }

    protected override void OnTextChanged(TextChangedEventArgs e)
    {
        if (_inEvents)
            return;

        _inEvents = true;
        var ev = new ValidateTextEventArgs(Text);
        OnValidateText(this, ev);
        if (ev.Cancel)
        {
            Text = _textBefore;
            SelectionStart = _selectionStart;
            SelectionLength = _selectionLength;
        }
        _inEvents = false;
    }

    protected virtual void OnValidateText(object sender, ValidateTextEventArgs e) => ValidateText?.Invoke(this, e);
}

public class ValidateTextEventArgs : CancelEventArgs
{
    public ValidateTextEventArgs(string text) => Text = text;

    public string Text { get; }
}

public class Int32TextBox : ValidatingTextBox
{
    protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !int.TryParse(e.Text, out var value);
}

public class Int64TextBox : ValidatingTextBox
{
    protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !long.TryParse(e.Text, out var value);
}

public class DoubleTextBox : ValidatingTextBox
{
    protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !double.TryParse(e.Text, out var value);
}

public class SingleTextBox : ValidatingTextBox
{
    protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !float.TryParse(e.Text, out var value);
}

public class DecimalTextBox : ValidatingTextBox
{
    protected override void OnValidateText(object sender, ValidateTextEventArgs e) => e.Cancel = !decimal.TryParse(e.Text, out var value);
}

Uwaga 1: Używając powiązania WPF, musisz upewnić się, że używasz klasy, która pasuje do powiązanego typu właściwości, w przeciwnym razie może to prowadzić do dziwnych wyników.

Uwaga 2: Jeśli używasz klas zmiennoprzecinkowych z powiązaniem WPF, upewnij się, że powiązanie używa bieżącej kultury w celu dopasowania metody TryParse, której użyłem.

Simon Mourier
źródło
1

Posługiwać się:

Private Sub DetailTextBox_PreviewTextInput( _
  ByVal sender As Object, _
  ByVal e As System.Windows.Input.TextCompositionEventArgs) _
  Handles DetailTextBox.PreviewTextInput

    If _IsANumber Then
        If Not Char.IsNumber(e.Text) Then
            e.Handled = True
        End If
    End If
End Sub
Jasio
źródło
Wytłumaczenie byłoby w porządku.
Peter Mortensen,
1

Pracowałem z niezwiązanym pudełkiem dla prostego projektu, nad którym pracowałem, więc nie mogłem zastosować standardowego podejścia do wiązania. W związku z tym stworzyłem prosty hack, który inni mogą uznać za przydatny, po prostu rozszerzając istniejącą kontrolę TextBox:

namespace MyApplication.InterfaceSupport
{
    public class NumericTextBox : TextBox
    {


        public NumericTextBox() : base()
        {
            TextChanged += OnTextChanged;
        }


        public void OnTextChanged(object sender, TextChangedEventArgs changed)
        {
            if (!String.IsNullOrWhiteSpace(Text))
            {
                try
                {
                    int value = Convert.ToInt32(Text);
                }
                catch (Exception e)
                {
                    MessageBox.Show(String.Format("{0} only accepts numeric input.", Name));
                    Text = "";
                }
            }
        }


        public int? Value
        {
            set
            {
                if (value != null)
                {
                    this.Text = value.ToString();
                }
                else 
                    Text = "";
            }
            get
            {
                try
                {
                    return Convert.ToInt32(this.Text);
                }
                catch (Exception ef)
                {
                    // Not numeric.
                }
                return null;
            }
        }
    }
}

Oczywiście dla typu zmiennoprzecinkowego chciałbyś go parsować jako zmiennoprzecinkowe i tak dalej. Obowiązują te same zasady.

Następnie w pliku XAML musisz dołączyć odpowiednią przestrzeń nazw:

<UserControl x:Class="MyApplication.UserControls.UnParameterisedControl"
             [ Snip ]
             xmlns:interfaceSupport="clr-namespace:MyApplication.InterfaceSupport"
             >

Następnie możesz używać go jako zwykłej kontroli:

<interfaceSupport:NumericTextBox Height="23" HorizontalAlignment="Left" Margin="168,51,0,0" x:Name="NumericBox" VerticalAlignment="Top" Width="120" >
glenatron
źródło
1

Po pewnym czasie korzystania z niektórych tutaj rozwiązań, opracowałem własny, który działa dobrze dla mojej konfiguracji MVVM. Zauważ, że nie jest tak dynamiczny jak niektóre inne w tym sensie, że wciąż pozwala użytkownikom wprowadzać błędne znaki, ale blokuje im to możliwość naciśnięcia przycisku i zrobienia czegokolwiek. Jest to zgodne z moim motywem wyszarzania przycisków, gdy nie można wykonać akcji.

Mam, TextBoxże użytkownik musi wprowadzić wiele stron dokumentu do wydrukowania:

<TextBox Text="{Binding NumberPagesToPrint, UpdateSourceTrigger=PropertyChanged}"/>

... z tą właściwością wiązania:

private string _numberPagesToPrint;
public string NumberPagesToPrint
{
    get { return _numberPagesToPrint; }
    set
    {
        if (_numberPagesToPrint == value)
        {
            return;
        }

        _numberPagesToPrint = value;
        OnPropertyChanged("NumberPagesToPrint");
    }
}

Mam też przycisk:

<Button Template="{DynamicResource CustomButton_Flat}" Content="Set"
        Command="{Binding SetNumberPagesCommand}"/>

... z tym wiązaniem polecenia:

private RelayCommand _setNumberPagesCommand;
public ICommand SetNumberPagesCommand
{
    get
    {
        if (_setNumberPagesCommand == null)
        {
            int num;
            _setNumberPagesCommand = new RelayCommand(param => SetNumberOfPages(),
                () => Int32.TryParse(NumberPagesToPrint, out num));
        }

        return _setNumberPagesCommand;
    }
}

Jest też metoda SetNumberOfPages(), ale nie ma znaczenia dla tego tematu. Działa dobrze w moim przypadku, ponieważ nie muszę dodawać żadnego kodu do pliku za kodem widoku i pozwala mi kontrolować zachowanie za pomocą Commandwłaściwości.

BK
źródło
1

W aplikacji WPF możesz sobie z tym poradzić, obsługując TextChangedzdarzenie:

void arsDigitTextBox_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)
{
    Regex regex = new Regex("[^0-9]+");
    bool handle = regex.IsMatch(this.Text);
    if (handle)
    {
        StringBuilder dd = new StringBuilder();
        int i = -1;
        int cursor = -1;
        foreach (char item in this.Text)
        {
            i++;
            if (char.IsDigit(item))
                dd.Append(item);
            else if(cursor == -1)
                cursor = i;
        }
        this.Text = dd.ToString();

        if (i == -1)
            this.SelectionStart = this.Text.Length;
        else
            this.SelectionStart = cursor;
    }
}
Mehdi Khademloo
źródło
1

Dla programistów, którzy chcą, aby ich pola tekstowe akceptowały tylko niepodpisane liczby, takie jak porty gniazd itp.:

WPF

<TextBox PreviewTextInput="Port_PreviewTextInput" MaxLines="1"/>

DO#

private void Port_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
    e.Handled = !int.TryParse(e.Text, out int x);
}
Beyondo
źródło
2
Zauważ, że jeśli naprawdę chcesz użyć tej metody z polem portu gniazda; Musisz sprawdzić, czy liczba całkowita jest mniejsza lub równa 65535. Jeśli jest większy, to nie jest prawidłowy port. Również ustawienie TextBox.MaxLengthTO 5pomogłoby albo programowo lub w XAML .
Beyondo
0

Oto, czego użyłbym, aby uzyskać pole tekstowe WPF, które akceptuje cyfry i kropkę dziesiętną:

class numericTextBox : TextBox
{
    protected override void OnKeyDown(KeyEventArgs e)
    {
        bool b = false;
        switch (e.Key)
        {
            case Key.Back: b = true; break;
            case Key.D0: b = true; break;
            case Key.D1: b = true; break;
            case Key.D2: b = true; break;
            case Key.D3: b = true; break;
            case Key.D4: b = true; break;
            case Key.D5: b = true; break;
            case Key.D6: b = true; break;
            case Key.D7: b = true; break;
            case Key.D8: b = true; break;
            case Key.D9: b = true; break;
            case Key.OemPeriod: b = true; break;
        }
        if (b == false)
        {
            e.Handled = true;
        }
        base.OnKeyDown(e);
    }
}

Umieść kod w nowym pliku klasy, dodaj

using System.Windows.Controls;
using System.Windows.Input;

u góry pliku i skompiluj rozwiązanie. Kontrolka numericTextBox pojawi się na górze przybornika.

matsolof
źródło
1
Zobacz wcześniejsze DUŻO łatwiejsze rozwiązanie za pomocą NumberValidationTextBox i wyrażeń regularnych. To jest niedorzeczne.
Scott Shaw-Smith
@ ScottShaw-Smith Być może zaakceptowanym rozwiązaniem jest mniej kodu, ale nie jest on szybszy. Zawsze są pewne projekty, które wymagają dużej mocy obliczeniowej zamiast używania wyrażenia regularnego.
Beyondo