I have a WPF ComboBox and am using MVVM to bind the ItemsSource and SelectedItem properties. Basically what I want to do is when a user selects a specific item in the combobox, the combobox instead selects a different item.
<ComboBox ItemsSource="{Binding TestComboItemsSource}" SelectedItem="{Binding TestComboItemsSourceSelected}"></ComboBox>
For demo purposes, I also have a button to update the SelectedItem.
<Button Command="{Binding DoStuffCommand}">Do stuff</Button>
I have this in my viewModel:
public ObservableCollection<string> TestComboItemsSource { get; private set; }
public MyConstructor()
{
TestComboItemsSource = new ObservableCollection<string>(new []{ "items", "all", "umbrella", "watch", "coat" });
}
private string _testComboItemsSourceSelected;
public string TestComboItemsSourceSelected
{
get { return _testComboItemsSourceSelected; }
set
{
if (value == "all")
{
TestComboItemsSourceSelected = "items";
return;
}
_testComboItemsSourceSelected = value;
PropertyChanged(this, new PropertyChangedEventArgs(TestComboItemsSourceSelected))
}
}
private ICommand _doStuffCommand;
public ICommand DoStuffCommand
{
get
{
return _doStuffCommand ?? (_doStuffCommand = new RelayCommand(p =>
{
TestComboItemsSourceSelected = "items";
})); }
}
OK, so I want to have the ComboBox select the item "items" whenever the user selects the item "all".
Using the button, I am able to update the combobox's SelectedItem, and I can see this reflected in the UI
I have similar logic to update the viewModel in my setter of the TestComboItemsSourceSelected property. If the user selects "all", instead set the SelectedItem to "items" So code-wise, the viewmodel property gets changed, but this is not reflected in the UI for some reason. Am I missing something? Is there some sort of side-effect of the way I've implemented this?
Well, this is because you change the property while another change is in progress. WPF will not listen to the PropertyChanged event for this property while setting it.
To workaround this, you can "schedule" the new change with the dispatcher, so it will be executed after it is done with the current change:
public string TestComboItemsSourceSelected
{
get { return _testComboItemsSourceSelected; }
set
{
if (value == "all")
{
Application.Current.Dispatcher.BeginInvoke(new Action(() => {
TestComboItemsSourceSelected = "items";
}));
return;
}
_testComboItemsSourceSelected = value;
PropertyChanged(this, new PropertyChangedEventArgs(TestComboItemsSourceSelected))
}
}
The behaviour you are describing seems very weird for me, but if you want a "Select All" feature, the standar way is to create a combobox where items has a CheckBox.
Each item is represented by a small ViewModel (tipically with Id, Name and IsChecked properties), and you manually create a "select all item" that is added first in the ObservableCollection and subscribe to its PropertyChanged in order to set the rest o the items IsChecked property to true.
Related
I have a ComboBox and a TextBox. Both of them receive values (ie SelectedValue and Text) via DataBinding to corresponding properties in ViewModel.
Upon changing the SelectedValue in this ComboBox, I want to populate a new value in this TextBox from a List<string>(which is part of the ViewModel).
I don't want to use SelectedIndexChanged event of ComboBox here to select the new TextBox. Text from List<string> of ViewModel. How can I do this?
In the setter of SelectedValue you should modify the value of the Text property and in setter of Text the PropertyChanged event shoud be fired.
public string SelectedValue
{
get { return _selectedValue; }
set
{
_selectedValue = value;
//here write your code to modify the Text property
}
}
public string Text
{
get { return _text; }
set
{
_text = value;
RaisePropertyChanged(() => Text);
}
}
I have a ComboBox that is binded to my ViewModel. The SelectedIndex is binded to a property on the ViewModel.
What I want to do is, with some conditions, some of the choices on the Index becomes invalid so that when the user tries to select it, it should show an error message and not change the currently selected item.
On the back-end, all is well. However, on the UI, the SelectedIndex of the ComboBox still changes. The error message shows properly, but then the 'shown' selected item in the combobox is not the proper one (ex. ComboBox is currently 'Item 4', User selects invalid item 'Item 3', shows error, but the ComboBox still shows 'Item 3').
Here is XAML code for reference:
<ComboBox x:Name="ComboBox_Cover"
HorizontalAlignment="Stretch"
ItemsSource="{Binding Path=Covers}"
SelectedIndex="{Binding Path=Cover,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource Style_ComboBox_CheckSelector}" />
And my ViewModel:
private int _Cover = 0;
public int Cover
{
get { return _Cover; }
set
{
bool canChangeCover = true;
if(IfInvalid())
{
canChangeCover = false;
ShowCoversError();
RaisePropertyChanged("Cover");
}
if(canChangeCover)
{
_Cover = value;
RaisePropertyChanged("Cover");
}
}
}
Am I doing something wrong?
A current workaround I found was using the OnSelectionChanged event and doing setting the SelectedIndex to the proper value there if Invalid. Though I'm not sure if that is a good workaround.
Thank you!
The easiest thing to do would be to take advantage of the IsEnabled property of the ComboBoxItem. Just modify the ItemsSource binding to point to a list of ComboBoxItem.
C#:
public class MainPageViewModel : INotifyPropertyChanged
{
public MainPageViewModel()
{
Covers = new List<ComboBoxItem>
{
new ComboBoxItem { Content = "Item 1", IsEnabled = true },
new ComboBoxItem { Content = "Item 2", IsEnabled = true },
new ComboBoxItem { Content = "Item 3", IsEnabled = true },
new ComboBoxItem { Content = "Item 4", IsEnabled = true }
};
}
public List<ComboBoxItem> Covers { get; set; }
private int selectedIndex;
public int SelectedIndex
{
get { return selectedIndex; }
set
{
if (SelectedIndex != value)
{
foreach (var cover in Covers)
{
if (Covers.IndexOf(cover) < value)
{
cover.IsEnabled = false;
}
}
selectedIndex = value;
NotifyPropertyChanged("SelectedIndex");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
XAML:
<ComboBox
HorizontalAlignment="Center"
VerticalAlignment="Center"
ItemsSource="{Binding Covers}"
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" />
If you want to keep a collection of your data models in C#, you could implement an IValueConverter to project a list of Cover into a list of ComboBoxItem. You could also create a new class that inherits from ComboBoxItem and adds some additional dependency properties if you need to bind more values to your control template. For instance, I toyed around with this:
public class CoverComboBoxItem : ComboBoxItem
{
public string Description
{
get { return (string)GetValue(DescriptionProperty); }
set
{
SetValue(DescriptionProperty, value);
this.Content = value;
}
}
public static readonly DependencyProperty DescriptionProperty =
DependencyProperty.Register("Description", typeof(string), typeof(CoverComboBoxItem), new PropertyMetadata(""));
}
It's worth noting that to maintain a nice separation of concerns, I would usually prefer binding a model property directly to the IsEnabled property of the ComboBoxItem. Unfortunately, to make that work you'd need to setup the binding inside of ComboBox.ItemContainerStyle. Unfortunately, declaring bindings there isn't supported. There are some workarounds I've seen, but the complexity of implementing them is a lot more than the solution I've described above.
Hope that helps!
I am new to wpf and MVVM, and I've spent all day trying to get the value of a ComboBox to my ViewModel on SelectionChanged. I want to call a function in the selection changed process. In mvvm, what is the solution for it?
In MVVM, we generally don't handle events, as it is not so good using UI code in view models. Instead of using events such as SelectionChanged, we often use a property to bind to the ComboBox.SelectedItem:
View model:
public ObservableCollection<SomeType> Items { get; set; } // Implement
public SomeType Item { get; set; } // INotifyPropertyChanged here
View:
<ComboBox ItemsSource="{Binding Items}" SelectedItem="{Binding Item}" />
Now whenever the selected item in the ComboBox is changed, so is the Item property. Of course, you have to ensure that you have set the DataContext of the view to an instance of the view model to make this work. If you want to do something when the selected item is changed, you can do that in the property setter:
public SomeType Item
{
get { return item; }
set
{
if (item != value)
{
item = value;
NotifyPropertyChanged("Item");
// New item has been selected. Do something here
}
}
}
I have a window with a combobox. This comboboxhas 5 ComboboxItems.
In the example I want that it is not possible to select the items 3, 4 and 5.
I've tried two different ways: MVVM way and codebehind way
MVVM way:
xaml:
<ComboBox SelectedIndex="{Binding Path=SaveIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Path=SaveSelectedItemCheck}" Name="SaveCombobox">
viewmodel:
public object SaveSelectedItemCheck
{
get { return _control.SaveCombobox.Items[CurrentSaveIndex]; }
set
{
if (value != _control.SaveCombobox.Items[0] && value != _control.SaveCombobox.Items[1])
{
OnPropertyChanged("SaveSelectedItemCheck");
}
}
}
codebehind way:
xaml:
<ComboBox SelectedIndex="{Binding Path=SaveIndex, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" SelectionChanged="Save_SelectionChanged">
codebehind:
private void Save_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ComboBox combobox = sender as ComboBox;
if(combobox == null)
{
return;
}
if (combobox.SelectedItem != combobox.Items[0] && combobox.SelectedItem != combobox.Items[1])
{
combobox.SelectedItem = combobox.Items[1];
e.Handled = true;
}
}
But it only works with the codebehind way, which is dirty.
Why doesn't work the MVVM way?
As others said, you do not actually set any value in the property setter.
But more important IMO, I think you've misunderstood the MVVM key concepts. There are lots of issues with your ViewModel code:
public object SaveSelectedItemCheck
{
get { return _control.SaveCombobox.Items[CurrentSaveIndex]; }
set
{
if (value != _control.SaveCombobox.Items[0] && value != _control.SaveCombobox.Items[1])
{
OnPropertyChanged("SaveSelectedItemCheck");
}
}
}
You're referring to _control.SaveCombobox.Items, which are UI concept/objects. This isn't the goal of the ViewModel. And you're returning an object, you should strongly type your model!
What you should have is the following:
a model (strongly typed POCO classes)
ViewModels that do not deal with the view controls in any way (you could even separate views and ViewModels into different assemblies to ensure you're following this rule)
Views, with binding to ItemsSource for control such as Combobox
Model:
public class SomeObject : INotifyPropertyChanged
{
private string someProperty;
public string SomeProperty
{
get { return this.someProperty; }
set
{
if (this.someProperty != value)
{
this.someProperty = value;
OnPropertyChanged("SomeProperty");
}
}
}
...
}
ViewModel:
public class ViewModel : SomeViewModelBase
{
private ObservableCollection<SomeObject> items;
private SomeObject selectedItem;
public ObservableCollection<SomeObject> Items
{
get
{
return items;
}
set
{
if (this.items != value)
{
this.items = value;
OnPropertyChanged("Items");
}
}
}
public ObservableCollection<SomeObject> SelectedItem
{
get
{
return selectedItem;
}
set
{
if (this.selectedItem != value)
{
this.selectedItem = value;
OnPropertyChanged("SelectedItem");
}
}
}
...
// Anywhere in your view model:
this.Items = new ObservableCollection<SomeObject>(...);
this.SelectedItem = this.Items[2];
// Etc.
}
View:
<ComboBox
ItemsSource={Binding Items}
SelectedItem="{Binding SelectedItem, Mode=TwoWay}">
View code-behind:
Nothing for your example
Your ViewModel method doesn't set the value of the property - regardless of whether the value is valid or not. It just fires an event based on whether the value is valid.
In fact, on closer inspection you appear to have misunderstood the MVVM pattern somewhat, as it appears that your ViewModel code might be referring directly to the control it is supporting. You should have a backing field for your property as per a "normal" property.
More importantly, you should throw the PropertyChanged event whether the value is valid or not, because if the value has been overriden by the viewmodel then PropertyChanged will notify the UI that the combobox value needs to be re-set to a valid value.
You don't store any value in the setter in your MVVM way.
Is it possible to bind data in the "wrong" direction? I want a value in a custom control to be bound to my ViewModel. I've tried binding with mode "OneWayToSource" but I can't get it to work.
Scenario (simplified):
I have a custom control (MyCustomControl) that has a dependency property that is a list of strings:
public class MyCustomControl : Control
{
static MyCustomControl()
{
//Make sure the template in Themes/Generic.xaml is used.
DefaultStyleKeyProperty.OverrideMetadata(typeof (MyCustomControl), new FrameworkPropertyMetadata(typeof (MyCustomControl)));
//Create/Register the dependency properties.
CheckedItemsProperty = DependencyProperty.Register("MyStringList", typeof (List<string>), typeof (MyCustomControl), new FrameworkPropertyMetadata(new List<string>()));
}
public List<string> MyStringList
{
get
{
return (List<string>)GetValue(MyCustomControl.MyStringListProperty);
}
set
{
var oldValue = (List<string>)GetValue(MyCustomControl.MyStringListProperty);
var newValue = value;
SetValue(MyCustomControl.MyStringListProperty, newValue);
OnPropertyChanged(new DependencyPropertyChangedEventArgs(MyCustomControl.MyStringListProperty, oldValue, newValue));
}
}
public static readonly DependencyProperty MyStringListProperty;
}
The control also contains code to manipulate this list.
I use this custom control in a UserControl that has a ViewModel. The ViewModel has a property that is also a list of strings:
public List<string> MyStringsInTheViewModel
{
get
{
return _myStringsInTheViewModel;
}
set
{
if (value != _myStringsInTheViewModel)
{
_myStringsInTheViewModel = value;
OnPropertyChanged("MyStringsInTheViewModel");
}
}
}
private List<string> _myStringsInTheViewModel;
Now I want to bind the list in my custom control (MyStringList) to the list in my ViewModel (MyStringsInTheViewModel) so that when the list is changed in the custom control it is also changed in the ViewModel. I've tried this but can't get it to work...
<myns:MyCustomControl MyStringList="{Binding Path=MyStringsInTheViewModel, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}">
How can I make such a binding?
Use ObservableCollection<T> instead of List<T>. It implements INotifyCollectionChanged Interface.