├── .gitignore
└── TimeLineControl
├── TimeLineTestApp
├── App.config
├── Properties
│ ├── Settings.settings
│ ├── Settings.Designer.cs
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── App.xaml.cs
├── App.xaml
├── MainWindow.xaml
├── MainWindow.xaml.cs
└── TimeLineTestApp.csproj
├── TimeLineControl
├── Properties
│ ├── Settings.settings
│ ├── Settings.Designer.cs
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ └── Resources.resx
├── MyObservableObject.cs
├── TimeLineData.cs
├── EventTimeAdorner.cs
├── FrmSetDuration.xaml
├── TimeLineEntryConverter.cs
├── FrmSetDuration.xaml.cs
├── TimeLineEntry.cs
├── TimeLineControl.csproj
├── TimeLine.xaml
└── TimeLine.xaml.cs
└── TimeLineControl.sln
/.gitignore:
--------------------------------------------------------------------------------
1 | TimeLineControl/.vs/
2 | TimeLineControl/TimeLineControl/bin/
3 | TimeLineControl/TimeLineControl/obj/
4 | TimeLineControl/TimeLineTestApp/bin/
5 | TimeLineControl/TimeLineTestApp/obj/
6 | TimeLineControl/.cr/personal/
7 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/App.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/App.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Configuration;
4 | using System.Data;
5 | using System.Linq;
6 | using System.Threading.Tasks;
7 | using System.Windows;
8 |
9 | namespace TimeLineTestApp
10 | {
11 | ///
12 | /// Interaction logic for App.xaml
13 | ///
14 | public partial class App : Application
15 | {
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/App.xaml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/MyObservableObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Linq;
4 | using System.Runtime.CompilerServices;
5 |
6 | namespace TimeLineControl
7 | {
8 | public class MyObservableObject : INotifyPropertyChanged
9 | {
10 |
11 | public MyObservableObject()
12 | {
13 |
14 | }
15 |
16 | public event PropertyChangedEventHandler PropertyChanged;
17 |
18 | protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
19 | {
20 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/MainWindow.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace TimeLineTestApp.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace TimeLineControl.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.0.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/TimeLineData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using System.Linq;
4 |
5 | namespace TimeLineControl
6 | {
7 | public class TimeLineData : MyObservableObject
8 | {
9 | private ObservableCollection entries = new ObservableCollection();
10 |
11 | public TimeLineData()
12 | {
13 | entries.CollectionChanged += Entries_CollectionChanged;
14 | }
15 |
16 |
17 |
18 | private void Entries_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
19 | {
20 | OnPropertyChanged("Entries");
21 | }
22 |
23 | public ObservableCollection Entries
24 | {
25 | get
26 | {
27 | return entries;
28 | }
29 | set
30 | {
31 | if (entries == value)
32 | return;
33 |
34 | if (entries != null)
35 | entries.CollectionChanged -= Entries_CollectionChanged;
36 |
37 | entries = value;
38 | OnPropertyChanged();
39 | }
40 | }
41 |
42 | public TimeLineEntry AddEntry(TimeSpan start, TimeSpan duration, string name, object data)
43 | {
44 | TimeLineEntry timeLineEntry = new TimeLineEntry() { Start = start, Duration = duration, Name = name, Data = data, Index = Entries.Count };
45 | Entries.Add(timeLineEntry);
46 | return timeLineEntry;
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/MainWindow.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Navigation;
14 | using System.Windows.Shapes;
15 | using TimeLineControl;
16 |
17 | namespace TimeLineTestApp
18 | {
19 | ///
20 | /// Interaction logic for MainWindow.xaml
21 | ///
22 | public partial class MainWindow : Window
23 | {
24 | TimeLineData timeLineData = new TimeLineData();
25 | public MainWindow()
26 | {
27 | InitializeComponent();
28 | timeLineData.AddEntry(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(3), "Test 1", null);
29 | timeLineData.AddEntry(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(2), "Test 2", null);
30 |
31 | timeLine.ItemsSource = timeLineData.Entries;
32 | timeLine.MinEntryDuration = 0.3;
33 |
34 | timeLineData.AddEntry(TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), "Test 3", null);
35 | timeLineData.AddEntry(TimeSpan.FromSeconds(4), TimeSpan.FromSeconds(0.5), "Test 4", null).DurationLocked = true;
36 | //timeLine.TotalDuration = TimeSpan.FromSeconds(5);
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.28711.60
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimeLineControl", "TimeLineControl\TimeLineControl.csproj", "{DF1E9DF3-204A-4DBF-8B8D-60CA5C0A5BC3}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimeLineTestApp", "TimeLineTestApp\TimeLineTestApp.csproj", "{9F5CDBA0-48FC-47AA-B2E2-27C7F6D90554}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {DF1E9DF3-204A-4DBF-8B8D-60CA5C0A5BC3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {DF1E9DF3-204A-4DBF-8B8D-60CA5C0A5BC3}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {DF1E9DF3-204A-4DBF-8B8D-60CA5C0A5BC3}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {DF1E9DF3-204A-4DBF-8B8D-60CA5C0A5BC3}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {9F5CDBA0-48FC-47AA-B2E2-27C7F6D90554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {9F5CDBA0-48FC-47AA-B2E2-27C7F6D90554}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {9F5CDBA0-48FC-47AA-B2E2-27C7F6D90554}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {9F5CDBA0-48FC-47AA-B2E2-27C7F6D90554}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {D42FFE66-8122-487F-A369-DEBBCE3F56F5}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/EventTimeAdorner.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Globalization;
3 | using System.Linq;
4 | using System.Windows;
5 | using System.Windows.Documents;
6 | using System.Windows.Media;
7 |
8 | namespace TimeLineControl
9 | {
10 | public class EventTimeAdorner : Adorner
11 | {
12 | public EventTimeAdorner(UIElement adornedElement) : base(adornedElement)
13 | {
14 | }
15 |
16 | protected override void OnRender(DrawingContext drawingContext)
17 | {
18 | Rect adornedElementRect = new Rect(this.AdornedElement.DesiredSize);
19 |
20 | SolidColorBrush brush = new SolidColorBrush(Color.FromRgb(255, 247, 222));
21 | Pen pen = new Pen(new SolidColorBrush(Color.FromRgb(201, 184, 151)), 1);
22 |
23 | // adornedElementRect.TopLeft
24 |
25 | FormattedText formattedText = new FormattedText(
26 | string.Format("{0:0.##} sec", EventTime.TotalSeconds),
27 | CultureInfo.GetCultureInfo("en-us"),
28 | FlowDirection.LeftToRight,
29 | new Typeface("Verdana"),
30 | 12,
31 | Brushes.Black
32 | // , VisualTreeHelper.GetDpi(this).PixelsPerDip
33 | );
34 |
35 | double textWidth = formattedText.Width;
36 | double textHeight = formattedText.Height;
37 | const double horizontalMargin = 3;
38 | const double verticalMargin = 2;
39 | Point adornerTopLeft = adornedElementRect.BottomLeft;
40 | Point bottomRight = new Point(adornerTopLeft.X + textWidth + horizontalMargin * 2, adornerTopLeft.Y + textHeight + verticalMargin * 2);
41 | Rect rect = new Rect(adornerTopLeft, bottomRight);
42 | const double radius = 3;
43 | drawingContext.DrawRoundedRectangle(brush, pen, rect, radius, radius);
44 |
45 | Point textTopLeft = adornerTopLeft;
46 | textTopLeft.Offset(horizontalMargin, verticalMargin);
47 | drawingContext.DrawText(formattedText, textTopLeft);
48 | }
49 |
50 | public TimeSpan EventTime { get; set; }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/FrmSetDuration.xaml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | 0
25 |
26 |
27 |
28 |
29 | Infinity
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/TimeLineEntryConverter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Windows;
4 | using System.Windows.Data;
5 |
6 | namespace TimeLineControl
7 | {
8 | public class TimeLineEntryConverter : IMultiValueConverter
9 | {
10 | public const double HackPercentWidth = 1; // TODO: Find the actual width of the parenting Grid in the Visual Tree.
11 | public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
12 | {
13 | TimeSpan timelineTimeScale = (TimeSpan)values[0];
14 | TimeSpan relativeTime = (TimeSpan)values[1];
15 | double containerWidth = (double)values[2] - 16;
16 | TimeSpan entryStart = (TimeSpan)values[3];
17 | TimeSpan timeLineEnd = (TimeSpan)values[4];
18 | double pixelsPerSecond = containerWidth / timelineTimeScale.TotalSeconds;
19 | double scrollViewWidth = timeLineEnd.TotalSeconds * pixelsPerSecond;
20 | if (scrollViewWidth < containerWidth)
21 | scrollViewWidth = containerWidth;
22 |
23 | double relativeDistance;
24 | if (relativeTime == System.Threading.Timeout.InfiniteTimeSpan)
25 | {
26 |
27 | double startPositionPixels = GetWidthPixels(entryStart, scrollViewWidth, timelineTimeScale.TotalSeconds);
28 | relativeDistance = containerWidth - startPositionPixels;
29 | }
30 | else
31 | {
32 | double containerWidthPixels = GetWidthPixels(relativeTime, containerWidth, timelineTimeScale.TotalSeconds);
33 | relativeDistance = containerWidthPixels * HackPercentWidth;
34 | }
35 |
36 | if (targetType == typeof(Thickness))
37 | return new Thickness(relativeDistance, 0, 0, 0);
38 | else
39 | return relativeDistance;
40 | }
41 |
42 | private static double GetWidthPixels(TimeSpan relativeTime, double containerWidth, double totalSeconds)
43 | {
44 | double factor;
45 | if (totalSeconds == 0)
46 | factor = 0;
47 | else
48 | factor = relativeTime.TotalSeconds / totalSeconds;
49 | return factor * containerWidth;
50 | }
51 |
52 | public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
53 | {
54 | throw new NotImplementedException();
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/FrmSetDuration.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using System.Windows;
7 | using System.Windows.Controls;
8 | using System.Windows.Data;
9 | using System.Windows.Documents;
10 | using System.Windows.Input;
11 | using System.Windows.Media;
12 | using System.Windows.Media.Imaging;
13 | using System.Windows.Shapes;
14 |
15 | namespace TimeLineControl
16 | {
17 | ///
18 | /// Interaction logic for FrmSetDuration.xaml
19 | ///
20 | public partial class FrmSetDuration : Window
21 | {
22 | const string STR_Infinity = "∞";
23 | string lastSetDurationStr;
24 | string lastRealDurationStr;
25 | bool changingInternally;
26 | public double Duration
27 | {
28 | get
29 | {
30 | if (double.TryParse(tbxDuration.Text, out double duration))
31 | return duration;
32 | return double.PositiveInfinity;
33 | }
34 | set
35 | {
36 | string durationStr = value.ToString();
37 | if (double.IsInfinity(value))
38 | {
39 | lastRealDurationStr = tbxDuration.Text;
40 | durationStr = STR_Infinity;
41 | }
42 | else
43 | lastRealDurationStr = value.ToString();
44 | lastSetDurationStr = durationStr;
45 | tbxDuration.Text = durationStr;
46 | }
47 | }
48 |
49 | public FrmSetDuration()
50 | {
51 | InitializeComponent();
52 | }
53 |
54 | private void CkIsInfinity_Checked(object sender, RoutedEventArgs e)
55 | {
56 | if (changingInternally)
57 | return;
58 | //tbxDuration.Text = STR_Infinity;
59 | Duration = double.PositiveInfinity;
60 | }
61 |
62 | private void BtnDialogOk_Click(object sender, RoutedEventArgs e)
63 | {
64 | DialogResult = true;
65 | }
66 |
67 | private void CkIsInfinity_Unchecked(object sender, RoutedEventArgs e)
68 | {
69 | if (changingInternally)
70 | return;
71 | tbxDuration.Text = lastRealDurationStr;
72 | }
73 |
74 | private void Window_Loaded(object sender, RoutedEventArgs e)
75 | {
76 | tbxDuration.Text = Duration.ToString();
77 | }
78 |
79 | private void TbxDuration_TextChanged(object sender, TextChangedEventArgs e)
80 | {
81 | if (ckIsInfinity == null)
82 | return;
83 | changingInternally = true;
84 | try
85 | {
86 | ckIsInfinity.IsChecked = tbxDuration.Text == STR_Infinity;
87 | }
88 | finally
89 | {
90 | changingInternally = false;
91 | }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("TimeLineControl")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("TimeLineControl")]
15 | [assembly: AssemblyCopyright("Copyright © 2019")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Resources;
3 | using System.Runtime.CompilerServices;
4 | using System.Runtime.InteropServices;
5 | using System.Windows;
6 |
7 | // General Information about an assembly is controlled through the following
8 | // set of attributes. Change these attribute values to modify the information
9 | // associated with an assembly.
10 | [assembly: AssemblyTitle("TimeLineTestApp")]
11 | [assembly: AssemblyDescription("")]
12 | [assembly: AssemblyConfiguration("")]
13 | [assembly: AssemblyCompany("")]
14 | [assembly: AssemblyProduct("TimeLineTestApp")]
15 | [assembly: AssemblyCopyright("Copyright © 2019")]
16 | [assembly: AssemblyTrademark("")]
17 | [assembly: AssemblyCulture("")]
18 |
19 | // Setting ComVisible to false makes the types in this assembly not visible
20 | // to COM components. If you need to access a type in this assembly from
21 | // COM, set the ComVisible attribute to true on that type.
22 | [assembly: ComVisible(false)]
23 |
24 | //In order to begin building localizable applications, set
25 | //CultureYouAreCodingWith in your .csproj file
26 | //inside a . For example, if you are using US english
27 | //in your source files, set the to en-US. Then uncomment
28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in
29 | //the line below to match the UICulture setting in the project file.
30 |
31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
32 |
33 |
34 | [assembly: ThemeInfo(
35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
36 | //(used if a resource is not found in the page,
37 | // or application resource dictionaries)
38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
39 | //(used if a resource is not found in the page,
40 | // app, or any theme specific resource dictionaries)
41 | )]
42 |
43 |
44 | // Version information for an assembly consists of the following four values:
45 | //
46 | // Major Version
47 | // Minor Version
48 | // Build Number
49 | // Revision
50 | //
51 | // You can specify all the values or you can default the Build and Revision Numbers
52 | // by using the '*' as shown below:
53 | // [assembly: AssemblyVersion("1.0.*")]
54 | [assembly: AssemblyVersion("1.0.0.0")]
55 | [assembly: AssemblyFileVersion("1.0.0.0")]
56 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace TimeLineTestApp.Properties
12 | {
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources
26 | {
27 |
28 | private static global::System.Resources.ResourceManager resourceMan;
29 |
30 | private static global::System.Globalization.CultureInfo resourceCulture;
31 |
32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
33 | internal Resources()
34 | {
35 | }
36 |
37 | ///
38 | /// Returns the cached ResourceManager instance used by this class.
39 | ///
40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
41 | internal static global::System.Resources.ResourceManager ResourceManager
42 | {
43 | get
44 | {
45 | if ((resourceMan == null))
46 | {
47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TimeLineTestApp.Properties.Resources", typeof(Resources).Assembly);
48 | resourceMan = temp;
49 | }
50 | return resourceMan;
51 | }
52 | }
53 |
54 | ///
55 | /// Overrides the current thread's CurrentUICulture property for all
56 | /// resource lookups using this strongly typed resource class.
57 | ///
58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
59 | internal static global::System.Globalization.CultureInfo Culture
60 | {
61 | get
62 | {
63 | return resourceCulture;
64 | }
65 | set
66 | {
67 | resourceCulture = value;
68 | }
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace TimeLineControl.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("TimeLineControl.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/TimeLineEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.ObjectModel;
3 | using System.ComponentModel;
4 | using System.Linq;
5 | using System.Runtime.CompilerServices;
6 | using System.Threading;
7 | using System.Windows.Media;
8 |
9 | namespace TimeLineControl
10 | {
11 | public class TimeLineEntry : MyObservableObject
12 | {
13 | //private fields...
14 | bool isDisplaying;
15 | Brush fill;
16 | Brush foreground;
17 | bool durationLocked;
18 | TimeSpan duration;
19 | TimeSpan start;
20 |
21 |
22 | public Brush Fill
23 | {
24 | get { return fill; }
25 | set
26 | {
27 | if (fill == value)
28 | return;
29 | fill = value;
30 | OnPropertyChanged();
31 | }
32 | }
33 | public Brush Foreground
34 | {
35 | get { return foreground; }
36 | set
37 | {
38 | if (foreground == value)
39 | return;
40 | foreground = value;
41 | OnPropertyChanged();
42 | }
43 | }
44 |
45 | public TimeSpan Start
46 | {
47 | get { return start; }
48 | set
49 | {
50 | if (start == value)
51 | return;
52 | start = value;
53 | OnPropertyChanged();
54 | OnPropertyChanged("End");
55 | }
56 | }
57 |
58 | public bool DurationLocked
59 | {
60 | get { return durationLocked; }
61 | set
62 | {
63 | if (durationLocked == value)
64 | return;
65 | durationLocked = value;
66 | OnPropertyChanged();
67 | OnPropertyChanged("DurationUnlocked");
68 | OnPropertyChanged("CanResize");
69 | }
70 | }
71 |
72 |
73 |
74 | public bool CanResize
75 | {
76 | get { return !DurationLocked && !IsInfinite; }
77 | }
78 |
79 |
80 | public bool IsInfinite
81 | {
82 | get
83 | {
84 | return Duration == Timeout.InfiniteTimeSpan;
85 | }
86 | }
87 |
88 |
89 |
90 | [EditorBrowsable(EditorBrowsableState.Never)]
91 | public bool DurationUnlocked
92 | {
93 | get { return !DurationLocked; }
94 | set
95 | {
96 | DurationLocked = !value;
97 | }
98 | }
99 |
100 | public TimeSpan Duration
101 | {
102 | get { return duration; }
103 | set
104 | {
105 | if (duration == value)
106 | return;
107 | duration = value;
108 | OnPropertyChanged();
109 | OnPropertyChanged("End");
110 | OnPropertyChanged("CanResize");
111 | OnPropertyChanged("IsInfinite");
112 | }
113 | }
114 |
115 | public TimeSpan End
116 | {
117 | get
118 | {
119 | return start + duration;
120 | }
121 | }
122 |
123 |
124 | string name;
125 | public string Name
126 | {
127 | get
128 | {
129 | return name;
130 | }
131 | set
132 | {
133 | if (name == value)
134 | {
135 | return;
136 | }
137 |
138 | name = value;
139 | OnPropertyChanged();
140 | }
141 | }
142 | object data;
143 | public object Data
144 | {
145 | get
146 | {
147 | return data;
148 | }
149 | set
150 | {
151 | if (data == value)
152 | {
153 | return;
154 | }
155 |
156 | data = value;
157 | OnPropertyChanged();
158 | }
159 | }
160 |
161 | int index;
162 | public int Index
163 | {
164 | get
165 | {
166 | return index;
167 | }
168 | set
169 | {
170 | if (index == value)
171 | return;
172 |
173 | index = value;
174 | OnPropertyChanged();
175 | }
176 | }
177 |
178 | bool isRenaming;
179 | string saveName;
180 |
181 | public bool IsRenaming
182 | {
183 | get
184 | {
185 | return isRenaming;
186 | }
187 | set
188 | {
189 | isRenaming = value;
190 | if (isRenaming)
191 | saveName = Name;
192 | OnPropertyChanged();
193 | OnPropertyChanged(nameof(IsDisplaying));
194 | }
195 | }
196 |
197 | public void CancelRename()
198 | {
199 | Name = saveName;
200 | OnPropertyChanged("Name");
201 | IsRenaming = false;
202 | }
203 |
204 |
205 | public bool IsDisplaying
206 | {
207 | get { return isDisplaying; }
208 | set
209 | {
210 | if (isDisplaying == value)
211 | return;
212 | isDisplaying = value;
213 | OnPropertyChanged();
214 | OnPropertyChanged(nameof(IsRenaming));
215 | }
216 | }
217 |
218 |
219 |
220 | public TimeLineEntry()
221 | {
222 | foreground = Brushes.White;
223 | fill = Brushes.DarkSlateBlue;
224 | IsDisplaying = true;
225 | }
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/TimeLineTestApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {9F5CDBA0-48FC-47AA-B2E2-27C7F6D90554}
8 | WinExe
9 | TimeLineTestApp
10 | TimeLineTestApp
11 | v4.7.2
12 | 512
13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 4
15 | true
16 | true
17 |
18 |
19 | AnyCPU
20 | true
21 | full
22 | false
23 | bin\Debug\
24 | DEBUG;TRACE
25 | prompt
26 | 4
27 |
28 |
29 | AnyCPU
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | 4.0
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | MSBuild:Compile
56 | Designer
57 |
58 |
59 | MSBuild:Compile
60 | Designer
61 |
62 |
63 | App.xaml
64 | Code
65 |
66 |
67 | MainWindow.xaml
68 | Code
69 |
70 |
71 |
72 |
73 | Code
74 |
75 |
76 | True
77 | True
78 | Resources.resx
79 |
80 |
81 | True
82 | Settings.settings
83 | True
84 |
85 |
86 | ResXFileCodeGenerator
87 | Resources.Designer.cs
88 |
89 |
90 | SettingsSingleFileGenerator
91 | Settings.Designer.cs
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | {df1e9df3-204a-4dbf-8b8d-60ca5c0a5bc3}
100 | TimeLineControl
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/TimeLineControl.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {DF1E9DF3-204A-4DBF-8B8D-60CA5C0A5BC3}
8 | Library
9 | TimeLineControl
10 | TimeLineControl
11 | v4.6.1
12 | 512
13 | {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 4
15 | true
16 | true
17 |
18 |
19 |
20 | AnyCPU
21 | true
22 | full
23 | false
24 | bin\Debug\
25 | DEBUG;TRACE
26 | prompt
27 | 4
28 |
29 |
30 | AnyCPU
31 | pdbonly
32 | true
33 | bin\Release\
34 | TRACE
35 | prompt
36 | 4
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 | 4.0
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | FrmSetDuration.xaml
62 |
63 |
64 |
65 |
66 | TimeLine.xaml
67 |
68 |
69 |
70 |
71 |
72 | Designer
73 | MSBuild:Compile
74 |
75 |
76 | Designer
77 | MSBuild:Compile
78 |
79 |
80 |
81 |
82 | Code
83 |
84 |
85 | True
86 | True
87 | Resources.resx
88 |
89 |
90 | True
91 | Settings.settings
92 | True
93 |
94 |
95 | ResXFileCodeGenerator
96 | Resources.Designer.cs
97 |
98 |
99 | SettingsSingleFileGenerator
100 | Settings.Designer.cs
101 |
102 |
103 |
104 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineTestApp/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | text/microsoft-resx
107 |
108 |
109 | 2.0
110 |
111 |
112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
113 |
114 |
115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/TimeLine.xaml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
--------------------------------------------------------------------------------
/TimeLineControl/TimeLineControl/TimeLine.xaml.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Collections.Specialized;
5 | using System.ComponentModel;
6 | using System.Linq;
7 | using System.Reflection;
8 | using System.Text;
9 | using System.Threading;
10 | using System.Threading.Tasks;
11 | using System.Windows;
12 | using System.Windows.Controls;
13 | using System.Windows.Data;
14 | using System.Windows.Documents;
15 | using System.Windows.Input;
16 | using System.Windows.Media;
17 | using System.Windows.Media.Imaging;
18 | using System.Windows.Navigation;
19 | using System.Windows.Shapes;
20 |
21 | namespace TimeLineControl
22 | {
23 | ///
24 | /// Interaction logic for TimeLine.xaml
25 | ///
26 | public partial class TimeLine : UserControl
27 | {
28 | public static readonly RoutedEvent TimeLineChangedEvent = EventManager.RegisterRoutedEvent("TimeLineChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(TimeLine));
29 |
30 | public event RoutedEventHandler TimeLineChanged
31 | {
32 | add { AddHandler(TimeLineChangedEvent, value); }
33 | remove { RemoveHandler(TimeLineChangedEvent, value); }
34 | }
35 |
36 | protected virtual void OnTimeLineChanged()
37 | {
38 | RoutedEventArgs eventArgs = new RoutedEventArgs(TimeLineChangedEvent);
39 | RaiseEvent(eventArgs);
40 | }
41 |
42 | public static readonly DependencyProperty MinEntryDurationProperty = DependencyProperty.Register("MinEntryDuration", typeof(double), typeof(TimeLine), new FrameworkPropertyMetadata(0.1, new PropertyChangedCallback(OnMinEntryDurationChanged), new CoerceValueCallback(OnCoerceMinEntryDuration)));
43 |
44 | TimeSpan mouseDownTime;
45 |
46 | public event SelectionChangedEventHandler SelectionChanged;
47 |
48 | public TimeLine()
49 | {
50 | InitializeComponent();
51 | MinEntryDuration = 0.1;
52 | lbTimeEntries.SelectionChanged += LbTimeEntries_SelectionChanged;
53 | }
54 |
55 | private void LbTimeEntries_SelectionChanged(object sender, SelectionChangedEventArgs e)
56 | {
57 | if (e.RemovedItems?.Count > 0 && e.RemovedItems[0] is TimeLineEntry timeLineEntry && timeLineEntry.IsRenaming)
58 | timeLineEntry.IsRenaming = false;
59 | SelectionChanged?.Invoke(this, e);
60 | }
61 |
62 | public static readonly DependencyProperty TotalDurationProperty = DependencyProperty.Register("TotalDuration", typeof(TimeSpan), typeof(TimeLine), new FrameworkPropertyMetadata(TimeSpan.Zero, new PropertyChangedCallback(OnTotalDurationChanged), new CoerceValueCallback(OnCoerceTotalDuration)));
63 | double mouseDownX;
64 | TimeLineEntry entry;
65 |
66 | private static object OnCoerceMinEntryDuration(DependencyObject o, object value)
67 | {
68 | TimeLine timeLine = o as TimeLine;
69 | if (timeLine != null)
70 | return timeLine.OnCoerceMinEntryDuration((double)value);
71 | else
72 | return value;
73 | }
74 |
75 | private static void OnMinEntryDurationChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
76 | {
77 | TimeLine timeLine = o as TimeLine;
78 | if (timeLine != null)
79 | timeLine.OnMinEntryDurationChanged((double)e.OldValue, (double)e.NewValue);
80 | }
81 |
82 | protected virtual double OnCoerceMinEntryDuration(double value)
83 | {
84 | // TODO: Keep the proposed value within the desired range.
85 | return value;
86 | }
87 |
88 | protected virtual void OnMinEntryDurationChanged(double oldValue, double newValue)
89 | {
90 | // TODO: Add your property changed side-effects. Descendants can override as well.
91 | }
92 | private static object OnCoerceEnd(DependencyObject o, object value)
93 | {
94 | TimeLine timeLine = o as TimeLine;
95 | if (timeLine != null)
96 | return timeLine.OnCoerceEnd((TimeSpan)value);
97 | else
98 | return value;
99 | }
100 |
101 | private static void OnEndChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
102 | {
103 | TimeLine timeLine = o as TimeLine;
104 | if (timeLine != null)
105 | timeLine.OnEndChanged((TimeSpan)e.OldValue, (TimeSpan)e.NewValue);
106 | }
107 |
108 | protected virtual TimeSpan OnCoerceEnd(TimeSpan value)
109 | {
110 | // TODO: Keep the proposed value within the desired range.
111 | return value;
112 | }
113 |
114 | protected virtual void OnEndChanged(TimeSpan oldValue, TimeSpan newValue)
115 | {
116 | // TODO: Add your property changed side-effects. Descendants can override as well.
117 | }
118 | private static object OnCoerceTotalDuration(DependencyObject o, object value)
119 | {
120 | TimeLine timeLine = o as TimeLine;
121 | if (timeLine != null)
122 | return timeLine.OnCoerceTotalDuration((TimeSpan)value);
123 | else
124 | return value;
125 | }
126 |
127 | private static void OnTotalDurationChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
128 | {
129 | TimeLine timeLine = o as TimeLine;
130 | if (timeLine != null)
131 | timeLine.OnTotalDurationChanged((TimeSpan)e.OldValue, (TimeSpan)e.NewValue);
132 | }
133 |
134 | protected virtual TimeSpan OnCoerceTotalDuration(TimeSpan value)
135 | {
136 | // TODO: Keep the proposed value within the desired range.
137 | return value;
138 | }
139 |
140 | protected virtual void OnTotalDurationChanged(TimeSpan oldValue, TimeSpan newValue)
141 | {
142 | // TODO: Add your property changed side-effects. Descendants can override as well.
143 | OnTimeLineChanged();
144 | }
145 |
146 | public double MinEntryDuration
147 | {
148 | // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
149 | get
150 | {
151 | return (double)GetValue(MinEntryDurationProperty);
152 | }
153 | set
154 | {
155 | SetValue(MinEntryDurationProperty, value);
156 | }
157 | }
158 | public TimeSpan End
159 | {
160 | // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
161 | get
162 | {
163 | return (TimeSpan)GetValue(EndProperty);
164 | }
165 | set
166 | {
167 | SetValue(EndProperty, value);
168 | }
169 | }
170 | public TimeSpan TotalDuration
171 | {
172 | // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
173 | get
174 | {
175 | return (TimeSpan)GetValue(TotalDurationProperty);
176 | }
177 | set
178 | {
179 | SetValue(TotalDurationProperty, value);
180 | }
181 | }
182 |
183 | public static readonly DependencyProperty EndProperty = DependencyProperty.Register("End", typeof(TimeSpan), typeof(TimeLine), new FrameworkPropertyMetadata(TimeSpan.Zero, new PropertyChangedCallback(OnEndChanged), new CoerceValueCallback(OnCoerceEnd)));
184 |
185 |
186 | public IEnumerable ItemsSource
187 | {
188 | get => lbTimeEntries.ItemsSource;
189 |
190 | set
191 | {
192 | if (lbTimeEntries.ItemsSource is INotifyCollectionChanged iNotifyPropertyChanged)
193 | {
194 | iNotifyPropertyChanged.CollectionChanged -= ItemsSource_CollectionChanged;
195 | foreach (TimeLineEntry timeLineEntry in lbTimeEntries.ItemsSource)
196 | {
197 | timeLineEntry.PropertyChanged -= TimeLineEntry_PropertyChanged;
198 | }
199 | }
200 |
201 | lbTimeEntries.ItemsSource = value;
202 |
203 | if (lbTimeEntries.ItemsSource is INotifyCollectionChanged iNotifyPropertyChanged2)
204 | {
205 | iNotifyPropertyChanged2.CollectionChanged += ItemsSource_CollectionChanged;
206 | foreach (TimeLineEntry timeLineEntry in lbTimeEntries.ItemsSource)
207 | {
208 | timeLineEntry.PropertyChanged += TimeLineEntry_PropertyChanged;
209 | }
210 | }
211 | }
212 | }
213 |
214 | public object SelectedItem { get => lbTimeEntries.SelectedItem; set => lbTimeEntries.SelectedItem = value; }
215 |
216 |
217 |
218 | private void TimeLineEntry_PropertyChanged(object sender, PropertyChangedEventArgs e)
219 | {
220 | UpdateTotalDuration();
221 | }
222 |
223 | private void ItemsSource_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
224 | {
225 | UpdateTotalDuration();
226 | }
227 |
228 | Dictionary adorners = new Dictionary();
229 | private void Rectangle_PreviewMouseDown(object sender, MouseButtonEventArgs e)
230 | {
231 | HandleMouseDown(sender, e, EventMoveType.Start);
232 | }
233 |
234 | public static T FindParentOfType(DependencyObject child) where T : DependencyObject
235 | {
236 | if (child == null)
237 | return null;
238 | DependencyObject parentDepObj = child;
239 | do
240 | {
241 | parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
242 | T parent = parentDepObj as T;
243 | if (parent != null) return parent;
244 | }
245 | while (parentDepObj != null);
246 | return null;
247 | }
248 |
249 | // il Rocks!
250 |
251 | private void HandleMouseDown(object sender, MouseButtonEventArgs e, EventMoveType eventMoveType)
252 | {
253 | e.Handled = false;
254 |
255 | Rectangle rectangle = sender as Rectangle;
256 | if (rectangle == null)
257 | return;
258 | int index = (int)rectangle.Tag;
259 | if (index != lbTimeEntries.SelectedIndex)
260 | lbTimeEntries.SelectedIndex = index;
261 | mouseDownX = e.GetPosition(lbTimeEntries).X;
262 | entry = lbTimeEntries.SelectedItem as TimeLineEntry;
263 | if (entry == null)
264 | return;
265 |
266 | AdornerLayer adornerLayer = GetAdornerLayer(rectangle);
267 |
268 | EventTimeAdorner adorner = new EventTimeAdorner(rectangle);
269 |
270 | RemoveRectangleAdorner(rectangle, adornerLayer); // Solves issue that happens only when reaching breakpoint after mouse down.
271 |
272 | adorners.Add(rectangle, adorner);
273 | adornerLayer.Add(adorner);
274 |
275 | if (eventMoveType == EventMoveType.Start)
276 | {
277 | mouseDownTime = entry.Start;
278 | adorner.EventTime = entry.Start;
279 | }
280 | else
281 | {
282 | mouseDownTime = entry.End;
283 | adorner.EventTime = entry.Duration;
284 | }
285 |
286 | rectangle.CaptureMouse();
287 |
288 | e.Handled = true;
289 | }
290 |
291 | private static AdornerLayer GetAdornerLayer(FrameworkElement frameworkElement)
292 | {
293 | AdornerLayer adornerLayer = AdornerLayer.GetAdornerLayer(frameworkElement);
294 | AdornerLayer parentAdornerLayer = adornerLayer;
295 | AdornerDecorator lastParentAdornerDecorator = null;
296 | do
297 | {
298 | AdornerDecorator parentAdornerDecorator = FindParentOfType(parentAdornerLayer);
299 | if (lastParentAdornerDecorator == parentAdornerDecorator)
300 | break;
301 | lastParentAdornerDecorator = parentAdornerDecorator;
302 | if (parentAdornerDecorator == null)
303 | parentAdornerLayer = null;
304 | else
305 | parentAdornerLayer = parentAdornerDecorator.AdornerLayer;
306 |
307 | if (parentAdornerLayer != null)
308 | adornerLayer = parentAdornerLayer;
309 | } while (parentAdornerLayer != null);
310 | return adornerLayer;
311 | }
312 |
313 | private void RemoveRectangleAdorner(Rectangle rectangle, AdornerLayer adornerLayer)
314 | {
315 | if (!adorners.ContainsKey(rectangle))
316 | return;
317 |
318 | adornerLayer.Remove(adorners[rectangle]);
319 | adorners.Remove(rectangle);
320 | }
321 |
322 | public enum EventMoveType
323 | {
324 | Start,
325 | Duration
326 | }
327 |
328 | private void Rectangle_PreviewMouseMove(object sender, MouseEventArgs e)
329 | {
330 | HandleMouseMove(sender, e, EventMoveType.Start);
331 | }
332 |
333 | private void HandleMouseMove(object sender, MouseEventArgs e, EventMoveType eventMoveType)
334 | {
335 | e.Handled = false;
336 | if (entry == null)
337 | return;
338 |
339 | Rectangle rectangle = sender as Rectangle;
340 | if (rectangle == null)
341 | return;
342 |
343 | double thisX = e.GetPosition(lbTimeEntries).X;
344 | double distanceMoved = thisX - mouseDownX;
345 | double pixelsPerSecond = ActualWidth * TimeLineEntryConverter.HackPercentWidth / TotalDuration.TotalSeconds;
346 |
347 | TimeSpan timeMoved = TimeSpan.FromSeconds(distanceMoved / pixelsPerSecond);
348 |
349 | EventTimeAdorner eventTimeAdorner = null;
350 | if (adorners.ContainsKey(rectangle))
351 | eventTimeAdorner = adorners[rectangle];
352 |
353 | TimeSpan newTime = mouseDownTime + timeMoved;
354 | if (eventMoveType == EventMoveType.Start)
355 | {
356 | if (newTime.TotalSeconds < 0)
357 | entry.Start = TimeSpan.FromSeconds(0);
358 | else
359 | entry.Start = newTime;
360 | if (eventTimeAdorner != null)
361 | eventTimeAdorner.EventTime = entry.Start;
362 | }
363 | else
364 | {
365 | TimeSpan newDuration = newTime - entry.Start;
366 | TimeSpan minDuration = TimeSpan.FromSeconds(MinEntryDuration);
367 | if (newDuration < minDuration)
368 | newDuration = minDuration;
369 | entry.Duration = newDuration;
370 | if (eventTimeAdorner != null)
371 | eventTimeAdorner.EventTime = entry.Duration;
372 | }
373 |
374 | e.Handled = true;
375 | }
376 |
377 | void UpdateTotalDuration()
378 | {
379 | double maxSeconds = 0;
380 | foreach (TimeLineEntry timeLineEntry in ItemsSource)
381 | {
382 | if (timeLineEntry.Duration == System.Threading.Timeout.InfiniteTimeSpan)
383 | continue;
384 | maxSeconds = Math.Max(maxSeconds, timeLineEntry.End.TotalSeconds);
385 | }
386 | /*
387 | var maxValue = (from timelineItem in ItemSource select timelineItem.duration).Max();
388 | wil_bennett: .. alternatively: maxValue = ((IEnumerable)ItemSource).Max(t => t.duration)... [untested] */
389 |
390 | End = TimeSpan.FromSeconds(maxSeconds);
391 | }
392 | private void Rectangle_PreviewMouseUp(object sender, MouseButtonEventArgs e)
393 | {
394 | entry = null;
395 | Rectangle rectangle = sender as Rectangle;
396 | if (rectangle == null)
397 | return;
398 |
399 | if (adorners.ContainsKey(rectangle))
400 | {
401 | AdornerLayer myAdornerLayer = GetAdornerLayer(rectangle);
402 | myAdornerLayer.Remove(adorners[rectangle]);
403 | adorners.Remove(rectangle);
404 | }
405 |
406 | rectangle.ReleaseMouseCapture();
407 | UpdateTotalDuration();
408 | OnTimeLineChanged();
409 | }
410 |
411 | private void LbTimeEntries_MouseWheel(object sender, MouseWheelEventArgs e)
412 | {
413 | // TODO: Add Mouse Wheel support.
414 | }
415 |
416 | private void ResizeRectangle_PreviewMouseMove(object sender, MouseEventArgs e)
417 | {
418 | HandleMouseMove(sender, e, EventMoveType.Duration);
419 | }
420 |
421 | private void ResizeRectangle_PreviewMouseDown(object sender, MouseButtonEventArgs e)
422 | {
423 | HandleMouseDown(sender, e, EventMoveType.Duration);
424 | }
425 |
426 | private void SetDuration_Click(object sender, RoutedEventArgs e)
427 | {
428 | if (!(sender is MenuItem menuItem && menuItem.DataContext is TimeLineEntry timeLineEntry))
429 | return;
430 |
431 | FrmSetDuration frmSetDuration = new FrmSetDuration();
432 | Point screenPos = menuItem.PointToScreen(new Point(0, 0));
433 | if (timeLineEntry.Duration == Timeout.InfiniteTimeSpan)
434 | frmSetDuration.Duration = double.PositiveInfinity;
435 | else
436 | frmSetDuration.Duration = timeLineEntry.Duration.TotalSeconds;
437 | frmSetDuration.Left = screenPos.X;
438 | frmSetDuration.Top = screenPos.Y;
439 | if (frmSetDuration.ShowDialog() == true)
440 | {
441 | if (double.IsInfinity(frmSetDuration.Duration))
442 | timeLineEntry.Duration = Timeout.InfiniteTimeSpan;
443 | else
444 | timeLineEntry.Duration = TimeSpan.FromSeconds(Math.Max(MinEntryDuration, frmSetDuration.Duration));
445 | OnTimeLineChanged();
446 | }
447 | }
448 |
449 | public static T GetChildOfType(DependencyObject depObj, int startingIndex = 0) where T : DependencyObject
450 | {
451 | if (depObj == null)
452 | return null;
453 |
454 | for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
455 | {
456 | var child = VisualTreeHelper.GetChild(depObj, i);
457 |
458 | var result = (child as T) ?? GetChildOfType(child);
459 | if (result != null) return result;
460 | }
461 | return null;
462 | }
463 |
464 | private T FindVisualChild(DependencyObject obj) where T : DependencyObject
465 | {
466 | for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
467 | {
468 | DependencyObject child = VisualTreeHelper.GetChild(obj, i);
469 | if (child != null && child is T)
470 | return (T)child;
471 | else
472 | {
473 | T childOfChild = FindVisualChild(child);
474 | if (childOfChild != null)
475 | return childOfChild;
476 | }
477 | }
478 | return null;
479 | }
480 |
481 | T GetDataTemplateControl(TimeLineEntry timeLineEntry, string controlName) where T : DependencyObject
482 | {
483 | ListBoxItem listBoxItem = (ListBoxItem)(lbTimeEntries.ItemContainerGenerator.ContainerFromItem(timeLineEntry));
484 |
485 | ContentPresenter contentPresenter = FindVisualChild(listBoxItem);
486 |
487 | DataTemplate myDataTemplate = contentPresenter.ContentTemplate;
488 | T result = (T)myDataTemplate.FindName(controlName, contentPresenter);
489 | return result;
490 | }
491 |
492 | private void Rename_Click(object sender, RoutedEventArgs e)
493 | {
494 | // TODO: inline the rename so there's no modal popup UI.
495 | if (SelectedItem is TimeLineEntry timeLineEntry)
496 | {
497 | TextBox textBox = GetDataTemplateControl(timeLineEntry, "TextBox");
498 | if (textBox != null)
499 | {
500 | textBox.SelectAll();
501 | textBox.Focus();
502 | }
503 |
504 | timeLineEntry.IsRenaming = true;
505 | }
506 | }
507 | public void DeleteSelected()
508 | {
509 | if (ItemsSource == null)
510 | return;
511 |
512 | // Uses reflection to allow users of this timeline to set the ItemsSource to an ObservableCollection<*TimeLineEntryDescendant*>...
513 |
514 | MethodInfo method = ItemsSource.GetType().GetMethod("Remove", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
515 | if (method == null)
516 | return;
517 |
518 | method.Invoke(ItemsSource, new object[] { SelectedItem });
519 |
520 | // We need to re-index the elements in the timeline...
521 | IEnumerator enumerator = ItemsSource.GetEnumerator();
522 | if (enumerator != null)
523 | {
524 | int i = 0;
525 | while (enumerator.MoveNext())
526 | {
527 | if (enumerator.Current is TimeLineEntry timeLineEntry)
528 | timeLineEntry.Index = i;
529 | i++;
530 | }
531 | }
532 | OnTimeLineChanged();
533 | //if (ItemsSource is System.Collections.ObjectModel.ObservableCollection entries)
534 | //{
535 | // entries.Remove(SelectedItem as TimeLineEntry);
536 | // for (int i = 0; i < entries.Count; i++)
537 | // {
538 | // entries[i].Index = i;
539 | // }
540 | //
541 | //}
542 | }
543 |
544 | private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
545 | {
546 | OnTimeLineChanged();
547 | }
548 |
549 | private void TextBox_KeyDown(object sender, KeyEventArgs e)
550 | {
551 | if (!(SelectedItem is TimeLineEntry timeLineEntry))
552 | return;
553 | if (e.Key == Key.Enter)
554 | timeLineEntry.IsRenaming = false;
555 | if (e.Key == Key.Escape)
556 | {
557 | timeLineEntry.CancelRename();
558 | OnTimeLineChanged();
559 | }
560 | }
561 |
562 | private void TextBox_LostFocus(object sender, RoutedEventArgs e)
563 | {
564 | if (!(SelectedItem is TimeLineEntry timeLineEntry))
565 | return;
566 | timeLineEntry.IsRenaming = false;
567 | }
568 | }
569 | }
570 |
--------------------------------------------------------------------------------