use Microsoft.Windows.CsWin32 to replace old p/invoke code

This commit is contained in:
DismissedLight
2022-08-02 10:34:07 +08:00
parent acb59488fb
commit 6d94ab9bd7
27 changed files with 204 additions and 490 deletions

View File

@@ -0,0 +1,5 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"allowMarshaling": true,
"public": true
}

View File

@@ -0,0 +1,16 @@
// ComCtl32
SetWindowSubclass
RemoveWindowSubclass
DefSubclassProc
// User32
SetWindowText
GetDpiForWindow
GetWindowPlacement
SetWindowPlacement
// Type definition
MINMAXINFO
// Const value
WM_GETMINMAXINFO

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.10-beta">
<PrivateAssets>all</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>

View File

@@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsUI", "SettingsUI\Se
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snap.Hutao.Win32", "Snap.Hutao.Win32\Snap.Hutao.Win32.csproj", "{29209B14-A6E1-442E-9287-2C65B03C96CD}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -82,6 +84,22 @@ Global
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU
{8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|arm64.ActiveCfg = Debug|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|arm64.Build.0 = Debug|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x64.ActiveCfg = Debug|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x64.Build.0 = Debug|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x86.ActiveCfg = Debug|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x86.Build.0 = Debug|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|Any CPU.Build.0 = Release|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|arm64.ActiveCfg = Release|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|arm64.Build.0 = Release|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x64.ActiveCfg = Release|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x64.Build.0 = Release|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x86.ActiveCfg = Release|Any CPU
{29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@@ -28,7 +28,6 @@ public partial class App : Application
/// </summary>
public App()
{
AppInstance.GetCurrent().Activated += OnActivated;
// load app resource
InitializeComponent();
InitializeDependencyInjection();
@@ -79,14 +78,10 @@ public partial class App : Application
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
AppInstance firstInstance = AppInstance.FindOrRegisterForKey("main");
if (!firstInstance.IsCurrent)
{
// Redirect the activation (and args) to the "main" instance, and exit.
await firstInstance.RedirectActivationToAsync(activatedEventArgs);
Process.GetCurrentProcess().Kill();
}
else
if (firstInstance.IsCurrent)
{
firstInstance.Activated += OnActivated;
Window = Ioc.Default.GetRequiredService<MainWindow>();
Window.Activate();
@@ -94,10 +89,16 @@ public partial class App : Application
Ioc.Default
.GetRequiredService<IMetadataService>()
.As<IMetadataInitializer>()?
.ImplictAs<IMetadataInitializer>()?
.InitializeInternalAsync()
.SafeForget(logger);
}
else
{
// Redirect the activation (and args) to the "main" instance, and exit.
await firstInstance.RedirectActivationToAsync(activatedEventArgs);
Process.GetCurrentProcess().Kill();
}
}
private static void InitializeDependencyInjection()

View File

@@ -30,12 +30,6 @@ public class SystemBackdrop
this.window = window;
}
private enum BackDropType
{
None,
Mica,
}
/// <summary>
/// 尝试设置背景
/// </summary>
@@ -63,8 +57,7 @@ public class SystemBackdrop
backdropController = new MicaController();
ICompositionSupportsSystemBackdrop target = window.As<ICompositionSupportsSystemBackdrop>();
backdropController.AddSystemBackdropTarget(target);
backdropController.AddSystemBackdropTarget(window.As<ICompositionSupportsSystemBackdrop>());
backdropController.SetSystemBackdropConfiguration(configurationSource);
return true;

View File

@@ -64,18 +64,11 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
/// <param name="storageFile">文件</param>
/// <param name="token">取消令牌</param>
/// <returns>加载的图像表面</returns>
protected virtual async Task<LoadedImageSurface?> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
protected virtual async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
{
try
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token))
{
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token))
{
return LoadedImageSurface.StartLoadFromStream(imageStream);
}
}
catch (COMException ex) when (ex.Is(COMError.WINCODEC_ERR_COMPONENTNOTFOUND))
{
return null;
return LoadedImageSurface.StartLoadFromStream(imageStream);
}
}
@@ -130,7 +123,19 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor;
if (await LoadImageSurfaceAsync(storageFile, token) is LoadedImageSurface imageSurface)
LoadedImageSurface? imageSurface = null;
try
{
imageSurface = await LoadImageSurfaceAsync(storageFile, token);
}
catch (COMException ex) when (ex.Is(COMError.WINCODEC_ERR_COMPONENTNOTFOUND))
{
// Image is broken, remove it
await imageCache.RemoveAsync(uri.Enumerate());
}
if (imageSurface != null)
{
spriteVisual = CompositeSpriteVisual(compositor, imageSurface);
OnUpdateVisual(spriteVisual);
@@ -139,11 +144,6 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
await AnimationBuilder.Create().Opacity(1d).StartAsync(this, token);
}
else
{
// Image is broken, remove it
await imageCache.RemoveAsync(uri.Enumerate());
}
}
}

View File

@@ -30,7 +30,7 @@ public class Gradient : CompositionImage
}
/// <inheritdoc/>
protected override async Task<LoadedImageSurface?> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
protected override async Task<LoadedImageSurface> LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token)
{
using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token))
{

View File

@@ -1,30 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.InteropServices;
namespace Snap.Hutao.Core.Win32;
[SuppressMessage("", "SA1600")]
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
public POINT(int x, int y)
{
X = x;
Y = y;
}
public static implicit operator System.Drawing.Point(POINT p)
{
return new System.Drawing.Point(p.X, p.Y);
}
public static implicit operator POINT(System.Drawing.Point p)
{
return new POINT(p.X, p.Y);
}
}

View File

@@ -1,134 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Globalization;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Core.Win32;
[SuppressMessage("", "SA1132")]
[SuppressMessage("", "SA1600")]
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left, Top, Right, Bottom;
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public RECT(System.Drawing.Rectangle r)
: this(r.Left, r.Top, r.Right, r.Bottom)
{
}
public int X
{
get => Left;
set
{
Right -= Left - value;
Left = value;
}
}
public int Y
{
get => Top;
set
{
Bottom -= Top - value;
Top = value;
}
}
public int Height
{
get => Bottom - Top;
set => Bottom = value + Top;
}
public int Width
{
get => Right - Left;
set => Right = value + Left;
}
public long Area
{
get => Math.BigMul(Width, Height);
}
public System.Drawing.Point Location
{
get => new(Left, Top);
set
{
X = value.X;
Y = value.Y;
}
}
public System.Drawing.Size Size
{
get => new(Width, Height);
set
{
Width = value.Width;
Height = value.Height;
}
}
public static implicit operator System.Drawing.Rectangle(RECT r)
{
return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height);
}
public static implicit operator RECT(System.Drawing.Rectangle r)
{
return new RECT(r);
}
public static bool operator ==(RECT r1, RECT r2)
{
return r1.Equals(r2);
}
public static bool operator !=(RECT r1, RECT r2)
{
return !r1.Equals(r2);
}
public bool Equals(RECT r)
{
return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom;
}
public override bool Equals(object? obj)
{
if (obj is RECT rect)
{
return Equals(rect);
}
else if (obj is System.Drawing.Rectangle rectangle)
{
return Equals(new RECT(rectangle));
}
return false;
}
public override int GetHashCode()
{
return ((System.Drawing.Rectangle)this).GetHashCode();
}
public override string ToString()
{
return string.Format(CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom);
}
}

View File

@@ -1,89 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Core.Win32;
[SuppressMessage("", "SA1600")]
public enum ShowWindowCommand
{
/// <summary>
/// Hides the window and activates another window.
/// </summary>
Hide = 0,
/// <summary>
/// Activates and displays a window. If the window is minimized or
/// maximized, the system restores it to its original size and position.
/// An application should specify this flag when displaying the window
/// for the first time.
/// </summary>
Normal = 1,
/// <summary>
/// Activates the window and displays it as a minimized window.
/// </summary>
ShowMinimized = 2,
/// <summary>
/// Maximizes the specified window.
/// </summary>
Maximize = 3, // is this the right value?
/// <summary>
/// Activates the window and displays it as a maximized window.
/// </summary>
ShowMaximized = 3,
/// <summary>
/// Displays a window in its most recent size and position. This value
/// is similar to <see cref="Win32.ShowWindowCommand.Normal"/>, except
/// the window is not activated.
/// </summary>
ShowNoActivate = 4,
/// <summary>
/// Activates the window and displays it in its current size and position.
/// </summary>
Show = 5,
/// <summary>
/// Minimizes the specified window and activates the next top-level
/// window in the Z order.
/// </summary>
Minimize = 6,
/// <summary>
/// Displays the window as a minimized window. This value is similar to
/// <see cref="Win32.ShowWindowCommand.ShowMinimized"/>, except the
/// window is not activated.
/// </summary>
ShowMinNoActive = 7,
/// <summary>
/// Displays the window in its current size and position. This value is
/// similar to <see cref="Win32.ShowWindowCommand.Show"/>, except the
/// window is not activated.
/// </summary>
ShowNA = 8,
/// <summary>
/// Activates and displays the window. If the window is minimized or
/// maximized, the system restores it to its original size and position.
/// An application should specify this flag when restoring a minimized window.
/// </summary>
Restore = 9,
/// <summary>
/// Sets the show state based on the SW_* value specified in the
/// STARTUPINFO structure passed to the CreateProcess function by the
/// program that started the application.
/// </summary>
ShowDefault = 10,
/// <summary>
/// <b>Windows 2000/XP:</b> Minimizes a window, even if the thread
/// that owns the window is not responding. This flag should only be
/// used when minimizing windows from a different thread.
/// </summary>
ForceMinimize = 11,
}

View File

@@ -1,65 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.InteropServices;
namespace Snap.Hutao.Core.Win32;
/// <summary>
/// 包含 user32.dll 平台调用的代码
/// </summary>
internal static class User32
{
[SuppressMessage("", "SA1600")]
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
/// <summary>
/// Sets the show state and the restored, minimized, and maximized positions of the specified window.
/// </summary>
/// <param name="hWnd">
/// A handle to the window.
/// </param>
/// <param name="lpwndpl">
/// A pointer to a WINDOWPLACEMENT structure that specifies the new show state and window positions.
/// <para>
/// Before calling SetWindowPlacement, set the length member of the WINDOWPLACEMENT structure to sizeof(WINDOWPLACEMENT). SetWindowPlacement fails if the length member is not set correctly.
/// </para>
/// </param>
/// <returns>
/// If the function succeeds, the return value is nonzero.
/// <para>
/// If the function fails, the return value is zero. To get extended error information, call GetLastError.
/// </para>
/// </returns>
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl);
/// <summary>
/// Changes the text of the specified window's title bar (if it has one). If the specified window is a control, the
/// text of the control is changed. However, SetWindowText cannot change the text of a control in another application.
/// <para>
/// Go to <see href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms633546%28v=vs.85%29.aspx"/> for more
/// information
/// </para>
/// </summary>
/// <param name="hwnd">C++ ( hWnd [in]. Type: HWND )<br />A handle to the window or control whose text is to be changed.</param>
/// <param name="lpString">C++ ( lpString [in, optional]. Type: LPCTSTR )<br />The new title or control text.</param>
/// <returns>
/// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.<br />
/// To get extended error information, call GetLastError.
/// </returns>
/// <remarks>
/// If the target window is owned by the current process, <see cref="SetWindowText" /> causes a WM_SETTEXT message to
/// be sent to the specified window or control. If the control is a list box control created with the WS_CAPTION style,
/// however, <see cref="SetWindowText" /> sets the text for the control, not for the list box entries.<br />To set the
/// text of a control in another process, send the WM_SETTEXT message directly instead of calling
/// <see cref="SetWindowText" />. The <see cref="SetWindowText" /> function does not expand tab characters (ASCII code
/// 0x09). Tab characters are displayed as vertical bar(|) characters.<br />For an example go to
/// <see href="https://msdn.microsoft.com/en-us/library/windows/desktop/ms644928%28v=vs.85%29.aspx#sending">Sending a Message. </see>
/// </remarks>
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool SetWindowText(IntPtr hwnd, string lpString);
}

View File

@@ -1,78 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Runtime.InteropServices;
namespace Snap.Hutao.Core.Win32;
/// <summary>
/// Contains information about the placement of a window on the screen.
/// </summary>
[Serializable]
[StructLayout(LayoutKind.Sequential)]
internal struct WINDOWPLACEMENT
{
/// <summary>
/// The length of the structure, in bytes. Before calling the GetWindowPlacement or SetWindowPlacement functions, set this member to sizeof(WINDOWPLACEMENT).
/// <para>
/// GetWindowPlacement and SetWindowPlacement fail if this member is not set correctly.
/// </para>
/// </summary>
public int Length;
/// <summary>
/// Specifies flags that control the position of the minimized window and the method by which the window is restored.
/// </summary>
public int Flags;
/// <summary>
/// The current show state of the window.
/// </summary>
public ShowWindowCommand ShowCmd;
/// <summary>
/// The coordinates of the window's upper-left corner when the window is minimized.
/// </summary>
public POINT MinPosition;
/// <summary>
/// The coordinates of the window's upper-left corner when the window is maximized.
/// </summary>
public POINT MaxPosition;
/// <summary>
/// The window's coordinates when the window is in the restored position.
/// </summary>
public RECT NormalPosition;
/// <summary>
/// Gets the default (empty) value.
/// </summary>
public static WINDOWPLACEMENT Default
{
get
{
WINDOWPLACEMENT result = default(WINDOWPLACEMENT);
result.Length = Marshal.SizeOf(result);
return result;
}
}
/// <summary>
/// 构造一个新的<see cref="WINDOWPLACEMENT"/>
/// </summary>
/// <param name="max">最大点</param>
/// <param name="normal">正常位置</param>
/// <param name="command">显示命令</param>
/// <returns>窗体位置</returns>
public static WINDOWPLACEMENT Create(POINT max, RECT normal, ShowWindowCommand command)
{
WINDOWPLACEMENT result = Default;
result.MaxPosition = max;
result.NormalPosition = normal;
result.ShowCmd = command;
return result;
}
}

View File

@@ -5,21 +5,33 @@ using Microsoft.UI.Xaml;
using Snap.Hutao.Control.HostBackdrop;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Core.Win32;
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.Shell;
using Windows.Win32.UI.WindowsAndMessaging;
using WinRT.Interop;
namespace Snap.Hutao.Core;
/// <summary>
/// 窗口状态管理器
/// 主要包含了各类 P/Inoke 代码
/// </summary>
internal class WindowManager
{
private readonly IntPtr handle;
private const int MinWidth = 800;
private const int MinHeight = 600;
private readonly HWND handle;
private readonly Window window;
private readonly UIElement titleBar;
private readonly ILogger<WindowManager> logger;
// We have to explictly hold a reference to the SUBCLASSPROC
// otherwise will casuse System.ExecutionEngineException
private SUBCLASSPROC? subClassProc;
/// <summary>
/// 构造一个新的窗口状态管理器
/// </summary>
@@ -30,9 +42,7 @@ internal class WindowManager
this.window = window;
this.titleBar = titleBar;
logger = Ioc.Default.GetRequiredService<ILogger<WindowManager>>();
handle = WindowNative.GetWindowHandle(window);
handle = (HWND)WindowNative.GetWindowHandle(window);
InitializeWindow();
}
@@ -43,18 +53,22 @@ internal class WindowManager
int right = LocalSetting.GetValueType<int>(SettingKeys.WindowRight);
int bottom = LocalSetting.GetValueType<int>(SettingKeys.WindowBottom);
return new RECT(left, top, right, bottom);
return new() { left = left, top = top, right = right, bottom = bottom };
}
private static void SaveWindowRect(IntPtr handle)
private static void SaveWindowRect(HWND handle)
{
WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Default;
User32.GetWindowPlacement(handle, ref windowPlacement);
WINDOWPLACEMENT windowPlacement = new()
{
length = (uint)Marshal.SizeOf<WINDOWPLACEMENT>(),
};
LocalSetting.Set(SettingKeys.WindowLeft, windowPlacement.NormalPosition.Left);
LocalSetting.Set(SettingKeys.WindowTop, windowPlacement.NormalPosition.Top);
LocalSetting.Set(SettingKeys.WindowRight, windowPlacement.NormalPosition.Right);
LocalSetting.Set(SettingKeys.WindowBottom, windowPlacement.NormalPosition.Bottom);
PInvoke.GetWindowPlacement(handle, ref windowPlacement);
LocalSetting.Set(SettingKeys.WindowLeft, windowPlacement.rcNormalPosition.left);
LocalSetting.Set(SettingKeys.WindowTop, windowPlacement.rcNormalPosition.top);
LocalSetting.Set(SettingKeys.WindowRight, windowPlacement.rcNormalPosition.right);
LocalSetting.Set(SettingKeys.WindowBottom, windowPlacement.rcNormalPosition.bottom);
}
private void InitializeWindow()
@@ -63,20 +77,52 @@ internal class WindowManager
window.SetTitleBar(titleBar);
window.Closed += OnWindowClosed;
User32.SetWindowText(handle, "胡桃");
PInvoke.SetWindowText(handle, "胡桃");
RECT rect = RetriveWindowRect();
if (rect.Area > 0)
if ((rect.right - rect.left) * (rect.bottom - rect.top) > 0)
{
WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Create(new POINT(-1, -1), rect, ShowWindowCommand.Normal);
User32.SetWindowPlacement(handle, ref windowPlacement);
WINDOWPLACEMENT windowPlacement = new()
{
length = (uint)Marshal.SizeOf<WINDOWPLACEMENT>(),
showCmd = SHOW_WINDOW_CMD.SW_SHOWNORMAL,
ptMaxPosition = new() { x = -1, y = -1 },
rcNormalPosition = rect,
};
PInvoke.SetWindowPlacement(handle, in windowPlacement);
}
bool micaApplied = new SystemBackdrop(window).TrySetBackdrop();
logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed");
subClassProc = new(OnWindowProcedure);
_ = PInvoke.SetWindowSubclass(handle, subClassProc, 101, 0);
}
private void OnWindowClosed(object sender, WindowEventArgs args)
{
PInvoke.RemoveWindowSubclass(handle, subClassProc, 101);
subClassProc = null;
SaveWindowRect(handle);
}
private LRESULT OnWindowProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
{
switch (uMsg)
{
case PInvoke.WM_GETMINMAXINFO:
{
uint dpi = PInvoke.GetDpiForWindow(handle);
float scalingFactor = dpi / 96f;
MINMAXINFO minMaxInfo = Marshal.PtrToStructure<MINMAXINFO>(lParam);
minMaxInfo.ptMinTrackSize.x = (int)Math.Max(MinWidth * scalingFactor, minMaxInfo.ptMinTrackSize.x);
minMaxInfo.ptMinTrackSize.y = (int)Math.Max(MinHeight * scalingFactor, minMaxInfo.ptMinTrackSize.y);
Marshal.StructureToPtr(minMaxInfo, lParam, true);
break;
}
}
return PInvoke.DefSubclassProc(hwnd, uMsg, wParam, lParam);
}
}

View File

@@ -14,7 +14,7 @@ public static class ObjectExtensions
/// <typeparam name="T">目标转换类型</typeparam>
/// <param name="obj">对象</param>
/// <returns>转换类型后的对象</returns>
public static T? As<T>(this object? obj)
public static T? ImplictAs<T>(this object? obj)
where T : class
{
return obj as T;

View File

@@ -28,15 +28,4 @@ public class User
/// 用户的Cookie
/// </summary>
public string? Cookie { get; set; }
/// <summary>
/// 设置用户的选中状态
/// 同时更新用户选择的角色信息
/// </summary>
/// <param name="user">用户</param>
/// <param name="isSelected">是否选中</param>
public static void SetSelectionState(User user, bool isSelected)
{
user!.IsSelected = isSelected;
}
}

View File

@@ -9,7 +9,7 @@
<Identity
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
Publisher="CN=DGP Studio"
Version="1.0.19.0" />
Version="1.0.20.0" />
<Properties>
<DisplayName>胡桃</DisplayName>

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Context.Database;
using Snap.Hutao.Model.Binding;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Web.Hoyolab.Bbs.User;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
@@ -22,8 +23,8 @@ internal class UserService : IUserService
private readonly UserClient userClient;
private readonly UserGameRoleClient userGameRoleClient;
private Model.Binding.User? currentUser;
private ObservableCollection<Model.Binding.User>? userCollection = null;
private User? currentUser;
private ObservableCollection<User>? userCollection = null;
/// <summary>
/// 构造一个新的用户服务
@@ -39,7 +40,7 @@ internal class UserService : IUserService
}
/// <inheritdoc/>
public Model.Binding.User? CurrentUser
public User? CurrentUser
{
get => currentUser;
set
@@ -68,12 +69,12 @@ internal class UserService : IUserService
}
/// <inheritdoc/>
public async Task<UserAddResult> TryAddUserAsync(Model.Binding.User newUser, string uid)
public async Task<UserAddResult> TryAddUserAsync(User newUser, string uid)
{
Must.NotNull(userCollection!);
// 查找是否有相同的uid
if (userCollection.SingleOrDefault(u => u.UserInfo!.Uid == uid) is Model.Binding.User userWithSameUid)
if (userCollection.SingleOrDefault(u => u.UserInfo!.Uid == uid) is User userWithSameUid)
{
// Prevent users from adding a completely same cookie.
if (userWithSameUid.Cookie == newUser.Cookie)
@@ -94,36 +95,39 @@ internal class UserService : IUserService
{
Verify.Operation(newUser.IsInitialized, "该用户尚未初始化");
// Sync cache
userCollection.Add(newUser);
// Sync database
appDbContext.Users.Add(newUser.Entity);
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
// Sync cache
userCollection.Add(newUser);
return UserAddResult.Added;
}
}
/// <inheritdoc/>
public Task RemoveUserAsync(Model.Binding.User user)
public Task RemoveUserAsync(User user)
{
// Sync cache
userCollection!.Remove(user);
// Sync database
appDbContext.Users.Remove(user.Entity);
return appDbContext.SaveChangesAsync();
}
/// <inheritdoc/>
public async Task<ObservableCollection<Model.Binding.User>> GetUserCollectionAsync()
public async Task<ObservableCollection<User>> GetUserCollectionAsync()
{
if (userCollection == null)
{
List<Model.Binding.User> users = new();
List<User> users = new();
foreach (Model.Entity.User user in appDbContext.Users)
foreach (Model.Entity.User entity in appDbContext.Users)
{
Model.Binding.User? initialized = await Model.Binding.User
.CreateAsync(user, userClient, userGameRoleClient)
User? initialized = await User
.CreateAsync(entity, userClient, userGameRoleClient)
.ConfigureAwait(false);
if (initialized != null)
@@ -133,23 +137,22 @@ internal class UserService : IUserService
else
{
// User is unable to be initialized, remove it.
appDbContext.Users.Remove(user);
appDbContext.Users.Remove(entity);
await appDbContext.SaveChangesAsync().ConfigureAwait(false);
}
}
CurrentUser = users.SingleOrDefault(user => user.IsSelected);
userCollection = new(users);
CurrentUser = users.SingleOrDefault(user => user.IsSelected);
}
return userCollection;
}
/// <inheritdoc/>
public Task<Model.Binding.User?> CreateUserAsync(string cookie)
public Task<User?> CreateUserAsync(string cookie)
{
return Model.Binding.User.CreateAsync(new() { Cookie = cookie }, userClient, userGameRoleClient);
return User.CreateAsync(new() { Cookie = cookie }, userClient, userGameRoleClient);
}
/// <inheritdoc/>

View File

@@ -95,6 +95,7 @@
<ItemGroup>
<ProjectReference Include="..\SettingsUI\SettingsUI.csproj" />
<ProjectReference Include="..\Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="..\Snap.Hutao.Win32\Snap.Hutao.Win32.csproj" />
</ItemGroup>
<!-- Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging

View File

@@ -29,6 +29,6 @@ public sealed partial class MainView : UserControl
navigationService = Ioc.Default.GetRequiredService<INavigationService>();
navigationService.Initialize(NavView, ContentFrame);
navigationService.Navigate<AnnouncementPage>(INavigationAwaiter.Default, true);
//navigationService.Navigate<AnnouncementPage>(INavigationAwaiter.Default, true);
}
}

View File

@@ -8,7 +8,7 @@ using Snap.Hutao.ViewModel;
namespace Snap.Hutao.View.Page;
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// 设置页面
/// </summary>
public sealed partial class SettingPage : Microsoft.UI.Xaml.Controls.Page
{

View File

@@ -8,7 +8,6 @@ using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Metadata.Achievement;
using Snap.Hutao.Service.Metadata;
using System.Collections.Generic;
using System.Linq;
namespace Snap.Hutao.ViewModel;

View File

@@ -100,27 +100,42 @@ internal class WikiAvatarViewModel : ObservableObject
/// <summary>
/// 筛选用元素信息集合
/// </summary>
public IList<Selectable<string>> FilterElementInfos => filterElementInfos;
public IList<Selectable<string>> FilterElementInfos
{
get => filterElementInfos;
}
/// <summary>
/// 筛选用所属国家集合
/// </summary>
public IList<Selectable<Pair<string, string>>> FilterAssociationInfos => filterAssociationInfos;
public IList<Selectable<Pair<string, string>>> FilterAssociationInfos
{
get => filterAssociationInfos;
}
/// <summary>
/// 筛选用武器信息集合
/// </summary>
public IList<Selectable<Pair<string, WeaponType>>> FilterWeaponTypeInfos => filterWeaponTypeInfos;
public IList<Selectable<Pair<string, WeaponType>>> FilterWeaponTypeInfos
{
get => filterWeaponTypeInfos;
}
/// <summary>
/// 筛选用星级信息集合
/// </summary>
public IList<Selectable<Pair<string, ItemQuality>>> FilterQualityInfos => filterQualityInfos;
public IList<Selectable<Pair<string, ItemQuality>>> FilterQualityInfos
{
get => filterQualityInfos;
}
/// <summary>
/// 筛选用体型信息集合
/// </summary>
public IList<Selectable<Pair<string, string>>> FilterBodyInfos => filterBodyInfos;
public IList<Selectable<Pair<string, string>>> FilterBodyInfos
{
get => filterBodyInfos;
}
/// <summary>
/// 打开页面命令

View File

@@ -6,6 +6,7 @@ namespace Snap.Hutao.Web.Hoyolab;
/// <summary>
/// 米哈游Url端点
/// </summary>
[SuppressMessage("", "SA1201")]
internal static class ApiEndpoints
{
/// <summary>
@@ -21,7 +22,13 @@ internal static class ApiEndpoints
/// <summary>
/// 游戏记录主页
/// </summary>
public const string GameRecordIndex = $"{ApiTakumiRecordApi}/index?role_id={{0}}&server={{1}}";
/// <param name="uid">uid</param>
/// <param name="server">服务器区域</param>
/// <returns>游戏记录主页字符串</returns>
public static string GameRecordIndex(string uid, string server)
{
return $"{ApiTakumiRecordApi}/index?role_id={uid}&server={server}";
}
/// <summary>
/// 深渊信息

View File

@@ -55,7 +55,7 @@ internal class GameRecordClient
/// <returns>玩家的基础信息</returns>
public async Task<PlayerInfo?> GetPlayerInfoAsync(User user, PlayerUid uid, CancellationToken token = default)
{
string url = string.Format(ApiEndpoints.GameRecordIndex, uid.Value, uid.Region);
string url = string.Format(ApiEndpoints.GameRecordIndex(uid.Value, uid.Region));
Response<PlayerInfo>? resp = await httpClient
.SetUser(user)

View File

@@ -3,7 +3,6 @@
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Extension;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.Avatar;