I have a simple DataGrid
with RowDetailsTemplate
where the DataTemplate
is also a DataGrid
.
A behavior
is used to bind in two way mode DataGrid's SelectedItems
to the ViewModel
.
The behavior
is used both in main DataGrid
and in subs DataGrids
which are details of the main DataGrid
rows.
I'm facing the problem that the behaviors in sub DataGrids seems to be never be referenced and each DataGrid.SelectionChanged
event in the sub DataGrid refers always to the behavior in the main DataGrid.
THE VIEW
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
<DataGrid Name="MainDataGrid"
AutoGenerateColumns="False"
HorizontalScrollBarVisibility="Disabled"
Height="Auto"
ItemsSource="{Binding ObCol_Model}"
VerticalAlignment="Stretch" CanUserAddRows="false" BorderThickness="1"
AlternatingRowBackground="#FFFFFFCC"
HorizontalGridLinesBrush="#FFA0A0A0"
VerticalGridLinesBrush="#FFA0A0A0"
SelectionUnit="FullRow"
HeadersVisibility="Column"
GridLinesVisibility="Horizontal"
ColumnHeaderHeight="25" IsReadOnly="True" CanUserResizeRows="False" RowHeight="22" VerticalContentAlignment="Center"
BorderBrush="DarkGray" HorizontalAlignment="Left"
RowDetailsVisibilityMode="VisibleWhenSelected">
<i:Interaction.Behaviors>
<local:DataGridSelectedItemsBehavior SelectedItems="{Binding SelectedItems}" />
</i:Interaction.Behaviors>
<DataGrid.Columns>
<DataGridTextColumn Header="Code" Binding="{Binding Code}" Width="80"/>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" Width="*"/>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid Name="SubDataGrid"
Margin="10,0,0,0" AutoGenerateColumns="False" ItemsSource="{Binding ObCol_SubModel}"
Height="Auto" Width="auto"
VerticalAlignment="Stretch" CanUserAddRows="false" BorderThickness="1"
AlternatingRowBackground="#FFFFFFCC"
HorizontalGridLinesBrush="#FFA0A0A0"
VerticalGridLinesBrush="#FFA0A0A0"
SelectionUnit="FullRow"
HeadersVisibility="Column"
GridLinesVisibility="Horizontal"
ColumnHeaderHeight="25" IsReadOnly="True" CanUserResizeRows="False" RowHeight="22" VerticalContentAlignment="Center"
BorderBrush="DarkGray" HorizontalAlignment="Left">
<i:Interaction.Behaviors>
<local:DataGridSelectedItemsBehavior SelectedItems="{Binding SubSelectedItems}" />
</i:Interaction.Behaviors>
<DataGrid.Style>
<Style TargetType="{x:Type DataGrid}">
<Setter Property="Visibility" Value="Visible"/>
<Style.Triggers>
<DataTrigger Binding="{Binding ObCol_SubModel}" Value="{x:Null}">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.Style>
<DataGrid.Columns>
<DataGridTextColumn Header="Detail" Binding="{Binding Detail}" Width="80"/>
<DataGridTextColumn Header="Detail Name" Binding="{Binding Name}" Width="150"/>
<DataGridTextColumn Header="Comment" Binding="{Binding Comment}" Width="*"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
THE BEHAVIOR
public class DataGridSelectedItemsBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
if (SelectedItems != null)
{
AssociatedObject.SelectedItems.Clear();
foreach (var item in SelectedItems)
{
AssociatedObject.SelectedItems.Add(item);
}
}
}
public IList SelectedItems
{
get { return (IList)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(DataGridSelectedItemsBehavior), new UIPropertyMetadata(null, SelectedItemsChanged));
private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var behavior = o as DataGridSelectedItemsBehavior;
if (behavior == null) return;
var oldValue = e.OldValue as INotifyCollectionChanged;
var newValue = e.NewValue as INotifyCollectionChanged;
if (oldValue != null)
{
oldValue.CollectionChanged -= behavior.SourceCollectionChanged;
behavior.AssociatedObject.SelectionChanged -= behavior.DataGridSelectionChanged;
}
if (newValue != null)
{
behavior.AssociatedObject.SelectedItems.Clear();
foreach (var item in (IEnumerable)newValue)
{
behavior.AssociatedObject.SelectedItems.Add(item);
}
behavior.AssociatedObject.SelectionChanged += behavior.DataGridSelectionChanged;
newValue.CollectionChanged += behavior.SourceCollectionChanged;
}
}
private bool _isUpdatingTarget;
private bool _isUpdatingSource;
void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_isUpdatingSource)
return;
try
{
_isUpdatingTarget = true;
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
AssociatedObject.SelectedItems.Remove(item);
}
}
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
AssociatedObject.SelectedItems.Add(item);
}
}
if (e.Action == NotifyCollectionChangedAction.Reset)
{
AssociatedObject.SelectedItems.Clear();
}
}
finally
{
_isUpdatingTarget = false;
}
}
private void DataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isUpdatingTarget)
return;
var selectedItems = this.SelectedItems;
if (selectedItems == null)
return;
try
{
_isUpdatingSource = true;
foreach (var item in e.RemovedItems)
{
selectedItems.Remove(item);
}
foreach (var item in e.AddedItems)
{
selectedItems.Add(item);
}
}
finally
{
_isUpdatingSource = false;
}
}
}
THE VM
public class ViewModel : ObservableObject
{
private readonly ObservableCollection<Model> myObCol_Model;
private ObservableCollection<Model> mySelectedItems;
public ViewModel()
{
mySelectedItems = new ObservableCollection<Model>();
myObCol_Model = new ObservableCollection<Model>()
{
new Model("100", "Hundred"),
new Model("200", "Two Hundred"),
new Model("300", "Three Hundred")
};
var item = new Model("400", "Four Hundred");
item.AddSubModel(new SubModel("10", "Ten", "sub 10"));
item.AddSubModel(new SubModel("20", "Twenty", "sub 20"));
item.AddSubModel(new SubModel("30", "Thirty", "sub 30"));
myObCol_Model.Add(item);
}
public ObservableCollection<Model> ObCol_Model { get { return myObCol_Model; } }
public ObservableCollection<Model> SelectedItems
{
get { return mySelectedItems; }
set
{
if (mySelectedItems == value) return;
mySelectedItems = value;
OnPropertyChanged(nameof(SelectedItems));
}
}
}
THE MODELS
public class Model : ObservableObject
{
private ObservableCollection<SubModel> myObCol_SubModel;
private ObservableCollection<SubModel> mySubModelSelectedItems;
public Model(string code, string name)
{
mySubModelSelectedItems = new ObservableCollection<SubModel>();
Code = code;
Name = name;
}
public string Code { get; set; }
public string Name { get; set; }
public ObservableCollection<SubModel> ObCol_SubModel { get { return myObCol_SubModel; } }
public void AddSubModel(SubModel subModel)
{
if (myObCol_SubModel == null) myObCol_SubModel = new ObservableCollection<SubModel>();
myObCol_SubModel.Add(subModel);
}
public ObservableCollection<SubModel> SubSelectedItems
{
get { return mySubModelSelectedItems; }
set
{
if (mySubModelSelectedItems == value) return;
mySubModelSelectedItems = value;
OnPropertyChanged(nameof(SubSelectedItems));
}
}
}
public class SubModel
{
public SubModel(string detail, string name, string comment)
{
Detail = detail;
Name = name;
Comment = comment;
}
public string Detail { get; set; }
public string Name { get; set; }
public string Comment { get; set; }
}
Below a behavior how it does work by me:
public class DataGridSelectedItemsBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
base.OnAttached();
if (SelectedItems != null)
{
AssociatedObject.SelectedItems.Clear();
foreach (var item in SelectedItems)
{
AssociatedObject.SelectedItems.Add(item);
}
}
AssociatedObject.SelectionChanged += DataGridSelectionChanged;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.SelectionChanged -= DataGridSelectionChanged;
}
public IList SelectedItems
{
get { return (IList)GetValue(SelectedItemsProperty); }
set { SetValue(SelectedItemsProperty, value); }
}
public static readonly DependencyProperty SelectedItemsProperty =
DependencyProperty.Register("SelectedItems", typeof(IList), typeof(DataGridSelectedItemsBehavior), new UIPropertyMetadata(null, SelectedItemsChanged));
private static void SelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var behavior = o as DataGridSelectedItemsBehavior;
if (behavior == null || behavior.AssociatedObject==null)
return;
var oldValue = e.OldValue as INotifyCollectionChanged;
var newValue = e.NewValue as INotifyCollectionChanged;
if (oldValue != null)
{
oldValue.CollectionChanged -= behavior.SourceCollectionChanged;
behavior.AssociatedObject.SelectionChanged -= behavior.DataGridSelectionChanged;
}
if (newValue != null)
{
behavior.AssociatedObject.SelectedItems.Clear();
foreach (var item in (IEnumerable)newValue)
{
behavior.AssociatedObject.SelectedItems.Add(item);
}
behavior.AssociatedObject.SelectionChanged += behavior.DataGridSelectionChanged;
newValue.CollectionChanged += behavior.SourceCollectionChanged;
}
}
private bool _isUpdatingTarget;
private bool _isUpdatingSource;
void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_isUpdatingSource)
return;
try
{
_isUpdatingTarget = true;
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
AssociatedObject.SelectedItems.Remove(item);
}
}
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
AssociatedObject.SelectedItems.Add(item);
}
}
if (e.Action == NotifyCollectionChangedAction.Reset)
{
AssociatedObject.SelectedItems.Clear();
}
}
finally
{
_isUpdatingTarget = false;
}
}
private void DataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_isUpdatingTarget)
return;
var selectedItems = this.SelectedItems;
if (selectedItems == null)
return;
try
{
_isUpdatingSource = true;
foreach (var item in e.RemovedItems)
{
selectedItems.Remove(item);
}
foreach (var item in e.AddedItems)
{
selectedItems.Add(item);
}
}
finally
{
_isUpdatingSource = false;
e.Handled = true;
}
}
}
What was fixed?
Added AssociatedObject.SelectionChanged += DataGridSelectionChanged;
to the OnAttached()
, you should access AssociatedObject
first after OnAttached()
was called, otherwise AssociatedObject
is null
Added override OnDetaching()
.
Added behavior.AssociatedObject==null
check to the DP changed call back.
Added e.Handled = true;
to the DataGridSelectionChanged
event handler, in order to stop bubbling the event up, otherwise it comes by parent data grid.
I didn't check the rest of the code, just fixed errors which prevented selection to work.