How to hookup TextBox's TextChanged event and Command in order to use MVVM pattern in Silverlight

Recently, I realized that MVVM pattern is so useful for Silverlight application and studying how to adopt it into my project.

BTW, how to hook up the textbox's textChanged event with Command? There is Command property for Button, however Textbox has no commapd property. If Controls don't have command property, how to combine ICommand and Control's event?

I got following xaml code

<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>

What I am trying to do is that if user input some text in the textbox, data will be shown in the datagrid instead of using button click. I know there is autocomplete box control built in. however, I want to know how to call Command property in the ViewModel class in the controls which does not have Command property such as textbox.

Thanks

Answers


Why not just bind the Text property to a property on your view model? That way you get notified when it has changed, and also get the new value:

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

XAML:

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

Here's the easiest way. Bind your text box to the property on the view model, as you described above. Then, simply add a code-behind (yes, I'm talking code-behind with MVVM, it's not the end of the world) event to the Text Box. Add a TextChanged event, then simply refresh the binding.

Altogether, you'll have something like this for a view model:

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
        }
    }
}

In your XAML, you'll have this:

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

Finally, in the code behind, simply do this:

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

That will cause your property to update anytime the text changes. }


Here's the MvvmLight way of doing it! Credit goes to 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>

Source: http://blog.galasoft.ch/archive/2010/05/19/handling-datagrid.selecteditems-in-an-mvvm-friendly-manner.aspx


Simply use

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

In the definition section we add:

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

If you use the TextBox, add the reference to the event we want to detect:

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

In the ViewModel add code for Commad:

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

private void OnRequestFilterTextChanged()
{
  // Add code
}

Do not forget to perform the binding text:

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

For the sake of conversation, lets say that you do need to hook up some arbitrary event to a Command rather than bind directly to a property on the ViewModel (due to a lack of support in the control or framework, defect, etc.) This can be done in the codebehind. Contrary to some misconceptions, MVVM does not preclude codebehind. It is just important to remember that the logic in the codebehind should not cross layers - it should be relate directly to the UI and the specific UI technology being used. (Note however, that putting 95% of your work in the markup file may make it a little unintutitive to have some functionality in the codebehind, so a comment or two in the markup about this one-off codebehind implementation may make things easier down the road for yourself or others.)

There are usually 2 parts to binding a command in codebehind. First, you have to respond to the event. Second, you (may) want to tie into the command's CanExecute property.

// 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));
    }
}

You should use a Behavior to execute the Command:

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);
    }
}

Now you can "Bind" your command in xaml:

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

If you need you can extend this Behavior with extra properties, for example if you need the sender or the DataContext of a different element..

Kind regards, Tamás

(I have found this on a blog post, but I can not remember for it's address)


I solved it by binding to a property on my view model and setting the binding's UpdateSourceTrigger to PropertyChanged. The property supports INotifyPropertyChanged.

In my view model I then subscribe to the PropertyChanged event for the property. When it triggers I perform the tasks I need to do (in my case updating a collection) and at the end I call PropertyChanged on the property that my other stuff in the view is listening to.


Jeremy answered it. However, if you want to reduce code behind just do something like this. In your view model:

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
        }
    }
    public void TextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        var binding = ((TextBox)sender).GetBindingExpression(TextBox.TextProperty);
        binding.UpdateSource();
    }
}

Then in code behind:

public void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
    YourViewModel.TextBox_TextChanged(sender, e);
}

I know it's duplicated code, but if this is what you want, then here it is.


I had the same question. Then I find this article.

http://deanchalk.com/wpf-mvvm-property-changed-command-behavior/

  1. Create behavior (behavior recive value to changend event and command to changend)
  2. Implement behavior in your WPF
  3. Bind Behavior your command to a ValueChanged

Need Your Help

ListView with jsoup and 2 textviews in a row of the listview

android listview android-listview jsoup android-arrayadapter

Wondering how I would code a listview with 2 textviews per row of the list view.

Changing ylim (axis limits) drops data falling outside range. How can this be prevented?

r ggplot2

df &lt;- data.frame(age=c(10,10,20,20,25,25,25),veg=c(0,1,0,1,1,0,1))