将DataGridColumns与多个ObservableCollections列表(WPF)绑定



我遇到了一个问题,尝试通过搜索解决问题没有成功。因此,我在这里尝试,并提供具体帮助:我有一个带有不同选项卡的主窗口应用程序。一个选项卡应该有一个包含4列的DataGrid。我想用对象列表来填充它们。我在MainWindow.xaml.cs:中的实现

// 4 times this kind of Code
private ObservableCollection<bool> _StatusBitsSimulateActiveList = null;
private ObservableCollection<string> _StatusBitsNameList = null;
private ObservableCollection<bool> _ActualStatusBitsList = null;
private ObservableCollection<bool> _SimulatedStatusBitsList = null;
public ObservableCollection<bool> StatusBitsSimulateActiveList
{
get
{
List<bool> list = Manager._StatusBits.GetSimulatedStatusBitsList();
_StatusBitsSimulateActiveList = new ObservableCollection<bool>(list);
return _StatusBitsSimulateActiveList;
}
set
{
_StatusBitsSimulateActiveList = value;
OnPropertyChanged();
}
}

MainWindow.xaml文件包含:

<DataGrid x:Name="SimulatedBitsDataGrid" MinRowHeight="25" AutoGenerateColumns="False"
Margin="5" AlternatingRowBackground="LightBlue" AlternationCount="2" Grid.Row="3" Grid.Column="0" ItemsSource="{Binding}">
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding StatusBitsSimulateActiveList, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Header="Simulation Active"/>
<DataGridTemplateColumn Width="*" Header="Status Bits Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<Binding Mode="TwoWay" Path="StatusBitsNameList"></Binding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridCheckBoxColumn Binding="{Binding ActualStatusBitsList, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Header="Status Bit SPS"/>
<DataGridCheckBoxColumn Binding="{Binding SimulatedStatusBitsList, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Header="Simulated Value"/>
</DataGrid.Columns>
</DataGrid>

我有一个类,其中存储和检索数据(请参阅Manager_StatusBits(,并使用一些方法来获取和设置数据。DataGrid未填充数据。有人能帮忙吗?

我尝试了ItemsSource="绑定";然后绑定到列表。不起作用。浏览了一下互联网,没有找到解决方案,也许我不了解其中的机制。我对wpf很陌生,只研究过C++和mfc。

编辑:所以我尝试了一些答案,现在了解了更多。但有一个问题我无法解决。我没有尝试过那些类型的ViewModel(也许这会有所帮助,现在不会尝试(。这是我的代码,它是单向工作的,但不是双向工作的,因为没有注意到GUI和网格的交互。缺少什么?注意,这段代码部分是别人写的,我试图解决一些问题并添加一些功能:

主窗口.xaml.cs

InitializeComponent();
this.DataContext = this; // Why do someone need to do this? Because of relativeSource Binding? see .xaml file
StatusBitDataGrid.DataContext = Manager._StatusBits.dataList; // there is a manager, which manages the objects, the Manager is created in the MainWindow

主窗口.xaml

<Window x:Class="APP.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"
mc:Ignorable="d"
Title="EFEM-Client Plus+" Height="900" Width="1200"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
WindowStartupLocation="CenterScreen"
Closing="OnWindowClosing"
x:Name="window1"
>
<DataGrid x:Name="StatusBitDataGrid" Grid.Row="2" Grid.RowSpan="3" Grid.Column="0" Grid.ColumnSpan="4" 
ItemsSource="{Binding}" CanUserAddRows="False" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn CanUserSort="False" Binding="{Binding SimulateActive, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Header="Simulation Active"/>
<DataGridTemplateColumn Width="*" Header="Status Bits Name" CanUserSort="False">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock>
<TextBlock.Text>
<Binding Mode="TwoWay" Path="StatusBitsName"></Binding>
</TextBlock.Text>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridCheckBoxColumn Binding="{Binding ActualBitValue, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Header="Status Bit SPS" CanUserSort="False" IsReadOnly="True"/>
<DataGridCheckBoxColumn Binding="{Binding SimulatedBitValue, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Header="Simulated Value" CanUserSort="False"/>
</DataGrid.Columns>
</DataGrid>

StatusBits.cs

public class StatusBits {
[...]
public StatusBits()
{
[...]
PopulateObservable();
}
[...]
private ObservableCollection<StatusBitsSingleData> _dataList = new ObservableCollection<StatusBitsSingleData>();
public ObservableCollection<StatusBitsSingleData> dataList
{
get { return _dataList; }
set { _dataList = value; } // Code and Style never enters the setter...
}
private void PopulateObservable()
{
dataList.Clear();
for (int i = 0; i < maxSemDexStatus; i++)
{
dataList.Add(new StatusBitsSingleData(StatusBitsSimulateActive[i], StatusBitsName[i], ActualStatusBits[i], SimulatedStatusBits[i]));
}
}
[...]
// The SingleData Class, here the setter is used by the program
public class StatusBitsSingleData
{
public bool SimulateActive { get; set; }
public string StatusBitsName { get; set; }
public bool ActualBitValue { get; set; }
public bool SimulatedBitValue { get; set; }
public StatusBitsSingleData(bool simA, string name, bool actu, bool sim)
{
SimulateActive = simA; StatusBitsName = name; ActualBitValue = actu; SimulatedBitValue = sim;
}
}
}

因为setter是在公共类StatusBitsSingleData中使用的,所以我试图制作一个自定义的Notifier,但失败了。。。

有几个问题需要说明。

  • DataGrid只能绑定到一个ObservableCollection
  • DataGrid.Column绑定到类的单个属性包含在ObservableCollection中的类型
  • ObservableCollection实现了INotifyProperty接口,因此在绑定时不需要进一步的代码支持

我不确定您试图对代码做什么,但在第一点上,我会将您希望在DataGrid.Row中显示的所有数据合并为一个单一的类类型,并拥有一个包括该类型的ObservableCollection的视图模型,例如

public class StatusBitClass 
{
public bool SimulatedActive { get; set; }
public string Name { get; set; }
public bool ActualStatus { get; set; }
public bool SimulatedStatus { get; set; }
}

我不建议使用ObservableCollection的getter来填充其数据源,而是使用ViewMode函数来填充它,这里我使用RelayCommand(ICommand实现(,但您可以链接到一些自动数据源,例如web API或数据库。

因此,这里有一个合适的ViewModel——注意IStatusBitsDataViewModel是一种接口类型,它只定义StatusBitsDataViewModel的公共部分。我还没有包括它,但稍后我将解释为什么我使用定义的接口。

public class StatusBitsDataViewModel:IStatusBitsDataViewModel
{
public StatusBitsDataViewModel()
{
PopulateListCommand = new RelayCommand(PopulateListData);
StatusBitClasses = new ObservableCollection<StatusBitClass>();
}
public ObservableCollection<StatusBitClass> StatusBitClasses { get; set; }
public RelayCommand PopulateListCommand { get; }
//This function populates the ObservableCollection with test data
// replace it with one that uses your real data source
private void PopulateListData(object o)
{
var data = new List<StatusBitClass>()
{
new StatusBitClass(){ActualStatus = false, Name = "Status 1", SimulatedActive = false, SimulatedStatus = false},
new StatusBitClass(){ActualStatus = true, Name = "Status 2", SimulatedActive = false, SimulatedStatus = true},
new StatusBitClass(){ActualStatus = false, Name = "Status 3", SimulatedActive = true, SimulatedStatus = false},
new StatusBitClass(){ActualStatus = true, Name = "Status 4", SimulatedActive = false, SimulatedStatus = true},
new StatusBitClass(){ActualStatus = false, Name = "Status 5", SimulatedActive = true, SimulatedStatus = false},
new StatusBitClass(){ActualStatus = true, Name = "Status 6", SimulatedActive = false, SimulatedStatus = true},
new StatusBitClass(){ActualStatus = false, Name = "Status 7", SimulatedActive = true, SimulatedStatus = false},
};
foreach (var entry in data)
{
StatusBitClasses.Add(entry);
}
}
}

现在,您的View将需要对ViewModel进行DataContext设置,我倾向于在Views代码隐藏文件中这样做,例如与之相关的.cs文件,如果只是因为我可以使用依赖项注入并将具体类型传递给它,从而在运行时更改行为,例如,在使用使用真实数据源的ViewModel或使用预定义测试数据的ViewModel之间切换,比如单元测试。

因此,背后的代码是:

/// <summary>
/// Interaction logic for StatusDataWindow.xaml
/// </summary>
public partial class StatusDataWindow : Window
{
public StatusDataWindow(IStatusBitsDataViewModel viewModel)
{
InitializeComponent();
DataContext = new StatusBitsDataViewModel(); //forcing to use concrete type
}
}

以下是示例窗口的XAML,该窗口演示了数据绑定以及如何用UI中的数据触发DataGrid的填充。

<Window x:Class="WpfApp1.StatusDataWindow"
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:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="StatusDataWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="200"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid Grid.Row="0"
ItemsSource="{Binding StatusBitClasses}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="Auto" Binding="{Binding Name }"></DataGridTextColumn>
<DataGridTextColumn Header="Simulated Active" Width="Auto" Binding="{Binding SimulatedActive }"></DataGridTextColumn>
<DataGridTextColumn Header="Actual Status" Width="Auto" Binding="{Binding ActualStatus }"></DataGridTextColumn>
<DataGridTextColumn Header="Simulated Status" Width="Auto" Binding="{Binding SimulatedStatus }"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<Button Grid.Row="1" Content="Fill" Width="40" HorizontalAlignment="Center" Margin="40"  Command="{Binding PopulateListCommand}" />
</Grid>
</Window>

我希望这能有所帮助。

编辑当您现在询问更改DataGrid中的数据并将其传递回来时,您需要进行轻微的更改,以便更改数据的操作实际触发绑定类中的数据更改。

让我们更改数据列类,使该部分现在显示为:

<DataGrid.Columns>
<DataGridTextColumn Header="Name" Width="Auto" Binding="{Binding Name, Mode=OneWay }"/>
<DataGridCheckBoxColumn Header="Simulated Active" Width="Auto" Binding="{Binding SimulatedActive, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged }"/>
<DataGridCheckBoxColumn Header="Actual Status" Width="Auto" Binding="{Binding ActualStatus, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged }"/>
<DataGridCheckBoxColumn Header="Simulated Status" Width="Auto" Binding="{Binding SimulatedStatus, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged }"/>
</DataGrid.Columns>

使用布尔值时,显示和更改复选框值比更改文本更容易。

指定何时触发绑定属性的更新事件是这里的关键。(提示在StatusBitClass属性setter上放置断点以证明其有效(。

如果您希望以编程方式更改StatusBitClass属性值,并在DataGrid中进行更新,则需要确保这些属性支持INotifyPropertyChanged

您可以创建一个单独的ViewModel类,或者只让StatusBitClass实现INotifyPripertyChanged

public class StatusBitClass: INotifyPropertyChanged 
{
private bool _simulatedActive;
private bool _actualStatus;
private bool _simulatedStatus;
public bool SimulatedActive 
{ 
get =>_simulatedActive;
set => SetField(ref _simulatedActive, value);
}
public string Name { get; set; }
public bool ActualStatus
{
get => _actualStatus;
set => SetField(ref _actualStatus, value);
}
public bool SimulatedStatus
{
get => _simulatedStatus;
set => SetField(ref _simulatedStatus, value);
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}

现在,如果在将StatusBitClass属性值添加到ObservableCollection之后更改该属性值,则会在DataGrid中看到值更新,如果没有它,则需要通过其他方式强制刷新窗口。

所以我认为我对wpf的问题有一个基本的误解。因此,我尝试了一个反映首要问题的基本项目:MainWindow.xaml包含

<Window x:Class="WPF_DataGrid_Binding.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"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<DataGrid x:Name="DataGridObject" AlternatingRowBackground="LightBlue" AlternationCount="2" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False"
>
<DataGrid.Columns>
<DataGridCheckBoxColumn CanUserSort="False" Binding="{Binding _StatusBitsSimulateActive, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Header="Simulation Active" />
<DataGridTextColumn CanUserSort="False" Binding="{Binding _StatusBitsName, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Header="Name" />
<DataGridCheckBoxColumn CanUserSort="False" Binding="{Binding _ActualStatusBits, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Header="Actual" />
<DataGridCheckBoxColumn CanUserSort="False" Binding="{Binding _SimulatedStatusBits, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}" Header="Simulation" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

数据.cs包含:

public class Data : INotifyCollectionChanged
{
public List<bool> StatusBitsSimulateActive = new List<bool>();
public List<string> StatusBitsName = new List<string>();
public List<bool> ActualStatusBits = new List<bool>();
public List<bool> SimulatedStatusBits = new List<bool>();
public Data()
{
PopulateLists();
}
event NotifyCollectionChangedEventHandler INotifyCollectionChanged.CollectionChanged
{
add
{
throw new NotImplementedException();
}
remove
{
throw new NotImplementedException();
}
}
private void PopulateLists()
{
Random rnd = new Random();
for (int i = 0; i < 10; i++)
{
if (rnd.Next(10) < 5)
{
StatusBitsSimulateActive.Add(true);
StatusBitsName.Add("LOL");
ActualStatusBits.Add(false);
SimulatedStatusBits.Add(true);
}
else
{
StatusBitsSimulateActive.Add(false);
StatusBitsName.Add("NotFunny");
ActualStatusBits.Add(true);
SimulatedStatusBits.Add(false);
}
}
}
public ObservableCollection<bool> _StatusBitsSimulateActive
{
get
{
ObservableCollection<bool> result = new ObservableCollection<bool>(StatusBitsSimulateActive);
return result;
}
set
{
_StatusBitsSimulateActive = value;
}
}
[...] 3 times
}

这不起作用。列表已填充,但绑定不起作用。这里少了什么?

最新更新