Create Event (Checked/Unchecked) for ComboBox inside an ObservableCollection - c#

I'm pretty new to WPF & MVVM, I'm having 3 CheckBox items on the panel, I need that each time when the user click one of them a method will be called. This method should check the status of each one of the 3 CheckBox (which one of them is 'Checked', and which of them is 'Unchecked') and use this information for displaying some text on a TextBlock. For example it will display "A" if all the 3 are Checked, and "B" if all of them are Unchecked.
I got little complicated with this, and hope that you can help me with this.
Here is the XAML code:
<StackPanel>
<ItemsControl Margin="5" ItemsSource="{Binding Path=CheckBoxCollection, Mode=TwoWay}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=CheckBoxValue, Mode=TwoWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<TextBlock>Result Here</TextBlock>
</StackPanel>
Here is the code behind:
public class CheckBoxValue
{
bool _theValue;
public bool theValue
{
get
{
return _theValue;
}
set
{
_theValue = value;
}
}
}
public class myViewModel : ViewModelBase
{
public ObservableCollection<CheckBoxValue> CheckBoxCollection { get; set; }
public myViewModel( Some Parameters.... )
{
CheckBoxCollection = new ObservableCollection<CheckBoxValue>();
CheckBoxCollection.Add(new CheckBoxValue { theValue = false });
CheckBoxCollection.Add(new CheckBoxValue { theValue = false });
CheckBoxCollection.Add(new CheckBoxValue { theValue = false });
base.RaisePropertyChanged( () => CheckBoxCollection );
}
}

I'd go a step further and define a reusable ViewModel for all types of "Selectable" things:
public class Selectable<T>: ViewModelBase //ViewModelBase should Implement NotifyPropertyChanged.
{
private T _model;
public T Model
{ get { return _model; }
set
{
_model = value;
NotifyPropertyChange("Model");
}
}
public Action OnIsSelectedChanged;
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
NotifyPropertyChange("IsSelected");
if (OnIsSelectedChanged != null)
OnIsSelectedChanged();
}
}
}
See how I'm using a Delegate that will be called when IsSelected is changed, so that you can handle the selection change at the ViewModel level:
public class CheckBoxValue: Selectable<string>
{
//.. no need for any special stuff here
}
public class myViewModel : ViewModelBase
{
public ObservableCollection<CheckBoxValue> CheckBoxCollection { get; set; }
public myViewModel( Some Parameters.... )
{
CheckBoxCollection = new ObservableCollection<CheckBoxValue>();
CheckBoxCollection.Add(new CheckBoxValue { Model = "CheckBox1" });
CheckBoxCollection.Add(new CheckBoxValue { Model = "CheckBox2" });
CheckBoxCollection.Add(new CheckBoxValue { Model = "CheckBox3" });
//Setting IsSelected to false is redundant because bools default to false, so I removed that.
//Calling NotifyPropertyChange in the constructor is also redundant because the object is just being constructed, therefore WPF did not even read the initial values from it yet.
//Now, here we handle the Selection change:
foreach (var item in CheckBoxCollection)
item.OnIsSelectedChanged = OnCheckBoxSelectionChanged;
}
private void OnCheckBoxSelectionChanged()
{
//... etc
}
XAML:
<!-- Two way binding to the ItemsSource property is redundant, it doesn't make sense. -->
<ItemsControl ItemsSource="{Binding Path=CheckBoxCollection}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!-- CheckBox.IsChecked BindsToWayByDefault, so it is also redundant
See http://msdn.microsoft.com/en-us/library/system.windows.frameworkpropertymetadata.bindstwowaybydefault(v=vs.110).aspx -->
<!-- Also see how I'm adding content to the CheckBox here -->
<CheckBox Content="{Binding Model}"
IsChecked="{Binding Path=IsSelected}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
}

I probably changed this too much but this may be the simpler way of doing it.
<StackPanel>
<CheckBox IsChecked="{Binding Path=CheckBoxValue1, Mode=TwoWay}" />
<CheckBox IsChecked="{Binding Path=CheckBoxValue2, Mode=TwoWay}" />
<CheckBox IsChecked="{Binding Path=CheckBoxValue3, Mode=TwoWay}" />
<TextBlock>
<TextBlock.Text>
<Binding Path="StringValue" UpdateSourceTrigger="PropertyChanged" />
</TextBlock.Text>
</TextBlock>
</StackPanel>
#region properties
string stringValue;
public StringValue
{
get {return stringValue;}
set
{
stringValue = value;
OnPropertyChanged("StringValue");
}
}
bool checkBoxValue1;
public bool CheckBoxValue1
{
get{ return checkBoxValue1; }
set
{
checkBoxValue1= value;
ChangedValue();
}
}
bool checkBoxValue2;
public bool CheckBoxValue2
{
get{ return checkBoxValue2; }
set
{
checkBoxValue2 = value;
ChangedValue();
}
}
bool checkBoxValue3;
public bool CheckBoxValue3
{
get{ return checkBoxValue3; }
set
{
checkBoxValue3 = value;
ChangedValue();
}
}
#endregion
public class myViewModel : ViewModelBase
{
public myViewModel( Some Parameters.... )
{
checkBoxValue1 = false;
checkBoxValue2 = false;
checkBoxValue3 = false;
StringValue = b;
}
public void ChangedValue()
{
if (checkBoxValue1 && checkBoxValue2 && checkBoxValue3)
{
StringValue = a
}
else
{
StringValue = b
}
}
}
Sorry this is kind of thrown together but hopefully you get the idea

Related

Get checked items from a listbox

I'm just getting used to MVVM and want to do without the code-behind and define everything in the view-models.
the combobox represents several selection options (works). I would like to query the elements that have been checked.
Unfortunately I can't access them. The textbox should display all selected elements as concatenated string.
View-Model
class MainViewModel : BaseViewModel
{
#region Fields
private ObservableCollection<EssayTypeViewModel> _essayTypes;
private EssayTypeViewModel _selectedEssayTypes;
#endregion
public ObservableCollection<EssayTypeViewModel> EssayTypes
{
get => _essayTypes;
set
{
if (_essayTypes == value) return;
_essayTypes = value; OnPropertyChanged("EssayTypes");
}
}
public EssayTypeViewModel SelectedEssayTypes
{
get => _selectedEssayTypes;
set { _selectedEssayTypes = value; OnPropertyChanged("SelectedEssayTypes"); }
}
public MainViewModel()
{
// Load Essay Types
EssayTypeRepository essayTypeRepository = new EssayTypeRepository();
var essayTypes = essayTypeRepository.GetEssayTypes();
var essayTypeViewModels = essayTypes.Select(m => new EssayTypeViewModel()
{
Text = m.Text
});
EssayTypes = new ObservableCollection<EssayTypeViewModel>(essayTypeViewModels);
}
}
XAML
<ListBox x:Name="Listitems" SelectionMode="Multiple" Height="75" Width="200" ItemsSource="{Binding EssayTypes}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Text}" IsChecked="{Binding Checked}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Text="{Binding Path=SelectedEssayTypes}" Grid.Column="0" Width="160" Height="25" Margin="0,140,0,0"/>
You could hook up an event handler to the PropertyChanged event of all EssayTypeViewModel objects in the EssayTypes collection and raise the PropertyChanged event for a read-only property of the MainViewModel that returns all selected elements as concatenated string:
public MainViewModel()
{
// Load Essay Types
EssayTypeRepository essayTypeRepository = new EssayTypeRepository();
var essayTypes = essayTypeRepository.GetEssayTypes();
var essayTypeViewModels = essayTypes.Select(m =>
{
var vm = EssayTypeViewModel()
{
Text = m.Text
};
vm.PropertyChanged += OnPropertyChanged;
return vm;
});
EssayTypes = new ObservableCollection<EssayTypeViewModel>(essayTypeViewModels);
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Checked")
OnPropertyChanged("SelectedItems");
}
public string SelectedItems => string.Join(",", EssayTypes.Where(x => x.Checked).ToArray());
This requires the EssayTypeViewModel class to implement the INotifyPropertyChanged interface (by for example deriving from your BaseViewModel class).
You can apply Mode = Two way on the checkbox binding.
<CheckBox Content="{Binding Text}" IsChecked="{Binding Checked, Mode=TwoWay}"/>
then you can iterate through the essay types collection to check if the item entry was checked.
For ex. Sample code can be:
foreach (var essayTypeInstance in EssayTypes)
{
if(essayTypeInstance.Checked)
{
// this value is selected
}
}
Hope this helps.
mm8 answer works. In the meantime i came up with another approach. Not 100% MVVM compatible but it works and is quite simple.
XAML
<ListBox x:Name="ListItems" SelectionMode="Multiple" Height="75" Width="200" ItemsSource="{Binding CollectionOfItems}" >
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Name}" IsChecked="{Binding Checked, Mode=TwoWay}" Unchecked="GetCheckedElements" Checked="GetCheckedElements" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Text="{Binding SelectedItemsString, UpdateSourceTrigger=PropertyChanged}" Grid.Column="0" Width="160" Height="25" Margin="0,140,0,0"/>
Code Behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
private void GetCheckedElements(object sender, RoutedEventArgs e)
{
(DataContext as MainViewModel)?.FindCheckedItems();
(DataContext as MainViewModel)?.ConcatSelectedElements();
}
}
Model
public class Items
{
public bool Checked { get; set; }
public string Name { get; set; }
}
ItemsViewModel (BaseViewModel only implements INotifyPropertyChanged)
class ItemsViewModel : BaseViewModel
{
private bool _checked;
private string _name;
public bool Checked
{
get => _checked;
set
{
if (value == _checked) return;
_checked = value;
OnPropertyChanged("Checked");
}
}
public string Name
{
get => _name;
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged("Name");
}
}
}
MainViewModel
public class MainViewModel : BaseViewModel
{
private string _selectedItemsString;
private ObservableCollection<Items> _selectedItems;
public ObservableCollection<Items> CollectionOfItems { get; set; }
public ObservableCollection<Items> SelectedItems
{
get => _selectedItems;
set
{
_selectedItems = value;
OnPropertyChanged("SelectedItems");
}
}
public string SelectedItemsString
{
get => _selectedItemsString;
set
{
if (value == _selectedItemsString) return;
_selectedItemsString = value;
OnPropertyChanged("SelectedItemsString");
}
}
public MainViewModel()
{
CollectionOfItems = new ObservableCollection<Items>();
SelectedItems = new ObservableCollection<Items>();
CollectionOfItems.Add(new Items { Checked = false, Name = "Item 1" });
CollectionOfItems.Add(new Items { Checked = false, Name = "Item 2" });
CollectionOfItems.Add(new Items { Checked = false, Name = "Item 3" });
}
public void FindCheckedItems()
{
CollectionOfItems.Where(x => x.Checked).ToList().ForEach(y => SelectedItems.Add(y));
}
public void ConcatSelectedElements()
{
SelectedItemsString = string.Join(", ", CollectionOfItems.Where(x => x.Checked).ToList().Select(x => x.Name)).Trim();
}
}

WPF nested properties from selected item in databound listBox

I've tried solving this myself, looking at several possible solutions here on Stack Overflow, but alas I've been unable to solve this issue.
TL;DR version:
The problem:
A listBox using databinding to show a list of RPG characters, which have a nested property for their attributes. I can't get the attributes to show due to the limitations with nested properties and databindings.
The code below is related to the issue.
I have a listBox that has a databinding that controls what is shown in the list. The databinding uses ObservableCollection for the list of objects that the list contains. All this works fine, but is related to the issue at hand.
The listBox databinding has several nested properties in each element, that I want to display and change in the form, yet I cannot get nested databinding to work correctly.
This is the listBox XAML:
<ListBox x:Name="listCharacters" Margin="2,0" ItemsSource="{Binding}" Grid.Row="1" ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Hidden" SelectionChanged="listCharacters_SelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="{x:Type local:Character}" x:Name="Symchar">
<Grid Width="125" HorizontalAlignment="Left" Background="{x:Null}">
<Grid.RowDefinitions>
<RowDefinition Height="18"/>
<RowDefinition Height="12"/>
<RowDefinition Height="16"/>
</Grid.RowDefinitions>
<Image Panel.ZIndex="5" HorizontalAlignment="Right" VerticalAlignment="Top" Height="16" Width="16" Margin="0,2,0,0" Source="Resources/1454889983_cross.png" MouseUp="DeleteCharacter" />
<TextBlock Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" FontWeight="Bold" FontSize="13.333" Grid.Row="0" TextTrimming="CharacterEllipsis" Padding="0,0,16,0" />
<TextBlock Text="{Binding RealRace.Label, UpdateSourceTrigger=PropertyChanged}" FontSize="9.333" Grid.Row="1" FontStyle="Italic" />
<TextBlock FontSize="9.333" Grid.Row="2" HorizontalAlignment="Left" VerticalAlignment="Top">
<Run Text="{Binding RealClass.Archetype.Label, UpdateSourceTrigger=PropertyChanged}"/>
<Run Text=" - "/>
<Run Text="{Binding RealClass.Label, UpdateSourceTrigger=PropertyChanged}"/>
</TextBlock>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And setting the listBox ItemSource:
this.listCharacters.ItemsSource = CharacterList;
This is the character class, I removed unrelated code (XML serialization attributes etc.)
public class Character : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
this.NotifyPropertyChanged("Name");
}
}
private string _player;
public string Player
{
get { return _player; }
set
{
_player = value;
this.NotifyPropertyChanged("Player");
}
}
private string _race;
public string Race
{
get { return _race; }
set
{
_race = value;
this.NotifyPropertyChanged("Race");
}
}
private Race _realRace;
public Race RealRace
{
get { return _realRace; }
set
{
_realRace = value;
Race = value.Id;
this.NotifyPropertyChanged("RealRace");
}
}
private string _gender;
public string Gender
{
get { return _gender; }
set
{
_gender = value;
this.NotifyPropertyChanged("Gender");
}
}
private Attributes _attributes;
public Attributes Attributes
{
get { return _attributes; }
set
{
_attributes = value;
this.NotifyPropertyChanged("Attributes");
}
}
private string _class;
public string Class
{
get { return _class; }
set
{
_class = value;
this.NotifyPropertyChanged("Class");
}
}
private Class _realClass;
public Class RealClass
{
get { return _realClass; }
set
{
_realClass = value;
Class = value.Id;
this.NotifyPropertyChanged("RealClass");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
To keep it simple, the property that I've been testing with, is the 'Attributes' property, this is the code for it:
public class Attributes : INotifyPropertyChanged
{
private int _accurate;
public int Accurate
{
get { return _accurate; }
set
{
_accurate = value;
this.NotifyPropertyChanged("Accurate");
}
}
private int _cunning;
public int Cunning
{
get { return _cunning; }
set
{
_cunning = value;
this.NotifyPropertyChanged("Cunning");
}
}
private int _discreet;
public int Discreet
{
get { return _discreet; }
set
{
_discreet = value;
this.NotifyPropertyChanged("Discreet");
}
}
private int _persuasive;
public int Persuasive
{
get { return _persuasive; }
set
{
_persuasive = value;
this.NotifyPropertyChanged("Persuasive");
}
}
private int _quick;
public int Quick
{
get { return _quick; }
set
{
_quick = value;
this.NotifyPropertyChanged("Quick");
}
}
private int _resolute;
public int Resolute
{
get { return _resolute; }
set
{
_resolute = value;
this.NotifyPropertyChanged("Resolute");
}
}
private int _strong;
public int Strong
{
get { return _strong; }
set
{
_strong = value;
this.NotifyPropertyChanged("Strong");
}
}
private int _vigilant;
public int Vigilant
{
get { return _vigilant; }
set
{
_vigilant = value;
this.NotifyPropertyChanged("Vigilant");
}
}
private int _toughness;
public int Toughness
{
get { return _toughness; }
set
{
_toughness = value;
this.NotifyPropertyChanged("Toughness");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
I want to display each individual attribute in a field when a character in the listBox is selected, this works fine with properties directly in the character class, but due to the limitations on nested properties and databindings, I haven't been able to get it to work with the 'Attributes' properties values.
XAML for one of the attribute input fields:
<TextBox x:Name="attr_Accurate" DataContext="{Binding Path=(local:Character.Attributes), XPath=SelectedItem, ElementName=listCharacters, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Text="{Binding Path=(local:Accurate)}" PreviewTextInput="NumericInput"/>
The UpdateSourceTrigger is simply a method to only allow integers to be input in the field:
private void NumericInput(object sender, TextCompositionEventArgs e)
{
if (!char.IsDigit(e.Text, e.Text.Length - 1))
{
e.Handled = true;
}
}
If anyone could help me get the values within the selected character's attributes to show up via databindings, I would greatly appreciate it.
Use binding as following:
To Show Accurate Property value:
<TextBox x:Name="attr_Accurate" Text="{Binding Path=SelectedItem.Attributes.Accurate), ElementName=listCharacters, Mode=OneWay}" PreviewTextInput="NumericInput"/>
To Show Cunning property value:
<TextBox x:Name="attr_Accurate" Text="{Binding Path=SelectedItem.Attributes.Cunning), ElementName=listCharacters, Mode=OneWay}" PreviewTextInput="NumericInput"/>
and so one.
(I'm not sure if you want binding to be two way or one so please
change as your need)

C# WPF - ComboBox, Values change but SelectedItem not

I know there are a lot questions about the SelectedItem and the ComboBox out there but it seems that they do not solve my problem.
I have a ComboBox where I bind a ObservableCollection of View Models and a TextBox which binds to the Description of the singe View models. I want to change the entries here... Everything works fine when i use the text box, the items will be changed in the ComboBox (even the selected one), but when i change the text in the code behind only the ComboBox List will be updated but not the current selected Value/Item.
My ComboBox looks like this at the moment:
<ComboBox ItemsSource="{Binding Sports, UpdateSourceTrigger=PropertyChanged}" SelectedValue="{Binding SelectedSport, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" IsSynchronizedWithCurrentItem="True" Width="365" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" Height="30" Margin="5 5 5 5">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Description, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
The used ViewModel looks like this:
public class SportViewModel : ViewModelBase
{
private ObservableCollection<DisciplineViewModel> _disciplinesProperty;
public Sport Model
{
get;
set;
}
public string Description
{
get { return Model.Description; }
set
{
if (Model.Description != value)
{
Model.Description = value;
RaisePropertyChanged(() => Description);
}
}
}
public ObservableCollection<DisciplineViewModel> Disciplines
{
get { return _disciplinesProperty; }
set
{
if (_disciplinesProperty != value)
{
_disciplinesProperty = value;
RaisePropertyChanged(() => Disciplines);
}
}
}
public SportViewModel(Sport source)
{
Model = source;
Disciplines = new ObservableCollection<DisciplineViewModel>();
foreach(Discipline d in Model.Disciplines)
{
Disciplines.Add(new DisciplineViewModel(d, this));
}
}
public override bool Equals(object obj)
{
if (obj == null || !(obj is SportViewModel))
return false;
return ((SportViewModel)obj).Description == this.Description;
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
and is used here:
class EditSportsDialogViewModel : ViewModelBase
{
...
public ObservableCollection<SportViewModel> Sports
{
get { return _sportsProperty; }
set
{
if (_sportsProperty != value)
{
_sportsProperty = value;
}
}
}
public SportViewModel SelectedSport
{
get { return _selectedSportPorperty; }
set
{
if (_selectedSportPorperty != value)
{
if (value != null)
{
Disciplines = value.Disciplines;
}
_selectedSportPorperty = value;
RaisePropertyChanged(() => SelectedSport);
}
}
}
private void SportRevertExecuted()
{
if (SelectedSport != null)
{
_context.Entry(SelectedSport.Model).Reload();
}
RaisePropertyChanged(() => SelectedSport.Description);
RaisePropertyChanged(() => SelectedSport);
RaisePropertyChanged(() => Sports);
RaisePropertyChanged();
}
}
The describtion is also changed by the one TextBox which works just fine
<TextBox Text="{Binding SelectedSport.Description, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" TextAlignment="Center" Margin="5" Width="410"/>
But when I call SportRevertExecuted() only the item in the list of the ComboBox will be changed but not the "Text" of the currently selected Item. Maybe someone can help me here, I've tried a lot until now, nothing seems to help.
Ok problem solved. Was quite stupid RaisePropertyChanged(() => SelectedSport.Description); doesn't work as i thought. I've introduced a Method UpdateView() in SportViewModel which raises the PropertyChanged Event in the right Object, now everything works just fine.

Add additional control to ObervableCollection

I'm really new to WPF so apologies in adavnced if this is an obvious question. I have a simple Checkbox in XAML as
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Selections}" >
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Path=Item.SelectionName}" />
</Grid >
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Simplified code behind to allow bindings and INotifyPropertyChanged is:
public ObservableCollection<CheckedListItem<Selection>> Selections { get; set; }
public class Selection
{
public String SelectionName { get; set; }
}
Selections = new ObservableCollection<CheckedListItem<Selection>>();
Selections.Add(new CheckedListItem<Selection>(new Selection()
{ SelectionName = "SomeName" }, isChecked: true));
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
}
I now need to add an additional TextBox associated with each Checkbox, so in XAML I have
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Selections}" Margin="12,22,12,94">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid >
<CheckBox IsChecked="{Binding IsChecked}"
Content="{Binding Path=Item.SelectionName}" />
<<TextBox />
</Grid >
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I'm a bit stumped how to include this as part of the ObservableCollection and set it up the binding on both the CheckBox and associated TextBox? Both are being added together using Selections.Add(new CheckedListItem<Selection>(new Selection()
{ SelectionName = "SomeName" }, isChecked: true)); which is causing me some confusion.
EDIT: Added full code
public partial class SelectionSettingWindow : Window
{
public ObservableCollection<CheckedListItem<Selection>> Selections { get; set; }
public class Selection
{
public String SelectionName { get; set; }
public string SelectionTextField { get; set; }
}
public SelectionSettingWindow()
{
InitializeComponent();
Selections = new ObservableCollection<CheckedListItem<Selection>>();
string fg = #"Item1,true,TExtbox1text:Item2,true,TExtbox2text:Item3,false,TExtbox3text"; //Test String
string[] splitSelections = fg.Split(':');
foreach (string item in splitSelections)
{
string[] spSelectionSetting = item.Split(',');
bool bchecked = bool.Parse(spSelectionSetting[1].ToString());
string tbText = spSelectionSetting[2].ToString();
Selections.Add(new CheckedListItem<Selection>(new Selection()
{ SelectionName = spSelectionSetting[0].ToString(),
SelectionTextField = bText }, isChecked: bchecked));
}
DataContext = this;
}
public class CheckedListItem<T> : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private bool isChecked;
private T item;
private string textField;
public CheckedListItem()
{ }
public CheckedListItem(T item, bool isChecked = false)
{
this.item = item;
this.isChecked = isChecked;
}
public T Item
{
get { return item; }
set
{
item = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Item"));
}
}
public bool IsChecked
{
get { return isChecked; }
set
{
isChecked = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("IsChecked"));
}
}
public string TextField
{
get { return textField; }
set
{
textField = value;
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("TextField"));
}
}
}
}
<ListBox ScrollViewer.VerticalScrollBarVisibility="Auto"
ItemsSource="{Binding Selections}" Margin="12,22,12,94">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Content="{Binding Path=Item.SelectionName}" />
<TextBox Text="{Binding Item.SelectionTextField, Mode=TwoWay}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
replace SelectionTextField above with whatever the field is that needs to be edited using the textbox on your Selection class.
Note that I changed the <Grid> to a <StackPanel> So they wouldn't appear on top of eachother and changed the bindings to TwoWay so the changes are reflected in the model.
Make sure your Selection class implements INotifyPropertyChanged (ObservableCollection updates the UI when things get added to/removed from the collection, it doesn't know anything about notifying when it's content's properties change so they need to do that on their own)
Implementing INotifyPropertyChanged on many classes can be cumbersome. I find implementing a base class useful for this. I've got this along with an extra reflection helper for raise property changed available here and a snippet I've made available. It's silverlight but it should work fine for WPF. Using the code I've provided via download you can simply type proprpc and hit tab and visual studio will stub in a property that notifies on change. Some explanation is in one of my old blog posts here and gives credit for where I based the code and snippet from.

ObservableCollection not updating View

I am just starting with MVVM and have hit a hurdle that I hope someone can help me with. I am trying to create a simple View with 2 listboxes. A selection from the first listbox will populate the second list box. I have a class created that stores the information I want to bind to.
MyObject Class (Observable Object is just a base class that implements INotifyPopertyChanged)
public class MyObject : ObservableObject
{
String _name = String.Empty;
ObservableCollection<MyObject> _subcategories;
public ObservableCollection<MyObject> SubCategories
{
get { return _subcategories; }
set
{
_subcategories = value;
RaisePropertyChanged("SubCategories");
}
}
public String Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged("Name");
}
}
public MyObject()
{
_subcategories = new ObservableCollection<EMSMenuItem>();
}
}
In my viewmodel I have two ObservableCollections created
public ObservableCollection<EMSMenuItem> Level1MenuItems { get; set; }
public ObservableCollection<EMSMenuItem> Level2MenuItems { get; set; }
In my constructor of the ViewModel I have:
this.Level1MenuItems = new ObservableCollection<EMSMenuItem>();
this.Level2MenuItems = new ObservableCollection<EMSMenuItem>();
this.Level1MenuItems = LoadEMSMenuItems("Sample.Xml");
That works fine for the Level1 items and they correctly show in the View. However I have a command that gets called when the user clicks an item in the listbox, which has the following:
Level2MenuItems = ClickedItem.SubCategories;
For some reason this does not update the UI of the second listbox. If I put a breakpoint at this location I can see that Level2MenuItems has the correct information stored in it. If I write a foreach loop and add them individually to the Level2MenuItems collection then it does display correctly.
Also as a test I added the following to the constructor:
Level2MenuItems = Level1MenuItems[0].SubCategories;
And that updated correctly.
So why would the code work as expected in the constructor, or when looping through, but not when a user clicks on an item in the listbox?
You need to raise the change notification on the Level2MenuItems property.
Instead of having
public ObservableCollection<EMSMenuItem> Level2MenuItems { get; set; }
you need
private ObservableCollection<EMSMenuItem> _level2MenuItems;
public ObservableCollection<EMSMenuItem> Level2MenuItems
{
get { return _level2MenuItems; }
set
{
_level2MenuItems = value;
RaisePropertyChanged(nameof(Level2MenuItems));
}
}
The reason the former works in the constructor is that the Binding has not taken place yet. However since you are changing the reference via a command execute which happens after the binding you need to tell view that it changed
You need to make your poco class within the ObservableCollection implement INotifyPropertyChanged.
Example:
<viewModels:LocationsViewModel x:Key="viewModel" />
.
.
.
<ListView
DataContext="{StaticResource viewModel}"
ItemsSource="{Binding Locations}"
IsItemClickEnabled="True"
ItemClick="GroupSection_ItemClick"
ContinuumNavigationTransitionInfo.ExitElementContainer="True">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" Margin="0,0,10,0" Style="{ThemeResource ListViewItemTextBlockStyle}" />
<TextBlock Text="{Binding Latitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="0,0,5,0"/>
<TextBlock Text="{Binding Longitude, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Style="{ThemeResource ListViewItemTextBlockStyle}" Margin="5,0,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
public class LocationViewModel : BaseViewModel
{
ObservableCollection<Location> _locations = new ObservableCollection<Location>();
public ObservableCollection<Location> Locations
{
get
{
return _locations;
}
set
{
if (_locations != value)
{
_locations = value;
OnNotifyPropertyChanged();
}
}
}
}
public class Location : BaseViewModel
{
int _locationId = 0;
public int LocationId
{
get
{
return _locationId;
}
set
{
if (_locationId != value)
{
_locationId = value;
OnNotifyPropertyChanged();
}
}
}
string _name = null;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
OnNotifyPropertyChanged();
}
}
}
float _latitude = 0;
public float Latitude
{
get
{
return _latitude;
}
set
{
if (_latitude != value)
{
_latitude = value;
OnNotifyPropertyChanged();
}
}
}
float _longitude = 0;
public float Longitude
{
get
{
return _longitude;
}
set
{
if (_longitude != value)
{
_longitude = value;
OnNotifyPropertyChanged();
}
}
}
}
public class BaseViewModel : INotifyPropertyChanged
{
#region Events
public event PropertyChangedEventHandler PropertyChanged;
#endregion
protected void OnNotifyPropertyChanged([CallerMemberName] string memberName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(memberName));
}
}
}
Your Subcategories property should be read-only.

Categories

Resources