Before getting into detail about how to implement the ISurfaceScrollInfo, I want to talk about creating a custom panel. But why create a custom panel? Of course there are several ways (as usual) to accomplish a behavior like the page panel, but I think creating a new panel is the best way to take advantage of the WPF framework.
The idea of implementing a custom panel is that we can use it with a ListBox control, SurfaceListBox to be more specific. By implementing a custom panel for a ListBox, we can actually tell the ListBox how we want to layout it’s items.
Now to creating a custom panel. In general, when a panel displays it’s content it does two things: measuring and arranging. First everything in the panel is measured. Measuring is needed to determine the size of the panel and the size depends on one thing: the sizes of the containing items. It is during measuring we have the chance to determine how much space we need to layout the content. In WPF this is the moment when the items desiredSize is set.
After all items are measured, they are arranged. The point of arranging is quite obvious. This is the time when we place each item relatively to each other. Where items are placed depends heavily on their size and that’s why measuring is done before the arrangement. The arrangement is “view independent”, which means you don’t have to think about where you place the items in respect of what is actually viewed to the user. In our case, this is taken care of by SurfaceScrollViewer and that’s the whole point of implementing ISurfaceScrollInfo later on.
To control the measurement and arrangement there are two methods that needs to be overridden in our custom panel:
- protected override Size MeasureOverride(Size availableSize)
- protected override Size ArrangeOverride(Size finalSize)
Big surprise huh? As you see MeasueOverride receives a size which describes the available space we have to layout our items. Constrains with other words. Here’s an example: a ListBox which measures 300 times 80 gives an availableSize of 298 times 76. The size returned from the method is the space we want (we may or may not get it). In our implementation only basic measurements are done:
protected override Size MeasureOverride(Size availableSize)
var resultSize = new Size(0, 0);
foreach (UIElement child in Children)
resultSize.Width = Math.Max(resultSize.Width, child.DesiredSize.Width);
resultSize.Height = Math.Max(resultSize.Height, child.DesiredSize.Height);
resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ? resultSize.Width : availableSize.Width;
resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ? resultSize.Height : availableSize.Height;
It’s important that we call Measure on each child or else we won’t have any desired sizes. We can also see that we tell WPF that we need the same space as given to us to layout our items. (Notice the safety precaution if we get infinite available size. It can happen.)
Now for arranging the items. The argument here is the size that WPF is willing to give us and, as before, it may or may not be equal to the size we wanted earlier. In this implementation all items are placed horizontally. Nothing fancy:
protected override Size ArrangeOverride(Size finalSize)Here we return the same size as given to us. The documentation only say: “The actual size used.” but I think this is probably important when doing more advanced layouting. In our case returning the same size works fine.
if (Children.Count == 0)
var startOffset = 0;
foreach (UIElement child in Children)
var destination = new Rect(startOffset, 0.0, child.DesiredSize.Width, child.DesiredSize.Height);
startOffset += child.DesiredSize.Width;
That’s all we need to do for measuring and arranging our items. Next post we will start looking at the ISurfaceScrollInfo interface! Stay tuned.