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,22 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class BackgroundSwitchElement
|
||||
{
|
||||
public static readonly DependencyProperty MouseHoverBackgroundProperty = DependencyProperty.RegisterAttached(
|
||||
"MouseHoverBackground", typeof(Brush), typeof(BackgroundSwitchElement), new FrameworkPropertyMetadata(Brushes.Transparent, FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetMouseHoverBackground(DependencyObject element, Brush value) => element.SetValue(MouseHoverBackgroundProperty, value);
|
||||
|
||||
public static Brush GetMouseHoverBackground(DependencyObject element) => (Brush)element.GetValue(MouseHoverBackgroundProperty);
|
||||
|
||||
public static readonly DependencyProperty MouseDownBackgroundProperty = DependencyProperty.RegisterAttached(
|
||||
"MouseDownBackground", typeof(Brush), typeof(BackgroundSwitchElement), new FrameworkPropertyMetadata(Brushes.Transparent, FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetMouseDownBackground(DependencyObject element, Brush value) => element.SetValue(MouseDownBackgroundProperty, value);
|
||||
|
||||
public static Brush GetMouseDownBackground(DependencyObject element) => (Brush)element.GetValue(MouseDownBackgroundProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class BorderElement
|
||||
{
|
||||
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.RegisterAttached(
|
||||
"CornerRadius", typeof(CornerRadius), typeof(BorderElement), new FrameworkPropertyMetadata(default(CornerRadius), FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetCornerRadius(DependencyObject element, CornerRadius value) => element.SetValue(CornerRadiusProperty, value);
|
||||
|
||||
public static CornerRadius GetCornerRadius(DependencyObject element) => (CornerRadius)element.GetValue(CornerRadiusProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Windows;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class EdgeElement
|
||||
{
|
||||
public static readonly DependencyProperty LeftContentProperty = DependencyProperty.RegisterAttached(
|
||||
"LeftContent", typeof(object), typeof(EdgeElement), new PropertyMetadata(default(object), OnEdgeContentChanged));
|
||||
|
||||
public static void SetLeftContent(DependencyObject element, object value) => element.SetValue(LeftContentProperty, value);
|
||||
|
||||
public static object GetLeftContent(DependencyObject element) => element.GetValue(LeftContentProperty);
|
||||
|
||||
public static readonly DependencyProperty TopContentProperty = DependencyProperty.RegisterAttached(
|
||||
"TopContent", typeof(object), typeof(EdgeElement), new PropertyMetadata(default(object), OnEdgeContentChanged));
|
||||
|
||||
public static void SetTopContent(DependencyObject element, object value) => element.SetValue(TopContentProperty, value);
|
||||
|
||||
public static object GetTopContent(DependencyObject element) => element.GetValue(TopContentProperty);
|
||||
|
||||
public static readonly DependencyProperty RightContentProperty = DependencyProperty.RegisterAttached(
|
||||
"RightContent", typeof(object), typeof(EdgeElement), new PropertyMetadata(default(object), OnEdgeContentChanged));
|
||||
|
||||
public static void SetRightContent(DependencyObject element, object value) => element.SetValue(RightContentProperty, value);
|
||||
|
||||
public static object GetRightContent(DependencyObject element) => element.GetValue(RightContentProperty);
|
||||
|
||||
public static readonly DependencyProperty BottomContentProperty = DependencyProperty.RegisterAttached(
|
||||
"BottomContent", typeof(object), typeof(EdgeElement), new PropertyMetadata(default(object), OnEdgeContentChanged));
|
||||
|
||||
public static void SetBottomContent(DependencyObject element, object value) => element.SetValue(BottomContentProperty, value);
|
||||
|
||||
public static object GetBottomContent(DependencyObject element) => element.GetValue(BottomContentProperty);
|
||||
|
||||
public static readonly DependencyProperty ShowEdgeContentProperty = DependencyProperty.RegisterAttached(
|
||||
"ShowEdgeContent", typeof(bool), typeof(EdgeElement), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public static void SetShowEdgeContent(DependencyObject element, bool value) => element.SetValue(ShowEdgeContentProperty, value);
|
||||
|
||||
public static bool GetShowEdgeContent(DependencyObject element) => (bool) element.GetValue(ShowEdgeContentProperty);
|
||||
|
||||
private static void OnEdgeContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) =>
|
||||
SetShowEdgeContent(d, !(GetLeftContent(d) == null && GetTopContent(d) == null &&
|
||||
GetRightContent(d) == null && GetBottomContent(d) == null));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class IconElement
|
||||
{
|
||||
public static readonly DependencyProperty GeometryProperty = DependencyProperty.RegisterAttached(
|
||||
"Geometry", typeof(Geometry), typeof(IconElement), new PropertyMetadata(default(Geometry)));
|
||||
|
||||
public static void SetGeometry(DependencyObject element, Geometry value)
|
||||
=> element.SetValue(GeometryProperty, value);
|
||||
|
||||
public static Geometry GetGeometry(DependencyObject element)
|
||||
=> (Geometry) element.GetValue(GeometryProperty);
|
||||
|
||||
public static readonly DependencyProperty WidthProperty = DependencyProperty.RegisterAttached(
|
||||
"Width", typeof(double), typeof(IconElement), new PropertyMetadata(double.NaN));
|
||||
|
||||
public static void SetWidth(DependencyObject element, double value)
|
||||
=> element.SetValue(WidthProperty, value);
|
||||
|
||||
public static double GetWidth(DependencyObject element)
|
||||
=> (double) element.GetValue(WidthProperty);
|
||||
|
||||
public static readonly DependencyProperty HeightProperty = DependencyProperty.RegisterAttached(
|
||||
"Height", typeof(double), typeof(IconElement), new PropertyMetadata(double.NaN));
|
||||
|
||||
public static void SetHeight(DependencyObject element, double value)
|
||||
=> element.SetValue(HeightProperty, value);
|
||||
|
||||
public static double GetHeight(DependencyObject element)
|
||||
=> (double) element.GetValue(HeightProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class IconSwitchElement : IconElement
|
||||
{
|
||||
public static readonly DependencyProperty GeometrySelectedProperty = DependencyProperty.RegisterAttached(
|
||||
"GeometrySelected", typeof(Geometry), typeof(IconSwitchElement), new PropertyMetadata(default(Geometry)));
|
||||
|
||||
public static void SetGeometrySelected(DependencyObject element, Geometry value)
|
||||
{
|
||||
element.SetValue(GeometrySelectedProperty, value);
|
||||
}
|
||||
|
||||
public static Geometry GetGeometrySelected(DependencyObject element)
|
||||
{
|
||||
return (Geometry)element.GetValue(GeometrySelectedProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Windows;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class InfoElement : TitleElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 占位符
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlaceholderProperty = DependencyProperty.RegisterAttached(
|
||||
"Placeholder", typeof(string), typeof(InfoElement), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetPlaceholder(DependencyObject element, string value) => element.SetValue(PlaceholderProperty, value);
|
||||
|
||||
public static string GetPlaceholder(DependencyObject element) => (string)element.GetValue(PlaceholderProperty);
|
||||
|
||||
/// <summary>
|
||||
/// 是否必填
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty NecessaryProperty = DependencyProperty.RegisterAttached(
|
||||
"Necessary", typeof(bool), typeof(InfoElement), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetNecessary(DependencyObject element, bool value) => element.SetValue(NecessaryProperty, value);
|
||||
|
||||
public static bool GetNecessary(DependencyObject element) => (bool)element.GetValue(NecessaryProperty);
|
||||
|
||||
/// <summary>
|
||||
/// 标记
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty SymbolProperty = DependencyProperty.RegisterAttached(
|
||||
"Symbol", typeof(string), typeof(InfoElement), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetSymbol(DependencyObject element, string value) => element.SetValue(SymbolProperty, value);
|
||||
|
||||
public static string GetSymbol(DependencyObject element) => (string)element.GetValue(SymbolProperty);
|
||||
|
||||
/// <summary>
|
||||
/// 内容高度
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ContentHeightProperty = DependencyProperty.RegisterAttached(
|
||||
"ContentHeight", typeof(double), typeof(InfoElement), new FrameworkPropertyMetadata(30.0, FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetContentHeight(DependencyObject element, double value) => element.SetValue(ContentHeightProperty, value);
|
||||
|
||||
public static double GetContentHeight(DependencyObject element) => (double)element.GetValue(ContentHeightProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class LinkElement
|
||||
{
|
||||
public static readonly DependencyProperty LinkProperty = DependencyProperty.RegisterAttached(
|
||||
"Link", typeof(string), typeof(LinkElement), new PropertyMetadata(default(string), OnLinkChanged));
|
||||
|
||||
private static void OnLinkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is UIElement element)
|
||||
{
|
||||
CreateTrriger(element, (string) e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly DependencyProperty CommandBindingProperty = DependencyProperty.RegisterAttached(
|
||||
"CommandBinding", typeof(CommandBinding), typeof(LinkElement), new PropertyMetadata(default(CommandBinding)));
|
||||
|
||||
private static void SetCommandBinding(DependencyObject element, CommandBinding value)
|
||||
=> element.SetValue(CommandBindingProperty, value);
|
||||
|
||||
private static CommandBinding GetCommandBinding(DependencyObject element)
|
||||
=> (CommandBinding) element.GetValue(CommandBindingProperty);
|
||||
|
||||
private static void CreateTrriger(UIElement element, string link)
|
||||
{
|
||||
element.CommandBindings.Remove(GetCommandBinding(element));
|
||||
SetCommandBinding(element, null);
|
||||
|
||||
if (string.IsNullOrEmpty(link)) return;
|
||||
var commandBinding = new CommandBinding(ControlCommands.OpenLink, (sender, args) => Process.Start(link));
|
||||
SetCommandBinding(element, commandBinding);
|
||||
element.CommandBindings.Add(commandBinding);
|
||||
}
|
||||
|
||||
public static void SetLink(DependencyObject element, string value)
|
||||
=> element.SetValue(LinkProperty, value);
|
||||
|
||||
public static string GetLink(DependencyObject element)
|
||||
=> (string) element.GetValue(LinkProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class PanelElement
|
||||
{
|
||||
public static readonly DependencyProperty FluidMoveBehaviorProperty = DependencyProperty.RegisterAttached(
|
||||
"FluidMoveBehavior", typeof(FluidMoveBehavior), typeof(PanelElement), new PropertyMetadata(default(FluidMoveBehavior), OnFluidMoveBehaviorChanged));
|
||||
|
||||
private static void OnFluidMoveBehaviorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is Panel panel)
|
||||
{
|
||||
var collection = Interaction.GetBehaviors(panel);
|
||||
var oldBehavior = GetTempFluidMoveBehavior(panel);
|
||||
collection.Remove(oldBehavior);
|
||||
|
||||
if (e.NewValue is FluidMoveBehavior behavior)
|
||||
{
|
||||
collection.Add(behavior);
|
||||
SetTempFluidMoveBehavior(panel, behavior);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetFluidMoveBehavior(DependencyObject element, FluidMoveBehavior value)
|
||||
=> element.SetValue(FluidMoveBehaviorProperty, value);
|
||||
|
||||
public static FluidMoveBehavior GetFluidMoveBehavior(DependencyObject element)
|
||||
=> (FluidMoveBehavior) element.GetValue(FluidMoveBehaviorProperty);
|
||||
|
||||
private static readonly DependencyProperty TempFluidMoveBehaviorProperty = DependencyProperty.RegisterAttached(
|
||||
"TempFluidMoveBehavior", typeof(FluidMoveBehavior), typeof(PanelElement), new PropertyMetadata(default(FluidMoveBehavior)));
|
||||
|
||||
private static void SetTempFluidMoveBehavior(DependencyObject element, FluidMoveBehavior value)
|
||||
=> element.SetValue(TempFluidMoveBehaviorProperty, value);
|
||||
|
||||
private static FluidMoveBehavior GetTempFluidMoveBehavior(DependencyObject element)
|
||||
=> (FluidMoveBehavior) element.GetValue(TempFluidMoveBehaviorProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Windows;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class PasswordBoxMonitor
|
||||
{
|
||||
/// <summary>
|
||||
/// 密码长度
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PasswordLengthProperty = DependencyProperty.RegisterAttached(
|
||||
"PasswordLength", typeof(int), typeof(PasswordBoxMonitor), new PropertyMetadata(default(int)));
|
||||
|
||||
public static void SetPasswordLength(DependencyObject element, int value) => element.SetValue(PasswordLengthProperty, value);
|
||||
|
||||
public static int GetPasswordLength(DependencyObject element) => (int)element.GetValue(PasswordLengthProperty);
|
||||
|
||||
/// <summary>
|
||||
/// 是否监测
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsMonitoringProperty = DependencyProperty.RegisterAttached(
|
||||
"IsMonitoring", typeof(bool), typeof(PasswordBoxMonitor), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits, OnIsMonitoringChanged));
|
||||
|
||||
private static void OnIsMonitoringChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is System.Windows.Controls.PasswordBox passwordBox)
|
||||
{
|
||||
if (e.NewValue is bool boolValue)
|
||||
{
|
||||
if (boolValue)
|
||||
{
|
||||
passwordBox.PasswordChanged += PasswordChanged;
|
||||
}
|
||||
else
|
||||
{
|
||||
passwordBox.PasswordChanged -= PasswordChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetIsMonitoring(DependencyObject element, bool value) => element.SetValue(IsMonitoringProperty, value);
|
||||
|
||||
public static bool GetIsMonitoring(DependencyObject element) => (bool)element.GetValue(IsMonitoringProperty);
|
||||
|
||||
private static void PasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is System.Windows.Controls.PasswordBox passwordBox)
|
||||
{
|
||||
SetPasswordLength(passwordBox, passwordBox.Password.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Windows;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class StatusSwitchElement
|
||||
{
|
||||
/// <summary>
|
||||
/// 选中时展示的元素
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CheckedElementProperty = DependencyProperty.RegisterAttached(
|
||||
"CheckedElement", typeof(object), typeof(StatusSwitchElement), new PropertyMetadata(default(object)));
|
||||
|
||||
public static void SetCheckedElement(DependencyObject element, object value) => element.SetValue(CheckedElementProperty, value);
|
||||
|
||||
public static object GetCheckedElement(DependencyObject element) => element.GetValue(CheckedElementProperty);
|
||||
|
||||
/// <summary>
|
||||
/// 是否隐藏元素
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty HiddenElementProperty = DependencyProperty.RegisterAttached(
|
||||
"HiddenElement", typeof(bool), typeof(StatusSwitchElement), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public static void SetHiddenElement(DependencyObject element, bool value) => element.SetValue(HiddenElementProperty, value);
|
||||
|
||||
public static bool GetHiddenElement(DependencyObject element) => (bool) element.GetValue(HiddenElementProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System.Windows;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class TipElement
|
||||
{
|
||||
public static readonly DependencyProperty VisibilityProperty = DependencyProperty.RegisterAttached(
|
||||
"Visibility", typeof(Visibility), typeof(TipElement), new PropertyMetadata(Visibility.Collapsed));
|
||||
|
||||
public static void SetVisibility(DependencyObject element, Visibility value) => element.SetValue(VisibilityProperty, value);
|
||||
|
||||
public static Visibility GetVisibility(DependencyObject element) => (Visibility) element.GetValue(VisibilityProperty);
|
||||
|
||||
public static readonly DependencyProperty PlacementProperty = DependencyProperty.RegisterAttached(
|
||||
"Placement", typeof(TipPlacement), typeof(TipElement), new PropertyMetadata(default(TipPlacement)));
|
||||
|
||||
public static void SetPlacement(DependencyObject element, TipPlacement value) => element.SetValue(PlacementProperty, value);
|
||||
|
||||
public static TipPlacement GetPlacement(DependencyObject element) => (TipPlacement) element.GetValue(PlacementProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class TitleElement
|
||||
{
|
||||
public static readonly DependencyProperty TitleProperty = DependencyProperty.RegisterAttached(
|
||||
"Title", typeof(string), typeof(TitleElement), new PropertyMetadata(default(string)));
|
||||
|
||||
public static void SetTitle(DependencyObject element, string value)
|
||||
=> element.SetValue(TitleProperty, value);
|
||||
|
||||
public static string GetTitle(DependencyObject element)
|
||||
=> (string) element.GetValue(TitleProperty);
|
||||
|
||||
public static readonly DependencyProperty BackgroundProperty = DependencyProperty.RegisterAttached(
|
||||
"Background", typeof(Brush), typeof(TitleElement), new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetBackground(DependencyObject element, Brush value)
|
||||
=> element.SetValue(BackgroundProperty, value);
|
||||
|
||||
public static Brush GetBackground(DependencyObject element)
|
||||
=> (Brush) element.GetValue(BackgroundProperty);
|
||||
|
||||
public static readonly DependencyProperty ForegroundProperty = DependencyProperty.RegisterAttached(
|
||||
"Foreground", typeof(Brush), typeof(TitleElement), new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetForeground(DependencyObject element, Brush value)
|
||||
=> element.SetValue(ForegroundProperty, value);
|
||||
|
||||
public static Brush GetForeground(DependencyObject element)
|
||||
=> (Brush) element.GetValue(ForegroundProperty);
|
||||
|
||||
public static readonly DependencyProperty BorderBrushProperty = DependencyProperty.RegisterAttached(
|
||||
"BorderBrush", typeof(Brush), typeof(TitleElement), new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetBorderBrush(DependencyObject element, Brush value)
|
||||
=> element.SetValue(BorderBrushProperty, value);
|
||||
|
||||
public static Brush GetBorderBrush(DependencyObject element)
|
||||
=> (Brush) element.GetValue(BorderBrushProperty);
|
||||
|
||||
/// <summary>
|
||||
/// 标题对齐方式
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TitleAlignmentProperty = DependencyProperty.RegisterAttached(
|
||||
"TitleAlignment", typeof(TitleAlignment), typeof(TitleElement), new FrameworkPropertyMetadata(TitleAlignment.Top, FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetTitleAlignment(DependencyObject element, TitleAlignment value)
|
||||
=> element.SetValue(TitleAlignmentProperty, value);
|
||||
|
||||
public static TitleAlignment GetTitleAlignment(DependencyObject element)
|
||||
=> (TitleAlignment) element.GetValue(TitleAlignmentProperty);
|
||||
|
||||
/// <summary>
|
||||
/// 标题宽度
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TitleWidthProperty = DependencyProperty.RegisterAttached(
|
||||
"TitleWidth", typeof(GridLength), typeof(TitleElement), new FrameworkPropertyMetadata(new GridLength(120.0), FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static void SetTitleWidth(DependencyObject element, GridLength value) => element.SetValue(TitleWidthProperty, value);
|
||||
|
||||
public static GridLength GetTitleWidth(DependencyObject element) => (GridLength)element.GetValue(TitleWidthProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class VisualElement
|
||||
{
|
||||
public static readonly DependencyProperty HighlightBrushProperty = DependencyProperty.RegisterAttached(
|
||||
"HighlightBrush", typeof(Brush), typeof(VisualElement), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public static void SetHighlightBrush(DependencyObject element, Brush value)
|
||||
=> element.SetValue(HighlightBrushProperty, value);
|
||||
|
||||
public static Brush GetHighlightBrush(DependencyObject element)
|
||||
=> (Brush) element.GetValue(HighlightBrushProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public static class WindowAttach
|
||||
{
|
||||
public static readonly DependencyProperty IsDragElementProperty = DependencyProperty.RegisterAttached(
|
||||
"IsDragElement", typeof(bool), typeof(WindowAttach), new PropertyMetadata(ValueBoxes.FalseBox, OnIsDragElementChanged));
|
||||
|
||||
public static void SetIsDragElement(DependencyObject element, bool value)
|
||||
=> element.SetValue(IsDragElementProperty, value);
|
||||
|
||||
public static bool GetIsDragElement(DependencyObject element)
|
||||
=> (bool) element.GetValue(IsDragElementProperty);
|
||||
|
||||
private static void OnIsDragElementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is UIElement ctl)
|
||||
{
|
||||
if ((bool)e.NewValue)
|
||||
{
|
||||
ctl.MouseLeftButtonDown += DragElement_MouseLeftButtonDown;
|
||||
}
|
||||
else
|
||||
{
|
||||
ctl.MouseLeftButtonDown -= DragElement_MouseLeftButtonDown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DragElement_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is DependencyObject obj && e.ButtonState == MouseButtonState.Pressed)
|
||||
{
|
||||
System.Windows.Window.GetWindow(obj)?.DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class HeaderedSelectableItem : SelectableItem
|
||||
{
|
||||
public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
|
||||
"Header", typeof(object), typeof(HeaderedSelectableItem), new PropertyMetadata(default(object)));
|
||||
|
||||
public object Header
|
||||
{
|
||||
get => GetValue(HeaderProperty);
|
||||
set => SetValue(HeaderProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public interface ISelectable
|
||||
{
|
||||
event RoutedEventHandler Selected;
|
||||
|
||||
bool IsSelected { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public interface ISingleOpen : IDisposable
|
||||
{
|
||||
bool CanDispose { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Windows;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 规则ItemsControl
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 该类的每一项都具有相同的大小和外边距
|
||||
/// </remarks>
|
||||
public class RegularItemsControl : SimpleItemsControl
|
||||
{
|
||||
public static readonly DependencyProperty ItemWidthProperty = DependencyProperty.Register(
|
||||
"ItemWidth", typeof(double), typeof(RegularItemsControl), new PropertyMetadata(ValueBoxes.Double200Box));
|
||||
|
||||
public double ItemWidth
|
||||
{
|
||||
get => (double) GetValue(ItemWidthProperty);
|
||||
set => SetValue(ItemWidthProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ItemHeightProperty = DependencyProperty.Register(
|
||||
"ItemHeight", typeof(double), typeof(RegularItemsControl), new PropertyMetadata(ValueBoxes.Double200Box));
|
||||
|
||||
public double ItemHeight
|
||||
{
|
||||
get => (double) GetValue(ItemHeightProperty);
|
||||
set => SetValue(ItemHeightProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ItemMarginProperty = DependencyProperty.Register(
|
||||
"ItemMargin", typeof(Thickness), typeof(RegularItemsControl), new PropertyMetadata(default(Thickness)));
|
||||
|
||||
public Thickness ItemMargin
|
||||
{
|
||||
get => (Thickness) GetValue(ItemMarginProperty);
|
||||
set => SetValue(ItemMarginProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[ContentProperty("Content")]
|
||||
public class SelectableItem : Control, ISelectable
|
||||
{
|
||||
private bool _isMouseLeftButtonDown;
|
||||
|
||||
protected override void OnMouseLeave(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseLeave(e);
|
||||
|
||||
_isMouseLeftButtonDown = false;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonDown(e);
|
||||
|
||||
_isMouseLeftButtonDown = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonUp(e);
|
||||
|
||||
if (_isMouseLeftButtonDown)
|
||||
{
|
||||
RaiseEvent(new RoutedEventArgs(SelectedEvent, this));
|
||||
_isMouseLeftButtonDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
|
||||
"IsSelected", typeof(bool), typeof(SelectableItem), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => (bool)GetValue(IsSelectedProperty);
|
||||
set => SetValue(IsSelectedProperty, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent SelectedEvent =
|
||||
EventManager.RegisterRoutedEvent("Selected", RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler), typeof(SelectableItem));
|
||||
|
||||
public event RoutedEventHandler Selected
|
||||
{
|
||||
add => AddHandler(SelectedEvent, value);
|
||||
remove => RemoveHandler(SelectedEvent, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
|
||||
"Content", typeof(object), typeof(SelectableItem), new PropertyMetadata(default(object)));
|
||||
|
||||
public object Content
|
||||
{
|
||||
get => GetValue(ContentProperty);
|
||||
set => SetValue(ContentProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// ItemsControl的轻量级版本
|
||||
/// </summary>
|
||||
[DefaultProperty("Items")]
|
||||
[ContentProperty("Items")]
|
||||
[TemplatePart(Name = ElementPanel, Type = typeof(Panel))]
|
||||
public class SimpleItemsControl : Control
|
||||
{
|
||||
private const string ElementPanel = "PART_Panel";
|
||||
|
||||
public static readonly DependencyProperty ItemTemplateProperty = DependencyProperty.Register(
|
||||
"ItemTemplate", typeof(DataTemplate), typeof(SimpleItemsControl),
|
||||
new FrameworkPropertyMetadata(default(DataTemplate), OnItemTemplateChanged));
|
||||
|
||||
public static readonly DependencyProperty ItemContainerStyleProperty = DependencyProperty.Register(
|
||||
"ItemContainerStyle", typeof(Style), typeof(SimpleItemsControl),
|
||||
new PropertyMetadata(default(Style), OnItemContainerStyleChanged));
|
||||
|
||||
public SimpleItemsControl()
|
||||
{
|
||||
var items = new ObservableCollection<object>();
|
||||
items.CollectionChanged += OnItemsCollectionChanged;
|
||||
Items = items;
|
||||
}
|
||||
|
||||
[Bindable(true)]
|
||||
[Category("Content")]
|
||||
public Style ItemContainerStyle
|
||||
{
|
||||
get => (Style) GetValue(ItemContainerStyleProperty);
|
||||
set => SetValue(ItemContainerStyleProperty, value);
|
||||
}
|
||||
|
||||
[Bindable(true)]
|
||||
public DataTemplate ItemTemplate
|
||||
{
|
||||
get => (DataTemplate) GetValue(ItemTemplateProperty);
|
||||
set => SetValue(ItemTemplateProperty, value);
|
||||
}
|
||||
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
|
||||
[Bindable(true)]
|
||||
public Collection<object> Items { get; }
|
||||
|
||||
internal Panel ItemsHost { get; set; }
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
ItemsHost?.Children.Clear();
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
ItemsHost = GetTemplateChild(ElementPanel) as Panel;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void OnItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
Refresh();
|
||||
UpdateItems();
|
||||
}
|
||||
|
||||
protected virtual DependencyObject GetContainerForItemOverride() => new ContentPresenter();
|
||||
|
||||
protected virtual bool IsItemItsOwnContainerOverride(object item) => item is ContentPresenter;
|
||||
|
||||
protected virtual void PrepareContainerForItemOverride(DependencyObject element, object item)
|
||||
{
|
||||
switch (element)
|
||||
{
|
||||
case ContentControl contentControl:
|
||||
contentControl.Content = item;
|
||||
contentControl.ContentTemplate = ItemTemplate;
|
||||
break;
|
||||
case ContentPresenter contentPresenter:
|
||||
contentPresenter.Content = item;
|
||||
contentPresenter.ContentTemplate = ItemTemplate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnItemTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var target = d as SimpleItemsControl;
|
||||
target?.Refresh();
|
||||
}
|
||||
|
||||
private static void OnItemContainerStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var target = d as SimpleItemsControl;
|
||||
target?.Refresh();
|
||||
}
|
||||
|
||||
protected virtual void Refresh()
|
||||
{
|
||||
if (ItemsHost == null) return;
|
||||
|
||||
ItemsHost.Children.Clear();
|
||||
foreach (var item in Items)
|
||||
{
|
||||
DependencyObject container;
|
||||
if (IsItemItsOwnContainerOverride(item))
|
||||
{
|
||||
container = item as DependencyObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
container = GetContainerForItemOverride();
|
||||
PrepareContainerForItemOverride(container, item);
|
||||
}
|
||||
|
||||
if (container is FrameworkElement element)
|
||||
{
|
||||
element.Style = ItemContainerStyle;
|
||||
ItemsHost.Children.Add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void UpdateItems()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 带上下文菜单的按钮
|
||||
/// </summary>
|
||||
public class ContextMenuButton : Button
|
||||
{
|
||||
public ContextMenu Menu { get; set; }
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
base.OnClick();
|
||||
if (Menu != null)
|
||||
{
|
||||
Menu.PlacementTarget = this;
|
||||
Menu.IsOpen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 带上下文菜单的切换按钮
|
||||
/// </summary>
|
||||
public class ContextMenuToggleButton : ToggleButton
|
||||
{
|
||||
public ContextMenu Menu { get; set; }
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
base.OnClick();
|
||||
if (Menu != null)
|
||||
{
|
||||
if (IsChecked == true)
|
||||
{
|
||||
Menu.PlacementTarget = this;
|
||||
Menu.IsOpen = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Menu.IsOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media.Media3D;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 封面流
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementViewport3D, Type = typeof(Viewport3D))]
|
||||
[TemplatePart(Name = ElementCamera, Type = typeof(ProjectionCamera))]
|
||||
[TemplatePart(Name = ElementVisualParent, Type = typeof(ModelVisual3D))]
|
||||
public class CoverFlow : Control
|
||||
{
|
||||
private const string ElementViewport3D = "PART_Viewport3D";
|
||||
|
||||
private const string ElementCamera = "PART_Camera";
|
||||
|
||||
private const string ElementVisualParent = "PART_VisualParent";
|
||||
|
||||
/// <summary>
|
||||
/// 最大显示数量的一半
|
||||
/// </summary>
|
||||
private const int MaxShowCountHalf = 7;
|
||||
|
||||
/// <summary>
|
||||
/// 页码
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PageIndexProperty = DependencyProperty.Register(
|
||||
"PageIndex", typeof(int), typeof(CoverFlow),
|
||||
new PropertyMetadata(ValueBoxes.Int0Box, OnPageIndexChanged, CoercePageIndex));
|
||||
|
||||
private static object CoercePageIndex(DependencyObject d, object baseValue)
|
||||
{
|
||||
var ctl = (CoverFlow) d;
|
||||
var v = (int) baseValue;
|
||||
|
||||
if (v < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (v >= ctl._uriList.Count)
|
||||
{
|
||||
return ctl._uriList.Count - 1;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
private static void OnPageIndexChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (CoverFlow) d;
|
||||
ctl.UpdateIndex((int)e.NewValue, (int)e.OldValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否循环
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty LoopProperty = DependencyProperty.Register(
|
||||
"Loop", typeof(bool), typeof(CoverFlow), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 存储所有的资源标识符
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, Uri> _uriList = new Dictionary<int, Uri>();
|
||||
|
||||
/// <summary>
|
||||
/// 当前在显示范围内的项
|
||||
/// </summary>
|
||||
private readonly Dictionary<int, CoverFlowItem> _itemShowList = new Dictionary<int, CoverFlowItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 相机
|
||||
/// </summary>
|
||||
private ProjectionCamera _camera;
|
||||
|
||||
/// <summary>
|
||||
/// 3d画布
|
||||
/// </summary>
|
||||
private Viewport3D _viewport3D;
|
||||
|
||||
/// <summary>
|
||||
/// 项容器
|
||||
/// </summary>
|
||||
private ModelVisual3D _visualParent;
|
||||
|
||||
/// <summary>
|
||||
/// 显示范围内第一个项的编号
|
||||
/// </summary>
|
||||
private int _firstShowIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 显示范围内最后一个项的编号
|
||||
/// </summary>
|
||||
private int _lastShowIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 在获取新的模板后是否需要更新项
|
||||
/// </summary>
|
||||
private bool _updateItems;
|
||||
|
||||
private bool _isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// 跳转编号
|
||||
/// </summary>
|
||||
private int _jumpToIndex = -1;
|
||||
|
||||
public CoverFlow()
|
||||
{
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (_isLoaded) return;
|
||||
_isLoaded = true;
|
||||
if (_updateItems)
|
||||
{
|
||||
UpdateShowRange();
|
||||
_updateItems = false;
|
||||
}
|
||||
if (_jumpToIndex > 0)
|
||||
{
|
||||
PageIndex = _jumpToIndex;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 页码
|
||||
/// </summary>
|
||||
public int PageIndex
|
||||
{
|
||||
get => (int) GetValue(PageIndexProperty);
|
||||
internal set => SetValue(PageIndexProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否循环
|
||||
/// </summary>
|
||||
public bool Loop
|
||||
{
|
||||
get => (bool)GetValue(LoopProperty);
|
||||
set => SetValue(LoopProperty, value);
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (_viewport3D != null)
|
||||
{
|
||||
_viewport3D.Children.Clear();
|
||||
_itemShowList.Clear();
|
||||
_viewport3D.MouseLeftButtonDown -= Viewport3D_MouseLeftButtonDown;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_viewport3D = GetTemplateChild(ElementViewport3D) as Viewport3D;
|
||||
if (_viewport3D != null)
|
||||
{
|
||||
_viewport3D.MouseLeftButtonDown += Viewport3D_MouseLeftButtonDown;
|
||||
}
|
||||
|
||||
_camera = GetTemplateChild(ElementCamera) as ProjectionCamera;
|
||||
_visualParent = GetTemplateChild(ElementVisualParent) as ModelVisual3D;
|
||||
|
||||
if (_isLoaded)
|
||||
{
|
||||
UpdateShowRange();
|
||||
_camera.Position = new Point3D(CoverFlowItem.Interval * PageIndex, _camera.Position.Y,
|
||||
_camera.Position.Z);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量添加资源
|
||||
/// </summary>
|
||||
/// <param name="uriList"></param>
|
||||
public void AddRange(List<Uri> uriList)
|
||||
{
|
||||
foreach (var uri in uriList)
|
||||
{
|
||||
_uriList.Add(_uriList.Count, uri);
|
||||
}
|
||||
_updateItems = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一项资源
|
||||
/// </summary>
|
||||
/// <param name="uriString"></param>
|
||||
public void Add(string uriString)
|
||||
{
|
||||
_uriList.Add(_uriList.Count, new Uri(uriString));
|
||||
_updateItems = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一项资源
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
public void Add(Uri uri)
|
||||
{
|
||||
_uriList.Add(_uriList.Count, uri);
|
||||
_updateItems = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 跳转
|
||||
/// </summary>
|
||||
public void JumpTo(int index) => _jumpToIndex = index;
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
base.OnMouseWheel(e);
|
||||
|
||||
if (e.Delta < 0)
|
||||
{
|
||||
var index = PageIndex + 1;
|
||||
PageIndex = index >= _uriList.Count ? (Loop ? 0 : _uriList.Count - 1) : index;
|
||||
}
|
||||
else
|
||||
{
|
||||
var index = PageIndex - 1;
|
||||
PageIndex = index < 0 ? (Loop ? _uriList.Count - 1 : 0) : index;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除指定位置的项
|
||||
/// </summary>
|
||||
/// <param name="pos"></param>
|
||||
private void Remove(int pos)
|
||||
{
|
||||
var item = _itemShowList[pos];
|
||||
_visualParent.Children.Remove(item);
|
||||
_itemShowList.Remove(pos);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移动项到指定的位置
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="animated"></param>
|
||||
private void Move(int index, bool animated) => _itemShowList[index].Move(PageIndex, animated);
|
||||
|
||||
private void Viewport3D_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var result = (RayMeshGeometry3DHitTestResult) VisualTreeHelper.HitTest(_viewport3D, e.GetPosition(_viewport3D));
|
||||
if (result != null)
|
||||
{
|
||||
foreach (var item in _itemShowList.Values)
|
||||
{
|
||||
if (item.HitTest(result.MeshHit))
|
||||
{
|
||||
PageIndex = item.Index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新项的位置
|
||||
/// </summary>
|
||||
/// <param name="newIndex"></param>
|
||||
/// <param name="oldIndex"></param>
|
||||
private void UpdateIndex(int newIndex, int oldIndex)
|
||||
{
|
||||
var animate = Math.Abs(newIndex - oldIndex) < MaxShowCountHalf;
|
||||
UpdateShowRange();
|
||||
if (newIndex > oldIndex)
|
||||
{
|
||||
if (oldIndex < _firstShowIndex)
|
||||
{
|
||||
oldIndex = _firstShowIndex;
|
||||
}
|
||||
for (var i = oldIndex; i <= newIndex; i++)
|
||||
{
|
||||
Move(i, animate);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (oldIndex > _lastShowIndex)
|
||||
{
|
||||
oldIndex = _lastShowIndex;
|
||||
}
|
||||
for (var i = oldIndex; i >= newIndex; i--)
|
||||
{
|
||||
Move(i, animate);
|
||||
}
|
||||
}
|
||||
_camera.Position = new Point3D(CoverFlowItem.Interval * newIndex, _camera.Position.Y,
|
||||
_camera.Position.Z);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新显示范围
|
||||
/// </summary>
|
||||
private void UpdateShowRange()
|
||||
{
|
||||
var newFirstShowIndex = Math.Max(PageIndex - MaxShowCountHalf, 0);
|
||||
var newLastShowIndex = Math.Min(PageIndex + MaxShowCountHalf, _uriList.Count - 1);
|
||||
|
||||
if (_firstShowIndex < newFirstShowIndex)
|
||||
{
|
||||
for (var i = _firstShowIndex; i < newFirstShowIndex; i++)
|
||||
{
|
||||
Remove(i);
|
||||
}
|
||||
}
|
||||
else if (newLastShowIndex < _lastShowIndex)
|
||||
{
|
||||
for (var i = newLastShowIndex; i < _lastShowIndex; i++)
|
||||
{
|
||||
Remove(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = newFirstShowIndex; i <= newLastShowIndex; i++)
|
||||
{
|
||||
if (!_itemShowList.ContainsKey(i))
|
||||
{
|
||||
var cover = new CoverFlowItem(i, PageIndex, BitmapFrame.Create(_uriList[i]));
|
||||
_itemShowList[i] = cover;
|
||||
_visualParent.Children.Add(cover);
|
||||
}
|
||||
}
|
||||
|
||||
_firstShowIndex = newFirstShowIndex;
|
||||
_lastShowIndex = newLastShowIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Media3D;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class CoverFlowItem : ModelVisual3D
|
||||
{
|
||||
internal static readonly double Interval = .2;
|
||||
|
||||
internal static readonly double AnimationSpeed = 400;
|
||||
|
||||
private readonly AxisAngleRotation3D _rotation3D;
|
||||
|
||||
private readonly TranslateTransform3D _transform3D;
|
||||
|
||||
private readonly Model3DGroup _model3DGroup;
|
||||
|
||||
private readonly ImageSource _source;
|
||||
|
||||
internal int Index { get; set; }
|
||||
|
||||
public CoverFlowItem(int itemIndex, int currentIndex, ImageSource source)
|
||||
{
|
||||
Index = itemIndex;
|
||||
_source = source;
|
||||
|
||||
_rotation3D = new AxisAngleRotation3D(new Vector3D(0, 1, 0), GetAngleByPos(currentIndex));
|
||||
_transform3D = new TranslateTransform3D(GetXByPos(currentIndex), 0, GetZByPos(currentIndex));
|
||||
|
||||
var transformGroup = new Transform3DGroup();
|
||||
transformGroup.Children.Add(new RotateTransform3D(_rotation3D));
|
||||
transformGroup.Children.Add(_transform3D);
|
||||
|
||||
_model3DGroup = new Model3DGroup();
|
||||
_model3DGroup.Children.Add(new GeometryModel3D(CreateItemGeometry(), new DiffuseMaterial(new ImageBrush(_source))));
|
||||
_model3DGroup.Transform = transformGroup;
|
||||
|
||||
Content = _model3DGroup;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建网格形状
|
||||
/// </summary>
|
||||
/// <param name="p0"></param>
|
||||
/// <param name="p1"></param>
|
||||
/// <param name="p2"></param>
|
||||
/// <param name="p3"></param>
|
||||
/// <returns></returns>
|
||||
private static MeshGeometry3D CreateMeshGeometry(Point3D p0, Point3D p1, Point3D p2, Point3D p3)
|
||||
{
|
||||
var positions = new Point3DCollection
|
||||
{
|
||||
p0,
|
||||
p1,
|
||||
p2,
|
||||
p3
|
||||
};
|
||||
|
||||
var triangleIndices = new Int32Collection
|
||||
{
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
2,
|
||||
3,
|
||||
0
|
||||
};
|
||||
|
||||
var textureCoordinates = new PointCollection
|
||||
{
|
||||
new Point(0, 1),
|
||||
new Point(1, 1),
|
||||
new Point(1, 0),
|
||||
new Point(0, 0),
|
||||
new Point(1, 0),
|
||||
new Point(1, 1)
|
||||
};
|
||||
|
||||
var normals = new Vector3DCollection
|
||||
{
|
||||
ArithmeticHelper.CalNormal(p0, p1, p2),
|
||||
ArithmeticHelper.CalNormal(p2, p3, p0)
|
||||
};
|
||||
|
||||
var geometry3D = new MeshGeometry3D
|
||||
{
|
||||
Positions = positions,
|
||||
TriangleIndices = triangleIndices,
|
||||
TextureCoordinates = textureCoordinates,
|
||||
Normals = normals
|
||||
};
|
||||
|
||||
geometry3D.Freeze();
|
||||
return geometry3D;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建内容形状
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Geometry3D CreateItemGeometry()
|
||||
{
|
||||
var sx = _source.Width > _source.Height ? 0 : 1 - _source.Width / _source.Height;
|
||||
var sy = _source.Width < _source.Height ? 0 : 1 - _source.Height / _source.Width;
|
||||
|
||||
var p0 = new Point3D(-1 + sx, -1 + sy, 0);
|
||||
var p1 = new Point3D(1 - sx, -1 + sy, 0);
|
||||
var p2 = new Point3D(1 - sx, 1 - sy, 0);
|
||||
var p3 = new Point3D(-1 + sx, 1 - sy, 0);
|
||||
|
||||
return CreateMeshGeometry(p0, p1, p2, p3);
|
||||
}
|
||||
|
||||
private double GetAngleByPos(int index) => Math.Sign(Index - index) * -90;
|
||||
|
||||
private double GetXByPos(int index) => Index * Interval + Math.Sign(Index - index) * 1.5;
|
||||
|
||||
private double GetZByPos(int index) => Index == index ? 1 : 0;
|
||||
|
||||
/// <summary>
|
||||
/// 移动
|
||||
/// </summary>
|
||||
internal void Move(int currentIndex, bool animated)
|
||||
{
|
||||
if (animated || _rotation3D.HasAnimatedProperties)
|
||||
{
|
||||
_rotation3D.BeginAnimation(AxisAngleRotation3D.AngleProperty,
|
||||
AnimationHelper.CreateAnimation(GetAngleByPos(currentIndex), AnimationSpeed));
|
||||
_transform3D.BeginAnimation(TranslateTransform3D.OffsetXProperty,
|
||||
AnimationHelper.CreateAnimation(GetXByPos(currentIndex), AnimationSpeed));
|
||||
_transform3D.BeginAnimation(TranslateTransform3D.OffsetZProperty,
|
||||
AnimationHelper.CreateAnimation(GetZByPos(currentIndex), AnimationSpeed));
|
||||
}
|
||||
else
|
||||
{
|
||||
_rotation3D.Angle = GetAngleByPos(currentIndex);
|
||||
_transform3D.OffsetX = GetXByPos(currentIndex);
|
||||
_transform3D.OffsetZ = GetZByPos(currentIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 命中测试
|
||||
/// </summary>
|
||||
/// <param name="mesh"></param>
|
||||
/// <returns></returns>
|
||||
internal bool HitTest(MeshGeometry3D mesh)
|
||||
{
|
||||
foreach (var item in _model3DGroup.Children)
|
||||
{
|
||||
if (item is GeometryModel3D geometryModel3D)
|
||||
{
|
||||
if (Equals(geometryModel3D.Geometry, mesh))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class CoverView : RegularItemsControl
|
||||
{
|
||||
private readonly CoverViewContent _viewContent;
|
||||
|
||||
private CoverViewItem _selectedItem;
|
||||
|
||||
public CoverView()
|
||||
{
|
||||
_viewContent = new CoverViewContent {ContentHeight = ItemContentHeight};
|
||||
|
||||
AddHandler(SelectableItem.SelectedEvent, new RoutedEventHandler(CoverViewItem_OnSelected));
|
||||
_viewContent.SetBinding(WidthProperty, new Binding(ActualWidthProperty.Name) { Source = this });
|
||||
}
|
||||
|
||||
private void CoverViewItem_OnSelected(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is CoverViewItem item)
|
||||
{
|
||||
if (_selectedItem == null)
|
||||
{
|
||||
item.IsSelected = true;
|
||||
_selectedItem = item;
|
||||
if (_viewContent != null)
|
||||
{
|
||||
_viewContent.Content = item.Content;
|
||||
UpdateCoverViewContent(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if(!Equals(_selectedItem, item))
|
||||
{
|
||||
_selectedItem.IsSelected = false;
|
||||
item.IsSelected = true;
|
||||
_selectedItem = item;
|
||||
if (_viewContent != null)
|
||||
{
|
||||
_viewContent.Content = item.Content;
|
||||
UpdateCoverViewContent(true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (_viewContent != null)
|
||||
{
|
||||
_viewContent.Content = null;
|
||||
UpdateCoverViewContent(false);
|
||||
}
|
||||
_selectedItem.IsSelected = false;
|
||||
_selectedItem = null;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
if (_viewContent.Style == null)
|
||||
{
|
||||
_viewContent.Style = CoverViewContentStyle;
|
||||
}
|
||||
|
||||
if (_selectedItem != null)
|
||||
{
|
||||
UpdateCoverViewContent(_selectedItem != null);
|
||||
}
|
||||
}
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride() => new CoverViewItem();
|
||||
|
||||
protected override bool IsItemItsOwnContainerOverride(object item) => item is CoverViewItem;
|
||||
|
||||
public static readonly DependencyProperty CoverViewContentStyleProperty = DependencyProperty.Register(
|
||||
"CoverViewContentStyle", typeof(Style), typeof(CoverView), new PropertyMetadata(default(Style)));
|
||||
|
||||
public Style CoverViewContentStyle
|
||||
{
|
||||
get => (Style) GetValue(CoverViewContentStyleProperty);
|
||||
set => SetValue(CoverViewContentStyleProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty GroupsProperty = DependencyProperty.Register(
|
||||
"Groups", typeof(int), typeof(CoverView), new FrameworkPropertyMetadata(ValueBoxes.Int5Box, FrameworkPropertyMetadataOptions.AffectsMeasure, OnGroupsChanged), IsGroupsValid);
|
||||
|
||||
internal static void OnGroupsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is CoverView ctl)
|
||||
{
|
||||
ctl.UpdateCoverViewContent(ctl._viewContent.IsOpen);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsGroupsValid(object value)
|
||||
{
|
||||
var v = (int)value;
|
||||
return v >= 1;
|
||||
}
|
||||
|
||||
public int Groups
|
||||
{
|
||||
get => (int)GetValue(GroupsProperty);
|
||||
set => SetValue(GroupsProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ItemContentHeightProperty = DependencyProperty.Register(
|
||||
"ItemContentHeight", typeof(double), typeof(CoverView), new PropertyMetadata(ValueBoxes.Double300Box));
|
||||
|
||||
public double ItemContentHeight
|
||||
{
|
||||
get => (double) GetValue(ItemContentHeightProperty);
|
||||
set => SetValue(ItemContentHeightProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
||||
{
|
||||
base.OnRenderSizeChanged(sizeInfo);
|
||||
|
||||
Groups = (int)(sizeInfo.NewSize.Width / (ItemWidth + ItemMargin.Left + ItemMargin.Right));
|
||||
}
|
||||
|
||||
protected override void Refresh()
|
||||
{
|
||||
if (ItemsHost == null) return;
|
||||
|
||||
ItemsHost.Children.Clear();
|
||||
var index = 0;
|
||||
foreach (var item in Items)
|
||||
{
|
||||
DependencyObject container;
|
||||
if (IsItemItsOwnContainerOverride(item))
|
||||
{
|
||||
container = item as DependencyObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
container = GetContainerForItemOverride();
|
||||
PrepareContainerForItemOverride(container, item);
|
||||
}
|
||||
|
||||
if (container is CoverViewItem element)
|
||||
{
|
||||
element.Index = index++;
|
||||
element.Style = ItemContainerStyle;
|
||||
ItemsHost.Children.Add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新内容视图
|
||||
/// </summary>
|
||||
private void UpdateCoverViewContent(bool isOpen)
|
||||
{
|
||||
if (_selectedItem == null) return;
|
||||
|
||||
ItemsHost.Children.Remove(_viewContent);
|
||||
|
||||
var total = Items.Count + 1;
|
||||
var totalRow = total / Groups + (total % Groups > 0 ? 1 : 0);
|
||||
if (total <= Groups)
|
||||
{
|
||||
ItemsHost.Children.Add(_viewContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
var row = _selectedItem.Index / Groups + 1;
|
||||
if (row == totalRow)
|
||||
{
|
||||
ItemsHost.Children.Add(_viewContent);
|
||||
}
|
||||
else
|
||||
{
|
||||
ItemsHost.Children.Insert(row * Groups, _viewContent);
|
||||
}
|
||||
}
|
||||
|
||||
_viewContent.UpdatePosition(_selectedItem.Index, Groups, _selectedItem.DesiredSize.Width);
|
||||
if (_viewContent.CanSwitch)
|
||||
{
|
||||
_viewContent.IsOpen = isOpen;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = ElementTriangle, Type = typeof(FrameworkElement))]
|
||||
[TemplatePart(Name = ElementContent, Type = typeof(FrameworkElement))]
|
||||
internal class CoverViewContent : ContentControl
|
||||
{
|
||||
private const string ElementTriangle = "PART_Triangle";
|
||||
|
||||
private const string ElementContent = "PART_Content";
|
||||
|
||||
private FrameworkElement _triangle;
|
||||
|
||||
private FrameworkElement _content;
|
||||
|
||||
internal bool WaitForUpdate { get; set; }
|
||||
|
||||
private int _index;
|
||||
|
||||
private int _groups;
|
||||
|
||||
private double _itemWidth;
|
||||
|
||||
private bool _isOpen;
|
||||
|
||||
internal bool CanSwitch { get; set; } = true;
|
||||
|
||||
internal bool IsOpen
|
||||
{
|
||||
get => _isOpen;
|
||||
set
|
||||
{
|
||||
if (_isOpen == value) return;
|
||||
_isOpen = value;
|
||||
OpenSwitch(value);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ContentHeightProperty = DependencyProperty.Register(
|
||||
"ContentHeight", typeof(double), typeof(CoverViewContent), new PropertyMetadata(ValueBoxes.Double300Box));
|
||||
|
||||
public double ContentHeight
|
||||
{
|
||||
get => (double) GetValue(ContentHeightProperty);
|
||||
set => SetValue(ContentHeightProperty, value);
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_triangle = GetTemplateChild(ElementTriangle) as FrameworkElement;
|
||||
_content = GetTemplateChild(ElementContent) as FrameworkElement;
|
||||
|
||||
if (WaitForUpdate)
|
||||
{
|
||||
_triangle.BeginAnimation(MarginProperty, AnimationHelper.CreateAnimation(new Thickness((_index % _groups + .5) * _itemWidth - _triangle.Width / 2, 0, 0, 0)));
|
||||
OpenSwitch(_isOpen);
|
||||
WaitForUpdate = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdatePosition(int index, int groups, double itemWidth)
|
||||
{
|
||||
if (_triangle == null)
|
||||
{
|
||||
_index = index;
|
||||
_groups = groups;
|
||||
_itemWidth = itemWidth;
|
||||
WaitForUpdate = true;
|
||||
return;
|
||||
}
|
||||
_triangle.BeginAnimation(MarginProperty, AnimationHelper.CreateAnimation(new Thickness((index % groups + .5) * itemWidth - _triangle.Width / 2, 0, 0, 0)));
|
||||
}
|
||||
|
||||
private void OpenSwitch(bool isOpen)
|
||||
{
|
||||
if (_content == null) return;
|
||||
var animation = AnimationHelper.CreateAnimation(isOpen ? ContentHeight : 0);
|
||||
_triangle.Show(false);
|
||||
this.Show(true);
|
||||
animation.Completed += (s, e) =>
|
||||
{
|
||||
CanSwitch = true;
|
||||
this.Show(IsOpen);
|
||||
if (IsOpen)
|
||||
{
|
||||
_triangle.Show(true);
|
||||
}
|
||||
};
|
||||
CanSwitch = false;
|
||||
_content.BeginAnimation(HeightProperty, animation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class CoverViewItem : HeaderedSelectableItem
|
||||
{
|
||||
internal int Index { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Security.Permissions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Resources;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class GifImage : Image, IDisposable
|
||||
{
|
||||
private static readonly Guid GifGuid = new Guid("{b96b3caa-0728-11d3-9d7b-0000f81ef32e}");
|
||||
|
||||
internal IntPtr NativeImage;
|
||||
|
||||
private byte[] _rawData;
|
||||
|
||||
private bool _isStart;
|
||||
|
||||
private bool _isLoaded;
|
||||
|
||||
static GifImage()
|
||||
{
|
||||
VisibilityProperty.OverrideMetadata(typeof(GifImage), new PropertyMetadata(default(Visibility), OnVisibilityChanged));
|
||||
}
|
||||
|
||||
private static void OnVisibilityChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (GifImage)s;
|
||||
var v = (Visibility)e.NewValue;
|
||||
if (v != Visibility.Visible)
|
||||
{
|
||||
ctl.StopAnimate();
|
||||
}
|
||||
else if (!ctl._isStart)
|
||||
{
|
||||
ctl.StartAnimate();
|
||||
}
|
||||
}
|
||||
|
||||
~GifImage() => Dispose(false);
|
||||
|
||||
public GifImage()
|
||||
{
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (DesignerProperties.GetIsInDesignMode(this)) return;
|
||||
if (_isLoaded) return;
|
||||
_isLoaded = true;
|
||||
if (Source is BitmapImage image)
|
||||
{
|
||||
var uri = image.UriSource;
|
||||
GetGifStreamFromPack(uri);
|
||||
StartAnimate();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public GifImage(string filename)
|
||||
{
|
||||
Source = null;
|
||||
CreateSourceFromFile(filename);
|
||||
StartAnimate();
|
||||
}
|
||||
|
||||
public GifImage(Stream stream)
|
||||
{
|
||||
Source = null;
|
||||
CreateSourceFromStream(stream);
|
||||
StartAnimate();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (NativeImage == IntPtr.Zero) return;
|
||||
try
|
||||
{
|
||||
StopAnimate();
|
||||
ExternDllHelper.Gdip.GdipDisposeImage(new HandleRef(this, NativeImage));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
finally
|
||||
{
|
||||
NativeImage = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateSourceFromFile(string filename)
|
||||
{
|
||||
filename = Path.GetFullPath(filename);
|
||||
|
||||
var status = ExternDllHelper.Gdip.GdipCreateBitmapFromFile(filename, out var bitmap);
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
|
||||
status = ExternDllHelper.Gdip.GdipImageForceValidation(new HandleRef(null, bitmap));
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
{
|
||||
ExternDllHelper.Gdip.GdipDisposeImage(new HandleRef(null, bitmap));
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
}
|
||||
|
||||
SetNativeImage(bitmap);
|
||||
|
||||
EnsureSave(this, filename, null);
|
||||
}
|
||||
|
||||
private void CreateSourceFromStream(Stream stream)
|
||||
{
|
||||
if (stream == null)
|
||||
throw new ArgumentException("stream null");
|
||||
|
||||
var status = ExternDllHelper.Gdip.GdipCreateBitmapFromStream(new GPStream(stream), out var bitmap);
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
|
||||
status = ExternDllHelper.Gdip.GdipImageForceValidation(new HandleRef(null, bitmap));
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
{
|
||||
ExternDllHelper.Gdip.GdipDisposeImage(new HandleRef(null, bitmap));
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
}
|
||||
|
||||
SetNativeImage(bitmap);
|
||||
|
||||
EnsureSave(this, null, stream);
|
||||
}
|
||||
|
||||
private void GetGifStreamFromPack(Uri uri)
|
||||
{
|
||||
try
|
||||
{
|
||||
StreamResourceInfo streamInfo;
|
||||
|
||||
if (!uri.IsAbsoluteUri)
|
||||
{
|
||||
streamInfo = Application.GetContentStream(uri) ?? Application.GetResourceStream(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (uri.GetLeftPart(UriPartial.Authority).Contains("siteoforigin"))
|
||||
{
|
||||
streamInfo = Application.GetRemoteStream(uri);
|
||||
}
|
||||
else
|
||||
{
|
||||
streamInfo = Application.GetContentStream(uri) ?? Application.GetResourceStream(uri);
|
||||
}
|
||||
}
|
||||
if (streamInfo == null)
|
||||
throw new FileNotFoundException("Resource not found.", uri.ToString());
|
||||
CreateSourceFromStream(streamInfo.Stream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
[ResourceExposure(ResourceScope.None)]
|
||||
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
|
||||
internal static void EnsureSave(GifImage image, string filename, Stream dataStream)
|
||||
{
|
||||
if (!image.RawGuid.Equals(GifGuid)) return;
|
||||
var animatedGif = false;
|
||||
|
||||
var dimensions = image.FrameDimensionsList;
|
||||
if (dimensions.Select(guid => new GifFrameDimension(guid)).Contains(GifFrameDimension.Time))
|
||||
{
|
||||
animatedGif = image.GetFrameCount(GifFrameDimension.Time) > 1;
|
||||
}
|
||||
|
||||
if (!animatedGif) return;
|
||||
|
||||
try
|
||||
{
|
||||
Stream created = null;
|
||||
long lastPos = 0;
|
||||
if (dataStream != null)
|
||||
{
|
||||
lastPos = dataStream.Position;
|
||||
dataStream.Position = 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (dataStream == null)
|
||||
{
|
||||
created = dataStream = File.OpenRead(filename);
|
||||
}
|
||||
|
||||
image._rawData = new byte[(int)dataStream.Length];
|
||||
dataStream.Read(image._rawData, 0, (int)dataStream.Length);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (created != null)
|
||||
{
|
||||
created.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
// ReSharper disable once PossibleNullReferenceException
|
||||
dataStream.Position = lastPos;
|
||||
}
|
||||
}
|
||||
}
|
||||
// possible exceptions for reading the filename
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
}
|
||||
catch (DirectoryNotFoundException)
|
||||
{
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
}
|
||||
// possible exceptions for setting/getting the position inside dataStream
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
}
|
||||
catch (ObjectDisposedException)
|
||||
{
|
||||
}
|
||||
// possible exception when reading stuff into dataStream
|
||||
catch (ArgumentException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal void SetNativeImage(IntPtr handle)
|
||||
{
|
||||
if (handle == IntPtr.Zero)
|
||||
throw new ArgumentException("NativeHandle0");
|
||||
|
||||
NativeImage = handle;
|
||||
}
|
||||
|
||||
internal Guid RawGuid
|
||||
{
|
||||
get
|
||||
{
|
||||
var guid = new Guid();
|
||||
|
||||
var status = ExternDllHelper.Gdip.GdipGetImageRawFormat(new HandleRef(this, NativeImage), ref guid);
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
|
||||
return guid;
|
||||
}
|
||||
}
|
||||
|
||||
internal void StartAnimate()
|
||||
{
|
||||
ImageAnimator.Animate(this, OnFrameChanged);
|
||||
_isStart = true;
|
||||
}
|
||||
|
||||
internal void StopAnimate()
|
||||
{
|
||||
ImageAnimator.StopAnimate(this, OnFrameChanged);
|
||||
_isStart = false;
|
||||
}
|
||||
|
||||
internal Guid[] FrameDimensionsList
|
||||
{
|
||||
get
|
||||
{
|
||||
var status = ExternDllHelper.Gdip.GdipImageGetFrameDimensionsCount(new HandleRef(this, NativeImage), out var count);
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
{
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
}
|
||||
|
||||
if (count <= 0)
|
||||
{
|
||||
return new Guid[0];
|
||||
}
|
||||
|
||||
var size = Marshal.SizeOf(typeof(Guid));
|
||||
|
||||
var buffer = Marshal.AllocHGlobal(checked(size * count));
|
||||
if (buffer == IntPtr.Zero)
|
||||
{
|
||||
throw ExternDllHelper.Gdip.StatusException(ExternDllHelper.Gdip.OutOfMemory);
|
||||
}
|
||||
|
||||
status = ExternDllHelper.Gdip.GdipImageGetFrameDimensionsList(new HandleRef(this, NativeImage), buffer, count);
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
{
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
}
|
||||
|
||||
var guids = new Guid[count];
|
||||
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
guids[i] = (Guid)ExternDllHelper.PtrToStructure((IntPtr)((long)buffer + size * i), typeof(Guid));
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(buffer);
|
||||
}
|
||||
|
||||
return guids;
|
||||
}
|
||||
}
|
||||
|
||||
internal int GetFrameCount(GifFrameDimension dimension)
|
||||
{
|
||||
var count = new[] { 0 };
|
||||
|
||||
var dimensionId = dimension.Guid;
|
||||
var status = ExternDllHelper.Gdip.GdipImageGetFrameCount(new HandleRef(this, NativeImage), ref dimensionId, count);
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
|
||||
return count[0];
|
||||
}
|
||||
|
||||
internal GifPropertyItem GetPropertyItem(int propid)
|
||||
{
|
||||
GifPropertyItem propitem;
|
||||
|
||||
var status = ExternDllHelper.Gdip.GdipGetPropertyItemSize(new HandleRef(this, NativeImage), propid, out var size);
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
|
||||
if (size == 0)
|
||||
return null;
|
||||
|
||||
var propdata = Marshal.AllocHGlobal(size);
|
||||
|
||||
if (propdata == IntPtr.Zero)
|
||||
throw ExternDllHelper.Gdip.StatusException(ExternDllHelper.Gdip.OutOfMemory);
|
||||
|
||||
status = ExternDllHelper.Gdip.GdipGetPropertyItem(new HandleRef(this, NativeImage), propid, size, propdata);
|
||||
|
||||
try
|
||||
{
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
{
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
}
|
||||
|
||||
propitem = GifPropertyItemInternal.ConvertFromMemory(propdata, 1)[0];
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(propdata);
|
||||
}
|
||||
|
||||
return propitem;
|
||||
}
|
||||
|
||||
internal int SelectActiveFrame(GifFrameDimension dimension, int frameIndex)
|
||||
{
|
||||
var count = new[] { 0 };
|
||||
|
||||
var dimensionId = dimension.Guid;
|
||||
var status = ExternDllHelper.Gdip.GdipImageSelectActiveFrame(new HandleRef(this, NativeImage), ref dimensionId, frameIndex);
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok)
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
|
||||
return count[0];
|
||||
}
|
||||
|
||||
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
||||
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||
[ResourceExposure(ResourceScope.Machine)]
|
||||
[ResourceConsumption(ResourceScope.Machine)]
|
||||
internal IntPtr GetHbitmap() => GetHbitmap(Colors.LightGray);
|
||||
|
||||
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode)]
|
||||
[EditorBrowsable(EditorBrowsableState.Advanced)]
|
||||
[ResourceExposure(ResourceScope.Machine)]
|
||||
[ResourceConsumption(ResourceScope.Machine)]
|
||||
internal IntPtr GetHbitmap(Color background)
|
||||
{
|
||||
var status = ExternDllHelper.Gdip.GdipCreateHBITMAPFromBitmap(new HandleRef(this, NativeImage), out var hBitmap,
|
||||
ColorHelper.ToWin32(background));
|
||||
if (status == 2 && (Width >= short.MaxValue || Height >= short.MaxValue))
|
||||
{
|
||||
throw new ArgumentException("GdiplusInvalidSize");
|
||||
}
|
||||
|
||||
if (status != ExternDllHelper.Gdip.Ok && NativeImage != IntPtr.Zero)
|
||||
{
|
||||
throw ExternDllHelper.Gdip.StatusException(status);
|
||||
}
|
||||
|
||||
return hBitmap;
|
||||
}
|
||||
|
||||
private void GetBitmapSource()
|
||||
{
|
||||
var handle = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
handle = GetHbitmap();
|
||||
Source = Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (handle != IntPtr.Zero)
|
||||
{
|
||||
ExternDllHelper.DeleteObject(handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnFrameChanged(object sender, EventArgs e)
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
ImageAnimator.UpdateFrames();
|
||||
Source?.Freeze();
|
||||
GetBitmapSource();
|
||||
InvalidateVisual();
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,883 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Imaging;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Properties.Langs;
|
||||
using HandyControl.Tools;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = ElementGridMain, Type = typeof(Grid))]
|
||||
[TemplatePart(Name = ElementCanvasSmallImg, Type = typeof(Canvas))]
|
||||
[TemplatePart(Name = ElementBorderMove, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementBorderBottom, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementImageMain, Type = typeof(Image))]
|
||||
public class ImageViewer : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementGridMain = "PART_GridMain";
|
||||
|
||||
private const string ElementCanvasSmallImg = "PART_CanvasSmallImg";
|
||||
|
||||
private const string ElementBorderMove = "PART_BorderMove";
|
||||
|
||||
private const string ElementBorderBottom = "PART_BorderBottom";
|
||||
|
||||
private const string ElementImageMain = "PART_ImageMain";
|
||||
|
||||
/// <summary>
|
||||
/// 缩放比间隔
|
||||
/// </summary>
|
||||
private const double ScaleInternal = 0.2;
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
/// <summary>
|
||||
/// 图片保存对话框
|
||||
/// </summary>
|
||||
private static readonly SaveFileDialog SaveFileDialog = new SaveFileDialog
|
||||
{
|
||||
Filter = $"{Lang.PngImg}|*.png"
|
||||
};
|
||||
|
||||
private Grid _gridMain;
|
||||
|
||||
private Canvas _canvasSmallImg;
|
||||
|
||||
private Border _borderMove;
|
||||
|
||||
private Border _borderBottom;
|
||||
|
||||
private Image _imageMain;
|
||||
|
||||
/// <summary>
|
||||
/// 右下角小图片是否加载过
|
||||
/// </summary>
|
||||
private bool _borderSmallIsLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// 图片是否可以在x轴方向移动
|
||||
/// </summary>
|
||||
private bool _canMoveX;
|
||||
|
||||
/// <summary>
|
||||
/// 图片是否可以在y轴方向移动
|
||||
/// </summary>
|
||||
private bool _canMoveY;
|
||||
|
||||
/// <summary>
|
||||
/// 图片实际位置
|
||||
/// </summary>
|
||||
private Thickness _imgActualMargin;
|
||||
|
||||
/// <summary>
|
||||
/// 图片实际旋转角度
|
||||
/// </summary>
|
||||
private double _imgActualRotate;
|
||||
|
||||
/// <summary>
|
||||
/// 图片实际旋缩放比
|
||||
/// </summary>
|
||||
private double _imgActualScale = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 在图片上鼠标移动时的即时位置
|
||||
/// </summary>
|
||||
private Point _imgCurrentPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标是否在图片上按下左键
|
||||
/// </summary>
|
||||
private bool _imgIsMouseLeftButtonDown;
|
||||
|
||||
/// <summary>
|
||||
/// 在图片上按下时图片的位置
|
||||
/// </summary>
|
||||
private Thickness _imgMouseDownMargin;
|
||||
|
||||
/// <summary>
|
||||
/// 在图片上按下时鼠标的位置
|
||||
/// </summary>
|
||||
private Point _imgMouseDownPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 在小图片上鼠标移动时的即时位置
|
||||
/// </summary>
|
||||
private Point _imgSmallCurrentPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标是否在小图片上按下左键
|
||||
/// </summary>
|
||||
private bool _imgSmallIsMouseLeftButtonDown;
|
||||
|
||||
/// <summary>
|
||||
/// 在小图片上按下时图片的位置
|
||||
/// </summary>
|
||||
private Thickness _imgSmallMouseDownMargin;
|
||||
|
||||
/// <summary>
|
||||
/// 在小图片上按下时鼠标的位置
|
||||
/// </summary>
|
||||
private Point _imgSmallMouseDownPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 图片长宽比
|
||||
/// </summary>
|
||||
private double _imgWidHeiScale;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已经初始化
|
||||
/// </summary>
|
||||
private bool _isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// 图片是否倾斜
|
||||
/// </summary>
|
||||
private bool _isOblique;
|
||||
|
||||
/// <summary>
|
||||
/// 缩放高度间隔
|
||||
/// </summary>
|
||||
private double _scaleInternalHeight;
|
||||
|
||||
/// <summary>
|
||||
/// 缩放宽度间隔
|
||||
/// </summary>
|
||||
private double _scaleInternalWidth;
|
||||
|
||||
/// <summary>
|
||||
/// 底部BorderBottom(包含一些图片操作)是否显示中
|
||||
/// </summary>
|
||||
private bool _showBorderBottom;
|
||||
|
||||
#endregion Data
|
||||
|
||||
#region ctor
|
||||
|
||||
public ImageViewer()
|
||||
{
|
||||
Loaded += ImageViewer_OnLoaded;
|
||||
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Save, ButtonSave_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Open, ButtonWindowsOpen_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Restore, ButtonActual_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Reduce, ButtonReduce_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Enlarge, ButtonEnlarge_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.RotateLeft, ButtonRotateLeft_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.RotateRight, ButtonRotateRight_OnClick));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带一个图片Uri的构造函数
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
public ImageViewer(Uri uri) : this()
|
||||
{
|
||||
try
|
||||
{
|
||||
ImageSource = BitmapFrame.Create(uri);
|
||||
ImgPath = uri.AbsolutePath;
|
||||
if (File.Exists(ImgPath))
|
||||
{
|
||||
var info = new FileInfo(ImgPath);
|
||||
ImgSize = info.Length;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show(Lang.ErrorImgPath);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带一个图片路径的构造函数
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
public ImageViewer(string path) : this(new Uri(path))
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Properties
|
||||
|
||||
public static readonly DependencyProperty ShowImgMapProperty = DependencyProperty.Register(
|
||||
"ShowImgMap", typeof(bool), typeof(ImageViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register(
|
||||
"ImageSource", typeof(BitmapFrame), typeof(ImageViewer), new PropertyMetadata(default(BitmapFrame)));
|
||||
|
||||
public static readonly DependencyProperty IsFullScreenProperty = DependencyProperty.Register(
|
||||
"IsFullScreen", typeof(bool), typeof(ImageViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
internal static readonly DependencyProperty ImgPathProperty = DependencyProperty.Register(
|
||||
"ImgPath", typeof(string), typeof(ImageViewer), new PropertyMetadata(default(string)));
|
||||
|
||||
internal static readonly DependencyProperty ImgSizeProperty = DependencyProperty.Register(
|
||||
"ImgSize", typeof(long), typeof(ImageViewer), new PropertyMetadata(-1L));
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示全屏按钮
|
||||
/// </summary>
|
||||
internal static readonly DependencyProperty ShowFullScreenButtonProperty = DependencyProperty.Register(
|
||||
"ShowFullScreenButton", typeof(bool), typeof(ImageViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 关闭按钮是否显示中
|
||||
/// </summary>
|
||||
internal static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.Register(
|
||||
"ShowCloseButton", typeof(bool), typeof(ImageViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
internal static readonly DependencyProperty ImageContentProperty = DependencyProperty.Register(
|
||||
"ImageContent", typeof(object), typeof(ImageViewer), new PropertyMetadata(default(object)));
|
||||
|
||||
internal static readonly DependencyProperty ImageMarginProperty = DependencyProperty.Register(
|
||||
"ImageMargin", typeof(Thickness), typeof(ImageViewer), new PropertyMetadata(default(Thickness)));
|
||||
|
||||
internal static readonly DependencyProperty ImageWidthProperty = DependencyProperty.Register(
|
||||
"ImageWidth", typeof(double), typeof(ImageViewer), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
internal static readonly DependencyProperty ImageHeightProperty = DependencyProperty.Register(
|
||||
"ImageHeight", typeof(double), typeof(ImageViewer), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
internal static readonly DependencyProperty ImageScaleProperty = DependencyProperty.Register(
|
||||
"ImageScale", typeof(double), typeof(ImageViewer), new PropertyMetadata(ValueBoxes.Double1Box, OnImageScaleChanged));
|
||||
|
||||
internal static readonly DependencyProperty ScaleStrProperty = DependencyProperty.Register(
|
||||
"ScaleStr", typeof(string), typeof(ImageViewer), new PropertyMetadata("100%"));
|
||||
|
||||
internal static readonly DependencyProperty ImageRotateProperty = DependencyProperty.Register(
|
||||
"ImageRotate", typeof(double), typeof(ImageViewer), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
internal static readonly DependencyProperty ShowSmallImgInternalProperty = DependencyProperty.Register(
|
||||
"ShowSmallImgInternal", typeof(bool), typeof(ImageViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsFullScreen
|
||||
{
|
||||
get => (bool)GetValue(IsFullScreenProperty);
|
||||
set => SetValue(IsFullScreenProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowImgMap
|
||||
{
|
||||
get => (bool)GetValue(ShowImgMapProperty);
|
||||
set => SetValue(ShowImgMapProperty, value);
|
||||
}
|
||||
|
||||
public BitmapFrame ImageSource
|
||||
{
|
||||
get => (BitmapFrame)GetValue(ImageSourceProperty);
|
||||
set => SetValue(ImageSourceProperty, value);
|
||||
}
|
||||
|
||||
internal object ImageContent
|
||||
{
|
||||
get => GetValue(ImageContentProperty);
|
||||
set => SetValue(ImageContentProperty, value);
|
||||
}
|
||||
|
||||
internal string ImgPath
|
||||
{
|
||||
get => (string)GetValue(ImgPathProperty);
|
||||
set => SetValue(ImgPathProperty, value);
|
||||
}
|
||||
|
||||
internal long ImgSize
|
||||
{
|
||||
get => (long)GetValue(ImgSizeProperty);
|
||||
set => SetValue(ImgSizeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示全屏按钮
|
||||
/// </summary>
|
||||
internal bool ShowFullScreenButton
|
||||
{
|
||||
get => (bool)GetValue(ShowFullScreenButtonProperty);
|
||||
set => SetValue(ShowFullScreenButtonProperty, value);
|
||||
}
|
||||
|
||||
internal Thickness ImageMargin
|
||||
{
|
||||
get => (Thickness)GetValue(ImageMarginProperty);
|
||||
set => SetValue(ImageMarginProperty, value);
|
||||
}
|
||||
|
||||
internal double ImageWidth
|
||||
{
|
||||
get => (double)GetValue(ImageWidthProperty);
|
||||
set => SetValue(ImageWidthProperty, value);
|
||||
}
|
||||
|
||||
internal double ImageHeight
|
||||
{
|
||||
get => (double)GetValue(ImageHeightProperty);
|
||||
set => SetValue(ImageHeightProperty, value);
|
||||
}
|
||||
|
||||
internal double ImageScale
|
||||
{
|
||||
get => (double)GetValue(ImageScaleProperty);
|
||||
set => SetValue(ImageScaleProperty, value);
|
||||
}
|
||||
|
||||
internal string ScaleStr
|
||||
{
|
||||
get => (string)GetValue(ScaleStrProperty);
|
||||
set => SetValue(ScaleStrProperty, value);
|
||||
}
|
||||
|
||||
internal double ImageRotate
|
||||
{
|
||||
get => (double)GetValue(ImageRotateProperty);
|
||||
set => SetValue(ImageRotateProperty, value);
|
||||
}
|
||||
|
||||
internal bool ShowSmallImgInternal
|
||||
{
|
||||
get => (bool)GetValue(ShowSmallImgInternalProperty);
|
||||
set => SetValue(ShowSmallImgInternalProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图片原始宽度
|
||||
/// </summary>
|
||||
private double ImageOriWidth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图片原始高度
|
||||
/// </summary>
|
||||
private double ImageOriHeight { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关闭按钮是否显示中
|
||||
/// </summary>
|
||||
internal bool ShowCloseButton
|
||||
{
|
||||
get => (bool)GetValue(ShowCloseButtonProperty);
|
||||
set => SetValue(ShowCloseButtonProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 底部BorderBottom(包含一些图片操作)是否显示中
|
||||
/// </summary>
|
||||
internal bool ShowBorderBottom
|
||||
{
|
||||
get => _showBorderBottom;
|
||||
set
|
||||
{
|
||||
if (_showBorderBottom == value) return;
|
||||
_borderBottom?.BeginAnimation(OpacityProperty,
|
||||
value ? AnimationHelper.CreateAnimation(1, 100) : AnimationHelper.CreateAnimation(0, 400));
|
||||
_showBorderBottom = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (_imageMain != null) _imageMain.MouseLeftButtonDown -= ImageMain_OnMouseLeftButtonDown;
|
||||
|
||||
if (_canvasSmallImg != null)
|
||||
{
|
||||
_canvasSmallImg.MouseLeftButtonDown -= CanvasSmallImg_OnMouseLeftButtonDown;
|
||||
_canvasSmallImg.MouseLeftButtonUp -= CanvasSmallImg_OnMouseLeftButtonUp;
|
||||
_canvasSmallImg.MouseMove -= CanvasSmallImg_OnMouseMove;
|
||||
_canvasSmallImg.MouseLeave -= CanvasSmallImg_OnMouseLeave;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_gridMain = GetTemplateChild(ElementGridMain) as Grid;
|
||||
_canvasSmallImg = GetTemplateChild(ElementCanvasSmallImg) as Canvas;
|
||||
_borderMove = GetTemplateChild(ElementBorderMove) as Border;
|
||||
_imageMain = GetTemplateChild(ElementImageMain) as Image;
|
||||
_borderBottom = GetTemplateChild(ElementBorderBottom) as Border;
|
||||
|
||||
if (_imageMain != null)
|
||||
{
|
||||
var t = new RotateTransform();
|
||||
BindingOperations.SetBinding(t, RotateTransform.AngleProperty, new Binding(ImageRotateProperty.Name) {Source = this});
|
||||
_imageMain.LayoutTransform = t;
|
||||
_imageMain.MouseLeftButtonDown += ImageMain_OnMouseLeftButtonDown;
|
||||
}
|
||||
|
||||
if (_canvasSmallImg != null)
|
||||
{
|
||||
_canvasSmallImg.MouseLeftButtonDown += CanvasSmallImg_OnMouseLeftButtonDown;
|
||||
_canvasSmallImg.MouseLeftButtonUp += CanvasSmallImg_OnMouseLeftButtonUp;
|
||||
_canvasSmallImg.MouseMove += CanvasSmallImg_OnMouseMove;
|
||||
_canvasSmallImg.MouseLeave += CanvasSmallImg_OnMouseLeave;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnImageScaleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ImageViewer imageViewer && e.NewValue is double newValue)
|
||||
{
|
||||
imageViewer.ImageWidth = imageViewer.ImageOriWidth * newValue;
|
||||
imageViewer.ImageHeight = imageViewer.ImageOriHeight * newValue;
|
||||
imageViewer.ScaleStr = $"{newValue * 100:#0}%";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
private void Init()
|
||||
{
|
||||
if (ImageSource == null) return;
|
||||
|
||||
double width;
|
||||
double height;
|
||||
|
||||
if (!_isOblique)
|
||||
{
|
||||
width = ImageSource.Width;
|
||||
height = ImageSource.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
width = ImageSource.Height;
|
||||
height = ImageSource.Width;
|
||||
}
|
||||
|
||||
ImageWidth = width;
|
||||
ImageHeight = height;
|
||||
ImageOriWidth = width;
|
||||
ImageOriHeight = height;
|
||||
_scaleInternalWidth = ImageOriWidth * ScaleInternal;
|
||||
_scaleInternalHeight = ImageOriHeight * ScaleInternal;
|
||||
|
||||
if (Math.Abs(height - 0) < 0.001 || Math.Abs(width - 0) < 0.001)
|
||||
{
|
||||
MessageBox.Show(Lang.ErrorImgSize);
|
||||
return;
|
||||
}
|
||||
|
||||
_imgWidHeiScale = width / height;
|
||||
var scaleWindow = ActualWidth / ActualHeight;
|
||||
if (_imgWidHeiScale > scaleWindow)
|
||||
{
|
||||
if (width > ActualWidth)
|
||||
ImageScale = ActualWidth / width;
|
||||
}
|
||||
else if (height > ActualHeight)
|
||||
{
|
||||
ImageScale = ActualHeight / height;
|
||||
}
|
||||
else
|
||||
{
|
||||
ImageScale = 1;
|
||||
}
|
||||
|
||||
ImageMargin = new Thickness((ActualWidth - ImageWidth) / 2, (ActualHeight - ImageHeight) / 2, 0, 0);
|
||||
|
||||
_imgActualScale = 1;
|
||||
_imgActualMargin = ImageMargin;
|
||||
}
|
||||
|
||||
private void ButtonActual_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var scaleAnimation = AnimationHelper.CreateAnimation(1);
|
||||
scaleAnimation.FillBehavior = FillBehavior.Stop;
|
||||
_imgActualScale = 1;
|
||||
scaleAnimation.Completed += (s, e1) =>
|
||||
{
|
||||
ImageScale = 1;
|
||||
_canMoveX = ImageWidth > ActualWidth;
|
||||
_canMoveY = ImageHeight > ActualHeight;
|
||||
BorderSmallShowSwitch();
|
||||
};
|
||||
var thickness = new Thickness((ActualWidth - ImageOriWidth) / 2, (ActualHeight - ImageOriHeight) / 2, 0, 0);
|
||||
var marginAnimation = AnimationHelper.CreateAnimation(thickness);
|
||||
marginAnimation.FillBehavior = FillBehavior.Stop;
|
||||
_imgActualMargin = thickness;
|
||||
marginAnimation.Completed += (s, e1) => { ImageMargin = thickness; };
|
||||
|
||||
BeginAnimation(ImageScaleProperty, scaleAnimation);
|
||||
BeginAnimation(ImageMarginProperty, marginAnimation);
|
||||
}
|
||||
|
||||
private void ButtonReduce_OnClick(object sender, RoutedEventArgs e) => ScaleImg(false);
|
||||
|
||||
private void ButtonEnlarge_OnClick(object sender, RoutedEventArgs e) => ScaleImg(true);
|
||||
|
||||
private void ButtonRotateLeft_OnClick(object sender, RoutedEventArgs e) => RotateImg(_imgActualRotate - 90);
|
||||
|
||||
private void ButtonRotateRight_OnClick(object sender, RoutedEventArgs e) => RotateImg(_imgActualRotate + 90);
|
||||
|
||||
private void ButtonSave_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ImageSource == null) return;
|
||||
SaveFileDialog.FileName = $"{DateTime.Now:yyyy-M-d-h-m-s.fff}";
|
||||
if (SaveFileDialog.ShowDialog() == true)
|
||||
{
|
||||
var path = SaveFileDialog.FileName;
|
||||
using (var fileStream = new FileStream(path, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
var encoder = new PngBitmapEncoder();
|
||||
encoder.Frames.Add(BitmapFrame.Create(ImageSource));
|
||||
encoder.Save(fileStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonWindowsOpen_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Process.Start(ImgPath);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
MessageBox.Show(exception.Message);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
MoveImg();
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseLeave(e);
|
||||
_imgIsMouseLeftButtonDown = false;
|
||||
ShowBorderBottom = false;
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
base.OnMouseWheel(e);
|
||||
ScaleImg(e.Delta > 0);
|
||||
}
|
||||
|
||||
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
||||
{
|
||||
base.OnRenderSizeChanged(sizeInfo);
|
||||
|
||||
if (!_isLoaded) return;
|
||||
|
||||
if (ImageWidth < 0.001 || ImageHeight < 0.001) return;
|
||||
|
||||
_canMoveX = true;
|
||||
_canMoveY = true;
|
||||
|
||||
var marginX = ImageMargin.Left;
|
||||
var marginY = ImageMargin.Top;
|
||||
|
||||
if (ImageWidth <= ActualWidth)
|
||||
{
|
||||
_canMoveX = false;
|
||||
marginX = (ActualWidth - ImageWidth) / 2;
|
||||
}
|
||||
|
||||
if (ImageHeight <= ActualHeight)
|
||||
{
|
||||
_canMoveY = false;
|
||||
marginY = (ActualHeight - ImageHeight) / 2;
|
||||
}
|
||||
|
||||
ImageMargin = new Thickness(marginX, marginY, 0, 0);
|
||||
_imgActualMargin = ImageMargin;
|
||||
|
||||
BorderSmallShowSwitch();
|
||||
_imgSmallMouseDownMargin = _borderMove.Margin;
|
||||
MoveSmallImg(_imgSmallMouseDownMargin.Left, _imgSmallMouseDownMargin.Top);
|
||||
}
|
||||
|
||||
private void ImageViewer_OnLoaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_isLoaded) return;
|
||||
Init();
|
||||
_isLoaded = true;
|
||||
}
|
||||
|
||||
private void ImageMain_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_imgMouseDownPoint = Mouse.GetPosition(_gridMain);
|
||||
_imgMouseDownMargin = ImageMargin;
|
||||
_imgIsMouseLeftButtonDown = true;
|
||||
}
|
||||
|
||||
protected override void OnPreviewMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnPreviewMouseLeftButtonUp(e);
|
||||
_imgIsMouseLeftButtonDown = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 右下角小图片显示切换
|
||||
/// </summary>
|
||||
private void BorderSmallShowSwitch()
|
||||
{
|
||||
if (_canMoveX || _canMoveY)
|
||||
{
|
||||
if (!_borderSmallIsLoaded)
|
||||
{
|
||||
_canvasSmallImg.Background = new VisualBrush(_imageMain);
|
||||
InitBorderSmall();
|
||||
_borderSmallIsLoaded = true;
|
||||
}
|
||||
|
||||
ShowSmallImgInternal = true;
|
||||
UpdateBorderSmall();
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowSmallImgInternal = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化右下角小图片
|
||||
/// </summary>
|
||||
private void InitBorderSmall()
|
||||
{
|
||||
var scaleWindow = _canvasSmallImg.MaxWidth / _canvasSmallImg.MaxHeight;
|
||||
if (_imgWidHeiScale > scaleWindow)
|
||||
{
|
||||
_canvasSmallImg.Width = _canvasSmallImg.MaxWidth;
|
||||
_canvasSmallImg.Height = _canvasSmallImg.Width / _imgWidHeiScale;
|
||||
}
|
||||
else
|
||||
{
|
||||
_canvasSmallImg.Width = _canvasSmallImg.MaxHeight * _imgWidHeiScale;
|
||||
_canvasSmallImg.Height = _canvasSmallImg.MaxHeight;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新右下角小图片
|
||||
/// </summary>
|
||||
private void UpdateBorderSmall()
|
||||
{
|
||||
if (!ShowSmallImgInternal) return;
|
||||
|
||||
var widthMin = Math.Min(ImageWidth, ActualWidth);
|
||||
var heightMin = Math.Min(ImageHeight, ActualHeight);
|
||||
|
||||
_borderMove.Width = widthMin / ImageWidth * _canvasSmallImg.Width;
|
||||
_borderMove.Height = heightMin / ImageHeight * _canvasSmallImg.Height;
|
||||
|
||||
var marginX = -ImageMargin.Left / ImageWidth * _canvasSmallImg.Width;
|
||||
var marginY = -ImageMargin.Top / ImageHeight * _canvasSmallImg.Height;
|
||||
|
||||
var marginXMax = _canvasSmallImg.Width - _borderMove.Width;
|
||||
var marginYMax = _canvasSmallImg.Height - _borderMove.Height;
|
||||
|
||||
marginX = Math.Max(0, marginX);
|
||||
marginX = Math.Min(marginXMax, marginX);
|
||||
marginY = Math.Max(0, marginY);
|
||||
marginY = Math.Min(marginYMax, marginY);
|
||||
|
||||
_borderMove.Margin = new Thickness(marginX, marginY, 0, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缩放图片
|
||||
/// </summary>
|
||||
/// <param name="isEnlarge"></param>
|
||||
private void ScaleImg(bool isEnlarge)
|
||||
{
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) return;
|
||||
var oldImageWidth = ImageWidth;
|
||||
var olgImageHeight = ImageHeight;
|
||||
|
||||
var tempScale = isEnlarge ? _imgActualScale + ScaleInternal : _imgActualScale - ScaleInternal;
|
||||
if (Math.Abs(tempScale) < ScaleInternal || Math.Abs(tempScale - 50) < 0.001)
|
||||
return;
|
||||
|
||||
ImageScale = tempScale;
|
||||
|
||||
var posCanvas = Mouse.GetPosition(_gridMain);
|
||||
var posImg = new Point(posCanvas.X - _imgActualMargin.Left, posCanvas.Y - _imgActualMargin.Top);
|
||||
|
||||
var marginX = .5 * _scaleInternalWidth;
|
||||
var marginY = .5 * _scaleInternalHeight;
|
||||
|
||||
if (ImageWidth > ActualWidth)
|
||||
{
|
||||
_canMoveX = true;
|
||||
if (ImageHeight > ActualHeight)
|
||||
{
|
||||
_canMoveY = true;
|
||||
marginX = posImg.X / oldImageWidth * _scaleInternalWidth;
|
||||
marginY = posImg.Y / olgImageHeight * _scaleInternalHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
_canMoveY = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_canMoveY = ImageHeight > ActualHeight;
|
||||
_canMoveX = false;
|
||||
}
|
||||
|
||||
Thickness thickness;
|
||||
if (isEnlarge)
|
||||
{
|
||||
thickness = new Thickness(_imgActualMargin.Left - marginX, _imgActualMargin.Top - marginY, 0, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
var marginActualX = _imgActualMargin.Left + marginX;
|
||||
var marginActualY = _imgActualMargin.Top + marginY;
|
||||
var subX = ImageWidth - ActualWidth;
|
||||
var subY = ImageHeight - ActualHeight;
|
||||
|
||||
var right = Math.Abs(_borderMove.Width - _canvasSmallImg.ActualWidth + _borderMove.Margin.Left);
|
||||
var top = Math.Abs(_borderMove.Height - _canvasSmallImg.ActualHeight + _borderMove.Margin.Top);
|
||||
if (Math.Abs(ImageMargin.Left) < 0.001 || right < 0.001)
|
||||
marginActualX = _imgActualMargin.Left + _borderMove.Margin.Left /
|
||||
(_canvasSmallImg.ActualWidth - _borderMove.Width) * _scaleInternalWidth;
|
||||
if (Math.Abs(ImageMargin.Top) < 0.001 || top < 0.001)
|
||||
marginActualY = _imgActualMargin.Top + _borderMove.Margin.Top /
|
||||
(_canvasSmallImg.ActualHeight - _borderMove.Height) * _scaleInternalHeight;
|
||||
if (subX < 0.001) marginActualX = (ActualWidth - ImageWidth) / 2;
|
||||
if (subY < 0.001) marginActualY = (ActualHeight - ImageHeight) / 2;
|
||||
thickness = new Thickness(marginActualX, marginActualY, 0, 0);
|
||||
}
|
||||
|
||||
ImageMargin = thickness;
|
||||
_imgActualScale = tempScale;
|
||||
_imgActualMargin = thickness;
|
||||
BorderSmallShowSwitch();
|
||||
|
||||
_imgSmallMouseDownMargin = _borderMove.Margin;
|
||||
MoveSmallImg(_imgSmallMouseDownMargin.Left, _imgSmallMouseDownMargin.Top);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 旋转图片
|
||||
/// </summary>
|
||||
/// <param name="rotate"></param>
|
||||
private void RotateImg(double rotate)
|
||||
{
|
||||
_imgActualRotate = rotate;
|
||||
|
||||
_isOblique = ((int) _imgActualRotate - 90) % 180 == 0;
|
||||
ShowSmallImgInternal = false;
|
||||
Init();
|
||||
InitBorderSmall();
|
||||
|
||||
var animation = AnimationHelper.CreateAnimation(rotate);
|
||||
animation.Completed += (s, e1) => { ImageRotate = rotate; };
|
||||
animation.FillBehavior = FillBehavior.Stop;
|
||||
BeginAnimation(ImageRotateProperty, animation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移动图片
|
||||
/// </summary>
|
||||
private void MoveImg()
|
||||
{
|
||||
_imgCurrentPoint = Mouse.GetPosition(_gridMain);
|
||||
ShowCloseButton = _imgCurrentPoint.Y < 200;
|
||||
ShowBorderBottom = _imgCurrentPoint.Y > ActualHeight - 200;
|
||||
|
||||
if (_imgIsMouseLeftButtonDown)
|
||||
{
|
||||
var subX = _imgCurrentPoint.X - _imgMouseDownPoint.X;
|
||||
var subY = _imgCurrentPoint.Y - _imgMouseDownPoint.Y;
|
||||
|
||||
var marginX = _imgMouseDownMargin.Left;
|
||||
if (ImageWidth > ActualWidth)
|
||||
{
|
||||
marginX = _imgMouseDownMargin.Left + subX;
|
||||
if (marginX >= 0)
|
||||
marginX = 0;
|
||||
else if (-marginX + ActualWidth >= ImageWidth) marginX = ActualWidth - ImageWidth;
|
||||
_canMoveX = true;
|
||||
}
|
||||
|
||||
var marginY = _imgMouseDownMargin.Top;
|
||||
if (ImageHeight > ActualHeight)
|
||||
{
|
||||
marginY = _imgMouseDownMargin.Top + subY;
|
||||
if (marginY >= 0)
|
||||
marginY = 0;
|
||||
else if (-marginY + ActualHeight >= ImageHeight) marginY = ActualHeight - ImageHeight;
|
||||
_canMoveY = true;
|
||||
}
|
||||
|
||||
ImageMargin = new Thickness(marginX, marginY, 0, 0);
|
||||
_imgActualMargin = ImageMargin;
|
||||
|
||||
UpdateBorderSmall();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移动小图片
|
||||
/// </summary>
|
||||
private void MoveSmallImg()
|
||||
{
|
||||
if (_imgSmallIsMouseLeftButtonDown)
|
||||
{
|
||||
_imgSmallCurrentPoint = Mouse.GetPosition(_canvasSmallImg);
|
||||
|
||||
var subX = _imgSmallCurrentPoint.X - _imgSmallMouseDownPoint.X;
|
||||
var subY = _imgSmallCurrentPoint.Y - _imgSmallMouseDownPoint.Y;
|
||||
|
||||
var marginX = _imgSmallMouseDownMargin.Left + subX;
|
||||
var marginY = _imgSmallMouseDownMargin.Top + subY;
|
||||
|
||||
MoveSmallImg(marginX, marginY);
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveSmallImg(double marginX, double marginY)
|
||||
{
|
||||
if (marginX < 0)
|
||||
marginX = 0;
|
||||
else if (marginX + _borderMove.Width >= _canvasSmallImg.Width)
|
||||
marginX = _canvasSmallImg.Width - _borderMove.Width;
|
||||
if (marginY < 0)
|
||||
marginY = 0;
|
||||
else if (marginY + _borderMove.Height >= _canvasSmallImg.Height)
|
||||
marginY = _canvasSmallImg.Height - _borderMove.Height;
|
||||
_borderMove.Margin = new Thickness(marginX, marginY, 0, 0);
|
||||
|
||||
var marginActualX = (ActualWidth - ImageWidth) / 2;
|
||||
var marginActualY = (ActualHeight - ImageHeight) / 2;
|
||||
|
||||
if (_canMoveX) marginActualX = -marginX / _canvasSmallImg.Width * ImageWidth;
|
||||
if (_canMoveY) marginActualY = -marginY / _canvasSmallImg.Height * ImageHeight;
|
||||
|
||||
ImageMargin = new Thickness(marginActualX, marginActualY, 0, 0);
|
||||
_imgActualMargin = ImageMargin;
|
||||
}
|
||||
|
||||
private void CanvasSmallImg_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_imgSmallMouseDownPoint = Mouse.GetPosition(_canvasSmallImg);
|
||||
_imgSmallMouseDownMargin = _borderMove.Margin;
|
||||
_imgSmallIsMouseLeftButtonDown = true;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void CanvasSmallImg_OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) => _imgSmallIsMouseLeftButtonDown = false;
|
||||
|
||||
private void CanvasSmallImg_OnMouseMove(object sender, MouseEventArgs e) => MoveSmallImg();
|
||||
|
||||
private void CanvasSmallImg_OnMouseLeave(object sender, MouseEventArgs e) => _imgSmallIsMouseLeftButtonDown = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <inheritdoc cref="IDataInput" />
|
||||
public class ComboBox : System.Windows.Controls.ComboBox, IDataInput
|
||||
{
|
||||
public ComboBox()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Clear, (s, e) =>
|
||||
{
|
||||
ClearValue(SelectedValueProperty);
|
||||
ClearValue(SelectedItemProperty);
|
||||
ClearValue(SelectedIndexProperty);
|
||||
ClearValue(TextProperty);
|
||||
}));
|
||||
}
|
||||
|
||||
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
|
||||
{
|
||||
base.OnSelectionChanged(e);
|
||||
VerifyData();
|
||||
}
|
||||
|
||||
public Func<string, OperationResult<bool>> VerifyFunc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据是否错误
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register(
|
||||
"IsError", typeof(bool), typeof(ComboBox), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsError
|
||||
{
|
||||
get => (bool) GetValue(IsErrorProperty);
|
||||
set => SetValue(IsErrorProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 错误提示
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ErrorStrProperty = DependencyProperty.Register(
|
||||
"ErrorStr", typeof(string), typeof(ComboBox), new PropertyMetadata(default(string)));
|
||||
|
||||
public string ErrorStr
|
||||
{
|
||||
get => (string) GetValue(ErrorStrProperty);
|
||||
set => SetValue(ErrorStrProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyPropertyKey TextTypePropertyKey =
|
||||
DependencyProperty.RegisterReadOnly("TextType", typeof(TextType), typeof(ComboBox),
|
||||
new PropertyMetadata(default(TextType)));
|
||||
|
||||
/// <summary>
|
||||
/// 文本类型
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TextTypeProperty = TextTypePropertyKey.DependencyProperty;
|
||||
|
||||
public TextType TextType
|
||||
{
|
||||
get => (TextType) GetValue(TextTypeProperty);
|
||||
set => SetValue(TextTypeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示清除按钮
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowClearButtonProperty = DependencyProperty.Register(
|
||||
"ShowClearButton", typeof(bool), typeof(ComboBox), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowClearButton
|
||||
{
|
||||
get => (bool) GetValue(ShowClearButtonProperty);
|
||||
set => SetValue(ShowClearButtonProperty, value);
|
||||
}
|
||||
|
||||
public virtual bool VerifyData()
|
||||
{
|
||||
OperationResult<bool> result;
|
||||
|
||||
if (VerifyFunc != null)
|
||||
{
|
||||
result = VerifyFunc.Invoke(Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Text))
|
||||
{
|
||||
result = OperationResult.Success();
|
||||
}
|
||||
else if (InfoElement.GetNecessary(this))
|
||||
{
|
||||
result = OperationResult.Failed(Properties.Langs.Lang.IsNecessary);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Success();
|
||||
}
|
||||
}
|
||||
|
||||
IsError = !result.Data;
|
||||
ErrorStr = result.Message;
|
||||
return result.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using HandyControl.Data;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 表示可以成为一个数据输入控件
|
||||
/// </summary>
|
||||
public interface IDataInput
|
||||
{
|
||||
/// <summary>
|
||||
/// 验证数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
bool VerifyData();
|
||||
|
||||
/// <summary>
|
||||
/// 数据验证委托
|
||||
/// </summary>
|
||||
Func<string, OperationResult<bool>> VerifyFunc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据是否错误
|
||||
/// </summary>
|
||||
bool IsError { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误提示
|
||||
/// </summary>
|
||||
string ErrorStr { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 文本类型
|
||||
/// </summary>
|
||||
TextType TextType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示清除按钮
|
||||
/// </summary>
|
||||
bool ShowClearButton { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,400 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <inheritdoc cref="IDataInput" />
|
||||
/// <summary>
|
||||
/// 数值选择控件
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementTextBox, Type = typeof(TextBox))]
|
||||
public class NumericUpDown : Control, IDataInput
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementTextBox = "PART_TextBox";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private TextBox _textBox;
|
||||
|
||||
#endregion Data
|
||||
|
||||
public NumericUpDown()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Prev, (s, e) =>
|
||||
{
|
||||
Value += Increment;
|
||||
_textBox.Text = CurrentText;
|
||||
_textBox.Select(_textBox.Text.Length, 0);
|
||||
}));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Next, (s, e) =>
|
||||
{
|
||||
Value -= Increment;
|
||||
_textBox.Text = CurrentText;
|
||||
_textBox.Select(_textBox.Text.Length, 0);
|
||||
}));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Clear, (s, e) =>
|
||||
{
|
||||
ClearValue(ValueProperty);
|
||||
_textBox.Text = string.Empty;
|
||||
}));
|
||||
|
||||
Loaded += (s, e) => OnApplyTemplate();
|
||||
}
|
||||
|
||||
protected override void OnGotFocus(RoutedEventArgs e)
|
||||
{
|
||||
base.OnGotFocus(e);
|
||||
|
||||
if (_textBox != null)
|
||||
{
|
||||
_textBox?.Focus();
|
||||
_textBox.Select(_textBox.Text.Length, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (_textBox != null)
|
||||
{
|
||||
_textBox.TextChanged -= TextBox_TextChanged;
|
||||
_textBox.PreviewKeyDown -= TextBox_PreviewKeyDown;
|
||||
_textBox.LostFocus -= TextBox_LostFocus;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_textBox = GetTemplateChild(ElementTextBox) as TextBox;
|
||||
|
||||
if (_textBox != null)
|
||||
{
|
||||
_textBox.TextChanged += TextBox_TextChanged;
|
||||
_textBox.PreviewKeyDown += TextBox_PreviewKeyDown;
|
||||
_textBox.LostFocus += TextBox_LostFocus;
|
||||
_textBox.Text = CurrentText;
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (IsError) return;
|
||||
_textBox.Text = CurrentText;
|
||||
}
|
||||
|
||||
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Up)
|
||||
{
|
||||
Value += Increment;
|
||||
_textBox.Text = CurrentText;
|
||||
}
|
||||
else if (e.Key == Key.Down)
|
||||
{
|
||||
Value -= Increment;
|
||||
_textBox.Text = CurrentText;
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (!VerifyData()) return;
|
||||
if (double.TryParse(_textBox.Text, out var value))
|
||||
{
|
||||
Value = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
base.OnMouseWheel(e);
|
||||
|
||||
if (_textBox.IsFocused)
|
||||
{
|
||||
Value += e.Delta > 0 ? Increment : -Increment;
|
||||
_textBox.Text = CurrentText;
|
||||
_textBox.Select(_textBox.Text.Length, 0);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private string CurrentText => DecimalPlaces.HasValue
|
||||
? Value.ToString($"#0.{new string('0', DecimalPlaces.Value)}")
|
||||
: Value.ToString("#0");
|
||||
|
||||
protected virtual void OnValueChanged(FunctionEventArgs<double> e) => RaiseEvent(e);
|
||||
|
||||
/// <summary>
|
||||
/// 值改变事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent ValueChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<double>>), typeof(NumericUpDown));
|
||||
|
||||
/// <summary>
|
||||
/// 值改变事件
|
||||
/// </summary>
|
||||
public event EventHandler<FunctionEventArgs<double>> ValueChanged
|
||||
{
|
||||
add => AddHandler(ValueChangedEvent, value);
|
||||
remove => RemoveHandler(ValueChangedEvent, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前值
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
|
||||
"Value", typeof(double), typeof(NumericUpDown),
|
||||
new FrameworkPropertyMetadata(default(double), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
OnValueChanged, CoerceValue), ArithmeticHelper.IsValidDoubleValue);
|
||||
|
||||
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NumericUpDown) d;
|
||||
var v = (double) e.NewValue;
|
||||
|
||||
ctl.OnValueChanged(new FunctionEventArgs<double>(ValueChangedEvent, ctl)
|
||||
{
|
||||
Info = v
|
||||
});
|
||||
}
|
||||
|
||||
private static object CoerceValue(DependencyObject d, object basevalue)
|
||||
{
|
||||
var ctl = (NumericUpDown)d;
|
||||
var minimum = ctl.Minimum;
|
||||
var num = (double)basevalue;
|
||||
if (num < minimum)
|
||||
{
|
||||
ctl.Value = minimum;
|
||||
return minimum;
|
||||
}
|
||||
var maximum = ctl.Maximum;
|
||||
if (num > maximum)
|
||||
{
|
||||
ctl.Value = maximum;
|
||||
}
|
||||
|
||||
var result = num > maximum ? maximum : num;
|
||||
if (!ctl.DecimalPlaces.HasValue)
|
||||
{
|
||||
result = Math.Floor(result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前值
|
||||
/// </summary>
|
||||
public double Value
|
||||
{
|
||||
get => (double) GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最大值
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(
|
||||
"Maximum", typeof(double), typeof(NumericUpDown), new PropertyMetadata(100.0, OnMaximumChanged, CoerceMaximum), ArithmeticHelper.IsValidDoubleValue);
|
||||
|
||||
private static void OnMaximumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NumericUpDown)d;
|
||||
ctl.CoerceValue(MinimumProperty);
|
||||
ctl.CoerceValue(ValueProperty);
|
||||
}
|
||||
|
||||
private static object CoerceMaximum(DependencyObject d, object basevalue)
|
||||
{
|
||||
var minimum = ((NumericUpDown)d).Minimum;
|
||||
return (double)basevalue < minimum ? minimum : basevalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最大值
|
||||
/// </summary>
|
||||
public double Maximum
|
||||
{
|
||||
get => (double) GetValue(MaximumProperty);
|
||||
set => SetValue(MaximumProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最小值
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register(
|
||||
"Minimum", typeof(double), typeof(NumericUpDown), new PropertyMetadata(default(double), OnMinimumChanged, CoerceMinimum), ArithmeticHelper.IsValidDoubleValue);
|
||||
|
||||
private static void OnMinimumChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NumericUpDown)d;
|
||||
ctl.CoerceValue(MaximumProperty);
|
||||
ctl.CoerceValue(ValueProperty);
|
||||
}
|
||||
|
||||
private static object CoerceMinimum(DependencyObject d, object basevalue)
|
||||
{
|
||||
var maximum = ((NumericUpDown)d).Maximum;
|
||||
return (double)basevalue > maximum ? maximum : basevalue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 最小值
|
||||
/// </summary>
|
||||
public double Minimum
|
||||
{
|
||||
get => (double) GetValue(MinimumProperty);
|
||||
set => SetValue(MinimumProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指示每单击一下按钮时增加或减少的数量
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IncrementProperty = DependencyProperty.Register(
|
||||
"Increment", typeof(double), typeof(NumericUpDown), new PropertyMetadata(ValueBoxes.Double1Box));
|
||||
|
||||
/// <summary>
|
||||
/// 指示每单击一下按钮时增加或减少的数量
|
||||
/// </summary>
|
||||
public double Increment
|
||||
{
|
||||
get => (double) GetValue(IncrementProperty);
|
||||
set => SetValue(IncrementProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 指示要显示的小数位数
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DecimalPlacesProperty = DependencyProperty.Register(
|
||||
"DecimalPlaces", typeof(int?), typeof(NumericUpDown), new PropertyMetadata(default(int?)));
|
||||
|
||||
/// <summary>
|
||||
/// 指示要显示的小数位数
|
||||
/// </summary>
|
||||
public int? DecimalPlaces
|
||||
{
|
||||
get => (int?) GetValue(DecimalPlacesProperty);
|
||||
set => SetValue(DecimalPlacesProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示上下调值按钮
|
||||
/// </summary>
|
||||
internal static readonly DependencyProperty ShowUpDownButtonProperty = DependencyProperty.Register(
|
||||
"ShowUpDownButton", typeof(bool), typeof(NumericUpDown), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示上下调值按钮
|
||||
/// </summary>
|
||||
internal bool ShowUpDownButton
|
||||
{
|
||||
get => (bool) GetValue(ShowUpDownButtonProperty);
|
||||
set => SetValue(ShowUpDownButtonProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据是否错误
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register(
|
||||
"IsError", typeof(bool), typeof(NumericUpDown), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsError
|
||||
{
|
||||
get => (bool) GetValue(IsErrorProperty);
|
||||
set => SetValue(IsErrorProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 错误提示
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ErrorStrProperty = DependencyProperty.Register(
|
||||
"ErrorStr", typeof(string), typeof(NumericUpDown), new PropertyMetadata(default(string)));
|
||||
|
||||
public string ErrorStr
|
||||
{
|
||||
get => (string) GetValue(ErrorStrProperty);
|
||||
set => SetValue(ErrorStrProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyPropertyKey TextTypePropertyKey =
|
||||
DependencyProperty.RegisterReadOnly("TextType", typeof(TextType), typeof(NumericUpDown),
|
||||
new PropertyMetadata(default(TextType)));
|
||||
|
||||
/// <summary>
|
||||
/// 文本类型
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TextTypeProperty = TextTypePropertyKey.DependencyProperty;
|
||||
|
||||
public TextType TextType
|
||||
{
|
||||
get => (TextType) GetValue(TextTypeProperty);
|
||||
set => SetValue(TextTypeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示清除按钮
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowClearButtonProperty = DependencyProperty.Register(
|
||||
"ShowClearButton", typeof(bool), typeof(NumericUpDown), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowClearButton
|
||||
{
|
||||
get => (bool) GetValue(ShowClearButtonProperty);
|
||||
set => SetValue(ShowClearButtonProperty, value);
|
||||
}
|
||||
|
||||
public Func<string, OperationResult<bool>> VerifyFunc { get; set; }
|
||||
|
||||
public virtual bool VerifyData()
|
||||
{
|
||||
OperationResult<bool> result;
|
||||
|
||||
if (VerifyFunc != null)
|
||||
{
|
||||
result = VerifyFunc.Invoke(_textBox.Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_textBox.Text))
|
||||
{
|
||||
if (double.TryParse(_textBox.Text, out var value))
|
||||
{
|
||||
if (value < Minimum || value > Maximum)
|
||||
{
|
||||
result = OperationResult.Failed(Properties.Langs.Lang.OutOfRange);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Success();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Failed(Properties.Langs.Lang.FormatError);
|
||||
}
|
||||
}
|
||||
else if (InfoElement.GetNecessary(this))
|
||||
{
|
||||
result = OperationResult.Failed(Properties.Langs.Lang.IsNecessary);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Success();
|
||||
}
|
||||
}
|
||||
|
||||
IsError = !result.Data;
|
||||
ErrorStr = result.Message;
|
||||
return result.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Security;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Properties.Langs;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <inheritdoc cref="IDataInput" />
|
||||
[TemplatePart(Name = ElementPasswordBox, Type = typeof(System.Windows.Controls.PasswordBox))]
|
||||
public class PasswordBox : Control, IDataInput
|
||||
{
|
||||
private const string ElementPasswordBox = "PART_PasswordBox";
|
||||
|
||||
/// <summary>
|
||||
/// 掩码字符
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PasswordCharProperty =
|
||||
System.Windows.Controls.PasswordBox.PasswordCharProperty.AddOwner(typeof(PasswordBox),
|
||||
new FrameworkPropertyMetadata('●'));
|
||||
|
||||
/// <summary>
|
||||
/// 数据是否错误
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register(
|
||||
"IsError", typeof(bool), typeof(PasswordBox), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 错误提示
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ErrorStrProperty = DependencyProperty.Register(
|
||||
"ErrorStr", typeof(string), typeof(PasswordBox), new PropertyMetadata(default(string)));
|
||||
|
||||
/// <summary>
|
||||
/// 文本类型
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TextTypeProperty = DependencyProperty.Register(
|
||||
"TextType", typeof(TextType), typeof(PasswordBox), new PropertyMetadata(default(TextType)));
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示清除按钮
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowClearButtonProperty = DependencyProperty.Register(
|
||||
"ShowClearButton", typeof(bool), typeof(PasswordBox), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
private string _password;
|
||||
|
||||
private char _passwordChar;
|
||||
|
||||
public PasswordBox()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Clear, (s, e) => ActualPasswordBox.Clear()));
|
||||
}
|
||||
|
||||
public System.Windows.Controls.PasswordBox ActualPasswordBox { get; set; }
|
||||
|
||||
[DefaultValue("")]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public string Password
|
||||
{
|
||||
get => ActualPasswordBox?.Password;
|
||||
set
|
||||
{
|
||||
if (ActualPasswordBox == null)
|
||||
{
|
||||
_password = value;
|
||||
return;
|
||||
}
|
||||
ActualPasswordBox.Password = value;
|
||||
}
|
||||
}
|
||||
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public SecureString SecurePassword => ActualPasswordBox?.SecurePassword;
|
||||
|
||||
public char PasswordChar
|
||||
{
|
||||
get => (char) GetValue(PasswordCharProperty);
|
||||
set => SetValue(PasswordCharProperty, value);
|
||||
}
|
||||
|
||||
public Func<string, OperationResult<bool>> VerifyFunc { get; set; }
|
||||
|
||||
public bool IsError
|
||||
{
|
||||
get => (bool) GetValue(IsErrorProperty);
|
||||
set => SetValue(IsErrorProperty, value);
|
||||
}
|
||||
|
||||
public string ErrorStr
|
||||
{
|
||||
get => (string) GetValue(ErrorStrProperty);
|
||||
set => SetValue(ErrorStrProperty, value);
|
||||
}
|
||||
|
||||
public TextType TextType
|
||||
{
|
||||
get => (TextType) GetValue(TextTypeProperty);
|
||||
set => SetValue(TextTypeProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowClearButton
|
||||
{
|
||||
get => (bool) GetValue(ShowClearButtonProperty);
|
||||
set => SetValue(ShowClearButtonProperty, value);
|
||||
}
|
||||
|
||||
public virtual bool VerifyData()
|
||||
{
|
||||
OperationResult<bool> result;
|
||||
|
||||
if (VerifyFunc != null)
|
||||
{
|
||||
result = VerifyFunc.Invoke(ActualPasswordBox.Password);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ActualPasswordBox.Password))
|
||||
result = OperationResult.Success();
|
||||
else if (InfoElement.GetNecessary(this))
|
||||
result = OperationResult.Failed(Lang.IsNecessary);
|
||||
else
|
||||
result = OperationResult.Success();
|
||||
}
|
||||
|
||||
IsError = !result.Data;
|
||||
ErrorStr = result.Message;
|
||||
return result.Data;
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (ActualPasswordBox != null)
|
||||
ActualPasswordBox.PasswordChanged -= PasswordBox_PasswordChanged;
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
ActualPasswordBox = GetTemplateChild(ElementPasswordBox) as System.Windows.Controls.PasswordBox;
|
||||
if (ActualPasswordBox != null)
|
||||
{
|
||||
ActualPasswordBox.PasswordChanged += PasswordBox_PasswordChanged;
|
||||
|
||||
if (!string.IsNullOrEmpty(_password))
|
||||
{
|
||||
ActualPasswordBox.Password = _password;
|
||||
_password = null;
|
||||
}
|
||||
if (_passwordChar != default(char))
|
||||
{
|
||||
ActualPasswordBox.PasswordChar = _passwordChar;
|
||||
_passwordChar = default(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Paste() => ActualPasswordBox.Paste();
|
||||
|
||||
public void SelectAll() => ActualPasswordBox.SelectAll();
|
||||
|
||||
public void Clear() => ActualPasswordBox.SelectAll();
|
||||
|
||||
|
||||
|
||||
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
VerifyData();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = ElementTextBox, Type = typeof(WatermarkTextBox))]
|
||||
public class SearchBar : Control, IDataInput
|
||||
{
|
||||
private const string ElementTextBox = "PART_TextBox";
|
||||
|
||||
private WatermarkTextBox _textBox;
|
||||
|
||||
public Func<string, OperationResult<bool>> VerifyFunc { get; set; }
|
||||
|
||||
public SearchBar()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Clear, (s, e) => _textBox.Text = string.Empty));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Search, (s, e) => OnSearchStarted()));
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent SearchStartedEvent =
|
||||
EventManager.RegisterRoutedEvent("SearchStarted", RoutingStrategy.Direct,
|
||||
typeof(EventHandler<FunctionEventArgs<string>>), typeof(SearchBar));
|
||||
|
||||
public event EventHandler<FunctionEventArgs<string>> SearchStarted
|
||||
{
|
||||
add => AddHandler(SearchStartedEvent, value);
|
||||
remove => RemoveHandler(SearchStartedEvent, value);
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (_textBox != null)
|
||||
{
|
||||
_textBox.TextChanged -= TextBox_TextChanged;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_textBox = GetTemplateChild(ElementTextBox) as WatermarkTextBox;
|
||||
if (_textBox != null)
|
||||
{
|
||||
SetBinding(TextProperty, new Binding(TextProperty.Name)
|
||||
{
|
||||
Source = _textBox,
|
||||
Mode = BindingMode.TwoWay,
|
||||
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
|
||||
});
|
||||
_textBox.TextChanged += TextBox_TextChanged;
|
||||
_textBox.KeyDown += TextBox_KeyDown;
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBox_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
OnSearchStarted();
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (IsRealTime)
|
||||
{
|
||||
OnSearchStarted();
|
||||
}
|
||||
VerifyData();
|
||||
}
|
||||
|
||||
private void OnSearchStarted()
|
||||
{
|
||||
RaiseEvent(new FunctionEventArgs<string>(SearchStartedEvent, this)
|
||||
{
|
||||
Info = Text
|
||||
});
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
"Text", typeof(string), typeof(SearchBar), new PropertyMetadata(default(string)));
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 数据是否错误
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register(
|
||||
"IsError", typeof(bool), typeof(SearchBar), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsError
|
||||
{
|
||||
get => (bool)GetValue(IsErrorProperty);
|
||||
set => SetValue(IsErrorProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 错误提示
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ErrorStrProperty = DependencyProperty.Register(
|
||||
"ErrorStr", typeof(string), typeof(SearchBar), new PropertyMetadata(default(string)));
|
||||
|
||||
public string ErrorStr
|
||||
{
|
||||
get => (string)GetValue(ErrorStrProperty);
|
||||
set => SetValue(ErrorStrProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文本类型
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TextTypeProperty = DependencyProperty.Register(
|
||||
"TextType", typeof(TextType), typeof(SearchBar), new PropertyMetadata(default(TextType)));
|
||||
|
||||
public TextType TextType
|
||||
{
|
||||
get => (TextType)GetValue(TextTypeProperty);
|
||||
set => SetValue(TextTypeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示清除按钮
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowClearButtonProperty = DependencyProperty.Register(
|
||||
"ShowClearButton", typeof(bool), typeof(SearchBar), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowClearButton
|
||||
{
|
||||
get => (bool)GetValue(ShowClearButtonProperty);
|
||||
set => SetValue(ShowClearButtonProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否实时搜索
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsRealTimeProperty = DependencyProperty.Register(
|
||||
"IsRealTime", typeof(bool), typeof(SearchBar), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 是否实时搜索
|
||||
/// </summary>
|
||||
public bool IsRealTime
|
||||
{
|
||||
get => (bool)GetValue(IsRealTimeProperty);
|
||||
set => SetValue(IsRealTimeProperty, value);
|
||||
}
|
||||
|
||||
public virtual bool VerifyData()
|
||||
{
|
||||
OperationResult<bool> result;
|
||||
|
||||
if (VerifyFunc != null)
|
||||
{
|
||||
result = VerifyFunc.Invoke(Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Text))
|
||||
{
|
||||
if (TextType != TextType.Common)
|
||||
{
|
||||
result = Text.IsKindOf(TextType) ? OperationResult.Success() : OperationResult.Failed(Properties.Langs.Lang.FormatError);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Success();
|
||||
}
|
||||
}
|
||||
else if (InfoElement.GetNecessary(this))
|
||||
{
|
||||
result = OperationResult.Failed(Properties.Langs.Lang.IsNecessary);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Success();
|
||||
}
|
||||
}
|
||||
|
||||
IsError = !result.Data;
|
||||
ErrorStr = result.Message;
|
||||
return result.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <inheritdoc cref="IDataInput" />
|
||||
public class TextBox : System.Windows.Controls.TextBox, IDataInput
|
||||
{
|
||||
public TextBox()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Clear, (s, e) =>
|
||||
{
|
||||
ClearValue(TextProperty);
|
||||
}));
|
||||
}
|
||||
|
||||
protected override void OnTextChanged(TextChangedEventArgs e)
|
||||
{
|
||||
base.OnTextChanged(e);
|
||||
VerifyData();
|
||||
}
|
||||
|
||||
public Func<string, OperationResult<bool>> VerifyFunc { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据是否错误
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register(
|
||||
"IsError", typeof(bool), typeof(TextBox), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsError
|
||||
{
|
||||
get => (bool) GetValue(IsErrorProperty);
|
||||
set => SetValue(IsErrorProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 错误提示
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ErrorStrProperty = DependencyProperty.Register(
|
||||
"ErrorStr", typeof(string), typeof(TextBox), new PropertyMetadata(default(string)));
|
||||
|
||||
public string ErrorStr
|
||||
{
|
||||
get => (string) GetValue(ErrorStrProperty);
|
||||
set => SetValue(ErrorStrProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 文本类型
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TextTypeProperty = DependencyProperty.Register(
|
||||
"TextType", typeof(TextType), typeof(TextBox), new PropertyMetadata(default(TextType)));
|
||||
|
||||
public TextType TextType
|
||||
{
|
||||
get => (TextType) GetValue(TextTypeProperty);
|
||||
set => SetValue(TextTypeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示清除按钮
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowClearButtonProperty = DependencyProperty.Register(
|
||||
"ShowClearButton", typeof(bool), typeof(TextBox), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowClearButton
|
||||
{
|
||||
get => (bool) GetValue(ShowClearButtonProperty);
|
||||
set => SetValue(ShowClearButtonProperty, value);
|
||||
}
|
||||
|
||||
public virtual bool VerifyData()
|
||||
{
|
||||
OperationResult<bool> result;
|
||||
|
||||
if (VerifyFunc != null)
|
||||
{
|
||||
result = VerifyFunc.Invoke(Text);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Text))
|
||||
{
|
||||
if (TextType != TextType.Common)
|
||||
{
|
||||
result = Text.IsKindOf(TextType) ? OperationResult.Success() : OperationResult.Failed(Properties.Langs.Lang.FormatError);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Success();
|
||||
}
|
||||
}
|
||||
else if (InfoElement.GetNecessary(this))
|
||||
{
|
||||
result = OperationResult.Failed(Properties.Langs.Lang.IsNecessary);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Success();
|
||||
}
|
||||
}
|
||||
|
||||
IsError = !result.Data;
|
||||
ErrorStr = result.Message;
|
||||
return result.Data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = ElementContentName, Type = typeof(ContentControl))]
|
||||
public class WatermarkTextBox : TextBox
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementContentName = "PART_Watermark";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private ContentControl _elementContent;
|
||||
|
||||
#endregion Data
|
||||
|
||||
#region Public Properties
|
||||
|
||||
#region Watermark
|
||||
|
||||
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(
|
||||
"Watermark", typeof(object), typeof(WatermarkTextBox), new PropertyMetadata(default(object), OnWatermarkChanged));
|
||||
|
||||
private static void OnWatermarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (WatermarkTextBox) d;
|
||||
ctl.OnWatermarkChanged();
|
||||
}
|
||||
|
||||
public object Watermark
|
||||
{
|
||||
get => GetValue(WatermarkProperty);
|
||||
set => SetValue(WatermarkProperty, value);
|
||||
}
|
||||
|
||||
#endregion Watermark
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_elementContent = GetTemplateChild(ElementContentName) as ContentControl;
|
||||
_elementContent?.SetBinding(ContentControl.ContentProperty, new Binding(WatermarkProperty.Name) {Source = this});
|
||||
|
||||
OnWatermarkChanged();
|
||||
}
|
||||
|
||||
private void OnWatermarkChanged()
|
||||
{
|
||||
if (_elementContent == null) return;
|
||||
if (Watermark is Control watermarkControl)
|
||||
{
|
||||
watermarkControl.IsTabStop = false;
|
||||
watermarkControl.IsHitTestVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnGotFocus(RoutedEventArgs e)
|
||||
{
|
||||
base.OnGotFocus(e);
|
||||
if (IsEnabled)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Text))
|
||||
{
|
||||
Select(0, Text.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
internal class InternalCanvas : Canvas
|
||||
{
|
||||
internal static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
||||
"Orientation", typeof(Orientation), typeof(InternalCanvas), new PropertyMetadata(default(Orientation)));
|
||||
|
||||
internal Orientation Orientation
|
||||
{
|
||||
get => (Orientation) GetValue(OrientationProperty);
|
||||
set => SetValue(OrientationProperty, value);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size arrangeSize)
|
||||
{
|
||||
foreach (UIElement internalChild in InternalChildren)
|
||||
{
|
||||
if (internalChild == null) continue;
|
||||
|
||||
var x = 0.0;
|
||||
var y = 0.0;
|
||||
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
x = (arrangeSize.Width - internalChild.DesiredSize.Width) / 2;
|
||||
|
||||
var top = GetTop(internalChild);
|
||||
if (!double.IsNaN(top))
|
||||
{
|
||||
y = top;
|
||||
}
|
||||
else
|
||||
{
|
||||
var bottom = GetBottom(internalChild);
|
||||
if (!double.IsNaN(bottom))
|
||||
y = arrangeSize.Height - internalChild.DesiredSize.Height - bottom;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
y = (arrangeSize.Height - internalChild.DesiredSize.Height) / 2;
|
||||
|
||||
var left = GetLeft(internalChild);
|
||||
if (!double.IsNaN(left))
|
||||
{
|
||||
x = left;
|
||||
}
|
||||
else
|
||||
{
|
||||
var right = GetRight(internalChild);
|
||||
if (!double.IsNaN(right))
|
||||
x = arrangeSize.Width - internalChild.DesiredSize.Width - right;
|
||||
}
|
||||
}
|
||||
|
||||
internalChild.Arrange(new Rect(new Point(x, y), internalChild.DesiredSize));
|
||||
}
|
||||
return arrangeSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shapes;
|
||||
using HandyControl.Data;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public abstract class LoadingBase : ContentControl
|
||||
{
|
||||
protected Storyboard Storyboard;
|
||||
|
||||
public static readonly DependencyProperty IsRunningProperty = DependencyProperty.Register(
|
||||
"IsRunning", typeof(bool), typeof(LoadingBase), new PropertyMetadata(ValueBoxes.TrueBox, (o, args) =>
|
||||
{
|
||||
var ctl = (LoadingBase) o;
|
||||
var v = (bool) args.NewValue;
|
||||
if (v)
|
||||
{
|
||||
ctl.Storyboard?.Resume();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctl.Storyboard?.Pause();
|
||||
}
|
||||
}));
|
||||
|
||||
public bool IsRunning
|
||||
{
|
||||
get => (bool) GetValue(IsRunningProperty);
|
||||
set => SetValue(IsRunningProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DotCountProperty = DependencyProperty.Register(
|
||||
"DotCount", typeof(int), typeof(LoadingBase),
|
||||
new FrameworkPropertyMetadata(5, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty DotIntervalProperty = DependencyProperty.Register(
|
||||
"DotInterval", typeof(double), typeof(LoadingBase),
|
||||
new FrameworkPropertyMetadata(10.0, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty DotBorderBrushProperty = DependencyProperty.Register(
|
||||
"DotBorderBrush", typeof(Brush), typeof(LoadingBase),
|
||||
new FrameworkPropertyMetadata(default(Brush), FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty DotBorderThicknessProperty = DependencyProperty.Register(
|
||||
"DotBorderThickness", typeof(double), typeof(LoadingBase),
|
||||
new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty DotDiameterProperty = DependencyProperty.Register(
|
||||
"DotDiameter", typeof(double), typeof(LoadingBase),
|
||||
new FrameworkPropertyMetadata(6.0, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty DotSpeedProperty = DependencyProperty.Register(
|
||||
"DotSpeed", typeof(double), typeof(LoadingBase),
|
||||
new FrameworkPropertyMetadata(4.0, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public static readonly DependencyProperty DotDelayTimeProperty = DependencyProperty.Register(
|
||||
"DotDelayTime", typeof(double), typeof(LoadingBase),
|
||||
new FrameworkPropertyMetadata(80.0, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
protected readonly Canvas PrivateCanvas = new Canvas
|
||||
{
|
||||
ClipToBounds = true
|
||||
};
|
||||
|
||||
protected LoadingBase()
|
||||
{
|
||||
Content = PrivateCanvas;
|
||||
}
|
||||
|
||||
public int DotCount
|
||||
{
|
||||
get => (int) GetValue(DotCountProperty);
|
||||
set => SetValue(DotCountProperty, value);
|
||||
}
|
||||
|
||||
public double DotInterval
|
||||
{
|
||||
get => (double) GetValue(DotIntervalProperty);
|
||||
set => SetValue(DotIntervalProperty, value);
|
||||
}
|
||||
|
||||
public Brush DotBorderBrush
|
||||
{
|
||||
get => (Brush) GetValue(DotBorderBrushProperty);
|
||||
set => SetValue(DotBorderBrushProperty, value);
|
||||
}
|
||||
|
||||
public double DotBorderThickness
|
||||
{
|
||||
get => (double) GetValue(DotBorderThicknessProperty);
|
||||
set => SetValue(DotBorderThicknessProperty, value);
|
||||
}
|
||||
|
||||
public double DotDiameter
|
||||
{
|
||||
get => (double) GetValue(DotDiameterProperty);
|
||||
set => SetValue(DotDiameterProperty, value);
|
||||
}
|
||||
|
||||
public double DotSpeed
|
||||
{
|
||||
get => (double) GetValue(DotSpeedProperty);
|
||||
set => SetValue(DotSpeedProperty, value);
|
||||
}
|
||||
|
||||
public double DotDelayTime
|
||||
{
|
||||
get => (double) GetValue(DotDelayTimeProperty);
|
||||
set => SetValue(DotDelayTimeProperty, value);
|
||||
}
|
||||
|
||||
protected abstract void UpdateDots();
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext)
|
||||
{
|
||||
base.OnRender(drawingContext);
|
||||
UpdateDots();
|
||||
}
|
||||
|
||||
protected Ellipse CreateEllipse(int index)
|
||||
{
|
||||
var ellipse = new Ellipse();
|
||||
ellipse.SetBinding(WidthProperty, new Binding(DotDiameterProperty.Name) {Source = this});
|
||||
ellipse.SetBinding(HeightProperty, new Binding(DotDiameterProperty.Name) {Source = this});
|
||||
ellipse.SetBinding(Shape.FillProperty, new Binding(ForegroundProperty.Name) {Source = this});
|
||||
ellipse.SetBinding(Shape.StrokeThicknessProperty, new Binding(DotBorderThicknessProperty.Name) {Source = this});
|
||||
ellipse.SetBinding(Shape.StrokeProperty, new Binding(DotBorderBrushProperty.Name) {Source = this});
|
||||
return ellipse;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class LoadingCircle : LoadingBase
|
||||
{
|
||||
public static readonly DependencyProperty DotOffSetProperty = DependencyProperty.Register(
|
||||
"DotOffSet", typeof(double), typeof(LoadingCircle),
|
||||
new FrameworkPropertyMetadata(20.0, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public double DotOffSet
|
||||
{
|
||||
get => (double) GetValue(DotOffSetProperty);
|
||||
set => SetValue(DotOffSetProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty NeedHiddenProperty = DependencyProperty.Register(
|
||||
"NeedHidden", typeof(bool), typeof(LoadingCircle),
|
||||
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
public bool NeedHidden
|
||||
{
|
||||
get => (bool) GetValue(NeedHiddenProperty);
|
||||
set => SetValue(NeedHiddenProperty, value);
|
||||
}
|
||||
|
||||
static LoadingCircle()
|
||||
{
|
||||
DotSpeedProperty.OverrideMetadata(typeof(LoadingCircle),
|
||||
new FrameworkPropertyMetadata(6.0, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
DotDelayTimeProperty.OverrideMetadata(typeof(LoadingCircle),
|
||||
new FrameworkPropertyMetadata(220.0, FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
}
|
||||
|
||||
protected sealed override void UpdateDots()
|
||||
{
|
||||
if (DotCount < 1) return;
|
||||
PrivateCanvas.Children.Clear();
|
||||
|
||||
//定义动画
|
||||
Storyboard = new Storyboard
|
||||
{
|
||||
RepeatBehavior = RepeatBehavior.Forever
|
||||
};
|
||||
|
||||
//创建圆点
|
||||
for (var i = 0; i < DotCount; i++)
|
||||
{
|
||||
var border = CreateBorder(i);
|
||||
|
||||
var framesMove = new DoubleAnimationUsingKeyFrames
|
||||
{
|
||||
BeginTime = TimeSpan.FromMilliseconds(DotDelayTime * i)
|
||||
};
|
||||
|
||||
var subAngle = -DotInterval * i;
|
||||
|
||||
//开始位置
|
||||
var frame0 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = 0 + subAngle,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero)
|
||||
};
|
||||
|
||||
//开始位置到第一次匀速开始
|
||||
var frame1 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = 180 + subAngle,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (0.75 / 7)))
|
||||
};
|
||||
|
||||
//第一次匀速开始到第一次匀速结束
|
||||
var frame2 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = 180 + DotOffSet + subAngle,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (2.75 / 7)))
|
||||
};
|
||||
|
||||
//第一次匀速结束到匀加速结束
|
||||
var frame3 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = 360 + subAngle,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (3.5 / 7)))
|
||||
};
|
||||
|
||||
//匀加速结束到匀减速结束
|
||||
var frame4 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = 540 + subAngle,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (4.25 / 7)))
|
||||
};
|
||||
|
||||
//匀减速结束到第二次匀速结束
|
||||
var frame5 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = 540 + DotOffSet + subAngle,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (6.25 / 7)))
|
||||
};
|
||||
|
||||
//第二次匀速结束到匀加速结束
|
||||
var frame6 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = 720 + subAngle,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed))
|
||||
};
|
||||
|
||||
framesMove.KeyFrames.Add(frame0);
|
||||
framesMove.KeyFrames.Add(frame1);
|
||||
framesMove.KeyFrames.Add(frame2);
|
||||
framesMove.KeyFrames.Add(frame3);
|
||||
framesMove.KeyFrames.Add(frame4);
|
||||
framesMove.KeyFrames.Add(frame5);
|
||||
framesMove.KeyFrames.Add(frame6);
|
||||
|
||||
Storyboard.SetTarget(framesMove, border);
|
||||
Storyboard.SetTargetProperty(framesMove, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(RotateTransform.Angle)"));
|
||||
Storyboard.Children.Add(framesMove);
|
||||
|
||||
if (NeedHidden)
|
||||
{
|
||||
//隐藏一段时间
|
||||
var frame7 = new DiscreteObjectKeyFrame
|
||||
{
|
||||
Value = Visibility.Collapsed,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed))
|
||||
};
|
||||
|
||||
var frame8 = new DiscreteObjectKeyFrame
|
||||
{
|
||||
Value = Visibility.Collapsed,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed + 0.4))
|
||||
};
|
||||
|
||||
var frame9 = new DiscreteObjectKeyFrame
|
||||
{
|
||||
Value = Visibility.Visible,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero)
|
||||
};
|
||||
var framesVisibility = new ObjectAnimationUsingKeyFrames
|
||||
{
|
||||
BeginTime = TimeSpan.FromMilliseconds(DotDelayTime * i)
|
||||
};
|
||||
framesVisibility.KeyFrames.Add(frame9);
|
||||
framesVisibility.KeyFrames.Add(frame7);
|
||||
framesVisibility.KeyFrames.Add(frame8);
|
||||
Storyboard.SetTarget(framesVisibility, border);
|
||||
Storyboard.SetTargetProperty(framesVisibility, new PropertyPath("(UIElement.Visibility)"));
|
||||
Storyboard.Children.Add(framesVisibility);
|
||||
}
|
||||
PrivateCanvas.Children.Add(border);
|
||||
}
|
||||
|
||||
Storyboard.Begin();
|
||||
}
|
||||
|
||||
private Border CreateBorder(int index)
|
||||
{
|
||||
var ellipse = CreateEllipse(index);
|
||||
ellipse.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
ellipse.VerticalAlignment = VerticalAlignment.Bottom;
|
||||
var rt = new RotateTransform
|
||||
{
|
||||
Angle = -DotInterval * index
|
||||
};
|
||||
var myTransGroup = new TransformGroup();
|
||||
myTransGroup.Children.Add(rt);
|
||||
var border = new Border
|
||||
{
|
||||
RenderTransformOrigin = new Point(0.5, 0.5),
|
||||
RenderTransform = myTransGroup,
|
||||
Child = ellipse,
|
||||
Visibility = NeedHidden ? Visibility.Collapsed : Visibility.Visible
|
||||
};
|
||||
border.SetBinding(WidthProperty, new Binding("Width") { Source = this });
|
||||
border.SetBinding(HeightProperty, new Binding("Height") { Source = this });
|
||||
|
||||
return border;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class LoadingLine : LoadingBase
|
||||
{
|
||||
private const double MoveLength = 80;
|
||||
|
||||
private const double UniformScale = .6;
|
||||
|
||||
public LoadingLine()
|
||||
{
|
||||
SetBinding(HeightProperty, new Binding("DotDiameter") { Source = this });
|
||||
}
|
||||
|
||||
protected sealed override void UpdateDots()
|
||||
{
|
||||
if (DotCount < 1) return;
|
||||
PrivateCanvas.Children.Clear();
|
||||
|
||||
//计算相关尺寸
|
||||
var centerWidth = DotDiameter * DotCount + DotInterval * (DotCount - 1) + MoveLength;
|
||||
var speedDownLength = (ActualWidth - MoveLength) / 2;
|
||||
var speedUniformLength = centerWidth / 2;
|
||||
|
||||
//定义动画
|
||||
Storyboard = new Storyboard
|
||||
{
|
||||
RepeatBehavior = RepeatBehavior.Forever
|
||||
};
|
||||
|
||||
//创建圆点
|
||||
for (var i = 0; i < DotCount; i++)
|
||||
{
|
||||
var ellipse = CreateEllipse(i);
|
||||
|
||||
var frames = new ThicknessAnimationUsingKeyFrames
|
||||
{
|
||||
BeginTime = TimeSpan.FromMilliseconds(DotDelayTime * i)
|
||||
};
|
||||
//开始位置
|
||||
var frame0 = new LinearThicknessKeyFrame
|
||||
{
|
||||
Value = new Thickness(ellipse.Margin.Left, 0, 0, 0),
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero)
|
||||
};
|
||||
|
||||
//开始位置到匀速开始
|
||||
var frame1 = new LinearThicknessKeyFrame
|
||||
{
|
||||
Value = new Thickness(speedDownLength + ellipse.Margin.Left, 0, 0, 0),
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (1 - UniformScale)/2))
|
||||
};
|
||||
|
||||
//匀速开始到匀速结束
|
||||
var frame2 = new LinearThicknessKeyFrame
|
||||
{
|
||||
Value = new Thickness(speedDownLength + speedUniformLength + ellipse.Margin.Left, 0, 0, 0),
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (1 + UniformScale) / 2))
|
||||
};
|
||||
|
||||
//匀速结束到匀加速结束
|
||||
var frame3 = new LinearThicknessKeyFrame
|
||||
{
|
||||
Value = new Thickness(ActualWidth + ellipse.Margin.Left + speedUniformLength, 0, 0, 0),
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed))
|
||||
};
|
||||
|
||||
frames.KeyFrames.Add(frame0);
|
||||
frames.KeyFrames.Add(frame1);
|
||||
frames.KeyFrames.Add(frame2);
|
||||
frames.KeyFrames.Add(frame3);
|
||||
|
||||
Storyboard.SetTarget(frames, ellipse);
|
||||
Storyboard.SetTargetProperty(frames, new PropertyPath(MarginProperty));
|
||||
Storyboard.Children.Add(frames);
|
||||
|
||||
PrivateCanvas.Children.Add(ellipse);
|
||||
}
|
||||
|
||||
Storyboard.Begin();
|
||||
}
|
||||
|
||||
private new Ellipse CreateEllipse(int index)
|
||||
{
|
||||
var ellipse = base.CreateEllipse(index);
|
||||
ellipse.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
ellipse.VerticalAlignment = VerticalAlignment.Top;
|
||||
ellipse.Margin = new Thickness(-(DotInterval + DotDiameter) * index, 0, 0, 0);
|
||||
return ellipse;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class CirclePanel : Panel
|
||||
{
|
||||
public static readonly DependencyProperty DiameterProperty = DependencyProperty.Register(
|
||||
"Diameter", typeof(double), typeof(CirclePanel), new FrameworkPropertyMetadata(170.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||||
|
||||
public double Diameter
|
||||
{
|
||||
get => (double) GetValue(DiameterProperty);
|
||||
set => SetValue(DiameterProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty KeepVerticalProperty = DependencyProperty.Register(
|
||||
"KeepVertical", typeof(bool), typeof(CirclePanel), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||||
|
||||
public bool KeepVertical
|
||||
{
|
||||
get => (bool) GetValue(KeepVerticalProperty);
|
||||
set => SetValue(KeepVerticalProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty OffsetAngleProperty = DependencyProperty.Register(
|
||||
"OffsetAngle", typeof(double), typeof(CirclePanel), new FrameworkPropertyMetadata(ValueBoxes.Double0Box, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||||
|
||||
public double OffsetAngle
|
||||
{
|
||||
get => (double) GetValue(OffsetAngleProperty);
|
||||
set => SetValue(OffsetAngleProperty, value);
|
||||
}
|
||||
|
||||
// ReSharper disable once RedundantAssignment
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
if (Children.Count == 0) return new Size(Diameter, Diameter);
|
||||
|
||||
availableSize = new Size(Diameter, Diameter);
|
||||
var i = 0;
|
||||
var perDeg = 360.0 / Children.Count;
|
||||
var radius = Diameter / 2;
|
||||
foreach (UIElement element in Children)
|
||||
{
|
||||
element.Measure(availableSize);
|
||||
var centerX = element.DesiredSize.Width / 2.0;
|
||||
var centerY = element.DesiredSize.Height / 2.0;
|
||||
var angle = perDeg * i++ + OffsetAngle;
|
||||
var transform = new RotateTransform
|
||||
{
|
||||
CenterX = centerX,
|
||||
CenterY = centerY,
|
||||
Angle = KeepVertical ? 0 : angle
|
||||
};
|
||||
element.RenderTransform = transform;
|
||||
var r = Math.PI * angle / 180.0;
|
||||
var x = radius * Math.Cos(r);
|
||||
var y = radius * Math.Sin(r);
|
||||
var rectX = x + availableSize.Width / 2 - centerX;
|
||||
var rectY = y + availableSize.Height / 2 - centerY;
|
||||
element.Arrange(new Rect(rectX, rectY, element.DesiredSize.Width, element.DesiredSize.Height));
|
||||
}
|
||||
return availableSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class WaterfallPanel : Panel
|
||||
{
|
||||
public static readonly DependencyProperty GroupsProperty = DependencyProperty.Register(
|
||||
"Groups", typeof(int), typeof(WaterfallPanel), new FrameworkPropertyMetadata(ValueBoxes.Int2Box, FrameworkPropertyMetadataOptions.AffectsMeasure), IsGroupsValid);
|
||||
|
||||
public int Groups
|
||||
{
|
||||
get => (int) GetValue(GroupsProperty);
|
||||
set => SetValue(GroupsProperty, value);
|
||||
}
|
||||
|
||||
private static bool IsGroupsValid(object value)
|
||||
{
|
||||
var v = (int)value;
|
||||
return v >= 1;
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
||||
"Orientation", typeof(Orientation), typeof(WaterfallPanel), new FrameworkPropertyMetadata(
|
||||
Orientation.Horizontal, FrameworkPropertyMetadataOptions.AffectsMeasure));
|
||||
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => (Orientation) GetValue(OrientationProperty);
|
||||
set => SetValue(OrientationProperty, value);
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
if (Groups < 1) return constraint;
|
||||
var children = InternalChildren;
|
||||
Size panelSize;
|
||||
|
||||
if (Orientation == Orientation.Horizontal)
|
||||
{
|
||||
var heightArr = new double[Groups].ToList();
|
||||
var itemWidth = constraint.Width / Groups;
|
||||
if (double.IsNaN(itemWidth) || double.IsInfinity(itemWidth)) return constraint;
|
||||
|
||||
for (int i = 0, count = children.Count; i < count; i++)
|
||||
{
|
||||
var child = children[i];
|
||||
if (child == null) continue;
|
||||
|
||||
child.Measure(constraint);
|
||||
var minIndex = heightArr.IndexOf(heightArr.Min());
|
||||
var minY = heightArr[minIndex];
|
||||
child.Arrange(new Rect(new Point(minIndex * itemWidth, minY), new Size(itemWidth, child.DesiredSize.Height)));
|
||||
|
||||
heightArr[minIndex] = minY + child.DesiredSize.Height;
|
||||
}
|
||||
panelSize = new Size(constraint.Width, heightArr.Max());
|
||||
}
|
||||
else
|
||||
{
|
||||
var widthArr = new double[Groups].ToList();
|
||||
var itemHeight = constraint.Height / Groups;
|
||||
if (double.IsNaN(itemHeight) || double.IsInfinity(itemHeight)) return constraint;
|
||||
|
||||
for (int i = 0, count = children.Count; i < count; i++)
|
||||
{
|
||||
var child = children[i];
|
||||
if (child == null) continue;
|
||||
|
||||
child.Measure(constraint);
|
||||
var minIndex = widthArr.IndexOf(widthArr.Min());
|
||||
var minX = widthArr[minIndex];
|
||||
child.Arrange(new Rect(new Point(minX, minIndex * itemHeight), new Size(child.DesiredSize.Width, itemHeight)));
|
||||
|
||||
widthArr[minIndex] = minX + child.DesiredSize.Width;
|
||||
}
|
||||
panelSize = new Size(widthArr.Max(), constraint.Height);
|
||||
}
|
||||
|
||||
return panelSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class Rate : RegularItemsControl
|
||||
{
|
||||
static Rate()
|
||||
{
|
||||
ItemWidthProperty.OverrideMetadata(typeof(Rate), new PropertyMetadata(ValueBoxes.Double20Box));
|
||||
ItemHeightProperty.OverrideMetadata(typeof(Rate), new PropertyMetadata(ValueBoxes.Double20Box));
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AllowHalfProperty = DependencyProperty.Register(
|
||||
"AllowHalf", typeof(bool), typeof(Rate), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static readonly DependencyProperty AllowClearProperty = DependencyProperty.Register(
|
||||
"AllowClear", typeof(bool), typeof(Rate), new FrameworkPropertyMetadata(ValueBoxes.TrueBox, FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
|
||||
"Icon", typeof(Geometry), typeof(Rate), new FrameworkPropertyMetadata(default(Geometry), FrameworkPropertyMetadataOptions.Inherits));
|
||||
|
||||
public static readonly DependencyProperty CountProperty = DependencyProperty.Register(
|
||||
"Count", typeof(int), typeof(Rate), new PropertyMetadata(ValueBoxes.Int5Box));
|
||||
|
||||
public static readonly DependencyProperty DefaultValueProperty = DependencyProperty.Register(
|
||||
"DefaultValue", typeof(double), typeof(Rate), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
|
||||
"Value", typeof(double), typeof(Rate), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
"Text", typeof(string), typeof(Rate), new PropertyMetadata(default(string)));
|
||||
|
||||
public static readonly DependencyProperty ShowTextProperty = DependencyProperty.Register(
|
||||
"ShowText", typeof(bool), typeof(Rate), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
|
||||
"IsReadOnly", typeof(bool), typeof(Rate), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
private bool _isLoaded;
|
||||
|
||||
public Rate()
|
||||
{
|
||||
AddHandler(RateItem.SelectedChangedEvent, new RoutedEventHandler(RateItemSelectedChanged));
|
||||
AddHandler(RateItem.ValueChangedEvent, new RoutedEventHandler(RateItemValueChanged));
|
||||
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (DesignerHelper.IsInDesignMode) return;
|
||||
if (_isLoaded) return;
|
||||
_isLoaded = true;
|
||||
if (Value <= 0)
|
||||
{
|
||||
if (DefaultValue > 0)
|
||||
{
|
||||
Value = DefaultValue;
|
||||
UpdateItems();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateItems();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public bool AllowHalf
|
||||
{
|
||||
get => (bool) GetValue(AllowHalfProperty);
|
||||
set => SetValue(AllowHalfProperty, value);
|
||||
}
|
||||
|
||||
public bool AllowClear
|
||||
{
|
||||
get => (bool) GetValue(AllowClearProperty);
|
||||
set => SetValue(AllowClearProperty, value);
|
||||
}
|
||||
|
||||
public Geometry Icon
|
||||
{
|
||||
get => (Geometry) GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => (int) GetValue(CountProperty);
|
||||
set => SetValue(CountProperty, value);
|
||||
}
|
||||
|
||||
public double DefaultValue
|
||||
{
|
||||
get => (double) GetValue(DefaultValueProperty);
|
||||
set => SetValue(DefaultValueProperty, value);
|
||||
}
|
||||
|
||||
public double Value
|
||||
{
|
||||
get => (double) GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string) GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowText
|
||||
{
|
||||
get => (bool) GetValue(ShowTextProperty);
|
||||
set => SetValue(ShowTextProperty, value);
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get => (bool)GetValue(IsReadOnlyProperty);
|
||||
set => SetValue(IsReadOnlyProperty, value);
|
||||
}
|
||||
|
||||
private void RateItemValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Value =
|
||||
(from RateItem item in Items where item.IsSelected select item.IsHalf ? 0.5 : 1).Sum();
|
||||
}
|
||||
|
||||
private void RateItemSelectedChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is RateItem rateItem)
|
||||
{
|
||||
var index = rateItem.Index;
|
||||
for (var i = 0; i < index; i++)
|
||||
{
|
||||
if (Items[i] is RateItem item)
|
||||
{
|
||||
item.IsSelected = true;
|
||||
item.IsHalf = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = index; i < Count; i++)
|
||||
{
|
||||
if (Items[i] is RateItem item)
|
||||
{
|
||||
item.IsSelected = false;
|
||||
item.IsHalf = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool IsItemItsOwnContainerOverride(object item)
|
||||
{
|
||||
return item is RateItem;
|
||||
}
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride()
|
||||
{
|
||||
return new RateItem();
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (!_isLoaded)
|
||||
{
|
||||
Items.Clear();
|
||||
|
||||
for (var i = 1; i <= Count; i++)
|
||||
{
|
||||
Items.Add(new RateItem
|
||||
{
|
||||
Index = i
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseLeave(e);
|
||||
|
||||
UpdateItems();
|
||||
}
|
||||
|
||||
protected override void UpdateItems()
|
||||
{
|
||||
if (!_isLoaded) return;
|
||||
var count = (int) Value;
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
if (Items[i] is RateItem rateItem)
|
||||
{
|
||||
rateItem.IsSelected = true;
|
||||
rateItem.IsHalf = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Value > count)
|
||||
{
|
||||
if (Items[count] is RateItem rateItem)
|
||||
{
|
||||
rateItem.IsSelected = true;
|
||||
rateItem.IsHalf = true;
|
||||
}
|
||||
|
||||
count += 1;
|
||||
}
|
||||
|
||||
for (var i = count; i < Count; i++)
|
||||
{
|
||||
if (Items[i] is RateItem rateItem)
|
||||
{
|
||||
rateItem.IsSelected = false;
|
||||
rateItem.IsHalf = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
Value = DefaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = ElementIcon, Type = typeof(FrameworkElement))]
|
||||
public class RateItem : Control
|
||||
{
|
||||
private const string ElementIcon = "PART_Icon";
|
||||
|
||||
public static readonly DependencyProperty AllowClearProperty =
|
||||
Rate.AllowClearProperty.AddOwner(typeof(RateItem));
|
||||
|
||||
public static readonly DependencyProperty AllowHalfProperty =
|
||||
Rate.AllowHalfProperty.AddOwner(typeof(RateItem), new PropertyMetadata(OnAllowHalfChanged));
|
||||
|
||||
public static readonly DependencyProperty IconProperty = Rate.IconProperty.AddOwner(typeof(RateItem));
|
||||
|
||||
public static readonly DependencyProperty IsReadOnlyProperty = Rate.IsReadOnlyProperty.AddOwner(typeof(RateItem));
|
||||
|
||||
internal static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
|
||||
"IsSelected", typeof(bool), typeof(RateItem),
|
||||
new PropertyMetadata(ValueBoxes.FalseBox, OnIsSelectedChanged));
|
||||
|
||||
public static readonly RoutedEvent SelectedChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("SelectedChanged", RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler), typeof(RateItem));
|
||||
|
||||
public static readonly RoutedEvent ValueChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler), typeof(RateItem));
|
||||
|
||||
private FrameworkElement _icon;
|
||||
|
||||
private bool _isHalf;
|
||||
|
||||
private bool _isLoaded;
|
||||
|
||||
private bool _isMouseLeftButtonDown;
|
||||
|
||||
private bool _isSentValue;
|
||||
|
||||
public RateItem()
|
||||
{
|
||||
Loaded += (s, e) => _isLoaded = true;
|
||||
}
|
||||
|
||||
public bool AllowClear
|
||||
{
|
||||
get => (bool) GetValue(AllowClearProperty);
|
||||
set => SetValue(AllowClearProperty, value);
|
||||
}
|
||||
|
||||
public bool AllowHalf
|
||||
{
|
||||
get => (bool) GetValue(AllowHalfProperty);
|
||||
set => SetValue(AllowHalfProperty, value);
|
||||
}
|
||||
|
||||
public Geometry Icon
|
||||
{
|
||||
get => (Geometry) GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
internal bool IsSelected
|
||||
{
|
||||
get => (bool) GetValue(IsSelectedProperty);
|
||||
set => SetValue(IsSelectedProperty, value);
|
||||
}
|
||||
|
||||
public bool IsReadOnly
|
||||
{
|
||||
get => (bool)GetValue(IsReadOnlyProperty);
|
||||
set => SetValue(IsReadOnlyProperty, value);
|
||||
}
|
||||
|
||||
internal bool IsHalf
|
||||
{
|
||||
get => _isHalf;
|
||||
set
|
||||
{
|
||||
if (_isHalf == value) return;
|
||||
_isHalf = value;
|
||||
if (_icon == null) return;
|
||||
_icon.Width = value ? ActualWidth / 2 : ActualWidth;
|
||||
}
|
||||
}
|
||||
|
||||
internal int Index { get; set; }
|
||||
|
||||
private static void OnAllowHalfChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (RateItem) d;
|
||||
ctl.HandleMouseMoveEvent((bool) e.NewValue);
|
||||
}
|
||||
|
||||
private static void OnIsSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (RateItem) d;
|
||||
ctl._icon?.Show((bool) e.NewValue);
|
||||
}
|
||||
|
||||
public event RoutedEventHandler SelectedChanged
|
||||
{
|
||||
add => AddHandler(SelectedChangedEvent, value);
|
||||
remove => RemoveHandler(SelectedChangedEvent, value);
|
||||
}
|
||||
|
||||
public event RoutedEventHandler ValueChanged
|
||||
{
|
||||
add => AddHandler(ValueChangedEvent, value);
|
||||
remove => RemoveHandler(ValueChangedEvent, value);
|
||||
}
|
||||
|
||||
private void HandleMouseMoveEvent(bool handle)
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
MouseMove += RateItem_MouseMove;
|
||||
}
|
||||
else
|
||||
{
|
||||
MouseMove -= RateItem_MouseMove;
|
||||
}
|
||||
}
|
||||
|
||||
private void RateItem_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (IsReadOnly) return;
|
||||
if (!AllowHalf) return;
|
||||
var p = e.GetPosition(this);
|
||||
IsHalf = p.X < ActualWidth / 2;
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_icon = GetTemplateChild(ElementIcon) as FrameworkElement;
|
||||
|
||||
if (_isLoaded)
|
||||
{
|
||||
if (_icon == null) return;
|
||||
_icon.Show(IsSelected);
|
||||
_icon.Width = IsHalf ? ActualWidth / 2 : ActualWidth;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseEnter(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseEnter(e);
|
||||
if (IsReadOnly) return;
|
||||
_isSentValue = false;
|
||||
IsSelected = true;
|
||||
RaiseEvent(new RoutedEventArgs(SelectedChangedEvent) {Source = this});
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonDown(e);
|
||||
if (IsReadOnly) return;
|
||||
_isMouseLeftButtonDown = true;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseLeave(e);
|
||||
if (IsReadOnly) return;
|
||||
_isMouseLeftButtonDown = false;
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonUp(e);
|
||||
if (IsReadOnly) return;
|
||||
if (_isMouseLeftButtonDown)
|
||||
{
|
||||
if (Index == 1 && AllowClear)
|
||||
{
|
||||
if (IsSelected)
|
||||
{
|
||||
if (!_isSentValue)
|
||||
{
|
||||
RaiseEvent(new RoutedEventArgs(ValueChangedEvent) {Source = this});
|
||||
_isMouseLeftButtonDown = false;
|
||||
_isSentValue = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_isSentValue = false;
|
||||
IsSelected = false;
|
||||
IsHalf = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsSelected = true;
|
||||
if (AllowHalf)
|
||||
{
|
||||
var p = e.GetPosition(this);
|
||||
IsHalf = p.X < ActualWidth / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RaiseEvent(new RoutedEventArgs(ValueChangedEvent) {Source = this});
|
||||
_isMouseLeftButtonDown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class CompareSlider : Slider
|
||||
{
|
||||
public static readonly DependencyProperty TargetContentProperty = DependencyProperty.Register(
|
||||
"TargetContent", typeof(object), typeof(CompareSlider), new PropertyMetadata(default(object)));
|
||||
|
||||
public static readonly DependencyProperty SourceContentProperty = DependencyProperty.Register(
|
||||
"SourceContent", typeof(object), typeof(CompareSlider), new PropertyMetadata(default(object)));
|
||||
|
||||
public object TargetContent
|
||||
{
|
||||
get => GetValue(TargetContentProperty);
|
||||
set => SetValue(TargetContentProperty, value);
|
||||
}
|
||||
|
||||
public object SourceContent
|
||||
{
|
||||
get => GetValue(SourceContentProperty);
|
||||
set => SetValue(SourceContentProperty, value);
|
||||
}
|
||||
|
||||
//private bool _isLoaded;
|
||||
|
||||
//private double _tempValue;
|
||||
|
||||
//public CompareSlider()
|
||||
//{
|
||||
// //Loaded += delegate
|
||||
// //{
|
||||
// // if (_isLoaded) return;
|
||||
// // _isLoaded = true;
|
||||
// // Value = _tempValue;
|
||||
// //};
|
||||
//}
|
||||
|
||||
////protected override void OnRender(DrawingContext drawingContext)
|
||||
////{
|
||||
//// base.OnRender(drawingContext);
|
||||
|
||||
//// Value = _tempValue;
|
||||
////}
|
||||
|
||||
////public override void OnApplyTemplate()
|
||||
////{
|
||||
//// base.OnApplyTemplate();
|
||||
//// _tempValue = Value;
|
||||
//// Value = Orientation == Orientation.Horizontal ? Maximum : 0;
|
||||
//// if (_isLoaded)
|
||||
//// {
|
||||
//// Value = _tempValue;
|
||||
//// }
|
||||
////}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class CompareTrack : Track
|
||||
{
|
||||
protected override Size ArrangeOverride(Size arrangeSize)
|
||||
{
|
||||
base.ArrangeOverride(arrangeSize);
|
||||
|
||||
var isVertical = Orientation == Orientation.Vertical;
|
||||
ComputeSliderLengths(arrangeSize, isVertical, out var decreaseButtonLength, out var thumbLength,
|
||||
out var increaseButtonLength);
|
||||
|
||||
var offset = new Point();
|
||||
var pieceSize = arrangeSize;
|
||||
var isDirectionReversed = IsDirectionReversed;
|
||||
|
||||
if (isVertical)
|
||||
{
|
||||
CoerceLength(ref decreaseButtonLength, arrangeSize.Height);
|
||||
CoerceLength(ref increaseButtonLength, arrangeSize.Height);
|
||||
CoerceLength(ref thumbLength, arrangeSize.Height);
|
||||
|
||||
offset.Y = isDirectionReversed ? decreaseButtonLength + thumbLength : 0.0;
|
||||
pieceSize.Height = increaseButtonLength;
|
||||
|
||||
IncreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
|
||||
|
||||
offset.Y = isDirectionReversed ? 0.0 : increaseButtonLength + thumbLength;
|
||||
pieceSize.Height = decreaseButtonLength;
|
||||
|
||||
if (DecreaseRepeatButton != null)
|
||||
{
|
||||
DecreaseRepeatButton.Arrange(new Rect(offset, pieceSize));
|
||||
DecreaseRepeatButton.Height = pieceSize.Height;
|
||||
}
|
||||
|
||||
offset.Y = isDirectionReversed ? decreaseButtonLength : increaseButtonLength;
|
||||
pieceSize.Height = thumbLength;
|
||||
|
||||
Thumb?.Arrange(new Rect(offset, pieceSize));
|
||||
}
|
||||
else
|
||||
{
|
||||
CoerceLength(ref decreaseButtonLength, arrangeSize.Width);
|
||||
CoerceLength(ref increaseButtonLength, arrangeSize.Width);
|
||||
CoerceLength(ref thumbLength, arrangeSize.Width);
|
||||
|
||||
offset.X = isDirectionReversed ? increaseButtonLength + thumbLength : 0.0;
|
||||
pieceSize.Width = decreaseButtonLength;
|
||||
|
||||
DecreaseRepeatButton?.Arrange(new Rect(offset, pieceSize));
|
||||
|
||||
|
||||
offset.X = isDirectionReversed ? 0.0 : decreaseButtonLength + thumbLength;
|
||||
pieceSize.Width = increaseButtonLength;
|
||||
|
||||
if (IncreaseRepeatButton != null)
|
||||
{
|
||||
IncreaseRepeatButton.Arrange(new Rect(offset, pieceSize));
|
||||
IncreaseRepeatButton.Width = pieceSize.Width;
|
||||
}
|
||||
|
||||
offset.X = isDirectionReversed ? increaseButtonLength : decreaseButtonLength;
|
||||
pieceSize.Width = thumbLength;
|
||||
|
||||
Thumb?.Arrange(new Rect(offset, pieceSize));
|
||||
}
|
||||
|
||||
return arrangeSize;
|
||||
}
|
||||
|
||||
private void ComputeSliderLengths(Size arrangeSize, bool isVertical, out double decreaseButtonLength,
|
||||
out double thumbLength, out double increaseButtonLength)
|
||||
{
|
||||
var min = Minimum;
|
||||
var range = Math.Max(0.0, Maximum - min);
|
||||
var offset = Math.Min(range, Value - min);
|
||||
|
||||
double trackLength;
|
||||
|
||||
// Compute thumb size
|
||||
if (isVertical)
|
||||
{
|
||||
trackLength = arrangeSize.Height;
|
||||
thumbLength = Thumb?.DesiredSize.Height ?? 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
trackLength = arrangeSize.Width;
|
||||
thumbLength = Thumb?.DesiredSize.Width ?? 0;
|
||||
}
|
||||
|
||||
CoerceLength(ref thumbLength, trackLength);
|
||||
|
||||
var remainingTrackLength = trackLength - thumbLength;
|
||||
|
||||
decreaseButtonLength = remainingTrackLength * offset / range;
|
||||
CoerceLength(ref decreaseButtonLength, remainingTrackLength);
|
||||
|
||||
increaseButtonLength = remainingTrackLength - decreaseButtonLength;
|
||||
CoerceLength(ref increaseButtonLength, remainingTrackLength);
|
||||
}
|
||||
|
||||
private static void CoerceLength(ref double componentLength, double trackLength)
|
||||
{
|
||||
if (componentLength < 0)
|
||||
componentLength = 0.0;
|
||||
else if (componentLength > trackLength || double.IsNaN(componentLength))
|
||||
componentLength = trackLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = PreviewContentKey, Type = typeof(ContentControl))]
|
||||
[TemplatePart(Name = TracKKey, Type = typeof(Track))]
|
||||
public class PreviewSlider : Slider
|
||||
{
|
||||
private const string PreviewContentKey = "PART_ContentBorder";
|
||||
|
||||
private const string TracKKey = "PART_Track";
|
||||
|
||||
private ContentControl _previewContent;
|
||||
|
||||
private TranslateTransform _transform;
|
||||
|
||||
private Track _track;
|
||||
|
||||
/// <summary>
|
||||
/// 预览内容
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PreviewContentProperty = DependencyProperty.Register(
|
||||
"PreviewContent", typeof(object), typeof(PreviewSlider), new PropertyMetadata(default(object)));
|
||||
|
||||
/// <summary>
|
||||
/// 预览内容
|
||||
/// </summary>
|
||||
public object PreviewContent
|
||||
{
|
||||
get => GetValue(PreviewContentProperty);
|
||||
set => SetValue(PreviewContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PreviewPositionProperty = DependencyProperty.Register(
|
||||
"PreviewPosition", typeof(double), typeof(PreviewSlider), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
public double PreviewPosition
|
||||
{
|
||||
get => (double) GetValue(PreviewPositionProperty);
|
||||
set => SetValue(PreviewPositionProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 值改变事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent PreviewPositionChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("PreviewPositionChanged", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<double>>), typeof(PreviewSlider));
|
||||
|
||||
/// <summary>
|
||||
/// 值改变事件
|
||||
/// </summary>
|
||||
public event EventHandler<FunctionEventArgs<double>> PreviewPositionChanged
|
||||
{
|
||||
add => AddHandler(PreviewPositionChangedEvent, value);
|
||||
remove => RemoveHandler(PreviewPositionChangedEvent, value);
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
|
||||
var p = e.GetPosition(_track);
|
||||
_transform.X = p.X - _previewContent.ActualWidth/2;
|
||||
|
||||
PreviewPosition = p.X / _track.ActualWidth * Maximum;
|
||||
RaiseEvent(new FunctionEventArgs<double>(PreviewPositionChangedEvent, this)
|
||||
{
|
||||
Info = PreviewPosition
|
||||
});
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
_previewContent = Template.FindName(PreviewContentKey, this) as ContentControl;
|
||||
_track = Template.FindName(TracKKey, this) as Track;
|
||||
|
||||
if (_previewContent != null)
|
||||
{
|
||||
_transform = new TranslateTransform();
|
||||
_previewContent.RenderTransform = _transform;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(StepBarItem))]
|
||||
[DefaultEvent("StepChanged")]
|
||||
[TemplatePart(Name = ElementProgressBarBack, Type = typeof(ProgressBar))]
|
||||
public class StepBar : ItemsControl
|
||||
{
|
||||
private ProgressBar _progressBarBack;
|
||||
|
||||
#region Constants
|
||||
|
||||
private const string ElementProgressBarBack = "PART_ProgressBarBack";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
public StepBar()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Next, (s, e) => Next()));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Prev, (s, e) => Prev()));
|
||||
ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
|
||||
}
|
||||
|
||||
private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
|
||||
{
|
||||
var count = Items.Count;
|
||||
if (count <= 0) return;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
if (ItemContainerGenerator.ContainerFromIndex(i) is StepBarItem stepBarItem)
|
||||
{
|
||||
stepBarItem.Index = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (StepIndex < count)
|
||||
{
|
||||
for (int i = 0; i < StepIndex; i++)
|
||||
{
|
||||
if (ItemContainerGenerator.ContainerFromIndex(i) is StepBarItem stepBarItem)
|
||||
{
|
||||
stepBarItem.Status = StepStatus.Complete;
|
||||
}
|
||||
}
|
||||
if (ItemContainerGenerator.ContainerFromIndex(StepIndex) is StepBarItem item)
|
||||
{
|
||||
item.Status = StepStatus.UnderWay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool IsItemItsOwnContainerOverride(object item) => item is StepBarItem;
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride() => new StepBarItem();
|
||||
|
||||
/// <summary>
|
||||
/// 步骤改变事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent StepChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("StepChanged", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<int>>), typeof(StepBar));
|
||||
|
||||
/// <summary>
|
||||
/// 步骤改变事件
|
||||
/// </summary>
|
||||
[Category("Behavior")]
|
||||
public event EventHandler<FunctionEventArgs<int>> StepChanged
|
||||
{
|
||||
add => AddHandler(StepChangedEvent, value);
|
||||
remove => RemoveHandler(StepChangedEvent, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty StepIndexProperty = DependencyProperty.Register(
|
||||
"StepIndex", typeof(int), typeof(StepBar), new PropertyMetadata(default(int)));
|
||||
|
||||
public int StepIndex
|
||||
{
|
||||
get => (int)GetValue(StepIndexProperty);
|
||||
protected set => SetValue(StepIndexProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DockProperty = DependencyProperty.Register(
|
||||
"Dock", typeof(Dock), typeof(StepBar), new PropertyMetadata(Dock.Top));
|
||||
|
||||
public Dock Dock
|
||||
{
|
||||
get => (Dock)GetValue(DockProperty);
|
||||
set => SetValue(DockProperty, value);
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_progressBarBack = GetTemplateChild(ElementProgressBarBack) as ProgressBar;
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext)
|
||||
{
|
||||
base.OnRender(drawingContext);
|
||||
|
||||
var colCount = Items.Count;
|
||||
if (_progressBarBack == null || colCount <= 0) return;
|
||||
_progressBarBack.Maximum = colCount - 1;
|
||||
_progressBarBack.Value = StepIndex;
|
||||
|
||||
if (Dock == Dock.Top || Dock == Dock.Bottom)
|
||||
{
|
||||
_progressBarBack.Width = (colCount - 1) * (ActualWidth / colCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
_progressBarBack.Height = (colCount - 1) * (ActualHeight / colCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void Next()
|
||||
{
|
||||
StepIndex++;
|
||||
if (StepIndex >= Items.Count)
|
||||
{
|
||||
StepIndex = Items.Count - 1;
|
||||
return;
|
||||
}
|
||||
RaiseEvent(new FunctionEventArgs<int>(StepChangedEvent, this)
|
||||
{
|
||||
Info = StepIndex
|
||||
});
|
||||
if (ItemContainerGenerator.ContainerFromIndex(StepIndex - 1) is StepBarItem stepItemFinished)
|
||||
stepItemFinished.Status = StepStatus.Complete;
|
||||
if (ItemContainerGenerator.ContainerFromIndex(StepIndex) is StepBarItem stepItemSelected)
|
||||
stepItemSelected.Status = StepStatus.UnderWay;
|
||||
_progressBarBack?.BeginAnimation(RangeBase.ValueProperty, AnimationHelper.CreateAnimation(StepIndex));
|
||||
}
|
||||
|
||||
public void Prev()
|
||||
{
|
||||
StepIndex--;
|
||||
if (StepIndex < 0)
|
||||
{
|
||||
StepIndex = 0;
|
||||
return;
|
||||
}
|
||||
RaiseEvent(new FunctionEventArgs<int>(StepChangedEvent, this)
|
||||
{
|
||||
Info = StepIndex
|
||||
});
|
||||
if (ItemContainerGenerator.ContainerFromIndex(StepIndex + 1) is StepBarItem stepItemWaiting)
|
||||
stepItemWaiting.Status = StepStatus.Waiting;
|
||||
if (ItemContainerGenerator.ContainerFromIndex(StepIndex) is StepBarItem stepItemSelected)
|
||||
stepItemSelected.Status = StepStatus.UnderWay;
|
||||
_progressBarBack?.BeginAnimation(RangeBase.ValueProperty, AnimationHelper.CreateAnimation(StepIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 步骤条单元项
|
||||
/// </summary>
|
||||
public class StepBarItem : ContentControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 步骤编号
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IndexProperty = DependencyProperty.Register(
|
||||
"Index", typeof(int), typeof(StepBarItem), new PropertyMetadata(-1));
|
||||
|
||||
/// <summary>
|
||||
/// 步骤编号
|
||||
/// </summary>
|
||||
public int Index
|
||||
{
|
||||
get => (int) GetValue(IndexProperty);
|
||||
internal set => SetValue(IndexProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 步骤状态
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty StatusProperty = DependencyProperty.Register(
|
||||
"Status", typeof(StepStatus), typeof(StepBarItem), new PropertyMetadata(StepStatus.Waiting));
|
||||
|
||||
/// <summary>
|
||||
/// 步骤状态
|
||||
/// </summary>
|
||||
public StepStatus Status
|
||||
{
|
||||
get => (StepStatus) GetValue(StatusProperty);
|
||||
internal set => SetValue(StatusProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
using System;
|
||||
using System.Collections.Specialized;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = OverflowButtonKey, Type = typeof(ContextMenuToggleButton))]
|
||||
[TemplatePart(Name = HeaderPanelKey, Type = typeof(TabPanel))]
|
||||
public class TabControl : System.Windows.Controls.TabControl
|
||||
{
|
||||
private const string OverflowButtonKey = "PART_OverflowButton";
|
||||
|
||||
private const string HeaderPanelKey = "PART_HeaderPanel";
|
||||
|
||||
private ContextMenuToggleButton _buttonOverflow;
|
||||
|
||||
private TabPanel _headerPanel;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内部操作
|
||||
/// </summary>
|
||||
internal bool IsInternalAction;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用动画
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsEnableAnimationProperty = DependencyProperty.Register(
|
||||
"IsEnableAnimation", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用动画
|
||||
/// </summary>
|
||||
public bool IsEnableAnimation
|
||||
{
|
||||
get => (bool)GetValue(IsEnableAnimationProperty);
|
||||
set => SetValue(IsEnableAnimationProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以拖动
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsDraggableProperty = DependencyProperty.Register(
|
||||
"IsDraggable", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以拖动
|
||||
/// </summary>
|
||||
public bool IsDraggable
|
||||
{
|
||||
get => (bool)GetValue(IsDraggableProperty);
|
||||
set => SetValue(IsDraggableProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示关闭按钮
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.Register(
|
||||
"ShowCloseButton", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示关闭按钮
|
||||
/// </summary>
|
||||
public bool ShowCloseButton
|
||||
{
|
||||
get => (bool)GetValue(ShowCloseButtonProperty);
|
||||
set => SetValue(ShowCloseButtonProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否将标签填充
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsEnableTabFillProperty = DependencyProperty.Register(
|
||||
"IsEnableTabFill", typeof(bool), typeof(TabControl), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 是否将标签填充
|
||||
/// </summary>
|
||||
public bool IsEnableTabFill
|
||||
{
|
||||
get => (bool)GetValue(IsEnableTabFillProperty);
|
||||
set => SetValue(IsEnableTabFillProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标签宽度
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TabItemWidthProperty = DependencyProperty.Register(
|
||||
"TabItemWidth", typeof(double), typeof(TabControl), new PropertyMetadata(200.0));
|
||||
|
||||
/// <summary>
|
||||
/// 标签宽度
|
||||
/// </summary>
|
||||
public double TabItemWidth
|
||||
{
|
||||
get => (double)GetValue(TabItemWidthProperty);
|
||||
set => SetValue(TabItemWidthProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标签高度
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TabItemHeightProperty = DependencyProperty.Register(
|
||||
"TabItemHeight", typeof(double), typeof(TabControl), new PropertyMetadata(30.0));
|
||||
|
||||
/// <summary>
|
||||
/// 标签高度
|
||||
/// </summary>
|
||||
public double TabItemHeight
|
||||
{
|
||||
get => (double)GetValue(TabItemHeightProperty);
|
||||
set => SetValue(TabItemHeightProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 可见的标签数量
|
||||
/// </summary>
|
||||
private int _itemShowCount;
|
||||
|
||||
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
base.OnItemsChanged(e);
|
||||
|
||||
if (_headerPanel == null)
|
||||
{
|
||||
IsInternalAction = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsEnableTabFill)
|
||||
{
|
||||
_itemShowCount = (int)(ActualWidth / TabItemWidth);
|
||||
_buttonOverflow.Show(Items.Count > 0 && Items.Count >= _itemShowCount);
|
||||
}
|
||||
|
||||
if (IsInternalAction)
|
||||
{
|
||||
IsInternalAction = false;
|
||||
return;
|
||||
}
|
||||
if (IsEnableAnimation)
|
||||
{
|
||||
_headerPanel.ClearValue(TabPanel.FluidMoveDurationProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
_headerPanel.FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0));
|
||||
}
|
||||
if (e.Action == NotifyCollectionChangedAction.Add)
|
||||
{
|
||||
foreach (TabItem item in e.NewItems)
|
||||
{
|
||||
item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
item.IsSelected = true;
|
||||
item.TabPanel = _headerPanel;
|
||||
}
|
||||
}
|
||||
IsInternalAction = false;
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
_headerPanel = Template.FindName(HeaderPanelKey, this) as TabPanel;
|
||||
|
||||
if (IsEnableTabFill) return;
|
||||
|
||||
_buttonOverflow = Template.FindName(OverflowButtonKey, this) as ContextMenuToggleButton;
|
||||
if (_buttonOverflow != null)
|
||||
{
|
||||
_itemShowCount = (int)(ActualWidth / TabItemWidth);
|
||||
_buttonOverflow.Show(Items.Count > 0 && Items.Count >= _itemShowCount);
|
||||
|
||||
var menu = new ContextMenu
|
||||
{
|
||||
Placement = PlacementMode.Bottom,
|
||||
PlacementTarget = _buttonOverflow
|
||||
};
|
||||
menu.Closed += (s, e) => _buttonOverflow.IsChecked = false;
|
||||
_buttonOverflow.Menu = menu;
|
||||
_buttonOverflow.Click += (s, e) =>
|
||||
{
|
||||
if (_buttonOverflow.IsChecked == true)
|
||||
{
|
||||
_buttonOverflow.Menu.Items.Clear();
|
||||
foreach (TabItem item in Items)
|
||||
{
|
||||
var menuItem = new MenuItem
|
||||
{
|
||||
Header = item.Header,
|
||||
Width = TabItemWidth,
|
||||
IsChecked = item.IsSelected,
|
||||
IsCheckable = true
|
||||
};
|
||||
menuItem.Click += delegate
|
||||
{
|
||||
_buttonOverflow.IsChecked = false;
|
||||
var index = Items.IndexOf(item);
|
||||
if (index >= _itemShowCount)
|
||||
{
|
||||
Items.Remove(item);
|
||||
Items.Insert(0, item);
|
||||
if (IsEnableAnimation)
|
||||
{
|
||||
_headerPanel.ClearValue(TabPanel.FluidMoveDurationProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
_headerPanel.FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0));
|
||||
}
|
||||
_headerPanel.ForceUpdate = true;
|
||||
_headerPanel.Measure(new Size(_headerPanel.DesiredSize.Width, ActualHeight));
|
||||
_headerPanel.ForceUpdate = false;
|
||||
}
|
||||
item.IsSelected = true;
|
||||
};
|
||||
_buttonOverflow.Menu.Items.Add(menuItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class TabItem : System.Windows.Controls.TabItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 动画速度
|
||||
/// </summary>
|
||||
private const int AnimationSpeed = 150;
|
||||
|
||||
/// <summary>
|
||||
/// 选项卡是否处于拖动状态
|
||||
/// </summary>
|
||||
private static bool ItemIsDragging;
|
||||
|
||||
/// <summary>
|
||||
/// 选项卡是否等待被拖动
|
||||
/// </summary>
|
||||
private bool _isWaiting;
|
||||
|
||||
/// <summary>
|
||||
/// 拖动中的选项卡坐标
|
||||
/// </summary>
|
||||
private Point _dragPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标按下时选项卡位置
|
||||
/// </summary>
|
||||
private int _mouseDownIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标按下时选项卡横向偏移
|
||||
/// </summary>
|
||||
private double _mouseDownOffsetX;
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标按下时的坐标
|
||||
/// </summary>
|
||||
private Point _mouseDownPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 右侧可移动的最大值
|
||||
/// </summary>
|
||||
private double _maxMoveRight;
|
||||
|
||||
/// <summary>
|
||||
/// 左侧可移动的最大值
|
||||
/// </summary>
|
||||
private double _maxMoveLeft;
|
||||
|
||||
/// <summary>
|
||||
/// 选项卡宽度
|
||||
/// </summary>
|
||||
public double ItemWidth { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// 选项卡拖动等待距离(在鼠标移动了超过20个像素无关单位后,选项卡才开始被拖动)
|
||||
/// </summary>
|
||||
private const double WaitLength = 20;
|
||||
|
||||
/// <summary>
|
||||
/// 选项卡是否处于拖动状态
|
||||
/// </summary>
|
||||
private bool _isDragging;
|
||||
|
||||
/// <summary>
|
||||
/// 选项卡是否已经被拖动
|
||||
/// </summary>
|
||||
private bool _isDragged;
|
||||
|
||||
/// <summary>
|
||||
/// 目标横向位移
|
||||
/// </summary>
|
||||
internal double TargetOffsetX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前编号
|
||||
/// </summary>
|
||||
private int _currentIndex;
|
||||
|
||||
/// <summary>
|
||||
/// 标签容器
|
||||
/// </summary>
|
||||
internal TabPanel TabPanel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前编号
|
||||
/// </summary>
|
||||
internal int CurrentIndex
|
||||
{
|
||||
get => _currentIndex;
|
||||
set
|
||||
{
|
||||
if (_currentIndex == value || value < 0) return;
|
||||
var oldIndex = _currentIndex;
|
||||
_currentIndex = value;
|
||||
UpdateItemOffsetX(oldIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新选项卡横向偏移
|
||||
/// </summary>
|
||||
/// <param name="oldIndex"></param>
|
||||
private void UpdateItemOffsetX(int oldIndex)
|
||||
{
|
||||
if (!_isDragging) return;
|
||||
var moveItem = TabPanel.ItemDic[CurrentIndex];
|
||||
moveItem.CurrentIndex -= CurrentIndex - oldIndex;
|
||||
var offsetX = moveItem.TargetOffsetX;
|
||||
var resultX = offsetX + (oldIndex - CurrentIndex) * ItemWidth;
|
||||
TabPanel.ItemDic[CurrentIndex] = this;
|
||||
TabPanel.ItemDic[moveItem.CurrentIndex] = moveItem;
|
||||
moveItem.CreateAnimation(offsetX, resultX);
|
||||
}
|
||||
|
||||
public TabItem()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Close, Close));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.CloseAll, (s, e) =>
|
||||
{
|
||||
TabControlParent.IsInternalAction = true;
|
||||
TabControlParent.Items.Clear();
|
||||
}));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.CloseOther, (s, e) =>
|
||||
{
|
||||
TabControlParent.IsInternalAction = true;
|
||||
var enumerator = ((IEnumerable)TabControlParent.Items).GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var item = enumerator.Current;
|
||||
if (!Equals(item) && item != null)
|
||||
{
|
||||
TabControlParent.Items.Remove(item);
|
||||
enumerator = ((IEnumerable)TabControlParent.Items).GetEnumerator();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private TabControl _tabControlParent;
|
||||
|
||||
private TabControl TabControlParent => _tabControlParent ?? (_tabControlParent = ItemsControl.ItemsControlFromItemContainer(this) as TabControl);
|
||||
|
||||
protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseRightButtonDown(e);
|
||||
IsSelected = true;
|
||||
Focus();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭
|
||||
/// </summary>
|
||||
private void Close(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (TabControlParent.IsEnableAnimation)
|
||||
{
|
||||
TabPanel.ClearValue(TabPanel.FluidMoveDurationProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
TabPanel.FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0));
|
||||
}
|
||||
TabControlParent.IsInternalAction = true;
|
||||
TabControlParent.Items.Remove(this);
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonDown(e);
|
||||
if (TabControlParent.IsDraggable && !ItemIsDragging && !_isDragging)
|
||||
{
|
||||
TabPanel.FluidMoveDuration = new Duration(TimeSpan.FromSeconds(0));
|
||||
_mouseDownOffsetX = RenderTransform.Value.OffsetX;
|
||||
var mx = TranslatePoint(new Point(), TabControlParent).X;
|
||||
_mouseDownIndex = CalLocationIndex(mx);
|
||||
_maxMoveLeft = -_mouseDownIndex * ItemWidth;
|
||||
_maxMoveRight = TabControlParent.ActualWidth - ActualWidth + _maxMoveLeft;
|
||||
|
||||
_isDragging = true;
|
||||
ItemIsDragging = true;
|
||||
_isWaiting = true;
|
||||
_dragPoint = e.GetPosition(TabControlParent);
|
||||
_mouseDownPoint = _dragPoint;
|
||||
CaptureMouse();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseMove(e);
|
||||
if (ItemIsDragging && _isDragging)
|
||||
{
|
||||
var subX = TranslatePoint(new Point(), TabControlParent).X;
|
||||
CurrentIndex = CalLocationIndex(subX);
|
||||
var p = e.GetPosition(TabControlParent);
|
||||
var subLeft = p.X - _dragPoint.X;
|
||||
var totalLeft = p.X - _mouseDownPoint.X;
|
||||
|
||||
if (Math.Abs(subLeft) <= WaitLength && _isWaiting) return;
|
||||
_isWaiting = false;
|
||||
_isDragged = true;
|
||||
var left = subLeft + RenderTransform.Value.OffsetX;
|
||||
if (totalLeft < _maxMoveLeft)
|
||||
{
|
||||
left = _maxMoveLeft + _mouseDownOffsetX;
|
||||
}
|
||||
else if (totalLeft > _maxMoveRight)
|
||||
{
|
||||
left = _maxMoveRight + _mouseDownOffsetX;
|
||||
}
|
||||
var t = new TranslateTransform(left, 0);
|
||||
RenderTransform = t;
|
||||
_dragPoint = p;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonUp(e);
|
||||
ReleaseMouseCapture();
|
||||
if (_isDragged)
|
||||
{
|
||||
var subX = TranslatePoint(new Point(), TabControlParent).X;
|
||||
var index = CalLocationIndex(subX);
|
||||
var left = index * ItemWidth;
|
||||
var offsetX = RenderTransform.Value.OffsetX;
|
||||
CreateAnimation(offsetX, offsetX - subX + left, index);
|
||||
}
|
||||
_isDragging = false;
|
||||
ItemIsDragging = false;
|
||||
_isDragged = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建动画
|
||||
/// </summary>
|
||||
internal void CreateAnimation(double offsetX, double resultX, int index = -1)
|
||||
{
|
||||
void AnimationCompleted()
|
||||
{
|
||||
RenderTransform = new TranslateTransform(resultX, 0);
|
||||
if (index == -1) return;
|
||||
var parent = TabControlParent;
|
||||
TabPanel.CanUpdate = false;
|
||||
parent.IsInternalAction = true;
|
||||
parent.Items.Remove(this);
|
||||
parent.IsInternalAction = true;
|
||||
parent.Items.Insert(index, this);
|
||||
TabPanel.CanUpdate = true;
|
||||
TabPanel.ForceUpdate = true;
|
||||
TabPanel.Measure(new Size(TabPanel.DesiredSize.Width, ActualHeight));
|
||||
TabPanel.ForceUpdate = false;
|
||||
Focus();
|
||||
IsSelected = true;
|
||||
}
|
||||
TargetOffsetX = resultX;
|
||||
if (!TabControlParent.IsEnableAnimation)
|
||||
{
|
||||
AnimationCompleted();
|
||||
return;
|
||||
}
|
||||
var animation = AnimationHelper.CreateAnimation(resultX, AnimationSpeed);
|
||||
animation.FillBehavior = FillBehavior.Stop;
|
||||
animation.Completed += (s1, e1) => AnimationCompleted();
|
||||
var f = new TranslateTransform(offsetX, 0);
|
||||
RenderTransform = f;
|
||||
f.BeginAnimation(TranslateTransform.XProperty, animation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算选项卡当前合适的位置编号
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <returns></returns>
|
||||
private int CalLocationIndex(double left)
|
||||
{
|
||||
var maxIndex = TabControlParent.Items.Count - 1;
|
||||
var div = (int)(left / ItemWidth);
|
||||
var rest = left % ItemWidth;
|
||||
var result = rest / ItemWidth > .5 ? div + 1 : div;
|
||||
|
||||
return result > maxIndex ? maxIndex : result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class TabPanel : Panel
|
||||
{
|
||||
private int _itemCount;
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以更新
|
||||
/// </summary>
|
||||
internal bool CanUpdate = true;
|
||||
|
||||
/// <summary>
|
||||
/// 选项卡字典
|
||||
/// </summary>
|
||||
internal Dictionary<int, TabItem> ItemDic = new Dictionary<int, TabItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 流式行为持续时间
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty FluidMoveDurationProperty = DependencyProperty.Register(
|
||||
"FluidMoveDuration", typeof(Duration), typeof(TabPanel), new PropertyMetadata(new Duration(TimeSpan.FromMilliseconds(200))));
|
||||
|
||||
/// <summary>
|
||||
/// 流式行为持续时间
|
||||
/// </summary>
|
||||
public Duration FluidMoveDuration
|
||||
{
|
||||
get => (Duration)GetValue(FluidMoveDurationProperty);
|
||||
set => SetValue(FluidMoveDurationProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否将标签填充
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsEnableTabFillProperty = DependencyProperty.Register(
|
||||
"IsEnableTabFill", typeof(bool), typeof(TabPanel), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 是否将标签填充
|
||||
/// </summary>
|
||||
public bool IsEnableTabFill
|
||||
{
|
||||
get => (bool)GetValue(IsEnableTabFillProperty);
|
||||
set => SetValue(IsEnableTabFillProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标签宽度
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TabItemWidthProperty = DependencyProperty.Register(
|
||||
"TabItemWidth", typeof(double), typeof(TabPanel), new PropertyMetadata(200.0));
|
||||
|
||||
/// <summary>
|
||||
/// 标签宽度
|
||||
/// </summary>
|
||||
public double TabItemWidth
|
||||
{
|
||||
get => (double)GetValue(TabItemWidthProperty);
|
||||
set => SetValue(TabItemWidthProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 标签高度
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty TabItemHeightProperty = DependencyProperty.Register(
|
||||
"TabItemHeight", typeof(double), typeof(TabPanel), new PropertyMetadata(30.0));
|
||||
|
||||
/// <summary>
|
||||
/// 标签高度
|
||||
/// </summary>
|
||||
public double TabItemHeight
|
||||
{
|
||||
get => (double)GetValue(TabItemHeightProperty);
|
||||
set => SetValue(TabItemHeightProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否可以强制更新
|
||||
/// </summary>
|
||||
internal bool ForceUpdate;
|
||||
|
||||
private Size _oldSize;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已经加载
|
||||
/// </summary>
|
||||
private bool _isLoaded;
|
||||
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
if ((_itemCount == InternalChildren.Count || !CanUpdate) && !ForceUpdate) return _oldSize;
|
||||
constraint.Height = TabItemHeight;
|
||||
_itemCount = InternalChildren.Count;
|
||||
|
||||
var size = new Size();
|
||||
|
||||
ItemDic.Clear();
|
||||
|
||||
var count = InternalChildren.Count;
|
||||
if (count == 0)
|
||||
{
|
||||
_oldSize = new Size();
|
||||
return _oldSize;
|
||||
}
|
||||
constraint.Width += InternalChildren.Count;
|
||||
|
||||
var itemWidth = .0;
|
||||
var arr = new int[count];
|
||||
|
||||
if (!IsEnableTabFill)
|
||||
{
|
||||
itemWidth = TabItemWidth;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TemplatedParent is TabControl tabControl)
|
||||
{
|
||||
arr = ArithmeticHelper.DivideInt2Arr((int)tabControl.ActualWidth + InternalChildren.Count, count);
|
||||
}
|
||||
}
|
||||
|
||||
for (var index = 0; index < count; index++)
|
||||
{
|
||||
if (IsEnableTabFill)
|
||||
{
|
||||
itemWidth = arr[index];
|
||||
}
|
||||
if (InternalChildren[index] is TabItem tabItem)
|
||||
{
|
||||
tabItem.RenderTransform = new TranslateTransform();
|
||||
tabItem.MaxWidth = itemWidth;
|
||||
var rect = new Rect
|
||||
{
|
||||
X = size.Width - tabItem.BorderThickness.Left,
|
||||
Width = itemWidth,
|
||||
Height = TabItemHeight
|
||||
};
|
||||
tabItem.Arrange(rect);
|
||||
tabItem.ItemWidth = itemWidth - tabItem.BorderThickness.Left;
|
||||
tabItem.CurrentIndex = index;
|
||||
tabItem.TargetOffsetX = 0;
|
||||
ItemDic[index] = tabItem;
|
||||
size.Width += tabItem.ItemWidth;
|
||||
}
|
||||
}
|
||||
size.Height = constraint.Height;
|
||||
_oldSize = size;
|
||||
return _oldSize;
|
||||
}
|
||||
|
||||
public TabPanel()
|
||||
{
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (_isLoaded) return;
|
||||
ForceUpdate = true;
|
||||
Measure(new Size(DesiredSize.Width, ActualHeight));
|
||||
ForceUpdate = false;
|
||||
foreach (var item in ItemDic.Values)
|
||||
{
|
||||
item.TabPanel = this;
|
||||
}
|
||||
_isLoaded = true;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class OutlineText : FrameworkElement
|
||||
{
|
||||
private FormattedText _formattedText;
|
||||
|
||||
private Geometry _textGeometry;
|
||||
|
||||
static OutlineText()
|
||||
{
|
||||
SnapsToDevicePixelsProperty.OverrideMetadata(typeof(OutlineText), new FrameworkPropertyMetadata(ValueBoxes.TrueBox));
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
"Text", typeof(string), typeof(OutlineText), new FrameworkPropertyMetadata(
|
||||
string.Empty,
|
||||
FrameworkPropertyMetadataOptions.AffectsMeasure |
|
||||
FrameworkPropertyMetadataOptions.AffectsRender, OnFormattedTextInvalidated));
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string) GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register(
|
||||
"TextAlignment", typeof(TextAlignment), typeof(OutlineText),
|
||||
new PropertyMetadata(default(TextAlignment), OnFormattedTextUpdated));
|
||||
|
||||
public TextAlignment TextAlignment
|
||||
{
|
||||
get => (TextAlignment) GetValue(TextAlignmentProperty);
|
||||
set => SetValue(TextAlignmentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextTrimmingProperty = DependencyProperty.Register(
|
||||
"TextTrimming", typeof(TextTrimming), typeof(OutlineText),
|
||||
new PropertyMetadata(default(TextTrimming), OnFormattedTextInvalidated));
|
||||
|
||||
public TextTrimming TextTrimming
|
||||
{
|
||||
get => (TextTrimming) GetValue(TextTrimmingProperty);
|
||||
set => SetValue(TextTrimmingProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(
|
||||
"TextWrapping", typeof(TextWrapping), typeof(OutlineText),
|
||||
new PropertyMetadata(TextWrapping.NoWrap, OnFormattedTextInvalidated));
|
||||
|
||||
public TextWrapping TextWrapping
|
||||
{
|
||||
get => (TextWrapping) GetValue(TextWrappingProperty);
|
||||
set => SetValue(TextWrappingProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
|
||||
"Fill", typeof(Brush), typeof(OutlineText), new PropertyMetadata(Brushes.Black, OnFormattedTextUpdated));
|
||||
|
||||
public Brush Fill
|
||||
{
|
||||
get => (Brush) GetValue(FillProperty);
|
||||
set => SetValue(FillProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
|
||||
"Stroke", typeof(Brush), typeof(OutlineText), new PropertyMetadata(Brushes.Black, OnFormattedTextUpdated));
|
||||
|
||||
public Brush Stroke
|
||||
{
|
||||
get => (Brush) GetValue(StrokeProperty);
|
||||
set => SetValue(StrokeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
|
||||
"StrokeThickness", typeof(double), typeof(OutlineText), new PropertyMetadata(ValueBoxes.Double0Box, OnFormattedTextUpdated));
|
||||
|
||||
public double StrokeThickness
|
||||
{
|
||||
get => (double) GetValue(StrokeThicknessProperty);
|
||||
set => SetValue(StrokeThicknessProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(
|
||||
typeof(OutlineText),
|
||||
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
|
||||
|
||||
public FontFamily FontFamily
|
||||
{
|
||||
get => (FontFamily)GetValue(FontFamilyProperty);
|
||||
set => SetValue(FontFamilyProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
|
||||
typeof(OutlineText),
|
||||
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
|
||||
|
||||
[TypeConverter(typeof(FontSizeConverter))]
|
||||
public double FontSize
|
||||
{
|
||||
get => (double)GetValue(FontSizeProperty);
|
||||
set => SetValue(FontSizeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(
|
||||
typeof(OutlineText),
|
||||
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
|
||||
|
||||
public FontStretch FontStretch
|
||||
{
|
||||
get => (FontStretch)GetValue(FontStretchProperty);
|
||||
set => SetValue(FontStretchProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(
|
||||
typeof(OutlineText),
|
||||
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
|
||||
|
||||
public FontStyle FontStyle
|
||||
{
|
||||
get => (FontStyle)GetValue(FontStyleProperty);
|
||||
set => SetValue(FontStyleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(
|
||||
typeof(OutlineText),
|
||||
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
|
||||
|
||||
public FontWeight FontWeight
|
||||
{
|
||||
get => (FontWeight)GetValue(FontWeightProperty);
|
||||
set => SetValue(FontWeightProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext)
|
||||
{
|
||||
EnsureGeometry();
|
||||
drawingContext.DrawGeometry(Fill, new Pen(Stroke, StrokeThickness), _textGeometry);
|
||||
}
|
||||
|
||||
private void EnsureFormattedText()
|
||||
{
|
||||
if (_formattedText != null || Text == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if netle45
|
||||
_formattedText = new FormattedText(
|
||||
Text,
|
||||
CultureInfo.CurrentUICulture,
|
||||
FlowDirection,
|
||||
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
|
||||
FontSize, Fill);
|
||||
#else
|
||||
var source = PresentationSource.FromVisual(this);
|
||||
var dpi = 1.0;
|
||||
if (source?.CompositionTarget != null)
|
||||
{
|
||||
dpi = 96.0 * source.CompositionTarget.TransformToDevice.M11 / 96.0;
|
||||
}
|
||||
_formattedText = new FormattedText(
|
||||
Text,
|
||||
CultureInfo.CurrentUICulture,
|
||||
FlowDirection,
|
||||
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
|
||||
FontSize, Fill, dpi);
|
||||
#endif
|
||||
|
||||
UpdateFormattedText();
|
||||
}
|
||||
|
||||
private void EnsureGeometry()
|
||||
{
|
||||
if (_textGeometry != null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureFormattedText();
|
||||
_textGeometry = _formattedText.BuildGeometry(new Point(0, 0));
|
||||
}
|
||||
|
||||
private void UpdateFormattedText()
|
||||
{
|
||||
if (_formattedText == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_formattedText.MaxLineCount = TextWrapping == TextWrapping.NoWrap ? 1 : int.MaxValue;
|
||||
_formattedText.TextAlignment = TextAlignment;
|
||||
_formattedText.Trimming = TextTrimming;
|
||||
|
||||
_formattedText.SetFontSize(FontSize);
|
||||
_formattedText.SetFontStyle(FontStyle);
|
||||
_formattedText.SetFontWeight(FontWeight);
|
||||
_formattedText.SetFontFamily(FontFamily);
|
||||
_formattedText.SetFontStretch(FontStretch);
|
||||
}
|
||||
|
||||
private static void OnFormattedTextUpdated(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var outlinedTextBlock = (OutlineText)d;
|
||||
outlinedTextBlock.UpdateFormattedText();
|
||||
outlinedTextBlock._textGeometry = null;
|
||||
|
||||
outlinedTextBlock.InvalidateMeasure();
|
||||
outlinedTextBlock.InvalidateVisual();
|
||||
}
|
||||
|
||||
private static void OnFormattedTextInvalidated(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var outlinedTextBlock = (OutlineText)d;
|
||||
outlinedTextBlock._formattedText = null;
|
||||
outlinedTextBlock._textGeometry = null;
|
||||
|
||||
outlinedTextBlock.InvalidateMeasure();
|
||||
outlinedTextBlock.InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
EnsureFormattedText();
|
||||
|
||||
// constrain the formatted text according to the available size
|
||||
// the Math.Min call is important - without this constraint (which seems arbitrary, but is the maximum allowable text width), things blow up when availableSize is infinite in both directions
|
||||
// the Math.Max call is to ensure we don't hit zero, which will cause MaxTextHeight to throw
|
||||
_formattedText.MaxTextWidth = Math.Min(3579139, availableSize.Width);
|
||||
_formattedText.MaxTextHeight = Math.Max(0.0001d, availableSize.Height);
|
||||
|
||||
// return the desired size
|
||||
return new Size(_formattedText.Width, _formattedText.Height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class Tag : ContentControl
|
||||
{
|
||||
public Tag()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Close, (s, e) =>
|
||||
{
|
||||
var argsClosing = new CancelRoutedEventArgs(ClosingEvent, this);
|
||||
RaiseEvent(argsClosing);
|
||||
if (argsClosing.Cancel) return;
|
||||
|
||||
RaiseEvent(new RoutedEventArgs(ClosedEvent, this));
|
||||
}));
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowCloseButtonProperty = DependencyProperty.Register(
|
||||
"ShowCloseButton", typeof(bool), typeof(Tag), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool ShowCloseButton
|
||||
{
|
||||
get => (bool) GetValue(ShowCloseButtonProperty);
|
||||
set => SetValue(ShowCloseButtonProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SelectableProperty = DependencyProperty.Register(
|
||||
"Selectable", typeof(bool), typeof(Tag), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool Selectable
|
||||
{
|
||||
get => (bool) GetValue(SelectableProperty);
|
||||
set => SetValue(SelectableProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(
|
||||
"IsSelected", typeof(bool), typeof(Tag), new PropertyMetadata(ValueBoxes.FalseBox, (o, args) =>
|
||||
{
|
||||
var ctl = (Tag) o;
|
||||
ctl.RaiseEvent(new RoutedEventArgs(SelectedEvent, ctl));
|
||||
}));
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => (bool) GetValue(IsSelectedProperty);
|
||||
set => SetValue(IsSelectedProperty, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent SelectedEvent = EventManager.RegisterRoutedEvent("Selected", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Tag));
|
||||
|
||||
public event EventHandler Selected
|
||||
{
|
||||
add => AddHandler(SelectedEvent, value);
|
||||
remove => RemoveHandler(SelectedEvent, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent ClosingEvent = EventManager.RegisterRoutedEvent("Closing", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Tag));
|
||||
|
||||
public event EventHandler Closing
|
||||
{
|
||||
add => AddHandler(ClosingEvent, value);
|
||||
remove => RemoveHandler(ClosingEvent, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent ClosedEvent = EventManager.RegisterRoutedEvent("Closed", RoutingStrategy.Bubble, typeof(EventHandler), typeof(Tag));
|
||||
|
||||
public event EventHandler Closed
|
||||
{
|
||||
add => AddHandler(ClosedEvent, value);
|
||||
remove => RemoveHandler(ClosedEvent, value);
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonDown(e);
|
||||
|
||||
if (Selectable)
|
||||
{
|
||||
IsSelected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class TagPanel : WrapPanel
|
||||
{
|
||||
private Button _addTagButton;
|
||||
|
||||
private bool _isInternalAction;
|
||||
|
||||
public TagPanel()
|
||||
{
|
||||
var behavior = new FluidMoveBehavior
|
||||
{
|
||||
AppliesTo = FluidMoveScope.Children,
|
||||
Duration = new Duration(TimeSpan.FromMilliseconds(200))
|
||||
};
|
||||
var collection = Interaction.GetBehaviors(this);
|
||||
collection.Add(behavior);
|
||||
|
||||
AddHandler(Controls.Tag.ClosedEvent, new RoutedEventHandler(Tag_OnClosed));
|
||||
}
|
||||
|
||||
private void Tag_OnClosed(object sender, RoutedEventArgs e) => Children.Remove(e.OriginalSource as Tag);
|
||||
|
||||
public static readonly RoutedEvent AddTagButtonClickEvent = EventManager.RegisterRoutedEvent("AddTagButtonClick", RoutingStrategy.Bubble, typeof(EventHandler), typeof(TagPanel));
|
||||
|
||||
public event EventHandler AddTagButtonClick
|
||||
{
|
||||
add => AddHandler(AddTagButtonClickEvent, value);
|
||||
remove => RemoveHandler(AddTagButtonClickEvent, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowAddButtonProperty = DependencyProperty.Register(
|
||||
"ShowAddButton", typeof(bool), typeof(TagPanel), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowAddButton
|
||||
{
|
||||
get => (bool) GetValue(ShowAddButtonProperty);
|
||||
set => SetValue(ShowAddButtonProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TagMarginProperty = DependencyProperty.Register(
|
||||
"TagMargin", typeof(Thickness), typeof(TagPanel), new PropertyMetadata(new Thickness(5)));
|
||||
|
||||
public Thickness TagMargin
|
||||
{
|
||||
get => (Thickness) GetValue(TagMarginProperty);
|
||||
set => SetValue(TagMarginProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
|
||||
{
|
||||
if (!(visualAdded is Tag tag)) return;
|
||||
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
|
||||
|
||||
tag.Margin = TagMargin;
|
||||
if (_isInternalAction) return;
|
||||
if (ShowAddButton)
|
||||
{
|
||||
_isInternalAction = true;
|
||||
if (!Children.Contains(_addTagButton))
|
||||
{
|
||||
_addTagButton = new Button
|
||||
{
|
||||
Style = ResourceHelper.GetResource<Style>(ResourceToken.AddTagButtonStyle),
|
||||
Margin = TagMargin
|
||||
};
|
||||
_addTagButton.Click += AddTagButton_Click;
|
||||
Children.Add(_addTagButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
Children.Remove(_addTagButton);
|
||||
Children.Add(_addTagButton);
|
||||
}
|
||||
_isInternalAction = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddTagButton_Click(object sender, RoutedEventArgs e) => RaiseEvent(new RoutedEventArgs(AddTagButtonClickEvent, this));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,431 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = ElementButtonAm, Type = typeof(RadioButton))]
|
||||
[TemplatePart(Name = ElementButtonPm, Type = typeof(RadioButton))]
|
||||
[TemplatePart(Name = ElementButtonConfirm, Type = typeof(Button))]
|
||||
[TemplatePart(Name = ElementCanvas, Type = typeof(Canvas))]
|
||||
[TemplatePart(Name = ElementBorderTitle, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementBorderClock, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementPanelNum, Type = typeof(CirclePanel))]
|
||||
[TemplatePart(Name = ElementTimeStr, Type = typeof(TextBlock))]
|
||||
public class Clock : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementButtonAm = "PART_ButtonAm";
|
||||
private const string ElementButtonPm = "PART_ButtonPm";
|
||||
private const string ElementButtonConfirm = "PART_ButtonConfirm";
|
||||
private const string ElementCanvas = "PART_Canvas";
|
||||
private const string ElementBorderTitle = "PART_BorderTitle";
|
||||
private const string ElementBorderClock = "PART_BorderClock";
|
||||
private const string ElementPanelNum = "PART_PanelNum";
|
||||
private const string ElementTimeStr = "PART_TimeStr";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private RadioButton _buttonAm;
|
||||
|
||||
private RadioButton _buttonPm;
|
||||
|
||||
private Button _buttonConfirm;
|
||||
|
||||
private Canvas _canvas;
|
||||
|
||||
private Border _borderTitle;
|
||||
|
||||
private Border _borderClock;
|
||||
|
||||
private ClockRadioButton _currentButton;
|
||||
|
||||
private RotateTransform _rotateTransformClock;
|
||||
|
||||
private CirclePanel _circlePanel;
|
||||
|
||||
private List<ClockRadioButton> _radioButtonList;
|
||||
|
||||
private TextBlock _blockTime;
|
||||
|
||||
private int _secValue;
|
||||
|
||||
private bool _appliedTemplate;
|
||||
|
||||
#endregion Data
|
||||
|
||||
#region Public Events
|
||||
|
||||
public static readonly RoutedEvent SelectedTimeChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("SelectedTimeChanged", RoutingStrategy.Direct,
|
||||
typeof(EventHandler<FunctionEventArgs<DateTime?>>), typeof(Clock));
|
||||
|
||||
public event EventHandler<FunctionEventArgs<DateTime?>> SelectedTimeChanged
|
||||
{
|
||||
add => AddHandler(SelectedTimeChangedEvent, value);
|
||||
remove => RemoveHandler(SelectedTimeChangedEvent, value);
|
||||
}
|
||||
|
||||
public event EventHandler<FunctionEventArgs<DateTime>> DisplayTimeChanged;
|
||||
|
||||
public event Action Confirmed;
|
||||
|
||||
#endregion Public Events
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register(
|
||||
"SelectedTime", typeof(DateTime?), typeof(Clock), new PropertyMetadata(default(DateTime?), OnSelectedTimeChanged));
|
||||
|
||||
private static void OnSelectedTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (Clock)d;
|
||||
var v = (DateTime?)e.NewValue;
|
||||
ctl.DisplayTime = v ?? DateTime.Now;
|
||||
ctl.OnSelectedTimeChanged(new FunctionEventArgs<DateTime?>(SelectedTimeChangedEvent, ctl)
|
||||
{
|
||||
Info = v
|
||||
});
|
||||
}
|
||||
|
||||
public DateTime? SelectedTime
|
||||
{
|
||||
get => (DateTime?) GetValue(SelectedTimeProperty);
|
||||
set => SetValue(SelectedTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TimeFormatProperty = DependencyProperty.Register(
|
||||
"TimeFormat", typeof(string), typeof(Clock), new PropertyMetadata("HH:mm:ss"));
|
||||
|
||||
public string TimeFormat
|
||||
{
|
||||
get => (string) GetValue(TimeFormatProperty);
|
||||
set => SetValue(TimeFormatProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ClockRadioButtonStyleProperty = DependencyProperty.Register(
|
||||
"ClockRadioButtonStyle", typeof(Style), typeof(Clock), new PropertyMetadata(default(Style)));
|
||||
|
||||
public Style ClockRadioButtonStyle
|
||||
{
|
||||
get => (Style) GetValue(ClockRadioButtonStyleProperty);
|
||||
set => SetValue(ClockRadioButtonStyleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DisplayTimeProperty = DependencyProperty.Register(
|
||||
"DisplayTime", typeof(DateTime), typeof(Clock), new FrameworkPropertyMetadata(DateTime.Now, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnDisplayTimeChanged));
|
||||
|
||||
private static void OnDisplayTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (Clock) d;
|
||||
var v = (DateTime) e.NewValue;
|
||||
ctl.Update(v);
|
||||
ctl.OnDisplayTimeChanged(new FunctionEventArgs<DateTime>(v));
|
||||
}
|
||||
|
||||
public DateTime DisplayTime
|
||||
{
|
||||
get => (DateTime) GetValue(DisplayTimeProperty);
|
||||
set => SetValue(DisplayTimeProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ShowConfirmButtonProperty = DependencyProperty.Register(
|
||||
"ShowConfirmButton", typeof(bool), typeof(Clock), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
internal bool ShowConfirmButton
|
||||
{
|
||||
get => (bool)GetValue(ShowConfirmButtonProperty);
|
||||
set => SetValue(ShowConfirmButtonProperty, value);
|
||||
}
|
||||
|
||||
private int SecValue
|
||||
{
|
||||
get => _secValue;
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
_secValue = 59;
|
||||
}
|
||||
else if (value > 59)
|
||||
{
|
||||
_secValue = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_secValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_appliedTemplate = false;
|
||||
if (_buttonAm != null)
|
||||
{
|
||||
_buttonAm.Click -= ButtonAm_OnClick;
|
||||
}
|
||||
|
||||
if (_buttonPm != null)
|
||||
{
|
||||
_buttonPm.Click -= ButtonPm_OnClick;
|
||||
}
|
||||
|
||||
if (_buttonConfirm != null)
|
||||
{
|
||||
_buttonConfirm.Click -= ButtonConfirm_OnClick;
|
||||
}
|
||||
|
||||
if (_borderTitle != null)
|
||||
{
|
||||
_borderTitle.MouseWheel -= BorderTitle_OnMouseWheel;
|
||||
}
|
||||
|
||||
if (_canvas != null)
|
||||
{
|
||||
_canvas.MouseWheel -= Canvas_OnMouseWheel;
|
||||
_canvas.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(Canvas_OnClick));
|
||||
_canvas.MouseMove -= Canvas_OnMouseMove;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_buttonAm = GetTemplateChild(ElementButtonAm) as RadioButton;
|
||||
_buttonPm = GetTemplateChild(ElementButtonPm) as RadioButton;
|
||||
_buttonConfirm = GetTemplateChild(ElementButtonConfirm) as Button;
|
||||
_borderTitle = GetTemplateChild(ElementBorderTitle) as Border;
|
||||
_canvas = GetTemplateChild(ElementCanvas) as Canvas;
|
||||
_borderClock = GetTemplateChild(ElementBorderClock) as Border;
|
||||
_circlePanel = GetTemplateChild(ElementPanelNum) as CirclePanel;
|
||||
_blockTime = GetTemplateChild(ElementTimeStr) as TextBlock;
|
||||
|
||||
CheckNull();
|
||||
|
||||
_buttonAm.Click += ButtonAm_OnClick;
|
||||
_buttonPm.Click += ButtonPm_OnClick;
|
||||
_buttonConfirm.Click += ButtonConfirm_OnClick;
|
||||
_borderTitle.MouseWheel += BorderTitle_OnMouseWheel;
|
||||
|
||||
_canvas.MouseWheel += Canvas_OnMouseWheel;
|
||||
_canvas.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(Canvas_OnClick));
|
||||
_canvas.MouseMove += Canvas_OnMouseMove;
|
||||
|
||||
_rotateTransformClock = new RotateTransform();
|
||||
_borderClock.RenderTransform = _rotateTransformClock;
|
||||
|
||||
_radioButtonList = new List<ClockRadioButton>();
|
||||
for (var i = 0; i < 12; i++)
|
||||
{
|
||||
var num = i + 1;
|
||||
var button = new ClockRadioButton
|
||||
{
|
||||
Num = num,
|
||||
Content = num
|
||||
};
|
||||
button.SetBinding(StyleProperty, new Binding(ClockRadioButtonStyleProperty.Name) { Source = this });
|
||||
_radioButtonList.Add(button);
|
||||
_circlePanel.Children.Add(button);
|
||||
}
|
||||
|
||||
_appliedTemplate = true;
|
||||
if (SelectedTime.HasValue)
|
||||
{
|
||||
Update(SelectedTime.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
DisplayTime = DateTime.Now;
|
||||
Update(DisplayTime);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected virtual void OnSelectedTimeChanged(FunctionEventArgs<DateTime?> e) => RaiseEvent(e);
|
||||
|
||||
protected virtual void OnDisplayTimeChanged(FunctionEventArgs<DateTime> e)
|
||||
{
|
||||
var handler = DisplayTimeChanged;
|
||||
handler?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
|
||||
if (_buttonPm == null || _buttonAm == null || _buttonConfirm == null || _canvas == null ||
|
||||
_borderTitle == null || _borderClock == null || _circlePanel == null ||
|
||||
_blockTime == null) throw new Exception();
|
||||
}
|
||||
|
||||
private void BorderTitle_OnMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (e.Delta < 0)
|
||||
{
|
||||
SecValue--;
|
||||
Update();
|
||||
}
|
||||
else
|
||||
{
|
||||
SecValue++;
|
||||
Update();
|
||||
}
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Canvas_OnMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
var value = (int)_rotateTransformClock.Angle;
|
||||
if (e.Delta < 0)
|
||||
{
|
||||
value += 6;
|
||||
}
|
||||
else
|
||||
{
|
||||
value -= 6;
|
||||
}
|
||||
if (value < 0)
|
||||
{
|
||||
value = value + 360;
|
||||
}
|
||||
_rotateTransformClock.Angle = value;
|
||||
|
||||
Update();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void Canvas_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
_currentButton = e.OriginalSource as ClockRadioButton;
|
||||
if (_currentButton != null)
|
||||
{
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
private void Canvas_OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
var value = ArithmeticHelper.CalAngle(new Point(85, 85), e.GetPosition(_canvas)) + 90;
|
||||
if (value < 0)
|
||||
{
|
||||
value = value + 360;
|
||||
}
|
||||
value = value - value % 6;
|
||||
_rotateTransformClock.Angle = value;
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
var hValue = _currentButton.Num;
|
||||
if (_buttonPm.IsChecked == true)
|
||||
{
|
||||
hValue += 12;
|
||||
if (hValue == 24) hValue = 12;
|
||||
}
|
||||
else if (hValue == 12)
|
||||
{
|
||||
hValue = 0;
|
||||
}
|
||||
if (hValue == 12 && _buttonAm.IsChecked == true)
|
||||
{
|
||||
_buttonPm.IsChecked = true;
|
||||
_buttonAm.IsChecked = false;
|
||||
}
|
||||
|
||||
if (_blockTime != null)
|
||||
{
|
||||
DisplayTime = GetDisplayTime();
|
||||
_blockTime.Text = DisplayTime.ToString(TimeFormat);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
internal void Update(DateTime time)
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
var h = time.Hour;
|
||||
var m = time.Minute;
|
||||
|
||||
if (h >= 12)
|
||||
{
|
||||
_buttonPm.IsChecked = true;
|
||||
_buttonAm.IsChecked = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_buttonPm.IsChecked = false;
|
||||
_buttonAm.IsChecked = true;
|
||||
}
|
||||
|
||||
_rotateTransformClock.Angle = m * 6;
|
||||
|
||||
var hRest = h % 12;
|
||||
if (hRest == 0) hRest = 12;
|
||||
var ctl = _radioButtonList[hRest-1];
|
||||
ctl.IsChecked = true;
|
||||
ctl.RaiseEvent(new RoutedEventArgs { RoutedEvent = ButtonBase.ClickEvent });
|
||||
|
||||
_secValue = time.Second;
|
||||
Update();
|
||||
}
|
||||
|
||||
private void ButtonConfirm_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SelectedTime = DisplayTime;
|
||||
Confirmed?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取显示时间
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private DateTime GetDisplayTime()
|
||||
{
|
||||
var hValue = _currentButton.Num;
|
||||
if (_buttonPm.IsChecked == true)
|
||||
{
|
||||
hValue += 12;
|
||||
if (hValue == 24) hValue = 12;
|
||||
}
|
||||
else if (hValue == 12)
|
||||
{
|
||||
hValue = 0;
|
||||
}
|
||||
var now = DateTime.Now;
|
||||
return new DateTime(now.Year, now.Month, now.Day, hValue, (int)Math.Abs(_rotateTransformClock.Angle) % 360 / 6, _secValue);
|
||||
}
|
||||
|
||||
private void ButtonAm_OnClick(object sender, RoutedEventArgs e) => Update();
|
||||
|
||||
private void ButtonPm_OnClick(object sender, RoutedEventArgs e) => Update();
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class ClockRadioButton : RadioButton
|
||||
{
|
||||
public int Num { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class FlipClock : Control
|
||||
{
|
||||
public static readonly DependencyProperty NumberListProperty = DependencyProperty.Register(
|
||||
"NumberList", typeof(List<int>), typeof(FlipClock), new PropertyMetadata(new List<int> {0, 0, 0, 0, 0, 0}));
|
||||
|
||||
public List<int> NumberList
|
||||
{
|
||||
get => (List<int>) GetValue(NumberListProperty);
|
||||
set => SetValue(NumberListProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DisplayTimeProperty = DependencyProperty.Register(
|
||||
"DisplayTime", typeof(DateTime), typeof(FlipClock), new PropertyMetadata(default(DateTime), OnDisplayTimeChanged));
|
||||
|
||||
private static void OnDisplayTimeChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (FlipClock) s;
|
||||
var v = (DateTime) e.NewValue;
|
||||
|
||||
ctl.NumberList = new List<int>
|
||||
{
|
||||
v.Hour / 10,
|
||||
v.Hour % 10,
|
||||
v.Minute / 10,
|
||||
v.Minute % 10,
|
||||
v.Second / 10,
|
||||
v.Second % 10
|
||||
};
|
||||
}
|
||||
|
||||
public DateTime DisplayTime
|
||||
{
|
||||
get => (DateTime) GetValue(DisplayTimeProperty);
|
||||
set => SetValue(DisplayTimeProperty, value);
|
||||
}
|
||||
|
||||
public FlipClock()
|
||||
{
|
||||
var dispatcherTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(200)
|
||||
};
|
||||
dispatcherTimer.Tick += DispatcherTimer_Tick;
|
||||
dispatcherTimer.Start();
|
||||
}
|
||||
|
||||
private void DispatcherTimer_Tick(object sender, EventArgs e) => DisplayTime = DateTime.Now;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Media3D;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class FlipNumber : Viewport3D
|
||||
{
|
||||
private bool _isLoaded;
|
||||
|
||||
private TextBlock _page1TextDown;
|
||||
|
||||
private TextBlock _page2TextUp;
|
||||
|
||||
private TextBlock _page2TextDown;
|
||||
|
||||
private TextBlock _page3TextUp;
|
||||
|
||||
private ContainerUIElement3D _page1;
|
||||
|
||||
private ContainerUIElement3D _page2;
|
||||
|
||||
private ContainerUIElement3D _page3;
|
||||
|
||||
private ContainerUIElement3D _content;
|
||||
|
||||
private readonly AxisAngleRotation3D _pageRotation3D;
|
||||
|
||||
private readonly DoubleAnimation _animation;
|
||||
|
||||
private bool _isAnimating;
|
||||
|
||||
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register(
|
||||
"CornerRadius", typeof(CornerRadius), typeof(FlipNumber), new PropertyMetadata(new CornerRadius(4)));
|
||||
|
||||
public CornerRadius CornerRadius
|
||||
{
|
||||
get => (CornerRadius)GetValue(CornerRadiusProperty);
|
||||
set => SetValue(CornerRadiusProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register(
|
||||
"Background", typeof(Brush), typeof(FlipNumber), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public Brush Background
|
||||
{
|
||||
get => (Brush)GetValue(BackgroundProperty);
|
||||
set => SetValue(BackgroundProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ForegroundProperty = DependencyProperty.Register(
|
||||
"Foreground", typeof(Brush), typeof(FlipNumber), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public Brush Foreground
|
||||
{
|
||||
get => (Brush)GetValue(ForegroundProperty);
|
||||
set => SetValue(ForegroundProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FontSizeProperty = DependencyProperty.Register(
|
||||
"FontSize", typeof(double), typeof(FlipNumber), new PropertyMetadata(70.0));
|
||||
|
||||
[TypeConverter(typeof(FontSizeConverter))]
|
||||
public double FontSize
|
||||
{
|
||||
get => (double)GetValue(FontSizeProperty);
|
||||
set => SetValue(FontSizeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty NumberProperty = DependencyProperty.Register(
|
||||
"Number", typeof(int), typeof(FlipNumber), new PropertyMetadata(ValueBoxes.Int0Box, OnNumberChanged));
|
||||
|
||||
private static void OnNumberChanged(DependencyObject s, DependencyPropertyChangedEventArgs e) =>
|
||||
((FlipNumber)s).OnNumberChanged();
|
||||
|
||||
public int Number
|
||||
{
|
||||
get => (int)GetValue(NumberProperty);
|
||||
set => SetValue(NumberProperty, value);
|
||||
}
|
||||
|
||||
public FlipNumber()
|
||||
{
|
||||
var visual3D = new ModelVisual3D
|
||||
{
|
||||
Content = new DirectionalLight()
|
||||
};
|
||||
Children.Add(visual3D);
|
||||
|
||||
_pageRotation3D = new AxisAngleRotation3D
|
||||
{
|
||||
Angle = 0,
|
||||
Axis = new Vector3D(1, 0, 0)
|
||||
};
|
||||
|
||||
_animation = new DoubleAnimation(0, 180, new Duration(TimeSpan.FromSeconds(0.8)))
|
||||
{
|
||||
FillBehavior = FillBehavior.Stop
|
||||
};
|
||||
_animation.Completed += Animation_Completed;
|
||||
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (_isLoaded) return;
|
||||
_isLoaded = true;
|
||||
InitNumber();
|
||||
var transform3D = new RotateTransform3D(_pageRotation3D);
|
||||
_page2.Transform = transform3D;
|
||||
};
|
||||
}
|
||||
|
||||
private void Animation_Completed(object sender, EventArgs e)
|
||||
{
|
||||
_isAnimating = false;
|
||||
UpdateNumber();
|
||||
}
|
||||
|
||||
private void InitNumber()
|
||||
{
|
||||
_page1 = new ContainerUIElement3D();
|
||||
var num1 = Number > 8 ? 0 : Number + 1;
|
||||
var page1NumDown = CreateNumber(num1, false, out _page1TextDown);
|
||||
_page1.Children.Add(page1NumDown);
|
||||
|
||||
_page2 = new ContainerUIElement3D();
|
||||
var page2NumUp = CreateNumber(num1, true, out _page2TextUp);
|
||||
var page2NumDown = CreateNumber(Number, false, out _page2TextDown);
|
||||
_page2.Children.Add(page2NumUp);
|
||||
_page2.Children.Add(page2NumDown);
|
||||
|
||||
_page3 = new ContainerUIElement3D();
|
||||
var page3NumUp = CreateNumber(Number, true, out _page3TextUp);
|
||||
_page3.Children.Add(page3NumUp);
|
||||
var transform3D = new RotateTransform3D(new AxisAngleRotation3D
|
||||
{
|
||||
Angle = 180,
|
||||
Axis = new Vector3D(1, 0, 0)
|
||||
});
|
||||
_page3.Transform = transform3D;
|
||||
|
||||
_content = new ContainerUIElement3D
|
||||
{
|
||||
Children =
|
||||
{
|
||||
_page1,
|
||||
_page2,
|
||||
_page3
|
||||
}
|
||||
};
|
||||
Children.Add(_content);
|
||||
}
|
||||
|
||||
private bool CheckNull() => _page1TextDown != null && _page2TextUp != null && _page2TextDown != null && _page3TextUp != null;
|
||||
|
||||
private void OnNumberChanged()
|
||||
{
|
||||
if (!CheckNull()) return;
|
||||
|
||||
InitNewNumber();
|
||||
|
||||
if (_isAnimating)
|
||||
{
|
||||
_isAnimating = false;
|
||||
UpdateNumber();
|
||||
return;
|
||||
}
|
||||
|
||||
_isAnimating = true;
|
||||
_pageRotation3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, _animation);
|
||||
}
|
||||
|
||||
private void InitNewNumber()
|
||||
{
|
||||
var num1 = Number.ToString();
|
||||
_page1TextDown.Text = num1;
|
||||
_page2TextUp.Text = num1;
|
||||
}
|
||||
|
||||
private void UpdateNumber()
|
||||
{
|
||||
_pageRotation3D.BeginAnimation(AxisAngleRotation3D.AngleProperty, null);
|
||||
_pageRotation3D.Angle = 0;
|
||||
|
||||
var num = Number.ToString();
|
||||
_page2TextDown.Text = num;
|
||||
_page3TextUp.Text = num;
|
||||
|
||||
_isAnimating = false;
|
||||
}
|
||||
|
||||
private Viewport2DVisual3D CreateNumber(int num, bool isUp, out TextBlock textBlock)
|
||||
{
|
||||
int flag;
|
||||
var rotateTransform = new RotateTransform();
|
||||
|
||||
if (isUp)
|
||||
{
|
||||
flag = -1;
|
||||
rotateTransform.Angle = 180;
|
||||
}
|
||||
else
|
||||
{
|
||||
flag = 1;
|
||||
}
|
||||
var halfWidth = ActualWidth / 2;
|
||||
var quarterWidth = ActualWidth / 4;
|
||||
var quarterHeight = ActualHeight / 4;
|
||||
|
||||
var meMaterial = new DiffuseMaterial();
|
||||
Viewport2DVisual3D.SetIsVisualHostMaterial(meMaterial, true);
|
||||
|
||||
textBlock = new TextBlock
|
||||
{
|
||||
RenderTransformOrigin = new Point(0.5, 0.5),
|
||||
Foreground = Foreground,
|
||||
FontSize = FontSize,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
TextAlignment = TextAlignment.Center,
|
||||
Text = num.ToString(),
|
||||
RenderTransform = rotateTransform,
|
||||
Margin = new Thickness(0, 0, 0, -quarterHeight),
|
||||
FontFamily = new FontFamily("Consolas")
|
||||
};
|
||||
|
||||
var border = new Border
|
||||
{
|
||||
ClipToBounds = true,
|
||||
CornerRadius = new CornerRadius(CornerRadius.TopLeft, CornerRadius.TopRight, 0, 0),
|
||||
Background = Background,
|
||||
Width = halfWidth,
|
||||
Height = quarterHeight,
|
||||
Child = textBlock
|
||||
};
|
||||
|
||||
var positions = new Point3DCollection
|
||||
{
|
||||
new Point3D(-quarterWidth * flag, quarterHeight, 0),
|
||||
new Point3D(-quarterWidth * flag, 0, 0),
|
||||
new Point3D(quarterWidth * flag, 0, 0),
|
||||
new Point3D(quarterWidth * flag, quarterHeight, 0)
|
||||
};
|
||||
|
||||
var triangleIndices = new Int32Collection
|
||||
{
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
0,
|
||||
2,
|
||||
3
|
||||
};
|
||||
|
||||
var textureCoordinates = new PointCollection
|
||||
{
|
||||
new Point(0, 0),
|
||||
new Point(0, 1),
|
||||
new Point(1, 1),
|
||||
new Point(1, 0)
|
||||
};
|
||||
|
||||
var geometry3D = new MeshGeometry3D
|
||||
{
|
||||
Positions = positions,
|
||||
TriangleIndices = triangleIndices,
|
||||
TextureCoordinates = textureCoordinates
|
||||
};
|
||||
|
||||
var child = new Viewport2DVisual3D
|
||||
{
|
||||
Geometry = geometry3D,
|
||||
Visual = border,
|
||||
Material = meMaterial
|
||||
};
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 作为刻度使用的文字块
|
||||
/// </summary>
|
||||
internal class SpeTextBlock : TextBlock
|
||||
{
|
||||
public double X { get; set; }
|
||||
|
||||
public SpeTextBlock() => Width = 60;
|
||||
|
||||
public SpeTextBlock(double x) : this()
|
||||
{
|
||||
X = x;
|
||||
Canvas.SetLeft(this, X);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间
|
||||
/// </summary>
|
||||
private DateTime _time;
|
||||
|
||||
/// <summary>
|
||||
/// 时间
|
||||
/// </summary>
|
||||
public DateTime Time
|
||||
{
|
||||
get => _time;
|
||||
set
|
||||
{
|
||||
_time = value;
|
||||
Text = $"{value.ToString(TimeFormat)}\r\n|";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间格式
|
||||
/// </summary>
|
||||
public string TimeFormat { get; set; } = "HH:mm";
|
||||
|
||||
/// <summary>
|
||||
/// 横向移动
|
||||
/// </summary>
|
||||
/// <param name="offsetX"></param>
|
||||
public void MoveX(double offsetX) => Canvas.SetLeft(this, X + offsetX);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,475 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 时间条
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementBorderTop, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementTextBlockMove, Type = typeof(TextBlock))]
|
||||
[TemplatePart(Name = ElementTextBlockSelected, Type = typeof(TextBlock))]
|
||||
[TemplatePart(Name = ElementCanvasSpe, Type = typeof(Canvas))]
|
||||
public class TimeBar : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementBorderTop = "PART_BorderTop";
|
||||
|
||||
private const string ElementTextBlockMove = "PART_TextBlockMove";
|
||||
|
||||
private const string ElementTextBlockSelected = "PART_TextBlockSelected";
|
||||
|
||||
private const string ElementCanvasSpe = "PART_CanvasSpe";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Border _borderTop;
|
||||
|
||||
private TextBlock _textBlockMove;
|
||||
|
||||
private TextBlock _textBlockSelected;
|
||||
|
||||
private Canvas _canvasSpe;
|
||||
|
||||
#endregion Data
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示刻度字符串
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowSpeStrProperty = DependencyProperty.Register(
|
||||
"ShowSpeStr", typeof(bool), typeof(TimeBar), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public static readonly DependencyProperty TimeFormatProperty = DependencyProperty.Register(
|
||||
"TimeFormat", typeof(string), typeof(TimeBar), new PropertyMetadata("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
public string TimeFormat
|
||||
{
|
||||
get => (string) GetValue(TimeFormatProperty);
|
||||
set => SetValue(TimeFormatProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刻度字符串
|
||||
/// </summary>
|
||||
internal static readonly DependencyProperty SpeStrProperty = DependencyProperty.Register(
|
||||
"SpeStr", typeof(string), typeof(TimeBar), new PropertyMetadata(Properties.Langs.Lang.Interval1h));
|
||||
|
||||
/// <summary>
|
||||
/// 选中时间
|
||||
/// </summary>
|
||||
internal static readonly DependencyProperty SelectedTimeProperty = DependencyProperty.Register(
|
||||
"SelectedTime", typeof(DateTime), typeof(TimeBar), new PropertyMetadata(default(DateTime), OnSelectedTimeChanged));
|
||||
|
||||
private static void OnSelectedTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is TimeBar timeBar && timeBar._textBlockSelected != null)
|
||||
{
|
||||
timeBar._textBlockSelected.Text = ((DateTime) e.NewValue).ToString(timeBar.TimeFormat);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间改变事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent TimeChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("TimeChanged", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<DateTime>>), typeof(TimeBar));
|
||||
|
||||
/// <summary>
|
||||
/// 刻度集合
|
||||
/// </summary>
|
||||
private readonly List<SpeTextBlock> _speBlockList = new List<SpeTextBlock>();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化时时间
|
||||
/// </summary>
|
||||
private readonly DateTime _starTime;
|
||||
|
||||
/// <summary>
|
||||
/// 时间段集合
|
||||
/// </summary>
|
||||
private readonly List<int> _timeSpeList = new List<int>
|
||||
{
|
||||
7200000,
|
||||
3600000,
|
||||
1800000,
|
||||
600000,
|
||||
300000,
|
||||
60000,
|
||||
30000
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 顶部border是否被按下
|
||||
/// </summary>
|
||||
private bool _borderTopIsMouseLeftButtonDown;
|
||||
|
||||
/// <summary>
|
||||
/// 控件是否处于拖动中
|
||||
/// </summary>
|
||||
private bool _isDragging;
|
||||
|
||||
/// <summary>
|
||||
/// 刻度单项宽度
|
||||
/// </summary>
|
||||
private double _itemWidth;
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标按下拖动时选中的时间
|
||||
/// </summary>
|
||||
private DateTime _mouseDownTime;
|
||||
|
||||
/// <summary>
|
||||
/// 显示的刻度数目
|
||||
/// </summary>
|
||||
private int _speCount = 13;
|
||||
|
||||
/// <summary>
|
||||
/// 刻度区间编号
|
||||
/// </summary>
|
||||
private int _speIndex = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 刻度单次偏移
|
||||
/// </summary>
|
||||
private double _tempOffsetX;
|
||||
|
||||
/// <summary>
|
||||
/// 刻度总偏移
|
||||
/// </summary>
|
||||
private double _totalOffsetX;
|
||||
|
||||
private readonly bool _isLoaded;
|
||||
|
||||
public TimeBar()
|
||||
{
|
||||
_starTime = DateTime.Now;
|
||||
SelectedTime = new DateTime(_starTime.Year, _starTime.Month, _starTime.Day, 0, 0, 0);
|
||||
_starTime = SelectedTime;
|
||||
_isLoaded = true;
|
||||
SizeChanged += TimeBar_OnSizeChanged;
|
||||
MouseMove += TimeBar_OnMouseMove;
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (_borderTop != null)
|
||||
{
|
||||
_borderTop.MouseLeftButtonDown -= BorderTop_OnMouseLeftButtonDown;
|
||||
_borderTop.PreviewMouseLeftButtonUp -= BorderTop_OnPreviewMouseLeftButtonUp;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_borderTop = GetTemplateChild(ElementBorderTop) as Border;
|
||||
_textBlockMove = GetTemplateChild(ElementTextBlockMove) as TextBlock;
|
||||
_textBlockSelected = GetTemplateChild(ElementTextBlockSelected) as TextBlock;
|
||||
_canvasSpe = GetTemplateChild(ElementCanvasSpe) as Canvas;
|
||||
|
||||
CheckNull();
|
||||
_borderTop.MouseLeftButtonDown += BorderTop_OnMouseLeftButtonDown;
|
||||
_borderTop.PreviewMouseLeftButtonUp += BorderTop_OnPreviewMouseLeftButtonUp;
|
||||
var behavior = new MouseDragElementBehaviorEx
|
||||
{
|
||||
LockY = true,
|
||||
};
|
||||
behavior.DragBegun += DragElementBehavior_OnDragBegun;
|
||||
behavior.Dragging += MouseDragElementBehavior_OnDragging;
|
||||
behavior.DragFinished += MouseDragElementBehavior_OnDragFinished;
|
||||
var collection = Interaction.GetBehaviors(_borderTop);
|
||||
collection.Add(behavior);
|
||||
|
||||
if (_isLoaded)
|
||||
{
|
||||
TimeBar_OnSizeChanged(null, null);
|
||||
}
|
||||
_textBlockSelected.Text = SelectedTime.ToString(TimeFormat);
|
||||
}
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
if (_borderTop == null || _textBlockMove == null|| _textBlockSelected == null || _canvasSpe == null) throw new Exception();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示刻度字符串
|
||||
/// </summary>
|
||||
public bool ShowSpeStr
|
||||
{
|
||||
get => (bool)GetValue(ShowSpeStrProperty);
|
||||
set => SetValue(ShowSpeStrProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刻度字符串
|
||||
/// </summary>
|
||||
internal string SpeStr
|
||||
{
|
||||
get => (string)GetValue(SpeStrProperty);
|
||||
set => SetValue(SpeStrProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 选中时间
|
||||
/// </summary>
|
||||
internal DateTime SelectedTime
|
||||
{
|
||||
get => (DateTime)GetValue(SelectedTimeProperty);
|
||||
set => SetValue(SelectedTimeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刻度区间编号
|
||||
/// </summary>
|
||||
public int SpeIndex
|
||||
{
|
||||
get => _speIndex;
|
||||
private set
|
||||
{
|
||||
if (_speIndex == value) return;
|
||||
|
||||
if (value < 0)
|
||||
{
|
||||
SpeStr = Properties.Langs.Lang.Interval2h;
|
||||
_speIndex = 0;
|
||||
return;
|
||||
}
|
||||
if (value > 6)
|
||||
{
|
||||
SpeStr = Properties.Langs.Lang.Interval30s;
|
||||
_speIndex = 6;
|
||||
return;
|
||||
}
|
||||
SetSpeTimeFormat("HH:mm");
|
||||
switch (value)
|
||||
{
|
||||
case 0:
|
||||
SpeStr = Properties.Langs.Lang.Interval2h;
|
||||
break;
|
||||
case 1:
|
||||
SpeStr = Properties.Langs.Lang.Interval1h;
|
||||
break;
|
||||
case 2:
|
||||
SpeStr = Properties.Langs.Lang.Interval30m;
|
||||
break;
|
||||
case 3:
|
||||
SpeStr = Properties.Langs.Lang.Interval10m;
|
||||
break;
|
||||
case 4:
|
||||
SpeStr = Properties.Langs.Lang.Interval5m;
|
||||
break;
|
||||
case 5:
|
||||
SpeStr = Properties.Langs.Lang.Interval1m;
|
||||
break;
|
||||
case 6:
|
||||
SetSpeTimeFormat("HH:mm:ss");
|
||||
SpeStr = Properties.Langs.Lang.Interval30s;
|
||||
break;
|
||||
}
|
||||
_speIndex = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间改变事件
|
||||
/// </summary>
|
||||
public event EventHandler<FunctionEventArgs<DateTime>> TimeChanged
|
||||
{
|
||||
add => AddHandler(TimeChangedEvent, value);
|
||||
remove => RemoveHandler(TimeChangedEvent, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置刻度时间格式
|
||||
/// </summary>
|
||||
/// <param name="format"></param>
|
||||
private void SetSpeTimeFormat(string format)
|
||||
{
|
||||
foreach (var item in _speBlockList)
|
||||
item.TimeFormat = format;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置显示的时间
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
public void SetShowTime(DateTime time)
|
||||
{
|
||||
if (_isDragging || _borderTopIsMouseLeftButtonDown) return;
|
||||
|
||||
SelectedTime = time;
|
||||
_totalOffsetX = (_starTime - SelectedTime).TotalMilliseconds / _timeSpeList[SpeIndex] * _itemWidth;
|
||||
Update();
|
||||
TimeBar_OnMouseMove(null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
var rest = (_totalOffsetX + _tempOffsetX) % _itemWidth;
|
||||
for (var i = 0; i < _speCount; i++)
|
||||
{
|
||||
var item = _speBlockList[i];
|
||||
item.MoveX(rest + (_itemWidth - item.Width) / 2);
|
||||
}
|
||||
var sub = rest <= 0 ? _speCount / 2 : _speCount / 2 - 1;
|
||||
|
||||
for (var i = 0; i < _speCount; i++)
|
||||
_speBlockList[i].Time = TimeConvert(SelectedTime).AddMilliseconds((i - sub) * _timeSpeList[_speIndex]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 时间转换
|
||||
/// </summary>
|
||||
/// <param name="time"></param>
|
||||
/// <returns></returns>
|
||||
private DateTime TimeConvert(DateTime time)
|
||||
{
|
||||
switch (_speIndex)
|
||||
{
|
||||
case 0:
|
||||
return new DateTime(time.Year, time.Month, time.Day, time.Hour / 2 * 2, 0, 0);
|
||||
case 1:
|
||||
return new DateTime(time.Year, time.Month, time.Day, time.Hour, 0, 0);
|
||||
case 2:
|
||||
return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute / 30 * 30, 0);
|
||||
case 3:
|
||||
return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute / 10 * 10, 0);
|
||||
case 4:
|
||||
return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute / 5 * 5, 0);
|
||||
case 5:
|
||||
return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, 0);
|
||||
case 6:
|
||||
return new DateTime(time.Year, time.Month, time.Day, time.Hour, time.Minute, time.Second / 30 * 30);
|
||||
default:
|
||||
return time;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标滚轮滚动时改变刻度区间
|
||||
/// </summary>
|
||||
/// <param name="e"></param>
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
base.OnMouseWheel(e);
|
||||
if (Mouse.LeftButton == MouseButtonState.Pressed) return;
|
||||
SpeIndex += e.Delta > 0 ? 1 : -1;
|
||||
_totalOffsetX = (_starTime - SelectedTime).TotalMilliseconds / _timeSpeList[SpeIndex] * _itemWidth;
|
||||
Update();
|
||||
TimeBar_OnMouseMove(null, null);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void MouseDragElementBehavior_OnDragging(object sender, MouseEventArgs e)
|
||||
{
|
||||
_isDragging = true;
|
||||
_tempOffsetX = _borderTop.RenderTransform.Value.OffsetX;
|
||||
|
||||
SelectedTime = _mouseDownTime -
|
||||
TimeSpan.FromMilliseconds(_tempOffsetX / _itemWidth * _timeSpeList[_speIndex]);
|
||||
Update();
|
||||
_borderTopIsMouseLeftButtonDown = false;
|
||||
}
|
||||
|
||||
private void MouseDragElementBehavior_OnDragFinished(object sender, MouseEventArgs e)
|
||||
{
|
||||
_tempOffsetX = 0;
|
||||
_totalOffsetX = (_totalOffsetX + _borderTop.RenderTransform.Value.OffsetX) % ActualWidth;
|
||||
_borderTop.RenderTransform = new TranslateTransform();
|
||||
RaiseEvent(new FunctionEventArgs<DateTime>(TimeChangedEvent, this)
|
||||
{
|
||||
Info = SelectedTime
|
||||
});
|
||||
_isDragging = false;
|
||||
}
|
||||
|
||||
private void DragElementBehavior_OnDragBegun(object sender, MouseEventArgs e) => _mouseDownTime = SelectedTime;
|
||||
|
||||
private void TimeBar_OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
_speBlockList.Clear();
|
||||
_canvasSpe.Children.Clear();
|
||||
|
||||
_speCount = (int)(ActualWidth / 800 * 9) | 1;
|
||||
|
||||
var itemWidthOld = _itemWidth;
|
||||
_itemWidth = ActualWidth / _speCount;
|
||||
_totalOffsetX = _itemWidth / itemWidthOld * _totalOffsetX % ActualWidth;
|
||||
if (double.IsNaN(_totalOffsetX))
|
||||
_totalOffsetX = 0;
|
||||
|
||||
var rest = (_totalOffsetX + _tempOffsetX) % _itemWidth;
|
||||
var sub = rest <= 0 || double.IsNaN(rest) ? _speCount / 2 : _speCount / 2 - 1;
|
||||
for (var i = 0; i < _speCount; i++)
|
||||
{
|
||||
var block = new SpeTextBlock
|
||||
{
|
||||
Time = TimeConvert(SelectedTime).AddMilliseconds((i - sub) * _timeSpeList[_speIndex]),
|
||||
TextAlignment = TextAlignment.Center,
|
||||
TimeFormat = "HH:mm"
|
||||
};
|
||||
_speBlockList.Add(block);
|
||||
_canvasSpe.Children.Add(block);
|
||||
}
|
||||
if (_speIndex == 6)
|
||||
SetSpeTimeFormat("HH:mm:ss");
|
||||
ShowSpeStr = ActualWidth > 320;
|
||||
for (var i = 0; i < _speCount; i++)
|
||||
{
|
||||
var item = _speBlockList[i];
|
||||
item.X = _itemWidth * i;
|
||||
item.MoveX((_itemWidth - item.Width) / 2);
|
||||
}
|
||||
|
||||
Update();
|
||||
TimeBar_OnMouseMove(null, null);
|
||||
}
|
||||
|
||||
private void TimeBar_OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
var p = Mouse.GetPosition(this);
|
||||
var mlliseconds = (p.X - ActualWidth / 2) / _itemWidth * _timeSpeList[_speIndex];
|
||||
if (double.IsInfinity(mlliseconds)) return;
|
||||
_textBlockMove.Text = mlliseconds < 0
|
||||
? (SelectedTime - TimeSpan.FromMilliseconds(-mlliseconds)).ToString(TimeFormat)
|
||||
: (SelectedTime + TimeSpan.FromMilliseconds(mlliseconds)).ToString(TimeFormat);
|
||||
_textBlockMove.Margin = new Thickness(p.X - _textBlockMove.ActualWidth / 2, 2, 0, 0);
|
||||
}
|
||||
|
||||
private void BorderTop_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) => _borderTopIsMouseLeftButtonDown = true;
|
||||
|
||||
private void BorderTop_OnPreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (_borderTopIsMouseLeftButtonDown)
|
||||
{
|
||||
_borderTopIsMouseLeftButtonDown = false;
|
||||
|
||||
var p = Mouse.GetPosition(this);
|
||||
_tempOffsetX = ActualWidth / 2 - p.X;
|
||||
SelectedTime -= TimeSpan.FromMilliseconds(_tempOffsetX / _itemWidth * _timeSpeList[_speIndex]);
|
||||
Update();
|
||||
_totalOffsetX = (_totalOffsetX + _tempOffsetX) % ActualWidth;
|
||||
_tempOffsetX = 0;
|
||||
TimeBar_OnMouseMove(null, null);
|
||||
RaiseEvent(new FunctionEventArgs<DateTime>(TimeChangedEvent, this)
|
||||
{
|
||||
Info = SelectedTime
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class BlurWindow : Window
|
||||
{
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
EnableBlur(this);
|
||||
}
|
||||
|
||||
public static SystemVersionInfo SystemVersionInfo { get; set; }
|
||||
|
||||
internal static void EnableBlur(Window window)
|
||||
{
|
||||
var accentPolicy = new ExternDllHelper.ACCENTPOLICY();
|
||||
var accentPolicySize = Marshal.SizeOf(accentPolicy);
|
||||
|
||||
if (SystemVersionInfo >= SystemVersionInfo.Windows10_1809)
|
||||
{
|
||||
accentPolicy.AccentState = ExternDllHelper.ACCENTSTATE.ACCENT_ENABLE_ACRYLICBLURBEHIND;
|
||||
}
|
||||
else if (SystemVersionInfo >= SystemVersionInfo.Windows10)
|
||||
{
|
||||
accentPolicy.AccentState = ExternDllHelper.ACCENTSTATE.ACCENT_ENABLE_BLURBEHIND;
|
||||
}
|
||||
else
|
||||
{
|
||||
var colorValue = ResourceHelper.GetResource<uint>(ResourceToken.BlurGradientValue);
|
||||
var color = ColorHelper.ToColor(colorValue);
|
||||
color = Color.FromRgb(color.R, color.G, color.B);
|
||||
window.Background = new SolidColorBrush(color);
|
||||
return;
|
||||
}
|
||||
|
||||
accentPolicy.AccentFlags = 2;
|
||||
accentPolicy.GradientColor = ResourceHelper.GetResource<uint>(ResourceToken.BlurGradientValue);
|
||||
|
||||
var accentPtr = Marshal.AllocHGlobal(accentPolicySize);
|
||||
Marshal.StructureToPtr(accentPolicy, accentPtr, false);
|
||||
|
||||
var data = new ExternDllHelper.WINCOMPATTRDATA
|
||||
{
|
||||
Attribute = ExternDllHelper.WINDOWCOMPOSITIONATTRIB.WCA_ACCENT_POLICY,
|
||||
DataSize = accentPolicySize,
|
||||
Data = accentPtr
|
||||
};
|
||||
|
||||
ExternDllHelper.SetWindowCompositionAttribute(new WindowInteropHelper(window).Handle, ref data);
|
||||
|
||||
Marshal.FreeHGlobal(accentPtr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media.Imaging;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 图片浏览器
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementGridTop, Type = typeof(Grid))]
|
||||
[TemplatePart(Name = ElementImageViewer, Type = typeof(ImageViewer))]
|
||||
public class ImageBrowser : Window
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementGridTop = "PART_GridTop";
|
||||
|
||||
private const string ElementImageViewer = "PART_ImageViewer";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Grid _gridTop;
|
||||
|
||||
private ImageViewer _imageViewer;
|
||||
|
||||
#endregion Data
|
||||
|
||||
static ImageBrowser()
|
||||
{
|
||||
IsFullScreenProperty.AddOwner(typeof(ImageBrowser), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
}
|
||||
|
||||
public ImageBrowser()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Close, ButtonClose_OnClick));
|
||||
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
WindowStyle = WindowStyle.None;
|
||||
AllowsTransparency = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带一个图片Uri的构造函数
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
public ImageBrowser(Uri uri) : this()
|
||||
{
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_imageViewer.ImageSource = BitmapFrame.Create(uri);
|
||||
_imageViewer.ImgPath = uri.AbsolutePath;
|
||||
if (File.Exists(_imageViewer.ImgPath))
|
||||
{
|
||||
var info = new FileInfo(_imageViewer.ImgPath);
|
||||
_imageViewer.ImgSize = info.Length;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
MessageBox.Show(Properties.Langs.Lang.ErrorImgPath);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 带一个图片路径的构造函数
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
public ImageBrowser(string path) : this(new Uri(path))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (_gridTop != null)
|
||||
{
|
||||
_gridTop.MouseLeftButtonDown -= GridTop_OnMouseLeftButtonDown;
|
||||
}
|
||||
|
||||
if (_imageViewer != null)
|
||||
{
|
||||
_imageViewer.MouseLeftButtonDown -= ImageViewer_MouseLeftButtonDown;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_gridTop = GetTemplateChild(ElementGridTop) as Grid;
|
||||
_imageViewer = GetTemplateChild(ElementImageViewer) as ImageViewer;
|
||||
|
||||
if (_gridTop != null)
|
||||
{
|
||||
_gridTop.MouseLeftButtonDown += GridTop_OnMouseLeftButtonDown;
|
||||
}
|
||||
|
||||
if (_imageViewer != null)
|
||||
{
|
||||
_imageViewer.MouseLeftButtonDown += ImageViewer_MouseLeftButtonDown;
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonClose_OnClick(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
private void GridTop_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
private void ImageViewer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed && !(_imageViewer.ImageWidth > ActualWidth || _imageViewer.ImageHeight > ActualHeight))
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息框
|
||||
/// </summary>
|
||||
public sealed class MessageBox : Window
|
||||
{
|
||||
private MessageBoxResult _messageBoxResult = MessageBoxResult.Cancel;
|
||||
|
||||
private bool _showOk;
|
||||
|
||||
private bool _showCancel;
|
||||
|
||||
private bool _showYes;
|
||||
|
||||
private bool _showNo;
|
||||
|
||||
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
|
||||
"Message", typeof(string), typeof(MessageBox), new PropertyMetadata(default(string)));
|
||||
|
||||
public string Message
|
||||
{
|
||||
get => (string) GetValue(MessageProperty);
|
||||
set => SetValue(MessageProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ImageProperty = DependencyProperty.Register(
|
||||
"Image", typeof(Geometry), typeof(MessageBox), new PropertyMetadata(default(Geometry)));
|
||||
|
||||
public Geometry Image
|
||||
{
|
||||
get => (Geometry) GetValue(ImageProperty);
|
||||
set => SetValue(ImageProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ImageBrushProperty = DependencyProperty.Register(
|
||||
"ImageBrush", typeof(Brush), typeof(MessageBox), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public Brush ImageBrush
|
||||
{
|
||||
get => (Brush) GetValue(ImageBrushProperty);
|
||||
set => SetValue(ImageBrushProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowImageProperty = DependencyProperty.Register(
|
||||
"ShowImage", typeof(bool), typeof(MessageBox), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowImage
|
||||
{
|
||||
get => (bool) GetValue(ShowImageProperty);
|
||||
set => SetValue(ShowImageProperty, value);
|
||||
}
|
||||
|
||||
private MessageBox()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Confirm, (s, e) =>
|
||||
{
|
||||
_messageBoxResult = MessageBoxResult.OK;
|
||||
Close();
|
||||
}, (s, e) => e.CanExecute = _showOk));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Cancel, (s, e) =>
|
||||
{
|
||||
_messageBoxResult = MessageBoxResult.Cancel;
|
||||
Close();
|
||||
}, (s, e) => e.CanExecute = _showCancel));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Yes, (s, e) =>
|
||||
{
|
||||
_messageBoxResult = MessageBoxResult.Yes;
|
||||
Close();
|
||||
}, (s, e) => e.CanExecute = _showYes));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.No, (s, e) =>
|
||||
{
|
||||
_messageBoxResult = MessageBoxResult.No;
|
||||
Close();
|
||||
}, (s, e) => e.CanExecute = _showNo));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText"></param>
|
||||
/// <param name="caption"></param>
|
||||
public static MessageBoxResult Success(string messageBoxText, string caption = null)
|
||||
{
|
||||
var messageBox = CreateMessageBox(null, messageBoxText, caption, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.OK);
|
||||
SetButtonStatus(messageBox, MessageBoxButton.OK);
|
||||
messageBox.ShowImage = true;
|
||||
messageBox.Image = ResourceHelper.GetResource<Geometry>(ResourceToken.SuccessGeometry);
|
||||
messageBox.ImageBrush = ResourceHelper.GetResource<Brush>(ResourceToken.SuccessBrush);
|
||||
messageBox.ShowDialog();
|
||||
|
||||
return messageBox._messageBoxResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText"></param>
|
||||
/// <param name="caption"></param>
|
||||
public static MessageBoxResult Info(string messageBoxText, string caption = null)
|
||||
{
|
||||
var messageBox = CreateMessageBox(null, messageBoxText, caption, MessageBoxButton.OK, MessageBoxImage.Information, MessageBoxResult.OK);
|
||||
SetButtonStatus(messageBox, MessageBoxButton.OK);
|
||||
SetImage(messageBox, MessageBoxImage.Information);
|
||||
messageBox.ShowDialog();
|
||||
|
||||
return messageBox._messageBoxResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText"></param>
|
||||
/// <param name="caption"></param>
|
||||
public static MessageBoxResult Warning(string messageBoxText, string caption = null)
|
||||
{
|
||||
var messageBox = CreateMessageBox(null, messageBoxText, caption, MessageBoxButton.OK, MessageBoxImage.Warning, MessageBoxResult.OK);
|
||||
SetButtonStatus(messageBox, MessageBoxButton.OK);
|
||||
SetImage(messageBox, MessageBoxImage.Warning);
|
||||
messageBox.ShowDialog();
|
||||
|
||||
return messageBox._messageBoxResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText"></param>
|
||||
/// <param name="caption"></param>
|
||||
public static MessageBoxResult Error(string messageBoxText, string caption = null)
|
||||
{
|
||||
var messageBox = CreateMessageBox(null, messageBoxText, caption, MessageBoxButton.OK, MessageBoxImage.Error, MessageBoxResult.OK);
|
||||
SetButtonStatus(messageBox, MessageBoxButton.OK);
|
||||
SetImage(messageBox, MessageBoxImage.Error);
|
||||
messageBox.ShowDialog();
|
||||
|
||||
return messageBox._messageBoxResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 严重
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText"></param>
|
||||
/// <param name="caption"></param>
|
||||
public static MessageBoxResult Fatal(string messageBoxText, string caption = null)
|
||||
{
|
||||
var messageBox = CreateMessageBox(null, messageBoxText, caption, MessageBoxButton.OK, MessageBoxImage.None, MessageBoxResult.OK);
|
||||
SetButtonStatus(messageBox, MessageBoxButton.OK);
|
||||
messageBox.ShowImage = true;
|
||||
messageBox.Image = ResourceHelper.GetResource<Geometry>(ResourceToken.FatalGeometry);
|
||||
messageBox.ImageBrush = ResourceHelper.GetResource<Brush>(ResourceToken.PrimaryTextBrush);
|
||||
messageBox.ShowDialog();
|
||||
|
||||
return messageBox._messageBoxResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 询问
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText"></param>
|
||||
/// <param name="caption"></param>
|
||||
public static MessageBoxResult Ask(string messageBoxText, string caption = null)
|
||||
{
|
||||
var messageBox = CreateMessageBox(null, messageBoxText, caption, MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.Cancel);
|
||||
SetButtonStatus(messageBox, MessageBoxButton.OKCancel);
|
||||
SetImage(messageBox, MessageBoxImage.Question);
|
||||
messageBox.ShowDialog();
|
||||
|
||||
return messageBox._messageBoxResult;
|
||||
}
|
||||
|
||||
public static MessageBoxResult CustomShow(string messageBoxText, string caption = null, MessageBoxButton button = MessageBoxButton.OK, string iconKey = "", string iconBrushKey = "", MessageBoxResult defaultResult = MessageBoxResult.None)
|
||||
{
|
||||
var messageBox = CreateMessageBox(null, messageBoxText, caption, button, MessageBoxImage.None, defaultResult);
|
||||
messageBox.ShowImage = true;
|
||||
messageBox.Image = ResourceHelper.GetResource<Geometry>(iconKey);
|
||||
messageBox.ImageBrush = ResourceHelper.GetResource<Brush>(iconBrushKey);
|
||||
messageBox.ShowDialog();
|
||||
|
||||
return messageBox._messageBoxResult;
|
||||
}
|
||||
|
||||
public static MessageBoxResult Show(string messageBoxText, string caption = null, MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.None)
|
||||
{
|
||||
return Show(null, messageBoxText, caption, button, icon, defaultResult);
|
||||
}
|
||||
|
||||
public static MessageBoxResult Show(System.Windows.Window owner, string messageBoxText, string caption = null, MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None, MessageBoxResult defaultResult = MessageBoxResult.None)
|
||||
{
|
||||
var messageBox = CreateMessageBox(owner, messageBoxText, caption, button, icon, defaultResult);
|
||||
SetButtonStatus(messageBox, button);
|
||||
SetImage(messageBox, icon);
|
||||
messageBox.ShowDialog();
|
||||
|
||||
return messageBox._messageBoxResult;
|
||||
}
|
||||
|
||||
private static MessageBox CreateMessageBox(
|
||||
System.Windows.Window owner,
|
||||
string messageBoxText,
|
||||
string caption,
|
||||
MessageBoxButton button,
|
||||
MessageBoxImage icon,
|
||||
MessageBoxResult defaultResult)
|
||||
{
|
||||
if (!IsValidMessageBoxButton(button))
|
||||
{
|
||||
throw new InvalidEnumArgumentException(nameof(button), (int)button, typeof(MessageBoxButton));
|
||||
}
|
||||
if (!IsValidMessageBoxImage(icon))
|
||||
{
|
||||
throw new InvalidEnumArgumentException(nameof(icon), (int)icon, typeof(MessageBoxImage));
|
||||
}
|
||||
if (!IsValidMessageBoxResult(defaultResult))
|
||||
{
|
||||
throw new InvalidEnumArgumentException(nameof(defaultResult), (int)defaultResult, typeof(MessageBoxResult));
|
||||
}
|
||||
|
||||
var ownerWindow = owner ?? Application.Current.Windows.OfType<Window>().SingleOrDefault(x => x.IsActive);
|
||||
var ownerIsNull = ownerWindow is null;
|
||||
|
||||
return new MessageBox
|
||||
{
|
||||
Message = messageBoxText,
|
||||
Owner = ownerWindow,
|
||||
WindowStartupLocation = ownerIsNull ? WindowStartupLocation.CenterScreen : WindowStartupLocation.CenterOwner,
|
||||
ShowTitle = true,
|
||||
Title = caption,
|
||||
Topmost = ownerIsNull,
|
||||
_messageBoxResult = defaultResult
|
||||
};
|
||||
}
|
||||
|
||||
private static void SetButtonStatus(MessageBox messageBox, MessageBoxButton messageBoxButton)
|
||||
{
|
||||
switch (messageBoxButton)
|
||||
{
|
||||
case MessageBoxButton.OK:
|
||||
messageBox._showOk = true;
|
||||
break;
|
||||
case MessageBoxButton.OKCancel:
|
||||
messageBox._showOk = true;
|
||||
messageBox._showCancel = true;
|
||||
break;
|
||||
case MessageBoxButton.YesNo:
|
||||
messageBox._showYes = true;
|
||||
messageBox._showNo = true;
|
||||
break;
|
||||
case MessageBoxButton.YesNoCancel:
|
||||
messageBox._showYes = true;
|
||||
messageBox._showNo = true;
|
||||
messageBox._showCancel = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetImage(MessageBox messageBox, MessageBoxImage messageBoxImage)
|
||||
{
|
||||
var iconKey = string.Empty;
|
||||
var iconBrushKey = string.Empty;
|
||||
|
||||
switch (messageBoxImage)
|
||||
{
|
||||
case MessageBoxImage.Error:
|
||||
iconKey = ResourceToken.ErrorGeometry;
|
||||
iconBrushKey = ResourceToken.DangerBrush;
|
||||
break;
|
||||
case MessageBoxImage.Question:
|
||||
iconKey = ResourceToken.AskGeometry;
|
||||
iconBrushKey = ResourceToken.AccentBrush;
|
||||
break;
|
||||
case MessageBoxImage.Warning:
|
||||
iconKey = ResourceToken.WarningGeometry;
|
||||
iconBrushKey = ResourceToken.WarningBrush;
|
||||
break;
|
||||
case MessageBoxImage.Information:
|
||||
iconKey = ResourceToken.InfoGeometry;
|
||||
iconBrushKey = ResourceToken.InfoBrush;
|
||||
break;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(iconKey)) return;
|
||||
messageBox.ShowImage = true;
|
||||
messageBox.Image = ResourceHelper.GetResource<Geometry>(iconKey);
|
||||
messageBox.ImageBrush = ResourceHelper.GetResource<Brush>(iconBrushKey);
|
||||
}
|
||||
|
||||
private static bool IsValidMessageBoxButton(MessageBoxButton value)
|
||||
{
|
||||
return value == MessageBoxButton.OK
|
||||
|| value == MessageBoxButton.OKCancel
|
||||
|| value == MessageBoxButton.YesNo
|
||||
|| value == MessageBoxButton.YesNoCancel;
|
||||
}
|
||||
|
||||
private static bool IsValidMessageBoxImage(MessageBoxImage value)
|
||||
{
|
||||
return value == MessageBoxImage.Asterisk
|
||||
|| value == MessageBoxImage.Error
|
||||
|| value == MessageBoxImage.Exclamation
|
||||
|| value == MessageBoxImage.Hand
|
||||
|| value == MessageBoxImage.Information
|
||||
|| value == MessageBoxImage.None
|
||||
|| value == MessageBoxImage.Question
|
||||
|| value == MessageBoxImage.Stop
|
||||
|| value == MessageBoxImage.Warning;
|
||||
}
|
||||
|
||||
private static bool IsValidMessageBoxResult(MessageBoxResult value)
|
||||
{
|
||||
return value == MessageBoxResult.Cancel
|
||||
|| value == MessageBoxResult.No
|
||||
|| value == MessageBoxResult.None
|
||||
|| value == MessageBoxResult.OK
|
||||
|| value == MessageBoxResult.Yes;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 弹出窗口
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementMainBorder, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementTitleBlock, Type = typeof(TextBlock))]
|
||||
public class PopupWindow : System.Windows.Window
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementMainBorder = "PART_MainBorder";
|
||||
|
||||
private const string ElementTitleBlock = "PART_TitleBlock";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
private Border _mainBorder;
|
||||
|
||||
private TextBlock _titleBlock;
|
||||
|
||||
private bool _showBackground = true;
|
||||
|
||||
private FrameworkElement _targetElement;
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (_titleBlock != null)
|
||||
{
|
||||
_titleBlock.MouseLeftButtonDown -= TitleBlock_OnMouseLeftButtonDown;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_mainBorder = GetTemplateChild(ElementMainBorder) as Border;
|
||||
_titleBlock = GetTemplateChild(ElementTitleBlock) as TextBlock;
|
||||
|
||||
if (_titleBlock != null)
|
||||
{
|
||||
_titleBlock.MouseLeftButtonDown += TitleBlock_OnMouseLeftButtonDown;
|
||||
}
|
||||
|
||||
if (PopupElement != null)
|
||||
{
|
||||
_mainBorder.Child = PopupElement;
|
||||
}
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ContentStrProperty = DependencyProperty.Register(
|
||||
"ContentStr", typeof(string), typeof(PopupWindow), new PropertyMetadata(default(string)));
|
||||
|
||||
internal string ContentStr
|
||||
{
|
||||
get => (string) GetValue(ContentStrProperty);
|
||||
set => SetValue(ContentStrProperty, value);
|
||||
}
|
||||
|
||||
private bool IsDialog { get; set; }
|
||||
|
||||
public PopupWindow()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Close, CloseButton_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Confirm, ButtonOk_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Cancel, ButtonCancle_OnClick));
|
||||
|
||||
Closing += (sender, args) =>
|
||||
{
|
||||
if (!IsDialog)
|
||||
Owner?.Focus();
|
||||
};
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (!_showBackground)
|
||||
{
|
||||
var point = ArithmeticHelper.CalSafePoint(_targetElement, PopupElement, BorderThickness);
|
||||
Left = point.X;
|
||||
Top = point.Y;
|
||||
Opacity = 1;
|
||||
}
|
||||
};
|
||||
try
|
||||
{
|
||||
Owner = Application.Current.MainWindow;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
public PopupWindow(System.Windows.Window owner) : this() => Owner = owner;
|
||||
|
||||
private void CloseButton_OnClick(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
public FrameworkElement PopupElement { get; set; }
|
||||
|
||||
public static readonly DependencyProperty ShowTitleProperty = DependencyProperty.Register(
|
||||
"ShowTitle", typeof(bool), typeof(PopupWindow), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool ShowTitle
|
||||
{
|
||||
get => (bool) GetValue(ShowTitleProperty);
|
||||
set => SetValue(ShowTitleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowCancelProperty = DependencyProperty.Register(
|
||||
"ShowCancel", typeof(bool), typeof(PopupWindow), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowCancel
|
||||
{
|
||||
get => (bool)GetValue(ShowCancelProperty);
|
||||
set => SetValue(ShowCancelProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowBorderProperty = DependencyProperty.Register(
|
||||
"ShowBorder", typeof(bool), typeof(PopupWindow), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowBorder
|
||||
{
|
||||
get => (bool) GetValue(ShowBorderProperty);
|
||||
set => SetValue(ShowBorderProperty, value);
|
||||
}
|
||||
|
||||
private void TitleBlock_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
}
|
||||
|
||||
public void Show(FrameworkElement element, bool showBackground = true)
|
||||
{
|
||||
if (!showBackground)
|
||||
{
|
||||
Opacity = 0;
|
||||
AllowsTransparency = true;
|
||||
WindowStyle = WindowStyle.None;
|
||||
ShowTitle = false;
|
||||
MinWidth = 0;
|
||||
MinHeight = 0;
|
||||
}
|
||||
|
||||
_showBackground = showBackground;
|
||||
_targetElement = element;
|
||||
Show();
|
||||
}
|
||||
|
||||
public void ShowDialog(FrameworkElement element, bool showBackground = true)
|
||||
{
|
||||
if (!showBackground)
|
||||
{
|
||||
Opacity = 0;
|
||||
AllowsTransparency = true;
|
||||
WindowStyle = WindowStyle.None;
|
||||
ShowTitle = false;
|
||||
MinWidth = 0;
|
||||
MinHeight = 0;
|
||||
}
|
||||
|
||||
_showBackground = showBackground;
|
||||
_targetElement = element;
|
||||
ShowDialog();
|
||||
}
|
||||
|
||||
public void Show(System.Windows.Window element, Point point)
|
||||
{
|
||||
Left = element.Left + point.X;
|
||||
Top = element.Top + point.Y;
|
||||
Show();
|
||||
}
|
||||
|
||||
public static void Show(string message)
|
||||
{
|
||||
var window = new PopupWindow
|
||||
{
|
||||
AllowsTransparency = true,
|
||||
WindowStyle = WindowStyle.None,
|
||||
ContentStr = message,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
Background = ResourceHelper.GetResource<Brush>(ResourceToken.PrimaryBrush)
|
||||
};
|
||||
window.Show();
|
||||
}
|
||||
|
||||
public static bool? ShowDialog(string message, string title = default(string), bool showCancel = false)
|
||||
{
|
||||
var window = new PopupWindow
|
||||
{
|
||||
AllowsTransparency = true,
|
||||
WindowStyle = WindowStyle.None,
|
||||
ContentStr = message,
|
||||
IsDialog = true,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
ShowBorder = true,
|
||||
Title = string.IsNullOrEmpty(title) ? Properties.Langs.Lang.Tip : title,
|
||||
ShowCancel = showCancel
|
||||
};
|
||||
return window.ShowDialog();
|
||||
}
|
||||
|
||||
private void ButtonOk_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (IsDialog)
|
||||
{
|
||||
DialogResult = true;
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
private void ButtonCancle_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (IsDialog)
|
||||
{
|
||||
DialogResult = false;
|
||||
}
|
||||
Close();
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
PopupElement = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Extension;
|
||||
#if netle40
|
||||
using Microsoft.Windows.Shell;
|
||||
#else
|
||||
using System.Windows.Shell;
|
||||
|
||||
#endif
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = ElementNonClientArea, Type = typeof(UIElement))]
|
||||
public class Window : System.Windows.Window
|
||||
{
|
||||
private const string ElementNonClientArea = "PART_NonClientArea";
|
||||
|
||||
public static readonly DependencyProperty NonClientAreaContentProperty = DependencyProperty.Register(
|
||||
"NonClientAreaContent", typeof(object), typeof(Window), new PropertyMetadata(default(object)));
|
||||
|
||||
public static readonly DependencyProperty CloseButtonHoverBackgroundProperty = DependencyProperty.Register(
|
||||
"CloseButtonHoverBackground", typeof(Brush), typeof(Window),
|
||||
new PropertyMetadata(default(Brush)));
|
||||
|
||||
public static readonly DependencyProperty CloseButtonHoverForegroundProperty =
|
||||
DependencyProperty.Register(
|
||||
"CloseButtonHoverForeground", typeof(Brush), typeof(Window),
|
||||
new PropertyMetadata(default(Brush)));
|
||||
|
||||
public static readonly DependencyProperty CloseButtonBackgroundProperty = DependencyProperty.Register(
|
||||
"CloseButtonBackground", typeof(Brush), typeof(Window), new PropertyMetadata(Brushes.Transparent));
|
||||
|
||||
public static readonly DependencyProperty CloseButtonForegroundProperty = DependencyProperty.Register(
|
||||
"CloseButtonForeground", typeof(Brush), typeof(Window),
|
||||
new PropertyMetadata(Brushes.White));
|
||||
|
||||
public static readonly DependencyProperty OtherButtonBackgroundProperty = DependencyProperty.Register(
|
||||
"OtherButtonBackground", typeof(Brush), typeof(Window), new PropertyMetadata(Brushes.Transparent));
|
||||
|
||||
public static readonly DependencyProperty OtherButtonForegroundProperty = DependencyProperty.Register(
|
||||
"OtherButtonForeground", typeof(Brush), typeof(Window),
|
||||
new PropertyMetadata(Brushes.White));
|
||||
|
||||
public static readonly DependencyProperty OtherButtonHoverBackgroundProperty = DependencyProperty.Register(
|
||||
"OtherButtonHoverBackground", typeof(Brush), typeof(Window),
|
||||
new PropertyMetadata(default(Brush)));
|
||||
|
||||
public static readonly DependencyProperty OtherButtonHoverForegroundProperty =
|
||||
DependencyProperty.Register(
|
||||
"OtherButtonHoverForeground", typeof(Brush), typeof(Window),
|
||||
new PropertyMetadata(default(Brush)));
|
||||
|
||||
public static readonly DependencyProperty NonClientAreaBackgroundProperty = DependencyProperty.Register(
|
||||
"NonClientAreaBackground", typeof(Brush), typeof(Window),
|
||||
new PropertyMetadata(default(Brush)));
|
||||
|
||||
public static readonly DependencyProperty NonClientAreaForegroundProperty = DependencyProperty.Register(
|
||||
"NonClientAreaForeground", typeof(Brush), typeof(Window),
|
||||
new PropertyMetadata(default(Brush)));
|
||||
|
||||
public static readonly DependencyProperty NonClientAreaHeightProperty = DependencyProperty.Register(
|
||||
"NonClientAreaHeight", typeof(double), typeof(Window),
|
||||
new PropertyMetadata(28.0));
|
||||
|
||||
public static readonly DependencyProperty ShowNonClientAreaProperty = DependencyProperty.Register(
|
||||
"ShowNonClientArea", typeof(bool), typeof(Window),
|
||||
new PropertyMetadata(ValueBoxes.TrueBox, OnShowNonClientAreaChanged));
|
||||
|
||||
public static readonly DependencyProperty ShowTitleProperty = DependencyProperty.Register(
|
||||
"ShowTitle", typeof(bool), typeof(Window), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public static readonly DependencyProperty IsFullScreenProperty = DependencyProperty.Register(
|
||||
"IsFullScreen", typeof(bool), typeof(Window),
|
||||
new PropertyMetadata(ValueBoxes.FalseBox, OnIsFullScreenChanged));
|
||||
|
||||
private bool _isFullScreen;
|
||||
|
||||
private UIElement _nonClientArea;
|
||||
|
||||
private bool _showNonClientArea = true;
|
||||
|
||||
private double _tempNonClientAreaHeight;
|
||||
|
||||
private Thickness _tempThickness;
|
||||
|
||||
private WindowState _tempWindowState;
|
||||
|
||||
private WindowStyle _tempWindowStyle;
|
||||
|
||||
private ResizeMode _tempResizeMode;
|
||||
|
||||
public Window()
|
||||
{
|
||||
var chrome = new WindowChrome
|
||||
{
|
||||
CornerRadius = new CornerRadius(),
|
||||
GlassFrameThickness = new Thickness(0, 0, 0, 1)
|
||||
};
|
||||
BindingOperations.SetBinding(chrome, WindowChrome.CaptionHeightProperty,
|
||||
new Binding(NonClientAreaHeightProperty.Name) {Source = this});
|
||||
WindowChrome.SetWindowChrome(this, chrome);
|
||||
|
||||
Loaded += delegate
|
||||
{
|
||||
_tempThickness = BorderThickness;
|
||||
if (WindowState == WindowState.Maximized)
|
||||
{
|
||||
BorderThickness = new Thickness();
|
||||
}
|
||||
|
||||
CommandBindings.Add(new CommandBinding(SystemCommands.MinimizeWindowCommand,
|
||||
(s, e) => WindowState = WindowState.Minimized));
|
||||
CommandBindings.Add(new CommandBinding(SystemCommands.MaximizeWindowCommand,
|
||||
(s, e) => WindowState = WindowState.Maximized));
|
||||
CommandBindings.Add(new CommandBinding(SystemCommands.RestoreWindowCommand,
|
||||
(s, e) => WindowState = WindowState.Normal));
|
||||
CommandBindings.Add(new CommandBinding(SystemCommands.CloseWindowCommand, (s, e) => Close()));
|
||||
CommandBindings.Add(new CommandBinding(SystemCommands.ShowSystemMenuCommand, ShowSystemMenu));
|
||||
|
||||
_tempNonClientAreaHeight = NonClientAreaHeight;
|
||||
_tempWindowState = WindowState;
|
||||
_tempWindowStyle = WindowStyle;
|
||||
_tempResizeMode = ResizeMode;
|
||||
|
||||
SwitchIsFullScreen(_isFullScreen);
|
||||
SwitchShowNonClientArea(_showNonClientArea);
|
||||
};
|
||||
}
|
||||
|
||||
public Brush CloseButtonBackground
|
||||
{
|
||||
get => (Brush) GetValue(CloseButtonBackgroundProperty);
|
||||
set => SetValue(CloseButtonBackgroundProperty, value);
|
||||
}
|
||||
|
||||
public Brush CloseButtonForeground
|
||||
{
|
||||
get => (Brush) GetValue(CloseButtonForegroundProperty);
|
||||
set => SetValue(CloseButtonForegroundProperty, value);
|
||||
}
|
||||
|
||||
public Brush OtherButtonBackground
|
||||
{
|
||||
get => (Brush) GetValue(OtherButtonBackgroundProperty);
|
||||
set => SetValue(OtherButtonBackgroundProperty, value);
|
||||
}
|
||||
|
||||
public Brush OtherButtonForeground
|
||||
{
|
||||
get => (Brush) GetValue(OtherButtonForegroundProperty);
|
||||
set => SetValue(OtherButtonForegroundProperty, value);
|
||||
}
|
||||
|
||||
public double NonClientAreaHeight
|
||||
{
|
||||
get => (double) GetValue(NonClientAreaHeightProperty);
|
||||
set => SetValue(NonClientAreaHeightProperty, value);
|
||||
}
|
||||
|
||||
public bool IsFullScreen
|
||||
{
|
||||
get => (bool) GetValue(IsFullScreenProperty);
|
||||
set => SetValue(IsFullScreenProperty, value);
|
||||
}
|
||||
|
||||
public object NonClientAreaContent
|
||||
{
|
||||
get => GetValue(NonClientAreaContentProperty);
|
||||
set => SetValue(NonClientAreaContentProperty, value);
|
||||
}
|
||||
|
||||
public Brush CloseButtonHoverBackground
|
||||
{
|
||||
get => (Brush) GetValue(CloseButtonHoverBackgroundProperty);
|
||||
set => SetValue(CloseButtonHoverBackgroundProperty, value);
|
||||
}
|
||||
|
||||
public Brush CloseButtonHoverForeground
|
||||
{
|
||||
get => (Brush) GetValue(CloseButtonHoverForegroundProperty);
|
||||
set => SetValue(CloseButtonHoverForegroundProperty, value);
|
||||
}
|
||||
|
||||
public Brush OtherButtonHoverBackground
|
||||
{
|
||||
get => (Brush) GetValue(OtherButtonHoverBackgroundProperty);
|
||||
set => SetValue(OtherButtonHoverBackgroundProperty, value);
|
||||
}
|
||||
|
||||
public Brush OtherButtonHoverForeground
|
||||
{
|
||||
get => (Brush) GetValue(OtherButtonHoverForegroundProperty);
|
||||
set => SetValue(OtherButtonHoverForegroundProperty, value);
|
||||
}
|
||||
|
||||
public Brush NonClientAreaBackground
|
||||
{
|
||||
get => (Brush) GetValue(NonClientAreaBackgroundProperty);
|
||||
set => SetValue(NonClientAreaBackgroundProperty, value);
|
||||
}
|
||||
|
||||
public Brush NonClientAreaForeground
|
||||
{
|
||||
get => (Brush) GetValue(NonClientAreaForegroundProperty);
|
||||
set => SetValue(NonClientAreaForegroundProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowNonClientArea
|
||||
{
|
||||
get => (bool) GetValue(ShowNonClientAreaProperty);
|
||||
set => SetValue(ShowNonClientAreaProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowTitle
|
||||
{
|
||||
get => (bool) GetValue(ShowTitleProperty);
|
||||
set => SetValue(ShowTitleProperty, value);
|
||||
}
|
||||
|
||||
private static void OnShowNonClientAreaChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (Window) d;
|
||||
ctl.SwitchShowNonClientArea((bool) e.NewValue);
|
||||
}
|
||||
|
||||
private void SwitchShowNonClientArea(bool showNonClientArea)
|
||||
{
|
||||
if (_nonClientArea == null)
|
||||
{
|
||||
_showNonClientArea = showNonClientArea;
|
||||
return;
|
||||
}
|
||||
|
||||
if (showNonClientArea)
|
||||
{
|
||||
if (IsFullScreen)
|
||||
{
|
||||
_nonClientArea.Show(false);
|
||||
_tempNonClientAreaHeight = NonClientAreaHeight;
|
||||
NonClientAreaHeight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_nonClientArea.Show(true);
|
||||
NonClientAreaHeight = _tempNonClientAreaHeight;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_nonClientArea.Show(false);
|
||||
_tempNonClientAreaHeight = NonClientAreaHeight;
|
||||
NonClientAreaHeight = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnIsFullScreenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (Window) d;
|
||||
ctl.SwitchIsFullScreen((bool) e.NewValue);
|
||||
}
|
||||
|
||||
private void SwitchIsFullScreen(bool isFullScreen)
|
||||
{
|
||||
if (_nonClientArea == null)
|
||||
{
|
||||
_isFullScreen = isFullScreen;
|
||||
return;
|
||||
}
|
||||
|
||||
if (isFullScreen)
|
||||
{
|
||||
_nonClientArea.Show(false);
|
||||
_tempNonClientAreaHeight = NonClientAreaHeight;
|
||||
NonClientAreaHeight = 0;
|
||||
|
||||
_tempWindowState = WindowState;
|
||||
_tempWindowStyle = WindowStyle;
|
||||
_tempResizeMode = ResizeMode;
|
||||
WindowStyle = WindowStyle.None;
|
||||
//下面三行不能改变,就是故意的
|
||||
WindowState = WindowState.Maximized;
|
||||
WindowState = WindowState.Minimized;
|
||||
WindowState = WindowState.Maximized;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ShowNonClientArea)
|
||||
{
|
||||
_nonClientArea.Show(true);
|
||||
NonClientAreaHeight = _tempNonClientAreaHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
_nonClientArea.Show(false);
|
||||
_tempNonClientAreaHeight = NonClientAreaHeight;
|
||||
NonClientAreaHeight = 0;
|
||||
}
|
||||
|
||||
WindowState = _tempWindowState;
|
||||
WindowStyle = _tempWindowStyle;
|
||||
ResizeMode = _tempResizeMode;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnStateChanged(EventArgs e)
|
||||
{
|
||||
base.OnStateChanged(e);
|
||||
if (WindowState == WindowState.Maximized)
|
||||
{
|
||||
BorderThickness = new Thickness();
|
||||
}
|
||||
else if (WindowState == WindowState.Normal)
|
||||
{
|
||||
BorderThickness = _tempThickness;
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_nonClientArea = GetTemplateChild(ElementNonClientArea) as UIElement;
|
||||
|
||||
if (SizeToContent != SizeToContent.WidthAndHeight)
|
||||
return;
|
||||
|
||||
SizeToContent = SizeToContent.Height;
|
||||
Dispatcher.BeginInvoke(new Action(() => { SizeToContent = SizeToContent.WidthAndHeight; }));
|
||||
}
|
||||
|
||||
private void ShowSystemMenu(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var point = WindowState == WindowState.Maximized
|
||||
? new Point(0, 28)
|
||||
: new Point(Left, Top + 28);
|
||||
SystemCommands.ShowSystemMenu(this, point);
|
||||
}
|
||||
|
||||
protected override void OnContentRendered(EventArgs e)
|
||||
{
|
||||
base.OnContentRendered(e);
|
||||
if (SizeToContent == SizeToContent.WidthAndHeight)
|
||||
InvalidateMeasure();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取自定义窗口
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static Window GetCustomWindow(FrameworkElement content)
|
||||
{
|
||||
var window = new Window
|
||||
{
|
||||
Style = ResourceHelper.GetResource<Style>(ResourceToken.WindowWin10),
|
||||
Content = content
|
||||
};
|
||||
window.Loaded += (s, e) =>
|
||||
{
|
||||
window.Width = window.BorderThickness.Left + window.BorderThickness.Right + content.Width;
|
||||
if (!(window.Template.FindName("GridMenu", window) is Grid nemuArea))
|
||||
throw new NullReferenceException("can not find GridMenu in template");
|
||||
window.Height = window.BorderThickness.Top + window.BorderThickness.Bottom + content.Height +
|
||||
nemuArea.ActualHeight;
|
||||
};
|
||||
|
||||
return window;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user