Visual Studio c# utilizza macchine a stati per limitare l'input nella casella di testo. Proprietà allegate per limitare l'immissione di testo

Gli antipiretici per i bambini sono prescritti da un pediatra. Ma ci sono situazioni di emergenza per la febbre in cui il bambino deve ricevere immediatamente le medicine. Quindi i genitori si assumono la responsabilità e usano farmaci antipiretici. Cosa è permesso dare ai bambini? Come abbassare la temperatura nei bambini più grandi? Quali farmaci sono i più sicuri?

WPF è tutt'altro che una nuova tecnologia sul mercato, ma relativamente nuova per me. E, come spesso accade quando si impara qualcosa di nuovo, c'è il desiderio/necessità di inventare biciclette con ruote quadre e cerchi in lega per risolvere alcuni problemi tipici.

Una di queste attività consiste nel limitare l'input dell'utente a determinati dati. Ad esempio, supponiamo di volere che una casella di testo accetti solo valori interi, un'altra che accetti una data in un formato specifico e una terza che accetti solo numeri in virgola mobile. Naturalmente, la convalida finale di tali valori avverrà ancora nei modelli di visualizzazione, ma tali restrizioni di input rendono l'interfaccia utente più intuitiva.

In Windows Forms, questa attività è stata risolta abbastanza facilmente e quando lo stesso TextBox di DevExpress era disponibile con la capacità integrata di limitare l'input utilizzando espressioni regolari, tutto era generalmente semplice. Ci sono alcuni esempi di risoluzione dello stesso problema in WPF, la maggior parte dei quali si riduce a una delle due opzioni: utilizzare un erede di classe TextBox o aggiungere una proprietà allegata con le restrizioni necessarie.

NOTA
Se non sei molto interessato al mio ragionamento, ma hai immediatamente bisogno di esempi di codice, puoi scaricare l'intero progetto
WpfEx da GitHub o scaricare l'implementazione principale, contenuta in TextBoxBehavior.cs e TextBoxDoubleValidator.cs .

Bene, iniziamo?

Poiché l'ereditarietà introduce una restrizione piuttosto rigida, personalmente preferisco in questo caso utilizzare le proprietà allegate, poiché questo meccanismo consente di limitare l'applicazione di queste proprietà a controlli di un certo tipo (non voglio che questa proprietà allegata È Doppio potrebbe essere applicato a un TextBlock per il quale non ha senso).
Inoltre, va notato che quando si limita l'input dell'utente, non è possibile utilizzare alcuno specifico separatore intero e frazionario (come '.' (punto) o ',' (virgola)), così come i segni '+' e ' -', poiché tutto dipende dalle impostazioni regionali dell'utente.
Per implementare la possibilità di limitare l'input dei dati, dobbiamo intercettare manualmente l'evento di input dell'utente, analizzarlo e scartare queste modifiche se non ci soddisfano. A differenza di Windows Forms, che utilizza una coppia di eventi XXXCambiato e XXXCambiamento, WPF utilizza le versioni di anteprima degli eventi per lo stesso scopo, che possono essere elaborati in modo tale che l'evento principale non venga attivato. (Un classico esempio potrebbe essere la gestione di eventi del mouse o della tastiera che disabilitano determinati tasti o le loro combinazioni.)

E tutto andrebbe bene se contenesse anche la classe TextBox, insieme all'evento TextChanged PreviewTextChanged, che potrebbe essere elaborato e "interrompere" l'input dell'utente se riteniamo che il testo di input non sia corretto. E siccome non esiste, allora tutti e tutti devono inventare la propria lisapet.

La soluzione del problema

La soluzione al problema è creare una classe TextBoxBehavior che contenga la proprietà IsDoubleProperty allegata, dopo aver impostato la quale l'utente non sarà in grado di inserire altro che +, -, caratteri in questo campo di testo. (separatore di numeri interi e decimali), nonché numeri (non dimenticare che è necessario utilizzare le impostazioni del flusso corrente e non i valori hardcoded).

Classe pubblica TextBoxBehavior ( // Proprietà allegata di tipo booleano, impostazione che limiterà l'input dell'utente public static readonly DependencyProperty IsDoubleProperty = DependencyProperty.RegisterAttached("IsDouble", typeof (bool), typeof (TextBoxBehavior), new FrameworkPropertyMetadata(false, OnIsDoubleChanged)) ; // Questo attributo non consentirà l'utilizzo di IsDouble con // elementi dell'interfaccia utente diversi da TextBox o suoi discendenti public static bool GetIsDouble(elemento DependencyObject) () public static void SetIsDouble(elemento DependencyObject, valore bool) () // Called quando TextBoxBehavior.IsDouble="True" è impostato in XAML private static void OnIsDoubleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) ( // Street magic ) )

La principale complessità dell'implementazione del gestore PreviewTextInput(così come l'evento di incollaggio del testo dagli appunti) sta nel fatto che non viene trasmesso il valore totale del testo negli argomenti dell'evento, ma solo la parte appena inserita di esso. Pertanto, il testo di riepilogo deve essere formato manualmente, tenendo conto della possibilità di selezionare il testo nella TextBox, la posizione corrente del cursore in essa ed, eventualmente, lo stato del pulsante Inserisci (che non analizzeremo):

// Chiamato quando TextBoxBehavior.IsDouble="True" è impostato in XAML private static void OnIsDoubleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) ( // Poiché abbiamo limitato la nostra proprietà allegata solo alla // classe TextBox o ai suoi discendenti, quanto segue la trasformazione è - safe var textBox = (TextBox) d; // Ora dobbiamo gestire due casi importanti: // 1. Input manuale dell'utente // 2. Incollare i dati dagli appunti textBox.PreviewTextInput += PreviewTextInputForDouble; DataObject.AddPastingHandler( textBox, OnPasteForDouble ); )

Classe TextBoxDoubleValidator

Il secondo punto importante è l'implementazione della logica di validazione del testo appena inserito, la cui responsabilità è affidata al metodo È valido classe separata TextBoxDoubleValidator.

al massimo in modo semplice capire come dovrebbe comportarsi il metodo È valido di questa classe, è scrivere un test unitario per esso che copra tutti i casi angolari (questo è solo uno di quei casi in cui i test unitari parametrizzati governano con una forza terribile):

NOTA
Questo è esattamente il caso in cui uno unit test non è solo un test che verifica la correttezza dell'implementazione di determinate funzionalità. Questo è esattamente il caso di cui Kent Beck ha parlato ripetutamente quando ha descritto la responsabilità; leggendo questo test si può capire a cosa stesse pensando lo sviluppatore del metodo di validazione, “riutilizzare” le sue conoscenze e trovare errori nel suo ragionamento e, quindi, probabilmente nel codice di implementazione. Non è solo una suite di test: è una parte importante delle specifiche per questo metodo!

Privato statico void PreviewTextInputForDouble(object sender, TextCompositionEventArgs e) ( // e.Text contiene solo nuovo testo, quindi lo stato corrente // del TextBox è indispensabile var textBox = (TextBox)sender; string fullText; // Se il TextBox contiene testo selezionato, quindi sostituirlo con e.Text if (textBox.SelectionLength > 0) ( fullText = textBox.Text.Replace(textBox.SelectedText, e.Text); ) else ( // Altrimenti, dobbiamo inserire un nuovo testo in la posizione del cursore fullText = textBox.Text.Insert(textBox.CaretIndex, e.Text); ) // Ora convalida il testo risultante bool isTextValid = TextBoxDoubleValidator.IsValid(fullText); // E impedisce l'evento TextChanged se il testo non è valido e.Handled = !isTextValid; )

Il metodo di prova ritorna VERO se il parametro testoè valido, il che significa che è possibile digitare il testo corrispondente casella di testo con annesso immobile IsDouble. Presta attenzione ad alcune cose: (1) l'uso dell'attributo SetCultura, che imposta la locale desiderata e (2) alcuni valori di input, come "-.", che non sono valori validi per il tipo Doppio.

È necessaria un'impostazione locale esplicita in modo che i test non falliscano per gli sviluppatori con altre impostazioni personali, perché nella lingua russa, il simbolo ',' (virgola) viene utilizzato come separatore e in quella americana - '.' (punto ). Testo strano come "-". è corretto perché è necessario che l'utente completi l'input se desidera inserire la stringa "-.1", che è il valore corretto per Doppio. (È interessante notare che StackOverflow è molto spesso consigliato di utilizzare semplicemente Double.TryParse per risolvere questo problema, che ovviamente non funzionerà in alcuni casi).

NOTA
Non voglio sporcare l'articolo con i dettagli di implementazione del metodo È valido, voglio solo notare l'uso del metodo ThreadLocal nel corpo di questo , che ti consente di ottenere e memorizzare nella cache Doppio separatore locale per ogni thread. Piena attuazione del metodo TextBoxDoubleValidator.IsValid puoi trovare maggiori informazioni su ThreadLocal Puoi leggere l'articolo di Joe Albahari Lavorare con i thread. Parte 3.

Soluzioni alternative

Oltre a catturare gli eventi PreviewTextInput e incollando il testo dagli appunti, ci sono altre soluzioni. Così, ad esempio, ho incontrato un tentativo di risolvere lo stesso problema intercettando l'evento PreviewKeyDown con filtraggio di tutte le chiavi tranne quelle digitali. Tuttavia, questa soluzione è più complicata, perché devi ancora preoccuparti dello stato "riassunto" del TextBox e, puramente teoricamente, il separatore delle parti intere e frazionarie può essere non un carattere, ma l'intera stringa (NumberFormatInfo.NumberDecimalSeparator ritorna corda, ma no car).
C'è un'altra opzione nell'evento chiave giù salva lo stato precedente TextBox.Text, e nell'evento Testo modificato restituirgli il vecchio valore se il nuovo valore non è adatto. Ma questa soluzione sembra innaturale e non sarà così facile implementarla utilizzando le proprietà allegate.

Conclusione

L'ultima volta che abbiamo discusso con i colleghi della mancanza di funzionalità utili e molto tipiche in WPF, siamo giunti alla conclusione che c'è una spiegazione per questo, e c'è un lato positivo in questo. La spiegazione si riduce al fatto che non c'è scampo dalla legge delle astrazioni che perdono, e WPF, essendo una "astrazione" molto complessa, scorre come un setaccio. Il lato utile è che la mancanza di alcune funzioni utili a volte ci fa pensare (!) e non dimentichiamo che siamo programmatori, non artigiani del copia-incolla.

Vi ricordo che la piena implementazione delle classi delle classi sopra, esempi del loro utilizzo e unit test si possono trovare su github.

Tag: aggiungi tag

In questo manuale, prenderemo in considerazione l'inserimento solo di numeri dell'utente. A Microsoft Visual Studio c'è un controllo" MaskedTextBox”, con la sua maschera di input personalizzabile, ma daremo per scontato che oggi ci interessa solo« casella di testo».
Per implementare questa attività, utilizzeremo l'evento " pressione dei tasti” che si verifica quando si preme un tasto mentre il controllo ha la messa a fuoco. Crea un progetto modulo di finestre in Microsoft Visual Studio e aggiungi un controllo al form principale casella di testo". Seleziona questo componente e fai clic destro su di esso, seleziona dal menu contestuale che compare, la voce " Proprietàpressione dei tastitextBox1_KeyPress", sviluppi" pressione dei tasti».
Con ogni evento pressione dei tasti» oggetto passato « KeyPressEventArgs". Questo oggetto include la proprietà " chiave” che rappresenta il carattere del tasto premuto. Ad esempio, premendo i tasti MAIUSC + D, questa proprietà restituisce il carattere D maiuscolo e il suo codice 68. Esiste anche una proprietà " gestito' che viene utilizzato per determinare se l'evento è stato gestito. Impostando il valore " gestito" in " VERO", l'evento di input non verrà inviato sistema operativo per l'elaborazione predefinita.
Diamo un'occhiata ad alcuni esempi di creazione di restrizioni per l'immissione di dati in un campo di testo.
Esempio 1:
textBox1_KeyPress".

se ((e.KeyChar<= 47 || e.KeyChar >= 58) && e.KeyChar != 8) e.Handled = true; Questo esempio include condizioni composte che utilizzano operatori logici come &&(e), || (o), ! (non) e controlla il codice decimale del carattere inserito, secondo due condizioni:

  • "e.KeyChar != 8" - Se è stato premuto il tasto "Backspace", consenti la cancellazione dei caratteri.
  • "(e.KeyChar<= 47 || e.KeyChar >= 58 )" - Se il carattere inserito ha un codice ASCII minore o uguale a 47 e maggiore o uguale a 58, l'immissione è vietata.
Sotto è Tabella codici ASCII, in cui i caratteri sono evidenziati in rosso, il cui ingresso è vietato e in verde, il cui ingresso è consentito.

Esempio 2:
Aggiungi il seguente elenco a " textBox1_KeyPress».
if (!Char.IsDigit(e.KeyChar) && e.KeyChar != Convert.ToChar(8)) ( e.Handled = true; ) Questo esempio utilizza anche operatori logici come &&(e), ! (non) e il codice decimale del carattere inserito viene verificato, secondo due condizioni. Il metodo viene utilizzato per verificare Char.IsDigit", che restituisce " VERO"" se il carattere Unicode inserito è una cifra decimale e " falso", altrimenti. C'è anche un segno di spunta per la pressione del tasto " spazio indietro».
Esempio 3:
if (!(Char.IsDigit(e.KeyChar)) && !((e.KeyChar == ".") && (((TextBox)sender).Text.IndexOf(".") == -1) && ( ((TextBox)sender).Text.Length != 0))) ( if (e.KeyChar != (char)Keys.Back) ( e.Handled = true; ) ) In questo esempio, così come nel precedente, il codice del carattere di input viene verificato utilizzando il tasto " Char.IsDigit”, ma esiste una condizione aggiuntiva che ne consente l'immissione Separatore decimale. Per questo, viene utilizzato il metodo Text.IndexOf". Questo metodo cerca un carattere punto in base alle parole usando le impostazioni cultura correnti. La ricerca inizia dalla prima posizione del carattere in questo caso (la riga corrente) e continua fino all'ultima posizione del carattere. Se il simbolo indicato non è stato trovato, il metodo restituisce il valore " -1 ". Se il carattere è stato trovato, il metodo restituisce un numero intero decimale che indica la posizione del carattere dato e vieta l'elaborazione del carattere immesso.

Separatore decimale- il segno utilizzato per separare la parte intera e frazionaria di un numero reale sotto forma di frazione decimale nel sistema decimale. Per le frazioni in altri sistemi numerici, è possibile utilizzare il termine separatore della parte intera e frazionaria di un numero. A volte possono essere utilizzati anche i termini punto decimale e punto decimale. (http://ru.wikipedia.org).
Per maggiori informazioni sul metodo Text.IndexOf» è possibile accedere a: http://msdn.microsoft.com .

Esempio 4:
Aggiungi il seguente elenco a " textBox1_KeyPress».

if (!System.Text.RegularExpressions.Regex.Match(e.KeyChar.ToString(), @"").Success) ( e.Handled = true; ) Per controllare i caratteri di input, in questo esempio, il metodo " Corrispondenze regolari". Questo metodo ricerca nella stringa di input tutte le occorrenze dell'espressione regolare data. Un'espressione regolare è un modello di carattere che rappresenta una sequenza di caratteri di lunghezza arbitraria. Puoi specificare qualsiasi espressione regolare, ad esempio, consenti solo caratteri " 'o consenti l'inserimento del separatore decimale ' ,|.| ».
Esempio 5:
Aggiungi il seguente elenco a " textBox1_KeyPress».

if (!System.Text.RegularExpressions.Regex.IsMatch(e.KeyChar.ToString(),@"\d+")) e.Handled = true; Questo esempio usa anche l'espressione regolare data per implementare la convalida del carattere immesso. L'espressione regolare usa " + ”, il che significa che devono essere trovati uno o più caratteri dello stesso tipo. Per esempio, \d+ corrisponde ai numeri "1", "11", "1234", ecc. Se non sei sicuro di quali numeri seguiranno il numero? È possibile specificare che sia consentito un numero qualsiasi di cifre o nessuna cifra. Per questo il simbolo " * ».
Esempio 6:
Per implementare questo esempio, è necessario utilizzare l'evento " chiave giù", l'elemento di controllo " casella di testo1". Vai al costruttore del modulo principale e seleziona il componente " casella di testo1". Effettuare un click destro su questo controllo, selezionare dal menu contestuale che compare, la voce " Proprietà". Nella finestra che si apre, vai su Eventi componente (l'icona del fulmine nella parte superiore della finestra) e trova l'evento " chiave giù”, fare doppio clic su questo evento. Dopo aver completato tutti i passaggi, andrai al metodo generato automaticamente " textBox1_KeyDown", sviluppi" chiave giù". Questo evento si verifica ogni volta che si preme un tasto mentre il controllo ha lo stato attivo.

Eventi di input da tastiera

Quando l'utente preme un tasto, vengono generati una serie di eventi. La tabella elenca questi eventi nell'ordine in cui si verificano:

Cronologia del verificarsi degli eventi
Nome Tipo di instradamento Descrizione
PreviewKeyDown tunneling Si verifica quando viene premuto un tasto.
chiave giù diffusione delle bolle Stesso
PreviewTextInput tunneling Si verifica quando viene completata una sequenza di tasti e l'elemento riceve l'input di testo. Questo evento non si verifica per i tasti che non "stampano" i caratteri (ad esempio, non si verifica quando si premono i tasti , , , tasti cursore, tasti funzione, ecc.)
L'immissione di testo diffusione delle bolle Stesso
PreviewKeyUp tunneling Si verifica quando viene rilasciata una chiave
chiave su diffusione delle bolle Stesso

Gestire gli eventi della tastiera non è affatto facile come potrebbe sembrare. Alcuni controlli possono bloccare alcuni di questi eventi per eseguire la propria gestione della tastiera. L'esempio più notevole è l'elemento TextBox, che blocca l'evento TextInput e l'evento KeyDown per la pressione di determinati tasti, come i tasti cursore. In questi casi, in genere è ancora possibile utilizzare gli eventi con tunnel (PreviewTextInput e PreviewKeyDown).

L'elemento TextBox aggiunge un nuovo evento, TextChanged. Questo evento si attiva immediatamente dopo che una sequenza di tasti modifica il testo nel campo di testo. Tuttavia, a questo punto, il nuovo testo è già visibile nel campo di testo, quindi è troppo tardi per annullare la sequenza di tasti indesiderata.

Gestione delle sequenze di tasti

Capire come funzionano e come vengono utilizzati gli eventi della tastiera è meglio fare con un esempio. Di seguito è riportato un programma di esempio che monitora e registra tutte le possibili sequenze di tasti mentre un campo di testo è attivo. In questo caso, viene mostrato il risultato della digitazione di una S maiuscola.

Questo esempio dimostra un punto importante. Gli eventi PreviewKeyDown e KeyDown vengono generati ogni volta che viene premuto un tasto. Tuttavia, l'evento TextInput viene attivato solo quando un carattere è stato "immesso" nell'elemento. In effetti, questo può significare premere molti tasti. Nell'esempio, è necessario premere due tasti per ottenere lettera maiuscola S: prima la chiave , e poi la chiave . Ciò comporta due eventi KeyDown e KeyUp, ma un solo evento TextInput.

Ignora le pressioni ripetute sui caratteri

Classe parziale pubblica MainWindow: Window ( public MainWindow() ( InitializeComponent(); ) private void Clear_Click(object sender, RoutedEventArgs e) ( lbxEvents.Items.Clear(); txtContent.Clear(); i = 0; ) protected int i = 0; private void KeyEvents(object sender, KeyEventArgs e) ( if ((bool)chkIgnoreRepeat.IsChecked && e.IsRepeat) return; i++; string s = "Event" + i + ": " + e.RoutedEvent + " Key : " + e.Key; lbxEvents.Items.Add(s); ) private void TextInputEvent(object sender, TextCompositionEventArgs e) ( i++; string s = "Event" + i + ": " + e.RoutedEvent + " Chiave: " + e.Text; lbxEvents.Items.Add(s); ) )

Gli eventi PreviewKeyDown, KeyDown, PreviewKey e KeyUp passano ciascuno le stesse informazioni all'oggetto KeyEventArgs. La parte più importante è la proprietà Key, che restituisce un valore dall'enumerazione System.Windows.Input.Key e identifica il tasto premuto o rilasciato.

Il valore Key non tiene conto dello stato di nessun altro tasto, ad esempio se il tasto è stato premuto al momento della pressatura ; in entrambi i casi otterrai lo stesso valore Key (Key.S).

C'è una difficoltà qui. A seconda della configurazione della tastiera in Windows, tenendo premuto un tasto il tasto viene premuto nuovamente dopo un breve periodo di tempo. Ad esempio, premendo un tasto nel campo di testo verrà inserita una serie di caratteri S. Allo stesso modo, premendo il tasto provoca clic ripetuti e una serie di eventi KeyDown. In un esempio reale, quando si preme una combinazione il campo di testo genererà una serie di eventi KeyDown per la chiave , quindi l'evento KeyDown per la chiave , l'evento TextInput (o l'evento TextChanged nel caso campo di testo) e quindi l'evento KeyUp per le chiavi e . Se vuoi ignorare le sequenze di tasti , è possibile verificare se la pressione è il risultato della pressione di un tasto utilizzando la proprietà KeyEventArgs.IsRepeat.

Gli eventi PreviewKeyDown, KeyDown, PreviewKey e KeyUp sono più adatti per la scrittura di codice di basso livello per la gestione dell'input da tastiera (raramente necessario, ad eccezione dei controlli utente) e per la gestione della pressione di tasti speciali (come i tasti funzione).

L'evento KeyDown è seguito dall'evento PreviewTextInput. (L'evento TextInput non viene generato perché TextBox lo blocca.) A questo punto, il testo non è ancora visualizzato nel controllo.

L'evento TextInput fornisce il codice oggetto TextCompositionEventArgs. Questo oggetto contiene una proprietà Text che fornisce il testo sottoposto a rendering pronto per essere passato al controllo.

Idealmente, l'evento PreviewTextInput potrebbe essere utilizzato per eseguire la convalida su controlli come TextBox. Ad esempio, se stai creando un campo di testo che accetta solo numeri, puoi verificare se la sequenza di tasti corrente ha inserito una lettera e impostare il flag Gestito se lo è. Purtroppo, l'evento PreviewTextIlnput non viene generato per alcune chiavi che devono essere gestite. Ad esempio, premendo la barra spaziatrice in un campo di testo si salta del tutto l'evento PreviewTextInput. Ciò significa che dovrai anche gestire l'evento PreviewKeyDown.

Sfortunatamente, è difficile implementare una solida logica di convalida dei dati nel gestore di eventi PreviewKeyDown, come è disponibile solo il valore Chiave e si tratta di un'informazione di livello troppo basso. Ad esempio, l'enumerazione dei tasti distingue tra i tasti del tastierino numerico (un blocco per l'immissione di soli numeri) e una tastiera normale. Ciò significa che, a seconda di dove viene premuto il tasto 9, otterrai Key.D9 o Key.NumPad9. Controllare tutti i valori validi è almeno molto noioso.

Una via d'uscita è usare la classe Convertitore di chiavi, che consente di convertire il valore della chiave in una stringa più utile. Ad esempio, una chiamata di funzione KeyConverter.ConvertToString() con uno qualsiasi dei valori Key.D9 e Key.NumPad9, restituisce il risultato della stringa "9". Chiamando la conversione Key.ToString() si ottiene un nome enum meno utile (o "D9" o "NumPad9"):

Convertitore KeyConverter = nuovo KeyConverter(); chiave stringa = converter.ConvertToString(e.Key);

Tuttavia, anche l'uso di KeyConverter non è molto conveniente, poiché devi gestire righe lunghe (ad esempio, "Backspace") per quei tasti che non risultano nell'input di testo.

L'opzione più appropriata consiste nel gestire l'evento PreviewTextInput (in cui viene eseguita la maggior parte della convalida) insieme all'evento PreviewKeyDown per le pressioni dei tasti che non generano un evento PreviewTextInput nel campo di testo (ad esempio la barra spaziatrice).



Sostieni il progetto - condividi il link, grazie!
Leggi anche
regole del gioco del combattimento di galli regole del gioco del combattimento di galli Mod per Minecraft 1.7 10 ricette per guardare.  Ricette per creare oggetti in Minecraft.  Armi in Minecraft Mod per Minecraft 1.7 10 ricette per guardare. Ricette per creare oggetti in Minecraft. Armi in Minecraft Scellino e sterlina: l'origine delle parole Scellino e sterlina: l'origine delle parole