From a0b6fc87f925dd6f32d8f974a31995de14792885 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=B3=E8=BE=B9?= <836904362@qq.com> Date: Tue, 22 May 2018 15:22:32 +0800 Subject: [PATCH] Add ColorPicker --- .gitignore | 1 + HandyControl/Controls/ColorPicker.xaml | 128 ++ HandyControl/Controls/ColorPicker.xaml.cs | 558 ++++++++ HandyControl/Controls/IconElement.cs | 35 + HandyControl/Controls/Info/InfoControl.cs | 374 ++++++ .../Controls/Info/InfoNumericUpDown.xaml | 65 + .../Controls/Info/InfoNumericUpDown.xaml.cs | 191 +++ HandyControl/Controls/Info/InfoTextBox.xaml | 61 + .../Controls/Info/InfoTextBox.xaml.cs | 45 + HandyControl/Data/ColorRange.cs | 75 ++ HandyControl/Data/Enum/IpType.cs | 29 + HandyControl/Data/Enum/TextType.cs | 26 + HandyControl/Data/Enum/TitleAlignment.cs | 11 + HandyControl/Data/FunctionEventArgs!1.cs | 14 + HandyControl/HandyControl.csproj | 165 +++ .../Interactivity/AdornerContainer.cs | 40 + .../Interactivity/AttachableCollection!1.cs | 168 +++ HandyControl/Interactivity/Behavior!1.cs | 13 + HandyControl/Interactivity/Behavior.cs | 87 ++ .../Interactivity/BehaviorCollection.cs | 42 + .../Interactivity/DefaultTriggerAttribute.cs | 49 + HandyControl/Interactivity/EventTrigger.cs | 29 + .../Interactivity/EventTriggerBase!1.cs | 21 + .../Interactivity/EventTriggerBase.cs | 313 +++++ .../Interactivity/ExceptionStringTable.cs | 58 + .../ExtendedVisualStateManager.cs | 1141 +++++++++++++++++ .../Interactivity/FluidMoveBehavior.cs | 448 +++++++ .../Interactivity/FluidMoveBehaviorBase.cs | 195 +++ HandyControl/Interactivity/FluidMoveScope.cs | 8 + HandyControl/Interactivity/IAttachedObject.cs | 12 + HandyControl/Interactivity/Interaction.cs | 77 ++ .../Interactivity/MouseDragElementBehavior.cs | 352 +++++ .../Interactivity/NameResolvedEventArgs.cs | 17 + HandyControl/Interactivity/NameResolver.cs | 124 ++ .../Interactivity/PreviewInvokeEventArgs.cs | 9 + HandyControl/Interactivity/TagType.cs | 8 + .../Interactivity/TransitionEffect.cs | 56 + HandyControl/Interactivity/TriggerAction.cs | 118 ++ .../Interactivity/TriggerActionCollection.cs | 46 + HandyControl/Interactivity/TriggerBase.cs | 110 ++ .../Interactivity/TriggerCollection.cs | 42 + .../Interactivity/TypeConstraintAttribute.cs | 15 + HandyControl/Resources/colorPickerBack.png | Bin 0 -> 34435 bytes HandyControl/Themes/Basic/Basic.xaml | 16 + HandyControl/Themes/Basic/Brushes.xaml | 35 + HandyControl/Themes/Basic/Colors.xaml | 35 + HandyControl/Themes/Basic/Converters.xaml | 18 + HandyControl/Themes/Basic/Effects.xaml | 13 + HandyControl/Themes/Basic/Fonts.xaml | 10 + HandyControl/Themes/Basic/Geometries.xaml | 23 + HandyControl/Themes/Basic/Paths.xaml | 109 ++ HandyControl/Themes/Basic/Sizes.xaml | 8 + .../Themes/Styles/Base/BaseStyle.xaml | 9 + .../Styles/Base/ButtonBaseBaseStyle.xaml | 20 + .../Themes/Styles/Base/ButtonBaseStyle.xaml | 90 ++ .../Styles/Base/TextBlockBaseStyle.xaml | 15 + HandyControl/Themes/Styles/Border.xaml | 24 + HandyControl/Themes/Styles/Button.xaml | 214 ++++ HandyControl/Themes/Styles/Slider.xaml | 123 ++ HandyControl/Themes/Styles/Style.xaml | 31 + HandyControl/Themes/Styles/TextBlock.xaml | 56 + HandyControl/Themes/Styles/Window.xaml | 9 + HandyControl/Themes/ThemesDefault.xaml | 8 + HandyControl/Tools/AnimationHelper.cs | 40 + .../Converter/Boolean2BooleanFeConverter.cs | 23 + .../Boolean2VisibilityFeConverter.cs | 20 + .../BooleanArr2VisibilityConverter.cs | 39 + .../Converter/Color2ChannelAConverter.cs | 39 + .../Tools/Converter/Color2HexStrConverter.cs | 40 + .../Tools/Converter/IsNullConverter.cs | 20 + .../Tools/Converter/Long2FileSizeConverter.cs | 52 + .../Converter/Object2BooleanConverter.cs | 16 + .../Tools/Converter/Source2ImageConverter.cs | 20 + .../Converter/String2VisibilityConverter.cs | 20 + .../Converter/String2VisibilityFeConverter.cs | 20 + .../Converter/TreeViewItemMarginConverter.cs | 29 + .../Tools/Extension/ColorExtension.cs | 33 + .../Tools/Extension/UIElementExtension.cs | 45 + HandyControl/Tools/RegularJudgment.cs | 96 ++ HandyControl/Tools/RegularPatterns.cs | 152 +++ HandyControlDemo/App.xaml | 9 +- HandyControlDemo/Data/Model/CellGateInfo.cs | 15 + HandyControlDemo/Data/Model/CellGateRecord.cs | 17 + HandyControlDemo/HandyControlDemo.csproj | 8 + HandyControlDemo/MainWindow.xaml | 853 +++++++++++- HandyControlDemo/MainWindow.xaml.cs | 321 ++++- 86 files changed, 8160 insertions(+), 9 deletions(-) create mode 100644 HandyControl/Controls/ColorPicker.xaml create mode 100644 HandyControl/Controls/ColorPicker.xaml.cs create mode 100644 HandyControl/Controls/IconElement.cs create mode 100644 HandyControl/Controls/Info/InfoControl.cs create mode 100644 HandyControl/Controls/Info/InfoNumericUpDown.xaml create mode 100644 HandyControl/Controls/Info/InfoNumericUpDown.xaml.cs create mode 100644 HandyControl/Controls/Info/InfoTextBox.xaml create mode 100644 HandyControl/Controls/Info/InfoTextBox.xaml.cs create mode 100644 HandyControl/Data/ColorRange.cs create mode 100644 HandyControl/Data/Enum/IpType.cs create mode 100644 HandyControl/Data/Enum/TextType.cs create mode 100644 HandyControl/Data/Enum/TitleAlignment.cs create mode 100644 HandyControl/Data/FunctionEventArgs!1.cs create mode 100644 HandyControl/Interactivity/AdornerContainer.cs create mode 100644 HandyControl/Interactivity/AttachableCollection!1.cs create mode 100644 HandyControl/Interactivity/Behavior!1.cs create mode 100644 HandyControl/Interactivity/Behavior.cs create mode 100644 HandyControl/Interactivity/BehaviorCollection.cs create mode 100644 HandyControl/Interactivity/DefaultTriggerAttribute.cs create mode 100644 HandyControl/Interactivity/EventTrigger.cs create mode 100644 HandyControl/Interactivity/EventTriggerBase!1.cs create mode 100644 HandyControl/Interactivity/EventTriggerBase.cs create mode 100644 HandyControl/Interactivity/ExceptionStringTable.cs create mode 100644 HandyControl/Interactivity/ExtendedVisualStateManager.cs create mode 100644 HandyControl/Interactivity/FluidMoveBehavior.cs create mode 100644 HandyControl/Interactivity/FluidMoveBehaviorBase.cs create mode 100644 HandyControl/Interactivity/FluidMoveScope.cs create mode 100644 HandyControl/Interactivity/IAttachedObject.cs create mode 100644 HandyControl/Interactivity/Interaction.cs create mode 100644 HandyControl/Interactivity/MouseDragElementBehavior.cs create mode 100644 HandyControl/Interactivity/NameResolvedEventArgs.cs create mode 100644 HandyControl/Interactivity/NameResolver.cs create mode 100644 HandyControl/Interactivity/PreviewInvokeEventArgs.cs create mode 100644 HandyControl/Interactivity/TagType.cs create mode 100644 HandyControl/Interactivity/TransitionEffect.cs create mode 100644 HandyControl/Interactivity/TriggerAction.cs create mode 100644 HandyControl/Interactivity/TriggerActionCollection.cs create mode 100644 HandyControl/Interactivity/TriggerBase.cs create mode 100644 HandyControl/Interactivity/TriggerCollection.cs create mode 100644 HandyControl/Interactivity/TypeConstraintAttribute.cs create mode 100644 HandyControl/Resources/colorPickerBack.png create mode 100644 HandyControl/Themes/Basic/Basic.xaml create mode 100644 HandyControl/Themes/Basic/Brushes.xaml create mode 100644 HandyControl/Themes/Basic/Colors.xaml create mode 100644 HandyControl/Themes/Basic/Converters.xaml create mode 100644 HandyControl/Themes/Basic/Effects.xaml create mode 100644 HandyControl/Themes/Basic/Fonts.xaml create mode 100644 HandyControl/Themes/Basic/Geometries.xaml create mode 100644 HandyControl/Themes/Basic/Paths.xaml create mode 100644 HandyControl/Themes/Basic/Sizes.xaml create mode 100644 HandyControl/Themes/Styles/Base/BaseStyle.xaml create mode 100644 HandyControl/Themes/Styles/Base/ButtonBaseBaseStyle.xaml create mode 100644 HandyControl/Themes/Styles/Base/ButtonBaseStyle.xaml create mode 100644 HandyControl/Themes/Styles/Base/TextBlockBaseStyle.xaml create mode 100644 HandyControl/Themes/Styles/Border.xaml create mode 100644 HandyControl/Themes/Styles/Button.xaml create mode 100644 HandyControl/Themes/Styles/Slider.xaml create mode 100644 HandyControl/Themes/Styles/Style.xaml create mode 100644 HandyControl/Themes/Styles/TextBlock.xaml create mode 100644 HandyControl/Themes/Styles/Window.xaml create mode 100644 HandyControl/Themes/ThemesDefault.xaml create mode 100644 HandyControl/Tools/AnimationHelper.cs create mode 100644 HandyControl/Tools/Converter/Boolean2BooleanFeConverter.cs create mode 100644 HandyControl/Tools/Converter/Boolean2VisibilityFeConverter.cs create mode 100644 HandyControl/Tools/Converter/BooleanArr2VisibilityConverter.cs create mode 100644 HandyControl/Tools/Converter/Color2ChannelAConverter.cs create mode 100644 HandyControl/Tools/Converter/Color2HexStrConverter.cs create mode 100644 HandyControl/Tools/Converter/IsNullConverter.cs create mode 100644 HandyControl/Tools/Converter/Long2FileSizeConverter.cs create mode 100644 HandyControl/Tools/Converter/Object2BooleanConverter.cs create mode 100644 HandyControl/Tools/Converter/Source2ImageConverter.cs create mode 100644 HandyControl/Tools/Converter/String2VisibilityConverter.cs create mode 100644 HandyControl/Tools/Converter/String2VisibilityFeConverter.cs create mode 100644 HandyControl/Tools/Converter/TreeViewItemMarginConverter.cs create mode 100644 HandyControl/Tools/Extension/ColorExtension.cs create mode 100644 HandyControl/Tools/Extension/UIElementExtension.cs create mode 100644 HandyControl/Tools/RegularJudgment.cs create mode 100644 HandyControl/Tools/RegularPatterns.cs create mode 100644 HandyControlDemo/Data/Model/CellGateInfo.cs create mode 100644 HandyControlDemo/Data/Model/CellGateRecord.cs diff --git a/.gitignore b/.gitignore index d01f5dd7..3a993721 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ /HandyControl/obj/Debug /HandyControlDemo/bin/Debug /HandyControlDemo/obj/Debug +/HandyControl.sln.DotSettings.user diff --git a/HandyControl/Controls/ColorPicker.xaml b/HandyControl/Controls/ColorPicker.xaml new file mode 100644 index 00000000..3aa4cecb --- /dev/null +++ b/HandyControl/Controls/ColorPicker.xaml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HandyControl/Controls/ColorPicker.xaml.cs b/HandyControl/Controls/ColorPicker.xaml.cs new file mode 100644 index 00000000..22350188 --- /dev/null +++ b/HandyControl/Controls/ColorPicker.xaml.cs @@ -0,0 +1,558 @@ +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.Tools.Extension; + +namespace HandyControl.Controls +{ + /// + /// 颜色拾取器 + /// + public partial class ColorPicker + { + /// + /// 当前显示的颜色类型 + /// + private int _colorType; + + /// + /// 是否已经加载控件 + /// + private bool _isLoaded; + + /// + /// 是否需要更新小球位置 + /// + private bool _isNeedUpdatePicker = true; + + /// + /// 是否在拖动小球 + /// + private bool _isOnDragging; + + /// + /// 是否需要更新信息 + /// + private bool IsNeedUpdateInfo { get; set; } = true; + + /// + /// 颜色分值 + /// + private const double ColorInterval = 1 / 6.0; + + /// + /// 颜色选取面板宽度 + /// + private const double ColorPanelWidth = 230; + + /// + /// 颜色选取面板高度 + /// + private const double ColorPanelHeight = 122; + + /// + /// 预设的颜色(一共18个,两行) + /// + private readonly List _colorPresetList = new List + { + "#f44336", + "#e91e63", + "#9c27b0", + "#673ab7", + "#3f51b5", + "#2196f3", + "#03a9f4", + "#00bcd4", + "#009688", + + "#4caf50", + "#8bc34a", + "#cddc39", + "#ffeb3b", + "#ffc107", + "#ff9800", + "#ff5722", + "#795548", + "#9e9e9e" + }; + + /// + /// 颜色范围集合 + /// + private readonly List _colorRangeList = new List + { + 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) + } + }; + + /// + /// 颜色分隔集合 + /// + private readonly List _colorSeparateList = new List + { + 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) + }; + + internal static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register( + "CornerRadius", typeof(CornerRadius), typeof(ColorPicker), new PropertyMetadata(new CornerRadius(2))); + + internal CornerRadius CornerRadius + { + get => (CornerRadius)GetValue(CornerRadiusProperty); + set => SetValue(CornerRadiusProperty, value); + } + + /// + /// 颜色改变事件 + /// + public static readonly RoutedEvent ColorSelectedEvent = + EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, + typeof(EventHandler>), typeof(ColorPicker)); + + /// + /// 颜色改变事件 + /// + public event EventHandler> ColorSelected + { + add => AddHandler(ColorSelectedEvent, value); + remove => RemoveHandler(ColorSelectedEvent, value); + } + + /// + /// 取消事件 + /// + public static readonly RoutedEvent CanceledEvent = + EventManager.RegisterRoutedEvent("Canceled", RoutingStrategy.Bubble, + typeof(EventHandler), typeof(ColorPicker)); + + /// + /// 取消事件 + /// + public event EventHandler Canceled + { + add => AddHandler(CanceledEvent, value); + remove => RemoveHandler(CanceledEvent, 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.ChannelR == null) return; + + if (ctl.IsNeedUpdateInfo) + { + ctl.IsNeedUpdateInfo = false; + ctl.ChannelR.Value = v.Color.R; + ctl.ChannelG.Value = v.Color.G; + ctl.ChannelB.Value = v.Color.B; + ctl.SliderOpacity.Value = 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)); + })); + + /// + /// 当前选中的颜色 + /// + public SolidColorBrush SelectedBrush + { + get => (SolidColorBrush)GetValue(SelectedBrushProperty); + set => SetValue(SelectedBrushProperty, value); + } + + private static readonly DependencyProperty SelectedBrushWithoutOpacityProperty = DependencyProperty.Register( + "SelectedBrushWithoutOpacity", typeof(SolidColorBrush), typeof(ColorPicker), new PropertyMetadata(Brushes.White)); + + private SolidColorBrush SelectedBrushWithoutOpacity + { + get => (SolidColorBrush)GetValue(SelectedBrushWithoutOpacityProperty); + set => SetValue(SelectedBrushWithoutOpacityProperty, value); + } + + private static readonly DependencyProperty BackColorProperty = DependencyProperty.Register( + "BackColor", typeof(SolidColorBrush), typeof(ColorPicker), new PropertyMetadata(Brushes.Red)); + + private SolidColorBrush BackColor + { + get => (SolidColorBrush)GetValue(BackColorProperty); + set => SetValue(BackColorProperty, value); + } + + private static readonly DependencyProperty ShowListProperty = DependencyProperty.Register( + "ShowList", typeof(List), typeof(ColorPicker), new PropertyMetadata(new List + { + true, false, false + })); + + private List ShowList + { + get => (List)GetValue(ShowListProperty); + set => SetValue(ShowListProperty, value); + } + + /// + /// 当前显示的颜色类型 + /// + private int ColorType + { + get => _colorType; + set + { + if (value < 0) + { + _colorType = 1; + } + else if (value > 1) + { + _colorType = 0; + } + else + { + _colorType = value; + } + + var list = new List(); + for (int i = 0; i < 2; i++) + { + list.Add(false); + } + list[_colorType] = true; + ShowList = list; + } + } + + public ColorPicker() + { + InitializeComponent(); + + UpdateStatus(SelectedBrush.Color); + + Loaded += (s, e) => + { + if (!_isLoaded) + { + Init(); + _isLoaded = true; + } + }; + } + + /// + /// 初始化 + /// + private void Init() + { + foreach (var item in _colorPresetList) + { + PanelColor.Children.Add(CreateColorButton(item)); + } + } + + /// + /// 创建颜色按钮 + /// + /// + private Button CreateColorButton(string colorStr) + { + var colorObj = ColorConverter.ConvertFromString(colorStr); + var color = default(Color); + if (colorObj != null) + { + color = (Color)colorObj; + } + var brush = new SolidColorBrush(color); + + var button = new Button + { + Margin = new Thickness(0, 0, 12, 12), + Style = FindResource("ButtonOpacityStyle") as Style, + Content = new Border + { + Background = brush, + Width = 12, + Height = 12, + CornerRadius = new CornerRadius(2) + } + }; + + button.Click += (s, e) => + { + SelectedBrush = brush; + SliderOpacity.Value = SliderOpacity.Maximum; + }; + + return button; + } + + /// + /// 内部更新 + /// + private void UpdateStatus(Color color) + { + if (_isOnDragging) return; + + var r = color.R; + var g = color.G; + var b = color.B; + var list = new List + { + 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; + list[commonIndex] = (byte)(255 * max * (min - common) / (double)(max * (min - max))); + 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 = ColorInterval * (cIndex - direc) + scale * ColorInterval; + IsNeedUpdateInfo = false; + if (!SliderColor.IsMouseOver && !SliderOpacity.IsMouseOver) + { + SliderColor.Value = SliderColor.Maximum * 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 e) + { + if (!_isLoaded || !IsNeedUpdateInfo) return; + var newValue = e.NewValue; + var div = newValue / ColorInterval; + var index = Math.Min(5, (int)Math.Floor(newValue / ColorInterval)); + var sub = div - 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 e) + { + if (!_isLoaded || !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; + } + + /// + /// 拖动时更新颜色 + /// + 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++; + + private void InfoNumericUpDownRgb_OnValueChanged(object sender, FunctionEventArgs e) + { + if (!_isLoaded || !IsNeedUpdateInfo) return; + if (e.OriginalSource is InfoNumericUpDown 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 ButtonSure_OnClick(object sender, RoutedEventArgs e) + => RaiseEvent(new FunctionEventArgs(ColorSelectedEvent, this) + { + Info = SelectedBrush.Color + }); + + private void ButtonCancel_OnClick(object sender, RoutedEventArgs e) => RaiseEvent(new RoutedEventArgs(CanceledEvent)); + } +} \ No newline at end of file diff --git a/HandyControl/Controls/IconElement.cs b/HandyControl/Controls/IconElement.cs new file mode 100644 index 00000000..3cc778b5 --- /dev/null +++ b/HandyControl/Controls/IconElement.cs @@ -0,0 +1,35 @@ +using System.Windows; +using System.Windows.Media; + +namespace HandyControl.Controls +{ + public class IconElement : DependencyObject + { + 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) + { + return (Geometry)element.GetValue(GeometryProperty); + } + + public static readonly DependencyProperty AngleProperty = DependencyProperty.RegisterAttached( + "Angle", typeof(double), typeof(IconElement), new PropertyMetadata(default(double))); + + public static void SetAngle(DependencyObject element, double value) + { + element.SetValue(AngleProperty, value); + } + + public static double GetAngle(DependencyObject element) + { + return (double)element.GetValue(AngleProperty); + } + } + +} \ No newline at end of file diff --git a/HandyControl/Controls/Info/InfoControl.cs b/HandyControl/Controls/Info/InfoControl.cs new file mode 100644 index 00000000..bbff8d29 --- /dev/null +++ b/HandyControl/Controls/Info/InfoControl.cs @@ -0,0 +1,374 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Media.Animation; +using HandyControl.Data.Enum; +using HandyControl.Tools; +using HandyControl.Tools.Converter; +using HandyControl.Tools.Extension; + +// ReSharper disable once CheckNamespace +namespace HandyControl.Controls +{ + public abstract class InfoControl : UserControl + { + private bool _isFocused; + + protected TextBlock ErrorTextBlock; + + protected TextBox ContentTextBox; + + protected StackPanel TitleStackPanel; + + /// + /// 标题 + /// + public static readonly DependencyProperty TitleProperty = DependencyProperty.Register( + "Title", typeof(string), typeof(InfoControl), new PropertyMetadata(default(string))); + + /// + /// 标题 + /// + public string Title + { + get => (string)GetValue(TitleProperty); + set => SetValue(TitleProperty, value); + } + + /// + /// 文本 + /// + public static readonly DependencyProperty TextProperty = DependencyProperty.Register( + "Text", typeof(string), typeof(InfoControl), new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); + + /// + /// 文本 + /// + public string Text + { + get => (string)GetValue(TextProperty); + set => SetValue(TextProperty, value); + } + + /// + /// 是否为必填项 + /// + public static readonly DependencyProperty IsNeedlyProperty = DependencyProperty.Register( + "IsNeedly", typeof(bool), typeof(InfoControl), new PropertyMetadata(true)); + + /// + /// 是否为必填项 + /// + public bool IsNeedly + { + get => (bool)GetValue(IsNeedlyProperty); + set => SetValue(IsNeedlyProperty, value); + } + + /// + /// 占位符 + /// + public static readonly DependencyProperty PlaceHolderProperty = DependencyProperty.Register( + "PlaceHolder", typeof(string), typeof(InfoControl), new PropertyMetadata(default(string))); + + /// + /// 占位符 + /// + public string PlaceHolder + { + get => (string)GetValue(PlaceHolderProperty); + set => SetValue(PlaceHolderProperty, value); + } + + /// + /// 错误信息 + /// + public static readonly DependencyProperty ErrorStrProperty = DependencyProperty.Register( + "ErrorStr", typeof(string), typeof(InfoControl), new PropertyMetadata(default(string))); + + /// + /// 错误信息 + /// + public string ErrorStr + { + get => (string)GetValue(ErrorStrProperty); + set => SetValue(ErrorStrProperty, value); + } + + /// + /// 控件是否包含错误 + /// + public static readonly DependencyProperty IsErrorProperty = DependencyProperty.Register( + "IsError", typeof(bool), typeof(InfoControl), new PropertyMetadata(default(bool))); + + /// + /// 控件是否包含错误 + /// + public bool IsError + { + get => (bool)GetValue(IsErrorProperty); + set => SetValue(IsErrorProperty, value); + } + + /// + /// 标题宽度 + /// + public static readonly DependencyProperty TitleWidthProperty = DependencyProperty.RegisterAttached( + "TitleWidth", typeof(GridLength), typeof(InfoControl), + new FrameworkPropertyMetadata(GridLength.Auto, FrameworkPropertyMetadataOptions.Inherits)); + + /// + /// 设置标题宽度 + /// + public static void SetTitleWidth(DependencyObject element, GridLength value) + { + element.SetValue(TitleWidthProperty, value); + } + + /// + /// 标题宽度 + /// + public static GridLength GetTitleWidth(DependencyObject element) + { + return (GridLength)element.GetValue(TitleWidthProperty); + } + + /// + /// 标题宽度 + /// + public GridLength TitleWidth + { + get => (GridLength)GetValue(TitleWidthProperty); + set => SetValue(TitleWidthProperty, value); + } + + /// + /// 标题对齐方式 + /// + public static readonly DependencyProperty TitleAlignmentProperty = DependencyProperty.Register( + "TitleAlignment", typeof(TitleAlignment), typeof(InfoControl), new PropertyMetadata(TitleAlignment.Left, + (o, args) => + { + var ctl = (InfoControl)o; + var v = (TitleAlignment)args.NewValue; + if (v == TitleAlignment.Left) + { + Grid.SetColumn(ctl.TitleStackPanel, 0); + Grid.SetRow(ctl.TitleStackPanel, 1); + ctl.HorizontalAlignment = HorizontalAlignment.Right; + ctl.Height = ctl.MaxHeight; + } + else + { + Grid.SetColumn(ctl.TitleStackPanel, 1); + Grid.SetRow(ctl.TitleStackPanel, 0); + ctl.TitleStackPanel.HorizontalAlignment = HorizontalAlignment.Left; + ctl.TitleStackPanel.Margin = new Thickness(4, 0, 0, 4); + ctl.Height += 18; + } + })); + + /// + /// 标题对齐方式 + /// + public TitleAlignment TitleAlignment + { + get => (TitleAlignment)GetValue(TitleAlignmentProperty); + set => SetValue(TitleAlignmentProperty, value); + } + + /// + /// 文本的水平对齐方式 + /// + public static readonly DependencyProperty HorizontalTextAlignmentProperty = DependencyProperty.Register( + "HorizontalTextAlignment", typeof(HorizontalAlignment), typeof(InfoControl), new PropertyMetadata(HorizontalAlignment.Left)); + + /// + /// 文本的水平对齐方式 + /// + public HorizontalAlignment HorizontalTextAlignment + { + get => (HorizontalAlignment)GetValue(HorizontalTextAlignmentProperty); + set => SetValue(HorizontalTextAlignmentProperty, value); + } + + /// + /// 是否显示清除按钮 + /// + public static readonly DependencyProperty ShowClearButtonProperty = DependencyProperty.Register( + "ShowClearButton", typeof(bool), typeof(InfoControl), new PropertyMetadata(true, (o, args) => + { + var ctl = (InfoControl)o; + var v = (bool)args.NewValue; + if (!(ctl.FindName("ButtonClear") is Button button)) return; + BindingOperations.ClearBinding(button, VisibilityProperty); + button.IsEnabled = v; + if (v) + { + button.SetBinding(VisibilityProperty, new Binding("Text") + { + Source = ctl, + Converter = new String2VisibilityConverter() + }); + } + else + { + button.Collapse(); + } + })); + + /// + /// 是否显示清除按钮 + /// + public bool ShowClearButton + { + get => (bool)GetValue(ShowClearButtonProperty); + set => SetValue(ShowClearButtonProperty, value); + } + + /// + /// 是否显示小圆角 + /// + public static readonly DependencyProperty ShowSmallCornerRadiusProperty = DependencyProperty.Register( + "ShowSmallCornerRadius", typeof(bool), typeof(InfoControl), new PropertyMetadata(default(bool), (o, args) => + { + var ctl = (InfoControl)o; + var v = (bool)args.NewValue; + ctl.CornerRadius = v ? new CornerRadius(2, 2, 2, 2) : new CornerRadius(4, 4, 4, 4); + })); + + /// + /// 是否显示小圆角 + /// + public bool ShowSmallCornerRadius + { + get => (bool)GetValue(ShowSmallCornerRadiusProperty); + set => SetValue(ShowSmallCornerRadiusProperty, value); + } + + /// + /// 圆角 + /// + protected static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register( + "CornerRadius", typeof(CornerRadius), typeof(InfoControl), new PropertyMetadata(new CornerRadius(4, 4, 4, 4))); + + /// + /// 圆角 + /// + protected CornerRadius CornerRadius + { + get => (CornerRadius)GetValue(CornerRadiusProperty); + set => SetValue(CornerRadiusProperty, value); + } + + /// + /// 文本内边距 + /// + public static readonly DependencyProperty TextPaddingProperty = DependencyProperty.Register( + "TextPadding", typeof(Thickness), typeof(InfoControl), new PropertyMetadata(new Thickness(6, 0, 0, 0))); + + /// + /// 文本内边距 + /// + public Thickness TextPadding + { + get => (Thickness)GetValue(TextPaddingProperty); + set => SetValue(TextPaddingProperty, value); + } + + /// + /// 更新状态 + /// + public virtual void Update() + { + if (!IsError) + { + if (!_isFocused) + { + (TryFindResource("Storyboard2") as Storyboard)?.Begin(); + } + else + { + (TryFindResource("Storyboard3") as Storyboard)?.Begin(); + } + ErrorTextBlock.BeginAnimation(MarginProperty, AnimationHelper.CreateAnimation(new Thickness(4, 0, 0, 1))); + return; + } + (TryFindResource("Storyboard4") as Storyboard)?.Begin(); + ErrorTextBlock.BeginAnimation(MarginProperty, AnimationHelper.CreateAnimation(new Thickness(4, 0, 0, -ErrorTextBlock.ActualHeight - 2))); + } + + protected virtual void InputBox_OnLostFocus(object sender, RoutedEventArgs e) + { + _isFocused = false; + CheckInfo(); + } + + protected virtual void InputBox_OnGotFocus(object sender, RoutedEventArgs e) + { + _isFocused = true; + if (!IsError) + { + (TryFindResource("Storyboard3") as Storyboard)?.Begin(); + } + } + + protected virtual void InputBox_OnMouseEnter(object sender, MouseEventArgs e) + { + if (!IsError && !ContentTextBox.IsFocused) + { + (TryFindResource("Storyboard1") as Storyboard)?.Begin(); + } + } + + protected virtual void InputBox_OnMouseLeave(object sender, MouseEventArgs e) + { + if (!IsError && !ContentTextBox.IsFocused) + { + (TryFindResource("Storyboard2") as Storyboard)?.Begin(); + } + } + + protected virtual void InputBox_OnTextChanged(object sender, TextChangedEventArgs e) => CheckInfo(); + + protected virtual void ButtonClear_OnClick(object sender, RoutedEventArgs e) => Text = string.Empty; + + /// + /// 文本类型 + /// + public TextType TextType { get; set; } + + /// + /// 正则字符串(当文本类型为Common时,此属性才生效) + /// + public string RegularStr { get; set; } + + /// + /// 用于监测数据的正确性 + /// + /// + public virtual bool CheckInfo() + { + bool result; + if (TextType == TextType.Common) + { + if (string.IsNullOrEmpty(RegularStr)) + { + result = !IsNeedly || !string.IsNullOrEmpty(Text); + } + else + { + result = Text.IsKindOf(RegularStr); + } + } + else + { + result = Text.IsKindOf(TextType); + } + IsError = !result; + Update(); + return result; + } + } +} \ No newline at end of file diff --git a/HandyControl/Controls/Info/InfoNumericUpDown.xaml b/HandyControl/Controls/Info/InfoNumericUpDown.xaml new file mode 100644 index 00000000..9609429f --- /dev/null +++ b/HandyControl/Controls/Info/InfoNumericUpDown.xaml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Controls/Info/InfoNumericUpDown.xaml.cs b/HandyControl/Controls/Info/InfoNumericUpDown.xaml.cs new file mode 100644 index 00000000..2195a6e9 --- /dev/null +++ b/HandyControl/Controls/Info/InfoNumericUpDown.xaml.cs @@ -0,0 +1,191 @@ +using System; +using System.Windows; +using System.Windows.Input; +using System.Windows.Media.Animation; +using HandyControl.Data; + +// ReSharper disable once CheckNamespace +namespace HandyControl.Controls +{ + /// + /// InfoNumericUpDown.xaml 的交互逻辑 + /// + public partial class InfoNumericUpDown + { + public InfoNumericUpDown() + { + InitializeComponent(); + + ErrorTextBlock = TextBlockError; + ContentTextBox = MyTextBox; + TitleStackPanel = StackPanelTitle; + + (TryFindResource("Storyboard2") as Storyboard)?.Begin(); + + Loaded += (s, e) => + { + StackPanelTitle.Focusable = true; + StackPanelTitle.Focus(); + StackPanelTitle.Focusable = false; + }; + } + + /// + /// 最大值 + /// + public static readonly DependencyProperty MaximumProperty = DependencyProperty.Register( + "Maximum", typeof(double), typeof(InfoNumericUpDown), new PropertyMetadata(100.0, (o, args) => + { + o.CoerceValue(MinimumProperty); + o.CoerceValue(ValueProperty); + }, (o, value) => + { + var info = (InfoNumericUpDown)o; + var max = (double)value; + if (max < info.Minimum) max = info.Minimum; + return max; + }), ValidateValueCallback); + + /// + /// 最大值 + /// + public double Maximum + { + get => (double)GetValue(MaximumProperty); + set => SetValue(MaximumProperty, value); + } + + /// + /// 最小值 + /// + public static readonly DependencyProperty MinimumProperty = DependencyProperty.Register( + "Minimum", typeof(double), typeof(InfoNumericUpDown), new PropertyMetadata(0.0, (o, args) => + { + o.CoerceValue(MaximumProperty); + o.CoerceValue(ValueProperty); + }, (o, value) => + { + var info = (InfoNumericUpDown)o; + var min = (double)value; + if (min > info.Maximum) min = info.Maximum; + return min; + }), ValidateValueCallback); + + /// + /// 最小值 + /// + public double Minimum + { + get => (double)GetValue(MinimumProperty); + set => SetValue(MinimumProperty, value); + } + + /// + /// 当前值 + /// + public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( + "Value", typeof(double), typeof(InfoNumericUpDown), new PropertyMetadata(1.0, (o, args) => + { + o.CoerceValue(MinimumProperty); + o.CoerceValue(MaximumProperty); + var ctl = (InfoNumericUpDown)o; + ctl.RaiseEvent(new FunctionEventArgs(ValueChangedEvent, ctl) + { + Info = (double)args.NewValue + }); + }, (o, value) => + { + var info = (InfoNumericUpDown)o; + var current = (double)value; + + if (current < info.Minimum) current = info.Minimum; + if (current > info.Maximum) current = info.Maximum; + + return current; + }), ValidateValueCallback); + + /// + /// 值验证回调 + /// + /// + /// + private static bool ValidateValueCallback(object value) + { + var v = (double)value; + return !(double.IsNaN(v) || double.IsInfinity(v)); + } + + /// + /// 当前值 + /// + public double Value + { + get => (double)GetValue(ValueProperty); + set => SetValue(ValueProperty, value); + } + + /// + /// 最小改变值 + /// + public static readonly DependencyProperty SmallChangeProperty = DependencyProperty.Register( + "SmallChange", typeof(double), typeof(InfoNumericUpDown), new PropertyMetadata(1.0), ValidateValueCallback); + + /// + /// 最小改变值 + /// + public double SmallChange + { + get => (double)GetValue(SmallChangeProperty); + set => SetValue(SmallChangeProperty, value); + } + + /// + /// 是否显示上下调整按钮 + /// + public static readonly DependencyProperty ShowUpDownButtonProperty = DependencyProperty.Register( + "ShowUpDownButton", typeof(bool), typeof(InfoNumericUpDown), new PropertyMetadata(true)); + + /// + /// 是否显示上下调整按钮 + /// + public bool ShowUpDownButton + { + get => (bool)GetValue(ShowUpDownButtonProperty); + set => SetValue(ShowUpDownButtonProperty, value); + } + + /// + /// 值改变事件 + /// + public static readonly RoutedEvent ValueChangedEvent = + EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, + typeof(EventHandler>), typeof(InfoNumericUpDown)); + + /// + /// 值改变事件 + /// + public event EventHandler> ValueChanged + { + add => AddHandler(ValueChangedEvent, value); + remove => RemoveHandler(ValueChangedEvent, value); + } + + private void ButtonUp_OnClick(object sender, RoutedEventArgs e) => Value += SmallChange; + + private void ButtonDown_OnClick(object sender, RoutedEventArgs e) => Value -= SmallChange; + + private void MyTextBox_OnMouseWheel(object sender, MouseWheelEventArgs e) + { + if (e.Delta > 0) + { + Value += SmallChange; + } + else + { + Value -= SmallChange; + } + MyTextBox.Select(MyTextBox.Text.Length, 0); + e.Handled = true; + } + } +} \ No newline at end of file diff --git a/HandyControl/Controls/Info/InfoTextBox.xaml b/HandyControl/Controls/Info/InfoTextBox.xaml new file mode 100644 index 00000000..9cf02129 --- /dev/null +++ b/HandyControl/Controls/Info/InfoTextBox.xaml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Controls/Info/InfoTextBox.xaml.cs b/HandyControl/Controls/Info/InfoTextBox.xaml.cs new file mode 100644 index 00000000..1e3c114d --- /dev/null +++ b/HandyControl/Controls/Info/InfoTextBox.xaml.cs @@ -0,0 +1,45 @@ +using System.Windows; +using System.Windows.Media.Animation; + +// ReSharper disable once CheckNamespace +namespace HandyControl.Controls +{ + /// + /// InfoTextBox.xaml 的交互逻辑 + /// + public partial class InfoTextBox + { + /// + /// 可输入的最大字符数 + /// + public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register( + "MaxLength", typeof(int), typeof(InfoTextBox), new PropertyMetadata(default(int))); + + /// + /// 可输入的最大字符数 + /// + public int MaxLength + { + get => (int)GetValue(MaxLengthProperty); + set => SetValue(MaxLengthProperty, value); + } + + public InfoTextBox() + { + InitializeComponent(); + + ErrorTextBlock = TextBlockError; + ContentTextBox = MyTextBox; + TitleStackPanel = StackPanelTitle; + + (TryFindResource("Storyboard2") as Storyboard)?.Begin(); + + Loaded += (s, e) => + { + StackPanelTitle.Focusable = true; + StackPanelTitle.Focus(); + StackPanelTitle.Focusable = false; + }; + } + } +} \ No newline at end of file diff --git a/HandyControl/Data/ColorRange.cs b/HandyControl/Data/ColorRange.cs new file mode 100644 index 00000000..3db15cb3 --- /dev/null +++ b/HandyControl/Data/ColorRange.cs @@ -0,0 +1,75 @@ +using System.Windows.Media; + + +namespace HandyControl.Data +{ + /// + /// 颜色范围 + /// + public class ColorRange + { + /// + /// 颜色1 + /// + private Color _color1; + + /// + /// 颜色1 + /// + public Color Color1 + { + get => _color1; + set + { + _color1 = value; + Update(); + } + } + + /// + /// 颜色2 + /// + private Color _color2; + + /// + /// 颜色2 + /// + public Color Color2 + { + get => _color2; + set + { + _color2 = value; + Update(); + } + } + + /// + /// 颜色差值 + /// + private readonly int[] _subColorArr = new int[4]; + + /// + /// 更新 + /// + private void Update() + { + _subColorArr[0] = Color1.A - Color2.A; + _subColorArr[1] = Color1.R - Color2.R; + _subColorArr[2] = Color1.G - Color2.G; + _subColorArr[3] = Color1.B - Color2.B; + } + + /// + /// 获取指定比例处的颜色 + /// + /// 范围(0-1) + /// + public Color GetColor(double range) + { + if (range < 0 || range > 1) return default(Color); + return Color.FromArgb((byte)(_color1.A - _subColorArr[0] * range), (byte)(_color1.R - _subColorArr[1] * range), + (byte)(_color1.G - _subColorArr[2] * range), (byte)(_color1.B - _subColorArr[3] * range)); + } + } +} \ No newline at end of file diff --git a/HandyControl/Data/Enum/IpType.cs b/HandyControl/Data/Enum/IpType.cs new file mode 100644 index 00000000..1551d04c --- /dev/null +++ b/HandyControl/Data/Enum/IpType.cs @@ -0,0 +1,29 @@ +namespace HandyControl.Data.Enum +{ + /// + /// IP类型枚举 + /// + public enum IpType + { + /// + /// A类IP地址 + /// + A = 0, + /// + /// B类IP地址 + /// + B, + /// + /// C类IP地址 + /// + C, + /// + /// D类IP地址 + /// + D, + /// + /// E类IP地址 + /// + E + } +} \ No newline at end of file diff --git a/HandyControl/Data/Enum/TextType.cs b/HandyControl/Data/Enum/TextType.cs new file mode 100644 index 00000000..867e5d3e --- /dev/null +++ b/HandyControl/Data/Enum/TextType.cs @@ -0,0 +1,26 @@ +namespace HandyControl.Data.Enum +{ + /// + /// 文本类型枚举 + /// + public enum TextType + { + Common = 0, + Phone, + Mail, + Url, + Chinese, + Number, + Digits, + PInt, + NInt, + Int, + NnInt, + NpInt, + PDouble, + NDouble, + Double, + NnDouble, + NpDouble, + } +} \ No newline at end of file diff --git a/HandyControl/Data/Enum/TitleAlignment.cs b/HandyControl/Data/Enum/TitleAlignment.cs new file mode 100644 index 00000000..af88faab --- /dev/null +++ b/HandyControl/Data/Enum/TitleAlignment.cs @@ -0,0 +1,11 @@ +namespace HandyControl.Data.Enum +{ + /// + /// 标题对齐方式 + /// + public enum TitleAlignment + { + Left, + Top + } +} \ No newline at end of file diff --git a/HandyControl/Data/FunctionEventArgs!1.cs b/HandyControl/Data/FunctionEventArgs!1.cs new file mode 100644 index 00000000..239f367a --- /dev/null +++ b/HandyControl/Data/FunctionEventArgs!1.cs @@ -0,0 +1,14 @@ +using System.Windows; + + +namespace HandyControl.Data +{ + public class FunctionEventArgs : RoutedEventArgs + { + public FunctionEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) + { + } + + public T Info { get; set; } + } +} \ No newline at end of file diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj index f4166407..6d854d8e 100644 --- a/HandyControl/HandyControl.csproj +++ b/HandyControl/HandyControl.csproj @@ -31,9 +31,174 @@ 4 + + ColorPicker.xaml + + + + + InfoNumericUpDown.xaml + + + InfoTextBox.xaml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Interactivity/AdornerContainer.cs b/HandyControl/Interactivity/AdornerContainer.cs new file mode 100644 index 00000000..4ffc4f93 --- /dev/null +++ b/HandyControl/Interactivity/AdornerContainer.cs @@ -0,0 +1,40 @@ +using System.Windows; +using System.Windows.Documents; +using System.Windows.Media; + +namespace HandyControl.Interactivity +{ + public class AdornerContainer : Adorner + { + private UIElement _child; + + public AdornerContainer(UIElement adornedElement) : base(adornedElement) + { + } + + public UIElement Child + { + get => + _child; + set + { + AddVisualChild(value); + _child = value; + } + } + + protected override int VisualChildrenCount => _child != null ? 1 : 0; + + protected override Size ArrangeOverride(Size finalSize) + { + _child?.Arrange(new Rect(finalSize)); + return finalSize; + } + + protected override Visual GetVisualChild(int index) + { + if (index == 0 && _child != null) return _child; + return base.GetVisualChild(index); + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/AttachableCollection!1.cs b/HandyControl/Interactivity/AttachableCollection!1.cs new file mode 100644 index 00000000..edcfdb83 --- /dev/null +++ b/HandyControl/Interactivity/AttachableCollection!1.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Globalization; +using System.Windows; + +namespace HandyControl.Interactivity +{ + public abstract class AttachableCollection : FreezableCollection, IAttachedObject + where T : DependencyObject, IAttachedObject + { + private DependencyObject _associatedObject; + private Collection _snapshot; + + internal AttachableCollection() + { + INotifyCollectionChanged changed = this; + changed.CollectionChanged += OnCollectionChanged; + _snapshot = new Collection(); + } + + public DependencyObject AssociatedObject + { + get + { + ReadPreamble(); + return _associatedObject; + } + } + + DependencyObject IAttachedObject.AssociatedObject => AssociatedObject; + + public void Attach(DependencyObject dependencyObject) + { + if (Equals(dependencyObject, AssociatedObject)) + return; + if (AssociatedObject != null) + throw new InvalidOperationException(); + if (Interaction.ShouldRunInDesignMode || !(bool) GetValue(DesignerProperties.IsInDesignModeProperty)) + { + WritePreamble(); + _associatedObject = dependencyObject; + WritePostscript(); + } + OnAttached(); + } + + public void Detach() + { + OnDetaching(); + WritePreamble(); + _associatedObject = null; + WritePostscript(); + } + + protected abstract void OnAttached(); + + protected abstract void OnDetaching(); + + internal abstract void ItemAdded(T item); + + internal abstract void ItemRemoved(T item); + + private void VerifyAdd(T item) + { + if (item == null) throw new ArgumentNullException(nameof(item)); + if (_snapshot.Contains(item)) + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, + ExceptionStringTable.DuplicateItemInCollectionExceptionMessage, new object[] + { + typeof(T).Name, + GetType().Name + })); + } + + private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + var enumerator1 = e.NewItems.GetEnumerator(); + try + { + while (enumerator1.MoveNext()) + { + var current = (T) enumerator1.Current; + try + { + VerifyAdd(current); + ItemAdded(current); + } + finally + { + _snapshot.Insert(IndexOf(current), current); + } + } + break; + } + finally + { + if (enumerator1 is IDisposable disposable) + disposable.Dispose(); + } + case NotifyCollectionChangedAction.Remove: + var enumerator2 = e.OldItems.GetEnumerator(); + try + { + while (enumerator2.MoveNext()) + { + var current = (T) enumerator2.Current; + ItemRemoved(current); + _snapshot.Remove(current); + } + break; + } + finally + { + if (enumerator2 is IDisposable disposable) + disposable.Dispose(); + } + case NotifyCollectionChangedAction.Replace: + foreach (T oldItem in e.OldItems) + { + ItemRemoved(oldItem); + _snapshot.Remove(oldItem); + } + var enumerator3 = e.NewItems.GetEnumerator(); + try + { + while (enumerator3.MoveNext()) + { + var current = (T) enumerator3.Current; + try + { + VerifyAdd(current); + ItemAdded(current); + } + finally + { + _snapshot.Insert(IndexOf(current), current); + } + } + break; + } + finally + { + if (enumerator3 is IDisposable disposable) + disposable.Dispose(); + } + case NotifyCollectionChangedAction.Reset: + foreach (var obj in _snapshot) + ItemRemoved(obj); + _snapshot = new Collection(); + using (var enumerator4 = GetEnumerator()) + { + while (enumerator4.MoveNext()) + { + var current = enumerator4.Current; + VerifyAdd(current); + ItemAdded(current); + } + break; + } + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/Behavior!1.cs b/HandyControl/Interactivity/Behavior!1.cs new file mode 100644 index 00000000..bb1b7a14 --- /dev/null +++ b/HandyControl/Interactivity/Behavior!1.cs @@ -0,0 +1,13 @@ +using System.Windows; + +namespace HandyControl.Interactivity +{ + public abstract class Behavior : Behavior where T : DependencyObject + { + protected Behavior() : base(typeof(T)) + { + } + + protected new T AssociatedObject => (T)base.AssociatedObject; + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/Behavior.cs b/HandyControl/Interactivity/Behavior.cs new file mode 100644 index 00000000..a8487a15 --- /dev/null +++ b/HandyControl/Interactivity/Behavior.cs @@ -0,0 +1,87 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Media.Animation; + +namespace HandyControl.Interactivity +{ + public abstract class Behavior : Animatable, IAttachedObject + { + private DependencyObject _associatedObject; + private readonly Type _associatedType; + + internal Behavior(Type associatedType) + { + _associatedType = associatedType; + } + + protected DependencyObject AssociatedObject + { + get + { + ReadPreamble(); + return _associatedObject; + } + } + + protected Type AssociatedType + { + get + { + ReadPreamble(); + return _associatedType; + } + } + + public void Attach(DependencyObject dependencyObject) + { + if (!Equals(dependencyObject, AssociatedObject)) + { + if (AssociatedObject != null) + throw new InvalidOperationException(ExceptionStringTable + .CannotHostBehaviorMultipleTimesExceptionMessage); + if (dependencyObject != null && !AssociatedType.IsInstanceOfType(dependencyObject)) + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, + ExceptionStringTable.TypeConstraintViolatedExceptionMessage, + new object[] {GetType().Name, dependencyObject.GetType().Name, AssociatedType.Name})); + WritePreamble(); + _associatedObject = dependencyObject; + WritePostscript(); + OnAssociatedObjectChanged(); + OnAttached(); + } + } + + public void Detach() + { + OnDetaching(); + WritePreamble(); + _associatedObject = null; + WritePostscript(); + OnAssociatedObjectChanged(); + } + + DependencyObject IAttachedObject.AssociatedObject => + AssociatedObject; + + internal event EventHandler AssociatedObjectChanged; + + protected override Freezable CreateInstanceCore() + { + return (Freezable) Activator.CreateInstance(GetType()); + } + + private void OnAssociatedObjectChanged() + { + AssociatedObjectChanged?.Invoke(this, new EventArgs()); + } + + protected virtual void OnAttached() + { + } + + protected virtual void OnDetaching() + { + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/BehaviorCollection.cs b/HandyControl/Interactivity/BehaviorCollection.cs new file mode 100644 index 00000000..125384f9 --- /dev/null +++ b/HandyControl/Interactivity/BehaviorCollection.cs @@ -0,0 +1,42 @@ +using System.Windows; + +namespace HandyControl.Interactivity +{ + public sealed class BehaviorCollection : AttachableCollection + { + internal BehaviorCollection() + { + } + + protected override void OnAttached() + { + foreach (var behavior in this) + behavior.Attach(AssociatedObject); + } + + protected override void OnDetaching() + { + foreach (var behavior in this) + behavior.Detach(); + } + + internal override void ItemAdded(Behavior item) + { + if (AssociatedObject == null) + return; + item.Attach(AssociatedObject); + } + + internal override void ItemRemoved(Behavior item) + { + if (((IAttachedObject) item).AssociatedObject == null) + return; + item.Detach(); + } + + protected override Freezable CreateInstanceCore() + { + return new BehaviorCollection(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/DefaultTriggerAttribute.cs b/HandyControl/Interactivity/DefaultTriggerAttribute.cs new file mode 100644 index 00000000..2eb1cf97 --- /dev/null +++ b/HandyControl/Interactivity/DefaultTriggerAttribute.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections; +using System.Globalization; + +namespace HandyControl.Interactivity +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = true)] + public sealed class DefaultTriggerAttribute : Attribute + { + private readonly object[] _parameters; + + public DefaultTriggerAttribute(Type targetType, Type triggerType, object parameter) : this(targetType, + triggerType, new[] {parameter}) + { + } + + public DefaultTriggerAttribute(Type targetType, Type triggerType, params object[] parameters) + { + if (!typeof(TriggerBase).IsAssignableFrom(triggerType)) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, + ExceptionStringTable.DefaultTriggerAttributeInvalidTriggerTypeSpecifiedExceptionMessage, + new object[] {triggerType.Name})); + TargetType = targetType; + TriggerType = triggerType; + _parameters = parameters; + } + + public IEnumerable Parameters => + _parameters; + + public Type TargetType { get; } + + public Type TriggerType { get; } + + public TriggerBase Instantiate() + { + object obj2 = null; + try + { + obj2 = Activator.CreateInstance(TriggerType, _parameters); + } + catch + { + // ignored + } + return (TriggerBase) obj2; + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/EventTrigger.cs b/HandyControl/Interactivity/EventTrigger.cs new file mode 100644 index 00000000..7df44802 --- /dev/null +++ b/HandyControl/Interactivity/EventTrigger.cs @@ -0,0 +1,29 @@ +using System.Windows; + +namespace HandyControl.Interactivity +{ + public class EventTrigger : EventTriggerBase + { + public static readonly DependencyProperty EventNameProperty = DependencyProperty.Register("EventName", typeof(string), typeof(EventTrigger), new FrameworkPropertyMetadata("Loaded", OnEventNameChanged)); + + public EventTrigger() + { + } + + public EventTrigger(string eventName) => EventName = eventName; + + protected override string GetEventName() => EventName; + + private static void OnEventNameChanged(object sender, DependencyPropertyChangedEventArgs args) + { + ((EventTrigger)sender).OnEventNameChanged((string)args.OldValue, (string)args.NewValue); + } + + public string EventName + { + get => (string)GetValue(EventNameProperty); + + set => SetValue(EventNameProperty, value); + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/EventTriggerBase!1.cs b/HandyControl/Interactivity/EventTriggerBase!1.cs new file mode 100644 index 00000000..ee9acd56 --- /dev/null +++ b/HandyControl/Interactivity/EventTriggerBase!1.cs @@ -0,0 +1,21 @@ +namespace HandyControl.Interactivity +{ + public abstract class EventTriggerBase : EventTriggerBase where T : class + { + protected EventTriggerBase() : base(typeof(T)) + { + } + + protected virtual void OnSourceChanged(T oldSource, T newSource) + { + } + + internal sealed override void OnSourceChangedImpl(object oldSource, object newSource) + { + base.OnSourceChangedImpl(oldSource, newSource); + OnSourceChanged(oldSource as T, newSource as T); + } + + public new T Source => (T)base.Source; + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/EventTriggerBase.cs b/HandyControl/Interactivity/EventTriggerBase.cs new file mode 100644 index 00000000..8dc8cc57 --- /dev/null +++ b/HandyControl/Interactivity/EventTriggerBase.cs @@ -0,0 +1,313 @@ +using System; +using System.ComponentModel; +using System.Globalization; +using System.Reflection; +using System.Windows; + +namespace HandyControl.Interactivity +{ + public abstract class EventTriggerBase : TriggerBase + { + public static readonly DependencyProperty SourceNameProperty = DependencyProperty.Register("SourceName", + typeof(string), typeof(EventTriggerBase), new PropertyMetadata(OnSourceNameChanged)); + + public static readonly DependencyProperty SourceObjectProperty = DependencyProperty.Register("SourceObject", + typeof(object), typeof(EventTriggerBase), new PropertyMetadata(OnSourceObjectChanged)); + + private MethodInfo _eventHandlerMethodInfo; + + internal EventTriggerBase(Type sourceTypeConstraint) : base(typeof(DependencyObject)) + { + SourceTypeConstraint = sourceTypeConstraint; + SourceNameResolver = new NameResolver(); + RegisterSourceChanged(); + } + + protected sealed override Type AssociatedObjectTypeConstraint + { + get + { + if (TypeDescriptor.GetAttributes(GetType())[typeof(TypeConstraintAttribute)] is TypeConstraintAttribute attribute) + return attribute.Constraint; + return typeof(DependencyObject); + } + } + + private bool IsLoadedRegistered { get; set; } + + private bool IsSourceChangedRegistered { get; set; } + + private bool IsSourceNameSet + { + get + { + if (string.IsNullOrEmpty(SourceName)) + return ReadLocalValue(SourceNameProperty) != DependencyProperty.UnsetValue; + return true; + } + } + + public object Source + { + get + { + object associatedObject = AssociatedObject; + if (SourceObject != null) + return SourceObject; + if (IsSourceNameSet) + { + associatedObject = SourceNameResolver.Object; + if (associatedObject != null && !SourceTypeConstraint.IsInstanceOfType(associatedObject)) + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, + ExceptionStringTable.RetargetedTypeConstraintViolatedExceptionMessage, GetType().Name, + associatedObject.GetType(), SourceTypeConstraint, "Source")); + } + return associatedObject; + } + } + + public string SourceName + { + get => + (string) GetValue(SourceNameProperty); + set => SetValue(SourceNameProperty, value); + } + + private NameResolver SourceNameResolver { get; } + + public object SourceObject + { + get => + GetValue(SourceObjectProperty); + set => SetValue(SourceObjectProperty, value); + } + + protected Type SourceTypeConstraint { get; } + + protected abstract string GetEventName(); + + private static bool IsValidEvent(EventInfo eventInfo) + { + var eventHandlerType = eventInfo.EventHandlerType; + if (!typeof(Delegate).IsAssignableFrom(eventInfo.EventHandlerType)) + return false; + var parameters = eventHandlerType.GetMethod("Invoke")?.GetParameters(); + return parameters != null && parameters.Length == 2 && typeof(object).IsAssignableFrom(parameters[0].ParameterType) && typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType); + } + + protected override void OnAttached() + { + base.OnAttached(); + var associatedObject = AssociatedObject; + var behavior = associatedObject as Behavior; + var element = associatedObject as FrameworkElement; + RegisterSourceChanged(); + if (behavior != null) + { + behavior.AssociatedObjectChanged += OnBehaviorHostChanged; + } + else if (SourceObject != null || element == null) + { + try + { + OnSourceChanged(null, Source); + } + catch (InvalidOperationException) + { + } + } + else + { + SourceNameResolver.NameScopeReferenceElement = element; + } + if (string.Compare(GetEventName(), "Loaded", StringComparison.Ordinal) == 0 && element != null && + !Interaction.IsElementLoaded(element)) + RegisterLoaded(element); + } + + private void OnBehaviorHostChanged(object sender, EventArgs e) + { + SourceNameResolver.NameScopeReferenceElement = + ((IAttachedObject) sender).AssociatedObject as FrameworkElement; + } + + protected override void OnDetaching() + { + base.OnDetaching(); + var associatedObject = AssociatedObject as Behavior; + var associatedElement = AssociatedObject as FrameworkElement; + try + { + OnSourceChanged(Source, null); + } + catch (InvalidOperationException) + { + } + UnregisterSourceChanged(); + if (associatedObject != null) + associatedObject.AssociatedObjectChanged -= OnBehaviorHostChanged; + SourceNameResolver.NameScopeReferenceElement = null; + if (string.Compare(GetEventName(), "Loaded", StringComparison.Ordinal) == 0 && associatedElement != null) + UnregisterLoaded(associatedElement); + } + + protected virtual void OnEvent(EventArgs eventArgs) + { + InvokeActions(eventArgs); + } + + private void OnEventImpl(object sender, EventArgs eventArgs) + { + OnEvent(eventArgs); + } + + internal void OnEventNameChanged(string oldEventName, string newEventName) + { + if (AssociatedObject != null) + { + var source = Source as FrameworkElement; + if (source != null && string.Compare(oldEventName, "Loaded", StringComparison.Ordinal) == 0) + UnregisterLoaded(source); + else if (!string.IsNullOrEmpty(oldEventName)) + UnregisterEvent(Source, oldEventName); + if (source != null && string.Compare(newEventName, "Loaded", StringComparison.Ordinal) == 0) + RegisterLoaded(source); + else if (!string.IsNullOrEmpty(newEventName)) + RegisterEvent(Source, newEventName); + } + } + + private void OnSourceChanged(object oldSource, object newSource) + { + if (AssociatedObject != null) + OnSourceChangedImpl(oldSource, newSource); + } + + internal virtual void OnSourceChangedImpl(object oldSource, object newSource) + { + if (!string.IsNullOrEmpty(GetEventName()) && + string.Compare(GetEventName(), "Loaded", StringComparison.Ordinal) != 0) + { + if (oldSource != null && SourceTypeConstraint.IsInstanceOfType(oldSource)) + UnregisterEvent(oldSource, GetEventName()); + if (newSource != null && SourceTypeConstraint.IsInstanceOfType(newSource)) + RegisterEvent(newSource, GetEventName()); + } + } + + private static void OnSourceNameChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) + { + var base2 = (EventTriggerBase) obj; + base2.SourceNameResolver.Name = (string) args.NewValue; + } + + private void OnSourceNameResolverElementChanged(object sender, NameResolvedEventArgs e) + { + if (SourceObject == null) + OnSourceChanged(e.OldObject, e.NewObject); + } + + private static void OnSourceObjectChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) + { + var base2 = (EventTriggerBase) obj; + object newSource = base2.SourceNameResolver.Object; + if (args.NewValue == null) + { + base2.OnSourceChanged(args.OldValue, newSource); + } + else + { + if (args.OldValue == null && newSource != null) + base2.UnregisterEvent(newSource, base2.GetEventName()); + base2.OnSourceChanged(args.OldValue, args.NewValue); + } + } + + private void RegisterEvent(object obj, string eventName) + { + var eventInfo = obj.GetType().GetEvent(eventName); + if (eventInfo == null) + { + if (SourceObject != null) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, + ExceptionStringTable.EventTriggerCannotFindEventNameExceptionMessage, + new object[] {eventName, obj.GetType().Name})); + } + else if (!IsValidEvent(eventInfo)) + { + if (SourceObject != null) + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, + ExceptionStringTable.EventTriggerBaseInvalidEventExceptionMessage, + new object[] {eventName, obj.GetType().Name})); + } + else + { + _eventHandlerMethodInfo = + typeof(EventTriggerBase).GetMethod("OnEventImpl", BindingFlags.NonPublic | BindingFlags.Instance); + eventInfo.AddEventHandler(obj, + Delegate.CreateDelegate(eventInfo.EventHandlerType, this, _eventHandlerMethodInfo ?? throw new InvalidOperationException())); + } + } + + private void RegisterLoaded(FrameworkElement associatedElement) + { + if (!IsLoadedRegistered && associatedElement != null) + { + associatedElement.Loaded += OnEventImpl; + IsLoadedRegistered = true; + } + } + + private void RegisterSourceChanged() + { + if (!IsSourceChangedRegistered) + { + SourceNameResolver.ResolvedElementChanged += OnSourceNameResolverElementChanged; + IsSourceChangedRegistered = true; + } + } + + private void UnregisterEvent(object obj, string eventName) + { + if (string.Compare(eventName, "Loaded", StringComparison.Ordinal) == 0) + { + if (obj is FrameworkElement associatedElement) + UnregisterLoaded(associatedElement); + } + else + { + UnregisterEventImpl(obj, eventName); + } + } + + private void UnregisterEventImpl(object obj, string eventName) + { + var type = obj.GetType(); + if (_eventHandlerMethodInfo != null) + { + var info = type.GetEvent(eventName); + info.RemoveEventHandler(obj, + Delegate.CreateDelegate(info.EventHandlerType, this, _eventHandlerMethodInfo)); + _eventHandlerMethodInfo = null; + } + } + + private void UnregisterLoaded(FrameworkElement associatedElement) + { + if (IsLoadedRegistered && associatedElement != null) + { + associatedElement.Loaded -= OnEventImpl; + IsLoadedRegistered = false; + } + } + + private void UnregisterSourceChanged() + { + if (IsSourceChangedRegistered) + { + SourceNameResolver.ResolvedElementChanged -= OnSourceNameResolverElementChanged; + IsSourceChangedRegistered = false; + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/ExceptionStringTable.cs b/HandyControl/Interactivity/ExceptionStringTable.cs new file mode 100644 index 00000000..60259a47 --- /dev/null +++ b/HandyControl/Interactivity/ExceptionStringTable.cs @@ -0,0 +1,58 @@ +using System.ComponentModel; +using System.Globalization; +using System.Resources; + +namespace HandyControl.Interactivity +{ + internal class ExceptionStringTable + { + private static ResourceManager ResourceMan; + + [EditorBrowsable(EditorBrowsableState.Advanced)] + internal static ResourceManager ResourceManager => ResourceMan ?? (ResourceMan = new ResourceManager(nameof(ExceptionStringTable), + typeof(ExceptionStringTable).Assembly)); + + [EditorBrowsable(EditorBrowsableState.Advanced)] + internal static CultureInfo Culture { get; set; } + + internal static string CannotHostBehaviorCollectionMultipleTimesExceptionMessage => + ResourceManager.GetString(nameof(CannotHostBehaviorCollectionMultipleTimesExceptionMessage), Culture); + + internal static string CannotHostBehaviorMultipleTimesExceptionMessage => + ResourceManager.GetString(nameof(CannotHostBehaviorMultipleTimesExceptionMessage), Culture); + + internal static string CannotHostTriggerActionMultipleTimesExceptionMessage => + ResourceManager.GetString(nameof(CannotHostTriggerActionMultipleTimesExceptionMessage), Culture); + + internal static string CannotHostTriggerCollectionMultipleTimesExceptionMessage => + ResourceManager.GetString(nameof(CannotHostTriggerCollectionMultipleTimesExceptionMessage), Culture); + + internal static string CannotHostTriggerMultipleTimesExceptionMessage => + ResourceManager.GetString(nameof(CannotHostTriggerMultipleTimesExceptionMessage), Culture); + + internal static string CommandDoesNotExistOnBehaviorWarningMessage => + ResourceManager.GetString(nameof(CommandDoesNotExistOnBehaviorWarningMessage), Culture); + + internal static string DefaultTriggerAttributeInvalidTriggerTypeSpecifiedExceptionMessage => + ResourceManager.GetString(nameof(DefaultTriggerAttributeInvalidTriggerTypeSpecifiedExceptionMessage), + Culture); + + internal static string DuplicateItemInCollectionExceptionMessage => + ResourceManager.GetString(nameof(DuplicateItemInCollectionExceptionMessage), Culture); + + internal static string EventTriggerBaseInvalidEventExceptionMessage => + ResourceManager.GetString(nameof(EventTriggerBaseInvalidEventExceptionMessage), Culture); + + internal static string EventTriggerCannotFindEventNameExceptionMessage => + ResourceManager.GetString(nameof(EventTriggerCannotFindEventNameExceptionMessage), Culture); + + internal static string RetargetedTypeConstraintViolatedExceptionMessage => + ResourceManager.GetString(nameof(RetargetedTypeConstraintViolatedExceptionMessage), Culture); + + internal static string TypeConstraintViolatedExceptionMessage => + ResourceManager.GetString(nameof(TypeConstraintViolatedExceptionMessage), Culture); + + internal static string UnableToResolveTargetNameWarningMessage => + ResourceManager.GetString(nameof(UnableToResolveTargetNameWarningMessage), Culture); + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/ExtendedVisualStateManager.cs b/HandyControl/Interactivity/ExtendedVisualStateManager.cs new file mode 100644 index 00000000..e4608be1 --- /dev/null +++ b/HandyControl/Interactivity/ExtendedVisualStateManager.cs @@ -0,0 +1,1141 @@ +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.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Effects; +using System.Windows.Media.Imaging; + +namespace HandyControl.Interactivity +{ + public class ExtendedVisualStateManager : VisualStateManager + { + // Fields + internal static readonly DependencyProperty CachedBackgroundProperty = + DependencyProperty.RegisterAttached("CachedBackground", typeof(object), typeof(ExtendedVisualStateManager), + new PropertyMetadata(null)); + + internal static readonly DependencyProperty CachedEffectProperty = + DependencyProperty.RegisterAttached("CachedEffect", typeof(Effect), typeof(ExtendedVisualStateManager), + new PropertyMetadata(null)); + + private static readonly List ChildAffectingLayoutProperties; + + internal static readonly DependencyProperty CurrentStateProperty = + DependencyProperty.RegisterAttached("CurrentState", typeof(VisualState), typeof(ExtendedVisualStateManager), + new PropertyMetadata(null)); + + internal static readonly DependencyProperty DidCacheBackgroundProperty = + DependencyProperty.RegisterAttached("DidCacheBackground", typeof(bool), typeof(ExtendedVisualStateManager), + new PropertyMetadata(false)); + + private static readonly List LayoutProperties; + + internal static readonly DependencyProperty LayoutStoryboardProperty = + DependencyProperty.RegisterAttached("LayoutStoryboard", typeof(Storyboard), + typeof(ExtendedVisualStateManager), new PropertyMetadata(null)); + + private static Storyboard LayoutTransitionStoryboard; + private static List MovingElements; + + internal static readonly DependencyProperty OriginalLayoutValuesProperty = + DependencyProperty.RegisterAttached("OriginalLayoutValues", typeof(List), + typeof(ExtendedVisualStateManager), new PropertyMetadata(null)); + + public static readonly DependencyProperty RuntimeVisibilityPropertyProperty = + DependencyProperty.RegisterAttached("RuntimeVisibilityProperty", typeof(DependencyProperty), + typeof(ExtendedVisualStateManager), new PropertyMetadata(null)); + + public static readonly DependencyProperty TransitionEffectProperty = + DependencyProperty.RegisterAttached("TransitionEffect", typeof(TransitionEffect), + typeof(ExtendedVisualStateManager), new PropertyMetadata(null)); + + internal static readonly DependencyProperty TransitionEffectStoryboardProperty = + DependencyProperty.RegisterAttached("TransitionEffectStoryboard", typeof(Storyboard), + typeof(ExtendedVisualStateManager), new PropertyMetadata(null)); + + public static readonly DependencyProperty UseFluidLayoutProperty = + DependencyProperty.RegisterAttached("UseFluidLayout", typeof(bool), typeof(ExtendedVisualStateManager), + new PropertyMetadata(false)); + + private bool _changingState; + + // Methods + static ExtendedVisualStateManager() + { + var list = new List + { + Grid.ColumnProperty, + Grid.ColumnSpanProperty, + Grid.RowProperty, + Grid.RowSpanProperty, + Canvas.LeftProperty, + Canvas.TopProperty, + FrameworkElement.WidthProperty, + FrameworkElement.HeightProperty, + FrameworkElement.MinWidthProperty, + FrameworkElement.MinHeightProperty, + FrameworkElement.MaxWidthProperty, + FrameworkElement.MaxHeightProperty, + FrameworkElement.MarginProperty, + FrameworkElement.HorizontalAlignmentProperty, + FrameworkElement.VerticalAlignmentProperty, + UIElement.VisibilityProperty, + StackPanel.OrientationProperty + }; + LayoutProperties = list; + var list2 = new List + { + StackPanel.OrientationProperty + }; + ChildAffectingLayoutProperties = list2; + } + + // Properties + public static bool IsRunningFluidLayoutTransition => + LayoutTransitionStoryboard != null; + + private static void AnimateTransitionEffect(FrameworkElement stateGroupsRoot, VisualTransition transition) + { + var element = new DoubleAnimation + { + Duration = transition.GeneratedDuration, + EasingFunction = transition.GeneratedEasingFunction, + From = 0.0, + To = 1.0 + }; + var sb = new Storyboard + { + Duration = transition.GeneratedDuration, + Children = {element} + }; + Storyboard.SetTarget(element, stateGroupsRoot); + Storyboard.SetTargetProperty(element, + new PropertyPath("(0).(1)", UIElement.EffectProperty, TransitionEffect.ProgressProperty)); + if (stateGroupsRoot is Panel panel && panel.Background == null) + { + SetDidCacheBackground(panel, true); + TransferLocalValue(panel, Panel.BackgroundProperty, CachedBackgroundProperty); + panel.Background = Brushes.Transparent; + } + sb.Completed += delegate + { + if (Equals(GetTransitionEffectStoryboard(stateGroupsRoot), sb)) + FinishTransitionEffectAnimation(stateGroupsRoot); + }; + SetTransitionEffectStoryboard(stateGroupsRoot, sb); + sb.Begin(); + } + + private static object CacheLocalValueHelper(DependencyObject dependencyObject, DependencyProperty property) + { + return dependencyObject.ReadLocalValue(property); + } + + private static void control_LayoutUpdated(object sender, EventArgs e) + { + if (LayoutTransitionStoryboard != null) + foreach (var element in MovingElements) + { + if (element.Parent is WrapperCanvas parent) + { + var layoutRect = GetLayoutRect(parent); + var newRect = parent.NewRect; + var renderTransform = parent.RenderTransform as TranslateTransform; + var num = renderTransform?.X ?? 0.0; + var num2 = renderTransform?.Y ?? 0.0; + var num3 = newRect.Left - layoutRect.Left; + var num4 = newRect.Top - layoutRect.Top; + if (Math.Abs(num - num3) > 0.001 || Math.Abs(num2 - num4) > 0.001) + { + if (renderTransform == null) + { + renderTransform = new TranslateTransform(); + parent.RenderTransform = renderTransform; + } + renderTransform.X = num3; + renderTransform.Y = num4; + } + } + } + } + + private static void CopyLayoutProperties(FrameworkElement source, FrameworkElement target, bool restoring) + { + var canvas = restoring ? (WrapperCanvas) source : (WrapperCanvas) target; + if (canvas.LocalValueCache == null) + canvas.LocalValueCache = new Dictionary(); + foreach (var property in LayoutProperties) + if (!ChildAffectingLayoutProperties.Contains(property)) + if (restoring) + { + ReplaceCachedLocalValueHelper(target, property, canvas.LocalValueCache[property]); + } + else + { + var obj2 = target.GetValue(property); + var obj3 = CacheLocalValueHelper(source, property); + canvas.LocalValueCache[property] = obj3; + if (IsVisibilityProperty(property)) + canvas.DestinationVisibilityCache = (Visibility) source.GetValue(property); + else + target.SetValue(property, source.GetValue(property)); + source.SetValue(property, obj2); + } + } + + private static Storyboard CreateLayoutTransitionStoryboard(VisualTransition transition, + List movingElements, Dictionary oldOpacities) + { + var duration = transition?.GeneratedDuration ?? new Duration(TimeSpan.Zero); + var generatedEasingFunction = transition?.GeneratedEasingFunction; + var storyboard = new Storyboard + { + Duration = duration + }; + foreach (var element in movingElements) + { + if (element.Parent is WrapperCanvas parent) + { + var animation = new DoubleAnimation + { + From = 1.0, + To = 0.0, + Duration = duration, + EasingFunction = generatedEasingFunction + }; + Storyboard.SetTarget(animation, parent); + Storyboard.SetTargetProperty(animation, new PropertyPath(WrapperCanvas.SimulationProgressProperty)); + storyboard.Children.Add(animation); + parent.SimulationProgress = 1.0; + var newRect = parent.NewRect; + if (!IsClose(parent.Width, newRect.Width)) + { + var animation3 = new DoubleAnimation + { + From = newRect.Width, + To = newRect.Width, + Duration = duration + }; + Storyboard.SetTarget(animation3, parent); + Storyboard.SetTargetProperty(animation3, new PropertyPath(FrameworkElement.WidthProperty)); + storyboard.Children.Add(animation3); + } + if (!IsClose(parent.Height, newRect.Height)) + { + var animation5 = new DoubleAnimation + { + From = newRect.Height, + To = newRect.Height, + Duration = duration + }; + Storyboard.SetTarget(animation5, parent); + Storyboard.SetTargetProperty(animation5, new PropertyPath(FrameworkElement.HeightProperty)); + storyboard.Children.Add(animation5); + } + if (parent.DestinationVisibilityCache == Visibility.Collapsed) + { + var margin = parent.Margin; + if (!IsClose(margin.Left, 0.0) || !IsClose(margin.Top, 0.0) || !IsClose(margin.Right, 0.0) || + !IsClose(margin.Bottom, 0.0)) + { + var frames = new ObjectAnimationUsingKeyFrames + { + Duration = duration + }; + var frame2 = new DiscreteObjectKeyFrame + { + KeyTime = TimeSpan.Zero + }; + var thickness2 = new Thickness(); + frame2.Value = thickness2; + var keyFrame = frame2; + frames.KeyFrames.Add(keyFrame); + Storyboard.SetTarget(frames, parent); + Storyboard.SetTargetProperty(frames, new PropertyPath(FrameworkElement.MarginProperty)); + storyboard.Children.Add(frames); + } + if (!IsClose(parent.MinWidth, 0.0)) + { + var animation7 = new DoubleAnimation + { + From = 0.0, + To = 0.0, + Duration = duration + }; + Storyboard.SetTarget(animation7, parent); + Storyboard.SetTargetProperty(animation7, + new PropertyPath(FrameworkElement.MinWidthProperty)); + storyboard.Children.Add(animation7); + } + if (!IsClose(parent.MinHeight, 0.0)) + { + var animation9 = new DoubleAnimation + { + From = 0.0, + To = 0.0, + Duration = duration + }; + Storyboard.SetTarget(animation9, parent); + Storyboard.SetTargetProperty(animation9, + new PropertyPath(FrameworkElement.MinHeightProperty)); + storyboard.Children.Add(animation9); + } + } + } + } + foreach (var element2 in oldOpacities.Keys) + { + if (element2.Parent is WrapperCanvas canvas2) + { + var a = oldOpacities[element2]; + var num2 = canvas2.DestinationVisibilityCache == Visibility.Visible ? 1.0 : 0.0; + if (!IsClose(a, 1.0) || !IsClose(num2, 1.0)) + { + var animation11 = new DoubleAnimation + { + From = a, + To = num2, + Duration = duration, + EasingFunction = generatedEasingFunction + }; + Storyboard.SetTarget(animation11, canvas2); + Storyboard.SetTargetProperty(animation11, new PropertyPath(UIElement.OpacityProperty)); + storyboard.Children.Add(animation11); + } + } + } + return storyboard; + } + + private static Storyboard ExtractLayoutStoryboard(VisualState state) + { + Storyboard layoutStoryboard = null; + if (state.Storyboard != null) + { + layoutStoryboard = GetLayoutStoryboard(state.Storyboard); + if (layoutStoryboard == null) + { + layoutStoryboard = new Storyboard(); + for (var i = state.Storyboard.Children.Count - 1; i >= 0; i--) + { + var timeline = state.Storyboard.Children[i]; + if (LayoutPropertyFromTimeline(timeline, false) != null) + { + state.Storyboard.Children.RemoveAt(i); + layoutStoryboard.Children.Add(timeline); + } + } + SetLayoutStoryboard(state.Storyboard, layoutStoryboard); + } + } + if (layoutStoryboard == null) + return new Storyboard(); + return layoutStoryboard; + } + + private static List FindTargetElements(FrameworkElement control, + FrameworkElement templateRoot, Storyboard layoutStoryboard, + List originalValueRecords, List movingElements) + { + var list = new List(); + if (movingElements != null) + list.AddRange(movingElements); + foreach (var timeline in layoutStoryboard.Children) + { + var item = (FrameworkElement) GetTimelineTarget(control, templateRoot, timeline); + if (item != null) + { + if (!list.Contains(item)) + list.Add(item); + if (ChildAffectingLayoutProperties.Contains(LayoutPropertyFromTimeline(timeline, false))) + { + if (item is Panel panel) + foreach (FrameworkElement element2 in panel.Children) + if (!list.Contains(element2) && !(element2 is WrapperCanvas)) + list.Add(element2); + } + } + } + foreach (var record in originalValueRecords) + { + if (!list.Contains(record.Element)) + list.Add(record.Element); + if (ChildAffectingLayoutProperties.Contains(record.Property)) + { + if (record.Element is Panel element) + foreach (FrameworkElement element3 in element.Children) + if (!list.Contains(element3) && !(element3 is WrapperCanvas)) + list.Add(element3); + } + } + for (var i = 0; i < list.Count; i++) + { + var reference = list[i]; + var parent = VisualTreeHelper.GetParent(reference) as FrameworkElement; + if (movingElements != null && movingElements.Contains(reference) && parent is WrapperCanvas) + parent = VisualTreeHelper.GetParent(parent) as FrameworkElement; + if (parent != null) + { + if (!list.Contains(parent)) + list.Add(parent); + for (var j = 0; j < VisualTreeHelper.GetChildrenCount(parent); j++) + { + if (VisualTreeHelper.GetChild(parent, j) is FrameworkElement child && !list.Contains(child) && !(child is WrapperCanvas)) + list.Add(child); + } + } + } + return list; + } + + private static VisualTransition FindTransition(VisualStateGroup group, VisualState previousState, + VisualState state) + { + var str = previousState != null ? previousState.Name : string.Empty; + var str2 = state != null ? state.Name : string.Empty; + var num = -1; + VisualTransition transition = null; + foreach (VisualTransition transition2 in group.Transitions) + { + var num2 = 0; + if (transition2.From == str) + num2++; + else if (!string.IsNullOrEmpty(transition2.From)) + continue; + if (transition2.To == str2) + num2 += 2; + else if (!string.IsNullOrEmpty(transition2.To)) + continue; + if (num2 > num) + { + num = num2; + transition = transition2; + } + } + return transition; + } + + private static bool FinishesWithZeroOpacity(FrameworkElement control, FrameworkElement stateGroupsRoot, + VisualState state, VisualState previousState) + { + if (state.Storyboard != null) + foreach (var timeline in state.Storyboard.Children) + if (TimelineIsAnimatingRootOpacity(timeline, control, stateGroupsRoot)) + { + var valueFromTimeline = GetValueFromTimeline(timeline, out var flag); + return flag && valueFromTimeline is double d && Math.Abs(d) < 0.001; + } + if (previousState?.Storyboard == null) + return Math.Abs(stateGroupsRoot.Opacity) < 0.001; + foreach (var timeline2 in previousState.Storyboard.Children) + TimelineIsAnimatingRootOpacity(timeline2, control, stateGroupsRoot); + var animationBaseValue = (double) stateGroupsRoot.GetAnimationBaseValue(UIElement.OpacityProperty); + return Math.Abs(animationBaseValue) < 0.001; + } + + private static void FinishTransitionEffectAnimation(FrameworkElement stateGroupsRoot) + { + SetTransitionEffectStoryboard(stateGroupsRoot, null); + TransferLocalValue(stateGroupsRoot, CachedEffectProperty, UIElement.EffectProperty); + if (GetDidCacheBackground(stateGroupsRoot)) + { + TransferLocalValue(stateGroupsRoot, CachedBackgroundProperty, Panel.BackgroundProperty); + SetDidCacheBackground(stateGroupsRoot, false); + } + } + + internal static object GetCachedBackground(DependencyObject obj) + { + return obj.GetValue(CachedBackgroundProperty); + } + + internal static Effect GetCachedEffect(DependencyObject obj) + { + return (Effect) obj.GetValue(CachedEffectProperty); + } + + internal static VisualState GetCurrentState(DependencyObject obj) + { + return (VisualState) obj.GetValue(CurrentStateProperty); + } + + internal static bool GetDidCacheBackground(DependencyObject obj) + { + return (bool) obj.GetValue(DidCacheBackgroundProperty); + } + + internal static Rect GetLayoutRect(FrameworkElement element) + { + var actualWidth = element.ActualWidth; + var actualHeight = element.ActualHeight; + if (element is Image || element is MediaElement) + if (element.Parent is Canvas) + { + actualWidth = double.IsNaN(element.Width) ? actualWidth : element.Width; + actualHeight = double.IsNaN(element.Height) ? actualHeight : element.Height; + } + else + { + actualWidth = element.RenderSize.Width; + actualHeight = element.RenderSize.Height; + } + actualWidth = element.Visibility == Visibility.Collapsed ? 0.0 : actualWidth; + actualHeight = element.Visibility == Visibility.Collapsed ? 0.0 : actualHeight; + var margin = element.Margin; + var layoutSlot = LayoutInformation.GetLayoutSlot(element); + var x = 0.0; + var y = 0.0; + switch (element.HorizontalAlignment) + { + case HorizontalAlignment.Left: + x = layoutSlot.Left + margin.Left; + break; + + case HorizontalAlignment.Center: + x = (layoutSlot.Left + margin.Left + layoutSlot.Right - margin.Right) / 2.0 - actualWidth / 2.0; + break; + + case HorizontalAlignment.Right: + x = layoutSlot.Right - margin.Right - actualWidth; + break; + + case HorizontalAlignment.Stretch: + x = Math.Max(layoutSlot.Left + margin.Left, + (layoutSlot.Left + margin.Left + layoutSlot.Right - margin.Right) / 2.0 - actualWidth / 2.0); + break; + } + switch (element.VerticalAlignment) + { + case VerticalAlignment.Top: + y = layoutSlot.Top + margin.Top; + break; + + case VerticalAlignment.Center: + y = (layoutSlot.Top + margin.Top + layoutSlot.Bottom - margin.Bottom) / 2.0 - actualHeight / 2.0; + break; + + case VerticalAlignment.Bottom: + y = layoutSlot.Bottom - margin.Bottom - actualHeight; + break; + + case VerticalAlignment.Stretch: + y = Math.Max(layoutSlot.Top + margin.Top, + (layoutSlot.Top + margin.Top + layoutSlot.Bottom - margin.Bottom) / 2.0 - actualHeight / 2.0); + break; + } + return new Rect(x, y, actualWidth, actualHeight); + } + + internal static Storyboard GetLayoutStoryboard(DependencyObject obj) + { + return (Storyboard) obj.GetValue(LayoutStoryboardProperty); + } + + private static Dictionary GetOldOpacities(FrameworkElement control, + FrameworkElement templateRoot, Storyboard layoutStoryboard, + List originalValueRecords, List movingElements) + { + var dictionary = new Dictionary(); + if (movingElements != null) + foreach (var element in movingElements) + { + if (element.Parent is WrapperCanvas parent) + dictionary.Add(element, parent.Opacity); + } + for (var i = originalValueRecords.Count - 1; i >= 0; i--) + { + var record = originalValueRecords[i]; + if (IsVisibilityProperty(record.Property) && !dictionary.TryGetValue(record.Element, out var num2)) + { + num2 = (Visibility) record.Element.GetValue(record.Property) == Visibility.Visible ? 1.0 : 0.0; + dictionary.Add(record.Element, num2); + } + } + foreach (var timeline in layoutStoryboard.Children) + { + var key = (FrameworkElement) GetTimelineTarget(control, templateRoot, timeline); + var property = LayoutPropertyFromTimeline(timeline, true); + if (key != null && IsVisibilityProperty(property) && !dictionary.TryGetValue(key, out var num3)) + { + num3 = (Visibility) key.GetValue(property) == Visibility.Visible ? 1.0 : 0.0; + dictionary.Add(key, num3); + } + } + return dictionary; + } + + internal static List GetOriginalLayoutValues(DependencyObject obj) + { + return (List) obj.GetValue(OriginalLayoutValuesProperty); + } + + private static Dictionary GetRectsOfTargets(IEnumerable targets, + ICollection movingElements) + { + var dictionary = new Dictionary(); + foreach (var element in targets) + { + Rect layoutRect; + if (movingElements != null && movingElements.Contains(element) && element.Parent is WrapperCanvas parent) + { + layoutRect = GetLayoutRect(parent); + var renderTransform = parent.RenderTransform as TranslateTransform; + var left = Canvas.GetLeft(element); + var top = Canvas.GetTop(element); + layoutRect = new Rect( + layoutRect.Left + (double.IsNaN(left) ? 0.0 : left) + + (renderTransform?.X ?? 0.0), + layoutRect.Top + (double.IsNaN(top) ? 0.0 : top) + + (renderTransform?.Y ?? 0.0), element.ActualWidth, element.ActualHeight); + } + else + { + layoutRect = GetLayoutRect(element); + } + dictionary.Add(element, layoutRect); + } + return dictionary; + } + + public static DependencyProperty GetRuntimeVisibilityProperty(DependencyObject obj) + { + return (DependencyProperty) obj.GetValue(RuntimeVisibilityPropertyProperty); + } + + private static object GetTimelineTarget(FrameworkElement control, FrameworkElement templateRoot, + Timeline timeline) + { + var targetName = Storyboard.GetTargetName(timeline); + if (string.IsNullOrEmpty(targetName)) + return null; + if (control is UserControl) + return control.FindName(targetName); + return templateRoot.FindName(targetName); + } + + public static TransitionEffect GetTransitionEffect(DependencyObject obj) + { + return (TransitionEffect) obj.GetValue(TransitionEffectProperty); + } + + internal static Storyboard GetTransitionEffectStoryboard(DependencyObject obj) + { + return (Storyboard) obj.GetValue(TransitionEffectStoryboardProperty); + } + + public static bool GetUseFluidLayout(DependencyObject obj) + { + return (bool) obj.GetValue(UseFluidLayoutProperty); + } + + private static object GetValueFromTimeline(Timeline timeline, out bool gotValue) + { + if (timeline is ObjectAnimationUsingKeyFrames frames) + { + gotValue = true; + return frames.KeyFrames[0].Value; + } + if (timeline is DoubleAnimationUsingKeyFrames frames2) + { + gotValue = true; + return frames2.KeyFrames[0].Value; + } + if (timeline is DoubleAnimation animation) + { + gotValue = true; + return animation.To; + } + if (timeline is ThicknessAnimationUsingKeyFrames frames3) + { + gotValue = true; + return frames3.KeyFrames[0].Value; + } + if (timeline is ThicknessAnimation animation2) + { + gotValue = true; + return animation2.To; + } + if (timeline is Int32AnimationUsingKeyFrames frames4) + { + gotValue = true; + return frames4.KeyFrames[0].Value; + } + if (timeline is Int32Animation animation3) + { + gotValue = true; + return animation3.To; + } + gotValue = false; + return null; + } + + protected override bool GoToStateCore(FrameworkElement control, FrameworkElement stateGroupsRoot, + string stateName, VisualStateGroup group, VisualState state, bool useTransitions) + { + if (_changingState) + return false; + var currentState = GetCurrentState(group); + if (!Equals(currentState, state)) + { + var transition = FindTransition(group, currentState, state); + var animateWithTransitionEffect = + PrepareTransitionEffectImage(stateGroupsRoot, useTransitions, transition); + if (!GetUseFluidLayout(group)) + return TransitionEffectAwareGoToStateCore(control, stateGroupsRoot, stateName, group, state, + useTransitions, transition, animateWithTransitionEffect, currentState); + var layoutStoryboard = ExtractLayoutStoryboard(state); + var originalLayoutValues = GetOriginalLayoutValues(group); + if (originalLayoutValues == null) + { + originalLayoutValues = new List(); + SetOriginalLayoutValues(group, originalLayoutValues); + } + if (!useTransitions) + { + if (LayoutTransitionStoryboard != null) + StopAnimations(); + var flag2 = TransitionEffectAwareGoToStateCore(control, stateGroupsRoot, stateName, group, state, + false, transition, animateWithTransitionEffect, currentState); + SetLayoutStoryboardProperties(control, stateGroupsRoot, layoutStoryboard, originalLayoutValues); + return flag2; + } + if (layoutStoryboard.Children.Count == 0 && originalLayoutValues.Count == 0) + return TransitionEffectAwareGoToStateCore(control, stateGroupsRoot, stateName, group, state, + true, transition, animateWithTransitionEffect, currentState); + try + { + _changingState = true; + stateGroupsRoot.UpdateLayout(); + var targets = FindTargetElements(control, stateGroupsRoot, layoutStoryboard, originalLayoutValues, + MovingElements); + var rectsOfTargets = GetRectsOfTargets(targets, MovingElements); + var oldOpacities = GetOldOpacities(control, stateGroupsRoot, layoutStoryboard, originalLayoutValues, + MovingElements); + if (LayoutTransitionStoryboard != null) + { + stateGroupsRoot.LayoutUpdated -= control_LayoutUpdated; + StopAnimations(); + stateGroupsRoot.UpdateLayout(); + } + TransitionEffectAwareGoToStateCore(control, stateGroupsRoot, stateName, group, state, + true, transition, animateWithTransitionEffect, currentState); + SetLayoutStoryboardProperties(control, stateGroupsRoot, layoutStoryboard, originalLayoutValues); + stateGroupsRoot.UpdateLayout(); + var newRects = GetRectsOfTargets(targets, null); + MovingElements = new List(); + foreach (var element in targets) + if (rectsOfTargets[element] != newRects[element]) + MovingElements.Add(element); + foreach (var element2 in oldOpacities.Keys) + if (!MovingElements.Contains(element2)) + MovingElements.Add(element2); + WrapMovingElementsInCanvases(MovingElements, rectsOfTargets, newRects); + stateGroupsRoot.LayoutUpdated += control_LayoutUpdated; + LayoutTransitionStoryboard = + CreateLayoutTransitionStoryboard(transition, MovingElements, oldOpacities); + + void Handler(object sender, EventArgs e) + { + stateGroupsRoot.LayoutUpdated -= control_LayoutUpdated; + StopAnimations(); + } + + LayoutTransitionStoryboard.Completed += Handler; + LayoutTransitionStoryboard.Begin(); + } + finally + { + _changingState = false; + } + } + return true; + } + + private static bool IsClose(double a, double b) + { + return Math.Abs(a - b) < 1E-07; + } + + private static bool IsVisibilityProperty(DependencyProperty property) + { + if (property != UIElement.VisibilityProperty) + return property.Name == "RuntimeVisibility"; + return true; + } + + private static DependencyProperty LayoutPropertyFromTimeline(Timeline timeline, bool forceRuntimeProperty) + { + var targetProperty = Storyboard.GetTargetProperty(timeline); + if (targetProperty != null && + targetProperty.PathParameters.Count != 0) + { + if (targetProperty.PathParameters[0] is DependencyProperty item) + { + if (item.Name == "RuntimeVisibility" && + item.OwnerType.Name.EndsWith("DesignTimeProperties", StringComparison.Ordinal)) + { + if (!LayoutProperties.Contains(item)) + LayoutProperties.Add(item); + if (!forceRuntimeProperty) + return UIElement.VisibilityProperty; + return item; + } + if (item.Name == "RuntimeWidth" && + item.OwnerType.Name.EndsWith("DesignTimeProperties", StringComparison.Ordinal)) + { + if (!LayoutProperties.Contains(item)) + LayoutProperties.Add(item); + if (!forceRuntimeProperty) + return FrameworkElement.WidthProperty; + return item; + } + if (item.Name == "RuntimeHeight" && + item.OwnerType.Name.EndsWith("DesignTimeProperties", StringComparison.Ordinal)) + { + if (!LayoutProperties.Contains(item)) + LayoutProperties.Add(item); + if (!forceRuntimeProperty) + return FrameworkElement.HeightProperty; + return item; + } + if (LayoutProperties.Contains(item)) + return item; + } + } + return null; + } + + private static bool PrepareTransitionEffectImage(FrameworkElement stateGroupsRoot, bool useTransitions, + VisualTransition transition) + { + var effect = transition == null ? null : GetTransitionEffect(transition); + var flag = false; + if (effect != null) + { + effect = effect.CloneCurrentValue(); + if (useTransitions) + { + flag = true; + var pixelWidth = (int) Math.Max(1.0, stateGroupsRoot.ActualWidth); + var pixelHeight = (int) Math.Max(1.0, stateGroupsRoot.ActualHeight); + var bitmap = new RenderTargetBitmap(pixelWidth, pixelHeight, 96.0, 96.0, PixelFormats.Pbgra32); + bitmap.Render(stateGroupsRoot); + var brush = new ImageBrush + { + ImageSource = bitmap + }; + effect.OldImage = brush; + } + var transitionEffectStoryboard = GetTransitionEffectStoryboard(stateGroupsRoot); + if (transitionEffectStoryboard != null) + { + transitionEffectStoryboard.Stop(); + FinishTransitionEffectAnimation(stateGroupsRoot); + } + if (useTransitions) + { + TransferLocalValue(stateGroupsRoot, UIElement.EffectProperty, CachedEffectProperty); + stateGroupsRoot.Effect = effect; + } + } + return flag; + } + + private static void ReplaceCachedLocalValueHelper(FrameworkElement element, DependencyProperty property, + object value) + { + if (value == DependencyProperty.UnsetValue) + { + element.ClearValue(property); + } + else + { + if (value is BindingExpressionBase base2) + element.SetBinding(property, base2.ParentBindingBase); + else + element.SetValue(property, value); + } + } + + internal static void SetCachedBackground(DependencyObject obj, object value) + { + obj.SetValue(CachedBackgroundProperty, value); + } + + internal static void SetCachedEffect(DependencyObject obj, Effect value) + { + obj.SetValue(CachedEffectProperty, value); + } + + internal static void SetCurrentState(DependencyObject obj, VisualState value) + { + obj.SetValue(CurrentStateProperty, value); + } + + internal static void SetDidCacheBackground(DependencyObject obj, bool value) + { + obj.SetValue(DidCacheBackgroundProperty, value); + } + + internal static void SetLayoutStoryboard(DependencyObject obj, Storyboard value) + { + obj.SetValue(LayoutStoryboardProperty, value); + } + + private static void SetLayoutStoryboardProperties(FrameworkElement control, FrameworkElement templateRoot, + Storyboard layoutStoryboard, List originalValueRecords) + { + foreach (var record in originalValueRecords) + ReplaceCachedLocalValueHelper(record.Element, record.Property, record.Value); + originalValueRecords.Clear(); + foreach (var timeline in layoutStoryboard.Children) + { + var dependencyObject = (FrameworkElement) GetTimelineTarget(control, templateRoot, timeline); + var property = LayoutPropertyFromTimeline(timeline, true); + if (dependencyObject != null && property != null) + { + var valueFromTimeline = GetValueFromTimeline(timeline, out var flag); + if (flag) + { + var item = new OriginalLayoutValueRecord + { + Element = dependencyObject, + Property = property, + Value = CacheLocalValueHelper(dependencyObject, property) + }; + originalValueRecords.Add(item); + dependencyObject.SetValue(property, valueFromTimeline); + } + } + } + } + + internal static void SetOriginalLayoutValues(DependencyObject obj, List value) + { + obj.SetValue(OriginalLayoutValuesProperty, value); + } + + public static void SetRuntimeVisibilityProperty(DependencyObject obj, DependencyProperty value) + { + obj.SetValue(RuntimeVisibilityPropertyProperty, value); + } + + public static void SetTransitionEffect(DependencyObject obj, TransitionEffect value) + { + obj.SetValue(TransitionEffectProperty, value); + } + + internal static void SetTransitionEffectStoryboard(DependencyObject obj, Storyboard value) + { + obj.SetValue(TransitionEffectStoryboardProperty, value); + } + + public static void SetUseFluidLayout(DependencyObject obj, bool value) + { + obj.SetValue(UseFluidLayoutProperty, value); + } + + private static void StopAnimations() + { + if (LayoutTransitionStoryboard != null) + { + LayoutTransitionStoryboard.Stop(); + LayoutTransitionStoryboard = null; + } + if (MovingElements != null) + { + UnwrapMovingElementsFromCanvases(MovingElements); + MovingElements = null; + } + } + + private static bool TimelineIsAnimatingRootOpacity(Timeline timeline, FrameworkElement control, + FrameworkElement stateGroupsRoot) + { + if (!Equals(GetTimelineTarget(control, stateGroupsRoot, timeline), stateGroupsRoot)) + return false; + var targetProperty = Storyboard.GetTargetProperty(timeline); + return targetProperty != null && + targetProperty.PathParameters.Count != 0 && + targetProperty.PathParameters[0] == UIElement.OpacityProperty; + } + + private static void TransferLocalValue(FrameworkElement element, DependencyProperty sourceProperty, + DependencyProperty destProperty) + { + var obj2 = CacheLocalValueHelper(element, sourceProperty); + ReplaceCachedLocalValueHelper(element, destProperty, obj2); + } + + private bool TransitionEffectAwareGoToStateCore(FrameworkElement control, FrameworkElement stateGroupsRoot, + string stateName, VisualStateGroup group, VisualState state, bool useTransitions, + VisualTransition transition, bool animateWithTransitionEffect, VisualState previousState) + { + IEasingFunction generatedEasingFunction = null; + if (animateWithTransitionEffect) + { + generatedEasingFunction = transition.GeneratedEasingFunction; + var function2 = new DummyEasingFunction + { + DummyValue = FinishesWithZeroOpacity(control, stateGroupsRoot, state, previousState) ? 0.01 : 0.0 + }; + transition.GeneratedEasingFunction = function2; + } + var flag = base.GoToStateCore(control, stateGroupsRoot, stateName, group, state, useTransitions); + if (animateWithTransitionEffect) + { + transition.GeneratedEasingFunction = generatedEasingFunction; + if (flag) + AnimateTransitionEffect(stateGroupsRoot, transition); + } + SetCurrentState(group, state); + return flag; + } + + private static void UnwrapMovingElementsFromCanvases(List movingElements) + { + foreach (var element in movingElements) + { + if (element.Parent is WrapperCanvas parent) + { + var obj2 = CacheLocalValueHelper(element, FrameworkElement.DataContextProperty); + element.DataContext = element.DataContext; + var element2 = VisualTreeHelper.GetParent(parent) as FrameworkElement; + parent.Children.Remove(element); + if (element2 is Panel panel) + { + var index = panel.Children.IndexOf(parent); + panel.Children.RemoveAt(index); + panel.Children.Insert(index, element); + } + else + { + if (element2 is Decorator decorator) + decorator.Child = element; + } + CopyLayoutProperties(parent, element, true); + ReplaceCachedLocalValueHelper(element, FrameworkElement.DataContextProperty, obj2); + } + } + } + + private static void WrapMovingElementsInCanvases(List movingElements, + Dictionary oldRects, Dictionary newRects) + { + foreach (var element in movingElements) + { + var parent = VisualTreeHelper.GetParent(element) as FrameworkElement; + var canvas = new WrapperCanvas + { + OldRect = oldRects[element], + NewRect = newRects[element] + }; + var obj2 = CacheLocalValueHelper(element, FrameworkElement.DataContextProperty); + element.DataContext = element.DataContext; + var flag = true; + if (parent is Panel panel && !panel.IsItemsHost) + { + var index = panel.Children.IndexOf(element); + panel.Children.RemoveAt(index); + panel.Children.Insert(index, canvas); + } + else + { + if (parent is Decorator decorator) + decorator.Child = canvas; + else + flag = false; + } + if (flag) + { + canvas.Children.Add(element); + CopyLayoutProperties(element, canvas, false); + ReplaceCachedLocalValueHelper(element, FrameworkElement.DataContextProperty, obj2); + } + } + } + + // Nested Types + private class DummyEasingFunction : EasingFunctionBase + { + // Fields + public static readonly DependencyProperty DummyValueProperty = DependencyProperty.Register("DummyValue", + typeof(double), typeof(DummyEasingFunction), new PropertyMetadata(0.0)); + + // Properties + public double DummyValue + { + private get => (double) GetValue(DummyValueProperty); + set => SetValue(DummyValueProperty, value); + } + + // Methods + protected override Freezable CreateInstanceCore() + { + return new DummyEasingFunction(); + } + + protected override double EaseInCore(double normalizedTime) + { + return DummyValue; + } + } + + internal class OriginalLayoutValueRecord + { + // Properties + public FrameworkElement Element { get; set; } + + public DependencyProperty Property { get; set; } + + public object Value { get; set; } + } + + internal class WrapperCanvas : Canvas + { + // Fields + internal static readonly DependencyProperty SimulationProgressProperty = + DependencyProperty.Register("SimulationProgress", typeof(double), typeof(WrapperCanvas), + new PropertyMetadata(0.0, SimulationProgressChanged)); + + // Properties + public Visibility DestinationVisibilityCache { get; set; } + + public Dictionary LocalValueCache { get; set; } + + public Rect NewRect { get; set; } + + public Rect OldRect { get; set; } + + public double SimulationProgress + { + get => + (double) GetValue(SimulationProgressProperty); + set => SetValue(SimulationProgressProperty, value); + } + + // Methods + private static void SimulationProgressChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var canvas = d as WrapperCanvas; + var newValue = (double) e.NewValue; + if (canvas != null && canvas.Children.Count > 0) + { + if (canvas.Children[0] is FrameworkElement element) + { + element.Width = Math.Max(0.0, + canvas.OldRect.Width * newValue + canvas.NewRect.Width * (1.0 - newValue)); + element.Height = Math.Max(0.0, + canvas.OldRect.Height * newValue + canvas.NewRect.Height * (1.0 - newValue)); + SetLeft(element, newValue * (canvas.OldRect.Left - canvas.NewRect.Left)); + SetTop(element, newValue * (canvas.OldRect.Top - canvas.NewRect.Top)); + } + } + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/FluidMoveBehavior.cs b/HandyControl/Interactivity/FluidMoveBehavior.cs new file mode 100644 index 00000000..e980e3a8 --- /dev/null +++ b/HandyControl/Interactivity/FluidMoveBehavior.cs @@ -0,0 +1,448 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; + +namespace HandyControl.Interactivity +{ + public sealed class FluidMoveBehavior : FluidMoveBehaviorBase + { + private static readonly DependencyProperty CacheDuringOverlayProperty = + DependencyProperty.RegisterAttached("CacheDuringOverlay", typeof(object), + typeof(FluidMoveBehavior), new PropertyMetadata(null)); + + public static readonly DependencyProperty DurationProperty = DependencyProperty.Register("Duration", + typeof(Duration), typeof(FluidMoveBehavior), + new PropertyMetadata(new Duration(TimeSpan.FromSeconds(1.0)))); + + public static readonly DependencyProperty EaseXProperty = DependencyProperty.Register("EaseX", + typeof(IEasingFunction), typeof(FluidMoveBehavior), new PropertyMetadata(null)); + + public static readonly DependencyProperty EaseYProperty = DependencyProperty.Register("EaseY", + typeof(IEasingFunction), typeof(FluidMoveBehavior), new PropertyMetadata(null)); + + public static readonly DependencyProperty FloatAboveProperty = + DependencyProperty.Register("FloatAbove", typeof(bool), typeof(FluidMoveBehavior), + new PropertyMetadata(true)); + + private static readonly DependencyProperty HasTransformWrapperProperty = + DependencyProperty.RegisterAttached("HasTransformWrapper", typeof(bool), + typeof(FluidMoveBehavior), new PropertyMetadata(false)); + + private static readonly DependencyProperty InitialIdentityTagProperty = + DependencyProperty.RegisterAttached("InitialIdentityTag", typeof(object), + typeof(FluidMoveBehavior), new PropertyMetadata(null)); + + public static readonly DependencyProperty InitialTagPathProperty = + DependencyProperty.Register("InitialTagPath", typeof(string), typeof(FluidMoveBehavior), + new PropertyMetadata(string.Empty)); + + public static readonly DependencyProperty InitialTagProperty = + DependencyProperty.Register("InitialTag", typeof(TagType), typeof(FluidMoveBehavior), + new PropertyMetadata(TagType.Element)); + + private static readonly DependencyProperty OverlayProperty = + DependencyProperty.RegisterAttached("Overlay", typeof(object), typeof(FluidMoveBehavior), + new PropertyMetadata(null)); + + private static readonly Dictionary TransitionStoryboardDictionary = + new Dictionary(); + + public Duration Duration + { + get => + (Duration) GetValue(DurationProperty); + set => SetValue(DurationProperty, value); + } + + public IEasingFunction EaseX + { + get => + (IEasingFunction) GetValue(EaseXProperty); + set => SetValue(EaseXProperty, value); + } + + public IEasingFunction EaseY + { + get => + (IEasingFunction) GetValue(EaseYProperty); + set => SetValue(EaseYProperty, value); + } + + public bool FloatAbove + { + get => + (bool) GetValue(FloatAboveProperty); + set => SetValue(FloatAboveProperty, value); + } + + public TagType InitialTag + { + get => + (TagType) GetValue(InitialTagProperty); + set => SetValue(InitialTagProperty, value); + } + + public string InitialTagPath + { + get => + (string) GetValue(InitialTagPathProperty); + set => SetValue(InitialTagPathProperty, value); + } + + protected override bool ShouldSkipInitialLayout + { + get + { + if (!base.ShouldSkipInitialLayout) return InitialTag == TagType.DataContext; + return true; + } + } + + private static void AddTransform(FrameworkElement child, Transform transform) + { + if (!(child.RenderTransform is TransformGroup renderTransform)) + { + renderTransform = new TransformGroup + { + Children = {child.RenderTransform} + }; + child.RenderTransform = renderTransform; + SetHasTransformWrapper(child, true); + } + + renderTransform.Children.Add(transform); + } + + private Storyboard CreateTransitionStoryboard(FrameworkElement child, bool usingBeforeLoaded, + ref Rect layoutRect, ref Rect currentRect) + { + var duration = Duration; + var storyboard = new Storyboard + { + Duration = duration + }; + var num = !usingBeforeLoaded || Math.Abs(layoutRect.Width) < 0.001 + ? 1.0 + : currentRect.Width / layoutRect.Width; + var num2 = !usingBeforeLoaded || Math.Abs(layoutRect.Height) < 0.001 + ? 1.0 + : currentRect.Height / layoutRect.Height; + var num3 = currentRect.Left - layoutRect.Left; + var num4 = currentRect.Top - layoutRect.Top; + var group = new TransformGroup(); + var transform = new ScaleTransform + { + ScaleX = num, + ScaleY = num2 + }; + group.Children.Add(transform); + var transform2 = new TranslateTransform + { + X = num3, + Y = num4 + }; + group.Children.Add(transform2); + AddTransform(child, group); + var str = "(FrameworkElement.RenderTransform)."; + if (child.RenderTransform is TransformGroup renderTransform && GetHasTransformWrapper(child)) + { + object obj2 = str; + str = string.Concat(obj2, "(TransformGroup.Children)[", renderTransform.Children.Count - 1, + "]."); + } + + if (usingBeforeLoaded) + { + if (Math.Abs(num - 1.0) > 0.001) + { + var element = new DoubleAnimation + { + Duration = duration, + From = num, + To = 1.0 + }; + Storyboard.SetTarget(element, child); + Storyboard.SetTargetProperty(element, + new PropertyPath(str + "(TransformGroup.Children)[0].(ScaleTransform.ScaleX)")); + element.EasingFunction = EaseX; + storyboard.Children.Add(element); + } + + if (Math.Abs(num2 - 1.0) > 0.001) + { + var animation3 = new DoubleAnimation + { + Duration = duration, + From = num2, + To = 1.0 + }; + Storyboard.SetTarget(animation3, child); + Storyboard.SetTargetProperty(animation3, + new PropertyPath(str + "(TransformGroup.Children)[0].(ScaleTransform.ScaleY)")); + animation3.EasingFunction = EaseY; + storyboard.Children.Add(animation3); + } + } + + if (Math.Abs(num3) > 0.001) + { + var animation5 = new DoubleAnimation + { + Duration = duration, + From = num3, + To = 0.0 + }; + Storyboard.SetTarget(animation5, child); + Storyboard.SetTargetProperty(animation5, + new PropertyPath(str + "(TransformGroup.Children)[1].(TranslateTransform.X)")); + animation5.EasingFunction = EaseX; + storyboard.Children.Add(animation5); + } + + if (Math.Abs(num4) > 0.001) + { + var animation7 = new DoubleAnimation + { + Duration = duration, + From = num4, + To = 0.0 + }; + Storyboard.SetTarget(animation7, child); + Storyboard.SetTargetProperty(animation7, + new PropertyPath(str + "(TransformGroup.Children)[1].(TranslateTransform.Y)")); + animation7.EasingFunction = EaseY; + storyboard.Children.Add(animation7); + } + + return storyboard; + } + + protected override void EnsureTags(FrameworkElement child) + { + base.EnsureTags(child); + if (InitialTag == TagType.DataContext && + !(child.ReadLocalValue(InitialIdentityTagProperty) is BindingExpression)) + child.SetBinding(InitialIdentityTagProperty, new Binding(InitialTagPath)); + } + + private static bool GetHasTransformWrapper(DependencyObject obj) + { + return (bool) obj.GetValue(HasTransformWrapperProperty); + } + + private static object GetInitialIdentityTag(DependencyObject obj) + { + return obj.GetValue(InitialIdentityTagProperty); + } + + private static object GetOverlay(DependencyObject obj) + { + return obj.GetValue(OverlayProperty); + } + + private static Transform GetTransform(FrameworkElement child) + { + if (child.RenderTransform is TransformGroup renderTransform && renderTransform.Children.Count > 0) + return renderTransform.Children[renderTransform.Children.Count - 1]; + return new TranslateTransform(); + } + + private static bool IsClose(double a, double b) + { + return Math.Abs(a - b) < 1E-07; + } + + private static bool IsEmptyRect(Rect rect) + { + if (!rect.IsEmpty && !double.IsNaN(rect.Left)) return double.IsNaN(rect.Top); + return true; + } + + private static void RemoveTransform(FrameworkElement child) + { + if (child.RenderTransform is TransformGroup renderTransform) + { + if (GetHasTransformWrapper(child)) + { + child.RenderTransform = renderTransform.Children[0]; + SetHasTransformWrapper(child, false); + } + else + { + renderTransform.Children.RemoveAt(renderTransform.Children.Count - 1); + } + } + } + + private static void SetHasTransformWrapper(DependencyObject obj, bool value) + { + obj.SetValue(HasTransformWrapperProperty, value); + } + + private static void SetOverlay(DependencyObject obj, object value) + { + obj.SetValue(OverlayProperty, value); + } + + private static void TransferLocalValue(FrameworkElement element, DependencyProperty source, + DependencyProperty dest) + { + var obj2 = element.ReadLocalValue(source); + if (obj2 is BindingExpressionBase base2) + element.SetBinding(dest, base2.ParentBindingBase); + else if (obj2 == DependencyProperty.UnsetValue) + element.ClearValue(dest); + else + element.SetValue(dest, element.GetAnimationBaseValue(source)); + element.ClearValue(source); + } + + internal override void UpdateLayoutTransitionCore(FrameworkElement child, FrameworkElement root, + object tag, TagData newTagData) + { + Rect empty; + var flag = false; + var usingBeforeLoaded = false; + var initialIdentityTag = GetInitialIdentityTag(child); + var flag3 = TagDictionary.TryGetValue(tag, out var data); + if (flag3 && data.InitialTag != initialIdentityTag) + { + flag3 = false; + TagDictionary.Remove(tag); + } + + if (!flag3) + { + if (initialIdentityTag != null && TagDictionary.TryGetValue(initialIdentityTag, out var data2)) + { + empty = TranslateRect(data2.AppRect, root, newTagData.Parent); + flag = true; + usingBeforeLoaded = true; + } + else + { + empty = Rect.Empty; + } + + data = new TagData + { + ParentRect = Rect.Empty, + AppRect = Rect.Empty, + Parent = newTagData.Parent, + Child = child, + Timestamp = DateTime.Now, + InitialTag = initialIdentityTag + }; + TagDictionary.Add(tag, data); + } + else if (!Equals(data.Parent, VisualTreeHelper.GetParent(child))) + { + empty = TranslateRect(data.AppRect, root, newTagData.Parent); + flag = true; + } + else + { + empty = data.ParentRect; + } + + var originalChild = child; + if (!IsEmptyRect(empty) && !IsEmptyRect(newTagData.ParentRect) && + (!IsClose(empty.Left, newTagData.ParentRect.Left) || + !IsClose(empty.Top, newTagData.ParentRect.Top)) || !Equals(child, data.Child) && + TransitionStoryboardDictionary.ContainsKey(tag)) + { + var rect = empty; + var flag4 = false; + if (TransitionStoryboardDictionary.TryGetValue(tag, out var storyboard)) + { + var obj3 = GetOverlay(data.Child); + var adorner = (AdornerContainer) obj3; + flag4 = obj3 != null; + var element = data.Child; + if (obj3 != null) + { + if (adorner.Child is Canvas canvas) element = canvas.Children[0] as FrameworkElement; + } + + if (!usingBeforeLoaded) rect = GetTransform(element).TransformBounds(rect); + TransitionStoryboardDictionary.Remove(tag); + storyboard.Stop(); + RemoveTransform(element); + if (obj3 != null) + { + AdornerLayer.GetAdornerLayer(root).Remove(adorner); + TransferLocalValue(data.Child, CacheDuringOverlayProperty, + UIElement.RenderTransformProperty); + SetOverlay(data.Child, null); + } + } + + object overlay = null; + if (flag4 || flag && FloatAbove) + { + var canvas2 = new Canvas + { + Width = newTagData.ParentRect.Width, + Height = newTagData.ParentRect.Height, + IsHitTestVisible = false + }; + var rectangle = new Rectangle + { + Width = newTagData.ParentRect.Width, + Height = newTagData.ParentRect.Height, + IsHitTestVisible = false, + Fill = new VisualBrush(child) + }; + canvas2.Children.Add(rectangle); + var container2 = new AdornerContainer(child) + { + Child = canvas2 + }; + overlay = container2; + SetOverlay(originalChild, overlay); + AdornerLayer.GetAdornerLayer(root).Add(container2); + TransferLocalValue(child, UIElement.RenderTransformProperty, CacheDuringOverlayProperty); + child.RenderTransform = new TranslateTransform(-10000.0, -10000.0); + canvas2.RenderTransform = new TranslateTransform(10000.0, 10000.0); + child = rectangle; + } + + var parentRect = newTagData.ParentRect; + var transitionStoryboard = + CreateTransitionStoryboard(child, usingBeforeLoaded, ref parentRect, ref rect); + TransitionStoryboardDictionary.Add(tag, transitionStoryboard); + transitionStoryboard.Completed += delegate + { + if (TransitionStoryboardDictionary.TryGetValue(tag, out var storyboard1) && + Equals(storyboard1, transitionStoryboard)) + { + TransitionStoryboardDictionary.Remove(tag); + transitionStoryboard.Stop(); + RemoveTransform(child); + child.InvalidateMeasure(); + if (overlay != null) + { + AdornerLayer.GetAdornerLayer(root).Remove((AdornerContainer) overlay); + TransferLocalValue(originalChild, CacheDuringOverlayProperty, + UIElement.RenderTransformProperty); + SetOverlay(originalChild, null); + } + } + }; + transitionStoryboard.Begin(); + } + + data.ParentRect = newTagData.ParentRect; + data.AppRect = newTagData.AppRect; + data.Parent = newTagData.Parent; + data.Child = newTagData.Child; + data.Timestamp = newTagData.Timestamp; + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/FluidMoveBehaviorBase.cs b/HandyControl/Interactivity/FluidMoveBehaviorBase.cs new file mode 100644 index 00000000..723194f8 --- /dev/null +++ b/HandyControl/Interactivity/FluidMoveBehaviorBase.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Media; + +namespace HandyControl.Interactivity +{ + public abstract class FluidMoveBehaviorBase : Behavior + { + public static readonly DependencyProperty AppliesToProperty = DependencyProperty.Register("AppliesTo", + typeof(FluidMoveScope), typeof(FluidMoveBehaviorBase), new PropertyMetadata(FluidMoveScope.Self)); + + protected static readonly DependencyProperty IdentityTagProperty = + DependencyProperty.RegisterAttached("IdentityTag", typeof(object), typeof(FluidMoveBehaviorBase), + new PropertyMetadata(null)); + + public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register("IsActive", + typeof(bool), typeof(FluidMoveBehaviorBase), new PropertyMetadata(true)); + + private static DateTime LastPurgeTick = DateTime.MinValue; + + private static readonly TimeSpan MinTickDelta = TimeSpan.FromSeconds(0.5); + + private static DateTime NextToLastPurgeTick = DateTime.MinValue; + + internal static Dictionary TagDictionary = new Dictionary(); + + public static readonly DependencyProperty TagPathProperty = DependencyProperty.Register("TagPath", + typeof(string), typeof(FluidMoveBehaviorBase), new PropertyMetadata(string.Empty)); + + public static readonly DependencyProperty TagProperty = DependencyProperty.Register("Tag", + typeof(TagType), typeof(FluidMoveBehaviorBase), new PropertyMetadata(TagType.Element)); + + public FluidMoveScope AppliesTo + { + get => + (FluidMoveScope) GetValue(AppliesToProperty); + set => SetValue(AppliesToProperty, value); + } + + public bool IsActive + { + get => + (bool) GetValue(IsActiveProperty); + set => SetValue(IsActiveProperty, value); + } + + protected virtual bool ShouldSkipInitialLayout => + Tag == TagType.DataContext; + + public TagType Tag + { + get => + (TagType) GetValue(TagProperty); + set => SetValue(TagProperty, value); + } + + public string TagPath + { + get => + (string) GetValue(TagPathProperty); + set => SetValue(TagPathProperty, value); + } + + private void AssociatedObject_LayoutUpdated(object sender, EventArgs e) + { + if (IsActive) + { + if (DateTime.Now - LastPurgeTick >= MinTickDelta) + { + List list = null; + foreach (var pair in TagDictionary) + if (pair.Value.Timestamp < NextToLastPurgeTick) + { + if (list == null) list = new List(); + list.Add(pair.Key); + } + + if (list != null) + foreach (var obj2 in list) + TagDictionary.Remove(obj2); + NextToLastPurgeTick = LastPurgeTick; + LastPurgeTick = DateTime.Now; + } + + if (AppliesTo == FluidMoveScope.Self) + { + UpdateLayoutTransition(AssociatedObject); + } + else + { + if (AssociatedObject is Panel associatedObject) + foreach (FrameworkElement element in associatedObject.Children) + UpdateLayoutTransition(element); + } + } + } + + protected virtual void EnsureTags(FrameworkElement child) + { + if (Tag == TagType.DataContext && + !(child.ReadLocalValue(IdentityTagProperty) is BindingExpression)) + child.SetBinding(IdentityTagProperty, new Binding(TagPath)); + } + + protected static object GetIdentityTag(DependencyObject obj) + { + return obj.GetValue(IdentityTagProperty); + } + + private static FrameworkElement GetVisualRoot(FrameworkElement child) + { + while (true) + { + if (!(VisualTreeHelper.GetParent(child) is FrameworkElement parent)) return child; + if (AdornerLayer.GetAdornerLayer(parent) == null) return child; + child = parent; + } + } + + protected override void OnAttached() + { + base.OnAttached(); + AssociatedObject.LayoutUpdated += AssociatedObject_LayoutUpdated; + } + + protected override void OnDetaching() + { + base.OnDetaching(); + AssociatedObject.LayoutUpdated -= AssociatedObject_LayoutUpdated; + } + + protected static void SetIdentityTag(DependencyObject obj, object value) + { + obj.SetValue(IdentityTagProperty, value); + } + + internal static Rect TranslateRect(Rect rect, FrameworkElement from, FrameworkElement to) + { + if (from == null || to == null) return rect; + var point = new Point(rect.Left, rect.Top); + point = from.TransformToVisual(to).Transform(point); + return new Rect(point.X, point.Y, rect.Width, rect.Height); + } + + private void UpdateLayoutTransition(FrameworkElement child) + { + if (child.Visibility != Visibility.Collapsed && child.IsLoaded || !ShouldSkipInitialLayout) + { + var visualRoot = GetVisualRoot(child); + var newTagData = new TagData + { + Parent = VisualTreeHelper.GetParent(child) as FrameworkElement, + ParentRect = ExtendedVisualStateManager.GetLayoutRect(child), + Child = child, + Timestamp = DateTime.Now + }; + try + { + newTagData.AppRect = TranslateRect(newTagData.ParentRect, newTagData.Parent, visualRoot); + } + catch (ArgumentException) + { + if (ShouldSkipInitialLayout) return; + } + + EnsureTags(child); + var identityTag = GetIdentityTag(child) ?? child; + UpdateLayoutTransitionCore(child, visualRoot, identityTag, newTagData); + } + } + + internal abstract void UpdateLayoutTransitionCore(FrameworkElement child, FrameworkElement root, + object tag, TagData newTagData); + + + internal class TagData + { + public Rect AppRect { get; set; } + + public FrameworkElement Child { get; set; } + + public object InitialTag { get; set; } + + public FrameworkElement Parent { get; set; } + + public Rect ParentRect { get; set; } + + public DateTime Timestamp { get; set; } + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/FluidMoveScope.cs b/HandyControl/Interactivity/FluidMoveScope.cs new file mode 100644 index 00000000..75e6cc08 --- /dev/null +++ b/HandyControl/Interactivity/FluidMoveScope.cs @@ -0,0 +1,8 @@ +namespace HandyControl.Interactivity +{ + public enum FluidMoveScope + { + Self, + Children + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/IAttachedObject.cs b/HandyControl/Interactivity/IAttachedObject.cs new file mode 100644 index 00000000..27a3bcbf --- /dev/null +++ b/HandyControl/Interactivity/IAttachedObject.cs @@ -0,0 +1,12 @@ +using System.Windows; + +namespace HandyControl.Interactivity +{ + public interface IAttachedObject + { + void Attach(DependencyObject dependencyObject); + void Detach(); + + DependencyObject AssociatedObject { get; } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/Interaction.cs b/HandyControl/Interactivity/Interaction.cs new file mode 100644 index 00000000..564f1cd1 --- /dev/null +++ b/HandyControl/Interactivity/Interaction.cs @@ -0,0 +1,77 @@ +using System; +using System.Windows; + +namespace HandyControl.Interactivity +{ + public static class Interaction + { + private static readonly DependencyProperty TriggersProperty = + DependencyProperty.RegisterAttached("ShadowTriggers", typeof(TriggerCollection), typeof(Interaction), + new FrameworkPropertyMetadata(OnTriggersChanged)); + + private static readonly DependencyProperty BehaviorsProperty = + DependencyProperty.RegisterAttached("ShadowBehaviors", typeof(BehaviorCollection), typeof(Interaction), + new FrameworkPropertyMetadata(OnBehaviorsChanged)); + + internal static bool ShouldRunInDesignMode { get; set; } + + public static TriggerCollection GetTriggers(DependencyObject obj) + { + var triggerCollection = (TriggerCollection) obj.GetValue(TriggersProperty); + if (triggerCollection == null) + { + triggerCollection = new TriggerCollection(); + obj.SetValue(TriggersProperty, triggerCollection); + } + return triggerCollection; + } + + public static BehaviorCollection GetBehaviors(DependencyObject obj) + { + var behaviorCollection = (BehaviorCollection) obj.GetValue(BehaviorsProperty); + if (behaviorCollection == null) + { + behaviorCollection = new BehaviorCollection(); + obj.SetValue(BehaviorsProperty, behaviorCollection); + } + return behaviorCollection; + } + + private static void OnBehaviorsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) + { + var oldValue = (BehaviorCollection) args.OldValue; + var newValue = (BehaviorCollection) args.NewValue; + if (Equals(oldValue, newValue)) + return; + if (oldValue?.AssociatedObject != null) + oldValue.Detach(); + if (newValue == null || obj == null) + return; + if (newValue.AssociatedObject != null) + throw new InvalidOperationException(ExceptionStringTable + .CannotHostBehaviorCollectionMultipleTimesExceptionMessage); + newValue.Attach(obj); + } + + private static void OnTriggersChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) + { + var oldValue = args.OldValue as TriggerCollection; + var newValue = args.NewValue as TriggerCollection; + if (Equals(oldValue, newValue)) + return; + if (oldValue?.AssociatedObject != null) + oldValue.Detach(); + if (newValue == null || obj == null) + return; + if (newValue.AssociatedObject != null) + throw new InvalidOperationException(ExceptionStringTable + .CannotHostTriggerCollectionMultipleTimesExceptionMessage); + newValue.Attach(obj); + } + + internal static bool IsElementLoaded(FrameworkElement element) + { + return element.IsLoaded; + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/MouseDragElementBehavior.cs b/HandyControl/Interactivity/MouseDragElementBehavior.cs new file mode 100644 index 00000000..27167d5f --- /dev/null +++ b/HandyControl/Interactivity/MouseDragElementBehavior.cs @@ -0,0 +1,352 @@ +using System.Windows; +using System.Windows.Input; +using System.Windows.Media; + +namespace HandyControl.Interactivity +{ + public class MouseDragElementBehavior : Behavior + { + public static readonly DependencyProperty ConstrainToParentBoundsProperty = + DependencyProperty.Register("ConstrainToParentBounds", typeof(bool), typeof(MouseDragElementBehavior), + new PropertyMetadata(false, OnConstrainToParentBoundsChanged)); + + public static readonly DependencyProperty XProperty = DependencyProperty.Register("X", typeof(double), + typeof(MouseDragElementBehavior), new PropertyMetadata(1.0 / 0.0, OnXChanged)); + + public static readonly DependencyProperty YProperty = DependencyProperty.Register("Y", typeof(double), + typeof(MouseDragElementBehavior), new PropertyMetadata(1.0 / 0.0, OnYChanged)); + + // Fields + private Transform _cachedRenderTransform; + + private Point _relativePosition; + private bool _settingPosition; + + // Properties + + public bool ConstrainToParentBounds + { + get => + (bool) GetValue(ConstrainToParentBoundsProperty); + set => SetValue(ConstrainToParentBoundsProperty, value); + } + + private Rect ElementBounds + { + get + { + var layoutRect = ExtendedVisualStateManager.GetLayoutRect(AssociatedObject); + return new Rect(new Point(0.0, 0.0), new Size(layoutRect.Width, layoutRect.Height)); + } + } + + private FrameworkElement ParentElement => + AssociatedObject.Parent as FrameworkElement; + + private Transform RenderTransform + { + get + { + if (_cachedRenderTransform == null || + !ReferenceEquals(_cachedRenderTransform, AssociatedObject.RenderTransform)) + { + var transform = CloneTransform(AssociatedObject.RenderTransform); + RenderTransform = transform; + } + return _cachedRenderTransform; + } + set + { + if (!Equals(_cachedRenderTransform, value)) + { + _cachedRenderTransform = value; + AssociatedObject.RenderTransform = value; + } + } + } + + private UIElement RootElement + { + get + { + DependencyObject associatedObject = AssociatedObject; + for (var obj3 = associatedObject; obj3 != null; obj3 = VisualTreeHelper.GetParent(associatedObject)) + associatedObject = obj3; + return associatedObject as UIElement; + } + } + + public double X + { + get => + (double) GetValue(XProperty); + set => SetValue(XProperty, value); + } + + public double Y + { + get => + (double) GetValue(YProperty); + set => SetValue(YProperty, value); + } + + // Events + public event MouseEventHandler DragBegun; + + public event MouseEventHandler DragFinished; + + public event MouseEventHandler Dragging; + + // Methods + private void ApplyTranslation(double x, double y) + { + if (ParentElement != null) + { + var point = TransformAsVector(RootElement.TransformToVisual(ParentElement), x, y); + x = point.X; + y = point.Y; + if (ConstrainToParentBounds) + { + var parentElement = ParentElement; + var rect = new Rect(0.0, 0.0, parentElement.ActualWidth, parentElement.ActualHeight); + var transform2 = AssociatedObject.TransformToVisual(parentElement); + var elementBounds = ElementBounds; + var rect3 = transform2.TransformBounds(elementBounds); + rect3.X += x; + rect3.Y += y; + if (!RectContainsRect(rect, rect3)) + { + if (rect3.X < rect.Left) + { + var num = rect3.X - rect.Left; + x -= num; + } + else if (rect3.Right > rect.Right) + { + var num2 = rect3.Right - rect.Right; + x -= num2; + } + if (rect3.Y < rect.Top) + { + var num3 = rect3.Y - rect.Top; + y -= num3; + } + else if (rect3.Bottom > rect.Bottom) + { + var num4 = rect3.Bottom - rect.Bottom; + y -= num4; + } + } + } + ApplyTranslationTransform(x, y); + } + } + + internal void ApplyTranslationTransform(double x, double y) + { + var renderTransform = RenderTransform; + var transform2 = renderTransform as TranslateTransform; + if (transform2 == null) + { + var group = renderTransform as TransformGroup; + var transform3 = renderTransform as MatrixTransform; + if (group != null) + { + if (group.Children.Count > 0) + transform2 = group.Children[group.Children.Count - 1] as TranslateTransform; + if (transform2 == null) + { + transform2 = new TranslateTransform(); + group.Children.Add(transform2); + } + } + else + { + if (transform3 != null) + { + var matrix = transform3.Matrix; + matrix.OffsetX += x; + matrix.OffsetY += y; + var transform4 = new MatrixTransform + { + Matrix = matrix + }; + RenderTransform = transform4; + return; + } + var group2 = new TransformGroup(); + transform2 = new TranslateTransform(); + if (renderTransform != null) + group2.Children.Add(renderTransform); + group2.Children.Add(transform2); + RenderTransform = group2; + } + } + transform2.X += x; + transform2.Y += y; + } + + internal static Transform CloneTransform(Transform transform) + { + if (transform == null) + return null; + if (transform is ScaleTransform transform2) + return new ScaleTransform + { + CenterX = transform2.CenterX, + CenterY = transform2.CenterY, + ScaleX = transform2.ScaleX, + ScaleY = transform2.ScaleY + }; + if (transform is RotateTransform transform3) + return new RotateTransform + { + Angle = transform3.Angle, + CenterX = transform3.CenterX, + CenterY = transform3.CenterY + }; + if (transform is SkewTransform transform4) + return new SkewTransform + { + AngleX = transform4.AngleX, + AngleY = transform4.AngleY, + CenterX = transform4.CenterX, + CenterY = transform4.CenterY + }; + if (transform is TranslateTransform transform5) + return new TranslateTransform + { + X = transform5.X, + Y = transform5.Y + }; + if (transform is MatrixTransform transform6) + return new MatrixTransform {Matrix = transform6.Matrix}; + if (!(transform is TransformGroup group)) + return null; + var group2 = new TransformGroup(); + foreach (var transform12 in group.Children) + group2.Children.Add(CloneTransform(transform12)); + return group2; + } + + internal void EndDrag() + { + AssociatedObject.MouseMove -= OnMouseMove; + AssociatedObject.LostMouseCapture -= OnLostMouseCapture; + AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonUpEvent, + new MouseButtonEventHandler(OnMouseLeftButtonUp)); + } + + private static Point GetTransformOffset(GeneralTransform transform) + { + return transform.Transform(new Point(0.0, 0.0)); + } + + internal void HandleDrag(Point newPositionInElementCoordinates) + { + var x = newPositionInElementCoordinates.X - _relativePosition.X; + var y = newPositionInElementCoordinates.Y - _relativePosition.Y; + var point = TransformAsVector(AssociatedObject.TransformToVisual(RootElement), x, y); + _settingPosition = true; + ApplyTranslation(point.X, point.Y); + UpdatePosition(); + _settingPosition = false; + } + + protected override void OnAttached() + { + AssociatedObject.AddHandler(UIElement.MouseLeftButtonDownEvent, + new MouseButtonEventHandler(OnMouseLeftButtonDown), false); + } + + private static void OnConstrainToParentBoundsChanged(object sender, DependencyPropertyChangedEventArgs args) + { + var behavior = (MouseDragElementBehavior) sender; + behavior.UpdatePosition(new Point(behavior.X, behavior.Y)); + } + + protected override void OnDetaching() + { + AssociatedObject.RemoveHandler(UIElement.MouseLeftButtonDownEvent, + new MouseButtonEventHandler(OnMouseLeftButtonDown)); + } + + private void OnLostMouseCapture(object sender, MouseEventArgs e) + { + EndDrag(); + DragFinished?.Invoke(this, e); + } + + private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + StartDrag(e.GetPosition(AssociatedObject)); + DragBegun?.Invoke(this, e); + } + + private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + AssociatedObject.ReleaseMouseCapture(); + } + + private void OnMouseMove(object sender, MouseEventArgs e) + { + HandleDrag(e.GetPosition(AssociatedObject)); + Dragging?.Invoke(this, e); + } + + private static void OnXChanged(object sender, DependencyPropertyChangedEventArgs args) + { + var behavior = (MouseDragElementBehavior) sender; + behavior.UpdatePosition(new Point((double) args.NewValue, behavior.Y)); + } + + private static void OnYChanged(object sender, DependencyPropertyChangedEventArgs args) + { + var behavior = (MouseDragElementBehavior) sender; + behavior.UpdatePosition(new Point(behavior.X, (double) args.NewValue)); + } + + private static bool RectContainsRect(Rect rect1, Rect rect2) + { + if (rect1.IsEmpty || rect2.IsEmpty) + return false; + return rect1.X <= rect2.X && rect1.Y <= rect2.Y && rect1.X + rect1.Width >= rect2.X + rect2.Width && + rect1.Y + rect1.Height >= rect2.Y + rect2.Height; + } + + internal void StartDrag(Point positionInElementCoordinates) + { + _relativePosition = positionInElementCoordinates; + AssociatedObject.CaptureMouse(); + AssociatedObject.MouseMove += OnMouseMove; + AssociatedObject.LostMouseCapture += OnLostMouseCapture; + AssociatedObject.AddHandler(UIElement.MouseLeftButtonUpEvent, + new MouseButtonEventHandler(OnMouseLeftButtonUp), false); + } + + private static Point TransformAsVector(GeneralTransform transform, double x, double y) + { + var point = transform.Transform(new Point(0.0, 0.0)); + var point2 = transform.Transform(new Point(x, y)); + return new Point(point2.X - point.X, point2.Y - point.Y); + } + + private void UpdatePosition() + { + var transformOffset = GetTransformOffset(AssociatedObject.TransformToVisual(RootElement)); + X = transformOffset.X; + Y = transformOffset.Y; + } + + private void UpdatePosition(Point point) + { + if (!_settingPosition && AssociatedObject != null) + { + var transformOffset = GetTransformOffset(AssociatedObject.TransformToVisual(RootElement)); + var x = double.IsNaN(point.X) ? 0.0 : point.X - transformOffset.X; + var y = double.IsNaN(point.Y) ? 0.0 : point.Y - transformOffset.Y; + ApplyTranslation(x, y); + } + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/NameResolvedEventArgs.cs b/HandyControl/Interactivity/NameResolvedEventArgs.cs new file mode 100644 index 00000000..6d61d85a --- /dev/null +++ b/HandyControl/Interactivity/NameResolvedEventArgs.cs @@ -0,0 +1,17 @@ +using System; + +namespace HandyControl.Interactivity +{ + internal sealed class NameResolvedEventArgs : EventArgs + { + public NameResolvedEventArgs(object oldObject, object newObject) + { + OldObject = oldObject; + NewObject = newObject; + } + + public object NewObject { get; } + + public object OldObject { get; } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/NameResolver.cs b/HandyControl/Interactivity/NameResolver.cs new file mode 100644 index 00000000..517f59ac --- /dev/null +++ b/HandyControl/Interactivity/NameResolver.cs @@ -0,0 +1,124 @@ +using System; +using System.Windows; + +namespace HandyControl.Interactivity +{ + internal sealed class NameResolver + { + private string _name; + private FrameworkElement _nameScopeReferenceElement; + + private FrameworkElement ActualNameScopeReferenceElement + { + get + { + if (NameScopeReferenceElement != null && Interaction.IsElementLoaded(NameScopeReferenceElement)) + return GetActualNameScopeReference(NameScopeReferenceElement); + return null; + } + } + + private bool HasAttempedResolve { get; set; } + + public string Name + { + get => + _name; + set + { + var oldObject = Object; + _name = value; + UpdateObjectFromName(oldObject); + } + } + + public FrameworkElement NameScopeReferenceElement + { + get => + _nameScopeReferenceElement; + set + { + var nameScopeReferenceElement = NameScopeReferenceElement; + _nameScopeReferenceElement = value; + OnNameScopeReferenceElementChanged(nameScopeReferenceElement); + } + } + + public DependencyObject Object + { + get + { + if (string.IsNullOrEmpty(Name) && HasAttempedResolve) + return NameScopeReferenceElement; + return ResolvedObject; + } + } + + private bool PendingReferenceElementLoad { get; set; } + + private DependencyObject ResolvedObject { get; set; } + + public event EventHandler ResolvedElementChanged; + + private FrameworkElement GetActualNameScopeReference(FrameworkElement initialReferenceElement) + { + var element = initialReferenceElement; + if (!IsNameScope(initialReferenceElement)) + return element; + return initialReferenceElement.Parent as FrameworkElement ?? element; + } + + private bool IsNameScope(FrameworkElement frameworkElement) + { + return frameworkElement.Parent is FrameworkElement parent && parent.FindName(Name) != null; + } + + private void OnNameScopeReferenceElementChanged(FrameworkElement oldNameScopeReference) + { + if (PendingReferenceElementLoad) + { + oldNameScopeReference.Loaded -= OnNameScopeReferenceLoaded; + PendingReferenceElementLoad = false; + } + HasAttempedResolve = false; + UpdateObjectFromName(Object); + } + + private void OnNameScopeReferenceLoaded(object sender, RoutedEventArgs e) + { + PendingReferenceElementLoad = false; + NameScopeReferenceElement.Loaded -= OnNameScopeReferenceLoaded; + UpdateObjectFromName(Object); + } + + private void OnObjectChanged(DependencyObject oldTarget, DependencyObject newTarget) + { + ResolvedElementChanged?.Invoke(this, new NameResolvedEventArgs(oldTarget, newTarget)); + } + + private void UpdateObjectFromName(DependencyObject oldObject) + { + DependencyObject obj2 = null; + ResolvedObject = null; + if (NameScopeReferenceElement != null) + { + if (!Interaction.IsElementLoaded(NameScopeReferenceElement)) + { + NameScopeReferenceElement.Loaded += OnNameScopeReferenceLoaded; + PendingReferenceElementLoad = true; + return; + } + if (!string.IsNullOrEmpty(Name)) + { + var actualNameScopeReferenceElement = ActualNameScopeReferenceElement; + if (actualNameScopeReferenceElement != null) + obj2 = actualNameScopeReferenceElement.FindName(Name) as DependencyObject; + } + } + HasAttempedResolve = true; + ResolvedObject = obj2; + if (!Equals(oldObject, Object)) + OnObjectChanged(oldObject, Object); + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/PreviewInvokeEventArgs.cs b/HandyControl/Interactivity/PreviewInvokeEventArgs.cs new file mode 100644 index 00000000..7877fadd --- /dev/null +++ b/HandyControl/Interactivity/PreviewInvokeEventArgs.cs @@ -0,0 +1,9 @@ +using System; + +namespace HandyControl.Interactivity +{ + public class PreviewInvokeEventArgs : EventArgs + { + public bool Cancelling { get; set; } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/TagType.cs b/HandyControl/Interactivity/TagType.cs new file mode 100644 index 00000000..bbda88c9 --- /dev/null +++ b/HandyControl/Interactivity/TagType.cs @@ -0,0 +1,8 @@ +namespace HandyControl.Interactivity +{ + public enum TagType + { + Element, + DataContext + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/TransitionEffect.cs b/HandyControl/Interactivity/TransitionEffect.cs new file mode 100644 index 00000000..07deaaf2 --- /dev/null +++ b/HandyControl/Interactivity/TransitionEffect.cs @@ -0,0 +1,56 @@ +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Effects; + +namespace HandyControl.Interactivity +{ + public abstract class TransitionEffect : ShaderEffect + { + // Fields + public static readonly DependencyProperty InputProperty = + RegisterPixelShaderSamplerProperty("Input", typeof(TransitionEffect), 0, SamplingMode.NearestNeighbor); + + public static readonly DependencyProperty OldImageProperty = + RegisterPixelShaderSamplerProperty("OldImage", typeof(TransitionEffect), 1, SamplingMode.NearestNeighbor); + + public static readonly DependencyProperty ProgressProperty = DependencyProperty.Register("Progress", + typeof(double), typeof(TransitionEffect), new PropertyMetadata(0.0, PixelShaderConstantCallback(0))); + + // Methods + protected TransitionEffect() + { + UpdateShaderValue(InputProperty); + UpdateShaderValue(OldImageProperty); + UpdateShaderValue(ProgressProperty); + } + + // Properties + public Brush Input + { + get => + (Brush) GetValue(InputProperty); + set => SetValue(InputProperty, value); + } + + public Brush OldImage + { + get => + (Brush) GetValue(OldImageProperty); + set => SetValue(OldImageProperty, value); + } + + public double Progress + { + get => + (double) GetValue(ProgressProperty); + set => SetValue(ProgressProperty, value); + } + + public new TransitionEffect CloneCurrentValue() + { + return (TransitionEffect) base.CloneCurrentValue(); + } + + protected abstract TransitionEffect DeepCopy(); + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/TriggerAction.cs b/HandyControl/Interactivity/TriggerAction.cs new file mode 100644 index 00000000..c859d6a1 --- /dev/null +++ b/HandyControl/Interactivity/TriggerAction.cs @@ -0,0 +1,118 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Controls.Primitives; +using System.Windows.Media.Animation; + +namespace HandyControl.Interactivity +{ + [DefaultTrigger(typeof(UIElement), typeof(EventTrigger), "MouseLeftButtonDown")] + [DefaultTrigger(typeof(ButtonBase), typeof(EventTrigger), "Click")] + public abstract class TriggerAction : Animatable, IAttachedObject + { + public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.Register("IsEnabled", + typeof(bool), typeof(TriggerAction), new FrameworkPropertyMetadata(true)); + + private readonly Type _associatedObjectTypeConstraint; + + private DependencyObject _associatedObject; + private bool _isHosted; + + internal TriggerAction(Type associatedObjectTypeConstraint) + { + _associatedObjectTypeConstraint = associatedObjectTypeConstraint; + } + + protected DependencyObject AssociatedObject + { + get + { + ReadPreamble(); + return _associatedObject; + } + } + + protected virtual Type AssociatedObjectTypeConstraint + { + get + { + ReadPreamble(); + return _associatedObjectTypeConstraint; + } + } + + public bool IsEnabled + { + get => + (bool) GetValue(IsEnabledProperty); + set => SetValue(IsEnabledProperty, value); + } + + internal bool IsHosted + { + get + { + ReadPreamble(); + return _isHosted; + } + set + { + WritePreamble(); + _isHosted = value; + WritePostscript(); + } + } + + public void Attach(DependencyObject dependencyObject) + { + if (!Equals(dependencyObject, AssociatedObject)) + { + if (AssociatedObject != null) + throw new InvalidOperationException(ExceptionStringTable + .CannotHostTriggerActionMultipleTimesExceptionMessage); + if (dependencyObject != null && + !AssociatedObjectTypeConstraint.IsInstanceOfType(dependencyObject)) + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, + ExceptionStringTable.TypeConstraintViolatedExceptionMessage, + new object[] + {GetType().Name, dependencyObject.GetType().Name, AssociatedObjectTypeConstraint.Name})); + WritePreamble(); + _associatedObject = dependencyObject; + WritePostscript(); + OnAttached(); + } + } + + public void Detach() + { + OnDetaching(); + WritePreamble(); + _associatedObject = null; + WritePostscript(); + } + + DependencyObject IAttachedObject.AssociatedObject => + AssociatedObject; + + internal void CallInvoke(object parameter) + { + if (IsEnabled) + Invoke(parameter); + } + + protected override Freezable CreateInstanceCore() + { + return (Freezable) Activator.CreateInstance(GetType()); + } + + protected abstract void Invoke(object parameter); + + protected virtual void OnAttached() + { + } + + protected virtual void OnDetaching() + { + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/TriggerActionCollection.cs b/HandyControl/Interactivity/TriggerActionCollection.cs new file mode 100644 index 00000000..dbcee6e3 --- /dev/null +++ b/HandyControl/Interactivity/TriggerActionCollection.cs @@ -0,0 +1,46 @@ +using System; +using System.Windows; + +namespace HandyControl.Interactivity +{ + public class TriggerActionCollection : AttachableCollection + { + internal TriggerActionCollection() + { + } + + protected override Freezable CreateInstanceCore() + { + return new TriggerActionCollection(); + } + + internal override void ItemAdded(TriggerAction item) + { + if (item.IsHosted) + throw new InvalidOperationException(ExceptionStringTable + .CannotHostTriggerActionMultipleTimesExceptionMessage); + if (AssociatedObject != null) + item.Attach(AssociatedObject); + item.IsHosted = true; + } + + internal override void ItemRemoved(TriggerAction item) + { + if (((IAttachedObject) item).AssociatedObject != null) + item.Detach(); + item.IsHosted = false; + } + + protected override void OnAttached() + { + foreach (var action in this) + action.Attach(AssociatedObject); + } + + protected override void OnDetaching() + { + foreach (var action in this) + action.Detach(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/TriggerBase.cs b/HandyControl/Interactivity/TriggerBase.cs new file mode 100644 index 00000000..be6cebc7 --- /dev/null +++ b/HandyControl/Interactivity/TriggerBase.cs @@ -0,0 +1,110 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Markup; +using System.Windows.Media.Animation; + +namespace HandyControl.Interactivity +{ + [ContentProperty("Actions")] + public abstract class TriggerBase : Animatable, IAttachedObject + { + public static readonly DependencyProperty ActionsProperty = ActionsPropertyKey.DependencyProperty; + + private static readonly DependencyPropertyKey ActionsPropertyKey = + DependencyProperty.RegisterReadOnly("Actions", typeof(TriggerActionCollection), typeof(TriggerBase), + new FrameworkPropertyMetadata()); + + private DependencyObject _associatedObject; + private readonly Type _associatedObjectTypeConstraint; + + internal TriggerBase(Type associatedObjectTypeConstraint) + { + _associatedObjectTypeConstraint = associatedObjectTypeConstraint; + var actions = new TriggerActionCollection(); + SetValue(ActionsPropertyKey, actions); + } + + public TriggerActionCollection Actions => + (TriggerActionCollection) GetValue(ActionsProperty); + + protected DependencyObject AssociatedObject + { + get + { + ReadPreamble(); + return _associatedObject; + } + } + + protected virtual Type AssociatedObjectTypeConstraint + { + get + { + ReadPreamble(); + return _associatedObjectTypeConstraint; + } + } + + public void Attach(DependencyObject dependencyObject) + { + if (!Equals(dependencyObject, AssociatedObject)) + { + if (AssociatedObject != null) + throw new InvalidOperationException(ExceptionStringTable + .CannotHostTriggerMultipleTimesExceptionMessage); + if (dependencyObject != null && + !AssociatedObjectTypeConstraint.IsInstanceOfType(dependencyObject)) + throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, + ExceptionStringTable.TypeConstraintViolatedExceptionMessage, + new object[] + {GetType().Name, dependencyObject.GetType().Name, AssociatedObjectTypeConstraint.Name})); + WritePreamble(); + _associatedObject = dependencyObject; + WritePostscript(); + Actions.Attach(dependencyObject); + OnAttached(); + } + } + + public void Detach() + { + OnDetaching(); + WritePreamble(); + _associatedObject = null; + WritePostscript(); + Actions.Detach(); + } + + DependencyObject IAttachedObject.AssociatedObject => + AssociatedObject; + + public event EventHandler PreviewInvoke; + + protected override Freezable CreateInstanceCore() + { + return (Freezable) Activator.CreateInstance(GetType()); + } + + protected void InvokeActions(object parameter) + { + if (PreviewInvoke != null) + { + var e = new PreviewInvokeEventArgs(); + PreviewInvoke(this, e); + if (e.Cancelling) + return; + } + foreach (var action in Actions) + action.CallInvoke(parameter); + } + + protected virtual void OnAttached() + { + } + + protected virtual void OnDetaching() + { + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/TriggerCollection.cs b/HandyControl/Interactivity/TriggerCollection.cs new file mode 100644 index 00000000..ce0a4c4d --- /dev/null +++ b/HandyControl/Interactivity/TriggerCollection.cs @@ -0,0 +1,42 @@ +using System.Windows; + +namespace HandyControl.Interactivity +{ + public sealed class TriggerCollection : AttachableCollection + { + internal TriggerCollection() + { + } + + protected override void OnAttached() + { + foreach (var triggerBase in this) + triggerBase.Attach(AssociatedObject); + } + + protected override void OnDetaching() + { + foreach (var triggerBase in this) + triggerBase.Detach(); + } + + internal override void ItemAdded(TriggerBase item) + { + if (AssociatedObject == null) + return; + item.Attach(AssociatedObject); + } + + internal override void ItemRemoved(TriggerBase item) + { + if (((IAttachedObject) item).AssociatedObject == null) + return; + item.Detach(); + } + + protected override Freezable CreateInstanceCore() + { + return new TriggerCollection(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Interactivity/TypeConstraintAttribute.cs b/HandyControl/Interactivity/TypeConstraintAttribute.cs new file mode 100644 index 00000000..edf0d49e --- /dev/null +++ b/HandyControl/Interactivity/TypeConstraintAttribute.cs @@ -0,0 +1,15 @@ +using System; + +namespace HandyControl.Interactivity +{ + [AttributeUsage(AttributeTargets.Class, Inherited = false)] + public sealed class TypeConstraintAttribute : Attribute + { + public TypeConstraintAttribute(Type constraint) + { + Constraint = constraint; + } + + public Type Constraint { get; } + } +} \ No newline at end of file diff --git a/HandyControl/Resources/colorPickerBack.png b/HandyControl/Resources/colorPickerBack.png new file mode 100644 index 0000000000000000000000000000000000000000..770865e9547abe10771b6b18121fed173f904860 GIT binary patch literal 34435 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 wc$R + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Basic/Brushes.xaml b/HandyControl/Themes/Basic/Brushes.xaml new file mode 100644 index 00000000..21ca0b88 --- /dev/null +++ b/HandyControl/Themes/Basic/Brushes.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Basic/Colors.xaml b/HandyControl/Themes/Basic/Colors.xaml new file mode 100644 index 00000000..9e595f9c --- /dev/null +++ b/HandyControl/Themes/Basic/Colors.xaml @@ -0,0 +1,35 @@ + + + #2196F3 + #1976D2 + #81d4fa + + #d9534f + #d43f3a + + #f0ad4e + #eea236 + + #5bc0de + #46b8da + + #5cb85c + #4cae4c + + #212121 + #757575 + #bdbdbd + White + + #e0e0e0 + #eeeeee + White + + White + #f4f4f4 + + #ff5722 + #d43f3a + + \ No newline at end of file diff --git a/HandyControl/Themes/Basic/Converters.xaml b/HandyControl/Themes/Basic/Converters.xaml new file mode 100644 index 00000000..59d88ec2 --- /dev/null +++ b/HandyControl/Themes/Basic/Converters.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Basic/Effects.xaml b/HandyControl/Themes/Basic/Effects.xaml new file mode 100644 index 00000000..874da734 --- /dev/null +++ b/HandyControl/Themes/Basic/Effects.xaml @@ -0,0 +1,13 @@ + + + #AA000000 + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Basic/Fonts.xaml b/HandyControl/Themes/Basic/Fonts.xaml new file mode 100644 index 00000000..f11e3b48 --- /dev/null +++ b/HandyControl/Themes/Basic/Fonts.xaml @@ -0,0 +1,10 @@ + + + 24 + 20 + 16 + 12 + + \ No newline at end of file diff --git a/HandyControl/Themes/Basic/Geometries.xaml b/HandyControl/Themes/Basic/Geometries.xaml new file mode 100644 index 00000000..621c8d07 --- /dev/null +++ b/HandyControl/Themes/Basic/Geometries.xaml @@ -0,0 +1,23 @@ + + + M377.897477 116.291025l265.298718 0 0 47.459125-265.298718 0 0-47.459125ZM888.317416 116.291025 748.229288 116.291025l0 47.459125 123.54866 0c18.167626 0 32.894732 14.727106 32.894732 32.894732l0 92.92987L115.42936 289.574752l0-92.92987c0-18.167626 14.727106-32.894732 32.894732-32.894732l123.54866 0 0-47.459125L131.776437 116.291025c-35.244356 0-63.814389 28.571056-63.814389 63.815412l0.004093 713.966329c0 35.243333 28.571056 63.814389 63.814389 63.814389l756.540979 0c35.244356 0 63.814389-28.571056 63.814389-63.815412l-0.004093-713.966329C952.131805 144.861058 923.560749 116.291025 888.317416 116.291025zM904.67268 337.033877l0 540.498398c0 18.167626-14.727106 32.894732-32.894732 32.894732L148.324092 910.427007c-18.167626 0-32.894732-14.727106-32.894732-32.894732L115.42936 337.033877 904.67268 337.033877zM300.659736 64.801927l47.459125 0 0 170.241292-47.459125 0 0-170.241292ZM672.97481 64.801927l47.450938 0 0 170.241292-47.450938 0 0-170.241292ZM358.384138 547.939214l0 231.021432 52.863464 0L411.247602 459.226591l-34.580198 0-2.651514 3.804835c-18.000819 25.896006-41.448959 48.758786-69.681285 67.955908l-3.886703 2.643327 0 53.742526 13.441772-8.069156C330.690097 569.215794 345.588103 558.712075 358.384138 547.939214zM618.031888 726.552576c-10.287791 6.395971-24.435678 8.069156-42.059902 4.948946-16.094309-2.842881-27.898712-18.373321-35.082666-46.159464l-2.261615-8.753781-48.312603 13.424375 1.68956 8.129534c6.270098 30.168514 18.347737 52.052967 35.884976 65.017855 17.120734 12.705979 35.542152 19.777364 54.731087 21.043255 3.358652 0.225138 6.655903 0.337707 9.897892 0.337707 14.859119 0 28.430857-2.374184 40.452211-7.072408 14.417029-5.641758 26.88866-13.338413 37.076162-22.86278 10.365566-9.723922 18.230051-22.143362 23.378552-36.920612 4.918246-14.170401 7.739636-28.296797 8.385373-42.025108 0.60685-13.658723-0.667228-26.650218-3.791531-38.619382l-0.008187-0.017397c-3.111-11.777796-8.133628-23.738773-14.946104-35.551363-7.353831-12.679372-19.028268-22.750211-34.710165-29.952586-15.037183-6.872854-30.927843-10.685876-47.24729-11.32752-11.414505-0.415482-23.487027 1.386646-36.04462 5.434017 3.813022-14.568486 7.648557-31.884681 11.465673-51.775637l113.87693 0 0-49.357449L535.645647 464.492778l-1.495122 7.028404c-24.621928 115.77116-31.589954 145.497585-33.102473 150.039235l-3.432334 10.338958 45.829943 5.391036 3.054716-4.20292c8.146931-11.214951 18.247448-18.139996 30.879746-21.164011 13.004799-3.137607 24.743707-3.180588 34.879018-0.129966 9.857981 2.95545 17.822756 8.771178 24.327202 17.758284 6.512633 9.03111 10.084143 20.392401 10.617311 33.757421 0.519864 13.849067-1.603598 26.442477-6.314102 37.440476C636.315153 711.429431 628.843637 719.871089 618.031888 726.552576z + M512 620.544l253.3376 253.3376a76.6976 76.6976 0 1 0 108.544-108.544L620.6464 512l253.2352-253.3376a76.6976 76.6976 0 1 0-108.544-108.544L512 403.3536 258.6624 150.1184a76.6976 76.6976 0 1 0-108.544 108.544L403.3536 512 150.1184 765.3376a76.6976 76.6976 0 1 0 108.544 108.544L512 620.6464z + M7.4038057,6.4038057 C7.1478834,6.4038057 6.8919611,6.5014372 6.6966991,6.6966991 6.3061748,7.0872235 6.3061748,7.7203884 6.6966991,8.1109123 L10.585787,12 6.6966991,15.889088 C6.3061748,16.279612 6.3061748,16.912777 6.6966991,17.303301 7.0872235,17.693825 7.7203879,17.693825 8.1109123,17.303301 L12,13.414213 15.889088,17.303301 C16.279612,17.693825 16.912777,17.693825 17.303301,17.303301 17.693825,16.912777 17.693825,16.279612 17.303301,15.889088 L13.414213,12 17.303301,8.1109123 C17.693825,7.7203884 17.693825,7.0872235 17.303301,6.6966991 16.912777,6.3061748 16.279612,6.3061748 15.889088,6.6966991 L12,10.585787 8.1109123,6.6966991 C7.9156504,6.5014372 7.6597281,6.4038057 7.4038057,6.4038057 z M12,0 C18.627417,0 24,5.3725829 24,12 24,18.627417 18.627417,24 12,24 5.3725829,24 0,18.627417 0,12 0,5.3725829 5.3725829,0 12,0 z + M0.353553533554077,0L3.71231077611446,3.35875718295574 7.07106781005859,0 7.42462134361267,0.353553295135498 7.07106781005859,0.707106828689575 4.06586414575577,3.71231056749821 7.42462134361267,7.07106781005859 7.07106781005859,7.42462134361267 6.7175145149231,7.07106781005859 3.71231067180634,4.06586402654648 0.707106828689575,7.07106781005859 0.353553533554077,7.42462110519409 0,7.07106781005859 3.35875730216503,3.71231067180634 0.353553533554077,0.707106828689575 0,0.353553533554077z + M512 801.6896L5.12 282.0096l58.1632-59.5968L512 682.3936l448.7168-460.0832 58.1632 59.5968-506.88 519.7824z + M50.000001,0 L100,51.262779 94.262627,57.141584 50.000001,11.767713 5.7373757,57.151686 0,51.27288 z + M498.506684 256.571517 425.359734 256.571517 425.359734 622.190635 720.166063 622.190635 720.166063 549.725206 498.506684 549.725206ZM512.714268 80.162738c-245.740844 0-445.181175 197.577914-445.181175 441.024505s199.439308 441.024505 445.181175 441.024505c245.738797 0 445.178105-197.577914 445.178105-441.024505S758.453065 80.162738 512.714268 80.162738zM512.714268 874.005824c-195.880249 0-356.145349-158.768003-356.145349-352.818581s160.2651-352.821651 356.145349-352.821651c195.877179 0 356.14228 158.77005 356.14228 352.821651S708.591447 874.005824 512.714268 874.005824z + M394.24 512L683.52 248.32c10.24-10.24 10.24-25.6 0-35.84-10.24-10.24-25.6-10.24-35.84 0l-307.2 279.04c-5.12 5.12-7.68 12.8-7.68 20.48 0 7.68 2.56 15.36 7.68 20.48l307.2 279.04c10.24 10.24 25.6 10.24 35.84 0 10.24-10.24 10.24-25.6 0-35.84L394.24 512z + M165.238899 508.178506C164.450369 456.442676 171.767779 404.485842 187.244542 353.863631 266.138632 95.812692 533.436225-51.353028 784.380224 25.368251 1035.324219 102.08953 1174.62994 373.566359 1095.735851 631.617298 1016.84176 889.66824 749.544168 1036.833959 498.600172 960.11268 424.134741 937.346313 357.837833 896.870788 303.973868 842.276436 291.310328 829.441176 291.396506 808.495802 304.166356 795.493625 316.936205 782.491452 337.554042 782.356136 350.217582 795.191397 396.640587 842.243889 453.73137 877.098901 517.975091 896.740177 734.436966 962.919213 965.320335 835.802028 1033.557737 612.607541 1101.795139 389.413057 981.467179 154.919792 765.005305 88.740755 548.543428 22.561718 317.660056 149.678904 249.422655 372.873388 235.597198 418.094418 229.294871 464.461039 230.4403 510.559881L311.240978 429.759202C323.584094 417.416085 343.892513 417.712357 356.601099 430.420946 369.309686 443.129535 369.605959 463.437952 357.262842 475.781068L230.778782 602.265128C229.273826 605.769679 227.098877 609.041396 224.251145 611.889128 211.908029 624.232245 191.59961 623.935974 178.891024 611.227385L40.825428 473.16179C28.11684 460.453206 27.820569 440.144784 40.163685 427.801668 52.506801 415.458551 72.815218 415.754827 85.523806 428.463411L165.238899 508.178506Z + M750.3 198.7C598 46.4 351.1 46.4 198.7 198.7s-152.3 399.2 0 551.5C345.1 896.6 578.8 902.3 732 767.3l172.1 172.1 35.4-35.4-172.1-171.9c135-153.2 129.3-387-17.1-533.4z m39.3 403.8c-17.1 42.1-42.2 80-74.7 112.4-32.5 32.5-70.3 57.6-112.4 74.7-40.7 16.5-83.8 24.9-128 24.9s-87.2-8.4-128-24.9c-42.1-17.1-80-42.2-112.4-74.7s-57.6-70.3-74.7-112.4c-16.5-40.7-24.9-83.8-24.9-128s8.4-87.2 24.9-128c17.1-42.1 42.2-80 74.7-112.4s70.3-57.6 112.4-74.7c40.7-16.5 83.8-24.9 128-24.9s87.2 8.4 128 24.9c42.1 17.1 80 42.2 112.4 74.7 32.5 32.5 57.6 70.3 74.7 112.4 16.5 40.7 24.9 83.8 24.9 128s-8.4 87.3-24.9 128zM671 502H496v175h-50V502H271v-50h175V277h50v175h175v50z + M750.3 198.7C598 46.4 351.1 46.4 198.7 198.7s-152.3 399.2 0 551.5C345.1 896.6 578.8 902.3 732 767.3l172.1 172.1 35.4-35.4-172.1-171.9c135-153.2 129.3-387-17.1-533.4z m39.3 403.8c-17.1 42.1-42.2 80-74.7 112.4-32.5 32.5-70.3 57.6-112.4 74.7-40.7 16.5-83.8 24.9-128 24.9s-87.2-8.4-128-24.9c-42.1-17.1-80-42.2-112.4-74.7s-57.6-70.3-74.7-112.4c-16.5-40.7-24.9-83.8-24.9-128s8.4-87.2 24.9-128c17.1-42.1 42.2-80 74.7-112.4s70.3-57.6 112.4-74.7c40.7-16.5 83.8-24.9 128-24.9s87.2 8.4 128 24.9c42.1 17.1 80 42.2 112.4 74.7 32.5 32.5 57.6 70.3 74.7 112.4 16.5 40.7 24.9 83.8 24.9 128s-8.4 87.3-24.9 128zM671 502H271v-50h400v50z + M868.100096 742.600704l-0.049152 0L216.246272 742.600704l-0.049152 0c-13.98784 0-25.327616 11.33568-25.327616 25.352192 0 13.993984 11.360256 25.327616 25.327616 25.327616l0.049152 0 651.804672 0 0.049152 0c13.996032 0 25.33376-11.333632 25.33376-25.327616C893.431808 753.936384 882.096128 742.600704 868.100096 742.600704L868.100096 742.600704 868.100096 742.600704M523.15136 688.433152c4.728832 4.757504 11.237376 7.733248 18.47296 7.733248l0 0 0 0c7.231488 0 13.740032-2.951168 18.42176-7.757824l314.836992-314.83904c4.732928-4.704256 7.714816-11.214848 7.7312-18.395136 0-14.342144-11.712512-25.9584-25.972736-25.9584-7.213056-0.02048-13.694976 2.930688-18.376704 7.636992L567.578624 607.51872 567.578624 117.52448c-0.024576-0.026624-0.024576-0.026624-0.024576-0.0512 0.024576-14.344192-11.59168-25.980928-25.901056-25.980928-14.348288 0-25.960448 11.634688-25.960448 25.980928 0 0 0 0.024576 0.024576 0.0512l0 489.967616L245.055488 336.826368c-4.683776-4.704256-11.2128-7.6288-18.374656-7.60832-14.342144-0.02048-25.954304 11.614208-26.005504 25.9072 0 7.20896 2.951168 13.768704 7.684096 18.423808L523.15136 688.433152 523.15136 688.433152 523.15136 688.433152M523.15136 688.433152 523.15136 688.433152z + M608 320C625.664 320 640 305.696 640 288L640 160C640 142.304 625.664 128 608 128 590.336 128 576 142.304 576 160L576 288C576 305.696 590.336 320 608 320L608 320ZM960 896C960 931.328 931.328 960 896 960L128 960C92.672 960 64 931.328 64 896L64 128C64 92.672 92.672 64 128 64L192 64 192 384C192 419.328 220.672 448 256 448L768 448C803.328 448 832 419.328 832 384L832 64 896 64C931.328 64 960 92.672 960 128L960 896 960 896ZM256 64 768 64 768 352C768 369.696 753.664 384 736 384L288 384C270.336 384 256 369.696 256 352L256 64 256 64ZM896 0 128 0C57.312 0 0 57.312 0 128L0 896C0 966.688 57.312 1024 128 1024L896 1024C966.688 1024 1024 966.688 1024 896L1024 128C1024 57.312 966.688 0 896 0L896 0Z + M426.276571 574.829714l0 372.004571-389.705143-53.686857 0-318.317714 389.705143 0zM426.276571 150.308571l0 376.539429-389.705143 0 0-322.852571zM987.428571 574.829714l0 449.170286-518.290286-71.460571 0-377.709714 518.290286 0zM987.428571 73.142857l0 453.705143-518.290286 0 0-382.317714z + M942 82v320h-50V167.4l-274 274-35.4-35.4 274-274H622V82h320zM406 582.6l-274 274V622H82v320h320v-50H167.4l274-274-35.4-35.4zM132 132h302V82H82v352h50V132z m760 760H590v50h352V590h-50v302z + M94.186081,59.07 L100,59.07 100,100 59.07,100 59.07,94.186081 94.186081,94.186081 z M4.5814285,58.208999 L41.791,58.208999 41.791,95.418571 35.977005,95.418571 35.977005,68.139304 4.1163101,100 0,95.88369 31.860695,64.022995 4.5814285,64.022995 z M95.88369,0 L100,4.1163101 68.139304,35.977005 95.418571,35.977005 95.418571,41.791 58.208999,41.791 58.208999,4.5814285 64.022995,4.5814285 64.022995,31.860695 z M0,0 L40.929998,0 40.929998,5.8139191 5.8139191,5.8139191 5.8139191,40.929998 0,40.929998 z + M716.6003 213.800623c-138.851102-138.915088-364.020238-138.915088-502.935326 0s-138.915088 364.084225 0 502.935326a355.573996 355.573996 0 0 0 502.935326 0 355.573996 355.573996 0 0 0 0-502.935326z m-580.359212 580.359212a465.183184 465.183184 0 1 1 657.783099 0 465.183184 465.183184 0 0 1-657.783099 0z m868.939152 211.156054a63.474789 63.474789 0 0 1-89.837303 0l-89.773316-89.837303a63.410803 63.410803 0 1 1 89.773316-89.773316l89.837303 89.773316a63.346816 63.346816 0 0 1 0 89.837303z + M454.464 143.68l-253.44 253.44c-12.032 12.032-12.032 31.36 0 43.392 11.968 11.968 31.36 11.968 43.328 0l237.376-237.376 237.376 237.376c11.968 11.968 31.36 11.968 43.328 0 12.032-12.032 12.032-31.36 0-43.392l-253.44-253.44C491.264 126.016 478.016 124.032 454.464 143.68zM507.968 947.648l252.48-253.376c11.968-11.968 11.968-31.36 0-43.392-11.968-11.968-31.296-11.968-43.264 0l-236.48 237.376L244.16 650.88c-11.968-11.968-31.296-11.968-43.264 0-11.968 12.032-11.968 31.424 0 43.392l252.48 253.376C465.984 960 491.136 964.48 507.968 947.648z + + \ No newline at end of file diff --git a/HandyControl/Themes/Basic/Paths.xaml b/HandyControl/Themes/Basic/Paths.xaml new file mode 100644 index 00000000..583e967c --- /dev/null +++ b/HandyControl/Themes/Basic/Paths.xaml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Basic/Sizes.xaml b/HandyControl/Themes/Basic/Sizes.xaml new file mode 100644 index 00000000..03923c22 --- /dev/null +++ b/HandyControl/Themes/Basic/Sizes.xaml @@ -0,0 +1,8 @@ + + + 30 + 12,6 + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Base/BaseStyle.xaml b/HandyControl/Themes/Styles/Base/BaseStyle.xaml new file mode 100644 index 00000000..6f9dbba1 --- /dev/null +++ b/HandyControl/Themes/Styles/Base/BaseStyle.xaml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Base/ButtonBaseBaseStyle.xaml b/HandyControl/Themes/Styles/Base/ButtonBaseBaseStyle.xaml new file mode 100644 index 00000000..dfe6c714 --- /dev/null +++ b/HandyControl/Themes/Styles/Base/ButtonBaseBaseStyle.xaml @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Base/ButtonBaseStyle.xaml b/HandyControl/Themes/Styles/Base/ButtonBaseStyle.xaml new file mode 100644 index 00000000..741d136a --- /dev/null +++ b/HandyControl/Themes/Styles/Base/ButtonBaseStyle.xaml @@ -0,0 +1,90 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Base/TextBlockBaseStyle.xaml b/HandyControl/Themes/Styles/Base/TextBlockBaseStyle.xaml new file mode 100644 index 00000000..b9603458 --- /dev/null +++ b/HandyControl/Themes/Styles/Base/TextBlockBaseStyle.xaml @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Border.xaml b/HandyControl/Themes/Styles/Border.xaml new file mode 100644 index 00000000..2a744837 --- /dev/null +++ b/HandyControl/Themes/Styles/Border.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Button.xaml b/HandyControl/Themes/Styles/Button.xaml new file mode 100644 index 00000000..4e19ae31 --- /dev/null +++ b/HandyControl/Themes/Styles/Button.xaml @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Slider.xaml b/HandyControl/Themes/Styles/Slider.xaml new file mode 100644 index 00000000..0cdae6ec --- /dev/null +++ b/HandyControl/Themes/Styles/Slider.xaml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Style.xaml b/HandyControl/Themes/Styles/Style.xaml new file mode 100644 index 00000000..2973af17 --- /dev/null +++ b/HandyControl/Themes/Styles/Style.xaml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/TextBlock.xaml b/HandyControl/Themes/Styles/TextBlock.xaml new file mode 100644 index 00000000..0f2fcff3 --- /dev/null +++ b/HandyControl/Themes/Styles/TextBlock.xaml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/Styles/Window.xaml b/HandyControl/Themes/Styles/Window.xaml new file mode 100644 index 00000000..2a731b32 --- /dev/null +++ b/HandyControl/Themes/Styles/Window.xaml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/HandyControl/Themes/ThemesDefault.xaml b/HandyControl/Themes/ThemesDefault.xaml new file mode 100644 index 00000000..38537110 --- /dev/null +++ b/HandyControl/Themes/ThemesDefault.xaml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Tools/AnimationHelper.cs b/HandyControl/Tools/AnimationHelper.cs new file mode 100644 index 00000000..feeb9cce --- /dev/null +++ b/HandyControl/Tools/AnimationHelper.cs @@ -0,0 +1,40 @@ +using System; +using System.Windows; +using System.Windows.Media.Animation; + +namespace HandyControl.Tools +{ + /// + /// 包含一些常用的动画辅助方法 + /// + public class AnimationHelper + { + /// + /// 创建一个Thickness动画 + /// + /// + /// + /// + public static ThicknessAnimation CreateAnimation(Thickness thickness = default(Thickness), double milliseconds = 200) + { + return new ThicknessAnimation(thickness, new Duration(TimeSpan.FromMilliseconds(milliseconds))) + { + EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut } + }; + } + + /// + /// 创建一个Double动画 + /// + /// + /// + /// + public static DoubleAnimation CreateAnimation(double toValue, double milliseconds = 200) + { + return new DoubleAnimation(toValue, new Duration(TimeSpan.FromMilliseconds(milliseconds))) + { + EasingFunction = new PowerEase { EasingMode = EasingMode.EaseInOut } + }; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/Boolean2BooleanFeConverter.cs b/HandyControl/Tools/Converter/Boolean2BooleanFeConverter.cs new file mode 100644 index 00000000..d97a711b --- /dev/null +++ b/HandyControl/Tools/Converter/Boolean2BooleanFeConverter.cs @@ -0,0 +1,23 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace HandyControl.Tools.Converter +{ + public class Boolean2BooleanFeConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is bool boolValue) + { + return !boolValue; + } + return value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/Boolean2VisibilityFeConverter.cs b/HandyControl/Tools/Converter/Boolean2VisibilityFeConverter.cs new file mode 100644 index 00000000..decb6007 --- /dev/null +++ b/HandyControl/Tools/Converter/Boolean2VisibilityFeConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace HandyControl.Tools.Converter +{ + public class Boolean2VisibilityFeConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value != null && (bool)value ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/BooleanArr2VisibilityConverter.cs b/HandyControl/Tools/Converter/BooleanArr2VisibilityConverter.cs new file mode 100644 index 00000000..146d532e --- /dev/null +++ b/HandyControl/Tools/Converter/BooleanArr2VisibilityConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows; +using System.Windows.Data; + +namespace HandyControl.Tools.Converter +{ + public class BooleanArr2VisibilityConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values == null) + { + return Visibility.Collapsed; + } + + var arr = new List(); + foreach (var item in values) + { + if (item is bool boolValue) + { + arr.Add(boolValue); + } + else + { + return Visibility.Collapsed; + } + } + return arr.All(item => item) ? Visibility.Visible : Visibility.Collapsed; + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/Color2ChannelAConverter.cs b/HandyControl/Tools/Converter/Color2ChannelAConverter.cs new file mode 100644 index 00000000..84f2ed4f --- /dev/null +++ b/HandyControl/Tools/Converter/Color2ChannelAConverter.cs @@ -0,0 +1,39 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +namespace HandyControl.Tools.Converter +{ + public class Color2ChannelAConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is SolidColorBrush brush) + { + return brush.Color.A; + } + return 0; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string str) + { + try + { + if (ColorConverter.ConvertFromString(str) is Color color) + { + return new SolidColorBrush(color); + } + } + catch + { + return parameter; + } + return parameter; + } + return parameter; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/Color2HexStrConverter.cs b/HandyControl/Tools/Converter/Color2HexStrConverter.cs new file mode 100644 index 00000000..00a54b28 --- /dev/null +++ b/HandyControl/Tools/Converter/Color2HexStrConverter.cs @@ -0,0 +1,40 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +namespace HandyControl.Tools.Converter +{ + public class Color2HexStrConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is SolidColorBrush brush) + { + var color = brush.Color; + return color.A < 255 ? $"#{color.A:X2}{color.R:X2}{color.G:X2}{color.B:X2}" : $"#{color.R:X2}{color.G:X2}{color.B:X2}"; + } + return ""; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string str) + { + try + { + if (ColorConverter.ConvertFromString(str) is Color color) + { + return new SolidColorBrush(color); + } + } + catch + { + return new SolidColorBrush(default(Color)); + } + return new SolidColorBrush(default(Color)); + } + return new SolidColorBrush(default(Color)); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/IsNullConverter.cs b/HandyControl/Tools/Converter/IsNullConverter.cs new file mode 100644 index 00000000..cc24a835 --- /dev/null +++ b/HandyControl/Tools/Converter/IsNullConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace HandyControl.Tools.Converter +{ + public class IsNullConverter : IValueConverter + { + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value == null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new InvalidOperationException("IsNullConverter can only be used OneWay."); + } + } +} diff --git a/HandyControl/Tools/Converter/Long2FileSizeConverter.cs b/HandyControl/Tools/Converter/Long2FileSizeConverter.cs new file mode 100644 index 00000000..200969df --- /dev/null +++ b/HandyControl/Tools/Converter/Long2FileSizeConverter.cs @@ -0,0 +1,52 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace HandyControl.Tools.Converter +{ + public class Long2FileSizeConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) return "未知大小"; + if (value is long intValue) + { + if (intValue < 0) + { + return "未知大小"; + } + if (intValue < 1024) + { + return $"{intValue} B"; + } + if (intValue < 1048576) + { + return $"{intValue / 1024.0:0.00} KB"; + } + if (intValue < 1073741824) + { + return $"{intValue / 1048576.0:0.00} MB"; + } + if (intValue < 1099511627776) + { + return $"{intValue / 1073741824.0:0.00} GB"; + } + if (intValue < 1125899906842624) + { + return $"{intValue / 1099511627776.0:0.00} TB"; + } + if (intValue < 1152921504606847000) + { + return $"{intValue / 1125899906842624.0:0.00} PB"; + } + return "过大"; + } + return "未知"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/Object2BooleanConverter.cs b/HandyControl/Tools/Converter/Object2BooleanConverter.cs new file mode 100644 index 00000000..399b4685 --- /dev/null +++ b/HandyControl/Tools/Converter/Object2BooleanConverter.cs @@ -0,0 +1,16 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace HandyControl.Tools.Converter +{ + public class Object2BooleanConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => !(value is null); + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/Source2ImageConverter.cs b/HandyControl/Tools/Converter/Source2ImageConverter.cs new file mode 100644 index 00000000..15ace93f --- /dev/null +++ b/HandyControl/Tools/Converter/Source2ImageConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace HandyControl.Tools.Converter +{ + public class Source2ImageConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value != null ? new Uri(value.ToString(), UriKind.Relative) : null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value != null && (Visibility)value == Visibility.Visible; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/String2VisibilityConverter.cs b/HandyControl/Tools/Converter/String2VisibilityConverter.cs new file mode 100644 index 00000000..7d420cef --- /dev/null +++ b/HandyControl/Tools/Converter/String2VisibilityConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace HandyControl.Tools.Converter +{ + public class String2VisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return string.IsNullOrEmpty((string)value) ? Visibility.Visible : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value != null && (Visibility)value == Visibility.Visible; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/String2VisibilityFeConverter.cs b/HandyControl/Tools/Converter/String2VisibilityFeConverter.cs new file mode 100644 index 00000000..ec1fbfad --- /dev/null +++ b/HandyControl/Tools/Converter/String2VisibilityFeConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace HandyControl.Tools.Converter +{ + public class String2VisibilityFeConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return string.IsNullOrEmpty((string)value) ? Visibility.Collapsed : Visibility.Visible; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value != null && (Visibility)value == Visibility.Visible; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Converter/TreeViewItemMarginConverter.cs b/HandyControl/Tools/Converter/TreeViewItemMarginConverter.cs new file mode 100644 index 00000000..b8872602 --- /dev/null +++ b/HandyControl/Tools/Converter/TreeViewItemMarginConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; + +namespace HandyControl.Tools.Converter +{ + public class TreeViewItemMarginConverter: IValueConverter + { + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + var left = 0.0; + UIElement element = value as TreeViewItem; + while (element != null && element.GetType() != typeof(TreeView)) + { + element = (UIElement)VisualTreeHelper.GetParent(element); + if (element is TreeViewItem) + left += 19.0; + } + return new Thickness(left, 0, 0, 0); + } + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Extension/ColorExtension.cs b/HandyControl/Tools/Extension/ColorExtension.cs new file mode 100644 index 00000000..f4f2e0c5 --- /dev/null +++ b/HandyControl/Tools/Extension/ColorExtension.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.Windows.Media; + + +namespace HandyControl.Tools.Extension +{ + /// + /// 颜色扩展类 + /// + public static class ColorExtension + { + /// + /// 将颜色转换为10进制表示(rgb顺序颠倒) + /// + /// + /// + public static int ToInt32(this Color color) => color.R << 16 | color.G << 8 | color.B; + + /// + /// 将颜色转换为10进制表示(rgb顺序颠倒) + /// + /// + /// + public static int ToInt32Reverse(this Color color) => color.R | color.G << 8 | color.B << 18; + + internal static List ToList(this Color color) => new List + { + color.R, + color.G, + color.B + }; + } +} \ No newline at end of file diff --git a/HandyControl/Tools/Extension/UIElementExtension.cs b/HandyControl/Tools/Extension/UIElementExtension.cs new file mode 100644 index 00000000..81f22d93 --- /dev/null +++ b/HandyControl/Tools/Extension/UIElementExtension.cs @@ -0,0 +1,45 @@ +using System.Windows; + +namespace HandyControl.Tools.Extension +{ + // ReSharper disable once InconsistentNaming + public static class UIElementExtension + { + /// + /// 显示元素 + /// + /// + public static void Show(this UIElement element) + { + element.Visibility = Visibility.Visible; + } + + /// + /// 显示元素 + /// + /// + /// + public static void Show(this UIElement element, bool show) + { + element.Visibility = show ? Visibility.Visible : Visibility.Collapsed; + } + + /// + /// 不现实元素,但保留空间 + /// + /// + public static void Hide(this UIElement element) + { + element.Visibility = Visibility.Hidden; + } + + /// + /// 不显示元素,且不保留空间 + /// + /// + public static void Collapse(this UIElement element) + { + element.Visibility = Visibility.Collapsed; + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/RegularJudgment.cs b/HandyControl/Tools/RegularJudgment.cs new file mode 100644 index 00000000..d7e19a16 --- /dev/null +++ b/HandyControl/Tools/RegularJudgment.cs @@ -0,0 +1,96 @@ +using System.Text.RegularExpressions; +using HandyControl.Data.Enum; + +namespace HandyControl.Tools +{ + /// + /// 包含一些正则验证操作 + /// + public static class RegularJudgment + { + private static readonly RegularPatterns RegularPatterns = new RegularPatterns(); + + /// + /// 判断字符串格式是否符合某种要求 + /// + /// 需要判断的字符串 + /// 正则表达式 + /// + public static bool IsKindOf(this string str, string pattern) + { + return Regex.IsMatch(str, pattern); + } + + /// + /// 判断字符串是否满足指定的格式 + /// + /// 需要判断的字符串 + /// 指定格式的文本 + /// + public static bool IsKindOf(this string text, TextType textType) + { + if (textType == TextType.Common) return true; + return Regex.IsMatch(text, + RegularPatterns.GetValue(System.Enum.GetName(typeof(TextType), textType) + "Pattern").ToString()); + } + + /// + /// 判断字符串格式是否为电子邮件 + /// + /// 需要判断的Email字符串 + /// 方法返回布尔值 + public static bool IsEmail(this string email) + { + return Regex.IsMatch(email, RegularPatterns.MailPattern); + } + + /// + /// 判断字符串格式是否为指定类型的IP地址 + /// + /// 需要判断的IP字符串 + /// 指定的IP类型 + /// 方法返回布尔值 + public static bool IsIp(this string ip, IpType ipType) + { + switch (ipType) + { + case IpType.A: return Regex.IsMatch(ip, RegularPatterns.IpAPattern); + case IpType.B: return Regex.IsMatch(ip, RegularPatterns.IpBPattern); + case IpType.C: return Regex.IsMatch(ip, RegularPatterns.IpCPattern); + case IpType.D: return Regex.IsMatch(ip, RegularPatterns.IpDPattern); + case IpType.E: return Regex.IsMatch(ip, RegularPatterns.IpEPattern); + default: return false; + } + } + + /// + /// 判断字符串格式是否为IP地址 + /// + /// 需要判断的IP字符串 + /// 方法返回布尔值 + public static bool IsIp(this string ip) + { + return Regex.IsMatch(ip, RegularPatterns.IpPattern); + } + + /// + /// 判断字符串格式是否为单个汉字 + /// + /// 需要判断的单个汉字字符串 + /// 方法返回布尔值 + public static bool IsChinese(this string str) + { + return Regex.IsMatch(str, RegularPatterns.ChinesePattern); + } + + /// + /// 判断字符串格式是否为url + /// + /// 需要判断的url字符串 + /// 方法返回布尔值 + public static bool IsUrl(this string str) + { + return Regex.IsMatch(str, RegularPatterns.UrlPattern); + } + } +} \ No newline at end of file diff --git a/HandyControl/Tools/RegularPatterns.cs b/HandyControl/Tools/RegularPatterns.cs new file mode 100644 index 00000000..28216073 --- /dev/null +++ b/HandyControl/Tools/RegularPatterns.cs @@ -0,0 +1,152 @@ +namespace HandyControl.Tools +{ + /// + /// 包含一些正则验证所需要的字符串 + /// + public sealed class RegularPatterns + { + /// + /// + /// + /// + /// + public object GetValue(string propertyName) + { + return GetType().GetField(propertyName).GetValue(null); + } + + /// + /// 邮件正则匹配表达式 + /// + public const string MailPattern = @"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$"; + + /// + /// 手机号正则匹配表达式 + /// + public const string PhonePattern = @"^((13[0-9])|(15[^4,\d])|(18[0,5-9]))\d{8}$"; + + /// + /// IP正则匹配 + /// + public const string IpPattern = + @"^(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$"; + + /// + /// A类IP正则匹配 + /// + public const string IpAPattern = + @"^(12[0-6]|1[0-1]\d|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$"; + + /// + /// B类IP正则匹配 + /// + public const string IpBPattern = + @"^(19[0-1]|12[8-9]|1[3-8]\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$"; + + /// + /// C类IP正则匹配 + /// + public const string IpCPattern = + @"^(19[2-9]|22[0-3]|2[0-1]\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$"; + + /// + /// D类IP正则匹配 + /// + public const string IpDPattern = + @"^(22[4-9]|23\d\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$"; + + /// + /// E类IP正则匹配 + /// + public const string IpEPattern = + @"^(25[0-5]|24\d\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)\." + + @"(25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)$"; + + /// + /// 汉字正则匹配 + /// + public const string ChinesePattern = @"^[\u4e00-\u9fa5]$"; + + /// + /// Url正则匹配 + /// + public const string UrlPattern = @"((http|ftp|https)://)(([a-zA-Z0-9\._-]+\.[a-zA-Z]{2,6})|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\&%_\./-~-]*)?"; + + /// + /// 数字正则匹配 + /// + public const string NumberPattern = @"^\d$"; + + /// + /// 计算性质数字正则匹配 + /// + public const string DigitsPattern = @"^[+-]?([0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)([eE][+-]?[0-9]+)?$"; + + /// + /// 正整数正则匹配 + /// + public const string PIntPattern = @"^[1-9]\d*$"; + + /// + /// 负整数正则匹配 + /// + public const string NIntPattern = @"^-[1-9]\d*$ "; + + /// + /// 整数正则匹配 + /// + public const string IntPattern = @"^-?[1-9]\d*$"; + + /// + /// 非负整数正则匹配 + /// + public const string NnIntPattern = @"^[1-9]\d*|0$"; + + /// + /// 非正整数正则匹配 + /// + public const string NpIntPattern = @"^-[1-9]\d*|0$"; + + /// + /// 正浮点数正则匹配 + /// + public const string PDoublePattern = @"^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$"; + + /// + /// 负浮点数正则匹配 + /// + public const string NDoublePattern = @"^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$"; + + /// + /// 浮点数正则匹配 + /// + public const string DoublePattern = @"^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0)$"; + + /// + /// 非负浮点数正则匹配 + /// + public const string NnDoublePattern = @"^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0+|0$"; + + /// + /// 非正浮点数正则匹配 + /// + public const string NpDoublePattern = @"^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0+|0$"; + } +} \ No newline at end of file diff --git a/HandyControlDemo/App.xaml b/HandyControlDemo/App.xaml index e98a8fef..59e91ab3 100644 --- a/HandyControlDemo/App.xaml +++ b/HandyControlDemo/App.xaml @@ -1,9 +1,14 @@  - + + + + + + + diff --git a/HandyControlDemo/Data/Model/CellGateInfo.cs b/HandyControlDemo/Data/Model/CellGateInfo.cs new file mode 100644 index 00000000..9c9c8939 --- /dev/null +++ b/HandyControlDemo/Data/Model/CellGateInfo.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; + +namespace HandyControlDemo.Data.Model +{ + public class CellGateInfo + { + public int Id { get; set; } + + public string Name { get; set; } + + public int Parent { get; set; } + + public List InfoList { get; set; } + } +} \ No newline at end of file diff --git a/HandyControlDemo/Data/Model/CellGateRecord.cs b/HandyControlDemo/Data/Model/CellGateRecord.cs new file mode 100644 index 00000000..88a76de6 --- /dev/null +++ b/HandyControlDemo/Data/Model/CellGateRecord.cs @@ -0,0 +1,17 @@ +using System; + +namespace HandyControlDemo.Data.Model +{ + public class CellGateRecord + { + public string CellGateId { get; set; } + + public string PersonId { get; set; } + + public DateTime CreateTime { get; set; } + + public string Direction { get; set; } + + public string DeviceId { get; set; } + } +} \ No newline at end of file diff --git a/HandyControlDemo/HandyControlDemo.csproj b/HandyControlDemo/HandyControlDemo.csproj index c40e5a63..ee7f0f79 100644 --- a/HandyControlDemo/HandyControlDemo.csproj +++ b/HandyControlDemo/HandyControlDemo.csproj @@ -55,6 +55,8 @@ App.xaml Code + + MainWindow.xaml Code @@ -65,5 +67,11 @@ Code + + + {dc966e3d-bcff-4652-98cf-070e5494749a} + HandyControl + + \ No newline at end of file diff --git a/HandyControlDemo/MainWindow.xaml b/HandyControlDemo/MainWindow.xaml index 697d1a60..d5ad588b 100644 --- a/HandyControlDemo/MainWindow.xaml +++ b/HandyControlDemo/MainWindow.xaml @@ -3,10 +3,853 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:local="clr-namespace:HandyControlDemo" + xmlns:sys="clr-namespace:System;assembly=mscorlib" + xmlns:controls="clr-namespace:HandyControl.Controls;assembly=HandyControl" + xmlns:model="clr-namespace:HandyControlDemo.Data.Model" mc:Ignorable="d" - Title="MainWindow" Height="450" Width="800"> - - + UseLayoutRounding="True" + Style="{StaticResource WindowDefaultStyle}" + WindowState="Maximized" + Title="MainWindow" + Name="MyWindow" + d:DesignHeight="800" + d:DesignWidth="1400"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (ColorPicker) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/HandyControlDemo/MainWindow.xaml.cs b/HandyControlDemo/MainWindow.xaml.cs index 41d4c844..e929df74 100644 --- a/HandyControlDemo/MainWindow.xaml.cs +++ b/HandyControlDemo/MainWindow.xaml.cs @@ -1,13 +1,330 @@ -namespace HandyControlDemo +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using HandyControl.Data; +using HandyControl.Tools.Extension; +using HandyControlDemo.Data.Model; + +namespace HandyControlDemo { /// /// MainWindow.xaml 的交互逻辑 /// public partial class MainWindow { + public static readonly DependencyProperty FruitListProperty = DependencyProperty.Register( + "FruitList", typeof(ObservableCollection), typeof(MainWindow), new PropertyMetadata(default(ObservableCollection))); + + public ObservableCollection FruitList + { + get => (ObservableCollection)GetValue(FruitListProperty); + set => SetValue(FruitListProperty, value); + } + public MainWindow() { InitializeComponent(); + + foreach (var border in StackPanelMain.Children.OfType()) + { + border.Show(); + } + + var listZ = new List(); + + for (int i = 1; i <= 40; i++) + { + var itemC = new CellGateInfo(); + var listC = new List(); + itemC.Name = $"{i}幢"; + for (int j = 1; j <= 2; j++) + { + var itemF = new CellGateInfo(); + var listF = new List(); + itemF.Name = $"{itemC.Name}{j}单元"; + for (int k = 1; k <= 10; k++) + { + for (int l = 1; l <= 4; l++) + { + var itemR = new CellGateInfo + { + Name = $"{itemF.Name}{k}0{l}室" + }; + listF.Add(itemR); + } + } + itemF.InfoList = listF; + listC.Add(itemF); + } + itemC.InfoList = listC; + listZ.Add(itemC); + } + + //TreeViewMain.ItemsSource = listZ; + //ListBoxMain.ItemsSource = listZ; + //ButtonContextMenu.ItemsSource = listZ; + + var showRecords = new List + { + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-1), + CellGateId = "1幢1单元A单元门", + PersonId = "宋立才", + DeviceId = "刷卡", + Direction = "进" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-2), + CellGateId = "1幢1单元A单元门", + PersonId = "宋立才", + DeviceId = "刷卡", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-2), + CellGateId = "20幢2单元A单元门", + PersonId = "周弘益", + DeviceId = "人脸", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-3), + CellGateId = "20幢2单元A单元门", + PersonId = "周弘益", + DeviceId = "人脸", + Direction = "进" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-2), + CellGateId = "20幢2单元A单元门", + PersonId = "彭念文", + DeviceId = "RFID", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-3), + CellGateId = "20幢2单元A单元门", + PersonId = "彭念文", + DeviceId = "RFID", + Direction = "进" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "王跃", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "赵兴隆", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "王跃", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "赵兴隆", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "王跃", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "赵兴隆", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "王跃", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "赵兴隆", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "王跃", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "赵兴隆", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "王跃", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "赵兴隆", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "王跃", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "赵兴隆", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "王跃", + DeviceId = "指纹", + Direction = "出" + }, + new CellGateRecord + { + CreateTime = DateTime.Now.AddDays(-4), + CellGateId = "20幢2单元A单元门", + PersonId = "赵兴隆", + DeviceId = "指纹", + Direction = "出" + } + }; + //DataGridMain.ItemsSource = showRecords; + + FruitList = new ObservableCollection + { + new Fruit + { + Name = "苹果", + Id = 0 + }, + new Fruit + { + Name = "香蕉", + Id = 1 + } + }; + + } + + public class Fruit + { + public string Name { get; set; } + + public int Id { get; set; } + } + + //private void ThemesComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) + //{ + // if (sender is ComboBox comboBox && comboBox.SelectedItem is ComboBoxItem comboBoxItem && + // comboBoxItem.Tag is string tag) + // { + // var str = File.ReadAllText($"./Files/Themes/{tag}.json"); + // var jObject = JObject.Parse(str); + // foreach (var item in jObject.Properties()) + // { + // Resources.Remove(item.Name); + // Resources.Add(item.Name, item.Value.ToObject()); + // } + // } + //} + + //private void ButtonStepPrec_OnClick(object sender, RoutedEventArgs e) + //{ + // StepsExample.Prev(); + //} + + + //private void ButtonStepNext_OnClick(object sender, RoutedEventArgs e) + //{ + // StepsExample.Next(); + //} + + //private void ButtonImageBrowser_OnClick(object sender, RoutedEventArgs e) + //{ + // var window = new ImageBrowser(new Uri("pack://application:,,,/Resources/demo.png")); + // window.Show(); + //} + + private void ColorPicker_OnColorSelected(object sender, FunctionEventArgs e) + { + BlockColor.Text = $"{e.Info}-----{e.Info.ToInt32()}"; + PopupColor.IsOpen = false; + } + + private void ColorPicker_OnCanceled(object sender, EventArgs e) + { + PopupColor.IsOpen = false; + } + + private void ButtonColor_OnClick(object sender, RoutedEventArgs e) + { + PopupColor.IsOpen = true; + } + + private void ButtonColorPreset_OnClick(object sender, RoutedEventArgs e) + { + ColorPicker.SelectedBrush = Brushes.Blue; + PopupColor.IsOpen = true; } } -} +} \ No newline at end of file