Welcome to MSDN Blogs Sign in | Join | Help

I got a question about WPF performance this morning regarding how the number of elements (controls, panels, et al.) affects performance.  Before I share more broadly, I'll mention that the WPF Performance team is working on plans for more detailed & prescriptive guidance; you should stay tuned here & MSDN for more info.  In the mean time, here's the response I provided:

--------

There is a cost for additional elements that you add; to name a few: 

  1. Working set – you’ve got more elements and more state that consumes memory.
  2. Layout – you’re performing more layout work even if these are fairly simple calls.
  3. Property invalidations – when a property changes there’s more work to update affected values.
  4. Hit testing – more elements to check when input is received.

One of the key ways to improve performance of your app is to reduce the number of elements.  You can do this in a variety of ways: 

  • Use virtualization when you can in ItemsControls using the VirtualizingStackPanel.
  • Use the Grid element as a top‑most layout when you have a fairly complex & nested layout instead of several panels.
  • Reduce the number of elements in and depth of templates when you can.

It's been a long while since I've posted to my blog.  What have I been up to?  Basically, since the PDC I've been working on WPF performance.  We've been working on reducing CPU consumption, memory consumption and, in some case, GPU consumption.

One of the things that I'd like to start doing is posting some snippets on some of the stuff we (the WPF Perf Team) learned as we've working on the performance of the platform.  For example, if you take a look at a profile of your WPF application, how do you know what to look for?

I'll start with small tidbits, though:)

Perf Tip: Use BitmapEffects sparingly.  BitmapEffects run entirely in software and cuase scenes which frequently invalidate to be very expensive to update (for example, from animations.)  BitmapEffects are best used on small static UI.

In one of my first posts ever I talked about doing custom scrolling using the IScrollInfo interface.  Well, Ben Constable, another Microsoftie, beat me to it:)  Check it out here.

While we're still tuning the platform, there are tips & tricks that you can use to optimize your WPF application's performance.  Where can you find these?  Check out the performance whitepaper that Kiran has put together.  As we go, we'll update and eventually get posted on MSDN.

I got a question about this while I was at the PDC.  I see they're now posted on MSDN.
I just got back into Seattle last night.  I'll get my samples posted as soon as have some free time.

I just got a look at this new book from O'Reilly written by Ian Griffiths and Chris Sells.  Good stuff.  Check it out!

Made it to L.A.!  The weather is beautiful.  Biggest event so far?  Downtown L.A. lost power for about 30 minutes (and I've been here all of about 3 hours); I was having lunch with Fil in the hotel lobby when it happened.  MSNBC already has an article.

I just tried MSN Messenger 7.5.  What can I say?  Amazing.

The Messenger team is cookin'!  Seems like everytime I've installed a new version of MSNIM there's been some useful new feature I've found even if it seems insignificant.

MSNIM 7.5's new feature?  Free voice conversations.  A friend of mine was in Vancouver, British Columbia today when he sent me an invitation to have a voice conversation; I was in Redmond, WA.  In two minutes, I had installed MSNIM 7.5 on my Dell D600 laptop using the built-in microphone and was having a voice chat.  The quality was amazing!!  No echo, no delay, clear audio.  I don't know how they did it but I've got to get my entire contact list to install it.  Awesome.

The PDC is almost here!!!  Just imagine:  Thousands of developers descending upon L.A. to find out all about new Microsoft technologies.  But it's not just that; it's that this technology will change not only software but the world!  Even though the PDC is a lot of work and many of us here at MS could probably use a few more hours of sleep, we're all pretty fired up.  It could be the large amounts of coffee but I think it's safe to say that it's the excitement about the PDC:)

Okay, time to get back to practicing my talk.

Check it out! Bill does an interview on Channel 9.

The PDC is almost here and I'm excited!

I've been working on my presentation.  I'll be giving PRS329 - Windows Presentation Foundation ("Avalon"): Building User Interface with Advanced Layout Techniques.  If there was something you wanted to know about Avalon Layout, this is a talk for you.  I'm still fine tuning the talk, so, if there are topics that you'd like to hear me discuss, drop me a line and I'll see what I can do.

See you in L.A.!

Download it here - http://www.microsoft.com/downloads/details.aspx?FamilyId=CE888B4C-CCBD-452F-9D90-F4B7190CCA24&displaylang=en

What's the difference between RenderTransform and ScaleTransform (no, this isn't a joke setup)?  It all starts with layout.

Layout is a feature in Avalon for negotiating how UI elements share space on the screen.  After layout is performed and the "final sizes" of the UI elements have been determined, the elements are drawn, or rendered, on the screen.  A variety of different transforms, such as rotation, scaling and skewing, can be applied to elements and will affect how they look on the screen, including their physical dimensions.  If the transform is applied after layout is performed, the relative sizes and positions of elements based on layout results will not be affected.  This is RenderTransform.  If the transform is applied before layout, the relative sizes and positions of elements will be affected.  This is LayoutTransform.

Below, I demonstrate this through an example using scaling.

No Transform

<StackPanel xmlns="http://schemas.microsoft.com/winfx/avalon/2005" Background="orange" Width="250" Margin="20">
 <TextBlock TextWrap="Wrap" Margin="10" Background="chartreuse">
  Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus tempor elit ut metus. Morbi massa. Nunc in urna.
 </TextBlock>

 <Button>Hello world!</Button>
 <Button>Hello again!</Button>
</StackPanel>

 

LayoutTransform

In this markup, the LayoutTransform property has been set on the TextBlock.  I used scaling causing everything to be magnified, including the layout.  Note that the Buttons are "pushed" down making room for the TextBlock.

<StackPanel xmlns="http://schemas.microsoft.com/winfx/avalon/2005" Background="orange" Width="250" Margin="20">
 <TextBlock TextWrap="Wrap" Margin="10" LayoutTransform="scale 2" Background="chartreuse">
  Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus tempor elit ut metus. Morbi massa. Nunc in urna.
 </TextBlock>

 <Button>Hello world!</Button>
 <Button>Hello again!</Button>
</StackPanel>

 

RenderTransform

Note that the RenderTransform property has been added to the TextBlock.  The Buttons are not pushed out of the way; this is because their position was determined during layout and RenderTransform applies after layout.

<StackPanel xmlns="http://schemas.microsoft.com/winfx/avalon/2005" Background="orange" Width="250" Margin="20">
 <TextBlock TextWrap="Wrap" Margin="10" RenderTransform="scale 2" Background="chartreuse">
  Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus tempor elit ut metus. Morbi massa. Nunc in urna.
 </TextBlock>

 <Button>Hello world!</Button>
 <Button>Hello again!</Button>
</StackPanel>

 

I’ve had a lot of questions about Adorners lately.  Guess it’s about time to post an example.  First, some background.

What is an Adorner?
In Avalon, an Adorner is a UI widget that can be applied to elements to allow a user to manipulate that element - resize, rotate, move, etc.  Avalon does not provide concrete Adorners but it does provide the basic infrastructure.  That means that you need to write your own, which is what I show in this posting.  Some quick terms:

Adorner
This is a base Adorner from which you will need to subclass.

AdornerLayer
The AdornerLayer can be thought of as a plane in which the Adorners are drawn.

AdornedElement
The AdornedElement is the one to which the Adorner has been applied.

This Example
In this example, I author a CustomResizeAdorner which is applied to the children of a Canvas.  As the name implies, the CustomResizeAdorner allows the user to resize the AdornedElement.  The application markup looks like the following.  Notice the Canvas Named mainCanvas which contains a Button, ListBox and another Canvas.  The children of mainCanvas will be what I apply the CustomResizeAdorners to.

 <Window x:Class="AvalonApplication7.Window1"
    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
    Text="AvalonApplication7"
    Loaded="StartUp">
    <Canvas Name="mainCanvas">
        <Button Canvas.Left="50" Canvas.Top="150">Hello world</Button>
        <ListBox Canvas.Left="150" Canvas.Top="95" Height="80">
            <ListBoxItem>Item 1</ListBoxItem>
            <ListBoxItem>Item 2</ListBoxItem>
            <ListBoxItem>Item 3</ListBoxItem>
            <ListBoxItem>Item 4</ListBoxItem>
            <ListBoxItem>Item 5</ListBoxItem>
        </ListBox>
        <Canvas Background="yellow" Width="40" Height="90" Canvas.Left="350" Canvas.Top="150"/>
    </Canvas>
</Window>

I have also defined my Adorner corners with the following style and template in the application resources.

<Style TargetType="{x:Type Thumb}">
   <Setter Property="Template" Value="{StaticResource AdornerTemplate}"/>
   <Setter Property="Width" Value="15"/>
   <Setter Property="Height" Value="15"/>
</Style>


<ControlTemplate x:Key="AdornerTemplate" TargetType="{x:Type Thumb}">
 <Border Background="green" BorderThickness="1" BorderBrush="black" />
</ControlTemplate>
 

The result:
 

 After a few resizes.

Now, after some small tweaks the style for the corners, I have the following UI.

Again, after a few resizes:

The markup for the updated Adorner widgets is shown below.

<Style TargetType="{x:Type Thumb}">
 <Setter Property="Template" Value="{StaticResource AdornerTemplate}"/>
 <Setter Property="Width" Value="18"/>
 <Setter Property="Height" Value="18"/>
</Style>

<ControlTemplate x:Key="AdornerTemplate" TargetType="{x:Type Thumb}">
 <Border Background="VerticalGradient purple silver" Opacity=".65" BorderThickness="0" BorderBrush="Navy" CornerRadius="7,2,7,2">
  <Border Background="VerticalGradient #DDFFDD purple" Margin="2" CornerRadius="5,2,5,2"/>
</Border>
</ControlTemplate>

Putting It All Together
I created this example on the May CTP bits using VS to create a new Avalon project.  Below I have pasted in the three main files that I created for this example.  I have added some rough comments to the code but the key is the CustomResizeAdorner.  If you have question, feel free to contact me.

MyApp.xaml

<Application x:Class="AvalonApplication7.MyApp"
    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
    StartingUp="AppStartingUp"
    >
    <Application.Resources>
            <Style TargetType="{x:Type Thumb}">
                <Setter Property="Template" Value="{StaticResource AdornerTemplate}"/>
                <Setter Property="Width" Value="15"/>
                <Setter Property="Height" Value="15"/>
            </Style>
            <ControlTemplate x:Key="AdornerTemplate" TargetType="{x:Type Thumb}">
                <Border Background="green" BorderThickness="1" BorderBrush="black" />
            </ControlTemplate>
    </Application.Resources>
</Application>
 
Window1.xaml
<Window x:Class="AvalonApplication7.Window1"
    xmlns="http://schemas.microsoft.com/winfx/avalon/2005"
    xmlns:x="http://schemas.microsoft.com/winfx/xaml/2005"
    Text="Adorner Example"
    Width ="700"
    Height="500"
    Loaded="StartUp">
 
    <Canvas Name="mainCanvas">
        <Button Canvas.Left="50" Canvas.Top="150">Hello world</Button>
        <ListBox Canvas.Left="150" Canvas.Top="95" Height="80">
            <ListBoxItem>ListBoxItem 1</ListBoxItem>
            <ListBoxItem>ListBoxItem 2</ListBoxItem>
            <ListBoxItem>ListBoxItem 3</ListBoxItem>
            <ListBoxItem>ListBoxItem 4</ListBoxItem>
            <ListBoxItem>ListBoxItem 5</ListBoxItem>
            <ListBoxItem>ListBoxItem 6</ListBoxItem>
            <ListBoxItem>ListBoxItem 7</ListBoxItem>
            <ListBoxItem>ListBoxItem 8</ListBoxItem>
            <ListBoxItem>ListBoxItem 9</ListBoxItem>
        </ListBox>
        <Canvas Background="yellow" Width="40" Height="90" Canvas.Left="350" Canvas.Top="150"/>
    </Canvas>
</Window>
 
Window1.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Input;
using System.Windows.Controls.Primitives;


namespace AvalonApplication7
{

    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
        }

        //Initialize the application by calling a method
        //to apply adorners to all children of the respective
        //Canvas.
        private void StartUp(object sender, EventArgs args)
        {
            ApplyAdornersToCanvasChildren(mainCanvas);
        }


        //Iterates through the Canvas' children finding all of
        //the FrameworkElements; it then applies a CustomResizeAdorner
        //to each one.

        void ApplyAdornersToCanvasChildren(Canvas canvas)
        {
            AdornerLayer al = AdornerDecorator.GetAdornerLayer(canvas);

            foreach (FrameworkElement fxe in canvas.Children)
                if (al.GetAdorners(fxe) == null)
                    al.Add(new CustomResizeAdorner(fxe));
        }

    }

   
    public class CustomResizeAdorner : Adorner
    {
        //The visual elements used as the Adorners.  The Thumb
        //element is used because it takes care of handling the
        //lower level mouse input.
        Thumb topLeft, topRight, bottomLeft, bottomRight;

        //Initialize the CustomResizeAdorner.
        public CustomResizeAdorner(UIElement adornedElement) : base(adornedElement)
        {
            //Call a helper method to instantiate the Thumbs
            //with a given Cursor.
            BuildAdornerCorner(ref topLeft, Cursors.SizeNWSE);
            BuildAdornerCorner(ref topRight, Cursors.SizeNESW);
            BuildAdornerCorner(ref bottomLeft, Cursors.SizeNESW);
            BuildAdornerCorner(ref bottomRight, Cursors.SizeNWSE);

            //Add handlers for resizing on the bottom left and right.
            //Leaving the handling of the other two corners as an exercise
            //to the reader.
            bottomLeft.DragDelta += new DragDeltaEventHandler(HandleBottomLeft);
            bottomRight.DragDelta += new DragDeltaEventHandler(HandleBottomRight);
        }


        //Handle resize for the bottom right adorner widget.
        void HandleBottomRight(object sender, DragDeltaEventArgs args)
        {
            FrameworkElement fxe = AdornedElement as FrameworkElement;
            Thumb hitThumb = sender as Thumb;
            if (fxe == null || hitThumb == null)
                return;

            EnforceSize(fxe);

            //Change the size by the amount the user drags the mouse as
            //long as it's larger than the width or height of an adorner, respectively.
            fxe.Width = Math.Max(args.HorizontalChange + fxe.Width, hitThumb.DesiredSize.Width);
            fxe.Height = Math.Max(args.VerticalChange + fxe.Height, hitThumb.DesiredSize.Height);
        }

        //Handle resize for the bottom left adorner widget.
        void HandleBottomLeft(object sender, DragDeltaEventArgs args)
        {
            FrameworkElement fxe = AdornedElement as FrameworkElement;
            Thumb hitThumb = sender as Thumb;

            if (fxe == null || hitThumb == null)
                return;

            EnforceSize(fxe);

            //Change the size by the amount the user drags the mouse as
            //long as it's larger than the width or height of an adorner, respectively.
            //Also, update the left position by the amount the user drags as long as
            //it's not past the right edge minus the adorner widget width.

            Canvas.SetLeft(fxe, Math.Min((double)Canvas.GetLeft(fxe) + args.HorizontalChange,(double)Canvas.GetLeft(fxe) + fxe.Width - hitThumb.DesiredSize.Width));
            fxe.Width = Math.Max(fxe.Width - args.HorizontalChange, hitThumb.DesiredSize.Width);
            fxe.Height = Math.Max(args.VerticalChange + fxe.Height, hitThumb.DesiredSize.Height);
        }


        //Arrange the Adorners.
        protected override Size ArrangeOverride(Size finalSize)
        {

            //w & h are the width and height of the element
            //that's being adorned.  These will be used to place
            //the Adorner at the corners.  adornerWidth &
            //adornerHeight are used for placement as well.

            double w = AdornedElement.DesiredSize.Width;
            double h = AdornedElement.DesiredSize.Height;
            double adornerWidth = this.DesiredSize.Width;
            double adornerHeight = this.DesiredSize.Height;

            topLeft.Arrange(new Rect(-adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
            topRight.Arrange(new Rect(w - adornerWidth / 2, -adornerHeight / 2, adornerWidth, adornerHeight));
            bottomLeft.Arrange(new Rect(-adornerWidth / 2, h - adornerHeight / 2, adornerWidth, adornerHeight));
            bottomRight.Arrange(new Rect(w - adornerWidth / 2, h -adornerHeight / 2, adornerWidth, adornerHeight));

            //Just using the size that the
            //adorner layer was arranged at.
            return finalSize;

        }


        //Helper code to instantiate the Thumbs, set the
        //Cursor property and add the elements to the
        //Visual tree.

        void BuildAdornerCorner(ref Thumb cornerThumb, Cursor c)
        {
            if (cornerThumb != null) return;
            cornerThumb = new Thumb();
            cornerThumb.Cursor = c;
            VisualOperations.GetChildren(this).Add(cornerThumb);
        }

        //This method ensures that the Widths and Heights
        //are initialized.  Sizing to content produces
        //Width and Height values of Double.NaN.  Because
        //this Adorner explicitly resizes, the Width and Height
        //need to be set first.
        void EnforceSize(FrameworkElement fxe)
        {
            if (fxe.Width.Equals(Double.NaN))
                fxe.Width = fxe.DesiredSize.Width;
            if (fxe.Height.Equals(Double.NaN))
                fxe.Height = fxe.DesiredSize.Height;
        }

    }
}

More Posts Next page »