using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using HandyControl.Data;
using HandyControl.Interactivity;
using HandyControl.Tools;
namespace HandyControl.Controls;
///
/// 时间条
///
[TemplatePart(Name = ElementBorderTop, Type = typeof(Border))]
[TemplatePart(Name = ElementTextBlockMove, Type = typeof(TextBlock))]
[TemplatePart(Name = ElementTextBlockSelected, Type = typeof(TextBlock))]
[TemplatePart(Name = ElementCanvasSpe, Type = typeof(Canvas))]
[TemplatePart(Name = ElementHotspots, Type = typeof(Panel))]
public class TimeBar : Control
{
#region Constants
private const string ElementBorderTop = "PART_BorderTop";
private const string ElementTextBlockMove = "PART_TextBlockMove";
private const string ElementTextBlockSelected = "PART_TextBlockSelected";
private const string ElementCanvasSpe = "PART_CanvasSpe";
private const string ElementHotspots = "PART_Hotspots";
#endregion Constants
#region Data
private Border _borderTop;
private TextBlock _textBlockMove;
private TextBlock _textBlockSelected;
private Canvas _canvasSpe;
private Panel _panelHotspots;
#endregion Data
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[Bindable(true)]
public Collection Hotspots { get; }
public static readonly DependencyProperty HotspotsBrushProperty = DependencyProperty.Register(
nameof(HotspotsBrush), typeof(Brush), typeof(TimeBar), new PropertyMetadata(default(Brush)));
public Brush HotspotsBrush
{
get => (Brush) GetValue(HotspotsBrushProperty);
set => SetValue(HotspotsBrushProperty, value);
}
///
/// 是否显示刻度字符串
///
public static readonly DependencyProperty ShowSpeStrProperty = DependencyProperty.Register(
nameof(ShowSpeStr), typeof(bool), typeof(TimeBar), new PropertyMetadata(ValueBoxes.FalseBox));
///
/// 是否显示刻度字符串
///
public bool ShowSpeStr
{
get => (bool) GetValue(ShowSpeStrProperty);
set => SetValue(ShowSpeStrProperty, ValueBoxes.BooleanBox(value));
}
public static readonly DependencyProperty TimeFormatProperty = DependencyProperty.Register(
nameof(TimeFormat), typeof(string), typeof(TimeBar), new PropertyMetadata("yyyy-MM-dd HH:mm:ss"));
public string TimeFormat
{
get => (string) GetValue(TimeFormatProperty);
set => SetValue(TimeFormatProperty, value);
}
///
/// 刻度字符串
///
internal static readonly DependencyProperty SpeStrProperty = DependencyProperty.Register(
nameof(SpeStr), typeof(string), typeof(TimeBar), new PropertyMetadata(Properties.Langs.Lang.Interval1h));
///
/// 刻度字符串
///
internal string SpeStr
{
get => (string) GetValue(SpeStrProperty);
set => SetValue(SpeStrProperty, value);
}
///
/// 选中时间
///
public static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register(
nameof(SelectedTime), typeof(DateTime), typeof(TimeBar), new PropertyMetadata(default(DateTime), OnSelectedTimeChanged));
private static void OnSelectedTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TimeBar { _textBlockSelected: { } } timeBar)
{
timeBar.OnSelectedTimeChanged((DateTime) e.NewValue);
}
}
///
/// 选中时间
///
public DateTime SelectedTime
{
get => (DateTime) GetValue(SelectedTimeProperty);
set => SetValue(SelectedTimeProperty, value);
}
private void OnSelectedTimeChanged(DateTime time)
{
_textBlockSelected.Text = time.ToString(TimeFormat);
if (!(_isDragging || _borderTopIsMouseLeftButtonDown))
{
_totalOffsetX = (_starTime - SelectedTime).TotalMilliseconds / _timeSpeList[SpeIndex] * _itemWidth;
}
UpdateSpeBlock();
UpdateMouseFollowBlockPos();
}
///
/// 时间改变事件
///
public static readonly RoutedEvent TimeChangedEvent =
EventManager.RegisterRoutedEvent("TimeChanged", RoutingStrategy.Bubble,
typeof(EventHandler>), typeof(TimeBar));
///
/// 刻度集合
///
private readonly List _speBlockList = new();
///
/// 初始化时时间
///
private readonly DateTime _starTime;
///
/// 时间段集合
///
private readonly List _timeSpeList = new()
{
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;
private readonly bool _isLoaded;
private readonly SortedSet _dateTimeRanges;
public TimeBar()
{
_starTime = DateTime.Now;
SelectedTime = new DateTime(_starTime.Year, _starTime.Month, _starTime.Day, 0, 0, 0);
_starTime = SelectedTime;
_isLoaded = true;
var hotspots = new ObservableCollection();
_dateTimeRanges = new SortedSet(ComparerGenerator.GetComparer());
hotspots.CollectionChanged += Items_CollectionChanged;
Hotspots = hotspots;
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
foreach (DateTimeRange item in e.NewItems)
{
_dateTimeRanges.Add(item);
}
}
else if (e.Action == NotifyCollectionChangedAction.Remove)
{
foreach (DateTimeRange item in e.OldItems)
{
_dateTimeRanges.Remove(item);
}
}
else if (e.Action == NotifyCollectionChangedAction.Replace)
{
foreach (DateTimeRange item in e.OldItems)
{
_dateTimeRanges.Remove(item);
}
foreach (DateTimeRange item in e.NewItems)
{
_dateTimeRanges.Add(item);
}
}
else if (e.Action == NotifyCollectionChangedAction.Reset)
{
_dateTimeRanges.Clear();
}
}
public override void OnApplyTemplate()
{
if (_borderTop != null)
{
_borderTop.MouseLeftButtonDown -= BorderTop_OnMouseLeftButtonDown;
_borderTop.PreviewMouseLeftButtonUp -= BorderTop_OnPreviewMouseLeftButtonUp;
}
base.OnApplyTemplate();
_borderTop = GetTemplateChild(ElementBorderTop) as Border;
_textBlockMove = GetTemplateChild(ElementTextBlockMove) as TextBlock;
_textBlockSelected = GetTemplateChild(ElementTextBlockSelected) as TextBlock;
_canvasSpe = GetTemplateChild(ElementCanvasSpe) as Canvas;
_panelHotspots = GetTemplateChild(ElementHotspots) as Panel;
CheckNull();
_borderTop.MouseLeftButtonDown += BorderTop_OnMouseLeftButtonDown;
_borderTop.PreviewMouseLeftButtonUp += BorderTop_OnPreviewMouseLeftButtonUp;
var behavior = new MouseDragElementBehaviorEx
{
LockY = true,
};
behavior.DragBegun += DragElementBehavior_OnDragBegun;
behavior.Dragging += MouseDragElementBehavior_OnDragging;
behavior.DragFinished += MouseDragElementBehavior_OnDragFinished;
var collection = Interaction.GetBehaviors(_borderTop);
collection.Add(behavior);
if (_isLoaded)
{
Update();
}
_textBlockSelected.Text = SelectedTime.ToString(TimeFormat);
}
private void CheckNull()
{
if (_borderTop == null || _textBlockMove == null || _textBlockSelected == null || _canvasSpe == null) throw new Exception();
}
///
/// 刻度区间编号
///
public int SpeIndex
{
get => _speIndex;
private set
{
if (_speIndex == value) return;
if (value < 0)
{
SpeStr = Properties.Langs.Lang.Interval2h;
_speIndex = 0;
return;
}
if (value > 6)
{
SpeStr = Properties.Langs.Lang.Interval30s;
_speIndex = 6;
return;
}
SetSpeTimeFormat("HH:mm");
switch (value)
{
case 0:
SpeStr = Properties.Langs.Lang.Interval2h;
break;
case 1:
SpeStr = Properties.Langs.Lang.Interval1h;
break;
case 2:
SpeStr = Properties.Langs.Lang.Interval30m;
break;
case 3:
SpeStr = Properties.Langs.Lang.Interval10m;
break;
case 4:
SpeStr = Properties.Langs.Lang.Interval5m;
break;
case 5:
SpeStr = Properties.Langs.Lang.Interval1m;
break;
case 6:
SetSpeTimeFormat("HH:mm:ss");
SpeStr = Properties.Langs.Lang.Interval30s;
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;
}
///
/// 更新刻度
///
private void UpdateSpeBlock()
{
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]);
if (_panelHotspots != null && _dateTimeRanges.Any())
{
UpdateHotspots();
}
}
///
/// 时间转换
///
///
///
private DateTime TimeConvert(DateTime time)
{
return _speIndex switch
{
0 => new DateTime(time.Year, time.Month, time.Day, time.Hour / 2 * 2, 0, 0),
1 => new DateTime(time.Year, time.Month, time.Day, time.Hour, 0, 0),
2 => new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute / 30 * 30, 0),
3 => new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute / 10 * 10, 0),
4 => new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute / 5 * 5, 0),
5 => new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, 0),
6 => new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second / 30 * 30),
_ => 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;
UpdateSpeBlock();
UpdateMouseFollowBlockPos();
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]);
_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;
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
base.OnRenderSizeChanged(sizeInfo);
Update();
}
///
/// 更新
///
private void Update()
{
if (_canvasSpe == null) return;
_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);
}
UpdateSpeBlock();
UpdateMouseFollowBlockPos();
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
UpdateMouseFollowBlockPos();
}
///
/// 更新鼠标跟随块位置
///
private void UpdateMouseFollowBlockPos()
{
var p = Mouse.GetPosition(this);
var mlliseconds = (p.X - ActualWidth / 2) / _itemWidth * _timeSpeList[_speIndex];
if (double.IsNaN(mlliseconds) || double.IsInfinity(mlliseconds)) return;
_textBlockMove.Text = mlliseconds < 0
? (SelectedTime - TimeSpan.FromMilliseconds(-mlliseconds)).ToString(TimeFormat)
: (SelectedTime + TimeSpan.FromMilliseconds(mlliseconds)).ToString(TimeFormat);
_textBlockMove.Margin = new Thickness(p.X - _textBlockMove.ActualWidth / 2, 2, 0, 0);
}
private void UpdateHotspots()
{
var mlliseconds = ActualWidth * 0.5 / _itemWidth * _timeSpeList[_speIndex];
if (double.IsNaN(mlliseconds) || double.IsInfinity(mlliseconds)) return;
_panelHotspots.Children.Clear();
foreach (var rect in GetHotspotsRectangle(mlliseconds))
{
_panelHotspots.Children.Add(rect);
}
}
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]);
_totalOffsetX = (_totalOffsetX + _tempOffsetX) % ActualWidth;
_tempOffsetX = 0;
UpdateMouseFollowBlockPos();
RaiseEvent(new FunctionEventArgs(TimeChangedEvent, this)
{
Info = SelectedTime
});
}
}
private IEnumerable GetHotspotsRectangle(double mlliseconds)
{
var timeSpan = TimeSpan.FromMilliseconds(mlliseconds);
var selectedTime = SelectedTime;
var start = selectedTime - timeSpan;
var end = selectedTime + timeSpan;
var set = _dateTimeRanges.GetViewBetween(new DateTimeRange(start), new DateTimeRange(end));
var unitLength = ActualWidth / mlliseconds * 0.5;
foreach (var range in set)
{
var width = range.TotalMilliseconds * unitLength;
var sub = range.Start - start;
var x = sub.TotalMilliseconds * unitLength;
yield return new Rectangle
{
Fill = HotspotsBrush,
Height = 4,
Width = width,
Margin = new Thickness(x, 0, 0, 0),
HorizontalAlignment = HorizontalAlignment.Left
};
}
}
}