Click here to Skip to main content
Click here to Skip to main content
Go to top

WPF Timeline Control - Part I

, 21 Aug 2011
Rate this:
Please Sign up or sign in to vote.
Overview of the controls to implement an interactive timeline control, specifically designed to let you edit items start and end times. Everything is designed to work with the MVVM pattern allowing you to use binding in your XAML to update your data objects, etc.
SampleTimelines.jpg

Introduction

This article will go over a tool that gives you the ability to create and manage multiple timelines with freely editable items. You can add items to multiple timelines in a structure that is MVVM compatible. The control manages logic to provide some intelligent behaviors, etc. The presentation is completely open to use your own data templates to allow you to have full control of all aspects of your presentation. All start and end time values are managed through data binding, which allows you to use this control and have real time updates to your own entities.

This is my first foray into control development, so I hope it will be useful to many, but I also really look forward to any feedback and contributions. My project does utilize a very excellent project created by Steve Kirk for drag and drop functionality. His articles can be found here. The project as it appears in my solution has been modified in a few ways to suit some of my needs.

Background

There are a lot of timeline tools out there for WPF, but none of them provide the kind of behavior I needed. I recently launched a project called BookWeaver to help fiction authors manage their writing. One of the big features I wanted to provide was a timeline tool that allows an author to track complex multi-threaded stories. I wanted something that allows a user to freely edit an events start and end times while intelligently preventing overlap on a given line. It needed to manage multiple timelines so that you can track concurrent storylines. The control needed to let users click on a given item and move it in time, effectively pushing other items when it bumps into them. I wanted to let the user edit start or end times by clicking on the left or right edges respectively. I also wanted to let users have a means to keep start and end times connected when editing one that has a contiguous neighbor.

Using the Code

The timeline control requires that all Items implement the ITimeLineDataItem interface that is defined in the TimeLineTool project. If you are using the MVVM pattern, then you should have a viewmodel that houses the observable collections for all of your timelines. So long as it also implements the ITimeLineManager interface, you can enable and disable a timeline.

public interface ITimeLineDataItem
{
	DateTime? StartTime { get; set; }
	DateTime? EndTime { get; set; }
    Boolean TimelineViewExpanded { get; set; }
}

Notice that the TimeLineControls in the sample project are housed inside an items control. In a more robust project, you would probably bind an ItemsControl with a collection of your own. When the Timeline controls are housed within an ItemsControl, then the control does a fairly good job of keeping all of the siblings properly synchronized. The following is a real world example of XAML for timelines that are generated using MVVM bindings:

<ItemsControl ItemsSource="{Binding Path=Threads}"

		AlternationCount="2"
		HorizontalAlignment="Stretch">
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <Style.Triggers>

                <Trigger Property="ItemsControl.AlternationIndex" Value="0">
                    <Setter Property="ContentTemplate"
                            Value="{StaticResource ThreadTemplate}"></Setter>

                </Trigger>
                <Trigger Property="ItemsControl.AlternationIndex" Value="1">
                    <Setter Property="ContentTemplate"
                            Value="{StaticResource ThreadAlternateTemplate}"></Setter>

                </Trigger>
            </Style.Triggers>
        </Style>

    </ItemsControl.ItemContainerStyle>

    <ItemsControl.ItemsPanel>

        <ItemsPanelTemplate>
            <StackPanel Orientation="Vertical" />
        </ItemsPanelTemplate>

    </ItemsControl.ItemsPanel>
</ItemsControl>

<!--********* ThreadTemplate **********-->
<DataTemplate DataType="{x:Type ViewModels:ThreadViewModel}"
            x:Key="ThreadTemplate">

<tt:TimeLineControl Items="{Binding Path=Scenes, Mode=TwoWay, 
		UpdateSourceTrigger=PropertyChanged}"
                  DrawTimeGrid="True"

		Background="{StaticResource NormalBaseColorBrush}"
		UnitSize="{Binding ElementName=ThreadCollection_Root, 
		Path=DataContext.TimeLineUnitSize, Mode=TwoWay, 
		UpdateSourceTrigger=PropertyChanged}"
		StartDate="{Binding ElementName=ThreadCollection_Root, 
		Path=DataContext.ActualStartDate}"
		FocusOnItem="{Binding ElementName=ThreadCollection_Root, 
		Path=DataContext.FocusScene}"
		Height="30"

		MinWidth="500"
		MinimumUnitWidth="20"
		ItemTemplate="{StaticResource TimeLineSceneTemplate}"
		HorizontalAlignment="Left"
		Manager="{Binding}"

		Grid.Column="1"
                  ToolTip="{Binding Path=MyModel.ThreadName.Value}"/>
</DataTemplate>

The TimeLineSceneTemplate is pretty effectively demonstrated in a simple form in the demo project, so I am not including a real world example of it. You can see a few other properties that you can bind to. FocusScene lets you tell the control to scroll and zoom as needed to bring a given item into view. You can bind to DrawTimeGrid, which I would recommend. The grids can slow down performance, so it is actually best to only show them when the user requests them. You can set brushes for various time unit grid lines, or you can use defaults that will be various intensities of Gray/Black.

The DataTemplate for a Scene is pretty simple in the demo.

<DataTemplate  DataType="{x:Type tt:TempDataType}"
                x:Key="UsedTemplateProperty">
    <Border BorderThickness="1"

            BorderBrush="Green"
            CornerRadius="3"
            Margin="0,10,0,10"
            Height="60"
            Background="{StaticResource BgBrush}">

        <StackPanel Orientation="Vertical">
            <TextBlock Text="{Binding Path=Name}" />
            <TextBlock Text="{Binding Path=StartTime}" />
            <TextBlock Text="{Binding Path=EndTime}" />

            <TextBlock Text="{Binding Path=Duration}" />
        </StackPanel>
    </Border>
</DataTemplate>

The TimelineControl will take the Data bound to its Scenes property and will create a TimeLineItemControl for each one, then apply the Data Template you indicate for the contents of said TimeLineItemControl. If you want to influence your TimeLineItemControl's appearance, you do that via its style. The style for the control lets you apply a control template as well as set any other properties you want to set for the TimeLineItemControl.

 <Style TargetType="{x:Type tt:TimeLineItemControl}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type tt:TimeLineItemControl}">

                <Grid>
                    <Border Background="Blue"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch"
                            Opacity="0" />

                    <ContentPresenter HorizontalAlignment="Stretch"
                                        VerticalAlignment="Stretch"
                                        x:Name="PART_ContentHostPART_ContentHost" />
                    <ContentPresenter HorizontalAlignment="Left"

                                        VerticalAlignment="Stretch"
                                        x:Name="PART_LeftIndicator">
                        <ContentPresenter.Content>
                            <Button /><!-- this is the content you can create 
				for whatever left indicator you want to show 
				when you mouse over the left edge.-->
                        </ContentPresenter.Content>

                    </ContentPresenter>
                    <ContentPresenter HorizontalAlignment="Right"
                                        VerticalAlignment="Stretch"
                                        x:Name="PART_RightIndicator">
                        <ContentPresenter.Content>

                            <Button /><!--this is the content you can create for 
				whatever right indicator you want to show 
				when you mouse over the right edge. -->
                        </ContentPresenter.Content>

                    </ContentPresenter>
                </Grid>
            </ControlTemplate>

        </Setter.Value>
    </Setter>
</Style>

You will want to take note of the PART_RightIndicator and PART_LeftIndicator. If you provide ContentPresenters with those respective names, the control will show whatever contents you place there when you mouse over the edges. If you use the style to set the property EditBorderThreshold then you can adjust the tolerances for those edges.

Points of Interest

It has been a long time since I actually did the implementation of the project, so a lot of the particulars aren't fresh in my memory. One thing I did find particularly challenging was dealing with interdependent dependency properties. Often times, I wanted similar actions to happen when multiple properties changed, but that action depends on all of the properties being initialized. So quite often, I would get exceptions depending on what order things happened. I am not entirely happy with how complicated everything seemed to get, and hope I can find some ways to simplify it and streamline it.

I also ran into something funky when I was tracking mouse move events for editing the scenes within the scene's code. The problem was that changes were relative to the scene itself. So when I moved the scene in response to the move event, that would cause the next move event to register a delta in the opposite direction. The result was that the scenes would flicker back and forth as I edited them. I felt pretty silly once I realized the reason for that behavior. Once I tracked movements relative to the parent canvas, that problem went away.

I had initially wanted to use left click for editing time values and right click for drag and drop to reorder. However, WPF doesn't provide any means to do drag operations with the right click. So I had to do all drag operation with the left click. That meant all time editing actions had to be relegated to the right click.

I ended up going for a simple canvas as the holding element for better and worse.

I added an IsExpanded dependency property for some of my specific needs. I had implemented a specialized expander that uses PopUp controls for the expander's contents. It caused some frustrating glitches under certain circumstances so I wired up the TimeLineItemControl so it will update that property and thus collapse the item under the different circumstances that were resulting in some unpleasant bugs. You likely can ignore that property for most of your uses. Binding to it will collapse expanders whenever your mouse rolls off the given TimeLineItemControl.

Intended Upgrades

I hope to do several things to upgrade it, but I am not sure when I will get to it. If anybody does provide updates, that would be awesome, but I don't expect it. I am using this in my commercial project BookWeaver, and upgrades to this tool will find their way into it.

I hope to improve several things about it in the future:

  • Snapping to discrete time units (though that may be tricky for me)
  • Give it the ability to collapse empty gaps and drag/drop across them as if they weren't present. The current version doesn't really work well for a timeline that has large breaks in time.
  • I hope to clean up and simplify the code. I am still somewhat of a newbie at building controls like this.
  • I hope to improve its start up draw behavior so stretch horizontal alignment would be meaningful.

Acknowledgements

Users here on CodeProject were extremely helpful as I was developing this tool. As mentioned in the introduction, Steve Kirk's drag drop solution was also very useful. I wanted to post this as a way of thanking those who helped me get the control together. I welcome and look forward to feedback. I am sure it can be improved and cleaned up. I also learned a lot from people at the MSDN forums.

I hope to write a follow up article that details the implementation of the control sometime soon.

History

  • Version 1 - August 2011
  • Version 1.01 - August 17 2011
    • Added the ability to template out left and right edge indicators. I also made the threshold for when you are editing start and end time a dependency property so you can bind to it.
  • Version 1.02 - August 20 2011
    • Fixed a bug with the DrawTimeGrid property. It wasn't properly updating the timeline control when it changed after initialization.

License

This article, along with any associated source code and files, is licensed under The MIT License

Share

About the Author

DannyStaten

United States United States
No Biography provided

Comments and Discussions

 
GeneralMy vote of 5 Pinmembermbcrump13-Aug-11 11:35 
QuestionReally Nice ( one question .. ) Pinmemberzebulon7501813-Aug-11 10:53 
AnswerRe: Really Nice ( one question .. ) PinmemberDannyStaten13-Aug-11 11:22 
GeneralRe: Really Nice ( one question .. ) [modified] Pinmemberzebulon7501814-Aug-11 5:42 
GeneralRe: Really Nice ( one question .. ) PinmemberDannyStaten16-Aug-11 19:11 
QuestionI have also done a timeline one PinmvpSacha Barber13-Aug-11 10:15 
QuestionGreat Job. Pinmemberalinux0813-Aug-11 5:15 
AnswerRe: Great Job. PinmemberDannyStaten13-Aug-11 7:50 
The screen shot is from a specific implementation where I took the time to fully style it out etc. I thought that would be a more meaningful preview of what the control can be made to look like etc than the demo, where I was focusing exclusively on getting something very basic running.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140916.1 | Last Updated 21 Aug 2011
Article Copyright 2011 by DannyStaten
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid