2010-08-16 10 views
11

Niedawno zdałem sobie sprawę, że wzorzec MVVM jest tak przydatny dla aplikacji Silverlight i studiując, w jaki sposób zastosować go w moim projekcie.Jak podłączyć TextCox zdarzenia TextChanged i Command w celu użycia wzorca MVVM w Silverlight

BTW, jak podłączyć tekst w polu tekstowymChanged zdarzenia z Command? Istnieje właściwość Command dla Button, jednak Textbox nie ma właściwości commapd. Jeśli elementy sterujące nie mają właściwości command, jak połączyć ICommand i zdarzenia Control?

Mam następujący kod xaml

<UserControl.Resources> 
     <vm:CustomerViewModel x:Key="customerVM"/>  
    </UserControl.Resources> 

    <Grid x:Name="LayoutRoot" 
      Background="White" 
      DataContext="{Binding Path=Customers, Source={StaticResource customerVM}, Mode=TwoWay}" > 

     <StackPanel> 
      <StackPanel Orientation="Horizontal" 
         Width="300" 
         HorizontalAlignment="Center"> 
       <TextBox x:Name="tbName" 
         Width="50" 
         Margin="10"/> 
       <Button Width="30" 
         Margin="10" 
         Content="Find" 
         Command="{Binding Path=GetCustomersByNameCommand, Source={StaticResource customerVM}}" 
         CommandParameter="{Binding Path=Text, ElementName=tbName}"/> 
      </StackPanel> 
      <sdk:DataGrid ItemsSource="{Binding Path=DataContext, ElementName=LayoutRoot}" 
          AutoGenerateColumns="True" 
          Width="300" 
          Height="300"/> 
     </StackPanel> 
    </Grid> 

Co staram się robić to, że jeśli dane wejściowe użytkownika jakiś tekst w polu tekstowym, dane zostaną przedstawione w DataGrid zamiast korzystania z przycisków. Wiem, że wbudowana jest wbudowana kontrolka autocomplete. Jednak chcę wiedzieć, jak wywołać właściwość Command w klasie ViewModel w formantach, które nie mają właściwości Command, takich jak textbox.

Dzięki

+0

Po zbadaniu kilka godzin, aby rozwiązać ten problem, znalazłem MvvmLightToolkit (h ttp: //mvvmlight.codeplex.com/) Ułatwia korzystanie z MVVM. Dziękuję wszystkim jeszcze raz. – Ray

+1

To [link] (http://stackoverflow.com/questions/20089739/wpf-mvvm-textbox-text- binding-vs-changedtext-event) jest lepszym rozwiązaniem dla UpdateSourceTrigger = PropertyChanged – dammel288

Odpowiedz

16

Dlaczego po prostu nie wiążą własności do nieruchomości na swoim modelu widoku Text? W ten sposób można otrzymywać powiadomienia, gdy to się zmieniło, a także uzyskać nową wartość:

public string MyData 
{ 
    get { return this.myData; } 
    set 
    { 
     if (this.myData != value) 
     { 
      this.myData = value; 
      this.OnPropertyChanged(() => this.MyData); 
     } 
    } 
} 

XAML:

<TextBox Text="{Binding MyData}"/> 
+0

proszę, odsyłam moje edytowane pytanie . – Ray

+12

@kwon, ta odpowiedź jest nadal ważna. Trzeba tylko ustawić "UpdateSourceTrigger = PropertyChanged" w powiązaniu, a właściwość VM będzie aktualizowana za każdym razem, gdy użytkownik wpisze znak w polu TextBox. Musisz więc zrobić wszystko, co musisz zrobić w ustawieniach nieruchomości –

+2

@Thomas, UpdateSourcetrigger jest WPF, nie działa w Silverlight. –

3

Dla rozmowie, powiedzmy, że trzeba podłączyć jakiś arbitralny Zdarzenie do polecenia zamiast wiązania bezpośrednio do właściwości na ViewModel (z powodu braku wsparcia w kontroli lub strukturze, defektu itp.) Można to zrobić w kodzie źródłowym. W przeciwieństwie do niektórych nieporozumień, MVVM nie wyklucza codebehind. Należy pamiętać, że logika kodu nie powinna przekraczać warstw - powinna odnosić się bezpośrednio do interfejsu użytkownika i konkretnej technologii interfejsu użytkownika. (Zwróć jednak uwagę, że umieszczenie 95% twojej pracy w pliku znaczników może sprawić, że będzie trochę niezrozumiałe, aby mieć jakąś funkcjonalność w kodzie, tak więc komentarz lub dwa w znacznikach dotyczących tej jednorazowej implementacji kodu mogą znacznie ułatwić droga dla siebie lub innych.)

Zwykle są 2 części do wiązania polecenia w codebehind. Najpierw musisz odpowiedzieć na wydarzenie. Po drugie, możesz (możesz) chcieć związać się z właściwością CanExecute polecenia.

// Execute the command from the codebehind 
private void HandleTheEvent(Object sender, EventArgs e) 
{ 
    var viewModel = DataContext as ViewModel; 
    if (viewModel != null) 
    { 
     var command = viewModel.SomeCommand; 
     command.Execute(null); 
    } 
} 

// Listen for the command's CanExecuteChanged event 
// Remember to call this (and unhook events as well) whenever the ViewModel instance changes 
private void ListenToCommandEvent() 
{ 
    var viewModel = DataContext as ViewModel; 
    if (viewModel != null) 
    { 
     var command = viewModel.SomeCommand; 
     command.CanExecuteChanged += (o, e) => EnableOrDisableControl(command.CanExecute(null)); 
    } 
} 
0

Należy użyć Zachowanie wykonać polecenie:

public class CommandBehavior : TriggerAction<FrameworkElement> 
{ 
    public static readonly DependencyProperty CommandBindingProperty = DependencyProperty.Register(
     "CommandBinding", 
     typeof(string), 
     typeof(CommandBehavior), 
     null); 

    public string CommandBinding 
    { 
     get { return (string)GetValue(CommandBindingProperty); } 
     set { SetValue(CommandBindingProperty, value); } 
    } 

    private ICommand _action; 

    protected override void OnAttached() 
    { 
     DataContextChangedHandler.Bind(AssociatedObject, _ProcessCommand); 
    } 

    private void _ProcessCommand(FrameworkElement obj) 
    { 
     if (AssociatedObject != null) 
     { 

      var dataContext = AssociatedObject.DataContext; 

      if (dataContext != null) 
      { 
       var property = dataContext.GetType().GetProperty(CommandBinding); 
       if (property != null) 
       { 
        var value = property.GetValue(dataContext, null); 
        if (value != null && value is ICommand) 
        { 
         _action = value as ICommand; 
         if (AssociatedObject is Control) 
         { 
          var associatedControl = AssociatedObject as Control; 
          associatedControl.IsEnabled = _action.CanExecute(null); 
          _action.CanExecuteChanged += 
           (o, e) => associatedControl.IsEnabled = _action.CanExecute(null); 
         } 

        } 
       } 
      } 
     } 
    } 

    protected override void Invoke(object parameter) 
    { 
     if (_action != null && _action.CanExecute(parameter)) 
     { 
      _action.Execute(parameter); 
     } 
    } 
} 

public static class DataContextChangedHandler 
{ 
    private const string INTERNAL_CONTEXT = "InternalDataContext"; 
    private const string CONTEXT_CHANGED = "DataContextChanged"; 

    public static readonly DependencyProperty InternalDataContextProperty = 
     DependencyProperty.Register(INTERNAL_CONTEXT, 
            typeof(Object), 
            typeof(FrameworkElement), 
            new PropertyMetadata(_DataContextChanged)); 

    public static readonly DependencyProperty DataContextChangedProperty = 
     DependencyProperty.Register(CONTEXT_CHANGED, 
            typeof(Action<FrameworkElement>), 
            typeof(FrameworkElement), 
            null); 


    private static void _DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) 
    { 
     var control = (FrameworkElement)sender; 
     var handler = (Action<FrameworkElement>)control.GetValue(DataContextChangedProperty); 
     if (handler != null) 
     { 
      handler(control); 
     } 
    } 

    public static void Bind(FrameworkElement control, Action<FrameworkElement> dataContextChanged) 
    { 
     control.SetBinding(InternalDataContextProperty, new Binding()); 
     control.SetValue(DataContextChangedProperty, dataContextChanged); 
    } 
} 

Teraz można "Bind" Twoje polecenia w XAML:

 <TextBox Text="{Binding SearchText, Mode=TwoWay}" > 
      <i:Interaction.Triggers> 
       <i:EventTrigger EventName="TextChanged"> 
        <utils:CommandBehavior CommandBinding="SearchCommand" /> 
       </i:EventTrigger> 
      </i:Interaction.Triggers> 
     </TextBox> 

Jeśli potrzebujesz można przedłużyć Takie zachowanie z dodatkowymi właściwościami, na przykład, jeśli potrzebujesz nadawcy lub DataContext innego elementu.

Kind re GaRDS, Tamás

(Znalazłem to na blogu, ale nie pamiętam, bo to adres)

+0

Dzięki Twojej odpowiedzi To bardzo skomplikowany kod, aby zrobić coś bardzo prostego. Rozczarowuję silverlight. W każdym razie, czy istnieją jakieś ramy do zmniejszenia złożonego kodu i rozwiązania tego rodzaju problemów dla MVVM? – Ray

19

Oto najprostszy sposób. Powiąż pole tekstowe z właściwością modelu widoku, jak opisano powyżej.Następnie po prostu dodaj do pola tekstowego zdarzenie z kodem (tak, mówię z tyłu kodu z MVVM, to nie koniec świata). Dodaj zdarzenie TextChanged, a następnie odśwież wiązanie.

sumie będziesz miał coś takiego na wzór Widok:

public class MyViewModel 
{ 
    private string _myText; 

    public string MyText 
    { 
     get { return _myText; } 
     set 
     { 
      _myText = value; 
      RaisePropertyChanged("MyText"); // this needs to be implemented 
      // now do whatever grid refresh/etc 
     } 
    } 
} 

W swojej XAML, musisz to:

<TextBox Text="{Binding MyText,Mode=TwoWay}" TextChanged="TextBox_TextChanged"/> 

Wreszcie w kodzie tyłu, po prostu zrób to:

public void TextBox_TextChanged(object sender, TextChangedEventArgs e) 
{ 
    var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty); 
    binding.UpdateSource(); 
} 

To spowoduje, że Twoja własność zaktualizuje się w dowolnym momencie zmiany tekstu. }

+0

Dzięki Jeremy, jest to sposób na rozwiązanie mojego problemu, jednak staram się zmniejszyć kody w kodzie z tyłu. Czy nie ma na to sposobu bez kodu? – Ray

+3

Pytanie brzmi ... dlaczego? Dlaczego próbujesz zmniejszyć niedozwolony kod? Jaka jest różnica między zdarzeniem w kodzie źródłowym, a występowaniem powiązanego zachowania określonego w XAML? Nadal masz kod-behidn, w jednym przypadku jest on jawny (zachowanie), drugi niejawny (plik z kodem źródłowym). Możesz rozwiązać ten problem za pomocą wyzwalacza, ale nie jestem pewien, dlaczego chcesz wykonać dodatkową pracę. –

+1

Muszę się zgodzić z JeremyMVVM nie chodzi o redukcję kodu lub jego eliminację, a tak naprawdę o rozdzielenie obaw i testowalności. Umieszczenie kodu UI w kodzie źródłowym ma sens, ponieważ jest to kod interfejsu użytkownika i zazwyczaj nie jest to coś, co będzie testowane. – Agies

7

Oto sposób działania MvvmLight! Kredyt trafia do GalaSoft Laurent Bugnion.

<sdk:DataGrid Name="dataGrid1" Grid.Row="1" 
    ItemsSource="{Binding Path=CollectionView}" 
    IsEnabled="{Binding Path=CanLoad}" 
    IsReadOnly="True"> 
    <i:Interaction.Triggers> 
     <i:EventTrigger EventName="SelectionChanged"> 
      <cmd:EventToCommand 
       Command="{Binding SelectionChangedCommand}" 
       CommandParameter="{Binding SelectedItems, ElementName=dataGrid1}" /> 
     </i:EventTrigger> 
    </i:Interaction.Triggers> 
</sdk:DataGrid> 

Źródło: http://blog.galasoft.ch/archive/2010/05/19/handling-datagrid.selecteditems-in-an-mvvm-friendly-manner.aspx

+2

Zauważ, że właściwym importem przestrzeni nazw 'i' w XAML jest' xmlns: i = "http: // schemas. microsoft.com/expression/2010/interactivity "' – cod3monk3y

+0

TextBox nie jest wybranym elementem. Z jakiego wydarzenia skorzystasz? TextChanged nie działa tutaj. – Craig

0

Rozwiązałem go poprzez wiązanie się z nieruchomości na moim modelu widoku i ustawiania UpdateSourceTrigger wiązania do PropertyChanged. Właściwość obsługuje INotifyPropertyChanged.

W moim widoku modelu I następnie subskrybować zdarzenie PropertyChanged dla właściwości. Kiedy się uruchamia, wykonuję zadania, które muszę wykonać (w moim przypadku aktualizuję kolekcję) i na końcu wywołuję PropertyChanged na obiekcie, którego słuchają inne moje rzeczy w widoku.

2

Wystarczy użyć

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

W sekcji definicji dodajemy:

xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 

Jeśli używasz TextBox, dodać odwołanie do zdarzenia chcemy wykryć:

<TextBox Text="{Binding TextPrintersFilter}"> 
    <i:Interaction.Triggers> 
    <i:EventTrigger EventName="TextChanged">   
     <i:InvokeCommandAction Command="{Binding FilterTextChangedCommand}"/> 
    </i:EventTrigger> 
    </i:Interaction.Triggers> 
</TextBox> 

W kodzie ViewModel dodaj kod Commad:

public ICommand FilterTextChangedCommand 
{ 
    get 
    { 
    if (this._filterTextChangedCommand == null) 
    { 
     this._filterTextChangedCommand = 
     new RelayCommand(param => this.OnRequestFilterTextChanged()); 
    } 
    return this._filterTextChangedCommand; 
    } 
} 

private void OnRequestFilterTextChanged() 
{ 
    // Add code 
} 

Nie zapomnij wykonać tekst Oprawa:

private string _textPrintersFilter; 
public string TextPrintersFilter 
{ 
    get { return _textPrintersFilter; } 
    set 
    { 
    _textPrintersFilter = value; 
    this.RaisePropertyChange(nameof(TextPrintersFilter)); 
    } 
} 
+0

Nie powinieneś mieć CommandParameter w swoim tagu InvokeCommandAction? W przeciwnym razie odzyskasz null. – Craig

Powiązane problemy