Home > Software engineering >  Binding DataGridColumns with multiple ObservableCollections List (WPF)
Binding DataGridColumns with multiple ObservableCollections List (WPF)

Time:10-28

I have a problem and trying to solve by searching did not succeed. So I try it here with specific help: I have a MainWindow Application with different Tabs. One Tab should have a DataGrid with 4 columns. I want to fill them with a List of objects. My implementation in 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();
   }
}

The MainWindow.xaml file contains:

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

I have a class, where the Data is stored and retrieved (see Manager._StatusBits) with some methods to get and set data. The DataGrid is not filling with data. can someone help?

I tried through ItemsSource="Binding" and then Binding to the Lists. Not working. Looked through the internet and found no solution, maybe I do not understand the mechanics. I am quite new to wpf, only worked on C and mfc.

CodePudding user response:

So I think I have a basic misunderstanding of the problem with wpf. So I tried a basic project which reflects the top problem: MainWindow.xaml contains

<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 contains:

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
    }

This does not work. The Lists are populated, but the Binding is not working. What is missing here?

CodePudding user response:

There are several issues that need stating.

  • A DataGrid can only be bound to one ObservableCollection.
  • A DataGrid.Column is bound to a single property of the class type contained within the ObservableCollection
  • An ObservableCollection implements the INotifyProperty interface and so does not require further code support when bound.

I'm not sure what you're attempting to do with your code but on the first point I would amalgamate all of the data that you wish to show within a DataGrid.Row into a single class type and have a view model that includes an ObservableCollection of that type e.g.

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

I don't recommend using the getter for an ObservableCollection to populate its data source, instead populate it with a ViewMode function, here I use a RelayCommand (an ICommand implementation), but you may link into some automated data source e.g., web API or database.

So, here's a suitable ViewModel - note that IStatusBitsDataViewModel is an interface type that just defines the public parts of the StatusBitsDataViewModel. I've not included it, but I'll explain why I use a defined interface later.

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);
        }
    }

}

Now, your View will require a DataContext setting to the ViewModel, this I tend to do in the Views code behind file e.g., the .cs file associated with it, if only because I can use dependency injection and pass a concrete type into it, and thus change the behaviour at run time e.g., switch between using a ViewModel that uses a real data source or one that uses predefined test data, say for unit testing.

So, the Code behind is:

/// <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
    }
}

Here's the XAML for an example window that demonstrates data binding and how to trigger the population of a DataGrid with data from the UI.

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

I hope that this.

  • Related