单独的 DataGrid 绑定到同一个 Observable 集合

问题描述 投票:0回答:1

我有一个带有 TabControl 的 WPF 应用程序,在两个不同的 TabItem 上,我想在每个 TabItem 上的 DataGrid 中显示相同的内容。 操作顺序如下:

  1. 用户从列表框中选择一个信号或一系列信号,并将其添加到同一选项卡上的第一个 DataGrid 中。 同样的信号被添加到另一个选项卡上的第二个 DataGrid,然后用于选择该 DataGrid 中的哪些信号要添加到 LiveGraph。

我做了一些研究,看过很多关于类似问题的帖子,但我也发现了this帖子,它与我想要做的事情最接近,但它仍然对我不起作用。

最初,我犯了一个错误,将两个 DataGrid ItemsSource 直接绑定到同一个 ObservableCollection,导致引发以下异常:“InvalidOperationException:使用 ItemsSource 时操作无效。请改为使用 ItemsControl.ItemsSource 访问和修改元素。 ”

因此,按照上面的链接,我添加了两个 ListCollectionView,并将它们绑定到每个 DataGrid,但是当应用程序首次启动时,仍然会引发相同的异常。 我还尝试在 ItemsSource 绑定上设置不同的模式,但没有成功。

我关注的上述帖子的一个要点是,该帖子上的 DataGrid 没有在 Xaml 中声明 ItemsSource,而是在后面的代码中完成的。我认为我对 Observables 的绑定也会起作用。

如果有人能帮助我找出我在这里缺少的细节,那就太好了,我真的很感激。

这是第一个 DataGrid 声明:

<DataGrid
    x:Name="PeekActionsDataGrid"
    Width="auto"
    HorizontalAlignment="Stretch"
    VerticalAlignment="Center"
    HorizontalContentAlignment="Stretch"
    VerticalContentAlignment="Stretch"
    AutoGenerateColumns="False"
    BorderBrush="Black"
    BorderThickness="1"
    DockPanel.Dock="Top"
    GridLinesVisibility="None"
    HorizontalScrollBarVisibility="Auto"
    IsEnabled="True"
    ItemsSource="{Binding PeekSignalCollectionView}"
    ScrollViewer.CanContentScroll="true"
    ScrollViewer.HorizontalScrollBarVisibility="Visible"
    ScrollViewer.VerticalScrollBarVisibility="Visible"
    VerticalScrollBarVisibility="Auto"/>

这是第二个 DataGrid 声明:

<DataGrid
    x:Name="GraphSignalDataGrid"
    Grid.Row="1"
    Grid.RowSpan="2"
    Grid.Column="2"
    Width="auto"
    MinWidth="50"
    MinHeight="50"
    HorizontalAlignment="Stretch"
    VerticalAlignment="Center"
    HorizontalContentAlignment="Stretch"
    VerticalContentAlignment="Stretch"
    AutoGenerateColumns="False"
    BorderBrush="Black"
    BorderThickness="1"
    DockPanel.Dock="Top"
    GridLinesVisibility="None"
    HorizontalScrollBarVisibility="Auto"
    IsEnabled="True"
    ItemsSource="{Binding PeekGraphCollectionView}"
    ScrollViewer.CanContentScroll="true"
    ScrollViewer.HorizontalScrollBarVisibility="Visible"
    ScrollViewer.VerticalScrollBarVisibility="Visible"
    VerticalScrollBarVisibility="Auto"/>

以下是我在后面的代码中使用的 MVVM ToolKit 项,以使它们对 ViewModel 中的视图可见:

    // holds the dictionary signals selected by the user 
    [ObservableProperty] public static RangeObservableCollection<EcwsPeekSignalModel>? _peekSignalsList = new();
    // trying to use the list above in two different datagrids on two separate tab items
    [ObservableProperty] private ListCollectionView _peekSignalCollectionView = new(_peekSignalsList);
    [ObservableProperty] private ListCollectionView _peekGraphCollectionView = new(_peekSignalsList);

**更新 - 可重现的代码**

使用@Sir Rufo 提供的示例,我删除了应用程序中重现此问题的部分。

主窗口.xaml

<Window
x:Class="DataGridBindingProblem.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:viewModels="clr-namespace:DataGridBindingProblem.ViewModels"
Title="MainWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<Window.DataContext>
    <viewModels:MainViewModel />
</Window.DataContext>
<DockPanel>
    <TabControl>
        <TabItem Header="Var. 1">
            <DataGrid
                Grid.Column="0"
                AutoGenerateColumns="False"
                ItemsSource="{Binding PeekSignalList}">
                <DataGridTextColumn
                    Width="auto"
                    Binding="{Binding SignalName}"
                    Header="Signal Name"
                    IsReadOnly="True">
                    <DataGridColumn.HeaderStyle>
                        <Style TargetType="DataGridColumnHeader">
                            <!--<Setter Property="BorderThickness" Value="0" />-->
                            <Setter Property="Background" Value="LightCyan" />
                            <Setter Property="HorizontalAlignment" Value="Center" />
                            <Setter Property="Padding" Value="5" />
                            <Setter Property="Margin" Value="2" />
                        </Style>
                    </DataGridColumn.HeaderStyle>
                    <DataGridTextColumn.CellStyle>
                        <Style TargetType="DataGridCell">
                            <!--<Setter Property="BorderThickness" Value="0" />-->
                            <Setter Property="IsTabStop" Value="False" />
                            <Setter Property="HorizontalAlignment" Value="Left" />
                            <Setter Property="Padding" Value="5" />
                            <Setter Property="Margin" Value="2" />
                        </Style>
                    </DataGridTextColumn.CellStyle>
                </DataGridTextColumn>
            </DataGrid>
        </TabItem>
        <TabItem Header="Var 2">
            <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding PeekSignalList}">
                <!--
                    Here is where the problem occurs.  In my application the xaml parser isn't
                    providing me with too much info but in this new project it says that the
                    itemsource is in use and to use ItemsControl instead but I am not sure how
                    to do that with a datagrid in this situation.
                -->
                <DataGridTextColumn
                    Width="auto"
                    Binding="{Binding SignalName}"
                    Header="Signal Name"
                    IsReadOnly="True">
                    <DataGridColumn.HeaderStyle>
                        <Style TargetType="DataGridColumnHeader">
                            <!--<Setter Property="BorderThickness" Value="0" />-->
                            <Setter Property="Background" Value="LightCyan" />
                            <Setter Property="HorizontalAlignment" Value="Center" />
                            <Setter Property="Padding" Value="5" />
                            <Setter Property="Margin" Value="2" />
                        </Style>
                    </DataGridColumn.HeaderStyle>
                    <DataGridTextColumn.CellStyle>
                        <Style TargetType="DataGridCell">
                            <!--<Setter Property="BorderThickness" Value="0" />-->
                            <Setter Property="IsTabStop" Value="False" />
                            <Setter Property="HorizontalAlignment" Value="Left" />
                            <Setter Property="Padding" Value="5" />
                            <Setter Property="Margin" Value="2" />
                        </Style>
                    </DataGridTextColumn.CellStyle>
                </DataGridTextColumn>
            </DataGrid>
        </TabItem>
    </TabControl>
</DockPanel>

MainViewModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using DataGridBindingProblem.Models;

using CommunityToolkit.Mvvm.ComponentModel;

using DataGridBindingProblem.Models;

namespace DataGridBindingProblem.ViewModels
{
    public partial class MainViewModel : ObservableObject
    {
        [ObservableProperty] private RangeObservableCollection<EcwsPeekSignalModel> _peekSignalList;

        public MainViewModel()
        {
            PeekSignalList = new();

            PeekSignalList.Add(new("Signal1"));

            PeekSignalList.Add(new("Signal2"));
        }
    }
}

EnumTypes.cs

namespace DataGridBindingProblem.CustomTypes
{
    public enum FccIdEnumType
    {
        LA = 0,   
        LB,    
        RA,    
        RB,     
    }

    public enum SignalDataTypeEnumType
    {
        FLOAT = 0,
        SIGNED,
        UNSIGNED,
    }
}

EcwsPeekSignalModel.cs

using CommunityToolkit.Mvvm.ComponentModel;
using DataGridBindingProblem.CustomTypes;
using Newtonsoft.Json;

namespace DataGridBindingProblem.Models
{
    public partial class EcwsPeekSignalModel : ObservableObject
    {

        #region constructors

        /* no defeault constructor a signal must at least have a name */
        public EcwsPeekSignalModel(string? signalName)
        {
            // current values
            _signalName = signalName;
            _laCurrentValue = "--";
            _lbCurrentValue = "--";
            _raCurrentValue = "--";
            _rbCurrentValue = "--";

            // last values
            LaLastValue = "--";
            LbLastValue = "--";
            RaLastValue = "--";
            RbLastValue = "--";

            LaLogList = new();
            LbLogList = new();
            RaLogList = new();
            RbLogList = new();
        }

        #endregion constructors

        // Used to create records for logging
        public void AddLogEntry(int? logTime,
                                        FccIdEnumType? sLane,
                                        string? sVal,
                                        int? rNum, bool? recordFlag = false)
        {
            switch(sLane)
            {
                case FccIdEnumType.LA:
                {
                    LaLastValue = LaCurrentValue;
                    LaCurrentValue = sVal;

                    // only create a log entry if recording is true
                    if(recordFlag == true)
                    {
                        // store the current value as history
                        LaLogList?.Add(new SignalLogEntry(logTime, sLane, sVal, rNum));
                    }

                    break;
                }
                case FccIdEnumType.LB:
                {
                    LbLastValue = LbCurrentValue;
                    LbCurrentValue = sVal;

                    // only create a log entry if recording is true
                    if(recordFlag == true)
                    {
                        // store the current value as history
                        LbLogList?.Add(new SignalLogEntry(logTime, sLane, sVal, rNum));
                    }

                    break;
                }
                case FccIdEnumType.RA:
                {
                    RaLastValue = RaCurrentValue;
                    RaCurrentValue = sVal;

                    // only create a log entry if recording is true
                    if(recordFlag == true)
                    {
                        // store the current value as history
                        RaLogList?.Add(new SignalLogEntry(logTime, sLane, sVal, rNum));

                    }
                    break;
                }
                case FccIdEnumType.RB:
                {
                    RbLastValue = RbCurrentValue;
                    RbCurrentValue = sVal;

                    // only create a log entry if recording is true
                    if(recordFlag == true)
                    {
                        // store the current value as history
                        RbLogList?.Add(new SignalLogEntry(logTime, sLane, sVal, rNum));

                    }
                    break;
                }
                case null:
                {
                    break;
                }
            }
        }

        // clears the log list and resets values
        public void ClearLogLists()
        {
            RemoveSignal = false;

            LaCurrentValue = "--";
            LbCurrentValue = "--";
            RaCurrentValue = "--";
            RbCurrentValue = "--";

            LaLastValue = LaCurrentValue;
            LbLastValue = LbCurrentValue;
            RbLastValue = RaCurrentValue;
            RaLastValue = RaCurrentValue;

            LaLogList?.Clear();
            LbLogList?.Clear();
            RaLogList?.Clear();
            RbLogList?.Clear();
        }

        #region LogLists

        [Newtonsoft.Json.JsonIgnore]
        private List<SignalLogEntry>? _laLogList;

        [Newtonsoft.Json.JsonIgnore]
        internal List<SignalLogEntry>? LaLogList { get => _laLogList; set => _laLogList = value; }

        [Newtonsoft.Json.JsonIgnore]
        private List<SignalLogEntry>? _lbLogList;

        [Newtonsoft.Json.JsonIgnore]
        internal List<SignalLogEntry>? LbLogList { get => _lbLogList; set => _lbLogList = value; }

        [Newtonsoft.Json.JsonIgnore]
        private List<SignalLogEntry>? _raLogList;

        [Newtonsoft.Json.JsonIgnore]
        internal List<SignalLogEntry>? RaLogList { get => _raLogList; set => _raLogList = value; }

        [Newtonsoft.Json.JsonIgnore]
        private List<SignalLogEntry>? _rbLogList;

        [Newtonsoft.Json.JsonIgnore]
        internal List<SignalLogEntry>? RbLogList { get => _rbLogList; set => _rbLogList = value; }

        #endregion LogLists

        #region jsonProperties


        //public string? SignalName { get; set; }
        [JsonProperty(Order = 1, PropertyName = "")]
        [ObservableProperty] private string? _signalName;

        #endregion jsonProperties

        [Newtonsoft.Json.JsonIgnore]
        [ObservableProperty] private SignalDataTypeEnumType? _signalDataType;

        [Newtonsoft.Json.JsonIgnore]
        [ObservableProperty] private bool? _removeSignal;

        // variables for historical data used when expBorting the log data
        [Newtonsoft.Json.JsonIgnore]
        [ObservableProperty] private string? _laCurrentValue;
        [Newtonsoft.Json.JsonIgnore]
        [ObservableProperty] private string? _lbCurrentValue;
        [Newtonsoft.Json.JsonIgnore]
        [ObservableProperty] private string? _raCurrentValue;
        [Newtonsoft.Json.JsonIgnore]
        [ObservableProperty] private string? _rbCurrentValue;

        // variables for historical data used when exporting the log data
        [Newtonsoft.Json.JsonIgnore]
        [ObservableProperty] private string? _laLastValue;
        [Newtonsoft.Json.JsonIgnore]
        [ObservableProperty] private string? _lbLastValue;
        [Newtonsoft.Json.JsonIgnore]
        [ObservableProperty] private string? _raLastValue;
        [Newtonsoft.Json.JsonIgnore]
        [ObservableProperty] private string? _rbLastValue;
    }
}

SignalLogEntry.cs

using System.Diagnostics.CodeAnalysis;

namespace DataGridBindingProblem.CustomTypes
{
    internal class SignalLogEntry
    {
        /* since this is a log entry I do not want to allow a default constructor
         * a logentry must have these items
         */
        private int? _rtcTime;
        private FccIdEnumType? _signalLane;
        private string? _signalValue;
        private int? _runNUm;

        [SetsRequiredMembers]
        public SignalLogEntry(int? rtc, FccIdEnumType? sLane, string? sVal, int?rNum)
        {
            _rtcTime = rtc;
            _signalLane = sLane;
            _signalValue = sVal;
            _runNUm = rNum;
        }

        // returns an array of strings for logging to excel
        public string[] GetLogArray()
        {
            return new string[] { $"{_rtcTime}", 
                $"{_signalValue}"};
        }

        public int? GetRtcTime()
        {
            return _rtcTime;
        }

        public FccIdEnumType? GetSignalLane()
        {
            return _signalLane;
        }

        public string? GetValue()
        {
            return _signalValue;
        }

        // returns a string representation of this log entry objects values
        public string GetLog()
        {
            return $"'{_rtcTime},{_signalValue},";
        }

        public int? GetLogEntryTime()
        {
            return _rtcTime;
        }

        public int? GetRunNum()
        {
            return (int?) _runNUm;
        }
    }
}

RangeObservableCollection.cs

using System.Collections.ObjectModel;
using System.Collections.Specialized;

namespace DataGridBindingProblem.Models
{
    public class RangeObservableCollection<T> : ObservableCollection<T>
    {
        private bool _suppressNotification = false;

        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (_suppressNotification == false)
            {
                base.OnCollectionChanged(e);
            }
        }

        public void AddRange(IEnumerable<T> list)
        {
            if (list == null)
            {
                throw new ArgumentNullException("list");           
            }
                

            _suppressNotification = true;

            foreach (T item in list)
            {
                Add(item);
            }
            _suppressNotification = false;

            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    }
}

所以我已经找到了它发生的位置,但不确定如何解决它。 当 AutoGenerateColumns 为 true 并且 DataGrid TextColumns 被删除时,应用程序会运行,但我不需要任一表中的所有自动生成的列。

wpf mvvm data-binding
1个回答
0
投票

检查您的代码后,我发现您的标记中有错误。

您有两种方法来填充 ItemsSource:

  • 通过
    ItemsSource
    属性
  • 或通过
    Items
    属性

ItemsControl 无法同时处理这两种情况,因此您必须决定一种方式,否则您会收到已发布的异常

“InvalidOperationException:使用 ItemsSource 时操作无效。请改为使用 ItemsControl.ItemsSource 访问和修改元素。” 重要的是要知道你不能

您也可以使用集合语法和 content 属性将项目添加到 XAML 中的

ItemsControl.Items
属性(
ItemsControl
Items
属性定义为 content 属性):

<ListBox>
  <ListBoxItem />
</ListBox>

因为内容属性是

ItemsControl.Items
,所以
ListBoxItem
被隐式添加到
ListBox.Items
属性。

这正是你所做的(可能是偶然的):

<DataGrid AutoGenerateColumns="False"
          ItemsSource="{Binding PeekSignalList}">
  <DataGridTextColumn Width="auto"
                      Binding="{Binding SignalName}"
                      Header="Signal Name"
                      IsReadOnly="True">
  </DataGridTextColumn>
</DataGrid>

同时,您正在绑定

DataGrid.ItemsSource
,这会导致 XAML 解析器抛出异常。

但是,

DataGrid
的项目容器是
DataGridRow
。因此,前面的 XAML 在语义上是错误的,因为您将
DataGridColumn
对象添加到
Items
属性。

如果您没有使用

ItemsSource
属性,正确的 XAML 将如下所示:

<DataGrid AutoGenerateColumns="False">
    <DataGridRow />
  </DataGridTextColumn>
</DataGrid>

但是,重点是

DataGridColumn
对象必须添加到
DataGrid.Columns
集合中。将它们添加到
Items
集合中是没有意义的,因为它们不是项目容器(我认为这是偶然发生的,不知何故)。它们只是告诉
DataGrid
表格的结构的布局元素。

固定的 XAML 如下所示:

<DataGrid AutoGenerateColumns="False"
          ItemsSource="{Binding PeekSignalList}">
  <DataGrid.Columns> <!-- Add DatGridColumns to the DataGrid.Columns collection -->
    <DataGridTextColumn Width="auto"
                        Binding="{Binding SignalName}"
                        Header="Signal Name"
                        IsReadOnly="True">
    </DataGridTextColumn>
  </DataGrid.Columns>
</DataGrid>
© www.soinside.com 2019 - 2024. All rights reserved.