ItemsControl Performance Improvements in .NET 4.5

WPF is Slow

One of the most common complaints from C++ or Windows Forms developers moving to WPF/XAML is the issue of performance. I have worked exclusively and intricately with WPF/XAML for the last 4 years and at times have come across performance issues that put me in a corner, including;

  • I needed to enumerate all the alarm and safety devices in an Airports security system inventory. This included fire, smoke, travelator, escalator, motion detection etc. in effect I needed to load up thousands of hardware devices into the WPF application for configuration
  • I was dealing with scientific data for DNA, RNA and Protein. Typically you would have an image with an electropherogram attached to it, which contains thousands of points of floating point data. Scientists required the ability to step through several different samples quickly in order to perform analysis on this data.
    In both these applications, I ended up having to make significant compromises, including using a Windows Forms chart in the scientific application as loading thousands of floating point data into the plethora of commercial and open source WPF charts we tried was unbearably slow. The Windows Forms chart would render the data in milliseconds, where WPF would take a minimum of 10 or 20 seconds with the same data-set, and given the fact that we needed to extend the chart with additional functionality, it added several months worth of development to alter the Windows Forms chart, where it would have been far easier and less time consuming using WPF.

    Staggering

    It turns out that peoples cries about the sluggish nature of WPF applications are correct. Microsoft have managed to attain simply staggering performance improvements to the ItemsControl in WPF. An ItemsControl represents a control that can be used to present a collection of items. In WPF, this includes the TreeView, ListBox, ListView and DataGrid controls that are built using ItemsControls.

DotNet4

In .NET 4 (Visual Studio 2010) and previous, a test was conducted where 12 000 items are loaded into an ItemsControl, the results are in the image below

OutOfMemory

After about 7 minutes, the computer throws an out of memory exception, which I am sure you will agree is absolutely terrible. Microsoft prioritised this issue and in the first iteration managed to increase the total number of items loaded to 200 000 (from 12 000) and load these in 24.5 seconds, an incredible improvement.

FirstIteration

The Pièce de résistance is that they continued to try and get this already considerable metric down even further, in the end  they got this down to 2.3 seconds.

Final

If you have a WPF application that is data centric, especially handling thousands of rows of data, then upgrading to .NET 4.5 is an absolute no brainer. WPF is now at least 182 times quicker whilst handling 16 times more data.

It means that WPF is going to be significantly faster with the additional improvements that have been made to virtualisation in ItemsControls and Cold/Warm start-up times for .NET applications in general.

Zoomable autosizing canvas in WPF

I have created a control for a project I am working on that uses a canvas to draw graph ticks for a custom scale, but ran into the limitation of the canvas control where child items are not scaled accordingly when the height (or width) of the canvas changes. Luckily, this is quite a straightforward problem to fix, but I just could not seem to find either the correct search terms to enter in Google or locating code samples in books that demonstrated how to resolve the issue. I have just knocked this code up this afternoon so it is not perfect, but should suffice should you require something similar.

Small

Large

In order to resolve the problem you will need to create a custom canvas and override both MeasureOverride and ArrangeOveride incorporating any custom logic you require in these methods.

using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
 
namespace AutosizingCanvasApp.Controls
{
public class TickCanvas : Canvas
{
// Vertical axis where the X value is never changed
private const double X = 0;
private Size initialSize;
 
// Override the default Measure method of Panel
protected override Size MeasureOverride(Size availableSize)
{
var canvasDesiredSize = new Size();
 
// In our example, we just have one child.
// Report that our canvas requires just the size of its only child.
 
foreach (UIElement child in InternalChildren)
{
child.Measure(availableSize);
canvasDesiredSize = child.DesiredSize;
}
 
return canvasDesiredSize;
}
 
protected override Size ArrangeOverride(Size finalSize)
{
if (initialSize.Height == 0)
{
initialSize = finalSize;
}
var ratio = finalSize.Height / initialSize.Height;
 
for (int index = 0; index < this.InternalChildren.Count; index++)
{
UIElement child = this.InternalChildren[index];
 
double y = ((Line)child).Y1;
 
child.Arrange(finalSize.Height > initialSize.Height
? new Rect(new Point(X, (ratio * y) – y), child.DesiredSize)
: new Rect(new Point(X, 0), child.DesiredSize));
}
return finalSize; // Returns the final Arranged size
}
}
}

In the .xaml I have a border with the custom canvas as a child that have some Line objects all affecting the Y axis position.

<Window x:Class="AutosizingCanvasApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:AutosizingCanvasApp.Controls"
xmlns:local="clr-namespace:AutosizingCanvasApp.Converters"
Title="mainWindow"
Width="300"
Height="768"
WindowStartupLocation="CenterScreen">
<Window.Resources>
<local:BorderHeightValueConverter x:Key="BorderHeightValueConverter" />
<local:LinePositionConverter x:Key="LinePositionConverter" />
</Window.Resources>
 
<Border x:Name="mainBorder"
Width="94"
Height="{Binding ElementName=mainWindow,
Path=ActualHeight,
Converter={StaticResource BorderHeightValueConverter}}"
Margin="50"
BorderBrush="Black"
BorderThickness="2"
ToolTip="{Binding ElementName=mainBorder,
Path=ActualHeight}">
<controls:TickCanvas x:Name="myCanvas">
<Line x:Name="line1"
Stroke="Red"
StrokeThickness="2"
ToolTip="{Binding ElementName=line1,
Path=Y1}"
X1="0"
X2="60"
Y1="{Binding ElementName=mainBorder,
Path=ActualHeight,
Converter={StaticResource LinePositionConverter},
ConverterParameter=0.2}"
Y2="{Binding ElementName=mainBorder,
Path=ActualHeight,
Converter={StaticResource LinePositionConverter},
ConverterParameter=0.2}" />
<Line x:Name="line2"
Stroke="Blue"
StrokeThickness="2"
ToolTip="{Binding ElementName=line2,
Path=Y1}"
X1="0"
X2="60"
Y1="{Binding ElementName=mainBorder,
Path=ActualHeight,
Converter={StaticResource LinePositionConverter},
ConverterParameter=0.4}"
Y2="{Binding ElementName=mainBorder,
Path=ActualHeight,
Converter={StaticResource LinePositionConverter},
ConverterParameter=0.4}" />
<Line x:Name="line3"
Stroke="Green"
StrokeThickness="2"
ToolTip="{Binding ElementName=line3,
Path=Y1}"
X1="0"
X2="60"
Y1="{Binding ElementName=mainBorder,
Path=ActualHeight,
Converter={StaticResource LinePositionConverter},
ConverterParameter=0.6}"
Y2="{Binding ElementName=mainBorder,
Path=ActualHeight,
Converter={StaticResource LinePositionConverter},
ConverterParameter=0.6}" />
<Line x:Name="line4"
Stroke="Purple"
StrokeThickness="2"
ToolTip="{Binding ElementName=line4,
Path=Y1}"
X1="0"
X2="60"
Y1="{Binding ElementName=mainBorder,
Path=ActualHeight,
Converter={StaticResource LinePositionConverter},
ConverterParameter=0.8}"
Y2="{Binding ElementName=mainBorder,
Path=ActualHeight,
Converter={StaticResource LinePositionConverter},
ConverterParameter=0.8}" />
</controls:TickCanvas>
</Border>
</Window>

I also have a couple of converters that converts the height of the border as that changes when you resize the parent window and a couple of converters that reposition the line objects in the canvas. Be aware that the border itself has a thickness, so you will need to incorporate that in any sizing but has been omitted here so the lines may be a couple of pixels off.

using System;
using System.Globalization;
using System.Windows.Data;
 
namespace AutosizingCanvasApp.Converters
{
public class LinePositionConverter : IValueConverter
{
#region Implementation of IValueConverter
 
/// <summary>
/// Converts a value.
/// </summary>
/// <returns>
/// A converted value. If the method returns null, the valid null value is used.
/// </returns>
/// <param name="value">The value produced by the binding source.</param>
/// <param name="targetType">The type of the binding target property.</param>
/// <param name="parameter">The converter parameter to use.</param>
/// <param name="culture">The culture to use in the converter.</param>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return ((double) value)*double.Parse(parameter.ToString());
}
 
/// <summary>
/// Converts a value.
/// </summary>
/// <returns>
/// A converted value. If the method returns null, the valid null value is used.
/// </returns>
/// <param name="value">The value that is produced by the binding target.</param><param name="targetType">The type to convert to.</param><param name="parameter">The converter parameter to use.</param><param name="culture">The culture to use in the converter.</param>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
 
#endregion
}
}

using System;
using System.Globalization;
using System.Windows.Data;
 
namespace AutosizingCanvasApp.Converters
{
public class BorderHeightValueConverter : IValueConverter
{
#region Implementation of IValueConverter
 
/// <summary>
/// Converts a value.
/// </summary>
/// <returns>
/// A converted value. If the method returns null, the valid null value is used.
/// </returns>
/// <param name="value">The value produced by the binding source.</param>
/// <param name="targetType">The type of the binding target property.</param>
/// <param name="parameter">The converter parameter to use.</param>
/// <param name="culture">The culture to use in the converter.</param>
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return (((double)value)*0.7);
}
 
/// <summary>
/// Converts a value.
/// </summary>
/// <returns>
/// A converted value. If the method returns null, the valid null value is used.
/// </returns>
/// <param name="value">The value that is produced by the binding target.</param><param name="targetType">The type to convert to.</param><param name="parameter">The converter parameter to use.</param><param name="culture">The culture to use in the converter.</param>
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
 
#endregion
}
}
 

The source code is for this example is available here.

WPF Message Box

The default MessageBox in WPF looks rather ugly on Windows 7 and Windows Vista

Before

 

Before

After

 

After

In order to update the messagebox, add a new app.manifest file

addManifest

ensure you have the following dependency tag

    1 <?xml version="1.0" encoding="utf-8"?>

    2 <asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

    3   <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>

    4   <description>iDesign – Media Burner</description>

    5   <dependency>

    6     <dependentAssembly>

    7       <assemblyIdentity name="Microsoft.Windows.Common-Controls" version="6.0.0.0" type="win32" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />

    8     </dependentAssembly>

    9   </dependency>

   10   <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">

   11     <security>

   12       <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">

   13         <!– UAC Manifest Options

   14             If you want to change the Windows User Account Control level replace the

   15             requestedExecutionLevel node with one of the following.

   16 

   17         <requestedExecutionLevel  level="asInvoker" uiAccess="false" />

   18         <requestedExecutionLevel  level="requireAdministrator" uiAccess="false" />

   19         <requestedExecutionLevel  level="highestAvailable" uiAccess="false" />

   20 

   21             Specifying requestedExecutionLevel node will disable file and registry virtualization.

   22             If you want to utilize File and Registry Virtualization for backward

   23             compatibility then delete the requestedExecutionLevel node.

   24         –>

   25         <requestedExecutionLevel level="asInvoker" uiAccess="false" />

   26       </requestedPrivileges>

   27     </security>

   28   </trustInfo>

   29 

   30   <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">

   31     <application>

   32       <!– A list of all Windows versions that this application is designed to work with. Windows will automatically select the most compatible environment.–>

   33 

   34       <!– If your application is designed to work with Windows 7, uncomment the following supportedOS node–>

   35       <!–<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>–>

   36 

   37     </application>

   38   </compatibility>

   39 

   40   <!– Enable themes for Windows common controls and dialogs (Windows XP and later) –>

   41   <!– <dependency>

   42     <dependentAssembly>

   43       <assemblyIdentity

   44           type="win32"

   45           name="Microsoft.Windows.Common-Controls"

   46           version="6.0.0.0"

   47           processorArchitecture="*"

   48           publicKeyToken="6595b64144ccf1df"

   49           language="*"

   50         />

   51     </dependentAssembly>

   52   </dependency>–>

   53 

   54 </asmv1:assembly>

   55 

Burning and Erasing CD/DVD/Blu-ray Media with WPF

Some time ago I worked on a project where I needed a DVD burner/formatter, and came across an open source version by Eric Haddan. The only problem with that excellent solution was that it was written in winforms ,and I was working on a WPF application. I have now ported that application to WPF using MVVM.

burneru

You can download the free WPF burner at http://wpfburner.codeplex.com/. Just select the downloads tab and save the source code.

If you are on XP then you will need Image Mastering API v2.0 (IMAPIv2.0) for Windows XP if you don’t have this already (this is included in Vista SP2 and Windows 7). To burn a Blu-ray you will need Windows Feature Pack for Storage 1.0 if you are not using Window 7.

Common sense lost with MVVM

I am a big fan of using MVVM in WPF applications, but am finding, increasingly,  that even seasoned developers frequently dispose of very good habits, in order to stay true to the pattern.

One such occurrence is is not using message boxes. A  cardinal sin in MVVM, is ensuring that message boxes are never used in View Models. The end result is that one starts to see code like this.

delete

If you don’t have a logging system for your exceptions then you should have, if you do then always ensure that you log the exception. During development, one instinctually starts to determine brittle code, and events where errors can occur and use try-catch blocks. The worst thing you can do here is nothing. It is better not to use the try-catch at all, and just let the application blow-up, because at least you know that there is a problem, and go about fixing it.

I really cannot sufficiently impress upon thee just how much of a bad practice this is, and the amount of time you or other developers that work on your project will spend trying to find obscure bugs that occur because you are swallowing exceptions, just because MVVM stipulates that message boxes should not be used, means they shouldn’t, they should!

Free WPF Ribbon with source code and MVVM samples

Finally after about 2 years as a CTP, Microsoft have released the WPF ribbon. Due to the delay, one can be certain that they were allowing their third party software vendors as much traction for users that needed a ribbon, but some brilliant open source versions including Fluent Ribbon Control Suite have been developed.

ribbon

The best thing about the release, is that the Office Fluent UI licensing is now no longer a requirement. This allows people to download the software straight away, without having to go through the cumbersome process of logging into the Office website, signing the licensing, then spending half a day trying to locate the installation because it was hidden in a way that made you think they don’t want anyone to use it.

The download for the ribbon is available here. Please ensure that you also take the time to install the source and samples if you want to build MVVM compliant applications with the ribbon (you should be doing this in WPF anyway) . The MSDN documentation is available here.

The installation notes say that this is not supported on XP, but on vista upwards but I have not tested this. If there are issues then you should be able to use another vendor.

After installing the MSI you should have the following template in Visual Studio

project

There is no information on the installation templates for the express editions, but I would hazard a guess as to them never being available, as building applications with a Ribbon is not something a learner ‘typically’ does. If you are using express, then you should be able to add the requisite .dll thus. If you look in my Tree View below in Visual Studio, you will find that there is a reference to RibbonControlsLibrary. This is located at; C:\Program Files (x86)\Microsoft Ribbon for WPF\v3.5.40729.1\RibbonControlsLibrary.dll

tree

Note:  If you installed the source and samples as well, a .zip file with both is available at the same location. Copy this, and unzip it in a location of your choosing

source

The code behind for my window has the following code, so inherit off RibbonWindow

ribboncode

Finally your XAML should be defined so, and you’re good to go!

xaml

Free WPF Themes

Update 1/10/2010 : Reuxables have now released some free themes including a lightweight version of Gemini (below) that has just the two colours. I have a sample application here showing the theme in an application I have just released.

Update : As rightly pointed out by a reader, Reuxables have stopped their free offer as they are now concentrating on the new .NET 4.0 controls and themes, including the elegant Gemini to be released soon.

Gemini

It seems the only freely available themes are the ones one codeplex. These were originally designed by the reuxables team, but I have lost faith in them with the withdrawal of the free themes they had available.

I think the best free themes at the moment are the Silverlight 4 themes, but they are yet to be ported to WPF.

Original Post

A little while back I noted that reuxables were gathering steam. Slightly earlier that year, we were all wowed by the Lawson Mango application

lawson_m3_cs_1

as this was a clear illustration that WPF could offer a vector based visual experience that left your ‘jaw on the floor’. Reuxables have now released a free version of the above theme called Inc

inc

I have been using this in my WPF applications, and have grown rather quite fond of it, principally because WPF themes can be a bit ‘too much’ for applications that one uses daily. You can get the theme here (there is also a free Metal theme [below] if that takes your fancy)

Metal

Follow

Get every new post delivered to your Inbox.