ViewModel Notify Bound ComboBox that List has been changed - c#

I have a ComboBox bound to a static List.
I want to change the items in the List, but the ComboBox will not update to reflect the changes.
XAML
<ComboBox x:Name="cbo"
ItemsSource="{Binding ComboBox_Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding cbo_SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
Margin="0,0,0,0"
VerticalAlignment="Top"
Width="100" />
C Sharp
ViewModel Class
Get/Set ComboBox Items
public static List<string> _cbo_Items = new List<string>()
{
"Item 1",
"Item 2",
"Item 3"
};
public static List<string> ComboBox_Items
{
get { return _cbo_Items; }
set { _cbo_Items = value;}
}
public static string cbo_SelectedItem { get; set; }
Another Class
Update the List with new items
ViewModel._cbo_Items = new List<string>()
{
"Item 4",
"Item 5",
"Item 6"
};
Solution
I tried this, it crashes with null exception on viewModel.OnPropertyChanged("ComboBox_Items")
public static ViewModel viewModel;
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public static List<string> ComboBox_Items
{
get { return _cbo_Items; }
set { _cbo_Items = value;
viewModel.OnPropertyChanged("ComboBox_Items");
}
}

The bind is broken when you 'new' the list, you can use observablecollection and clear then add items instead of creating new instance.
also fix the binding for the combobox's selecteditem property
<ComboBox x:Name="cbo"
ItemsSource="{Binding ComboBox_Items}"
SelectedItem="{Binding cbo_SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
Margin="0,0,0,0"
VerticalAlignment="Top"
Width="100" />
and it will also be better to remove the setter for the ComboBox_Items to prevent it from being re-created.

Related

Returning bound checkbox values using MVVM in a WPF form

I have an object that consists of a string and an array. The string populates a ComboBox and the array populates a ListView depending on the selected string value. Each line of the ListViewconsists of a TextBlock and a CheckBox.
On submit I want to be able to verify which items have been selected for further processing but there's a disconnect when using the MVVM approach. I currently have the DataContext of the submit Button binding to the ListView but only the first value is being returned upon submit (somewhere I need to save the selected values to a list I assume but I'm not sure where). I added an IsSelected property to the model which I think is the first step, but after that I've been grasping at straws.
Model
namespace DataBinding_WPF.Model
{
public class ExampleModel { }
public class Example : INotifyPropertyChanged
{
private string _name;
private string[] _ids;
private bool _isSelected;
public bool IsSelected
{
get => _isSelected;
set
{
if (_isSelected != value)
{
_isSelected = value;
RaisePropertyChanged("IsSelected");
}
}
}
public string Name
{
get => _name;
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
public string[] IDs
{
get => _ids;
set
{
if (_ids != value)
{
_ids = value;
RaisePropertyChanged("IDs");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
}
}
ViewModel
namespace DataBinding_WPF.ViewModel
{
public class ExampleViewModel : INotifyPropertyChanged
{
public ObservableCollection<Example> Examples
{
get;
set;
}
// SelectedItem in the ComboBox
// SelectedItem.Ids will be ItemsSource for the ListBox
private Example _selectedItem;
public Example SelectedItem
{
get => _selectedItem;
set
{
_selectedItem = value;
RaisePropertyChanged(nameof(SelectedItem));
}
}
// SelectedId in ListView
private string _selectedId;
public string SelectedId
{
get => _selectedId;
set
{
_selectedId = value;
RaisePropertyChanged(nameof(SelectedId));
}
}
private string _selectedCheckBox;
public string IsSelected
{
get => _selectedCheckBox;
set
{
_selectedCheckBox = value;
RaisePropertyChanged(nameof(IsSelected));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new
PropertyChangedEventArgs(property));
}
}
public void LoadExample()
{
ObservableCollection<Example> examples = new ObservableCollection<Example>();
examples.Add(new Example { Name = "Mark", IDs = new string[] { "123", "456" }, IsSelected = false });
examples.Add(new Example { Name = "Sally", IDs = new string[] { "789", "101112" }, IsSelected = false });
Examples = examples;
}
/* BELOW IS A SNIPPET I ADDED FROM AN EXAMPLE I FOUND ONLINE BUT NOT SURE IF IT'S NEEDED */
private ObservableCollection<Example> _bindCheckBox;
public ObservableCollection<Example> BindingCheckBox
{
get => _bindCheckBox;
set
{
_bindCheckBox = value;
RaisePropertyChanged("BindingCheckBox");
}
}
}
}
View
<UserControl x:Class = "DataBinding_WPF.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:DataBinding_WPF"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
SelectedItem="{Binding SelectedItem}"
DisplayMemberPath="Name"/>
<ListView x:Name="myListView"
ItemsSource="{Binding SelectedItem.IDs}"
DataContext="{Binding DataContext, ElementName=submit_btn}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox
Name="myCheckBox"
IsChecked="{Binding IsSelected,
RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}" FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left" Height="20" Width="100"
Click="Submit" x:Name="submit_btn">Submit</Button>
</StackPanel>
</Grid>
</UserControl>
View.cs
namespace DataBinding_WPF.Views
{
/// <summary>
/// Interaction logic for StudentView.xaml
/// </summary>
public partial class StudentView : UserControl
{
public StudentView()
{
InitializeComponent();
}
private void Submit(object sender, EventArgs e)
{
var selectedItems = ((Button)sender).DataContext;
// process each selected item
// foreach (var selected in ....) { }
}
}
}
The ListView control already exposes a selected items collection as property SelectedItems.
private void Submit(object sender, RoutedEventArgs e)
{
var selectedIds = myListView.SelectedItems.Cast<string>().ToList();
// ...do something with the items.
}
However, I doubt that you want to do this in the code-behind, but rather in the view model. For this purpose, WPF offers the concept of commands.
MVVM - Commands, RelayCommands and EventToCommand
What you need is a relay command or delegate command (the name varies across frameworks). It encapsulates a method that should be executed for e.g. a button click and a method to determine whether the command can be executed as an object that can be bound in the view. Unfortunately, WPF does not provide an implementation out-of-the-box, so you either have to copy an implementation like here or use an MVVM framework that already provides one, e.g. Microsoft MVVM Tookit.
You would expose a property Submit of type ICommand in your ExampleViewModel and initialize it in the constructor with an instance of RelayCommand<T> that delegates to a method to execute.
public class ExampleViewModel : INotifyPropertyChanged
{
public ExampleViewModel()
{
Submit = new RelayCommand<IList>(ExecuteSubmit);
}
public RelayCommand<IList> Submit { get; }
// ...other code.
private void ExecuteSubmit(IList selectedItems)
{
// ...do something with the items.
var selectedIds = selectedItems.Cast<string>().ToList();
return;
}
}
In your view, you would remove the Click event handler and bind the Submit property to the Command property of the Button. You can also bind the SelectedItems property of the ListView to the CommandParameter property, so the selected items are passed to the command on execution.
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
x:Name="submit_btn"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
Additionally, a few remarks about your XAML.
Names of controls in XAML should be Pascal-Case, starting with a capital letter.
You should remove the DataContext binding from ListView completely, as it automatically receives the same data context as the Button anyway.
DataContext="{Binding DataContext, ElementName=submit_btn}"
You can save yourself from exposing and binding the SelectedItem property in your ExampleViewModel, by using Master/Detail pattern for hierarchical data.
<Grid>
<StackPanel HorizontalAlignment = "Left" >
<ComboBox HorizontalAlignment="Left"
VerticalAlignment="Top"
Width="120"
ItemsSource="{Binding Path=Examples}"
IsSynchronizedWithCurrentItem="True"
DisplayMemberPath="Name"/>
<ListView ItemsSource="{Binding Examples/IDs}"
SelectedItem="{Binding SelectedId}"
Height="200" Margin="10,50,0,0"
Width="Auto"
VerticalAlignment="Top"
Background="AliceBlue">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<CheckBox Name="myCheckBox"
IsChecked="{Binding IsSelected, RelativeSource={RelativeSource AncestorType=ListViewItem}}"
Margin="5, 0"/>
<TextBlock Text="{Binding}"
FontWeight="Bold" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<Button HorizontalAlignment="Left"
Height="20"
Width="100"
Command="{Binding Submit}"
CommandParameter="{Binding SelectedItems, ElementName=myListView}">Submit</Button>
</StackPanel>
</Grid>
If the view's data context is bound to the view then remove the DataContext from the ListView.
You could remove the item template and instead use a GridView like:
<ListView.View>
<GridView >
<GridViewColumn Header="Selected" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsSelected}" Content="{Binding Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
Since the ItemSource is an Observable collection, there are several options to monitor changes in the checkboxes:
Add an event handler to the item changed event of the collection and then you can add the Name or the collection index to a local collection. e.g Examples[e.CollectionIndex].Name
Alternatively iterate over the observable collection and select those Examples where Selected = "true"

Change ComboBox Item Source and Update Notify with CollectionChanged

I have a ComboBox cbo1.
I'm trying to change the item source using the ViewModel with CollectionChanged but the ComboBox items stay blank and won't update.
I've tried several examples and solutions here, but don't know how to implement them right.
XAML
<ComboBox x:Name="cbo1"
ItemsSource="{Binding cbo1_Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding cbo1_SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
HorizontalAlignment="Left"
Margin="0,0,0,0"
VerticalAlignment="Top"
Width="105"
Height="22" />
ViewModelBase Class
Bind ComboBox Items
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void Notify(string propName)
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
public ViewModelBase()
{
_cbo1_Items = new ObservableCollection<string>();
_cbo1_Items.CollectionChanged += cbo1_Items_CollectionChanged;
}
// Notify Collection Changed
//
public void cbo1_Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
Notify("cbo1_Items");
}
// Item Source
//
public static ObservableCollection<string> _cbo1_Items = new ObservableCollection<string>();
public static ObservableCollection<string> cbo1_Items
{
get { return _cbo1_Items; }
set { _cbo1_Items = value; }
}
// Selected Item
//
public static string cbo1_SelectedItem { get; set; }
}
Example Class
In this class I want to change the ComboBox Item Source.
// Change ViewModel Item Source
//
ViewModelBase._cbo1_Items = new ObservableCollection<string>()
{
"Item 1",
"Item 2",
"Item 3"
};
// ...
// Change Item Source Again
//
ViewModelBase._cbo1_Items = new ObservableCollection<string>()
{
"Item 4",
"Item 5",
"Item 6"
};
Implement - RaisePropertyChanged("ComboBoxItemsource");/NotifyPropertyChanged("ComboBoxItemsource") in your property declaration.
Ex: -
In View
<ComboBox Width="40" Height="40" ItemsSource="{Binding ComboBoxItemsource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
In View Model-
private ObservableCollection<string> comboBoxItemsource;
public ObservableCollection<string> ComboBoxItemsource
{
get { return comboBoxItemsource; }
set
{
if (comboBoxItemsource != value)
{
comboBoxItemsource = value;
RaisePropertyChanged("ComboBoxItemsource");
}
}
}
In Class Constructor-
public ClassViewModel()
{
ComboBoxItemsource = new ObservableCollection<string>();
ComboBoxItemsource.Add("Item1");
ComboBoxItemsource.Add("Item2");
....
}
//Event on which you want to change the collection
public void OnClickEvent()
{
ComboBoxItemsource = new ObservableCollection<string>();
ComboBoxItemsource.Add("Item5");
ComboBoxItemsource.Add("Item6");
}
Class should Inherit and Implement INotifyPropertyChanged.
Hope this Helps..

Getting keys as Combobox selected items in Dictionary bind

I have a Dictionary binded with ComboBox. For example let's say the Dictionary has data set like this:
{1,item1}
{2,item2}
Now when you select any of the option the ComboBox.SelectedItem should get only the integer key not the value.
Here is the code:
public static Dictionary<int, string> newDict { get; set; }
newDict = MTMInteraction.getPlanId();
txtPlanId.ItemsSource = newDict;
XAML code:
<ComboBox x:Name="txtPlanId" ItemsSource="{Binding newDict}" IsEditable="True" Margin="-2,0,79,3" Text="Enter ID" HorizontalAlignment="Center" VerticalAlignment="Bottom"/>
Instead of SelectedItem use SelectedValuePath/SelectedValue properties. Set SelectedValuePath against ComboBox to property that you want to get and bind SelectedValue to some property in view model
<ComboBox
x:Name="txtPlanId"
ItemsSource="{Binding newDict}"
...
SelectedValuePath="Key"
SelectedValue="{Binding SomeIntProperty}"/>
or in code txtPlanId.SelectedValue should give you Key part of your KeyValuePair<int,string>
DisplayMemberPath="Key"
More than you asked but it does answer the question (I think)
<Window x:Class="BindDictionary.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext="{Binding RelativeSource={RelativeSource self}}"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox ItemsSource="{Binding Path=DLBS, Mode=OneWay}"
DisplayMemberPath="Key"
SelectedValuePath="Key"
SelectedValue="{Binding Path=DLBSkey}"/>
</Grid>
</Window>
using System.ComponentModel;
namespace BindDictionary
{
public partial class MainWindow : Window , INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
private byte? dlBSkey = 1;
private Dictionary<byte, string> dlBS = new Dictionary<byte, string>() { { 1, "one" }, { 2, "two" }, { 5, "five" } };
public MainWindow()
{
InitializeComponent();
}
public Dictionary<byte, string> DLBS { get { return dlBS; } }
public byte? DLBSkey
{
get { return dlBSkey; }
set
{
if (dlBSkey == value) return;
dlBSkey = value;
NotifyPropertyChanged("DLBSkey");
}
}
}
}

Automatically add a combobox when you fill another one

Hi i need to make a program where you have to add an undefined number of elements to a list by choosing them from a combobox. I planned to use 4 basic comboboxes and when the user choose an element from the last one, the program should automatically add another one under the last one (i want to use a stackpanel).
How could i do?
Thanks.
My XAML:
<StackPanel Name="listPanel" Grid.Column="0" Margin="10">
<Label Content="Example" FontWeight="Bold" HorizontalAlignment="Center"/>
<ComboBox Name="ex1Combobox" Margin="0,10,0,0"
ItemsSource="{Binding ExList, Mode=TwoWay}"
SelectedValue="{Binding SelectedEx}"
DisplayMemberPath="Name"
SelectedValuePath="ID"/>
<ComboBox Name="ex2Combobox" Margin="0,10,0,0"
ItemsSource="{Binding ExList, Mode=TwoWay}"
SelectedValue="{Binding SelectedEx}"
DisplayMemberPath="Name"
SelectedValuePath="ID"/>
<ComboBox Name="ex3Combobox" Margin="0,10,0,0"
ItemsSource="{Binding ExList, Mode=TwoWay}"
SelectedValue="{Binding SelectedEx}"
DisplayMemberPath="Name"
SelectedValuePath="ID"/>
</StackPanel>
This is a pretty good example of why you should use MVVM.
Model
Has a collection of selected values only something like
public class MyChoices
{
public IEnumerable<string> Selections {get; set;}
}
ViewModel
Has a collection that extends as soon as you modify the last item
public class MyChoicesViewModel
{
public MyChoicesViewModel()
{
Selections = new ObservableCollection<ChoiceViewModel>();
//Add first empty value
AddNewItem();
Selections.CollectionChanged += (sender, e) =>
{
// If you change the last add another
if (e.NewItems.Contains(Selections.Last()))
AddNewItem();
};
}
public ObservableCollection<ChoiceViewModel> Selections {get; private set;}
public void AddNewItem()
{
var newItem = new ChoiceViewModel();
Selections.Add(newItem);
newItem.PropertyChanged += () =>
{
//This is where we update the model from the ViewModel
Model.Selections = from x in Selections
select x.Value;
}
}
}
public class ChoiceViewModel : INotifyPropertyChanged
{
private string _chosen;
public string Chosen
{
get { return _chosen; }
set {
if (_chosen != value)
{
_chose = value;
OnPropertyChanged();
}
}
}
public void OnPropertyChanged([CallerMemberName] string property)
{
var temp = PropertyChanged;
if (temp != null)
{
temp(this, new PropertyChangedEventArgs(property));
}
}
}
}
View
<!-- Then show many of them-->
<ListBox ItemsSource="{Binding Selections}"/>

Binding problem with CurrentItem in WPF C#, Can't access "second level data"

I have a problem in c# wpf tring to bind to CurrentItem, i have a list of persons, and each persons can have one of two items. You can select a person in the list and then select it's item in a combobox.
The combobox binds to Persons.CurrentItem.Item and shows what the person have as selected item. But i cant change it, or rather i cant keep the change that is made, it changes back as soon as i select a new person.
The XAML looks like this:
<!--This dose not work-->
<TextBox Height="23" HorizontalAlignment="Left" Margin="148,55,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=Persons.CurrentItem.Item.Name}"/>
<!--This works-->
<TextBox Height="23" HorizontalAlignment="Left" Margin="16,306,0,0" Name="textBox2" VerticalAlignment="Top" Width="120" Text="{Binding Path=Persons.CurrentItem.Name}"/>
<!--This works, we do not bind to CurrentItem-->
<ListBox Height="274" HorizontalAlignment="Left" ItemsSource="{Binding Path=Persons2}" SelectedItem="{Binding Path=SelectedPerson}" Margin="292,26,0,0" Name="listBox2" VerticalAlignment="Top" Width="120" DisplayMemberPath="Name" />
<ComboBox Height="23" HorizontalAlignment="Left" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Path=SelectedPerson.Item}" Margin="431,26,0,0" Name="comboBox2" VerticalAlignment="Top" Width="120" DisplayMemberPath="Name"/>
</Grid>
As you can see i have added a persons2 with SelectedItem as SelectedPerson. This works fine and i want to mimic it's function but i want to use Current item.
This is the C# code:
// Selectable items
public List<Item> Items { get; set; }
// List of persons, we will bind to it's CurrentItem
public List<Person> Persons { get; set; }
// This works, we do not use CurrentItem
public List<Person> Persons2 { get; set; }
private Person _selectedPerson;
public Person SelectedPerson
{
get { return _selectedPerson; }
set
{
_selectedPerson = value;
NotifyPropertyChanged("SelectedPerson");
}
}
#region Constructo
public Window()
{
InitializeComponent();
DataContext = this;
// Populate Items
Items = new List<Item>
{
new Item {Name = "Hammer"},
new Item {Name = "Axe"}
};
// Populate persons
Persons = new List<Person>() { new Person { Name = "Lisa", Item = Items.FirstOrDefault()}, new Person { Name = "Kauf" } };
Persons2 = new List<Person>(Persons); // make a copy
}
#endregion
#region PropertyChangeHandler
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string name)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(name));
}
#endregion
}
public class Item
{
public string Name { get; set; }
}
public class Person
{
public string Name { get; set; }
private Item _item;
public Item Item
{
get { return _item; }
set
{
// We only accass this if we do not bind to CurrentItem
_item = value;
}
}
}
If you test the example you can see that Persons.CurrentItem.Name works, but Persons.CurrentItem.Item.Name dose not, Why? Have i missed something with the level of access?
Is there something i have missed on how to use CurrentItem?
Thanks for enlightening me.
Asked the same question on msdn:
http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/18eee956-3a91-4db3-a4ff-7e8d127ae301
The solution is not use CurentItem but instead / like this:
<StackPanel>
<ListBox ItemsSource="{Binding Path=Persons}" IsSynchronizedWithCurrentItem="True" DisplayMemberPath="Name" />
<ComboBox HorizontalAlignment="Left" VerticalAlignment="Top" ItemsSource="{Binding Path=Items}" SelectedItem="{Binding Persons/Item}" DisplayMemberPath="Name"/>
</StackPanel>

Categories

Resources