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}" />
Related
I have a ListBox containing Name. Now I need to select multiple items from ListBox.
ViewModel.CS
private Person selectedListOfPeople_;
public Person SelectedListOfPeople
{
get
{ return selectedListOfPeople_;}
set
{ this.SetProperty(ref selectedListOfPeople_, value, nameof(SelectedListOfPeople));}
}
private ObservableCollection<Person> listOfPeople_;
public ObservableCollection<Person> ListOfPeople
{
get { return listOfPeople_; }
set
{
this.SetProperty(ref listOfPeople_, value, nameof(ListOfPeople));
}
}
public ShellViewModel()
{
ListOfPeople = new ObservableCollection<Person>
{
new Person("ABC"),new Person("DEF"),new Person("GHI"),new Person("JKL")
};
}
public class Person : Screen
{
private string personName_;
public string PersonName
{
get { return personName_; }
set { this.SetProperty(ref personName_, value, nameof(PersonName)); }
}
public Person(string personName)
{
PersonName = personName;
}
private bool isSelected_;
public bool IsSelected
{
get { return isSelected_; }
set { this.SetProperty(ref isSelected_, value, nameof(IsSelected)); }
}
}
View.XAML
<Grid Width="500" Height="500" Background="LightBlue">
<ListBox x:Name="ListOfPeople" SelectionMode="Multiple" Height="300" Width="300" Margin="120,100,80,100">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
in that SelectedListOfPeople is not called when the second item is selected in ListBox set to Multiple selections. How can I make sure that this event is raised every time the user makes a selection in ListBox?
One way of doing this would be to break from the convention available in that framework and bind the property manually.
But first you would need to update the property for multiselect in the view model
private ObservableCollection<Person> selectedListOfPeople;
public ObservableCollection<Person> SelectedListOfPeople {
get { return selectedListOfPeople; }
set { this.SetProperty(ref selectedListOfPeople, value, nameof(SelectedListOfPeople)); }
}
private ObservableCollection<Person> listOfPeople;
public ObservableCollection<Person> ListOfPeople {
get { return listOfPeople; }
set { this.SetProperty(ref listOfPeople, value, nameof(ListOfPeople)); }
}
public ShellViewModel() {
ListOfPeople = new ObservableCollection<Person> {
new Person("ABC"),
new Person("DEF"),
new Person("GHI"),
new Person("JKL")
};
SelectedListOfPeople = new ObservableCollection<Person>();
}
And then bind to the desired property in the view's XAML
<ListBox x:Name="ListOfPeople" SelectionMode="Multiple"
Height="300" Width="300" Margin="120,100,80,100"
SelectedItems = "{Bining SelectedListOfPeople}"
>
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding PersonName}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
The convention will bind the items source or the ListBox and the manual binding of the SelectedItems will provided the desired behavior.
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>
I have a simple class as such:
public class Item
{
public int ID{get;set;}
public string Name{get;set;}
}
I have a List of this class in my Mainwindow.xaml.cs as such:
public List<Item> AllItems=GetAllItems();
I have four properties of Item class in my Mainwindow.xaml.cs as such:
public Item Item1{get;set;}
public Item Item2{get;set;}
public Item Item3{get;set;}
public Item Item4{get;set;}
This List:AllItems is databinded to four Comboboxes as under:
<ComboBox x:Name="cmbCode1" ItemsSource="{Binding AllItems}" DisplayMemberPath="ID" SelectedValuePath="ID" SelectedValue="{Binding Item1.ID,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Item1,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<ComboBox x:Name="cmbCode2" ItemsSource="{Binding AllItems}" DisplayMemberPath="ID" SelectedValuePath="ID" SelectedValue="{Binding Item1.ID,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Item1,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<ComboBox x:Name="cmbCode3" ItemsSource="{Binding AllItems}" DisplayMemberPath="ID" SelectedValuePath="ID" SelectedValue="{Binding Item1.ID,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Item1,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
<ComboBox x:Name="cmbCode4" ItemsSource="{Binding AllItems}" DisplayMemberPath="ID" SelectedValuePath="ID" SelectedValue="{Binding Item1.ID,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding Item1,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
I have four TextBoxes corresponding to these four Comboboxes as such:
<TextBlock x:Name="txtName1" Text="{Binding Item1.Name,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock x:Name="txtName2" Text="{Binding Item2.Name,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock x:Name="txtName3" Text="{Binding Item3.Name,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock x:Name="txtName4" Text="{Binding Item4.Name,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"/>
What i want is that the user should never be able to select the same ID from more than one combobox.
Is there some simple way that could be done,especially using xaml only?How can i hide or show the items selected/unselected from other comboboxes so that the user can't select the same ID from more than one combobox ever?
So far i have tried to send the selected Item and the entire List to a MultivalueConverter and eliminating/adding items to the Lists there itself,but this seems too meesy.Any other better idea would be appreciated.
You could have a separate list for each of your ComboBoxes. You could then add a LostFocus event handler to each of them. You could use this to repopulate the lists for the other ComboBoxes to exclude the selection.
For example, if I've got 5 items in my list; initially I'll be able to select all 5 in any of my ComboBoxes. When I select Item1 in ComboBox1 the LostFocus event handler will update the lists behind ComboBoxes 2-4 to remove Item1. When I then select Item2 in ComboBox2 the LostFocus event handler will then update the lists behind ComboBoxes 3 and 4 to remove Item2. And so on...
An alternative approach might be to let the user select whatever they like and then run some kind of validation on the selected values to make sure that they're unique. This article goes through some of your options.
Personally, I'd go with the second approach; perhaps with a message above the textboxes indicating that the selection must be unique. You could indicate any errors and block any actions that rely on the selection while it's invalid but you're not having to update your data constantly which will probably lead to a smoother UI.
You can use the code to hide the selected item in different combobox
for (int count = 0; count <= cmb1.Items.Count -1; count++)
{
if((ComboBoxItem)(cmb1.Items[count])).SelectedValue==TextBox1.Text)
((ComboBoxItem)(cmb1.Items[count])).Visibility = System.Windows.Visibility.Collapsed;
}
This code you can write in selected event of the comboxes.
I guess you can write the same logic using triggers in XAML
You should handle this kind of logic in your view models. Here is an example for you that should give you the idea:
View Model:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
AllItems = new List<Item>() { new Item { Name = "1" }, new Item { Name = "2" }, new Item { Name = "3" }, new Item { Name = "4" } };
}
public List<Item> AllItems { get; set; }
private Item _item1;
public Item Item1
{
get { return _item1; }
set
{
_item1 = value;
NotifyPropertyChanged();
if (value != null)
value.CanSelect = false;
}
}
private Item _item2;
public Item Item2
{
get { return _item2; }
set
{
_item2 = value;
NotifyPropertyChanged();
if (value != null)
value.CanSelect = false;
}
}
private Item _item3;
public Item Item3
{
get { return _item3; }
set
{
_item3 = value; NotifyPropertyChanged();
if (value != null)
value.CanSelect = false;
}
}
private Item _item4;
public Item Item4
{
get { return _item4; }
set
{
_item4 = value;
NotifyPropertyChanged();
if (value != null)
value.CanSelect = false;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Item : INotifyPropertyChanged
{
public int ID { get; set; }
public string Name { get; set; }
private bool _canSelect = true;
public bool CanSelect
{
get { return _canSelect; }
set { _canSelect = value; NotifyPropertyChanged(); }
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
XAML:
<Style x:Key="icstyle" TargetType="ComboBoxItem">
<Setter Property="IsEnabled" Value="{Binding CanSelect}" />
</Style>
...
<ComboBox x:Name="cmbCode1" ItemsSource="{Binding AllItems}" SelectedItem="{Binding Item1}" DisplayMemberPath="Name" ItemContainerStyle="{StaticResource icstyle}"/>
<ComboBox x:Name="cmbCode2" ItemsSource="{Binding AllItems}" SelectedItem="{Binding Item2}" DisplayMemberPath="Name" ItemContainerStyle="{StaticResource icstyle}"/>
<ComboBox x:Name="cmbCode3" ItemsSource="{Binding AllItems}" SelectedItem="{Binding Item3}" DisplayMemberPath="Name" ItemContainerStyle="{StaticResource icstyle}"/>
<ComboBox x:Name="cmbCode4" ItemsSource="{Binding AllItems}" SelectedItem="{Binding Item4}" DisplayMemberPath="Name" ItemContainerStyle="{StaticResource icstyle}"/>
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 have a ComboBox which is has an ItemTemplate applied on it and is bind to a List of entity return using linq. I'm using mvvm. It is bind to it successfully but when I set the selected value of it from code at runtime to show the selected value coming from db it doesn't select it. For reference here is my ComboBox xaml.
<DataTemplate x:Key="ManufacturerDataTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Image x:Name="imgManufacturer" Width="25" Height="25"
Source="{Binding Path=ManufacturerImage}" Grid.Column="0"/>
<TextBlock x:Name="txtManufacturer" Grid.Column="1" HorizontalAlignment="Left"
VerticalAlignment="Center" Text="{Binding Path=ManufacturerName}"
Tag="{Binding Path=ManufacturerID}"/>
</Grid>
</DataTemplate>
<ComboBox x:Name="cboManufacturer"
SelectionChanged="cboManufacturer_SelectionChanged"
ItemsSource = "{Binding Path=CurrentManufacturers}"
SelectedValue="{Binding Path=SelectedManufacturer}"
Grid.Column="3" Grid.Row="2" Margin="20,9.25,68,7.75"
ItemTemplate="{StaticResource ManufacturerDataTemplate}" TabIndex="6"/>
Here is my part from code behind from viewModel.
List<tblManufacturer> currentManufacturers
= new List<tblManufacturer>();
tblManufacturer selectedManufacturer = null;
public List<tblManufacturer> CurrentManufacturers
{
get
{
return currentManufacturers;
}
set
{
currentManufacturers = value;
NotifyPropertyChanged("CurrentManufacturers");
}
}
public tblManufacturer SelectedManufacturer
{
get
{
return selectedManufacturer;
}
set
{
selectedManufacturer = currentManufacturers.Where(mm => mm.ManufacturerID == Convert.ToInt32(selectedDevice.tblManufacturer.EntityKey.EntityKeyValues[0].Value)).First();
NotifyPropertyChanged("SelectedManufacturer");
}
}
Here is the sample code snippet:
Xaml for ComboBox:
<ComboBox ItemsSource="{Binding ManufacturerList}" DisplayMemberPath="Name" SelectedValuePath="ID"
SelectedItem="{Binding SelectedManufacturer}"/>
ViewModel code :
public class Manufacturer
{
public int ID { get; set; }
public string Name { get; set; }
}
private List<Manufacturer> _manufactuerlist;
private Manufacturer _selectedManufacturer;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
public Manufacturer SelectedManufacturer
{
get
{
return _selectedManufacturer;
}
set
{
_selectedManufacturer = value;
NotifyPropertyChanged("SelectedManufacturer");
}
}
public List<Manufacturer> ManufacturerList
{
get
{
return _manufactuerlist;
}
set
{
_manufactuerlist = value;
NotifyPropertyChanged("ManufacturerList");
}
}
And finally Set the Selected Manufacturer in your view model like this:
SelectedManufacturer = _manufactuerlist.Find(m => m.ID == 2);