У меня есть приложение WPF с TabControl, и на двух разных элементах TabItem я хочу отображать один и тот же контент в DataGrid на каждом из них. Порядок действий следующий:
Я провел небольшое исследование и видел много сообщений о подобных проблемах, но я также нашел этот пост, который наиболее точно соответствует тому, что я пытаюсь сделать, но он все равно у меня не работает.
Первоначально я допустил ошибку, напрямую привязав оба DataGrid ItemsSource к одной и той же ObservableCollection, что привело к возникновению следующего исключения: «InvalidOperationException: операция недействительна, пока используется ItemsSource. Вместо этого обращайтесь к элементам и изменяйте их с помощью ItemsControl.ItemsSource».
Итак, следуя приведенной выше ссылке, я добавил два ListCollectionViews и привязал их к каждому DataGrid, но это же исключение все еще возникает при первом запуске приложения. Я также безуспешно пытался установить различные режимы для привязок ItemsSource.
В сообщении выше, за которым я следил, есть одна особенность: DataGrids в этом сообщении не объявляют ItemsSource в Xaml, но это делается в коде позади. Я предполагал, что моя привязка к 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, я удалил части своего приложения, которые воспроизводят эту проблему.
MainWindow.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,
}
}
ЭквсПикСигналМодел.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;
}
}
Сигналлогэнтри.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 удаляется, приложение запускается, но мне не нужны все автоматически сгенерированные столбцы ни в одной таблице.
Вы можете привязать к коллекции столько элементов управления ItemControl, сколько захотите. Исключение выдается, поскольку вы где-то изменяете (добавляете/удаляете/очищаете/перемещаете) ItemsControl через свойство ItemsControl.Items, что является незаконным, если возвращаемое представление коллекции было создано из значения ItemsSource.
@SirRufo спасибо за ссылку. В моем XAML я устанавливал DataContext MainView в MainViewModel. В вашем случае вы делаете это в коде, а также создаете объект MainViewModel. Когда я попытался имитировать остальную часть вашего примера, я не увидел, чтобы значения в первом DataGrid отображались во втором, но я думаю, что поработаю над рефакторингом с этим новым DataContext, а затем посмотрю, что произойдет. Я обновлю пост, когда закончу и получу больше результатов. Спасибо!
@MichaelRiley Это вообще не имеет значения. я это тоже проверил
@SirRufo да, ты прав. Я также загрузил и изменил ваше решение, и оно работает, но мое все еще не работает.
@BionicCode Да, я понимаю, о чем ты говоришь. В моем коде, когда существовал только первый DataGrid, я выполнял очистку PeekSignalList, но при загрузке формы единственное, что я делал, — это создавал экземпляр PeekSignalList с помощью new(). В исключении говорится: «Вместо этого получайте доступ и изменяйте элементы с помощью ItemsControl.ItemsSource», поэтому я не понимаю, относится ли это к коду позади или к XAML. Второму DataGrid нужны только данные, содержащиеся в списке PeekSignal, и он не будет изменяться. Я попытался изменить второй, чтобы он имел <ItemsControl ItemsSource = "{Binding PeekSignalsList}" />, но и там ничего не понравилось.
Если вы вызываетеclear непосредственно в исходной коллекции, значит, вы делаете это правильно. Вы не можете вызвать ItemsControl.Items.Clear. Также не создавайте дополнительных затрат на регистрацию этих ListCollectionViews, если только вам не нужно сортировать/фильтровать каждое представление коллекции независимо. Не могли бы вы опубликовать минимальный воспроизводимый пример, пожалуйста? Таким образом, мы сможем просмотреть ваш код. Трудно или невозможно догадаться, где вы ошиблись. XAML действительно недостаточно. XAML здесь даже не очень уместен. Гораздо интереснее, как вы обрабатываете коллекции и представления коллекций.
Было бы здорово, если бы вы также могли показать строку кода, вызывающую исключение. Если вы обращаетесь за помощью, всегда следует предоставлять сообщение об исключении и контекст. Дело в том, что мы ничего не знаем о деталях вашей реализации.
Вопрос @BionicCode обновлен по запросу. простите за опоздание
Спасибо за пример. Теперь я могу правильно просмотреть ваш код.
Просмотрев ваш код, я обнаружил ошибку в вашей разметке.
У вас есть два варианта заполнить ItemsControl
:
ItemsSource
Items
ItemsControl
не может обработать оба варианта, поэтому вам придется выбрать один из способов, иначе парсер XAML выдаст опубликованное вами исключение:
«InvalidOperationException: операция недопустима, пока используется ItemsSource. Вместо этого обращайтесь к элементам и изменяйте их с помощью ItemsControl.ItemsSource». важно знать, что ты не можешь
Вы также можете добавлять элементы в свойство ItemsControl.Items
в XAML, используя синтаксис коллекции и свойство содержимого (у ItemsControl
свойство Items
определено как свойство содержимого):
<ListBox>
<ListBoxItem />
</ListBox>
Поскольку свойство содержимого — ItemsControl.Items
, ListBoxItem
из предыдущего примера неявно добавляется к свойству ListBox.Items
.
Вы сделали именно это (вероятно случайно):
<DataGrid AutoGenerateColumns = "False"
ItemsSource = "{Binding PeekSignalList}"> <!-- ItemsSource is used... -->
<DataGridTextColumn Width = "auto"
Binding = "{Binding SignalName}"
Header = "Signal Name"
IsReadOnly = "True"> <!-- ...At the same time, this object is added to the Items property -->
</DataGridTextColumn>
</DataGrid>
В то же время вы привязываете DataGrid.ItemsSource
, что приводит к выдаче синтаксического анализатора XAML.
Однако контейнером элемента DataGrid
является DataGridRow
. Следовательно, предыдущий XAML семантически неверен, поскольку вы добавляли объекты DataGridColumn
к свойству Items
(вместо объектов DataGridRow
).
Если бы вы не использовали свойство ItemsSource
, правильный XAML был бы следующим:
<DataGrid AutoGenerateColumns = "False">
<DataGridRow />
</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>
@BionicCode...Друг мой, я очень ценю время и усилия, потраченные на то, чтобы прояснить это для меня. Когда я впервые использовал первый DataGrid, это было около 8 месяцев назад и раньше я его не использовал. Итак, вы были очень правы в том, что произошло вырезание и вставка или какая-то другая случайность, но это сработало, поэтому я оставил это позади. Глядя на ваше решение моей проблемы, я абсолютно вижу и согласен с ним. Еще раз спасибо!
Привет, Майкл, я рад, что смог помочь. И Вам огромное спасибо за высокую оценку и добрые слова! Спасибо. Хорошего дня!
Я не могу это воспроизвести gist.github.com/SirRufo/39b5903c8adc116e962572405b5c8af2