I solved this by creating an attached dependency property in a static class, PaginationBlocking
:
public static class PaginationBlocking
{
public static IList<UIElement> GetBlockList(UIElement element)
{
return (IList<UIElement>)element.GetValue(BlockListProperty);
}
public static void SetBlockList(UIElement element, IList<UIElement> value)
{
element.SetValue(BlockListProperty, value);
}
public static readonly DependencyProperty BlockListProperty =
DependencyProperty.RegisterAttached("BlockList", typeof(IList<UIElement>),
typeof(PaginationBlocking), new UIPropertyMetadata(null, OnBlockListPropertyAttached));
static void OnBlockListPropertyAttached(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var element = d as UIElement;
if(element.is_null() || (bool)element.GetValue(IsBlockListOwnerProperty)) return;
var list = e.NewValue as IList<UIElement>;
if(list.is_null()) return;
if(!list.Contains(element)) list.Add(element);
}
}
I attach the property to any element that I don't want to break across pages:
<local:MyUserControl Height="20" UI:PaginationBlocking.BlockList="{Binding blockList}" />
<local:MyUserControl Height="30" UI:PaginationBlocking.BlockList="{Binding blockList}" />
Obviously you have to have a List<UIElement>
to which you can bind. Binding a UIElement
adds it to the bound list.
I paginate by creating a VisualBrush
and painting the background of a Canvas
with it. When I am paginating, I enumerate all of the elements in the list to get their y-offset relative to the upper-left corner. I iterate until I reach an item whose y-offset exceeds the available space for the page. I basically subtract one item and make that the last one for the page.
I don't really have code to show because my implementation is littered with other things (like scaling and capturing column headers to re-render at the top of the next page), but that is the gist of it.