
A general idea of what a WPF Layout System is can be obtained from msdn (
1 ,
2 ). It says that the controls form a Visual-tree, that each of the controls has its own specific rectangle, within which it is drawn, that the definition of these rectangles rests on the Layout System and is performed in 2 steps (measure and arrange) and that WPF is retained mode graphic system, as opposed to ordinary Immediate and what are the advantages of this approach.
However, reading msdn raises a number of questions to which there are no answers in the documentation, and one can only guess what is happening. For example, what happens if a child control at the measure stage requests a size larger than the availableSize given to it? Or - how, if necessary, to implement the
MeasureOverride and
ArrangeOverride methods correctly, so that the written code does not contradict accepted agreements on how the
Measure and
Arrange stages should be performed? Does the result obtained at the
Measure stage affect the
Arrange stage and the drawing, or does only the call to
Arrange affect the drawing, and the
Measure the purely informational stage?
')
Let's try to understand in more detail what is happening behind the scenes.
Retained mode system
To begin with, let's remember what a
retained mode graphic system is , for example, WPF. This is simply an approach to drawing graphics, in which the responsibility for identifying areas requiring redrawing and performing this redrawing is transferred to the graphics system. That is, WPF is responsible for ensuring that every window and control is redrawn if necessary. The programmer is no longer soared about handling
WM_PAINT events, as was the case in the Win32 API and Windows Forms, but simply sets the draw method for the control, if necessary, and WPF will determine when and which piece it will need to revalidate. Programmatically, it looks like this: instead of
each time the WM_PAINT message
arrives , it is necessary to determine the regions that need to be redrawn and to perform this procedure, the programmer
once specifies the sequence of commands necessary for drawing the control. This is done in the
OnRender method. The
OnRender method
is called by the WPF system:
- If it is still unknown how to draw this control
- If some DependencyProperty has changed with the FrameworkPropertyMetadataOptions.AffectsRender flag (for example, Button.Content ) or with other flags that implicitly result in AffectsRender
- If the control was clearly marked for revalidation (by calling InvalidateMeasure , InvalidateArrange, or InvalidateVisual )

The
OnRender method
is called with the
DrawingContext argument, in which we pull the supposedly drawing methods like
DrawEllipse, etc. Allegedly, because in fact there is no rendering, and all our calls are added to the command queue, and will be used when WPF decides that the control needs to be redrawn.
Actually, here is the answer to a frequent question - let's say we have a button on which something is written, we change the text of this button - but at what point and how does it determine that we need to reposition ourselves and redraw? After all, we just changed the value of the property. And the following happens: the text of the button affects the rendering, and is marked with the appropriate flag, so changing the value of this property causes the button to be marked as “dirty”, that is, one that requires redrawing. And soon after this, WPF will update the rendering for it. This will not happen immediately, but only when WPF has time for this. That is, if immediately after the replacement you get
RenderSize , then it will not change. But if you force a forced update of the markup using the
UpdateLayout () method, then
RenderSize will become the one that corresponds to the modified text. Actually,
Dispatcher.Invoke is connected with this mechanism, by the way - Priority.Render is among the available priorities, which means that when a delegate is called with this priority, it will be executed along with the procedures performed for element rendering.
Measuring
Why is this so important? Because the
Measure and
Arrange stages, in which elements are positioned, work in a similar way. They are called once, and the control has the
MeasureIsValid and
ArrangeIsValid flags . After that, the
Measure and
Arrange calls return immediately, doing nothing. In order to force the control to recalculate these things, you must again either change some
DependencyProperty with the
AffectsMeasure / AffectsArrange flag , or explicitly reset the flags by calling InvalidateMeasure / InvalidateArrange. The documentation also suggests that the revalidation of a
Measure automatically entails a revalidation of
Arrange , however, this is already quite obvious. In general, the first conclusion is this: if there is a certain property in your control that can change the size and / or placement of child controls when changing, then you should make it
DependencyProperty and set the
AffectsMeasure / AffectsArrange flag . If not every change in the value of this property leads to the need for revalidation of the control, then it is better not to do this, but to do a
DependencyProperty with the given
valueChangedCallback , in which, if necessary, manually call
InvalidateMeasure / InvalidateArrange not to burden WPF with unnecessary work.
Now consider these two methods in terms of software design. That is - what are their areas of responsibility and input / output data. This is probably the most important thing in understanding how the WPF Layout System works. I personally took a lot of time in order to enjoy this topic. It was necessary even to rummage in source codes WPF, the blessing they are accessible.
Measure and Arrange
The documentation for the
Measure is written in such a way that it seems as if this is a purely informational step and should not affect the display. It seems to be all logical - the parent control polls the children, learns from them how much they would like to occupy space, well, it decides for whom how much space to cut off, and calls
Arrange . In general, at the PresentationCore level this is the case (there UIElement contains empty virtual methods MeasureCore and ArrangeCore), but we are most likely interested in more specific behavior, the behavior of FrameworkElement and its heirs, and the FrameworkElement is already defined in the PresentationFramework assembly.
I will try to formulate as clearly and correctly as I can. If there are inaccuracies and errors, correct, make changes.
public void Measure (Size availableSize) - a method that, according to the specified availableSize, determines the desired sizes and sets them to this.DesiredSize. In the description of the method, it is written that the resulting DesiredSize may be> availableSize, but this is not so for the heirs of FrameworkElement.
The essence of the
Measure method is to do the following things:
- Call recursively Measure for all VisualChild elements (otherwise, the IsMeasureValid flag will not be set and the child element will not be drawn). At least once Measure must be called. Measure can be called more than once (for example, you can first call Measure with the argument Size = double.PositiveInfinity to determine the full size of the control), with the last call to Measure should be made with the sizes that will actually be used to draw this child control.
- Prepare the state of the control to fit into the availableSize dimensions. This is the reason that the last call to Measure should determine the actual dimensions of the element. The reason is because if the control sets DesiredSize to a value greater than the availableSize, the grid cell with fixed dimensions will not know what to do. It seems that she has a fixed size, but no - the child knocks her fist on the table and wants more! Therefore, the documentation clause stating that availableSize is 'soft constraint' is strictly speaking incorrect in the context of FrameworkElement .
The second point is very important. Indeed, in the
Measure phase, preparation for drawing takes place. For example, the listbox when calling
Measure , if it understands that it does not fit in the dimensions, determines that a scrollbar will appear. And when you call
Arrange with a larger size, the scrollbar will still remain. And if on the contrary,
Measure was performed with
PositiveInfinity , and
Arrange - with real sizes, all that goes beyond the limits of arrangeRect - is simply cut off.
By the way, why the
FrameworkElement ? Is the light on it? It turns out that it really came together, and
FrameworkElement overrides
UIElement.MeasureCore and
UIElement.ArrangeCore with the sealed modifier, that is, all heirs of FrameworkElement (all controls, windows, etc.) will not change the behavior of MeasureCore and ArrangeCore. They can only leave wishes - for this, the MeasureOverride and ArrangeOverride methods are made. And here in
MeasureOverride, availableSize is really a soft constraint, and it is quite legal to return a value that exceeds the value of the argument.
public void Arrange (Rect finalRect) - simply indicates the control of its place (x, y) and the size of the rectangle. Anything that goes beyond these limits will be cut off.
The following relationship exists between
Measure and
Arrange - ideally, the last call to
Measure should take the Size, which is the same size as the subsequent call to
Arrange . If so, then the control will be rendered perfectly. If the condition is not met, then problems are possible. That is, it is possible that everything will be rendered correctly, and maybe not quite. For example, the listbox in this situation (when arrangeSize <measureSize) on the right shifts the naura (the scrollbar slides to the left along with the borders, rather than being cut off), and from the bottom it is cut off.
Now it remains to consider the methods
MeasureOverride and
ArrangeOverride .
MeasureOverride and ArrangeOverride
protected virtual Size MeasureOverride (Size availableSize) is designed to enable developers to create their own control panels with their placement logic. Usually, the
MeasureOverride algorithm should perform the following steps:
- To estimate the full size of children - by a recursive call Measure with the parameters Size.Width and Size.Height = double.PositiveInfinity
- Rate your own full size considering the size of the children.
- If we fit into availableSize, then we return the value of our own full size.
- Otherwise, we may need to re-call Measure in children, but not with PositiveInfinity but with specific values in order to meet the availableSize allocated to us. The concrete implementation of this stage depends on the placement logic that we want to implement.
- We return availableSize as DesiredSize , if it turned out to be within the availableSize, well, or the minimum value exceeding the availableSize, which will allow our control to be fully rendered
protected virtual Size ArrangeOverride (Size finalSize) - and here we simply call the Arrange method for each child element with the appropriate bounds and position.
Note - in
MeasureOverride, you can return a value greater than the availableSize! But if you do this and test DesiredSize controls, then we will be surprised that
DesiredSize = availableSize. That is, someone ignored our result and wrote down the value of the
Measure argument. However, if we further call
ArrangeOverride , our argument will magically
return to our value, which we returned from
MeasureOverride .
What's happening? And that's what happens.
If
FrameworkElement.Measure is called with finite constraints, regardless of what our
MeasureOverride returns, FrameworkElement.MeasureCore will cut it and set
DesiredSize <= availableSize. And our DesiredSize will cache it, later transferring it to ArrangeOverride. This happens because FrameworkElement ensures that when you call Measure, it will
fit into the piece allotted to it, even if it has to trim our content.
Otherwise, it would be the case that grid cells with specific width / height values would disperse on controls that return
DesiredSize > availableSize. And so it turns out that
FrameworkElement retains the real requirements for DesiredSize, and when the time comes to
Arrange , it calls our
ArrangeOverride method with the
DesiredSize value that we returned to
MeasureOverride . And we in ArrangeOverride arrange the children as we want.
After that,
FrameworkElement.ArrangeCore , in the context of which our
ArrangeOverride is called , performs the clipping of our content, and we see in the grid
part of our control. And which part depends on the properties of
Horizontal / VerticalAlignment , etc. But the content is virtually drawn as we wanted, because our
DesiredSize was saved and transferred to
ArrangeOverride .
And when we implement the heirs of
FrameworkElement, we don’t have to sweat about clipping in such situations - he will do everything for us. And if we need a panel that processes
DesiredSize > availableSize, then we either do something wrong, or we will have to go down a level to
UIElement 's, which does not finalize (seal)
MeasureCore and
ArrangeCore .
To test all this, you can create a
Button inheritor and return a fixed size to
MeasureOverride , and place the button in a grid cell with a smaller size (say, 50x50). The button will be cropped.
protected override Size MeasureOverride(Size constraint) {
return new Size(80, 80);
}
This feature is described in
http://social.msdn.microsoft.com/In conclusion, I would like to bring the
code to some of the methods from WPF sources (UIElement and FrameworkElement classes) related to the considered material. Pieces with comments, enough detailing the essence.
The whole source WPF can be downloaded
from here (download .Net - 4).
UPD:
afsherman suggests that the WPF source code download optional. Who has ReSharper installed can use the Ctrl + Click source download option built into it on the class / method / property name, etc.