Specifying data context for DataGridTemplateColumn - wpf

DataGrid is bound to List, where T has several nested properties (amongst others).
class T
{
public X PropertyX {get;}
public Y PropertyY {get;}
public Z PropertyZ {get;}
}
class X { public A SomeProperty {get;}}
class Y { public A SomeProperty {get;}}
class Z { public A SomeProperty {get;}}
X, Y and Z classes have the same property SomeProperty of type A.
I need to show data for SomeProperty of PropertyX, PropertyY and PropertyZ, respectively.
So, I need something like this:
<DataGridTemplateColumn DataContext="{Binding X.A}" CellTemplate="{StaticResource CommonTemplate}" />
<DataGridTemplateColumn DataContext="{Binding Y.A}" CellTemplate="{StaticResource CommonTemplate}" />
<DataGridTemplateColumn DataContext="{Binding Z.A}" CellTemplate="{StaticResource CommonTemplate}" />
Obviously DataGridTemplate column does not have DataContext, so I am wondering is this possible to do? CommonTemplate is quite large, and I would like to reuse it.
Any ideas?

you can reuse existing DataTemplate in another DataTemplate, which also specifies DataContext:
<DataGridTemplateColumn Header="x">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding PropertyX.SomeProperty}"
ContentTemplate="{StaticResource CellTemplate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="y">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding PropertyY.SomeProperty}"
ContentTemplate="{StaticResource CellTemplate}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Related

DataGridColumn binding fail

This is my DataGrid;
<DataGrid Visibility="Visible"
Grid.Row="1"
SelectionUnit="CellOrRowHeader"
Name="dataGrid"
SelectionMode="Single"
ItemsSource="{Binding collcection}">
<DataGrid.ContextMenu>
<ContextMenu>
<MenuItem Command="Copy" Click="MenuItem_Click_1"/>
</ContextMenu>
</DataGrid.ContextMenu>
<DataGrid.Columns>
<DataGridTemplateColumn Header="Select">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox
x:Name="cbRunRobot"
IsChecked="{Binding Value}"
Width="60"
Height="25"
Checked="cbRunRobot_Checked"
Unchecked="cbRunRobot_Unchecked"
Margin="25,0,0,0" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
View Model:
list<MyData> collcection;
public class MyData
{
public string Name;
public string Id;
}
I try to add this Column:
<DataGridColumn Binding="{Binding Name}" Header="Name" Width="180"/>
And got this errors:
Error 1 The type "DataGridColumn" is abstract and must include an explicit value.
Error 2 The member "Binding" is not recognized or is not accessible.
DataGridColumn is an abstract class which means it cannot be instantiated. The same applies to DataGridBoundColumn.
You're choices are:
DataGridCheckBoxColumn for boolean values
DataGridComboBoxColumn for enumerable values
DataGridHyperlinkColumn for Uri values
DataGridTemplateColumn to show any types of data by defining your own cell template
DataGridTextColumn to show text values
It looks like DataGridTextColumn is what you're looking for.
Hi I can suggest you the next:
Bind to ObservableCollection instead the list.
Make your MyData model to implement InotifyPropertyChanged.
Make each binding involved property in MyData model to fire OnPropertyChanged event.
Here is the link to the working example:How to Display and select items in a Datagrid ComboBox with WPF C#, using MVVM.
regards,

Pass values to ValueConverter from two different binding sources in single DataGrid

What I want to do is to color DataGridRow red if validation fails. I attempted to do so with ValueConverter but I have no idea how to pass value from more than one binding source.
I've got datagrid with binding to collection of objects. Then there's a
DataGridTemplateColumn with ComboBox and Textbox inside (only one is visible at time), and the ComboBox has non related to bound collection binding source.
See code:
<DataGrid Grid.Row="4" Grid.Column="0" x:Name="DGR_Slots" Grid.ColumnSpan="5" Grid.RowSpan="3" ItemsSource="{Binding ElementName=WND_ItemCraftingWindow, Path=SelectedSchematic.Slots}" AutoGenerateColumns="False" ColumnWidth="*">
<DataGrid.Resources>
<classes:ValidationConverter x:Key="ValidationConverter" />
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{Binding Converter={StaticResource ValidationConverter}}" />
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Id" Binding="{Binding Id}" Visibility="Collapsed"/>
<DataGridTextColumn Header="Slot Type" Binding="{Binding SlotType}" />
<DataGridTextColumn Header="Mat Type" Binding="{Binding MaterialType}" />
<DataGridTextColumn Header="Count" Binding="{Binding MaterialCount}" />
<!--<DataGridComboBoxColumn Header="Material" />-->
<DataGridTemplateColumn Header="Material">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Visibility="Visible" MouseDoubleClick="MaterialCell_MouseDoubleClick" ItemsSource="{Binding ElementName=WND_ItemCraftingWindow, Path=Materials}" DisplayMemberPath="Name" SelectedValuePath="Name" SelectionChanged="MaterialComboBox_SelectionChanged"/>
<TextBox Visibility="Collapsed" MouseDoubleClick="MaterialCell_MouseDoubleClick" PreviewKeyUp="MaterialCell_TextBox_PreviewKeyUp" KeyUp="MaterialCell_TextBox_KeyUp" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
WND_ItemCraftingWindow is the Window that owns the datagrid and the Materials is ObservableCollection property inside that window.
I need to pass the object from DataGrid.ItemsSource collection (that works now) and SelectedItem from Combobox in same row (i dont know how to do that) into ValidationConverter.
Validation of the row can be performed only with the object from DataGridRow and SelectedItem from combobox.
Also I'm open to different solutions to color the row after such validation.
Alright I managed to do it hackish way.
Datagrid code (removed meaningless parts):
<DataGrid x:Name="DGR_Slots" ItemsSource="{Binding ElementName=WND_ItemCraftingWindow, Path=SelectedSchematic.Slots}" AutoGenerateColumns="False" ColumnWidth="*" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Material">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<ComboBox Visibility="Visible" ItemsSource="{Binding ElementName=WND_ItemCraftingWindow, Path=Materials}" SelectionChanged="MaterialComboBox_SelectionChanged" />
<TextBox Visibility="Collapsed" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
SelectionChanged event
private void MaterialComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// ...
var slot = (DGR_Slots.SelectedItem as SchematicSlot);
var mat = ((sender as ComboBox).SelectedItem as Material);
var row = WPFHelper.GetRow(DGR_Slots, DGR_Slots.SelectedIndex);
row.Background = new SolidColorBrush(slot.MaterialType != mat.MaterialType ? Colors.Red : Colors.White);
}
WPFHelper GetRow method (found somewhere on the internet)
static public DataGridRow GetRow(DataGrid dg, int index)
{
DataGridRow row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(index);
if (row == null)
{
// may be virtualized, bring into view and try again
dg.ScrollIntoView(dg.Items[index]);
row = (DataGridRow)dg.ItemContainerGenerator.ContainerFromIndex(index);
}
return row;
}
Although I'm still open to other solutions.

Two way binding with a DataGrid vs. a ListView

I'm having trouble understanding a discrepancy in the handling of two-way binding between a DataGrid vs. a ListView. To illustrate, I have a class DataItem with a few properties, and a List of DataItems for binding to the DataGrid/ListView:
public class DataItem
{
public bool Flag { get; set; }
public int IntValue { get; set; }
public string StringValue { get; set; }
public List<DataItem> SubList { get; set; }
public DataItem()[...]
}
I create a main DataItem object with a number of additional DataItem objects adde into the SubList. The main DataItem is set to the DataContext of a containing Grid, and the SubList is bound to both a ListView:
<ListView ItemsSource="{Binding Path=SubList}">
<ListView.View>
<GridView>
<GridViewColumn Header="Flag" Width="Auto">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="FlagCheckBox" IsChecked="{Binding Path=Flag}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="String Value" Width="100">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBox Name="StringTextBox" Text="{Binding Path=StringValue}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
and to a DataGrid:
<DataGrid ItemsSource="{Binding Path=SubList}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Flag" Width="SizeToCells">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox Name="FlagCheckBox" IsChecked="{Binding Path=Flag}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="String Value" Width="SizeToCells">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Name="StringTextBox" Text="{Binding Path=StringValue}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
Both the ListView and the DataGrid properly display the SubList items. However, when I modify the data in the UI, and examine the source DataItem.SubList, the ListView works and the DataGrid doesn't: I can see the changes when they are made in the ListView, but when the changes are made in the DataGrid, there are no changes.
The bindings must be correct, otherwise I wouldn't see the values displayed properly. But for some reason, two-way binding works in the ListView to move changes made in the UI back to the source object, but not in the DataGrid.
You need to set UpdateSourceTrigger to PropertyChanged to propagate changes back to your DataObject class in case of DataGrid.
<CheckBox Name="FlagCheckBox" IsChecked="{Binding Path=Flag,
UpdateSourceTrigger=PropertyChanged}"/>
And on TextBox too -
<TextBox Name="StringTextBox" Text="{Binding Path=StringValue,
UpdateSourceTrigger=PropertyChanged}"/>

WPF DataGrid to BindingList not working, never shows results

I'm trying to bind data to a DataGrid via a property and then later update that property to change(sorting) the results in the datagrid.
I initially bind the DataGrid:
BindingList<Booking> tourBookings;
private async void PageFrame_Loaded_1(object sender, RoutedEventArgs e) {
tourBookings = new BindingList<Booking>((await DataManager.BookingsRef.GetBookingHeaders(PageSize, CurrentPage)).TourBookings);
dgBookings.DataContext = tourBookings;
}
Nothing is in the DataGrid at this point, either way I have a sort method:
private async void DataGrid_Sorting_1(object sender, DataGridSortingEventArgs e) {
tourBookings = new BindingList<Booking>((await DataManager.BookingsRef.GetBookingHeaders(s, asc, PageSize, CurrentPage)).TourBookings);
}
.. there are still no results in my datagrid...
Where are my results?
Here are my bindings too:
<DataGrid x:Name="dgBookings" DataContext="{Binding Path=tourBookings}" Style="{StaticResource DataGridStyle}" AutoGenerateColumns="False" CanUserAddRows="False"
ScrollViewer.PanningMode="VerticalOnly" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.CanContentScroll="True"
ScrollViewer.PanningDeceleration="5" ScrollViewer.PanningRatio="1" Grid.RowSpan="2"
Grid.Row="1" RowEditEnding="DataGrid_RowEditEnding_1" CanUserSortColumns="True" Sorting="DataGrid_Sorting_1">
<DataGrid.Columns>
<DataGridTemplateColumn Width="*" SortMemberPath="Time" SortDirection="Ascending">
<DataGridTemplateColumn.Header>
Time
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding StartTime, StringFormat=HH:mm}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" SortMemberPath="TourID">
<DataGridTemplateColumn.Header>
Tour ID 1
</DataGridTemplateColumn.Header>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding TourID}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
..... and load of other properties and then close datagrid.columns etc
binding just works with public properties, your tourbookings is just a field.
BindingList<Booking> tourBookings {get;set;}
EDIT:
you have to set the itemssource
<DataGrid ItemsSource="{Binding Path=tourBookings}" />
EDIT2:
if you set the datacontext to your list, then your binding should look like this
<DataGrid ItemsSource="{Binding}" />

How to figure out if a DataGrid Row Has been Changed in MVVM?

I want to Bind the WpfDatagrid Rows to a boolean Property in a ViewModel,that shows if Row Has been changed.in the fact I have a datagrid that bind to a class in the model, and have a property IsRowChanged in the ViewModel,and don't know how to bind datagrid to IsRowChanged ?
<DataGrid ItemsSource="{Binding Produts}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn IsReadOnly="True" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding IsRowChanged}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
If you use entity framework you can directly extend your entity partial class with this line
public bool IsRowChanged { get { return myEntity.EntityState == EntityState.Modified; } }

Resources