diff --git a/HandyControl/Controls/TimeBar/SpeTextBlock.cs b/HandyControl/Controls/TimeBar/SpeTextBlock.cs
new file mode 100644
index 00000000..d91ab67b
--- /dev/null
+++ b/HandyControl/Controls/TimeBar/SpeTextBlock.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Windows.Controls;
+
+// ReSharper disable once CheckNamespace
+namespace HandyControl.Controls
+{
+ ///
+ /// 作为刻度使用的文字块
+ ///
+ internal class SpeTextBlock : TextBlock
+ {
+ public double X { get; set; }
+
+ public SpeTextBlock() => Width = 60;
+
+ public SpeTextBlock(double x) : this()
+ {
+ X = x;
+ Canvas.SetLeft(this, X);
+ }
+
+ ///
+ /// 时间
+ ///
+ private DateTime _time;
+
+ ///
+ /// 时间
+ ///
+ public DateTime Time
+ {
+ get => _time;
+ set
+ {
+ _time = value;
+ Text = $"{value.ToString(TimeFormat)}\r\n|";
+ }
+ }
+
+ ///
+ /// 时间格式
+ ///
+ public string TimeFormat { get; set; } = "HH:mm";
+
+ ///
+ /// 横向移动
+ ///
+ ///
+ public void MoveX(double offsetX) => Canvas.SetLeft(this, X + offsetX);
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Controls/TimeBar/TimeBar.xaml b/HandyControl/Controls/TimeBar/TimeBar.xaml
new file mode 100644
index 00000000..051cbb01
--- /dev/null
+++ b/HandyControl/Controls/TimeBar/TimeBar.xaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HandyControl/Controls/TimeBar/TimeBar.xaml.cs b/HandyControl/Controls/TimeBar/TimeBar.xaml.cs
new file mode 100644
index 00000000..af1523de
--- /dev/null
+++ b/HandyControl/Controls/TimeBar/TimeBar.xaml.cs
@@ -0,0 +1,390 @@
+using System;
+using System.Collections.Generic;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
+using HandyControl.Data;
+
+// ReSharper disable once CheckNamespace
+namespace HandyControl.Controls
+{
+ ///
+ /// TimeBar.xaml 的交互逻辑
+ ///
+ public partial class TimeBar
+ {
+ ///
+ /// 是否显示刻度字符串
+ ///
+ public static readonly DependencyProperty ShowSpeStrProperty = DependencyProperty.Register(
+ "ShowSpeStr", typeof(bool), typeof(TimeBar), new PropertyMetadata(default(bool)));
+
+ ///
+ /// 刻度字符串
+ ///
+ private static readonly DependencyProperty SpeStrProperty = DependencyProperty.Register(
+ "SpeStr", typeof(string), typeof(TimeBar), new PropertyMetadata("间隔1小时"));
+
+ ///
+ /// 选中时间
+ ///
+ private static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register(
+ "SelectedTime", typeof(DateTime), typeof(TimeBar));
+
+ ///
+ /// 时间改变事件
+ ///
+ public static readonly RoutedEvent TimeChangedEvent =
+ EventManager.RegisterRoutedEvent("ButtonCloseClick", RoutingStrategy.Bubble,
+ typeof(EventHandler>), typeof(TimeBar));
+
+ ///
+ /// 刻度集合
+ ///
+ private readonly List _speBlockList = new List();
+
+ ///
+ /// 初始化时时间
+ ///
+ private readonly DateTime _starTime;
+
+ ///
+ /// 时间段集合
+ ///
+ private readonly List _timeSpeList = new List
+ {
+ 7200000,
+ 3600000,
+ 1800000,
+ 600000,
+ 300000,
+ 60000,
+ 30000
+ };
+
+ ///
+ /// 顶部border是否被按下
+ ///
+ private bool _borderTopIsMouseLeftButtonDown;
+
+ ///
+ /// 控件是否处于拖动中
+ ///
+ private bool _isDragging;
+
+ ///
+ /// 刻度单项宽度
+ ///
+ private double _itemWidth;
+
+ ///
+ /// 鼠标按下拖动时选中的时间
+ ///
+ private DateTime _mouseDownTime;
+
+ ///
+ /// 显示的刻度数目
+ ///
+ private int _speCount = 13;
+
+ ///
+ /// 刻度区间编号
+ ///
+ private int _speIndex = 1;
+
+ ///
+ /// 刻度单次偏移
+ ///
+ private double _tempOffsetX;
+
+ ///
+ /// 刻度总偏移
+ ///
+ private double _totalOffsetX;
+
+ public TimeBar()
+ {
+ InitializeComponent();
+
+ _starTime = DateTime.Now;
+ SelectedTime = new DateTime(_starTime.Year, _starTime.Month, _starTime.Day, 0, 0, 0);
+ _starTime = SelectedTime;
+ }
+
+ ///
+ /// 是否显示刻度字符串
+ ///
+ public bool ShowSpeStr
+ {
+ get => (bool)GetValue(ShowSpeStrProperty);
+ set => SetValue(ShowSpeStrProperty, value);
+ }
+
+ ///
+ /// 刻度字符串
+ ///
+ private string SpeStr
+ {
+ get => (string)GetValue(SpeStrProperty);
+ set => SetValue(SpeStrProperty, value);
+ }
+
+ ///
+ /// 选中时间
+ ///
+ private DateTime SelectedTime
+ {
+ get => (DateTime)GetValue(SelectedTimeProperty);
+ set => SetValue(SelectedTimeProperty, value);
+ }
+
+ ///
+ /// 刻度区间编号
+ ///
+ public int SpeIndex
+ {
+ get => _speIndex;
+ private set
+ {
+ if (_speIndex == value) return;
+
+ if (value < 0)
+ {
+ SpeStr = "间隔2小时";
+ _speIndex = 0;
+ return;
+ }
+ if (value > 6)
+ {
+ SpeStr = "间隔30秒";
+ _speIndex = 6;
+ return;
+ }
+ SetSpeTimeFormat("HH:mm");
+ switch (value)
+ {
+ case 0:
+ SpeStr = "间隔2小时";
+ break;
+ case 1:
+ SpeStr = "间隔1小时";
+ break;
+ case 2:
+ SpeStr = "间隔30分钟";
+ break;
+ case 3:
+ SpeStr = "间隔10分钟";
+ break;
+ case 4:
+ SpeStr = "间隔5分钟";
+ break;
+ case 5:
+ SpeStr = "间隔1分钟";
+ break;
+ case 6:
+ SetSpeTimeFormat("HH:mm:ss");
+ SpeStr = "间隔30秒";
+ break;
+ }
+ _speIndex = value;
+ }
+ }
+
+ ///
+ /// 时间改变事件
+ ///
+ public event EventHandler> TimeChanged
+ {
+ add => AddHandler(TimeChangedEvent, value);
+ remove => RemoveHandler(TimeChangedEvent, value);
+ }
+
+ ///
+ /// 设置刻度时间格式
+ ///
+ ///
+ private void SetSpeTimeFormat(string format)
+ {
+ foreach (var item in _speBlockList)
+ item.TimeFormat = format;
+ }
+
+ ///
+ /// 设置显示的时间
+ ///
+ ///
+ public void SetShowTime(DateTime time)
+ {
+ if (_isDragging || _borderTopIsMouseLeftButtonDown) return;
+
+ SelectedTime = time;
+ _totalOffsetX = (_starTime - SelectedTime).TotalMilliseconds / _timeSpeList[SpeIndex] * _itemWidth;
+ Update();
+ TimeBar_OnMouseMove(null, null);
+ }
+
+ ///
+ /// 更新
+ ///
+ private void Update()
+ {
+ var rest = (_totalOffsetX + _tempOffsetX) % _itemWidth;
+ for (var i = 0; i < _speCount; i++)
+ {
+ var item = _speBlockList[i];
+ item.MoveX(rest + (_itemWidth - item.Width) / 2);
+ }
+ var sub = rest <= 0 ? _speCount / 2 : _speCount / 2 - 1;
+
+ for (var i = 0; i < _speCount; i++)
+ _speBlockList[i].Time = TimeConvert(SelectedTime).AddMilliseconds((i - sub) * _timeSpeList[_speIndex]);
+ }
+
+ ///
+ /// 时间转换
+ ///
+ ///
+ ///
+ private DateTime TimeConvert(DateTime time)
+ {
+ switch (_speIndex)
+ {
+ case 0:
+ return new DateTime(time.Year, time.Month, time.Day, time.Hour / 2 * 2, 0, 0);
+ case 1:
+ return new DateTime(time.Year, time.Month, time.Day, time.Hour, 0, 0);
+ case 2:
+ return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute / 30 * 30, 0);
+ case 3:
+ return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute / 10 * 10, 0);
+ case 4:
+ return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute / 5 * 5, 0);
+ case 5:
+ return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, 0);
+ case 6:
+ return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second / 30 * 30);
+ default:
+ return time;
+ }
+ }
+
+ ///
+ /// 鼠标滚轮滚动时改变刻度区间
+ ///
+ ///
+ protected override void OnMouseWheel(MouseWheelEventArgs e)
+ {
+ base.OnMouseWheel(e);
+ if (Mouse.LeftButton == MouseButtonState.Pressed) return;
+ SpeIndex += e.Delta > 0 ? 1 : -1;
+ _totalOffsetX = (_starTime - SelectedTime).TotalMilliseconds / _timeSpeList[SpeIndex] * _itemWidth;
+ Update();
+ TimeBar_OnMouseMove(null, null);
+ e.Handled = true;
+ }
+
+ private void MouseDragElementBehavior_OnDragging(object sender, MouseEventArgs e)
+ {
+ _isDragging = true;
+ _tempOffsetX = BorderTop.RenderTransform.Value.OffsetX;
+
+ SelectedTime = _mouseDownTime -
+ TimeSpan.FromMilliseconds(_tempOffsetX / _itemWidth * _timeSpeList[_speIndex]);
+ Update();
+ _borderTopIsMouseLeftButtonDown = false;
+ }
+
+ private void MouseDragElementBehavior_OnDragFinished(object sender, MouseEventArgs e)
+ {
+ _tempOffsetX = 0;
+ _totalOffsetX = (_totalOffsetX + BorderTop.RenderTransform.Value.OffsetX) % ActualWidth;
+ BorderTop.RenderTransform = new TranslateTransform();
+ RaiseEvent(new FunctionEventArgs(TimeChangedEvent, this)
+ {
+ Info = SelectedTime
+ });
+ _isDragging = false;
+ }
+
+ private void DragElementBehavior_OnDragBegun(object sender, MouseEventArgs e)
+ {
+ _mouseDownTime = SelectedTime;
+ }
+
+ private void TimeBar_OnSizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ _speBlockList.Clear();
+ CanvasSpe.Children.Clear();
+
+ _speCount = (int)(ActualWidth / 800 * 9) | 1;
+
+ var itemWidthOld = _itemWidth;
+ _itemWidth = ActualWidth / _speCount;
+ _totalOffsetX = _itemWidth / itemWidthOld * _totalOffsetX % ActualWidth;
+ if (double.IsNaN(_totalOffsetX))
+ _totalOffsetX = 0;
+
+ var rest = (_totalOffsetX + _tempOffsetX) % _itemWidth;
+ var sub = rest <= 0 || double.IsNaN(rest) ? _speCount / 2 : _speCount / 2 - 1;
+ for (var i = 0; i < _speCount; i++)
+ {
+ var block = new SpeTextBlock
+ {
+ Time = TimeConvert(SelectedTime).AddMilliseconds((i - sub) * _timeSpeList[_speIndex]),
+ TextAlignment = TextAlignment.Center,
+ TimeFormat = "HH:mm"
+ };
+ _speBlockList.Add(block);
+ CanvasSpe.Children.Add(block);
+ }
+ if (_speIndex == 6)
+ SetSpeTimeFormat("HH:mm:ss");
+ ShowSpeStr = ActualWidth > 320;
+ for (var i = 0; i < _speCount; i++)
+ {
+ var item = _speBlockList[i];
+ item.X = _itemWidth * i;
+ item.MoveX((_itemWidth - item.Width) / 2);
+ }
+
+ Update();
+ TimeBar_OnMouseMove(null, null);
+ }
+
+ private void TimeBar_OnMouseMove(object sender, MouseEventArgs e)
+ {
+ var p = Mouse.GetPosition(this);
+ var mlliseconds = (p.X - ActualWidth / 2) / _itemWidth * _timeSpeList[_speIndex];
+ if (double.IsInfinity(mlliseconds)) return;
+ TextBlockSelected.Text = mlliseconds < 0
+ ? (SelectedTime - TimeSpan.FromMilliseconds(-mlliseconds)).ToString("yyyy-MM-dd HH:mm:ss")
+ : (SelectedTime + TimeSpan.FromMilliseconds(mlliseconds)).ToString("yyyy-MM-dd HH:mm:ss");
+ TextBlockSelected.Margin = new Thickness(p.X - TextBlockSelected.ActualWidth / 2, 2, 0, 0);
+ }
+
+ private void BorderTop_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ _borderTopIsMouseLeftButtonDown = true;
+ }
+
+ private void BorderTop_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+ {
+ if (_borderTopIsMouseLeftButtonDown)
+ {
+ _borderTopIsMouseLeftButtonDown = false;
+
+ var p = Mouse.GetPosition(this);
+ _tempOffsetX = ActualWidth / 2 - p.X;
+ SelectedTime -= TimeSpan.FromMilliseconds(_tempOffsetX / _itemWidth * _timeSpeList[_speIndex]);
+ Update();
+ _totalOffsetX = (_totalOffsetX + _tempOffsetX) % ActualWidth;
+ _tempOffsetX = 0;
+ TimeBar_OnMouseMove(null, null);
+ RaiseEvent(new FunctionEventArgs(TimeChangedEvent, this)
+ {
+ Info = SelectedTime
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj
index c4ad4433..392f2094 100644
--- a/HandyControl/HandyControl.csproj
+++ b/HandyControl/HandyControl.csproj
@@ -53,6 +53,10 @@
Pagination.xaml
+
+
+ TimeBar.xaml
+
@@ -89,6 +93,7 @@
Code
+
@@ -103,6 +108,7 @@
+
@@ -130,6 +136,10 @@
Designer
MSBuild:Compile
+
+ Designer
+ MSBuild:Compile
+
Designer
MSBuild:Compile
diff --git a/HandyControl/Tools/ArithmeticHelper.cs b/HandyControl/Tools/ArithmeticHelper.cs
new file mode 100644
index 00000000..f7856388
--- /dev/null
+++ b/HandyControl/Tools/ArithmeticHelper.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+
+namespace HandyControl.Tools
+{
+ public class ArithmeticHelper
+ {
+ ///
+ /// 获取布局范围框
+ ///
+ ///
+ ///
+ internal static Rect GetLayoutRect(FrameworkElement element)
+ {
+ var num1 = element.ActualWidth;
+ var num2 = element.ActualHeight;
+ if (element is Image || element is MediaElement)
+ if (element.Parent is Canvas)
+ {
+ num1 = double.IsNaN(element.Width) ? num1 : element.Width;
+ num2 = double.IsNaN(element.Height) ? num2 : element.Height;
+ }
+ else
+ {
+ num1 = element.RenderSize.Width;
+ num2 = element.RenderSize.Height;
+ }
+ var width = element.Visibility == Visibility.Collapsed ? 0.0 : num1;
+ var height = element.Visibility == Visibility.Collapsed ? 0.0 : num2;
+ var margin = element.Margin;
+ var layoutSlot = LayoutInformation.GetLayoutSlot(element);
+ var x = 0.0;
+ var y = 0.0;
+ switch (element.HorizontalAlignment)
+ {
+ case HorizontalAlignment.Left:
+ x = layoutSlot.Left + margin.Left;
+ break;
+ case HorizontalAlignment.Center:
+ x = (layoutSlot.Left + margin.Left + layoutSlot.Right - margin.Right) / 2.0 - width / 2.0;
+ break;
+ case HorizontalAlignment.Right:
+ x = layoutSlot.Right - margin.Right - width;
+ break;
+ case HorizontalAlignment.Stretch:
+ x = Math.Max(layoutSlot.Left + margin.Left,
+ (layoutSlot.Left + margin.Left + layoutSlot.Right - margin.Right) / 2.0 - width / 2.0);
+ break;
+ }
+ switch (element.VerticalAlignment)
+ {
+ case VerticalAlignment.Top:
+ y = layoutSlot.Top + margin.Top;
+ break;
+ case VerticalAlignment.Center:
+ y = (layoutSlot.Top + margin.Top + layoutSlot.Bottom - margin.Bottom) / 2.0 - height / 2.0;
+ break;
+ case VerticalAlignment.Bottom:
+ y = layoutSlot.Bottom - margin.Bottom - height;
+ break;
+ case VerticalAlignment.Stretch:
+ y = Math.Max(layoutSlot.Top + margin.Top,
+ (layoutSlot.Top + margin.Top + layoutSlot.Bottom - margin.Bottom) / 2.0 - height / 2.0);
+ break;
+ }
+ return new Rect(x, y, width, height);
+ }
+ }
+}
\ No newline at end of file
diff --git a/HandyControl/Tools/MouseDragElementBehaviorEx.cs b/HandyControl/Tools/MouseDragElementBehaviorEx.cs
new file mode 100644
index 00000000..b0757a00
--- /dev/null
+++ b/HandyControl/Tools/MouseDragElementBehaviorEx.cs
@@ -0,0 +1,368 @@
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media;
+using HandyControl.Interactivity;
+
+namespace HandyControl.Tools
+{
+ ///
+ /// 鼠标拖动行为,该类是反编译微软的System.Windows.Interactivity程序集得到的,并对其做了些扩展
+ ///
+ internal class MouseDragElementBehaviorEx : Behavior
+ {
+ public static readonly DependencyProperty XProperty = DependencyProperty.Register(nameof(X), typeof(double),
+ typeof(MouseDragElementBehaviorEx), new PropertyMetadata(double.NaN, OnXChanged));
+
+ public static readonly DependencyProperty YProperty = DependencyProperty.Register(nameof(Y), typeof(double),
+ typeof(MouseDragElementBehaviorEx), new PropertyMetadata(double.NaN, OnYChanged));
+
+ public static readonly DependencyProperty ConstrainToParentBoundsProperty =
+ DependencyProperty.Register(nameof(ConstrainToParentBounds), typeof(bool),
+ typeof(MouseDragElementBehaviorEx), new PropertyMetadata(false, OnConstrainToParentBoundsChanged));
+
+ private Transform _cachedRenderTransform;
+ private Point _relativePosition;
+ private bool _settingPosition;
+
+ ///
+ /// 是否固定住Y轴
+ ///
+ public bool LockY { get; set; }
+
+ ///
+ /// 是否固定住X轴
+ ///
+ public bool LockX { get; set; }
+
+ public double X
+ {
+ get => (double)GetValue(MouseDragElementBehavior.XProperty);
+ set => SetValue(MouseDragElementBehavior.XProperty, value);
+ }
+
+ public double Y
+ {
+ get => (double)GetValue(MouseDragElementBehavior.YProperty);
+ set => SetValue(MouseDragElementBehavior.YProperty, value);
+ }
+
+ public bool ConstrainToParentBounds
+ {
+ get => (bool)GetValue(MouseDragElementBehavior.ConstrainToParentBoundsProperty);
+ set => SetValue(MouseDragElementBehavior.ConstrainToParentBoundsProperty, value);
+ }
+
+ private Rect ElementBounds
+ {
+ get
+ {
+ var layoutRect = ArithmeticHelper.GetLayoutRect(AssociatedObject);
+ return new Rect(new Point(0.0, 0.0), new Size(layoutRect.Width, layoutRect.Height));
+ }
+ }
+
+ private FrameworkElement ParentElement => AssociatedObject.Parent as FrameworkElement;
+
+ private UIElement RootElement
+ {
+ get
+ {
+ DependencyObject reference = AssociatedObject;
+ for (var dependencyObject = reference;
+ dependencyObject != null;
+ dependencyObject = VisualTreeHelper.GetParent(reference))
+ reference = dependencyObject;
+ return reference as UIElement;
+ }
+ }
+
+ private Transform RenderTransform
+ {
+ get
+ {
+ if (_cachedRenderTransform == null ||
+ !ReferenceEquals(_cachedRenderTransform, AssociatedObject.RenderTransform))
+ RenderTransform = CloneTransform(AssociatedObject.RenderTransform);
+ return _cachedRenderTransform;
+ }
+ set
+ {
+ if (Equals(_cachedRenderTransform, value))
+ return;
+ _cachedRenderTransform = value;
+ AssociatedObject.RenderTransform = value;
+ }
+ }
+
+ public event MouseEventHandler DragBegun;
+
+ public event MouseEventHandler Dragging;
+
+ public event MouseEventHandler DragFinished;
+
+ private static void OnXChanged(object sender, DependencyPropertyChangedEventArgs args)
+ {
+ var dragElementBehavior = (MouseDragElementBehaviorEx)sender;
+ dragElementBehavior.UpdatePosition(new Point((double)args.NewValue, dragElementBehavior.Y));
+ }
+
+ private static void OnYChanged(object sender, DependencyPropertyChangedEventArgs args)
+ {
+ var dragElementBehavior = (MouseDragElementBehaviorEx)sender;
+ dragElementBehavior.UpdatePosition(new Point(dragElementBehavior.X, (double)args.NewValue));
+ }
+
+ private static void OnConstrainToParentBoundsChanged(object sender, DependencyPropertyChangedEventArgs args)
+ {
+ var dragElementBehavior = (MouseDragElementBehaviorEx)sender;
+ dragElementBehavior.UpdatePosition(new Point(dragElementBehavior.X, dragElementBehavior.Y));
+ }
+
+ private void UpdatePosition(Point point)
+ {
+ if (_settingPosition || AssociatedObject == null)
+ return;
+ var transformOffset = GetTransformOffset(AssociatedObject.TransformToVisual(RootElement));
+ ApplyTranslation(double.IsNaN(point.X) ? 0.0 : point.X - transformOffset.X,
+ double.IsNaN(point.Y) ? 0.0 : point.Y - transformOffset.Y);
+ }
+
+ private void ApplyTranslation(double x, double y)
+ {
+ if (ParentElement == null)
+ return;
+ var point = TransformAsVector(RootElement.TransformToVisual(ParentElement), x, y);
+ x = point.X;
+ y = point.Y;
+ if (ConstrainToParentBounds)
+ {
+ var parentElement = ParentElement;
+ var rect1 = new Rect(0.0, 0.0, parentElement.ActualWidth, parentElement.ActualHeight);
+ var rect2 = AssociatedObject.TransformToVisual(parentElement).TransformBounds(ElementBounds);
+ rect2.X += x;
+ rect2.Y += y;
+ if (!RectContainsRect(rect1, rect2))
+ {
+ if (rect2.X < rect1.Left)
+ {
+ var num = rect2.X - rect1.Left;
+ x -= num;
+ }
+ else if (rect2.Right > rect1.Right)
+ {
+ var num = rect2.Right - rect1.Right;
+ x -= num;
+ }
+ if (rect2.Y < rect1.Top)
+ {
+ var num = rect2.Y - rect1.Top;
+ y -= num;
+ }
+ else if (rect2.Bottom > rect1.Bottom)
+ {
+ var num = rect2.Bottom - rect1.Bottom;
+ y -= num;
+ }
+ }
+ }
+ ApplyTranslationTransform(x, y);
+ }
+
+ internal void ApplyTranslationTransform(double x, double y)
+ {
+ var renderTransform = RenderTransform;
+ var translateTransform = renderTransform as TranslateTransform;
+ if (translateTransform == null)
+ {
+ var matrixTransform = renderTransform as MatrixTransform;
+ if (renderTransform is TransformGroup transformGroup1)
+ {
+ if (transformGroup1.Children.Count > 0)
+ translateTransform =
+ transformGroup1.Children[transformGroup1.Children.Count - 1] as TranslateTransform;
+ if (translateTransform == null)
+ {
+ translateTransform = new TranslateTransform();
+ transformGroup1.Children.Add(translateTransform);
+ }
+ }
+ else
+ {
+ if (matrixTransform != null)
+ {
+ var matrix = matrixTransform.Matrix;
+ //在该处对微软的类进行了修改
+ if (!LockX)
+ {
+ matrix.OffsetX += x;
+ }
+ if (!LockY)
+ {
+ matrix.OffsetY += y;
+ }
+ //修改结束
+ RenderTransform = new MatrixTransform
+ {
+ Matrix = matrix
+ };
+ return;
+ }
+ var transformGroup2 = new TransformGroup();
+ translateTransform = new TranslateTransform();
+ if (renderTransform != null)
+ transformGroup2.Children.Add(renderTransform);
+ transformGroup2.Children.Add(translateTransform);
+ RenderTransform = transformGroup2;
+ }
+ }
+ //在该处对微软的类进行了修改
+ if (!LockX)
+ {
+ translateTransform.X += x;
+ }
+ if (!LockY)
+ {
+ translateTransform.Y += y;
+ }
+ //修改结束
+ }
+
+ internal static Transform CloneTransform(Transform transform)
+ {
+ if (transform == null)
+ return null;
+ if (transform is ScaleTransform scaleTransform)
+ return new ScaleTransform
+ {
+ CenterX = scaleTransform.CenterX,
+ CenterY = scaleTransform.CenterY,
+ ScaleX = scaleTransform.ScaleX,
+ ScaleY = scaleTransform.ScaleY
+ };
+ if (transform is RotateTransform rotateTransform)
+ return new RotateTransform
+ {
+ Angle = rotateTransform.Angle,
+ CenterX = rotateTransform.CenterX,
+ CenterY = rotateTransform.CenterY
+ };
+ if (transform is SkewTransform skewTransform)
+ return new SkewTransform
+ {
+ AngleX = skewTransform.AngleX,
+ AngleY = skewTransform.AngleY,
+ CenterX = skewTransform.CenterX,
+ CenterY = skewTransform.CenterY
+ };
+ if (transform is TranslateTransform translateTransform)
+ return new TranslateTransform
+ {
+ X = translateTransform.X,
+ Y = translateTransform.Y
+ };
+ if (transform is MatrixTransform matrixTransform)
+ return new MatrixTransform
+ {
+ Matrix = matrixTransform.Matrix
+ };
+ if (!(transform is TransformGroup transformGroup1))
+ return null;
+ var transformGroup2 = new TransformGroup();
+ foreach (var child in transformGroup1.Children)
+ transformGroup2.Children.Add(CloneTransform(child));
+ return transformGroup2;
+ }
+
+ private void UpdatePosition()
+ {
+ var transformOffset = GetTransformOffset(AssociatedObject.TransformToVisual(RootElement));
+ X = transformOffset.X;
+ Y = transformOffset.Y;
+ }
+
+ internal void StartDrag(Point positionInElementCoordinates)
+ {
+ _relativePosition = positionInElementCoordinates;
+ AssociatedObject.CaptureMouse();
+ AssociatedObject.MouseMove += OnMouseMove;
+ AssociatedObject.LostMouseCapture += OnLostMouseCapture;
+ AssociatedObject.AddHandler(UIElement.MouseLeftButtonUpEvent,
+ new MouseButtonEventHandler(OnMouseLeftButtonUp), false);
+ }
+
+ internal void HandleDrag(Point newPositionInElementCoordinates)
+ {
+ var point = TransformAsVector(AssociatedObject.TransformToVisual(RootElement),
+ newPositionInElementCoordinates.X - _relativePosition.X,
+ newPositionInElementCoordinates.Y - _relativePosition.Y);
+ _settingPosition = true;
+ ApplyTranslation(point.X, point.Y);
+ UpdatePosition();
+ _settingPosition = false;
+ }
+
+ internal void EndDrag()
+ {
+ AssociatedObject.MouseMove -= OnMouseMove;
+ AssociatedObject.LostMouseCapture -= OnLostMouseCapture;
+ AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonUpEvent,
+ new MouseButtonEventHandler(OnMouseLeftButtonUp));
+ }
+
+ private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ StartDrag(e.GetPosition(AssociatedObject));
+ DragBegun?.Invoke(this, e);
+ }
+
+ private void OnLostMouseCapture(object sender, MouseEventArgs e)
+ {
+ EndDrag();
+ DragFinished?.Invoke(this, e);
+ }
+
+ private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
+ {
+ AssociatedObject.ReleaseMouseCapture();
+ }
+
+ private void OnMouseMove(object sender, MouseEventArgs e)
+ {
+ HandleDrag(e.GetPosition(AssociatedObject));
+ Dragging?.Invoke(this, e);
+ }
+
+ private static bool RectContainsRect(Rect rect1, Rect rect2)
+ {
+ if (rect1.IsEmpty || rect2.IsEmpty || rect1.X > rect2.X || rect1.Y > rect2.Y ||
+ rect1.X + rect1.Width < rect2.X + rect2.Width)
+ return false;
+ return rect1.Y + rect1.Height >= rect2.Y + rect2.Height;
+ }
+
+ private static Point TransformAsVector(GeneralTransform transform, double x, double y)
+ {
+ var point1 = transform.Transform(new Point(0.0, 0.0));
+ var point2 = transform.Transform(new Point(x, y));
+ return new Point(point2.X - point1.X, point2.Y - point1.Y);
+ }
+
+ private static Point GetTransformOffset(GeneralTransform transform)
+ {
+ return transform.Transform(new Point(0.0, 0.0));
+ }
+
+ protected override void OnAttached()
+ {
+ AssociatedObject.AddHandler(UIElement.MouseLeftButtonDownEvent,
+ new MouseButtonEventHandler(OnMouseLeftButtonDown), false);
+ }
+
+ protected override void OnDetaching()
+ {
+ AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonDownEvent,
+ new MouseButtonEventHandler(OnMouseLeftButtonDown));
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/HandyControlDemo/MainWindow.xaml b/HandyControlDemo/MainWindow.xaml
index ab14c10e..17ac7585 100644
--- a/HandyControlDemo/MainWindow.xaml
+++ b/HandyControlDemo/MainWindow.xaml
@@ -354,7 +354,7 @@
-->
-
+
@@ -399,7 +399,7 @@
-
+
-
+
diff --git a/README.md b/README.md
index 5e89a5bb..2aa57f29 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@

-  [](https://www.nuget.org/packages/HandyControl)
+  [](https://www.nuget.org/packages/HandyControl)
# 欢迎使用 HandyControl
@@ -26,4 +26,8 @@
### 5、展开折叠框Expander
-
\ No newline at end of file
+
+
+### 6、时间条TimeBar
+
+
\ No newline at end of file
diff --git a/Resources/Carousel.gif b/Resources/Carousel.gif
index 6bd68263..00d30c61 100644
Binary files a/Resources/Carousel.gif and b/Resources/Carousel.gif differ
diff --git a/Resources/Expander.gif b/Resources/Expander.gif
index 7d85b984..8d0266d5 100644
Binary files a/Resources/Expander.gif and b/Resources/Expander.gif differ
diff --git a/Resources/Loading.gif b/Resources/Loading.gif
index 28e0c5f3..b3916949 100644
Binary files a/Resources/Loading.gif and b/Resources/Loading.gif differ
diff --git a/Resources/TimeBar.gif b/Resources/TimeBar.gif
new file mode 100644
index 00000000..0bba8301
Binary files /dev/null and b/Resources/TimeBar.gif differ