英文:
Wpf Interaction Behavior in nested DataGrids MVVM pattern
问题
我已翻译你提供的代码部分,如下所示:
我有一个简单的`DataGrid`,其中`RowDetailsTemplate`的`DataTemplate`也是一个`DataGrid`。
使用一个`behavior`以双向绑定模式将`DataGrid`的`SelectedItems`绑定到`ViewModel`中。
该`behavior`同时用于主`DataGrid`和主`DataGrid`行的详细信息。
我面临的问题是,子`DataGrids`中的`behaviors`似乎永远不会被引用,子`DataGrid`中的每个`DataGrid.SelectionChanged`事件总是引用主`DataGrid`中的`behavior`。
**视图**
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>
**行为(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;
}
}
}
**视图模型(ViewModel)**
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
<details>
<summary>英文:</summary>
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; }
}
</details>
# 答案1
**得分**: 1
下面是我翻译的代码部分:
```csharp
以下是我如何解决的问题:
- 在`OnAttached()`中添加了`AssociatedObject.SelectionChanged += DataGridSelectionChanged;`,应该在调用`OnAttached()`后首先访问`AssociatedObject`,否则`AssociatedObject`为`null`。
- 添加了`override OnDetaching()`。
- 在依赖属性更改回调中添加了`behavior.AssociatedObject==null`检查。
- 在`DataGridSelectionChanged`事件处理程序中添加了`e.Handled = true;`,以阻止事件冒泡,否则它将被父数据网格捕获。
我没有检查代码的其余部分,只修复了阻止选择工作的错误。
请注意,这是您提供的代码的翻译部分,不包括问题或其他内容。如果您有任何其他需求或问题,可以告诉我。
英文:
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 theOnAttached()
, you should accessAssociatedObject
first afterOnAttached()
was called, otherwiseAssociatedObject
isnull
-
Added
override OnDetaching()
. -
Added
behavior.AssociatedObject==null
check to the DP changed call back. -
Added
e.Handled = true;
to theDataGridSelectionChanged
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论