Search code examples
wpfdata-bindingattached-properties

Are WPF Inherited Attached Dependency properties "expensive"?


I've got a working app that draws shapes and images on a zoomable canvas. I'm wondering about the effects of changing my current approach from using a global, transient settings object to using inherited attached properties.

To explain: A few aspects of the drawing (e.g. line widths, font sizes, etc) are user-configurable but are also affected by zoom level. I quickly found that merely binding, say, the StrokeThickness of my shapes to a configured value caused problems. If the user zoomed the canvas way in or out, the thickness changed. I wanted it to stay constant.

So I chose up with a solution that instead bound my shapes to a global, transient set of "Live" settings derived from the configured settings and the current zoom scale. The code-behind changes these live settings as the user zooms my canvas in or out.

private void UpdateScaledSizesAfterZoom()
{
    // Get the scale from the canvas' scale transform. 

    if (!(Scene.LayoutTransform is ScaleTransform st))
        return;

    var div = st.ScaleX > 0 ? st.ScaleX : 1; 

    // Update all live settings with the new scale.

    LiveSettings.LineWidth       = Settings.LineWidth/ div;
    LiveSettings.FontSize        = Settings.FontSize / div;

}

Binding:

<Path StrokeThickness="{Binding Source={x:Static LiveSettings.Default}, Path=LineWidth}" Data=... blah blah blah .../>

This all works well enough but something about tying all my objects to a global object just plain bothers me. I can stay with it if I must but I wouldn't mind something cleaner.

So I wondered about an approach that used WPF Property Inheritance instead; I could, alternately register properties like this as inherited attached properties on my canvas ("ShapeCanvas"). Then my shapes could bind to "ShapeCanvas.LineWidth" and not need to rely on the existence of some global settings object.

However I might have many, many shapes. So I am wondering about how this might affect performance. How expensive is it for WPF to propagate a property like this through containment inheritance?

I've already debugged some of how attached properties work and it appears that when an attached inherited property changes, literally every item in the inheritance context gets notified about it. So it seems like this could be quite expensive indeed. I wouldn't want to make my zooming laggy or anything.

Does anyone have any experience with such issues? Is this something to be concerned about.


Solution

  • Here is a very simple example of how to transform the Geometry of a Path instead of the Path element itself, which avoids the need to re-scale its StrokeThickness:

    <Window.Resources>
        <MatrixTransform x:Key="GeometryTransform"/>
    </Window.Resources>
    <Canvas Background="Transparent" MouseWheel="Canvas_MouseWheel">
        <Path Fill="Yellow" Stroke="Blue" StrokeThickness="3">
            <Path.Data>
                <PathGeometry Figures="M100,50 L150,100 100,150 50,100Z"
                              Transform="{StaticResource GeometryTransform}"/>
            </Path.Data>
        </Path>
    </Canvas>
    

    with this MouseWheel handler:

    private void Canvas_MouseWheel(object sender, MouseWheelEventArgs e)
    {
        var transform = (MatrixTransform)Resources["GeometryTransform"];
        var matrix = transform.Matrix;
        var scale = e.Delta > 0 ? 1.1 : 1 / 1.1;
        var pos = e.GetPosition((IInputElement)sender);
        matrix.ScaleAt(scale, scale, pos.X, pos.Y);
        transform.Matrix = matrix;
    }