I wrote a panel similar to the built-in StackPanel. I works almost fine except for a slight problem:
Changing layout properties on children do not always cause the panel's MeasureOverride and ArrangeOverride to be called. They are always called when a child's Visibility property changes, but not when the Width and Height properties change.
I haven't yet managed to reproduce this behavior in a sample small enough to be appropriate for being included in a question on StackOverflow: But since it works fine in the trivial sample I made, I know I must do something avoidable in my actual panel.
So my question: In which circumstances does an element not invalidate its parents measure when changing size-related properties?
I tag this wpf also (I used Silverlight) to have a broader audience - I suspect this will apply to both xaml implementations equally.
I figured what my mistake was and under which condition the panel's MeasureOverride is no longer called on certain changes for size-related properties.
My panel called Measure on children with the exact size the children should have, rather on the size of the panel.
A panel doesn't get it's MeasureOverride method called when children begin to desire more space than was told to them is available in the last Measure call - which makes sense.
Summary: The parameter for the Measure method you call on a child must denote the space the parent panel allots to all children, not just the one Measure is called on.
You must make sure you call the base methods MeasureOverride.
protected override Size MeasureOverride(Size availableSize)
{
// you must call this
var throwaway = base.MeasureOverride(availableSize);
// your code here
return yourNewSize;
}
Related
A custom WPF Control overrides OnRender. The method generates and displays a Path from custom data. The data provider is bound using a Dependency Property. The Dependency Property registers for an event when data changed. This event in turn calls InvalidateVisual().
However, after the InvalidateVisual() the OnRender is not always called.
We use the Prism Framework and the Region functionallity. The Control in question is embedded in such a Region, which is activated and deactivated. However, the Control's property "IsVisible" is true whenever the region is active. But still, when calling InvalidateVisual() the OnRender method is not called...
What could prevent the OnRender method from being called?
I just had this problem, too.
Context
I've got a load of controls based on the DynamicDataDisplay graph components inside a VirtualizingStackPanel (inside a ListBox).
When there are more controls that are visible at once, but not enough for the VirtualizingStackPanel to start re-using them when you scroll then I see this issue with the D3 AxisControl class. For some reason it does a lot of work in it's OnRender method, which it tries to trigger by calling InvalidateVisual when something changes.
In the problem case the problem controls call InvalidateVisual but they never get a call to MeasureOverride, ArrangeOverride or OnRender. Interestingly, most of the controls still work, in one particular problem case I get the last 3 out of a set of 11 failing to work properly. Notably those 3 (and only those 3) receive a call to MeasureOverride immediately before the data binding update that triggers the call to InvalidateVisual.
My Fix
In the end I managed to fix it by adding a call to InvalidateMeasure alongside the call to InvalidateVisual.
It's a horrible solution, but it's not a performance critical part of our application, so I seem to be getting away with it.
If the size of your control is staying the same, you should not be using InvalidateMeasure() or InvalidateVisual() because they trigger an expensive re-layout.
WPF is a retained drawing system. OnRender() might be better called AccumulateDrawingObjects(), because it doesn't actually draw. It accumulates a set of drawing objects which WPF uses to draw your UI whenever it wants. The magic thing is, if you put a DrawingGroup into the DrawingContext during OnRender(), you can actually efficiently update it after OnRender, anytime you like.
See my answer here for more details..
https://stackoverflow.com/a/44426783/519568
I just had this problem, too.
I had a scrollbar for a control which only figured out during OnRender() how much space is really needed to display all content, which could be bigger than the available display space and therefor needed a scrollbar. It could happen that OnRender() called some methods which ultimately changed the value of the scrollbar which was supposed to start OnRender() with InvalidateVisual().
However, OnRender() did not get called again after InvalidateVisual(). I guess the reason is that InvalidateVisual() sets some flags which tells WPF that the control needs to get drawn again, but once OnRender() finishes, that flag gets reset. Here some pseudo code how I expect it to happen:
//someCode:
control.InvalidateVisual()
//code of InvalidateVisual()
control.RedrawFlag = true;
//WPF some time later:
if (control.RedrawFlag){
control.OnRender()
//OnRender code
//do some stuff
//decide control needs to be redrawn
//however, RedrawFlag is alreday true!
//next line is not changing anything
control.RedrawFlag = true;
//WPF finished executing control.OnRender
control.RedrawFlag = false;
}
I didn't further investigate if WPF really works this way, but it would explain why OnRender() does not get called a second time.
Instead of wasting even more time, I changed how to calculate the total width of the control content can be and put this code outside of OnRender().
I'm doing custom rendering in a Decorator subclass. Our rendering requires creating complex geometries which only neded to be re-created when the actual rendered size changes. As such, I have moved the geometry creation into its own function called UpdateGeometry which creates, then freezes the geometry for use in OnRender. This new function only needs to be called in response to a change in ActualWidth or ActualHeight.
Even better, it looks like we should be able to simply override OnRenderSizeChanged, which according to the documentation states...
"When overridden in a derived class,
participates in rendering operations
that are directed by the layout
system. This method is invoked after
layout update, and before rendering,
if the element's RenderSize has
changed as a result of layout update."
However, regardless if I'm using the override or listening to the property change notifications of ActualWidth and ActualHeight, my logging consistently shows OnRender as happening first! Um... Wha??
To be sure it wasn't something I was doing in my code, I created a bare-bones test decorator subclass and added logging there, both on entry and exit to the overrides. Here is the entire class...
using System;
using System.Windows.Controls;
public class TestControl : Decorator
{
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext)
{
Console.WriteLine("OnRender Entered");
base.OnRender(drawingContext);
Console.WriteLine("OnRender Exited");
}
protected override void OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo)
{
Console.WriteLine("OnRenderSizeChanged Entered");
base.OnRenderSizeChanged(sizeInfo);
Console.WriteLine("OnRenderSizeChanged Exited");
}
}
And as I feared... here is the output...
OnRender Entered
OnRender Exited
OnRenderSizeChanged Entered
OnRenderSizeChanged Exited
So what am I missing here?
More importantly, how can I get the ActualWidth and ActualHeight values after the layout subsystem has done its job, but before the control is rendered so I can create the geometry before it's needed in the OnRender override?
My latest implementation overrides ArrangeOverride as the value that's passed in there is a size containing what the ActualWidth and ActualHeight values should be after the core layout system takes into consideration HorizontalAlignment and VerticalAlignment with values of 'Stretch', minimums and maximums, etc, but what they actually are depends on the value that's returned from that override so it's a little more complex than that.
Either way, I'm still wondering why the OnRenderSizeChanged call doesn't happen when it's supposed to. Thoughts?
Mark
In general, you should be able to get the correct size from ArrangeOverride. This doesn't include things like Margin, but that probably shouldn't be taken into account. You could either use the size passed as a parameter as your "render" size or use the return value of the base.ArrangeOverride call.
EDIT:
The OnRender method is called from the Arrange method, after OnArrangeOverride is ultimately called. The OnRenderSizeChanged on the other hand is called from UpdateLayout, which is effectively dispatched to be executed all at once for a given section of the visual tree. This is why the OnRenderSizeChanged is called after the OnRender.
The documentation may refer to the "rendering" as in actually rendered to the screen, not when OnRender is called. WPF can cache the rendering instructions for a given element and execute them when needed. So the fact that OnRender is called before OnRenderSizeChanged, doesn't mean it's actual rendering instructions are committed to the screen at that time.
You can modify your OnRenderSizeChanged to force OnRender to be called again using:
protected override void OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo)
{
Console.WriteLine("OnRenderSizeChanged Entered");
base.OnRenderSizeChanged(sizeInfo);
this.InvalidateVisual();
Console.WriteLine("OnRenderSizeChanged Exited");
}
You may also want to skip your OnRender code if RenderSize is "0,0".
does anyone have a functioning virtualising WrapPanel I can use in a WPF application?
I have downloaded and tried the implementation at http://virtualwrappanel.codeplex.com/. However, I get the following exception:
"Layout measurement override of element 'MyNamespace.VirtualizingWrapPanel' should not return PositiveInfinity as its DesiredSize, even if Infinity is passed in as available size."
This is when trying to apply the wrappanel to a ListBox
This is probably a bug that you might be able to fix yourself. Look for the MeasureOverride method. It always seem to return the availableSize wich was passed to the method. As the exception states you must not return availableSize when it contains double.PositiveInfinity. So try this:
if(availableSize.Width == double.PositiveInfinity || availableSize.Height == double.PositiveInfinity)
{
return Size.Empty;
}
// all the measureoverride code comes here
return availableSize;
I haven't looked at the implementation in details. But who knows, you might be able to get a away with this if the panel doesn't save state between MeasureOverride and ArrangeOverride (wich it shouldn't if it is well implemented).
That problem is probably occuring because you have your listbox inside another control, such as a stack panel or scroll viewer, which allows the listbox to grow to whatever size it likes. While the virtual wrap panel shouldn't give an error in this case, it does explain the performance problem.
Even using one of Microsoft's own virtualising panels won't fix the performance issues in this case because the virtualisation is defeated. Since the listbox can grow to whatever size it likes, it does so and draws all the items even if they're not on screen... hence the virtualisation doesn't apply.
If you ensure your listbox isn't inside one of these sorts of containers, you should find the virtualisation starts working performance improves significantly.
I was looking at FrameworkElement.MesureOverride on MSDN, trying to understand the mechanism behind the layout engine. I stumbled upon this interesting note :
During this process, child elements might return a larger DesiredSize size than the initial availableSize to indicate that the child element wants more space.
Ok. I had reflector near so I looked into MesureCore, which call MesureOverride and I noticed that, from what I could understand, the return value of MesureOverride is always capped between 0 and the availableSize. So what's up with that?
A child element can ask for more space. Whether that is honored by the parent element is up to the parent element.
MeasureCore only calls MeasureOverride on this. You're only getting a very small part of the story. The Layout System starts with calling Measure on the topmost Panel in the tree of elements, which calls MeasureCore on this. However, MeasureCore in FrameworkElement calls MeasureOverride in a couple of places.
Where are you seeing it cap between 0 and availableSize?
Edit: Re: "well, the last line of MeasureCore..."
Like I said, you're looking at a small part of all that goes on.
All controls have 1 very common way to request more space than they actually need: Margin. You'd have to write a custom control to request even more space than that.
The constraints you see in MeasureCore, from what I can tell, have to do with the MinWidth/MinHeight and MaxWidth/MaxHeight limits, if they are set.
So yeah, a control -- like the documentation says -- can request more space than is needed. None of the default controls seem to do this aside from their Margins, and containers such as panels don't have to respect it. Most circumstances don't take advantage of what you read in the documentation because in most circumstances, it wouldn't make sense from either the perspective of the parent of the child.
If you created a UserControl, got rid of the Width and Height values in the XAML and override MeasureOverride to return an arbitrary Size, then place an instance of it in a Canvas, you would see it display at the Size you returned.
This feature of the layout system may be of use if you are creating custom panels and custom controls or user controls, but otherwise probably not. But it is there. The documentation is correct.
If you return a Size > availableSize from your own MeasureOverride method, FrameworkElement.MeasureCore (calling your method) will remember it, but DesiredSize will be set = availableSize. This guarantees that child control will be suitable for (for instance) Grid cell with explicitly specified Width/Height. BUT because FrameworkElement.MeasureCore remembers your "unclipped" DesiredSize, in ArrangeOverride you should receive a parameter = your original DesiredSize. At result of it your control "virtually" arranges children according to its original DesiredSize, but FrameworkElement implementation will clip your control for such parent Grid cell. Concrete clipping manner will depend of actual values of properties like Horizontal/VerticalAlignment (properties of your control).
I have a custom Panel which upon resizing has its LayoutUpdated event and ArrangeOverride called twice.
So initially MeasureOverride and ArrangeOverride do their thing once upon open of the client window. Then, if I maximize the window each are called twice. The Available and Final Sizes respectively are not different between each iteration so I'm not sure what's initiating this.
Is there a way to determine the cause of the Invalidation?
I think that it is called twice because the Height and Width changes. I think that both of those properties affect Measure and thus there is a layout pass for each one.
Is there a way to determine the cause of the Invalidation?
Invalidation usually is caused by a change of a DependencyProperty which among it's FrameworkPropertyMetadataOptions has flags AffectsMeasure/AffectsOverride.
As Pavel already said - it's likely that invalidation fires for both changes in Width and Height.
Anyway, you shouldn't rely on the number of those invalidations.
I would say the easiest way is setting a breakpoint for each method and observing the call stack. You will be able to see what happened before your method was called.