WPF CheckBox Command inside a Listview DataTemplate - c#

My CheckBox Command does not work. I want to pass the SelectedItem of the ListView to the Command when the SelectedItem is checked or unchecked, but the Command does not execute at all. I also suspect my CommandParameter is not configured correctly?
I am pretty sure the problem is because the CheckBox is within a ListView DataTemplate.
Can someone show me how to set this up? I tried to follow examples I found, but nothing seems to work. thanks.
XAML
<ListView x:Name="lvReferralSource" ItemsSource="{Binding ReferralSourceTypeObsCollection}" Style="{StaticResource TypeListViewStyle}">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Vertical">
<CheckBox x:Name="ckbReferralIsChecked" Content="{Binding Value}" IsChecked="{Binding Active}" Style="{StaticResource CheckBoxStyleBase2}"
Command="{Binding CheckBoxIsChecked}" CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}, Path=SelectedItem}">
</CheckBox>
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
CODE
private ICommand _CheckBoxIsChecked;
public ICommand CheckBoxIsChecked
{
get
{
if (_CheckBoxIsChecked == null)
{
_CheckBoxIsChecked = new RelayCommand<object>(ExecuteCheckBoxIsChecked, CanExecuteCheckBoxIsChecked);
}
return _CheckBoxIsChecked;
}
}
public bool CanExecuteCheckBoxIsChecked(object parameter)
{
return true;
}
public void ExecuteCheckBoxIsChecked(object parameter)
{
Mouse.OverrideCursor = Cursors.Wait;
if (parameter != null)
{
//Do Stuff...
}
Mouse.OverrideCursor = Cursors.Hand;
}

Your command should get executed provided that the CheckBoxIsChecked property belongs to the data object where the Value and Active properties are defined.
If it belongs to the view model, you could bind to it using a RelativeSource:
<CheckBox x:Name="ckbReferralIsChecked" Content="{Binding Value}" IsChecked="{Binding Active}"
Style="{StaticResource CheckBoxStyleBase2}"
Command="{Binding DataContext.CheckBoxIsChecked, RelativeSource={RelativeSource AncestorType=ListView}}"
CommandParameter="{Binding}">

Related

Bind MenuItem.IsEnabled to property of a view model

I have an MVVM WPF project with the following code:
MultiplexerVM.cs
public class MultiplexerVM : BaseViewModel
{
public ObservableCollection<MultiplexVM> Multiplexes { get; set; } = new();
public MultiplexVM SelectedMultiplex { get; set; }
public ICommand CheckAll => new CheckBoxCommand(Multiplexes);
}
MultiplexVM.cs
public class MultiplexVM : BaseViewModel
{
public bool IsChecked { get; set; }
}
MultiplexerV.xaml
<UserControl x:Class="MKVStudio.Views.MultiplexerV"
xmlns:vm="clr-namespace:MKVStudio.ViewModels"
xmlns:s="clr-namespace:System;assembly=mscorlib">
<UserControl.Resources>
<s:Boolean x:Key="True">True</s:Boolean>
<s:Boolean x:Key="False">False</s:Boolean>
</UserControl.Resources>
<Grid>
<ListView ItemsSource="{Binding Multiplexes}"
SelectedItem="{Binding SelectedMultiplex}">
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsChecked}"Margin="3"/>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
...
</GridView>
</ListView.View>
<ListView.ContextMenu>
<ContextMenu>
<MenuItem Command="{Binding CheckAll}"
CommandParameter="{StaticResource True}">
<MenuItem.Header>
<TextBlock Text="Check all"/>
</MenuItem.Header>
</MenuItem>
<MenuItem Command="{Binding CheckAll}"
CommandParameter="{StaticResource False}">
<MenuItem.Header>
<TextBlock Text="Uncheck all"/>
</MenuItem.Header>
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
</ListView>
</Grid>
</UserControl>
My goal is to bind IsEnabled of the context menu items to the property IsChecked of MultiplexVM.cs. The idea was to implement an IValueConverter (passing Multiplexes as value and bool as parameter). The converter returns value.Where(m => m.IsChecked == parameter).Count > 0. Essentially, when all Multiplexes are unchecked the menu item Check all is enabled and the menu item Uncheck all is disabled. The reverse thing is happening when all Multiplexes are checked. The problem here is that the converter is invoked only once when it is declared basically, and checking and unchecking the items does not trigger the converter to see what is happening.
I have tried to implement an IMultiValueConverter (but failing to use it correctly) and pass three values like this:
<MenuItem.IsEnabled>
<MultiBinding>
<Binding Source="{Binding Multiplexes.Count}" />
<Binding Source="{Binding Multiplexes}" />
<Binding Source="{StaticResource True}" /> <!--respectivly False to the other menu item-->
</MultiBinding>
</MenuItem.IsEnabled>
This doesn't work. I've tried <Binding Path="Multiplexes.Count" /> and <Binding Path="Multiplexes" />, but also doesn't work (the values passed to the converter are Unset).
Is my idea for using MultiBinding even feasible and what am I doing wrong when using it?
Why do you need to bind IsChecked to IsChecked and IsEnabled at once? This is very strange if you look at it from the Single Responsibility Principle. If you are sure that you are doing it right, you can do it like this:
<CheckBox IsChecked="{Binding IsChecked}"
IsEnabled="{Binding IsEnabled}" />
And make your class look like something like this:
public class MultiplexVM : BaseViewModel
{
public bool IsChecked
{
get => isChecked;
set
{
isChecked = value;
isEnabled = value;
RaisePropertyChanged(nameof(IsChecked));
RaisePropertyChanged(nameof(IsEnabled));
};
}
private bool isChecked;
public bool IsEnabled
{
get => isEnabled;
set
{
isChecked = value;
isEnabled = value;
RaisePropertyChanged(nameof(IsChecked));
RaisePropertyChanged(nameof(IsEnabled));
};
}
private bool isChecked;
}
From what I understand, you want to make an object bound to a "parent" (MenuItem => MultiplexerVM) be dependant on a property of its child collection (CheckBox => MultiplexVM.IsChecked, which is an item in MultiplexerVM.Multiplexes)
In this scenario, a child has to be somehow aware of its parent (when the child changes, it has to "push" the change up to the parent; in other words, the parent has to be informed when the change happens).
I can think of two ways to do it:
on the VM level: in every MultiplexVM, set a reference to the parent view model or collection, then you can update the CanCheckAll / CanUncheckAll functionality (however you implement it) every time the child's IsChecked changes (tedious; I suppose you can also do this with events, but attaching PropertyChanged handler to every child item is also a bit much)
cheat a bit by using the GUI level: you can update the CanCheckAll / CanUncheckAll functionality whenever the CheckBox is clicked
Below is an example of how you can implement the 2nd version.
In your MultiplexerVM:
public bool CanCheckAll => Multiplexes.Any(a => !a.IsChecked);
public bool CanUncheckAll => Multiplexes.Any(a => a.IsChecked);
public void RefreshCheckUncheckAll()
{
NotifyPropertyChanged(nameof(CanCheckAll));
NotifyPropertyChanged(nameof(CanUncheckAll));
}
Then, call RefreshCheckUncheckAll() in CheckAll command implementation and in:
private void CheckBox_Click(object sender, RoutedEventArgs e)
{
((MultiplexerVM)this.DataContext).RefreshCheckUncheckAll();
}
Then, the xaml will look something like this:
<ListView ItemsSource="{Binding Multiplexes}" SelectedItem="{Binding SelectedMultiplex}">
<ListView.ContextMenu>
<ContextMenu>
<MenuItem
Command="{Binding CheckAll}"
CommandParameter="{StaticResource True}"
IsEnabled="{Binding CanCheck}">
<MenuItem.Header>
<TextBlock Text="Check all" />
</MenuItem.Header>
</MenuItem>
<MenuItem
Command="{Binding CheckAll}"
CommandParameter="{StaticResource False}"
IsEnabled="{Binding CanUncheck}">
<MenuItem.Header>
<TextBlock Text="Uncheck all" />
</MenuItem.Header>
</MenuItem>
</ContextMenu>
</ListView.ContextMenu>
<ListView.View>
<GridView>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Margin="3" Text="{Binding Name}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn>
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Margin="3" IsChecked="{Binding IsChecked}" Click="CheckBox_Click" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>

How update ListBox when ItemsSource changed

I have buttons "ADD" and "DEL", but "DEL" does not work. What is wrong?
count in my ObservableCollection<User> was changed but ListBox does not
sample project: https://github.com/Veselov-Dmitry/MyQuestion
view:
<StackPanel>
<Button Content="ADD"
Command="{Binding AddUsers_OASUCommand}"
CommandParameter="">
</Button>
<ListBox ItemsSource="{Binding Users_OASU}">
<ListBox.ItemTemplate>
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Login}" />
<Button Content="DEL"
Command="{Binding DelUsers_OASUCommand}"
CommandParameter="{Binding Path=Content,
RelativeSource={RelativeSource Mode=FindAncestor ,
AncestorType={x:Type ListBoxItem}}}">
<Button.DataContext>
<local:ViewModel />
</Button.DataContext>
</Button>
</WrapPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</StackPanel>
I set datacontext in constructor MainView
viewvmodel:
class ViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<User> Users_OASU{get; set;}
public ICommand AddUsers_OASUCommand{get; set;}
public ICommand DelUsers_OASUCommand{get; set;}
public ViewModel()
{
Users_OASU = new ObservableCollection<User>(GetUsers());
AddUsers_OASUCommand = new Command<object>(arg => AddUsers_OASUMethod());
DelUsers_OASUCommand = new Command<object>(arg => DelUsers_OASUMethod(arg));
}
private void DelUsers_OASUMethod(object arg)
{
User find = Users_OASU.Where(x => x.Login == (arg as User).Login).FirstOrDefault();
Users_OASU.Remove(find);
}
private void AddUsers_OASUMethod()
{
Users_OASU.Add(new User("52221", "John X."));
}
private List<User> GetUsers()
{
List<User> list = new List<User>();
list.Add(new User("52222", "John W."));
list.Add(new User("52223", "John Z."));
list.Add(new User("52224", "John A."));
list.Add(new User("52225", "John M."));
return list;
}
}
"count in my ObservableCollection was changed but ListBox does not" - you have multiple instances of ViewModel, count was changed, but not in the collection which is displayed
you need to setup DataTemplate correctly to avoid that
first, each Button will get User object for DataContext (it will be provided by ListBox from ItemsSource). You mustn't declare new <Button.DataContext>
second, DelUsers_OASUCommand is declared in a ViewModel class, it is accessible on ListBox level, from DataContext. Change binding path accordingly.
<DataTemplate>
<WrapPanel>
<TextBlock Text="{Binding Name}" />
<TextBlock Text="{Binding Login}" />
<Button Command="{Binding DataContext.DelUsers_OASUCommand,
RelativeSource={RelativeSource AncestorType=ListBox}}"
CommandParameter="{Binding Path=Content,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}"
Content="DEL" />
</WrapPanel>
</DataTemplate>
additionally I would change DelUsers_OASUMethod to accept User as argument
private void DelUsers_OASUMethod(object arg)
{
Users_OASU.Remove(arg as User);
}
and pass CommandParameter like this:
CommandParameter="{Binding Path=.}"
or the same, but shorter:
CommandParameter="{Binding}"

Binding property in ContextMenu from Parent's Parent's DataContext

I wanted to bind a property in a MenuItem within an ItemsControl container. I have a hierarchical model ; Item class has a list of SubItem. The ViewModel itself has a list of Item (so there's two ItemsControl, one being in the ItemTemplate of the other).
I found several other questions on SO asking about that (this one for instance) and I learned that the Visual Tree of a ContextMenu is separated from the rest.
I managed to do it and it works (but it feels kind of hacky) by "transferring" the model's data via the Tag property.
Here's the two model classes:
public class SubItem
{
public int Current { get; set; }
public Subitem(int current)
{
Current = current;
}
}
public class Item
{
public ObservableCollection<SubItem> SubItems { get; set; }
public string Parent { get; set; }
public Item(string Parent)
{
Parent = Parent;
SubItems = new ObservableCollection<SubItem>();
}
}
Here's the view model:
public class ViewModel
{
public ObservableCollection<Item> Items { get; set; }
public ViewModel()
{
Items = new ObservableCollection<Item>();
FillData();
}
private void FillData()
{
//...
}
}
And here's the ItemsControl at the root of the page (the page's DataContext is an instance of the ViewModel class):
<ItemsControl ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<ItemsControl ItemsSource="{Binding SubItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Current}"
Tag="{Binding DataContext.Parent, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, Mode=FindAncestor}}">
<TextBlock.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}, Mode=FindAncestor}}"/>
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My question is: is this the right way to do it? I tried many other ways to avoid binding the property to the Tag but couldn't make it work.
The ugly part is specifically:
Tag="{Binding DataContext.Parent, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}, Mode=FindAncestor}}"
Followed by:
Header="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}, Mode=FindAncestor}}"
I think it's kind of ugly and I'm sure there's a better way to do that. The solution must work with .NET 4.0.
You need to add a tag to the menu's container and bind to it using placement target.
View this example:
<StackPanel x:Key="ConfigurationListItem" x:Shared="False" Tag="{Binding ElementName=UserControl}">
<StackPanel Orientation="Horizontal">
<Button>
<Button.InputBindings>
<MouseBinding Gesture="LeftDoubleClick" Command="{Binding ElementName=UserControl, Path=LaunchCommand}" CommandParameter="{Binding}" />
<MouseBinding Gesture="LeftClick" Command="{Binding ElementName=UserControl, Path=SelectCommand}" CommandParameter="{Binding}" />
</Button.InputBindings>
</StackPanel>
<StackPanel.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}" Tag="{Binding}">
<MenuItem Header="Sync Environment Dependencies"
Command="{Binding Parent.PlacementTarget.Tag.SyncEnvironmentCommand, RelativeSource={RelativeSource Self}}"
CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContextMenu}}, Path=PlacementTarget.DataContext}" />
</ContextMenu>
</StackPanel.ContextMenu>
</StackPanel>

Delete item from ItemControl

My class is has a ObservableCollection of my viewmodel class and I set the itemsource of the Itemcontrol in xaml as below
<ItemsControl ItemsSource="{Binding ConditionItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Expander Background="#FFD0D7EB">
<StackPanel>
<Button Content="Delete" HorizontalAlignment="Right" Width="180" Margin="0,0,12,10" Command="{Binding DeleteItem}" CommandParameter="{Binding}">
</Button> </StackPanel>
</Expander>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
For some reason my DeleteItem is never called.
private RelayCommand _DeleteRule;
private void DoDeleteRule(object item)
{
if (item != null)
{
MessageBox.Show("in del");
}
}
public ICommand DeleteItem
{
get
{
if (_DeleteRule == null)
_DeleteRule = new RelayCommand(o => DoDeleteRule(o));
return _DeleteRule;
}
}
Am I doing anything wrong in xaml?
The ItemsControl is bound using {Binding ConditionItems}, so it expects the DeleteItem command to be inside the subitems of that list. I guess this is not the case, the DeleteItem exists on the ViewModel.
You could bind to the DataContext of the Window for example, where you can find the DeleteItem command. Or create a proxy element.
I found it. My xaml should be
<Button Content="Delete" Command="{Binding DataContext.DeleteItem,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type ItemsControl}}}" CommandParameter="{Binding}">
</Button>

Set CommandParameter to currently tapped item on a ListBox (WP7)

I want to set command parameter to currently selected item on a ListBox.
XAML:
<!--<ListBox ItemsSource="{Binding Places}" SelectedItem="{Binding SelectedPlace, Mode=TwoWay}">-->
<ListBox ItemsSource="{Binding Places}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<i:InvokeCommandAction Command="{Binding ListBoxClick}" CommandParameter="{Binding}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
(...)
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
C# (part of ViewModel code exposing the ListBoxClick command)
public RelayCommand ListBoxClick { get; set; }
ListBoxClick = new RelayCommand((o) => {
//model is always null
var model = o as BasicModel;
SelectedPlace = model;
});
I added appropriate references, and namespace:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
The problem is that in action called by RelayCommand object the o parameter is always null.
UPDATE
C# code for SelectedPlace property
public BasicModel SelectedPlace {
get {
return _selectedPlace;
}
set {
_selectedPlace = value;
RaisePropertyChanged("SelectedPlace");
}
}
When I use this:
<ListBox ItemsSource="{Binding Places}" SelectedItem="{Binding SelectedPlace, Mode=TwoWay}">
everything works fine if I click ListBoxItem for the first time, but when I click on a selected ListBoxItem nothing happens, because selection doesn't change. I need to be able to detect item click in both situations.
I figured out an ugly way to achieve my goal.
XAML
<ListBox ItemsSource="{Binding Places}" SelectedItem="{Binding SelectedPlace, Mode=TwoWay}">
C# (ViewModel)
private bool _placeSelected;
public BasicModel SelectedPlace {
get {
return _selectedPlace;
}
set {
_placeSelected = true;
_selectedPlace = value;
RaisePropertyChanged("SelectedPlace");
}
}
ListBoxClick = new RelayCommand((o) => {
if (!_placeSelected) {
SelectedPlace = _selectedPlace;
}
else {
_placeSelected = false;
}
});
This way RaisePropertyChanged("SelectedPlace"); will be called in both cases.
Try this:
<ListBox ItemsSource="{Binding Places}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<i:InvokeCommandAction Command="{Binding ListBoxClick}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource AncestorType={
x:Type ListBox}}}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
(...)
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
If that doesn't work, try changing the Binding to this:
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource Self}}"
UPDATE >>>
Oh sorry, I didn't see that you were using Windows Phone 7. As an alternative, try adding a property into your code behind/view model that binds to the ListBox.SelectedItem property:
<ListBox ItemsSource="{Binding Places}" SelectedItem="{Binding SelectedItem}" ... />
Then you should be able to do this:
ListBoxClick = new RelayCommand(() => {
SelectedPlace = SelectedItem;
});
UPDATE 2 >>>
I don't know if Windows Phone 7 supports the Binding.ElementName property, but if it does, try this:
<ListBox Name="ListBox" ItemsSource="{Binding Places}">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Tap">
<i:InvokeCommandAction Command="{Binding ListBoxClick}"
CommandParameter="{Binding SelectedItem, ElementNameListBox}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<ListBox.ItemTemplate>
<DataTemplate>
(...)
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>

Categories

Resources