Combobox with Checkboxes - c#

I am trying to make a combobox that shows check boxes, and selecting each checkbox will update the text of the combobox to display everything that has been checked. For some strange reason it works only on the first item in the checkbox and it has completely baffled me as to why. I have a dummy project that is pretty small that demonstrates it...
public partial class MainWindow : Window
{
public ObservableCollection<DataObject> Collection { get; set; }
#region Private Methods
public MainWindow()
{
InitializeComponent();
Collection = new ObservableCollection<DataObject>();
Collection.Add(new DataObject { Name = "item1" });
Collection.Add(new DataObject { Name = "item2" });
Collection.Add(new DataObject { Name = "item3" });
Collection.Add(new DataObject { Name = "item4" });
Collection.Add(new DataObject { Name = "item5" });
this.DataContext = Collection;
}
#endregion
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBox chk = sender as CheckBox;
DataObject data = chk.DataContext as DataObject;
if ((bool)chk.IsChecked)
data.CboItems.Add(data.Name);
else if (data.CboItems.Contains(data.Name))
data.CboItems.Remove(data.Name);
}
}
public class DataObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; set; }
private string cbotext;
public string CBOText {
get
{
return cbotext;
}
set
{
cbotext = value;
FirePropertyChanged("CBOText");
}
}
public ObservableCollection<string> CboItems { get; set; }
private void FirePropertyChanged(string name)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
public DataObject()
{
CboItems = new ObservableCollection<string>();
CboItems.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(CboItems_CollectionChanged);
}
void CboItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
string text = string.Empty;
foreach (string item in CboItems)
{
if (text == string.Empty)
text = item;
else
text += ", " + item;
}
CBOText = text;
}
}
and the Xaml...
<ComboBox Text="{Binding CBOText}" Width="150" Height="30" ItemsSource="{Binding}" x:Name="cbo" HorizontalContentAlignment="Stretch" IsEditable="True" Margin="12,12,342,270">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I can see the event CBOText string getting set correctly and firing PropertyChanged, but the combobox does not reflect it unless it's the first item. Quite weird, any ideas?

Your bindings are setup incorrectly. Try something like this:
public partial class MainWindow : Window, INotifyPropertyChanged
{
public ObservableCollection<DataObject> Collection { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
#region Private Methods
public MainWindow()
{
InitializeComponent();
Collection = new ObservableCollection<DataObject>();
Collection.Add(new DataObject { Name = "item1" });
Collection.Add(new DataObject { Name = "item2" });
Collection.Add(new DataObject { Name = "item3" });
Collection.Add(new DataObject { Name = "item4" });
Collection.Add(new DataObject { Name = "item5" });
this.DataContext = this;
}
#endregion
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
CheckBox chk = sender as CheckBox;
DataObject data = chk.DataContext as DataObject;
string combinedText = string.Empty;
foreach (var item in this.Collection)
{
if (item.IsChecked.HasValue && item.IsChecked.Value)
{
if (combinedText == string.Empty)
combinedText = item.Name;
else
combinedText += ", " + item.Name;
}
}
CboText = combinedText;
}
private string _cboCombinedText = "" ;
public string CboText
{
get
{
return this._cboCombinedText; }
set
{
this._cboCombinedText = value;
PropertyChanged(this, new PropertyChangedEventArgs("CboText"));
}
}
public class DataObject
{
private bool? _isChecked = false;
public string Name { get; set; }
public bool? IsChecked { get { return _isChecked; } set { _isChecked = value; } }
}
}
And xaml:
<ComboBox Text="{Binding CboText}" Width="150" Height="30" ItemsSource="{Binding Path=Collection}" x:Name="cbo" HorizontalContentAlignment="Stretch" IsEditable="True" Margin="12,12,342,270">
<ComboBox.ItemTemplate >
<DataTemplate >
<CheckBox Content="{Binding Name}" Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" IsChecked="{Binding IsChecked}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>

Related

Wpf two way binding in listbox

trying to do two way binding in the listbox unfortunately does not work.
Here's the xaml code:
<ListBox Margin="19,0,21,149" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="{Binding Path=Age}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
here's the viewmodel and person code:
public class ViewModel
{
public ObservableCollection<Person> Items
{
get
{
return new ObservableCollection<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
}
}
}
public class Person : INotifyPropertyChanged
{
public string _name;
public int Age { get; set; }
public string Name
{
get
{
return _name;
}
set
{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
}
private void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
and MainWindow:
public MainWindow()
{
InitializeComponent();
model = new ViewModel();
this.DataContext = model;
}
I do not know what is wrong, trying to bind the property "Name", but it does not work. Please, let me know what may be wrong.
It was enough to change:
public class ViewModel
{
ObservableCollection<Person> _items = new ObservableCollection<Person>
{
new Person { Name = "P1", Age = 1 },
new Person { Name = "P2", Age = 2 }
};
public ObservableCollection<Person> Items
{
get
{
return _items;
}
}
}

ComboBox SelectedItem not getting set when using custom ItemTemplate

I have a ComboBox with custom ItemTemplate, it has a CheckBox which is binding to a ViewModel of that page.
What I am trying to do is to set ComboBox item selected to the first checked value whenever ComboBox is closed, but the value is not getting set accordingly.
Is there anything that I am missing?
Xaml:
<ComboBox x:Name="myComboBox" ItemsSource="{Binding ComboBoxList}" SelectedItem="{Binding SelectedPerson, Mode=TwoWay}" HorizontalAlignment="Center" VerticalAlignment="Center" Height="50" Width="150">
<ComboBox.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsChecked, Mode=TwoWay}" Content="{Binding}" Margin="0" />
</DataTemplate>
</ComboBox.ItemTemplate>
<interactivity:Interaction.Behaviors>
<core:DataTriggerBehavior Binding="{Binding IsDropDownOpen, ElementName=myComboBox}" ComparisonCondition="NotEqual" Value="True">
<core:InvokeCommandAction Command="{Binding DropDownClosedCommand}"/>
</core:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ComboBox>
ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private string header;
public string Header
{
get { return header; }
set
{
header = value;
RaisePropertyChange(nameof(Header));
}
}
private Person selectedPerson;
public Person SelectedPerson
{
get { return selectedPerson; }
set { selectedPerson = value; RaisePropertyChange(nameof(SelectedPerson)); }
}
private ObservableCollection<Person> comboBoxList;
public ObservableCollection<Person> ComboBoxList
{
get { return comboBoxList; }
set { comboBoxList = value; }
}
public DelegateCommand DropDownClosedCommand { get; set; }
public MainViewModel()
{
Header = "My Header";
ComboBoxList = new ObservableCollection<Person> {
new Person() { Name = "Person 1", IsChecked = false },
new Person() { Name = "Person 2", IsChecked = false },
new Person() { Name = "Person 3", IsChecked = false },
new Person() { Name = "Person 4", IsChecked = false }
};
DropDownClosedCommand = new DelegateCommand(OnDropDownClosed);
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChange(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void OnDropDownClosed(object e)
{
SelectedPerson = ComboBoxList.FirstOrDefault(x => x.IsChecked);
}
}
public class Person
{
public string Name { get; set; }
public bool IsChecked { get; set; }
public override string ToString()
{
return Name;
}
}
Finally, I figured out the issue that was preventing ComboBox to set SelectedItem. It was because SelectedPerson was not getting set on UI thread, so I wrap that code in dispatcher and it's working as expected.
Code:
CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() =>
{
SelectedPerson = ComboBoxList.FirstOrDefault(x => x.IsChecked);
});

Implement Visibility Property to a custom class

I've created a class that I need to have Visibility property like other UI controls. It looks like this:
More extended code:
xaml:
<ListBox x:Name="itemsHolder" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Surname}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Code behind:
public ObservableCollection<MyClass > myVM= new ObservableCollection<MyClass>();
public class MyClass : Control //FrameworkElement
{
public string Name { get; set; }
public string Surname { get; set; }
}
...
MyClass my1 = new MyClass();
my1.Name = "Test";
my1.Surname = "Test2";
myVM.Add(my1);
itemsHolder.ItemsSource = myVm;
...
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
foreach(MyClass item in itemsHolder.Items)
{
if(!item.Name.Contains((sender as TextBox).Text))
{
item.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
else
{
item.Visibility = Windows.UI.Xaml.Visibility.Visible;
}
}
}
What I try to do is something like a filter(search) and I want to hide items. Just adding a property to the class also does not work.
You are very close... to get this working your class MyClass must implement INotifyPropertyChanged, I use the base class bindable which makes implementing INotifyPropertyChanged much simpler.
Below is the answer
xaml:
<ListBox Grid.Row="1" x:Name="itemsHolder" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Visibility="{Binding IsVisible}">
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Surname}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
code:
public ObservableCollection<MyClass > myVM= new ObservableCollection<MyClass>();
public class Bindable:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class MyClass : Bindable {
private string _Name;
public string Name {
get { return _Name; }
set
{
if (_Name != value)
{
_Name = value;
OnPropertyChanged();
}
}
}
private string _Surname;
public string Surname
{
get { return _Surname; }
set{
if (_Surname != value)
{
_Surname = value;
OnPropertyChanged();
}
}
}
public Visibility _IsVisible;
public Visibility IsVisible {
get { return _IsVisible; }
set {
if (_IsVisible != value)
{
_IsVisible = value;
OnPropertyChanged();
}
}
}
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
foreach(MyClass item in itemsHolder.Items)
{
if(!item.Name.Contains((sender as TextBox).Text))
{
item.Visibility = Windows.UI.Xaml.Visibility.Collapsed;
}
else
{
item.Visibility = Windows.UI.Xaml.Visibility.Visible;
}
}
}

WPF MVVM Populate combobox OnPropertyChanged of another combobox

I want to populate my combobox2 after combobox1 selection changed event.
Here's some part of my XAML:
<ComboBox Name="cmbWorkcode"
ItemsSource="{Binding Workcodes}"
DisplayMemberPath="WorkcodeName"
SelectedValuePath="WorkcodeID"
SelectedValue="{Binding Path=WorkcodeId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
<ComboBox Name="cmbProcess"
ItemsSource="{Binding Processes}"
DisplayMemberPath="ProcessName" SelectedValuePath="ProcessId"
SelectedValue="{Binding Path=ProcessId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
Some part of my ViewModel:
class MainWindowViewModel : ObservableObject
{
private ObservableCollection<Workcode> _workcodes = new ObservableCollection<Workcode>();
public ObservableCollection<Workcode> Workcodes
{
get { return _workcodes; }
set
{
_workcodes = value;
OnPropertyChanged("Workcodes");
}
}
private int _workcodeId;
public int WorkcodeId
{
get { return _workcodeId; }
set
{
_workcodeId = value;
OnPropertyChanged("WorkcodeId");
}
}
private ObservableCollection<Process> _processes = new ObservableCollection<Process>();
public ObservableCollection<Process> Processes
{
get { return _processes; }
set
{
_processes = value;
OnPropertyChanged("Processes");
}
}
private int _processId;
public int ProcessId
{
get { return _processId; }
set
{
_processId = value;
OnPropertyChanged("ProcessId");
}
}
public MainWindowViewModel()
{
PopulateWorkcode();
}
private void PopulateWorkcode()
{
using (var db = new DBAccess())
{
db.ConnectionString = ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
db.Query = #"SELECT workcodeId, workcode FROM workcode";
DataTable data = db.GetData();
if (data != null)
{
foreach (DataRow row in data.Rows)
{
int workcodeId = Convert.ToInt32(row["workcodeId"].ToString());
string workcodeName = row["workcode"].ToString();
_workcodes.Add(new Workcode(workcodeId, workcodeName));
}
}
}
}
private void PopulateProcess()
{
using (var db = new DBAccess())
{
db.ConnectionString = ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
db.Query = #"SELECT ProcessId, ProcessName FROM `process` WHERE WorkcodeId = #workcodeId";
DataTable data = db.GetData(new[] {new MySqlParameter("#workcodeId", _workcodeId.ToString())});
if (data != null)
{
foreach (DataRow row in data.Rows)
{
int id = Convert.ToInt32(row["ProcessId"].ToString());
string name = row["ProcessName"].ToString();
_processes.Add(new Process(id, name));
}
}
}
}
}
My problem is I don't know where do I trigger my PopulateProcess() method so that my combobox2 will be populated base on the selection of combobox1. Thanks for all the time and help! :)
--EDIT--
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Workcode
{
public int WorkcodeId { get; set; }
public string WorkcodeName { get; set; }
public Workcode(int id, string name)
{
WorkcodeId = id;
WorkcodeName = name;
}
}
initially the second combobox is empty and on select of the first combobox changed just pupulate the process
private int _workcodeId;
public int WorkcodeId
{
get { return _workcodeId; }
set
{
_workcodeId = value;
OnPropertyChanged("WorkcodeId");
if(WorkcodeID>0) PopulateProcess();
}
}
I can understand you want to have the next combobox to fill with data based on the previous value. Since i don't have classes of your type, i will give a simple example,
class ItemListViewModel<T> : INotifyPropertyChanged where T : class
{
private T _item;
private ObservableCollection<T> _items;
public ItemListViewModel()
{
_items = new ObservableCollection<T>();
_item = null;
}
public void SetItems(IEnumerable<T> items)
{
Items = new ObservableCollection<T>(items);
SelectedItem = null;
}
public ObservableCollection<T> Items
{
get { return _items; }
private set
{
_items = value;
RaisePropertyChanged("Items");
}
}
public T SelectedItem
{
get { return _item; }
set
{
_item = value;
RaisePropertyChanged("SelectedItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Then have the main viewmodel that will be bound to the DataContext of the view. Have the Load methods do what you want
class MyViewModel : INotifyPropertyChanged
{
public MyViewModel()
{
First = new ItemListViewModel<string>();
Second = new ItemListViewModel<string>();
Third = new ItemListViewModel<string>();
First.PropertyChanged += (s, e) => Update(e.PropertyName, First, Second, LoadSecond);
Second.PropertyChanged += (s, e) => Update(e.PropertyName, Second, Third, LoadThird);
LoadFirst();
}
public ItemListViewModel<string> First { get; set; }
public ItemListViewModel<string> Second { get; set; }
public ItemListViewModel<string> Third { get; set; }
private void LoadFirst()
{
First.SetItems(new List<string> { "One", "Two", "Three" });
}
private void LoadSecond()
{
Second.SetItems(new List<string> { "First", "Second", "Third" });
}
private void LoadThird()
{
Third.SetItems(new List<string> { "Firsty", "Secondly", "Thirdly" });
}
private void Update<T0, T1>(string propertyName, ItemListViewModel<T0> parent, ItemListViewModel<T1> child, Action loadAction)
where T0 : class
where T1 : class
{
if (propertyName == "SelectedItem")
{
if (parent.SelectedItem == null)
{
child.SetItems(Enumerable.Empty<T1>());
}
else
{
loadAction();
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
In XAML,
<ComboBox ItemsSource="{Binding First.Items}" SelectedItem="{Binding First.SelectedItem}" />
<ComboBox ItemsSource="{Binding Second.Items}" SelectedItem="{Binding Second.SelectedItem}" />
<ComboBox ItemsSource="{Binding Third.Items}" SelectedItem="{Binding Third.SelectedItem}" />
The issue is here
<ComboBox Name="cmbWorkcode"
ItemsSource="{Binding Workcodes}"
DisplayMemberPath="WorkcodeName"
SelectedValuePath="WorkcodeId"
SelectedValue="{Binding Path=WorkcodeId, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
It should be WorkcodeId instead of WorkcodeID. rest you can try as Nishanth replied
public int WorkcodeId
{
get { return _workcodeId; }
set
{
if(_workcodeId !=value)
{
_workcodeId = value;
OnPropertyChanged("WorkcodeId");
PopulateProcess();
}
}
}

Why INotifyPropertyChanged's event PropertyChangedEventHandler is always null?

This is my sample code, why PropertyChangedEventHandler property is null?
the list is bounded to Listbox which should subscribe to the event. Shouldn't it?
public class Data<T> : INotifyPropertyChanged where T : class
{
T _data;
public T MyData
{
get { return _data; }
set
{
_data = value;
this.OnPropertyChanged("MyData");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string property)
{
var h = this.PropertyChanged;
if (h != null)
{
h(this, new PropertyChangedEventArgs(property));
}
}
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
public Window1()
{
InitializeComponent();
List<Data<Person>> list = new List<Data<Person>>();
list.Add(new Data<Person> { MyData = new Person { Name = "Sam", Age = 21 } });
list.Add(new Data<Person> { MyData = new Person { Name = "Tom", Age = 33 } });
this.DataContext = list;
}
<Grid>
<ListBox Name="listbox1"
ItemsSource="{Binding}"
Style="{DynamicResource lStyle}" >
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" >
<Label Width="100" Content="{Binding Path=MyData.Name}"></Label>
<Label Width="100" Content="{Binding Path=MyData.Age}"></Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
Probably because the ListBox hasn't had a chance to render and start listening for change events yet.
When I tested the following PropertyChanged was not null during the change inside listbox1_MouseLeftButtonDown.
Data<Person> p;
public MainWindow()
{
InitializeComponent();
List<Data<Person>> list = new List<Data<Person>>();
this.p = new Data<Person> { MyData = new Person { Name = "Sam", Age = 21 } };
list.Add(this.p);
this.DataContext = list;
}
private void listbox1_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
p.DataContext = null;
}
In your provided code, the PropertyChanged event handler will be subscribed to. Why do you think that is not the case?
Changes like this will update the listbox correctly:
List<Data<Person>> list = DataContext as List<Data<Person>>;
list[0].MyData = new Person() { Name = "Bob", Age = 12 };
Remember that your Data class don't support property notify on individual properties,
so the following changes will not update the listbox.
List<Data<Person>> list = DataContext as List<Data<Person>>;
list[0].MyData.Name = "Bob";
list[0].MyData.Age = 12;

Categories