Wpf Interaction Behavior in nested DataGrids MVVM pattern

huangapple go评论71阅读模式
英文:

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&#39;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&#39;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=&quot;http://schemas.microsoft.com/xaml/behaviors&quot;

        &lt;DataGrid Name=&quot;MainDataGrid&quot;
                  AutoGenerateColumns=&quot;False&quot;
                  HorizontalScrollBarVisibility=&quot;Disabled&quot;
                  Height=&quot;Auto&quot; 
                  ItemsSource=&quot;{Binding ObCol_Model}&quot;
                  VerticalAlignment=&quot;Stretch&quot; CanUserAddRows=&quot;false&quot; BorderThickness=&quot;1&quot;
                  AlternatingRowBackground=&quot;#FFFFFFCC&quot;
                  HorizontalGridLinesBrush=&quot;#FFA0A0A0&quot;
                  VerticalGridLinesBrush=&quot;#FFA0A0A0&quot;
                  SelectionUnit=&quot;FullRow&quot;
                  HeadersVisibility=&quot;Column&quot;
                  GridLinesVisibility=&quot;Horizontal&quot;
                  ColumnHeaderHeight=&quot;25&quot; IsReadOnly=&quot;True&quot; CanUserResizeRows=&quot;False&quot; RowHeight=&quot;22&quot; VerticalContentAlignment=&quot;Center&quot;
                  BorderBrush=&quot;DarkGray&quot; HorizontalAlignment=&quot;Left&quot;
                  RowDetailsVisibilityMode=&quot;VisibleWhenSelected&quot;&gt;
            
            &lt;i:Interaction.Behaviors&gt;
                &lt;local:DataGridSelectedItemsBehavior SelectedItems=&quot;{Binding SelectedItems}&quot; /&gt;
            &lt;/i:Interaction.Behaviors&gt;
            
            &lt;DataGrid.Columns&gt;
                &lt;DataGridTextColumn Header=&quot;Code&quot; Binding=&quot;{Binding Code}&quot; Width=&quot;80&quot;/&gt;
                &lt;DataGridTextColumn Header=&quot;Name&quot; Binding=&quot;{Binding Name}&quot; Width=&quot;*&quot;/&gt;
            &lt;/DataGrid.Columns&gt;
            
            &lt;DataGrid.RowDetailsTemplate&gt;
                &lt;DataTemplate&gt;
                        &lt;DataGrid Name=&quot;SubDataGrid&quot;
                                  Margin=&quot;10,0,0,0&quot; AutoGenerateColumns=&quot;False&quot; ItemsSource=&quot;{Binding ObCol_SubModel}&quot;
                                  Height=&quot;Auto&quot; Width=&quot;auto&quot;
                                  VerticalAlignment=&quot;Stretch&quot; CanUserAddRows=&quot;false&quot; BorderThickness=&quot;1&quot;
                                  AlternatingRowBackground=&quot;#FFFFFFCC&quot;
                                  HorizontalGridLinesBrush=&quot;#FFA0A0A0&quot;
                                  VerticalGridLinesBrush=&quot;#FFA0A0A0&quot;
                                  SelectionUnit=&quot;FullRow&quot;
                                  HeadersVisibility=&quot;Column&quot;
                                  GridLinesVisibility=&quot;Horizontal&quot;
                                  ColumnHeaderHeight=&quot;25&quot; IsReadOnly=&quot;True&quot; CanUserResizeRows=&quot;False&quot; RowHeight=&quot;22&quot; VerticalContentAlignment=&quot;Center&quot;
                                  BorderBrush=&quot;DarkGray&quot; HorizontalAlignment=&quot;Left&quot;&gt;

                        &lt;i:Interaction.Behaviors&gt;
                            &lt;local:DataGridSelectedItemsBehavior SelectedItems=&quot;{Binding SubSelectedItems}&quot; /&gt;
                        &lt;/i:Interaction.Behaviors&gt;
                            
                        &lt;DataGrid.Style&gt;
                            &lt;Style TargetType=&quot;{x:Type DataGrid}&quot;&gt;
                                &lt;Setter Property=&quot;Visibility&quot; Value=&quot;Visible&quot;/&gt;
                                &lt;Style.Triggers&gt;
                                    &lt;DataTrigger Binding=&quot;{Binding ObCol_SubModel}&quot; Value=&quot;{x:Null}&quot;&gt;
                                        &lt;Setter Property=&quot;Visibility&quot; Value=&quot;Collapsed&quot;/&gt;
                                    &lt;/DataTrigger&gt;
                                &lt;/Style.Triggers&gt;
                            &lt;/Style&gt;
                        &lt;/DataGrid.Style&gt;

                        &lt;DataGrid.Columns&gt;
                            &lt;DataGridTextColumn Header=&quot;Detail&quot; Binding=&quot;{Binding Detail}&quot; Width=&quot;80&quot;/&gt;
                            &lt;DataGridTextColumn Header=&quot;Detail Name&quot; Binding=&quot;{Binding Name}&quot; Width=&quot;150&quot;/&gt;
                            &lt;DataGridTextColumn Header=&quot;Comment&quot; Binding=&quot;{Binding Comment}&quot; Width=&quot;*&quot;/&gt;
                        &lt;/DataGrid.Columns&gt;
                    &lt;/DataGrid&gt;
                &lt;/DataTemplate&gt;
            &lt;/DataGrid.RowDetailsTemplate&gt;
        &lt;/DataGrid&gt;

**THE BEHAVIOR**

    public class DataGridSelectedItemsBehavior : Behavior&lt;DataGrid&gt;
    {
        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(&quot;SelectedItems&quot;, 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&lt;Model&gt; myObCol_Model;
        private ObservableCollection&lt;Model&gt; mySelectedItems;

        public ViewModel()
        {
            mySelectedItems = new ObservableCollection&lt;Model&gt;();

            myObCol_Model = new ObservableCollection&lt;Model&gt;()
            {
                new Model(&quot;100&quot;, &quot;Hundred&quot;),
                new Model(&quot;200&quot;, &quot;Two Hundred&quot;),
                new Model(&quot;300&quot;, &quot;Three Hundred&quot;)
            };

            var item = new Model(&quot;400&quot;, &quot;Four Hundred&quot;);
            item.AddSubModel(new SubModel(&quot;10&quot;, &quot;Ten&quot;, &quot;sub 10&quot;));
            item.AddSubModel(new SubModel(&quot;20&quot;, &quot;Twenty&quot;, &quot;sub 20&quot;));
            item.AddSubModel(new SubModel(&quot;30&quot;, &quot;Thirty&quot;, &quot;sub 30&quot;));

            myObCol_Model.Add(item);
        }

        public ObservableCollection&lt;Model&gt; ObCol_Model { get { return myObCol_Model; } }

        public ObservableCollection&lt;Model&gt; SelectedItems
        {
            get { return mySelectedItems; }
            set
            {
                if (mySelectedItems == value) return;

                mySelectedItems = value;
                OnPropertyChanged(nameof(SelectedItems));
            }
        }
    }

**THE MODELS**

    public class Model : ObservableObject
    {
        private ObservableCollection&lt;SubModel&gt; myObCol_SubModel;
        private ObservableCollection&lt;SubModel&gt; mySubModelSelectedItems;

        public Model(string code, string name)
        {
            mySubModelSelectedItems = new ObservableCollection&lt;SubModel&gt;();

            Code = code;
            Name = name;
        }

        public string Code { get; set; }
        public string Name { get; set; }
        public ObservableCollection&lt;SubModel&gt; ObCol_SubModel { get { return myObCol_SubModel; } }

        public void AddSubModel(SubModel subModel)
        { 
            if (myObCol_SubModel == null) myObCol_SubModel = new ObservableCollection&lt;SubModel&gt;();
            myObCol_SubModel.Add(subModel);
        }

        public ObservableCollection&lt;SubModel&gt; 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&lt;DataGrid&gt;
{
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(&quot;SelectedItems&quot;, 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.

huangapple
  • 本文由 发表于 2023年2月27日 03:08:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/75574382.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定