I am trying to bind DataGrid using MVVM approach in WPF, model is getting values but nothing is showing in DataGrid
Following is my code
public class TalleyEditorGrid : INotifyPropertyChanged
{
#region Properties
private string _Quantity;
private string _Ft;
private string _Inch;
private string _Comment;
public string Quantity { get { return _Quantity; } set { _Quantity = value; NotifyPropertyChanged("Quantity"); } }
public string Ft { get { return _Ft; } set { _Ft = value; NotifyPropertyChanged("Ft"); } }
public string Inch { get { return _Inch; } set { _Inch = value; NotifyPropertyChanged("Inch"); } }
public string Comment { get { return _Comment; } set { _Comment = value; NotifyPropertyChanged("Comment"); } }
#endregion
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged(string propName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName)); }
}
In my xaml.cs
private ObservableCollection<TalleyEditorGrid> _TalleyEditorGrid = new ObservableCollection<TalleyEditorGrid>();
public ObservableCollection<TalleyEditorGrid> TalleyEditorCol
{
get { return _TalleyEditorGrid; }
}
On button click I am filling this collection
_TalleyEditorGrid.Add(new TalleyEditorGrid() { Quantity = Q, Ft = FT, Inch = In, Comment = Comment});
Xaml as follows
<DataGrid x:Name="TalleyEditor" ItemsSource="{Binding Path=TalleyEditorCol}" AutoGenerateColumns="True" Visibility="Collapsed"
CanUserAddRows="False"
HorizontalGridLinesBrush="{x:Null}"
VerticalGridLinesBrush="Silver"
Background="White"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
SelectionMode="Single"
SelectionUnit="FullRow"
CanUserReorderColumns="True"
CanUserSortColumns="True" DataGridCell.GotFocus="TalleyEditor_GotFocus"
RowHeaderWidth="0" HorizontalAlignment="Left" VerticalAlignment="Top" RowHeight="17" ColumnHeaderHeight="21" PreviewKeyDown="TalleyEditor_PreviewKeyDown" LostFocus="TalleyEditor_LostFocus" CellEditEnding="myDG_CellEditEnding">
Set the DataContext of the window to itself:
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
Or set the DataContext of the DataGrid:
public MainWindow()
{
InitializeComponent();
TalleyEditor.DataContext = this;
}
If you put the DataGrid inside another DataGrid, you need to use a {RelativeSource} to be able to bind to a property of the parent window:
<DataGrid ... ItemsSource="{Binding Path=TalleyEditorCol, RelativeSource={RelativeSource AncestorType=Window}}">
</DataGrid>
Related
Goal is to have multiple Combobox, once the item is selected in any of the Combobox, it should be removed or hided from other Comboboxes for selection.
I'm able to change the source whenever the selection got changed on any of the Comboboxes. But Problem is when I remove the selected item from the ItemsSource, item got removed from all the sources including the one on which I selected.
Here is the sample
xaml
<StackPanel>
<ComboBox Width="100" Height="50" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding ComboboxItems}" />
<ComboBox Width="100" Height="50" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding ComboboxItems}" />
<ComboBox Width="100" Height="50" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding ComboboxItems}" />
<ComboBox Width="100" Height="50" SelectedItem="{Binding SelectedItem}" ItemsSource="{Binding ComboboxItems}" />
</StackPanel>
Codebehind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
ObservableCollection<string> s = new ObservableCollection<string>();
s.Add("item1");
s.Add("item2");
s.Add("item3");
s.Add("item4");
s.Add("item5");
s.Add("item6");
DataContext = new MainViewModel() { ComboboxItems = s , SelectedItem = "item2" };
}
}
public class MainViewModel : INotifyPropertyChanged
{
private ObservableCollection<string> comboboxitems;
public ObservableCollection<string> ComboboxItems
{
get { return comboboxitems; }
set { comboboxitems = value; OnPropertyChanged("ComboboxItem"); }
}
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
comboboxitems.Remove(value); //here removing selected item from itemssource
OnPropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propname)
{
if(PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propname));
}
}
I know I can have multiple Collection for each Combobox but that may take lot of memory if number of Combobox increases.
Hope, there should be a easy way to achieve this in WPF.
You could define different SelectedItem for each ComboBox, and then create a wrapper of the SharedItemSource for each ComboBoxto filter out the SelectedItem of other ComboBox. eg:
C# :
public IEnumerable<string> ComboboxItems1
{
get
{
return ComboboxItems.Where(x => x != SelectedItem2 && x != SelectedItem3);
}
}
public string SelectedItem1
{
get { return _selectedItem1; }
set
{
if (_selectedItem1 != value)
{
_selectedItem1 = value;
RaisePropertyChanged("SelectedItem1");
RaisePropertyChanged("ComboboxItems2"); //raise propertychanged to refresh GUI
RaisePropertyChanged("ComboboxItems3");
}
}
}
public IEnumerable<string> ComboboxItems2
{
get
{
return ComboboxItems.Where(x => x!=SelectedItem1&&x!=SelectedItem3);
}
}
public string SelectedItem2
{
get { return _selectedItem2; }
set
{
if (_selectedItem2 != value)
{
_selectedItem2 = value;
RaisePropertyChanged("SelectedItem2");
RaisePropertyChanged("ComboboxItems1"); //raise propertychanged to refresh GUI
RaisePropertyChanged("ComboboxItems3");
}
}
}
public IEnumerable<string> ComboboxItems3
{
get
{
return ComboboxItems.Where(x => x != SelectedItem1 && x != SelectedItem2);
}
}
public string SelectedItem3
{
get { return _selectedItem3; }
set
{
if (_selectedItem3 != value)
{
_selectedItem3 = value;
RaisePropertyChanged("SelectedItem3");
RaisePropertyChanged("ComboboxItems1"); //raise propertychanged to refresh GUI
RaisePropertyChanged("ComboboxItems2");
}
}
}
XAML:
<ComboBox SelectedItem="{Binding SelectedItem1}" ItemsSource="{Binding ComboboxItems1}" />
<ComboBox SelectedItem="{Binding SelectedItem2}" ItemsSource="{Binding ComboboxItems2}" />
<ComboBox SelectedItem="{Binding SelectedItem3}" ItemsSource="{Binding ComboboxItems3}" />
I am trying to set some of the ListBox elements as selected when binding through DataContext. ListBox is binded through code behind.
I am binding my listbox on user control's constructor
TradesListBox.ItemsSource = config.OfType<Trade>().ToList();
XAML below is a part of UserControl whose DisplayMemberPath property is being set through a constructor as shown in line above, while I am trying to set SelectedItem property from DataContext that is being passed through owing window. But SelectedItem are not being displayed
<Label Grid.Row="1" Grid.Column="0" Target="{Binding ElementName=TradesListBox}" Style="{StaticResource LabelStyle}" FontSize="18" HorizontalAlignment="Right">_Trades</Label>
<ListBox Grid.Row="1" Grid.Column="1" Name="TradesListBox" HorizontalAlignment="Stretch" Height="70" Margin="2" DisplayMemberPath="ConfigValue" SelectedItem="{Binding Trade.ConfigValue}" SelectionMode="Multiple" />
private List<Trade> trade;
[DataMember]
public virtual List<Trade> Trade
{
get
{
if (trade == null)
trade = new List<Trade>();
return trade;
}
set
{ trade = value == null ? new List<Trade>() : value; }
}
I think items are selected but not highlighted, if so then try to set Focus on ListBox and see if it works for you.
TradesListBox.ItemsSource = config.OfType<Trade>().ToList();
// Let say you want to Select first and second item
TradesListBox.SelectedItems.Add(TradesListBox.Items[0]);
TradesListBox.SelectedItems.Add(TradesListBox.Items[1]);
// Set focus on ListBox
TradesListBox.Focus();
Hi If you are using MVVM then you can bind SelectedItem of ListBox like
xaml
<ListBox ItemsSource="{Binding Students}" SelectedItem="{Binding SelectedStudent}" DisplayMemberPath="Name"></ListBox>
.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
ViewModel
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
Students = new ObservableCollection<Student>();
Students.Add(new Student { Name = "abc", Age = 20 });
Students.Add(new Student { Name = "pqr", Age = 30 });
Students.Add(new Student { Name = "xyz", Age = 40 });
SelectedStudent = Students[0];
}
public ObservableCollection<Student> Students { get; set; }
Student selectedStudent;
public Student SelectedStudent
{
get { return selectedStudent; }
set { selectedStudent = value; Notify("SelectedStudent"); }
}
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
CustomType
public class Student:INotifyPropertyChanged
{
string name;
public string Name {
get
{ return name; }
set
{
name = value;
Notify("Name");
}
}
int age;
public int Age
{
get
{ return age; }
set
{
age = value;
Notify("Age");
}
}
private void Notify(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Remember the reference of SelectedItem must be same as that one of the item in Collection binded to ItemsSource
>Edit
<ListBox Grid.Row="1" Grid.Column="1" Name="TradesListBox" HorizontalAlignment="Stretch"
Height="70" Margin="2" DisplayMemberPath="ConfigValue"
SelectedValue="{Binding Trade.ConfigValue}"
SelectedValuePath="ConfigValue" SelectionMode="Multiple" />
I am using WPF and MVVM light framework (and I am new in using them)
Here is the situation:
I have a combobox displaying a list of items (loaded from a database) and I am using the DisplayMemberPath to display the title of the items in the combobox.
In the same GUI, the user can modify the item title in a text box. A button 'Save' allows the user to save the data into the database.
What I want to do is when the user clicks 'Save', the item title in the combobox gets updated too and the new value is displayed at that time. However, I do not know how to do that...
Some details on my implementation:
MainWindow.xaml
<ComboBox ItemsSource="{Binding SourceData}" SelectedItem="{Binding SelectedSourceData,Mode=TwoWay}" DisplayMemberPath="Title" />
<TextBlock Text="{Binding SelectedDataInTextFormat}"/>
MainWindow.xaml.cs
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Closing += (s, e) => ViewModelLocator.Cleanup();
}
}
MainViewModel.xaml
public class MainViewModel:ViewModelBase
{
public ObservableCollection<Foo> SourceData{get;set;}
public Foo SelectedSourceData
{
get{return _selectedFoo;}
set{_selectedFoo=value; RaisePropertyChanged("SelectedSourceData"); }
}
public string SelectedDataInTextFormat
{
get{return _selectedDataInTextFormat;}
set{_selectedDataInTextFormat=value; RaisePropertyChanged("SelectedDataInTextFormat");
}
}
I would appreciate if anyone could help me on this one.
Thanks for your help.
Romain
You might simply update the SelectedSourceData property when SelectedDataInTextFormat changes:
public string SelectedDataInTextFormat
{
get { return _selectedDataInTextFormat; }
set
{
_selectedDataInTextFormat = value;
RaisePropertyChanged("SelectedDataInTextFormat");
SelectedSourceData = SourceData.FirstOrDefault(f => f.Title == _selectedDataInTextFormat)
}
}
EDIT: In order to change the Title property of the currently selected Foo item in the ComboBox, you could implement INotifyPropertyChanged in your Foo class:
public class Foo : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string title = string.Empty;
public string Title
{
get { return title; }
set
{
title = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Title"));
}
}
}
}
Then simply set the Title property of the selected item:
SelectedSourceData.Title = SelectedDataInTextFormat;
There is many ways to do this, This example takes advantage of the Button Tag property to send some data to the save button handler(or ICommand), Then we can set the TextBox UpdateSourceTrigger to Explicit and call the update when the Button is clicked.
Example:
Xaml:
<Window x:Class="WpfApplication8.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="105" Width="156" Name="UI">
<Grid DataContext="{Binding ElementName=UI}">
<StackPanel Name="stackPanel1">
<ComboBox x:Name="combo" ItemsSource="{Binding SourceData}" DisplayMemberPath="Title" SelectedIndex="0"/>
<TextBox x:Name="txtbox" Text="{Binding ElementName=combo, Path=SelectedItem.Title, UpdateSourceTrigger=Explicit}"/>
<Button Content="Save" Tag="{Binding ElementName=txtbox}" Click="Button_Click"/>
</StackPanel>
</Grid>
</Window>
Code:
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ObservableCollection<Foo> _sourceData = new ObservableCollection<Foo>();
public MainWindow()
{
InitializeComponent();
SourceData.Add(new Foo { Title = "Stack" });
SourceData.Add(new Foo { Title = "Overflow" });
}
public ObservableCollection<Foo> SourceData
{
get { return _sourceData; }
set { _sourceData = value; }
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var txtbx = (sender as Button).Tag as TextBox;
txtbx.GetBindingExpression(TextBox.TextProperty).UpdateSource();
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class Foo : INotifyPropertyChanged
{
private string _title;
public string Title
{
get { return _title; }
set { _title = value; RaisePropertyChanged("Title"); }
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
Code:
public ObservableCollection<Foo> SourceData{get;set;}
public Foo SelectedSourceData
{
get{
return _selectedFoo;
}
set{
_selectedFoo=value;
RaisePropertyChanged("SelectedSourceData");
}
}
public string SelectedDataInTextFormat //Bind the text to the SelectedItem title
{
get{
return SelectedSourceData.Title
}
set{
SelectedSourceData.Title=value;
RaisePropertyChanged("SelectedDataInTextFormat");
}
}
XAML:
<ComboBox ItemsSource="{Binding SourceData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedSourceData,Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" DisplayMemberPath="Title" />
<TextBlock Text="{Binding SelectedDataInTextFormat, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
I cant figure this one out. If I just have the combo-box by itself not embedded in a list box it will populate and selected the values I need.
The XAML
<ComboBox Name="comboBox1"
Height="23"
DataContext="{Binding Combox}"
ItemsSource="{Binding Comboxes}"
DisplayMemberPath="PV"
SelectedValuePath="PK"
SelectedItem="{Binding SelectedItem}"
VerticalAlignment="Top"
Width="120" />
The Code Behind
public MainWindow()
{
InitializeComponent();
DataAttribute d = new DataAttribute(2, "blue");
Combox c = new Combox();
c.SelectedItem = d;
c.Comboxes.Add(new DataAttribute(1, "red"));
c.Comboxes.Add(new DataAttribute(3, "Black"));
c.Comboxes.Add(c.SelectedItem);
comboBox1.DataContext = c;
}
Class for holding data
public class Combox: INotifyPropertyChanged
{
public Combox()
{
Comboxes = new List<DataAttribute>();
}
private DataAttribute _selectedItem;// = new DataAttribute(-1, "NA");
public List<DataAttribute> Comboxes { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public DataAttribute SelectedItem
{
get { return _selectedItem; }
set
{
if (_selectedItem == value) return;
_selectedItem = value;
OnPropertyChanged("SelectedValue");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class DataAttribute
{
public DataAttribute() { }
public DataAttribute(int pk, string pv)
{
PK = pk;
PV = pv;
}
public int PK { get; set; }
public string PV { get; set; }
public override string ToString()
{
return PV;
}
}
All that works fine, but as soon as I try to create a list of com boxes in the listbox nothing. I can see the combo but no data. How on earth to you bind to it is XAML?
public MainWindow()
{
InitializeComponent();
List<Combox> com = new List<Combox>();
DataAttribute d = new DataAttribute(2, "blue");
Combox c = new Combox();
c.SelectedItem = d;
c.Comboxes.Add(new DataAttribute(1, "red"));
c.Comboxes.Add(new DataAttribute(3, "Black"));
c.Comboxes.Add(c.SelectedItem);
com.Add(c);
lstTest.ItemSource = com;
}
As here is the XAML with a listbox. It no longer binds...
<ListBox Name="lstTest" ItemsSource="{Binding}">
<ListBox.ItemTemplate>
<DataTemplate>
<ComboBox Name="comboBox1"
DataContext="{Binding Combox}"
ItemsSource="{Binding Comboxes}"
DisplayMemberPath="PV"
SelectedValuePath="PK"
SelectedItem="{Binding SelectedItem}"
VerticalAlignment="Top"
Width="120" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am stumped as it was hard enough trying to figure out how to just get the selected object to appear without the list-box...
Inside the items of an ItemsControl the DataContext is the currectly templated item, if you want to get to a list that is to be used as the ItemsSource for the ComboBoxes you normally need to modify the bindings to use another source, e.g. RelativeSource or ElementName. (This is the case for one list that is to be used for all ComboBoxes)
In this case, where the list seems to be part of the item, you should only need to remove the binding on the DataContext as the DataContext is already the item (an instance of Combox).
I'm not sure my Title is right but this is the problem I am facing now.. I have the below XAML code..
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox ItemsSource="{Binding Path=AvailableFields}"
SelectedItem="{Binding Path=SelectedField}"
></ComboBox>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
What this basically does is, If my data source contains ten items, this is going to generate 10 row of comboboxes and all comboboxes are bounded to the same itemsource.
Now my requirement is Once an item is selected in the first combo box, that item should not be available in the subsequent combo boxes. How to satisfy this requirement in MVVM and WPF?
This turned out to be harder than I thought when I started coding it. Below sample does what you want. The comboboxes will contain all letters that are still available and not selected in another combobox.
XAML:
<Window x:Class="TestApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<StackPanel>
<ItemsControl ItemsSource="{Binding Path=SelectedLetters}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox
ItemsSource="{Binding Path=AvailableLetters}"
SelectedItem="{Binding Path=Letter}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Window>
Code behind:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
namespace TestApp
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
DataContext = new VM();
}
}
public class VM : INotifyPropertyChanged
{
public VM()
{
SelectedLetters = new List<LetterItem>();
for (int i = 0; i < 10; i++)
{
LetterItem letterItem = new LetterItem();
letterItem.PropertyChanged += OnLetterItemPropertyChanged;
SelectedLetters.Add(letterItem);
}
}
public List<LetterItem> SelectedLetters { get; private set; }
private void OnLetterItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName != "Letter")
{
return;
}
foreach (LetterItem letterItem in SelectedLetters)
{
letterItem.RefreshAvailableLetters(SelectedLetters);
}
}
public event PropertyChangedEventHandler PropertyChanged;
public class LetterItem : INotifyPropertyChanged
{
static LetterItem()
{
_allLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".Select(c => c.ToString());
}
public LetterItem()
{
AvailableLetters = _allLetters;
}
public void RefreshAvailableLetters(IEnumerable<LetterItem> letterItems)
{
AvailableLetters = _allLetters.Where(c => !letterItems.Any(li => li.Letter == c) || c == Letter);
}
private IEnumerable<string> _availableLetters;
public IEnumerable<string> AvailableLetters
{
get { return _availableLetters; }
private set
{
_availableLetters = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("AvailableLetters"));
}
}
}
private string _letter;
public string Letter
{
get { return _letter; }
set
{
if (_letter == value)
{
return;
}
_letter = value;
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Letter"));
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private static readonly IEnumerable<string> _allLetters;
}
}
}
This functionality is not provided by WPF, but it can be implemented using some custom coding.
I've created 3 ViewModel classes:
PreferencesVM - This will be our DataContext. It contains the master list of options which can appear in the ComboBoxes, and also contains a SelectedOptions property, which keeps track of which items are selected in the various ComboBoxes. It also has a Preferences property, which we will bind our ItemsControl.ItemsSource to.
PreferenceVM - This represents one ComboBox. It has a SelectedOption property, which ComboBox.SelectedItem is bound to. It also has a reference to PreferencesVM, and a property named Options (ComboBox.ItemsSource is bound to this), which returns the Options on PreferencesVM via a filter which checks if the item may be displayed in the ComboBox.
OptionVM - Represents a row in the ComboBox.
The following points form the key to the solution:
When PreferenceVM.SelectedOption is set (ie a ComboBoxItem is selected), the item is added to the PreferencesVM.AllOptions collection.
PreferenceVM handles Preferences.SelectedItems.CollectionChanged, and triggers a refresh by raising PropertyChanged for the Options property.
PreferenceVM.Options uses a filter to decide which items to return - which only allows items which are not in PreferencesVM.SelectedOptions, unless they are the SelectedOption.
What I've described above might be enough to get you going, but to save you the headache I'll post my code below.
PreferencesVM.cs:
public class PreferencesVM
{
public PreferencesVM()
{
PreferenceVM pref1 = new PreferenceVM(this);
PreferenceVM pref2 = new PreferenceVM(this);
PreferenceVM pref3 = new PreferenceVM(this);
this._preferences.Add(pref1);
this._preferences.Add(pref2);
this._preferences.Add(pref3);
//Only three ComboBoxes, but you can add more here.
OptionVM optRed = new OptionVM("Red");
OptionVM optGreen = new OptionVM("Green");
OptionVM optBlue = new OptionVM("Blue");
_allOptions.Add(optRed);
_allOptions.Add(optGreen);
_allOptions.Add(optBlue);
}
private ObservableCollection<OptionVM> _selectedOptions =new ObservableCollection<OptionVM>();
public ObservableCollection<OptionVM> SelectedOptions
{
get { return _selectedOptions; }
}
private ObservableCollection<OptionVM> _allOptions = new ObservableCollection<OptionVM>();
public ObservableCollection<OptionVM> AllOptions
{
get { return _allOptions; }
}
private ObservableCollection<PreferenceVM> _preferences = new ObservableCollection<PreferenceVM>();
public ObservableCollection<PreferenceVM> Preferences
{
get { return _preferences; }
}
}
PreferenceVM.cs:
public class PreferenceVM:INotifyPropertyChanged
{
private PreferencesVM _preferencesVM;
public PreferenceVM(PreferencesVM preferencesVM)
{
_preferencesVM = preferencesVM;
_preferencesVM.SelectedOptions.CollectionChanged += new NotifyCollectionChangedEventHandler(SelectedOptions_CollectionChanged);
}
void SelectedOptions_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this,new PropertyChangedEventArgs("Options"));
}
private OptionVM _selectedOption;
public OptionVM SelectedOption
{
get { return _selectedOption; }
set
{
if (value == _selectedOption)
return;
if (_selectedOption != null)
_preferencesVM.SelectedOptions.Remove(_selectedOption);
_selectedOption = value;
if (_selectedOption != null)
_preferencesVM.SelectedOptions.Add(_selectedOption);
}
}
private ObservableCollection<OptionVM> _options = new ObservableCollection<OptionVM>();
public IEnumerable<OptionVM> Options
{
get { return _preferencesVM.AllOptions.Where(x=>Filter(x)); }
}
private bool Filter(OptionVM optVM)
{
if(optVM==_selectedOption)
return true;
if(_preferencesVM.SelectedOptions.Contains(optVM))
return false;
return true;
}
public event PropertyChangedEventHandler PropertyChanged;
}
OptionVM.cs:
public class OptionVM
{
private string _name;
public string Name
{
get { return _name; }
}
public OptionVM(string name)
{
_name = name;
}
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new PreferencesVM();
}
}
MainWindow.xaml:
<Window x:Class="WpfApplication64.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ItemsControl ItemsSource="{Binding Path=Preferences}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=Options}" DisplayMemberPath="Name" SelectedItem="{Binding Path=SelectedOption}"></ComboBox>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>
**Note that to reduce lines of code, my provided solution only generates 3 ComboBoxes (not 10).