将DataGridColumns与多个可观察集合列表(WPF)绑定

w41d8nur  于 2022-11-18  发布在  其他
关注(0)|答案(2)|浏览(165)

我遇到了一个问题,试图通过搜索来解决,但没有成功。因此,我在这里尝试具体的帮助:我有一个带有不同标签的MainWindow应用程序。一个标签应该有一个带有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>

我有一个类,其中的数据是存储和检索(见管理器._StatusBits)与一些方法来获取和设置数据。数据网格是不是填充数据。有人可以帮助?
我尝试通过ItemsSource=“Binding”然后绑定到列表。没有工作。在互联网上看了看,没有找到解决方案,可能我不明白的机制。我是相当新的wpf,只在C++和mfc工作。

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

MainWindow.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

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中使用的,所以我尝试制作一个自定义通告程序,但是失败了...

fnvucqvd

fnvucqvd1#

所以我认为我对wpf的问题有一个基本的误解。所以我尝试了一个反映首要问题的基本项目:主窗口.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>

Data.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
    }

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

7uhlpewt

7uhlpewt2#

有几个问题需要说明。

  • 一个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函数来填充它,这里我使用了RelayCommandICommand实现),但是您可以链接到一些自动化的数据源,例如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);
        }
    }

}

现在,您的视图将需要对ViewModel进行DataContext设置,我倾向于在视图代码隐藏文件(例如与之关联的.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>

我希望这对你有帮助。

Edit由于您现在询问如何更改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中的值更新,如果没有它,您将需要通过其他方法强制刷新窗口。

相关问题