From 92bb88ec144e34f78ec7df79a9759915d1e2f092 Mon Sep 17 00:00:00 2001 From: Hakoyu Date: Mon, 18 Dec 2023 22:53:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=20`VPet.Solution`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VPet.Solution/App.xaml | 26 +- VPet.Solution/App.xaml.cs | 58 +- VPet.Solution/Converters.xaml | 15 + .../Converters/BrushToMediaColorConverter.cs | 22 + .../Converters/CalculatorConverter.cs | 97 + VPet.Solution/Converters/EqualsConverter.cs | 28 + .../Converters/FalseToCollapsedConverter.cs | 20 + .../Converters/FalseToHiddenConverter.cs | 20 + VPet.Solution/Converters/MarginConverter.cs | 84 + VPet.Solution/Converters/MaxConverter.cs | 26 + .../Converters/MediaColorToBrushConverter.cs | 22 + .../Converters/NotEqualsConverter.cs | 28 + .../Converters/NullToFalseConverter.cs | 17 + .../Converters/RatioMarginConverter.cs | 91 + .../Converters/StringFormatConverter.cs | 51 + VPet.Solution/MainWindow.xaml | 10 - VPet.Solution/MainWindow.xaml.cs | 33 - VPet.Solution/NativeStyles.xaml | 12 + VPet.Solution/NativeStyles.xaml.cs | 11 + VPet.Solution/Properties/AssemblyInfo.cs | 13 +- VPet.Solution/Resources/NativeResources.cs | 72 + .../INotifyPropertyChangedX.cs | 12 + .../INotifyPropertyChangingX.cs | 12 + .../ObservableClass/ObservableClass.cs | 106 + .../PropertyChangedXEventArgs.cs | 33 + .../PropertyChangedXEventHandler.cs | 11 + .../PropertyChangingXEventArgs.cs | 35 + .../PropertyChangingXEventHandler.cs | 11 + .../ExecuteAsyncEventHandler.cs | 12 + .../ObservableCommand/ExecuteEventHandler.cs | 12 + .../NotifyReceivedEventHandler.cs | 11 + .../ObservableCommand/ObservableCommand.cs | 115 + .../ObservableCommand/ObservableCommandT.cs | 116 ++ VPet.Solution/Styles.xaml | 136 ++ VPet.Solution/Templates.xaml | 58 + VPet.Solution/Usings.cs | 6 + VPet.Solution/Utils/Expansions.cs | 312 +++ VPet.Solution/Utils/HashCode.cs | 66 + VPet.Solution/Utils/ObservableEnumFlags.cs | 72 + VPet.Solution/Utils/ObservablePoint.cs | 77 + VPet.Solution/Utils/ObservableRange.cs | 77 + VPet.Solution/Utils/ObservableRect.cs | 89 + VPet.Solution/Utils/Utils.cs | 79 + VPet.Solution/VPet.Solution.csproj | 56 +- VPet.Solution/ViewModels/MainWindowVM.cs | 22 + VPet.Solution/Views/MainWindow.xaml | 1849 +++++++++++++++++ VPet.Solution/Views/MainWindow.xaml.cs | 19 + 47 files changed, 4064 insertions(+), 96 deletions(-) create mode 100644 VPet.Solution/Converters.xaml create mode 100644 VPet.Solution/Converters/BrushToMediaColorConverter.cs create mode 100644 VPet.Solution/Converters/CalculatorConverter.cs create mode 100644 VPet.Solution/Converters/EqualsConverter.cs create mode 100644 VPet.Solution/Converters/FalseToCollapsedConverter.cs create mode 100644 VPet.Solution/Converters/FalseToHiddenConverter.cs create mode 100644 VPet.Solution/Converters/MarginConverter.cs create mode 100644 VPet.Solution/Converters/MaxConverter.cs create mode 100644 VPet.Solution/Converters/MediaColorToBrushConverter.cs create mode 100644 VPet.Solution/Converters/NotEqualsConverter.cs create mode 100644 VPet.Solution/Converters/NullToFalseConverter.cs create mode 100644 VPet.Solution/Converters/RatioMarginConverter.cs create mode 100644 VPet.Solution/Converters/StringFormatConverter.cs delete mode 100644 VPet.Solution/MainWindow.xaml delete mode 100644 VPet.Solution/MainWindow.xaml.cs create mode 100644 VPet.Solution/NativeStyles.xaml create mode 100644 VPet.Solution/NativeStyles.xaml.cs create mode 100644 VPet.Solution/Resources/NativeResources.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangedX.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangingX.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableClass/ObservableClass.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventArgs.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventHandler.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventArgs.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventHandler.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableCommand/ExecuteAsyncEventHandler.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableCommand/ExecuteEventHandler.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableCommand/NotifyReceivedEventHandler.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommand.cs create mode 100644 VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommandT.cs create mode 100644 VPet.Solution/Styles.xaml create mode 100644 VPet.Solution/Templates.xaml create mode 100644 VPet.Solution/Usings.cs create mode 100644 VPet.Solution/Utils/Expansions.cs create mode 100644 VPet.Solution/Utils/HashCode.cs create mode 100644 VPet.Solution/Utils/ObservableEnumFlags.cs create mode 100644 VPet.Solution/Utils/ObservablePoint.cs create mode 100644 VPet.Solution/Utils/ObservableRange.cs create mode 100644 VPet.Solution/Utils/ObservableRect.cs create mode 100644 VPet.Solution/Utils/Utils.cs create mode 100644 VPet.Solution/ViewModels/MainWindowVM.cs create mode 100644 VPet.Solution/Views/MainWindow.xaml create mode 100644 VPet.Solution/Views/MainWindow.xaml.cs diff --git a/VPet.Solution/App.xaml b/VPet.Solution/App.xaml index 085dea1..d4f4782 100644 --- a/VPet.Solution/App.xaml +++ b/VPet.Solution/App.xaml @@ -1,9 +1,17 @@ - - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/VPet.Solution/App.xaml.cs b/VPet.Solution/App.xaml.cs index a7005a0..bde1717 100644 --- a/VPet.Solution/App.xaml.cs +++ b/VPet.Solution/App.xaml.cs @@ -1,42 +1,36 @@ -using System; -using System.Collections.Generic; -using System.Configuration; -using System.Data; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Threading.Tasks; +using System.Diagnostics; using System.Windows; -using VPet_Simulator.Windows.Interface; -namespace VPet.Solution +namespace VPet.Solution; + +/// +/// App.xaml 的交互逻辑 +/// +public partial class App : Application { - /// - /// App.xaml 的交互逻辑 - /// - public partial class App : Application + public static bool IsDone { get; set; } = false; + + protected override void OnStartup(StartupEventArgs e) { - public static bool IsDone { get; set; } = false; - protected override void OnStartup(StartupEventArgs e) + if (e.Args != null && e.Args.Count() > 0) { - if (e.Args != null && e.Args.Count() > 0) + IsDone = true; + switch (e.Args[0].ToLower()) { - IsDone = true; - switch (e.Args[0].ToLower()) - { - case "removestarup": - var path = Environment.GetFolderPath(Environment.SpecialFolder.Startup) + @"\VPET_Simulator.lnk"; - if (File.Exists(path)) - { - File.Delete(path); - } - return; - case "launchsteam": - Process.Start("steam://rungameid/1920960"); - return; - } + case "removestarup": + var path = + Environment.GetFolderPath(Environment.SpecialFolder.Startup) + + @"\VPET_Simulator.lnk"; + if (File.Exists(path)) + { + File.Delete(path); + } + return; + case "launchsteam": + Process.Start("steam://rungameid/1920960"); + return; } - IsDone = false; } + IsDone = false; } } diff --git a/VPet.Solution/Converters.xaml b/VPet.Solution/Converters.xaml new file mode 100644 index 0000000..4c4b9ec --- /dev/null +++ b/VPet.Solution/Converters.xaml @@ -0,0 +1,15 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/VPet.Solution/Converters/BrushToMediaColorConverter.cs b/VPet.Solution/Converters/BrushToMediaColorConverter.cs new file mode 100644 index 0000000..773e095 --- /dev/null +++ b/VPet.Solution/Converters/BrushToMediaColorConverter.cs @@ -0,0 +1,22 @@ +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +namespace VPet.House.Converters; + +public class BrushToMediaColorConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not SolidColorBrush brush) + throw new ArgumentException("Not SolidColorBrush", nameof(value)); + return brush.Color; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not Color color) + throw new ArgumentException("Not media color", nameof(value)); + return new SolidColorBrush(color); + } +} diff --git a/VPet.Solution/Converters/CalculatorConverter.cs b/VPet.Solution/Converters/CalculatorConverter.cs new file mode 100644 index 0000000..0bfab22 --- /dev/null +++ b/VPet.Solution/Converters/CalculatorConverter.cs @@ -0,0 +1,97 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace VPet.House.Converters; + +/// +/// 计算器转换器 +/// 示例: +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// +/// // +/// +/// +/// +/// +/// +/// +/// +/// ]]> +/// +/// 绑定的数量不正确 +public class CalculatorConverter : IMultiValueConverter +{ + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (values.Any(i => i == DependencyProperty.UnsetValue)) + return 0.0; + if (values.Length == 1) + return values[0]; + double result = System.Convert.ToDouble(values[0]); + if (parameter is string operators) + { + if (operators.Length != values.Length - 1) + throw new Exception("Parameter error: operator must be one more than parameter"); + for (int i = 1; i < values.Length - 1; i++) + result = Operation(result, operators[i - 1], System.Convert.ToDouble(values[i])); + result = Operation(result, operators.Last(), System.Convert.ToDouble(values.Last())); + } + else + { + if (System.Convert.ToBoolean(values.Length & 1) is false) + throw new Exception("Parameter error: Incorrect quantity"); + bool isNumber = false; + char currentOperator = '0'; + for (int i = 1; i < values.Length - 1; i++) + { + if (isNumber is false) + { + currentOperator = ((string)values[i])[0]; + isNumber = true; + } + else + { + var value = System.Convert.ToDouble(values[i]); + result = Operation(result, currentOperator, value); + isNumber = false; + } + } + result = Operation(result, currentOperator, System.Convert.ToDouble(values.Last())); + } + return result; + } + + public static double Operation(double value1, char operatorChar, double value2) + { + return operatorChar switch + { + '+' => value1 + value2, + '-' => value1 - value2, + '*' => value1 * value2, + '/' => value1 / value2, + '%' => value1 % value2, + _ => throw new NotImplementedException(), + }; + } + + public object[] ConvertBack( + object value, + Type[] targetTypes, + object parameter, + CultureInfo culture + ) + { + throw new NotImplementedException(); + } +} diff --git a/VPet.Solution/Converters/EqualsConverter.cs b/VPet.Solution/Converters/EqualsConverter.cs new file mode 100644 index 0000000..84c7a92 --- /dev/null +++ b/VPet.Solution/Converters/EqualsConverter.cs @@ -0,0 +1,28 @@ +using System.Windows.Data; + +namespace VPet.House.Converters; + +public class EqualsConverter : IMultiValueConverter +{ + public object Convert( + object[] values, + Type targetType, + object parameter, + System.Globalization.CultureInfo culture + ) + { + if (values.Length != 2) + throw new NotImplementedException("Values length must be 2"); + return values[0].Equals(values[1]); + } + + public object[] ConvertBack( + object value, + Type[] targetTypes, + object parameter, + System.Globalization.CultureInfo culture + ) + { + throw new NotImplementedException(); + } +} diff --git a/VPet.Solution/Converters/FalseToCollapsedConverter.cs b/VPet.Solution/Converters/FalseToCollapsedConverter.cs new file mode 100644 index 0000000..9fbe94b --- /dev/null +++ b/VPet.Solution/Converters/FalseToCollapsedConverter.cs @@ -0,0 +1,20 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace VPet.House.Converters; + +public class FalseToCollapsedConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (bool.TryParse(value.ToString(), out var result) && result) + ? Visibility.Visible + : Visibility.Collapsed; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is Visibility visibility && visibility == Visibility.Collapsed; + } +} diff --git a/VPet.Solution/Converters/FalseToHiddenConverter.cs b/VPet.Solution/Converters/FalseToHiddenConverter.cs new file mode 100644 index 0000000..b0856df --- /dev/null +++ b/VPet.Solution/Converters/FalseToHiddenConverter.cs @@ -0,0 +1,20 @@ +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace VPet.House.Converters; + +public class FalseToHiddenConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return (bool.TryParse(value.ToString(), out var result) && result) + ? Visibility.Visible + : Visibility.Hidden; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is Visibility visibility && visibility == Visibility.Hidden; + } +} diff --git a/VPet.Solution/Converters/MarginConverter.cs b/VPet.Solution/Converters/MarginConverter.cs new file mode 100644 index 0000000..2575c79 --- /dev/null +++ b/VPet.Solution/Converters/MarginConverter.cs @@ -0,0 +1,84 @@ +using System.Windows; +using System.Windows.Data; + +namespace VPet.House.Converters; + +/// +/// 边距转换器 +/// 示例: +/// +/// +/// +/// +/// +/// +/// ]]> +/// +public class MarginConverter : IMultiValueConverter +{ + public object Convert( + object[] values, + Type targetType, + object parameter, + System.Globalization.CultureInfo culture + ) + { + if (values.Length == 0) + { + return new Thickness(); + } + else if (values.Length == 1) + { + return new Thickness() + { + Left = System.Convert.ToDouble(values[0]), + Top = default, + Right = default, + Bottom = default + }; + } + else if (values.Length == 2) + { + return new Thickness() + { + Left = System.Convert.ToDouble(values[0]), + Top = System.Convert.ToDouble(values[1]), + Right = default, + Bottom = default + }; + } + else if (values.Length == 3) + { + return new Thickness() + { + Left = System.Convert.ToDouble(values[0]), + Top = System.Convert.ToDouble(values[1]), + Right = System.Convert.ToDouble(values[2]), + Bottom = default + }; + } + else if (values.Length == 4) + { + return new Thickness() + { + Left = System.Convert.ToDouble(values[0]), + Top = System.Convert.ToDouble(values[1]), + Right = System.Convert.ToDouble(values[2]), + Bottom = System.Convert.ToDouble(values[3]) + }; + } + else + throw new NotImplementedException(); + } + + public object[] ConvertBack( + object value, + Type[] targetTypes, + object parameter, + System.Globalization.CultureInfo culture + ) + { + throw new NotImplementedException(); + } +} diff --git a/VPet.Solution/Converters/MaxConverter.cs b/VPet.Solution/Converters/MaxConverter.cs new file mode 100644 index 0000000..3880126 --- /dev/null +++ b/VPet.Solution/Converters/MaxConverter.cs @@ -0,0 +1,26 @@ +using System.Windows.Data; + +namespace VPet.House.Converters; + +public class MaxConverter : IMultiValueConverter +{ + public object Convert( + object[] values, + Type targetType, + object parameter, + System.Globalization.CultureInfo culture + ) + { + return values.Max(i => (double)i); + } + + public object[] ConvertBack( + object value, + Type[] targetTypes, + object parameter, + System.Globalization.CultureInfo culture + ) + { + throw new NotImplementedException(); + } +} diff --git a/VPet.Solution/Converters/MediaColorToBrushConverter.cs b/VPet.Solution/Converters/MediaColorToBrushConverter.cs new file mode 100644 index 0000000..e513ca1 --- /dev/null +++ b/VPet.Solution/Converters/MediaColorToBrushConverter.cs @@ -0,0 +1,22 @@ +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +namespace VPet.House.Converters; + +public class MediaColorToBrushConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not Color color) + throw new ArgumentException("Not media color", nameof(value)); + return new SolidColorBrush(color); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is not SolidColorBrush brush) + throw new ArgumentException("Not SolidColorBrush", nameof(value)); + return brush.Color; + } +} diff --git a/VPet.Solution/Converters/NotEqualsConverter.cs b/VPet.Solution/Converters/NotEqualsConverter.cs new file mode 100644 index 0000000..0559d8f --- /dev/null +++ b/VPet.Solution/Converters/NotEqualsConverter.cs @@ -0,0 +1,28 @@ +using System.Windows.Data; + +namespace VPet.House.Converters; + +public class NotEqualsConverter : IMultiValueConverter +{ + public object Convert( + object[] values, + Type targetType, + object parameter, + System.Globalization.CultureInfo culture + ) + { + if (values.Length != 2) + throw new NotImplementedException("Values length must be 2"); + return !values[0].Equals(values[1]); + } + + public object[] ConvertBack( + object value, + Type[] targetTypes, + object parameter, + System.Globalization.CultureInfo culture + ) + { + throw new NotImplementedException(); + } +} diff --git a/VPet.Solution/Converters/NullToFalseConverter.cs b/VPet.Solution/Converters/NullToFalseConverter.cs new file mode 100644 index 0000000..909721c --- /dev/null +++ b/VPet.Solution/Converters/NullToFalseConverter.cs @@ -0,0 +1,17 @@ +using System.Globalization; +using System.Windows.Data; + +namespace VPet.House.Converters; + +public class NullToFalseConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value is not null; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/VPet.Solution/Converters/RatioMarginConverter.cs b/VPet.Solution/Converters/RatioMarginConverter.cs new file mode 100644 index 0000000..5e31dac --- /dev/null +++ b/VPet.Solution/Converters/RatioMarginConverter.cs @@ -0,0 +1,91 @@ +using System.Windows; +using System.Windows.Data; + +namespace VPet.House.Converters; + +/// +/// 边距转换器 +/// 示例: +/// +/// +/// +/// +/// +/// +/// ]]> +/// +public class RatioMarginConverter : IMultiValueConverter +{ + public object Convert( + object[] values, + Type targetType, + object parameter, + System.Globalization.CultureInfo culture + ) + { + if (values.Any(i => i == DependencyProperty.UnsetValue)) + return new Thickness(); + if (values.Length == 0) + { + return new Thickness(); + } + else if (values.Length == 1) + { + return new Thickness(); + } + var ratio = System.Convert.ToDouble(values[0]); + if (values.Length == 2) + { + return new Thickness() + { + Left = System.Convert.ToDouble(values[1]) * ratio, + Top = default, + Right = default, + Bottom = default + }; + } + else if (values.Length == 3) + { + return new Thickness() + { + Left = System.Convert.ToDouble(values[1]) * ratio, + Top = System.Convert.ToDouble(values[2]) * ratio, + Right = default, + Bottom = default + }; + } + else if (values.Length == 4) + { + return new Thickness() + { + Left = System.Convert.ToDouble(values[1]) * ratio, + Top = System.Convert.ToDouble(values[2]) * ratio, + Right = System.Convert.ToDouble(values[3]) * ratio, + Bottom = default + }; + } + else if (values.Length == 5) + { + return new Thickness() + { + Left = System.Convert.ToDouble(values[1]) * ratio, + Top = System.Convert.ToDouble(values[2]) * ratio, + Right = System.Convert.ToDouble(values[3]) * ratio, + Bottom = System.Convert.ToDouble(values[4]) * ratio + }; + } + else + throw new NotImplementedException(); + } + + public object[] ConvertBack( + object value, + Type[] targetTypes, + object parameter, + System.Globalization.CultureInfo culture + ) + { + throw new NotImplementedException(); + } +} diff --git a/VPet.Solution/Converters/StringFormatConverter.cs b/VPet.Solution/Converters/StringFormatConverter.cs new file mode 100644 index 0000000..26730e0 --- /dev/null +++ b/VPet.Solution/Converters/StringFormatConverter.cs @@ -0,0 +1,51 @@ +using System.Windows.Data; + +namespace VPet.House.Converters; + +/// +/// 边距转换器 +/// 示例: +/// +/// +/// +/// +/// +/// OR +/// +/// +/// +/// +/// ]]> +/// +public class StringFormatConverter : IMultiValueConverter +{ + public object Convert( + object[] values, + Type targetType, + object parameter, + System.Globalization.CultureInfo culture + ) + { + var formatStr = (string)parameter; + if (string.IsNullOrWhiteSpace(formatStr)) + { + formatStr = (string)values[0]; + return string.Format(formatStr, values.Skip(1).ToArray()); + } + else + { + return string.Format(formatStr, values); + } + } + + public object[] ConvertBack( + object value, + Type[] targetTypes, + object parameter, + System.Globalization.CultureInfo culture + ) + { + throw new NotImplementedException(); + } +} diff --git a/VPet.Solution/MainWindow.xaml b/VPet.Solution/MainWindow.xaml deleted file mode 100644 index d01cfd8..0000000 --- a/VPet.Solution/MainWindow.xaml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - diff --git a/VPet.Solution/MainWindow.xaml.cs b/VPet.Solution/MainWindow.xaml.cs deleted file mode 100644 index f6d127e..0000000 --- a/VPet.Solution/MainWindow.xaml.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; - -namespace VPet.Solution -{ - /// - /// MainWindow.xaml 的交互逻辑 - /// - public partial class MainWindow : Window - { - public MainWindow() - { - if (App.IsDone) - { - Close(); - return; - } - InitializeComponent(); - } - } -} diff --git a/VPet.Solution/NativeStyles.xaml b/VPet.Solution/NativeStyles.xaml new file mode 100644 index 0000000..2009db2 --- /dev/null +++ b/VPet.Solution/NativeStyles.xaml @@ -0,0 +1,12 @@ + + + + + + + + + \ No newline at end of file diff --git a/VPet.Solution/NativeStyles.xaml.cs b/VPet.Solution/NativeStyles.xaml.cs new file mode 100644 index 0000000..ed6f24c --- /dev/null +++ b/VPet.Solution/NativeStyles.xaml.cs @@ -0,0 +1,11 @@ +using System.Windows; + +namespace VPet.Solution; + +public partial class NativeStyles : ResourceDictionary +{ + public NativeStyles() + { + InitializeComponent(); + } +} diff --git a/VPet.Solution/Properties/AssemblyInfo.cs b/VPet.Solution/Properties/AssemblyInfo.cs index c186668..dfb6a99 100644 --- a/VPet.Solution/Properties/AssemblyInfo.cs +++ b/VPet.Solution/Properties/AssemblyInfo.cs @@ -1,6 +1,4 @@ using System.Reflection; -using System.Resources; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Windows; @@ -33,15 +31,14 @@ using System.Windows; [assembly: ThemeInfo( ResourceDictionaryLocation.None, //主题特定资源词典所处位置 - //(未在页面中找到资源时使用, - //或应用程序资源字典中找到时使用) + //(未在页面中找到资源时使用, + //或应用程序资源字典中找到时使用) ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置 - //(未在页面中找到资源时使用, - //、应用程序或任何主题专用资源字典中找到时使用) +//(未在页面中找到资源时使用, +//、应用程序或任何主题专用资源字典中找到时使用) )] - -// 程序集的版本信息由下列四个值组成: +// 程序集的版本信息由下列四个值组成: // // 主版本 // 次版本 diff --git a/VPet.Solution/Resources/NativeResources.cs b/VPet.Solution/Resources/NativeResources.cs new file mode 100644 index 0000000..09f7b1d --- /dev/null +++ b/VPet.Solution/Resources/NativeResources.cs @@ -0,0 +1,72 @@ +using System.Reflection; + +namespace VPet.House.Resources; + +/// +/// 本地资源 +/// +internal class NativeResources +{ + #region Resources + + public const string Wall = ResourcePath + "Wall.png"; + public const string Floor = ResourcePath + "Floor.png"; + public const string Chair = ResourcePath + "Chair.png"; + public const string Table = ResourcePath + "Table.png"; + public const string Bed = ResourcePath + "Bed.png"; + + public const string OakPlanks = ResourcePath + "oak_planks.png"; + public const string Stone = ResourcePath + "stone.png"; + + #endregion Resources + + /// + /// 资源基路径 + /// + public const string ResourcePath = $"{nameof(VPet)}.{nameof(House)}.{nameof(Resources)}."; + + #region Native + + private static readonly Assembly _assembly = Assembly.GetExecutingAssembly(); + + /// + /// 获取资源流 + /// + /// 资源名 + /// 资源流 + public static Stream GetStream(string resourceName) => + _assembly.GetManifestResourceStream(resourceName)!; + + /// + /// 尝试获取资源流 + /// + /// 资源名 + /// 资源流 + /// 成功为 失败为 + public static bool TryGetStream(string resourceName, out Stream resourceStream) + { + resourceStream = null; + if (_assembly.GetManifestResourceStream(resourceName) is not Stream stream) + return false; + resourceStream = stream; + return true; + } + + /// + /// 将流保存至文件 + /// + /// 资源名 + /// 文件路径 + /// 成功为 失败为 + public static bool SaveTo(string resourceName, string path) + { + if (_assembly.GetManifestResourceStream(resourceName) is not Stream stream) + return false; + using var sr = new StreamReader(stream); + using var sw = new StreamWriter(path); + sr.BaseStream.CopyTo(sw.BaseStream); + return true; + } + + #endregion Native +} diff --git a/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangedX.cs b/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangedX.cs new file mode 100644 index 0000000..141f509 --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangedX.cs @@ -0,0 +1,12 @@ +namespace HKW.HKWUtils.Observable; + +/// +/// 通知属性改变后接口 +/// +public interface INotifyPropertyChangedX +{ + /// + /// 通知属性改变后事件 + /// + public event PropertyChangedXEventHandler? PropertyChangedX; +} diff --git a/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangingX.cs b/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangingX.cs new file mode 100644 index 0000000..8b47081 --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableClass/INotifyPropertyChangingX.cs @@ -0,0 +1,12 @@ +namespace HKW.HKWUtils.Observable; + +/// +/// 通知属性改变前接口 +/// +public interface INotifyPropertyChangingX +{ + /// + /// 属性改变前事件 + /// + public event PropertyChangingXEventHandler? PropertyChangingX; +} diff --git a/VPet.Solution/SimpleObservable/ObservableClass/ObservableClass.cs b/VPet.Solution/SimpleObservable/ObservableClass/ObservableClass.cs new file mode 100644 index 0000000..b8e0794 --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableClass/ObservableClass.cs @@ -0,0 +1,106 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace HKW.HKWUtils.Observable; + +/// +/// 可观察对象 +/// 示例: +/// { +/// int _value = 0; +/// public int Value +/// { +/// get => _value; +/// set => SetProperty(ref _value, value); +/// } +/// }]]> +/// +public abstract class ObservableClass + : INotifyPropertyChanging, + INotifyPropertyChanged, + INotifyPropertyChangingX, + INotifyPropertyChangedX + where TObject : ObservableClass +{ + #region OnPropertyChange + /// + /// 设置属性值 + /// + /// 值 + /// 新值 + /// 属性名称 + /// 成功为 失败为 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual bool SetProperty( + ref TValue value, + TValue newValue, + [CallerMemberName] string propertyName = null! + ) + { + if (EqualityComparer.Default.Equals(value, newValue) is true) + return false; + var oldValue = value; + if (OnPropertyChanging(oldValue, newValue, propertyName)) + return false; + value = newValue; + OnPropertyChanged(oldValue, newValue, propertyName); + return true; + } + + /// + /// 属性改变前 + /// + /// 旧值 + /// 新值 + /// 属性名称 + /// 取消为 否则为 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual bool OnPropertyChanging( + object? oldValue, + object? newValue, + [CallerMemberName] string propertyName = null! + ) + { + PropertyChanging?.Invoke(this, new(propertyName)); + if (PropertyChangingX is null) + return false; + var e = new PropertyChangingXEventArgs(propertyName, oldValue, newValue); + PropertyChangingX?.Invoke((TObject)this, e); + if (e.Cancel) + PropertyChanged?.Invoke(this, new(propertyName)); + return e.Cancel; + } + + /// + /// 属性改变后 + /// + /// 旧值 + /// 新值 + /// 属性名称 + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected virtual void OnPropertyChanged( + object? oldValue, + object? newValue, + [CallerMemberName] string propertyName = null! + ) + { + PropertyChanged?.Invoke(this, new(propertyName)); + PropertyChangedX?.Invoke((TObject)this, new(propertyName, oldValue, newValue)); + } + #endregion + + #region Event + /// + public event PropertyChangingEventHandler? PropertyChanging; + + /// + public event PropertyChangedEventHandler? PropertyChanged; + + /// + public event PropertyChangingXEventHandler? PropertyChangingX; + + /// + public event PropertyChangedXEventHandler? PropertyChangedX; + #endregion +} diff --git a/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventArgs.cs b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventArgs.cs new file mode 100644 index 0000000..d9f80c2 --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventArgs.cs @@ -0,0 +1,33 @@ +namespace HKW.HKWUtils.Observable; + +/// +/// 属性改变后事件参数 +/// +public class PropertyChangedXEventArgs : EventArgs +{ + /// + /// 属性名 + /// + public string PropertyName { get; } + + /// + /// 旧值 + /// + public object? OldValue { get; } + + /// + /// 新值 + /// + public object? NewValue { get; } + + /// + /// 属性名 + /// 旧值 + /// 新值 + public PropertyChangedXEventArgs(string propertyName, object? oldValue, object? newValue) + { + PropertyName = propertyName; + OldValue = oldValue; + NewValue = newValue; + } +} diff --git a/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventHandler.cs b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventHandler.cs new file mode 100644 index 0000000..178e3c9 --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangedXEventHandler.cs @@ -0,0 +1,11 @@ +namespace HKW.HKWUtils.Observable; + +/// +/// 属性改变后事件 +/// +/// 发送者 +/// 参数 +public delegate void PropertyChangedXEventHandler( + TSender sender, + PropertyChangedXEventArgs e +); diff --git a/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventArgs.cs b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventArgs.cs new file mode 100644 index 0000000..bbd6eed --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventArgs.cs @@ -0,0 +1,35 @@ +using System.ComponentModel; + +namespace HKW.HKWUtils.Observable; + +/// +/// 属性改变前事件参数 +/// +public class PropertyChangingXEventArgs : CancelEventArgs +{ + /// + /// 属性名 + /// + public string PropertyName { get; } + + /// + /// 旧值 + /// + public object? OldValue { get; } + + /// + /// 新值 + /// + public object? NewValue { get; } + + /// + /// 属性名 + /// 旧值 + /// 新值 + public PropertyChangingXEventArgs(string propertyName, object? oldValue, object? newValue) + { + PropertyName = propertyName; + OldValue = oldValue; + NewValue = newValue; + } +} diff --git a/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventHandler.cs b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventHandler.cs new file mode 100644 index 0000000..9a1e472 --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableClass/PropertyChangingXEventHandler.cs @@ -0,0 +1,11 @@ +namespace HKW.HKWUtils.Observable; + +/// +/// 属性改变前事件 +/// +/// 发送者 +/// 参数 +public delegate void PropertyChangingXEventHandler( + TSender sender, + PropertyChangingXEventArgs e +); diff --git a/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteAsyncEventHandler.cs b/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteAsyncEventHandler.cs new file mode 100644 index 0000000..07004c4 --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteAsyncEventHandler.cs @@ -0,0 +1,12 @@ +namespace HKW.HKWUtils.Observable; + +/// +/// 异步执行命令事件 +/// +public delegate Task ExecuteAsyncEventHandler(); + +/// +/// 异步执行命令事件 +/// +/// 值 +public delegate Task ExecuteAsyncEventHandler(T parameter); diff --git a/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteEventHandler.cs b/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteEventHandler.cs new file mode 100644 index 0000000..52075fe --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableCommand/ExecuteEventHandler.cs @@ -0,0 +1,12 @@ +namespace HKW.HKWUtils.Observable; + +/// +/// 执行事件 +/// +public delegate void ExecuteEventHandler(); + +/// +/// 执行事件 +/// +/// 参数 +public delegate void ExecuteEventHandler(T parameter); diff --git a/VPet.Solution/SimpleObservable/ObservableCommand/NotifyReceivedEventHandler.cs b/VPet.Solution/SimpleObservable/ObservableCommand/NotifyReceivedEventHandler.cs new file mode 100644 index 0000000..ba2a50e --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableCommand/NotifyReceivedEventHandler.cs @@ -0,0 +1,11 @@ +using System.ComponentModel; +using System.Windows.Input; + +namespace HKW.HKWUtils.Observable; + +/// +/// 通知接收器 +/// +/// 发送者 +/// 参数 +public delegate void NotifyReceivedEventHandler(ICommand sender, CancelEventArgs e); diff --git a/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommand.cs b/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommand.cs new file mode 100644 index 0000000..7dc7e3d --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommand.cs @@ -0,0 +1,115 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Windows.Input; + +namespace HKW.HKWUtils.Observable; + +/// +/// 可观察命令 +/// +[DebuggerDisplay("\\{ObservableCommand, CanExecute = {IsCanExecute.Value}\\}")] +public class ObservableCommand : ObservableClass, ICommand +{ + bool _isCanExecute = true; + + /// + /// 能执行的属性 + /// + public bool IsCanExecute + { + get => _isCanExecute; + set => SetProperty(ref _isCanExecute, value); + } + + bool _currentCanExecute = true; + + /// + /// 当前可执行状态 + /// + /// 在执行异步事件时会强制为 , 但异步结束后会恢复为 的值 + /// + /// + public bool CurrentCanExecute + { + get => _currentCanExecute; + private set => SetProperty(ref _currentCanExecute, value); + } + + /// + public ObservableCommand() + { + PropertyChanged += OnCanExecuteChanged; + } + + private void OnCanExecuteChanged(object? sender, PropertyChangedEventArgs e) + { + CanExecuteChanged?.Invoke(this, new()); + } + + #region ICommand + /// + /// 能否被执行 + /// + /// 参数 + /// 能被执行为 否则为 + public bool CanExecute(object? parameter) + { + return CurrentCanExecute && IsCanExecute; + } + + /// + /// 执行方法 + /// + /// 参数 + public async void Execute(object? parameter) + { + if (IsCanExecute is not true) + return; + ExecuteCommand?.Invoke(); + await ExecuteAsync(); + } + + /// + /// 执行异步方法, 会在等待中修改 , 完成后恢复 + /// + /// 若要在执行此方法时触发 事件, 请将 设置为 + /// + /// + /// 设置为 时触发 事件 + /// 任务 + public async Task ExecuteAsync(bool runAlone = false) + { + if (IsCanExecute is not true) + return; + if (runAlone) + ExecuteCommand?.Invoke(); + if (ExecuteAsyncCommand is null) + return; + CurrentCanExecute = false; + foreach ( + var asyncEvent in ExecuteAsyncCommand + .GetInvocationList() + .Cast() + ) + await asyncEvent.Invoke(); + CurrentCanExecute = true; + } + #endregion + + #region Event + /// + /// 能否执行属性改变后事件 + /// + public event EventHandler? CanExecuteChanged; + + /// + /// 执行事件 + /// + public event ExecuteEventHandler? ExecuteCommand; + + /// + /// 异步执行事件 + /// + public event ExecuteAsyncEventHandler? ExecuteAsyncCommand; + #endregion +} diff --git a/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommandT.cs b/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommandT.cs new file mode 100644 index 0000000..0c63112 --- /dev/null +++ b/VPet.Solution/SimpleObservable/ObservableCommand/ObservableCommandT.cs @@ -0,0 +1,116 @@ +using System.ComponentModel; +using System.Diagnostics; +using System.Windows.Input; + +namespace HKW.HKWUtils.Observable; + +/// +/// 具有参数的可观察命令 +/// +[DebuggerDisplay("\\{ObservableCommand, CanExecute = {IsCanExecute.Value}\\}")] +public class ObservableCommand : ObservableClass, ICommand +{ + bool _isCanExecute = true; + + /// + /// 能执行的属性 + /// + public bool IsCanExecute + { + get => _isCanExecute; + set => SetProperty(ref _isCanExecute, value); + } + + bool _currentCanExecute = true; + + /// + /// 当前可执行状态 + /// + /// 在执行异步事件时会强制为 , 但异步结束后会恢复为 的值 + /// + /// + public bool CurrentCanExecute + { + get => _currentCanExecute; + private set => SetProperty(ref _currentCanExecute, value); + } + + /// + public ObservableCommand() + { + PropertyChanged += OnCanExecuteChanged; + } + + private void OnCanExecuteChanged(object? sender, PropertyChangedEventArgs e) + { + CanExecuteChanged?.Invoke(this, new()); + } + + #region ICommand + /// + /// 能否被执行 + /// + /// 参数 + /// 能被执行为 否则为 + public bool CanExecute(object? parameter) + { + return CurrentCanExecute && IsCanExecute; + } + + /// + /// 执行方法 + /// + /// 参数 + public async void Execute(object? parameter) + { + if (IsCanExecute is not true) + return; + ExecuteCommand?.Invoke((T)parameter!); + await ExecuteAsync((T)parameter!); + } + + /// + /// 执行异步方法, 会在等待中修改 , 完成后恢复 + /// + /// 若要在执行此方法时触发 事件, 请将 设置为 + /// + /// + /// 参数 + /// 设置为 时触发 事件 + /// 任务 + public async Task ExecuteAsync(T parameter, bool runAlone = false) + { + if (IsCanExecute is not true) + return; + if (runAlone) + ExecuteCommand?.Invoke(parameter); + if (ExecuteAsyncCommand is null) + return; + CurrentCanExecute = false; + foreach ( + var asyncEvent in ExecuteAsyncCommand + .GetInvocationList() + .Cast>() + ) + await asyncEvent.Invoke(parameter); + CurrentCanExecute = true; + } + #endregion + + #region Event + /// + /// 能否执行属性改变后事件 + /// + public event EventHandler? CanExecuteChanged; + + /// + /// 执行事件 + /// + public event ExecuteEventHandler? ExecuteCommand; + + /// + /// 异步执行事件 + /// + public event ExecuteAsyncEventHandler? ExecuteAsyncCommand; + #endregion +} diff --git a/VPet.Solution/Styles.xaml b/VPet.Solution/Styles.xaml new file mode 100644 index 0000000..5903eb4 --- /dev/null +++ b/VPet.Solution/Styles.xaml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VPet.Solution/Templates.xaml b/VPet.Solution/Templates.xaml new file mode 100644 index 0000000..ac14da7 --- /dev/null +++ b/VPet.Solution/Templates.xaml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VPet.Solution/Usings.cs b/VPet.Solution/Usings.cs new file mode 100644 index 0000000..953c41b --- /dev/null +++ b/VPet.Solution/Usings.cs @@ -0,0 +1,6 @@ +global using global::HKW.HKWUtils.Observable; +global using global::System; +global using global::System.Collections.Generic; +global using global::System.IO; +global using global::System.Linq; +global using global::System.Threading.Tasks; diff --git a/VPet.Solution/Utils/Expansions.cs b/VPet.Solution/Utils/Expansions.cs new file mode 100644 index 0000000..60b6345 --- /dev/null +++ b/VPet.Solution/Utils/Expansions.cs @@ -0,0 +1,312 @@ +using System.Collections; +using System.Diagnostics; +using System.Drawing; +using System.Drawing.Imaging; +using System.Globalization; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Imaging; + +namespace HKW.HKWUtils; + +/// +/// 拓展 +/// +public static class Extensions +{ + /// + /// + /// + /// + /// + /// + /// + public static bool Contains(this string source, string value, StringComparison comparisonType) + { + return source.IndexOf(value, comparisonType) >= 0; + } + + //public static string GetSourceFile(this BitmapImage image) + //{ + // return ((FileStream)image.StreamSource).Name; + //} + + /// + /// 关闭流 + /// + /// 图像资源 + public static void CloseStream(this ImageSource source) + { + if (source is not BitmapImage image) + return; + image.StreamSource?.Close(); + } + + /// + /// 图像复制 + /// + /// 图像 + /// 复制的图像 + public static BitmapImage Copy(this BitmapImage image) + { + if (image is null) + return null; + BitmapImage newImage = new(); + newImage.BeginInit(); + newImage.DecodePixelWidth = image.DecodePixelWidth; + newImage.DecodePixelHeight = image.DecodePixelHeight; + try + { + using var bitmap = new Bitmap(image.StreamSource); + var ms = new MemoryStream(); + bitmap.Save(ms, ImageFormat.Png); + image.StreamSource.CopyTo(ms); + newImage.StreamSource = ms; + } + finally + { + newImage.EndInit(); + } + return newImage; + } + + /// + /// 保存至Png图片 + /// + /// 图片资源 + /// 路径 + //public static void SaveToPng(this BitmapImage image, string path) + //{ + // if (image is null) + // return; + // if (path.EndsWith(".png") is false) + // path += ".png"; + // var encoder = new PngBitmapEncoder(); + // var stream = image.StreamSource; + // // 保存位置 + // var position = stream.Position; + // // 必须要重置位置, 否则EndInit将出错 + // stream.Seek(0, SeekOrigin.Begin); + // encoder.Frames.Add(BitmapFrame.Create(image.StreamSource)); + // // 恢复位置 + // stream.Seek(position, SeekOrigin.Begin); + // using var fs = new FileStream(path, FileMode.Create); + // encoder.Save(fs); + //} + public static void SaveToPng(this BitmapImage image, string path) + { + if (image is null) + return; + if (path.EndsWith(".png") is false) + path += ".png"; + var stream = image.StreamSource; + // 保存位置 + var position = stream.Position; + // 必须要重置位置, 否则EndInit将出错 + stream.Seek(0, SeekOrigin.Begin); + using var fs = new FileStream(path, FileMode.Create); + stream.CopyTo(fs); + // 恢复位置 + stream.Seek(position, SeekOrigin.Begin); + } + + /// + /// 尝试添加 + /// + /// 键类型 + /// + /// + /// 键 + /// 值 + /// 成功为 失败为 + public static bool TryAdd( + this IDictionary dictionary, + TKey key, + TValue value + ) + { + if (dictionary.ContainsKey(key)) + return false; + dictionary.Add(key, value); + return true; + } + + /// + /// 流内容对比 + /// + /// 原始流 + /// 目标流 + /// 缓冲区大小 (越大速度越快(流内容越大效果越明显), 但会提高内存占用 (bufferSize = bufferLength * sizeof(long) * 2)) + /// 内容相同为 否则为 + public static bool ContentsEqual(this Stream source, Stream target, int bufferLength = 8) + { + int bufferSize = bufferLength * sizeof(long); + var sourceBuffer = new byte[bufferSize]; + var targetBuffer = new byte[bufferSize]; + while (true) + { + int sourceCount = ReadFullBuffer(source, sourceBuffer); + int targetCount = ReadFullBuffer(target, targetBuffer); + if (sourceCount != targetCount) + return false; + if (sourceCount == 0) + return true; + + for (int i = 0; i < sourceCount; i += sizeof(long)) + if (BitConverter.ToInt64(sourceBuffer, i) != BitConverter.ToInt64(targetBuffer, i)) + return false; + } + static int ReadFullBuffer(Stream stream, byte[] buffer) + { + int bytesRead = 0; + while (bytesRead < buffer.Length) + { + int read = stream.Read(buffer, bytesRead, buffer.Length - bytesRead); + if (read == 0) + return bytesRead; + bytesRead += read; + } + return bytesRead; + } + } + + public static T? FindVisualChild(this DependencyObject obj) + where T : DependencyObject + { + if (obj is null) + return null; + var count = VisualTreeHelper.GetChildrenCount(obj); + for (int i = 0; i < count; i++) + { + var child = VisualTreeHelper.GetChild(obj, i); + if (child is T t) + return t; + if (FindVisualChild(child) is T childItem) + return childItem; + } + return null; + } + + public static T FindParent(this DependencyObject obj) + where T : class + { + while (obj != null) + { + if (obj is T) + return obj as T; + obj = VisualTreeHelper.GetParent(obj); + } + return null; + } + + public static string GetFullInfo(this CultureInfo cultureInfo) + { + return $"{cultureInfo.DisplayName} [{cultureInfo.Name}]"; + } + + /// + /// 尝试使用索引获取值 + /// + /// 值类型 + /// 列表 + /// 索引 + /// 值 + /// 成功为 失败为 + public static bool TryGetValue(this IList list, int index, out T value) + { + value = default; + if (index < 0 || index >= list.Count) + return false; + value = list[index]; + return true; + } + + /// + /// 尝试使用索引获取值 + /// + /// 值类型 + /// 列表 + /// 索引 + /// 值 + /// 成功为 失败为 + public static bool TryGetValue(this IList list, int index, out object value) + { + value = default; + if (index < 0 || index >= list.Count) + return false; + value = list[index]; + return true; + } + + /// + /// 获取目标 + /// + /// 类型 + /// 弱引用 + /// 获取成功返回目标值, 获取失败则返回 + public static T? GetTarget(this WeakReference weakReference) + where T : class + { + return weakReference.TryGetTarget(out var t) ? t : null; + } + + /// + /// 枚举出带有索引值的枚举值 + /// + /// 值类型 + /// 集合 + /// 带有索引的枚举值 + public static IEnumerable> Enumerate(this IEnumerable collection) + { + var index = 0; + foreach (var item in collection) + yield return new(index++, item); + } + + public static void SetDataContext(this Window window) + where T : new() + { + window.DataContext = new T(); + window.Closed += (s, e) => + { + try + { + window.DataContext = null; + } + catch { } + }; + } +} + +/// +/// 项信息 +/// +/// +[DebuggerDisplay("[{Index}, {Value}]")] +public readonly struct ItemInfo +{ + /// + /// 索引值 + /// + public int Index { get; } + + /// + /// 值 + /// + public T Value { get; } + + /// + /// 值 + /// 索引值 + public ItemInfo(int index, T value) + { + Index = index; + Value = value; + } + + /// + public override string ToString() + { + return $"[{Index}, {Value}]"; + } +} diff --git a/VPet.Solution/Utils/HashCode.cs b/VPet.Solution/Utils/HashCode.cs new file mode 100644 index 0000000..b88ff89 --- /dev/null +++ b/VPet.Solution/Utils/HashCode.cs @@ -0,0 +1,66 @@ +namespace HKW.HKWUtils; + +/// +/// 哈希值 +/// +public class HashCode +{ + /// + /// 默认种子 + /// + public const int DefaultSeed = 114514; + + /// + /// 默认系数 + /// + public const int DefaultFactor = 1919810; + + /// + /// 组合哈希值 + /// + /// 值 + /// 组合的哈希值 + public static int Combine(params object[] values) + { + return CustomHash(DefaultSeed, DefaultFactor, values.Select(v => v.GetHashCode())); + } + + /// + /// 组合哈希值 + /// + /// 种子 + /// 系数 + /// 值 + /// 组合的哈希值 + public static int Combine(int seed, int factor, params object[] values) + { + return CustomHash(seed, factor, values.Select(v => v.GetHashCode())); + } + + /// + /// 自定义组合哈希 + /// + /// 种子 + /// 系数 + /// 哈希集合 + /// 组合的哈希 + public static int CustomHash(int seed, int factor, IEnumerable collection) + { + int hash = seed; + foreach (int i in collection) + hash = unchecked((hash * factor) + i); + return hash; + } + + /// + /// 自定义组合哈希 + /// + /// 种子 + /// 系数 + /// 哈希集合 + /// 组合的哈希 + public static int CustomHash(int seed, int factor, params int[] values) + { + return CustomHash(seed, factor, collection: values); + } +} diff --git a/VPet.Solution/Utils/ObservableEnumFlags.cs b/VPet.Solution/Utils/ObservableEnumFlags.cs new file mode 100644 index 0000000..3e863ce --- /dev/null +++ b/VPet.Solution/Utils/ObservableEnumFlags.cs @@ -0,0 +1,72 @@ +namespace HKW.HKWUtils; + +/// +/// 可观察的枚举标签模型 +/// +/// 枚举类型 +public class ObservableEnumFlags : ObservableClass> + where T : Enum +{ + private T _EnumValue; + public T EnumValue + { + get => _EnumValue; + set => SetProperty(ref _EnumValue, value); + } + + /// + /// 添加枚举命令 + /// + public ObservableCommand AddCommand { get; } = new(); + + /// + /// 删除枚举命令 + /// + public ObservableCommand RemoveCommand { get; } = new(); + + /// + /// 枚举类型 + /// + public Type EnumType = typeof(T); + + /// + /// 枚举基类 + /// + public Type UnderlyingType { get; } = Enum.GetUnderlyingType(typeof(T)); + + public ObservableEnumFlags() + { + if (Attribute.IsDefined(EnumType, typeof(FlagsAttribute)) is false) + throw new Exception($"此枚举类型未使用特性 [{nameof(FlagsAttribute)}]"); + AddCommand.ExecuteCommand += AddCommand_Execute; + RemoveCommand.ExecuteCommand += RemoveCommand_Execute; + } + + public ObservableEnumFlags(T value) + : this() + { + EnumValue = value; + } + + private void AddCommand_Execute(T v) + { + if (UnderlyingType == typeof(int)) + { + EnumValue = (T) + Enum.Parse(EnumType, (Convert.ToInt32(EnumValue) | Convert.ToInt32(v)).ToString()); + } + else + throw new NotImplementedException($"Value type: {UnderlyingType}"); + } + + private void RemoveCommand_Execute(T v) + { + if (UnderlyingType == typeof(int)) + { + EnumValue = (T) + Enum.Parse(EnumType, (Convert.ToInt32(EnumValue) & ~Convert.ToInt32(v)).ToString()); + } + else + throw new NotImplementedException($"Value type: {UnderlyingType}"); + } +} diff --git a/VPet.Solution/Utils/ObservablePoint.cs b/VPet.Solution/Utils/ObservablePoint.cs new file mode 100644 index 0000000..eff89d6 --- /dev/null +++ b/VPet.Solution/Utils/ObservablePoint.cs @@ -0,0 +1,77 @@ +namespace HKW.HKWUtils; + +/// +/// 可观察地点 +/// +/// 类型 +public class ObservablePoint + : ObservableClass>, + IEquatable> +{ + private T _x; + public T X + { + get => _x; + set => SetProperty(ref _x, value); + } + + private T _y; + public T Y + { + get => _y; + set => SetProperty(ref _y, value); + } + + public ObservablePoint() { } + + public ObservablePoint(T x, T y) + { + X = x; + Y = y; + } + + /// + /// 复制一个新的对象 + /// + /// 新对象 + public ObservablePoint Copy() + { + return new(X, Y); + } + + #region Other + + /// + public override int GetHashCode() + { + return HashCode.Combine(X, Y); + } + + /// + public override bool Equals(object? obj) + { + return obj is ObservablePoint temp + && EqualityComparer.Default.Equals(X, temp.X) + && EqualityComparer.Default.Equals(Y, temp.Y); + } + + /// + public bool Equals(ObservablePoint? other) + { + return Equals(obj: other); + } + + /// + public static bool operator ==(ObservablePoint a, ObservablePoint b) + { + return Equals(a, b); + } + + /// + public static bool operator !=(ObservablePoint a, ObservablePoint b) + { + return Equals(a, b) is not true; + } + + #endregion +} diff --git a/VPet.Solution/Utils/ObservableRange.cs b/VPet.Solution/Utils/ObservableRange.cs new file mode 100644 index 0000000..76a1d6f --- /dev/null +++ b/VPet.Solution/Utils/ObservableRange.cs @@ -0,0 +1,77 @@ +namespace HKW.HKWUtils; + +/// +/// 可观察的范围 +/// +/// 类型 +public class ObservableRange + : ObservableClass>, + IEquatable> +{ + private T _min; + public T Min + { + get => _min; + set => SetProperty(ref _min, value); + } + + private T _max; + public T Max + { + get => _max; + set => SetProperty(ref _max, value); + } + + public ObservableRange() { } + + public ObservableRange(T min, T max) + { + _min = min; + _max = max; + } + + /// + /// 复制一个新的对象 + /// + /// 新对象 + public ObservableRange Copy() + { + return new(Min, Max); + } + + #region Other + + /// + public override int GetHashCode() + { + return HashCode.Combine(Min, Max); + } + + /// + public override bool Equals(object? obj) + { + return obj is ObservableRange temp + && EqualityComparer.Default.Equals(Min, temp.Min) + && EqualityComparer.Default.Equals(Max, temp.Max); + } + + /// + public bool Equals(ObservableRange? other) + { + return Equals(obj: other); + } + + /// + public static bool operator ==(ObservableRange a, ObservableRange b) + { + return Equals(a, b); + } + + /// + public static bool operator !=(ObservableRange a, ObservableRange b) + { + return Equals(a, b) is not true; + } + + #endregion +} diff --git a/VPet.Solution/Utils/ObservableRect.cs b/VPet.Solution/Utils/ObservableRect.cs new file mode 100644 index 0000000..49924a7 --- /dev/null +++ b/VPet.Solution/Utils/ObservableRect.cs @@ -0,0 +1,89 @@ +namespace HKW.HKWUtils; + +public class ObservableRect : ObservableClass>, IEquatable> +{ + private T _x; + public T X + { + get => _x; + set => SetProperty(ref _x, value); + } + + private T _y; + public T Y + { + get => _y; + set => SetProperty(ref _y, value); + } + + private T _width; + public T Width + { + get => _width; + set => SetProperty(ref _width, value); + } + + private T _heigth; + public T Height + { + get => _heigth; + set => SetProperty(ref _heigth, value); + } + + public ObservableRect() { } + + public ObservableRect(T x, T y, T width, T hetght) + { + X = x; + Y = y; + Width = width; + Height = hetght; + } + + /// + /// 复制一个新的对象 + /// + /// 新对象 + public ObservableRect Copy() + { + return new(X, Y, Width, Height); + } + + #region Other + + /// + public override int GetHashCode() + { + return HashCode.Combine(X, Y, Width, Height); + } + + /// + public override bool Equals(object? obj) + { + return obj is ObservableRect temp + && EqualityComparer.Default.Equals(X, temp.X) + && EqualityComparer.Default.Equals(Y, temp.Y) + && EqualityComparer.Default.Equals(Width, temp.Width) + && EqualityComparer.Default.Equals(Height, temp.Height); + } + + /// + public bool Equals(ObservableRect? other) + { + return Equals(obj: other); + } + + /// + public static bool operator ==(ObservableRect a, ObservableRect b) + { + return Equals(a, b); + } + + /// + public static bool operator !=(ObservableRect a, ObservableRect b) + { + return Equals(a, b) is not true; + } + + #endregion +} diff --git a/VPet.Solution/Utils/Utils.cs b/VPet.Solution/Utils/Utils.cs new file mode 100644 index 0000000..c628c1b --- /dev/null +++ b/VPet.Solution/Utils/Utils.cs @@ -0,0 +1,79 @@ +using System.Windows.Media.Imaging; + +namespace HKW.HKWUtils; + +/// +/// 工具 +/// +public static class Utils +{ + /// + /// 解码像素宽度 + /// + public const int DecodePixelWidth = 250; + + /// + /// 解码像素高度 + /// + public const int DecodePixelHeight = 250; + public static char[] Separator { get; } = new char[] { '_' }; + + //public static BitmapImage LoadImageToStream(string imagePath) + //{ + // BitmapImage bitmapImage = new(); + // bitmapImage.BeginInit(); + // bitmapImage.DecodePixelWidth = DecodePixelWidth; + // try + // { + // bitmapImage.StreamSource = new StreamReader(imagePath).BaseStream; + // } + // finally + // { + // bitmapImage.EndInit(); + // } + // return bitmapImage; + //} + + /// + /// 载入图片至内存流 + /// + /// 图片路径 + /// + public static BitmapImage LoadImageToMemoryStream(string imagePath) + { + BitmapImage bitmapImage = new(); + bitmapImage.BeginInit(); + try + { + var bytes = File.ReadAllBytes(imagePath); + bitmapImage.StreamSource = new MemoryStream(bytes); + bitmapImage.DecodePixelWidth = DecodePixelWidth; + } + finally + { + bitmapImage.EndInit(); + } + return bitmapImage; + } + + /// + /// 载入图片至内存流 + /// + /// 图片流 + /// + public static BitmapImage LoadImageToMemoryStream(Stream imageStream) + { + BitmapImage bitmapImage = new(); + bitmapImage.BeginInit(); + try + { + bitmapImage.StreamSource = imageStream; + bitmapImage.DecodePixelWidth = DecodePixelWidth; + } + finally + { + bitmapImage.EndInit(); + } + return bitmapImage; + } +} diff --git a/VPet.Solution/VPet.Solution.csproj b/VPet.Solution/VPet.Solution.csproj index daa478f..dceafe2 100644 --- a/VPet.Solution/VPet.Solution.csproj +++ b/VPet.Solution/VPet.Solution.csproj @@ -14,6 +14,8 @@ 4 true true + enable + latest AnyCPU @@ -39,7 +41,8 @@ ..\packages\LinePutScript.1.9.2\lib\net462\LinePutScript.dll - ..\packages\LinePutScript.Localization.WPF.1.0.6\lib\net462\LinePutScript.Localization.WPF.dll + + ..\packages\LinePutScript.Localization.WPF.1.0.6\lib\net462\LinePutScript.Localization.WPF.dll ..\packages\Panuon.WPF.1.0.2\lib\net462\Panuon.WPF.dll @@ -49,6 +52,7 @@ + @@ -67,7 +71,7 @@ MSBuild:Compile Designer - + MSBuild:Compile Designer @@ -75,9 +79,50 @@ App.xaml Code - + + MainWindow.xaml - Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + true + + + NativeStyles.xaml @@ -117,5 +162,8 @@ VPet-Simulator.Windows.Interface + + + \ No newline at end of file diff --git a/VPet.Solution/ViewModels/MainWindowVM.cs b/VPet.Solution/ViewModels/MainWindowVM.cs new file mode 100644 index 0000000..3f2ed0e --- /dev/null +++ b/VPet.Solution/ViewModels/MainWindowVM.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VPet_Simulator.Core; +using VPet_Simulator.Windows.Interface; + +namespace VPet.Solution.ViewModels; + +public class MainWindowVM : ObservableClass +{ + public MainWindowVM() { } + + public static void LoadSettings(string path) + { + foreach (var file in Directory.EnumerateFiles(path)) + { + var setting = new Setting(path); + } + } +} diff --git a/VPet.Solution/Views/MainWindow.xaml b/VPet.Solution/Views/MainWindow.xaml new file mode 100644 index 0000000..199086c --- /dev/null +++ b/VPet.Solution/Views/MainWindow.xaml @@ -0,0 +1,1849 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +