Checkbox Command Not Firing When Initially Unchecked - c#

I have a column of checkboxes in a datagrid.
<DataGridTemplateColumn CanUserResize="False" Header="" Width="auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Style="{StaticResource CheckBoxSelectTypeStyle}" IsChecked="{Binding Path=Selected}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I have a command bound to unchecking and checking.
<Style x:Key="CheckBoxSelectTypeStyle" TargetType="{x:Type CheckBox}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Command" Value="{Binding DataContext.CheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
<Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Command" Value="{Binding DataContext.UncheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
<Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
</Trigger>
</Style.Triggers>
</Style>
My problem is that if the checkboxes start as unchecked and you check one, then no commands are fired (This is the problem). If you then uncheck the same checkbox, the uncheck command will fire (As expected). If you then check the same checkbox again, the check command will fire (As expected). Everything will work fine for that checkbox at that point, but the others still have the same problem.
If a checkbox starts as checked it will work fine. My question is how do I make the command fire when the checkbox starts as unchecked. I can't find any reason for it not to work.
Response to suggestion:
I tried to add a trigger for Null, but it has the exact same problem, nothing changed.
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Command" Value="{Binding DataContext.CheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
<Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Command" Value="{Binding DataContext.UncheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
<Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
</Trigger>
<Trigger Property="IsChecked" Value="{x:Null}">
<Setter Property="Command" Value="{Binding DataContext.UncheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
<Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
</Trigger>
</Style.Triggers>
ViewModel Code
public ICommand CheckedCommand { get; set; }
public ICommand UncheckedCommand { get; set; }
CheckedCommand = new RelayCommand<IList>(Checked);
UncheckedCommand = new RelayCommand<IList>(Unchecked);
private void Checked(IList selectedItems)
{
ChangedChecked(selectedItems, true);
}
private void Unchecked(IList selectedItems)
{
ChangedChecked(selectedItems, false);
}
private void ChangedChecked(IList selectedItems, bool selected)
{
if (selectedItems.HasValue())
foreach (var item in selectedItems)
if(item is SelectedTypeModel) (item as SelectedTypeModel).Selected = selected;
}
RelayCommand implements ICommand. As I mentioned before, the Checked method is not called when the checkbox starts as unchecked, but it is called when it is checked, unchecked, and checked again or if it starts checked and is unchecked.
What I'm Trying To Do
I have a DataGrid with a column of CheckBoxs. I want to be able to highlight multiple rows, then check/uncheck a CheckBox in one of the selected rows and have all the other rows update to be the same. I also have a keyword filter for the datagrid, so the DataGrid is binding to a ListCollectionView.
Output Information
I enabled debug information for data binding and get this message:
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=Selected; DataItem=null; target element is 'CheckBox' (Name=''); target property is 'IsChecked' (type 'Nullable`1')
I'm still not sure how to use this information to correct the problem though.
SOLUTION
I did not fix my original problem and I still don't know why it doesn't work correctly, but I have used another method with the same desired result. Here is the changed code:
<Style x:Key="CheckBoxSelectTypeStyle" TargetType="{x:Type CheckBox}">
<Setter Property="Command" Value="{Binding DataContext.CheckedChangedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{StaticResource SelectedItemsCheckedMultiValueConverter}">
<Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}" Path="SelectedItems"/>
<Binding RelativeSource="{RelativeSource Self}" Path="IsChecked"/>
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
public ICommand CheckedChangedCommand { get; set; }
CheckedChangedCommand = new RelayCommand<Tuple<IList, bool>>(CheckedChanged);

Solution
I think you can achieve your goals without any need in triggers (I've tested this solution in small sample app):
so your style would looks like this:
<Style x:Key="CheckBoxSelectTypeStyle" TargetType="{x:Type CheckBox}">
<Setter Property="Command" Value="{Binding DataContext.CheckedCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
<Setter Property="CommandParameter" Value="{Binding Path=SelectedItems, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
...
There will be only one command in your view model. And as parameter that command will receive list of selected items. So only one thing is missing - whether user is about to check or uncheck checkbox. At the moment I can think of one possible way of passing this info along with SelectedItems - write MultiBinding with custom converter which will put selectedItems and current value of IsChecked in something like Tuple<..., bool>
<Setter Property="CommandParameter">
<Setter.Value>
<MultiBinding Converter="{x:Static PackTupleConverter.Instance}">
<Binding RelativeSource="{RelativeSource AncestorType=DataGrid}" Path="SelectedItems"/>
<Binding RelativeSource="{x:Static RelativeSource.Self}" Path="IsChecked"/>
</MultiBinding>
</Setter.Value>
</Setter>
end
I was able to repro your problem by simply putting same checkbox inside ListBox. Once that done first time I click on checkbox - command don't get called
here is sample window xaml:
<Window.Resources>
<Style x:Key="CheckBoxSelectTypeStyle" TargetType="{x:Type CheckBox}">
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Center" />
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Command" Value="{Binding DataContext.Test, RelativeSource={RelativeSource AncestorType=Window}}" />
</Trigger>
<Trigger Property="IsChecked" Value="False">
<Setter Property="Command" Value="{Binding DataContext.Test, RelativeSource={RelativeSource AncestorType=Window}}" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<ListBox>
<ListBox.Items>
<CheckBox Style="{StaticResource CheckBoxSelectTypeStyle}" />
</ListBox.Items>
</ListBox>
</Grid>
and sode behaind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
Test = new DelegateCommand(TestCommand);
}
public ICommand Test { get; set; }
private void TestCommand()
{
}
}
Here is what I've discovered in output regarding binding:
System.Windows.Data Information: 10 : Cannot retrieve value using the binding and no valid fallback value exists; using default instead. BindingExpression:Path=DataContext.Test; DataItem=null; target element is 'CheckBox' (Name=''); target property is 'Command' (type 'ICommand')

Related

How to enable NonChecked checkbox in WPF

I am unchecking a disabled Checkbox using some code behind.
I need to enable this Checkbox when it is unchecked and do it using XAML.
I do this
<CheckBox Margin="10,320,427,0" Name="chkIsDefault"
IsEnabled="{Binding IsChecked, ElementName=Self, Mode=OneWay}"
IsChecked="{Binding IsDefault, Mode=TwoWay}" />
But it is not working. Any clue?
If you want to directly bind to another property of the same object use RelativeSource=Self
<CheckBox ... IsEnabled="{Binding IsChecked, RelativeSource={RelativeSource Self}, Mode=OneWay}"/>
but since you want invert behaviour (disable when checked) you could modify binding to use custom IValueConverter but I would suggest to use a simple Trigger instead
<CheckBox IsChecked="{Binding IsDefault, Mode=TwoWay}">
<CheckBox.Style>
<Style TargetType="{x:Type CheckBox}">
<Setter Property="IsEnabled" Value="True"/>
<Style.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="IsEnabled" Value="False"/>
</Trigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>

WPF DataGrid ComboBox SelectedItem Property Setter

I am looking for an example of how to use the SelectedItem property inside a combobox in a WPF DataGrid, I have
<DataGridComboBoxColumn SelectedValueBinding="{Binding CID, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
SelectedValuePath="CID"
Header="CID"
Width="70">
<DataGridComboBoxColumn.EditingElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding DataContext.ListCustomerCollection,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
<Setter Property="DisplayMemberPath" Value="Name"/>
<Setter Property="SelectedItem" Value="{Binding DataContext.Customer, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"></Setter>
</Style>
</DataGridComboBoxColumn.EditingElementStyle>
<DataGridComboBoxColumn.ElementStyle>
<Style TargetType="ComboBox">
<Setter Property="ItemsSource" Value="{Binding DataContext.ListCustomerCollection, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
<Setter Property="DisplayMemberPath" Value="Name"/>
<Setter Property="HorizontalAlignment" Value="Center"></Setter>
<Setter Property="SelectedItem" Value="{Binding DataContext.Customer, RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"></Setter>
</Style>
</DataGridComboBoxColumn.ElementStyle>
The DataContext I bind to (ListCustomerCollection) is a List object
List<Customer>
so the property in the ViewModel property I have set is
private Customer m_Customer = null;
public Customer Customer
{
get { return m_Customer; }
set
{
m_Customer = value;
OnPropertyChanged("Customer");
}
}
So how do I writethe XAML to set the above property with the SelectedItem?
If property resides in window's ViewModel, you have to get window's DataContext like you did for ItemsSource.
<Setter Property="SelectedItem"
Value="{Binding DataContext.Customer,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType=Window}}"/>

How to bind with a relative source that is Ancestor and Self?

I have the following code:
<ItemsControl ItemsSource="{Binding MyDictionary}" >
<Image Width="16">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Value}" Value="False">
<Setter Property="Source" Value="{Binding RelativeSource =
{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}},
Path=DataContext.SecDic}"/>
</DataTrigger>
<DataTrigger Binding="{Binding Value}" Value="True">
<Setter Property="Source" Value="{Binding RelativeSource =
{RelativeSource FindAncestor, AncestorType={x:Type ItemsControl}},
Path=DataContext.ThirdDic}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</ItemsControl>
MyDictionary - Is a dictionary that the key is of type enum
Now This binding works if I change the SecDic and ThirdDic from dictionaries to BitmapImages. But I want that for each key (enum) in MyDictionary there will be a different True/False image.
So I tried to write:
<Setter Property="Source" Value="{Binding Path=DataContext.SecDic[Key],
RelativeSource={AncestorType={x:ItemsControl}}}"/>
But it doesn't work since the "Key" belongs to the data context of the image (of MyDictionary) and I changed the relative source to be the items control so there is no "Key".
Any suggestions?
What I did to solve this is a multibinding + a custom multibinding converter.
There was one binding for the dictionary and one for the key.

Make xaml datatrigger work in c# code

I am new to xaml, i have following code below, my question is how can i call InvalidForeground from c# code to change the color of checkbox text?
<ControlTemplate x:Key="ItemTemplate"
TargetType="ListViewItem">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="CkBoxVisual">
<CheckBox.IsChecked>
<Binding Path="IsSelected"
Mode="TwoWay">
<Binding.RelativeSource>
<RelativeSource Mode="TemplatedParent" />
</Binding.RelativeSource>
</Binding>
</CheckBox.IsChecked>
<DataTrigger Binding="{Binding InvalidForeground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}" Value="true">
<Setter TargetName="CkBoxVisual" Property="Foreground" Value="Red"/>
</DataTrigger>
</CheckBox>
<ContentPresenter />
</StackPanel>
</ControlTemplate>
In your code, you are not calling anything. In it, you're hoping a change of dependency property..
But a control of type Window does not have a dependency property with the name: "InvalidForeground".
This trigger will never be triggered.
What is your goal, change a property or be notified of a change (trigger)?
Edit: There are a number of rules you must follow:
1) The control referenced in Binding property from DataTrigger (RelativeSource):
Binding="{Binding Path=InvalidForeground, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"
Must have a dependency property called "InvalidForeground", which works correctly, based on this, will not work:
{x:Type Window}
its type must be declared, will work, for example:
{x:Type my:ControlName}
2) A property changed by a Trigger or DataTrigger can never be explicitly stated, for example. Will not work:
<TextBlock Text="{Binding Any}" Foreground="#FFCCCCCC">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabItem, Mode=FindAncestor}, Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
Will work:
<TextBlock Text="{Binding Any}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#FFCCCCCC"/>
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabItem, Mode=FindAncestor}, Path=IsSelected}" Value="True">
<Setter Property="Foreground" Value="Black"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
3) Check the operation of Bindings using RelativeSource they have some peculiarities (you should know a little about the visual tree of WPF).
http://msdn.microsoft.com/en-us/library/ms743599.aspx
4) You should check that the binding is working properly and can follow these steps:
http://www.codeproject.com/Articles/244107/Debugging-WPF-data-bindings

MenuItem.Icon not greyed out with DataTrigger

I try to show a different image as menuitem.icon if the menuitem is disabled (just a greyed out version). This is what I tried:
<tk:DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Header="_Divorce" Command="{Binding Path=DivorceCommand}">
<MenuItem.Icon>
<Image>
<Image.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType=MenuItem, AncestorLevel=3}}" Value="true">
<Setter Property="Image.Source" Value="eraser.ico" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType=MenuItem, AncestorLevel=3}}" Value="false"
<Setter Property="Image.Source" Value="eraser_grey.ico" />
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</tk:DataGrid.ContextMenu>
</tk:DataGrid>
Error message:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Controls.MenuItem', AncestorLevel='1''. BindingExpression:Path=IsEnabled; DataItem=null; target element is 'Image' (Name=''); target property is 'NoTarget' (type 'Object')
What is wrong with my current XAML datatrigger binding?
I appreciate any help!
You could try the following:
Add a Trigger like CubaLibre mentioned
But instead of using a target name, which will not work. Use the following.
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Image">
<Setter.Value>
<Image Source="eraser_grey.ico"/>
</Setter.Value>
</Setter>
</Trigger>
if that will not work (sorry can't test it for myself at the moment). You could also do it the hard way:
Create a Style for menuitem, you can just completely copy the original style
Now in the control template, of the desired role template, you just add under ControlTemplate.Triggers. Your trigger like
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Source" Value="eraser_grey.ico"/>
</Trigger>
to make it a little bit cleaner. I would add a service class, which contains an attached property named "DisabledIcon" or similiar and use that as an value, instead the hard coded one.
Unfortunately changing the whole style is the only working way i can think of right now.
[Edit]
Ok after some further investigation, here is a solution that works fine, even though i'm not happy with it because it is quiet ugly :( But sorry i have no better idea at the moment.
<MenuItem Header="_Divorce" Command="...">
<MenuItem.Icon>
<ContentControl>
<ContentControl.ContentTemplate>
<DataTemplate>
<Image x:Name="iconImage" Source="arrowDown.png"/>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type MenuItem}}}" Value="False">
<Setter TargetName="iconImage" Property="Source" Value="close.png"/>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ContentControl.ContentTemplate>
</ContentControl>
</MenuItem.Icon>
</MenuItem>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Source" TargetName="Image" Value="eraser_grey.ico"/>
</Trigger>
Does it work when you try to do so

Categories