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 oneObservableCollection
. - A
DataGrid.Column
is bound to a single property of the class type contained within theObservableCollection
- An
ObservableCollection
implements theINotifyProperty
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.