I have a WPF application with a datagrid. On load my ViewModel populates a list called HldChangeList. This list is bound to the data grid. The list contains approx. 200 items but at the moment the list shows 10 empty rows but no column headers. I've put a stop in my setter and can see the code is getting there. Not sure what else I am missing.
View Model
private List<HoldingPrePost> _hldChangeList;
public List<HoldingPrePost> HldChangeList
{
get
{
return _hldChangeList;
}
set
{
_hldChangeList = value;
OnPropertyChanged("HldChangeList");
}
}
XAML
<DataGrid x:Name="dataGridHoldings"
DataContext="{Binding HldChangeList}"
AutoGenerateColumns="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="Silver"
RowHeaderWidth="30"
ItemsSource="{Binding Path=HldChangeList, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource DataGridTemplate}"
ColumnHeaderStyle="{StaticResource DG_ColumnHeader}"
RowStyle="{StaticResource DG_Row}"
CellStyle="{StaticResource DG_Cell}"
RowHeaderStyle="{StaticResource DG_RowHeader}"
Margin="15,5,20,15" >
<DataGridTextColumn Header="ABC" Binding="{Binding ABC}" IsReadOnly="True"/>
<DataGridTextColumn Header="DEF" Binding="{Binding DEF}" IsReadOnly="True"/>
<DataGridTextColumn Header="GHI" Binding="{Binding GHI}" IsReadOnly="True"/>
</DataGrid>
You're setting both DataContext and ItemsSource to HldChangeList
<DataGrid
DataContext="{Binding HldChangeList}"
ItemsSource="{Binding Path=HldChangeList, UpdateSourceTrigger=PropertyChanged}"/>
WPF will search for HldChangeList items source property in current binding context which you also set to HldChangeList so in your case it will look for HldChangeList.HldChangeList property. If HldChangeList is already part of current binding context then you don't need to change DataContext otherwise you need to set it to something that contains HldChangeList property
EDIT
You forgot to enclose column definitions in DataGrid.Columns tag
<DataGrid x:Name="dataGridHoldings" ... ItemsSource="{Binding Path=HldChangeList}">
<DataGrid.Columns>
<DataGridTextColumn Header="ABC" Binding="{Binding ABC}" IsReadOnly="True"/>
<DataGridTextColumn Header="DEF" Binding="{Binding DEF}" IsReadOnly="True"/>
<DataGridTextColumn Header="GHI" Binding="{Binding GHI}" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
As dkozl said,
you need to set ItemsSource of your DataGrid explicitly without setting its DataContext or implicitly by setting the DataContext
Implicit
<DataGrid ...
DataContext="{Binding HldChangeList}" ...
ItemsSource="{Binding}" ... />
Explicit
<DataGrid ...
ItemsSource="{Binding HldChangeList}" ... />
Related
When the main window - containing only my DataGrid shows, the DataGrid shows zero rows.
This is what my grid looks like:
<DataGrid
AutoGenerateColumns="True"
CanUserAddRows="False"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
DataContext="ClientListViewModel"
ItemsSource="{Binding Source=RowItems}">
<DataGrid.Columns>
<DataGridTextColumn Header="Given Name" Binding="{Binding GivenName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="Family Name" Binding="{Binding FamilyName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="Gender" Binding="{Binding Gender, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="Date of Birth" Binding="{Binding DateOfBirth, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
In the Source={Binding Source=RowItems} the source is a property, RowItems on my main view model, ClientListViewModel.
The viewmodel has a Read method called in the ctor of ClientListView, my main view. When I debug this problem, I can see that the RowItems items source is correctly populated.
My initialization of the main view, the UserControl containing the DataGrid in question, looks like this:
public MainWindow()
{
InitializeComponent();
InitializeListView();
}
private void InitializeListView()
{
var model = new ClientListViewModel();
model.Read();
var view = new ClientListView();
view.DataContext = model;
Content = view;
}
When I put a breakpoint straight after model.Read();, its model.RowItems property shows 12 items, yet the DataGrid shows no rows.
Why is my DataGrid rendering zero rows?
{Binding Source=RowItems}
doesn't bind to a property RowItems.
You need to use
{Binding RowItems}
Also make sure the property RowItems in your ViewModel is of type ObservableCollection as it notifies the UI that there are changes in the Collection, which a normal List doesn't.
(You will need this if you modify data in the list after it is bound)
All the part are there, but they are not being arranged properly. To take advantage of DataBinding you need to follow a specific pattern. In this case you should follow these steps.
public MainWindow() {
InitializeComponent();
this.Clients = InitializeListView();
this.DataContext = this;
}
public ClientListViewModel Clients { get; set; }
private ClientListViewModel InitializeListView() {
var model = new ClientListViewModel();
model.Read();
return model;
}
And MainWindow, which you indicated only contains the DataGrid should bind its ItemSource accordingly to the properties of the DataContext of the view, which in this case is the MainWindow itself and the DataGrid will inherit as its DataContext
<DataGrid
AutoGenerateColumns="False"
CanUserAddRows="False"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
ItemsSource="{Binding Clients.RowItems}">
<DataGrid.Columns>
<DataGridTextColumn Header="Given Name" Binding="{Binding GivenName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="Family Name" Binding="{Binding FamilyName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="Gender" Binding="{Binding Gender, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="Date of Birth" Binding="{Binding DateOfBirth, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
You need to make sure that your binding source (class)'s access modifier is set to public.
Remove Source from the binding of ItemsSource in DataGrid and remove "ClientListViewModel" from DataContext.
<DataGrid
AutoGenerateColumns="True"
CanUserAddRows="False"
CanUserReorderColumns="True"
CanUserResizeColumns="True"
ItemsSource="{Binding RowItems}">
<DataGrid.Columns>
<DataGridTextColumn Header="Given Name" Binding="{Binding GivenName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="Family Name" Binding="{Binding FamilyName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="Gender" Binding="{Binding Gender, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
<DataGridTextColumn Header="Date of Birth" Binding="{Binding DateOfBirth, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
By default, bindings inherit the data context specified by the DataContext property, if one has been set. However, the Source property is one of the ways you can explicitly set the source of a Binding and override the inherited data context.
If you set
ItemsSource="{Binding Source=RowItems}"
then RowItems is translated to string (with lenght 8) and therefore you see eight lines.
EDIT
I've deviated from the methods illustrated here (which I was unable to get to work).
Binding data worked when I implemented MVVM as per this question
ORIGINAL QUESTION
I followed a tutorial (which I've since lost the link to) to create a data binding for my DataGrid.
The process involved creating a data source from my model which allowed me to drag a DataGrid onto my Window.
The DataGrid renders empty on the window when I run it and I know for a fact there is data that needs to go into it.
Here's the code:
<Page.Resources>
<CollectionViewSource x:Key="campaignViewSource" d:DesignSource="{d:DesignInstance {x:Type Models:Campaign}, CreateList=True}"/>
</Page.Resources>
<DataGrid x:Name="campaignDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True" ItemsSource="{Binding}" Grid.Row="1" RowDetailsVisibilityMode="VisibleWhenSelected">
<DataGrid.Columns>
<DataGridTextColumn x:Name="clientColumn" Binding="{Binding Client}" Header="Client" Width="SizeToHeader"/>
<DataGridTemplateColumn x:Name="dateSentColumn" Header="Date Sent" Width="SizeToHeader">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding DateSent, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn x:Name="idColumn" Binding="{Binding Id}" Header="#" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="nameColumn" Binding="{Binding Name}" Header="Name" Width="SizeToHeader"/>
<DataGridTextColumn x:Name="emailAddressColumn" Binding="{Binding EmailAddress}" Header="Email Address" Width="SizeToHeader"/>
</DataGrid.Columns>
</DataGrid>
According to the tutorial I built this from, there's nothing more that I need to do to make this work. So what have I missed?
How should I proceed?
Try to bind the ItemsSource property to your CollectionViewSource:
<DataGrid x:Name="campaignDataGrid" AutoGenerateColumns="False" EnableRowVirtualization="True"
ItemsSource="{Binding Source={StaticResource campaignViewSource}}" Grid.Row="1" RowDetailsVisibilityMode="VisibleWhenSelected">
You also need to set the Source property of the CollectionViewSource to some source collection:
var cvs = this.Resources["campaignViewSource"] as CollectionViewSource;
cvs.Source = new List<YourDataObject> { new YourDataObject(), new YourDataObject() };
The drag-and-drop approach is really no good way of either doing or learning something. You should learn XAML and MVVM if you want to become a successful WPF developer.
I’m using WPF MVVM DataGrid and one of the columns is the equivalent of DataGridComboBoxColumn, but made of DataGridTemplateColumn .
DataGrid itself is binded to one object, and ComboBox column is binded to the separate one.
The XAML code is:
<DataGrid Grid.Column="0" AutoGenerateColumns="False" CanUserAddRows="False" ItemsSource="{Binding ItemNamesSetting}">
<DataGrid.Columns>
<DataGridTextColumn Header="Item1" Binding="{Binding Path=OriginalItemName}" />
<DataGridTextColumn Header="Item2" Binding="{Binding Path=FinalItemName}" />
<DataGridTemplateColumn Header="Attribute">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AttributesBindingList, ElementName=ThirdStepTab}" DisplayMemberPath="PropName" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Item3" Binding="{Binding Path=Separatopr}" />
</DataGrid.Columns>
</DataGrid>
The question is how can I get the full row data? I’m planning to have one “Save” button that would send data to the database and I need to get data in text columns + data at ComboBox from different ource – row by row. Is there a way to do it?
Thank you.
You need to bind the SelectedItem property of DataGrid to your MVVM view Model. Also, the one of relevant property of view model should bind to Combobox SelectedValue
<DataGrid Grid.Column="0" AutoGenerateColumns="False" CanUserAddRows="False" ItemsSource="{Binding ItemNamesSetting}" SelectedItem="{Binding VMPropertyName}" >
<DataGrid.Columns >
<DataGridTextColumn Header="Item1" Binding="{Binding Path=OriginalItemName}" />
<DataGridTextColumn Header="Item2" Binding="{Binding Path=FinalItemName}" />
<DataGridTemplateColumn Header="Attribute">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding DataContext.AttributesBindingList, ElementName=ThirdStepTab}" DisplayMemberPath="PropName" SelectedValue="{Binding PropertyOfVM}" />
</DataTemplate> </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Item3" Binding="{Binding Path=Separatopr}" />
I'm trying to learn MVVM (Using MVVMLight Toolkit). But i'm stuck.
In ViewModel have an ObservableCollection
private ObservableCollection<Phone> _phoneNumbers;
public ObservableCollection<Phone> PhoneNumbers
{
get { return _phoneNumbers; }
private set
{
_phoneNumbers = value;
}
}
In ViewModel constructor fill it in such way PhoneNumbers = new ObservableCollection<Phone>(Guest.Person.Phones);
In view have
<DataGrid x:Name="PhoneNumbersDataGrid" HorizontalAlignment="Left" Grid.Column="3" Grid.Row="11" VerticalAlignment="Top" Height="86" Width="auto" ItemsSource="{Binding PhoneNumbers, Mode=OneWay}" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding PhoneNumber}" Header="Phone Number" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Guest.DeletePhoneCommand, Mode=OneWay, Source={StaticResource Locator}}">Delete</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
In DeletePhoneCommand I'm trying to change PhoneNumbers e.g.
PhoneNumbers = new ObservableCollection<Phone>();
RaisePropertyChanged("PhoneNumbers");
Collection became empty but datagrid displays filled collection without any changes. "get" of Collection fires only when view is loading. Even when i RaisePropertyChanged("PhoneNumbers") it doesn't fire.
What am I doing wrong?
If you want to notify the changes to your collection you should use Mode = TwoWay
Two way will enable any change in object is reflected to the UI and any change in UI is reflected in the object.
<DataGrid x:Name="PhoneNumbersDataGrid" HorizontalAlignment="Left" Grid.Column="3" Grid.Row="11" VerticalAlignment="Top" Height="86" Width="auto" ItemsSource="{Binding PhoneNumbers, Mode=TwoWay}" AutoGenerateColumns="False" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding PhoneNumber,Mode=TwoWay}" Header="Phone Number" IsReadOnly="True"/>
<DataGridTemplateColumn Header="Delete">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button Command="{Binding Guest.DeletePhoneCommand, Mode=OneWay, Source={StaticResource Locator}}">Delete</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
If I'm not mistaken, the binding is broken when you reinitialize the collection. to fix this you will have to work on the already initialized collection. e.g :
clear the list using the
PhoneNumbers.Clear()
to add to the list, using
PhoneNumbers.Add(item)
you don't need to raise the property changed event to update the list in the UI when you're using ObservableCollection. You will need it if you change a property of the item on the list.
at first clear the list with
Clear()
then add item
PhoneNumbers.Add(item)
I have a DataGrid like below
<DataGrid ItemsSource="{Binding Collection}" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Col1" Binding="{Binding Col1}" IsReadOnly="True" />
<DataGridComboBoxColumn Header="Col2" ItemsSource="{Binding Col2}" SelectedItemBinding="{Binding Selected}" />
</DataGrid.Columns>
</DataGrid>
The values that end up in Col1 are actually Window.DataContext.Collection[index].Col1, but WPF seems to be looking for Col2's ItemsSource at Window.DataContext.Col2. The actual path I need is Window.DataConext.Collection[index].Col2
Please note the reference to index above is not to a static value but to the fact that each row gets a value from one of the collection items.
How do I accomplish this?
Try this:
<DataGridComboBoxColumn Header="Col2" SelectedItemBinding="{Binding Selected}"
ItemsSource="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}},
Path=DataContext.Collection[index].Col2}" />
Poor formatting. (insert a number for index)