13

I am using a ComboBox in my WPF application and following MVVM. There is a list of strings which I want to show in my ComboBox.

XAML:

<ComboBox ItemsSource="{Binding ItemsCollection}" SelectedItem="{Binding SelectedItem}" />

View Model:

public Collection<string> ItemsCollection; // Suppose this has 10 values.
private string _selectedItem;
public string SelectedItem
{
    get { return _selectedItem; }
    set
    {
        _selectedItem = value;
        Trigger Notify of property changed.
    }
}

Now this code is working absolutely fine. I am able to select from view and I can get changes in ViewModel and if I change SelectedItem from my ViewModel I can see it in my view.

Now here is what I am trying to achieve. When I change selected item from my view I need to put a check that value is good/bad (or anything) set selected item else do not set it. So my view model changes like to this.

public string SelectedItem
{
    get { return _selectedItem; }
    set
    {
        if (SomeCondition(value))
            _selectedItem = value;           // Update selected item.
        else
            _selectedItem = _selectedItem;   // Do not update selected item.
        Trigger Notify of property changed.
    }
}

Now when I execute this code and SomeCondition(value) returns false, SelectedItem returns old string value, but in my view selected item in ComboBox is the the value which I selected. So lets assume I have collection of 10 strings showing in my ComboBox. All values are good except second and fourth element (SomeCondition returns false for 2nd and 4th value). What I want that if I select 2nd or 4th element selectedItem do not change. But my code is not doing this properly. If I select 2nd element then view still displays 2nd element as selected. I know there is something wrong in my code. But what is it?

fhnaseer
  • 7,159
  • 16
  • 60
  • 112
  • 1
    That isn't a very user friendly design. If I select something in a combobox, I'd expect that to be my selected item. You should remove invalid options from the combobox instead. If a selections validity is based on a selected value of another UI element, then changing that selection should trigger rebuilding the ItemSource of the combobox. – Lee O. Jul 25 '13 at 10:39
  • 1
    That is the requirement to show all items, cannot change that. – fhnaseer Jul 25 '13 at 11:05
  • 1
    I agree dont show invalid options, or gray them out and disable selection of them, that is what a UI is for. – Mark Homer Jul 25 '13 at 11:57
  • let the 2nd item be selected in your view but show an error with IDataErrorInfo. so you can remove your "validation" from your setter and put it in the IDataErrorInfo. but nevertheless it should work if you set the last "good" value to your selecteditem and do OnPropertyChanged() – blindmeis Jul 25 '13 at 12:07

5 Answers5

24

This is a very interesting question. First I agree with other guys that this is a not recommended approach to handle invalid selection. As @blindmeis suggests, IDataErrorInfo is one of good way to solve it.

Back to the question itself. A solution satisfying what @Faisal Hafeez wants is:

public string SelectedItem
{
    get { return _selectedItem; }
    set
    {
        var oldItem=_selectedItem;
        _selectedItem=value;
        OnPropertyChanged("SelectedItem")

        if (!SomeCondition(value)) //If does not satisfy condition, set item back to old item
            Dispatcher.CurrentDispatcher.BeginInvoke(new Action(() => SelectedItem = oldItem),
                                                 DispatcherPriority.ApplicationIdle);
    }
}

Dispatcher is an elegant way to handle some UI synchronization during another UI sync. For example in this case, you want to reset selection during a selection binding.

A question here is why we have to update selection anyway at first. That's because SelectedItem and SelectedValue are separately assigned and what display on ComboBox does not depend on SelectedItem (maybe SelectedValue, I am not sure here). And another interesting point is if SelectedValue changes, SelectedItem must change but SelectedItem does not update SelectedValue when it changes. Therefore, you can choose to bind to SelectedValue so that you do not have to assign first.

Bill Zhang
  • 1,909
  • 13
  • 10
  • We had exactly the same problem and your workaround works. We also want to show all items though its not possible to select one. And we don't want to disable the control because its only a short period of time that it's not possible to select another item. Is there a way to write a behavior that makes sure a Selector UI Element always shows the right property value ? – Lumo Jul 26 '16 at 14:51
  • For me this causes a slight "flicker" of the originally selected value, but works well enough. – Ddddan May 23 '19 at 19:03
  • I'm also very appreciative of this solution. Seems like the right answer is to let the framework set "value" as passed and then reset it the original value. But, can someone explain why OP's solution does not work (testing and assigning before the notification). – msr Oct 06 '20 at 16:30
11

I know this is a bit late but as of WPF 4.5 you can use the Delay command like so:

    <ComboBox ItemsSource="{Binding ItemsCollection}" SelectedItem="{Binding SelectedItem, Mode=TwoWay, Delay=1, UpdateSourceTrigger=PropertyChanged}" />

This saved me after hours of looking up stuff the other day. For other methods which may or may not work you can read this post and its comments.

LukeVidalis
  • 111
  • 1
  • 4
3

Try changing the XAML to this

<ComboBox ItemsSource="{Binding ItemsCollection}" SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
Haris Hasan
  • 29,856
  • 10
  • 92
  • 122
  • "Mode=TwoWay" is this required? I tried to set default value in my view model constructor, that was working fine. But setting it inside setter is failing. – fhnaseer Jul 25 '13 at 11:07
  • 1
    @FaisalHafeez yes because you are changing property in View and ViewModel as well – Haris Hasan Jul 25 '13 at 11:11
0
<ComboBox ItemsSource="{Binding ItemsCollection}" SelectedIndex="{Binding currSelection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged} " />

And in your VM

{
    set{ 
        currSelection = -1;
    }
}
MigRome
  • 1,095
  • 1
  • 12
  • 28
0

If you are looking for a MVVM / XAML re-usable solution, I put this together for another thread. This uses a WPF Behavior and is very simple to manage. No external libs. Copy in solution.

ComboBox Selected Item Clear Behavior

TravisWhidden
  • 2,142
  • 1
  • 19
  • 43
  • Please don't post link-only answers to other Stack Overflow questions. Instead, vote/flag to close as duplicate, or, if the question is not a duplicate, *tailor the answer to this specific question.* – Paul Roub Mar 20 '19 at 00:30