WPF, how can I optimize lines and circles drawing ?
- by Aurélien Ribon
Hello !
I am developping an application where I need to draw a graph on the screen.
For this purpose, I use a Canvas and I put Controls on it.
An example of such a draw as shown in the app can be found here :
http://free0.hiboox.com/images/1610/d82e0b7cc3521071ede601d3542c7bc5.png
It works fine for simple graphs, but I also want to be able to draw very large graphs (hundreds of nodes). And when I try to draw a very large graph, it takes a LOT of time to render.
My problem is that the code is not optimized at all, I just wanted it to work. Until now, I have a Canvas on the one hand, and multiple Controls on the other hands. Actually, circles and lines are listed in collections, and for each item of these collections, I use a ControlTemplate, defining a red circle, a black circle, a line, etc.
Here is an example, the definition of a graph circle :
<!--
STYLE : DISPLAY DATA NODE
-->
<Style TargetType="{x:Type flow.elements:DisplayNode}">
<Setter Property="Canvas.Left" Value="{Binding X, RelativeSource={RelativeSource Self}}" />
<Setter Property="Canvas.Top" Value="{Binding Y, RelativeSource={RelativeSource Self}}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type flow.elements:DisplayNode}">
<!--TEMPLATE-->
<Grid x:Name="grid" Margin="-30,-30,0,0">
<Ellipse x:Name="selectionEllipse" StrokeThickness="0" Width="60"
Height="60" Opacity="0" IsHitTestVisible="False">
<Ellipse.Fill>
<RadialGradientBrush>
<GradientStop Color="Black" Offset="0.398" />
<GradientStop Offset="1" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse Stroke="Black" Width="30" Height="30" x:Name="ellipse">
<Ellipse.Fill>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Offset="0" Color="White" />
<GradientStop Offset="1.5" Color="LightGray" />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<TextBlock x:Name="tblock"
Text="{Binding NodeName, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Foreground="Black" VerticalAlignment="Center"
HorizontalAlignment="Center" FontSize="10.667" />
</Grid>
<!--TRIGGERS-->
<ControlTemplate.Triggers>
<!--DATAINPUT-->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="SkinMode" Value="NODETYPE" />
<Condition Property="NodeType" Value="DATAINPUT" />
</MultiTrigger.Conditions>
<Setter TargetName="tblock" Property="Foreground" Value="White" />
<Setter TargetName="ellipse" Property="Fill">
<Setter.Value>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Offset="-0.5" Color="White" />
<GradientStop Offset="1" Color="Black" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</MultiTrigger>
<!--DATAOUTPUT-->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="SkinMode" Value="NODETYPE" />
<Condition Property="NodeType" Value="DATAOUTPUT" />
</MultiTrigger.Conditions>
<Setter TargetName="tblock" Property="Foreground" Value="White" />
<Setter TargetName="ellipse" Property="Fill">
<Setter.Value>
<LinearGradientBrush EndPoint="0,1">
<GradientStop Offset="-0.5" Color="White" />
<GradientStop Offset="1" Color="Black" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
</MultiTrigger>
....... THERE IS A TOTAL OF 7 MULTITRIGGERS .......
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Also, the lines are drawn using the Line Control.
<!--
STYLE : DISPLAY LINK
-->
<Style TargetType="{x:Type flow.elements:DisplayLink}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type flow.elements:DisplayLink}">
<!--TEMPLATE-->
<Line X1="{Binding X1, RelativeSource={RelativeSource TemplatedParent}}"
X2="{Binding X2, RelativeSource={RelativeSource TemplatedParent}}"
Y1="{Binding Y1, RelativeSource={RelativeSource TemplatedParent}}"
Y2="{Binding Y2, RelativeSource={RelativeSource TemplatedParent}}"
Stroke="Gray" StrokeThickness="2" x:Name="line" />
<!--TRIGGERS-->
<ControlTemplate.Triggers>
<!--BRANCH : ASSERTION-->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="SkinMode" Value="BRANCHTYPE" />
<Condition Property="BranchType" Value="ASSERTION" />
</MultiTrigger.Conditions>
<Setter TargetName="line" Property="Stroke" Value="#E0E0E0" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
So, I need your advices. How can I drastically improve the rendering performances ? Should I define each MultiTrigger circle rendering possibility in its own ControlTemplate instead ? Is there a better line drawing technique ?
Should I open a DrawingContext and draw everything in one control, instead of having hundreds of controls ?