Group DataGrid with XmlDataProvider source - wpf

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}})

Related

WPF conditional datagrid grouping

I have a DataGrid with grouping based on customer name, and it works.
<GroupBox Header="{Binding ElementName=MainWindow, Path=ocS3FileListCount}" ContentStringFormat="" Name="grpRemote" Margin="5,0,0,0" Grid.Column="1" Grid.Row="2">
<GroupBox.Resources>
<CollectionViewSource x:Key="S3List" Source="{Binding ElementName=MainWindow, Path=ocS3FileList}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="CustomerName"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</GroupBox.Resources>
<DataGrid x:Name="dgS3List" Margin="0,0,0,0" ItemsSource="{Binding Source={StaticResource S3List}}" CanUserAddRows="False" CanUserDeleteRows="False" AutoGenerateColumns="False">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Label Content="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="File Name" Width="*" Binding="{Binding Path=FileName}" IsReadOnly="True" />
<DataGridTextColumn Header="Received" Width="100" Binding="{Binding Path=JobReceived,StringFormat=d}" IsReadOnly="True" Stylus.IsPressAndHoldEnabled="True" />
<DataGridTextColumn Header="Date" Width="100" Binding="{Binding Path=JobDate,StringFormat=d}" IsReadOnly="True" Stylus.IsPressAndHoldEnabled="True" />
</DataGrid.Columns>
</DataGrid>
</GroupBox>
I do have a few users that would like the option to turn grouping on and off for different uses.
I've used conditional styles before based on the status of a different control, but is it possible to have a conditional GroupStyle?
Having it enabled based on a toolbar checkbox seems like a good solution:
<CheckBox x:Name="chkGroupSwitch" IsChecked="True">Enable Grouping</CheckBox>
I don't know about a way to make your GroupStyle style conditional in XAML, but since the GroupStyle only applies if you have a GroupDescription in your ViewSource, I would use a different approach:
The solution in C# is simple. All you have to do is add/remove the ViewSource's PropertyGroupDescription whenever the state of your checkbox changes.
eg.
private void ToggleGroupingEnabled(bool mode)
{
CollectionViewSource viewSource = grpRemote.Resources["MyViewSourceName"] as CollectionViewSource;
viewSource.GroupDescriptions.Clear();
if (mode)
{
var groupDesc = new PropertyGroupDescription("CustomerName");
viewSource.GroupDescriptions.Add(groupDesc);
}
}
Assuming your ViewSource has a name:
<CollectionViewSource x:Key="S3List" x:Name="MyViewSourceName" Source="{Binding ElementName=MainWindow, Path=ocS3FileList}">

WPF DataGrid TemplateColumn not switching between edit and normal templates

I have a WPF DataGrid and I am trying to display a TextBlock when not in edit mode and a ComboBox when editing. I would think this would be easy, but it is not. I can only get it to working using a brute force method of placing the controls in a Grid and using a Visibility binding to show the controls in turn; which is a real pain.
Why does the following not work:
I can get one or the other to show, but not both based on cell editing.
<DataGrid Background="DarkGray" ItemsSource="{Binding Items}" CanUserAddRows="false" AutoGenerateColumns="False"
ScrollViewer.CanContentScroll="True" ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
SelectionChanged="DataGrid_SelectionChanged" SelectedIndex="{Binding SelectedIndex}"
behaviors:DataGridBehavior.OnSelectAction="{Binding Path=OnSelectionChanged}">
<DataGrid.Columns>
<DataGridTextColumn Header="Button" Binding="{Binding Button}" />
<DataGridTextColumn Header="Button Display" Binding="{Binding ButtonDisplay}" />
<DataGridTextColumn Header="Reason Code" Binding="{Binding ReasonCode}" />
<!--<DataGridTextColumn Header="Count Stamps" Binding="{Binding CountStamps}" />-->
<DataGridTemplateColumn Header="Count Stamps">
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<ComboBox IsEditable="True" SelectedValue="{Binding CountStamps}"
ItemsSource="{Binding Values}" DisplayMemberPath="Name" SelectedValuePath="Value" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding CountStamps}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" Width="*" />
</DataGrid.Columns>

DataGrid sorting doesn't work

I have a TabControl that has one TabItemwith a DataGrid inside.
<TabControl Background="{DynamicResource StandardBackgroundColor}"
Grid.Row="2" Grid.Column="1"
BorderBrush="{DynamicResource StandardBorderColor}"
DataContext="{Binding ChartViewModel}">
<TabItem Header="{lex:Loc Data}">
<DataGrid Name="TagGrid" ItemsSource="{Binding UnionAllSerie}"
ColumnWidth="*" Background="#CCCCCC">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="{lex:Loc time}" SortDirection="Descending"
Binding="{Binding X, Converter={StaticResource OleDateToDateTimeConverter}, StringFormat=\{0:dd/MM/yy HH:mm\}}" />
<DataGridTextColumn Header="{lex:Loc Measure}"
Binding="{Binding Y}" />
</DataGrid.Columns>
</DataGrid>
</TabItem>
Where my UnionAllSerie is declared as follow:
public ObservableCollection<Data> UnionAllSerie { get; set; } = new ObservableCollection<Data>();
This collection of data is populated depending on the action performed on the window. What I want is to have the Grid automatically sorted by the second column, which represents the date.
Does anybody know what I am doing wrong or missing?
Thanks!
Setting the SortDirection on a Datagrid column does not actually sort the column. (see here for details).
I would recommend using a CollectionViewSource which has build in functionality for your purpose.
The result should look something like this:
<Window.Resources>
<CollectionViewSource x:Key="UnionAllSerieViewSource" Source="{Binding UnionAllSerie}">
<CollectionViewSource.SortDescriptions>
<scm:SortDescription PropertyName="X" Direction="Descending"/>
</CollectionViewSource.SortDescriptions>
</CollectionViewSource>
</Window.Resources>
...
<DataGrid Name="TagGrid" ItemsSource=""{Binding Source={StaticResource UnionAllSerieViewSource}}""
ColumnWidth="*" Background="#CCCCCC">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="{lex:Loc time}" SortDirection="Descending"
Binding="{Binding X, Converter={StaticResource OleDateToDateTimeConverter}, StringFormat=\{0:dd/MM/yy HH:mm\}}" />
<DataGridTextColumn Header="{lex:Loc Measure}"
Binding="{Binding Y}" />
</DataGrid.Columns>
</DataGrid>
For more information on how to sort CollectionViewSources see this msdn article.

WPF Binding in DataGridColumnHeader DataTemplate

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>

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)

Resources