views:

758

answers:

2

I need to calculate the logical width of a visual element, before it gets rendered by WPF.

For simplicity of explanation, I'll say that this visual element will likely be a Polygon object. It could be something else, but a Polygon makes it easy to visualize.

So the XAML might look something like this:

<Window x:Class="MyCLRNamespace.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;
</Window>

And the code-behind might look something like this:

namespace MyCLRNamespace
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            //This is the visual element in question. It's a simple triangle.
            Polygon MyPolygon = new Polygon();
            MyPolygon.Points = new PointCollection {    new Point(100, 0),
                                                        new Point(200, 200),
                                                        new Point(0, 200)   };
            double PolyWidth = MyPolygon.Width;
            /* In this case, PolyWidth will be set to double.NaN, since
               MyPolygon.Width is never set.

               I need to be able to calculate the logical width of an element,
               unrelated to the WPF rendering system. This means that I can't
               rely on FrameworkElement.ActualWidth to calculate the width for
               me. I need to be able to look at the MyPolygon object (or its
               content) and figure out that it is set to a visual element that
               should be 200dips wide before any parent element applies any
               operations to it - regardless of what MyPolygon.Width may or may
               not be set to.

               It should also be noted that I don't have to rely on
               FrameorkElement. If there are more generic alternatives, such as
               the UIElement or Visual classes, I'd prefer to use those instead
               of the more specific FrameworkElement. The more robust I can make
               this, the better. */
        }
    }
}
A: 

Alas we meet again. Perhaps it would help if you tell us more about what you are trying to achieve. WPF actually uses device independent units for its sizes and that's what ActualWidth is (from MSDN):

The element's width, as a value in device-independent units (1/96th inch per unit). The default value is 0 (zero).

If you are seeing weirdness in availability of ActualWidth values you might want to listen to SizeChanged event or override OnRenderSizeChanged. I think the two are subtly different, but I am not sure what those differences are.

Igor Zevaka
I tend to do most of the explaining in code comments. Maybe I shouldn't do that... Anyway, ActualWidth is only helpful if you want the rendered width of something. I want the logical width. The value I want is the "200" value from the polygon declaration in XAML. ActualWidth is not guaranteed to be equal to the declared width of the element, and in this case I'm not even setting the regualar width property. But the polygon will in fact be 200 dips wide if no other factors are present. That's the value I need my program to be able to calculate dynamically.
Giffyguy
Right, So under what circumstances is ActualWidth not 200? The only time that wouldn't be the case, IMHO, would be when a parent of it clips it somehow. Have you tried to debug the code to interrogate the value of ActualWidth?
Igor Zevaka
Under the circumstances that the polygon hasn't been rendered yet. I wanted to make a code example of this scenario, but I couldn't think of any good ways to show it without posting my entire project. It'd be way too confusing.
Giffyguy
Right, in that case you would need to modify the rest of your application to wait until they they can find out the size of the control. It's a pain, but there's no other way to find out what the rendered size is. Which kinda makes sense since the framework simply doesn't do anything with the control until it has to render it.
Igor Zevaka
I edited my question to reflect this scenario. It wasn't so confusing in the end, I suppose... And I believe I figured out how to accomplish my goal. Refer to my answer if you'd like to follow up on my findings.
Giffyguy
+2  A: 

The System.Windows.UIElement class provides methods for measuring itself outside of any parent-child element relationships.

It is critical that you check for IsMeasureValid before you attempt to use the measurement value(s). If IsMeasureValid is false, you need to manually call the UIElement.Measure() method to ensure that you have an up-to-date measurement the element and its content. If IsMeasureValid is true, it won't hurt to measure again. It will just overwrite any previous measurements it has stored.

If you want a solid measurement of the element with no outside restrictions, provide an infinite size as the availableSize parameter to the UIElement.Measure() method.

The UIElement.Measure() method will store the measured size of the element in the UIElement.DesiredSize property. I don't believe this has any negative impact on the WPF rendering system, due to the fact that any parent element is guaranteed to re-measure the element with its own available size constraints before rendering it. This may affect the final size of the element on screen, but it will not affect the original desired size of the element before parent-child constraints are applied.

namespace MyCLRNamespace
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            Polygon MyPolygon = new Polygon();
            MyPolygon.Points = new PointCollection {    new Point(100, 0),
                                                        new Point(200, 200),
                                                        new Point(0, 200)   };
            //if (MyPolygon.IsMeasureValid == false)
                MyPolygon.Measure(new Size( double.PositiveInfinity,
                                            double.PositiveInfinity));

            double PolyWidth = MyPolygon.DesiredSize.Width;
        }
    }
}
Giffyguy