2013-05-16 15 views
5

Ho la seguente TextBox:TextBox non sempre aggiorna

<TextBox Text="{Binding SearchString, 
       UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" /> 

associato per la seguente proprietà:

private string _searchString; 
public string SearchString 
{ 
    get 
    { 
     return _searchString; 
    } 
    set 
    { 
     value = Regex.Replace(value, "[^0-9]", string.Empty);    
     _searchString = value; 
     DoNotifyPropertyChanged("SearchString"); 
    } 
} 

La classe eredita da una classe base che implementa INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged; 
protected void DoNotifyPropertyChanged(string propertyName) 
{ 
    if (PropertyChanged != null) 
     PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); 
} 

Tutto quello che voglio è un modo rapido e sporco per disabilitare caratteri non numerici per un intero-on la casella di testo (so che non è completa, solo per la dimostrazione). Non voglio una semplice notifica che ci sia un testo illegale o altro, voglio scartare immediatamente tutti i caratteri su input che non sono consentiti.

Tuttavia, il TextBox si comporta in modo strano. Posso ancora inserire qualsiasi testo che desidero, verrà visualizzato come inserito, ad es. "1AAA". Anche se la proprietà è stata correttamente ripulita su "1" in questo esempio, la casella di testo mostra ancora "1aaa". Solo quando inserisco una cifra effettiva che causerebbe la modifica di _searchString, aggiorna anche il testo visualizzato, ad esempio quando ho "1aaa2" verrà aggiornato correttamente a "12". Qual è il problema qui?

risposta

3

Sembra una logica specifica della vista, quindi non vedo alcun motivo per non utilizzare il codice: dietro la vista per controllarlo. Personalmente implementerei questo tipo di comportamento con uno PreviewKeyDown sullo TextBox che scarta qualsiasi carattere non numerico.

Probabilmente non sarebbe male avere qualcosa di generico che si può riutilizzare, come ad esempio un controllo personalizzato NumbersOnlyTextBox, o un AttachedProperty si potrebbe allegare al TextBox per specificare che consente solo i numeri.

In effetti, ricordo di aver creato una proprietà associata che consente di specificare un'espressione regolare per una casella di testo e limiterà l'immissione di caratteri solo all'espressione regolare. Non l'ho usato da un po ', quindi probabilmente vorrai testarlo o aggiornarlo, ma ecco il codice.

// When set to a Regex, the TextBox will only accept characters that match the RegEx 
#region AllowedCharactersRegex Property 

/// <summary> 
/// Lets you enter a RegexPattern of what characters are allowed as input in a TextBox 
/// </summary> 
public static readonly DependencyProperty AllowedCharactersRegexProperty = 
    DependencyProperty.RegisterAttached("AllowedCharactersRegex", 
             typeof(string), typeof(TextBoxProperties), 
             new UIPropertyMetadata(null, AllowedCharactersRegexChanged)); 

// Get 
public static string GetAllowedCharactersRegex(DependencyObject obj) 
{ 
    return (string)obj.GetValue(AllowedCharactersRegexProperty); 
} 

// Set 
public static void SetAllowedCharactersRegex(DependencyObject obj, string value) 
{ 
    obj.SetValue(AllowedCharactersRegexProperty, value); 
} 

// Events 
public static void AllowedCharactersRegexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
{ 
    var tb = obj as TextBox; 
    if (tb != null) 
    { 
     if (e.NewValue != null) 
     { 
      tb.PreviewTextInput += Textbox_PreviewTextChanged; 
      DataObject.AddPastingHandler(tb, TextBox_OnPaste); 
     } 
     else 
     { 
      tb.PreviewTextInput -= Textbox_PreviewTextChanged; 
      DataObject.RemovePastingHandler(tb, TextBox_OnPaste); 
     } 
    } 
} 

public static void TextBox_OnPaste(object sender, DataObjectPastingEventArgs e) 
{ 
    var tb = sender as TextBox; 

    bool isText = e.SourceDataObject.GetDataPresent(DataFormats.Text, true); 
    if (!isText) return; 

    var newText = e.SourceDataObject.GetData(DataFormats.Text) as string; 
    string re = GetAllowedCharactersRegex(tb); 
    re = "[^" + re + "]"; 

    if (Regex.IsMatch(newText.Trim(), re, RegexOptions.IgnoreCase)) 
    { 
     e.CancelCommand(); 
    } 
} 

public static void Textbox_PreviewTextChanged(object sender, TextCompositionEventArgs e) 
{ 
    var tb = sender as TextBox; 
    if (tb != null) 
    { 
     string re = GetAllowedCharactersRegex(tb); 
     re = "[^" + re + "]"; 

     if (Regex.IsMatch(e.Text, re, RegexOptions.IgnoreCase)) 
     { 
      e.Handled = true; 
     } 
    } 
} 

#endregion // AllowedCharactersRegex Property 

Sarebbe essere utilizzato in questo modo:

<TextBox Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}" 
     local:TextBoxHelpers.AllowedCharactersRegex="[0-9]" /> 

Ma per quanto riguarda il motivo per cui non aggiornerà l'interfaccia utente. L'interfaccia utente sa che il valore non è effettivamente cambiato, quindi non si preoccupa di rivalutare l'associazione quando riceve la notifica di PropertyChange.

per andare in giro che, si potrebbe provare a impostare temporaneamente il valore per qualcosa d'altro prima di impostare al valore regex, e alzando una notifica PropertyChange modo l'interfaccia utente rivaluta le associazioni, ma onestamente che non è realmente un ideale soluzione.

+0

hai ragione. ma perché non funziona in questo modo? – user1064519

+0

@ user1064519 L'interfaccia utente sa che il valore non è stato effettivamente modificato quando viene sollevata la notifica PropertyChange, quindi non si preoccupa di rivalutare l'associazione e aggiornare l'interfaccia utente. Vedere l'aggiornamento alla mia risposta :) – Rachel

+1

Se ha funzionato in questo modo, si finirebbe con loop infiniti: digitando una chiave si cambierebbe la casella di testo, che imposta la proprietà, che genererebbe l'evento PropertyChanged, che aggiornerebbe l'associazione, che cambierebbe la casella di testo, che imposterà la proprietà ... e così via all'infinito. –

0

Direi che questo ha qualcosa a che fare con la logica di prevenzione del ciclo infinito integrata di WPF. Come scritto, la tua logica dovrebbe informare WPF che la proprietà è cambiata ogni volta che viene chiamato "Set". Quando viene notificato a WPF che la proprietà è cambiata, aggiornerà il controllo. Quando il controllo si aggiorna, esso (secondo l'associazione) chiama di nuovo la proprietà "Imposta". verso l'infinito. WPF è stato progettato per rilevare questo tipo di loop e prevenirli in un certo senso. Probabilmente è la trappola in cui sei finito.

Non so esattamente come funzioni questa logica, ma penso che Rachel's answer stia per ottenere tu i migliori risultati.In generale, ViewModel (ciò a cui si sta vincolando) dovrebbe essere un riflesso della vista, input errato e tutto. ViewModel dovrebbe essere in grado di convalidare l'input (non sapendo da dove proviene o in che modo è stato inserito) e impedire che input errati si propaghino al Model (spostando in uno "stato di errore", ad esempio).

Quello che si sta tentando di fare è controllare l'immissione dell'utente, che è probabilmente meglio lasciata alla logica di visualizzazione.

0

Perché non si guarda

BindingOperations.GetBindingExpressionBase(_textBoxName, TextBox.TextProperty).UpdateTarget(); 

l'aggiornamento del codice XAML

<TextBox x:Name="_textBoxName" Text="{Binding SearchString, 
       UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" /> 

Ciò forza un aggiornamento dalla sorgente alla destinazione, si sta utilizzando un DependencyProperty e il vostro controllo non aggiornerà perché conosce il valore durante l'invio alla fonte di associazione.

MSDN: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingexpressionbase.updatetarget.aspx