Adjust project structure
This commit is contained in:
1 parent
d38912f5da
commit
e3c436f939
2111 files changed
+14028
-47199
No files matched your search
@@ -0,0 +1,210 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shapes;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Expression.Drawing;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class AnimationPath : Shape
|
||||
{
|
||||
private bool _isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// 故事版
|
||||
/// </summary>
|
||||
private Storyboard _storyboard;
|
||||
|
||||
/// <summary>
|
||||
/// 路径长度
|
||||
/// </summary>
|
||||
private double _pathLength;
|
||||
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(nameof(Data),
|
||||
typeof(Geometry), typeof(AnimationPath), new FrameworkPropertyMetadata(null,
|
||||
OnPropertiesChanged));
|
||||
|
||||
private static void OnPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is AnimationPath path)
|
||||
{
|
||||
path.UpdatePath();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
public Geometry Data
|
||||
{
|
||||
get => (Geometry)GetValue(DataProperty);
|
||||
set => SetValue(DataProperty, value);
|
||||
}
|
||||
|
||||
protected override Geometry DefiningGeometry => Data ?? Geometry.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 路径长度
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PathLengthProperty = DependencyProperty.Register(
|
||||
"PathLength", typeof(double), typeof(AnimationPath), new FrameworkPropertyMetadata(ValueBoxes.Double0Box, OnPropertiesChanged));
|
||||
|
||||
/// <summary>
|
||||
/// 路径长度
|
||||
/// </summary>
|
||||
public double PathLength
|
||||
{
|
||||
get => (double)GetValue(PathLengthProperty);
|
||||
set => SetValue(PathLengthProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画间隔时间
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DurationProperty = DependencyProperty.Register(
|
||||
"Duration", typeof(Duration), typeof(AnimationPath), new FrameworkPropertyMetadata(new Duration(TimeSpan.FromSeconds(2)),
|
||||
OnPropertiesChanged));
|
||||
|
||||
/// <summary>
|
||||
/// 动画间隔时间
|
||||
/// </summary>
|
||||
public Duration Duration
|
||||
{
|
||||
get => (Duration)GetValue(DurationProperty);
|
||||
set => SetValue(DurationProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsPlayingProperty = DependencyProperty.Register(
|
||||
"IsPlaying", typeof(bool), typeof(AnimationPath), new FrameworkPropertyMetadata(true, (o, args) =>
|
||||
{
|
||||
var ctl = (AnimationPath)o;
|
||||
var v = (bool)args.NewValue;
|
||||
if (v)
|
||||
{
|
||||
ctl.UpdatePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctl._storyboard?.Pause();
|
||||
}
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在播放动画
|
||||
/// </summary>
|
||||
public bool IsPlaying
|
||||
{
|
||||
get => (bool)GetValue(IsPlayingProperty);
|
||||
set => SetValue(IsPlayingProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty RepeatBehaviorProperty = DependencyProperty.Register(
|
||||
"RepeatBehavior", typeof(RepeatBehavior), typeof(AnimationPath), new PropertyMetadata(RepeatBehavior.Forever));
|
||||
|
||||
/// <summary>
|
||||
/// 动画重复行为
|
||||
/// </summary>
|
||||
public RepeatBehavior RepeatBehavior
|
||||
{
|
||||
get => (RepeatBehavior)GetValue(RepeatBehaviorProperty);
|
||||
set => SetValue(RepeatBehaviorProperty, value);
|
||||
}
|
||||
|
||||
static AnimationPath()
|
||||
{
|
||||
StretchProperty.AddOwner(typeof(AnimationPath), new FrameworkPropertyMetadata(Stretch.Uniform,
|
||||
OnPropertiesChanged));
|
||||
|
||||
StrokeThicknessProperty.AddOwner(typeof(AnimationPath), new FrameworkPropertyMetadata(1.0,
|
||||
OnPropertiesChanged));
|
||||
}
|
||||
|
||||
public AnimationPath()
|
||||
{
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (_isLoaded) return;
|
||||
UpdatePath();
|
||||
_isLoaded = true;
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画完成事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent CompletedEvent =
|
||||
EventManager.RegisterRoutedEvent("Completed", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler), typeof(AnimationPath));
|
||||
|
||||
/// <summary>
|
||||
/// 动画完成事件
|
||||
/// </summary>
|
||||
public event EventHandler Completed
|
||||
{
|
||||
add => AddHandler(CompletedEvent, value);
|
||||
remove => RemoveHandler(CompletedEvent, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新路径
|
||||
/// </summary>
|
||||
private void UpdatePath()
|
||||
{
|
||||
if (!Duration.HasTimeSpan || !IsPlaying) return;
|
||||
|
||||
_pathLength = PathLength > 0 ? PathLength : Data.GetTotalLength(new Size(ActualWidth, ActualHeight), StrokeThickness);
|
||||
|
||||
if (MathHelper.IsVerySmall(_pathLength)) return;
|
||||
|
||||
StrokeDashOffset = _pathLength;
|
||||
StrokeDashArray = new DoubleCollection(new List<double>
|
||||
{
|
||||
_pathLength,
|
||||
_pathLength
|
||||
});
|
||||
|
||||
//定义动画
|
||||
if (_storyboard != null)
|
||||
{
|
||||
_storyboard.Stop();
|
||||
_storyboard.Completed -= Storyboard_Completed;
|
||||
}
|
||||
_storyboard = new Storyboard
|
||||
{
|
||||
RepeatBehavior = RepeatBehavior
|
||||
};
|
||||
_storyboard.Completed += Storyboard_Completed;
|
||||
|
||||
var frames = new DoubleAnimationUsingKeyFrames();
|
||||
//开始位置
|
||||
var frame0 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = _pathLength,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero)
|
||||
};
|
||||
//结束位置
|
||||
var frame1 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = -_pathLength,
|
||||
KeyTime = KeyTime.FromTimeSpan(Duration.TimeSpan)
|
||||
};
|
||||
frames.KeyFrames.Add(frame0);
|
||||
frames.KeyFrames.Add(frame1);
|
||||
|
||||
Storyboard.SetTarget(frames, this);
|
||||
Storyboard.SetTargetProperty(frames, new PropertyPath(StrokeDashOffsetProperty));
|
||||
_storyboard.Children.Add(frames);
|
||||
|
||||
_storyboard.Begin();
|
||||
}
|
||||
|
||||
private void Storyboard_Completed(object sender, EventArgs e) => RaiseEvent(new RoutedEventArgs(CompletedEvent));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 标记
|
||||
/// </summary>
|
||||
public class Badge : ContentControl
|
||||
{
|
||||
public static readonly RoutedEvent ValueChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<int>>), typeof(Badge));
|
||||
|
||||
public event EventHandler<FunctionEventArgs<int>> ValueChanged
|
||||
{
|
||||
add => AddHandler(ValueChangedEvent, value);
|
||||
remove => RemoveHandler(ValueChangedEvent, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
"Text", typeof(string), typeof(Badge), new PropertyMetadata(default(string)));
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string) GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
|
||||
"Value", typeof(int), typeof(Badge), new PropertyMetadata(ValueBoxes.Int0Box, OnValueChanged));
|
||||
|
||||
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (Badge)d;
|
||||
var v = (int)e.NewValue;
|
||||
ctl.Text = v <= ctl.Maximum ? v.ToString() : $"{ctl.Maximum}+";
|
||||
if (ctl.IsInitialized)
|
||||
{
|
||||
ctl.RaiseEvent(new FunctionEventArgs<int>(ValueChangedEvent, ctl)
|
||||
{
|
||||
Info = v
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public int Value
|
||||
{
|
||||
get => (int)GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty StatusProperty = DependencyProperty.Register(
|
||||
"Status", typeof(BadgeStatus), typeof(Badge), new PropertyMetadata(default(BadgeStatus)));
|
||||
|
||||
public BadgeStatus Status
|
||||
{
|
||||
get => (BadgeStatus)GetValue(StatusProperty);
|
||||
set => SetValue(StatusProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(
|
||||
"Maximum", typeof(int), typeof(Badge), new PropertyMetadata(ValueBoxes.Int99Box, OnValueChanged));
|
||||
|
||||
public int Maximum
|
||||
{
|
||||
get => (int)GetValue(MaximumProperty);
|
||||
set => SetValue(MaximumProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty BadgeMarginProperty = DependencyProperty.Register(
|
||||
"BadgeMargin", typeof(Thickness), typeof(Badge), new PropertyMetadata(default(Thickness)));
|
||||
|
||||
public Thickness BadgeMargin
|
||||
{
|
||||
get => (Thickness)GetValue(BadgeMarginProperty);
|
||||
set => SetValue(BadgeMarginProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 轮播控件
|
||||
/// </summary>
|
||||
[DefaultProperty("Items")]
|
||||
[ContentProperty("Items")]
|
||||
[TemplatePart(Name = ElementPanelPage, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementItemsControl, Type = typeof(ItemsPresenter))]
|
||||
public class Carousel : ListBox
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementPanelPage = "PART_PanelPage";
|
||||
private const string ElementItemsControl = "PART_ItemsControl";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Panel _panelPage;
|
||||
|
||||
private bool _appliedTemplate;
|
||||
|
||||
private ItemsPresenter _itemsControl;
|
||||
|
||||
private int _pageIndex = -1;
|
||||
|
||||
private Button _selectedButton;
|
||||
|
||||
private DispatcherTimer _updateTimer;
|
||||
|
||||
private readonly List<double> _widthList = new List<double>();
|
||||
|
||||
#endregion Data
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_appliedTemplate = false;
|
||||
|
||||
_panelPage?.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ButtonPages_OnClick));
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_itemsControl = GetTemplateChild(ElementItemsControl) as ItemsPresenter;
|
||||
_panelPage = GetTemplateChild(ElementPanelPage) as Panel;
|
||||
|
||||
CheckNull();
|
||||
|
||||
_panelPage.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ButtonPages_OnClick));
|
||||
_appliedTemplate = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
TimerSwitch(AutoRun);
|
||||
UpdatePageButtons(_pageIndex);
|
||||
}
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
if (_itemsControl == null || _panelPage == null) throw new Exception();
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AutoRunProperty = DependencyProperty.Register(
|
||||
"AutoRun", typeof(bool), typeof(Carousel), new PropertyMetadata(ValueBoxes.FalseBox, (o, args) =>
|
||||
{
|
||||
var ctl = (Carousel)o;
|
||||
ctl.TimerSwitch((bool)args.NewValue);
|
||||
}));
|
||||
|
||||
public static readonly DependencyProperty IntervalProperty = DependencyProperty.Register(
|
||||
"Interval", typeof(TimeSpan), typeof(Carousel), new PropertyMetadata(TimeSpan.FromSeconds(2)));
|
||||
|
||||
public static readonly DependencyProperty ExtendWidthProperty = DependencyProperty.Register(
|
||||
"ExtendWidth", typeof(double), typeof(Carousel), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
public double ExtendWidth
|
||||
{
|
||||
get => (double)GetValue(ExtendWidthProperty);
|
||||
set => SetValue(ExtendWidthProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsCenterProperty = DependencyProperty.Register(
|
||||
"IsCenter", typeof(bool), typeof(Carousel), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsCenter
|
||||
{
|
||||
get => (bool)GetValue(IsCenterProperty);
|
||||
set => SetValue(IsCenterProperty, value);
|
||||
}
|
||||
|
||||
public Carousel()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Prev, ButtonPrev_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Next, ButtonNext_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Selected, ButtonPages_OnClick));
|
||||
|
||||
Loaded += (s, e) => UpdatePageButtons();
|
||||
}
|
||||
|
||||
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
base.OnItemsChanged(e);
|
||||
|
||||
UpdatePageButtons();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否自动跳转
|
||||
/// </summary>
|
||||
public bool AutoRun
|
||||
{
|
||||
get => (bool)GetValue(AutoRunProperty);
|
||||
set => SetValue(AutoRunProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 跳转时间间隔
|
||||
/// </summary>
|
||||
public TimeSpan Interval
|
||||
{
|
||||
get => (TimeSpan)GetValue(IntervalProperty);
|
||||
set => SetValue(IntervalProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 页码
|
||||
/// </summary>
|
||||
public int PageIndex
|
||||
{
|
||||
get => _pageIndex;
|
||||
set
|
||||
{
|
||||
if (Items.Count == 0) return;
|
||||
if (_pageIndex == value) return;
|
||||
if (value < 0)
|
||||
_pageIndex = Items.Count - 1;
|
||||
else if (value >= Items.Count)
|
||||
_pageIndex = 0;
|
||||
else
|
||||
_pageIndex = value;
|
||||
UpdatePageButtons(_pageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计时器开关
|
||||
/// </summary>
|
||||
private void TimerSwitch(bool run)
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
_updateTimer.Tick -= UpdateTimer_Tick;
|
||||
_updateTimer.Stop();
|
||||
_updateTimer = null;
|
||||
}
|
||||
|
||||
if (!run) return;
|
||||
_updateTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = Interval
|
||||
};
|
||||
_updateTimer.Tick += UpdateTimer_Tick;
|
||||
_updateTimer.Start();
|
||||
}
|
||||
|
||||
private void UpdateTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (IsMouseOver) return;
|
||||
PageIndex++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新页按钮
|
||||
/// </summary>
|
||||
public void UpdatePageButtons(int index = -1)
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
|
||||
var count = Items.Count;
|
||||
_widthList.Clear();
|
||||
_widthList.Add(0);
|
||||
var width = .0;
|
||||
foreach (FrameworkElement item in Items)
|
||||
{
|
||||
item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
width += item.DesiredSize.Width;
|
||||
_widthList.Add(width);
|
||||
}
|
||||
|
||||
_itemsControl.Width = _widthList.Last() + ExtendWidth;
|
||||
_panelPage.Children.Clear();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_panelPage.Children.Add(CreatePateButton());
|
||||
}
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
var button = _panelPage.Children[0];
|
||||
button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
|
||||
}
|
||||
}
|
||||
else if (index >= 0 && index < count)
|
||||
{
|
||||
var button = _panelPage.Children[index];
|
||||
button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
|
||||
UpdateItemsPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新项的位置
|
||||
/// </summary>
|
||||
private void UpdateItemsPosition()
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
if (Items.Count == 0) return;
|
||||
if (!IsCenter)
|
||||
{
|
||||
_itemsControl.BeginAnimation(MarginProperty,
|
||||
AnimationHelper.CreateAnimation(new Thickness(-_widthList[PageIndex], 0, 0, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ctl = (FrameworkElement)Items[PageIndex];
|
||||
var ctlWidth = ctl.DesiredSize.Width;
|
||||
_itemsControl.BeginAnimation(MarginProperty,
|
||||
AnimationHelper.CreateAnimation(
|
||||
new Thickness(-_widthList[PageIndex] + (ActualWidth - ctlWidth) / 2, 0, 0, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
||||
{
|
||||
base.OnRenderSizeChanged(sizeInfo);
|
||||
UpdateItemsPosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建页按钮
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Button CreatePateButton()
|
||||
{
|
||||
var button = new Button
|
||||
{
|
||||
Style = ResourceHelper.GetResource<Style>(ResourceToken.ButtonCustom),
|
||||
Content = new Border
|
||||
{
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
CornerRadius = new CornerRadius(5),
|
||||
Background = Brushes.White,
|
||||
Margin = new Thickness(5, 0, 5, 0),
|
||||
BorderThickness = new Thickness(1),
|
||||
BorderBrush = ResourceHelper.GetResource<Brush>(ResourceToken.PrimaryBrush)
|
||||
}
|
||||
};
|
||||
return button;
|
||||
}
|
||||
|
||||
private void ButtonPages_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedButton != null && _selectedButton.Content is Border borderOri)
|
||||
borderOri.Background = Brushes.White;
|
||||
_selectedButton = e.OriginalSource as Button;
|
||||
if (_selectedButton != null && _selectedButton.Content is Border border)
|
||||
border.Background = ResourceHelper.GetResource<Brush>(ResourceToken.PrimaryBrush);
|
||||
var index = _panelPage.Children.IndexOf(_selectedButton);
|
||||
if (index != -1)
|
||||
PageIndex = index;
|
||||
}
|
||||
|
||||
private void ButtonPrev_OnClick(object sender, RoutedEventArgs e) => PageIndex--;
|
||||
|
||||
private void ButtonNext_OnClick(object sender, RoutedEventArgs e) => PageIndex++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class ChatBubble : SelectableItem
|
||||
{
|
||||
public static readonly DependencyProperty RoleProperty = DependencyProperty.Register(
|
||||
"Role", typeof(ChatRoleType), typeof(ChatBubble), new PropertyMetadata(default(ChatRoleType)));
|
||||
|
||||
public ChatRoleType Role
|
||||
{
|
||||
get => (ChatRoleType) GetValue(RoleProperty);
|
||||
set => SetValue(RoleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register(
|
||||
"Type", typeof(ChatMessageType), typeof(ChatBubble), new PropertyMetadata(default(ChatMessageType)));
|
||||
|
||||
public ChatMessageType Type
|
||||
{
|
||||
get => (ChatMessageType) GetValue(TypeProperty);
|
||||
set => SetValue(TypeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsReadProperty = DependencyProperty.Register(
|
||||
"IsRead", typeof(bool), typeof(ChatBubble), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsRead
|
||||
{
|
||||
get => (bool) GetValue(IsReadProperty);
|
||||
set => SetValue(IsReadProperty, value);
|
||||
}
|
||||
|
||||
public static void SetMaxWidth(DependencyObject element, double value)
|
||||
=> element.SetValue(MaxWidthProperty, value);
|
||||
|
||||
public static double GetMaxWidth(DependencyObject element)
|
||||
=> (double) element.GetValue(MaxWidthProperty);
|
||||
|
||||
public Action<object> ReadAction { get; set; }
|
||||
|
||||
protected override void OnSelected(RoutedEventArgs e)
|
||||
{
|
||||
base.OnSelected(e);
|
||||
|
||||
IsRead = true;
|
||||
ReadAction?.Invoke(Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,682 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 颜色拾取器
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementBorderColor, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementBorderPicker, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementBorderDrag, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementPanelColor, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementSliderColor, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementSliderOpacity, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementPanelRgb, Type = typeof(Panel))]
|
||||
public class ColorPicker : Control, ISingleOpen
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementBorderColor = "PART_BorderColor";
|
||||
private const string ElementBorderPicker = "PART_BorderPicker";
|
||||
private const string ElementBorderDrag = "PART_BorderDrag";
|
||||
private const string ElementPanelColor = "PART_PanelColor";
|
||||
private const string ElementSliderColor = "PART_SliderColor";
|
||||
private const string ElementSliderOpacity = "PART_SliderOpacity";
|
||||
private const string ElementPanelRgb = "PART_PanelRgb";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Panel _panelRgb;
|
||||
|
||||
private Border _borderColor;
|
||||
|
||||
private Border _borderPicker;
|
||||
|
||||
private Border _borderDrag;
|
||||
|
||||
private Panel _panelColor;
|
||||
|
||||
private Slider _sliderColor;
|
||||
|
||||
private Slider _sliderOpacity;
|
||||
|
||||
private bool _appliedTemplate;
|
||||
|
||||
/// <summary>
|
||||
/// 当前显示的颜色类型
|
||||
/// </summary>
|
||||
private int _colorType;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已经加载控件
|
||||
/// </summary>
|
||||
private bool _isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要更新小球位置
|
||||
/// </summary>
|
||||
private bool _isNeedUpdatePicker = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否在拖动小球
|
||||
/// </summary>
|
||||
private bool _isOnDragging;
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要更新信息
|
||||
/// </summary>
|
||||
private bool IsNeedUpdateInfo { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 颜色选取面板宽度
|
||||
/// </summary>
|
||||
private const double ColorPanelWidth = 230;
|
||||
|
||||
/// <summary>
|
||||
/// 颜色选取面板高度
|
||||
/// </summary>
|
||||
private const double ColorPanelHeight = 122;
|
||||
|
||||
/// <summary>
|
||||
/// 预设的颜色(一共18个,两行)
|
||||
/// </summary>
|
||||
private readonly List<string> _colorPresetList = new List<string>
|
||||
{
|
||||
"#f44336",
|
||||
"#e91e63",
|
||||
"#9c27b0",
|
||||
"#673ab7",
|
||||
"#3f51b5",
|
||||
"#2196f3",
|
||||
"#03a9f4",
|
||||
"#00bcd4",
|
||||
"#009688",
|
||||
|
||||
"#4caf50",
|
||||
"#8bc34a",
|
||||
"#cddc39",
|
||||
"#ffeb3b",
|
||||
"#ffc107",
|
||||
"#ff9800",
|
||||
"#ff5722",
|
||||
"#795548",
|
||||
"#9e9e9e"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 颜色范围集合
|
||||
/// </summary>
|
||||
private readonly List<ColorRange> _colorRangeList = new List<ColorRange>
|
||||
{
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(255, 0, 0),
|
||||
Color2 = Color.FromRgb(255, 0, 255)
|
||||
},
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(255, 0, 255),
|
||||
Color2 = Color.FromRgb(0, 0, 255)
|
||||
},
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(0, 0, 255),
|
||||
Color2 = Color.FromRgb(0, 255, 255)
|
||||
},
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(0, 255, 255),
|
||||
Color2 = Color.FromRgb(0, 255, 0)
|
||||
},
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(0, 255, 0),
|
||||
Color2 = Color.FromRgb(255, 255, 0)
|
||||
},
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(255, 255, 0),
|
||||
Color2 = Color.FromRgb(255, 0, 0)
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 颜色分隔集合
|
||||
/// </summary>
|
||||
private readonly List<Color> _colorSeparateList = new List<Color>
|
||||
{
|
||||
Color.FromRgb(255, 0, 0),
|
||||
Color.FromRgb(255, 0, 255),
|
||||
Color.FromRgb(0, 0, 255),
|
||||
Color.FromRgb(0, 255, 255),
|
||||
Color.FromRgb(0, 255, 0),
|
||||
Color.FromRgb(255, 255, 0)
|
||||
};
|
||||
|
||||
#endregion Data
|
||||
|
||||
#region Public Events
|
||||
|
||||
/// <summary>
|
||||
/// 颜色改变事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent SelectedColorChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("SelectedColorChanged", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<Color>>), typeof(ColorPicker));
|
||||
|
||||
/// <summary>
|
||||
/// 颜色改变事件
|
||||
/// </summary>
|
||||
public event EventHandler<FunctionEventArgs<Color>> SelectedColorChanged
|
||||
{
|
||||
add => AddHandler(SelectedColorChangedEvent, value);
|
||||
remove => RemoveHandler(SelectedColorChangedEvent, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent CanceledEvent =
|
||||
EventManager.RegisterRoutedEvent("Canceled", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler), typeof(ColorPicker));
|
||||
|
||||
/// <summary>
|
||||
/// 取消事件
|
||||
/// </summary>
|
||||
public event EventHandler Canceled
|
||||
{
|
||||
add => AddHandler(CanceledEvent, value);
|
||||
remove => RemoveHandler(CanceledEvent, value);
|
||||
}
|
||||
|
||||
#endregion Public Events
|
||||
|
||||
#region Properties
|
||||
|
||||
internal static readonly DependencyProperty ChannelAProperty = DependencyProperty.Register(
|
||||
"ChannelA", typeof(int), typeof(ColorPicker), new PropertyMetadata(255));
|
||||
|
||||
internal int ChannelA
|
||||
{
|
||||
get => (int)GetValue(ChannelAProperty);
|
||||
set => SetValue(ChannelAProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ChannelRProperty = DependencyProperty.Register(
|
||||
"ChannelR", typeof(int), typeof(ColorPicker), new PropertyMetadata(255));
|
||||
|
||||
internal int ChannelR
|
||||
{
|
||||
get => (int)GetValue(ChannelRProperty);
|
||||
set => SetValue(ChannelRProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ChannelGProperty = DependencyProperty.Register(
|
||||
"ChannelG", typeof(int), typeof(ColorPicker), new PropertyMetadata(255));
|
||||
|
||||
internal int ChannelG
|
||||
{
|
||||
get => (int)GetValue(ChannelGProperty);
|
||||
set => SetValue(ChannelGProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ChannelBProperty = DependencyProperty.Register(
|
||||
"ChannelB", typeof(int), typeof(ColorPicker), new PropertyMetadata(255));
|
||||
|
||||
internal int ChannelB
|
||||
{
|
||||
get => (int)GetValue(ChannelBProperty);
|
||||
set => SetValue(ChannelBProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SelectedBrushProperty = DependencyProperty.Register(
|
||||
"SelectedBrush", typeof(SolidColorBrush), typeof(ColorPicker), new PropertyMetadata(Brushes.White,
|
||||
(o, args) =>
|
||||
{
|
||||
var ctl = (ColorPicker)o;
|
||||
var v = (SolidColorBrush)args.NewValue;
|
||||
|
||||
if (ctl.IsNeedUpdateInfo)
|
||||
{
|
||||
ctl.IsNeedUpdateInfo = false;
|
||||
ctl.ChannelR = v.Color.R;
|
||||
ctl.ChannelG = v.Color.G;
|
||||
ctl.ChannelB = v.Color.B;
|
||||
ctl.ChannelA = v.Color.A;
|
||||
ctl.IsNeedUpdateInfo = true;
|
||||
}
|
||||
ctl.UpdateStatus(v.Color);
|
||||
ctl.SelectedBrushWithoutOpacity = new SolidColorBrush(Color.FromRgb(v.Color.R, v.Color.G, v.Color.B));
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// 当前选中的颜色
|
||||
/// </summary>
|
||||
public SolidColorBrush SelectedBrush
|
||||
{
|
||||
get => (SolidColorBrush)GetValue(SelectedBrushProperty);
|
||||
set => SetValue(SelectedBrushProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty SelectedBrushWithoutOpacityProperty = DependencyProperty.Register(
|
||||
"SelectedBrushWithoutOpacity", typeof(SolidColorBrush), typeof(ColorPicker), new PropertyMetadata(Brushes.White));
|
||||
|
||||
internal SolidColorBrush SelectedBrushWithoutOpacity
|
||||
{
|
||||
get => (SolidColorBrush)GetValue(SelectedBrushWithoutOpacityProperty);
|
||||
set => SetValue(SelectedBrushWithoutOpacityProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty BackColorProperty = DependencyProperty.Register(
|
||||
"BackColor", typeof(SolidColorBrush), typeof(ColorPicker), new PropertyMetadata(Brushes.Red));
|
||||
|
||||
internal SolidColorBrush BackColor
|
||||
{
|
||||
get => (SolidColorBrush)GetValue(BackColorProperty);
|
||||
set => SetValue(BackColorProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ShowListProperty = DependencyProperty.Register(
|
||||
"ShowList", typeof(List<bool>), typeof(ColorPicker), new PropertyMetadata(new List<bool>
|
||||
{
|
||||
true, false, false
|
||||
}));
|
||||
|
||||
internal List<bool> ShowList
|
||||
{
|
||||
get => (List<bool>)GetValue(ShowListProperty);
|
||||
set => SetValue(ShowListProperty, value);
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
/// <summary>
|
||||
/// 当前显示的颜色类型
|
||||
/// </summary>
|
||||
private int ColorType
|
||||
{
|
||||
get => _colorType;
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
_colorType = 1;
|
||||
}
|
||||
else if (value > 1)
|
||||
{
|
||||
_colorType = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_colorType = value;
|
||||
}
|
||||
|
||||
var list = new List<bool>();
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
list.Add(false);
|
||||
}
|
||||
list[_colorType] = true;
|
||||
ShowList = list;
|
||||
}
|
||||
}
|
||||
|
||||
public ColorPicker()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Confirm, ButtonConfirm_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Switch, ButtonSwitch_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Cancel, ButtonCancel_OnClick));
|
||||
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (!_isLoaded)
|
||||
{
|
||||
Init();
|
||||
_isLoaded = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_appliedTemplate = false;
|
||||
if (_sliderColor != null)
|
||||
{
|
||||
_sliderColor.ValueChanged -= SliderColor_OnValueChanged;
|
||||
}
|
||||
|
||||
if (_sliderOpacity != null)
|
||||
{
|
||||
_sliderOpacity.ValueChanged -= SliderOpacity_OnValueChanged;
|
||||
}
|
||||
|
||||
_panelColor?.Children.Clear();
|
||||
|
||||
_panelRgb?.RemoveHandler(NumericUpDown.ValueChangedEvent, new EventHandler<FunctionEventArgs<double>>(NumericUpDownRgb_OnValueChanged));
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_borderColor = GetTemplateChild(ElementBorderColor) as Border;
|
||||
_borderDrag = GetTemplateChild(ElementBorderDrag) as Border;
|
||||
_borderPicker = GetTemplateChild(ElementBorderPicker) as Border;
|
||||
_panelColor = GetTemplateChild(ElementPanelColor) as Panel;
|
||||
_sliderColor = GetTemplateChild(ElementSliderColor) as Slider;
|
||||
_sliderOpacity = GetTemplateChild(ElementSliderOpacity) as Slider;
|
||||
_panelRgb = GetTemplateChild(ElementPanelRgb) as Panel;
|
||||
|
||||
if (_sliderColor != null)
|
||||
{
|
||||
_sliderColor.ValueChanged += SliderColor_OnValueChanged;
|
||||
}
|
||||
|
||||
if (_sliderOpacity != null)
|
||||
{
|
||||
_sliderOpacity.ValueChanged += SliderOpacity_OnValueChanged;
|
||||
}
|
||||
|
||||
if (_borderDrag != null)
|
||||
{
|
||||
var behavior = new MouseDragElementBehavior();
|
||||
behavior.DragFinished += MouseDragElementBehavior_OnDragFinished;
|
||||
behavior.DragBegun += MouseDragElementBehavior_OnDragging;
|
||||
behavior.Dragging += MouseDragElementBehavior_OnDragging;
|
||||
var collection = Interaction.GetBehaviors(_borderDrag);
|
||||
collection.Add(behavior);
|
||||
}
|
||||
|
||||
_appliedTemplate = true;
|
||||
if (_isLoaded)
|
||||
{
|
||||
Init();
|
||||
}
|
||||
_panelRgb?.AddHandler(NumericUpDown.ValueChangedEvent, new EventHandler<FunctionEventArgs<double>>(NumericUpDownRgb_OnValueChanged));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
private void Init()
|
||||
{
|
||||
UpdateStatus(SelectedBrush.Color);
|
||||
_panelColor.Children.Clear();
|
||||
foreach (var item in _colorPresetList)
|
||||
{
|
||||
_panelColor.Children.Add(CreateColorButton(item));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建颜色按钮
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Button CreateColorButton(string colorStr)
|
||||
{
|
||||
var color = ColorConverter.ConvertFromString(colorStr) ?? default(Color);
|
||||
var brush = new SolidColorBrush((Color)color);
|
||||
|
||||
var button = new Button
|
||||
{
|
||||
Margin = new Thickness(0, 0, 12, 12),
|
||||
Style = FindResource("ButtonCustom") as Style,
|
||||
Content = new Border
|
||||
{
|
||||
Background = brush,
|
||||
Width = 12,
|
||||
Height = 12,
|
||||
CornerRadius = new CornerRadius(2)
|
||||
}
|
||||
};
|
||||
|
||||
button.Click += (s, e) =>
|
||||
{
|
||||
SelectedBrush = brush;
|
||||
ChannelA = byte.MaxValue;
|
||||
};
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部更新
|
||||
/// </summary>
|
||||
private void UpdateStatus(Color color)
|
||||
{
|
||||
if (_isOnDragging) return;
|
||||
|
||||
var r = color.R;
|
||||
var g = color.G;
|
||||
var b = color.B;
|
||||
var list = new List<byte>
|
||||
{
|
||||
r,
|
||||
g,
|
||||
b
|
||||
};
|
||||
|
||||
var max = list.Max();
|
||||
var min = list.Min();
|
||||
|
||||
if (min == max)
|
||||
{
|
||||
if (!(r == g && b == g))
|
||||
{
|
||||
BackColor = Brushes.Red;
|
||||
IsNeedUpdateInfo = false;
|
||||
if (!_sliderColor.IsMouseOver && !_sliderOpacity.IsMouseOver)
|
||||
{
|
||||
_sliderColor.Value = 0;
|
||||
}
|
||||
IsNeedUpdateInfo = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var maxIndex = list.IndexOf(max);
|
||||
var minIndex = list.IndexOf(min);
|
||||
var commonIndex = 3 - maxIndex - minIndex;
|
||||
if (commonIndex == 3)
|
||||
{
|
||||
BackColor = Brushes.Red;
|
||||
IsNeedUpdateInfo = false;
|
||||
if (!_sliderColor.IsMouseOver && !_sliderOpacity.IsMouseOver)
|
||||
{
|
||||
_sliderColor.Value = 0;
|
||||
}
|
||||
IsNeedUpdateInfo = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var common = list[commonIndex];
|
||||
list[maxIndex] = 255;
|
||||
list[minIndex] = 0;
|
||||
common = (byte)(255 * (min - common) / (double)(min - max));
|
||||
list[commonIndex] = common;
|
||||
BackColor = new SolidColorBrush(Color.FromRgb(list[0], list[1], list[2]));
|
||||
|
||||
list[commonIndex] = 0;
|
||||
var cIndex = _colorSeparateList.IndexOf(Color.FromRgb(list[0], list[1], list[2]));
|
||||
int sub;
|
||||
var direc = 0;
|
||||
if (cIndex < 5 && cIndex > 0)
|
||||
{
|
||||
var nextColorList = _colorSeparateList[cIndex + 1].ToList();
|
||||
var prevColorList = _colorSeparateList[cIndex - 1].ToList();
|
||||
if (nextColorList[minIndex] > 0)
|
||||
{
|
||||
var target = prevColorList[commonIndex];
|
||||
direc = 1;
|
||||
sub = target - common;
|
||||
}
|
||||
else
|
||||
{
|
||||
sub = common;
|
||||
}
|
||||
}
|
||||
else if (cIndex == 0)
|
||||
{
|
||||
sub = common;
|
||||
if (minIndex == 2)
|
||||
{
|
||||
sub = 255 - common;
|
||||
direc = -5;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sub = 255 - common;
|
||||
}
|
||||
var scale = sub / 255.0;
|
||||
var scaleTotal = cIndex - direc + scale;
|
||||
IsNeedUpdateInfo = false;
|
||||
if (!_sliderColor.IsMouseOver && !_sliderOpacity.IsMouseOver)
|
||||
{
|
||||
_sliderColor.Value = scaleTotal;
|
||||
}
|
||||
IsNeedUpdateInfo = true;
|
||||
}
|
||||
}
|
||||
|
||||
var matrix = _borderPicker.RenderTransform.Value;
|
||||
var x = max == 0 ? 0 : (1 - min / (double)max) * ColorPanelWidth;
|
||||
var y = (1 - max / 255.0) * ColorPanelHeight;
|
||||
if (_isNeedUpdatePicker)
|
||||
{
|
||||
_borderPicker.RenderTransform = new MatrixTransform(matrix.M11, matrix.M12, matrix.M21, matrix.M22, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private void SliderColor_OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (!_appliedTemplate || !IsNeedUpdateInfo) return;
|
||||
var index = Math.Min(5, (int)Math.Floor(e.NewValue));
|
||||
var sub = e.NewValue - index;
|
||||
var range = _colorRangeList[index];
|
||||
|
||||
var color = range.GetColor(sub);
|
||||
BackColor = new SolidColorBrush(color);
|
||||
var matrix = _borderPicker.RenderTransform.Value;
|
||||
_isNeedUpdatePicker = false;
|
||||
UpdateColorWhenDrag(new Point(matrix.OffsetX, matrix.OffsetY));
|
||||
_isNeedUpdatePicker = true;
|
||||
}
|
||||
|
||||
private void SliderOpacity_OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (!_appliedTemplate || !IsNeedUpdateInfo) return;
|
||||
var color = SelectedBrush.Color;
|
||||
SelectedBrush = new SolidColorBrush(Color.FromArgb((byte)_sliderOpacity.Value, color.R, color.G, color.B));
|
||||
}
|
||||
|
||||
private void MouseDragElementBehavior_OnDragging(object sender, MouseEventArgs e)
|
||||
{
|
||||
var p = e.GetPosition(_borderColor);
|
||||
_isOnDragging = true;
|
||||
UpdateColorWhenDrag(p);
|
||||
_isOnDragging = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拖动时更新颜色
|
||||
/// </summary>
|
||||
private void UpdateColorWhenDrag(Point p)
|
||||
{
|
||||
var matrix = _borderPicker.RenderTransform.Value;
|
||||
|
||||
if (p.X < 0)
|
||||
{
|
||||
p.X = 0;
|
||||
}
|
||||
else if (p.X > ColorPanelWidth)
|
||||
{
|
||||
p.X = ColorPanelWidth;
|
||||
}
|
||||
|
||||
if (p.Y < 0)
|
||||
{
|
||||
p.Y = 0;
|
||||
}
|
||||
else if (p.Y > ColorPanelHeight)
|
||||
{
|
||||
p.Y = ColorPanelHeight;
|
||||
}
|
||||
|
||||
if (_isNeedUpdatePicker)
|
||||
{
|
||||
_borderPicker.RenderTransform = new MatrixTransform(matrix.M11, matrix.M12, matrix.M21, matrix.M22, p.X, p.Y);
|
||||
}
|
||||
|
||||
var scaleX = p.X / ColorPanelWidth;
|
||||
var scaleY = 1 - p.Y / ColorPanelHeight;
|
||||
|
||||
var colorYLeft = Color.FromRgb((byte)(255 * scaleY), (byte)(255 * scaleY), (byte)(255 * scaleY));
|
||||
var colorYRight = Color.FromRgb((byte)(BackColor.Color.R * scaleY), (byte)(BackColor.Color.G * scaleY), (byte)(BackColor.Color.B * scaleY));
|
||||
|
||||
var subR = colorYLeft.R - colorYRight.R;
|
||||
var subG = colorYLeft.G - colorYRight.G;
|
||||
var subB = colorYLeft.B - colorYRight.B;
|
||||
|
||||
var color = Color.FromArgb((byte)_sliderOpacity.Value, (byte)(colorYLeft.R - subR * scaleX),
|
||||
(byte)(colorYLeft.G - subG * scaleX), (byte)(colorYLeft.B - subB * scaleX));
|
||||
SelectedBrush = new SolidColorBrush(color);
|
||||
}
|
||||
|
||||
private void MouseDragElementBehavior_OnDragFinished(object sender, MouseEventArgs e) => _borderDrag.RenderTransform = new MatrixTransform();
|
||||
|
||||
private void ButtonSwitch_OnClick(object sender, RoutedEventArgs e) => ColorType++;
|
||||
|
||||
// ReSharper disable once UnusedParameter.Local
|
||||
private void NumericUpDownRgb_OnValueChanged(object sender, FunctionEventArgs<double> e)
|
||||
{
|
||||
if (!_appliedTemplate || !IsNeedUpdateInfo) return;
|
||||
if (e.OriginalSource is NumericUpDown ctl && ctl.Tag is string tag)
|
||||
{
|
||||
var color = SelectedBrush.Color;
|
||||
IsNeedUpdateInfo = false;
|
||||
|
||||
switch (tag)
|
||||
{
|
||||
case "R":
|
||||
{
|
||||
SelectedBrush = new SolidColorBrush(Color.FromArgb(color.A, (byte)e.Info, color.G, color.B));
|
||||
break;
|
||||
}
|
||||
case "G":
|
||||
{
|
||||
SelectedBrush = new SolidColorBrush(Color.FromArgb(color.A, color.R, (byte)e.Info, color.B));
|
||||
break;
|
||||
}
|
||||
case "B":
|
||||
{
|
||||
SelectedBrush = new SolidColorBrush(Color.FromArgb(color.A, color.R, color.G, (byte)e.Info));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IsNeedUpdateInfo = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonConfirm_OnClick(object sender, RoutedEventArgs e)
|
||||
=> RaiseEvent(new FunctionEventArgs<Color>(SelectedColorChangedEvent, this)
|
||||
{
|
||||
Info = SelectedBrush.Color
|
||||
});
|
||||
|
||||
private void ButtonCancel_OnClick(object sender, RoutedEventArgs e) => RaiseEvent(new RoutedEventArgs(CanceledEvent));
|
||||
|
||||
public void Dispose() => System.Windows.Window.GetWindow(this)?.Close();
|
||||
|
||||
public bool CanDispose { get; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class Dialog : ContentControl
|
||||
{
|
||||
private Adorner _container;
|
||||
|
||||
public static readonly DependencyProperty IsClosedProperty = DependencyProperty.Register(
|
||||
"IsClosed", typeof(bool), typeof(Dialog), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsClosed
|
||||
{
|
||||
get => (bool) GetValue(IsClosedProperty);
|
||||
internal set => SetValue(IsClosedProperty, value);
|
||||
}
|
||||
|
||||
public Dialog()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Close, (s, e) => Close()));
|
||||
}
|
||||
|
||||
public static Dialog Show<T>() where T : new() => Show(new T());
|
||||
|
||||
public static Dialog Show(object content)
|
||||
{
|
||||
var dialog = new Dialog
|
||||
{
|
||||
Content = content
|
||||
};
|
||||
|
||||
var window = VisualHelper.GetActiveWindow();
|
||||
if (window != null)
|
||||
{
|
||||
var decorator = VisualHelper.GetChild<AdornerDecorator>(window);
|
||||
if (decorator != null)
|
||||
{
|
||||
if (decorator.Child != null)
|
||||
{
|
||||
decorator.Child.IsEnabled = false;
|
||||
}
|
||||
var layer = decorator.AdornerLayer;
|
||||
if (layer != null)
|
||||
{
|
||||
var container = new AdornerContainer(layer)
|
||||
{
|
||||
Child = dialog
|
||||
};
|
||||
dialog._container = container;
|
||||
dialog.IsClosed = false;
|
||||
layer.Add(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
var window = VisualHelper.GetActiveWindow();
|
||||
if (window != null)
|
||||
{
|
||||
var decorator = VisualHelper.GetChild<AdornerDecorator>(window);
|
||||
if (decorator != null)
|
||||
{
|
||||
if (decorator.Child != null)
|
||||
{
|
||||
decorator.Child.IsEnabled = true;
|
||||
}
|
||||
var layer = decorator.AdornerLayer;
|
||||
layer?.Remove(_container);
|
||||
IsClosed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class GotoTop : Button
|
||||
{
|
||||
private Action _gotoTopAction;
|
||||
|
||||
private System.Windows.Controls.ScrollViewer _scrollViewer;
|
||||
|
||||
public static readonly DependencyProperty TargetProperty = DependencyProperty.Register(
|
||||
"Target", typeof(DependencyObject), typeof(GotoTop), new PropertyMetadata(default(DependencyObject)));
|
||||
|
||||
public DependencyObject Target
|
||||
{
|
||||
get => (DependencyObject)GetValue(TargetProperty);
|
||||
set => SetValue(TargetProperty, value);
|
||||
}
|
||||
|
||||
public GotoTop() => Loaded += (s, e) => CreateGotoAction(Target);
|
||||
|
||||
public virtual void CreateGotoAction(DependencyObject obj)
|
||||
{
|
||||
if (_scrollViewer != null)
|
||||
{
|
||||
_scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
|
||||
}
|
||||
_scrollViewer = VisualHelper.GetChild<System.Windows.Controls.ScrollViewer>(obj);
|
||||
if (_scrollViewer != null)
|
||||
{
|
||||
_scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
|
||||
|
||||
if (_scrollViewer is ScrollViewer scrollViewerHandy && Animated && scrollViewerHandy.IsEnableInertia)
|
||||
{
|
||||
_gotoTopAction = () => scrollViewerHandy.ScrollToTopInternal(AnimationTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gotoTopAction = () => _scrollViewer.ScrollToTop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (AutoHiding)
|
||||
{
|
||||
this.Show(e.VerticalOffset >= HidingHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AnimatedProperty = DependencyProperty.Register(
|
||||
"Animated", typeof(bool), typeof(GotoTop), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool Animated
|
||||
{
|
||||
get => (bool) GetValue(AnimatedProperty);
|
||||
set => SetValue(AnimatedProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AnimationTimeProperty = DependencyProperty.Register(
|
||||
"AnimationTime", typeof(double), typeof(GotoTop), new PropertyMetadata(ValueBoxes.Double200Box));
|
||||
|
||||
public double AnimationTime
|
||||
{
|
||||
get => (double) GetValue(AnimationTimeProperty);
|
||||
set => SetValue(AnimationTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty HidingHeightProperty = DependencyProperty.Register(
|
||||
"HidingHeight", typeof(double), typeof(GotoTop), new PropertyMetadata(default(double)));
|
||||
|
||||
public double HidingHeight
|
||||
{
|
||||
get => (double) GetValue(HidingHeightProperty);
|
||||
set => SetValue(HidingHeightProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AutoHidingProperty = DependencyProperty.Register(
|
||||
"AutoHiding", typeof(bool), typeof(GotoTop), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool AutoHiding
|
||||
{
|
||||
get => (bool) GetValue(AutoHidingProperty);
|
||||
set => SetValue(AutoHidingProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
base.OnClick();
|
||||
|
||||
_gotoTopAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class Gravatar : ContentControl
|
||||
{
|
||||
public static readonly DependencyProperty GeneratorProperty = DependencyProperty.Register(
|
||||
"Generator", typeof(IGravatarGenerator), typeof(Gravatar), new PropertyMetadata(new GithubGravatarGenerator()));
|
||||
|
||||
public IGravatarGenerator Generator
|
||||
{
|
||||
get => (IGravatarGenerator) GetValue(GeneratorProperty);
|
||||
set => SetValue(GeneratorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IdProperty = DependencyProperty.Register(
|
||||
"Id", typeof(string), typeof(Gravatar), new PropertyMetadata(default(string), OnIdChanged));
|
||||
|
||||
private static void OnIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (Gravatar) d;
|
||||
if (ctl.Source != null) return;
|
||||
ctl.Content = ctl.Generator.GetGravatar((string)e.NewValue);
|
||||
}
|
||||
|
||||
public string Id
|
||||
{
|
||||
get => (string) GetValue(IdProperty);
|
||||
set => SetValue(IdProperty, value);
|
||||
}
|
||||
|
||||
|
||||
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
|
||||
"Source", typeof(ImageSource), typeof(Gravatar), new PropertyMetadata(default(ImageSource), OnSourceChanged));
|
||||
|
||||
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (Gravatar)d;
|
||||
ctl.Background = new ImageBrush((ImageSource)e.NewValue);
|
||||
}
|
||||
|
||||
public ImageSource Source
|
||||
{
|
||||
get => (ImageSource) GetValue(SourceProperty);
|
||||
set => SetValue(SourceProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息提醒
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementPanelMore, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementGridMain, Type = typeof(Grid))]
|
||||
[TemplatePart(Name = ElementButtonClose, Type = typeof(Button))]
|
||||
public class Growl : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementPanelMore = "PART_PanelMore";
|
||||
private const string ElementGridMain = "PART_GridMain";
|
||||
private const string ElementButtonClose = "PART_ButtonClose";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Panel _panelMore;
|
||||
|
||||
private Grid _gridMain;
|
||||
|
||||
private Button _buttonClose;
|
||||
|
||||
private bool _showCloseButton;
|
||||
|
||||
private bool _staysOpen;
|
||||
|
||||
private int _waitTime = 6;
|
||||
|
||||
/// <summary>
|
||||
/// 计数
|
||||
/// </summary>
|
||||
private int _tickCount;
|
||||
|
||||
/// <summary>
|
||||
/// 关闭计时器
|
||||
/// </summary>
|
||||
private DispatcherTimer _timerClose;
|
||||
|
||||
private static readonly Dictionary<string, Panel> PanelDic = new Dictionary<string, Panel>();
|
||||
|
||||
#endregion Data
|
||||
|
||||
public Growl()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Close, ButtonClose_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Cancel, ButtonCancel_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Confirm, ButtonOk_OnClick));
|
||||
}
|
||||
|
||||
public static void Register(string token, Panel panel)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token) || panel == null) return;
|
||||
PanelDic[token] = panel;
|
||||
InitGrowlPanel(panel);
|
||||
}
|
||||
|
||||
public static void Unregister(string token, Panel panel)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token) || panel == null) return;
|
||||
|
||||
if (PanelDic.ContainsKey(token))
|
||||
{
|
||||
if (ReferenceEquals(PanelDic[token], panel))
|
||||
{
|
||||
PanelDic.Remove(token);
|
||||
panel.ContextMenu = null;
|
||||
panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unregister(Panel panel)
|
||||
{
|
||||
if (panel == null) return;
|
||||
var first = PanelDic.FirstOrDefault(item => ReferenceEquals(panel, item.Value));
|
||||
if (!string.IsNullOrEmpty(first.Key))
|
||||
{
|
||||
PanelDic.Remove(first.Key);
|
||||
panel.ContextMenu = null;
|
||||
panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseEnter(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseEnter(e);
|
||||
|
||||
_buttonClose.Show(_showCloseButton);
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseLeave(e);
|
||||
|
||||
_buttonClose.Collapse();
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_panelMore = GetTemplateChild(ElementPanelMore) as Panel;
|
||||
_gridMain = GetTemplateChild(ElementGridMain) as Grid;
|
||||
_buttonClose = GetTemplateChild(ElementButtonClose) as Button;
|
||||
|
||||
CheckNull();
|
||||
Update();
|
||||
}
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
if (_panelMore == null || _gridMain == null || _buttonClose == null) throw new Exception();
|
||||
}
|
||||
|
||||
private Func<bool, bool> ActionBeforeClose { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息容器
|
||||
/// </summary>
|
||||
public static Panel GrowlPanel { get; set; }
|
||||
|
||||
internal static readonly DependencyProperty CancelStrProperty = DependencyProperty.Register(
|
||||
"CancelStr", typeof(string), typeof(Growl), new PropertyMetadata(default(string)));
|
||||
|
||||
internal static readonly DependencyProperty ConfirmStrProperty = DependencyProperty.Register(
|
||||
"ConfirmStr", typeof(string), typeof(Growl), new PropertyMetadata(default(string)));
|
||||
|
||||
public static readonly DependencyProperty ShowDateTimeProperty = DependencyProperty.Register(
|
||||
"ShowDateTime", typeof(bool), typeof(Growl), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
|
||||
"Message", typeof(string), typeof(Growl), new PropertyMetadata(default(string)));
|
||||
|
||||
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register(
|
||||
"Time", typeof(DateTime), typeof(Growl), new PropertyMetadata(default(DateTime)));
|
||||
|
||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
|
||||
"Icon", typeof(Geometry), typeof(Growl), new PropertyMetadata(default(Geometry)));
|
||||
|
||||
public static readonly DependencyProperty IconBrushProperty = DependencyProperty.Register(
|
||||
"IconBrush", typeof(Brush), typeof(Growl), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register(
|
||||
"Type", typeof(InfoType), typeof(Growl), new PropertyMetadata(default(InfoType)));
|
||||
|
||||
public static readonly DependencyProperty GrowlParentProperty = DependencyProperty.RegisterAttached(
|
||||
"GrowlParent", typeof(bool), typeof(Growl), new PropertyMetadata(ValueBoxes.FalseBox, (o, args) =>
|
||||
{
|
||||
if ((bool)args.NewValue && o is Panel panel)
|
||||
{
|
||||
SetGrowlPanel(panel);
|
||||
}
|
||||
}));
|
||||
|
||||
public static readonly DependencyProperty TokenProperty = DependencyProperty.RegisterAttached(
|
||||
"Token", typeof(string), typeof(Growl), new PropertyMetadata(default(string), OnTokenChanged));
|
||||
|
||||
private static void OnTokenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is Panel panel)
|
||||
{
|
||||
if (e.NewValue == null)
|
||||
{
|
||||
Unregister(panel);
|
||||
}
|
||||
else
|
||||
{
|
||||
Register(e.NewValue.ToString(), panel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetToken(DependencyObject element, string value)
|
||||
=> element.SetValue(TokenProperty, value);
|
||||
|
||||
public static string GetToken(DependencyObject element)
|
||||
=> (string) element.GetValue(TokenProperty);
|
||||
|
||||
public static void SetGrowlParent(DependencyObject element, bool value) => element.SetValue(GrowlParentProperty, value);
|
||||
|
||||
public static bool GetGrowlParent(DependencyObject element) => (bool)element.GetValue(GrowlParentProperty);
|
||||
|
||||
public InfoType Type
|
||||
{
|
||||
get => (InfoType)GetValue(TypeProperty);
|
||||
set => SetValue(TypeProperty, value);
|
||||
}
|
||||
|
||||
internal string CancelStr
|
||||
{
|
||||
get => (string)GetValue(CancelStrProperty);
|
||||
set => SetValue(CancelStrProperty, value);
|
||||
}
|
||||
|
||||
internal string ConfirmStr
|
||||
{
|
||||
get => (string)GetValue(ConfirmStrProperty);
|
||||
set => SetValue(ConfirmStrProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowDateTime
|
||||
{
|
||||
get => (bool)GetValue(ShowDateTimeProperty);
|
||||
set => SetValue(ShowDateTimeProperty, value);
|
||||
}
|
||||
|
||||
public string Message
|
||||
{
|
||||
get => (string)GetValue(MessageProperty);
|
||||
set => SetValue(MessageProperty, value);
|
||||
}
|
||||
|
||||
public DateTime Time
|
||||
{
|
||||
get => (DateTime)GetValue(TimeProperty);
|
||||
set => SetValue(TimeProperty, value);
|
||||
}
|
||||
|
||||
public Geometry Icon
|
||||
{
|
||||
get => (Geometry)GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public Brush IconBrush
|
||||
{
|
||||
get => (Brush)GetValue(IconBrushProperty);
|
||||
set => SetValue(IconBrushProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始计时器
|
||||
/// </summary>
|
||||
private void StartTimer()
|
||||
{
|
||||
_timerClose = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
_timerClose.Tick += delegate
|
||||
{
|
||||
if (IsMouseOver)
|
||||
{
|
||||
_tickCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_tickCount++;
|
||||
if (_tickCount >= _waitTime) Close();
|
||||
};
|
||||
_timerClose.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息容器
|
||||
/// </summary>
|
||||
/// <param name="panel"></param>
|
||||
private static void SetGrowlPanel(Panel panel)
|
||||
{
|
||||
GrowlPanel = panel;
|
||||
InitGrowlPanel(panel);
|
||||
}
|
||||
|
||||
private static void InitGrowlPanel(Panel panel)
|
||||
{
|
||||
var menuItem = new MenuItem
|
||||
{
|
||||
Header = Properties.Langs.Lang.Clear
|
||||
};
|
||||
menuItem.Click += (s, e) =>
|
||||
{
|
||||
foreach (var item in panel.Children.OfType<Growl>())
|
||||
{
|
||||
item.Close();
|
||||
}
|
||||
};
|
||||
panel.ContextMenu = new ContextMenu
|
||||
{
|
||||
Items =
|
||||
{
|
||||
menuItem
|
||||
}
|
||||
};
|
||||
|
||||
PanelElement.SetFluidMoveBehavior(panel, ResourceHelper.GetResource<FluidMoveBehavior>(ResourceToken.BehaviorXY400));
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (ActionBeforeClose != null)
|
||||
{
|
||||
_staysOpen = true;
|
||||
_showCloseButton = false;
|
||||
_panelMore.IsEnabled = true;
|
||||
_panelMore.Show();
|
||||
}
|
||||
|
||||
var transform = new TranslateTransform
|
||||
{
|
||||
X = MaxWidth
|
||||
};
|
||||
_gridMain.RenderTransform = transform;
|
||||
transform.BeginAnimation(TranslateTransform.XProperty, AnimationHelper.CreateAnimation(0));
|
||||
if (!_staysOpen) StartTimer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示信息
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
private static void Show(GrowlInfo growlInfo)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var ctl = new Growl
|
||||
{
|
||||
Message = growlInfo.Message,
|
||||
Time = DateTime.Now,
|
||||
Icon = ResourceHelper.GetResource<Geometry>(growlInfo.IconKey),
|
||||
IconBrush = ResourceHelper.GetResource<Brush>(growlInfo.IconBrushKey),
|
||||
_showCloseButton = growlInfo.ShowCloseButton,
|
||||
ActionBeforeClose = growlInfo.ActionBeforeClose,
|
||||
_staysOpen = growlInfo.StaysOpen,
|
||||
ShowDateTime = growlInfo.ShowDateTime,
|
||||
ConfirmStr = growlInfo.ConfirmStr,
|
||||
CancelStr = growlInfo.CancelStr,
|
||||
Type = growlInfo.Type,
|
||||
_waitTime = Math.Max(growlInfo.WaitTime, 2)
|
||||
};
|
||||
if (!string.IsNullOrEmpty(growlInfo.Token))
|
||||
{
|
||||
if (PanelDic.TryGetValue(growlInfo.Token, out var panel))
|
||||
{
|
||||
panel?.Children.Insert(0, ctl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GrowlPanel.Children.Insert(0, ctl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Success(string message, string token = "") => Success(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Success(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.SuccessGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.SuccessBrush;
|
||||
growlInfo.Type = InfoType.Success;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Info(string message, string token = "") => Info(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Info(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.InfoGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.InfoBrush;
|
||||
growlInfo.Type = InfoType.Info;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Warning(string message, string token = "") => Warning(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Warning(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.WarningGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.WarningBrush;
|
||||
growlInfo.Type = InfoType.Warning;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Error(string message, string token = "") => Error(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Error(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.ErrorGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.DangerBrush;
|
||||
growlInfo.StaysOpen = true;
|
||||
growlInfo.Type = InfoType.Error;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 严重
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Fatal(string message, string token = "") => Fatal(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 严重
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Fatal(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.FatalGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.PrimaryTextBrush;
|
||||
growlInfo.StaysOpen = true;
|
||||
growlInfo.ShowCloseButton = false;
|
||||
growlInfo.Type = InfoType.Fatal;
|
||||
if (GrowlPanel.ContextMenu != null) GrowlPanel.ContextMenu.Opacity = 0;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 询问
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="actionBeforeClose"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Ask(string message, Func<bool, bool> actionBeforeClose, string token = "") => Ask(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
ActionBeforeClose = actionBeforeClose,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 询问
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Ask(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.AskGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.AccentBrush;
|
||||
growlInfo.StaysOpen = true;
|
||||
growlInfo.ShowCloseButton = false;
|
||||
growlInfo.Type = InfoType.Ask;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
private void ButtonClose_OnClick(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
/// <summary>
|
||||
/// 关闭
|
||||
/// </summary>
|
||||
private void Close()
|
||||
{
|
||||
_timerClose?.Stop();
|
||||
var transform = new TranslateTransform();
|
||||
_gridMain.RenderTransform = transform;
|
||||
var animation = AnimationHelper.CreateAnimation(MaxWidth);
|
||||
animation.Completed += (s, e) =>
|
||||
{
|
||||
if (Parent is Panel panel)
|
||||
{
|
||||
panel.Children.Remove(this);
|
||||
}
|
||||
};
|
||||
transform.BeginAnimation(TranslateTransform.XProperty, animation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
public static void Clear(string token = "")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
if (PanelDic.TryGetValue(token, out var panel))
|
||||
{
|
||||
Clear(panel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Clear(GrowlPanel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除
|
||||
/// </summary>
|
||||
/// <param name="panel"></param>
|
||||
private static void Clear(Panel panel)
|
||||
{
|
||||
panel.Children.Clear();
|
||||
if (panel.ContextMenu != null)
|
||||
{
|
||||
panel.ContextMenu.IsOpen = false;
|
||||
panel.ContextMenu.Opacity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonCancel_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ActionBeforeClose?.Invoke(false) == true)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonOk_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ActionBeforeClose?.Invoke(true) == true)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Interop;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class NotifyIcon : FrameworkElement, IDisposable
|
||||
{
|
||||
private bool _added;
|
||||
|
||||
private readonly object _syncObj = new object();
|
||||
|
||||
private readonly int _id;
|
||||
|
||||
private static int NextId;
|
||||
|
||||
private ImageSource _icon;
|
||||
|
||||
private IntPtr _iconCurrentHandle;
|
||||
|
||||
private IntPtr _iconDefaultHandle;
|
||||
|
||||
private IconHandle _iconHandle;
|
||||
|
||||
private const int WmTrayMouseMessage = NativeMethods.WM_USER + 1024;
|
||||
|
||||
private string _windowClassName;
|
||||
|
||||
private int _wmTaskbarCreated;
|
||||
|
||||
private IntPtr _messageWindowHandle;
|
||||
|
||||
private readonly WndProc _callback;
|
||||
|
||||
private Popup _contextContent;
|
||||
|
||||
private bool _doubleClick;
|
||||
|
||||
private DispatcherTimer _dispatcherTimer;
|
||||
|
||||
private bool _isTransparent;
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
static NotifyIcon()
|
||||
{
|
||||
VisibilityProperty.OverrideMetadata(typeof(NotifyIcon), new PropertyMetadata(Visibility.Visible, OnVisibilityChanged));
|
||||
}
|
||||
|
||||
private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NotifyIcon) d;
|
||||
var v = (Visibility)e.NewValue;
|
||||
|
||||
if (v == Visibility.Visible)
|
||||
{
|
||||
if (ctl._iconCurrentHandle == IntPtr.Zero)
|
||||
{
|
||||
ctl.OnIconChanged();
|
||||
}
|
||||
ctl.UpdateIcon(true);
|
||||
}
|
||||
else if(ctl._iconCurrentHandle != IntPtr.Zero)
|
||||
{
|
||||
ctl.UpdateIcon(false);
|
||||
}
|
||||
}
|
||||
|
||||
public NotifyIcon()
|
||||
{
|
||||
_id = ++NextId;
|
||||
_callback = Callback;
|
||||
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
RegisterClass();
|
||||
if (Visibility == Visibility.Visible)
|
||||
{
|
||||
OnIconChanged();
|
||||
UpdateIcon(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (Application.Current != null) Application.Current.Exit += (s, e) => Dispose();
|
||||
}
|
||||
|
||||
~NotifyIcon()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
"Text", typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string)));
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string) GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
|
||||
"Icon", typeof(ImageSource), typeof(NotifyIcon), new PropertyMetadata(default(ImageSource), OnIconChanged));
|
||||
|
||||
private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NotifyIcon)d;
|
||||
ctl._icon = (ImageSource)e.NewValue;
|
||||
ctl.OnIconChanged();
|
||||
}
|
||||
|
||||
public ImageSource Icon
|
||||
{
|
||||
get => (ImageSource)GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ContextContentProperty = DependencyProperty.Register(
|
||||
"ContextContent", typeof(object), typeof(NotifyIcon), new PropertyMetadata(default(object)));
|
||||
|
||||
public object ContextContent
|
||||
{
|
||||
get => GetValue(ContextContentProperty);
|
||||
set => SetValue(ContextContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty BlinkIntervalProperty = DependencyProperty.Register(
|
||||
"BlinkInterval", typeof(TimeSpan), typeof(NotifyIcon), new PropertyMetadata(TimeSpan.FromMilliseconds(500), OnBlinkIntervalChanged));
|
||||
|
||||
private static void OnBlinkIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NotifyIcon)d;
|
||||
if (ctl._dispatcherTimer != null)
|
||||
{
|
||||
ctl._dispatcherTimer.Interval = (TimeSpan) e.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan BlinkInterval
|
||||
{
|
||||
get => (TimeSpan) GetValue(BlinkIntervalProperty);
|
||||
set => SetValue(BlinkIntervalProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsBlinkProperty = DependencyProperty.Register(
|
||||
"IsBlink", typeof(bool), typeof(NotifyIcon), new PropertyMetadata(ValueBoxes.FalseBox, OnIsBlinkChanged));
|
||||
|
||||
private static void OnIsBlinkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NotifyIcon)d;
|
||||
if (ctl.Visibility != Visibility.Visible) return;
|
||||
if ((bool) e.NewValue)
|
||||
{
|
||||
if (ctl._dispatcherTimer == null)
|
||||
{
|
||||
ctl._dispatcherTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = ctl.BlinkInterval
|
||||
};
|
||||
ctl._dispatcherTimer.Tick += ctl.DispatcherTimer_Tick;
|
||||
}
|
||||
ctl._dispatcherTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctl._dispatcherTimer?.Stop();
|
||||
ctl._dispatcherTimer = null;
|
||||
ctl.UpdateIcon(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatcherTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (Visibility != Visibility.Visible || _iconCurrentHandle == IntPtr.Zero) return;
|
||||
UpdateIcon(true, !_isTransparent);
|
||||
}
|
||||
|
||||
public bool IsBlink
|
||||
{
|
||||
get => (bool) GetValue(IsBlinkProperty);
|
||||
set => SetValue(IsBlinkProperty, value);
|
||||
}
|
||||
|
||||
private void OnIconChanged()
|
||||
{
|
||||
if (_icon != null)
|
||||
{
|
||||
IconHelper.GetIconHandlesFromImageSource(_icon, out _, out _iconHandle);
|
||||
_iconCurrentHandle = _iconHandle.CriticalGetHandle();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_iconDefaultHandle == IntPtr.Zero)
|
||||
{
|
||||
IconHelper.GetDefaultIconHandles(out _, out _iconHandle);
|
||||
_iconDefaultHandle = _iconHandle.CriticalGetHandle();
|
||||
}
|
||||
_iconCurrentHandle = _iconDefaultHandle;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIcon(bool showIconInTray, bool isTransparent = false)
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (DesignerHelper.IsInDesignMode) return;
|
||||
|
||||
_isTransparent = isTransparent;
|
||||
var data = new NOTIFYICONDATA
|
||||
{
|
||||
uCallbackMessage = WmTrayMouseMessage,
|
||||
uFlags = NativeMethods.NIF_MESSAGE | NativeMethods.NIF_ICON | NativeMethods.NIF_TIP,
|
||||
hWnd = _messageWindowHandle,
|
||||
uID = _id,
|
||||
dwInfoFlags = NativeMethods.NIF_TIP,
|
||||
hIcon = isTransparent ? IntPtr.Zero : _iconCurrentHandle,
|
||||
szTip = Text
|
||||
};
|
||||
|
||||
if (showIconInTray)
|
||||
{
|
||||
if (!_added)
|
||||
{
|
||||
UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_ADD, data);
|
||||
_added = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_MODIFY, data);
|
||||
}
|
||||
}
|
||||
else if (_added)
|
||||
{
|
||||
UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_DELETE, data);
|
||||
_added = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterClass()
|
||||
{
|
||||
_windowClassName = $"HandyControl.Controls.NotifyIcon{Guid.NewGuid()}";
|
||||
var wndclass = new WNDCLASS
|
||||
{
|
||||
style = 0,
|
||||
lpfnWndProc = _callback,
|
||||
cbClsExtra = 0,
|
||||
cbWndExtra = 0,
|
||||
hInstance = IntPtr.Zero,
|
||||
hIcon = IntPtr.Zero,
|
||||
hCursor = IntPtr.Zero,
|
||||
hbrBackground = IntPtr.Zero,
|
||||
lpszMenuName = "",
|
||||
lpszClassName = _windowClassName
|
||||
};
|
||||
|
||||
UnsafeNativeMethods.RegisterClass(wndclass);
|
||||
_wmTaskbarCreated = NativeMethods.RegisterWindowMessage("TaskbarCreated");
|
||||
_messageWindowHandle = UnsafeNativeMethods.CreateWindowEx(0, _windowClassName, "", 0, 0, 0, 1, 1,
|
||||
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
private IntPtr Callback(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam)
|
||||
{
|
||||
if (IsLoaded)
|
||||
{
|
||||
if (msg == _wmTaskbarCreated)
|
||||
{
|
||||
UpdateIcon(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (lparam.ToInt64())
|
||||
{
|
||||
case NativeMethods.WM_LBUTTONDBLCLK:
|
||||
WmMouseDown(MouseButton.Left, 2);
|
||||
break;
|
||||
case NativeMethods.WM_LBUTTONUP:
|
||||
WmMouseUp(MouseButton.Left);
|
||||
break;
|
||||
case NativeMethods.WM_RBUTTONUP:
|
||||
ShowContextMenu();
|
||||
WmMouseUp(MouseButton.Right);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return UnsafeNativeMethods.DefWindowProc(hWnd, msg, wparam, lparam);
|
||||
}
|
||||
|
||||
private void WmMouseDown(MouseButton button, int clicks)
|
||||
{
|
||||
if (clicks == 2)
|
||||
{
|
||||
RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button)
|
||||
{
|
||||
RoutedEvent = MouseDoubleClickEvent
|
||||
});
|
||||
_doubleClick = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void WmMouseUp(MouseButton button)
|
||||
{
|
||||
if (!_doubleClick && button == MouseButton.Left)
|
||||
{
|
||||
RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button)
|
||||
{
|
||||
RoutedEvent = ClickEvent
|
||||
});
|
||||
}
|
||||
_doubleClick = false;
|
||||
}
|
||||
|
||||
private void ShowContextMenu()
|
||||
{
|
||||
var point = new POINT();
|
||||
UnsafeNativeMethods.GetCursorPos(point);
|
||||
|
||||
if (ContextContent != null)
|
||||
{
|
||||
if (_contextContent == null)
|
||||
{
|
||||
_contextContent = new Popup
|
||||
{
|
||||
Placement = PlacementMode.AbsolutePoint,
|
||||
AllowsTransparency = true,
|
||||
StaysOpen = false
|
||||
};
|
||||
}
|
||||
|
||||
_contextContent.HorizontalOffset = point.x;
|
||||
_contextContent.VerticalOffset = point.y;
|
||||
_contextContent.Child = new ContentControl
|
||||
{
|
||||
Content = ContextContent
|
||||
};
|
||||
_contextContent.IsOpen = true;
|
||||
var handle = IntPtr.Zero;
|
||||
var hwndSource = (HwndSource)PresentationSource.FromVisual(_contextContent.Child);
|
||||
if (hwndSource != null)
|
||||
{
|
||||
handle = hwndSource.Handle;
|
||||
}
|
||||
UnsafeNativeMethods.SetForegroundWindow(handle);
|
||||
}
|
||||
else if (ContextMenu != null)
|
||||
{
|
||||
ContextMenu.Placement = PlacementMode.AbsolutePoint;
|
||||
ContextMenu.HorizontalOffset = point.x;
|
||||
ContextMenu.VerticalOffset = point.y;
|
||||
ContextMenu.IsOpen = true;
|
||||
|
||||
var handle = IntPtr.Zero;
|
||||
var hwndSource = (HwndSource)PresentationSource.FromVisual(ContextMenu);
|
||||
if (hwndSource != null)
|
||||
{
|
||||
handle = hwndSource.Handle;
|
||||
}
|
||||
UnsafeNativeMethods.SetForegroundWindow(handle);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent ClickEvent =
|
||||
EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler), typeof(NotifyIcon));
|
||||
|
||||
public event RoutedEventHandler Click
|
||||
{
|
||||
add => AddHandler(ClickEvent, value);
|
||||
remove => RemoveHandler(ClickEvent, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent MouseDoubleClickEvent =
|
||||
EventManager.RegisterRoutedEvent("MouseDoubleClick", RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler), typeof(NotifyIcon));
|
||||
|
||||
public event RoutedEventHandler MouseDoubleClick
|
||||
{
|
||||
add => AddHandler(MouseDoubleClickEvent, value);
|
||||
remove => RemoveHandler(MouseDoubleClickEvent, value);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
if (_dispatcherTimer != null && IsBlink)
|
||||
{
|
||||
_dispatcherTimer.Stop();
|
||||
}
|
||||
UpdateIcon(false);
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void CloseContextControl()
|
||||
{
|
||||
if (_contextContent != null)
|
||||
{
|
||||
_contextContent.IsOpen = false;
|
||||
}
|
||||
else if (ContextMenu != null)
|
||||
{
|
||||
ContextMenu.IsOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 页码
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementButtonLeft, Type = typeof(Button))]
|
||||
[TemplatePart(Name = ElementButtonRight, Type = typeof(Button))]
|
||||
[TemplatePart(Name = ElementButtonFirst, Type = typeof(RadioButton))]
|
||||
[TemplatePart(Name = ElementTextBlockLeft, Type = typeof(TextBlock))]
|
||||
[TemplatePart(Name = ElementPanelMain, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementTextBlockRight, Type = typeof(TextBlock))]
|
||||
[TemplatePart(Name = ElementButtonLast, Type = typeof(RadioButton))]
|
||||
public class Pagination : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementButtonLeft = "PART_ButtonLeft";
|
||||
private const string ElementButtonRight = "PART_ButtonRight";
|
||||
private const string ElementButtonFirst = "PART_ButtonFirst";
|
||||
private const string ElementTextBlockLeft = "PART_TextBlockLeft";
|
||||
private const string ElementPanelMain = "PART_PanelMain";
|
||||
private const string ElementTextBlockRight = "PART_TextBlockRight";
|
||||
private const string ElementButtonLast = "PART_ButtonLast";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Button _buttonLeft;
|
||||
private Button _buttonRight;
|
||||
private RadioButton _buttonFirst;
|
||||
private TextBlock _textBlockLeft;
|
||||
private Panel _panelMain;
|
||||
private TextBlock _textBlockRight;
|
||||
private RadioButton _buttonLast;
|
||||
|
||||
private bool _appliedTemplate;
|
||||
|
||||
#endregion Data
|
||||
|
||||
#region Public Events
|
||||
|
||||
/// <summary>
|
||||
/// 页面更新事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent PageUpdatedEvent =
|
||||
EventManager.RegisterRoutedEvent("PageUpdated", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<int>>), typeof(Pagination));
|
||||
|
||||
/// <summary>
|
||||
/// 页面更新事件
|
||||
/// </summary>
|
||||
public event EventHandler<FunctionEventArgs<int>> PageUpdated
|
||||
{
|
||||
add => AddHandler(PageUpdatedEvent, value);
|
||||
remove => RemoveHandler(PageUpdatedEvent, value);
|
||||
}
|
||||
|
||||
#endregion Public Events
|
||||
|
||||
public Pagination()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Prev, ButtonPrev_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Next, ButtonNext_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Selected, ToggleButton_OnChecked));
|
||||
}
|
||||
|
||||
#region Public Properties
|
||||
|
||||
#region MaxPageCount
|
||||
|
||||
/// <summary>
|
||||
/// 最大页数
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty MaxPageCountProperty = DependencyProperty.Register(
|
||||
"MaxPageCount", typeof(int), typeof(Pagination), new PropertyMetadata(1, (o, args) =>
|
||||
{
|
||||
if (o is Pagination pagination && args.NewValue is int value)
|
||||
{
|
||||
if (pagination.PageIndex > pagination.MaxPageCount)
|
||||
{
|
||||
pagination.PageIndex = pagination.MaxPageCount;
|
||||
}
|
||||
|
||||
pagination.Show(value > 1);
|
||||
pagination.Update();
|
||||
}
|
||||
}, (o, value) =>
|
||||
{
|
||||
if (!(o is Pagination)) return 1;
|
||||
var intValue = (int)value;
|
||||
if (intValue < 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return intValue;
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// 最大页数
|
||||
/// </summary>
|
||||
public int MaxPageCount
|
||||
{
|
||||
get => (int)GetValue(MaxPageCountProperty);
|
||||
set => SetValue(MaxPageCountProperty, value);
|
||||
}
|
||||
|
||||
#endregion MaxPageCount
|
||||
|
||||
#region DataCountPerPage
|
||||
|
||||
/// <summary>
|
||||
/// 每页的数据量
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DataCountPerPageProperty = DependencyProperty.Register(
|
||||
"DataCountPerPage", typeof(int), typeof(Pagination), new PropertyMetadata(20, (o, args) =>
|
||||
{
|
||||
if (o is Pagination pagination)
|
||||
{
|
||||
pagination.Update();
|
||||
}
|
||||
}, (o, value) =>
|
||||
{
|
||||
if (!(o is Pagination)) return 1;
|
||||
var intValue = (int)value;
|
||||
if (intValue < 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return intValue;
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// 每页的数据量
|
||||
/// </summary>
|
||||
public int DataCountPerPage
|
||||
{
|
||||
get => (int)GetValue(DataCountPerPageProperty);
|
||||
set => SetValue(DataCountPerPageProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PageIndex
|
||||
|
||||
/// <summary>
|
||||
/// 当前页
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PageIndexProperty = DependencyProperty.Register(
|
||||
"PageIndex", typeof(int), typeof(Pagination), new PropertyMetadata(1, (o, args) =>
|
||||
{
|
||||
if (o is Pagination pagination && args.NewValue is int value)
|
||||
{
|
||||
pagination.Update();
|
||||
pagination.RaiseEvent(new FunctionEventArgs<int>(PageUpdatedEvent, pagination)
|
||||
{
|
||||
Info = value
|
||||
});
|
||||
}
|
||||
}, (o, value) =>
|
||||
{
|
||||
if (!(o is Pagination pagination)) return 1;
|
||||
var intValue = (int)value;
|
||||
if (intValue < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (intValue > pagination.MaxPageCount) return pagination.MaxPageCount;
|
||||
return intValue;
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// 当前页
|
||||
/// </summary>
|
||||
public int PageIndex
|
||||
{
|
||||
get => (int)GetValue(PageIndexProperty);
|
||||
set => SetValue(PageIndexProperty, value);
|
||||
}
|
||||
|
||||
#endregion PageIndex
|
||||
|
||||
#region MaxPageInterval
|
||||
|
||||
/// <summary>
|
||||
/// 表示当前选中的按钮距离左右两个方向按钮的最大间隔(4表示间隔4个按钮,如果超过则用省略号表示)
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty MaxPageIntervalProperty = DependencyProperty.Register(
|
||||
"MaxPageInterval", typeof(int), typeof(Pagination), new PropertyMetadata(3, (o, args) =>
|
||||
{
|
||||
if (o is Pagination pagination)
|
||||
{
|
||||
pagination.Update();
|
||||
}
|
||||
}), value =>
|
||||
{
|
||||
var intValue = (int)value;
|
||||
return intValue >= 0;
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 表示当前选中的按钮距离左右两个方向按钮的最大间隔(4表示间隔4个按钮,如果超过则用省略号表示)
|
||||
/// </summary>
|
||||
public int MaxPageInterval
|
||||
{
|
||||
get => (int)GetValue(MaxPageIntervalProperty);
|
||||
set => SetValue(MaxPageIntervalProperty, value);
|
||||
}
|
||||
|
||||
#endregion MaxPageInterval
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_appliedTemplate = false;
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_buttonLeft = GetTemplateChild(ElementButtonLeft) as Button;
|
||||
_buttonRight = GetTemplateChild(ElementButtonRight) as Button;
|
||||
_buttonFirst = GetTemplateChild(ElementButtonFirst) as RadioButton;
|
||||
_textBlockLeft = GetTemplateChild(ElementTextBlockLeft) as TextBlock;
|
||||
_panelMain = GetTemplateChild(ElementPanelMain) as Panel;
|
||||
_textBlockRight = GetTemplateChild(ElementTextBlockRight) as TextBlock;
|
||||
_buttonLast = GetTemplateChild(ElementButtonLast) as RadioButton;
|
||||
|
||||
CheckNull();
|
||||
|
||||
_appliedTemplate = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
if (_buttonLeft == null || _buttonRight == null || _buttonFirst == null ||
|
||||
_textBlockLeft == null || _panelMain == null || _textBlockRight == null ||
|
||||
_buttonLast == null) throw new Exception();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
_buttonLeft.IsEnabled = PageIndex > 1;
|
||||
_buttonRight.IsEnabled = PageIndex < MaxPageCount;
|
||||
if (MaxPageInterval == 0)
|
||||
{
|
||||
_buttonFirst.Collapse();
|
||||
_buttonLast.Collapse();
|
||||
_textBlockLeft.Collapse();
|
||||
_textBlockRight.Collapse();
|
||||
_panelMain.Children.Clear();
|
||||
var selectButton = CreateButton(PageIndex);
|
||||
_panelMain.Children.Add(selectButton);
|
||||
selectButton.IsChecked = true;
|
||||
return;
|
||||
}
|
||||
_buttonFirst.Show();
|
||||
_buttonLast.Show();
|
||||
_textBlockLeft.Show();
|
||||
_textBlockRight.Show();
|
||||
|
||||
//更新最后一页
|
||||
if (MaxPageCount == 1)
|
||||
{
|
||||
_buttonLast.Collapse();
|
||||
}
|
||||
else
|
||||
{
|
||||
_buttonLast.Show();
|
||||
_buttonLast.Tag = MaxPageCount.ToString();
|
||||
}
|
||||
|
||||
//更新省略号
|
||||
var right = MaxPageCount - PageIndex;
|
||||
var left = PageIndex - 1;
|
||||
_textBlockRight.Show(right > MaxPageInterval);
|
||||
_textBlockLeft.Show(left > MaxPageInterval);
|
||||
|
||||
//更新中间部分
|
||||
_panelMain.Children.Clear();
|
||||
if (PageIndex > 1 && PageIndex < MaxPageCount)
|
||||
{
|
||||
var selectButton = CreateButton(PageIndex);
|
||||
_panelMain.Children.Add(selectButton);
|
||||
selectButton.IsChecked = true;
|
||||
}
|
||||
else if (PageIndex == 1)
|
||||
{
|
||||
_buttonFirst.IsChecked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_buttonLast.IsChecked = true;
|
||||
}
|
||||
|
||||
var sub = PageIndex;
|
||||
for (int i = 0; i < MaxPageInterval - 1; i++)
|
||||
{
|
||||
if (--sub > 1)
|
||||
{
|
||||
_panelMain.Children.Insert(0, CreateButton(sub));
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
var add = PageIndex;
|
||||
for (int i = 0; i < MaxPageInterval - 1; i++)
|
||||
{
|
||||
if (++add < MaxPageCount)
|
||||
{
|
||||
_panelMain.Children.Add(CreateButton(add));
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonPrev_OnClick(object sender, RoutedEventArgs e) => PageIndex--;
|
||||
|
||||
private void ButtonNext_OnClick(object sender, RoutedEventArgs e) => PageIndex++;
|
||||
|
||||
private RadioButton CreateButton(int page)
|
||||
{
|
||||
return new RadioButton
|
||||
{
|
||||
Style = ResourceHelper.GetResource<Style>(ResourceToken.PaginationButtonStyle),
|
||||
Tag = page.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!(e.OriginalSource is RadioButton button)) return;
|
||||
if (button.IsChecked == false) return;
|
||||
PageIndex = int.Parse(button.Tag.ToString());
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class RadioGroup : ItemsControl
|
||||
{
|
||||
protected override DependencyObject GetContainerForItemOverride() => new RadioButton();
|
||||
|
||||
protected override bool IsItemItsOwnContainerOverride(object item) => item is RadioButton;
|
||||
|
||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
||||
"Orientation", typeof(Orientation), typeof(RadioGroup), new PropertyMetadata(default(Orientation)));
|
||||
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => (Orientation)GetValue(OrientationProperty);
|
||||
set => SetValue(OrientationProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ItemStyleSelectorProperty = DependencyProperty.Register(
|
||||
"ItemStyleSelector", typeof(StyleSelector), typeof(RadioGroup), new PropertyMetadata(new RadioGroupItemStyleSelector()));
|
||||
|
||||
public StyleSelector ItemStyleSelector
|
||||
{
|
||||
get => (StyleSelector)GetValue(ItemStyleSelectorProperty);
|
||||
set => SetValue(ItemStyleSelectorProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
|
||||
{
|
||||
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
|
||||
|
||||
var count = Items.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var item = (RadioButton)Items[i];
|
||||
item.Style = ItemStyleSelector?.SelectStyle(item, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class ScrollViewer : System.Windows.Controls.ScrollViewer
|
||||
{
|
||||
private double _totalVerticalOffset;
|
||||
|
||||
private double _totalHorizontalOffset;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
/// <summary>
|
||||
/// 滚动方向
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
||||
"Orientation", typeof(Orientation), typeof(ScrollViewer), new PropertyMetadata(Orientation.Vertical));
|
||||
|
||||
/// <summary>
|
||||
/// 滚动方向
|
||||
/// </summary>
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => (Orientation) GetValue(OrientationProperty);
|
||||
set => SetValue(OrientationProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否响应鼠标滚轮操作
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CanMouseWheelProperty = DependencyProperty.Register(
|
||||
"CanMouseWheel", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
/// <summary>
|
||||
/// 是否响应鼠标滚轮操作
|
||||
/// </summary>
|
||||
public bool CanMouseWheel
|
||||
{
|
||||
get => (bool) GetValue(CanMouseWheelProperty);
|
||||
set => SetValue(CanMouseWheelProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
if (!CanMouseWheel) return;
|
||||
|
||||
if (!IsEnableInertia)
|
||||
{
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
base.OnMouseWheel(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
_totalHorizontalOffset = HorizontalOffset;
|
||||
CurrentHorizontalOffset = HorizontalOffset;
|
||||
_totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
|
||||
CurrentHorizontalOffset = _totalHorizontalOffset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
e.Handled = true;
|
||||
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalVerticalOffset = VerticalOffset;
|
||||
CurrentVerticalOffset = VerticalOffset;
|
||||
}
|
||||
_totalVerticalOffset = Math.Min(Math.Max(0, _totalVerticalOffset - e.Delta), ScrollableHeight);
|
||||
ScrollToVerticalOffsetInternal(_totalVerticalOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalHorizontalOffset = HorizontalOffset;
|
||||
CurrentHorizontalOffset = HorizontalOffset;
|
||||
}
|
||||
_totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
|
||||
ScrollToHorizontalOffsetInternal(_totalHorizontalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ScrollToTopInternal(double milliseconds = 500)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalVerticalOffset = VerticalOffset;
|
||||
CurrentVerticalOffset = VerticalOffset;
|
||||
}
|
||||
ScrollToVerticalOffsetInternal(0, milliseconds);
|
||||
}
|
||||
|
||||
internal void ScrollToVerticalOffsetInternal(double offset, double milliseconds = 500)
|
||||
{
|
||||
var animation = AnimationHelper.CreateAnimation(offset, milliseconds);
|
||||
animation.EasingFunction = new CubicEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
};
|
||||
animation.FillBehavior = FillBehavior.Stop;
|
||||
animation.Completed += (s, e1) =>
|
||||
{
|
||||
CurrentVerticalOffset = offset;
|
||||
_isRunning = false;
|
||||
};
|
||||
_isRunning = true;
|
||||
BeginAnimation(CurrentVerticalOffsetProperty, animation);
|
||||
}
|
||||
|
||||
internal void ScrollToHorizontalOffsetInternal(double offset, double milliseconds = 500)
|
||||
{
|
||||
var animation = AnimationHelper.CreateAnimation(offset, milliseconds);
|
||||
animation.EasingFunction = new CubicEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
};
|
||||
animation.FillBehavior = FillBehavior.Stop;
|
||||
animation.Completed += (s, e1) =>
|
||||
{
|
||||
CurrentHorizontalOffset = offset;
|
||||
_isRunning = false;
|
||||
};
|
||||
_isRunning = true;
|
||||
BeginAnimation(CurrentHorizontalOffsetProperty, animation);
|
||||
}
|
||||
|
||||
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) =>
|
||||
IsPenetrating ? null : base.HitTestCore(hitTestParameters);
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持惯性
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsEnableInertiaProperty = DependencyProperty.RegisterAttached(
|
||||
"IsEnableInertia", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public static void SetIsEnableInertia(DependencyObject element, bool value)
|
||||
{
|
||||
element.SetValue(IsEnableInertiaProperty, value);
|
||||
}
|
||||
|
||||
public static bool GetIsEnableInertia(DependencyObject element)
|
||||
{
|
||||
return (bool) element.GetValue(IsEnableInertiaProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持惯性
|
||||
/// </summary>
|
||||
public bool IsEnableInertia
|
||||
{
|
||||
get => (bool)GetValue(IsEnableInertiaProperty);
|
||||
set => SetValue(IsEnableInertiaProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控件是否可以穿透点击
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsPenetratingProperty = DependencyProperty.RegisterAttached(
|
||||
"IsPenetrating", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 控件是否可以穿透点击
|
||||
/// </summary>
|
||||
public bool IsPenetrating
|
||||
{
|
||||
get => (bool)GetValue(IsPenetratingProperty);
|
||||
set => SetValue(IsPenetratingProperty, value);
|
||||
}
|
||||
|
||||
public static void SetIsPenetrating(DependencyObject element, bool value)
|
||||
{
|
||||
element.SetValue(IsPenetratingProperty, value);
|
||||
}
|
||||
|
||||
public static bool GetIsPenetrating(DependencyObject element)
|
||||
{
|
||||
return (bool) element.GetValue(IsPenetratingProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前垂直滚动偏移
|
||||
/// </summary>
|
||||
private static readonly DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register(
|
||||
"CurrentVerticalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(default(double), OnCurrentVerticalOffsetChanged));
|
||||
|
||||
private static void OnCurrentVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ScrollViewer ctl && e.NewValue is double v)
|
||||
{
|
||||
ctl.ScrollToVerticalOffset(v);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前垂直滚动偏移
|
||||
/// </summary>
|
||||
private double CurrentVerticalOffset
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
get => (double) GetValue(CurrentVerticalOffsetProperty);
|
||||
set => SetValue(CurrentVerticalOffsetProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前水平滚动偏移
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CurrentHorizontalOffsetProperty = DependencyProperty.Register(
|
||||
"CurrentHorizontalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(default(double), OnCurrentHorizontalOffsetChanged));
|
||||
|
||||
private static void OnCurrentHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ScrollViewer ctl && e.NewValue is double v)
|
||||
{
|
||||
ctl.ScrollToHorizontalOffset(v);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前水平滚动偏移
|
||||
/// </summary>
|
||||
public double CurrentHorizontalOffset
|
||||
{
|
||||
get => (double) GetValue(CurrentHorizontalOffsetProperty);
|
||||
set => SetValue(CurrentHorizontalOffsetProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[ContentProperty(nameof(Status))]
|
||||
public class Shield : ButtonBase
|
||||
{
|
||||
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
|
||||
"Subject", typeof(string), typeof(Shield), new PropertyMetadata(default(string)));
|
||||
|
||||
public string Subject
|
||||
{
|
||||
get => (string) GetValue(SubjectProperty);
|
||||
set => SetValue(SubjectProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty StatusProperty = DependencyProperty.Register(
|
||||
"Status", typeof(object), typeof(Shield), new PropertyMetadata(default(object)));
|
||||
|
||||
public object Status
|
||||
{
|
||||
get => GetValue(StatusProperty);
|
||||
set => SetValue(StatusProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register(
|
||||
"Color", typeof(Brush), typeof(Shield), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public Brush Color
|
||||
{
|
||||
get => (Brush) GetValue(ColorProperty);
|
||||
set => SetValue(ColorProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Animation;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplateVisualState(GroupName = PresentationGroup, Name = NormalState)]
|
||||
[TemplateVisualState(GroupName = PresentationGroup, Name = DefaultTransitionState)]
|
||||
[TemplatePart(Name = PreviousContentPresentationSitePartName, Type = typeof(ContentControl))]
|
||||
[TemplatePart(Name = CurrentContentPresentationSitePartName, Type = typeof(ContentControl))]
|
||||
public class TransitioningContentControl : ContentControl
|
||||
{
|
||||
private Storyboard _currentTransition;
|
||||
|
||||
public TransitioningContentControl()
|
||||
{
|
||||
if (!DesignerProperties.GetIsInDesignMode(this))
|
||||
{
|
||||
DefaultStyleKey = typeof(TransitioningContentControl);
|
||||
}
|
||||
}
|
||||
|
||||
private Storyboard CurrentTransition
|
||||
{
|
||||
set
|
||||
{
|
||||
if (_currentTransition != null)
|
||||
_currentTransition.Completed -= OnTransitionCompleted;
|
||||
|
||||
_currentTransition = value;
|
||||
|
||||
if (_currentTransition != null)
|
||||
_currentTransition.Completed += OnTransitionCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
public event RoutedEventHandler TransitionCompleted;
|
||||
|
||||
#endregion Events
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (DesignerProperties.GetIsInDesignMode(this)) return;
|
||||
|
||||
if (IsTransitioning)
|
||||
AbortTransition();
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
PreviousContentPresentationSite =
|
||||
GetTemplateChild(PreviousContentPresentationSitePartName) as ContentPresenter;
|
||||
CurrentContentPresentationSite =
|
||||
GetTemplateChild(CurrentContentPresentationSitePartName) as ContentPresenter;
|
||||
|
||||
if (CurrentContentPresentationSite != null)
|
||||
CurrentContentPresentationSite.Content = Content;
|
||||
|
||||
var transition = GetStoryboard(Transition);
|
||||
CurrentTransition = transition;
|
||||
if (transition == null)
|
||||
{
|
||||
Transition = DefaultTransitionState;
|
||||
|
||||
throw new ArgumentException("TransitioningContentControl_TransitionNotFound");
|
||||
}
|
||||
|
||||
VisualStateManager.GoToState(this, NormalState, false);
|
||||
VisualStateManager.GoToState(this, Transition, true);
|
||||
}
|
||||
|
||||
protected override void OnContentChanged(object oldContent, object newContent)
|
||||
{
|
||||
base.OnContentChanged(oldContent, newContent);
|
||||
|
||||
StartTransition(oldContent, newContent);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "newContent", Justification = "Should be used in the future.")]
|
||||
private void StartTransition(object oldContent, object newContent)
|
||||
{
|
||||
if (CurrentContentPresentationSite != null && PreviousContentPresentationSite != null)
|
||||
{
|
||||
CurrentContentPresentationSite.Content = newContent;
|
||||
|
||||
PreviousContentPresentationSite.Content = oldContent;
|
||||
|
||||
if (!IsTransitioning || RestartTransitionOnContentChange)
|
||||
{
|
||||
IsTransitioning = true;
|
||||
VisualStateManager.GoToState(this, NormalState, false);
|
||||
VisualStateManager.GoToState(this, Transition, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTransitionCompleted(object sender, EventArgs e)
|
||||
{
|
||||
AbortTransition();
|
||||
|
||||
var handler = TransitionCompleted;
|
||||
handler?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
public void AbortTransition()
|
||||
{
|
||||
VisualStateManager.GoToState(this, NormalState, false);
|
||||
IsTransitioning = false;
|
||||
if (PreviousContentPresentationSite != null)
|
||||
PreviousContentPresentationSite.Content = null;
|
||||
}
|
||||
|
||||
private Storyboard GetStoryboard(string newTransition)
|
||||
{
|
||||
var presentationGroup = VisualHelper.TryGetVisualStateGroup(this, PresentationGroup);
|
||||
Storyboard newStoryboard = null;
|
||||
if (presentationGroup != null)
|
||||
newStoryboard = presentationGroup.States
|
||||
.OfType<VisualState>()
|
||||
.Where(state => state.Name == newTransition)
|
||||
.Select(state => state.Storyboard)
|
||||
.FirstOrDefault();
|
||||
return newStoryboard;
|
||||
}
|
||||
|
||||
#region Visual state names
|
||||
|
||||
private const string PresentationGroup = "PresentationStates";
|
||||
|
||||
private const string NormalState = "Normal";
|
||||
|
||||
public const string DefaultTransitionState = "DefaultTransition";
|
||||
|
||||
#endregion Visual state names
|
||||
|
||||
#region Template part names
|
||||
|
||||
internal const string PreviousContentPresentationSitePartName = "PreviousContentPresentationSite";
|
||||
|
||||
internal const string CurrentContentPresentationSitePartName = "CurrentContentPresentationSite";
|
||||
|
||||
#endregion Template part names
|
||||
|
||||
#region TemplateParts
|
||||
|
||||
private ContentPresenter CurrentContentPresentationSite { get; set; }
|
||||
|
||||
private ContentPresenter PreviousContentPresentationSite { get; set; }
|
||||
|
||||
#endregion TemplateParts
|
||||
|
||||
#region public bool IsTransitioning
|
||||
|
||||
private bool _allowIsTransitioningWrite;
|
||||
|
||||
public bool IsTransitioning
|
||||
{
|
||||
get => (bool)GetValue(IsTransitioningProperty);
|
||||
private set
|
||||
{
|
||||
_allowIsTransitioningWrite = true;
|
||||
SetValue(IsTransitioningProperty, value);
|
||||
_allowIsTransitioningWrite = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsTransitioningProperty =
|
||||
DependencyProperty.Register(
|
||||
"IsTransitioning",
|
||||
typeof(bool),
|
||||
typeof(TransitioningContentControl),
|
||||
new PropertyMetadata(OnIsTransitioningPropertyChanged));
|
||||
|
||||
private static void OnIsTransitioningPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var source = (TransitioningContentControl)d;
|
||||
|
||||
if (!source._allowIsTransitioningWrite)
|
||||
{
|
||||
source.IsTransitioning = (bool)e.OldValue;
|
||||
throw new InvalidOperationException("TransitiotioningContentControl_IsTransitioningReadOnly");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion public bool IsTransitioning
|
||||
|
||||
#region public string Transition
|
||||
|
||||
public string Transition
|
||||
{
|
||||
get => GetValue(TransitionProperty) as string;
|
||||
set => SetValue(TransitionProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TransitionProperty =
|
||||
DependencyProperty.Register(
|
||||
"Transition",
|
||||
typeof(string),
|
||||
typeof(TransitioningContentControl),
|
||||
new PropertyMetadata(DefaultTransitionState, OnTransitionPropertyChanged));
|
||||
|
||||
private static void OnTransitionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var source = (TransitioningContentControl)d;
|
||||
var oldTransition = e.OldValue as string;
|
||||
var newTransition = e.NewValue as string;
|
||||
|
||||
if (source.IsTransitioning)
|
||||
source.AbortTransition();
|
||||
|
||||
var newStoryboard = source.GetStoryboard(newTransition);
|
||||
|
||||
if (newStoryboard == null)
|
||||
if (VisualHelper.TryGetVisualStateGroup(source, PresentationGroup) == null)
|
||||
{
|
||||
source.CurrentTransition = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
source.SetValue(TransitionProperty, oldTransition);
|
||||
|
||||
throw new ArgumentException(
|
||||
"TransitioningContentControl_TransitionNotFound");
|
||||
}
|
||||
else
|
||||
source.CurrentTransition = newStoryboard;
|
||||
}
|
||||
|
||||
#endregion public string Transition
|
||||
|
||||
#region public bool RestartTransitionOnContentChange
|
||||
|
||||
public bool RestartTransitionOnContentChange
|
||||
{
|
||||
get => (bool)GetValue(RestartTransitionOnContentChangeProperty);
|
||||
set => SetValue(RestartTransitionOnContentChangeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty RestartTransitionOnContentChangeProperty =
|
||||
DependencyProperty.Register(
|
||||
"RestartTransitionOnContentChange",
|
||||
typeof(bool),
|
||||
typeof(TransitioningContentControl),
|
||||
new PropertyMetadata(ValueBoxes.FalseBox, OnRestartTransitionOnContentChangePropertyChanged));
|
||||
|
||||
private static void OnRestartTransitionOnContentChangePropertyChanged(DependencyObject d,
|
||||
DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((TransitioningContentControl)d).OnRestartTransitionOnContentChangeChanged((bool)e.OldValue,
|
||||
(bool)e.NewValue);
|
||||
}
|
||||
|
||||
protected virtual void OnRestartTransitionOnContentChangeChanged(bool oldValue, bool newValue)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion public bool RestartTransitionOnContentChange
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user