While I was at it, I included support for frozen columns. We will be working in C# today.
Start by creating a new WPF project in C# called CheapDataGridFooters. I'm not going to support INotifyPropertyChanged because I want to focus on the scrolling functionality.
We will create a scrolling DataGrid that doesn't have footers. Later, we will add the footers.
The XAML and C# look like this. The LineCount etc. properties will be used later.
<Window x:Class="CheapDataGridFooters.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CheapDataGridFooters"
mc:Ignorable="d"
Title="MainWindow" Height="350"
Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<DataGrid ItemsSource="{Binding Items}" AutoGenerateColumns="False" HorizontalScrollBarVisibility="Auto" FrozenColumnCount="1" IsReadOnly="True">
<DataGrid.Columns>
<DataGridTextColumn Header="Description" Binding="{Binding Description}" Width="200"/>
<DataGridTextColumn Header="Sub Total" Binding="{Binding SubTotal}" Width="100"/>
<DataGridTextColumn Header="Tax"
Binding="{Binding Tax}" Width="100"/>
<DataGridTextColumn Header="S&H" Binding="{Binding SH}" Width="100"/>
<DataGridTextColumn Header="Other"
Binding="{Binding Other}" Width="100"/>
<DataGridTextColumn Header="Total"
Binding="{Binding Total}" Width="100" IsReadOnly="True"/>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
namespace CheapDataGridFooters
{
public partial class MainWindow : Window
{
public class cLine
{
public String Description { get; set; }
public Decimal SubTotal { get; set; }
public Decimal Tax { get; set; }
public Decimal SH { get; set; }
public Decimal Other { get; set; }
public Decimal Total { get; set; }
}
public List<cLine> Items { get; set; }
public Decimal LineCount { get; set; }
public Decimal TotalSubTotal { get; set; }
public Decimal TotalTax { get; set; }
public decimal TotalSH { get; set; }
public decimal TotalOther { get; set; }
public decimal TotalTotal { get; set; }
public MainWindow()
{
Items = new List<cLine>();
for
(int i = 0; i < 20; i++)
{
cLine oItem = new cLine();
oItem.Description = "Item " + i.ToString();
oItem.SubTotal = i;
oItem.Tax = ((Decimal)i) / 10;
oItem.SH = 0;
oItem.Other = 0;
oItem.Total = oItem.SubTotal +
oItem.Tax + oItem.SH + oItem.Other;
Items.Add(oItem);
oItem = null;
}
LineCount = Items.Count;
TotalSubTotal = Items.Sum((i) =>
i.SubTotal);
TotalTax = Items.Sum((i) =>
i.Tax);
TotalSH = Items.Sum((i) =>
i.SH);
TotalOther = Items.Sum((i) =>
i.Other);
TotalTotal = Items.Sum((i) =>
i.Total);
InitializeComponent();
}
}
}
To add our footers we need to put the DataGrid inside a two-row grid. It's tempting to use a vertical StackPanel, but you have to remember that StackPanels are intended to fully show all their children which means their children won't scroll.
Add some row definitions to the grid.
<Grid.RowDefinitions>
<RowDefinition
Height="*"/>
<RowDefinition
Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="*"/>
</Grid.ColumnDefinitions>
In this example, I don't have row headers but I do have a single frozen column.
<DockPanel Grid.Row="1">
<TextBlock
DockPanel.Dock="Left" Width="200" Text="{Binding LineCount, StringFormat='Count = {0}'}"/>
<ScrollViewer
DockPanel.Dock="Right" ScrollChanged="ScrollViewer_ScrollChanged" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Hidden">
<StackPanel Orientation="Horizontal" Margin="10,0,0,0">
<TextBlock Width="100" Text="{Binding TotalSubTotal}"/>
<TextBlock Width="100" Text="{Binding TotalTax}"/>
<TextBlock Width="100" Text="{Binding TotalSH}"/>
<TextBlock Width="100" Text="{Binding TotalOther}"/>
<TextBlock Width="100" Text="{Binding TotalTotal}"/>
</StackPanel>
</ScrollViewer>
</DockPanel>
<DataGrid... Loaded="DataGrid_Loaded"
HorizontalScrollBarVisibility="Hidden" BorderBrush="Black" BorderThickness="0,0,0,1">
The DataGrid_Loaded event handler will drill down to the DataGrid's ScrollViewer and store a reference to it. Add this private member and event handler to the code behind. You probably already have a generic library function that can find child controls of a specified type.
using System.Windows.Controls;
using System.Windows.Media;
private ScrollViewer sv = null;
private void DataGrid_Loaded(object sender, EventArgs e)
{
sv = GetScrollViewer((DataGrid)sender);
}
public static ScrollViewer GetScrollViewer(UIElement parent)
{
if
(parent == null) return null;
ScrollViewer sv = null;
for
(int i = 0; i < VisualTreeHelper.GetChildrenCount(parent)
&& sv == null; i++)
{
if (VisualTreeHelper.GetChild(parent, i) is ScrollViewer)
sv = (ScrollViewer)(VisualTreeHelper.GetChild(parent, i));
else
sv = GetScrollViewer(VisualTreeHelper.GetChild(parent, i) as UIElement);
}
return sv;
}
The last thing we have to do is tie the scroll viewer's horizontal scroll offset to the DataGrid's horizontal scroll offset. It would be nice if we could just bind them to the same property, but the horizontal scroll offset is read only.
Add an event handler called ScrollViewer_ScrollChanged
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
if
(sv != null)
sv.ScrollToHorizontalOffset(e.HorizontalOffset);
}
I took this approach rather that using an Infragistics grid or my FooterGrid because the original grid was complex and already written. This approach reuses the original grid and can be implemented in less than an hour.
No comments:
Post a Comment