So, after hacking away at it, I figured out a way. It is pretty nasty, but it works.
If I were in WPF, I would use the AlternationIndex and set a style trigger for index 0,1,2 and set the border thickness of index 2 to have a bottom index.
In Silverlight, we don't have AlternationIndex OR style triggers. So, I had to hack. I wrapped the code into a behavior, so my XAML is pretty clean:
Controls:DataGrid DataGridLines:HorizontalGridLineModulus.Index="3" ... />
The code to support it looks like this:
public static class HorizontalGridLineModulus
{
private static readonly DependencyProperty HorizontalGridLineModulusBehaviorProperty =
DependencyProperty.RegisterAttached(
"HorizontalGridLineModulusBehavior",
typeof (HorizontalGridLineModulusBehavior),
typeof (DataGrid),
null);
public static readonly DependencyProperty IndexProperty =
DependencyProperty.RegisterAttached(
"Index",
typeof (int),
typeof (HorizontalGridLineModulus),
new PropertyMetadata(1, IndexChanged)
);
public static int GetIndex(DependencyObject dependencyObject)
{
return (int)dependencyObject.GetValue(IndexProperty);
}
public static void SetIndex(DependencyObject dependencyObject, int value)
{
dependencyObject.SetValue(IndexProperty, value);
}
private static void IndexChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var behavior = dependencyObject.GetValue(HorizontalGridLineModulusBehaviorProperty) as HorizontalGridLineModulusBehavior;
if (behavior == null)
{
behavior = new HorizontalGridLineModulusBehavior(dependencyObject as DataGrid, (int)e.NewValue);
dependencyObject.SetValue(HorizontalGridLineModulusBehaviorProperty, behavior);
}
}
}
public class HorizontalGridLineModulusBehavior
{
private readonly DataGrid _grid;
private readonly int _modulus;
public HorizontalGridLineModulusBehavior(DataGrid grid, int modulus)
{
if(grid == null)
throw new ArgumentException("grid");
_grid = grid;
_modulus = modulus;
_grid.LayoutUpdated += GridLayoutUpdated;
}
private void GridLayoutUpdated(object sender, EventArgs e)
{
DrawBorders();
}
private void DrawBorders()
{
var presenter = _grid.Descendants<DataGridRowsPresenter>().FirstOrDefault();
if(presenter == null) return;
var orderedRows = presenter.Children.OrderBy(child => RowIndex(child));
int count = 0;
foreach (var row in orderedRows)
{
count++;
var gridLine = row.Descendants<Rectangle>().Where(x => x.Name == "BottomGridLine").FirstOrDefault();
if(gridLine != null)
gridLine.Visibility = count % _modulus != 0 ? Visibility.Collapsed : Visibility.Visible;
}
}
private static int RowIndex(UIElement element)
{
var row = element as DataGridRow;
return row == null ? 0 : row.GetIndex();
}
}
That code makes use of a few extension methods, defined here:
public static class DependencyObjectExtensions
{
public static IEnumerable<DependencyObject> Children(this DependencyObject element)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
yield return VisualTreeHelper.GetChild(element, i);
}
public static IEnumerable<T> Children<T>(this DependencyObject element) where T : DependencyObject
{
return element.Children().Filter<T>();
}
public static IEnumerable<DependencyObject> Descendants(this DependencyObject element)
{
foreach (var child in element.Children())
{
yield return child;
foreach (var descendent in child.Descendants())
yield return descendent;
}
}
public static IEnumerable<T> Descendants<T>(this DependencyObject element) where T : DependencyObject
{
return element.Descendants().Filter<T>();
}
public static IEnumerable<T> Filter<T>(this IEnumerable list) where T : class
{
foreach (var item in list)
{
if (item is T)
yield return (T)item;
}
}
}