Click here to Skip to main content
15,881,743 members
Articles / Desktop Programming / WPF

WPF Timeline Control - Part I

Rate me:
Please Sign up or sign in to vote.
4.88/5 (37 votes)
21 Aug 2011MIT7 min read 126K   8.6K   97   31
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.

C#
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:

XML
<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.

XML
<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.

XML
 <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


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionNice article.Thanks you Pin
ChinaGovermentCEO22-Nov-17 20:47
ChinaGovermentCEO22-Nov-17 20:47 
QuestionSource for the sample image? Pin
David Johnson20-Jan-14 13:55
David Johnson20-Jan-14 13:55 
QuestionMy vote of 5 and question Pin
User 946612718-Apr-13 0:29
User 946612718-Apr-13 0:29 
AnswerRe: My vote of 5 and question Pin
DannyStaten18-Apr-13 1:49
DannyStaten18-Apr-13 1:49 
GeneralRe: My vote of 5 and question Pin
User 946612718-Apr-13 2:09
User 946612718-Apr-13 2:09 
GeneralRe: My vote of 5 and question Pin
DannyStaten18-Apr-13 13:43
DannyStaten18-Apr-13 13:43 
GeneralRe: My vote of 5 and question Pin
User 94661272-Aug-13 22:00
User 94661272-Aug-13 22:00 
QuestionMy vote of 5 and question Pin
Member 984428518-Feb-13 4:04
Member 984428518-Feb-13 4:04 
AnswerRe: My vote of 5 and question Pin
DannyStaten18-Feb-13 5:28
DannyStaten18-Feb-13 5:28 
QuestionChange time period Pin
adri_serce27-Nov-12 22:49
adri_serce27-Nov-12 22:49 
AnswerRe: Change time period Pin
DannyStaten28-Nov-12 3:57
DannyStaten28-Nov-12 3:57 
GeneralRe: Change time period Pin
adri_serce14-Jan-13 5:30
adri_serce14-Jan-13 5:30 
GeneralRe: Change time period Pin
DannyStaten14-Jan-13 13:31
DannyStaten14-Jan-13 13:31 
QuestionMy vote of 5 Pin
AlirezaDehqani27-Oct-12 4:07
AlirezaDehqani27-Oct-12 4:07 
GeneralMy vote of 5 Pin
chirag.khatsuriya6-Mar-12 22:33
chirag.khatsuriya6-Mar-12 22:33 
Its help me to develop this kind of application.... thx a lot
GeneralMy vote of 5 Pin
eshaq9-Sep-11 10:07
eshaq9-Sep-11 10:07 
Questionnice Pin
BillW336-Sep-11 4:52
professionalBillW336-Sep-11 4:52 
GeneralMy vote of 5 Pin
TeapotDev22-Aug-11 0:06
TeapotDev22-Aug-11 0:06 
QuestionMy vote of 5 Pin
Mike Hankey13-Aug-11 16:47
mveMike Hankey13-Aug-11 16:47 
AnswerRe: My vote of 5 Pin
DannyStaten13-Aug-11 17:58
DannyStaten13-Aug-11 17:58 
GeneralRe: My vote of 5 Pin
Mike Hankey13-Aug-11 18:04
mveMike Hankey13-Aug-11 18:04 
GeneralRe: My vote of 5 Pin
DannyStaten14-Aug-11 4:24
DannyStaten14-Aug-11 4:24 
GeneralRe: My vote of 5 Pin
Mike Hankey14-Aug-11 4:34
mveMike Hankey14-Aug-11 4:34 
GeneralMy vote of 5 Pin
mbcrump13-Aug-11 11:35
mentormbcrump13-Aug-11 11:35 
QuestionReally Nice ( one question .. ) Pin
zebulon7501813-Aug-11 10:53
zebulon7501813-Aug-11 10:53 

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

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