This was a tricky one. My idea would be to create an adorner that will be responsible for drawing the different lines you need. I'm not fond of creating unnecessary row objects.
Here is a starting example (there are still some glitches and it will need tweaking, but I think it's a good start.)
XAML
<Window x:Class="WpfApplication11.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
xmlns:local="clr-namespace:WpfApplication11"
Title="MainWindow" Height="350" Width="525">
<local:MyDataGrid HeadersVisibility="Column">
<local:MyDataGrid.Columns>
<DataGridTextColumn Header="Column 123" Binding="{Binding}" />
<DataGridTextColumn Header="Column 2" Binding="{Binding}" />
<DataGridTextColumn Header="Column 33333333333333333333333" Binding="{Binding}" />
</local:MyDataGrid.Columns>
<sys:String>Row</sys:String>
<sys:String>Row</sys:String>
</local:MyDataGrid>
</Window>
Control Code
public static class Visual_ExtensionMethods
{
public static T FindDescendant<T>(this Visual @this, Predicate<T> predicate = null) where T : Visual
{
return @this.FindDescendant(v => v is T && (predicate == null || predicate((T)v))) as T;
}
public static Visual FindDescendant(this Visual @this, Predicate<Visual> predicate)
{
if (@this == null)
return null;
var frameworkElement = @this as FrameworkElement;
if (frameworkElement != null)
{
frameworkElement.ApplyTemplate();
}
Visual child = null;
for (int i = 0, count = VisualTreeHelper.GetChildrenCount(@this); i < count; i++)
{
child = VisualTreeHelper.GetChild(@this, i) as Visual;
if (predicate(child))
return child;
child = child.FindDescendant(predicate);
if (child != null)
return child;
}
return child;
}
}
public class GridAdorner : Adorner
{
public GridAdorner(MyDataGrid dataGrid)
: base(dataGrid)
{
dataGrid.LayoutUpdated += new EventHandler(dataGrid_LayoutUpdated);
}
void dataGrid_LayoutUpdated(object sender, EventArgs e)
{
InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
var myDataGrid = AdornedElement as MyDataGrid;
if (myDataGrid == null)
throw new InvalidOperationException();
// Draw Horizontal lines
var lastRowBottomOffset = myDataGrid.LastRowBottomOffset;
var remainingSpace = myDataGrid.RenderSize.Height - lastRowBottomOffset;
var placeHolderRowHeight = myDataGrid.PlaceHolderRowHeight;
var lineNumber = (int)(Math.Floor(remainingSpace / placeHolderRowHeight));
for (int i = 1; i <= lineNumber; i++)
{
Rect rectangle = new Rect(new Size(base.RenderSize.Width, 1)) { Y = lastRowBottomOffset + (i * placeHolderRowHeight) };
drawingContext.DrawRectangle(Brushes.Black, null, rectangle);
}
// Draw vertical lines
var reorderedColumns = myDataGrid.Columns.OrderBy(c => c.DisplayIndex);
double verticalLineOffset = - myDataGrid.ScrollViewer.HorizontalOffset;
foreach (var column in reorderedColumns)
{
verticalLineOffset += column.ActualWidth;
Rect rectangle = new Rect(new Size(1, Math.Max(0, remainingSpace))) { X = verticalLineOffset, Y = lastRowBottomOffset };
drawingContext.DrawRectangle(Brushes.Black, null, rectangle);
}
}
}
public class MyDataGrid : DataGrid
{
public MyDataGrid()
{
Background = Brushes.White;
Loaded += new RoutedEventHandler(MyDataGrid_Loaded);
PlaceHolderRowHeight = 20.0D; // random value, can be changed
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
}
private static void MyDataGrid_Loaded(object sender, RoutedEventArgs e)
{
var dataGrid = sender as MyDataGrid;
if (dataGrid == null)
throw new InvalidOperationException();
// Add the adorner that will be responsible for drawing grid lines
var adornerLayer = AdornerLayer.GetAdornerLayer(dataGrid);
if (adornerLayer != null)
{
adornerLayer.Add(new GridAdorner(dataGrid));
}
// Find DataGridRowsPresenter and set alignment to top to easily retrieve last row vertical offset
dataGrid.DataGridRowsPresenter.VerticalAlignment = System.Windows.VerticalAlignment.Top;
}
public double PlaceHolderRowHeight
{
get;
set;
}
public double LastRowBottomOffset
{
get
{
return DataGridColumnHeadersPresenter.RenderSize.Height + DataGridRowsPresenter.RenderSize.Height;
}
}
public DataGridColumnHeadersPresenter DataGridColumnHeadersPresenter
{
get
{
if (dataGridColumnHeadersPresenter == null)
{
dataGridColumnHeadersPresenter = this.FindDescendant<DataGridColumnHeadersPresenter>();
if (dataGridColumnHeadersPresenter == null)
throw new InvalidOperationException();
}
return dataGridColumnHeadersPresenter;
}
}
public DataGridRowsPresenter DataGridRowsPresenter
{
get
{
if (dataGridRowsPresenter == null)
{
dataGridRowsPresenter = this.FindDescendant<DataGridRowsPresenter>();
if (dataGridRowsPresenter == null)
throw new InvalidOperationException();
}
return dataGridRowsPresenter;
}
}
public ScrollViewer ScrollViewer
{
get
{
if (scrollViewer == null)
{
scrollViewer = this.FindDescendant<ScrollViewer>();
if (scrollViewer == null)
throw new InvalidOperationException();
}
return scrollViewer;
}
}
private DataGridRowsPresenter dataGridRowsPresenter;
private DataGridColumnHeadersPresenter dataGridColumnHeadersPresenter;
private ScrollViewer scrollViewer;
}
This specific piece of code
void dataGrid_LayoutUpdated(object sender, EventArgs e)
{
InvalidateVisual();
}
you really don't want. It's the easiest but ugliest way to get OnRender being called when it has to. You should only force OnRender to be called on Column reordering and Column Size changed. Good luck !
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…