Checkbox not showing as checked inside ListView - wpf

I have a checkbox inside a listview. The listview is bound to an observable collection. When I use the context menu to try to select all Checkboxes, they do not show as checked. What am I doing wrong?
<ListView Grid.Row="1" Grid.Column="0" ItemsSource="{Binding AvailableModels}"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
SelectionMode="Extended">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Select All Models" Command="{Binding
SelectAllModelsAction}" />
<MenuItem Header="Deselect All Models" Command="{Binding
DeselectAllModelsAction}" />
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<CheckBox IsChecked="{Binding DataContext.IsSelected,
RelativeSource={RelativeSource
FindAncestor, AncestorType={x:Type
ListViewItem}}}" VerticalAlignment="Center" />
<Label Content="{Binding Name}" Margin="2,0,0,0" />
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
ViewModel
public ObservableCollection<ListItems> AvailableModels
{
get
{
return this.availableModels;
}
set
{
this.availableModels = value;
this.NotifyPropertyChanged(m => m.AvailableModels);
}
}
Context Menu Action
private void SelectAllModels()
{
foreach (var model in this.AvailableModels)
{
model.IsSelected = true;
}
this.NotifyPropertyChanged(m => m.AvailableModels);
}
ListItems Object
public class ListItems
{
public string Name
{
get;
set;
}
public object Value
{
get;
set;
}
public bool IsSelected
{
get;
set;
}
}

ListItems isn't implementing INotifyPropertyChanged so changing IsSelected isn't updating the UI.
Calling NotifyPropertyChanged in SelectAllModels() is unnecessary since the collection itself isn't changed. The NotifyProperyChanged() call in the AvailableModels setter updates the UI when a new collection is set. And ObservableCollection will handle when the collection is modified (items added/removed). However changes to the properties within the ListItems does not update the UI unless they call NotifyPropertyChanged in the setters.

Related

CurrentViewModel Property Change Not Reflected in ContentControl

Hello I have looked everywhere and I can't understand what I am doing wrong.
My command is called and the onpropertychanged event is called but the ContentControl in my MainWindow will not show the next view.
I am new to WPF and Xaml and really trying to figure this out.
I have attached a link to the sample application on my Github.
The End game here is I want the Radio button to be checked when the view is active. I also want to be able to change from the home view to other views via a button on that view once I am past this first hurtle.
Github repository
public class ApplicationViewModel : ObservableObject
{
private ICommand _changeViewCommand;
private IViewModel _CurrentViewModel;
private List<IViewModel> _viewModels;
private bool _isActive;
public ApplicationViewModel()
{
ViewModels.Add(new HomeViewModel());
ViewModels.Add(new AccountViewModel());
CurrentViewModel = ViewModels.Find(r => r.Name == "Home");
CurrentViewModel.IsActive = true;
}
public List<IViewModel> ViewModels
{
get
{
if (_viewModels == null)
_viewModels = new List<IViewModel>();
return _viewModels;
}
}
public IViewModel CurrentViewModel
{
get
{
return _CurrentViewModel;
}
set
{
if (_CurrentViewModel != value)
{
_CurrentViewModel = value;
OnPropertyChanged();
}
}
}
public ICommand ChangePageCommand
{
get
{
if (_changeViewCommand == null)
{
_changeViewCommand = new RelayCommand(
p => ChangeViewModel((IViewModel)p),
p => p is IViewModel);
}
return _changeViewCommand;
}
}
private void ChangeViewModel(IViewModel viewModel)
{
CurrentViewModel = ViewModels.Find(r => r.Name == viewModel.Name);
}
public bool IsActive
{
get
{
return _isActive;
}
set
{
_isActive = value;
OnPropertyChanged("IsActive");
}
}
}
<UserControl.DataContext>
<viewModels:ApplicationViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModels:ApplicationViewModel}"/>
<DataTemplate DataType="{x:Type viewModels:HomeViewModel}">
<views:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:AccountViewModel}">
<views:AccountView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<RadioButton Content="Home" IsChecked="{Binding IsActive, Mode=TwoWay}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding }">
<RadioButton.DataContext>
<viewModels:HomeViewModel/>
</RadioButton.DataContext>
</RadioButton>
<RadioButton Content="Account" IsChecked="{Binding IsActive, Mode=TwoWay}" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding }">
<RadioButton.DataContext>
<viewModels:AccountViewModel/>
</RadioButton.DataContext>
</RadioButton>
</StackPanel>
you should connect RadioButtons to the ViewModels which you have in the list. don't create new view models in xaml.
and CurrentViewModel can be displayed via ContentControl with one of templates decalred in Resources
<UserControl.DataContext>
<viewModels:ApplicationViewModel/>
</UserControl.DataContext>
<UserControl.Resources>
<DataTemplate DataType="{x:Type viewModels:HomeViewModel}">
<views:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type viewModels:AccountViewModel}">
<views:AccountView/>
</DataTemplate>
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding ViewModels}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<RadioButton Content="{Binding Name}"
IsChecked="{Binding IsActive}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
CommandParameter="{Binding }">
</RadioButton>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
after that you should probably change StackPanel to Grid to allow ContentControl to take all available screen space
Looks like if I assign the AccountViewModel to the button DataContext and set the RelativeSource AncestorType to x:Type Window the button on the HomeView will switch the view to the AccountView.
<Button Height="40" Content="Account" Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }">
<Button.DataContext>
<viewModels:AccountViewModel/>
</Button.DataContext>
</Button>

Bind ListViewItem ContextMenu MenuItem Command to ViewModel of the ListView's ItemsSource

I have a ListView of Expanders. Each Expander (representing a database table) will have items under it, in another ListView. I want to Right-Click and have an "Edit" option on the innermost items, which represent records in the corresponding database table.
There is an ICommand named 'Edit' in my MainEditorViewModel. The Datacontext in which this command resides is the same as that of the outermost ListView named "TestGroupsListView"
Here is the XAML markup for the ListView of Expanders. The outermost ListView I've named for referencing in the binding via ElementName for the MenuItem's Binding:
<ListView Name="TestGroupsListView" ItemsSource="{Binding TestGroups}" Grid.Row="1">
<ListView.ItemTemplate>
<DataTemplate>
<Expander Style="{StaticResource MaterialDesignExpander}" >
<Expander.Header>
<Grid MaxHeight="50">
<TextBlock Text="{Binding Name}"/>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Add..." Command="{Binding Add}"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</Expander.Header>
<ListView ItemsSource="{Binding Records}" Style="{StaticResource MaterialDesignListView}" Margin="30 0 0 0">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit"
Command="{Binding ElementName=TestGroupsListView, Path=DataContext.Edit}"
CommandParameter="{Binding }"/>
</ContextMenu>
</Grid.ContextMenu>
<Button Content="{Binding RecordName}" Command="{Binding ElementName=TestGroupsListView, Path=DataContext.Edit}"/>
<!--<TextBlock Text="{Binding RecordName}" AllowDrop="True"/>-->
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I am able to bind a button in the DataTemplate to 'Edit' successfully, but when I attempt to bind the MenuItem's Command to 'Edit', nothing happens. Why might this be that the button command binding works using ElementName but the same binding in the ContextMenu doesn't?
I think it will be better to use the context menu globally for ListView and globally for each Child ListView. Ok, here is my solution:
<ListBox ItemsSource="{Binding Groups}">
<ListBox.ContextMenu>
<ContextMenu>
<MenuItem Header="Add..." Command="{Binding Add}"/>
</ContextMenu>
</ListBox.ContextMenu>
<ListBox.ItemTemplate>
<DataTemplate>
<Expander Header="{Binding Name}">
<ListView ItemsSource="{Binding Records}" SelectedItem="{Binding SelectedRecord}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Header="Edit" Command="{Binding Edit}" IsEnabled="{Binding CanEdit}"/>
</ContextMenu>
</ListView.ContextMenu>
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Expander>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And for better understanding code behind:
public class GroupsVM : ViewModelBase
{
public ICommand Add
{
get => null; //Command implementation
}
public ObservableCollection<GroupVM> Groups { get; set; } = new ObservableCollection<GroupVM>()
{
new GroupVM { Name = "First" },
new GroupVM { Name = "Second" },
new GroupVM { Name = "Third" }
};
}
public class GroupVM : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
public ICommand Edit
{
get => null; //Command implementation
}
public bool CanEdit => SelectedRecord != null;
public ObservableCollection<RecordVM> Records { get; set; } = new ObservableCollection<RecordVM>()
{
new RecordVM { Name="Record1" },
new RecordVM { Name="Record2" },
new RecordVM { Name="Record3" }
};
private RecordVM _selectedRecord = null;
public RecordVM SelectedRecord
{
get => _selectedRecord;
set
{
_selectedRecord = value;
OnPropertyChanged();
OnPropertyChanged("CanEdit");
}
}
}
public class RecordVM : ViewModelBase
{
private string _name;
public string Name
{
get => _name;
set { _name = value; OnPropertyChanged(); }
}
}

Adding an extra level in wpf mvvm data bound menu item

I've a menu item named 'Authors' whose ItemsSource is bound to an ObservableCollection called CollectionOfAuthors. Clicking on Authors opens a list of author names. What modifications I need to do to add an extra level of menu item, In simple words, I wish to display list of books for each author-name.
Like this:
Author
|__AuthorName 1
|__Book 1
|__Book 2
Here's my existing code:
MainWindow.xaml
<MenuItem Header="Authors" ItemsSource="{Binding CollectionOfAuthors}">
<MenuItem.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding AuthorName}" />
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
MainWindowViewModel.cs
private ObservableCollection<Author> _collectionOfAuthors;
public ObservableCollection<Author> CollectionOfAuthors
{
get { return _collectionOfAuthors; }
set { SetProperty(ref _collectionOfAuthors, value); }
}
public class Author
{
public string AuthorName {get; set;}
private ObservableCollection<BookDetails> _Books;
public ObservableCollection<BookDetails> Books
{
get { return _Books; }
set { SetProperty(ref _Books, value); }
}
}
Use HierarchicalDataTemplate for this.
<MenuItem Header="Authors" ItemsSource="{Binding CollectionOfAuthors}">
<MenuItem.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Path=Books}">
<TextBlock Text="{Binding AuthorName}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding BookName}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>

How to bind Dictionary object to checkedlistbox using WPF

I have a generic dictionary collection Dictionary. I need to bind the displaymember path key to the content of the checkbox and checkbox Ischecked property to the value member of the Dictionary
private Dictionary<string, bool> _columnHeaderList;
public Dictionary<string, bool> ColumnHeaderList
{
get { return _columnHeaderList; }
set { _columnHeaderList = value; RaisePropertyChanged("ColumnHeaderList"); }
}
private Dictionary<string, bool> GetColumnList()
{
Dictionary<string, bool> dictColumns = new Dictionary<string, bool>();
Array columns = Enum.GetValues(typeof(ColumnHeaders));
int arrayIndex=0;
for(int i=0;i<columns.Length;i++)
{
dictColumns.Add(columns.GetValue(arrayIndex).ToString(), true);
}
return dictColumns;
}
My XAML looks like
<ListBox Grid.Column="0" Grid.Row="1" Height="200"
ItemsSource="{Binding ColumnHeaderList}"
VerticalAlignment="Top">
<ListBox.ItemTemplate>
<HierarchicalDataTemplate>
<CheckBox Content="{Binding key}" IsChecked="{Binding Path=Value}"></CheckBox>
</HierarchicalDataTemplate>
</ListBox.ItemTemplate>
</ListBox>
You will need to use OneWay binding if you bind to Dictionary because the KeyValuePair has read-only properties.
<CheckBox Content="{Binding Key, Mode=OneWay}" IsChecked="{Binding Path=Value, Mode=OneWay}" Width="100" /></CheckBox>
make sure you have set the DataContext. Note this will not update the dictionary values when the user presses on the checkboxes.
Since Value property is readonly and OneWay binding will not allow you track the changes if user checks or unchecks the checkboxes. It is recommended to bind them with an array new class ListItem:
class ListItem
{
public string Text { get; set; }
public bool IsChecked { get; set; }
}
private ListItem[] GetColumnList()
{
return Enum.GetValues(typeof(ColumnHeaders))
.Select(h => new ListItem{ Text = h.ToString(),IsChecked = true})
.ToArray();
}
Yeah its possible and it should work too, although you need to bind with Value with Binding Mode as OneWay since the dictionary value can't be set since its readOnly. If you wish to change the value you can hook the Command(if following MVVVM) or can handle in code behind on Checked event.
Also Binding for Key is not correct, replace your keywith Key. Your final xaml should be like this -
<ListBox Grid.Column="0" Grid.Row="1" Height="200"
ItemsSource="{Binding ColumnHeaderList}"
VerticalAlignment="Top">
<ListBox.ItemTemplate>
<DataTemplate>
<CheckBox Content="{Binding Key}"
IsChecked="{Binding Path=Value, Mode=OneWay}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Note i have changed the HierarchicalDataTemplate with DataTemplate since i could not see any hierarchy in the template.

how to access the label inside datatemplate

hello everybody i have a listbox within which is a datatemplate.Inside it is checkbox,textbox,label...Wat i want is to get the value of the label wen the checkbox is unchecked? or any alternative as to how to access the label value but only wen the checkbox is unselected............PLease help me out.
the code is as
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding RelativeSource{RelativeSource AncestorType={x:Type ListBoxItem}}, Path=IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked">
<TextBlock FontSize="11" Text="{Binding subject_name}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding subject_id}" Visibility="Visible">
</Label>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Since you're using binding on label, I'd go for accessing subject_id from the object the datatemplate is describing. Like this:
var subjectId = dataBoundItem.subject_id;
That's the correct way to go with MVVM and bindings.
UPDATE:
Here's the basic MVVM approach to solving this problem. First of all, I've cleaned up a bit your listbox declaration and added a trigger that sets IsSelected binding:
<ListBox ItemsSource="{Binding}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Name="sp" Orientation="Horizontal" Margin="3,3,3,3" >
<CheckBox Name="chkSubject" IsChecked="{Binding IsSelected}" VerticalAlignment="Center" Margin="0,0,4,0" Unchecked="chkSubject_Unchecked_1">
<TextBlock FontSize="11" Text="{Binding SubjectName}" />
</CheckBox>
<Label Name="lbl_idOfSub" Content="{Binding SubjectId}" Visibility="Visible"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
Here, whenever value IsSelected on individual ListBoxItem changes, the "IsSelected" binding of the viewModel is changed. Here's the model:
public class SelectableItem : INotifyPropertyChanged
{
private string _subjectId;
private bool _isSelected;
private string _subjectName;
public string SubjectId
{
get { return _subjectId; }
set { _subjectId = value; OnPropertyChanged("SubjectId"); }
}
public bool IsSelected
{
get { return _isSelected; }
set { _isSelected = value; OnPropertyChanged("IsSelected"); }
}
public string SubjectName
{
get { return _subjectName; }
set { _subjectName = value; OnPropertyChanged("SubjectName"); }
}
// .. INotifyPropertyChangedImplementation
Your IsSelected will be set to true whenever relevant item is selected and to false whenever it is unselected. You may put your code in to the "set" item of the "IsSelected" property and check (value == false) and execute necessary piece of code as you see fit. This would be MVVM approach to the matter.
Using the event, you can do as follows:
private void chkSubject_Unchecked_1(object sender, RoutedEventArgs e)
{
FrameworkElement control = sender as FrameworkElement;
if (control == null)
return;
SelectableItem item = control.DataContext as SelectableItem;
if (item == null)
return;
string yourValue = item.SubjectId;
}
I strongly recommend you read about MVVM and bindings.
What about using the Checked and UnChecked events of your CheckBox, so that you can retrieve the value of subject_id which is binded to your Label.

Resources