Speed of WPF Gantt

It is easy to make the WPF Gantt slow, just bog it down with lots of time items that has lots of details (complex internal markup).

This article is about how to make the WPF Gantt really fast.

There are two important things to optimize:

1. The Gantt rows

2. The Time Items

The Gantt rows

The Gantt rows can be optimized by limiting their rendering to only show the ones on screen. This is handled by standard WPF Virtualization implemented by VirtualizingStackPanel.

Example:

If you have your Gantt Rows in a Items control (if it is a tree view then it is really one items control per node):

      <ItemsControl x:Name="_ItemsControl"
                    VirtualizingStackPanel.IsVirtualizing="True"
                    VirtualizingStackPanel.VirtualizationMode="Standard"
    ScrollViewer.CanContentScroll="True"

                    ItemTemplate="{StaticResource _GanttRowTemplate}"
                    ItemsSource="{Binding Path=Resources}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <VirtualizingStackPanel />
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                    <ItemsControl.Template>
          <ControlTemplate>
              
            <Border HorizontalAlignment="Stretch"
                    BorderThickness="{TemplateBinding Border.BorderThickness}"
                    Padding="{TemplateBinding Control.Padding}"
                    BorderBrush="{TemplateBinding Border.BorderBrush}"
                    Background="{TemplateBinding Panel.Background}">
              <ScrollViewer x:Name="_ScrollViewer"
                            HorizontalAlignment="Stretch"
                            HorizontalContentAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            VerticalContentAlignment="Stretch"
                            IsDeferredScrollingEnabled="True"
                            CanContentScroll="True"
                            Padding="{TemplateBinding Control.Padding}"
                            ScrollChanged="ScrollViewer_ScrollChanged"
                            Focusable="False">
                <ItemsPresenter HorizontalAlignment="Stretch"
                                VerticalAlignment="Stretch" />
              </ScrollViewer>
            </Border>
          </ControlTemplate>
        </ItemsControl.Template>
      </ItemsControl>

Doing this will stop WPF from wasting time on stuff that is not currently on screen. And WPF will create the GanttRows when scrolled into view.

The TimeItems

The Time items are not in a StackPanel so we need to use a different technique. We need a way to decide if a should be on screen or not.

We do this by implementing the event OnVirtualizeTimeItemCheck. In this event we get information about the bound object – that YOU know how to interpret – and the ability to say if it should be visible or not. We inform the Gantt that it should not be visible by setting e.SuggestedVisibility to Collapsed.

    void _GanttControl_OnVirtualizeTimeItemCheck(object sender, OnVirtualizeTimeItemCheckArgs e)
    {
      if (e.TimeItemObject is WpfApplication10.Task)
      { 
        var t=e.TimeItemObject as WpfApplication10.Task;
        if (t.Begin < e.VisibleAreaStop && t.End > e.VisibleAreaStart)
          e.SuggestedVisibility = System.Windows.Visibility.Visible;
        else
          e.SuggestedVisibility = System.Windows.Visibility.Collapsed;
      }
    }

The easiest way to decide if it should be on screen is like above: If the start is smaller than stop of view AND the stop is bigger than start of view. This covers the cases when time items are partly on screen, and when time items span the whole screen and beyond.

Summary

If you have large datasets like 1000 rows and 1000 time items per row – you really have to follow these guidelines. But you see good effects long before that.

Below 1 million Time items – Gantt still renders fast.

image

11557 : OnGanttRowMinHeightChange

If you want your GanttRow to increase in height to make room for collided time items; read on.

 

Your row looks something like this:

<HierarchicalDataTemplate  x:Key=”RowInfoTemplate”  ItemsSource=”{Binding SubNodes}” ItemTemplate=”{StaticResource SubRowInfoTemplate}”>
        <Grid  MinHeight=”25″>
            <Grid.ColumnDefinitions>
                <ColumnDefinition ></ColumnDefinition>
                <ColumnDefinition ></ColumnDefinition>
            </Grid.ColumnDefinitions>
            <ph:TreeViewMultiColumnRow MultiColumnCoordinator=”{Binding Source={StaticResource MCC}}” LevelReduce=”16″>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <TextBlock Text=”{Binding Text}” Grid.Column=”0″></TextBlock>
                <TextBlock Text=”{Binding Text}” Grid.Column=”1″></TextBlock>
            </ph:TreeViewMultiColumnRow>
                <Canvas >
                    <ph:GanttRow 
                        FollowDateScalerPosition=”True”
                        Canvas.Top=”-3″ BorderThickness=”0″ Background=”Transparent”
                        IncreaseRow_StartHeight =”30″ IncreaseRowHeightOnCollision=”True” CollisionTimeItemOffset=”20″
                        ItemsSource=”{Binding Items}” ItemTemplate=”{StaticResource SpanTimeItem}” 
                        OnUserDrawTimeItem=”GanttRow_OnUserDrawTimeItem” 
                        OnGanttRowMinHeightChange=”GanttRow_OnGanttRowMinHeightChange”></ph:GanttRow>
                </Canvas>
        </Grid>
    </HierarchicalDataTemplate>

Notice the the GanttRow is enclosed in a Canvas to allow it to follow the datescaler.
The GanttRow will increase in MinHeight since the IncreaseRowHeightOnCollision=”True”, but the canvas will not care, since a canvas does not care how big its content is.

So I implemented the OnGanttRowMinHeightChange event:

        private void GanttRow_OnGanttRowMinHeightChange(object sender, OnGanttRowMinHeightChangeArgs e)
        {
            (e.GanttRow.Parent as Canvas).MinHeight=e.NewHeight;
        }
Now the Canvas increased MinHeight will force to enclosing Grid to Increase height…

 

11432 : Scrollbar for all GanttRows

Question

 

I used your toolkit tree view example as a basis for this but I have set the tree view’s height to auto so as to use the whole screen – but now I have lost the scroll bar on the right. Can I have full screen and a toolbar?

 

Answer

< ?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /> 

You can do this many ways I guess. One way:

                    <ScrollViewer 

                        Name=”_myscrollview”

                        Grid.Row=”1″

                        Grid.Column=”0″

                        Grid.ColumnSpan=”3″

                        Height=”300″

                        VerticalScrollBarVisibility=”Visible”>

                        <ScrollViewer.Content>

                        <controls:TreeView  

                        BorderThickness=”0″

                        Margin=”0″

                        x:Name=”treeview1″

                        ItemTemplate=”{StaticResource SubRowInfoTemplate}”

                        Background=”Transparent” />

                    </ScrollViewer.Content></ScrollViewer>

 

Then add a event handler:

        private void LayoutRoot5_SizeChanged(object sender, SizeChangedEventArgs e)

        {

            _myscrollview.Height = e.NewSize.Height-100;

        }

 

There is probably some smarter way to this in xaml (there always is it seems… J)

11431 : Is there any way to show a background line between the days

Question

 

Is there any way to show a background line between the days? Maybe even hours/minutes depending on scale?< ?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

 

Answer

You can do things like that by using the events, this code draws a line each day:

        void gantt1_OnDrawBackground(object sender, GanttUserDrawArgs e)

        {

            e.Canvas.Children.Clear();

            DateTime aDate=gantt10.DateScaler.StartTime;

            aDate=aDate.Date; // only keep date part

            while (aDate<gantt10.DateScaler.StopTime)

            {

                Line line = new Line();

                e.Canvas.Children.Add(line);

                line.X1 = gantt10.DateScaler.TimeToPixel(aDate);

                line.X2 = line.X1;

                line.Y1 = 0;

                line.Y2 = gantt10.ActualHeight;

                line.Stroke = new SolidColorBrush(Colors.Black);

                line.StrokeThickness = 1;

                aDate=aDate.AddDays(1);

            }

        }

11433 : Position texts before and and after a time item

Question

Howto get textblocks to appear before and after the TimeItem?

Answer

In this time item the Grid with name Rect is the one that gets the size based on Start and Stop datetime values.

The Text after the time item is the one named Text1, the one infront is Text2.

Text1 is positioned in the event implementation: SizeChanged=”TimeItem_SizeChanged” see impl at the bottom

Text2 is positioned with a Translate transform with a negative value.

        <DataTemplate x:Name=”SquaredTimeItem”>< ?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

            <l:TimeItem Height=”20″

                Start=”{Binding Start,Mode=TwoWay}”

                Stop=”{Binding Stop,Mode=TwoWay}”

                Identity=”{Binding Id}”

                TimeItemSizedFrameworkElementName=”Rect” SizeChanged=”TimeItem_SizeChanged”>

 

                <Grid Height=”20″ Name=”Rect”>

                    <Border  BorderBrush=”Black” BorderThickness=”0″ CornerRadius=”3″>

                        <Border.Background>

                            <LinearGradientBrush>

                                <LinearGradientBrush.GradientStops>

                                    <GradientStop Offset=”0″ Color=”LightGray”/>

                                    <GradientStop Offset=”1″ Color=”Gray”/>

                                </LinearGradientBrush.GradientStops>

                            </LinearGradientBrush>

                        </Border.Background>

                    </Border>

                </Grid>

                <TextBlock Name=”Text1″ HorizontalAlignment=”Right” Margin=”3,6,0,0″ Text=”{Binding To}” >

                            <TextBlock.RenderTransform>

                                <TranslateTransform X=”20″></TranslateTransform>

                            </TextBlock.RenderTransform>

                </TextBlock>

                <TextBlock Name=”Text2″ HorizontalAlignment=”Left” Margin=”3,6,0,0″ Text=”{Binding From}” >                           

                            <TextBlock.RenderTransform>

                                <TranslateTransform X=”-25″></TranslateTransform>

                            </TextBlock.RenderTransform>

                </TextBlock>

 

            </l:TimeItem>

        </DataTemplate>

 

        private void TimeItem_SizeChanged(object sender, SizeChangedEventArgs e)

        {

            if (sender is TimeItem)

            {

                TextBlock tb1=(sender as TimeItem).FindName(“Text1″) as TextBlock;

                (tb1.RenderTransform as TranslateTransform).X = e.NewSize.Width + 10;

            }

        }

 

GTP.NET.SL 1.0.2.15

——————————————
——-GTP.NET.SL 1.0.2.15—-
——————————————
***************** Doc *****************
User: Hasse Date: 09-02-04 Time: 14:38
Added LICENSE.GTP.NET.SL.rtf

***************** Doc *****************
User: Hasse Date: 09-02-04 Time: 14:38
Added info.mht

***************** Doc *****************
User: Hasse Date: 09-02-04 Time: 14:38
Added gettingstarted.mht

***************** Doc *****************
User: Hasse Date: 09-02-04 Time: 14:38
Added GettingStarted.dxc

***************** Gantt *****************
User: Hasse Date: 09-01-31 Time: 21:28
Added LicenseCheckCall.cs

***************** ClientBin *****************
User: Hasse Date: 09-01-23 Time: 12:54
Deleted placeholder.txt

***************** Doc *****************
User: Hasse Date: 09-01-21 Time: 10:58
Added GTP.NET.SL.dxp

***************** Doc *****************
User: Hasse Date: 09-01-21 Time: 10:58
Added GTP.NET.SL.dxc

***************** Version 14 *****************
User: Hasse Date: 09-01-21 Time: 10:58
Added Doc

***************** GanttRow.cs *****************
***************** GTP.NET.SL.csproj *****************
***************** TimeItem.cs *****************
User: Hasse Date: 09-01-18 Time: 20:36
Checked in $/ph.NET/Components/GTP.NET.SL/Gantt
Comment:
UserDraw

***************** SLDev1 *****************
***************** SLDev1 *****************
User: Hasse Date: 09-01-18 Time: 20:25
Added SampleUsingToolkitTreeview.xaml

***************** MoveBetweenControls.xaml *****************
***************** MoveBetweenControls.xaml.cs *****************
***************** SampleChoose.xaml *****************
***************** TimeItem.cs *****************
***************** TimeItemLink.cs *****************
***************** TODOS.txt.txt *****************
***************** tankar.txt *****************
***************** Gantt.cs *****************
***************** GanttRow.cs *****************
User: Hasse Date: 09-01-18 Time: 17:01
Checked in $/ph.NET/Components/GTP.NET.SL/Gantt
Comment:
Cleans up links when removing time items