I'm using the EventToCommand Class from the MVVM-light-Toolkit to handle the AutoGeneratingColumn-Event in the WPF-DataGrid. It works fine in my Main-DataGrid, but I use another DataGrid in the RowDetailsTemplate and here I got a problem:
The AutoGeneratingColumn fires before the EventToCommand-Object was generated. Is there a solution for this problem?
Here is a piece of my Xaml-Code:
<DataGrid DockPanel.Dock="Top" AutoGenerateColumns="True" Name="table" VerticalAlignment="Top" ItemsSource="{Binding PartBatchList}" IsReadOnly="True">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn">
<hgc:EventToCommand Command="{Binding AutoGeneratingColumnCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Margin="30,0,30,30" Orientation="Vertical">
<Border CornerRadius="4" Padding="5" Background="White">
<DataGrid ItemsSource="{Binding Workpieces}"
CanUserReorderColumns="False" CanUserResizeColumns="False" CanUserSortColumns="False"
AutoGenerateColumns="True" AutoGeneratingColumn="WorkpieceListAutoGeneratingColumn">
<i:Interaction.Triggers>
<i:EventTrigger EventName="AutoGeneratingColumn">
<hgc:EventToCommand Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid},AncestorLevel=2}, Path=DataContext.AutoGeneratingColumnCommand}" PassEventArgsToCommand="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</DataGrid>
</Border>
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
The Event-Handler WorkpieceListAutoGeneratingColumn in the Code-Behind File is called, the Command in my ViewModel is never called.
Andreas
The reason should be that you can't have an event andler and an event to command on the same object/event combination. Remove the AutoGeneratingColumn="WorkpieceListAutoGeneratingColumn" from your DataGrid an the command should be called.
Had the problem once myself :-)
Edit
If you need the eventhandler in the code behind, remove the EventToCommand and call the command in your code behind, e.g.
public void WorkpieceListAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs args) {
var vm = ((YourViewModel) this.DataContext);
if (vm.AutoGeneratingColumnCommand.CanExecute(eventArgs))
vm.AutoGeneratingColumnCommand.Execute(eventArgs);
}
But, I think that the first option is the better one.
Edit 2
OK, had some look around and it seems that <i:Interaction.Triggers/> only works after the object is already rendered and user interaction takes place (hence the name?). Well, this means that there are simply just some events - the ones that are called during the construction of the object - that cannot be handled by the EventToCommand mechanism. In these cases it is OK to use code behind to call your command from there, see my first edit.
You don't have to use evil code behind ;-) You can do this using an attached behaviour...
public class AutoGeneratingColumnEventToCommandBehaviour
{
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached(
"Command",
typeof(ICommand),
typeof(AutoGeneratingColumnEventToCommandBehaviour),
new PropertyMetadata(
null,
CommandPropertyChanged));
public static void SetCommand(DependencyObject o, ICommand value)
{
o.SetValue(CommandProperty, value);
}
public static ICommand GetCommand(DependencyObject o)
{
return o.GetValue(CommandProperty) as ICommand;
}
private static void CommandPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var dataGrid = d as DataGrid;
if (dataGrid != null)
{
if (e.OldValue != null)
{
dataGrid.AutoGeneratingColumn -= OnAutoGeneratingColumn;
}
if (e.NewValue != null)
{
dataGrid.AutoGeneratingColumn += OnAutoGeneratingColumn;
}
}
}
private static void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
var dependencyObject = sender as DependencyObject;
if (dependencyObject != null)
{
var command = dependencyObject.GetValue(CommandProperty) as ICommand;
if (command != null && command.CanExecute(e))
{
command.Execute(e);
}
}
}
}
Then use it in XAML like this...
<DataGrid ItemsSource="{Binding MyGridSource}"
AttachedCommand:AutoGeneratingColumnEventToCommandBehaviour.Command="{Binding CreateColumnsCommand}">
</DataGrid>
Related
I want to have function in WPF:Double click on row in Data Grid will show
cells in window. How can I do it that?I programm so but I never have double click event. Can somebody help me?
Thanks.
<DataGrid AutoGenerateColumns="False" HorizontalAlignment="Left"Margin="14,55,0,46"
Name="dataGridCustomers" Width="575" ItemsSource=" {Binding Path=LoadDataBinding}"
CanUserResizeRows="False">
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseDoubleClick" Handler="dataGridCustomers_MouseDoubleClick"/>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=DEPARTMENT_ID}" Header="DepartmentID" Width="100"
IsReadOnly="True" />
<DataGridTextColumn Binding="{Binding Path=DEPARTMENT_NAME}" Header="Department name" Width="100"
IsReadOnly="True" />
<DataGridTextColumn Binding="{Binding Path=LOCATION}" Header="Location" Width="150"
IsReadOnly="True" />
</DataGrid.Columns>
</DataGrid>
Try this
So, for instance, your XAML might look something like this:
<SomeControl MouseDown="MyMouseDownHandler">
...
</SomeControl>
code behind the click event..
private void MyMouseDownHandler(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
//Handle double-click
}
}
Just create your own dependency property DataGridDoubleClickProperty where you attach handler for DataGrid.MouseDoubleClick event
public static class Commands
{
public static readonly DependencyProperty DataGridDoubleClickProperty =
DependencyProperty.RegisterAttached("DataGridDoubleClickCommand", typeof ( ICommand ), typeof ( Commands ),
new PropertyMetadata(new PropertyChangedCallback(AttachOrRemoveDataGridDoubleClickEvent)));
public static ICommand GetDataGridDoubleClickCommand(DependencyObject obj)
{
return (ICommand) obj.GetValue(DataGridDoubleClickProperty);
}
public static void SetDataGridDoubleClickCommand(DependencyObject obj, ICommand value)
{
obj.SetValue(DataGridDoubleClickProperty, value);
}
public static void AttachOrRemoveDataGridDoubleClickEvent(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
DataGrid dataGrid = obj as DataGrid;
if ( dataGrid != null )
{
ICommand cmd = (ICommand) args.NewValue;
if ( args.OldValue == null && args.NewValue != null )
{
dataGrid.MouseDoubleClick += ExecuteDataGridDoubleClick;
}
else if ( args.OldValue != null && args.NewValue == null )
{
dataGrid.MouseDoubleClick -= ExecuteDataGridDoubleClick;
}
}
}
private static void ExecuteDataGridDoubleClick(object sender, MouseButtonEventArgs args)
{
DependencyObject obj = sender as DependencyObject;
ICommand cmd = (ICommand) obj.GetValue(DataGridDoubleClickProperty);
if ( cmd != null )
{
if ( cmd.CanExecute(obj) )
{
cmd.Execute(obj);
}
}
}
}
In your View you use Binding to map this DependencyProperty to Command
<Grid DataContext="{StaticResource viewModel}">
<DataGrid AutoGenerateColumns="True"
ItemsSource="{Binding Data}"
SelectedItem="{Binding SelectedItem}"
clr:Commands.DataGridDoubleClickCommand="{Binding DataGridDoubleClick}"
/>
</Grid>
DataGridDoubleClick is ICommand property in your ViewModel class
You can subscribe on PreviewMouseDoubleClick event.
<Style TargetType="DataGridRow">
<EventSetter Event="PreviewMouseDoubleClick" Handler="OnDoubleClick"/>
</Style>
Handler:
private void OnDoubleClick(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
System.Diagnostics.Debug.WriteLine("DoubleClick!");
}
I want to show dialog when i double click any of the list items. But the flow of the program never go in the ShowTextCommand property. I get the list of the names (that works fine) but I can't get the dialog. This is my XAML:
<ListView ItemsSource="{Binding List}" >
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding ShowTextCommand, UpdateSourceTrigger=PropertyChanged}"></MouseBinding>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
this is my Command class:
public class EnterTextCommand : ICommand
{
public EnterTextCommand(TekstoviViewModel vm)
{
ViewModel = vm;
}
private TekstoviViewModel ViewModel;
#region ICommand interface
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
// return ViewModel.CanExecute;
}
public void Execute(object parameter)
{
ViewModel.EnterText();
}
#endregion
}
and the view model
private ICommand command;
public ICommand ShowTextCommand
{
get
{
if (command == null)
command = new EnterTextCommand(this);
return command;
}
internal void EnterText()
{
MessageBox.Show("Event Success");
}
Can someone help ?
Your DataTemplate can't find the command, specify the full path to it using ElementName Binding
<ListView ItemsSource="{Binding List}" x:Name="MainList">
<ListView.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding DataContext.ShowTextCommand, UpdateSourceTrigger=PropertyChanged,ElementName=MainList}"></MouseBinding>
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
I have two tasks that need to be done when a check box is checked. This check box is defined in a DataGrid (which is bound to a collection) as a data template of DataGridTemplateColumn.
Also, there is a DataGridTemplateColumn with data template of text box.
On checked event of a check box, I have to clear the text of the text box on the same row and also set the focus on that text box.
Clearing the text works fine on using the EventTrigger.
Problem is the second part where I have to set the focus on the text box.
TO achieve this goal, I thought to use attached behavior with ChangePropertyAction.
For this, I created a dependency property, hoping to get the reference of the datagrid and finding the text box on which focus needs to be set (which I'll do later).
public class TestDependencyProperty : DependencyObject
{
public static readonly DependencyProperty
FlagCheckedProperty =
DependencyProperty.RegisterAttached(
"FlagChecked", typeof(bool), typeof(TestDependencyProperty),
new PropertyMetadata(false, PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var datagrid = dependencyObject as DataGrid;
//get the textbox from the datagrid on which focus
//has to set and set the focus
}
public static bool GetFlagChecked(
DependencyObject d)
{
return (bool)d.GetValue(FlagCheckedProperty);
}
public static void SetFlagChecked(
DependencyObject d, bool value)
{
d.SetValue(FlagCheckedProperty, value);
}
}
and here is the xaml
<DataGrid ItemsSource="{Binding FlagCollection}" AutoGenerateColumns="False" CanUserAddRows="False" Margin="5"
VerticalScrollBarVisibility="Auto" v:TestDependencyProperty.FlagChecked="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Non Kittable" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Flag,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Window}},Path=DataContext.FlaggedCheckedCommand}"
CommandParameter="{Binding}"/>
<isc:ChangePropertyAction TargetObject="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type DataGrid}}}" PropertyName="FlagChecked" Value="True"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</CheckBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Tracking No." Width="2*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding XTrackingNum,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
But, on checking the check box I get error saying, "Cannot find a property named "v:TestDependencyProperty.FlagChecked" on type "DataGrid"
I also tried binding in this way
, but it didn't work -
"Cannot find a property named "FlagChecked" on type "DataGrid".
Not sure if this is the right way to do this.
Can someone point me in right direction..?? Thanks in advance..
In the xaml.cs you have set the focus of the textbox.
Create an event on checkbox when it is checked.
<CheckBox Name="sampleChkbox" Grid.Row="0" Grid.Column="0" Content="Flag" Checked="sampleChkbox_Checked"/>
<TextBox Name="txtSample" Grid.Row="2" Width="200px" Height="40"/>
xaml.cs
private void sampleChkbox_Checked(object sender, RoutedEventArgs e)
{
CheckBox chk = sender as CheckBox;
if((bool)chk.IsChecked)
txtSample.Focus();
}
Finally got it working by adding a boolean property "FocusOnChecked" to the class whose collection is getting binded to the datagrid and setting property as true or false on checked and unchecked event of checkbox using ChangePropertyAction.
<i:Interaction.Triggers>
<i:EventTrigger EventName="Checked">
<i:InvokeCommandAction Command="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Window}},Path=DataContext.FlaggedCheckedCommand}"
CommandParameter="{Binding}"/>
<isc:ChangePropertyAction TargetObject="{Binding}" PropertyName="FocusOnChecked" Value="True"/>
</i:EventTrigger>
<i:EventTrigger EventName="Unchecked">
<isc:ChangePropertyAction TargetObject="{Binding}" PropertyName="FocusOnChecked" Value="False"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Then created a attached property for textbox and binded it with the new property.
public class FocusBehavior : DependencyObject
{
public static readonly DependencyProperty
FocusProperty =
DependencyProperty.RegisterAttached(
"Focus",
typeof(bool),
typeof(FocusBehavior),
new FrameworkPropertyMetadata(false, PropertyChangedCallback));
private static void PropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var txtBox = dependencyObject as TextBox;
if (txtBox == null)
return;
if (dependencyPropertyChangedEventArgs.NewValue is bool == false)
return;
if ((bool )dependencyPropertyChangedEventArgs.NewValue)
txtBox.Focus();
}
public static bool GetFocus(
DependencyObject d)
{
return (bool)d.GetValue(FocusProperty);
}
public static void SetFocus(
DependencyObject d, bool value)
{
d.SetValue(FocusProperty, value);
}
}
below is the xaml for textbox.
<DataTemplate>
<TextBox vm:FocusBehavior.Focus="{Binding Path= FocusOnChecked}" Text="{Binding TrackingNum,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" >
</TextBox>
</DataTemplate>
I am having a WPF MVVM application which has a TreeView with all the static items maintained in XAML page. How do I know in my view-model which MenuItem is clicked so that I can show that respective page accordingly.
<TreeView Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="MyTreeViewMenu"
VerticalAlignment="Stretch" Width="Auto" Opacity="1"
BorderThickness="1" BorderBrush="Black" Grid.Row="2">
<TreeViewItem Header="Country" Width="Auto" HorizontalAlignment="Stretch"
></TreeViewItem>
<TreeViewItem Header="View Details" Width="Auto" HorizontalAlignment="Stretch" IsEnabled="False">
<TreeViewItem Header="User" />
<TreeViewItem Header="Group" />
<TreeViewItem Header="User Group" />
</TreeViewItem>
</TreeView>
I suppose that Selected event will have same effect as a click in your case. To determine which one TreeViewItem was selected you should add event Trigger:
<TreeView Height="Auto" HorizontalAlignment="Stretch" Margin="0" Name="MyTreeViewMenu"
VerticalAlignment="Stretch" Width="Auto" Opacity="1"
BorderThickness="1" BorderBrush="Black" Grid.Row="2">
<TreeViewItem Header="Country" Width="Auto" HorizontalAlignment="Stretch"></TreeViewItem>
<TreeViewItem Header="View Details" Width="Auto" HorizontalAlignment="Stretch" IsEnabled="False">
<TreeViewItem Header="User" />
<TreeViewItem Header="Group" />
<TreeViewItem Header="User Group" />
</TreeViewItem>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction
Command="{Binding selectItemCommand}"
CommandParameter="{Binding SelectedItem, ElementName=MyTreeViewMenu}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
As a result you can use and determine which item was selected by a parameter passed to Command.
ViewModel should look something like this:
private ICommand _selectItemCommand;
public ICommand selectItemCommand
{
get
{
return _selectItemCommand ?? (_selectItemCommand = new RelayCommand(param => this.LoadPage(param)));
}
}
private void LoadPage(object selectedMenuItem)
{
...
}
Take a look at the TreeView.SelectedItem Property page at MSDN.
You can bind directly to the TreeView.SelectedItem property:
<TreeView ItemsSource="{Binding Items}" SelectedItem="{Binding Item, Mode=OneWay}" />
Note that the TreeView.SelectedItem property is only read only, so you must use a OneWay binding... this means that you cannot set the selected item from your view model. To do that, you will need to create your own two way selected item property using an Attached Property.
EDIT >>>
My apologies #Scroog1, I normally use an AttachedProperty to do this. You are right that even with a OneWay binding, there is an error using this method. Unfortuately, my AttachedProperty code is long, but there is another way to do this.
I wouldn't necessarily recommend this as it's never really a good idea to put UI properties into your data objects, but if you add an IsSelected property to your data object, then you can bind it directly to the TreeViewItem.IsSelected property:
<TreeView ItemsSource="Items" HorizontalAlignment="Stretch" ... Name="MyTreeViewMenu">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsSelected" Value="{Binding IsSelected}" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
I just searched and found a 'fuller' answer for you in the WPF MVVM TreeView SelectedItem post here on StackOverflow.
Alternatively, there is another way... you could also use the TreeView.SelectedValue and TreeView.SelectedValuePath properties. The basic idea is to set the TreeView.SelectedValuePath property to the name of a property on your data object. When an item is selected, the TreeView.SelectedValue property will then be set to the value of that property of the selected data item. You can find out more about this method from the How to: Use SelectedValue, SelectedValuePath, and SelectedItem page at MSDN. This generally works best if you have a uniquely identifiable property like an identifier of some kind. This code example is from MSDN:
<TreeView ItemsSource="{Binding Source={StaticResource myEmployeeData},
XPath=EmployeeInfo}" Name="myTreeView" SelectedValuePath="EmployeeNumber" />
<TextBlock Margin="10">SelectedValuePath: </TextBlock>
<TextBlock Margin="10,0,0,0" Text="{Binding ElementName=myTreeView,
Path=SelectedValuePath}" Foreground="Blue"/>
<TextBlock Margin="10">SelectedValue: </TextBlock>
<TextBlock Margin="10,0,0,0" Text="{Binding ElementName=myTreeView,
Path=SelectedValue}" Foreground="Blue"/>
In addition to binding to the TreeView.SelectedItem property:
When using MVVM it helped me to stop thinking about events in the UI and start thinking about state in the UI.
You can bind the ViewModel to properties of the View. So in general I try to bind a SelectedItem to a property on the ViewModel so the ViewModel knows what is selected.
In the same way you could add a property to the ViewModel items being shown called Selected and bind this property to a checkbox in the View. That way you can enable multiple selection and access the selected items easily within the ViewModel.
For completeness, here are the attached property and TreeView subclass options:
Attached property option
public static class TreeViewSelectedItemHelper
{
public static readonly DependencyProperty BindableSelectedItemProperty
= DependencyProperty.RegisterAttached(
"BindableSelectedItem",
typeof (object),
typeof (TreeViewSelectedItemHelper),
new FrameworkPropertyMetadata(false,
OnSelectedItemPropertyChanged)
{
BindsTwoWayByDefault = true
});
public static object GetBindableSelectedItem(TreeView treeView)
{
return treeView.GetValue(BindableSelectedItemProperty);
}
public static void SetBindableSelectedItem(
TreeView treeView,
object selectedItem)
{
treeView.SetValue(BindableSelectedItemProperty, selectedItem);
}
private static void OnSelectedItemPropertyChanged(
DependencyObject sender,
DependencyPropertyChangedEventArgs args)
{
var treeView = sender as TreeView;
if (treeView == null) return;
SetBindableSelectedItem(treeView, args.NewValue);
treeView.SelectedItemChanged -= HandleSelectedItemChanged;
treeView.SelectedItemChanged += HandleSelectedItemChanged;
if (args.OldValue != args.NewValue)
SetSelected(treeView, args.NewValue);
}
private static void SetSelected(ItemsControl treeViewItem,
object itemToSelect)
{
foreach (var item in treeViewItem.Items)
{
var generator = treeViewItem.ItemContainerGenerator;
var child = (TreeViewItem) generator.ContainerFromItem(item);
if (child == null) continue;
child.IsSelected = (item == itemToSelect);
if (child.HasItems) SetSelected(child, itemToSelect);
}
}
private static void HandleSelectedItemChanged(
object sender,
RoutedPropertyChangedEventArgs<object> args)
{
if (args.NewValue is TreeViewItem) return;
var treeView = sender as TreeView;
if (treeView == null) return;
var binding = BindingOperations.GetBindingExpression(treeView,
BindableSelectedItemProperty);
if (binding == null) return;
var propertyName = binding.ParentBinding.Path.Path;
var property = binding.DataItem.GetType().GetProperty(propertyName);
if (property != null)
property.SetValue(binding.DataItem, treeView.SelectedItem, null);
}
}
Subclass option
public class BindableTreeView : TreeView
{
public BindableTreeView()
{
SelectedItemChanged += HandleSelectedItemChanged;
}
public static readonly DependencyProperty BindableSelectedItemProperty =
DependencyProperty.Register(
"BindableSelectedItem",
typeof (object),
typeof (BindableTreeView),
new FrameworkPropertyMetadata(
default(object),
OnBindableSelectedItemChanged) {BindsTwoWayByDefault = true});
public object BindableSelectedItem
{
get { return GetValue(BindableSelectedItemProperty); }
set { SetValue(BindableSelectedItemProperty, value); }
}
private static void OnBindableSelectedItemChanged(
DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var treeView = d as TreeView;
if (treeView != null) SetSelected(treeView, e.NewValue);
}
private static void SetSelected(ItemsControl treeViewItem,
object itemToSelect)
{
foreach (var item in treeViewItem.Items)
{
var generator = treeViewItem.ItemContainerGenerator;
var child = (TreeViewItem) generator.ContainerFromItem(item);
if (child == null) continue;
child.IsSelected = (item == itemToSelect);
if (child.HasItems) SetSelected(child, itemToSelect);
}
}
private void HandleSelectedItemChanged(
object sender,
RoutedPropertyChangedEventArgs<object> e)
{
SetValue(BindableSelectedItemProperty, SelectedItem);
}
}
I have a DataGrid bound to an ICollectionView in my ViewModel. The DataGrid is inside a UserControl which is used in a few different data scenarios, some of which require certain DataGrid columns while others don't.
I just want to bind the DataGridTemplateColumn's Visibility property to the inner label's Content property so if none of the rows contain a value, it will be hidden. I have a String to Visibility converter set, but can't figure out how to find the inner lable's Content property.
<DataGridTemplateColumn Header="Groups" Width="*" CanUserSort="True" SortMemberPath="Groups" Visibility="{Binding ElementName=lbl, Path=Content, Converter={StaticResource StringToVisibilityConverter}}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Name="lbl" Content="{Binding Path=Groups}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Any suggestions?
I read somewhere on Stack Overflow(can't find exact post) that the DataGridColumn's aren't assigned a data context because they aren't a FrameworkElement. To get around this, I had to use code similiar to this:
<DataGridTemplateColumn
Header="Groups"
Width="*"
CanUserSort="True"
SortMemberPath="Groups"
Visibility"{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(FrameworkElement.DataContext).IsGroupsVisible,
Converter={StaticResource booleanToVisiblityConverter}}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Name="lbl" Content="{Binding Path=Groups}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Where
<UserControl.Resources>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" />
</UserControl.Resources>
To use RelativeSource.Self as a RelativeSource binding for a DataGridTemplateColumn - you need to add the DataGridContextHelper to your application. This is still required for the WPF 4 DataGrid.
<DataGridTemplateColumn
Header="Groups"
Width="*"
CanUserSort="True"
SortMemberPath="Groups"
Visibility"{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(FrameworkElement.DataContext).IsGroupsVisible,
Converter={StaticResource booleanToVisiblityConverter}}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Name="lbl" Content="{Binding Path=Groups}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
This would be better achieved going through the Groups property on the ViewModel; since that is ultimately what the Label is using anyways.
<DataGridTemplateColumn Header="Groups" Width="*" CanUserSort="True" SortMemberPath="Groups" Visibility="{Binding Groups, Converter={StaticResource SomeConverter}}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Label Name="lbl" Content="{Binding Path=Groups}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
One hundred thanks to SliverNinja and this article DataGridContextHelper. Links to source code already not working and was not able download, so i wrote my own Attached Proeprty to make it work for all possible cases (DataContext changed, Attached Property value changed, Column added)
My application use DataGrid with AutoGenerateColumns=False and use DataGridTemplateColumn, so DataContext was set before columns added to grid.
Here is Attached Property class:
public sealed class DataGridColumnDataContextForwardBehavior
{
private DataGrid dataGrid = null;
public DataGridColumnDataContextForwardBehavior(DataGrid dataGrid)
{
this.dataGrid = dataGrid;
dataGrid.Columns.CollectionChanged += DataGridColumns_CollectionChanged;
}
private void DataGridColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
var IsDataContextForwardingEnabled = GetIsDataContextForwardingEnabled(dataGrid);
if (IsDataContextForwardingEnabled && dataGrid.DataContext != null)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (DataGridColumn column in e.NewItems)
{
column.SetValue(FrameworkElement.DataContextProperty, dataGrid.DataContext);
}
}
}
}
static DataGridColumnDataContextForwardBehavior()
{
FrameworkElement.DataContextProperty.AddOwner(typeof(DataGridColumn));
FrameworkElement.DataContextProperty.OverrideMetadata(typeof(DataGrid),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnDataContextChanged)));
}
public static readonly DependencyProperty IsDataContextForwardingEnabledProperty =
DependencyProperty.RegisterAttached("IsDataContextForwardingEnabled", typeof(bool), typeof(DataGridColumnDataContextForwardBehavior),
new FrameworkPropertyMetadata(false, OnIsDataContextForwardingEnabledChanged));
public static void OnDataContextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
DataGrid dataGrid = obj as DataGrid;
if (dataGrid == null) return;
var IsDataContextForwardingEnabled = GetIsDataContextForwardingEnabled(dataGrid);
if (IsDataContextForwardingEnabled)
{
foreach (DataGridColumn col in dataGrid.Columns)
{
col.SetValue(FrameworkElement.DataContextProperty, e.NewValue);
}
}
}
static void OnIsDataContextForwardingEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var dataGrid = obj as DataGrid;
if (dataGrid == null) return;
new DataGridColumnDataContextForwardBehavior(dataGrid);
if (!(e.NewValue is bool)) return;
if ((bool)e.NewValue && dataGrid.DataContext != null)
OnDataContextChanged(obj, new DependencyPropertyChangedEventArgs(FrameworkElement.DataContextProperty, dataGrid.DataContext, dataGrid.DataContext));
}
public static bool GetIsDataContextForwardingEnabled(DependencyObject dataGrid)
{
return (bool)dataGrid.GetValue(IsDataContextForwardingEnabledProperty);
}
public static void SetIsDataContextForwardingEnabled(DependencyObject dataGrid, bool value)
{
dataGrid.SetValue(IsDataContextForwardingEnabledProperty, value);
}
}
Another non obvious things is how to properly use binding for DataGridTemplateColumn:
<DataGrid bhv:DataGridColumnDataContextForwardBehavior.IsDataContextForwardingEnabled="True">
<DataGrid.Columns>
<DataGridTemplateColumn Visibility="{Binding Path=DataContext.Mode, RelativeSource={RelativeSource Self}, Converter={StaticResource SharedFilesModeToVisibilityConverter}, ConverterParameter={x:Static vmsf:SharedFilesMode.SharedOut}}"/>
It is important to use Path=DataContext.MyProp and RelativeSource Self
Only thing i don't like in current implementation - to handle DataGrid.Columns.CollectionChanged event i create instance of my class and do not keep reference for it. So in theory GC may kill it within the time, not sure how to handle it correctly at present moment, will think on it and update my post later. Any ideas and critique are welcome.
You can't do this. Binding/name resolution doesn't work this way. Why not, instead of a StringToVisibilityConverter create a CollectionToVisibilityConverter which examines the data source (possibly passing in the column/property to examine), looks to see if that column/property is completely empty, and then convert that to a Visibility?