1
votes

I have implemented the prism MVVM pattern in my desktop WPF non-silverlight application. During the screen loading, I am populating a listbox with a collection of values. This listbox is bound to the ViewModel ( which is my DataContext ) and each item is rendered using a DataTemplate. This is working fine. I am then trying to create a textbox that will allow the user to filter down which records are displayed when they type into it. My natural thought was to go after the KeyUp or TextChanged events, but it turns out that this is more difficult than just binding to the event if trying to maintain the use of ICommand as recommended. I could not find an easy way around this problem and did not want to spend anymore time figuring it out as I have a deadline fast approaching. With that in mind, I attempted the following solution:

XAML:

<TextBox Name="ChannelSearch" Text="{Binding Path=ChannelFilter, Mode=TwoWay}"  TextChanged="ChannelSearch_TextChanged" />

Code-Behind:

private void ChannelSearch_TextChanged(object sender, TextChangedEventArgs e) {
        var vm = ((IExpressionEditorViewViewModel)ViewModel);
        if (vm.FilterChannels.CanExecute())
            vm.FilterChannels.Execute();
    }

ViewModel:

 //In the constructor:
 FilterChannels = new DelegateCommand(doFilterChannels, doCanFilterChannels);

 //Class properties:
 public string ChannelFilter { get; set; }

 //Methods used by DelegateCommand:
 private void doFilterChannels(){
        if (string.IsNullOrWhiteSpace(ChannelFilter))
            this.IdFileRows = _allIdFileRows;
        else{
            var lower = ChannelFilter.Trim().ToLower();

            this.IdFileRows = _allIdFileRows.Where(
                r => (DisplayChannel && r.A_RowChannelNum.ToLower().Contains(lower))
                    || (DisplayMnemonic && r.B_Mnemonic.ToLower().Contains(lower))
                    || (DisplayDescription && r.F_Description.ToLower().Contains(lower))
                ).ToList();
        }
    }

 private bool doCanFilterChannels() {

        return true;
    }

So with all of that in place, I started debugging. The results did not differ between KeyUp and TextChanged. When "doFilterChannels" is hit, the ChannelFilter text is null everytime. What starts to boggle my mind is that if I hit a save button I made on the screen which hits a DelegateCommmand directly like so:

<Button Content="Save" Command="{Binding Path=SaveExpression}"></Button>

SaveExpression = new DelegateCommand(doSaveExpression, doCanSaveExpression);

      private void doSaveExpression() {
        Result result = new Result();
        try {

            result.Success = true;
        }
        catch (Exception e) {
            result.ResultMessages.Add(new ResultMessage { ErrorCode = "500", Message = e.ToString() });
        }

        result.ThrowErrors();
    }
    private bool doCanSaveExpression() {
        return true; //TODO validation
    }

When I break inside the doSaveExpression method and check ChannelFilter, the value is there and accurate. Why is this happening here and not when the KeyUp/TextChanged events are fired? Is there a better way to handle these situations?

1

1 Answers

3
votes

The default value of UpdateSourceTrigger property is LostFocus for a TextBox, that is why your ViewModel property is not updated until you move focus away from textbox to a different control (in your case, hitting the button). Set the value of UpdateSourceTrigger to PropertyChanged in your binding.

e.g:

<TextBox Name="ChannelSearch" 
         Text="{Binding Path=ChannelFilter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"  
         TextChanged="ChannelSearch_TextChanged" />

Refer to this MSDN article to read more about UpdateSourceTrigger property.

And for your original problem, you could call doFilterChannels method from the setter of ChannelFilter property on the viewModel (of course, without blocking the UI thread).