Can't call method from CallMethodAction on EventTrigger of custom TextBox - c#

Getting error : 'Could not find method named 'LostFocus' on object of type 'MyType' that matches the expected signature.'
<DataGridTemplateColumn MinWidth="80" Width="1.25*" Header="6">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<customControlls:NumericTextBox x:Name="cc"
Style="{StaticResource NumericTextboxStyle}"
Text="{Binding AccountsReceivable.OverdueAtTheEndOfTheReportingPeriod, UpdateSourceTrigger=LostFocus}">
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="LostFocus" SourceName="cc">
<interactions:CallMethodAction TargetObject="{Binding}" MethodName="LostFocus"/>
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</customControlls:NumericTextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
And the method in ViewModel which I'm trying to call. I also tried to remove parameters from method, still same error.
public void LostFocus(object sender, EventArgs e){}

I got it working. You need to bind TargetObject to DataGrid's DataContext.
<DataGridTemplateColumn MinWidth="80" Width="1.25*" Header="6">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<customControlls:NumericTextBox
Style="{StaticResource NumericTextboxStyle}"
Text="{Binding AccountsReceivable.OverdueAtTheEndOfTheReportingPeriod, UpdateSourceTrigger=LostFocus}">
<interactivity:Interaction.Triggers>
<interactivity:EventTrigger EventName="LostFocus">
<interactions:CallMethodAction MethodName="LostFocus" TargetObject="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext}" />
</interactivity:EventTrigger>
</interactivity:Interaction.Triggers>
</customControlls:NumericTextBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
And the method signature which will be called should be:
public void LostFocus(object sender, RoutedEventArgs e){}

I had this issue too and instead wrote my own TriggerAction to get rid of the constraint to have a specific method signature. Mind you that this code would have to be improved, in order to be fully viable (method arguments)
public class InvokeMethodAction : Microsoft.Xaml.Behaviors.TriggerAction<DependencyObject>
{
public static readonly DependencyProperty TargetObjectProperty = DependencyProperty.Register(
nameof(TargetObject), typeof(FrameworkElement), typeof(InvokeMethodAction), new PropertyMetadata(default(FrameworkElement)));
public FrameworkElement TargetObject
{
get { return (FrameworkElement) GetValue(TargetObjectProperty); }
set { SetValue(TargetObjectProperty, value); }
}
public static readonly DependencyProperty MethodNameProperty = DependencyProperty.Register(
nameof(MethodName), typeof(string), typeof(InvokeMethodAction), new PropertyMetadata(default(string)));
public string MethodName
{
get { return (string) GetValue(MethodNameProperty); }
set { SetValue(MethodNameProperty, value); }
}
/// <inheritdoc />
protected override void Invoke(object parameter)
{
if (TargetObject != null && MethodName != null)
{
var method = TargetObject.GetType().GetMethod(MethodName);
if (method != null)
{
method.Invoke(TargetObject, null);
}
}
}
}

Related

Usercontrol Binding inside Itemscontrol [duplicate]

This question already has answers here:
DependencyProperty not triggered
(2 answers)
Closed 5 days ago.
This post was edited and submitted for review 5 days ago.
Edit2 if anyone is interested.
This is how i fixed the Problem (sadly not really mvvm but the binding did not work somehow if the datagrid was inside an itemscontrol)
public ObservableCollection<int> SelectedCells
{
get { return (ObservableCollection<int>)GetValue(SelectedCellsProperty); }
set { SetValue(SelectedCellsProperty, value); }
}
public static readonly DependencyProperty SelectedCellsProperty = DependencyProperty.Register(
"SelectedCells",
typeof(ObservableCollection<int>),
typeof(DataGrid),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, SelectedCellsChanged));
private static void SelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var control = d as DataGrid;
control?.RaiseSelectedCellsChangedEvent(e.OldValue, e.NewValue);
}
public event RoutedPropertyChangedEventHandler<object> SelectedCellsChangedEvent;
public void RaiseSelectedCellsChangedEvent(object oldValue, object newValue)
{
SelectedCellsChangedEvent?.Invoke(this, new RoutedPropertyChangedEventArgs<object>(oldValue, newValue));
}
In The mainView:
<uc:DataGrid Height="160" Width="300"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DataGridSource="{Binding}"
Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
SelectedCellsChangedEvent="DataGrid_SelectedCellsChangedEvent"
code behind mainwindow:
private void DataGrid_SelectedCellsChangedEvent(object sender, RoutedPropertyChangedEventArgs<object> e)
{
ObservableCollection<int> newValue = e.NewValue as ObservableCollection<int>;
}
Edit:
The PropertyChangedCallback Function is getting called, but the problem is that it is only not working if the binding is inside my itemscontrol.
If the <uc:datagrid> is not inside an itemscontrol it is working!
public ObservableCollection<int> SelectedCells
{
get { return (ObservableCollection<int>)GetValue(SelectedCellsProperty); }
set { SetValue(SelectedCellsProperty, value); }
}
public static readonly DependencyProperty SelectedCellsProperty =
DependencyProperty.Register("SelectedCells", typeof(ObservableCollection<int>), typeof(DataGrid), new PropertyMetadata(null, new PropertyChangedCallback(OnSelectedCellsChanged)));
private static void OnSelectedCellsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// This method will be called whenever the value of the SelectedCells property changes
// You can perform any additional logic you need to here
Console.WriteLine("change"); <-- This is getting called, also if in my itemscontrol
}
I have a problem with the binding inside of an Itemscontrol.
I want to know which cells are currently selected in my Datagrid(inside a usercontrol).
This is my Datagrid Usercontrol.
Here I added a Dependency Property SelectedCells.
/// <summary>
/// Interaction logic for DataGrid.xaml
/// </summary>
public partial class DataGrid : UserControl
{
public DataGrid()
{
InitializeComponent();
SelectedCells = new ObservableCollection<int>();
}
public DataGridValue DataGridSource
{
get { return (DataGridValue)GetValue(DataGridSourceProperty); }
set { SetValue(DataGridSourceProperty, value); }
}
public static readonly DependencyProperty DataGridSourceProperty =
DependencyProperty.Register("DataGridSource", typeof(DataGridValue), typeof(DataGrid), new PropertyMetadata(null));
public ObservableCollection<int> SelectedCells
{
get { return (ObservableCollection<int>)GetValue(SelectedCellsProperty); }
set { SetValue(SelectedCellsProperty, value); }
}
public static readonly DependencyProperty SelectedCellsProperty =
DependencyProperty.Register("SelectedCells", typeof(ObservableCollection<int>), typeof(DataGrid), new PropertyMetadata(null));
private void datagrid_SelectedCellsChanged(object sender, SelectedCellsChangedEventArgs e)
{
SelectedCells = new ObservableCollection<int>();
foreach (DataGridCellInfo cellInfo in datagrid.SelectedCells)
{
int columnIndex = cellInfo.Column.DisplayIndex;
int rowIndex = datagrid.ItemContainerGenerator.IndexFromContainer(
datagrid.ItemContainerGenerator.ContainerFromItem(cellInfo.Item));
//Console.WriteLine($"Cell ({rowIndex}, {columnIndex}) is selected.");
switch((DayName)columnIndex)
{
case 0: break;
case DayName.Mo:
if (DataGridSource.DataGridList[rowIndex].MondayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].MondayCell.Text));
}
break;
case DayName.Di:
if (DataGridSource.DataGridList[rowIndex].TuesdayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].TuesdayCell.Text));
}
break;
case DayName.Mi:
if (DataGridSource.DataGridList[rowIndex].WednesdayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].WednesdayCell.Text));
}
break;
case DayName.Do:
if (DataGridSource.DataGridList[rowIndex].ThursdayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].ThursdayCell.Text));
}
break;
case DayName.Fr:
if (DataGridSource.DataGridList[rowIndex].FridayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].FridayCell.Text));
}
break;
case DayName.Sa:
if (DataGridSource.DataGridList[rowIndex].SaturdayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].SaturdayCell.Text));
}
break;
case DayName.So:
if (DataGridSource.DataGridList[rowIndex].SundayCell.Text != string.Empty)
{
SelectedCells.Add(Convert.ToInt32(DataGridSource.DataGridList[rowIndex].SundayCell.Text));
}
break;
}
}
}
}
<DataGrid x:Name="datagrid"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserResizeColumns="False"
CanUserReorderColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False"
AutoGenerateColumns="False"
SelectionUnit="Cell"
SelectionMode="Extended"
HeadersVisibility="Column"
Background="Transparent"
BorderBrush="Transparent"
SelectedCellsChanged="datagrid_SelectedCellsChanged"
ItemsSource="{Binding DataGridSource.DataGridList, RelativeSource={RelativeSource AncestorType=UserControl}}"
>
If I just add my DataGrid to my mainView the binding is working:
<uc:DataGrid Height="160" Width="300"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DataGridSource="{Binding TestGrid}"
SelectedCells="{Binding TestList, Mode=TwoWay}"
Margin="20,20,0,0"/>
property:
public partial class MainViewModel : BaseViewModel
{
private ObservableCollection<int> testList;
public ObservableCollection<int> TestList
{
get
{
if (testList == null)
{
testList = new ObservableCollection<int>();
}
return testList;
}
set
{
testList = value;
}
}
But if I am doing this Inside of an Itemscontrol the binding is not working.
I want it to bind to the Property inside of my MainViewModel.
In the debugger there are no binding issues, but the setter is never called.
This is my Itemscontrol:
<ItemsControl Name="MyItemsControl" Visibility="Visible"
ItemsSource="{Binding Path=DataGridSelectedYear, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
Margin="0,55,0,0">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<uc:DataGrid Height="160" Width="300"
HorizontalAlignment="Center"
VerticalAlignment="Center"
DataGridSource="{Binding}"
Tag="{Binding RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"
SelectedCells="{Binding DataContext.TestList, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, RelativeSource={RelativeSource AncestorType=Window}}"
Margin="20,20,0,0">
<uc:DataGrid.ContextMenu>
<ContextMenu DataContext="{Binding Path=PlacementTarget.Tag, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Test"
Command="{Binding Path=DataContext.AddBackColorCommand}"
CommandParameter="{Binding}"
/>
</ContextMenu>
</uc:DataGrid.ContextMenu>
</uc:DataGrid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But it also does not work if I add the property to my class where the Itemscontrol does get it’s data from (DataGridSelectedYear). Also no Binding errors in the debugger but the setter never gets called.
The binding engine calls the SetValue method of the DependencyObject when setting a dependency property so the setter of your SelectedCells property is not supposed to be invoked when you bind to a source property.
You could register a PropertyChangedCallback if you want to confirm that the property is set as expected.

WinUI 3 How to Bind Command to ViewModel property when using a DataTemplate?

I am using a Grid called MainGrid to position an ItemsRepeater whose ItemsSource is bound to an ObservableCollection within my ViewModel.
<muxc:ItemsRepeater
ItemsSource="{Binding Path=Molts}"
Layout="{StaticResource VerticalStackLayout}"
ItemTemplate="{StaticResource MoltTemplate}">
</muxc:ItemsRepeater>
I have created a DataTemplate
<DataTemplate x:Key="MoltTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="text" Text="{Binding ID}"></TextBlock>
<Button Command="{Binding DisplayAvailAIsCommand}" CommandParameter="{Binding ElementName=text, Path=Text}">Add</Button>
</StackPanel>
</DataTemplate>
which has a TextBox and Button. I want the Button to fire a command in my ViewModel but items within the ItemsRepeater have their DataContext set to their Model class and not the ViewModel. I found this post which states that I can change the Command of my Button to set the DataContext to my ViewModel by setting ElementName to a UI element that has as its DataContext the ViewModel
<Button Command="{Binding DataContext.DisplayAvailAIsCommand, ElementName=MainGrid}" CommandParameter="{Binding ElementName=text, Path=Text}">Add</Button>
The constructor of my window sets the DataContext of the MainGrid like this
public MainWindow()
{
this.InitializeComponent();
MainGrid.DataContext = new MoltViewModel();
}
However, this does not work and the command does not fire. What am I doing wrong?
You could create an attached property that sets the DataContext of the Button to a parent element of a specific type such as for example ItemsRepeater:
public static class AncestorSource
{
public static readonly DependencyProperty AncestorTypeProperty =
DependencyProperty.RegisterAttached(
"AncestorType",
typeof(Type),
typeof(AncestorSource),
new PropertyMetadata(default(Type), OnAncestorTypeChanged)
);
public static void SetAncestorType(FrameworkElement element, Type value) =>
element.SetValue(AncestorTypeProperty, value);
public static Type GetAncestorType(FrameworkElement element) =>
(Type)element.GetValue(AncestorTypeProperty);
private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement target = (FrameworkElement)d;
if (target.IsLoaded)
SetDataContext(target);
else
target.Loaded += OnTargetLoaded;
}
private static void OnTargetLoaded(object sender, RoutedEventArgs e)
{
FrameworkElement target = (FrameworkElement)sender;
target.Loaded -= OnTargetLoaded;
SetDataContext(target);
}
private static void SetDataContext(FrameworkElement target)
{
Type ancestorType = GetAncestorType(target);
if (ancestorType != null)
target.DataContext = FindParent(target, ancestorType);
}
private static object FindParent(DependencyObject dependencyObject, Type ancestorType)
{
DependencyObject parent = VisualTreeHelper.GetParent(dependencyObject);
if (parent == null)
return null;
if (ancestorType.IsAssignableFrom(parent.GetType()))
return parent;
return FindParent(parent, ancestorType);
}
}
Usage:
<DataTemplate x:Key="MoltTemplate">
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="text" Text="{Binding ID}"></TextBlock>
<Button local:AncestorSource.AncestorType="muxc:ItemsRepeater"
Command="{Binding DataContext.DisplayAvailAIsCommand}"
CommandParameter="{Binding ElementName=text, Path=Text}">Add</Button>
</StackPanel>
</DataTemplate>
Please refer to this blog post for more information.

How to convert wpf AutoCompleteBox to all uppercase input

I have a AutoCompleteBox as a DataGrid column type. Like so:
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Thing, UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<SLToolkit:AutoCompleteBox Text="{Binding Path=Thing,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
However, I want to restrict the user's input to uppercase. On TextBoxes I can do so like the following, but I can't get that to work with the AutoCompleteBoxes.
<DataGridTextColumn Binding="{Binding UpdateSourceTrigger=PropertyChanged, Path=Thing}">
<DataGridTextColumn.EditingElementStyle>
<Style TargetType="TextBox">
<Setter Property="CharacterCasing" Value="Upper" />
</Style>
</DataGridTextColumn.EditingElementStyle>
</DataGridTextColumn>
I've tried this:
<SLToolkit:AutoCompleteBox Text="{Binding Path=Thing,
UpdateSourceTrigger=PropertyChanged}"
TextChanged="AutoComplete_TextChanged" />
With this:
private void AutoComplete_TextChanged(object sender, RoutedEventArgs e)
{
AutoCompleteBox box = sender as AutoCompleteBox;
if (box == null) return;
box.Text = box.Text.ToUpper();
}
That kind of works except that it writes backwards. When the user inputs a character, the cursor goes back to the start of the box so the next word is in front of the previous one. If I wrote 'example', I would see "ELPMAXE".
Any ideas?
I solved a similar problem where I only wanted entry of numbers in a textbox, so I used a behavior. If a non-number is entered, the character is deleted. I also used the interactivity library which uses the System.Windows.Interactivity.dll (just import this DLL into your project, if you don't have it its part of the blend sdk http://www.microsoft.com/en-us/download/details.aspx?id=10801).
Here is the simplified XAML:
<Window x:Class="Sample.SampleWindow"
xmlns:main="clr-namespace:MySampleApp"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Title="Sample"
Height="800"
Width="1025"
>
<Grid>
<TextBox Text="{Binding Path=Entry, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
Width="30"
MaxLength="4"
HorizontalAlignment="Left">
<i:Interaction.Behaviors>
<main:KeyPressesWithArgsBehavior
KeyUpCommand="{Binding KeyUpFilterForUpperCaseSymbols}" />
</i:Interaction.Behaviors>
</TextBox>
</Grid>
</Window>
Uses the following Behavior class:
public class KeyPressesWithArgsBehavior : Behavior<UIElement>
{
#region KeyDown Press DependencyProperty
public ICommand KeyDownCommand
{
get { return (ICommand) GetValue(KeyDownCommandProperty); }
set { SetValue(KeyDownCommandProperty, value); }
}
public static readonly DependencyProperty KeyDownCommandProperty =
DependencyProperty.Register("KeyDownCommand", typeof (ICommand), typeof (KeyPressesWithArgsBehavior));
#endregion KeyDown Press DependencyProperty
#region KeyUp Press DependencyProperty
public ICommand KeyUpCommand
{
get { return (ICommand) GetValue(KeyUpCommandProperty); }
set { SetValue(KeyUpCommandProperty, value);}
}
public static readonly DependencyProperty KeyUpCommandProperty =
DependencyProperty.Register("KeyUpCommand", typeof(ICommand), typeof (KeyPressesWithArgsBehavior));
#endregion KeyUp Press DependencyProperty
protected override void OnAttached()
{
AssociatedObject.KeyDown += new KeyEventHandler(AssociatedUIElementKeyDown);
AssociatedObject.KeyUp += new KeyEventHandler(AssociatedUIElementKeyUp);
base.OnAttached();
}
protected override void OnDetaching()
{
AssociatedObject.KeyDown -= new KeyEventHandler(AssociatedUIElementKeyDown);
AssociatedObject.KeyUp -= new KeyEventHandler(AssociatedUIElementKeyUp);
base.OnDetaching();
}
private void AssociatedUIElementKeyDown(object sender, KeyEventArgs e)
{
if (KeyDownCommand != null)
{
ObjectAndArgs oa = new ObjectAndArgs {Args = e, Object = AssociatedObject};
KeyDownCommand.Execute(oa);
}
}
private void AssociatedUIElementKeyUp(object sender, KeyEventArgs e)
{
if (KeyUpCommand != null)
{
KeyUpCommand.Execute(AssociatedObject);
}
}
}
Then in your View Model you can implement the command.
SampleWindowViewModel.cs:
public ICommand KeyUpFilterForUpperCaseSymbolsCommand
{
get
{
if (_keyUpFilterForUpperCaseSymbolsCommand== null)
{
_keyUpFilterForUpperCaseSymbolsCommand= new RelayCommand(KeyUpFilterForUpperCaseSymbols);
}
return _keyUpFilterForUpperCaseSymbolsCommand;
}
}
...
private void KeyUpFilterForUpperCaseSymbols(object sender)
{
TextBox tb = sender as TextBox;
if (tb is TextBox)
{
// check for a lowercase character here
// then modify tb.Text, to exclude that character.
// Example: tb.Text = oldText.Substring(0, x);
}
}

How to Select All CheckBox of a Column by DataGrid Header CheckBox in WPF DataGrid

I have a DataGrid with one CheckBoxColumn. In the header of that CheckBoxColumn I have added a CheckBox to Select all CheckBoxes of that Datagrid Row.
How can I achieve that?
My XAML Code for WPF dataGrid:
<DataGrid AutoGenerateColumns="False" CanUserAddRows="False" Grid.RowSpan="2" Height="130" HorizontalAlignment="Left" IsReadOnly="False" Margin="189,340,0,0" Name="dgCandidate" TabIndex="7" VerticalAlignment="Top" Width="466" Grid.Row="1" >
<DataGrid.Columns>
<DataGridTextColumn x:Name="colCandidateID" Binding="{Binding CandidateID}" Header="SlNo" MinWidth="20" IsReadOnly="True" />
<DataGridTextColumn x:Name="colRegistraion" Binding="{Binding RegisterNo}" Header="Reg. No." IsReadOnly="True" />
<DataGridTextColumn x:Name="colCandidate" Binding="{Binding CandidateName}" Header="Name" MinWidth="250" IsReadOnly="True" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<CheckBox Name="chkSelectAll" Checked="chkSelectAll_Checked" Unchecked="chkSelectAll_Unchecked"></CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<CheckBox x:Name="colchkSelect1" Checked="colchkSelect1_Checked" Unchecked="colchkSelect1_Unchecked" ></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Convert your Candidate class into something like this:
public class Candidate : DependencyObject
{
//CandidateID Dependency Property
public int CandidateID
{
get { return (int)GetValue(CandidateIDProperty); }
set { SetValue(CandidateIDProperty, value); }
}
public static readonly DependencyProperty CandidateIDProperty =
DependencyProperty.Register("CandidateID", typeof(int), typeof(Candidate), new UIPropertyMetadata(0));
//RegisterNo Dependency Property
public int RegisterNo
{
get { return (int)GetValue(RegisterNoProperty); }
set { SetValue(RegisterNoProperty, value); }
}
public static readonly DependencyProperty RegisterNoProperty =
DependencyProperty.Register("RegisterNo", typeof(int), typeof(Candidate), new UIPropertyMetadata(0));
//CandidateName Dependency Property
public string CandidateName
{
get { return (string)GetValue(CandidateNameProperty); }
set { SetValue(CandidateNameProperty, value); }
}
public static readonly DependencyProperty CandidateNameProperty =
DependencyProperty.Register("CandidateName", typeof(string), typeof(Candidate), new UIPropertyMetadata(""));
//BooleanFlag Dependency Property
public bool BooleanFlag
{
get { return (bool)GetValue(BooleanFlagProperty); }
set { SetValue(BooleanFlagProperty, value); }
}
public static readonly DependencyProperty BooleanFlagProperty =
DependencyProperty.Register("BooleanFlag", typeof(bool), typeof(Candidate), new UIPropertyMetadata(false));
}
in MainWindow.xaml:
<DataGrid ItemsSource="{Binding CandidateList}">
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding CandidateID}"/>
<DataGridTextColumn Header="RegNr" Binding="{Binding RegisterNo}"/>
<DataGridTextColumn Header="Name" Binding="{Binding CandidateName}"/>
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<CheckBox Checked="CheckBox_Checked" Unchecked="CheckBox_Checked"></CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate >
<DataTemplate>
<CheckBox IsChecked="{Binding BooleanFlag}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
in MainWindow.xaml.cs:
public MainWindow()
{
DataContext = this;
CandidateList.Add(new Candidate()
{
CandidateID = 1,
CandidateName = "Jack",
RegisterNo = 123,
BooleanFlag = true
});
CandidateList.Add(new Candidate()
{
CandidateID = 2,
CandidateName = "Jim",
RegisterNo = 234,
BooleanFlag = false
});
InitializeComponent();
}
//List Observable Collection
private ObservableCollection<Candidate> _candidateList = new ObservableCollection<Candidate>();
public ObservableCollection<Candidate> CandidateList { get { return _candidateList; } }
private void CheckBox_Checked(object sender, RoutedEventArgs e)
{
foreach (var item in CandidateList)
{
item.BooleanFlag = true;
}
}
private void UnheckBox_Checked(object sender, RoutedEventArgs e)
{
foreach (var item in CandidateList)
{
item.BooleanFlag = false;
}
}
Strictly speaking the model should not know about the view and so the solution proposed by blindmeis, where the model change is updating every row in the datagrid, breaks the MVVM/Presentation Design pattern. Remember that in MVVM the dependency flow is View -> ViewModel -> Model so if you are referencing controls in your view model (or control codebehind) then you have effectively broken the pattern and you will probably run into issues further down the track.
I have added CheckBox to Select all CheckBox in Datagrid Row
if you mean select all checkbox in datagrid column, then i would say: simply update your itemssource collection with checked/unchecked.
public bool SelectAll
{
get{return this._selectAll;}
set
{
this._selectAll = value;
this.MyItemsSourceCollection.ForEach(x=>x.MyRowCheckProperty=value);
this.OnPropertyChanged("SelectAll");
}
}
xaml
<DataGridTemplateColumn>
<DataGridTemplateColumn.Header>
<CheckBox isChecked="{Binding SelectAll}"></CheckBox>
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate >
<DataTemplate >
<CheckBox IsChecked="{Binding MyRowCheckProperty}"></CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
i dunno if the xaml bindings are right, but i hope you can see my intention
It turns out that this is quite a lot harder to get right than one would hope.
The first problem is that you can't just bind the view model to the column header because it doesn't have the view model as its data context, so you need a binding proxy to correctly route the binding to the view model.
public class BindingProxy : Freezable
{
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(
"Data",
typeof(object),
typeof(BindingProxy),
new UIPropertyMetadata(null));
public object Data
{
get { return this.GetValue(DataProperty); }
set { this.SetValue(DataProperty, value); }
}
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
}
Now create a binding proxy in your data grid's resources:
<DataGrid.Resources>
<aon:BindingProxy
x:Key="DataContextProxy"
Data="{Binding}" />
</DataGrid.Resources>
Then the column needs to be defined as:
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
<CheckBox
Command="{Binding
Data.SelectAllCommand,
Source={StaticResource DataContextProxy}}"
IsChecked="{Binding
Data.AreAllSelected,
Mode=OneWay,
Source={StaticResource DataContextProxy},
UpdateSourceTrigger=PropertyChanged}"
IsThreeState="True" />
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
IsChecked="{Binding
Path=IsSelected,
UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Note that there needs to be a binding to both the check box's IsChecked dependency property and its Command property and the IsChecked binding is OneWay. The IsChecked binding gets the check box to display the current state of the items and the Command binding performs the bulk selection. You need both.
Now in the view model:
public bool? AreAllSelected
{
get
{
return this.Items.All(candidate => candidate.IsSelected)
? true
: this.Items.All(candidate => !candidate.IsSelected)
? (bool?)false
: null;
}
set
{
if (value != null)
{
foreach (var item in this.Items)
{
item.IsSelected = value.Value;
}
}
this.RaisePropertyChanged();
}
}
And the SelectAllCommand property is an implementation of ICommand where the Execute method is:
public void Execute(object parameter)
{
var allSelected = this.AreAllSelected;
switch (allSelected)
{
case true:
this.AreAllSelected = false;
break;
case false:
case null:
this.AreAllSelected = true;
break;
}
}
Finally your row item view models (i.e. the things in Items) need to raise PropertyChanged on the main view model each time the value of IsSelected changes. How you do that is pretty much up to you.

DataGrid CellStyle Setters with Freezable StaticRecource

I wanted to set the command of a button in a WPF datagrid with a setter. But it seems that the DP property CommandProperty gets ovewritten with its default value null after I returned a copy in
CreateInstanceCore(), so the original command gets lost.
If I bind the StaticResource directly it works without problems.
Is there a way to stop that behavior or another solution?
public class ResourceCommand : Freezable, ICommand {
public ICommand Command {
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
// Using a DependencyProperty as the backing store for Command. This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandProperty =
DependencyProperty.Register("Command", typeof(ICommand), typeof(ResourceCommand), new UIPropertyMetadata(null, CommandPropertyChangedCallback));
static void CommandPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) {
ResourceCommand resourceCommand = (ResourceCommand)d;
int h = resourceCommand.GetHashCode();
if (e.OldValue != null)
((ICommand)e.OldValue).CanExecuteChanged -= resourceCommand.OnCanExecuteChanged;
if (e.NewValue != null)
((ICommand)e.NewValue).CanExecuteChanged += resourceCommand.OnCanExecuteChanged;
}
#region ICommand Member
public bool CanExecute(object parameter) {
if (Command == null)
return false;
return Command.CanExecute(parameter);
}
public event EventHandler CanExecuteChanged;
void OnCanExecuteChanged(object sender, EventArgs e) {
if (CanExecuteChanged != null)
CanExecuteChanged(sender, e);
}
public void Execute(object parameter) {
Command.Execute(parameter);
}
#endregion
protected override Freezable CreateInstanceCore() {
ResourceCommand ResourceCommand = new ResourceCommand();
ResourceCommand.Command = Command;
return ResourceCommand;
}
}
xaml:
<Window.Resources>
<local:ResourceCommand x:Key="FirstCommand" Command="{Binding FirstCommand}" />
<local:ResourceCommand x:Key="SecondCommand" Command="{Binding SecondCommand}" />
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding Collection}">
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Content="Click me">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Command" Value="{StaticResource FirstCommand}" />
</Style>
</Button.Style></Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
It works if you define your resource commands like this:
<local:ResourceCommand x:Key="FirstCommand" Command="{Binding FirstCommand}" x:Shared="False"/>
Using this technique you can even throw not-implemented in CreateInstanceCore and so you'll just be using Freezable to enable data binding.

Categories