我有一个带有 TabControl 的 WPF 应用程序,在两个不同的 TabItem 上,我想在每个 TabItem 上的 DataGrid 中显示相同的内容。 操作顺序如下:
我做了一些研究,看过很多关于类似问题的帖子,但我也发现了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 被删除时,应用程序会运行,但我不需要任一表中的所有自动生成的列。
检查您的代码后,我发现您的标记中有错误。
您有两种方法来填充 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>