2010-02-11 6 views
12

Ho un TextBox con un ContextMenu in esso. Quando l'utente fa clic con il pulsante destro all'interno della casella di testo e sceglie l'oggetto MenuItem appropriato, desidero prelevare SelectedText nel mio viewmodel. Non ho trovato un buon modo per fare questo il modo "MVVM".MVVM e la proprietà SelectedText del TextBox

Finora ho la mia applicazione utilizzando il modo di MVVM di Josh Smith. Sto cercando di trasferire su Cinch. Non sono sicuro se il framework Cinch gestirà problemi come questo. Pensieri?

risposta

18

Non esiste un modo semplice per associare SelectedText a un'origine dati, perché non è una proprietà di dipendenza ... tuttavia, è piuttosto facile creare una proprietà associata che è possibile associare.

Ecco un'implementazione di base:

public static class TextBoxHelper 
{ 

    public static string GetSelectedText(DependencyObject obj) 
    { 
     return (string)obj.GetValue(SelectedTextProperty); 
    } 

    public static void SetSelectedText(DependencyObject obj, string value) 
    { 
     obj.SetValue(SelectedTextProperty, value); 
    } 

    // Using a DependencyProperty as the backing store for SelectedText. This enables animation, styling, binding, etc... 
    public static readonly DependencyProperty SelectedTextProperty = 
     DependencyProperty.RegisterAttached(
      "SelectedText", 
      typeof(string), 
      typeof(TextBoxHelper), 
      new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedTextChanged)); 

    private static void SelectedTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) 
    { 
     TextBox tb = obj as TextBox; 
     if (tb != null) 
     { 
      if (e.OldValue == null && e.NewValue != null) 
      { 
       tb.SelectionChanged += tb_SelectionChanged; 
      } 
      else if (e.OldValue != null && e.NewValue == null) 
      { 
       tb.SelectionChanged -= tb_SelectionChanged; 
      } 

      string newValue = e.NewValue as string; 

      if (newValue != null && newValue != tb.SelectedText) 
      { 
       tb.SelectedText = newValue as string; 
      } 
     } 
    } 

    static void tb_SelectionChanged(object sender, RoutedEventArgs e) 
    { 
     TextBox tb = sender as TextBox; 
     if (tb != null) 
     { 
      SetSelectedText(tb, tb.SelectedText); 
     } 
    } 

} 

È possibile quindi utilizzarlo come quella in XAML:

<TextBox Text="{Binding Message}" u:TextBoxHelper.SelectedText="{Binding SelectedText}" /> 
+0

Grazie !! Questo ha fatto il trucco. Così ovvio e l'ho perso. Grazie ancora. – Eric

+0

Sto cercando di fare lo stesso per la proprietà CaretIndex ma sembra che non funzioni. Potete aiutare – TheITGuy

+0

@TheITGuy, non senza vedere il vostro codice ... Probabilmente dovreste creare una nuova domanda (potete postare il link qui, risponderò se posso) –

2

Le applicazioni di esempio nella WPF Application Framework (WAF) hanno scelto un altro modo per risolvere questo problema. Lì il ViewModel è autorizzato ad accedere alla vista attraverso un'interfaccia (IView) e quindi può richiedere il SelectedText corrente.

Credo che Binding non dovrebbe essere utilizzato in tutti gli scenari. A volte scrivere poche righe nel codice è molto più pulito rispetto all'utilizzo di classi helper altamente avanzate. Ma questa è solo la mia opinione :-)

JBE

+0

Questa soluzione ha il vantaggio di poter spingere il valore del testo selezionato su qualsiasi proprietà stringa con un setter pubblico. La flessibilità supera probabilmente le linee extra di codice. Inoltre, con alcune piccole modifiche, è possibile utilizzare la soluzione per associare le proprietà SelectionStart e SelectionEnd, consentendo di impostare facilmente i modelli di visualizzazione e ricevere la selezione del testo. – Gusdor

1

So che è stato risposto e accettato, ma ho pensato che vorrei aggiungere la mia soluzione. Uso un comportamento per il bridge tra il modello di visualizzazione e il controllo TextBox. Il comportamento ha una proprietà di dipendenza (CaretPositionProperty) che può essere associata in due modi al modello di vista. Internamente il comportamento riguarda gli aggiornamenti da/verso il TextBox.

public class SetCaretIndexBehavior : Behavior<TextBox> 
    { 
     public static readonly DependencyProperty CaretPositionProperty; 
     private bool _internalChange; 

    static SetCaretIndexBehavior() 
    { 

    CaretPositionProperty = DependencyProperty.Register("CaretPosition", typeof(int), typeof(SetCaretIndexBehavior), new PropertyMetadata(0, OnCaretPositionChanged)); 
} 

public int CaretPosition 
{ 
    get { return Convert.ToInt32(GetValue(CaretPositionProperty)); } 
    set { SetValue(CaretPositionProperty, value); } 
} 

protected override void OnAttached() 
{ 
    base.OnAttached(); 
    AssociatedObject.KeyUp += OnKeyUp; 
} 

private static void OnCaretPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
{ 
    var behavior = (SetCaretIndexBehavior)d; 
    if (!behavior._internalChange) 
    { 
     behavior.AssociatedObject.CaretIndex = Convert.ToInt32(e.NewValue); 
    } 
} 

    private void OnKeyUp(object sender, KeyEventArgs e) 
    { 
     _internalChange = true; 
     CaretPosition = AssociatedObject.CaretIndex; 
     _internalChange = false; 
    } 
}