I'm creating a WPF View with a DataGrid.
The DataGrid is bound to the field Properties on the ViewModel.
However, for one of the columns I want each row to have the same value bound to some other property on the View model.
Specificically, the table is showing named monetary values and the repeated column will show a currency code (which is the same for each row).
I can't figure out how to do this, I've tried to use the following:
<DataGrid ItemsSource="{Binding Properties}">
<DataGrid.Columns>
<DataGridTextColumn Header="Target" Binding="{Binding Target}"/>
<DataGridTextColumn Header="Value" Binding="{Binding Value}"/>
<DataGridTemplateColumn Header="Currency">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Properties.NodeCurrency}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I'm using the Caliburn framework with no typed DataContext in the View. I'm not sure if this matters to the question though.
You can try to do it via RelativeSource of Binding. For example:
<TextBlock Text="{Binding Property.NodeCurrency, RelativeSource={RelativeSource AncestorType=Window}}"/>
You can use RelativeSource/FindAncestor on your binding to bind to an other DataContext than the current one :
<TextBlock
Text="{Binding Property.NodeCurrency, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"/>
With assistance from both answerers the actual binding I needed was:
<TextBlock Text="{Binding DataContext.Property.NodeCurrency, RelativeSource={RelativeSource AncestorType=UserControl}}"/>
The missing bit was specifying that I'm binding to the DataContext of the UserControl and then delving down into the properties on the ViewModel.
Related
I have the following code:
<DataGrid ItemsSource="{Binding FilteredLectureViewModels}">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}"/>
<!-- Profs -->
<DataGridTemplateColumn Header="Profs">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate DataType="viewModels:LectureViewModel">
<ItemsControl ItemsSource="{Binding Profs}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ProfString}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
I want the datagrid to sort its content by clicking on the header 'Profs' like it's working for a DataGridTextColumn with the header 'Name' already by default. I know the attribute 'SortMemberPath' of the DataGridTemplateColumn, but i don't know how to use that in this situation.
In bindings, you can use brackets [ ] to supply an index for a collection as part of the binding path. Upon testing, this same approach seems to work for SortMemberPath:
<DataGridTemplateColumn Header="Profs" SortMemberPath="Profs[0].ProfString">
This sorts based on the ProfString property of the first item in Profs. I'm not sure why you would want to sort based on the first item in a collection, but this does it.
I am trying to bind some data to a datagrid using the MVVM pattern with WPF. I have confirmed that the datagrid is populating and indeed, that the specific value (Gender) is populated. I've also tried every fix that I could find online (including other questions on this site) that's why I am seeking out an answer here.
<DataGridTemplateColumn Header="Gender" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Genders}" SelectedItem="{Binding Gender, UpdateSourceTrigger=PropertyChanged}" IsSynchronizedWithCurrentItem="True">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Things I've tried: Mode=TwoWay, UpdateSourceTrigger=PropertyChanged", IsSynchronizedWithCurrentItem="True". Although, I am not a super experienced WPF and MVVM programmer, so it could be something simple that I just don't know about. My models seem to be working elsewhere and they implement observable/are in observable collections where applicable.
Edit: I got it sorted out. Here is the code that worked for my issue (in case someone else has a similar problem).
<DataGridTemplateColumn Header="Gender" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Path=DataContext.Genders, RelativeSource={RelativeSource FindAncestor, AncestorType = Window}}" SelectedItem="{Binding Gender, UpdateSourceTrigger=PropertyChanged}">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Assuming you DataGrid's DataContext is the Patient object, try using a RelativeSource binding to point at the DataGrid:
<DataGridTemplateColumn Header="Gender" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}, Path=DataContext.Genders}"
SelectedItem="{Binding Gender, UpdateSourceTrigger=PropertyChanged}"
IsSynchronizedWithCurrentItem="True">
</ComboBox>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
I have an ObservableCollection:
public ObservableCollection<RepairInfo> RepairList { get; set; }
And I'm binding it to a DataGrid:
<DataGrid SelectionMode="Single" ItemsSource="{Binding RepairList}" Name="RepairsGrid" AutoGenerateColumns="False">
I don't use all properties of my RepairInfo class so I specify which property have to use each columns, like this:
<DataGridTextColumn Binding="{Binding IMEI}">
<DataGridTextColumn.Header>
<TextBox MinWidth="90" Text="{Binding IMEIFilter, UpdateSourceTrigger=PropertyChanged}" />
</DataGridTextColumn.Header>
</DataGridTextColumn>
The TextBox inside DataGridTextColumn.Header using for filtering data.
So here is my problem - value from TextBox don't update property in ViewModel. If I put this TextBox outside DataGridTextColumn.Header everything is fine. I guess it causes by Binding property of my DataGridTextColumn but I don't know how to resolve it.
IMO. . better way would be having the property for the Header inside the MainWindow Viewmodel and you can Bind it like this
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding IMEI}">
<DataGridTextColumn.Header>
<TextBox MinWidth="90" Text="{Binding DataContext.IMEIFilter,
RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
Reference: WPF datagrid header text binding
I am using wpf in .NET 4.5 and I would like to have a data grid template to embed in other DataTemplates. I use a DataTemplateSelector to display the content of a list of children of a common parent class.
I would just like to change the ItemsSource of my DataGrid, as it has a different property name in each class.
The general Binding of the in my DataGrid template can have the same bindings, as the list always contains the same class.
Therefore, I created a custom class deriving from DataGrid. In line it works great, but when i use my custom class i do not get any output in the DataGrid.
So how can i make my custom control make to behave like the normal datagrid. I just would like to change the datasource there.
Thanks in advance.
Inline example works fine:
<DataGrid ItemsSource="{Binding Path=KnotPoints, Converter={StaticResource geoArrayToDataGrid}}" AutoGenerateColumns="False">
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.Num, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}}"/>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
<DataGrid.Columns>
<DataGridTextColumn Header="X" Binding="{Binding X}"/>
<DataGridTextColumn Header="Y" Binding="{Binding Y}"/>
<DataGridTextColumn Header="Z" Binding="{Binding Z}"/>
</DataGrid.Columns>
</DataGrid>
Custom UserControl does not return any content:
<DataGrid x:Class="FlexForCFK.CAD.Controls.MultiGeoStruct"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
AutoGenerateColumns="False">
<DataGrid.RowHeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.Num, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGridRow}}"/>
</DataTemplate>
</DataGrid.RowHeaderTemplate>
<DataGrid.Columns>
<DataGridTextColumn Header="X" Binding="{Binding X}"/>
<DataGridTextColumn Header="Y" Binding="{Binding Y}"/>
<DataGridTextColumn Header="Z" Binding="{Binding Z}"/>
</DataGrid.Columns>
</DataGrid>
EDIT:
The custom Control MutliGeoStruct is placed here in the code:
<TreeViewItem Header="Knot Points" IsExpanded="True">
<y:MultiGeoStruct ItemsSource="{Binding Path=KnotPoints, Converter={StaticResource geoArrayToDataGrid}}"/>
</TreeViewItem>
(y is the namespace of my control)
xmlns:y="clr-namespace:FlexForCFK.CAD.Controls"
EDIT: Not the solution!
Elgonzo is right. I have to set the
DataContext="{Binding}"
for my MultiGeoStruct DataGrid.
<y:MultiGeoStruct DataContext="{Binding}" ItemsSource="{Binding Path=KnotPoints, Converter={StaticResource geoArrayToDataGrid}}"/>
That solved the problem. But why is that? In my top TreeView i never set DataContext at all, as I understood it, if you use ItemSource the Collection is directly given to the Items and the DataContext of the parent does not need any defining at all.
So I just set, when my Data is loaded in my TreeView
this.ItemsSource = m;
and it works just fine.
Thank you very much for your help.
If you know the answer please enlighten me. If I have an explaination I'll update this.
The binding for productColumn2 works perfect both ways. When I added a converter for each, productColumn1 called the converter; but always has it's value set to null when loading from observable collection, or value set to product when assigning (but doesn't actually assign observable collection).
Issue has to do with DataContext and LogicalTree. The DataContext for ProductSelectorTextBoxUserControl is itself, and is used for it's own code. I want to be able to bind its 'text' property to my observable collection, as in productColumn2. I so far can't seem to set ProductSelectorTextBoxUserControl DataContext to the DataContext used here.
<DataGrid ItemsSource="{Binding Path=ObservableCollectionItems, Mode=OneWay}" AutoGenerateColumns="False" EnableRowVirtualization="True" >
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="productColumn1" SortMemberPath="Product" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<productSelector:ProductSelectorTextBoxUserControl Text="{Binding Path=Product, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=LostFocus, ValidatesOnExceptions=True}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn x:Name="productColumn2" Binding="{Binding Path=Product, Mode=TwoWay, NotifyOnSourceUpdated=True}" />
</DataGrid.Columns>
If ProductSelectorTextBoxUserControl has a DataContext set to itself, the Binding won't be able to find the Product property as it doesn't exist. You need to modify your binding so that it knows where to find the Product property; something like this may work:
<productSelector:ProductSelectorTextBoxUserControl Text="{Binding Path=Product, RelativeSource={RelativeSource AncestorType={x:Type DataGridRow}}, Mode=TwoWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=LostFocus, ValidatesOnExceptions=True}" />
By adding a RelativeSource, you're telling the binding to look for the Product property in the DataGridRow.DataContext.
UPDATE
Have you tried {RelativeSource AncestorType={x:Type DataGridRow}}? You should be targeting the row and not the grid.
When the ItemContainerGenerator creates each DataGridRow it sets the row's DataContext to the corresponding item in the ObservableCollectionItems. So, the logical tree something like this:
DataGrid (DataContext = object that defines ObservableCollectionItems)
DataGridRow (DataContext = ObservableCollectionItems[0])
ProductSelectorTextBoxUserControl (DataContext = self)
DataGridRow (DataContext = ObservableCollectionItems[0])
ProductSelectorTextBoxUserControl (DataContext = self)
The grid's DataContext doesn't expose the Product property, it's defined on each element in the collection (unless I've missed something). The Product property should be in the context of each row.
Thanks #SellMeADog for helping me out on this, however this still took me way way to long to figure out. Final line is:
<productSelector:ProductSelectorTextBoxUserControl x:Name="productSelector" Product="{Binding Path=Item.Product, RelativeSource={RelativeSource FindAncestor, AncestorType=DataGridRow, AncestorLevel=1}, Converter={StaticResource productNameToProductConverter}, Mode=TwoWay, NotifyOnSourceUpdated=True, ValidatesOnExceptions=True}" />
Key points are RelativeSource DataGridRow, Path is Item(ObservableCollection).Property
If you notice question referred to text and this refers to product, I had to switch to product and add converter. UserControl would then set its own text