I want to remove an item from an ObservableCollection while adhering to MVVM. I understand the task, I think I understand the logic pretty well and have implemented it, but the item is never removed in the view.
I have traced the application with breakpoints and the value of selectedProject is being read correctly. I also added variables to check the Collection size before and after the remove statement, which were the same value so it therefore does not remove the item. My question is why? What have I missed? What rules have I not adhered to? Pretty new to .NET.
**I am using a WCF Service, to return an ObservableCollection of Projects from my CodeFirst DB and this is called as soon as a user opens the Projects view.
View
<ListBox ItemsSource="{Binding ProjectList, UpdateSourceTrigger=PropertyChanged}" SelectedItem="{Binding SelectedProject}" SelectedIndex="{Binding ProjectIndex}" BorderThickness="0" Margin="60,195,218.8,212.4">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProjectName}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding DeleteCommand}" Content="Up" HorizontalAlignment="Left" Margin="563,195,0,0" VerticalAlignment="Top" Height="35" Width="75"/>
ViewModel
private ObservableCollection<Project> _projectList;
public ObservableCollection<Project> ProjectList
{
get
{
var q = client.ReturnProjects().ToList();
_projectList = new ObservableCollection<Project>(q.ToList());
return _projectList;
}
set
{
_projectList = value;
OnPropertyChanged("ProjectList");
}
public int SelectedProject
{
get { return _selectedProject; }
set
{
_selectedProject = value;
OnPropertyChanged("SelectedProject");
}
}
The method executed by the command is as follows, the command is being hit and the method called.
public void DeleteProject()
{
if (SelectedProject != null)
{
ProjectList.Remove(SelectedProject);
}
}
You need a two-way-binding for the SelectedItem property.
View
<ListBox ItemsSource="{Binding ProjectList}"
SelectedItem="{Binding SelectedProject, Mode=TwoWay}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Command="{Binding DeleteCommand}"
Content="Delete"
HorizontalAlignment="Right"
VerticalAlignment="Bottom" />
ViewModel, Model and ICommand Implementation
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
var q = new[] { new Project() { Name = "A" }, new Project() { Name = "B" }, new Project() { Name = "C" } };
ProjectList = new ObservableCollection<Project>(q);
}
private ObservableCollection<Project> _projectList;
public ObservableCollection<Project> ProjectList
{
get
{
return _projectList;
}
set
{
_projectList = value;
OnPropertyChanged("ProjectList");
}
}
Project _selectedProject;
public Project SelectedProject
{
get { return _selectedProject; }
set
{
_selectedProject = value;
OnPropertyChanged("SelectedProject");
}
}
public ICommand DeleteCommand => new SimpleCommand(DeleteProject);
private void DeleteProject()
{
if (SelectedProject != null)
{
ProjectList.Remove(SelectedProject);
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Project
{
public string Name { get; set; }
}
public class SimpleCommand : ICommand
{
Action _execute;
public SimpleCommand(Action execute)
{
this._execute = execute;
}
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
_execute();
}
}
I think OnPropertyChanged("ProjectList") needs to be called after deleting the item to raise the notification for updating the view .
The ObservableCollection has to interact with the model layer.
Maybe you need this:
https://blogs.msdn.microsoft.com/bethmassi/2009/05/08/using-the-wpf-observablecollection-with-ef-entities/
Related
I've been struggling with this issue for a while now, tried all of the answers I have seen so far and I still get the same error message
Cannot find source: RelativeSource FindAncestor, AncestorType='System.Windows.Controls.Menu', AncestorLevel='1'.
And my button does nothing
What I'm trying to accomplish
I have this main window called Menu which has a content control and a top bar menu. The idea is that when I press a button from the homeView it changes the mainView to the one selected.
I have already tried this solution WPF MVVM navigate views but got this error message as a result. What I can do for the moment is change views using the top bar on the Menu window but I can not make the childView to execute a command from the parentView
Here's what I have
In my menu window
<Window.DataContext>
<viewModel:MenuPrincipalVistaControlador/>
</Window.DataContext>
<TheThingsInsideMyWindow/>
<ContentControl Grid.Row="1" Margin="0" Content="{Binding vistaActual}"/>
where vistaActual is a reference to a property currentView in my main ViewModel
My data templates
<DataTemplate DataType="{x:Type viewModel:CasaVistaControlador}">
<view:MenuInicioVista/>
</DataTemplate>
<DataTempate DataType="{x:Type viewModel:CajaVistaControlador}">
<view:CajaVista/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:AgregarUsuarioVistaControlador}">
<view:VistaAgregarUsuario/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:AjusteVistaControlador}">
<view:AjustesVista/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModel:CitaVistaControlador}">
<view:CitaVista/>
</DataTemplate>
Inside of my HomeView
<Button x:Name="btnCitas" Height="150" Width="150" Margin="250,80,0,0" VerticalAlignment="Top" Style="{StaticResource MaterialDesignRaisedButton}" RenderTransformOrigin="0.496,2.246" materialDesign:ButtonAssist.CornerRadius="10" FontFamily="Bahnschrift" FontSize="20" BorderBrush="{x:Null}" Command="{Binding Path=DataContext.CitaVistaComando , RelativeSource={RelativeSource AncestorType={x:Type Menu}}}" />
Classes
Part of me thinks it might be something on my base classes that's making the bind to not work so going from here will be the classes that make everything work
My MainViewModel (datacontext for menu)
class MenuPrincipalVistaControlador: ObservableObject
{
public CasaVistaControlador CasaVista { get; set; }
public CajaVistaControlador CajaVista { get; set; }
public CitaVistaControlador CitaVista { get; set; }
private object _vistaActual;
public RelayCommand CasaVistaComando { get; set; }
public RelayCommand CajaVistaComando { get; set; }
public RelayCommand CitaVistaComando { get; set; }
public object vistaActual
{
get { return _vistaActual; }
set { _vistaActual = value;
OnPropertyChanged();
}
}
public MenuPrincipalVistaControlador()
{
CasaVista = new CasaVistaControlador();
CajaVista = new CajaVistaControlador();
vistaActual = CasaVista;
CasaVistaComando = new RelayCommand(o =>
{
vistaActual = CasaVista;
});
CajaVistaComando = new RelayCommand(o =>
{
vistaActual = CajaVista;
});
CitaVistaComando = new RelayCommand(o =>
{
vistaActual = CitaVista;
});
}
}
My ObservableObject class
class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
My custom RelayCommand class
class RelayCommand :ICommand
{
private Action<object> _execute;
private Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand (Action<object> execute,Func<object,bool>canExecute=null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute (object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
There are two views, the first view contains a ListView along with 2 buttons for adding and removing a person. View 2 shows the selected view by using a ContentControl, when the Add button is clicked it shows a form with the name, gender, …, and a button to store the information and display the person name in the ListView.
When I try to remove a person from the ListView it works since the button is within the same view/context of the ListView. But when I call the Add method via AddViewModel it does not update the UI.
Below it is available the relevant parts code of the different classes.
View sample
View1.xaml
<Window.Resources>
<vm:ViewModelBase x:Key="ViewModel"/>
<DataTemplate x:Name="addViewTemplate" DataType="{x:Type vm:AddViewModel}">
<views:AddView/>
</DataTemplate>
</Window.Resources>
<Button Command="{Binding change2addViewCommand,
Source={StaticResource ViewModel}}">
<materialDesign:PackIcon Kind="Plus" Width="12"/>
</Button>
<Button Command="{Binding removeFromListCommand, Source={StaticResource ViewModel}}"
CommandParameter="{Binding ElementName=lvNames, Path=SelectedItem}">
<materialDesign:PackIcon Kind="Delete" Width="12"/>
</Button>
<ListView MinHeight="560"
DataContext="{Binding Source={StaticResource ViewModel}}"
ItemsSource="{Binding}"
x:Name="lvNames" >
</ListView>
<ContentControl x:Name="controllerViews" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="4"
Grid.RowSpan="5" Content="{Binding CurrentView, Mode=TwoWay}"/>
ViewModelBase.cs
public class ViewModelBase: ObservableCollection<Person>, INotifyPropertyChanged
{
private ViewModelBase currentView;
public ViewModelBase CurrentView
{
get
{
return currentView;
}
set
{
currentView = value;
OnPropertyChanged("CurrentView");
}
}
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public ViewModelBase()
{
for (int i = 1; i <= 2; i++)
{
Add(new Person()
{
Name = "Person" + i,
Age = i,
Gender = Gender.Female,
Comments = "",
Photo_Path = ""
});
}
change2addViewCommand = new RelayCommand(ExecuteAddCommand, CanExectuteAddCommand);
removeFromListCommand = new RelayCommand(ExecuteRemoveCommand, CanExectuteRemoveCommand);
}
public event PropertyChangedEventHandler PropertyChanged;
public bool CanExectuteAddCommand(object param)
{
return true;
}
public void ExecuteAddCommand(object param)
{
CurrentView = new AddViewModel();
}
public bool CanExectuteRemoveCommand(object param)
{
return true;
}
public void ExecuteRemoveCommand(object param)
{
Person player = param as Person;
Remove(player); //Working
}
protected void AddToListView(Person player)
{
Add(player) // Not Working
}
}
}
AddViewModel.cs
public class AddViewModel: ViewModelBase
{
public RelayCommand addViewCheckCommand { get; set; }
public AddViewModel()
{
addViewCheckCommand = new RelayCommand(ExecuteCheckCommand, CanExecuteCheckCommand);
}
private bool CanExecuteCheckCommand(object param)
{
return true;
}
private void ExecuteCheckCommand(object param)
{
BocciaPlayer player = param as Person;
AddToListView(player); // Inherited method
}
}
I have a collection of Patients which I set in my ComboBox, whenever I ran and test the form, it seems fine, but whenever I update a record or add another one (from another form), the ComboBox doesn't get updated. I can do a remedy to this by using the code behind and IContent interface but I like to reduce the use of code behind as much as possible. Can you pinpoint to me what markup or code that is lacking?
Here is my Grid Markup (I omitted the RowDefinitions and ColumnDefinitions)
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
<businessLogic:PatientMgr x:Key="PatientsViewModel" />
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid" DataContext="{StaticResource ItemsSource}">
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage"
VerticalAlignment="Center"
HorizontalAlignment="Center"
StretchDirection="Both" Stretch="Uniform"
Source="{Binding ElementName=FullNameComboBox, Path=SelectedItem.PictureId, Converter={StaticResource ToImageSourceConverter}, UpdateSourceTrigger=PropertyChanged}"
/>
</Border>
<TextBox x:Name="IdTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.Id, UpdateSourceTrigger=PropertyChanged}" Grid.Row="0" Grid.Column="1" Visibility="Collapsed"/>
<ComboBox x:Name="FullNameComboBox" Grid.Row="10" Grid.Column="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath = "FullName"
SelectedIndex="0"
SelectionChanged="FullNameComboBox_OnSelectionChanged"
/>
<TextBox x:Name="GenderTextBox" Text="{Binding ElementName=FullNameComboBox, Path=SelectedItem.GenderName, UpdateSourceTrigger=PropertyChanged}" Grid.Row="11" Grid.Column="1" IsReadOnly="True" IsReadOnlyCaretVisible="True"/>
</Grid>
Here is my BusinessLogicLayer
public class PatientMgr :INotifyPropertyChanged
{
#region Fields
private readonly PatientDb _db;
private Patient _entity;
private List<Patient> _entityList;
private ObservableCollection<Patient> _comboBoxItemsCollection;
private Patient _selectedItem;
#endregion
#region Properties
public Patient Entity
{
get { return _entity; }
set
{
if (Equals(value, _entity)) return;
_entity = value;
OnPropertyChanged();
}
}
public List<Patient> EntityList
{
get { return _entityList; }
set
{
if (Equals(value, _entityList)) return;
_entityList = value;
OnPropertyChanged();
}
}
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
public Patient SelectedItem
{
get { return _selectedItem; }
set
{
if (Equals(value, _selectedItem)) return;
_selectedItem = value;
OnPropertyChanged();
}
}
#endregion
#region Constructor
public PatientMgr()
{
_db = new PatientDb();
Entity = new Patient();
EntityList = new List<Patient>();
Parameters = new Patient();
ComboBoxItemsCollection = new ObservableCollection<Patient>(_db.RetrieveMany(Entity));
SelectedItem = ComboBoxItemsCollection[0];
}
#endregion
public List<Patient> RetrieveMany(Patient parameters)
{
return _db.RetrieveMany(parameters);
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Because ItemsSource used statically, your ComboBox doesn't get notified when it's source changed. Try using an instance of PatientMgr as your UserControl.DataContext like this:
public partial class YourUserControl: UserControl
{
private PatientMgr PatientsViewModel;
public YourUserControl()
{
InitializeComponent();
PatientsViewModel = new PatientMgr();
DataContext = PatientsViewModel;
}
public void AddComboItem(Patient item)
{
PatientsViewModel.ComboBoxItemsCollection.Add(item);
}
public void UpdateComboItem(Patient item)
{
//your update code
}
After removing static bindings, your XAML should looks like this:
<UserControl.Resources>
<common:ImageSourceConverter x:Key="ToImageSourceConverter" />
</UserControl.Resources>
<Grid x:Name="DetailsGrid"
<Border Grid.Row="1" Grid.RowSpan="8" Grid.Column="0" Grid.ColumnSpan="2" BorderBrush="Turquoise" BorderThickness="2">
<Image x:Name="InsideButtonImage" ...
Since ComboBoxItemsCollection is an ObservableCollection, it notifies UI on add/remove automatically, however you have to write proper update functionality in order to force UI to refresh.
EDIT:
To implement an update method by taking advantage of Editable feature of ComboBox you could follow these steps:
Defined an integer property to track the last valid index (index != -1) of combo-box selected item and bind it to FullNameComboBox.SelectedIndex.
Defined a string property that represents the text value of combo-box selected item and bind it to FullNameComboBox.Text (the binding should trigger on LostFocus so the changes only accepted when user finished typing).
Find and remove ComboBoxItemsCollection.OldItem (find it by last valid index) and insert ComboBoxItemsCollection.ModifiedItem when FullNameComboBox.Text changes. Using remove and insert instead of assigning (OldItem = ModifiedItem;) will force UI to update.
In PatientMgr Code:
public int LastValidIndex
{
get { return _lastIndex; }
set
{
if (value == -1) return;
_lastIndex = value;
OnPropertyChanged();
}
}
public string CurrentFullName
{
get
{
return SelectedItem.FullName;
}
set
{
var currentItem = SelectedItem;
ComboBoxItemsCollection.RemoveAt(LastValidIndex);
currentItem.FullName = value;
ComboBoxItemsCollection.Insert(LastValidIndex, currentItem);
SelectedItem = currentItem;
}
}
In UserControl.Xaml :
<ComboBox x:Name="FullNameComboBox" Grid.Row="1" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection,
UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem}"
SelectedIndex="{Binding LastValidIndex}"
IsTextSearchEnabled="False"
Text="{Binding CurrentFullName, UpdateSourceTrigger=LostFocus}"
DisplayMemberPath = "FullName"
/>
I don’t like this:
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get { return _comboBoxItemsCollection; }
set
{
if (Equals(value, _comboBoxItemsCollection)) return;
_comboBoxItemsCollection = value;
OnPropertyChanged();
}
}
Try this:
OnPropertyChanged(“ComboBoxItemsCollection”);
And, are you sure this equals is resolved right?
if (Equals(value, _comboBoxItemsCollection)) return;
Try to debug it …
Your problem has to do with that IsEditable="True" set on the ComboBox. Your bindings work fine for me.
Here is what i've just tried:
Simple Patient model with its FullName property:
public class Patient : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged = delegate { };
private string _FullName;
public string FullName
{
get { return _FullName; }
set
{
_FullName = value;
PropertyChanged(this, new PropertyChangedEventArgs("FullName"));
}
}
}
This is the XAML:
<Window x:Class="PatientsStack.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:PatientsStack"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:PatientsMgr x:Key="PatientsViewModel"/>
<ObjectDataProvider x:Key="ItemsSource" ObjectInstance="{StaticResource PatientsViewModel}" />
</Window.Resources>
<Grid DataContext="{StaticResource ItemsSource}">
<StackPanel>
<ComboBox x:Name="FullNameComboBox" IsEditable="True"
ItemsSource="{Binding Path=ComboBoxItemsCollection, UpdateSourceTrigger=PropertyChanged}"
TextSearch.TextPath="FullName"
DisplayMemberPath="FullName"
SelectedItem="{Binding SelectedPatient}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
VerticalAlignment="Top"
IsTextSearchEnabled="False"
SelectedIndex="0">
</ComboBox>
<Button Content="Change name" Command="{Binding ChangeNameCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
<Button Content="Add patient" Command="{Binding AddPatientCommand}" CommandParameter="{Binding ElementName=FullNameComboBox, Path=SelectedItem}"/>
</StackPanel>
</Grid>
This two pieces right here did the job:
SelectedItem="{Binding SelectedFilter}"
Text="{Binding SelectedPatient.FullName, Mode=TwoWay}"
Without the Text binding, the updated worked, but you didn't see it unless you click the drop down.
As you see, i have 2 command to change one existing Patient's FullName or to add a new one. Both work as expected.
Here is the ViewModel for this:
public class PatientsMgr : INotifyPropertyChanged
{
private ObservableCollection<Patient> _ComboBoxItemsCollection;
public ObservableCollection<Patient> ComboBoxItemsCollection
{
get
{
return _ComboBoxItemsCollection;
}
set
{
_ComboBoxItemsCollection = value;
PropertyChanged(this, new PropertyChangedEventArgs("ComboBoxItemsCollection"));
}
}
private Patient _SelectedPatient;
public Patient SelectedPatient
{
get { return _SelectedPatient; }
set
{
_SelectedPatient = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedPatient"));
}
}
public ICommand ChangeNameCommand { get; set; }
public ICommand AddPatientCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged = delegate { };
public PatientsMgr()
{
ComboBoxItemsCollection = new ObservableCollection<Patient>();
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient1" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient2" });
ComboBoxItemsCollection.Add(new Patient() { FullName = "Patient3" });
ChangeNameCommand = new RelayCommand<Patient>(ChangePatientName);
AddPatientCommand = new RelayCommand<Patient>(AddPatient);
}
public void ChangePatientName(Patient patient)
{
patient.FullName = "changed at request";
}
public void AddPatient(Patient p)
{
ComboBoxItemsCollection.Add(new Patient() { FullName = "patient added" });
}
}
I am posting my RelayCommand too, but i am sure you have it defined for your actions:
public class RelayCommand<T> : ICommand
{
public Action<T> _TargetExecuteMethod;
public Func<T, bool> _TargetCanExecuteMethod;
public RelayCommand(Action<T> executeMethod)
{
_TargetExecuteMethod = executeMethod;
}
public bool CanExecute(object parameter)
{
if (_TargetExecuteMethod != null)
return true;
return false;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
T tParam = (T)parameter;
if (_TargetExecuteMethod != null)
_TargetExecuteMethod(tParam);
}
}
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.
Binding checkbox in WPF is common issue, but I am still not finding example code which is easy to follow for beginners. I have check box list in WPF to select favorite sports’ name. The number of checkboxes is static in my case. Can anyone show me how to implement ViewModel for this issue?
FavoriteSportsView.xaml:
<StackPanel Height="50" HorizontalAlignment="Left" VerticalAlignment="Top"
Width="150">
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Football"
Content="Football"
Margin="5" />
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Hockey"
Content="Hockey"
Margin="5" />
<CheckBox IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Golf"
Content="Golf"
Margin="5" />
</StackPanel>
FavoriteSportsViewModel.cs
public class FavoriteSportsViewModel.cs {
//Since I am using the same IsChecked in all check box options, I found all check
//boxes gets either checked or unchecked when I just check or uncheck one option.
//How do i resolve this issue? I don't think i need seprate IsChecked for each
//check box option.
private bool _isChecked;
public bool IsChecked{
get {
return _isChecked;
}
set { if (value != _isChecked)
_isChecked = value;
this.OnPropertyChanged("IsChecked");
}
}
//How do i detect parameter in this method?
private ICommand _sportsResponseCommand;
public ICommand SportsResponseCommand
{
get
{
if (_sportsResponseCommand== null)
_sportsResponseCommand= new
RelayCommand(a => DoCollectSelectedGames(), p => true);
return _sportsResponseCommand;
}
set
{
_sportsResponseCommand= value;
}
}
private void DoCollectSelectedGames(){
//Here i push all selected games in an array
}
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
I'm not sure how to do the following in above ViewModel:
1. How do I implement single method to handle all my options?
2. how do I detect each one of the checkboxes to see whether checked or not
3. How do i utlize CommandParameter?
4. How do i implement SportsResponseCommand correctly
Your view model should look something like this:
public class MyViewModel : INotifyPropertyChanged
{
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
//bindable property
private bool _football;
public bool Football
{
get { return _football; }
set
{
if (value != _football)
{
_football = value;
this.OnPropertyChanged("Football");
}
}
}
//... and the same for Golf and Hockey
}
Then you associate your view model with the view by setting the DataContext property (this will most likely be in the Window or UserControl code behind, though there are a lot of ways to achieve this).
Finally, update your bindings so that they look like:
<CheckBox IsChecked="{Binding Football, Mode=TwoWay}"
Content="Football"
Margin="5" />
<CheckBox IsChecked="{Binding Golf, Mode=TwoWay}"
Content="Football"
Margin="5" />
As a final comment, you shouldn't really need to bind the Command property - you can just write whatever code you need to run in the property setter on the view model.
I highly recommend you to read this http://msdn.microsoft.com/en-us/magazine/dd419663.aspx
I describe a solution below I tried to not modify your XAML code but it is not the only way (or the best approach) but contains all necessary elements!
At first step you need your model I call it Model_Sport
public class Model_Sport : INotifyPropertyChanged
{
#region Constructor
public Model_Sport(string name, ICommand command)
{
Name = name;
SportsResponseCommand = command;
}
#endregion
static readonly PropertyChangedEventArgs _NameEventArgs = new PropertyChangedEventArgs("Name");
private string _Name = null;
public string Name
{
get { return _Name; }
set
{
_Name = value;
OnPropertyChanged(_NameEventArgs);
}
}
static readonly PropertyChangedEventArgs _SportsResponseCommandEventArgs = new PropertyChangedEventArgs("SportsResponseCommand");
private ICommand _SportsResponseCommand = null;
public ICommand SportsResponseCommand
{
get { return _SportsResponseCommand; }
set
{
_SportsResponseCommand = value;
OnPropertyChanged(_SportsResponseCommandEventArgs);
}
}
static readonly PropertyChangedEventArgs _IsCheckedEventArgs = new PropertyChangedEventArgs("IsChecked");
private bool _IsChecked = false;
public bool IsChecked
{
get { return _IsChecked; }
set
{
_IsChecked = value;
OnPropertyChanged(_IsCheckedEventArgs);
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs eventArgs)
{
if (PropertyChanged != null)
{
PropertyChanged(this, eventArgs);
}
}
#endregion
}
Now you need a way to delegate your command “SportsResponseCommand”, DelegateCommand object will help you to do that
public class DelegateCommand : ICommand
{
private readonly Action<object> _ExecuteMethod;
private readonly Func< object, bool> _CanExecuteMethod;
#region Constructors
public DelegateCommand(Action<object>executeMethod, Func<object, bool> canExecuteMethod)
{
if (null == executeMethod)
{
throw new ArgumentNullException("executeMethod", "Delegate Command Delegates Cannot Be Null");
}
_ExecuteMethod = executeMethod;
_CanExecuteMethod = canExecuteMethod;
}
public DelegateCommand(Action<object>executeMethod) : this(executeMethod, null) { }
#endregion
#region Methods
public bool CanExecute(object parameter)
{
if (_CanExecuteMethod == null) return true;
return _CanExecuteMethod(parameter);
}
public void Execute(object parameter)
{
if (_ExecuteMethod == null) return;
_ExecuteMethod(parameter);
}
bool ICommand.CanExecute(object parameter)
{
return CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
void ICommand.Execute(object parameter)
{
Execute(parameter);
}
#endregion
}
Now “ViewModel”
public class ViewModel
{
#region property
public Dictionary<string, Model_Sport> Sports { get; set; }
public DelegateCommand SportsResponseCommand { get; set; }
#endregion
public ViewModel()
{
Sports = new Dictionary<string, Model_Sport>();
SportsResponseCommand = new DelegateCommand(p => execute_SportsResponseCommand(p));
buildSports();
}
private void buildSports()
{
Model_Sport football = new Model_Sport("Football", SportsResponseCommand);
Model_Sport golf = new Model_Sport("Golf", SportsResponseCommand);
Model_Sport hockey = new Model_Sport("Hockey", SportsResponseCommand);
football.IsChecked = true; // just for test
Sports.Add(football.Name, football);
Sports.Add(golf.Name, golf);
Sports.Add(hockey.Name, hockey);
}
private void execute_SportsResponseCommand(object p)
{
// TODO :what ever you want
MessageBox.Show(p.ToString());
}
}
Now View
Remember to set datacontext for your Window
public MainWindow()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
Then in XAML
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" >
<CheckBox DataContext="{Binding Path=Sports[Football]}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Football"
Content="Football"
Margin="5" />
<CheckBox DataContext="{Binding Path=Sports[Hockey]}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Hockey"
Content="Hockey"
Margin="5" />
<CheckBox DataContext="{Binding Path=Sports[Golf]}" IsChecked="{Binding IsChecked, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Golf"
Content="Golf"
Margin="5" />
</StackPanel>
If you just want a property in your ViewModel to get updated when the IsChecked changes, replace the Binding for IsChecked to a boolean property in your ViewModel that raises NotifyPropertyChanged on its "set".
Now if you want to perform an action everytime IsChecked changes for one of the 3 CheckBoxes:
First of all, replace your CommandParameter with "{Binding RelativeSource={RelativeSource Mode=Self}}"
In your ViewModel (that should implement INotifyPropertyChanged), create an ICommand (SportsResponseCommand) that takes a CheckBox in parameter.
In the command's method, check for the Content of your CheckBox, and for the "IsChecked" property then do your stuff with them.
If you have further questions let me know.
You can assign a view model by using this
//for the view
partial class MainView:Window
{
InitializeComponent();
this.DataContext=new MainViewModel();
}
//ViewModel Code
public class MainViewModel: INotifyPropertyChanged
{
//INotifyPropertyChanged implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
//bindable property
private bool _football;
public bool Football
{
get { return _football; }
set
{
if (value != _football)
{
_football = value;
this.OnPropertyChanged("Football");
}
}
}
//... and the same for Golf and Hockey
}`
and then you can implement Binding in XAML as
<CheckBox IsChecked="{Binding Football, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Football"
Content="Football"
Margin="5" />
<CheckBox IsChecked="{Binding Golf, Mode=TwoWay}"
Command="{Binding Path=SportsResponseCommand}"
CommandParameter="Football"
Content="Football"
Margin="5" />