初始化 VPet.Solution

This commit is contained in:
Hakoyu 2023-12-18 22:53:56 +08:00
parent 4c0d985854
commit 92bb88ec14
47 changed files with 4064 additions and 96 deletions

View File

@ -1,9 +1,17 @@
<Application x:Class="VPet.Solution.App"
<Application
x:Class="VPet.Solution.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VPet.Solution"
StartupUri="MainWindow.xaml">
StartupUri="Views/MainWindow.xaml">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Panuon.WPF.UI;component/Control.xaml" />
<ResourceDictionary Source="/VPet-Simulator.Windows.Interface;component/ResourceStyle.xaml" />
<ResourceDictionary Source="Converters.xaml" />
<ResourceDictionary Source="Templates.xaml" />
<ResourceDictionary Source="Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>

View File

@ -1,22 +1,15 @@
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;
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
public static bool IsDone { get; set; } = false;
protected override void OnStartup(StartupEventArgs e)
{
if (e.Args != null && e.Args.Count() > 0)
@ -25,7 +18,9 @@ namespace VPet.Solution
switch (e.Args[0].ToLower())
{
case "removestarup":
var path = Environment.GetFolderPath(Environment.SpecialFolder.Startup) + @"\VPET_Simulator.lnk";
var path =
Environment.GetFolderPath(Environment.SpecialFolder.Startup)
+ @"\VPET_Simulator.lnk";
if (File.Exists(path))
{
File.Delete(path);
@ -38,5 +33,4 @@ namespace VPet.Solution
}
IsDone = false;
}
}
}

View File

@ -0,0 +1,15 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:c="clr-namespace:VPet.House.Converters">
<c:MarginConverter x:Key="MarginConverter" />
<c:RatioMarginConverter x:Key="RatioMarginConverter" />
<c:CalculatorConverter x:Key="CalculatorConverter" />
<c:StringFormatConverter x:Key="StringFormatConverter" />
<c:BrushToMediaColorConverter x:Key="BrushToMediaColorConverter" />
<c:MaxConverter x:Key="MaxConverter" />
<c:FalseToHiddenConverter x:Key="FalseToHiddenConverter" />
<c:EqualsConverter x:Key="EqualsConverter" />
<c:NotEqualsConverter x:Key="NotEqualsConverter" />
<c:NullToFalseConverter x:Key="NullToFalseConverter" />
</ResourceDictionary>

View File

@ -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);
}
}

View File

@ -0,0 +1,97 @@
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace VPet.House.Converters;
/// <summary>
/// 计算器转换器
/// <para>示例:
/// <code><![CDATA[
/// <MultiBinding Converter="{StaticResource CalculatorConverter}">
/// <Binding Path="Num1" />
/// <Binding Source="+" />
/// <Binding Path="Num2" />
/// <Binding Source="-" />
/// <Binding Path="Num3" />
/// <Binding Source="*" />
/// <Binding Path="Num4" />
/// <Binding Source="/" />
/// <Binding Path="Num5" />
/// </MultiBinding>
/// //
/// <MultiBinding Converter="{StaticResource CalculatorConverter}" ConverterParameter="+-*/">
/// <Binding Path="Num1" />
/// <Binding Path="Num2" />
/// <Binding Path="Num3" />
/// <Binding Path="Num4" />
/// <Binding Path="Num5" />
/// </MultiBinding>
/// ]]></code></para>
/// </summary>
/// <exception cref="Exception">绑定的数量不正确</exception>
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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,84 @@
using System.Windows;
using System.Windows.Data;
namespace VPet.House.Converters;
/// <summary>
/// 边距转换器
/// <para>示例:
/// <code><![CDATA[
/// <MultiBinding Converter="{StaticResource MarginConverter}">
/// <Binding Path="Left" />
/// <Binding Path="Top" />
/// <Binding Path="Right" />
/// <Binding Path="Bottom" />
/// </MultiBinding>
/// ]]></code></para>
/// </summary>
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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -0,0 +1,91 @@
using System.Windows;
using System.Windows.Data;
namespace VPet.House.Converters;
/// <summary>
/// 边距转换器
/// <para>示例:
/// <code><![CDATA[
/// <MultiBinding Converter="{StaticResource RatioMarginConverter}">
/// <Binding Path="Left" />
/// <Binding Path="Top" />
/// <Binding Path="Right" />
/// <Binding Path="Bottom" />
/// </MultiBinding>
/// ]]></code></para>
/// </summary>
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();
}
}

View File

@ -0,0 +1,51 @@
using System.Windows.Data;
namespace VPet.House.Converters;
/// <summary>
/// 边距转换器
/// <para>示例:
/// <code><![CDATA[
/// <MultiBinding Converter="{StaticResource MarginConverter}">
/// <Binding Path="StringFormat" />
/// <Binding Path="Value1" />
/// <Binding Path="Value2" />
/// </MultiBinding>
/// OR
/// <MultiBinding Converter="{StaticResource MarginConverter}" ConverterParameter="{}{0}{1}">
/// <Binding Path="Value1" />
/// <Binding Path="Value2" />
/// </MultiBinding>
/// ]]></code></para>
/// </summary>
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();
}
}

View File

@ -1,10 +0,0 @@
<Window x:Class="VPet.Solution.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:VPet.Solution"
xmlns:ll="clr-namespace:LinePutScript.Localization.WPF;assembly=LinePutScript.Localization.WPF" mc:Ignorable="d"
xmlns:pu="clr-namespace:Panuon.WPF.UI;assembly=Panuon.WPF.UI" Title="{ll:Str 'VPET 问题解决工具'}" Height="450" Width="800">
<Grid>
</Grid>
</Window>

View File

@ -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
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
if (App.IsDone)
{
Close();
return;
}
InitializeComponent();
}
}
}

View File

@ -0,0 +1,12 @@
<ResourceDictionary
x:Class="VPet.Solution.NativeStyles"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/Panuon.WPF.UI;component/Control.xaml" />
<ResourceDictionary Source="/VPet-Simulator.Windows.Interface;component/ResourceStyle.xaml" />
<ResourceDictionary Source="Converters.xaml" />
<ResourceDictionary Source="Templates.xaml" />
<ResourceDictionary Source="Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@ -0,0 +1,11 @@
using System.Windows;
namespace VPet.Solution;
public partial class NativeStyles : ResourceDictionary
{
public NativeStyles()
{
InitializeComponent();
}
}

View File

@ -1,6 +1,4 @@
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
@ -36,11 +34,10 @@ using System.Windows;
//(未在页面中找到资源时使用,
//或应用程序资源字典中找到时使用)
ResourceDictionaryLocation.SourceAssembly //常规资源词典所处位置
//(未在页面中找到资源时使用,
//、应用程序或任何主题专用资源字典中找到时使用)
//(未在页面中找到资源时使用,
//、应用程序或任何主题专用资源字典中找到时使用)
)]
// 程序集的版本信息由下列四个值组成:
//
// 主版本

View File

@ -0,0 +1,72 @@
using System.Reflection;
namespace VPet.House.Resources;
/// <summary>
/// 本地资源
/// </summary>
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
/// <summary>
/// 资源基路径
/// </summary>
public const string ResourcePath = $"{nameof(VPet)}.{nameof(House)}.{nameof(Resources)}.";
#region Native
private static readonly Assembly _assembly = Assembly.GetExecutingAssembly();
/// <summary>
/// 获取资源流
/// </summary>
/// <param name="resourceName">资源名</param>
/// <returns>资源流</returns>
public static Stream GetStream(string resourceName) =>
_assembly.GetManifestResourceStream(resourceName)!;
/// <summary>
/// 尝试获取资源流
/// </summary>
/// <param name="resourceName">资源名</param>
/// <param name="resourceStream">资源流</param>
/// <returns>成功为 <see langword="true"/> 失败为 <see langword="false"/></returns>
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;
}
/// <summary>
/// 将流保存至文件
/// </summary>
/// <param name="resourceName">资源名</param>
/// <param name="path">文件路径</param>
/// <returns>成功为 <see langword="true"/> 失败为 <see langword="false"/></returns>
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
}

View File

@ -0,0 +1,12 @@
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 通知属性改变后接口
/// </summary>
public interface INotifyPropertyChangedX<TSender>
{
/// <summary>
/// 通知属性改变后事件
/// </summary>
public event PropertyChangedXEventHandler<TSender>? PropertyChangedX;
}

View File

@ -0,0 +1,12 @@
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 通知属性改变前接口
/// </summary>
public interface INotifyPropertyChangingX<TSender>
{
/// <summary>
/// 属性改变前事件
/// </summary>
public event PropertyChangingXEventHandler<TSender>? PropertyChangingX;
}

View File

@ -0,0 +1,106 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 可观察对象
/// <para>示例:<code><![CDATA[
/// public class ObservableClassExample : ObservableClass<ObservableClassExample>
/// {
/// int _value = 0;
/// public int Value
/// {
/// get => _value;
/// set => SetProperty(ref _value, value);
/// }
/// }]]></code></para>
/// </summary>
public abstract class ObservableClass<TObject>
: INotifyPropertyChanging,
INotifyPropertyChanged,
INotifyPropertyChangingX<TObject>,
INotifyPropertyChangedX<TObject>
where TObject : ObservableClass<TObject>
{
#region OnPropertyChange
/// <summary>
/// 设置属性值
/// </summary>
/// <param name="value">值</param>
/// <param name="newValue">新值</param>
/// <param name="propertyName">属性名称</param>
/// <returns>成功为 <see langword="true"/> 失败为 <see langword="false"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected virtual bool SetProperty<TValue>(
ref TValue value,
TValue newValue,
[CallerMemberName] string propertyName = null!
)
{
if (EqualityComparer<TValue>.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;
}
/// <summary>
/// 属性改变前
/// </summary>
/// <param name="oldValue">旧值</param>
/// <param name="newValue">新值</param>
/// <param name="propertyName">属性名称</param>
/// <returns>取消为 <see langword="true"/> 否则为 <see langword="false"/></returns>
[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;
}
/// <summary>
/// 属性改变后
/// </summary>
/// <param name="oldValue">旧值</param>
/// <param name="newValue">新值</param>
/// <param name="propertyName">属性名称</param>
[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
/// <inheritdoc/>
public event PropertyChangingEventHandler? PropertyChanging;
/// <inheritdoc/>
public event PropertyChangedEventHandler? PropertyChanged;
/// <inheritdoc/>
public event PropertyChangingXEventHandler<TObject>? PropertyChangingX;
/// <inheritdoc/>
public event PropertyChangedXEventHandler<TObject>? PropertyChangedX;
#endregion
}

View File

@ -0,0 +1,33 @@
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 属性改变后事件参数
/// </summary>
public class PropertyChangedXEventArgs : EventArgs
{
/// <summary>
/// 属性名
/// </summary>
public string PropertyName { get; }
/// <summary>
/// 旧值
/// </summary>
public object? OldValue { get; }
/// <summary>
/// 新值
/// </summary>
public object? NewValue { get; }
/// <inheritdoc/>
/// <param name="propertyName">属性名</param>
/// <param name="oldValue">旧值</param>
/// <param name="newValue">新值</param>
public PropertyChangedXEventArgs(string propertyName, object? oldValue, object? newValue)
{
PropertyName = propertyName;
OldValue = oldValue;
NewValue = newValue;
}
}

View File

@ -0,0 +1,11 @@
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 属性改变后事件
/// </summary>
/// <param name="sender">发送者</param>
/// <param name="e">参数</param>
public delegate void PropertyChangedXEventHandler<TSender>(
TSender sender,
PropertyChangedXEventArgs e
);

View File

@ -0,0 +1,35 @@
using System.ComponentModel;
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 属性改变前事件参数
/// </summary>
public class PropertyChangingXEventArgs : CancelEventArgs
{
/// <summary>
/// 属性名
/// </summary>
public string PropertyName { get; }
/// <summary>
/// 旧值
/// </summary>
public object? OldValue { get; }
/// <summary>
/// 新值
/// </summary>
public object? NewValue { get; }
/// <inheritdoc/>
/// <param name="propertyName">属性名</param>
/// <param name="oldValue">旧值</param>
/// <param name="newValue">新值</param>
public PropertyChangingXEventArgs(string propertyName, object? oldValue, object? newValue)
{
PropertyName = propertyName;
OldValue = oldValue;
NewValue = newValue;
}
}

View File

@ -0,0 +1,11 @@
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 属性改变前事件
/// </summary>
/// <param name="sender">发送者</param>
/// <param name="e">参数</param>
public delegate void PropertyChangingXEventHandler<TSender>(
TSender sender,
PropertyChangingXEventArgs e
);

View File

@ -0,0 +1,12 @@
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 异步执行命令事件
/// </summary>
public delegate Task ExecuteAsyncEventHandler();
/// <summary>
/// 异步执行命令事件
/// </summary>
/// <param name="parameter">值</param>
public delegate Task ExecuteAsyncEventHandler<T>(T parameter);

View File

@ -0,0 +1,12 @@
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 执行事件
/// </summary>
public delegate void ExecuteEventHandler();
/// <summary>
/// 执行事件
/// </summary>
/// <param name="parameter">参数</param>
public delegate void ExecuteEventHandler<T>(T parameter);

View File

@ -0,0 +1,11 @@
using System.ComponentModel;
using System.Windows.Input;
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 通知接收器
/// </summary>
/// <param name="sender">发送者</param>
/// <param name="e">参数</param>
public delegate void NotifyReceivedEventHandler(ICommand sender, CancelEventArgs e);

View File

@ -0,0 +1,115 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 可观察命令
/// </summary>
[DebuggerDisplay("\\{ObservableCommand, CanExecute = {IsCanExecute.Value}\\}")]
public class ObservableCommand : ObservableClass<ObservableCommand>, ICommand
{
bool _isCanExecute = true;
/// <summary>
/// 能执行的属性
/// </summary>
public bool IsCanExecute
{
get => _isCanExecute;
set => SetProperty(ref _isCanExecute, value);
}
bool _currentCanExecute = true;
/// <summary>
/// 当前可执行状态
/// <para>
/// 在执行异步事件时会强制为 <see langword="false"/>, 但异步结束后会恢复为 <see cref="IsCanExecute"/> 的值
/// </para>
/// </summary>
public bool CurrentCanExecute
{
get => _currentCanExecute;
private set => SetProperty(ref _currentCanExecute, value);
}
/// <inheritdoc/>
public ObservableCommand()
{
PropertyChanged += OnCanExecuteChanged;
}
private void OnCanExecuteChanged(object? sender, PropertyChangedEventArgs e)
{
CanExecuteChanged?.Invoke(this, new());
}
#region ICommand
/// <summary>
/// 能否被执行
/// </summary>
/// <param name="parameter">参数</param>
/// <returns>能被执行为 <see langword="true"/> 否则为 <see langword="false"/></returns>
public bool CanExecute(object? parameter)
{
return CurrentCanExecute && IsCanExecute;
}
/// <summary>
/// 执行方法
/// </summary>
/// <param name="parameter">参数</param>
public async void Execute(object? parameter)
{
if (IsCanExecute is not true)
return;
ExecuteCommand?.Invoke();
await ExecuteAsync();
}
/// <summary>
/// 执行异步方法, 会在等待中修改 <see cref="CurrentCanExecute"/>, 完成后恢复
/// <para>
/// 若要在执行此方法时触发 <see cref="ExecuteCommand"/> 事件, 请将 <paramref name="runAlone"/> 设置为 <see langword="true"/>
/// </para>
/// </summary>
/// <param name="runAlone">设置为 <see langword="true"/> 时触发 <see cref="ExecuteCommand"/> 事件</param>
/// <returns>任务</returns>
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<ExecuteAsyncEventHandler>()
)
await asyncEvent.Invoke();
CurrentCanExecute = true;
}
#endregion
#region Event
/// <summary>
/// 能否执行属性改变后事件
/// </summary>
public event EventHandler? CanExecuteChanged;
/// <summary>
/// 执行事件
/// </summary>
public event ExecuteEventHandler? ExecuteCommand;
/// <summary>
/// 异步执行事件
/// </summary>
public event ExecuteAsyncEventHandler? ExecuteAsyncCommand;
#endregion
}

View File

@ -0,0 +1,116 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Windows.Input;
namespace HKW.HKWUtils.Observable;
/// <summary>
/// 具有参数的可观察命令
/// </summary>
[DebuggerDisplay("\\{ObservableCommand, CanExecute = {IsCanExecute.Value}\\}")]
public class ObservableCommand<T> : ObservableClass<ObservableCommand>, ICommand
{
bool _isCanExecute = true;
/// <summary>
/// 能执行的属性
/// </summary>
public bool IsCanExecute
{
get => _isCanExecute;
set => SetProperty(ref _isCanExecute, value);
}
bool _currentCanExecute = true;
/// <summary>
/// 当前可执行状态
/// <para>
/// 在执行异步事件时会强制为 <see langword="false"/>, 但异步结束后会恢复为 <see cref="IsCanExecute"/> 的值
/// </para>
/// </summary>
public bool CurrentCanExecute
{
get => _currentCanExecute;
private set => SetProperty(ref _currentCanExecute, value);
}
/// <inheritdoc/>
public ObservableCommand()
{
PropertyChanged += OnCanExecuteChanged;
}
private void OnCanExecuteChanged(object? sender, PropertyChangedEventArgs e)
{
CanExecuteChanged?.Invoke(this, new());
}
#region ICommand
/// <summary>
/// 能否被执行
/// </summary>
/// <param name="parameter">参数</param>
/// <returns>能被执行为 <see langword="true"/> 否则为 <see langword="false"/></returns>
public bool CanExecute(object? parameter)
{
return CurrentCanExecute && IsCanExecute;
}
/// <summary>
/// 执行方法
/// </summary>
/// <param name="parameter">参数</param>
public async void Execute(object? parameter)
{
if (IsCanExecute is not true)
return;
ExecuteCommand?.Invoke((T)parameter!);
await ExecuteAsync((T)parameter!);
}
/// <summary>
/// 执行异步方法, 会在等待中修改 <see cref="CurrentCanExecute"/>, 完成后恢复
/// <para>
/// 若要在执行此方法时触发 <see cref="ExecuteCommand"/> 事件, 请将 <paramref name="runAlone"/> 设置为 <see langword="true"/>
/// </para>
/// </summary>
/// <param name="parameter">参数</param>
/// <param name="runAlone">设置为 <see langword="true"/> 时触发 <see cref="ExecuteCommand"/> 事件</param>
/// <returns>任务</returns>
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<ExecuteAsyncEventHandler<T>>()
)
await asyncEvent.Invoke(parameter);
CurrentCanExecute = true;
}
#endregion
#region Event
/// <summary>
/// 能否执行属性改变后事件
/// </summary>
public event EventHandler? CanExecuteChanged;
/// <summary>
/// 执行事件
/// </summary>
public event ExecuteEventHandler<T>? ExecuteCommand;
/// <summary>
/// 异步执行事件
/// </summary>
public event ExecuteAsyncEventHandler<T>? ExecuteAsyncCommand;
#endregion
}

136
VPet.Solution/Styles.xaml Normal file
View File

@ -0,0 +1,136 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:pu="https://opensource.panuon.com/wpf-ui">
<Style
x:Key="AddButton"
BasedOn="{StaticResource {x:Type Button}}"
TargetType="Button">
<Setter Property="Background" Value="{StaticResource DARKPrimary}" />
<Setter Property="pu:ButtonHelper.ShadowColor" Value="{StaticResource ShadowColor}" />
<Setter Property="FontSize" Value="24" />
<Setter Property="Padding" Value="10" />
<Setter Property="Margin" Value="10" />
<Setter Property="pu:ButtonHelper.CornerRadius" Value="24" />
</Style>
<Style
x:Key="Button_Cancel"
BasedOn="{StaticResource ThemedButtonStyle}"
TargetType="Button">
<Setter Property="Background" Value="{StaticResource PrimaryLight}" />
<Setter Property="Foreground" Value="{StaticResource SecondaryText}" />
</Style>
<Style
x:Key="Button_HiddenOnTagNull"
BasedOn="{StaticResource ThemedButtonStyle}"
TargetType="Button">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Mode=Self}}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style
x:Key="Button_CollapsedOnTagNull"
BasedOn="{StaticResource ThemedButtonStyle}"
TargetType="Button">
<Setter Property="Visibility" Value="Collapsed" />
<Style.Triggers>
<DataTrigger Binding="{Binding Tag, RelativeSource={RelativeSource Mode=Self}}" Value="{x:Null}">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
<Style
x:Key="TextBlock_Wrap"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="ToolTip" Value="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" />
</Style>
<Style
x:Key="TextBlock_Center"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="ToolTip" Value="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" />
</Style>
<Style
x:Key="TextBlock_LeftCenter"
BasedOn="{StaticResource {x:Type TextBlock}}"
TargetType="TextBlock">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="ToolTip" Value="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" />
</Style>
<Style
x:Key="TextBox_Wrap"
BasedOn="{StaticResource {x:Type TextBox}}"
TargetType="TextBox">
<Setter Property="TextWrapping" Value="Wrap" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="ToolTip" Value="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" />
</Style>
<Style
x:Key="TextBox_Center"
BasedOn="{StaticResource {x:Type TextBox}}"
TargetType="TextBox">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="ToolTip" Value="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" />
</Style>
<Style
x:Key="TextBox_LeftCenter"
BasedOn="{StaticResource {x:Type TextBox}}"
TargetType="TextBox">
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="ToolTip" Value="{Binding Text, RelativeSource={RelativeSource Mode=Self}}" />
</Style>
<Style x:Key="WindowXStyle" TargetType="pu:WindowX">
<Setter Property="pu:WindowXCaption.Background" Value="{DynamicResource DARKPrimary}" />
<Setter Property="pu:WindowXCaption.Foreground" Value="{DynamicResource DARKPrimaryText}" />
</Style>
<Style
x:Key="Menu_Style"
BasedOn="{StaticResource {x:Static pu:StyleKeys.MenuStyle}}"
TargetType="Menu">
<Setter Property="Height" Value="NaN" />
<Setter Property="Margin" Value="0" />
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Foreground" Value="{DynamicResource DARKPrimaryText}" />
<Setter Property="Background" Value="{DynamicResource DARKPrimary}" />
<Setter Property="pu:MenuHelper.CornerRadius" Value="4" />
<Setter Property="pu:DropDownHelper.CornerRadius" Value="4" />
<!-- -->
<Setter Property="pu:MenuHelper.TopLevelItemsBorderThickness" Value="0" />
<Setter Property="pu:MenuHelper.TopLevelItemsPadding" Value="5,0,5,0" />
<Setter Property="pu:MenuHelper.TopLevelItemsMargin" Value="0" />
<Setter Property="pu:MenuHelper.TopLevelItemsHorizontalContentAlignment" Value="Center" />
<Setter Property="pu:MenuHelper.TopLevelItemsBackground" Value="{DynamicResource DARKPrimary}" />
<Setter Property="pu:MenuHelper.TopLevelItemsForeground" Value="{DynamicResource DARKPrimaryText}" />
<!-- -->
<Setter Property="pu:MenuHelper.SubmenuItemsBorderThickness" Value="0" />
<Setter Property="pu:MenuHelper.SubmenuItemsBorderBrush" Value="{DynamicResource DARKPrimary}" />
<Setter Property="pu:MenuHelper.SubmenuItemsWidth" Value="NaN" />
<Setter Property="pu:MenuHelper.SubmenuItemsHeight" Value="NaN" />
<Setter Property="pu:MenuHelper.SubmenuItemsPadding" Value="5" />
<Setter Property="pu:MenuHelper.SubmenuItemsMargin" Value="0" />
<!--<Setter Property="pu:MenuHelper.SubmenuItemsCornerRadius" Value="4" />-->
<Setter Property="pu:MenuHelper.SubmenuItemsBackground" Value="{DynamicResource DARKPrimary}" />
<Setter Property="pu:MenuHelper.SubmenuItemsForeground" Value="{DynamicResource DARKPrimaryText}" />
<!--<Setter Property="Width" Value="NaN" />
<Setter Property="Height" Value="NaN" />
<Setter Property="Padding" Value="5" />
<Setter Property="Background" Value="{DynamicResource BackgroundColor}" />
<Setter Property="Foreground" Value="{DynamicResource ForegroundColor}" />
<Setter Property="FontSize" Value="{DynamicResource BodyFontSize}" />
<Setter Property="pu:MenuItemHelper.HoverBackground" Value="{DynamicResource HoverColor}" />
<Setter Property="pu:MenuItemHelper.ClickBackground" Value="{DynamicResource ClickColor}" />
<Setter Property="pu:MenuItemHelper.CheckedBackground" Value="{DynamicResource CheckedColor}" />-->
</Style>
</ResourceDictionary>

View File

@ -0,0 +1,58 @@
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ll="clr-namespace:LinePutScript.Localization.WPF;assembly=LinePutScript.Localization.WPF"
xmlns:pu="https://opensource.panuon.com/wpf-ui">
<ContextMenu x:Key="ContextMenu_DataGridRow" x:Shared="false">
<MenuItem
Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource AncestorType=Page, Mode=FindAncestor}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGridRow, Mode=FindAncestor}}"
Header="{ll:Str 修改}" />
<MenuItem
Command="{Binding DataContext.RemoveCommand, RelativeSource={RelativeSource AncestorType=Page, Mode=FindAncestor}}"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=DataGridRow, Mode=FindAncestor}}"
Header="{ll:Str 删除}" />
</ContextMenu>
<ControlTemplate x:Key="ListBoxItem_RangeData" TargetType="ListBoxItem">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<Label HorizontalContentAlignment="Center" Content="{Binding Tag, RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}" />
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Content="{ll:Str 最小值}" />
<pu:NumberInput
Grid.Column="1"
ToolTip="{Binding Value, RelativeSource={RelativeSource Mode=Self}}"
Value="{Binding DataContext.Min.Value, RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}" />
<Label Grid.Row="1" Content="{ll:Str 最大值}" />
<pu:NumberInput
Grid.Row="1"
Grid.Column="1"
ToolTip="{Binding Value, RelativeSource={RelativeSource Mode=Self}}"
Value="{Binding DataContext.Max.Value, RelativeSource={RelativeSource AncestorType=ListBoxItem, Mode=FindAncestor}}" />
</Grid>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ListBox_ShowLangs" TargetType="ListBox">
<ListBox
ItemsSource="{Binding I18nData.CultureNames}"
ScrollViewer.VerticalScrollBarVisibility="Auto"
SelectedItem="{Binding I18nData.CultureName.Value}">
<ListBox.ItemContainerStyle>
<Style BasedOn="{StaticResource {x:Type ListBoxItem}}" TargetType="ListBoxItem">
<Setter Property="Content" Value="{Binding}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</ControlTemplate>
</ResourceDictionary>

6
VPet.Solution/Usings.cs Normal file
View File

@ -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;

View File

@ -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;
/// <summary>
/// 拓展
/// </summary>
public static class Extensions
{
/// <summary>
///
/// </summary>
/// <param name="source"></param>
/// <param name="value"></param>
/// <param name="comparisonType"></param>
/// <returns></returns>
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;
//}
/// <summary>
/// 关闭流
/// </summary>
/// <param name="source">图像资源</param>
public static void CloseStream(this ImageSource source)
{
if (source is not BitmapImage image)
return;
image.StreamSource?.Close();
}
/// <summary>
/// 图像复制
/// </summary>
/// <param name="image">图像</param>
/// <returns>复制的图像</returns>
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;
}
/// <summary>
/// 保存至Png图片
/// </summary>
/// <param name="image">图片资源</param>
/// <param name="path">路径</param>
//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);
}
/// <summary>
/// 尝试添加
/// </summary>
/// <typeparam name="TKey">键类型</typeparam>
/// <typeparam name="TValue"></typeparam>
/// <param name="dictionary"></param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <returns>成功为 <see langword="true"/> 失败为 <see langword="false"/></returns>
public static bool TryAdd<TKey, TValue>(
this IDictionary<TKey, TValue> dictionary,
TKey key,
TValue value
)
{
if (dictionary.ContainsKey(key))
return false;
dictionary.Add(key, value);
return true;
}
/// <summary>
/// 流内容对比
/// </summary>
/// <param name="source">原始流</param>
/// <param name="target">目标流</param>
/// <param name="bufferLength">缓冲区大小 (越大速度越快(流内容越大效果越明显), 但会提高内存占用 (bufferSize = bufferLength * sizeof(long) * 2))</param>
/// <returns>内容相同为 <see langword="true"/> 否则为 <see langword="false"/></returns>
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<T>(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<T>(child) is T childItem)
return childItem;
}
return null;
}
public static T FindParent<T>(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}]";
}
/// <summary>
/// 尝试使用索引获取值
/// </summary>
/// <typeparam name="T">值类型</typeparam>
/// <param name="list">列表</param>
/// <param name="index">索引</param>
/// <param name="value">值</param>
/// <returns>成功为 <see langword="true"/> 失败为 <see langword="false"/></returns>
public static bool TryGetValue<T>(this IList<T> list, int index, out T value)
{
value = default;
if (index < 0 || index >= list.Count)
return false;
value = list[index];
return true;
}
/// <summary>
/// 尝试使用索引获取值
/// </summary>
/// <typeparam name="T">值类型</typeparam>
/// <param name="list">列表</param>
/// <param name="index">索引</param>
/// <param name="value">值</param>
/// <returns>成功为 <see langword="true"/> 失败为 <see langword="false"/></returns>
public static bool TryGetValue<T>(this IList list, int index, out object value)
{
value = default;
if (index < 0 || index >= list.Count)
return false;
value = list[index];
return true;
}
/// <summary>
/// 获取目标
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="weakReference">弱引用</param>
/// <returns>获取成功返回目标值, 获取失败则返回 <see langword="null"/></returns>
public static T? GetTarget<T>(this WeakReference<T> weakReference)
where T : class
{
return weakReference.TryGetTarget(out var t) ? t : null;
}
/// <summary>
/// 枚举出带有索引值的枚举值
/// </summary>
/// <typeparam name="T">值类型</typeparam>
/// <param name="collection">集合</param>
/// <returns>带有索引的枚举值</returns>
public static IEnumerable<ItemInfo<T>> Enumerate<T>(this IEnumerable<T> collection)
{
var index = 0;
foreach (var item in collection)
yield return new(index++, item);
}
public static void SetDataContext<T>(this Window window)
where T : new()
{
window.DataContext = new T();
window.Closed += (s, e) =>
{
try
{
window.DataContext = null;
}
catch { }
};
}
}
/// <summary>
/// 项信息
/// </summary>
/// <typeparam name="T"></typeparam>
[DebuggerDisplay("[{Index}, {Value}]")]
public readonly struct ItemInfo<T>
{
/// <summary>
/// 索引值
/// </summary>
public int Index { get; }
/// <summary>
/// 值
/// </summary>
public T Value { get; }
/// <inheritdoc/>
/// <param name="value">值</param>
/// <param name="index">索引值</param>
public ItemInfo(int index, T value)
{
Index = index;
Value = value;
}
/// <inheritdoc/>
public override string ToString()
{
return $"[{Index}, {Value}]";
}
}

View File

@ -0,0 +1,66 @@
namespace HKW.HKWUtils;
/// <summary>
/// 哈希值
/// </summary>
public class HashCode
{
/// <summary>
/// 默认种子
/// </summary>
public const int DefaultSeed = 114514;
/// <summary>
/// 默认系数
/// </summary>
public const int DefaultFactor = 1919810;
/// <summary>
/// 组合哈希值
/// </summary>
/// <param name="values">值</param>
/// <returns>组合的哈希值</returns>
public static int Combine(params object[] values)
{
return CustomHash(DefaultSeed, DefaultFactor, values.Select(v => v.GetHashCode()));
}
/// <summary>
/// 组合哈希值
/// </summary>
/// <param name="seed">种子</param>
/// <param name="factor">系数</param>
/// <param name="values">值</param>
/// <returns>组合的哈希值</returns>
public static int Combine(int seed, int factor, params object[] values)
{
return CustomHash(seed, factor, values.Select(v => v.GetHashCode()));
}
/// <summary>
/// 自定义组合哈希
/// </summary>
/// <param name="seed">种子</param>
/// <param name="factor">系数</param>
/// <param name="collection">哈希集合</param>
/// <returns>组合的哈希</returns>
public static int CustomHash(int seed, int factor, IEnumerable<int> collection)
{
int hash = seed;
foreach (int i in collection)
hash = unchecked((hash * factor) + i);
return hash;
}
/// <summary>
/// 自定义组合哈希
/// </summary>
/// <param name="seed">种子</param>
/// <param name="factor">系数</param>
/// <param name="values">哈希集合</param>
/// <returns>组合的哈希</returns>
public static int CustomHash(int seed, int factor, params int[] values)
{
return CustomHash(seed, factor, collection: values);
}
}

View File

@ -0,0 +1,72 @@
namespace HKW.HKWUtils;
/// <summary>
/// 可观察的枚举标签模型
/// </summary>
/// <typeparam name="T">枚举类型</typeparam>
public class ObservableEnumFlags<T> : ObservableClass<ObservableEnumFlags<T>>
where T : Enum
{
private T _EnumValue;
public T EnumValue
{
get => _EnumValue;
set => SetProperty(ref _EnumValue, value);
}
/// <summary>
/// 添加枚举命令
/// </summary>
public ObservableCommand<T> AddCommand { get; } = new();
/// <summary>
/// 删除枚举命令
/// </summary>
public ObservableCommand<T> RemoveCommand { get; } = new();
/// <summary>
/// 枚举类型
/// </summary>
public Type EnumType = typeof(T);
/// <summary>
/// 枚举基类
/// </summary>
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}");
}
}

View File

@ -0,0 +1,77 @@
namespace HKW.HKWUtils;
/// <summary>
/// 可观察地点
/// </summary>
/// <typeparam name="T">类型</typeparam>
public class ObservablePoint<T>
: ObservableClass<ObservablePoint<T>>,
IEquatable<ObservablePoint<T>>
{
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;
}
/// <summary>
/// 复制一个新的对象
/// </summary>
/// <returns>新对象</returns>
public ObservablePoint<T> Copy()
{
return new(X, Y);
}
#region Other
/// <inheritdoc/>
public override int GetHashCode()
{
return HashCode.Combine(X, Y);
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is ObservablePoint<T> temp
&& EqualityComparer<T>.Default.Equals(X, temp.X)
&& EqualityComparer<T>.Default.Equals(Y, temp.Y);
}
/// <inheritdoc/>
public bool Equals(ObservablePoint<T>? other)
{
return Equals(obj: other);
}
/// <inheritdoc/>
public static bool operator ==(ObservablePoint<T> a, ObservablePoint<T> b)
{
return Equals(a, b);
}
/// <inheritdoc/>
public static bool operator !=(ObservablePoint<T> a, ObservablePoint<T> b)
{
return Equals(a, b) is not true;
}
#endregion
}

View File

@ -0,0 +1,77 @@
namespace HKW.HKWUtils;
/// <summary>
/// 可观察的范围
/// </summary>
/// <typeparam name="T">类型</typeparam>
public class ObservableRange<T>
: ObservableClass<ObservableRange<T>>,
IEquatable<ObservableRange<T>>
{
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;
}
/// <summary>
/// 复制一个新的对象
/// </summary>
/// <returns>新对象</returns>
public ObservableRange<T> Copy()
{
return new(Min, Max);
}
#region Other
/// <inheritdoc/>
public override int GetHashCode()
{
return HashCode.Combine(Min, Max);
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is ObservableRange<T> temp
&& EqualityComparer<T>.Default.Equals(Min, temp.Min)
&& EqualityComparer<T>.Default.Equals(Max, temp.Max);
}
/// <inheritdoc/>
public bool Equals(ObservableRange<T>? other)
{
return Equals(obj: other);
}
/// <inheritdoc/>
public static bool operator ==(ObservableRange<T> a, ObservableRange<T> b)
{
return Equals(a, b);
}
/// <inheritdoc/>
public static bool operator !=(ObservableRange<T> a, ObservableRange<T> b)
{
return Equals(a, b) is not true;
}
#endregion
}

View File

@ -0,0 +1,89 @@
namespace HKW.HKWUtils;
public class ObservableRect<T> : ObservableClass<ObservableRect<T>>, IEquatable<ObservableRect<T>>
{
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;
}
/// <summary>
/// 复制一个新的对象
/// </summary>
/// <returns>新对象</returns>
public ObservableRect<T> Copy()
{
return new(X, Y, Width, Height);
}
#region Other
/// <inheritdoc/>
public override int GetHashCode()
{
return HashCode.Combine(X, Y, Width, Height);
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is ObservableRect<T> temp
&& EqualityComparer<T>.Default.Equals(X, temp.X)
&& EqualityComparer<T>.Default.Equals(Y, temp.Y)
&& EqualityComparer<T>.Default.Equals(Width, temp.Width)
&& EqualityComparer<T>.Default.Equals(Height, temp.Height);
}
/// <inheritdoc/>
public bool Equals(ObservableRect<T>? other)
{
return Equals(obj: other);
}
/// <inheritdoc/>
public static bool operator ==(ObservableRect<T> a, ObservableRect<T> b)
{
return Equals(a, b);
}
/// <inheritdoc/>
public static bool operator !=(ObservableRect<T> a, ObservableRect<T> b)
{
return Equals(a, b) is not true;
}
#endregion
}

View File

@ -0,0 +1,79 @@
using System.Windows.Media.Imaging;
namespace HKW.HKWUtils;
/// <summary>
/// 工具
/// </summary>
public static class Utils
{
/// <summary>
/// 解码像素宽度
/// </summary>
public const int DecodePixelWidth = 250;
/// <summary>
/// 解码像素高度
/// </summary>
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;
//}
/// <summary>
/// 载入图片至内存流
/// </summary>
/// <param name="imagePath">图片路径</param>
/// <returns></returns>
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;
}
/// <summary>
/// 载入图片至内存流
/// </summary>
/// <param name="imageStream">图片流</param>
/// <returns></returns>
public static BitmapImage LoadImageToMemoryStream(Stream imageStream)
{
BitmapImage bitmapImage = new();
bitmapImage.BeginInit();
try
{
bitmapImage.StreamSource = imageStream;
bitmapImage.DecodePixelWidth = DecodePixelWidth;
}
finally
{
bitmapImage.EndInit();
}
return bitmapImage;
}
}

View File

@ -14,6 +14,8 @@
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -39,7 +41,8 @@
<HintPath>..\packages\LinePutScript.1.9.2\lib\net462\LinePutScript.dll</HintPath>
</Reference>
<Reference Include="LinePutScript.Localization.WPF, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\LinePutScript.Localization.WPF.1.0.6\lib\net462\LinePutScript.Localization.WPF.dll</HintPath>
<HintPath>
..\packages\LinePutScript.Localization.WPF.1.0.6\lib\net462\LinePutScript.Localization.WPF.dll</HintPath>
</Reference>
<Reference Include="Panuon.WPF, Version=1.0.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\Panuon.WPF.1.0.2\lib\net462\Panuon.WPF.dll</HintPath>
@ -49,6 +52,7 @@
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Xml" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Core" />
@ -67,7 +71,7 @@
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</ApplicationDefinition>
<Page Include="MainWindow.xaml">
<Page Include="Views\MainWindow.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
</Page>
@ -75,9 +79,50 @@
<DependentUpon>App.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="MainWindow.xaml.cs">
<Compile Include="ViewModels\MainWindowVM.cs" />
<Compile Include="Views\MainWindow.xaml.cs">
<DependentUpon>MainWindow.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="Converters\BrushToMediaColorConverter.cs" />
<Compile Include="Converters\CalculatorConverter.cs" />
<Compile Include="Converters\EqualsConverter.cs" />
<Compile Include="Converters\FalseToCollapsedConverter.cs" />
<Compile Include="Converters\FalseToHiddenConverter.cs" />
<Compile Include="Converters\MediaColorToBrushConverter.cs" />
<Compile Include="Converters\NotEqualsConverter.cs" />
<Compile Include="Converters\NullToFalseConverter.cs" />
<Compile Include="Converters\StringFormatConverter.cs" />
<Compile Include="Converters\RatioMarginConverter.cs" />
<Compile Include="Converters\MaxConverter.cs" />
<Compile Include="Converters\MarginConverter.cs" />
<Compile Include="SimpleObservable\ObservableClass\INotifyPropertyChangedX.cs" />
<Compile Include="SimpleObservable\ObservableClass\INotifyPropertyChangingX.cs" />
<Compile Include="SimpleObservable\ObservableClass\ObservableClass.cs" />
<Compile Include="SimpleObservable\ObservableClass\PropertyChangedXEventArgs.cs" />
<Compile Include="SimpleObservable\ObservableClass\PropertyChangedXEventHandler.cs" />
<Compile Include="SimpleObservable\ObservableClass\PropertyChangingXEventArgs.cs" />
<Compile Include="SimpleObservable\ObservableClass\PropertyChangingXEventHandler.cs" />
<Compile Include="SimpleObservable\ObservableCommand\ExecuteAsyncEventHandler.cs" />
<Compile Include="SimpleObservable\ObservableCommand\ExecuteEventHandler.cs" />
<Compile Include="SimpleObservable\ObservableCommand\NotifyReceivedEventHandler.cs" />
<Compile Include="SimpleObservable\ObservableCommand\ObservableCommand.cs" />
<Compile Include="SimpleObservable\ObservableCommand\ObservableCommandT.cs" />
<Compile Include="Utils\ObservableEnumFlags.cs" />
<Compile Include="Utils\Expansions.cs" />
<Compile Include="Utils\HashCode.cs" />
<Compile Include="Utils\ObservableRange.cs" />
<Compile Include="Utils\ObservablePoint.cs" />
<Compile Include="Utils\ObservableRect.cs" />
<Compile Include="Utils\Utils.cs" />
<Compile Include="Resources\NativeResources.cs" />
<Compile Include="Usings.cs" />
<Page Include="NativeStyles.xaml">
<Generator>MSBuild:Compile</Generator>
<SubType>Designer</SubType>
<ContainsDesignTimeResources>true</ContainsDesignTimeResources>
</Page>
<Compile Include="NativeStyles.xaml.cs">
<DependentUpon>NativeStyles.xaml</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
@ -117,5 +162,8 @@
<Name>VPet-Simulator.Windows.Interface</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View File

@ -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<MainWindowVM>
{
public MainWindowVM() { }
public static void LoadSettings(string path)
{
foreach (var file in Directory.EnumerateFiles(path))
{
var setting = new Setting(path);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
using Panuon.WPF.UI;
namespace VPet.Solution;
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : WindowX
{
public MainWindow()
{
if (App.IsDone)
{
Close();
return;
}
InitializeComponent();
}
}