Animate moving a datagridrow - wpf

I'm having an issue using animation to show the translation of a row in a grouped datagrid from one group to another. The animation happens, but the row moves behind other rows on it's way to the new position.
The grouping is done based on the content of one of the cells, so when a user changes the content, the row moves to the new group. Here's some XAML that shows the grid:
<DataGrid x:Name="dataGrid" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<TextBlock Text="{Binding Name}"/>
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
<DataGridTextColumn Header="Email" Binding="{Binding Email}"/>
<DataGridTemplateColumn Header="Country">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Country}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Text="{Binding Country, UpdateSourceTrigger=PropertyChanged}" TextChanged="TextBoxBase_OnTextChanged"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
To show the animation, I'm firing it off in the TextChanged event handler. The code there locates the DataGridRow and a target point in the new group, and calls a method to do the animation:
private void Animate(FrameworkElement element, Point target)
{
if (element != null)
{
Point sourcePoint = element.PointToScreen(new Point(0, 0));
double yOffset = target.Y - sourcePoint.Y;
element.SetValue(Panel.ZIndexProperty, 10); // this doesn't help
TranslateTransform translateTransform = new TranslateTransform();
element.RenderTransform = translateTransform;
Duration duration = new Duration(TimeSpan.FromSeconds(3));
DoubleAnimation anim = new DoubleAnimation(0, yOffset, duration);
translateTransform.BeginAnimation(TranslateTransform.YProperty, anim);
}
}
This works, but a row moving down to another group is shown going behind the other rows; i.e. the moving row is below and hidden by the other rows along the way. Setting ZIndex on the DataGridRow doesn't seem to do anything. Also note that moving a row up works fine -- the moving row is shown over the other rows. I've also tried firing the animation off of events other than TextChanged (which I expect I'll need to do eventually anyway) but I haven't found any that seem to matter.
Thanks.

I've come up with an answer for my question. The hint I needed came from this Silverlight post. In particular, the line "All the list items must be siblings if you want to lay them out in layers." What was going on for me was that because of the grouping, the rows weren't siblings of each other -- they were not direct children of a common parent. Instead, it was the GroupItems containing the rows that were the siblings. So I needed to add to my Animate() method the code:
GroupItem groupItem = TryFindParent<GroupItem>(element);
groupItem.SetValue(Panel.ZIndexProperty, 10);
where element is the DataGridRow to be moved.
I still need to come up with a better trigger for the animation, but at least the row is moving in front of the other rows along the path.

Related

WPF DataGrid - Last Column MinWidth = Auto, Width = *

In WPF how can I have the last column fill the remaining space BUT have a minimum width based on the content (Auto).
Therefore if the content is short it will stretch to fill the remaining space but if the content is long it will scroll rather than cropping the text.
Unfortunately the MinWidth property on the column is a Double and cannot be set to Auto.
Top grid shows column with short text and Width="Auto". Bottom grid shows long text with Width="*".
Ideally in the top example the 2nd col would extend to the end and in the bottom example we would get scrolling.
XAML:
<StackPanel>
<DataGrid Name="DataGrid1" Margin="5" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Width="100" Binding="{Binding Col1}"></DataGridTextColumn>
<DataGridTextColumn Width="Auto" Binding="{Binding Col2}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
<DataGrid Name="DataGrid2" Margin="5" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Width="100" Binding="{Binding Col1}"></DataGridTextColumn>
<DataGridTextColumn Width="1*" Binding="{Binding Col2}"></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
Code Behind:
Public Class ExampleObject
Public Property Col1 As String
Public Property Col2 As String
End Class
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
Dim items1 As New List(Of ExampleObject)
items1.Add(New ExampleObject With {.Col1 = "Hello", .Col2 = "World"})
DataGrid1.ItemsSource = items1
Dim items2 As New List(Of ExampleObject)
items2.Add(New ExampleObject With {.Col1 = "Hello", .Col2 = "World, Please provide a code sample when asking a question: stackoverflow.com/help/mcve"})
DataGrid2.ItemsSource = items2
End Sub
You can't do that in default DataGrid. Min and max size (either width or height) has to be set to a number value. Setting column size to Auto or to * value defines how it will behave in the DataGrid and you have to choose which kind of behavior you want to use. You can't have both at the same time. It's kinda like saying that you want your control to be red and blue at the same time.
You probably could achieve your goal somehow, but I think it would require rewriting DataGrid to a custom implementation.
While not technically making the last column fill the space but with a minimum width of Auto the following gives the same appearance to the user by adding a dummy column which fills the space but styled to appear as part of the last real column.
Couple of points to note:
If handling events based on a cell or if the grid is not read only there you may need to take into account the user may select the filler column
I have not styled the header in this example.
<DataGrid Name="DataGrid1" Margin="5" IsReadOnly="True" AutoGenerateColumns="False" GridLinesVisibility="None">
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderBrush" Value="Black"></Setter>
<Setter Property="BorderThickness" Value="0,0,1,1"></Setter>
</Style>
</DataGrid.CellStyle>
<DataGrid.Columns>
<DataGridTextColumn Width="100" Binding="{Binding Col1}"/>
<DataGridTextColumn Width="Auto" Binding="{Binding Col2}">
<DataGridTextColumn.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="BorderBrush" Value="Black"></Setter>
<Setter Property="BorderThickness" Value="0,0,0,1"></Setter>
</Style>
</DataGridTextColumn.CellStyle>
</DataGridTextColumn>
<DataGridTextColumn Width="1*" x:Name="FillerColumn"/>
</DataGrid.Columns>
</DataGrid>

WPF datagrid and Item Container Recycling

In WPF I have a dialog window with an embedded datagrid.
The purpose of the dialog is to select files to upload. I've added a column in the datagrid to allow selection. But there is a fairly complicated relationship between the rows -- uploading one file may require the upload a parent file, for example. Because of that, there is a handler that updates the checkboxes (checking/unchecking and setting isEnabled off/on)
I seem to have run into a problem with "Item Container Recycling" when I get many rows in the datagrid. Some of the definition looks like
<DataGrid Name="myGrid"
...
VirtualizingPanel.IsVirtualizing="True"
VirtualizingPanel.VirtualizationMode="Standard">
<DataGrid.Resources>
<Style TargetType="DataGridColumnHeader" x:Key="DataGridHeaderStyle" >
</Style>
<Style x:Key="SingleClickEditing" TargetType="{x:Type DataGridCell}">
<EventSetter Event="PreviewMouseLeftButtonDown" Handler="DataGridCell_PreviewMouseLeftButtonDown"></EventSetter>
<EventSetter Event="CheckBox.Checked" Handler="OnChecked"/>
<EventSetter Event="CheckBox.Unchecked" Handler="OnChecked"/>
</Style>
...
When I first created the datagrid I was not able to access all rows of the datagrid until I set:
VirtualizingPanel.VirtualizationMode="Standard"
Now on dialog initialization I find that I can access all rows of the grid and set them up OK.
But the odd thing is that later when the user clicks on a checkbox, the checkbox handler method attempts to access the checkbox from other rows to set them. But then again not all of the rows are available and I see strange results.
It is as if the following got turned off for the datagrid.
VirtualizingPanel.VirtualizationMode="Standard"
Programmatically in the cs code for the dialog I don't see that I am able to access a parameter grid.VirtualizingPanel to see what the value is.
I try to get all the rows of the grid as follows:
private List<DataGridRow> GetDataGridRows(DataGrid grid)
{
var itemsSource = grid.ItemsSource as IEnumerable;
List<DataGridRow> rows = new List<DataGridRow>();
if (null == itemsSource) return null;
foreach (var item in itemsSource)
{
DataGridRow row = grid.ItemContainerGenerator.ContainerFromItem(item) as DataGridRow;
if (row!=null) rows.Add(row);
}
return rows;
}
The problem is that sometimes the following returns null:
grid.ItemContainerGenerator.ContainerFromItem(item)
How I can I make sure that I can always get a list of all rows?
==================================
Added:
The XAML column definition for the checkbox column is as follows:
<DataGrid.Columns>
<DataGridCheckBoxColumn ElementStyle="{StaticResource CenteredCheckStyle}" MinWidth="15"
CellStyle="{StaticResource SingleClickEditing}" Visibility="{Binding exists}"
Binding="{Binding Path=toTransfer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" IsReadOnly="False"
CanUserSort="False" CanUserResize="false" CanUserReorder="false">
<DataGridCheckBoxColumn.HeaderTemplate>
<DataTemplate>
<CheckBox Checked="CheckBox_Checked" Unchecked="CheckBox_Checked" Loaded="CheckBox_Loaded"
HorizontalContentAlignment="Center" HorizontalAlignment="Center"
IsThreeState="False" Margin="8 0 0 0" Padding="0 5 0 0"/>
</DataTemplate>
</DataGridCheckBoxColumn.HeaderTemplate>
</DataGridCheckBoxColumn>

WPF ListView/Gridview allow users to select multiple items and group them together

I have a WPF ListView/GridViwe in a MVVM application. GridView is bound to a List in the ViewModel.
The requirement is that the users should be able to select multiple rows of the gridview, right-click on it and see a context menu "Group These Together". Once selected, all these items should be collapsed into one group with a expander or + sign added at the beginning.
Can somebody please help me in getting this working?
I was working on similar problem, but I had to group items by their let's say name. What I've done was create DataTrigger or MultiDataTrigger depending on your data requirements and then when conditions are true i.e. item selected change the container for GroupItem. What I mean is when you create your list view, you have to create it with grouped view, which btw is not grouped as you can declare it without expander and just use the StackPanel. After that you need 3 lines of Code to set the grouping. Here is an example for you:
MAIN.xaml
<ListView
ScrollViewer.CanContentScroll="False"
x:Name="lsvProducts"
ItemsSource="{Binding Products, NotifyOnSourceUpdated=True}"
MouseDown="lsvProducts_MouseDown">
<ListView.View>
<GridView>
<GridViewColumn Width="Auto" Header="Code" DisplayMemberBinding="{Binding ID}"/>
<GridViewColumn Width="Auto" Header="Description" DisplayMemberBinding="{Binding Desc}"></GridViewColumn>
<GridViewColumn Width="Auto" Header="Qty" DisplayMemberBinding="{Binding Qty}"></GridViewColumn>
</GridView>
</ListView.View>
<ListView.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource GroupedView}"/>
</ListView.GroupStyle>
</ListView>
As you can see I have declared an empty container for the grouped style, reson why is because you can't assign it without previous declaration. After this declaration you need this in your generic.xaml
<Style x:Key="GroupedView" TargetType="{x:Type GroupItem}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=chbx, Path=IsChecked}" Value="True">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Expander IsExpanded="False">
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" FontWeight="Bold" Foreground="Gray" FontSize="22" VerticalAlignment="Bottom" />
<TextBlock Text="{Binding ItemCount}" FontSize="16" Foreground="DimGray" FontWeight="Bold" FontStyle="Italic" Margin="10,0,0,0" VerticalAlignment="Bottom" />
<TextBlock Text=" item(s)" FontSize="16" Foreground="Silver" FontStyle="Italic" VerticalAlignment="Bottom" />
</StackPanel>
</Expander.Header>
<ItemsPresenter />
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
After you declared your style for the true value you need to declare one for without expander
<!--This goes in the same style as the prevoius sample code -->
<DataTrigger Binding="{Binding ElementName=chbx, Path=IsChecked}" Value="False">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<ItemsPresenter />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
Next step is to define the grouping:
//lsvProducts is our ListView
view = (CollectionView) CollectionViewSource.GetDefaultView(lsvProducts.ItemsSource);
//in the creation parameter you should put field that you want to group by :-)
PropertyGroupDescription grouping = new PropertyGroupDescription("FieldToGroupBy");
view.GroupDescriptions.Add(grouping);
Which should apper in your ViewModel.
Good luck!
Let us know if you need any more help, I'll try my best with my English ;-)
EDIT
I forgot to mention when you assign the grouping you need to check the number of groups already in the collection and only apply it when there is non or 0, otherwise you'll apply the grouping multiple times and the expander will be repeeated in the result window as many times as you added the grouping :-)
What in my mind to select multiple row Ctrl+click
It will select multiple row and on right click open a context menu after setting its Isopen to true
You should also bind selected item to list and Use this item to make an expander

How to get grip of a DataGrid's active RowDetails

I have a DataGrid with a RowDetailsTemplate containing another grid.
I want to react on a doubleclick on a row in that detailgrid and fill the cell's content into a corresponding cell of the selected parent row.
<DataGrid Name="dataGrid1" DataContext="{Binding}" ItemsSource="{Binding Source={StaticResource ..}}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Old Link Source" Binding="{Binding Path=OldLinkSource}"/>
<DataGridTextColumn Header="New Link Source" Binding="{Binding Path=NewLinkSource}"/>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid Name="dataGrid1Details" ItemsSource="{Binding Path=PossibleCandidates}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Similarity" Binding="{Binding Path=Key}"/>
<DataGridTextColumn Header="Possible New Link Source" Binding="{Binding Path=Value}"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
<DataGrid.RowDetailsTemplate>
</DataGrid>
From my understanding the detailgrid is recreated every time the row is changed. I'm new to WPF and clueless on how to get grip of the currently visible detailgrid and subscribe to its events.
You can add a Style for DataGridRow inside the RowDetails DataGrid and subscribe to the MouseDoubleClick event from there.
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<DataGrid Name="dataGrid1Details" ItemsSource="{Binding Path=PossibleCandidates}" AutoGenerateColumns="False">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseDoubleClick" Handler="DetailedDataGridRow_MouseDoubleClick"/>
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="Similarity" Binding="{Binding Path=Key}"/>
<DataGridTextColumn Header="Possible New Link Source" Binding="{Binding Path=Value}"/>
</DataGrid.Columns>
</DataGrid>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
Code behind, simple EventHandler
// Fill cell data.. You can access the values like this
void DetailedDataGridRow_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
DataGridRow clickedDataGridRow = sender as DataGridRow;
// Details: clickedDataGridRow.Item
// Main DataGrid: dataGrid1.SelectedItem
}
Update
The RowDetails and the DataGridRow are connected, sort of. The RowDetails is in the DataGridRow in the VisualTree so there's many ways to access it (events, walking VisualTree etc.) but I don't think there's a Property or something like that that'll give you direct access (as far as I know). Screenshot from Snoop showing the DataGridDetailsPresenter in a DataGridRow

Styling columns based on DataGridTemplateColumn in a WPF DataGrid

I am using a WPF DataGrid where one of the columns has a requirement to show an "Edit" hyperlink if the row is editable - this is indicated by a boolean flag in the backing model for the row. I was able to achieve this using a DataGridTemplateColumn - no problems. However an additional requirement on the entire row is not to show any highlights when the row is selected (this is a blue background by default). I have been able to achieve this on other columns by defining the DataGridCell style with a transparent background, e.g.
<DataGridTextColumn
Header="Id"
Binding="{Binding Path=Id}"
HeaderStyle="{StaticResource DataGridColumnHeaderStyle}"
CellStyle="{StaticResource DataGridCellStyle}" />
where DataGridCellStyle is defined as follows:
<Style x:Key="DataGridCellStyle" TargetType="{x:Type DataGridCell}">
<Setter Property="Background" Value="Transparent" />
...
</Style>
However the column in question, a DataGridTemplateColumn, does not offer a "CellStyle" attribute which I can use for turning off selection highlights. So my question is how to set the cell style when using a DataGridTemplateColumn? Here's my implementation of the column which satisfies the first requirement (i.e. showing an "Edit" hyperlink if the row is editable):
<DataGridTemplateColumn
Header="Actions"
HeaderStyle="{StaticResource CenterAlignedColumnHeaderStyle}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock
Visibility="{Binding Path=Editable, Converter={StaticResource convVisibility}}"
Style="{StaticResource CenterAlignedElementStyle}">
<Hyperlink
Command="..."
CommandParameter="{Binding}">
<TextBlock Text="Edit" />
</Hyperlink>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Thanks.
At least in WPF4, there is a CellStyle for DataGridTemplateColumns: http://msdn.microsoft.com/en-us/library/cc189163.aspx

Resources