Adjust project structure
This commit is contained in:
1 parent
d38912f5da
commit
e3c436f939
2111 files changed
+14028
-47199
No files matched your search
@@ -0,0 +1,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,50 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools.Converter;
|
||||
|
||||
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);
|
||||
|
||||
public static readonly DependencyProperty CircularProperty = DependencyProperty.RegisterAttached(
|
||||
"Circular", typeof(bool), typeof(BorderElement), new PropertyMetadata(ValueBoxes.FalseBox, OnCircularChanged));
|
||||
|
||||
private static void OnCircularChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is Border border)
|
||||
{
|
||||
if ((bool)e.NewValue)
|
||||
{
|
||||
var binding = new MultiBinding
|
||||
{
|
||||
Converter = new BorderCircularConverter()
|
||||
};
|
||||
binding.Bindings.Add(new Binding(FrameworkElement.ActualWidthProperty.Name) {Source = border});
|
||||
binding.Bindings.Add(new Binding(FrameworkElement.ActualHeightProperty.Name) { Source = border });
|
||||
border.SetBinding(Border.CornerRadiusProperty, binding);
|
||||
}
|
||||
else
|
||||
{
|
||||
BindingOperations.ClearBinding(border, FrameworkElement.MinWidthProperty);
|
||||
BindingOperations.ClearBinding(border, FrameworkElement.MinHeightProperty);
|
||||
BindingOperations.ClearBinding(border, Border.CornerRadiusProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetCircular(DependencyObject element, bool value)
|
||||
=> element.SetValue(CircularProperty, value);
|
||||
|
||||
public static bool GetCircular(DependencyObject element)
|
||||
=> (bool) element.GetValue(CircularProperty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class DataGridAttach
|
||||
{
|
||||
public static readonly DependencyProperty TextColumnStyleProperty = DependencyProperty.RegisterAttached(
|
||||
"TextColumnStyle", typeof(Style), typeof(DataGridAttach), new PropertyMetadata(default(Style), OnTextColumnStyleChanged));
|
||||
|
||||
private static void OnTextColumnStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var grid = (DataGrid)d;
|
||||
if (e.OldValue == null && e.NewValue != null)
|
||||
{
|
||||
UpdateTextColumnStyles(grid);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetTextColumnStyle(DependencyObject element, Style value) => element.SetValue(TextColumnStyleProperty, value);
|
||||
|
||||
public static Style GetTextColumnStyle(DependencyObject element) => (Style)element.GetValue(TextColumnStyleProperty);
|
||||
|
||||
public static readonly DependencyProperty EditingTextColumnStyleProperty = DependencyProperty.RegisterAttached(
|
||||
"EditingTextColumnStyle", typeof(Style), typeof(DataGridAttach), new PropertyMetadata(default(Style), OnTextColumnStyleChanged));
|
||||
|
||||
public static void SetEditingTextColumnStyle(DependencyObject element, Style value) => element.SetValue(EditingTextColumnStyleProperty, value);
|
||||
|
||||
public static Style GetEditingTextColumnStyle(DependencyObject element) => (Style) element.GetValue(EditingTextColumnStyleProperty);
|
||||
|
||||
private static void UpdateTextColumnStyles(DataGrid grid)
|
||||
{
|
||||
var textColumnStyle = GetTextColumnStyle(grid);
|
||||
var editingTextColumnStyle = GetEditingTextColumnStyle(grid);
|
||||
|
||||
if (textColumnStyle != null)
|
||||
{
|
||||
foreach (var column in grid.Columns.OfType<DataGridTextColumn>())
|
||||
{
|
||||
var elementStyle = new Style
|
||||
{
|
||||
BasedOn = column.ElementStyle,
|
||||
TargetType = textColumnStyle.TargetType
|
||||
};
|
||||
|
||||
foreach (var setter in textColumnStyle.Setters.OfType<Setter>())
|
||||
{
|
||||
elementStyle.Setters.Add(setter);
|
||||
}
|
||||
|
||||
column.ElementStyle = elementStyle;
|
||||
}
|
||||
}
|
||||
|
||||
if (editingTextColumnStyle != null)
|
||||
{
|
||||
foreach (var column in grid.Columns.OfType<DataGridTextColumn>())
|
||||
{
|
||||
var editingElementStyle = new Style
|
||||
{
|
||||
BasedOn = column.EditingElementStyle,
|
||||
TargetType = editingTextColumnStyle.TargetType
|
||||
};
|
||||
|
||||
foreach (var setter in editingTextColumnStyle.Setters.OfType<Setter>())
|
||||
{
|
||||
editingElementStyle.Setters.Add(setter);
|
||||
}
|
||||
|
||||
column.EditingElementStyle = editingElementStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ComboBoxColumnStyleProperty = DependencyProperty.RegisterAttached(
|
||||
"ComboBoxColumnStyle", typeof(Style), typeof(DataGridAttach), new PropertyMetadata(default(Style), OnComboBoxColumnStyleChanged));
|
||||
|
||||
private static void OnComboBoxColumnStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var grid = (DataGrid)d;
|
||||
if (e.OldValue == null && e.NewValue != null)
|
||||
{
|
||||
UpdateComboBoxColumnStyles(grid);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetComboBoxColumnStyle(DependencyObject element, Style value) => element.SetValue(ComboBoxColumnStyleProperty, value);
|
||||
|
||||
public static Style GetComboBoxColumnStyle(DependencyObject element) => (Style) element.GetValue(ComboBoxColumnStyleProperty);
|
||||
|
||||
public static readonly DependencyProperty EditingComboBoxColumnStyleProperty = DependencyProperty.RegisterAttached(
|
||||
"EditingComboBoxColumnStyle", typeof(Style), typeof(DataGridAttach), new PropertyMetadata(default(Style), OnComboBoxColumnStyleChanged));
|
||||
|
||||
public static void SetEditingComboBoxColumnStyle(DependencyObject element, Style value) => element.SetValue(EditingComboBoxColumnStyleProperty, value);
|
||||
|
||||
public static Style GetEditingComboBoxColumnStyle(DependencyObject element) => (Style) element.GetValue(EditingComboBoxColumnStyleProperty);
|
||||
|
||||
private static void UpdateComboBoxColumnStyles(DataGrid grid)
|
||||
{
|
||||
var comboBoxColumnStyle = GetComboBoxColumnStyle(grid);
|
||||
var editingComboBoxColumnStyle = GetEditingComboBoxColumnStyle(grid);
|
||||
|
||||
if (comboBoxColumnStyle != null)
|
||||
{
|
||||
foreach (var column in grid.Columns.OfType<DataGridComboBoxColumn>())
|
||||
{
|
||||
var elementStyle = new Style
|
||||
{
|
||||
BasedOn = column.ElementStyle,
|
||||
TargetType = comboBoxColumnStyle.TargetType
|
||||
};
|
||||
|
||||
foreach (var setter in comboBoxColumnStyle.Setters.OfType<Setter>())
|
||||
{
|
||||
elementStyle.Setters.Add(setter);
|
||||
}
|
||||
|
||||
column.ElementStyle = elementStyle;
|
||||
}
|
||||
}
|
||||
|
||||
if (editingComboBoxColumnStyle != null)
|
||||
{
|
||||
foreach (var column in grid.Columns.OfType<DataGridComboBoxColumn>())
|
||||
{
|
||||
var editingElementStyle = new Style
|
||||
{
|
||||
BasedOn = column.EditingElementStyle,
|
||||
TargetType = editingComboBoxColumnStyle.TargetType
|
||||
};
|
||||
|
||||
foreach (var setter in editingComboBoxColumnStyle.Setters.OfType<Setter>())
|
||||
{
|
||||
editingElementStyle.Setters.Add(setter);
|
||||
}
|
||||
|
||||
column.EditingElementStyle = editingElementStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CheckBoxColumnStyleProperty = DependencyProperty.RegisterAttached(
|
||||
"CheckBoxColumnStyle", typeof(Style), typeof(DataGridAttach), new PropertyMetadata(default(Style), OnCheckBoxColumnStyleChanged));
|
||||
|
||||
private static void OnCheckBoxColumnStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var grid = (DataGrid)d;
|
||||
if (e.OldValue == null && e.NewValue != null)
|
||||
{
|
||||
UpdateCheckBoxColumnStyles(grid);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetCheckBoxColumnStyle(DependencyObject element, Style value) => element.SetValue(CheckBoxColumnStyleProperty, value);
|
||||
|
||||
public static Style GetCheckBoxColumnStyle(DependencyObject element) => (Style)element.GetValue(CheckBoxColumnStyleProperty);
|
||||
|
||||
public static readonly DependencyProperty EditingCheckBoxColumnStyleProperty = DependencyProperty.RegisterAttached(
|
||||
"EditingCheckBoxColumnStyle", typeof(Style), typeof(DataGridAttach), new PropertyMetadata(default(Style), OnCheckBoxColumnStyleChanged));
|
||||
|
||||
public static void SetEditingCheckBoxColumnStyle(DependencyObject element, Style value) => element.SetValue(EditingCheckBoxColumnStyleProperty, value);
|
||||
|
||||
public static Style GetEditingCheckBoxColumnStyle(DependencyObject element) => (Style) element.GetValue(EditingCheckBoxColumnStyleProperty);
|
||||
|
||||
private static void UpdateCheckBoxColumnStyles(DataGrid grid)
|
||||
{
|
||||
var checkBoxColumnStyle = GetCheckBoxColumnStyle(grid);
|
||||
var editingCheckBoxColumnStyle = GetEditingCheckBoxColumnStyle(grid);
|
||||
|
||||
if (checkBoxColumnStyle != null)
|
||||
{
|
||||
foreach (var column in grid.Columns.OfType<DataGridCheckBoxColumn>())
|
||||
{
|
||||
var elementStyle = new Style
|
||||
{
|
||||
BasedOn = column.ElementStyle,
|
||||
TargetType = checkBoxColumnStyle.TargetType
|
||||
};
|
||||
|
||||
foreach (var setter in checkBoxColumnStyle.Setters.OfType<Setter>())
|
||||
{
|
||||
elementStyle.Setters.Add(setter);
|
||||
}
|
||||
|
||||
column.ElementStyle = elementStyle;
|
||||
}
|
||||
}
|
||||
|
||||
if (editingCheckBoxColumnStyle != null)
|
||||
{
|
||||
foreach (var column in grid.Columns.OfType<DataGridCheckBoxColumn>())
|
||||
{
|
||||
var editingElementStyle = new Style
|
||||
{
|
||||
BasedOn = column.EditingElementStyle,
|
||||
TargetType = editingCheckBoxColumnStyle.TargetType
|
||||
};
|
||||
|
||||
foreach (var setter in editingCheckBoxColumnStyle.Setters.OfType<Setter>())
|
||||
{
|
||||
editingElementStyle.Setters.Add(setter);
|
||||
}
|
||||
|
||||
column.EditingElementStyle = editingElementStyle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static DependencyProperty ShowRowNumberProperty = DependencyProperty.RegisterAttached("ShowRowNumber",
|
||||
typeof(bool), typeof(DataGridAttach),
|
||||
new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.Inherits, OnShowRowNumberChanged));
|
||||
|
||||
public static bool GetShowRowNumber(DependencyObject target) => (bool)target.GetValue(ShowRowNumberProperty);
|
||||
|
||||
public static void SetShowRowNumber(DependencyObject target, bool value) => target.SetValue(ShowRowNumberProperty, value);
|
||||
|
||||
private static void OnShowRowNumberChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!(target is DataGrid dataGrid)) return;
|
||||
var show = (bool)e.NewValue;
|
||||
|
||||
if (show)
|
||||
{
|
||||
dataGrid.LoadingRow += DataGrid_LoadingRow;
|
||||
dataGrid.ItemContainerGenerator.ItemsChanged += ItemContainerGenerator_ItemsChanged;
|
||||
}
|
||||
else
|
||||
{
|
||||
dataGrid.LoadingRow -= DataGrid_LoadingRow;
|
||||
dataGrid.ItemContainerGenerator.ItemsChanged -= ItemContainerGenerator_ItemsChanged;
|
||||
}
|
||||
|
||||
UpdateItems(dataGrid.ItemContainerGenerator, dataGrid.Items.Count, show);
|
||||
}
|
||||
|
||||
private static void ItemContainerGenerator_ItemsChanged(object sender, ItemsChangedEventArgs e)
|
||||
{
|
||||
if (!(sender is ItemContainerGenerator generator)) return;
|
||||
UpdateItems(generator, e.ItemCount);
|
||||
}
|
||||
|
||||
private static void UpdateItems(ItemContainerGenerator generator, int itemsCount, bool show = true)
|
||||
{
|
||||
if (show)
|
||||
{
|
||||
for (int i = 0; i < itemsCount; i++)
|
||||
{
|
||||
var row = (DataGridRow)generator.ContainerFromIndex(i);
|
||||
if (row != null)
|
||||
{
|
||||
row.Header = (i + 1).ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < itemsCount; i++)
|
||||
{
|
||||
var row = (DataGridRow)generator.ContainerFromIndex(i);
|
||||
if (row != null)
|
||||
{
|
||||
row.Header = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DataGrid_LoadingRow(object sender, DataGridRowEventArgs e) => e.Row.Header = (e.Row.GetIndex() + 1).ToString();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
CreateTrigger(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 CreateTrigger(UIElement element, string link)
|
||||
{
|
||||
element.CommandBindings.Remove(GetCommandBinding(element));
|
||||
element.SetCurrentValue(CommandBindingProperty, DependencyProperty.UnsetValue);
|
||||
|
||||
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,44 @@
|
||||
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.ItemRemoved(oldBehavior);
|
||||
panel.SetCurrentValue(TempFluidMoveBehaviorProperty, DependencyProperty.UnsetValue);
|
||||
|
||||
if (e.NewValue is FluidMoveBehavior behavior)
|
||||
{
|
||||
collection.ItemAdded(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,25 @@
|
||||
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);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register(
|
||||
"HeaderTemplate", typeof(DataTemplate), typeof(HeaderedSelectableItem), new PropertyMetadata(default(DataTemplate)));
|
||||
|
||||
public DataTemplate HeaderTemplate
|
||||
{
|
||||
get => (DataTemplate) GetValue(HeaderTemplateProperty);
|
||||
set => SetValue(HeaderTemplateProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public interface IGravatarGenerator
|
||||
{
|
||||
object GetGravatar(string id);
|
||||
}
|
||||
}
|
||||
@@ -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,111 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class SelectableItem : ContentControl, 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)
|
||||
{
|
||||
if (SelfManage)
|
||||
{
|
||||
if (!IsSelected)
|
||||
{
|
||||
IsSelected = true;
|
||||
OnSelected(new RoutedEventArgs(SelectedEvent, this));
|
||||
}
|
||||
else if (CanDeselect)
|
||||
{
|
||||
IsSelected = false;
|
||||
OnSelected(new RoutedEventArgs(DeselectedEvent, this));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CanDeselect)
|
||||
{
|
||||
OnSelected(IsSelected
|
||||
? new RoutedEventArgs(DeselectedEvent, this)
|
||||
: new RoutedEventArgs(SelectedEvent, this));
|
||||
}
|
||||
else
|
||||
{
|
||||
OnSelected(new RoutedEventArgs(SelectedEvent, this));
|
||||
}
|
||||
}
|
||||
_isMouseLeftButtonDown = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnSelected(RoutedEventArgs e) => RaiseEvent(e);
|
||||
|
||||
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 DependencyProperty SelfManageProperty = DependencyProperty.Register(
|
||||
"SelfManage", typeof(bool), typeof(SelectableItem), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool SelfManage
|
||||
{
|
||||
get => (bool) GetValue(SelfManageProperty);
|
||||
set => SetValue(SelfManageProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CanDeselectProperty = DependencyProperty.Register(
|
||||
"CanDeselect", typeof(bool), typeof(SelectableItem), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool CanDeselect
|
||||
{
|
||||
get => (bool) GetValue(CanDeselectProperty);
|
||||
set => SetValue(CanDeselectProperty, 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 RoutedEvent DeselectedEvent =
|
||||
EventManager.RegisterRoutedEvent("Deselected", RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler), typeof(SelectableItem));
|
||||
|
||||
public event RoutedEventHandler Deselected
|
||||
{
|
||||
add => AddHandler(DeselectedEvent, value);
|
||||
remove => RemoveHandler(DeselectedEvent, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
using System.Collections;
|
||||
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 static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
|
||||
"ItemsSource", typeof(IEnumerable), typeof(SimpleItemsControl),
|
||||
new PropertyMetadata(default(IEnumerable), OnItemsSourceChanged));
|
||||
|
||||
public SimpleItemsControl()
|
||||
{
|
||||
var items = new ObservableCollection<object>();
|
||||
items.CollectionChanged += OnItemsChanged;
|
||||
Items = items;
|
||||
}
|
||||
|
||||
public IEnumerable ItemsSource
|
||||
{
|
||||
get => (IEnumerable) GetValue(ItemsSourceProperty);
|
||||
set => SetValue(ItemsSourceProperty, value);
|
||||
}
|
||||
|
||||
[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; }
|
||||
|
||||
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((SimpleItemsControl) d).OnItemsSourceChanged((IEnumerable) e.OldValue, (IEnumerable) e.NewValue);
|
||||
}
|
||||
|
||||
protected virtual void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
ItemsHost?.Children.Clear();
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
ItemsHost = GetTemplateChild(ElementPanel) as Panel;
|
||||
Refresh();
|
||||
}
|
||||
|
||||
protected virtual void OnItemsChanged(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)
|
||||
=> (d as SimpleItemsControl)?.OnItemTemplateChanged(e);
|
||||
|
||||
protected virtual void OnItemTemplateChanged(DependencyPropertyChangedEventArgs e) => Refresh();
|
||||
|
||||
private static void OnItemContainerStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
=> (d as SimpleItemsControl)?.OnItemContainerStyleChanged(e);
|
||||
|
||||
protected virtual void OnItemContainerStyleChanged(DependencyPropertyChangedEventArgs e) => 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,27 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class ProgressButton : ToggleButton
|
||||
{
|
||||
public static readonly DependencyProperty ProgressStyleProperty = DependencyProperty.Register(
|
||||
"ProgressStyle", typeof(Style), typeof(ProgressButton), new PropertyMetadata(default(Style)));
|
||||
|
||||
public Style ProgressStyle
|
||||
{
|
||||
get => (Style) GetValue(ProgressStyleProperty);
|
||||
set => SetValue(ProgressStyleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register(
|
||||
"Progress", typeof(double), typeof(ProgressButton), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
public double Progress
|
||||
{
|
||||
get => (double) GetValue(ProgressProperty);
|
||||
set => SetValue(ProgressProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
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 int _jumpToIndex = -1;
|
||||
|
||||
/// <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;
|
||||
|
||||
UpdateShowRange();
|
||||
if (_jumpToIndex > 0)
|
||||
{
|
||||
PageIndex = _jumpToIndex;
|
||||
_jumpToIndex = -1;
|
||||
}
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加一项资源
|
||||
/// </summary>
|
||||
/// <param name="uriString"></param>
|
||||
public void Add(string uriString) => _uriList.Add(_uriList.Count, new Uri(uriString));
|
||||
|
||||
/// <summary>
|
||||
/// 添加一项资源
|
||||
/// </summary>
|
||||
/// <param name="uri"></param>
|
||||
public void Add(Uri uri) => _uriList.Add(_uriList.Count, uri);
|
||||
|
||||
/// <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,426 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class CoverView : RegularItemsControl
|
||||
{
|
||||
private readonly CoverViewContent _viewContent;
|
||||
|
||||
private CoverViewItem _selectedItem;
|
||||
|
||||
private IEnumerable _itemsSourceInternal;
|
||||
|
||||
private readonly Dictionary<object, CoverViewItem> _entryDic = new Dictionary<object, CoverViewItem>();
|
||||
|
||||
private bool _isRefresh;
|
||||
|
||||
public CoverView()
|
||||
{
|
||||
_viewContent = new CoverViewContent();
|
||||
|
||||
AddHandler(SelectableItem.SelectedEvent, new RoutedEventHandler(CoverViewItem_OnSelected));
|
||||
_viewContent.SetBinding(CoverViewContent.ContentHeightProperty, new Binding(ItemContentHeightProperty.Name) { Source = this });
|
||||
_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;
|
||||
_viewContent.ContentTemplate = ItemTemplate;
|
||||
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;
|
||||
_viewContent.ContentTemplate = 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();
|
||||
|
||||
private void SetBindingForItem(FrameworkElement item)
|
||||
{
|
||||
item.SetBinding(MarginProperty, new Binding(ItemMarginProperty.Name) { Source = this });
|
||||
item.SetBinding(WidthProperty, new Binding(ItemWidthProperty.Name) { Source = this });
|
||||
item.SetBinding(HeightProperty, new Binding(ItemHeightProperty.Name) { Source = this });
|
||||
item.SetBinding(HeaderedSelectableItem.HeaderTemplateProperty, new Binding(ItemHeaderTemplateProperty.Name) { Source = this });
|
||||
}
|
||||
|
||||
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),
|
||||
value => ValidateHelper.IsInRangeOfPosDouble(value, true));
|
||||
|
||||
public double ItemContentHeight
|
||||
{
|
||||
get => (double)GetValue(ItemContentHeightProperty);
|
||||
set => SetValue(ItemContentHeightProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ItemContentHeightFixedProperty = DependencyProperty.Register(
|
||||
"ItemContentHeightFixed", typeof(bool), typeof(CoverView), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool ItemContentHeightFixed
|
||||
{
|
||||
get => (bool)GetValue(ItemContentHeightFixedProperty);
|
||||
set => SetValue(ItemContentHeightFixedProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ItemHeaderTemplateProperty = DependencyProperty.Register(
|
||||
"ItemHeaderTemplate", typeof(DataTemplate), typeof(CoverView), new PropertyMetadata(default(DataTemplate)));
|
||||
|
||||
public DataTemplate ItemHeaderTemplate
|
||||
{
|
||||
get => (DataTemplate) GetValue(ItemHeaderTemplateProperty);
|
||||
set => SetValue(ItemHeaderTemplateProperty, 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;
|
||||
|
||||
_entryDic.Clear();
|
||||
_isRefresh = true;
|
||||
|
||||
foreach (var item in Items)
|
||||
{
|
||||
AddItem(item);
|
||||
}
|
||||
|
||||
_isRefresh = false;
|
||||
|
||||
GenerateIndex();
|
||||
|
||||
UpdateCoverViewContent(_viewContent.IsOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新内容视图
|
||||
/// </summary>
|
||||
private void UpdateCoverViewContent(bool isOpen)
|
||||
{
|
||||
if (_selectedItem == null) return;
|
||||
|
||||
ItemsHost.Children.Remove(_viewContent);
|
||||
|
||||
if (!ItemContentHeightFixed)
|
||||
{
|
||||
_viewContent.ManualHeight = 0;
|
||||
if (_viewContent.Content is FrameworkElement element)
|
||||
{
|
||||
element.VerticalAlignment = VerticalAlignment.Top;
|
||||
element.Arrange(new Rect(new Size(double.MaxValue, double.MaxValue)));
|
||||
_viewContent.ManualHeight = element.ActualHeight;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCoverViewContentPosition();
|
||||
if (_viewContent.CanSwitch)
|
||||
{
|
||||
_viewContent.IsOpen = isOpen;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新内容视图位置
|
||||
/// </summary>
|
||||
private void UpdateCoverViewContentPosition()
|
||||
{
|
||||
if (_viewContent.Parent != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
|
||||
{
|
||||
if (_itemsSourceInternal != null)
|
||||
{
|
||||
if (_itemsSourceInternal is INotifyCollectionChanged s)
|
||||
{
|
||||
s.CollectionChanged -= InternalCollectionChanged;
|
||||
}
|
||||
|
||||
Items.Clear();
|
||||
ClearItems();
|
||||
}
|
||||
_itemsSourceInternal = newValue;
|
||||
if (_itemsSourceInternal != null)
|
||||
{
|
||||
if (_itemsSourceInternal is INotifyCollectionChanged s)
|
||||
{
|
||||
s.CollectionChanged += InternalCollectionChanged;
|
||||
}
|
||||
foreach (var item in _itemsSourceInternal)
|
||||
{
|
||||
AddItem(item);
|
||||
}
|
||||
}
|
||||
|
||||
if (ItemsHost != null)
|
||||
{
|
||||
GenerateIndex();
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearItems()
|
||||
{
|
||||
_selectedItem = null;
|
||||
_viewContent.Content = null;
|
||||
_viewContent.ContentTemplate = null;
|
||||
|
||||
if (ItemsHost != null)
|
||||
{
|
||||
ItemsHost.Children.Remove(_viewContent);
|
||||
ItemsHost.Children.Clear();
|
||||
}
|
||||
|
||||
_entryDic.Clear();
|
||||
}
|
||||
|
||||
private void RemoveItem(object item)
|
||||
{
|
||||
if (_entryDic.TryGetValue(item, out var entry))
|
||||
{
|
||||
if (ReferenceEquals(entry, _selectedItem))
|
||||
{
|
||||
_selectedItem = null;
|
||||
if (_viewContent != null)
|
||||
{
|
||||
_viewContent.Content = null;
|
||||
_viewContent.IsOpen = false;
|
||||
ItemsHost.Children.Remove(_viewContent);
|
||||
}
|
||||
}
|
||||
|
||||
ItemsHost.Children.Remove(entry);
|
||||
Items.Remove(item);
|
||||
_entryDic.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddItem(object item) => InsertItem(_entryDic.Count, item);
|
||||
|
||||
private void InsertItem(int index, object item)
|
||||
{
|
||||
if (ItemsHost == null)
|
||||
{
|
||||
Items.Insert(index, item);
|
||||
_entryDic.Add(item, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
DependencyObject container;
|
||||
if (IsItemItsOwnContainerOverride(item))
|
||||
{
|
||||
container = item as DependencyObject;
|
||||
}
|
||||
else
|
||||
{
|
||||
container = GetContainerForItemOverride();
|
||||
PrepareContainerForItemOverride(container, item);
|
||||
}
|
||||
|
||||
if (container is CoverViewItem element)
|
||||
{
|
||||
SetBindingForItem(element);
|
||||
element.Style = ItemContainerStyle;
|
||||
if (element.Header == null)
|
||||
{
|
||||
element.Header = item;
|
||||
}
|
||||
_entryDic[item] = element;
|
||||
ItemsHost.Children.Insert(index, element);
|
||||
|
||||
if (IsLoaded && !_isRefresh && _itemsSourceInternal != null)
|
||||
{
|
||||
Items.Insert(index, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 生成序号
|
||||
/// </summary>
|
||||
private void GenerateIndex()
|
||||
{
|
||||
var index = 0;
|
||||
foreach (var item in Items)
|
||||
{
|
||||
_entryDic[item].Index = index++;
|
||||
}
|
||||
}
|
||||
|
||||
private void InternalCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (ItemsHost == null) return;
|
||||
|
||||
if (e.Action == NotifyCollectionChangedAction.Reset)
|
||||
{
|
||||
if (_entryDic.Count == 0) return;
|
||||
ClearItems();
|
||||
Items.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
{
|
||||
RemoveItem(item);
|
||||
}
|
||||
}
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
if (_viewContent.IsOpen)
|
||||
{
|
||||
ItemsHost.Children.Remove(_viewContent);
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
var index = e.NewStartingIndex + count++;
|
||||
InsertItem(index, item);
|
||||
}
|
||||
}
|
||||
|
||||
GenerateIndex();
|
||||
if (_viewContent.IsOpen)
|
||||
{
|
||||
UpdateCoverViewContentPosition();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (_itemsSourceInternal != null) return;
|
||||
|
||||
InternalCollectionChanged(sender, e);
|
||||
}
|
||||
|
||||
protected override void OnItemTemplateChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void OnItemContainerStyleChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Expression.Drawing;
|
||||
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;
|
||||
|
||||
internal bool CanSwitch { get; set; } = true;
|
||||
|
||||
private bool _isOpen;
|
||||
|
||||
internal bool IsOpen
|
||||
{
|
||||
get => _isOpen;
|
||||
set
|
||||
{
|
||||
if (_isOpen == value) return;
|
||||
_isOpen = value;
|
||||
OpenSwitch(value);
|
||||
}
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ManualHeightProperty = DependencyProperty.Register(
|
||||
"ManualHeight", typeof(double), typeof(CoverViewContent), new PropertyMetadata(default(double)), value => ValidateHelper.IsInRangeOfPosDouble(value, true));
|
||||
|
||||
internal double ManualHeight
|
||||
{
|
||||
get => (double)GetValue(ManualHeightProperty);
|
||||
set => SetValue(ManualHeightProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ContentHeightProperty = DependencyProperty.Register(
|
||||
"ContentHeight", typeof(double), typeof(CoverViewContent), new PropertyMetadata(ValueBoxes.Double300Box),
|
||||
value => ValidateHelper.IsInRangeOfPosDouble(value, true));
|
||||
|
||||
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)));
|
||||
if (IsOpen)
|
||||
{
|
||||
if (ManualHeight > 0 && !MathHelper.AreClose(ManualHeight, ContentHeight))
|
||||
{
|
||||
_content.BeginAnimation(HeightProperty, AnimationHelper.CreateAnimation(ManualHeight));
|
||||
}
|
||||
else
|
||||
{
|
||||
_content.BeginAnimation(HeightProperty, AnimationHelper.CreateAnimation(ContentHeight));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OpenSwitch(bool isOpen)
|
||||
{
|
||||
if (_content == null) return;
|
||||
var animation = AnimationHelper.CreateAnimation(isOpen ? ManualHeight > 0 ? ManualHeight : 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 = ElementPanelMain, Type = typeof(Panel))]
|
||||
[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 ElementPanelMain = "PART_PanelMain";
|
||||
|
||||
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 Panel _panelMain;
|
||||
|
||||
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();
|
||||
|
||||
_panelMain = GetTemplateChild(ElementPanelMain) as Panel;
|
||||
_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(_panelMain);
|
||||
_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(_panelMain);
|
||||
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(_panelMain);
|
||||
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,113 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <inheritdoc cref="IDataInput" />
|
||||
[TemplatePart(Name = ElementTextBox, Type = typeof(DatePickerTextBox))]
|
||||
public class DatePicker : System.Windows.Controls.DatePicker, IDataInput
|
||||
{
|
||||
private const string ElementTextBox = "PART_TextBox";
|
||||
|
||||
private System.Windows.Controls.TextBox _textBox;
|
||||
|
||||
public DatePicker()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Clear, (s, e) =>
|
||||
{
|
||||
ClearValue(SelectedDateProperty);
|
||||
ClearValue(TextProperty);
|
||||
}));
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (_textBox != null)
|
||||
{
|
||||
_textBox.TextChanged -= TextBox_TextChanged;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_textBox = GetTemplateChild(ElementTextBox) as System.Windows.Controls.TextBox;
|
||||
if (_textBox != null)
|
||||
{
|
||||
_textBox.TextChanged += TextBox_TextChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBox_TextChanged(object sender, TextChangedEventArgs e) => VerifyData();
|
||||
|
||||
public Func<string, OperationResult<bool>> VerifyFunc { get; set; }
|
||||
|
||||
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register(
|
||||
"IsError", typeof(bool), typeof(DatePicker), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsError
|
||||
{
|
||||
get => (bool) GetValue(IsErrorProperty);
|
||||
set => SetValue(IsErrorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ErrorStrProperty = DependencyProperty.Register(
|
||||
"ErrorStr", typeof(string), typeof(DatePicker), new PropertyMetadata(default(string)));
|
||||
|
||||
public string ErrorStr
|
||||
{
|
||||
get => (string) GetValue(ErrorStrProperty);
|
||||
set => SetValue(ErrorStrProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextTypeProperty = DependencyProperty.Register(
|
||||
"TextType", typeof(TextType), typeof(DatePicker), new PropertyMetadata(default(TextType)));
|
||||
|
||||
public TextType TextType
|
||||
{
|
||||
get => (TextType) GetValue(TextTypeProperty);
|
||||
set => SetValue(TextTypeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowClearButtonProperty = DependencyProperty.Register(
|
||||
"ShowClearButton", typeof(bool), typeof(DatePicker), 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,690 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 时间日期选择器
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementRoot, Type = typeof(Grid))]
|
||||
[TemplatePart(Name = ElementTextBox, Type = typeof(WatermarkTextBox))]
|
||||
[TemplatePart(Name = ElementButton, Type = typeof(Button))]
|
||||
[TemplatePart(Name = ElementPopup, Type = typeof(Popup))]
|
||||
public class DateTimePicker : Control, IDataInput
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementRoot = "PART_Root";
|
||||
|
||||
private const string ElementTextBox = "PART_TextBox";
|
||||
|
||||
private const string ElementButton = "PART_Button";
|
||||
|
||||
private const string ElementPopup = "PART_Popup";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private CalendarWithClock _calendarWithClock;
|
||||
|
||||
private string _defaultText;
|
||||
|
||||
private ButtonBase _dropDownButton;
|
||||
|
||||
private Popup _popUp;
|
||||
|
||||
private bool _disablePopupReopen;
|
||||
|
||||
private WatermarkTextBox _textBox;
|
||||
|
||||
private IDictionary<DependencyProperty, bool> _isHandlerSuspended;
|
||||
|
||||
private DateTime? _originalSelectedDateTime;
|
||||
|
||||
#endregion Data
|
||||
|
||||
#region Public Events
|
||||
|
||||
public static readonly RoutedEvent SelectedDateTimeChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("SelectedDateTimeChanged", RoutingStrategy.Direct,
|
||||
typeof(EventHandler<FunctionEventArgs<DateTime?>>), typeof(DateTimePicker));
|
||||
|
||||
public event EventHandler<FunctionEventArgs<DateTime?>> SelectedDateTimeChanged
|
||||
{
|
||||
add => AddHandler(SelectedDateTimeChangedEvent, value);
|
||||
remove => RemoveHandler(SelectedDateTimeChangedEvent, value);
|
||||
}
|
||||
|
||||
public event RoutedEventHandler PickerClosed;
|
||||
|
||||
public event RoutedEventHandler PickerOpened;
|
||||
|
||||
#endregion Public Events
|
||||
|
||||
static DateTimePicker()
|
||||
{
|
||||
EventManager.RegisterClassHandler(typeof(DateTimePicker), GotFocusEvent, new RoutedEventHandler(OnGotFocus));
|
||||
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(DateTimePicker), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
|
||||
KeyboardNavigation.IsTabStopProperty.OverrideMetadata(typeof(DateTimePicker), new FrameworkPropertyMetadata(false));
|
||||
}
|
||||
|
||||
public DateTimePicker()
|
||||
{
|
||||
InitCalendarWithClock();
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Clear, (s, e) =>
|
||||
{
|
||||
ClearValue(SelectedDateTimeProperty);
|
||||
ClearValue(TextProperty);
|
||||
_textBox.Text = string.Empty;
|
||||
}));
|
||||
}
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public static readonly DependencyProperty DateTimeFormatProperty = DependencyProperty.Register(
|
||||
"DateTimeFormat", typeof(string), typeof(DateTimePicker), new PropertyMetadata("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
public string DateTimeFormat
|
||||
{
|
||||
get => (string) GetValue(DateTimeFormatProperty);
|
||||
set => SetValue(DateTimeFormatProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ClockStyleProperty = DependencyProperty.Register(
|
||||
"ClockStyle", typeof(Style), typeof(DateTimePicker), new PropertyMetadata(default(Style)));
|
||||
|
||||
public Style ClockStyle
|
||||
{
|
||||
get => (Style) GetValue(ClockStyleProperty);
|
||||
set => SetValue(ClockStyleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CalendarStyleProperty = DependencyProperty.Register(
|
||||
"CalendarStyle", typeof(Style), typeof(DateTimePicker), new PropertyMetadata(default(Style)));
|
||||
|
||||
public Style CalendarStyle
|
||||
{
|
||||
get => (Style) GetValue(CalendarStyleProperty);
|
||||
set => SetValue(CalendarStyleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DisplayDateTimeProperty = DependencyProperty.Register(
|
||||
"DisplayDateTime", typeof(DateTime), typeof(DateTimePicker), new FrameworkPropertyMetadata(DateTime.Now, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceDisplayDateTime));
|
||||
|
||||
private static object CoerceDisplayDateTime(DependencyObject d, object value)
|
||||
{
|
||||
var dp = (DateTimePicker)d;
|
||||
dp._calendarWithClock.DisplayDateTime = (DateTime)value;
|
||||
|
||||
return dp._calendarWithClock.DisplayDateTime;
|
||||
}
|
||||
|
||||
public DateTime DisplayDateTime
|
||||
{
|
||||
get => (DateTime) GetValue(DisplayDateTimeProperty);
|
||||
set => SetValue(DisplayDateTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsDropDownOpenProperty = DependencyProperty.Register(
|
||||
"IsDropDownOpen", typeof(bool), typeof(DateTimePicker), new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsDropDownOpenChanged, OnCoerceIsDropDownOpen));
|
||||
|
||||
private static object OnCoerceIsDropDownOpen(DependencyObject d, object baseValue) => d is DateTimePicker dp && !dp.IsEnabled ? false : baseValue;
|
||||
|
||||
private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var dp = d as DateTimePicker;
|
||||
|
||||
var newValue = (bool)e.NewValue;
|
||||
if (dp?._popUp != null && dp._popUp.IsOpen != newValue)
|
||||
{
|
||||
dp._popUp.IsOpen = newValue;
|
||||
if (newValue)
|
||||
{
|
||||
dp._originalSelectedDateTime = dp.SelectedDateTime;
|
||||
|
||||
dp.Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate
|
||||
{
|
||||
dp._calendarWithClock.Focus();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDropDownOpen
|
||||
{
|
||||
get => (bool) GetValue(IsDropDownOpenProperty);
|
||||
set => SetValue(IsDropDownOpenProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SelectedDateTimeProperty = DependencyProperty.Register(
|
||||
"SelectedDateTime", typeof(DateTime?), typeof(DateTimePicker), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedDateTimeChanged, CoerceSelectedDateTime));
|
||||
|
||||
private static object CoerceSelectedDateTime(DependencyObject d, object value)
|
||||
{
|
||||
var dp = (DateTimePicker)d;
|
||||
dp._calendarWithClock.SelectedDateTime = (DateTime?)value;
|
||||
return dp._calendarWithClock.SelectedDateTime;
|
||||
}
|
||||
|
||||
private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!(d is DateTimePicker dp)) return;
|
||||
|
||||
if (dp.SelectedDateTime.HasValue)
|
||||
{
|
||||
var time = dp.SelectedDateTime.Value;
|
||||
dp.SetTextInternal(dp.DateTimeToString(time));
|
||||
}
|
||||
|
||||
dp.RaiseEvent(new FunctionEventArgs<DateTime?>(SelectedDateTimeChangedEvent, dp)
|
||||
{
|
||||
Info = dp.SelectedDateTime
|
||||
});
|
||||
}
|
||||
|
||||
public DateTime? SelectedDateTime
|
||||
{
|
||||
get => (DateTime?) GetValue(SelectedDateTimeProperty);
|
||||
set => SetValue(SelectedDateTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
"Text", typeof(string), typeof(DateTimePicker), new FrameworkPropertyMetadata(string.Empty, OnTextChanged));
|
||||
|
||||
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is DateTimePicker dp && !dp.IsHandlerSuspended(TextProperty))
|
||||
{
|
||||
if (e.NewValue is string newValue)
|
||||
{
|
||||
if (dp._textBox != null)
|
||||
{
|
||||
dp._textBox.Text = newValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
dp._defaultText = newValue;
|
||||
}
|
||||
|
||||
dp.SetSelectedDateTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
dp.SetValueNoCallback(SelectedDateTimeProperty, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string) GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the local Text property without breaking bindings
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
private void SetTextInternal(string value)
|
||||
{
|
||||
SetCurrentValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public Func<string, OperationResult<bool>> VerifyFunc { get; set; }
|
||||
|
||||
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register(
|
||||
"IsError", typeof(bool), typeof(DateTimePicker), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsError
|
||||
{
|
||||
get => (bool) GetValue(IsErrorProperty);
|
||||
set => SetValue(IsErrorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ErrorStrProperty = DependencyProperty.Register(
|
||||
"ErrorStr", typeof(string), typeof(DateTimePicker), new PropertyMetadata(default(string)));
|
||||
|
||||
public string ErrorStr
|
||||
{
|
||||
get => (string) GetValue(ErrorStrProperty);
|
||||
set => SetValue(ErrorStrProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextTypeProperty = DependencyProperty.Register(
|
||||
"TextType", typeof(TextType), typeof(DateTimePicker), new PropertyMetadata(default(TextType)));
|
||||
|
||||
public TextType TextType
|
||||
{
|
||||
get => (TextType) GetValue(TextTypeProperty);
|
||||
set => SetValue(TextTypeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowClearButtonProperty = DependencyProperty.Register(
|
||||
"ShowClearButton", typeof(bool), typeof(DateTimePicker), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowClearButton
|
||||
{
|
||||
get => (bool) GetValue(ShowClearButtonProperty);
|
||||
set => SetValue(ShowClearButtonProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (DesignerProperties.GetIsInDesignMode(this)) return;
|
||||
if (_popUp != null)
|
||||
{
|
||||
_popUp.PreviewMouseLeftButtonDown -= PopUp_PreviewMouseLeftButtonDown;
|
||||
_popUp.Opened -= PopUp_Opened;
|
||||
_popUp.Closed -= PopUp_Closed;
|
||||
_popUp.Child = null;
|
||||
}
|
||||
|
||||
if (_dropDownButton != null)
|
||||
{
|
||||
_dropDownButton.Click -= DropDownButton_Click;
|
||||
_dropDownButton.MouseLeave -= DropDownButton_MouseLeave;
|
||||
}
|
||||
|
||||
if (_textBox != null)
|
||||
{
|
||||
_textBox.KeyDown -= TextBox_KeyDown;
|
||||
_textBox.TextChanged -= TextBox_TextChanged;
|
||||
_textBox.LostFocus -= TextBox_LostFocus;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_popUp = GetTemplateChild(ElementPopup) as Popup;
|
||||
_dropDownButton = GetTemplateChild(ElementButton) as Button;
|
||||
_textBox = GetTemplateChild(ElementTextBox) as WatermarkTextBox;
|
||||
|
||||
CheckNull();
|
||||
|
||||
_popUp.PreviewMouseLeftButtonDown += PopUp_PreviewMouseLeftButtonDown;
|
||||
_popUp.Opened += PopUp_Opened;
|
||||
_popUp.Closed += PopUp_Closed;
|
||||
_popUp.Child = _calendarWithClock;
|
||||
|
||||
if (IsDropDownOpen)
|
||||
{
|
||||
_popUp.IsOpen = true;
|
||||
}
|
||||
|
||||
_dropDownButton.Click += DropDownButton_Click;
|
||||
_dropDownButton.MouseLeave += DropDownButton_MouseLeave;
|
||||
if (SelectedDateTime == null)
|
||||
{
|
||||
_textBox.Text = DateTime.Now.ToString(DateTimeFormat);
|
||||
}
|
||||
_textBox.KeyDown += TextBox_KeyDown;
|
||||
_textBox.TextChanged += TextBox_TextChanged;
|
||||
_textBox.LostFocus += TextBox_LostFocus;
|
||||
|
||||
if (SelectedDateTime == null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_defaultText))
|
||||
{
|
||||
_textBox.Text = _defaultText;
|
||||
SetSelectedDateTime();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_textBox.Text = DateTimeToString(SelectedDateTime.Value);
|
||||
}
|
||||
|
||||
if (_originalSelectedDateTime == null)
|
||||
{
|
||||
_originalSelectedDateTime = DateTime.Now;
|
||||
}
|
||||
SetCurrentValue(DisplayDateTimeProperty, _originalSelectedDateTime);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public override string ToString() => SelectedDateTime?.ToString(DateTimeFormat) ?? string.Empty;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected virtual void OnPickerClosed(RoutedEventArgs e)
|
||||
{
|
||||
var handler = PickerClosed;
|
||||
handler?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnPickerOpened(RoutedEventArgs e)
|
||||
{
|
||||
var handler = PickerOpened;
|
||||
handler?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
if (_dropDownButton == null || _popUp == null || _textBox == null)
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
private void InitCalendarWithClock()
|
||||
{
|
||||
_calendarWithClock = new CalendarWithClock
|
||||
{
|
||||
ShowConfirmButton = true
|
||||
};
|
||||
_calendarWithClock.SelectedDateTimeChanged += CalendarWithClock_SelectedDateTimeChanged;
|
||||
_calendarWithClock.Confirmed += CalendarWithClock_Confirmed;
|
||||
}
|
||||
|
||||
private void CalendarWithClock_Confirmed() => TogglePopUp();
|
||||
|
||||
private void CalendarWithClock_SelectedDateTimeChanged(object sender, FunctionEventArgs<DateTime?> e) => SelectedDateTime = e.Info;
|
||||
|
||||
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetSelectedDateTime();
|
||||
}
|
||||
|
||||
private void SetIsHandlerSuspended(DependencyProperty property, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
if (_isHandlerSuspended == null)
|
||||
{
|
||||
_isHandlerSuspended = new Dictionary<DependencyProperty, bool>(2);
|
||||
}
|
||||
|
||||
_isHandlerSuspended[property] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isHandlerSuspended?.Remove(property);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueNoCallback(DependencyProperty property, object value)
|
||||
{
|
||||
SetIsHandlerSuspended(property, true);
|
||||
try
|
||||
{
|
||||
SetCurrentValue(property, value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetIsHandlerSuspended(property, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
SetValueNoCallback(TextProperty, _textBox.Text);
|
||||
VerifyData();
|
||||
}
|
||||
|
||||
private bool ProcessDateTimePickerKey(KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.System:
|
||||
{
|
||||
switch (e.SystemKey)
|
||||
{
|
||||
case Key.Down:
|
||||
{
|
||||
if ((Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt)
|
||||
{
|
||||
TogglePopUp();
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Key.Enter:
|
||||
{
|
||||
SetSelectedDateTime();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TextBox_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
e.Handled = ProcessDateTimePickerKey(e) || e.Handled;
|
||||
}
|
||||
|
||||
private void DropDownButton_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
_disablePopupReopen = false;
|
||||
}
|
||||
|
||||
private bool IsHandlerSuspended(DependencyProperty property)
|
||||
{
|
||||
return _isHandlerSuspended != null && _isHandlerSuspended.ContainsKey(property);
|
||||
}
|
||||
|
||||
private void PopUp_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is Popup popup && !popup.StaysOpen)
|
||||
{
|
||||
if (_dropDownButton?.InputHitTest(e.GetPosition(_dropDownButton)) != null)
|
||||
{
|
||||
_disablePopupReopen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void PopUp_Opened(object sender, EventArgs e)
|
||||
{
|
||||
if (!IsDropDownOpen)
|
||||
{
|
||||
SetCurrentValue(IsDropDownOpenProperty, ValueBoxes.TrueBox);
|
||||
}
|
||||
|
||||
_calendarWithClock?.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
|
||||
|
||||
OnPickerOpened(new RoutedEventArgs());
|
||||
}
|
||||
|
||||
private void PopUp_Closed(object sender, EventArgs e)
|
||||
{
|
||||
if (IsDropDownOpen)
|
||||
{
|
||||
SetCurrentValue(IsDropDownOpenProperty, ValueBoxes.FalseBox);
|
||||
}
|
||||
|
||||
if (_calendarWithClock.IsKeyboardFocusWithin)
|
||||
{
|
||||
MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
|
||||
}
|
||||
|
||||
OnPickerClosed(new RoutedEventArgs());
|
||||
}
|
||||
|
||||
private void DropDownButton_Click(object sender, RoutedEventArgs e) => TogglePopUp();
|
||||
|
||||
private void TogglePopUp()
|
||||
{
|
||||
if (IsDropDownOpen)
|
||||
{
|
||||
SetCurrentValue(IsDropDownOpenProperty, ValueBoxes.FalseBox);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_disablePopupReopen)
|
||||
{
|
||||
_disablePopupReopen = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSelectedDateTime();
|
||||
SetCurrentValue(IsDropDownOpenProperty, ValueBoxes.TrueBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SafeSetText(string s)
|
||||
{
|
||||
if (string.Compare(Text, s, StringComparison.Ordinal) != 0)
|
||||
{
|
||||
SetCurrentValue(TextProperty, s);
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime? ParseText(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DateTime.Parse(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private DateTime? SetTextBoxValue(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
{
|
||||
SafeSetText(s);
|
||||
return SelectedDateTime;
|
||||
}
|
||||
|
||||
var d = ParseText(s);
|
||||
|
||||
if (d != null)
|
||||
{
|
||||
SafeSetText(DateTimeToString((DateTime)d));
|
||||
return d;
|
||||
}
|
||||
|
||||
if (SelectedDateTime != null)
|
||||
{
|
||||
var newtext = DateTimeToString(SelectedDateTime.Value);
|
||||
SafeSetText(newtext);
|
||||
return SelectedDateTime;
|
||||
}
|
||||
SafeSetText(DateTimeToString(DisplayDateTime));
|
||||
return DisplayDateTime;
|
||||
}
|
||||
|
||||
private void SetSelectedDateTime()
|
||||
{
|
||||
if (_textBox != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_textBox.Text))
|
||||
{
|
||||
var s = _textBox.Text;
|
||||
|
||||
if (SelectedDateTime != null)
|
||||
{
|
||||
var selectedTime = DateTimeToString(SelectedDateTime.Value);
|
||||
|
||||
if (string.Compare(selectedTime, s, StringComparison.Ordinal) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var d = SetTextBoxValue(s);
|
||||
if (!SelectedDateTime.Equals(d))
|
||||
{
|
||||
SetCurrentValue(SelectedDateTimeProperty, d);
|
||||
SetCurrentValue(DisplayDateTimeProperty, d);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SelectedDateTime.HasValue)
|
||||
{
|
||||
SetCurrentValue(SelectedDateTimeProperty, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var d = SetTextBoxValue(_defaultText);
|
||||
if (!SelectedDateTime.Equals(d))
|
||||
{
|
||||
SetCurrentValue(SelectedDateTimeProperty, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string DateTimeToString(DateTime d) => d.ToString(DateTimeFormat);
|
||||
|
||||
private static void OnGotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker = (DateTimePicker)sender;
|
||||
if (!e.Handled && picker._textBox != null)
|
||||
{
|
||||
if (Equals(e.OriginalSource, picker))
|
||||
{
|
||||
picker._textBox.Focus();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (Equals(e.OriginalSource, picker._textBox))
|
||||
{
|
||||
picker._textBox.SelectAll();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -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,170 @@
|
||||
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,130 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class SearchBar : TextBox, ICommandSource
|
||||
{
|
||||
public SearchBar()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Search, (s, e) => OnSearchStarted()));
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent SearchStartedEvent =
|
||||
EventManager.RegisterRoutedEvent("SearchStarted", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<string>>), typeof(SearchBar));
|
||||
|
||||
public event EventHandler<FunctionEventArgs<string>> SearchStarted
|
||||
{
|
||||
add => AddHandler(SearchStartedEvent, value);
|
||||
remove => RemoveHandler(SearchStartedEvent, value);
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnKeyDown(e);
|
||||
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
OnSearchStarted();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnTextChanged(TextChangedEventArgs e)
|
||||
{
|
||||
base.OnTextChanged(e);
|
||||
|
||||
if (IsRealTime)
|
||||
{
|
||||
OnSearchStarted();
|
||||
}
|
||||
VerifyData();
|
||||
}
|
||||
|
||||
private void OnSearchStarted()
|
||||
{
|
||||
RaiseEvent(new FunctionEventArgs<string>(SearchStartedEvent, this)
|
||||
{
|
||||
Info = Text
|
||||
});
|
||||
|
||||
if (Command == null) return;
|
||||
if (Command is RoutedCommand command)
|
||||
{
|
||||
command.Execute(CommandParameter, CommandTarget);
|
||||
}
|
||||
else
|
||||
{
|
||||
Command.Execute(CommandParameter);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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 static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
|
||||
"Command", typeof(ICommand), typeof(SearchBar), new PropertyMetadata(default(ICommand), OnCommandChanged));
|
||||
|
||||
private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (SearchBar)d;
|
||||
if (e.OldValue is ICommand oldCommand)
|
||||
{
|
||||
oldCommand.CanExecuteChanged -= ctl.CanExecuteChanged;
|
||||
}
|
||||
if (e.NewValue is ICommand newCommand)
|
||||
{
|
||||
newCommand.CanExecuteChanged += ctl.CanExecuteChanged;
|
||||
}
|
||||
}
|
||||
|
||||
public ICommand Command
|
||||
{
|
||||
get => (ICommand) GetValue(CommandProperty);
|
||||
set => SetValue(CommandProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register(
|
||||
"CommandParameter", typeof(object), typeof(SearchBar), new PropertyMetadata(default(object)));
|
||||
|
||||
public object CommandParameter
|
||||
{
|
||||
get => GetValue(CommandParameterProperty);
|
||||
set => SetValue(CommandParameterProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(
|
||||
"CommandTarget", typeof(IInputElement), typeof(SearchBar), new PropertyMetadata(default(IInputElement)));
|
||||
|
||||
public IInputElement CommandTarget
|
||||
{
|
||||
get => (IInputElement) GetValue(CommandTargetProperty);
|
||||
set => SetValue(CommandTargetProperty, value);
|
||||
}
|
||||
|
||||
private void CanExecuteChanged(object sender, EventArgs e)
|
||||
{
|
||||
if (Command == null) return;
|
||||
|
||||
IsEnabled = Command is RoutedCommand command
|
||||
? command.CanExecute(CommandParameter, CommandTarget)
|
||||
: Command.CanExecute(CommandParameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,718 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 时间选择器
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementRoot, Type = typeof(Grid))]
|
||||
[TemplatePart(Name = ElementTextBox, Type = typeof(WatermarkTextBox))]
|
||||
[TemplatePart(Name = ElementButton, Type = typeof(Button))]
|
||||
[TemplatePart(Name = ElementPopup, Type = typeof(Popup))]
|
||||
public class TimePicker : Control, IDataInput
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementRoot = "PART_Root";
|
||||
|
||||
private const string ElementTextBox = "PART_TextBox";
|
||||
|
||||
private const string ElementButton = "PART_Button";
|
||||
|
||||
private const string ElementPopup = "PART_Popup";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Clock _clock;
|
||||
|
||||
private string _defaultText;
|
||||
|
||||
private ButtonBase _dropDownButton;
|
||||
|
||||
private Popup _popUp;
|
||||
|
||||
private bool _disablePopupReopen;
|
||||
|
||||
private WatermarkTextBox _textBox;
|
||||
|
||||
private IDictionary<DependencyProperty, bool> _isHandlerSuspended;
|
||||
|
||||
private DateTime? _originalSelectedTime;
|
||||
|
||||
#endregion Data
|
||||
|
||||
#region Public Events
|
||||
|
||||
public static readonly RoutedEvent SelectedTimeChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("SelectedTimeChanged", RoutingStrategy.Direct,
|
||||
typeof(EventHandler<FunctionEventArgs<DateTime?>>), typeof(TimePicker));
|
||||
|
||||
public event EventHandler<FunctionEventArgs<DateTime?>> SelectedTimeChanged
|
||||
{
|
||||
add => AddHandler(SelectedTimeChangedEvent, value);
|
||||
remove => RemoveHandler(SelectedTimeChangedEvent, value);
|
||||
}
|
||||
|
||||
public event RoutedEventHandler ClockClosed;
|
||||
|
||||
public event RoutedEventHandler ClockOpened;
|
||||
|
||||
#endregion Public Events
|
||||
|
||||
static TimePicker()
|
||||
{
|
||||
EventManager.RegisterClassHandler(typeof(TimePicker), GotFocusEvent, new RoutedEventHandler(OnGotFocus));
|
||||
KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(TimePicker), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
|
||||
KeyboardNavigation.IsTabStopProperty.OverrideMetadata(typeof(TimePicker), new FrameworkPropertyMetadata(false));
|
||||
}
|
||||
|
||||
public TimePicker()
|
||||
{
|
||||
InitClock();
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Clear, (s, e) =>
|
||||
{
|
||||
ClearValue(SelectedTimeProperty);
|
||||
ClearValue(TextProperty);
|
||||
_textBox.Text = string.Empty;
|
||||
}));
|
||||
}
|
||||
|
||||
#region Public Properties
|
||||
|
||||
#region TimeFormat
|
||||
|
||||
public static readonly DependencyProperty TimeFormatProperty = DependencyProperty.Register(
|
||||
"TimeFormat", typeof(string), typeof(TimePicker), new PropertyMetadata("HH:mm:ss"));
|
||||
|
||||
public string TimeFormat
|
||||
{
|
||||
get => (string)GetValue(TimeFormatProperty);
|
||||
set => SetValue(TimeFormatProperty, value);
|
||||
}
|
||||
|
||||
#endregion TimeFormat
|
||||
|
||||
#region ClockStyle
|
||||
|
||||
public Style ClockStyle
|
||||
{
|
||||
get => (Style)GetValue(ClockStyleProperty);
|
||||
set => SetValue(ClockStyleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ClockStyleProperty = DependencyProperty.Register("ClockStyle", typeof(Style), typeof(TimePicker));
|
||||
|
||||
#endregion CalendarStyle
|
||||
|
||||
#region DisplayTime
|
||||
|
||||
public DateTime DisplayTime
|
||||
{
|
||||
get => (DateTime)GetValue(DisplayTimeProperty);
|
||||
set => SetValue(DisplayTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DisplayTimeProperty =
|
||||
DependencyProperty.Register(
|
||||
"DisplayTime",
|
||||
typeof(DateTime),
|
||||
typeof(TimePicker),
|
||||
new FrameworkPropertyMetadata(DateTime.Now, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceDisplayTime));
|
||||
|
||||
private static object CoerceDisplayTime(DependencyObject d, object value)
|
||||
{
|
||||
var dp = (TimePicker)d;
|
||||
dp._clock.DisplayTime = (DateTime)value;
|
||||
return dp._clock.DisplayTime;
|
||||
}
|
||||
|
||||
#endregion DisplayTime
|
||||
|
||||
#region IsDropDownOpen
|
||||
|
||||
public bool IsDropDownOpen
|
||||
{
|
||||
get => (bool)GetValue(IsDropDownOpenProperty);
|
||||
set => SetValue(IsDropDownOpenProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsDropDownOpenProperty =
|
||||
DependencyProperty.Register(
|
||||
"IsDropDownOpen",
|
||||
typeof(bool),
|
||||
typeof(TimePicker),
|
||||
new FrameworkPropertyMetadata(ValueBoxes.FalseBox, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnIsDropDownOpenChanged, OnCoerceIsDropDownOpen));
|
||||
|
||||
private static object OnCoerceIsDropDownOpen(DependencyObject d, object baseValue) => d is TimePicker dp && !dp.IsEnabled ? false : baseValue;
|
||||
|
||||
private static void OnIsDropDownOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var dp = d as TimePicker;
|
||||
|
||||
var newValue = (bool)e.NewValue;
|
||||
if (dp?._popUp != null && dp._popUp.IsOpen != newValue)
|
||||
{
|
||||
dp._popUp.IsOpen = newValue;
|
||||
if (newValue)
|
||||
{
|
||||
dp._originalSelectedTime = dp.SelectedTime;
|
||||
|
||||
dp.Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action)delegate
|
||||
{
|
||||
dp._clock.Focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion IsDropDownOpen
|
||||
|
||||
#region SelectedTime
|
||||
|
||||
public DateTime? SelectedTime
|
||||
{
|
||||
get => (DateTime?)GetValue(SelectedTimeProperty);
|
||||
set => SetValue(SelectedTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SelectedTimeProperty =
|
||||
DependencyProperty.Register(
|
||||
"SelectedTime",
|
||||
typeof(DateTime?),
|
||||
typeof(TimePicker),
|
||||
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedTimeChanged, CoerceSelectedTime));
|
||||
|
||||
private static void OnSelectedTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!(d is TimePicker dp)) return;
|
||||
|
||||
if (dp.SelectedTime.HasValue)
|
||||
{
|
||||
var time = dp.SelectedTime.Value;
|
||||
dp.SetTextInternal(dp.DateTimeToString(time));
|
||||
}
|
||||
|
||||
dp.RaiseEvent(new FunctionEventArgs<DateTime?>(SelectedTimeChangedEvent, dp)
|
||||
{
|
||||
Info = dp.SelectedTime
|
||||
});
|
||||
}
|
||||
|
||||
private static object CoerceSelectedTime(DependencyObject d, object value)
|
||||
{
|
||||
var dp = (TimePicker)d;
|
||||
dp._clock.SelectedTime = (DateTime?)value;
|
||||
return dp._clock.SelectedTime;
|
||||
}
|
||||
|
||||
#endregion SelectedDate
|
||||
|
||||
#region Text
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty =
|
||||
DependencyProperty.Register(
|
||||
"Text",
|
||||
typeof(string),
|
||||
typeof(TimePicker),
|
||||
new FrameworkPropertyMetadata(string.Empty, OnTextChanged));
|
||||
|
||||
private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is TimePicker dp && !dp.IsHandlerSuspended(TextProperty))
|
||||
{
|
||||
if (e.NewValue is string newValue)
|
||||
{
|
||||
if (dp._textBox != null)
|
||||
{
|
||||
dp._textBox.Text = newValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
dp._defaultText = newValue;
|
||||
}
|
||||
|
||||
dp.SetSelectedTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
dp.SetValueNoCallback(SelectedTimeProperty, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the local Text property without breaking bindings
|
||||
/// </summary>
|
||||
/// <param name="value"></param>
|
||||
private void SetTextInternal(string value)
|
||||
{
|
||||
SetCurrentValue(TextProperty, value);
|
||||
}
|
||||
|
||||
#endregion Text
|
||||
|
||||
public Func<string, OperationResult<bool>> VerifyFunc { get; set; }
|
||||
|
||||
public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register(
|
||||
"IsError", typeof(bool), typeof(TimePicker), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsError
|
||||
{
|
||||
get => (bool) GetValue(IsErrorProperty);
|
||||
set => SetValue(IsErrorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ErrorStrProperty = DependencyProperty.Register(
|
||||
"ErrorStr", typeof(string), typeof(TimePicker), new PropertyMetadata(default(string)));
|
||||
|
||||
public string ErrorStr
|
||||
{
|
||||
get => (string) GetValue(ErrorStrProperty);
|
||||
set => SetValue(ErrorStrProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextTypeProperty = DependencyProperty.Register(
|
||||
"TextType", typeof(TextType), typeof(TimePicker), new PropertyMetadata(default(TextType)));
|
||||
|
||||
public TextType TextType
|
||||
{
|
||||
get => (TextType) GetValue(TextTypeProperty);
|
||||
set => SetValue(TextTypeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowClearButtonProperty = DependencyProperty.Register(
|
||||
"ShowClearButton", typeof(bool), typeof(TimePicker), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowClearButton
|
||||
{
|
||||
get => (bool) GetValue(ShowClearButtonProperty);
|
||||
set => SetValue(ShowClearButtonProperty, value);
|
||||
}
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
#region Public Methods
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (DesignerProperties.GetIsInDesignMode(this)) return;
|
||||
if (_popUp != null)
|
||||
{
|
||||
_popUp.PreviewMouseLeftButtonDown -= PopUp_PreviewMouseLeftButtonDown;
|
||||
_popUp.Opened -= PopUp_Opened;
|
||||
_popUp.Closed -= PopUp_Closed;
|
||||
_popUp.Child = null;
|
||||
}
|
||||
|
||||
if (_dropDownButton != null)
|
||||
{
|
||||
_dropDownButton.Click -= DropDownButton_Click;
|
||||
_dropDownButton.MouseLeave -= DropDownButton_MouseLeave;
|
||||
}
|
||||
|
||||
if (_textBox != null)
|
||||
{
|
||||
_textBox.KeyDown -= TextBox_KeyDown;
|
||||
_textBox.TextChanged -= TextBox_TextChanged;
|
||||
_textBox.LostFocus -= TextBox_LostFocus;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_popUp = GetTemplateChild(ElementPopup) as Popup;
|
||||
_dropDownButton = GetTemplateChild(ElementButton) as Button;
|
||||
_textBox = GetTemplateChild(ElementTextBox) as WatermarkTextBox;
|
||||
|
||||
CheckNull();
|
||||
|
||||
_popUp.PreviewMouseLeftButtonDown += PopUp_PreviewMouseLeftButtonDown;
|
||||
_popUp.Opened += PopUp_Opened;
|
||||
_popUp.Closed += PopUp_Closed;
|
||||
_popUp.Child = _clock;
|
||||
|
||||
if (IsDropDownOpen)
|
||||
{
|
||||
_popUp.IsOpen = true;
|
||||
}
|
||||
|
||||
_dropDownButton.Click += DropDownButton_Click;
|
||||
_dropDownButton.MouseLeave += DropDownButton_MouseLeave;
|
||||
if (SelectedTime == null)
|
||||
{
|
||||
_textBox.Text = DateTime.Now.ToString(TimeFormat);
|
||||
}
|
||||
_textBox.KeyDown += TextBox_KeyDown;
|
||||
_textBox.TextChanged += TextBox_TextChanged;
|
||||
_textBox.LostFocus += TextBox_LostFocus;
|
||||
|
||||
if (SelectedTime == null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_defaultText))
|
||||
{
|
||||
_textBox.Text = _defaultText;
|
||||
SetSelectedTime();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_textBox.Text = DateTimeToString((DateTime)SelectedTime);
|
||||
}
|
||||
|
||||
if (_originalSelectedTime == null)
|
||||
{
|
||||
_originalSelectedTime = DateTime.Now;
|
||||
}
|
||||
SetCurrentValue(DisplayTimeProperty, _originalSelectedTime);
|
||||
}
|
||||
|
||||
public override string ToString() => SelectedTime?.ToString(TimeFormat) ?? string.Empty;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected virtual void OnClockClosed(RoutedEventArgs e)
|
||||
{
|
||||
var handler = ClockClosed;
|
||||
handler?.Invoke(this, e);
|
||||
}
|
||||
|
||||
protected virtual void OnClockOpened(RoutedEventArgs e)
|
||||
{
|
||||
var handler = ClockOpened;
|
||||
handler?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
if (_dropDownButton == null || _popUp == null || _textBox == null)
|
||||
throw new Exception();
|
||||
}
|
||||
|
||||
private void InitClock()
|
||||
{
|
||||
_clock = new Clock
|
||||
{
|
||||
ShowConfirmButton = true
|
||||
};
|
||||
_clock.SelectedTimeChanged += Clock_SelectedTimeChanged;
|
||||
_clock.Confirmed += Clock_Confirmed;
|
||||
}
|
||||
|
||||
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SetSelectedTime();
|
||||
}
|
||||
|
||||
private void SetIsHandlerSuspended(DependencyProperty property, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
if (_isHandlerSuspended == null)
|
||||
{
|
||||
_isHandlerSuspended = new Dictionary<DependencyProperty, bool>(2);
|
||||
}
|
||||
|
||||
_isHandlerSuspended[property] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isHandlerSuspended?.Remove(property);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueNoCallback(DependencyProperty property, object value)
|
||||
{
|
||||
SetIsHandlerSuspended(property, true);
|
||||
try
|
||||
{
|
||||
SetCurrentValue(property, value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetIsHandlerSuspended(property, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
SetValueNoCallback(TextProperty, _textBox.Text);
|
||||
VerifyData();
|
||||
}
|
||||
|
||||
private bool ProcessTimePickerKey(KeyEventArgs e)
|
||||
{
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.System:
|
||||
{
|
||||
switch (e.SystemKey)
|
||||
{
|
||||
case Key.Down:
|
||||
{
|
||||
if ((Keyboard.Modifiers & ModifierKeys.Alt) == ModifierKeys.Alt)
|
||||
{
|
||||
TogglePopUp();
|
||||
return true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case Key.Enter:
|
||||
{
|
||||
SetSelectedTime();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void TextBox_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
e.Handled = ProcessTimePickerKey(e) || e.Handled;
|
||||
}
|
||||
|
||||
private void DropDownButton_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
_disablePopupReopen = false;
|
||||
}
|
||||
|
||||
private bool IsHandlerSuspended(DependencyProperty property)
|
||||
{
|
||||
return _isHandlerSuspended != null && _isHandlerSuspended.ContainsKey(property);
|
||||
}
|
||||
|
||||
private void PopUp_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is Popup popup && !popup.StaysOpen)
|
||||
{
|
||||
if (_dropDownButton?.InputHitTest(e.GetPosition(_dropDownButton)) != null)
|
||||
{
|
||||
_disablePopupReopen = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Clock_SelectedTimeChanged(object sender, FunctionEventArgs<DateTime?> e) => SelectedTime = e.Info;
|
||||
|
||||
private void Clock_Confirmed() => TogglePopUp();
|
||||
|
||||
private void PopUp_Opened(object sender, EventArgs e)
|
||||
{
|
||||
if (!IsDropDownOpen)
|
||||
{
|
||||
SetCurrentValue(IsDropDownOpenProperty, ValueBoxes.TrueBox);
|
||||
}
|
||||
|
||||
_clock?.MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
|
||||
|
||||
OnClockOpened(new RoutedEventArgs());
|
||||
}
|
||||
|
||||
private void PopUp_Closed(object sender, EventArgs e)
|
||||
{
|
||||
if (IsDropDownOpen)
|
||||
{
|
||||
SetCurrentValue(IsDropDownOpenProperty, ValueBoxes.FalseBox);
|
||||
}
|
||||
|
||||
if (_clock.IsKeyboardFocusWithin)
|
||||
{
|
||||
MoveFocus(new TraversalRequest(FocusNavigationDirection.First));
|
||||
}
|
||||
|
||||
OnClockClosed(new RoutedEventArgs());
|
||||
}
|
||||
|
||||
private void DropDownButton_Click(object sender, RoutedEventArgs e) => TogglePopUp();
|
||||
|
||||
private void TogglePopUp()
|
||||
{
|
||||
if (IsDropDownOpen)
|
||||
{
|
||||
SetCurrentValue(IsDropDownOpenProperty, ValueBoxes.FalseBox);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_disablePopupReopen)
|
||||
{
|
||||
_disablePopupReopen = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetSelectedTime();
|
||||
SetCurrentValue(IsDropDownOpenProperty, ValueBoxes.TrueBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SafeSetText(string s)
|
||||
{
|
||||
if (string.Compare(Text, s, StringComparison.Ordinal) != 0)
|
||||
{
|
||||
SetCurrentValue(TextProperty, s);
|
||||
}
|
||||
}
|
||||
|
||||
private DateTime? ParseText(string text)
|
||||
{
|
||||
try
|
||||
{
|
||||
return DateTime.Parse(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private DateTime? SetTextBoxValue(string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
{
|
||||
SafeSetText(s);
|
||||
return SelectedTime;
|
||||
}
|
||||
|
||||
var d = ParseText(s);
|
||||
|
||||
if (d != null)
|
||||
{
|
||||
SafeSetText(DateTimeToString((DateTime)d));
|
||||
return d;
|
||||
}
|
||||
|
||||
if (SelectedTime != null)
|
||||
{
|
||||
var newtext = DateTimeToString((DateTime) SelectedTime);
|
||||
SafeSetText(newtext);
|
||||
return SelectedTime;
|
||||
}
|
||||
SafeSetText(DateTimeToString(DisplayTime));
|
||||
return DisplayTime;
|
||||
}
|
||||
|
||||
private void SetSelectedTime()
|
||||
{
|
||||
if (_textBox != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_textBox.Text))
|
||||
{
|
||||
var s = _textBox.Text;
|
||||
|
||||
if (SelectedTime != null)
|
||||
{
|
||||
var selectedTime = DateTimeToString(SelectedTime.Value);
|
||||
|
||||
if (string.Compare(selectedTime, s, StringComparison.Ordinal) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var d = SetTextBoxValue(s);
|
||||
if (!SelectedTime.Equals(d))
|
||||
{
|
||||
SetCurrentValue(SelectedTimeProperty, d);
|
||||
SetCurrentValue(DisplayTimeProperty, d);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (SelectedTime.HasValue)
|
||||
{
|
||||
SetCurrentValue(SelectedTimeProperty, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var d = SetTextBoxValue(_defaultText);
|
||||
if (!SelectedTime.Equals(d))
|
||||
{
|
||||
SetCurrentValue(SelectedTimeProperty, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string DateTimeToString(DateTime d) => d.ToString(TimeFormat);
|
||||
|
||||
private static void OnGotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var picker = (TimePicker)sender;
|
||||
if (!e.Handled && picker._textBox != null)
|
||||
{
|
||||
if (Equals(e.OriginalSource, picker))
|
||||
{
|
||||
picker._textBox.Focus();
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (Equals(e.OriginalSource, picker._textBox))
|
||||
{
|
||||
picker._textBox.SelectAll();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
internal class WatermarkTextBox : TextBox
|
||||
{
|
||||
#region Public Properties
|
||||
|
||||
#region Watermark
|
||||
|
||||
public static readonly DependencyProperty WatermarkProperty = DependencyProperty.Register(
|
||||
"Watermark", typeof(object), typeof(WatermarkTextBox), new PropertyMetadata(default(object)));
|
||||
|
||||
public object Watermark
|
||||
{
|
||||
get => GetValue(WatermarkProperty);
|
||||
set => SetValue(WatermarkProperty, value);
|
||||
}
|
||||
|
||||
#endregion Watermark
|
||||
|
||||
#endregion Public Properties
|
||||
|
||||
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,202 @@
|
||||
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 EasingDoubleKeyFrame
|
||||
{
|
||||
EasingFunction = new PowerEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
},
|
||||
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 EasingDoubleKeyFrame
|
||||
{
|
||||
EasingFunction = new PowerEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseIn
|
||||
},
|
||||
Value = 360 + subAngle,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromSeconds(DotSpeed * (3.5 / 7)))
|
||||
};
|
||||
|
||||
//匀加速结束到匀减速结束
|
||||
var frame4 = new EasingDoubleKeyFrame
|
||||
{
|
||||
EasingFunction = new PowerEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
},
|
||||
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 EasingDoubleKeyFrame
|
||||
{
|
||||
EasingFunction = new PowerEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseIn
|
||||
},
|
||||
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,106 @@
|
||||
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 EasingThicknessKeyFrame
|
||||
{
|
||||
EasingFunction = new PowerEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
},
|
||||
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 EasingThicknessKeyFrame
|
||||
{
|
||||
EasingFunction = new PowerEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseIn
|
||||
},
|
||||
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,210 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Shapes;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Expression.Drawing;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class AnimationPath : Shape
|
||||
{
|
||||
private bool _isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// 故事版
|
||||
/// </summary>
|
||||
private Storyboard _storyboard;
|
||||
|
||||
/// <summary>
|
||||
/// 路径长度
|
||||
/// </summary>
|
||||
private double _pathLength;
|
||||
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DataProperty = DependencyProperty.Register(nameof(Data),
|
||||
typeof(Geometry), typeof(AnimationPath), new FrameworkPropertyMetadata(null,
|
||||
OnPropertiesChanged));
|
||||
|
||||
private static void OnPropertiesChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is AnimationPath path)
|
||||
{
|
||||
path.UpdatePath();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 路径
|
||||
/// </summary>
|
||||
public Geometry Data
|
||||
{
|
||||
get => (Geometry)GetValue(DataProperty);
|
||||
set => SetValue(DataProperty, value);
|
||||
}
|
||||
|
||||
protected override Geometry DefiningGeometry => Data ?? Geometry.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 路径长度
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PathLengthProperty = DependencyProperty.Register(
|
||||
"PathLength", typeof(double), typeof(AnimationPath), new FrameworkPropertyMetadata(ValueBoxes.Double0Box, OnPropertiesChanged));
|
||||
|
||||
/// <summary>
|
||||
/// 路径长度
|
||||
/// </summary>
|
||||
public double PathLength
|
||||
{
|
||||
get => (double)GetValue(PathLengthProperty);
|
||||
set => SetValue(PathLengthProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画间隔时间
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DurationProperty = DependencyProperty.Register(
|
||||
"Duration", typeof(Duration), typeof(AnimationPath), new FrameworkPropertyMetadata(new Duration(TimeSpan.FromSeconds(2)),
|
||||
OnPropertiesChanged));
|
||||
|
||||
/// <summary>
|
||||
/// 动画间隔时间
|
||||
/// </summary>
|
||||
public Duration Duration
|
||||
{
|
||||
get => (Duration)GetValue(DurationProperty);
|
||||
set => SetValue(DurationProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsPlayingProperty = DependencyProperty.Register(
|
||||
"IsPlaying", typeof(bool), typeof(AnimationPath), new FrameworkPropertyMetadata(true, (o, args) =>
|
||||
{
|
||||
var ctl = (AnimationPath)o;
|
||||
var v = (bool)args.NewValue;
|
||||
if (v)
|
||||
{
|
||||
ctl.UpdatePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctl._storyboard?.Pause();
|
||||
}
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// 是否正在播放动画
|
||||
/// </summary>
|
||||
public bool IsPlaying
|
||||
{
|
||||
get => (bool)GetValue(IsPlayingProperty);
|
||||
set => SetValue(IsPlayingProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty RepeatBehaviorProperty = DependencyProperty.Register(
|
||||
"RepeatBehavior", typeof(RepeatBehavior), typeof(AnimationPath), new PropertyMetadata(RepeatBehavior.Forever));
|
||||
|
||||
/// <summary>
|
||||
/// 动画重复行为
|
||||
/// </summary>
|
||||
public RepeatBehavior RepeatBehavior
|
||||
{
|
||||
get => (RepeatBehavior)GetValue(RepeatBehaviorProperty);
|
||||
set => SetValue(RepeatBehaviorProperty, value);
|
||||
}
|
||||
|
||||
static AnimationPath()
|
||||
{
|
||||
StretchProperty.AddOwner(typeof(AnimationPath), new FrameworkPropertyMetadata(Stretch.Uniform,
|
||||
OnPropertiesChanged));
|
||||
|
||||
StrokeThicknessProperty.AddOwner(typeof(AnimationPath), new FrameworkPropertyMetadata(1.0,
|
||||
OnPropertiesChanged));
|
||||
}
|
||||
|
||||
public AnimationPath()
|
||||
{
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (_isLoaded) return;
|
||||
UpdatePath();
|
||||
_isLoaded = true;
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画完成事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent CompletedEvent =
|
||||
EventManager.RegisterRoutedEvent("Completed", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler), typeof(AnimationPath));
|
||||
|
||||
/// <summary>
|
||||
/// 动画完成事件
|
||||
/// </summary>
|
||||
public event EventHandler Completed
|
||||
{
|
||||
add => AddHandler(CompletedEvent, value);
|
||||
remove => RemoveHandler(CompletedEvent, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新路径
|
||||
/// </summary>
|
||||
private void UpdatePath()
|
||||
{
|
||||
if (!Duration.HasTimeSpan || !IsPlaying) return;
|
||||
|
||||
_pathLength = PathLength > 0 ? PathLength : Data.GetTotalLength(new Size(ActualWidth, ActualHeight), StrokeThickness);
|
||||
|
||||
if (MathHelper.IsVerySmall(_pathLength)) return;
|
||||
|
||||
StrokeDashOffset = _pathLength;
|
||||
StrokeDashArray = new DoubleCollection(new List<double>
|
||||
{
|
||||
_pathLength,
|
||||
_pathLength
|
||||
});
|
||||
|
||||
//定义动画
|
||||
if (_storyboard != null)
|
||||
{
|
||||
_storyboard.Stop();
|
||||
_storyboard.Completed -= Storyboard_Completed;
|
||||
}
|
||||
_storyboard = new Storyboard
|
||||
{
|
||||
RepeatBehavior = RepeatBehavior
|
||||
};
|
||||
_storyboard.Completed += Storyboard_Completed;
|
||||
|
||||
var frames = new DoubleAnimationUsingKeyFrames();
|
||||
//开始位置
|
||||
var frame0 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = _pathLength,
|
||||
KeyTime = KeyTime.FromTimeSpan(TimeSpan.Zero)
|
||||
};
|
||||
//结束位置
|
||||
var frame1 = new LinearDoubleKeyFrame
|
||||
{
|
||||
Value = -_pathLength,
|
||||
KeyTime = KeyTime.FromTimeSpan(Duration.TimeSpan)
|
||||
};
|
||||
frames.KeyFrames.Add(frame0);
|
||||
frames.KeyFrames.Add(frame1);
|
||||
|
||||
Storyboard.SetTarget(frames, this);
|
||||
Storyboard.SetTargetProperty(frames, new PropertyPath(StrokeDashOffsetProperty));
|
||||
_storyboard.Children.Add(frames);
|
||||
|
||||
_storyboard.Begin();
|
||||
}
|
||||
|
||||
private void Storyboard_Completed(object sender, EventArgs e) => RaiseEvent(new RoutedEventArgs(CompletedEvent));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 标记
|
||||
/// </summary>
|
||||
public class Badge : ContentControl
|
||||
{
|
||||
public static readonly RoutedEvent ValueChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<int>>), typeof(Badge));
|
||||
|
||||
public event EventHandler<FunctionEventArgs<int>> ValueChanged
|
||||
{
|
||||
add => AddHandler(ValueChangedEvent, value);
|
||||
remove => RemoveHandler(ValueChangedEvent, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
"Text", typeof(string), typeof(Badge), new PropertyMetadata(default(string)));
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string) GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
|
||||
"Value", typeof(int), typeof(Badge), new PropertyMetadata(ValueBoxes.Int0Box, OnValueChanged));
|
||||
|
||||
private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (Badge)d;
|
||||
var v = (int)e.NewValue;
|
||||
ctl.Text = v <= ctl.Maximum ? v.ToString() : $"{ctl.Maximum}+";
|
||||
if (ctl.IsInitialized)
|
||||
{
|
||||
ctl.RaiseEvent(new FunctionEventArgs<int>(ValueChangedEvent, ctl)
|
||||
{
|
||||
Info = v
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public int Value
|
||||
{
|
||||
get => (int)GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty StatusProperty = DependencyProperty.Register(
|
||||
"Status", typeof(BadgeStatus), typeof(Badge), new PropertyMetadata(default(BadgeStatus)));
|
||||
|
||||
public BadgeStatus Status
|
||||
{
|
||||
get => (BadgeStatus)GetValue(StatusProperty);
|
||||
set => SetValue(StatusProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register(
|
||||
"Maximum", typeof(int), typeof(Badge), new PropertyMetadata(ValueBoxes.Int99Box, OnValueChanged));
|
||||
|
||||
public int Maximum
|
||||
{
|
||||
get => (int)GetValue(MaximumProperty);
|
||||
set => SetValue(MaximumProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty BadgeMarginProperty = DependencyProperty.Register(
|
||||
"BadgeMargin", typeof(Thickness), typeof(Badge), new PropertyMetadata(default(Thickness)));
|
||||
|
||||
public Thickness BadgeMargin
|
||||
{
|
||||
get => (Thickness)GetValue(BadgeMarginProperty);
|
||||
set => SetValue(BadgeMarginProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 轮播控件
|
||||
/// </summary>
|
||||
[DefaultProperty("Items")]
|
||||
[ContentProperty("Items")]
|
||||
[TemplatePart(Name = ElementPanelPage, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementItemsControl, Type = typeof(ItemsPresenter))]
|
||||
public class Carousel : ListBox
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementPanelPage = "PART_PanelPage";
|
||||
private const string ElementItemsControl = "PART_ItemsControl";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Panel _panelPage;
|
||||
|
||||
private bool _appliedTemplate;
|
||||
|
||||
private ItemsPresenter _itemsControl;
|
||||
|
||||
private int _pageIndex = -1;
|
||||
|
||||
private Button _selectedButton;
|
||||
|
||||
private DispatcherTimer _updateTimer;
|
||||
|
||||
private readonly List<double> _widthList = new List<double>();
|
||||
|
||||
#endregion Data
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_appliedTemplate = false;
|
||||
|
||||
_panelPage?.RemoveHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ButtonPages_OnClick));
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_itemsControl = GetTemplateChild(ElementItemsControl) as ItemsPresenter;
|
||||
_panelPage = GetTemplateChild(ElementPanelPage) as Panel;
|
||||
|
||||
CheckNull();
|
||||
|
||||
_panelPage.AddHandler(ButtonBase.ClickEvent, new RoutedEventHandler(ButtonPages_OnClick));
|
||||
_appliedTemplate = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
TimerSwitch(AutoRun);
|
||||
UpdatePageButtons(_pageIndex);
|
||||
}
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
if (_itemsControl == null || _panelPage == null) throw new Exception();
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AutoRunProperty = DependencyProperty.Register(
|
||||
"AutoRun", typeof(bool), typeof(Carousel), new PropertyMetadata(ValueBoxes.FalseBox, (o, args) =>
|
||||
{
|
||||
var ctl = (Carousel)o;
|
||||
ctl.TimerSwitch((bool)args.NewValue);
|
||||
}));
|
||||
|
||||
public static readonly DependencyProperty IntervalProperty = DependencyProperty.Register(
|
||||
"Interval", typeof(TimeSpan), typeof(Carousel), new PropertyMetadata(TimeSpan.FromSeconds(2)));
|
||||
|
||||
public static readonly DependencyProperty ExtendWidthProperty = DependencyProperty.Register(
|
||||
"ExtendWidth", typeof(double), typeof(Carousel), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
public double ExtendWidth
|
||||
{
|
||||
get => (double)GetValue(ExtendWidthProperty);
|
||||
set => SetValue(ExtendWidthProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsCenterProperty = DependencyProperty.Register(
|
||||
"IsCenter", typeof(bool), typeof(Carousel), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsCenter
|
||||
{
|
||||
get => (bool)GetValue(IsCenterProperty);
|
||||
set => SetValue(IsCenterProperty, value);
|
||||
}
|
||||
|
||||
public Carousel()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Prev, ButtonPrev_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Next, ButtonNext_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Selected, ButtonPages_OnClick));
|
||||
|
||||
Loaded += (s, e) => UpdatePageButtons();
|
||||
}
|
||||
|
||||
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
base.OnItemsChanged(e);
|
||||
|
||||
UpdatePageButtons();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否自动跳转
|
||||
/// </summary>
|
||||
public bool AutoRun
|
||||
{
|
||||
get => (bool)GetValue(AutoRunProperty);
|
||||
set => SetValue(AutoRunProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 跳转时间间隔
|
||||
/// </summary>
|
||||
public TimeSpan Interval
|
||||
{
|
||||
get => (TimeSpan)GetValue(IntervalProperty);
|
||||
set => SetValue(IntervalProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 页码
|
||||
/// </summary>
|
||||
public int PageIndex
|
||||
{
|
||||
get => _pageIndex;
|
||||
set
|
||||
{
|
||||
if (Items.Count == 0) return;
|
||||
if (_pageIndex == value) return;
|
||||
if (value < 0)
|
||||
_pageIndex = Items.Count - 1;
|
||||
else if (value >= Items.Count)
|
||||
_pageIndex = 0;
|
||||
else
|
||||
_pageIndex = value;
|
||||
UpdatePageButtons(_pageIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计时器开关
|
||||
/// </summary>
|
||||
private void TimerSwitch(bool run)
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
|
||||
if (_updateTimer != null)
|
||||
{
|
||||
_updateTimer.Tick -= UpdateTimer_Tick;
|
||||
_updateTimer.Stop();
|
||||
_updateTimer = null;
|
||||
}
|
||||
|
||||
if (!run) return;
|
||||
_updateTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = Interval
|
||||
};
|
||||
_updateTimer.Tick += UpdateTimer_Tick;
|
||||
_updateTimer.Start();
|
||||
}
|
||||
|
||||
private void UpdateTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (IsMouseOver) return;
|
||||
PageIndex++;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新页按钮
|
||||
/// </summary>
|
||||
public void UpdatePageButtons(int index = -1)
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
|
||||
var count = Items.Count;
|
||||
_widthList.Clear();
|
||||
_widthList.Add(0);
|
||||
var width = .0;
|
||||
foreach (FrameworkElement item in Items)
|
||||
{
|
||||
item.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
width += item.DesiredSize.Width;
|
||||
_widthList.Add(width);
|
||||
}
|
||||
|
||||
_itemsControl.Width = _widthList.Last() + ExtendWidth;
|
||||
_panelPage.Children.Clear();
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
_panelPage.Children.Add(CreatePateButton());
|
||||
}
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
if (count > 0)
|
||||
{
|
||||
var button = _panelPage.Children[0];
|
||||
button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
|
||||
}
|
||||
}
|
||||
else if (index >= 0 && index < count)
|
||||
{
|
||||
var button = _panelPage.Children[index];
|
||||
button.RaiseEvent(new RoutedEventArgs(ButtonBase.ClickEvent, button));
|
||||
UpdateItemsPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新项的位置
|
||||
/// </summary>
|
||||
private void UpdateItemsPosition()
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
if (Items.Count == 0) return;
|
||||
if (!IsCenter)
|
||||
{
|
||||
_itemsControl.BeginAnimation(MarginProperty,
|
||||
AnimationHelper.CreateAnimation(new Thickness(-_widthList[PageIndex], 0, 0, 0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ctl = (FrameworkElement)Items[PageIndex];
|
||||
var ctlWidth = ctl.DesiredSize.Width;
|
||||
_itemsControl.BeginAnimation(MarginProperty,
|
||||
AnimationHelper.CreateAnimation(
|
||||
new Thickness(-_widthList[PageIndex] + (ActualWidth - ctlWidth) / 2, 0, 0, 0)));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
|
||||
{
|
||||
base.OnRenderSizeChanged(sizeInfo);
|
||||
UpdateItemsPosition();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建页按钮
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Button CreatePateButton()
|
||||
{
|
||||
var button = new Button
|
||||
{
|
||||
Style = ResourceHelper.GetResource<Style>(ResourceToken.ButtonCustom),
|
||||
Content = new Border
|
||||
{
|
||||
Width = 10,
|
||||
Height = 10,
|
||||
CornerRadius = new CornerRadius(5),
|
||||
Background = Brushes.White,
|
||||
Margin = new Thickness(5, 0, 5, 0),
|
||||
BorderThickness = new Thickness(1),
|
||||
BorderBrush = ResourceHelper.GetResource<Brush>(ResourceToken.PrimaryBrush)
|
||||
}
|
||||
};
|
||||
return button;
|
||||
}
|
||||
|
||||
private void ButtonPages_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedButton != null && _selectedButton.Content is Border borderOri)
|
||||
borderOri.Background = Brushes.White;
|
||||
_selectedButton = e.OriginalSource as Button;
|
||||
if (_selectedButton != null && _selectedButton.Content is Border border)
|
||||
border.Background = ResourceHelper.GetResource<Brush>(ResourceToken.PrimaryBrush);
|
||||
var index = _panelPage.Children.IndexOf(_selectedButton);
|
||||
if (index != -1)
|
||||
PageIndex = index;
|
||||
}
|
||||
|
||||
private void ButtonPrev_OnClick(object sender, RoutedEventArgs e) => PageIndex--;
|
||||
|
||||
private void ButtonNext_OnClick(object sender, RoutedEventArgs e) => PageIndex++;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class ChatBubble : SelectableItem
|
||||
{
|
||||
public static readonly DependencyProperty RoleProperty = DependencyProperty.Register(
|
||||
"Role", typeof(ChatRoleType), typeof(ChatBubble), new PropertyMetadata(default(ChatRoleType)));
|
||||
|
||||
public ChatRoleType Role
|
||||
{
|
||||
get => (ChatRoleType) GetValue(RoleProperty);
|
||||
set => SetValue(RoleProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register(
|
||||
"Type", typeof(ChatMessageType), typeof(ChatBubble), new PropertyMetadata(default(ChatMessageType)));
|
||||
|
||||
public ChatMessageType Type
|
||||
{
|
||||
get => (ChatMessageType) GetValue(TypeProperty);
|
||||
set => SetValue(TypeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsReadProperty = DependencyProperty.Register(
|
||||
"IsRead", typeof(bool), typeof(ChatBubble), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsRead
|
||||
{
|
||||
get => (bool) GetValue(IsReadProperty);
|
||||
set => SetValue(IsReadProperty, value);
|
||||
}
|
||||
|
||||
public static void SetMaxWidth(DependencyObject element, double value)
|
||||
=> element.SetValue(MaxWidthProperty, value);
|
||||
|
||||
public static double GetMaxWidth(DependencyObject element)
|
||||
=> (double) element.GetValue(MaxWidthProperty);
|
||||
|
||||
public Action<object> ReadAction { get; set; }
|
||||
|
||||
protected override void OnSelected(RoutedEventArgs e)
|
||||
{
|
||||
base.OnSelected(e);
|
||||
|
||||
IsRead = true;
|
||||
ReadAction?.Invoke(Content);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,682 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 颜色拾取器
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementBorderColor, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementBorderPicker, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementBorderDrag, Type = typeof(Border))]
|
||||
[TemplatePart(Name = ElementPanelColor, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementSliderColor, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementSliderOpacity, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementPanelRgb, Type = typeof(Panel))]
|
||||
public class ColorPicker : Control, ISingleOpen
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementBorderColor = "PART_BorderColor";
|
||||
private const string ElementBorderPicker = "PART_BorderPicker";
|
||||
private const string ElementBorderDrag = "PART_BorderDrag";
|
||||
private const string ElementPanelColor = "PART_PanelColor";
|
||||
private const string ElementSliderColor = "PART_SliderColor";
|
||||
private const string ElementSliderOpacity = "PART_SliderOpacity";
|
||||
private const string ElementPanelRgb = "PART_PanelRgb";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Panel _panelRgb;
|
||||
|
||||
private Border _borderColor;
|
||||
|
||||
private Border _borderPicker;
|
||||
|
||||
private Border _borderDrag;
|
||||
|
||||
private Panel _panelColor;
|
||||
|
||||
private Slider _sliderColor;
|
||||
|
||||
private Slider _sliderOpacity;
|
||||
|
||||
private bool _appliedTemplate;
|
||||
|
||||
/// <summary>
|
||||
/// 当前显示的颜色类型
|
||||
/// </summary>
|
||||
private int _colorType;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已经加载控件
|
||||
/// </summary>
|
||||
private bool _isLoaded;
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要更新小球位置
|
||||
/// </summary>
|
||||
private bool _isNeedUpdatePicker = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否在拖动小球
|
||||
/// </summary>
|
||||
private bool _isOnDragging;
|
||||
|
||||
/// <summary>
|
||||
/// 是否需要更新信息
|
||||
/// </summary>
|
||||
private bool IsNeedUpdateInfo { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 颜色选取面板宽度
|
||||
/// </summary>
|
||||
private const double ColorPanelWidth = 230;
|
||||
|
||||
/// <summary>
|
||||
/// 颜色选取面板高度
|
||||
/// </summary>
|
||||
private const double ColorPanelHeight = 122;
|
||||
|
||||
/// <summary>
|
||||
/// 预设的颜色(一共18个,两行)
|
||||
/// </summary>
|
||||
private readonly List<string> _colorPresetList = new List<string>
|
||||
{
|
||||
"#f44336",
|
||||
"#e91e63",
|
||||
"#9c27b0",
|
||||
"#673ab7",
|
||||
"#3f51b5",
|
||||
"#2196f3",
|
||||
"#03a9f4",
|
||||
"#00bcd4",
|
||||
"#009688",
|
||||
|
||||
"#4caf50",
|
||||
"#8bc34a",
|
||||
"#cddc39",
|
||||
"#ffeb3b",
|
||||
"#ffc107",
|
||||
"#ff9800",
|
||||
"#ff5722",
|
||||
"#795548",
|
||||
"#9e9e9e"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 颜色范围集合
|
||||
/// </summary>
|
||||
private readonly List<ColorRange> _colorRangeList = new List<ColorRange>
|
||||
{
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(255, 0, 0),
|
||||
Color2 = Color.FromRgb(255, 0, 255)
|
||||
},
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(255, 0, 255),
|
||||
Color2 = Color.FromRgb(0, 0, 255)
|
||||
},
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(0, 0, 255),
|
||||
Color2 = Color.FromRgb(0, 255, 255)
|
||||
},
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(0, 255, 255),
|
||||
Color2 = Color.FromRgb(0, 255, 0)
|
||||
},
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(0, 255, 0),
|
||||
Color2 = Color.FromRgb(255, 255, 0)
|
||||
},
|
||||
new ColorRange
|
||||
{
|
||||
Color1 = Color.FromRgb(255, 255, 0),
|
||||
Color2 = Color.FromRgb(255, 0, 0)
|
||||
}
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 颜色分隔集合
|
||||
/// </summary>
|
||||
private readonly List<Color> _colorSeparateList = new List<Color>
|
||||
{
|
||||
Color.FromRgb(255, 0, 0),
|
||||
Color.FromRgb(255, 0, 255),
|
||||
Color.FromRgb(0, 0, 255),
|
||||
Color.FromRgb(0, 255, 255),
|
||||
Color.FromRgb(0, 255, 0),
|
||||
Color.FromRgb(255, 255, 0)
|
||||
};
|
||||
|
||||
#endregion Data
|
||||
|
||||
#region Public Events
|
||||
|
||||
/// <summary>
|
||||
/// 颜色改变事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent SelectedColorChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("SelectedColorChanged", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<Color>>), typeof(ColorPicker));
|
||||
|
||||
/// <summary>
|
||||
/// 颜色改变事件
|
||||
/// </summary>
|
||||
public event EventHandler<FunctionEventArgs<Color>> SelectedColorChanged
|
||||
{
|
||||
add => AddHandler(SelectedColorChangedEvent, value);
|
||||
remove => RemoveHandler(SelectedColorChangedEvent, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent CanceledEvent =
|
||||
EventManager.RegisterRoutedEvent("Canceled", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler), typeof(ColorPicker));
|
||||
|
||||
/// <summary>
|
||||
/// 取消事件
|
||||
/// </summary>
|
||||
public event EventHandler Canceled
|
||||
{
|
||||
add => AddHandler(CanceledEvent, value);
|
||||
remove => RemoveHandler(CanceledEvent, value);
|
||||
}
|
||||
|
||||
#endregion Public Events
|
||||
|
||||
#region Properties
|
||||
|
||||
internal static readonly DependencyProperty ChannelAProperty = DependencyProperty.Register(
|
||||
"ChannelA", typeof(int), typeof(ColorPicker), new PropertyMetadata(255));
|
||||
|
||||
internal int ChannelA
|
||||
{
|
||||
get => (int)GetValue(ChannelAProperty);
|
||||
set => SetValue(ChannelAProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ChannelRProperty = DependencyProperty.Register(
|
||||
"ChannelR", typeof(int), typeof(ColorPicker), new PropertyMetadata(255));
|
||||
|
||||
internal int ChannelR
|
||||
{
|
||||
get => (int)GetValue(ChannelRProperty);
|
||||
set => SetValue(ChannelRProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ChannelGProperty = DependencyProperty.Register(
|
||||
"ChannelG", typeof(int), typeof(ColorPicker), new PropertyMetadata(255));
|
||||
|
||||
internal int ChannelG
|
||||
{
|
||||
get => (int)GetValue(ChannelGProperty);
|
||||
set => SetValue(ChannelGProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ChannelBProperty = DependencyProperty.Register(
|
||||
"ChannelB", typeof(int), typeof(ColorPicker), new PropertyMetadata(255));
|
||||
|
||||
internal int ChannelB
|
||||
{
|
||||
get => (int)GetValue(ChannelBProperty);
|
||||
set => SetValue(ChannelBProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SelectedBrushProperty = DependencyProperty.Register(
|
||||
"SelectedBrush", typeof(SolidColorBrush), typeof(ColorPicker), new PropertyMetadata(Brushes.White,
|
||||
(o, args) =>
|
||||
{
|
||||
var ctl = (ColorPicker)o;
|
||||
var v = (SolidColorBrush)args.NewValue;
|
||||
|
||||
if (ctl.IsNeedUpdateInfo)
|
||||
{
|
||||
ctl.IsNeedUpdateInfo = false;
|
||||
ctl.ChannelR = v.Color.R;
|
||||
ctl.ChannelG = v.Color.G;
|
||||
ctl.ChannelB = v.Color.B;
|
||||
ctl.ChannelA = v.Color.A;
|
||||
ctl.IsNeedUpdateInfo = true;
|
||||
}
|
||||
ctl.UpdateStatus(v.Color);
|
||||
ctl.SelectedBrushWithoutOpacity = new SolidColorBrush(Color.FromRgb(v.Color.R, v.Color.G, v.Color.B));
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// 当前选中的颜色
|
||||
/// </summary>
|
||||
public SolidColorBrush SelectedBrush
|
||||
{
|
||||
get => (SolidColorBrush)GetValue(SelectedBrushProperty);
|
||||
set => SetValue(SelectedBrushProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty SelectedBrushWithoutOpacityProperty = DependencyProperty.Register(
|
||||
"SelectedBrushWithoutOpacity", typeof(SolidColorBrush), typeof(ColorPicker), new PropertyMetadata(Brushes.White));
|
||||
|
||||
internal SolidColorBrush SelectedBrushWithoutOpacity
|
||||
{
|
||||
get => (SolidColorBrush)GetValue(SelectedBrushWithoutOpacityProperty);
|
||||
set => SetValue(SelectedBrushWithoutOpacityProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty BackColorProperty = DependencyProperty.Register(
|
||||
"BackColor", typeof(SolidColorBrush), typeof(ColorPicker), new PropertyMetadata(Brushes.Red));
|
||||
|
||||
internal SolidColorBrush BackColor
|
||||
{
|
||||
get => (SolidColorBrush)GetValue(BackColorProperty);
|
||||
set => SetValue(BackColorProperty, value);
|
||||
}
|
||||
|
||||
internal static readonly DependencyProperty ShowListProperty = DependencyProperty.Register(
|
||||
"ShowList", typeof(List<bool>), typeof(ColorPicker), new PropertyMetadata(new List<bool>
|
||||
{
|
||||
true, false, false
|
||||
}));
|
||||
|
||||
internal List<bool> ShowList
|
||||
{
|
||||
get => (List<bool>)GetValue(ShowListProperty);
|
||||
set => SetValue(ShowListProperty, value);
|
||||
}
|
||||
|
||||
#endregion Properties
|
||||
|
||||
/// <summary>
|
||||
/// 当前显示的颜色类型
|
||||
/// </summary>
|
||||
private int ColorType
|
||||
{
|
||||
get => _colorType;
|
||||
set
|
||||
{
|
||||
if (value < 0)
|
||||
{
|
||||
_colorType = 1;
|
||||
}
|
||||
else if (value > 1)
|
||||
{
|
||||
_colorType = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
_colorType = value;
|
||||
}
|
||||
|
||||
var list = new List<bool>();
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
list.Add(false);
|
||||
}
|
||||
list[_colorType] = true;
|
||||
ShowList = list;
|
||||
}
|
||||
}
|
||||
|
||||
public ColorPicker()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Confirm, ButtonConfirm_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Switch, ButtonSwitch_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Cancel, ButtonCancel_OnClick));
|
||||
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (!_isLoaded)
|
||||
{
|
||||
Init();
|
||||
_isLoaded = true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_appliedTemplate = false;
|
||||
if (_sliderColor != null)
|
||||
{
|
||||
_sliderColor.ValueChanged -= SliderColor_OnValueChanged;
|
||||
}
|
||||
|
||||
if (_sliderOpacity != null)
|
||||
{
|
||||
_sliderOpacity.ValueChanged -= SliderOpacity_OnValueChanged;
|
||||
}
|
||||
|
||||
_panelColor?.Children.Clear();
|
||||
|
||||
_panelRgb?.RemoveHandler(NumericUpDown.ValueChangedEvent, new EventHandler<FunctionEventArgs<double>>(NumericUpDownRgb_OnValueChanged));
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_borderColor = GetTemplateChild(ElementBorderColor) as Border;
|
||||
_borderDrag = GetTemplateChild(ElementBorderDrag) as Border;
|
||||
_borderPicker = GetTemplateChild(ElementBorderPicker) as Border;
|
||||
_panelColor = GetTemplateChild(ElementPanelColor) as Panel;
|
||||
_sliderColor = GetTemplateChild(ElementSliderColor) as Slider;
|
||||
_sliderOpacity = GetTemplateChild(ElementSliderOpacity) as Slider;
|
||||
_panelRgb = GetTemplateChild(ElementPanelRgb) as Panel;
|
||||
|
||||
if (_sliderColor != null)
|
||||
{
|
||||
_sliderColor.ValueChanged += SliderColor_OnValueChanged;
|
||||
}
|
||||
|
||||
if (_sliderOpacity != null)
|
||||
{
|
||||
_sliderOpacity.ValueChanged += SliderOpacity_OnValueChanged;
|
||||
}
|
||||
|
||||
if (_borderDrag != null)
|
||||
{
|
||||
var behavior = new MouseDragElementBehavior();
|
||||
behavior.DragFinished += MouseDragElementBehavior_OnDragFinished;
|
||||
behavior.DragBegun += MouseDragElementBehavior_OnDragging;
|
||||
behavior.Dragging += MouseDragElementBehavior_OnDragging;
|
||||
var collection = Interaction.GetBehaviors(_borderDrag);
|
||||
collection.Add(behavior);
|
||||
}
|
||||
|
||||
_appliedTemplate = true;
|
||||
if (_isLoaded)
|
||||
{
|
||||
Init();
|
||||
}
|
||||
_panelRgb?.AddHandler(NumericUpDown.ValueChangedEvent, new EventHandler<FunctionEventArgs<double>>(NumericUpDownRgb_OnValueChanged));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化
|
||||
/// </summary>
|
||||
private void Init()
|
||||
{
|
||||
UpdateStatus(SelectedBrush.Color);
|
||||
_panelColor.Children.Clear();
|
||||
foreach (var item in _colorPresetList)
|
||||
{
|
||||
_panelColor.Children.Add(CreateColorButton(item));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建颜色按钮
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private Button CreateColorButton(string colorStr)
|
||||
{
|
||||
var color = ColorConverter.ConvertFromString(colorStr) ?? default(Color);
|
||||
var brush = new SolidColorBrush((Color)color);
|
||||
|
||||
var button = new Button
|
||||
{
|
||||
Margin = new Thickness(0, 0, 12, 12),
|
||||
Style = FindResource("ButtonCustom") as Style,
|
||||
Content = new Border
|
||||
{
|
||||
Background = brush,
|
||||
Width = 12,
|
||||
Height = 12,
|
||||
CornerRadius = new CornerRadius(2)
|
||||
}
|
||||
};
|
||||
|
||||
button.Click += (s, e) =>
|
||||
{
|
||||
SelectedBrush = brush;
|
||||
ChannelA = byte.MaxValue;
|
||||
};
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部更新
|
||||
/// </summary>
|
||||
private void UpdateStatus(Color color)
|
||||
{
|
||||
if (_isOnDragging) return;
|
||||
|
||||
var r = color.R;
|
||||
var g = color.G;
|
||||
var b = color.B;
|
||||
var list = new List<byte>
|
||||
{
|
||||
r,
|
||||
g,
|
||||
b
|
||||
};
|
||||
|
||||
var max = list.Max();
|
||||
var min = list.Min();
|
||||
|
||||
if (min == max)
|
||||
{
|
||||
if (!(r == g && b == g))
|
||||
{
|
||||
BackColor = Brushes.Red;
|
||||
IsNeedUpdateInfo = false;
|
||||
if (!_sliderColor.IsMouseOver && !_sliderOpacity.IsMouseOver)
|
||||
{
|
||||
_sliderColor.Value = 0;
|
||||
}
|
||||
IsNeedUpdateInfo = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var maxIndex = list.IndexOf(max);
|
||||
var minIndex = list.IndexOf(min);
|
||||
var commonIndex = 3 - maxIndex - minIndex;
|
||||
if (commonIndex == 3)
|
||||
{
|
||||
BackColor = Brushes.Red;
|
||||
IsNeedUpdateInfo = false;
|
||||
if (!_sliderColor.IsMouseOver && !_sliderOpacity.IsMouseOver)
|
||||
{
|
||||
_sliderColor.Value = 0;
|
||||
}
|
||||
IsNeedUpdateInfo = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var common = list[commonIndex];
|
||||
list[maxIndex] = 255;
|
||||
list[minIndex] = 0;
|
||||
common = (byte)(255 * (min - common) / (double)(min - max));
|
||||
list[commonIndex] = common;
|
||||
BackColor = new SolidColorBrush(Color.FromRgb(list[0], list[1], list[2]));
|
||||
|
||||
list[commonIndex] = 0;
|
||||
var cIndex = _colorSeparateList.IndexOf(Color.FromRgb(list[0], list[1], list[2]));
|
||||
int sub;
|
||||
var direc = 0;
|
||||
if (cIndex < 5 && cIndex > 0)
|
||||
{
|
||||
var nextColorList = _colorSeparateList[cIndex + 1].ToList();
|
||||
var prevColorList = _colorSeparateList[cIndex - 1].ToList();
|
||||
if (nextColorList[minIndex] > 0)
|
||||
{
|
||||
var target = prevColorList[commonIndex];
|
||||
direc = 1;
|
||||
sub = target - common;
|
||||
}
|
||||
else
|
||||
{
|
||||
sub = common;
|
||||
}
|
||||
}
|
||||
else if (cIndex == 0)
|
||||
{
|
||||
sub = common;
|
||||
if (minIndex == 2)
|
||||
{
|
||||
sub = 255 - common;
|
||||
direc = -5;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sub = 255 - common;
|
||||
}
|
||||
var scale = sub / 255.0;
|
||||
var scaleTotal = cIndex - direc + scale;
|
||||
IsNeedUpdateInfo = false;
|
||||
if (!_sliderColor.IsMouseOver && !_sliderOpacity.IsMouseOver)
|
||||
{
|
||||
_sliderColor.Value = scaleTotal;
|
||||
}
|
||||
IsNeedUpdateInfo = true;
|
||||
}
|
||||
}
|
||||
|
||||
var matrix = _borderPicker.RenderTransform.Value;
|
||||
var x = max == 0 ? 0 : (1 - min / (double)max) * ColorPanelWidth;
|
||||
var y = (1 - max / 255.0) * ColorPanelHeight;
|
||||
if (_isNeedUpdatePicker)
|
||||
{
|
||||
_borderPicker.RenderTransform = new MatrixTransform(matrix.M11, matrix.M12, matrix.M21, matrix.M22, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private void SliderColor_OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (!_appliedTemplate || !IsNeedUpdateInfo) return;
|
||||
var index = Math.Min(5, (int)Math.Floor(e.NewValue));
|
||||
var sub = e.NewValue - index;
|
||||
var range = _colorRangeList[index];
|
||||
|
||||
var color = range.GetColor(sub);
|
||||
BackColor = new SolidColorBrush(color);
|
||||
var matrix = _borderPicker.RenderTransform.Value;
|
||||
_isNeedUpdatePicker = false;
|
||||
UpdateColorWhenDrag(new Point(matrix.OffsetX, matrix.OffsetY));
|
||||
_isNeedUpdatePicker = true;
|
||||
}
|
||||
|
||||
private void SliderOpacity_OnValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (!_appliedTemplate || !IsNeedUpdateInfo) return;
|
||||
var color = SelectedBrush.Color;
|
||||
SelectedBrush = new SolidColorBrush(Color.FromArgb((byte)_sliderOpacity.Value, color.R, color.G, color.B));
|
||||
}
|
||||
|
||||
private void MouseDragElementBehavior_OnDragging(object sender, MouseEventArgs e)
|
||||
{
|
||||
var p = e.GetPosition(_borderColor);
|
||||
_isOnDragging = true;
|
||||
UpdateColorWhenDrag(p);
|
||||
_isOnDragging = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 拖动时更新颜色
|
||||
/// </summary>
|
||||
private void UpdateColorWhenDrag(Point p)
|
||||
{
|
||||
var matrix = _borderPicker.RenderTransform.Value;
|
||||
|
||||
if (p.X < 0)
|
||||
{
|
||||
p.X = 0;
|
||||
}
|
||||
else if (p.X > ColorPanelWidth)
|
||||
{
|
||||
p.X = ColorPanelWidth;
|
||||
}
|
||||
|
||||
if (p.Y < 0)
|
||||
{
|
||||
p.Y = 0;
|
||||
}
|
||||
else if (p.Y > ColorPanelHeight)
|
||||
{
|
||||
p.Y = ColorPanelHeight;
|
||||
}
|
||||
|
||||
if (_isNeedUpdatePicker)
|
||||
{
|
||||
_borderPicker.RenderTransform = new MatrixTransform(matrix.M11, matrix.M12, matrix.M21, matrix.M22, p.X, p.Y);
|
||||
}
|
||||
|
||||
var scaleX = p.X / ColorPanelWidth;
|
||||
var scaleY = 1 - p.Y / ColorPanelHeight;
|
||||
|
||||
var colorYLeft = Color.FromRgb((byte)(255 * scaleY), (byte)(255 * scaleY), (byte)(255 * scaleY));
|
||||
var colorYRight = Color.FromRgb((byte)(BackColor.Color.R * scaleY), (byte)(BackColor.Color.G * scaleY), (byte)(BackColor.Color.B * scaleY));
|
||||
|
||||
var subR = colorYLeft.R - colorYRight.R;
|
||||
var subG = colorYLeft.G - colorYRight.G;
|
||||
var subB = colorYLeft.B - colorYRight.B;
|
||||
|
||||
var color = Color.FromArgb((byte)_sliderOpacity.Value, (byte)(colorYLeft.R - subR * scaleX),
|
||||
(byte)(colorYLeft.G - subG * scaleX), (byte)(colorYLeft.B - subB * scaleX));
|
||||
SelectedBrush = new SolidColorBrush(color);
|
||||
}
|
||||
|
||||
private void MouseDragElementBehavior_OnDragFinished(object sender, MouseEventArgs e) => _borderDrag.RenderTransform = new MatrixTransform();
|
||||
|
||||
private void ButtonSwitch_OnClick(object sender, RoutedEventArgs e) => ColorType++;
|
||||
|
||||
// ReSharper disable once UnusedParameter.Local
|
||||
private void NumericUpDownRgb_OnValueChanged(object sender, FunctionEventArgs<double> e)
|
||||
{
|
||||
if (!_appliedTemplate || !IsNeedUpdateInfo) return;
|
||||
if (e.OriginalSource is NumericUpDown ctl && ctl.Tag is string tag)
|
||||
{
|
||||
var color = SelectedBrush.Color;
|
||||
IsNeedUpdateInfo = false;
|
||||
|
||||
switch (tag)
|
||||
{
|
||||
case "R":
|
||||
{
|
||||
SelectedBrush = new SolidColorBrush(Color.FromArgb(color.A, (byte)e.Info, color.G, color.B));
|
||||
break;
|
||||
}
|
||||
case "G":
|
||||
{
|
||||
SelectedBrush = new SolidColorBrush(Color.FromArgb(color.A, color.R, (byte)e.Info, color.B));
|
||||
break;
|
||||
}
|
||||
case "B":
|
||||
{
|
||||
SelectedBrush = new SolidColorBrush(Color.FromArgb(color.A, color.R, color.G, (byte)e.Info));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
IsNeedUpdateInfo = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonConfirm_OnClick(object sender, RoutedEventArgs e)
|
||||
=> RaiseEvent(new FunctionEventArgs<Color>(SelectedColorChangedEvent, this)
|
||||
{
|
||||
Info = SelectedBrush.Color
|
||||
});
|
||||
|
||||
private void ButtonCancel_OnClick(object sender, RoutedEventArgs e) => RaiseEvent(new RoutedEventArgs(CanceledEvent));
|
||||
|
||||
public void Dispose() => System.Windows.Window.GetWindow(this)?.Close();
|
||||
|
||||
public bool CanDispose { get; } = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class Dialog : ContentControl
|
||||
{
|
||||
private Adorner _container;
|
||||
|
||||
public static readonly DependencyProperty IsClosedProperty = DependencyProperty.Register(
|
||||
"IsClosed", typeof(bool), typeof(Dialog), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsClosed
|
||||
{
|
||||
get => (bool) GetValue(IsClosedProperty);
|
||||
internal set => SetValue(IsClosedProperty, value);
|
||||
}
|
||||
|
||||
public Dialog()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Close, (s, e) => Close()));
|
||||
}
|
||||
|
||||
public static Dialog Show<T>() where T : new() => Show(new T());
|
||||
|
||||
public static Dialog Show(object content)
|
||||
{
|
||||
var dialog = new Dialog
|
||||
{
|
||||
Content = content
|
||||
};
|
||||
|
||||
var window = VisualHelper.GetActiveWindow();
|
||||
if (window != null)
|
||||
{
|
||||
var decorator = VisualHelper.GetChild<AdornerDecorator>(window);
|
||||
if (decorator != null)
|
||||
{
|
||||
if (decorator.Child != null)
|
||||
{
|
||||
decorator.Child.IsEnabled = false;
|
||||
}
|
||||
var layer = decorator.AdornerLayer;
|
||||
if (layer != null)
|
||||
{
|
||||
var container = new AdornerContainer(layer)
|
||||
{
|
||||
Child = dialog
|
||||
};
|
||||
dialog._container = container;
|
||||
dialog.IsClosed = false;
|
||||
layer.Add(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
public void Close()
|
||||
{
|
||||
var window = VisualHelper.GetActiveWindow();
|
||||
if (window != null)
|
||||
{
|
||||
var decorator = VisualHelper.GetChild<AdornerDecorator>(window);
|
||||
if (decorator != null)
|
||||
{
|
||||
if (decorator.Child != null)
|
||||
{
|
||||
decorator.Child.IsEnabled = true;
|
||||
}
|
||||
var layer = decorator.AdornerLayer;
|
||||
layer?.Remove(_container);
|
||||
IsClosed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class GotoTop : Button
|
||||
{
|
||||
private Action _gotoTopAction;
|
||||
|
||||
private System.Windows.Controls.ScrollViewer _scrollViewer;
|
||||
|
||||
public static readonly DependencyProperty TargetProperty = DependencyProperty.Register(
|
||||
"Target", typeof(DependencyObject), typeof(GotoTop), new PropertyMetadata(default(DependencyObject)));
|
||||
|
||||
public DependencyObject Target
|
||||
{
|
||||
get => (DependencyObject)GetValue(TargetProperty);
|
||||
set => SetValue(TargetProperty, value);
|
||||
}
|
||||
|
||||
public GotoTop() => Loaded += (s, e) => CreateGotoAction(Target);
|
||||
|
||||
public virtual void CreateGotoAction(DependencyObject obj)
|
||||
{
|
||||
if (_scrollViewer != null)
|
||||
{
|
||||
_scrollViewer.ScrollChanged -= ScrollViewer_ScrollChanged;
|
||||
}
|
||||
_scrollViewer = VisualHelper.GetChild<System.Windows.Controls.ScrollViewer>(obj);
|
||||
if (_scrollViewer != null)
|
||||
{
|
||||
_scrollViewer.ScrollChanged += ScrollViewer_ScrollChanged;
|
||||
|
||||
if (_scrollViewer is ScrollViewer scrollViewerHandy && Animated && scrollViewerHandy.IsEnableInertia)
|
||||
{
|
||||
_gotoTopAction = () => scrollViewerHandy.ScrollToTopInternal(AnimationTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
_gotoTopAction = () => _scrollViewer.ScrollToTop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (AutoHiding)
|
||||
{
|
||||
this.Show(e.VerticalOffset >= HidingHeight);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AnimatedProperty = DependencyProperty.Register(
|
||||
"Animated", typeof(bool), typeof(GotoTop), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool Animated
|
||||
{
|
||||
get => (bool) GetValue(AnimatedProperty);
|
||||
set => SetValue(AnimatedProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AnimationTimeProperty = DependencyProperty.Register(
|
||||
"AnimationTime", typeof(double), typeof(GotoTop), new PropertyMetadata(ValueBoxes.Double200Box));
|
||||
|
||||
public double AnimationTime
|
||||
{
|
||||
get => (double) GetValue(AnimationTimeProperty);
|
||||
set => SetValue(AnimationTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty HidingHeightProperty = DependencyProperty.Register(
|
||||
"HidingHeight", typeof(double), typeof(GotoTop), new PropertyMetadata(default(double)));
|
||||
|
||||
public double HidingHeight
|
||||
{
|
||||
get => (double) GetValue(HidingHeightProperty);
|
||||
set => SetValue(HidingHeightProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty AutoHidingProperty = DependencyProperty.Register(
|
||||
"AutoHiding", typeof(bool), typeof(GotoTop), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool AutoHiding
|
||||
{
|
||||
get => (bool) GetValue(AutoHidingProperty);
|
||||
set => SetValue(AutoHidingProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
base.OnClick();
|
||||
|
||||
_gotoTopAction?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class Gravatar : ContentControl
|
||||
{
|
||||
public static readonly DependencyProperty GeneratorProperty = DependencyProperty.Register(
|
||||
"Generator", typeof(IGravatarGenerator), typeof(Gravatar), new PropertyMetadata(new GithubGravatarGenerator()));
|
||||
|
||||
public IGravatarGenerator Generator
|
||||
{
|
||||
get => (IGravatarGenerator) GetValue(GeneratorProperty);
|
||||
set => SetValue(GeneratorProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IdProperty = DependencyProperty.Register(
|
||||
"Id", typeof(string), typeof(Gravatar), new PropertyMetadata(default(string), OnIdChanged));
|
||||
|
||||
private static void OnIdChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (Gravatar) d;
|
||||
if (ctl.Source != null) return;
|
||||
ctl.Content = ctl.Generator.GetGravatar((string)e.NewValue);
|
||||
}
|
||||
|
||||
public string Id
|
||||
{
|
||||
get => (string) GetValue(IdProperty);
|
||||
set => SetValue(IdProperty, value);
|
||||
}
|
||||
|
||||
|
||||
public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
|
||||
"Source", typeof(ImageSource), typeof(Gravatar), new PropertyMetadata(default(ImageSource), OnSourceChanged));
|
||||
|
||||
private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (Gravatar)d;
|
||||
ctl.Background = new ImageBrush((ImageSource)e.NewValue);
|
||||
}
|
||||
|
||||
public ImageSource Source
|
||||
{
|
||||
get => (ImageSource) GetValue(SourceProperty);
|
||||
set => SetValue(SourceProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息提醒
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementPanelMore, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementGridMain, Type = typeof(Grid))]
|
||||
[TemplatePart(Name = ElementButtonClose, Type = typeof(Button))]
|
||||
public class Growl : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementPanelMore = "PART_PanelMore";
|
||||
private const string ElementGridMain = "PART_GridMain";
|
||||
private const string ElementButtonClose = "PART_ButtonClose";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Panel _panelMore;
|
||||
|
||||
private Grid _gridMain;
|
||||
|
||||
private Button _buttonClose;
|
||||
|
||||
private bool _showCloseButton;
|
||||
|
||||
private bool _staysOpen;
|
||||
|
||||
private int _waitTime = 6;
|
||||
|
||||
/// <summary>
|
||||
/// 计数
|
||||
/// </summary>
|
||||
private int _tickCount;
|
||||
|
||||
/// <summary>
|
||||
/// 关闭计时器
|
||||
/// </summary>
|
||||
private DispatcherTimer _timerClose;
|
||||
|
||||
private static readonly Dictionary<string, Panel> PanelDic = new Dictionary<string, Panel>();
|
||||
|
||||
#endregion Data
|
||||
|
||||
public Growl()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Close, ButtonClose_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Cancel, ButtonCancel_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Confirm, ButtonOk_OnClick));
|
||||
}
|
||||
|
||||
public static void Register(string token, Panel panel)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token) || panel == null) return;
|
||||
PanelDic[token] = panel;
|
||||
InitGrowlPanel(panel);
|
||||
}
|
||||
|
||||
public static void Unregister(string token, Panel panel)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token) || panel == null) return;
|
||||
|
||||
if (PanelDic.ContainsKey(token))
|
||||
{
|
||||
if (ReferenceEquals(PanelDic[token], panel))
|
||||
{
|
||||
PanelDic.Remove(token);
|
||||
panel.ContextMenu = null;
|
||||
panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void Unregister(Panel panel)
|
||||
{
|
||||
if (panel == null) return;
|
||||
var first = PanelDic.FirstOrDefault(item => ReferenceEquals(panel, item.Value));
|
||||
if (!string.IsNullOrEmpty(first.Key))
|
||||
{
|
||||
PanelDic.Remove(first.Key);
|
||||
panel.ContextMenu = null;
|
||||
panel.SetCurrentValue(PanelElement.FluidMoveBehaviorProperty, DependencyProperty.UnsetValue);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnMouseEnter(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseEnter(e);
|
||||
|
||||
_buttonClose.Show(_showCloseButton);
|
||||
}
|
||||
|
||||
protected override void OnMouseLeave(MouseEventArgs e)
|
||||
{
|
||||
base.OnMouseLeave(e);
|
||||
|
||||
_buttonClose.Collapse();
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_panelMore = GetTemplateChild(ElementPanelMore) as Panel;
|
||||
_gridMain = GetTemplateChild(ElementGridMain) as Grid;
|
||||
_buttonClose = GetTemplateChild(ElementButtonClose) as Button;
|
||||
|
||||
CheckNull();
|
||||
Update();
|
||||
}
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
if (_panelMore == null || _gridMain == null || _buttonClose == null) throw new Exception();
|
||||
}
|
||||
|
||||
private Func<bool, bool> ActionBeforeClose { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息容器
|
||||
/// </summary>
|
||||
public static Panel GrowlPanel { get; set; }
|
||||
|
||||
internal static readonly DependencyProperty CancelStrProperty = DependencyProperty.Register(
|
||||
"CancelStr", typeof(string), typeof(Growl), new PropertyMetadata(default(string)));
|
||||
|
||||
internal static readonly DependencyProperty ConfirmStrProperty = DependencyProperty.Register(
|
||||
"ConfirmStr", typeof(string), typeof(Growl), new PropertyMetadata(default(string)));
|
||||
|
||||
public static readonly DependencyProperty ShowDateTimeProperty = DependencyProperty.Register(
|
||||
"ShowDateTime", typeof(bool), typeof(Growl), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
|
||||
"Message", typeof(string), typeof(Growl), new PropertyMetadata(default(string)));
|
||||
|
||||
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register(
|
||||
"Time", typeof(DateTime), typeof(Growl), new PropertyMetadata(default(DateTime)));
|
||||
|
||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
|
||||
"Icon", typeof(Geometry), typeof(Growl), new PropertyMetadata(default(Geometry)));
|
||||
|
||||
public static readonly DependencyProperty IconBrushProperty = DependencyProperty.Register(
|
||||
"IconBrush", typeof(Brush), typeof(Growl), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public static readonly DependencyProperty TypeProperty = DependencyProperty.Register(
|
||||
"Type", typeof(InfoType), typeof(Growl), new PropertyMetadata(default(InfoType)));
|
||||
|
||||
public static readonly DependencyProperty GrowlParentProperty = DependencyProperty.RegisterAttached(
|
||||
"GrowlParent", typeof(bool), typeof(Growl), new PropertyMetadata(ValueBoxes.FalseBox, (o, args) =>
|
||||
{
|
||||
if ((bool)args.NewValue && o is Panel panel)
|
||||
{
|
||||
SetGrowlPanel(panel);
|
||||
}
|
||||
}));
|
||||
|
||||
public static readonly DependencyProperty TokenProperty = DependencyProperty.RegisterAttached(
|
||||
"Token", typeof(string), typeof(Growl), new PropertyMetadata(default(string), OnTokenChanged));
|
||||
|
||||
private static void OnTokenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is Panel panel)
|
||||
{
|
||||
if (e.NewValue == null)
|
||||
{
|
||||
Unregister(panel);
|
||||
}
|
||||
else
|
||||
{
|
||||
Register(e.NewValue.ToString(), panel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetToken(DependencyObject element, string value)
|
||||
=> element.SetValue(TokenProperty, value);
|
||||
|
||||
public static string GetToken(DependencyObject element)
|
||||
=> (string) element.GetValue(TokenProperty);
|
||||
|
||||
public static void SetGrowlParent(DependencyObject element, bool value) => element.SetValue(GrowlParentProperty, value);
|
||||
|
||||
public static bool GetGrowlParent(DependencyObject element) => (bool)element.GetValue(GrowlParentProperty);
|
||||
|
||||
public InfoType Type
|
||||
{
|
||||
get => (InfoType)GetValue(TypeProperty);
|
||||
set => SetValue(TypeProperty, value);
|
||||
}
|
||||
|
||||
internal string CancelStr
|
||||
{
|
||||
get => (string)GetValue(CancelStrProperty);
|
||||
set => SetValue(CancelStrProperty, value);
|
||||
}
|
||||
|
||||
internal string ConfirmStr
|
||||
{
|
||||
get => (string)GetValue(ConfirmStrProperty);
|
||||
set => SetValue(ConfirmStrProperty, value);
|
||||
}
|
||||
|
||||
public bool ShowDateTime
|
||||
{
|
||||
get => (bool)GetValue(ShowDateTimeProperty);
|
||||
set => SetValue(ShowDateTimeProperty, value);
|
||||
}
|
||||
|
||||
public string Message
|
||||
{
|
||||
get => (string)GetValue(MessageProperty);
|
||||
set => SetValue(MessageProperty, value);
|
||||
}
|
||||
|
||||
public DateTime Time
|
||||
{
|
||||
get => (DateTime)GetValue(TimeProperty);
|
||||
set => SetValue(TimeProperty, value);
|
||||
}
|
||||
|
||||
public Geometry Icon
|
||||
{
|
||||
get => (Geometry)GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public Brush IconBrush
|
||||
{
|
||||
get => (Brush)GetValue(IconBrushProperty);
|
||||
set => SetValue(IconBrushProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始计时器
|
||||
/// </summary>
|
||||
private void StartTimer()
|
||||
{
|
||||
_timerClose = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromSeconds(1)
|
||||
};
|
||||
_timerClose.Tick += delegate
|
||||
{
|
||||
if (IsMouseOver)
|
||||
{
|
||||
_tickCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_tickCount++;
|
||||
if (_tickCount >= _waitTime) Close();
|
||||
};
|
||||
_timerClose.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息容器
|
||||
/// </summary>
|
||||
/// <param name="panel"></param>
|
||||
private static void SetGrowlPanel(Panel panel)
|
||||
{
|
||||
GrowlPanel = panel;
|
||||
InitGrowlPanel(panel);
|
||||
}
|
||||
|
||||
private static void InitGrowlPanel(Panel panel)
|
||||
{
|
||||
var menuItem = new MenuItem
|
||||
{
|
||||
Header = Properties.Langs.Lang.Clear
|
||||
};
|
||||
menuItem.Click += (s, e) =>
|
||||
{
|
||||
foreach (var item in panel.Children.OfType<Growl>())
|
||||
{
|
||||
item.Close();
|
||||
}
|
||||
};
|
||||
panel.ContextMenu = new ContextMenu
|
||||
{
|
||||
Items =
|
||||
{
|
||||
menuItem
|
||||
}
|
||||
};
|
||||
|
||||
PanelElement.SetFluidMoveBehavior(panel, ResourceHelper.GetResource<FluidMoveBehavior>(ResourceToken.BehaviorXY400));
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (ActionBeforeClose != null)
|
||||
{
|
||||
_staysOpen = true;
|
||||
_showCloseButton = false;
|
||||
_panelMore.IsEnabled = true;
|
||||
_panelMore.Show();
|
||||
}
|
||||
|
||||
var transform = new TranslateTransform
|
||||
{
|
||||
X = MaxWidth
|
||||
};
|
||||
_gridMain.RenderTransform = transform;
|
||||
transform.BeginAnimation(TranslateTransform.XProperty, AnimationHelper.CreateAnimation(0));
|
||||
if (!_staysOpen) StartTimer();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示信息
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
private static void Show(GrowlInfo growlInfo)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
var ctl = new Growl
|
||||
{
|
||||
Message = growlInfo.Message,
|
||||
Time = DateTime.Now,
|
||||
Icon = ResourceHelper.GetResource<Geometry>(growlInfo.IconKey),
|
||||
IconBrush = ResourceHelper.GetResource<Brush>(growlInfo.IconBrushKey),
|
||||
_showCloseButton = growlInfo.ShowCloseButton,
|
||||
ActionBeforeClose = growlInfo.ActionBeforeClose,
|
||||
_staysOpen = growlInfo.StaysOpen,
|
||||
ShowDateTime = growlInfo.ShowDateTime,
|
||||
ConfirmStr = growlInfo.ConfirmStr,
|
||||
CancelStr = growlInfo.CancelStr,
|
||||
Type = growlInfo.Type,
|
||||
_waitTime = Math.Max(growlInfo.WaitTime, 2)
|
||||
};
|
||||
if (!string.IsNullOrEmpty(growlInfo.Token))
|
||||
{
|
||||
if (PanelDic.TryGetValue(growlInfo.Token, out var panel))
|
||||
{
|
||||
panel?.Children.Insert(0, ctl);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GrowlPanel.Children.Insert(0, ctl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Success(string message, string token = "") => Success(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Success(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.SuccessGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.SuccessBrush;
|
||||
growlInfo.Type = InfoType.Success;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Info(string message, string token = "") => Info(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Info(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.InfoGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.InfoBrush;
|
||||
growlInfo.Type = InfoType.Info;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Warning(string message, string token = "") => Warning(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Warning(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.WarningGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.WarningBrush;
|
||||
growlInfo.Type = InfoType.Warning;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Error(string message, string token = "") => Error(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Error(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.ErrorGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.DangerBrush;
|
||||
growlInfo.StaysOpen = true;
|
||||
growlInfo.Type = InfoType.Error;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 严重
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Fatal(string message, string token = "") => Fatal(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 严重
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Fatal(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.FatalGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.PrimaryTextBrush;
|
||||
growlInfo.StaysOpen = true;
|
||||
growlInfo.ShowCloseButton = false;
|
||||
growlInfo.Type = InfoType.Fatal;
|
||||
if (GrowlPanel.ContextMenu != null) GrowlPanel.ContextMenu.Opacity = 0;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 询问
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="actionBeforeClose"></param>
|
||||
/// <param name="token"></param>
|
||||
public static void Ask(string message, Func<bool, bool> actionBeforeClose, string token = "") => Ask(new GrowlInfo
|
||||
{
|
||||
Message = message,
|
||||
ActionBeforeClose = actionBeforeClose,
|
||||
Token = token
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 询问
|
||||
/// </summary>
|
||||
/// <param name="growlInfo"></param>
|
||||
public static void Ask(GrowlInfo growlInfo)
|
||||
{
|
||||
if (growlInfo == null) throw new ArgumentNullException(nameof(growlInfo));
|
||||
if (!growlInfo.IsCustom)
|
||||
{
|
||||
growlInfo.IconKey = ResourceToken.AskGeometry;
|
||||
growlInfo.IconBrushKey = ResourceToken.AccentBrush;
|
||||
growlInfo.StaysOpen = true;
|
||||
growlInfo.ShowCloseButton = false;
|
||||
growlInfo.Type = InfoType.Ask;
|
||||
}
|
||||
Show(growlInfo);
|
||||
}
|
||||
|
||||
private void ButtonClose_OnClick(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
/// <summary>
|
||||
/// 关闭
|
||||
/// </summary>
|
||||
private void Close()
|
||||
{
|
||||
_timerClose?.Stop();
|
||||
var transform = new TranslateTransform();
|
||||
_gridMain.RenderTransform = transform;
|
||||
var animation = AnimationHelper.CreateAnimation(MaxWidth);
|
||||
animation.Completed += (s, e) =>
|
||||
{
|
||||
if (Parent is Panel panel)
|
||||
{
|
||||
panel.Children.Remove(this);
|
||||
}
|
||||
};
|
||||
transform.BeginAnimation(TranslateTransform.XProperty, animation);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除
|
||||
/// </summary>
|
||||
/// <param name="token"></param>
|
||||
public static void Clear(string token = "")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(token))
|
||||
{
|
||||
if (PanelDic.TryGetValue(token, out var panel))
|
||||
{
|
||||
Clear(panel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Clear(GrowlPanel);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除
|
||||
/// </summary>
|
||||
/// <param name="panel"></param>
|
||||
private static void Clear(Panel panel)
|
||||
{
|
||||
panel.Children.Clear();
|
||||
if (panel.ContextMenu != null)
|
||||
{
|
||||
panel.ContextMenu.IsOpen = false;
|
||||
panel.ContextMenu.Opacity = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonCancel_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ActionBeforeClose?.Invoke(false) == true)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonOk_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (ActionBeforeClose?.Invoke(true) == true)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,425 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Interop;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class NotifyIcon : FrameworkElement, IDisposable
|
||||
{
|
||||
private bool _added;
|
||||
|
||||
private readonly object _syncObj = new object();
|
||||
|
||||
private readonly int _id;
|
||||
|
||||
private static int NextId;
|
||||
|
||||
private ImageSource _icon;
|
||||
|
||||
private IntPtr _iconCurrentHandle;
|
||||
|
||||
private IntPtr _iconDefaultHandle;
|
||||
|
||||
private IconHandle _iconHandle;
|
||||
|
||||
private const int WmTrayMouseMessage = NativeMethods.WM_USER + 1024;
|
||||
|
||||
private string _windowClassName;
|
||||
|
||||
private int _wmTaskbarCreated;
|
||||
|
||||
private IntPtr _messageWindowHandle;
|
||||
|
||||
private readonly WndProc _callback;
|
||||
|
||||
private Popup _contextContent;
|
||||
|
||||
private bool _doubleClick;
|
||||
|
||||
private DispatcherTimer _dispatcherTimer;
|
||||
|
||||
private bool _isTransparent;
|
||||
|
||||
private bool _isDisposed;
|
||||
|
||||
static NotifyIcon()
|
||||
{
|
||||
VisibilityProperty.OverrideMetadata(typeof(NotifyIcon), new PropertyMetadata(Visibility.Visible, OnVisibilityChanged));
|
||||
}
|
||||
|
||||
private static void OnVisibilityChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NotifyIcon) d;
|
||||
var v = (Visibility)e.NewValue;
|
||||
|
||||
if (v == Visibility.Visible)
|
||||
{
|
||||
if (ctl._iconCurrentHandle == IntPtr.Zero)
|
||||
{
|
||||
ctl.OnIconChanged();
|
||||
}
|
||||
ctl.UpdateIcon(true);
|
||||
}
|
||||
else if(ctl._iconCurrentHandle != IntPtr.Zero)
|
||||
{
|
||||
ctl.UpdateIcon(false);
|
||||
}
|
||||
}
|
||||
|
||||
public NotifyIcon()
|
||||
{
|
||||
_id = ++NextId;
|
||||
_callback = Callback;
|
||||
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
RegisterClass();
|
||||
if (Visibility == Visibility.Visible)
|
||||
{
|
||||
OnIconChanged();
|
||||
UpdateIcon(true);
|
||||
}
|
||||
};
|
||||
|
||||
if (Application.Current != null) Application.Current.Exit += (s, e) => Dispose();
|
||||
}
|
||||
|
||||
~NotifyIcon()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
"Text", typeof(string), typeof(NotifyIcon), new PropertyMetadata(default(string)));
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string) GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
|
||||
"Icon", typeof(ImageSource), typeof(NotifyIcon), new PropertyMetadata(default(ImageSource), OnIconChanged));
|
||||
|
||||
private static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NotifyIcon)d;
|
||||
ctl._icon = (ImageSource)e.NewValue;
|
||||
ctl.OnIconChanged();
|
||||
}
|
||||
|
||||
public ImageSource Icon
|
||||
{
|
||||
get => (ImageSource)GetValue(IconProperty);
|
||||
set => SetValue(IconProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ContextContentProperty = DependencyProperty.Register(
|
||||
"ContextContent", typeof(object), typeof(NotifyIcon), new PropertyMetadata(default(object)));
|
||||
|
||||
public object ContextContent
|
||||
{
|
||||
get => GetValue(ContextContentProperty);
|
||||
set => SetValue(ContextContentProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty BlinkIntervalProperty = DependencyProperty.Register(
|
||||
"BlinkInterval", typeof(TimeSpan), typeof(NotifyIcon), new PropertyMetadata(TimeSpan.FromMilliseconds(500), OnBlinkIntervalChanged));
|
||||
|
||||
private static void OnBlinkIntervalChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NotifyIcon)d;
|
||||
if (ctl._dispatcherTimer != null)
|
||||
{
|
||||
ctl._dispatcherTimer.Interval = (TimeSpan) e.NewValue;
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan BlinkInterval
|
||||
{
|
||||
get => (TimeSpan) GetValue(BlinkIntervalProperty);
|
||||
set => SetValue(BlinkIntervalProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsBlinkProperty = DependencyProperty.Register(
|
||||
"IsBlink", typeof(bool), typeof(NotifyIcon), new PropertyMetadata(ValueBoxes.FalseBox, OnIsBlinkChanged));
|
||||
|
||||
private static void OnIsBlinkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (NotifyIcon)d;
|
||||
if (ctl.Visibility != Visibility.Visible) return;
|
||||
if ((bool) e.NewValue)
|
||||
{
|
||||
if (ctl._dispatcherTimer == null)
|
||||
{
|
||||
ctl._dispatcherTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = ctl.BlinkInterval
|
||||
};
|
||||
ctl._dispatcherTimer.Tick += ctl.DispatcherTimer_Tick;
|
||||
}
|
||||
ctl._dispatcherTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctl._dispatcherTimer?.Stop();
|
||||
ctl._dispatcherTimer = null;
|
||||
ctl.UpdateIcon(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void DispatcherTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (Visibility != Visibility.Visible || _iconCurrentHandle == IntPtr.Zero) return;
|
||||
UpdateIcon(true, !_isTransparent);
|
||||
}
|
||||
|
||||
public bool IsBlink
|
||||
{
|
||||
get => (bool) GetValue(IsBlinkProperty);
|
||||
set => SetValue(IsBlinkProperty, value);
|
||||
}
|
||||
|
||||
private void OnIconChanged()
|
||||
{
|
||||
if (_icon != null)
|
||||
{
|
||||
IconHelper.GetIconHandlesFromImageSource(_icon, out _, out _iconHandle);
|
||||
_iconCurrentHandle = _iconHandle.CriticalGetHandle();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_iconDefaultHandle == IntPtr.Zero)
|
||||
{
|
||||
IconHelper.GetDefaultIconHandles(out _, out _iconHandle);
|
||||
_iconDefaultHandle = _iconHandle.CriticalGetHandle();
|
||||
}
|
||||
_iconCurrentHandle = _iconDefaultHandle;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateIcon(bool showIconInTray, bool isTransparent = false)
|
||||
{
|
||||
lock (_syncObj)
|
||||
{
|
||||
if (DesignerHelper.IsInDesignMode) return;
|
||||
|
||||
_isTransparent = isTransparent;
|
||||
var data = new NOTIFYICONDATA
|
||||
{
|
||||
uCallbackMessage = WmTrayMouseMessage,
|
||||
uFlags = NativeMethods.NIF_MESSAGE | NativeMethods.NIF_ICON | NativeMethods.NIF_TIP,
|
||||
hWnd = _messageWindowHandle,
|
||||
uID = _id,
|
||||
dwInfoFlags = NativeMethods.NIF_TIP,
|
||||
hIcon = isTransparent ? IntPtr.Zero : _iconCurrentHandle,
|
||||
szTip = Text
|
||||
};
|
||||
|
||||
if (showIconInTray)
|
||||
{
|
||||
if (!_added)
|
||||
{
|
||||
UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_ADD, data);
|
||||
_added = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_MODIFY, data);
|
||||
}
|
||||
}
|
||||
else if (_added)
|
||||
{
|
||||
UnsafeNativeMethods.Shell_NotifyIcon(NativeMethods.NIM_DELETE, data);
|
||||
_added = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterClass()
|
||||
{
|
||||
_windowClassName = $"HandyControl.Controls.NotifyIcon{Guid.NewGuid()}";
|
||||
var wndclass = new WNDCLASS
|
||||
{
|
||||
style = 0,
|
||||
lpfnWndProc = _callback,
|
||||
cbClsExtra = 0,
|
||||
cbWndExtra = 0,
|
||||
hInstance = IntPtr.Zero,
|
||||
hIcon = IntPtr.Zero,
|
||||
hCursor = IntPtr.Zero,
|
||||
hbrBackground = IntPtr.Zero,
|
||||
lpszMenuName = "",
|
||||
lpszClassName = _windowClassName
|
||||
};
|
||||
|
||||
UnsafeNativeMethods.RegisterClass(wndclass);
|
||||
_wmTaskbarCreated = NativeMethods.RegisterWindowMessage("TaskbarCreated");
|
||||
_messageWindowHandle = UnsafeNativeMethods.CreateWindowEx(0, _windowClassName, "", 0, 0, 0, 1, 1,
|
||||
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
private IntPtr Callback(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam)
|
||||
{
|
||||
if (IsLoaded)
|
||||
{
|
||||
if (msg == _wmTaskbarCreated)
|
||||
{
|
||||
UpdateIcon(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (lparam.ToInt64())
|
||||
{
|
||||
case NativeMethods.WM_LBUTTONDBLCLK:
|
||||
WmMouseDown(MouseButton.Left, 2);
|
||||
break;
|
||||
case NativeMethods.WM_LBUTTONUP:
|
||||
WmMouseUp(MouseButton.Left);
|
||||
break;
|
||||
case NativeMethods.WM_RBUTTONUP:
|
||||
ShowContextMenu();
|
||||
WmMouseUp(MouseButton.Right);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return UnsafeNativeMethods.DefWindowProc(hWnd, msg, wparam, lparam);
|
||||
}
|
||||
|
||||
private void WmMouseDown(MouseButton button, int clicks)
|
||||
{
|
||||
if (clicks == 2)
|
||||
{
|
||||
RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button)
|
||||
{
|
||||
RoutedEvent = MouseDoubleClickEvent
|
||||
});
|
||||
_doubleClick = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void WmMouseUp(MouseButton button)
|
||||
{
|
||||
if (!_doubleClick && button == MouseButton.Left)
|
||||
{
|
||||
RaiseEvent(new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, button)
|
||||
{
|
||||
RoutedEvent = ClickEvent
|
||||
});
|
||||
}
|
||||
_doubleClick = false;
|
||||
}
|
||||
|
||||
private void ShowContextMenu()
|
||||
{
|
||||
var point = new POINT();
|
||||
UnsafeNativeMethods.GetCursorPos(point);
|
||||
|
||||
if (ContextContent != null)
|
||||
{
|
||||
if (_contextContent == null)
|
||||
{
|
||||
_contextContent = new Popup
|
||||
{
|
||||
Placement = PlacementMode.AbsolutePoint,
|
||||
AllowsTransparency = true,
|
||||
StaysOpen = false
|
||||
};
|
||||
}
|
||||
|
||||
_contextContent.HorizontalOffset = point.x;
|
||||
_contextContent.VerticalOffset = point.y;
|
||||
_contextContent.Child = new ContentControl
|
||||
{
|
||||
Content = ContextContent
|
||||
};
|
||||
_contextContent.IsOpen = true;
|
||||
var handle = IntPtr.Zero;
|
||||
var hwndSource = (HwndSource)PresentationSource.FromVisual(_contextContent.Child);
|
||||
if (hwndSource != null)
|
||||
{
|
||||
handle = hwndSource.Handle;
|
||||
}
|
||||
UnsafeNativeMethods.SetForegroundWindow(handle);
|
||||
}
|
||||
else if (ContextMenu != null)
|
||||
{
|
||||
ContextMenu.Placement = PlacementMode.AbsolutePoint;
|
||||
ContextMenu.HorizontalOffset = point.x;
|
||||
ContextMenu.VerticalOffset = point.y;
|
||||
ContextMenu.IsOpen = true;
|
||||
|
||||
var handle = IntPtr.Zero;
|
||||
var hwndSource = (HwndSource)PresentationSource.FromVisual(ContextMenu);
|
||||
if (hwndSource != null)
|
||||
{
|
||||
handle = hwndSource.Handle;
|
||||
}
|
||||
UnsafeNativeMethods.SetForegroundWindow(handle);
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent ClickEvent =
|
||||
EventManager.RegisterRoutedEvent("Click", RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler), typeof(NotifyIcon));
|
||||
|
||||
public event RoutedEventHandler Click
|
||||
{
|
||||
add => AddHandler(ClickEvent, value);
|
||||
remove => RemoveHandler(ClickEvent, value);
|
||||
}
|
||||
|
||||
public static readonly RoutedEvent MouseDoubleClickEvent =
|
||||
EventManager.RegisterRoutedEvent("MouseDoubleClick", RoutingStrategy.Bubble,
|
||||
typeof(RoutedEventHandler), typeof(NotifyIcon));
|
||||
|
||||
public event RoutedEventHandler MouseDoubleClick
|
||||
{
|
||||
add => AddHandler(MouseDoubleClickEvent, value);
|
||||
remove => RemoveHandler(MouseDoubleClickEvent, value);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
if (_dispatcherTimer != null && IsBlink)
|
||||
{
|
||||
_dispatcherTimer.Stop();
|
||||
}
|
||||
UpdateIcon(false);
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public void CloseContextControl()
|
||||
{
|
||||
if (_contextContent != null)
|
||||
{
|
||||
_contextContent.IsOpen = false;
|
||||
}
|
||||
else if (ContextMenu != null)
|
||||
{
|
||||
ContextMenu.IsOpen = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 页码
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementButtonLeft, Type = typeof(Button))]
|
||||
[TemplatePart(Name = ElementButtonRight, Type = typeof(Button))]
|
||||
[TemplatePart(Name = ElementButtonFirst, Type = typeof(RadioButton))]
|
||||
[TemplatePart(Name = ElementTextBlockLeft, Type = typeof(TextBlock))]
|
||||
[TemplatePart(Name = ElementPanelMain, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementTextBlockRight, Type = typeof(TextBlock))]
|
||||
[TemplatePart(Name = ElementButtonLast, Type = typeof(RadioButton))]
|
||||
public class Pagination : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementButtonLeft = "PART_ButtonLeft";
|
||||
private const string ElementButtonRight = "PART_ButtonRight";
|
||||
private const string ElementButtonFirst = "PART_ButtonFirst";
|
||||
private const string ElementTextBlockLeft = "PART_TextBlockLeft";
|
||||
private const string ElementPanelMain = "PART_PanelMain";
|
||||
private const string ElementTextBlockRight = "PART_TextBlockRight";
|
||||
private const string ElementButtonLast = "PART_ButtonLast";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Button _buttonLeft;
|
||||
private Button _buttonRight;
|
||||
private RadioButton _buttonFirst;
|
||||
private TextBlock _textBlockLeft;
|
||||
private Panel _panelMain;
|
||||
private TextBlock _textBlockRight;
|
||||
private RadioButton _buttonLast;
|
||||
|
||||
private bool _appliedTemplate;
|
||||
|
||||
#endregion Data
|
||||
|
||||
#region Public Events
|
||||
|
||||
/// <summary>
|
||||
/// 页面更新事件
|
||||
/// </summary>
|
||||
public static readonly RoutedEvent PageUpdatedEvent =
|
||||
EventManager.RegisterRoutedEvent("PageUpdated", RoutingStrategy.Bubble,
|
||||
typeof(EventHandler<FunctionEventArgs<int>>), typeof(Pagination));
|
||||
|
||||
/// <summary>
|
||||
/// 页面更新事件
|
||||
/// </summary>
|
||||
public event EventHandler<FunctionEventArgs<int>> PageUpdated
|
||||
{
|
||||
add => AddHandler(PageUpdatedEvent, value);
|
||||
remove => RemoveHandler(PageUpdatedEvent, value);
|
||||
}
|
||||
|
||||
#endregion Public Events
|
||||
|
||||
public Pagination()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Prev, ButtonPrev_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Next, ButtonNext_OnClick));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Selected, ToggleButton_OnChecked));
|
||||
}
|
||||
|
||||
#region Public Properties
|
||||
|
||||
#region MaxPageCount
|
||||
|
||||
/// <summary>
|
||||
/// 最大页数
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty MaxPageCountProperty = DependencyProperty.Register(
|
||||
"MaxPageCount", typeof(int), typeof(Pagination), new PropertyMetadata(1, (o, args) =>
|
||||
{
|
||||
if (o is Pagination pagination && args.NewValue is int value)
|
||||
{
|
||||
if (pagination.PageIndex > pagination.MaxPageCount)
|
||||
{
|
||||
pagination.PageIndex = pagination.MaxPageCount;
|
||||
}
|
||||
|
||||
pagination.Show(value > 1);
|
||||
pagination.Update();
|
||||
}
|
||||
}, (o, value) =>
|
||||
{
|
||||
if (!(o is Pagination)) return 1;
|
||||
var intValue = (int)value;
|
||||
if (intValue < 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return intValue;
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// 最大页数
|
||||
/// </summary>
|
||||
public int MaxPageCount
|
||||
{
|
||||
get => (int)GetValue(MaxPageCountProperty);
|
||||
set => SetValue(MaxPageCountProperty, value);
|
||||
}
|
||||
|
||||
#endregion MaxPageCount
|
||||
|
||||
#region DataCountPerPage
|
||||
|
||||
/// <summary>
|
||||
/// 每页的数据量
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty DataCountPerPageProperty = DependencyProperty.Register(
|
||||
"DataCountPerPage", typeof(int), typeof(Pagination), new PropertyMetadata(20, (o, args) =>
|
||||
{
|
||||
if (o is Pagination pagination)
|
||||
{
|
||||
pagination.Update();
|
||||
}
|
||||
}, (o, value) =>
|
||||
{
|
||||
if (!(o is Pagination)) return 1;
|
||||
var intValue = (int)value;
|
||||
if (intValue < 1)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
return intValue;
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// 每页的数据量
|
||||
/// </summary>
|
||||
public int DataCountPerPage
|
||||
{
|
||||
get => (int)GetValue(DataCountPerPageProperty);
|
||||
set => SetValue(DataCountPerPageProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region PageIndex
|
||||
|
||||
/// <summary>
|
||||
/// 当前页
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PageIndexProperty = DependencyProperty.Register(
|
||||
"PageIndex", typeof(int), typeof(Pagination), new PropertyMetadata(1, (o, args) =>
|
||||
{
|
||||
if (o is Pagination pagination && args.NewValue is int value)
|
||||
{
|
||||
pagination.Update();
|
||||
pagination.RaiseEvent(new FunctionEventArgs<int>(PageUpdatedEvent, pagination)
|
||||
{
|
||||
Info = value
|
||||
});
|
||||
}
|
||||
}, (o, value) =>
|
||||
{
|
||||
if (!(o is Pagination pagination)) return 1;
|
||||
var intValue = (int)value;
|
||||
if (intValue < 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (intValue > pagination.MaxPageCount) return pagination.MaxPageCount;
|
||||
return intValue;
|
||||
}));
|
||||
|
||||
/// <summary>
|
||||
/// 当前页
|
||||
/// </summary>
|
||||
public int PageIndex
|
||||
{
|
||||
get => (int)GetValue(PageIndexProperty);
|
||||
set => SetValue(PageIndexProperty, value);
|
||||
}
|
||||
|
||||
#endregion PageIndex
|
||||
|
||||
#region MaxPageInterval
|
||||
|
||||
/// <summary>
|
||||
/// 表示当前选中的按钮距离左右两个方向按钮的最大间隔(4表示间隔4个按钮,如果超过则用省略号表示)
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty MaxPageIntervalProperty = DependencyProperty.Register(
|
||||
"MaxPageInterval", typeof(int), typeof(Pagination), new PropertyMetadata(3, (o, args) =>
|
||||
{
|
||||
if (o is Pagination pagination)
|
||||
{
|
||||
pagination.Update();
|
||||
}
|
||||
}), value =>
|
||||
{
|
||||
var intValue = (int)value;
|
||||
return intValue >= 0;
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// 表示当前选中的按钮距离左右两个方向按钮的最大间隔(4表示间隔4个按钮,如果超过则用省略号表示)
|
||||
/// </summary>
|
||||
public int MaxPageInterval
|
||||
{
|
||||
get => (int)GetValue(MaxPageIntervalProperty);
|
||||
set => SetValue(MaxPageIntervalProperty, value);
|
||||
}
|
||||
|
||||
#endregion MaxPageInterval
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_appliedTemplate = false;
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_buttonLeft = GetTemplateChild(ElementButtonLeft) as Button;
|
||||
_buttonRight = GetTemplateChild(ElementButtonRight) as Button;
|
||||
_buttonFirst = GetTemplateChild(ElementButtonFirst) as RadioButton;
|
||||
_textBlockLeft = GetTemplateChild(ElementTextBlockLeft) as TextBlock;
|
||||
_panelMain = GetTemplateChild(ElementPanelMain) as Panel;
|
||||
_textBlockRight = GetTemplateChild(ElementTextBlockRight) as TextBlock;
|
||||
_buttonLast = GetTemplateChild(ElementButtonLast) as RadioButton;
|
||||
|
||||
CheckNull();
|
||||
|
||||
_appliedTemplate = true;
|
||||
Update();
|
||||
}
|
||||
|
||||
#endregion Public Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
if (_buttonLeft == null || _buttonRight == null || _buttonFirst == null ||
|
||||
_textBlockLeft == null || _panelMain == null || _textBlockRight == null ||
|
||||
_buttonLast == null) throw new Exception();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新
|
||||
/// </summary>
|
||||
private void Update()
|
||||
{
|
||||
if (!_appliedTemplate) return;
|
||||
_buttonLeft.IsEnabled = PageIndex > 1;
|
||||
_buttonRight.IsEnabled = PageIndex < MaxPageCount;
|
||||
if (MaxPageInterval == 0)
|
||||
{
|
||||
_buttonFirst.Collapse();
|
||||
_buttonLast.Collapse();
|
||||
_textBlockLeft.Collapse();
|
||||
_textBlockRight.Collapse();
|
||||
_panelMain.Children.Clear();
|
||||
var selectButton = CreateButton(PageIndex);
|
||||
_panelMain.Children.Add(selectButton);
|
||||
selectButton.IsChecked = true;
|
||||
return;
|
||||
}
|
||||
_buttonFirst.Show();
|
||||
_buttonLast.Show();
|
||||
_textBlockLeft.Show();
|
||||
_textBlockRight.Show();
|
||||
|
||||
//更新最后一页
|
||||
if (MaxPageCount == 1)
|
||||
{
|
||||
_buttonLast.Collapse();
|
||||
}
|
||||
else
|
||||
{
|
||||
_buttonLast.Show();
|
||||
_buttonLast.Tag = MaxPageCount.ToString();
|
||||
}
|
||||
|
||||
//更新省略号
|
||||
var right = MaxPageCount - PageIndex;
|
||||
var left = PageIndex - 1;
|
||||
_textBlockRight.Show(right > MaxPageInterval);
|
||||
_textBlockLeft.Show(left > MaxPageInterval);
|
||||
|
||||
//更新中间部分
|
||||
_panelMain.Children.Clear();
|
||||
if (PageIndex > 1 && PageIndex < MaxPageCount)
|
||||
{
|
||||
var selectButton = CreateButton(PageIndex);
|
||||
_panelMain.Children.Add(selectButton);
|
||||
selectButton.IsChecked = true;
|
||||
}
|
||||
else if (PageIndex == 1)
|
||||
{
|
||||
_buttonFirst.IsChecked = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_buttonLast.IsChecked = true;
|
||||
}
|
||||
|
||||
var sub = PageIndex;
|
||||
for (int i = 0; i < MaxPageInterval - 1; i++)
|
||||
{
|
||||
if (--sub > 1)
|
||||
{
|
||||
_panelMain.Children.Insert(0, CreateButton(sub));
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
var add = PageIndex;
|
||||
for (int i = 0; i < MaxPageInterval - 1; i++)
|
||||
{
|
||||
if (++add < MaxPageCount)
|
||||
{
|
||||
_panelMain.Children.Add(CreateButton(add));
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonPrev_OnClick(object sender, RoutedEventArgs e) => PageIndex--;
|
||||
|
||||
private void ButtonNext_OnClick(object sender, RoutedEventArgs e) => PageIndex++;
|
||||
|
||||
private RadioButton CreateButton(int page)
|
||||
{
|
||||
return new RadioButton
|
||||
{
|
||||
Style = ResourceHelper.GetResource<Style>(ResourceToken.PaginationButtonStyle),
|
||||
Tag = page.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
private void ToggleButton_OnChecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!(e.OriginalSource is RadioButton button)) return;
|
||||
if (button.IsChecked == false) return;
|
||||
PageIndex = int.Parse(button.Tag.ToString());
|
||||
}
|
||||
|
||||
#endregion Private Methods
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class RadioGroup : ItemsControl
|
||||
{
|
||||
protected override DependencyObject GetContainerForItemOverride() => new RadioButton();
|
||||
|
||||
protected override bool IsItemItsOwnContainerOverride(object item) => item is RadioButton;
|
||||
|
||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
||||
"Orientation", typeof(Orientation), typeof(RadioGroup), new PropertyMetadata(default(Orientation)));
|
||||
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => (Orientation)GetValue(OrientationProperty);
|
||||
set => SetValue(OrientationProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ItemStyleSelectorProperty = DependencyProperty.Register(
|
||||
"ItemStyleSelector", typeof(StyleSelector), typeof(RadioGroup), new PropertyMetadata(new RadioGroupItemStyleSelector()));
|
||||
|
||||
public StyleSelector ItemStyleSelector
|
||||
{
|
||||
get => (StyleSelector)GetValue(ItemStyleSelectorProperty);
|
||||
set => SetValue(ItemStyleSelectorProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
|
||||
{
|
||||
base.OnVisualChildrenChanged(visualAdded, visualRemoved);
|
||||
|
||||
var count = Items.Count;
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
var item = (RadioButton)Items[i];
|
||||
item.Style = ItemStyleSelector?.SelectStyle(item, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class ScrollViewer : System.Windows.Controls.ScrollViewer
|
||||
{
|
||||
private double _totalVerticalOffset;
|
||||
|
||||
private double _totalHorizontalOffset;
|
||||
|
||||
private bool _isRunning;
|
||||
|
||||
/// <summary>
|
||||
/// 滚动方向
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
|
||||
"Orientation", typeof(Orientation), typeof(ScrollViewer), new PropertyMetadata(Orientation.Vertical));
|
||||
|
||||
/// <summary>
|
||||
/// 滚动方向
|
||||
/// </summary>
|
||||
public Orientation Orientation
|
||||
{
|
||||
get => (Orientation) GetValue(OrientationProperty);
|
||||
set => SetValue(OrientationProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否响应鼠标滚轮操作
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CanMouseWheelProperty = DependencyProperty.Register(
|
||||
"CanMouseWheel", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
/// <summary>
|
||||
/// 是否响应鼠标滚轮操作
|
||||
/// </summary>
|
||||
public bool CanMouseWheel
|
||||
{
|
||||
get => (bool) GetValue(CanMouseWheelProperty);
|
||||
set => SetValue(CanMouseWheelProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
if (!CanMouseWheel) return;
|
||||
|
||||
if (!IsEnableInertia)
|
||||
{
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
base.OnMouseWheel(e);
|
||||
}
|
||||
else
|
||||
{
|
||||
_totalHorizontalOffset = HorizontalOffset;
|
||||
CurrentHorizontalOffset = HorizontalOffset;
|
||||
_totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
|
||||
CurrentHorizontalOffset = _totalHorizontalOffset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
e.Handled = true;
|
||||
|
||||
if (Orientation == Orientation.Vertical)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalVerticalOffset = VerticalOffset;
|
||||
CurrentVerticalOffset = VerticalOffset;
|
||||
}
|
||||
_totalVerticalOffset = Math.Min(Math.Max(0, _totalVerticalOffset - e.Delta), ScrollableHeight);
|
||||
ScrollToVerticalOffsetInternal(_totalVerticalOffset);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalHorizontalOffset = HorizontalOffset;
|
||||
CurrentHorizontalOffset = HorizontalOffset;
|
||||
}
|
||||
_totalHorizontalOffset = Math.Min(Math.Max(0, _totalHorizontalOffset - e.Delta), ScrollableWidth);
|
||||
ScrollToHorizontalOffsetInternal(_totalHorizontalOffset);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ScrollToTopInternal(double milliseconds = 500)
|
||||
{
|
||||
if (!_isRunning)
|
||||
{
|
||||
_totalVerticalOffset = VerticalOffset;
|
||||
CurrentVerticalOffset = VerticalOffset;
|
||||
}
|
||||
ScrollToVerticalOffsetInternal(0, milliseconds);
|
||||
}
|
||||
|
||||
internal void ScrollToVerticalOffsetInternal(double offset, double milliseconds = 500)
|
||||
{
|
||||
var animation = AnimationHelper.CreateAnimation(offset, milliseconds);
|
||||
animation.EasingFunction = new CubicEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
};
|
||||
animation.FillBehavior = FillBehavior.Stop;
|
||||
animation.Completed += (s, e1) =>
|
||||
{
|
||||
CurrentVerticalOffset = offset;
|
||||
_isRunning = false;
|
||||
};
|
||||
_isRunning = true;
|
||||
BeginAnimation(CurrentVerticalOffsetProperty, animation);
|
||||
}
|
||||
|
||||
internal void ScrollToHorizontalOffsetInternal(double offset, double milliseconds = 500)
|
||||
{
|
||||
var animation = AnimationHelper.CreateAnimation(offset, milliseconds);
|
||||
animation.EasingFunction = new CubicEase
|
||||
{
|
||||
EasingMode = EasingMode.EaseOut
|
||||
};
|
||||
animation.FillBehavior = FillBehavior.Stop;
|
||||
animation.Completed += (s, e1) =>
|
||||
{
|
||||
CurrentHorizontalOffset = offset;
|
||||
_isRunning = false;
|
||||
};
|
||||
_isRunning = true;
|
||||
BeginAnimation(CurrentHorizontalOffsetProperty, animation);
|
||||
}
|
||||
|
||||
protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) =>
|
||||
IsPenetrating ? null : base.HitTestCore(hitTestParameters);
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持惯性
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsEnableInertiaProperty = DependencyProperty.RegisterAttached(
|
||||
"IsEnableInertia", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public static void SetIsEnableInertia(DependencyObject element, bool value)
|
||||
{
|
||||
element.SetValue(IsEnableInertiaProperty, value);
|
||||
}
|
||||
|
||||
public static bool GetIsEnableInertia(DependencyObject element)
|
||||
{
|
||||
return (bool) element.GetValue(IsEnableInertiaProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否支持惯性
|
||||
/// </summary>
|
||||
public bool IsEnableInertia
|
||||
{
|
||||
get => (bool)GetValue(IsEnableInertiaProperty);
|
||||
set => SetValue(IsEnableInertiaProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 控件是否可以穿透点击
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty IsPenetratingProperty = DependencyProperty.RegisterAttached(
|
||||
"IsPenetrating", typeof(bool), typeof(ScrollViewer), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
/// <summary>
|
||||
/// 控件是否可以穿透点击
|
||||
/// </summary>
|
||||
public bool IsPenetrating
|
||||
{
|
||||
get => (bool)GetValue(IsPenetratingProperty);
|
||||
set => SetValue(IsPenetratingProperty, value);
|
||||
}
|
||||
|
||||
public static void SetIsPenetrating(DependencyObject element, bool value)
|
||||
{
|
||||
element.SetValue(IsPenetratingProperty, value);
|
||||
}
|
||||
|
||||
public static bool GetIsPenetrating(DependencyObject element)
|
||||
{
|
||||
return (bool) element.GetValue(IsPenetratingProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前垂直滚动偏移
|
||||
/// </summary>
|
||||
private static readonly DependencyProperty CurrentVerticalOffsetProperty = DependencyProperty.Register(
|
||||
"CurrentVerticalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(default(double), OnCurrentVerticalOffsetChanged));
|
||||
|
||||
private static void OnCurrentVerticalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ScrollViewer ctl && e.NewValue is double v)
|
||||
{
|
||||
ctl.ScrollToVerticalOffset(v);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前垂直滚动偏移
|
||||
/// </summary>
|
||||
private double CurrentVerticalOffset
|
||||
{
|
||||
// ReSharper disable once UnusedMember.Local
|
||||
get => (double) GetValue(CurrentVerticalOffsetProperty);
|
||||
set => SetValue(CurrentVerticalOffsetProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前水平滚动偏移
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty CurrentHorizontalOffsetProperty = DependencyProperty.Register(
|
||||
"CurrentHorizontalOffset", typeof(double), typeof(ScrollViewer), new PropertyMetadata(default(double), OnCurrentHorizontalOffsetChanged));
|
||||
|
||||
private static void OnCurrentHorizontalOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is ScrollViewer ctl && e.NewValue is double v)
|
||||
{
|
||||
ctl.ScrollToHorizontalOffset(v);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前水平滚动偏移
|
||||
/// </summary>
|
||||
public double CurrentHorizontalOffset
|
||||
{
|
||||
get => (double) GetValue(CurrentHorizontalOffsetProperty);
|
||||
set => SetValue(CurrentHorizontalOffsetProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Markup;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[ContentProperty(nameof(Status))]
|
||||
public class Shield : ButtonBase
|
||||
{
|
||||
public static readonly DependencyProperty SubjectProperty = DependencyProperty.Register(
|
||||
"Subject", typeof(string), typeof(Shield), new PropertyMetadata(default(string)));
|
||||
|
||||
public string Subject
|
||||
{
|
||||
get => (string) GetValue(SubjectProperty);
|
||||
set => SetValue(SubjectProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty StatusProperty = DependencyProperty.Register(
|
||||
"Status", typeof(object), typeof(Shield), new PropertyMetadata(default(object)));
|
||||
|
||||
public object Status
|
||||
{
|
||||
get => GetValue(StatusProperty);
|
||||
set => SetValue(StatusProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ColorProperty = DependencyProperty.Register(
|
||||
"Color", typeof(Brush), typeof(Shield), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public Brush Color
|
||||
{
|
||||
get => (Brush) GetValue(ColorProperty);
|
||||
set => SetValue(ColorProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Animation;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Tools;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplateVisualState(GroupName = PresentationGroup, Name = NormalState)]
|
||||
[TemplateVisualState(GroupName = PresentationGroup, Name = DefaultTransitionState)]
|
||||
[TemplatePart(Name = PreviousContentPresentationSitePartName, Type = typeof(ContentControl))]
|
||||
[TemplatePart(Name = CurrentContentPresentationSitePartName, Type = typeof(ContentControl))]
|
||||
public class TransitioningContentControl : ContentControl
|
||||
{
|
||||
private Storyboard _currentTransition;
|
||||
|
||||
public TransitioningContentControl()
|
||||
{
|
||||
if (!DesignerProperties.GetIsInDesignMode(this))
|
||||
{
|
||||
DefaultStyleKey = typeof(TransitioningContentControl);
|
||||
}
|
||||
}
|
||||
|
||||
private Storyboard CurrentTransition
|
||||
{
|
||||
set
|
||||
{
|
||||
if (_currentTransition != null)
|
||||
_currentTransition.Completed -= OnTransitionCompleted;
|
||||
|
||||
_currentTransition = value;
|
||||
|
||||
if (_currentTransition != null)
|
||||
_currentTransition.Completed += OnTransitionCompleted;
|
||||
}
|
||||
}
|
||||
|
||||
#region Events
|
||||
|
||||
public event RoutedEventHandler TransitionCompleted;
|
||||
|
||||
#endregion Events
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (DesignerProperties.GetIsInDesignMode(this)) return;
|
||||
|
||||
if (IsTransitioning)
|
||||
AbortTransition();
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
PreviousContentPresentationSite =
|
||||
GetTemplateChild(PreviousContentPresentationSitePartName) as ContentPresenter;
|
||||
CurrentContentPresentationSite =
|
||||
GetTemplateChild(CurrentContentPresentationSitePartName) as ContentPresenter;
|
||||
|
||||
if (CurrentContentPresentationSite != null)
|
||||
CurrentContentPresentationSite.Content = Content;
|
||||
|
||||
var transition = GetStoryboard(Transition);
|
||||
CurrentTransition = transition;
|
||||
if (transition == null)
|
||||
{
|
||||
Transition = DefaultTransitionState;
|
||||
|
||||
throw new ArgumentException("TransitioningContentControl_TransitionNotFound");
|
||||
}
|
||||
|
||||
VisualStateManager.GoToState(this, NormalState, false);
|
||||
VisualStateManager.GoToState(this, Transition, true);
|
||||
}
|
||||
|
||||
protected override void OnContentChanged(object oldContent, object newContent)
|
||||
{
|
||||
base.OnContentChanged(oldContent, newContent);
|
||||
|
||||
StartTransition(oldContent, newContent);
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "newContent", Justification = "Should be used in the future.")]
|
||||
private void StartTransition(object oldContent, object newContent)
|
||||
{
|
||||
if (CurrentContentPresentationSite != null && PreviousContentPresentationSite != null)
|
||||
{
|
||||
CurrentContentPresentationSite.Content = newContent;
|
||||
|
||||
PreviousContentPresentationSite.Content = oldContent;
|
||||
|
||||
if (!IsTransitioning || RestartTransitionOnContentChange)
|
||||
{
|
||||
IsTransitioning = true;
|
||||
VisualStateManager.GoToState(this, NormalState, false);
|
||||
VisualStateManager.GoToState(this, Transition, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnTransitionCompleted(object sender, EventArgs e)
|
||||
{
|
||||
AbortTransition();
|
||||
|
||||
var handler = TransitionCompleted;
|
||||
handler?.Invoke(this, new RoutedEventArgs());
|
||||
}
|
||||
|
||||
public void AbortTransition()
|
||||
{
|
||||
VisualStateManager.GoToState(this, NormalState, false);
|
||||
IsTransitioning = false;
|
||||
if (PreviousContentPresentationSite != null)
|
||||
PreviousContentPresentationSite.Content = null;
|
||||
}
|
||||
|
||||
private Storyboard GetStoryboard(string newTransition)
|
||||
{
|
||||
var presentationGroup = VisualHelper.TryGetVisualStateGroup(this, PresentationGroup);
|
||||
Storyboard newStoryboard = null;
|
||||
if (presentationGroup != null)
|
||||
newStoryboard = presentationGroup.States
|
||||
.OfType<VisualState>()
|
||||
.Where(state => state.Name == newTransition)
|
||||
.Select(state => state.Storyboard)
|
||||
.FirstOrDefault();
|
||||
return newStoryboard;
|
||||
}
|
||||
|
||||
#region Visual state names
|
||||
|
||||
private const string PresentationGroup = "PresentationStates";
|
||||
|
||||
private const string NormalState = "Normal";
|
||||
|
||||
public const string DefaultTransitionState = "DefaultTransition";
|
||||
|
||||
#endregion Visual state names
|
||||
|
||||
#region Template part names
|
||||
|
||||
internal const string PreviousContentPresentationSitePartName = "PreviousContentPresentationSite";
|
||||
|
||||
internal const string CurrentContentPresentationSitePartName = "CurrentContentPresentationSite";
|
||||
|
||||
#endregion Template part names
|
||||
|
||||
#region TemplateParts
|
||||
|
||||
private ContentPresenter CurrentContentPresentationSite { get; set; }
|
||||
|
||||
private ContentPresenter PreviousContentPresentationSite { get; set; }
|
||||
|
||||
#endregion TemplateParts
|
||||
|
||||
#region public bool IsTransitioning
|
||||
|
||||
private bool _allowIsTransitioningWrite;
|
||||
|
||||
public bool IsTransitioning
|
||||
{
|
||||
get => (bool)GetValue(IsTransitioningProperty);
|
||||
private set
|
||||
{
|
||||
_allowIsTransitioningWrite = true;
|
||||
SetValue(IsTransitioningProperty, value);
|
||||
_allowIsTransitioningWrite = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsTransitioningProperty =
|
||||
DependencyProperty.Register(
|
||||
"IsTransitioning",
|
||||
typeof(bool),
|
||||
typeof(TransitioningContentControl),
|
||||
new PropertyMetadata(OnIsTransitioningPropertyChanged));
|
||||
|
||||
private static void OnIsTransitioningPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var source = (TransitioningContentControl)d;
|
||||
|
||||
if (!source._allowIsTransitioningWrite)
|
||||
{
|
||||
source.IsTransitioning = (bool)e.OldValue;
|
||||
throw new InvalidOperationException("TransitiotioningContentControl_IsTransitioningReadOnly");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion public bool IsTransitioning
|
||||
|
||||
#region public string Transition
|
||||
|
||||
public string Transition
|
||||
{
|
||||
get => GetValue(TransitionProperty) as string;
|
||||
set => SetValue(TransitionProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TransitionProperty =
|
||||
DependencyProperty.Register(
|
||||
"Transition",
|
||||
typeof(string),
|
||||
typeof(TransitioningContentControl),
|
||||
new PropertyMetadata(DefaultTransitionState, OnTransitionPropertyChanged));
|
||||
|
||||
private static void OnTransitionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var source = (TransitioningContentControl)d;
|
||||
var oldTransition = e.OldValue as string;
|
||||
var newTransition = e.NewValue as string;
|
||||
|
||||
if (source.IsTransitioning)
|
||||
source.AbortTransition();
|
||||
|
||||
var newStoryboard = source.GetStoryboard(newTransition);
|
||||
|
||||
if (newStoryboard == null)
|
||||
if (VisualHelper.TryGetVisualStateGroup(source, PresentationGroup) == null)
|
||||
{
|
||||
source.CurrentTransition = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
source.SetValue(TransitionProperty, oldTransition);
|
||||
|
||||
throw new ArgumentException(
|
||||
"TransitioningContentControl_TransitionNotFound");
|
||||
}
|
||||
else
|
||||
source.CurrentTransition = newStoryboard;
|
||||
}
|
||||
|
||||
#endregion public string Transition
|
||||
|
||||
#region public bool RestartTransitionOnContentChange
|
||||
|
||||
public bool RestartTransitionOnContentChange
|
||||
{
|
||||
get => (bool)GetValue(RestartTransitionOnContentChangeProperty);
|
||||
set => SetValue(RestartTransitionOnContentChangeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty RestartTransitionOnContentChangeProperty =
|
||||
DependencyProperty.Register(
|
||||
"RestartTransitionOnContentChange",
|
||||
typeof(bool),
|
||||
typeof(TransitioningContentControl),
|
||||
new PropertyMetadata(ValueBoxes.FalseBox, OnRestartTransitionOnContentChangePropertyChanged));
|
||||
|
||||
private static void OnRestartTransitionOnContentChangePropertyChanged(DependencyObject d,
|
||||
DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
((TransitioningContentControl)d).OnRestartTransitionOnContentChangeChanged((bool)e.OldValue,
|
||||
(bool)e.NewValue);
|
||||
}
|
||||
|
||||
protected virtual void OnRestartTransitionOnContentChangeChanged(bool oldValue, bool newValue)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion public bool RestartTransitionOnContentChange
|
||||
}
|
||||
}
|
||||
@@ -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,44 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 用以代替Grid
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 当不需要Grid的行、列分隔等功能时建议用此轻量级类代替
|
||||
/// </remarks>
|
||||
public class SimplePanel : Panel
|
||||
{
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var maxSize = new Size();
|
||||
var children = InternalChildren;
|
||||
|
||||
for (int i = 0, count = children.Count; i < count; ++i)
|
||||
{
|
||||
var child = children[i];
|
||||
if (child != null)
|
||||
{
|
||||
child.Measure(availableSize);
|
||||
maxSize.Width = Math.Max(maxSize.Width, child.DesiredSize.Width);
|
||||
maxSize.Height = Math.Max(maxSize.Height, child.DesiredSize.Height);
|
||||
}
|
||||
}
|
||||
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size arrangeSize)
|
||||
{
|
||||
foreach (UIElement child in InternalChildren)
|
||||
{
|
||||
child?.Arrange(new Rect(arrangeSize));
|
||||
}
|
||||
|
||||
return arrangeSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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,119 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
using HandyControl.Expression.Drawing;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 波浪进度条
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementWave, Type = typeof(FrameworkElement))]
|
||||
[TemplatePart(Name = ElementClip, Type = typeof(FrameworkElement))]
|
||||
public class WaveProgressBar : RangeBase
|
||||
{
|
||||
private const string ElementWave = "PART_Wave";
|
||||
|
||||
private const string ElementClip = "PART_Clip";
|
||||
|
||||
private FrameworkElement _waveElement;
|
||||
|
||||
private const double TranslateTransformMinY = -20;
|
||||
|
||||
private double _translateTransformYRange;
|
||||
|
||||
private TranslateTransform _translateTransform;
|
||||
|
||||
public WaveProgressBar() => Loaded += (s, e) => UpdateWave(Value);
|
||||
|
||||
static WaveProgressBar()
|
||||
{
|
||||
FocusableProperty.OverrideMetadata(typeof(WaveProgressBar),
|
||||
new FrameworkPropertyMetadata(ValueBoxes.FalseBox));
|
||||
MaximumProperty.OverrideMetadata(typeof(WaveProgressBar),
|
||||
new FrameworkPropertyMetadata(ValueBoxes.Double100Box));
|
||||
}
|
||||
|
||||
protected override void OnValueChanged(double oldValue, double newValue)
|
||||
{
|
||||
base.OnValueChanged(oldValue, newValue);
|
||||
|
||||
UpdateWave(newValue);
|
||||
}
|
||||
|
||||
private void UpdateWave(double value)
|
||||
{
|
||||
if (_translateTransform == null || MathHelper.IsVerySmall(Maximum)) return;
|
||||
var scale = 1 - value / Maximum;
|
||||
var y = _translateTransformYRange * scale + TranslateTransformMinY;
|
||||
_translateTransform.Y = y;
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_waveElement = GetTemplateChild(ElementWave) as FrameworkElement;
|
||||
var clipElement = GetTemplateChild(ElementClip) as FrameworkElement;
|
||||
|
||||
if (_waveElement != null && clipElement != null)
|
||||
{
|
||||
_translateTransform = new TranslateTransform
|
||||
{
|
||||
Y = clipElement.Height
|
||||
};
|
||||
_translateTransformYRange = clipElement.Height - TranslateTransformMinY;
|
||||
_waveElement.RenderTransform = new TransformGroup
|
||||
{
|
||||
Children = {_translateTransform}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
|
||||
"Text", typeof(string), typeof(WaveProgressBar), new PropertyMetadata(default(string)));
|
||||
|
||||
public string Text
|
||||
{
|
||||
get => (string)GetValue(TextProperty);
|
||||
set => SetValue(TextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowTextProperty = DependencyProperty.Register(
|
||||
"ShowText", typeof(bool), typeof(WaveProgressBar), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool ShowText
|
||||
{
|
||||
get => (bool)GetValue(ShowTextProperty);
|
||||
set => SetValue(ShowTextProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty WaveFillProperty = DependencyProperty.Register(
|
||||
"WaveFill", typeof(Brush), typeof(WaveProgressBar), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public Brush WaveFill
|
||||
{
|
||||
get => (Brush) GetValue(WaveFillProperty);
|
||||
set => SetValue(WaveFillProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty WaveThicknessProperty = DependencyProperty.Register(
|
||||
"WaveThickness", typeof(double), typeof(WaveProgressBar), new PropertyMetadata(ValueBoxes.Double0Box));
|
||||
|
||||
public double WaveThickness
|
||||
{
|
||||
get => (double) GetValue(WaveThicknessProperty);
|
||||
set => SetValue(WaveThicknessProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty WaveStrokeProperty = DependencyProperty.Register(
|
||||
"WaveStroke", typeof(Brush), typeof(WaveProgressBar), new PropertyMetadata(default(Brush)));
|
||||
|
||||
public Brush WaveStroke
|
||||
{
|
||||
get => (Brush) GetValue(WaveStrokeProperty);
|
||||
set => SetValue(WaveStrokeProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,27 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,294 @@
|
||||
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 => new Lazy<TabControl>(() => ItemsControl.ItemsControlFromItemContainer(this) as TabControl).Value;
|
||||
|
||||
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,244 @@
|
||||
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));
|
||||
UseLayoutRoundingProperty.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,91 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Animation;
|
||||
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)),
|
||||
EaseY = new PowerEase(),
|
||||
EaseX = new PowerEase()
|
||||
};
|
||||
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,260 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
[TemplatePart(Name = ElementButtonConfirm, Type = typeof(Button))]
|
||||
[TemplatePart(Name = ElementClockPresenter, Type = typeof(ContentPresenter))]
|
||||
[TemplatePart(Name = ElementCalendarPresenter, Type = typeof(ContentPresenter))]
|
||||
public class CalendarWithClock : Control
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementButtonConfirm = "PART_ButtonConfirm";
|
||||
|
||||
private const string ElementClockPresenter = "PART_ClockPresenter";
|
||||
|
||||
private const string ElementCalendarPresenter = "PART_CalendarPresenter";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private ContentPresenter _clockPresenter;
|
||||
|
||||
private ContentPresenter _calendarPresenter;
|
||||
|
||||
private Clock _clock;
|
||||
|
||||
private Calendar _calendar;
|
||||
|
||||
private Button _buttonConfirm;
|
||||
|
||||
private bool _isLoaded;
|
||||
|
||||
private IDictionary<DependencyProperty, bool> _isHandlerSuspended;
|
||||
|
||||
#endregion Data
|
||||
|
||||
#region Public Events
|
||||
|
||||
public static readonly RoutedEvent SelectedDateTimeChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("SelectedDateTimeChanged", RoutingStrategy.Direct,
|
||||
typeof(EventHandler<FunctionEventArgs<DateTime?>>), typeof(CalendarWithClock));
|
||||
|
||||
public event EventHandler<FunctionEventArgs<DateTime?>> SelectedDateTimeChanged
|
||||
{
|
||||
add => AddHandler(SelectedDateTimeChangedEvent, value);
|
||||
remove => RemoveHandler(SelectedDateTimeChangedEvent, value);
|
||||
}
|
||||
|
||||
public event EventHandler<FunctionEventArgs<DateTime>> DisplayDateTimeChanged;
|
||||
|
||||
public event Action Confirmed;
|
||||
|
||||
#endregion Public Events
|
||||
|
||||
public CalendarWithClock()
|
||||
{
|
||||
InitCalendarAndClock();
|
||||
Loaded += (s, e) =>
|
||||
{
|
||||
if (_isLoaded) return;
|
||||
_isLoaded = true;
|
||||
DisplayDateTime = SelectedDateTime ?? DateTime.Now;
|
||||
};
|
||||
}
|
||||
|
||||
#region Public Properties
|
||||
|
||||
public static readonly DependencyProperty DateTimeFormatProperty = DependencyProperty.Register(
|
||||
"DateTimeFormat", typeof(string), typeof(CalendarWithClock), new PropertyMetadata("yyyy-MM-dd HH:mm:ss"));
|
||||
|
||||
public string DateTimeFormat
|
||||
{
|
||||
get => (string)GetValue(DateTimeFormatProperty);
|
||||
set => SetValue(DateTimeFormatProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowConfirmButtonProperty = DependencyProperty.Register(
|
||||
"ShowConfirmButton", typeof(bool), typeof(CalendarWithClock), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool ShowConfirmButton
|
||||
{
|
||||
get => (bool)GetValue(ShowConfirmButtonProperty);
|
||||
set => SetValue(ShowConfirmButtonProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty SelectedDateTimeProperty = DependencyProperty.Register(
|
||||
"SelectedDateTime", typeof(DateTime?), typeof(CalendarWithClock), new PropertyMetadata(default(DateTime?), OnSelectedDateTimeChanged));
|
||||
|
||||
private static void OnSelectedDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (CalendarWithClock)d;
|
||||
var v = (DateTime?)e.NewValue;
|
||||
ctl.OnSelectedDateTimeChanged(new FunctionEventArgs<DateTime?>(SelectedDateTimeChangedEvent, ctl)
|
||||
{
|
||||
Info = v
|
||||
});
|
||||
}
|
||||
|
||||
public DateTime? SelectedDateTime
|
||||
{
|
||||
get => (DateTime?)GetValue(SelectedDateTimeProperty);
|
||||
set => SetValue(SelectedDateTimeProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DisplayDateTimeProperty = DependencyProperty.Register(
|
||||
"DisplayDateTime", typeof(DateTime), typeof(CalendarWithClock), new FrameworkPropertyMetadata(DateTime.MinValue, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnDisplayDateTimeChanged));
|
||||
|
||||
private static void OnDisplayDateTimeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
var ctl = (CalendarWithClock)d;
|
||||
if (ctl.IsHandlerSuspended(DisplayDateTimeProperty)) return;
|
||||
var v = (DateTime)e.NewValue;
|
||||
ctl._clock.SelectedTime = v;
|
||||
ctl._calendar.SelectedDate = v;
|
||||
ctl.OnDisplayDateTimeChanged(new FunctionEventArgs<DateTime>(v));
|
||||
}
|
||||
|
||||
public DateTime DisplayDateTime
|
||||
{
|
||||
get => (DateTime)GetValue(DisplayDateTimeProperty);
|
||||
set => SetValue(DisplayDateTimeProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
if (_buttonConfirm != null)
|
||||
{
|
||||
_buttonConfirm.Click -= ButtonConfirm_OnClick;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_buttonConfirm = GetTemplateChild(ElementButtonConfirm) as Button;
|
||||
_clockPresenter = GetTemplateChild(ElementClockPresenter) as ContentPresenter;
|
||||
_calendarPresenter = GetTemplateChild(ElementCalendarPresenter) as ContentPresenter;
|
||||
|
||||
CheckNull();
|
||||
|
||||
_clockPresenter.Content = _clock;
|
||||
_calendarPresenter.Content = _calendar;
|
||||
|
||||
_buttonConfirm.Click += ButtonConfirm_OnClick;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Protected Methods
|
||||
|
||||
protected virtual void OnSelectedDateTimeChanged(FunctionEventArgs<DateTime?> e) => RaiseEvent(e);
|
||||
|
||||
protected virtual void OnDisplayDateTimeChanged(FunctionEventArgs<DateTime> e)
|
||||
{
|
||||
var handler = DisplayDateTimeChanged;
|
||||
handler?.Invoke(this, e);
|
||||
}
|
||||
|
||||
#endregion Protected Methods
|
||||
|
||||
#region Private Methods
|
||||
|
||||
private void SetIsHandlerSuspended(DependencyProperty property, bool value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
if (_isHandlerSuspended == null)
|
||||
{
|
||||
_isHandlerSuspended = new Dictionary<DependencyProperty, bool>(2);
|
||||
}
|
||||
|
||||
_isHandlerSuspended[property] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_isHandlerSuspended?.Remove(property);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetValueNoCallback(DependencyProperty property, object value)
|
||||
{
|
||||
SetIsHandlerSuspended(property, true);
|
||||
try
|
||||
{
|
||||
SetCurrentValue(property, value);
|
||||
}
|
||||
finally
|
||||
{
|
||||
SetIsHandlerSuspended(property, false);
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsHandlerSuspended(DependencyProperty property)
|
||||
{
|
||||
return _isHandlerSuspended != null && _isHandlerSuspended.ContainsKey(property);
|
||||
}
|
||||
|
||||
private void CheckNull()
|
||||
{
|
||||
if (_buttonConfirm == null || _clockPresenter == null || _calendarPresenter == null) throw new Exception();
|
||||
}
|
||||
|
||||
private void ButtonConfirm_OnClick(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SelectedDateTime = DisplayDateTime;
|
||||
Confirmed?.Invoke();
|
||||
}
|
||||
|
||||
private void InitCalendarAndClock()
|
||||
{
|
||||
_clock = new Clock
|
||||
{
|
||||
BorderThickness = new Thickness(),
|
||||
Background = Brushes.Transparent
|
||||
};
|
||||
TitleElement.SetBackground(_clock, Brushes.Transparent);
|
||||
_clock.DisplayTimeChanged += Clock_DisplayTimeChanged;
|
||||
|
||||
_calendar = new Calendar
|
||||
{
|
||||
BorderThickness = new Thickness(),
|
||||
Background = Brushes.Transparent,
|
||||
Focusable = false
|
||||
};
|
||||
TitleElement.SetBackground(_calendar, Brushes.Transparent);
|
||||
_calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged;
|
||||
}
|
||||
|
||||
private void Calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
Mouse.Capture(null);
|
||||
UpdateDisplayTime();
|
||||
}
|
||||
|
||||
private void Clock_DisplayTimeChanged(object sender, FunctionEventArgs<DateTime> e) => UpdateDisplayTime();
|
||||
|
||||
private void UpdateDisplayTime()
|
||||
{
|
||||
if (_calendar.SelectedDate != null)
|
||||
{
|
||||
var date = _calendar.SelectedDate.Value;
|
||||
var time = _clock.DisplayTime;
|
||||
|
||||
var result = new DateTime(date.Year, date.Month, date.Day, time.Hour, time.Minute, time.Second);
|
||||
SetValueNoCallback(DisplayDateTimeProperty, result);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -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,223 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using HandyControl.Interactivity;
|
||||
using HandyControl.Tools.Extension;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 穿梭框
|
||||
/// </summary>
|
||||
[TemplatePart(Name = ElementItemsOrigin, Type = typeof(SimpleItemsControl))]
|
||||
[TemplatePart(Name = ElementItemsSelected, Type = typeof(SimpleItemsControl))]
|
||||
public class Transfer : SimpleItemsControl
|
||||
{
|
||||
private const string ElementItemsOrigin = "PART_ItemsOrigin";
|
||||
|
||||
private const string ElementItemsSelected = "PART_ItemsSelected";
|
||||
|
||||
private readonly Dictionary<object, TransferEntry> _entryDic = new Dictionary<object, TransferEntry>();
|
||||
|
||||
private SimpleItemsControl _itemsOrigin;
|
||||
|
||||
private SimpleItemsControl _itemsSelected;
|
||||
|
||||
private IEnumerable _itemsSourceInternal;
|
||||
|
||||
public static readonly RoutedEvent SelectionChangedEvent =
|
||||
EventManager.RegisterRoutedEvent("SelectionChanged", RoutingStrategy.Bubble,
|
||||
typeof(SelectionChangedEventHandler), typeof(Transfer));
|
||||
|
||||
public event SelectionChangedEventHandler SelectionChanged
|
||||
{
|
||||
add => AddHandler(SelectionChangedEvent, value);
|
||||
remove => RemoveHandler(SelectionChangedEvent, value);
|
||||
}
|
||||
|
||||
private static readonly DependencyPropertyKey SelectedItemsPropertyKey =
|
||||
DependencyProperty.RegisterReadOnly(
|
||||
"SelectedItems",
|
||||
typeof(IList),
|
||||
typeof(Transfer),
|
||||
new FrameworkPropertyMetadata(default(IList)));
|
||||
|
||||
public static readonly DependencyProperty SelectedItemsProperty = SelectedItemsPropertyKey.DependencyProperty;
|
||||
|
||||
public IList SelectedItems
|
||||
{
|
||||
get => (IList) GetValue(SelectedItemsProperty);
|
||||
set => SetValue(SelectedItemsProperty, value);
|
||||
}
|
||||
|
||||
public Transfer()
|
||||
{
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Selected, SelectItems));
|
||||
CommandBindings.Add(new CommandBinding(ControlCommands.Cancel, DeselectItems));
|
||||
|
||||
SetValue(SelectedItemsPropertyKey, new ObservableCollection<object>());
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
_itemsOrigin?.ItemsHost.Children.Clear();
|
||||
_itemsSelected?.ItemsHost.Children.Clear();
|
||||
|
||||
_itemsOrigin = GetTemplateChild(ElementItemsOrigin) as SimpleItemsControl;
|
||||
_itemsSelected = GetTemplateChild(ElementItemsSelected) as SimpleItemsControl;
|
||||
|
||||
Refresh();
|
||||
}
|
||||
|
||||
private void AddItem(object item)
|
||||
{
|
||||
var origin = new TransferItem
|
||||
{
|
||||
Content = item
|
||||
};
|
||||
var selected = new TransferItem
|
||||
{
|
||||
Content = item,
|
||||
IsOrigin = false
|
||||
};
|
||||
var entry = new TransferEntry
|
||||
{
|
||||
OriginItem = origin,
|
||||
SelectedItem = selected
|
||||
};
|
||||
_entryDic.Add(item, entry);
|
||||
_itemsOrigin.Items.Add(origin);
|
||||
_itemsSelected.Items.Add(selected);
|
||||
}
|
||||
|
||||
private void RemoveItem(object item)
|
||||
{
|
||||
if (_entryDic.TryGetValue(item, out var entry))
|
||||
{
|
||||
_itemsOrigin.Items.Remove(entry.OriginItem);
|
||||
_itemsSelected.Items.Remove(entry.SelectedItem);
|
||||
_entryDic.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearItems()
|
||||
{
|
||||
_entryDic.Clear();
|
||||
_itemsOrigin.Items.Clear();
|
||||
_itemsSelected.Items.Clear();
|
||||
}
|
||||
|
||||
protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue)
|
||||
{
|
||||
if (_itemsSourceInternal != null)
|
||||
{
|
||||
if (_itemsSourceInternal is INotifyCollectionChanged s)
|
||||
{
|
||||
s.CollectionChanged -= InternalCollectionChanged;
|
||||
}
|
||||
|
||||
ClearItems();
|
||||
}
|
||||
_itemsSourceInternal = newValue;
|
||||
if (_itemsSourceInternal != null)
|
||||
{
|
||||
if (_itemsSourceInternal is INotifyCollectionChanged s)
|
||||
{
|
||||
s.CollectionChanged += InternalCollectionChanged;
|
||||
}
|
||||
foreach (var item in _itemsSourceInternal)
|
||||
{
|
||||
AddItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void InternalCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
foreach (var item in e.OldItems)
|
||||
{
|
||||
RemoveItem(item);
|
||||
}
|
||||
}
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
foreach (var item in e.NewItems)
|
||||
{
|
||||
AddItem(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride() => new TransferItem();
|
||||
|
||||
protected override bool IsItemItsOwnContainerOverride(object item) => item is TransferItem;
|
||||
|
||||
private void SelectItems(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var list = _entryDic.Where(item => item.Value.OriginItem.IsSelected).ToList();
|
||||
list.Do(item => item.Value.StatusSwitch(true)).Do(item => SelectedItems.Add(item.Key));
|
||||
RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, new List<object>(), list));
|
||||
}
|
||||
|
||||
private void DeselectItems(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var list = _entryDic.Where(item => item.Value.SelectedItem.IsSelected).ToList();
|
||||
list.Do(item => item.Value.StatusSwitch(false)).Do(item => SelectedItems.Remove(item.Key));
|
||||
RaiseEvent(new SelectionChangedEventArgs(SelectionChangedEvent, list, new List<object>()));
|
||||
}
|
||||
|
||||
protected override void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void OnItemTemplateChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void OnItemContainerStyleChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Refresh()
|
||||
{
|
||||
if (_entryDic.Count > 0)
|
||||
{
|
||||
_entryDic.Select(item => item.Value.OriginItem).Do(item => _itemsOrigin.Items.Add(item));
|
||||
_entryDic.Select(item => item.Value.SelectedItem).Do(item => _itemsSelected.Items.Add(item));
|
||||
}
|
||||
}
|
||||
|
||||
private struct TransferEntry
|
||||
{
|
||||
public TransferItem OriginItem { get; set; }
|
||||
|
||||
public TransferItem SelectedItem { get; set; }
|
||||
|
||||
public void StatusSwitch(bool isTransferred)
|
||||
{
|
||||
if (isTransferred)
|
||||
{
|
||||
OriginItem.IsTransferred = true;
|
||||
SelectedItem.IsTransferred = true;
|
||||
OriginItem.IsSelected = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
OriginItem.IsTransferred = false;
|
||||
SelectedItem.IsTransferred = false;
|
||||
SelectedItem.IsSelected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Collections.Specialized;
|
||||
using System.Windows;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class TransferGroup : SimpleItemsControl
|
||||
{
|
||||
protected override void Refresh()
|
||||
{
|
||||
if (Items.Count > 0)
|
||||
{
|
||||
foreach (FrameworkElement item in Items)
|
||||
{
|
||||
item.Style = ItemContainerStyle;
|
||||
ItemsHost.Children.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnItemTemplateChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void OnItemContainerStyleChanged(DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override DependencyObject GetContainerForItemOverride() => new TransferItem();
|
||||
|
||||
protected override bool IsItemItsOwnContainerOverride(object item) => item is TransferItem;
|
||||
|
||||
protected override void OnItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
if (ItemsHost == null) return;
|
||||
if (e.OldItems != null)
|
||||
{
|
||||
foreach (FrameworkElement item in e.OldItems)
|
||||
{
|
||||
ItemsHost.Children.Remove(item);
|
||||
}
|
||||
}
|
||||
if (e.NewItems != null)
|
||||
{
|
||||
foreach (FrameworkElement item in e.NewItems)
|
||||
{
|
||||
item.Style = ItemContainerStyle;
|
||||
ItemsHost.Children.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Windows;
|
||||
using HandyControl.Data;
|
||||
|
||||
namespace HandyControl.Controls
|
||||
{
|
||||
public class TransferItem : SelectableItem
|
||||
{
|
||||
public static readonly DependencyProperty IsTransferredProperty = DependencyProperty.Register(
|
||||
"IsTransferred", typeof(bool), typeof(TransferItem), new PropertyMetadata(ValueBoxes.FalseBox));
|
||||
|
||||
public bool IsTransferred
|
||||
{
|
||||
get => (bool) GetValue(IsTransferredProperty);
|
||||
set => SetValue(IsTransferredProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsOriginProperty = DependencyProperty.Register(
|
||||
"IsOrigin", typeof(bool), typeof(TransferItem), new PropertyMetadata(ValueBoxes.TrueBox));
|
||||
|
||||
public bool IsOrigin
|
||||
{
|
||||
get => (bool) GetValue(IsOriginProperty);
|
||||
internal set => SetValue(IsOriginProperty, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 = ElementPanelTop, Type = typeof(Panel))]
|
||||
[TemplatePart(Name = ElementImageViewer, Type = typeof(ImageViewer))]
|
||||
public class ImageBrowser : Window
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const string ElementPanelTop = "PART_PanelTop";
|
||||
|
||||
private const string ElementImageViewer = "PART_ImageViewer";
|
||||
|
||||
#endregion Constants
|
||||
|
||||
#region Data
|
||||
|
||||
private Panel _panelTop;
|
||||
|
||||
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 (_panelTop != null)
|
||||
{
|
||||
_panelTop.MouseLeftButtonDown -= PanelTopOnMouseLeftButtonDown;
|
||||
}
|
||||
|
||||
if (_imageViewer != null)
|
||||
{
|
||||
_imageViewer.MouseLeftButtonDown -= ImageViewer_MouseLeftButtonDown;
|
||||
}
|
||||
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_panelTop = GetTemplateChild(ElementPanelTop) as Panel;
|
||||
_imageViewer = GetTemplateChild(ElementImageViewer) as ImageViewer;
|
||||
|
||||
if (_panelTop != null)
|
||||
{
|
||||
_panelTop.MouseLeftButtonDown += PanelTopOnMouseLeftButtonDown;
|
||||
}
|
||||
|
||||
if (_imageViewer != null)
|
||||
{
|
||||
_imageViewer.MouseLeftButtonDown += ImageViewer_MouseLeftButtonDown;
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonClose_OnClick(object sender, RoutedEventArgs e) => Close();
|
||||
|
||||
private void PanelTopOnMouseLeftButtonDown(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,436 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Text;
|
||||
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));
|
||||
}
|
||||
|
||||
protected override void OnPreviewKeyDown(KeyEventArgs e)
|
||||
{
|
||||
base.OnPreviewKeyDown(e);
|
||||
|
||||
if (e.KeyboardDevice.Modifiers == ModifierKeys.Control && e.Key == Key.C)
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
var line = new string('-', 27);
|
||||
builder.Append(line);
|
||||
builder.Append(Environment.NewLine);
|
||||
builder.Append(Title);
|
||||
builder.Append(Environment.NewLine);
|
||||
builder.Append(line);
|
||||
builder.Append(Environment.NewLine);
|
||||
builder.Append(Message);
|
||||
builder.Append(Environment.NewLine);
|
||||
builder.Append(line);
|
||||
builder.Append(Environment.NewLine);
|
||||
if (_showOk)
|
||||
{
|
||||
builder.Append(Properties.Langs.Lang.Confirm);
|
||||
builder.Append(" ");
|
||||
}
|
||||
if (_showYes)
|
||||
{
|
||||
builder.Append(Properties.Langs.Lang.Yes);
|
||||
builder.Append(" ");
|
||||
}
|
||||
if (_showNo)
|
||||
{
|
||||
builder.Append(Properties.Langs.Lang.No);
|
||||
builder.Append(" ");
|
||||
}
|
||||
if (_showCancel)
|
||||
{
|
||||
builder.Append(Properties.Langs.Lang.Cancel);
|
||||
builder.Append(" ");
|
||||
}
|
||||
builder.Append(Environment.NewLine);
|
||||
builder.Append(line);
|
||||
builder.Append(Environment.NewLine);
|
||||
Clipboard.SetText(builder.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText"></param>
|
||||
/// <param name="caption"></param>
|
||||
public static MessageBoxResult Success(string messageBoxText, string caption = null)
|
||||
{
|
||||
MessageBox messageBox = null;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
MessageBox messageBox = null;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
MessageBox messageBox = null;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
MessageBox messageBox = null;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
MessageBox messageBox = null;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
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)
|
||||
{
|
||||
MessageBox messageBox = null;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
messageBox = CreateMessageBox(null, messageBoxText, caption, MessageBoxButton.OKCancel, MessageBoxImage.Question, MessageBoxResult.Cancel);
|
||||
SetButtonStatus(messageBox, MessageBoxButton.OKCancel);
|
||||
SetImage(messageBox, MessageBoxImage.Question);
|
||||
messageBox.ShowDialog();
|
||||
});
|
||||
|
||||
return messageBox._messageBoxResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 自定义信息展示
|
||||
/// </summary>
|
||||
/// <param name="info"></param>
|
||||
/// <returns></returns>
|
||||
public static MessageBoxResult Show(MessageBoxInfo info)
|
||||
{
|
||||
MessageBox messageBox = null;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
messageBox = CreateMessageBox(null, info.MessageBoxText, info.Caption, info.Button, MessageBoxImage.None, info.DefaultResult);
|
||||
SetButtonStatus(messageBox, info.Button);
|
||||
|
||||
if (!string.IsNullOrEmpty(info.IconKey))
|
||||
{
|
||||
messageBox.ShowImage = true;
|
||||
messageBox.Image = ResourceHelper.GetResource<Geometry>(info.IconKey);
|
||||
messageBox.ImageBrush = ResourceHelper.GetResource<Brush>(info.IconBrushKey);
|
||||
}
|
||||
|
||||
messageBox.Style = info.Style;
|
||||
messageBox.ShowDialog();
|
||||
});
|
||||
|
||||
return messageBox._messageBoxResult;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 信息展示
|
||||
/// </summary>
|
||||
/// <param name="messageBoxText"></param>
|
||||
/// <param name="caption"></param>
|
||||
/// <param name="button"></param>
|
||||
/// <param name="icon"></param>
|
||||
/// <param name="defaultResult"></param>
|
||||
/// <returns></returns>
|
||||
public static MessageBoxResult Show(string messageBoxText, string caption = null,
|
||||
MessageBoxButton button = MessageBoxButton.OK, MessageBoxImage icon = MessageBoxImage.None,
|
||||
MessageBoxResult defaultResult = MessageBoxResult.None) =>
|
||||
Show(null, messageBoxText, caption, button, icon, defaultResult);
|
||||
|
||||
/// <summary>
|
||||
/// 信息展示
|
||||
/// </summary>
|
||||
/// <param name="owner"></param>
|
||||
/// <param name="messageBoxText"></param>
|
||||
/// <param name="caption"></param>
|
||||
/// <param name="button"></param>
|
||||
/// <param name="icon"></param>
|
||||
/// <param name="defaultResult"></param>
|
||||
/// <returns></returns>
|
||||
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)
|
||||
{
|
||||
MessageBox messageBox = null;
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
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 ?? VisualHelper.GetActiveWindow();
|
||||
var ownerIsNull = ownerWindow is null;
|
||||
|
||||
return new MessageBox
|
||||
{
|
||||
Message = messageBoxText,
|
||||
Owner = ownerWindow,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
ShowTitle = true,
|
||||
Title = caption ?? string.Empty,
|
||||
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,352 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using HandyControl.Data;
|
||||
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 Thickness _actualBorderThickness;
|
||||
|
||||
private UIElement _nonClientArea;
|
||||
|
||||
private bool _showNonClientArea = true;
|
||||
|
||||
private double _tempNonClientAreaHeight;
|
||||
|
||||
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 += (s, e) => OnLoaded(e);
|
||||
}
|
||||
|
||||
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 = _actualBorderThickness;
|
||||
}
|
||||
}
|
||||
|
||||
protected void OnLoaded(RoutedEventArgs args)
|
||||
{
|
||||
_actualBorderThickness = 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);
|
||||
|
||||
if (SizeToContent != SizeToContent.WidthAndHeight)
|
||||
return;
|
||||
|
||||
SizeToContent = SizeToContent.Height;
|
||||
Dispatcher.BeginInvoke(new Action(() => { SizeToContent = SizeToContent.WidthAndHeight; }));
|
||||
}
|
||||
|
||||
public override void OnApplyTemplate()
|
||||
{
|
||||
base.OnApplyTemplate();
|
||||
|
||||
_nonClientArea = GetTemplateChild(ElementNonClientArea) as UIElement;
|
||||
}
|
||||
|
||||
private void ShowSystemMenu(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
var point = WindowState == WindowState.Maximized
|
||||
? new Point(0, NonClientAreaHeight)
|
||||
: new Point(Left, Top + NonClientAreaHeight);
|
||||
SystemCommands.ShowSystemMenu(this, point);
|
||||
}
|
||||
|
||||
protected override void OnContentRendered(EventArgs e)
|
||||
{
|
||||
base.OnContentRendered(e);
|
||||
if (SizeToContent == SizeToContent.WidthAndHeight)
|
||||
InvalidateMeasure();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user