support for .NET framework 3.5
Still have bugs
This commit is contained in:
1 parent
5878039e21
commit
7d6b491f25
704 files changed
+48273
-33
No files matched your search
@@ -0,0 +1,189 @@
|
||||
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;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class AnimationPath : Shape
|
||||
{
|
||||
private bool _isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// 故事版
|
||||
/// </summary>
|
||||
private Storyboard _storyboard;
|
||||
|
||||
/// <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);
|
||||
}
|
||||
|
||||
private void UpdatePath()
|
||||
{
|
||||
if (!Duration.HasTimeSpan || !IsPlaying) return;
|
||||
StrokeDashOffset = PathLength;
|
||||
StrokeDashArray = new DoubleCollection(new List<double>
|
||||
{
|
||||
PathLength,
|
||||
PathLength
|
||||
});
|
||||
|
||||
//定义动画
|
||||
_storyboard = new Storyboard
|
||||
{
|
||||
RepeatBehavior = RepeatBehavior
|
||||
};
|
||||
_storyboard.Completed += (s, e) => RaiseEvent(new RoutedEventArgs(CompletedEvent));
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,91 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Expression.Shapes;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = IndicatorTemplateName, Type = typeof(Arc))]
|
||||
public class CircleProgressBar : RangeBase
|
||||
{
|
||||
private const string IndicatorTemplateName = "PART_Indicator";
|
||||
|
||||
public static readonly DependencyProperty ArcThicknessProperty = DependencyProperty.Register(
|
||||
"ArcThickness", typeof(double), typeof(CircleProgressBar), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
public static readonly DependencyProperty ShowTextProperty = DependencyProperty.Register(
|
||||
"ShowText", typeof(bool), typeof(CircleProgressBar), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
"Text", typeof(string), typeof(CircleProgressBar), new PropertyMetadata(default(string)));
|
||||
|
||||
private Arc _indicator;
|
||||
|
||||
static CircleProgressBar()
|
||||
{
|
||||
FocusableProperty.OverrideMetadata(typeof(CircleProgressBar),
|
||||
new FrameworkPropertyMetadata(ValueBoxes.FalseBox));
|
||||
MaximumProperty.OverrideMetadata(typeof(CircleProgressBar),
|
||||
new FrameworkPropertyMetadata(ValueBoxes.Double100Box));
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string) GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowText
|
||||
{
|
||||
get => (bool) GetValue(ShowTextProperty);
|
||||
set => SetValue(ShowTextProperty, value);
|
||||
}
|
||||
|
||||
public double ArcThickness
|
||||
{
|
||||
get => (double) GetValue(ArcThicknessProperty);
|
||||
set => SetValue(ArcThicknessProperty, value);
|
||||
}
|
||||
|
||||
private void SetProgressBarIndicatorAngle()
|
||||
{
|
||||
if (_indicator == null) return;
|
||||
var minimum = Minimum;
|
||||
var maximum = Maximum;
|
||||
var num = Value;
|
||||
_indicator.EndAngle = (maximum <= minimum ? 0 : (num - minimum) / (maximum - minimum)) * 360;
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_indicator = GetTemplateChild(IndicatorTemplateName) as Arc;
|
||||
if (_indicator != null)
|
||||
{
|
||||
_indicator.StartAngle = 0;
|
||||
_indicator.EndAngle = 0;
|
||||
}
|
||||
|
||||
SetProgressBarIndicatorAngle();
|
||||
}
|
||||
|
||||
protected override void OnMinimumChanged(double oldMinimum, double newMinimum)
|
||||
{
|
||||
base.OnMinimumChanged(oldMinimum, newMinimum);
|
||||
SetProgressBarIndicatorAngle();
|
||||
}
|
||||
|
||||
protected override void OnMaximumChanged(double oldMaximum, double newMaximum)
|
||||
{
|
||||
base.OnMaximumChanged(oldMaximum, newMaximum);
|
||||
SetProgressBarIndicatorAngle();
|
||||
}
|
||||
|
||||
protected override void OnValueChanged(double oldValue, double newValue)
|
||||
{
|
||||
base.OnValueChanged(oldValue, newValue);
|
||||
SetProgressBarIndicatorAngle();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,482 @@
|
||||
using System;
|
||||
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>
|
||||
public static Panel GrowlPanel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 计数
|
||||
/// </summary>
|
||||
private int _tickCount;
|
||||
|
||||
/// <summary>
|
||||
/// 关闭计时器
|
||||
/// </summary>
|
||||
private DispatcherTimer _timerClose;
|
||||
|
||||
#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));
|
||||
|
||||
MouseEnter += Growl_MouseEnter;
|
||||
MouseLeave += Growl_MouseLeave;
|
||||
}
|
||||
|
||||
private void Growl_MouseLeave(object sender, MouseEventArgs e) => _buttonClose.Collapse();
|
||||
|
||||
private void Growl_MouseEnter(object sender, MouseEventArgs e) => _buttonClose.Show(_showCloseButton);
|
||||
|
||||
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; }
|
||||
|
||||
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 (o is Panel panel)
|
||||
{
|
||||
SetGrowlPanel(panel);
|
||||
}
|
||||
}));
|
||||
|
||||
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;
|
||||
var menuItem = new MenuItem
|
||||
{
|
||||
Header = Properties.Langs.Lang.Clear
|
||||
};
|
||||
menuItem.Click += (s, e) =>
|
||||
{
|
||||
foreach (var item in GrowlPanel.Children.OfType<Growl>())
|
||||
{
|
||||
item.Close();
|
||||
}
|
||||
};
|
||||
GrowlPanel.ContextMenu = new ContextMenu
|
||||
{
|
||||
Items =
|
||||
{
|
||||
menuItem
|
||||
}
|
||||
};
|
||||
var behavior = new FluidMoveBehavior
|
||||
{
|
||||
AppliesTo = FluidMoveScope.Children,
|
||||
Duration = new Duration(TimeSpan.FromMilliseconds(400))
|
||||
};
|
||||
var collection = Interaction.GetBehaviors(GrowlPanel);
|
||||
collection.Add(behavior);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
};
|
||||
GrowlPanel.Children.Insert(0, ctl);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public static void Success(string message) => Success(new GrowlInfo
|
||||
{
|
||||
Message = message
|
||||
});
|
||||
|
||||
/// <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>
|
||||
public static void Info(string message) => Info(new GrowlInfo
|
||||
{
|
||||
Message = message
|
||||
});
|
||||
|
||||
/// <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>
|
||||
public static void Warning(string message) => Warning(new GrowlInfo
|
||||
{
|
||||
Message = message
|
||||
});
|
||||
|
||||
/// <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>
|
||||
public static void Error(string message) => Error(new GrowlInfo
|
||||
{
|
||||
Message = message
|
||||
});
|
||||
|
||||
/// <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>
|
||||
public static void Fatal(string message) => Fatal(new GrowlInfo
|
||||
{
|
||||
Message = message
|
||||
});
|
||||
|
||||
/// <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>
|
||||
public static void Ask(string message, Func<bool, bool> actionBeforeClose) => Ask(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
ActionBeforeClose = actionBeforeClose
|
||||
});
|
||||
|
||||
/// <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 += delegate { GrowlPanel.Children.Remove(this); };
|
||||
transform.BeginAnimation(TranslateTransform.XProperty, animation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除
|
||||
/// </summary>
|
||||
public static void Clear()
|
||||
{
|
||||
GrowlPanel.Children.Clear();
|
||||
if (GrowlPanel.ContextMenu != null)
|
||||
{
|
||||
GrowlPanel.ContextMenu.IsOpen = false;
|
||||
GrowlPanel.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,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,219 @@
|
||||
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 ScrollToVerticalOffsetInternal(double offset, double milliseconds = 500)
|
||||
{
|
||||
var animation = AnimationHelper.CreateAnimation(offset, milliseconds);
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user