WPF Binding in DataGridColumnHeader DataTemplate - wpf

So, this is an extension to the following question: Style DataGridColumnHeader with Styles in WPF
In short, I'm trying to put filters in my DataGridColumnHeaders by templating the column headers with a combobox. So the difference with the other example is that I'm using ComboBoxes instead.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="300" Width="300" Loaded="Window_Loaded">
<Window.Resources>
<DataTemplate x:Key="MySpecialHeaderTemplate">
<ComboBox ItemsSource="{Binding Codes}" />
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn
Binding="{Binding Id}" />
<DataGridTextColumn HeaderTemplate="{StaticResource MySpecialHeaderTemplate}"
Binding="{Binding Name}" />
<DataGridTextColumn HeaderTemplate="{StaticResource MySpecialHeaderTemplate}"
Binding="{Binding Age}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
My question pertains to binding the ComboBox to some values. I'm currently having issues with binding the ItemsSource to a property in my ViewModel as shown above, but I can't get it to work. My second question would be how would I alter the code so that I could bind to different values per column??

The DataGridColumnHeaders doesn't inherit the DataContext so they have nothing to bind against. Use RelativeSource to find the parent DataGrid in the binding instead and point to DataContext.Codes
<DataTemplate x:Key="MySpecialHeaderTemplate">
<ComboBox ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path=DataContext.Codes}" />
</DataTemplate>

Related

Binding textboxes inside a usercontrol to datagrid

Is the way that I'm binding the UserControl to the DataGrid correct?
In my MainWindow I have a datagrid (code below):
<DataGrid x:Name="MusicListGrid" ItemsSource="{Binding MusicList}" AutoGenerateColumns="False" IsSynchronizedWithCurrentItem="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Artist" Binding="{Binding Artist,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Album" Binding="{Binding Album,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn Header="Track" Binding="{Binding Track,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
</DataGrid>
The usercontrol inside my MainWindow, that I'm binding to the selected item of the datagrid:
DataTemplate(in MainWindow):
<Window.Resources>
<DataTemplate x:Key="MusicDetailListTemplate" >
<v:MusicDetailView DataContext="{Binding ElementName=MusicListGrid,Path=SelectedItem}" />
</DataTemplate>
</Window.Resources>
ContentControl(in MainWindow):
<ContentControl x:Name="musicDetail" Content="{Binding}" ContentTemplate="{StaticResource MusicDetailListTemplate}" Grid.Column="1" />
The textboxes inside my UserControl looks like this:
<TextBox Grid.Column="1" Grid.Row="0" Width="200" Margin="10,5" Text="{Binding Artist,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBox Grid.Column="1" Grid.Row="1" Width="200" Margin="10,5" Text="{Binding Album,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
<TextBox Grid.Column="1" Grid.Row="2" Width="200" Margin="10,5" Text="{Binding Track,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
This works, but I'm not sure if I'm doing the correct way?
Can I also bind a textbox to the datagrid like this:
<TextBox Grid.Column="1" Grid.Row="3" Text="{Binding Path=MusicListGrid.Artist.SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}" />
The data in the datagrid is an ObservableCollection<Music> (music is my model).
If I replace my UserControl with a tabcontrol, should I have the tabcontrol be a separate view or just be part of MainWindow? And have the content of the tabcontrol be separate views? I was thinking of having the datagrid and a tabcontrol with 2 tabs, one for editing and the other for displaying (to look more presentable).
Sorry if these are very basic questions, I just want to be on the right path.
It is more common to set the Content property of the ContentControl element to the data object:
<ContentControl x:Name="musicDetail" Content="{Binding ElementName=MusicListGrid,
Path=SelectedItem}" ContentTemplate="{StaticResource MusicDetailListTemplate}" />
The DataContext defines what the data of the relevant type should look like in the UI, so you shouldn't really set the DataContext there... it is automatically set to the relevant data object instance... it should look more like this (where Prefix is the XML Namespace Prefix that you set up for your project and YourClass is the type of object in the DataGrid):
<Window.Resources>
<DataTemplate x:Key="MusicDetailListTemplate" DataType="{x:Type Prefix:YourClass}">
<v:MusicDetailView DataContext="{Binding}" />
</DataTemplate>
</Window.Resources>

wpf Datagrid columns width fail in ListBox

I have a ListBox that contains items derived from UserControl. The concrete UserControl contains (among others) a DataGrid. Basically I cannot change the concrete UserControl (changes only for testing purpose; its kind of third party). The DataGrid column width of all columns gets broken as soon as one column width is set to "*", which is required to fill the complete space.
I can reproduce it with a simplified example (w/o UserControl). So if I add a DataGrid as ListBoxItem, specifying a column width of "*" breaks the column width. BTW, the columns also cannot be resized by the user anymore.
Simple example:
<Window x:Class="DataGridSpike.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox HorizontalContentAlignment="Stretch" >
<!-- Column width is basically ok but only since not "*" -->
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Header="Column1"/>
<DataGridTextColumn Header="Column2"/>
<DataGridTextColumn Header="Column3"/>
</DataGrid.Columns>
</DataGrid>
<!-- Broken column width: -->
<DataGrid>
<DataGrid.Columns>
<DataGridTextColumn Header="Column1"/>
<DataGridTextColumn Header="Column2" Width="*"/>
<DataGridTextColumn Header="Column3"/>
</DataGrid.Columns>
</DataGrid>
</ListBox>
</Grid>
Does anyone know how to fix this?
Regards
Based in the suggestion of sa_ddam213 I found a kludge that works.
I have no other idea so far.
I wrapped the DataGrid by a ListView, removed all "decoration" from the ListView, and bound the width of the GridViewColumn to the ActualWidth of the ListView. Maybe some control other than ListView works, too. I was just playing around with ListView. A simple border doesn't work.
The margins have to be tweaked some way and there is a gap on the right side I don't get rid of (but I can live with it). Unless I disable HorizontalScrollBarVisibility of the DataGrid I get strange flashing ghost horizontal scrollbars during dragging the widow width by the mouse.
So here is the XAML that seems to be sufficient.
Note that the actual ListView kludge will be part of the concrete UserControl.
However, the following (simplified) code shows what I did:
<Window x:Class="DataGridSpike.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<ListBox HorizontalContentAlignment="Stretch">
<!-- ListView is just a kludge -->
<ListView HorizontalContentAlignment="Stretch" Padding="-4,2,-10,0" BorderThickness="0">
<ListView.View>
<GridView>
<GridView.ColumnHeaderContainerStyle>
<Style>
<Setter Property="FrameworkElement.Visibility" Value="Collapsed"/>
</Style>
</GridView.ColumnHeaderContainerStyle>
<GridViewColumn Width="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}}}" />
</GridView>
</ListView.View>
<DataGrid HorizontalContentAlignment="Stretch" HorizontalAlignment="Stretch"
Padding="-2,0" HorizontalScrollBarVisibility="Disabled">
<DataGrid.Columns>
<DataGridTextColumn Header="Column1"/>
<DataGridTextColumn Header="Column2" Width="1*" />
<DataGridTextColumn Header="Column3" />
</DataGrid.Columns>
</DataGrid>
</ListView>
</ListBox>
</Grid>
</Window>
Still, this is only a workaround. Any reasonable "fix" still welcome.
You could bind the DataGrid width back to the ListBox ActualWidth, this will give the DataGrid an actual size other than Auto and the * sizing should work.
<!-- Broken column width: -->
<DataGrid Width="{Binding ActualWidth, Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ItemsPresenter}}}" Margin="-2,0,0,0">
<DataGrid.Columns>
<DataGridTextColumn Header="Column1"/>
<DataGridTextColumn Header="Column2" Width="*"/>
<DataGridTextColumn Header="Column3"/>
</DataGrid.Columns>
</DataGrid>
However due to the fact the is some spacing between the content and the edges of the ListBox, you may have to tweak the Margin (like I did above) a tiny bit to avoid the DataGrid going over to the right.
Result:
Width="Auto" will solve the problem. Also you can specify 'MaxWidth' to limit the columnwidth expansion.
<DataGrid.Columns>
<DataGridTextColumn Header="Column1"/>
<DataGridTextColumn Header="Column2" Width="Auto" MaxWidth="200" />
<DataGridTextColumn Header="Column3"/>
</DataGrid.Columns>
Hopes this will help.

Group DataGrid with XmlDataProvider source

I have a XmlDataProvider, a ListBox and a DataGrid.
The underlying xml file has this kind of structure:
<Root>
<Person name="test">
<item name="bla" value="test"/>
<item name="bla" value="test2"/>
</Person>
<Root>
The ListBox lists all persons, while the DataGrid lists all items, corresponding to the selected Person. This works as intended.
Now i want to group the data in the DataGrid, but having looked at examples i still don't get how to do it with an XmlDataProvider (how/where to create a ListCollectionView off of the XmlDataProvider).
Could someone please give me a quick xaml example for doing this by e.g grouping the items by name?:)
Thanks for any help in advance :)
regards
UPDATE:
Now the grouping works, but when i add something to the xml, it is not shown instantly anymore (in listbox or datagrid).What is wrong? I am really new to wpf, so there might be things redundant or unnecessary, i got no problems with you pointing them out :)
Here is the relevant code that is used:
<Grid.DataContext>
<XmlDataProvider x:Name="XmlData" Source="entries.xml" XPath="Root/Person" />
</Grid.DataContext>
<ListBox Name="PersonListBox"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource listBoxTemplate}"
IsSynchronizedWithCurrentItem="True"
Visibility="Visible" SelectionMode="Single" SelectedIndex="-1" DataContext="{Binding}">
</ListBox>
<DataGrid IsSynchronizedWithCurrentItem="True" Name="itemGrid"
DataContext="{Binding ElementName=PersonListBox, Path=SelectedItem}"
CanUserAddRows="true"
IsReadOnly="true"
AutoGenerateColumns="False">
<DataGrid.Resources>
<CollectionViewSource x:Key="items" Source="{Binding XPath=item}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="#name"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</DataGrid.Resources>
<DataGrid.ItemsSource>
<Binding Source="{StaticResource items}"/>
</DataGrid.ItemsSource>
<DataGrid.Columns>
<DataGridTextColumn Width="*" Header="Name" Binding="{Binding XPath=#name}"/>
<DataGridTextColumn Header="Wert" Binding="{Binding XPath=#value}"/>
</DataGrid.Columns>
<DataGrid.GroupStyle>
<GroupStyle />
</DataGrid.GroupStyle>
</DataGrid>
Here is an example, should be quite self-explanatory but if something is not clear feel free to ask:
<DataGrid>
<DataGrid.Resources>
<CollectionViewSource x:Key="items" Source="{Binding SelectedItem, ElementName=lb}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="#name"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</DataGrid.Resources>
<DataGrid.ItemsSource>
<Binding Source="{StaticResource items}"/>
</DataGrid.ItemsSource>
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding XPath=#value}"/>
</DataGrid.Columns>
<DataGrid.GroupStyle>
<GroupStyle />
</DataGrid.GroupStyle>
</DataGrid>
(You can also set IsSynchronizedWithCurrentItem to true on the ListBox and then bind the Source via the current item instead (i.e. {Binding /, Source={StaticResource data}})

How do I add an autocompletebox to a datagrid?

I have a datagrid in my application with two columns and 4 rows. Each of the cells needs to be a WPF Toolkit autocompletebox control. I'd like to implement the whole thing using the MVVM pattern. It's easy to populate the cells with text boxes with:
<DataGrid ItemsSource="{Binding viewModel, Path=Fields}" AutoGenerateColumns="False" CanUserResizeColumns="True" CanUserSortColumns="True" AlternatingRowBackground="Gainsboro">
<DataGrid.Columns>
<DataGridTextColumn Header="Predicate" Binding="{Binding Key}"/>
<DataGridTextColumn Header="Value" Binding="{Binding Value}"/>
</DataGrid.Columns>
</DataGrid>
But trying to do the same with autocomplete boxes doesn't work for some reason. It compiles and runs but the autocomplete box doesn't open when typing. I used:
<DataGrid ItemsSource="{Binding viewModel, Path=Fields}" AutoGenerateColumns="False" CanUserResizeColumns="True" CanUserSortColumns="True" AlternatingRowBackground="Gainsboro">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Pre">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<wpfToolkit:AutoCompleteBox ItemsSource="{Binding viewModel, Path=AvailableFields}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
The definitions for the bound properties are:
public IEnumerable<KeyValuePair<string, string>> Fields
public IEnumerable<string> AvailableFields
Any ideas?
Update: The following is the entire XAML based on #Damascus reply. Doesn't work either for some reason.
<UserControl x:Class="IKB.Views.IKBInputView"
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"
xmlns:vm="clr-namespace:IKB.ViewModels"
xmlns:wpfToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input.Toolkit"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<UserControl.Resources>
<vm:IKBInputVM x:Key="viewModel" />
</UserControl.Resources>
<StackPanel Orientation="Vertical">
<DataGrid ItemsSource="{Binding viewModel, Path=Fields}" AutoGenerateColumns="False" CanUserResizeColumns="True" CanUserSortColumns="True" AlternatingRowBackground="Gainsboro">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Pre">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<wpfToolkit:AutoCompleteBox ItemsSource="{Binding DataContext.viewModel.AvailableFields, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</UserControl>
It is because you are not in the same DataContext here.
Each AutoCompleteBox will have its own DataContext (not sure about its exact position, but it is related to the current cell itself) , so it will look for the ItemsSource in a wrong place.
Your Binding has to refer to the original DataContext.
Assuming that this DataGrid is in a UserControl, try:
<wpfToolkit:AutoCompleteBox ItemsSource="{Binding DataContext.viewModel.AvailableFields,
RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}} />
This should do the trick (it will now look into the UserControl's DataContext, which is the one you are working with)

How can I pass data back from my XAML to my binding source?

I've XAML that looks a bit like this:
<UserControl x:Class="MyNamespace.MyClass"
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"
xmlns:local="clr-namespace:MyNamespace"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="500">
<UserControl.Resources>
<local:MyViewModel x:Key="ViewModel"/>
</UserControl.Resources>
<UserControl.DataContext>
<Binding Source="{StaticResource ViewModel}"/>
</UserControl.DataContext>
<DataGrid Name="_myGrid" AutoGenerateColumns="False"
ItemsSource="{Binding Path=MyDataTable}">
<DataGrid.Columns>
<DataGridTemplateColumn Header="Name">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path=name}" />
<TextBlock Text="{Binding
Source={StaticResource ViewModel}, Path=IsValidName}"/>
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<Grid FocusManager.FocusedElement="{Binding ElementName=NameTextBox}">
<TextBox Name="NameTextBox" Text="{Binding Path=name}" />
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
As you can probably see above, my DataGrid binds to a data table as its ItemsSource. I typically bind to columns in the data table view, such as 'name'. In some cases, I bind to my view model, rather than a DataRowView. That can be seen in the 2nd TextBlock above, where I bind to the view model's IsValidName property. My question is, how can I pass information from my XAML back to my binding source, in this case my view model? I'd like to pass the rowIndex, DataRowView object, name, or something similar so that the IsValidName property on the view model would have context as to which row is being validated.
Thanks,
Notre
Looks like you want to call your IsValidName Property like a Method passing Parameters as to which row is being validated.
if you have control over each row's Type, implement the IsValidName Property there, completely avoiding the StaticResource Binding!

Resources