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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ -1
+
+
+
+
+ 2
+
+
+
+
+ 5
+
+
+
+
+ 10
+
+
+
+
+ 20
+
+
+
+
+ 30
+
+
+
+
+ 60
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 30
+
+
+
+
+ 60
+
+
+
+
+ 120
+
+
+
+
+ 300
+
+
+
+
+ 600
+
+
+
+
+ 1200
+
+
+
+
+ 1800
+
+
+
+
+ 2400
+
+
+
+
+ 3000
+
+
+
+
+ 3600
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 4
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Power By
+
+ exLB.net
+
+
+
+ 洛里斯杨远
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 杨远洛里斯
+ v1.0 (100)
+ 已通过Steam[uid]激活服务注册
+
+
+
+
+ ,
+ ,
+
+
+
+ , トニーちゃん
+
+
+
+ ,
+
+
+
+ Panuon.WPF.UI
+ LinePutScript
+ Facepunch.Steamworks
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/VPet.Solution/Views/MainWindow.xaml.cs b/VPet.Solution/Views/MainWindow.xaml.cs
new file mode 100644
index 0000000..677eedc
--- /dev/null
+++ b/VPet.Solution/Views/MainWindow.xaml.cs
@@ -0,0 +1,19 @@
+using Panuon.WPF.UI;
+
+namespace VPet.Solution;
+
+///
+/// MainWindow.xaml 的交互逻辑
+///
+public partial class MainWindow : WindowX
+{
+ public MainWindow()
+ {
+ if (App.IsDone)
+ {
+ Close();
+ return;
+ }
+ InitializeComponent();
+ }
+}