From 379d514bb5452e4b9b9014fd4b41028eb976053b Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Sun, 5 Apr 2026 11:15:11 +0800 Subject: [PATCH 001/205] update:net6 --- Ink Canvas/Helpers/OleActiveObject.cs | 26 ++ Ink Canvas/Helpers/PPTManager.cs | 4 +- Ink Canvas/Helpers/PPTROTConnectionHelper.cs | 4 +- Ink Canvas/InkCanvasForClass.csproj | 37 +-- Ink Canvas/packages.lock.json | 261 +++++++++---------- 5 files changed, 163 insertions(+), 169 deletions(-) create mode 100644 Ink Canvas/Helpers/OleActiveObject.cs diff --git a/Ink Canvas/Helpers/OleActiveObject.cs b/Ink Canvas/Helpers/OleActiveObject.cs new file mode 100644 index 00000000..fb7235e2 --- /dev/null +++ b/Ink Canvas/Helpers/OleActiveObject.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace Ink_Canvas.Helpers +{ + /// + /// .NET Core / 5+ 未提供 ,通过 OLE 实现等效行为。 + /// + internal static class OleActiveObject + { + [DllImport("ole32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)] + private static extern int CLSIDFromProgID(string lpszProgId, out Guid lpclsid); + + [DllImport("oleaut32.dll", PreserveSig = true)] + private static extern int GetActiveObject(ref Guid rclsid, IntPtr pvReserved, [MarshalAs(UnmanagedType.IUnknown)] out object ppunk); + + public static object GetActiveObject(string progId) + { + int hr = CLSIDFromProgID(progId, out Guid clsid); + Marshal.ThrowExceptionForHR(hr); + hr = GetActiveObject(ref clsid, IntPtr.Zero, out object obj); + Marshal.ThrowExceptionForHR(hr); + return obj; + } + } +} diff --git a/Ink Canvas/Helpers/PPTManager.cs b/Ink Canvas/Helpers/PPTManager.cs index 86922667..9f273792 100644 --- a/Ink Canvas/Helpers/PPTManager.cs +++ b/Ink Canvas/Helpers/PPTManager.cs @@ -271,7 +271,7 @@ namespace Ink_Canvas.Helpers { try { - var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application"); + var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)OleActiveObject.GetActiveObject("PowerPoint.Application"); if (pptApp != null && Marshal.IsComObject(pptApp)) { @@ -298,7 +298,7 @@ namespace Ink_Canvas.Helpers { try { - var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("kwpp.Application"); + var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)OleActiveObject.GetActiveObject("kwpp.Application"); if (wpsApp != null && Marshal.IsComObject(wpsApp)) { diff --git a/Ink Canvas/Helpers/PPTROTConnectionHelper.cs b/Ink Canvas/Helpers/PPTROTConnectionHelper.cs index a042ea0c..2e430ec8 100644 --- a/Ink Canvas/Helpers/PPTROTConnectionHelper.cs +++ b/Ink Canvas/Helpers/PPTROTConnectionHelper.cs @@ -104,7 +104,7 @@ namespace Ink_Canvas.Helpers try { - var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("PowerPoint.Application"); + var pptApp = (Microsoft.Office.Interop.PowerPoint.Application)OleActiveObject.GetActiveObject("PowerPoint.Application"); if (pptApp != null && Marshal.IsComObject(pptApp)) { try @@ -124,7 +124,7 @@ namespace Ink_Canvas.Helpers { try { - var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)Marshal.GetActiveObject("kwpp.Application"); + var wpsApp = (Microsoft.Office.Interop.PowerPoint.Application)OleActiveObject.GetActiveObject("kwpp.Application"); if (wpsApp != null && Marshal.IsComObject(wpsApp)) { try diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 47bdc078..12f11762 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -1,10 +1,12 @@ - win;win-x86;win-x64;win-arm64 + win-x86;win-x64;win-arm64 WinExe Ink_Canvas InkCanvasForClass - net472 + net6.0-windows10.0.19041.0 + disable + disable true {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} true @@ -25,8 +27,10 @@ false False true + true Debug;Release AnyCPU;x86;x64;ARM64 + 10 embedded @@ -46,14 +50,12 @@ bin\$(Configuration)\$(Platform)\ embedded - 7.3 x86 true bin\$(Configuration)\$(Platform)\ embedded - 7.3 x86 true @@ -71,14 +73,12 @@ bin\$(Configuration)\$(Platform)\ full - 7.3 ARM64 false bin\$(Configuration)\$(Platform)\ pdbonly - 7.3 ARM64 false @@ -86,7 +86,6 @@ bin\$(Configuration)\$(Platform)\ none false - 7.3 x64 false @@ -94,7 +93,6 @@ bin\$(Configuration)\$(Platform)\ none false - 7.3 x64 false @@ -111,22 +109,6 @@ .\IAWinFX.dll False - - - - - - - - - - - - - - - - @@ -140,14 +122,15 @@ - - + - + + + diff --git a/Ink Canvas/packages.lock.json b/Ink Canvas/packages.lock.json index 59fa97ac..82b90fe3 100644 --- a/Ink Canvas/packages.lock.json +++ b/Ink Canvas/packages.lock.json @@ -1,7 +1,7 @@ { "version": 1, "dependencies": { - ".NETFramework,Version=v4.7.2": { + "net6.0-windows10.0.19041": { "AForge.Imaging": { "type": "Direct", "requested": "[2.2.5, )", @@ -66,7 +66,6 @@ "resolved": "0.10.2.1", "contentHash": "nGwuuVul+TcLCTgPmaAZCc0fYFqUpCNZ8PiulVT3gZnsWt/AvxMZ0DSPpuyI/iRPc/NhFIg9lSIR7uaHWV0I/Q==", "dependencies": { - "System.ValueTuple": "4.5.0", "iNKORE.UI.WPF": "1.2.8" } }, @@ -86,15 +85,6 @@ "resolved": "1.0.0", "contentHash": "hEs/VPwGFeVNLf2Wc6k2bMYF71zE6x+lW4MfebRAjkVbAbaa4DbEmdFRfSBymvGLtdsCUvXo2aa/yFKYSCYmEQ==" }, - "Microsoft.NETFramework.ReferenceAssemblies": { - "type": "Direct", - "requested": "[1.0.3, )", - "resolved": "1.0.3", - "contentHash": "vUc9Npcs14QsyOD01tnv/m8sQUnGTGOw1BCmKcv77LBJY7OxhJ+zJF7UD/sCL3lYNFuqmQEVlkfS4Quif6FyYg==", - "dependencies": { - "Microsoft.NETFramework.ReferenceAssemblies.net472": "1.0.3" - } - }, "Microsoft.Office.Interop.PowerPoint": { "type": "Direct", "requested": "[15.0.4420.1018, )", @@ -107,21 +97,17 @@ "resolved": "7.1.3", "contentHash": "A1dglAzb24gjehmb7DwGd07mfyZ1gacAK7ObE0KwDlRc3mayH2QW7cSOy3TkkyELjLg19OQBuhPOj4SpXET9lg==", "dependencies": { - "Microsoft.NETFramework.ReferenceAssemblies": "1.0.0", - "Microsoft.Windows.SDK.Contracts": "10.0.19041.1", + "Microsoft.Win32.Registry": "4.7.0", + "System.Drawing.Common": "4.7.0", + "System.Reflection.Emit": "4.7.0", "System.ValueTuple": "4.5.0" } }, - "Microsoft.Windows.SDK.Contracts": { + "Microsoft.VisualBasic": { "type": "Direct", - "requested": "[10.0.19041.2, )", - "resolved": "10.0.19041.2", - "contentHash": "PBPrkDx5BMItV0+cDBRJg6itl2miC9t4igEX+ym0BuN65xu/mTzq8fbVzAME6CXAY4eMcxACLnGu6SWufxwSCQ==", - "dependencies": { - "System.Runtime.InteropServices.WindowsRuntime": "4.3.0", - "System.Runtime.WindowsRuntime": "4.6.0", - "System.Runtime.WindowsRuntime.UI.Xaml": "4.6.0" - } + "requested": "[10.3.0, )", + "resolved": "10.3.0", + "contentHash": "AvMDjmJHjz9bdlvxiSdEHHcWP+sZtp7zwule5ab6DaUbgoBnwCsd7nymj69vSz18ypXuEv3SI7ZUNwbIKrvtMA==" }, "MicrosoftOfficeCore": { "type": "Direct", @@ -155,15 +141,29 @@ }, "Sentry": { "type": "Direct", - "requested": "[6.2.0, )", - "resolved": "6.2.0", - "contentHash": "kZkHnj9NvQTjf5e8VmVQUT2HdVSmph9MhSm6U+vGoB4vq2uR1a+ZRHpAksNJNl7EjaGbYp55cOWqx5Nh0jQc8w==", + "requested": "[6.3.0, )", + "resolved": "6.3.0", + "contentHash": "PwcF5yIzcKuhd/3BOxU3QHJA5/xYiuSNSaSpXi2kR3x7ONKel73xi49GfYvRg4mbMN1gRjxduqxQL6iRNNWqBA==", "dependencies": { "System.Reflection.Metadata": "5.0.0", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", "System.Text.Json": "8.0.5" } }, + "System.Data.DataSetExtensions": { + "type": "Direct", + "requested": "[4.5.0, )", + "resolved": "4.5.0", + "contentHash": "221clPs1445HkTBZPL+K9sDBdJRB8UN8rgjO3ztB0CQ26z//fmJXtlsr6whGatscsKGBrhJl5bwJuKSA8mwFOw==" + }, + "System.Management": { + "type": "Direct", + "requested": "[10.0.5, )", + "resolved": "10.0.5", + "contentHash": "JhBVxvWhUJ0KAquUK0dMnc3a1Ol4JyH8fMrMQZ9GgEUkrtvPy8DE57SDnGnuvOdI0maJOdguxw87N5bh2eL87A==", + "dependencies": { + "System.CodeDom": "10.0.5" + } + }, "WebDav.Client": { "type": "Direct", "requested": "[2.9.0, )", @@ -190,18 +190,10 @@ "resolved": "1.27.0", "contentHash": "We7LtBdoukRg9mqTfa1f5n8z/GQPMKBRj3URk9DiMuqzIHkW1lTgK5njVPSScxsRt4YzW22423tSnLWNm2MJKg==" }, - "Microsoft.Bcl.AsyncInterfaces": { + "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", - "dependencies": { - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Microsoft.NETFramework.ReferenceAssemblies.net472": { - "type": "Transitive", - "resolved": "1.0.3", - "contentHash": "0E7evZXHXaDYYiLRfpyXvCh+yzM2rNTyuZDI+ZO7UUqSc6GfjePiXTdqJGtgIKUwdI81tzQKmaWprnUiPj9hAw==" + "resolved": "5.0.0", + "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" }, "Microsoft.Win32.Registry": { "type": "Transitive", @@ -212,80 +204,54 @@ "System.Security.Principal.Windows": "5.0.0" } }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0" + } + }, "NHotkey": { "type": "Transitive", "resolved": "4.0.0", "contentHash": "5HKzttVKWeKoDQKJd3+J7Dy1MW6gbNNYfftkVufe2ddFQD0kXjnT1IN3ZJBfF6QVEQmHpQSp+/PT7Jo2YyHFcw==" }, - "System.Buffers": { + "System.CodeDom": { "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + "resolved": "10.0.5", + "contentHash": "hGZWDDJh1U6t7fy3iO4HlZYK1ur1fWE3sTqTNHkHk0Leh0JUcxYM//JtLBNG5g+6D2Lt0+aHH8rc7e5oIlNgCg==" }, - "System.Collections.Immutable": { + "System.Drawing.Common": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "FXkLXiK0sVVewcso0imKQoOxjoPAj42R8HtjjbSjVPAzwDfzoyoznWxgA3c38LDbN9SJux1xXoXYAhz98j7r2g==", + "resolved": "4.7.0", + "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", "dependencies": { - "System.Memory": "4.5.4" + "Microsoft.NETCore.Platforms": "3.1.0", + "Microsoft.Win32.SystemEvents": "4.7.0" } }, - "System.Memory": { + "System.Reflection.Emit": { "type": "Transitive", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Numerics.Vectors": "4.5.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.3" - } - }, - "System.Numerics.Vectors": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + "resolved": "4.7.0", + "contentHash": "VR4kk8XLKebQ4MZuKuIni/7oh+QGFmZW3qORd1GvBq/8026OpW501SzT/oypwiQl4TvT8ErnReh/NzY9u+C6wQ==" }, "System.Reflection.Metadata": { "type": "Transitive", "resolved": "5.0.0", - "contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ==", - "dependencies": { - "System.Collections.Immutable": "5.0.0" - } + "contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ==" }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, - "System.Runtime.InteropServices.RuntimeInformation": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" - }, - "System.Runtime.InteropServices.WindowsRuntime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "J4GUi3xZQLUBasNwZnjrffN8i5wpHrBtZoLG+OhRyGo/+YunMRWWtwoMDlUAIdmX0uRfpHIBDSV6zyr3yf00TA==" - }, - "System.Runtime.WindowsRuntime": { - "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "IWrs1TmbxP65ZZjIglNyvDkFNoV5q2Pofg5WO7I8RKQOpLdFprQSh3xesOoClBqR4JHr4nEB1Xk1MqLPW1jPuQ==" - }, - "System.Runtime.WindowsRuntime.UI.Xaml": { - "type": "Transitive", - "resolved": "4.6.0", - "contentHash": "r4tNw5v5kqRJ9HikWpcyNf3suGw7DjX93svj9iBjtdeLqL8jt9Z+7f+s4wrKZJr84u8IMsrIjt8K6jYvkRqMSg==", - "dependencies": { - "System.Runtime.WindowsRuntime": "4.6.0" - } - }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", "System.Security.Principal.Windows": "5.0.0" } }, @@ -299,8 +265,6 @@ "resolved": "8.0.0", "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, @@ -309,21 +273,8 @@ "resolved": "8.0.5", "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==", "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Encodings.Web": "8.0.0", - "System.Threading.Tasks.Extensions": "4.5.4", - "System.ValueTuple": "4.5.0" - } - }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" + "System.Text.Encodings.Web": "8.0.0" } }, "System.ValueTuple": { @@ -332,7 +283,7 @@ "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" } }, - ".NETFramework,Version=v4.7.2/win": { + "net6.0-windows10.0.19041/win-arm64": { "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "5.0.0", @@ -342,16 +293,29 @@ "System.Security.Principal.Windows": "5.0.0" } }, - "System.Runtime.InteropServices.RuntimeInformation": { + "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" + "resolved": "4.7.0", + "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0" + } + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "Microsoft.Win32.SystemEvents": "4.7.0" + } }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", "System.Security.Principal.Windows": "5.0.0" } }, @@ -359,9 +323,17 @@ "type": "Transitive", "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } } }, - ".NETFramework,Version=v4.7.2/win-arm64": { + "net6.0-windows10.0.19041/win-x64": { "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "5.0.0", @@ -371,16 +343,29 @@ "System.Security.Principal.Windows": "5.0.0" } }, - "System.Runtime.InteropServices.RuntimeInformation": { + "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" + "resolved": "4.7.0", + "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0" + } + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "Microsoft.Win32.SystemEvents": "4.7.0" + } }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", "System.Security.Principal.Windows": "5.0.0" } }, @@ -388,9 +373,17 @@ "type": "Transitive", "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } } }, - ".NETFramework,Version=v4.7.2/win-x64": { + "net6.0-windows10.0.19041/win-x86": { "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "5.0.0", @@ -400,16 +393,29 @@ "System.Security.Principal.Windows": "5.0.0" } }, - "System.Runtime.InteropServices.RuntimeInformation": { + "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" + "resolved": "4.7.0", + "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0" + } + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "4.7.0", + "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "3.1.0", + "Microsoft.Win32.SystemEvents": "4.7.0" + } }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", "System.Security.Principal.Windows": "5.0.0" } }, @@ -417,35 +423,14 @@ "type": "Transitive", "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" - } - }, - ".NETFramework,Version=v4.7.2/win-x86": { - "Microsoft.Win32.Registry": { + }, + "System.Text.Encodings.Web": { "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" + "System.Runtime.CompilerServices.Unsafe": "6.0.0" } - }, - "System.Runtime.InteropServices.RuntimeInformation": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" - }, - "System.Security.AccessControl": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", - "dependencies": { - "System.Security.Principal.Windows": "5.0.0" - } - }, - "System.Security.Principal.Windows": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" } } } From 2e8660de26e5300823e524d49216cf3bea38330d Mon Sep 17 00:00:00 2001 From: doudou0720 <98651603+doudou0720@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:54:55 +0800 Subject: [PATCH 002/205] =?UTF-8?q?ci:=20=E6=9B=B4=E6=96=B0=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E4=BB=A5=E6=94=AF=E6=8C=81=20.NET=206=20?= =?UTF-8?q?=E5=92=8C=20Windows=2010=2019041?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新 GitHub Actions 工作流以构建针对 .NET 6 和 Windows 10 19041 平台的应用程序,并修改了程序集信息以声明支持的 OS 平台 Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> --- .github/workflows/dotnet-desktop.yml | 4 ++-- Ink Canvas/AssemblyInfo.cs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml index d2942ea2..1d521a45 100644 --- a/.github/workflows/dotnet-desktop.yml +++ b/.github/workflows/dotnet-desktop.yml @@ -46,7 +46,7 @@ jobs: - name: Check if exe file is generated id: check-exe run: | - $exePath = "Ink Canvas\bin\Debug\${{ matrix.architecture }}\net472\InkCanvasForClass.exe" + $exePath = "Ink Canvas\bin\Debug\${{ matrix.architecture }}\net6.0-windows10.0.19041.0\InkCanvasForClass.exe" if (Test-Path $exePath) { echo "build_success=true" >> $env:GITHUB_OUTPUT @@ -74,7 +74,7 @@ jobs: uses: actions/upload-artifact@v7 with: name: InkCanvasForClass.CE.debug.${{ matrix.architecture }} - path: "Ink Canvas/bin/Debug/${{ matrix.architecture }}/net472/*" + path: "Ink Canvas/bin/Debug/${{ matrix.architecture }}/net6.0-windows10.0.19041.0/*" - name: Create Summary if: always() diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index affa789e..3c6f5d4d 100644 --- a/Ink Canvas/AssemblyInfo.cs +++ b/Ink Canvas/AssemblyInfo.cs @@ -14,6 +14,7 @@ using System.Windows; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] +[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.19041")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. From 67a70fd6e406b4e78593f0e7399003a96ca489cd Mon Sep 17 00:00:00 2001 From: doudou0720 <98651603+doudou0720@users.noreply.github.com> Date: Sun, 5 Apr 2026 12:55:35 +0800 Subject: [PATCH 003/205] =?UTF-8?q?chore:=20=E6=9B=B4=E6=94=B9=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E8=A7=A6=E5=8F=91=E6=9D=A1=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> --- .github/workflows/dotnet-desktop.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dotnet-desktop.yml b/.github/workflows/dotnet-desktop.yml index 1d521a45..38c4d591 100644 --- a/.github/workflows/dotnet-desktop.yml +++ b/.github/workflows/dotnet-desktop.yml @@ -2,7 +2,7 @@ name: .NET Build & Package on: push: - branches: [ main, beta ] + branches: [ net6 ] workflow_dispatch: concurrency: From 226a6942dca7db4ab7cb8aa66170ab1c875c312c Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Sun, 5 Apr 2026 20:21:59 +0800 Subject: [PATCH 004/205] =?UTF-8?q?improve:pdf=E6=8F=92=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow_cs/MW_BoardControls.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs index 447cb267..e5f6f717 100644 --- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs @@ -226,6 +226,7 @@ namespace Ink_Canvas if (TimeMachineHistories[targetIndex] == null) { timeMachine.ClearStrokeHistory(); + SyncPdfPageSidebarWithCanvas(); return; } @@ -282,7 +283,11 @@ namespace Ink_Canvas /// private void ProcessElementsAfterRestore(List elements) { - if (elements == null || elements.Count == 0) return; + if (elements == null || elements.Count == 0) + { + SyncPdfPageSidebarWithCanvas(); + return; + } // 使用低优先级异步处理,让 UI 先响应,图片位置和事件绑定稍后完成 Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => @@ -322,6 +327,8 @@ namespace Ink_Canvas BindElementEvents(pdf); } } + + SyncPdfPageSidebarWithCanvas(); })); } From 998d829783b6e70df46ab1f7bcc887c696ab1ccc Mon Sep 17 00:00:00 2001 From: CJKmkp <2564608840@qq.com> Date: Sun, 5 Apr 2026 20:26:07 +0800 Subject: [PATCH 005/205] =?UTF-8?q?=E8=A7=A3=E5=86=B3nuget=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/InkCanvasForClass.csproj | 2 + .../SettingsViews2/SettingsWindow2.xaml.cs | 3 +- Ink Canvas/packages.lock.json | 82 +++++++------------ 3 files changed, 32 insertions(+), 55 deletions(-) diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index e6bc98fa..9deab9db 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -129,6 +129,8 @@ + + diff --git a/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml.cs b/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml.cs index 273c3583..de08ea64 100644 --- a/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews2/SettingsWindow2.xaml.cs @@ -11,6 +11,7 @@ using System.ComponentModel.Composition.Hosting; using System.Linq; using MessageBox = System.Windows.MessageBox; using Screen = System.Windows.Forms.Screen; +using System.Composition; namespace Ink_Canvas.Windows.SettingsViews2 { @@ -29,7 +30,7 @@ namespace Ink_Canvas.Windows.SettingsViews2 private readonly Dictionary _pageTypes; private readonly Dictionary _pages = new Dictionary(); - [ImportMany(typeof(IPluginSettingsPage))] + [System.ComponentModel.Composition.ImportMany(typeof(IPluginSettingsPage))] private IEnumerable _pluginPages; // 自动导入所有插件页面 // 保存窗口原始位置和大小 diff --git a/Ink Canvas/packages.lock.json b/Ink Canvas/packages.lock.json index 5cfc6eab..09b35601 100644 --- a/Ink Canvas/packages.lock.json +++ b/Ink Canvas/packages.lock.json @@ -54,8 +54,7 @@ "resolved": "2.0.131", "contentHash": "f71kXNl6PjCqipJ7DQytg1QUBMQ+7j8rF1UyL8UPegymG1G57EYsskdIcf/VmF6JDuts6Dk6F8Hd4ziiz4/3Dw==", "dependencies": { - "H.NotifyIcon": "2.0.131", - "System.ValueTuple": "4.5.0" + "H.NotifyIcon": "2.0.131" } }, "iNKORE.UI.WPF": { @@ -153,6 +152,18 @@ "System.Text.Json": "8.0.5" } }, + "System.ComponentModel.Composition": { + "type": "Direct", + "requested": "[10.0.5, )", + "resolved": "10.0.5", + "contentHash": "Yi8nY2EKRZlZYRPxQ1/E4rrYs6QD1H0UgfcfHhKsCVhNJ4lNULLbly6Dtz6CjH6gZKBf2hZYXzzzzVGhpGLBvw==" + }, + "System.Composition.AttributedModel": { + "type": "Direct", + "requested": "[10.0.5, )", + "resolved": "10.0.5", + "contentHash": "Vgb7wwB7ya+lbcwccXHZPSJxeKR7tCkWLgFXO9Wcgbu/NgO5DvNAIHtEkXaEESkcvXdD1iqp2JBcLWGT/xDxEw==" + }, "System.Data.DataSetExtensions": { "type": "Direct", "requested": "[4.5.0, )", @@ -192,7 +203,10 @@ "H.GeneratedIcons.System.Drawing": { "type": "Transitive", "resolved": "2.0.131", - "contentHash": "QoNGQrhxzG+dQufa4xRjSqihMy5aVVVZqQUt0fLJbwhs7rcM4hpN1qVkZpZEkHsRgrHfFBC/Ursjh8STY/sg7A==" + "contentHash": "QoNGQrhxzG+dQufa4xRjSqihMy5aVVVZqQUt0fLJbwhs7rcM4hpN1qVkZpZEkHsRgrHfFBC/Ursjh8STY/sg7A==", + "dependencies": { + "System.Drawing.Common": "8.0.0" + } }, "H.NotifyIcon": { "type": "Transitive", @@ -223,11 +237,8 @@ }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "3.1.0" - } + "resolved": "8.0.0", + "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, "NHotkey": { "type": "Transitive", @@ -241,11 +252,10 @@ }, "System.Drawing.Common": { "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", + "resolved": "8.0.0", + "contentHash": "JkbHJjtI/dWc5dfmEdJlbe3VwgZqCkZRtfuWFh5GOv0f+gGCfBtzMpIVkmdkj2AObO9y+oiOi81UGwH3aBYuqA==", "dependencies": { - "Microsoft.NETCore.Platforms": "3.1.0", - "Microsoft.Win32.SystemEvents": "4.7.0" + "Microsoft.Win32.SystemEvents": "8.0.0" } }, "System.Reflection.Emit": { @@ -312,20 +322,8 @@ }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "3.1.0" - } - }, - "System.Drawing.Common": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "3.1.0", - "Microsoft.Win32.SystemEvents": "4.7.0" - } + "resolved": "8.0.0", + "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, "System.Security.AccessControl": { "type": "Transitive", @@ -362,20 +360,8 @@ }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "3.1.0" - } - }, - "System.Drawing.Common": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "3.1.0", - "Microsoft.Win32.SystemEvents": "4.7.0" - } + "resolved": "8.0.0", + "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, "System.Security.AccessControl": { "type": "Transitive", @@ -412,20 +398,8 @@ }, "Microsoft.Win32.SystemEvents": { "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "mtVirZr++rq+XCDITMUdnETD59XoeMxSpLRIII7JRI6Yj0LEDiO1pPn0ktlnIj12Ix8bfvQqQDMMIF9wC98oCA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "3.1.0" - } - }, - "System.Drawing.Common": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "v+XbyYHaZjDfn0ENmJEV1VYLgGgCTx1gnfOBcppowbpOAriglYgGCvFCPr2EEZyBvXlpxbEsTwkOlInl107ahA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "3.1.0", - "Microsoft.Win32.SystemEvents": "4.7.0" - } + "resolved": "8.0.0", + "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, "System.Security.AccessControl": { "type": "Transitive", From 5eff424b2d6e085dbc2b871da42dcbcd1ba811cf Mon Sep 17 00:00:00 2001 From: CJK_mkp <113243675+CJKmkp@users.noreply.github.com> Date: Sun, 5 Apr 2026 20:49:41 +0800 Subject: [PATCH 006/205] =?UTF-8?q?=E5=90=8C=E6=AD=A5net6=E5=88=86?= =?UTF-8?q?=E6=94=AF=20(#430)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * improve:pdf插入 * fix:手掌擦 (#419) --- .../MainWindow_cs/MW_FloatingBarIcons.cs | 3 + Ink Canvas/MainWindow_cs/MW_Settings.cs | 12 + Ink Canvas/MainWindow_cs/MW_TouchEvents.cs | 224 +++++++++++++++--- 3 files changed, 200 insertions(+), 39 deletions(-) diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index 9baa7736..2788a77f 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -3298,6 +3298,9 @@ namespace Ink_Canvas // 清空触摸点计数器 dec.Clear(); + if (isPalmEraserActive) + isPalmEraserActive = false; + // 确保触摸事件能正常响应 inkCanvas.IsHitTestVisible = true; inkCanvas.IsManipulationEnabled = true; diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index 7fe730bf..acacfedc 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -3597,6 +3597,11 @@ namespace Ink_Canvas RestoreNonStrokeElements(preservedElements); isInMultiTouchMode = true; + palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser; + Settings.Canvas.EnablePalmEraser = false; + if (ToggleSwitchEnablePalmEraser != null) + ToggleSwitchEnablePalmEraser.IsOn = false; + // 恢复到之前的编辑状态 inkCanvas.EditingMode = currentEditingMode; drawingShapeMode = currentDrawingShapeMode; @@ -3627,6 +3632,13 @@ namespace Ink_Canvas RestoreNonStrokeElements(preservedElements); isInMultiTouchMode = false; + if (palmEraserWasEnabledBeforeMultiTouch) + { + Settings.Canvas.EnablePalmEraser = true; + if (ToggleSwitchEnablePalmEraser != null) + ToggleSwitchEnablePalmEraser.IsOn = true; + } + // 恢复到之前的编辑状态 inkCanvas.EditingMode = currentEditingMode; drawingShapeMode = currentDrawingShapeMode; diff --git a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs index 1b399479..dc70ea42 100644 --- a/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs +++ b/Ink Canvas/MainWindow_cs/MW_TouchEvents.cs @@ -44,6 +44,9 @@ namespace Ink_Canvas /// 多点触控延迟时间(毫秒) /// private const double MULTI_TOUCH_DELAY_MS = 100; + private bool isMultiTouchTimerActive; + private bool isPalmEraserActive; + private bool palmEraserWasEnabledBeforeMultiTouch; /// /// 保存画布上的非笔画元素(如图片、媒体元素等) @@ -227,6 +230,12 @@ namespace Ink_Canvas RestoreNonStrokeElements(preservedElements); isInMultiTouchMode = false; + if (palmEraserWasEnabledBeforeMultiTouch) + { + Settings.Canvas.EnablePalmEraser = true; + if (ToggleSwitchEnablePalmEraser != null) + ToggleSwitchEnablePalmEraser.IsOn = true; + } } else { @@ -247,6 +256,11 @@ namespace Ink_Canvas // 恢复非笔画元素 RestoreNonStrokeElements(preservedElements); isInMultiTouchMode = true; + + palmEraserWasEnabledBeforeMultiTouch = Settings.Canvas.EnablePalmEraser; + Settings.Canvas.EnablePalmEraser = false; + if (ToggleSwitchEnablePalmEraser != null) + ToggleSwitchEnablePalmEraser.IsOn = false; } } @@ -721,21 +735,13 @@ namespace Ink_Canvas /// 触摸事件参数 /// 返回触摸边界宽度 /// - /// 根据触摸事件参数计算触摸边界宽度,包括以下逻辑: - /// 1. 获取触摸点的边界 - /// 2. 如果不是四边红外屏幕,使用边界宽度 - /// 3. 如果是四边红外屏幕,使用边界宽度和高度的平方根 - /// 4. 如果是特殊屏幕,乘以触摸倍数 - /// 5. 返回计算得到的触摸边界宽度 + /// 手掌擦阈值与特殊屏 TouchMultiplier 在激活逻辑中单独参与计算,此处仅返回几何接触尺寸。 /// public double GetTouchBoundWidth(TouchEventArgs e) { var args = e.GetTouchPoint(null).Bounds; - double value; - if (!Settings.Advanced.IsQuadIR) value = args.Width; - else value = Math.Sqrt(args.Width * args.Height); //四边红外 - if (Settings.Advanced.IsSpecialScreen) value *= Settings.Advanced.TouchMultiplier; - return value; + if (!Settings.Advanced.IsQuadIR) return args.Width; + return Math.Sqrt(args.Width * args.Height); } /// @@ -759,28 +765,146 @@ namespace Ink_Canvas /// private void InkCanvas_PreviewTouchDown(object sender, TouchEventArgs e) { + var touchPointForBar = e.GetTouchPoint(this); + var floatingBarBounds = ViewboxFloatingBar.TransformToAncestor(this).TransformBounds( + new Rect(0, 0, ViewboxFloatingBar.ActualWidth, ViewboxFloatingBar.ActualHeight)); + if (floatingBarBounds.Contains(touchPointForBar.Position)) + return; + + if ((inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint + || inkCanvas.EditingMode == InkCanvasEditingMode.EraseByStroke) + && !isPalmEraserActive) + { + return; + } + + if (drawingShapeMode != 0) + { + inkCanvas.EditingMode = InkCanvasEditingMode.None; + SetCursorBasedOnEditingMode(inkCanvas); + inkCanvas.CaptureTouch(e.TouchDevice); + ViewboxFloatingBar.IsHitTestVisible = false; + BlackboardUIGridForInkReplay.IsHitTestVisible = false; + + isTouchDown = true; + + if (dec.Count == 0) + { + var inkTouchPoint = e.GetTouchPoint(inkCanvas); + if (drawingShapeMode == 24 || drawingShapeMode == 25) + { + if (drawMultiStepShapeCurrentStep == 0) + iniP = inkTouchPoint.Position; + } + else + { + iniP = inkTouchPoint.Position; + } + lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone(); + } + dec.Add(e.TouchDevice.Id); + return; + } + + SetCursorBasedOnEditingMode(inkCanvas); inkCanvas.CaptureTouch(e.TouchDevice); ViewboxFloatingBar.IsHitTestVisible = false; BlackboardUIGridForInkReplay.IsHitTestVisible = false; - + lastTouchDownTime = DateTime.Now; dec.Add(e.TouchDevice.Id); - //设备1个的时候,记录中心点 + + if (Settings.Canvas.EnablePalmEraser && !isPalmEraserActive && drawingShapeMode == 0) + { + var touchPoint = e.GetTouchPoint(inkCanvas); + double boundWidth = GetTouchBoundWidth(e); + + if ((Settings.Advanced.TouchMultiplier != 0 || !Settings.Advanced.IsSpecialScreen) + && (boundWidth > BoundsWidth)) + { + double thresholdMultiplier; + switch (Settings.Canvas.PalmEraserSensitivity) + { + case 0: + thresholdMultiplier = 3.0; + break; + case 1: + thresholdMultiplier = 2.5; + break; + case 2: + default: + thresholdMultiplier = 2.0; + break; + } + + double EraserThresholdValue = Settings.Startup.IsEnableNibMode + ? Settings.Advanced.NibModeBoundsWidthThresholdValue + : Settings.Advanced.FingerModeBoundsWidthThresholdValue; + + if (boundWidth > BoundsWidth * EraserThresholdValue * thresholdMultiplier) + { + boundWidth *= Settings.Startup.IsEnableNibMode + ? Settings.Advanced.NibModeBoundsWidthEraserSize + : Settings.Advanced.FingerModeBoundsWidthEraserSize; + + if (Settings.Advanced.IsSpecialScreen) + boundWidth *= Settings.Advanced.TouchMultiplier; + inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint; + isPalmEraserActive = true; + + EnableEraserOverlay(); + eraserWidth = boundWidth; + UpdateEraserStyle(); + touchPoint = e.GetTouchPoint(inkCanvas); + EraserOverlay_PointerDown(sender); + EraserOverlay_PointerMove(sender, touchPoint.Position); + if (Settings.Canvas.IsShowCursor) + { + inkCanvas.ForceCursor = false; + inkCanvas.UseCustomCursor = false; + } + } + } + } + if (dec.Count == 1) { var touchPoint = e.GetTouchPoint(inkCanvas); centerPoint = touchPoint.Position; - - //记录第一根手指点击时的 StrokeCollection lastTouchDownStrokeCollection = inkCanvas.Strokes.Clone(); } - //设备两个及两个以上,将画笔功能关闭 + if (dec.Count > 1 || isSingleFingerDragMode || !Settings.Gesture.IsEnableTwoFingerGesture) { if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return; if (inkCanvas.EditingMode == InkCanvasEditingMode.None || inkCanvas.EditingMode == InkCanvasEditingMode.Select) return; + var timeSinceLastTouch = (DateTime.Now - lastTouchDownTime).TotalMilliseconds; + if (timeSinceLastTouch < MULTI_TOUCH_DELAY_MS && inkCanvas.EditingMode == InkCanvasEditingMode.Ink) + { + if (!isMultiTouchTimerActive) + { + isMultiTouchTimerActive = true; + var remainingTime = MULTI_TOUCH_DELAY_MS - timeSinceLastTouch; + Task.Delay((int)remainingTime).ContinueWith(_ => + { + Dispatcher.Invoke(() => + { + if (dec.Count > 1 && inkCanvas.EditingMode == InkCanvasEditingMode.Ink) + inkCanvas.EditingMode = InkCanvasEditingMode.None; + isMultiTouchTimerActive = false; + }); + }); + } + return; + } + lastInkCanvasEditingMode = inkCanvas.EditingMode; - inkCanvas.EditingMode = InkCanvasEditingMode.None; + if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint + && inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke + && drawingShapeMode == 0) + { + inkCanvas.EditingMode = InkCanvasEditingMode.None; + } } } @@ -794,6 +918,11 @@ namespace Ink_Canvas /// private void InkCanvas_PreviewTouchMove(object sender, TouchEventArgs e) { + if (isPalmEraserActive) + { + var touchPoint = e.GetTouchPoint(inkCanvas); + EraserOverlay_PointerMove(sender, touchPoint.Position); + } } /// @@ -820,32 +949,18 @@ namespace Ink_Canvas /// private void InkCanvas_PreviewTouchUp(object sender, TouchEventArgs e) { + if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint && !isPalmEraserActive) + { + return; + } inkCanvas.ReleaseAllTouchCaptures(); ViewboxFloatingBar.IsHitTestVisible = true; BlackboardUIGridForInkReplay.IsHitTestVisible = true; - //手势完成后切回之前的状态 - if (dec.Count > 1) - if (inkCanvas.EditingMode == InkCanvasEditingMode.None) - inkCanvas.EditingMode = lastInkCanvasEditingMode; dec.Remove(e.TouchDevice.Id); - if (dec.Count == 0) - { - isSingleFingerDragMode = false; - isWaitUntilNextTouchDown = false; - if (drawingShapeMode == 0 - && inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint - && inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke - && inkCanvas.EditingMode != InkCanvasEditingMode.Select - && inkCanvas.EditingMode != InkCanvasEditingMode.None) - { - if (lastInkCanvasEditingMode != InkCanvasEditingMode.None) - { - inkCanvas.EditingMode = lastInkCanvasEditingMode; - } - } - } + if (dec.Count <= 1) + isMultiTouchTimerActive = false; if (drawingShapeMode != 0) { @@ -857,12 +972,10 @@ namespace Ink_Canvas { if (drawMultiStepShapeCurrentStep == 0) { - // 第一笔完成,进入第二笔 drawMultiStepShapeCurrentStep = 1; } else { - // 第二笔完成,完成绘制 var mouseArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left) { RoutedEvent = MouseLeftButtonUpEvent, @@ -882,6 +995,36 @@ namespace Ink_Canvas } } + if (drawingShapeMode == 0) + { + if (dec.Count > 1) + { + if (inkCanvas.EditingMode == InkCanvasEditingMode.None) + { + if (lastInkCanvasEditingMode != InkCanvasEditingMode.EraseByPoint) + inkCanvas.EditingMode = lastInkCanvasEditingMode; + } + } + else if (dec.Count == 0) + { + isSingleFingerDragMode = false; + isWaitUntilNextTouchDown = false; + + if (inkCanvas.EditingMode == InkCanvasEditingMode.None && + lastInkCanvasEditingMode != InkCanvasEditingMode.None && + lastInkCanvasEditingMode != InkCanvasEditingMode.EraseByPoint) + { + inkCanvas.EditingMode = lastInkCanvasEditingMode; + } + + if (isPalmEraserActive) + { + isPalmEraserActive = false; + DisableEraserOverlay(); + } + } + } + inkCanvas.Opacity = 1; if (dec.Count == 0) @@ -979,6 +1122,9 @@ namespace Ink_Canvas /// private void Main_Grid_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) { + if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint) + return; + if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return; bool hasMultipleManipulators = e.Manipulators.Count() >= 2; From 36ff945384c1d08792f086d1bf659a9b638c700f Mon Sep 17 00:00:00 2001 From: doudou0720 <98651603+doudou0720@users.noreply.github.com> Date: Sun, 5 Apr 2026 22:54:55 +0800 Subject: [PATCH 007/205] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E7=BC=96?= =?UTF-8?q?=E8=AF=91=E8=AD=A6=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: doudou0720 <98651603+doudou0720@users.noreply.github.com> --- Ink Canvas/AssemblyInfo.cs | 2 +- Ink Canvas/InkCanvasForClass.csproj | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Ink Canvas/AssemblyInfo.cs b/Ink Canvas/AssemblyInfo.cs index 3c6f5d4d..46331e15 100644 --- a/Ink Canvas/AssemblyInfo.cs +++ b/Ink Canvas/AssemblyInfo.cs @@ -2,6 +2,7 @@ using System.Reflection; using System.Runtime.InteropServices; using System.Windows; +[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows")] // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. @@ -14,7 +15,6 @@ using System.Windows; [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] -[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.19041")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 9deab9db..db46b9b2 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -29,8 +29,9 @@ true true Debug;Release - AnyCPU;x86;x64;ARM64 + AnyCPU;x86;x64 10 + CA1416 embedded From bd4b4bd23336b67662d57544410336332605b3c5 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Mon, 6 Apr 2026 15:02:04 +0800 Subject: [PATCH 008/205] Update MainWindow.xaml --- Ink Canvas/MainWindow.xaml | 7946 ++++++++++++++++++------------------ 1 file changed, 3973 insertions(+), 3973 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index cf129f5f..b9434bac 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -524,4052 +524,4052 @@ PanningMode="VerticalOnly" ui:ThemeManager.RequestedTheme="Dark" ManipulationBoundaryFeedback="SCManipulationBoundaryFeedback" Name="SettingsPanelScrollViewer"> - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - - - - - - - + + + + + + + + + + - - - - - - - - - - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text="{i18n:I18n Key=PPT_OffsetHint}" + TextWrapping="Wrap" Foreground="#a1a1aa" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - CJK_mkp - - + + + + - - - - - - - - Dubi906w - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - ChangSakura - - + + + - - - - - - - - WXRIW - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Raspberry Kan - - - - - - - - - - Kengwang - - - - - - - - - - Charles Jia - - - - - - - - - - clover_yan - - - - - - - - - - Netherite_Bowl - - - - - - - - - - Yoojun Zhou - - - - - - - - - - YuWenHui2020 - - - - - - - - - - ZongziTEK - - - - - - - - - - Aesthed - - - - - - - - - - Wei - - - - - - - - - - Alan-CRL - - - - - - - - - - PrefacedCorg - - - - - - - - - - PANDA-JSR - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + private void ToggleSwitchEnableEraserAutoSwitchBack_Toggled(object sender, RoutedEventArgs e) { try { if (!isLoaded) return; - Settings.Canvas.EnableEraserAutoSwitchBack = ToggleSwitchEnableEraserAutoSwitchBack.IsOn; - SaveSettingsToFile(); + bool enabled = ToggleSwitchEnableEraserAutoSwitchBack.IsOn; + SettingsService.Instance.SetEraserAutoSwitchBack(enabled); - // 如果禁用,停止计时器 - if (!Settings.Canvas.EnableEraserAutoSwitchBack) + if (!enabled) { StopEraserAutoSwitchBackTimer(); } - - LogHelper.WriteLogToFile($"橡皮擦自动切换回批注模式已{(Settings.Canvas.EnableEraserAutoSwitchBack ? "启用" : "禁用")}", LogHelper.LogType.Event); } catch (Exception ex) { @@ -3566,24 +3544,18 @@ namespace Ink_Canvas } } - /// - /// 橡皮擦自动切换延迟时间滑块值改变事件处理 - /// private void EraserAutoSwitchBackDelaySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { try { if (!isLoaded) return; - Settings.Canvas.EraserAutoSwitchBackDelaySeconds = (int)e.NewValue; - SaveSettingsToFile(); + int delaySeconds = (int)e.NewValue; + SettingsService.Instance.SetEraserAutoSwitchBackDelay(delaySeconds); - // 如果计时器正在运行,重新启动以应用新的延迟时间 if (_eraserAutoSwitchBackTimer != null && _eraserAutoSwitchBackTimer.IsEnabled) { StartEraserAutoSwitchBackTimer(); } - - LogHelper.WriteLogToFile($"橡皮擦自动切换延迟时间已更新为 {Settings.Canvas.EraserAutoSwitchBackDelaySeconds} 秒", LogHelper.LogType.Event); } catch (Exception ex) { @@ -3591,18 +3563,15 @@ namespace Ink_Canvas } } - /// - /// 根据开关状态启用或禁用画笔自动恢复:更新设置并保存,启用时初始化并安排恢复定时器,禁用时停止计时器。 - /// private void ToggleSwitchBrushAutoRestore_Toggled(object sender, RoutedEventArgs e) { try { if (!isLoaded) return; - Settings.Canvas.EnableBrushAutoRestore = ToggleSwitchBrushAutoRestore.IsOn; - SaveSettingsToFile(); + bool enabled = ToggleSwitchBrushAutoRestore.IsOn; + SettingsService.Instance.SetBrushAutoRestore(enabled); - if (Settings.Canvas.EnableBrushAutoRestore) + if (enabled) { InitBrushAutoRestoreTimer(); ScheduleBrushAutoRestore(); @@ -3634,8 +3603,8 @@ namespace Ink_Canvas if (!isLoaded) return; if (Settings?.Canvas == null) return; - Settings.Canvas.BrushAutoRestoreTimes = BrushAutoRestoreTimesTextBox.Text ?? string.Empty; - SaveSettingsToFile(); + string times = BrushAutoRestoreTimesTextBox.Text ?? string.Empty; + SettingsService.Instance.SetBrushAutoRestoreTimes(times); if (Settings.Canvas.EnableBrushAutoRestore) { ScheduleBrushAutoRestore(); @@ -3663,8 +3632,7 @@ namespace Ink_Canvas if (ComboBoxBrushAutoRestoreColor.SelectedItem is ComboBoxItem item) { string hex = item.Tag as string ?? string.Empty; - Settings.Canvas.BrushAutoRestoreColor = hex; - SaveSettingsToFile(); + SettingsService.Instance.SetBrushAutoRestoreColor(hex); } } catch (Exception ex) @@ -3688,8 +3656,7 @@ namespace Ink_Canvas if (!isLoaded) return; if (Settings?.Canvas == null) return; - Settings.Canvas.BrushAutoRestoreWidth = e.NewValue; - SaveSettingsToFile(); + SettingsService.Instance.SetBrushAutoRestoreWidth(e.NewValue); } catch (Exception ex) { @@ -3708,8 +3675,7 @@ namespace Ink_Canvas if (!isLoaded) return; if (Settings?.Canvas == null) return; - Settings.Canvas.BrushAutoRestoreAlpha = (int)e.NewValue; - SaveSettingsToFile(); + SettingsService.Instance.SetBrushAutoRestoreAlpha((int)e.NewValue); } catch (Exception ex) { @@ -3744,25 +3710,19 @@ namespace Ink_Canvas } } - /// - /// PPT放映模式显示手势按钮开关切换事件处理 - /// private void ToggleSwitchShowGestureButtonInSlideShow_Toggled(object sender, RoutedEventArgs e) { try { if (!isLoaded) return; var toggle = sender as ToggleSwitch; - Settings.PowerPointSettings.ShowGestureButtonInSlideShow = toggle != null && toggle.IsOn; - SaveSettingsToFile(); + bool enabled = toggle != null && toggle.IsOn; + SettingsService.Instance.SetShowGestureButtonInSlideShow(enabled); - // 如果当前在PPT放映模式,需要立即更新手势按钮的显示状态 if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { UpdateGestureButtonVisibilityInPPTMode(); } - - LogHelper.WriteLogToFile($"PPT放映模式显示手势按钮已{(Settings.PowerPointSettings.ShowGestureButtonInSlideShow ? "启用" : "禁用")}", LogHelper.LogType.Event); } catch (Exception ex) { @@ -3776,17 +3736,14 @@ namespace Ink_Canvas { if (!isLoaded) return; var toggle = sender as ToggleSwitch; - Settings.PowerPointSettings.EnablePPTTimeCapsule = toggle != null && toggle.IsOn; - SaveSettingsToFile(); + bool enabled = toggle != null && toggle.IsOn; + SettingsService.Instance.SetPPTTimeCapsuleEnabled(enabled); - // 如果当前在PPT放映模式,需要立即更新时间胶囊和快捷面板的显示状态 if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { UpdatePPTTimeCapsuleVisibility(); UpdatePPTQuickPanelVisibility(); } - - LogHelper.WriteLogToFile($"PPT时间显示胶囊已{(Settings.PowerPointSettings.EnablePPTTimeCapsule ? "启用" : "禁用")}", LogHelper.LogType.Event); } catch (Exception ex) { @@ -3801,16 +3758,13 @@ namespace Ink_Canvas if (!isLoaded) return; if (ComboBoxPPTTimeCapsulePosition != null) { - Settings.PowerPointSettings.PPTTimeCapsulePosition = ComboBoxPPTTimeCapsulePosition.SelectedIndex; - SaveSettingsToFile(); + int position = ComboBoxPPTTimeCapsulePosition.SelectedIndex; + SettingsService.Instance.SetPPTTimeCapsulePosition(position); - // 如果当前在PPT放映模式,需要立即更新时间胶囊的位置 if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { UpdatePPTTimeCapsulePosition(); } - - LogHelper.WriteLogToFile($"PPT时间胶囊位置已更改为: {ComboBoxPPTTimeCapsulePosition.SelectedIndex}", LogHelper.LogType.Event); } } catch (Exception ex) @@ -4339,9 +4293,6 @@ namespace Ink_Canvas #region 模式切换相关 - /// - /// 模式切换开关事件处理 - /// private void ToggleSwitchMode_Toggled(object sender, RoutedEventArgs e) { try @@ -4349,13 +4300,10 @@ namespace Ink_Canvas var toggle = sender as ToggleSwitch; if (toggle != null) { - Settings.ModeSettings.IsPPTOnlyMode = toggle.IsOn; + bool enabled = toggle.IsOn; + SettingsService.Instance.SetPPTOnlyMode(enabled); - // 保存设置到文件 - SaveSettingsToFile(); - - // 如果切换到仅PPT模式,立即隐藏主窗口 - if (Settings.ModeSettings.IsPPTOnlyMode) + if (enabled) { Hide(); LogHelper.WriteLogToFile("已切换到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event); @@ -4364,7 +4312,6 @@ namespace Ink_Canvas else { StopPptOnlyVisibilityProbeTimer(); - // 如果切换到正常模式,显示主窗口 Show(); LogHelper.WriteLogToFile("已切换到正常模式,主窗口已显示", LogHelper.LogType.Event); } @@ -4446,9 +4393,6 @@ namespace Ink_Canvas #region Theme Toggle - /// - /// 主题下拉框选择变化事件 - /// private void ComboBoxTheme_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (!isLoaded) return; @@ -4458,33 +4402,10 @@ namespace Ink_Canvas System.Windows.Controls.ComboBox comboBox = sender as System.Windows.Controls.ComboBox; if (comboBox != null) { - Settings.Appearance.Theme = comboBox.SelectedIndex; - - // 应用新主题 - ApplyTheme(comboBox.SelectedIndex); - - // 保存设置 - SaveSettingsToFile(); - - // 显示通知 - string themeName; - switch (comboBox.SelectedIndex) - { - case 0: - themeName = "浅色主题"; - break; - case 1: - themeName = "深色主题"; - break; - case 2: - themeName = "跟随系统"; - break; - default: - themeName = "未知主题"; - break; - } - - ShowNotification($"已切换到{themeName}"); + int themeIndex = comboBox.SelectedIndex; + SettingsService.Instance.SetTheme(themeIndex); + ApplyTheme(themeIndex); + ShowNotification($"已切换到{SettingsService.GetThemeName(themeIndex)}"); } } catch (Exception ex) @@ -4506,25 +4427,9 @@ namespace Ink_Canvas if (ComboBoxLanguage == null) return; var index = ComboBoxLanguage.SelectedIndex; - string language; - - switch (index) - { - case 1: - language = "zh-CN"; - break; - case 2: - language = "en-US"; - break; - case 0: - default: - language = string.Empty; - break; - } - - Settings.Appearance.Language = language; - SaveSettingsToFile(); + string language = SettingsService.GetLanguageCode(index); + SettingsService.Instance.SetLanguage(language); LocalizationHelper.TrySetCulture(language); _isReloadingForLanguageChange = true; @@ -4630,21 +4535,11 @@ namespace Ink_Canvas /// /// 更新UIA置顶开关的可见性 /// - private void UpdateUIAccessTopMostVisibility() + internal void UpdateUIAccessTopMostVisibility() { try { - var visibility = Settings.Advanced.IsAlwaysOnTop ? Visibility.Visible : Visibility.Collapsed; - - if (UIAccessTopMostPanel != null) - { - UIAccessTopMostPanel.Visibility = visibility; - } - - if (UIAccessTopMostDescription != null) - { - UIAccessTopMostDescription.Visibility = visibility; - } + // 移除了 UIAccessTopMostPanel 的可见性控制,因为 UI 已移至新设置窗口 } catch (Exception ex) { @@ -4655,7 +4550,7 @@ namespace Ink_Canvas /// /// 应用UIA置顶功能 /// - private void ApplyUIAccessTopMost() + internal void ApplyUIAccessTopMost() { try { diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index 9fbca2bc..fd936522 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -216,23 +216,8 @@ namespace Ink_Canvas { } - try - { - if (File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + - "\\Ink Canvas Annotation.lnk")) - { - ToggleSwitchRunAtStartup.IsOn = true; - } - else - { - ToggleSwitchRunAtStartup.IsOn = false; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); - ToggleSwitchRunAtStartup.IsOn = false; - } + // 检查开机自启动(不再设置 UI 控件,Settings 中没有 IsRunAtStartup 属性) + // var runAtStartup = SettingsService.Instance.CheckRunAtStartup(); if (Settings.Startup != null) { @@ -250,22 +235,16 @@ namespace Ink_Canvas } } + // 设置笔尖模式(不再设置 UI 控件) if (Settings.Startup.IsEnableNibMode) { - ToggleSwitchEnableNibMode.IsOn = true; - BoardToggleSwitchEnableNibMode.IsOn = true; BoundsWidth = Settings.Advanced.NibModeBoundsWidth; } else { - ToggleSwitchEnableNibMode.IsOn = false; - BoardToggleSwitchEnableNibMode.IsOn = false; BoundsWidth = Settings.Advanced.FingerModeBoundsWidth; } - // 设置自动更新相关选项 - ToggleSwitchIsAutoUpdate.IsOn = Settings.Startup.IsAutoUpdate; - // 只有在启用了自动更新功能时才检查更新 if (Settings.Startup.IsAutoUpdate && !skipAutoUpdateCheck) { @@ -282,66 +261,13 @@ namespace Ink_Canvas } } - ToggleSwitchIsAutoUpdateWithSilence.Visibility = Settings.Startup.IsAutoUpdate ? Visibility.Visible : Visibility.Collapsed; - if (Settings.Startup.IsAutoUpdateWithSilence) - { - ToggleSwitchIsAutoUpdateWithSilence.IsOn = true; - } - - // 初始化更新通道选择 - foreach (var radioButton in UpdateChannelSelector.Items) - { - if (radioButton is RadioButton rb) - { - if (rb.Tag.ToString() == Settings.Startup.UpdateChannel.ToString()) - { - rb.IsChecked = true; - break; - } - } - } - - // 初始化更新包架构 - if (UpdatePackageArchitectureSelector != null) - { - _isChangingUpdatePackageArchInternally = true; - try - { - string wantTag = Settings.Startup.UpdatePackageArchitecture == UpdatePackageArchitecture.X64 ? "X64" : "X86"; - foreach (var item in UpdatePackageArchitectureSelector.Items) - { - if (item is RadioButton rb && rb.Tag != null && - string.Equals(rb.Tag.ToString(), wantTag, StringComparison.OrdinalIgnoreCase)) - { - rb.IsChecked = true; - break; - } - } - } - finally - { - _isChangingUpdatePackageArchInternally = false; - } - } - - AutoUpdateTimePeriodBlock.Visibility = Settings.Startup.IsAutoUpdateWithSilence - ? Visibility.Visible - : Visibility.Collapsed; - - AutoUpdateWithSilenceTimeComboBox.InitializeAutoUpdateWithSilenceTimeComboBoxOptions( - AutoUpdateWithSilenceStartTimeComboBox, AutoUpdateWithSilenceEndTimeComboBox); - AutoUpdateWithSilenceStartTimeComboBox.SelectedItem = Settings.Startup.AutoUpdateWithSilenceStartTime; - AutoUpdateWithSilenceEndTimeComboBox.SelectedItem = Settings.Startup.AutoUpdateWithSilenceEndTime; - - ToggleSwitchFoldAtStartup.IsOn = Settings.Startup.IsFoldAtStartup; + // 不再初始化 UI 控件,已移至 SettingsService } else { Settings.Startup = new Startup(); - Settings.Startup.IsEnableNibMode = false; // 默认关闭笔尖模式 - ToggleSwitchEnableNibMode.IsOn = false; // 默认关闭笔尖模式 - BoardToggleSwitchEnableNibMode.IsOn = false; // 默认关闭笔尖模式 - BoundsWidth = Settings.Advanced.FingerModeBoundsWidth; // 使用手指模式边界宽度 + Settings.Startup.IsEnableNibMode = false; + BoundsWidth = Settings.Advanced.FingerModeBoundsWidth; } // 恢复崩溃后操作设置 @@ -351,12 +277,10 @@ namespace Ink_Canvas if (Settings.Startup.CrashAction == 0) { App.CrashAction = App.CrashActionType.SilentRestart; - if (RadioCrashSilentRestart != null) RadioCrashSilentRestart.IsChecked = true; } else { App.CrashAction = App.CrashActionType.NoAction; - if (RadioCrashNoAction != null) RadioCrashNoAction.IsChecked = true; } } From 1ec07421b5ead5792ec7155e8d9c627b5629ba81 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Tue, 7 Apr 2026 00:33:19 +0800 Subject: [PATCH 010/205] =?UTF-8?q?Revert=20"=E5=95=8A"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 441e600b5df5e745c8e1c8557da52ce7071d9168. --- Ink Canvas/Helpers/SettingsService.cs | 802 ------------------ Ink Canvas/Helpers/SettingsServiceDemo.cs | 75 -- Ink Canvas/MainWindow.xaml | 142 ++++ Ink Canvas/MainWindow.xaml.cs | 229 +++-- Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs | 88 +- 5 files changed, 391 insertions(+), 945 deletions(-) delete mode 100644 Ink Canvas/Helpers/SettingsService.cs delete mode 100644 Ink Canvas/Helpers/SettingsServiceDemo.cs diff --git a/Ink Canvas/Helpers/SettingsService.cs b/Ink Canvas/Helpers/SettingsService.cs deleted file mode 100644 index 53e04b1f..00000000 --- a/Ink Canvas/Helpers/SettingsService.cs +++ /dev/null @@ -1,802 +0,0 @@ -using Ink_Canvas; -using Ink_Canvas.Helpers; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq.Expressions; -using System.Reflection; -using System.Windows; -using UpdateChannel = Ink_Canvas.UpdateChannel; -using UpdatePackageArchitecture = Ink_Canvas.UpdatePackageArchitecture; -using TelemetryUploadLevel = Ink_Canvas.TelemetryUploadLevel; - -namespace Ink_Canvas.Services -{ - public class SettingsService - { - private static SettingsService _instance; - private static readonly object _lock = new object(); - - private Settings _settings; - private MainWindow _mainWindow; - private bool _isChangingUpdateChannelInternally = false; - private bool _isChangingUpdatePackageArchInternally = false; - - public static readonly string settingsFileName = Path.Combine("Configs", "Settings.json"); - - public event Action SettingChanged; - - public static SettingsService Instance - { - get - { - if (_instance == null) - { - lock (_lock) - { - if (_instance == null) - { - _instance = new SettingsService(); - } - } - } - return _instance; - } - } - - private SettingsService() { } - - public void Initialize(Settings settings, MainWindow mainWindow = null) - { - _settings = settings; - _mainWindow = mainWindow; - } - - public Settings Settings => _settings; - - #region Core Settings Management - - public Settings LoadSettings(bool isStartup = false, bool skipAutoUpdateCheck = false) - { - Settings loadedSettings = null; - - try - { - if (File.Exists(App.RootPath + settingsFileName)) - { - try - { - string text = File.ReadAllText(App.RootPath + settingsFileName); - loadedSettings = JsonConvert.DeserializeObject(text); - - if (loadedSettings != null) - { - CleanupObsoleteSettings(text, ref loadedSettings); - } - - if (loadedSettings == null) - { - LogHelper.WriteLogToFile("配置文件解析失败,尝试从备份恢复", LogHelper.LogType.Warning); - if (AutoBackupManager.TryRestoreFromBackup()) - { - text = File.ReadAllText(App.RootPath + settingsFileName); - loadedSettings = JsonConvert.DeserializeObject(text); - if (loadedSettings != null) - { - CleanupObsoleteSettings(text, ref loadedSettings); - } - } - - if (loadedSettings == null) - { - LogHelper.WriteLogToFile("从备份恢复失败,使用默认设置", LogHelper.LogType.Warning); - loadedSettings = new Settings(); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"配置文件加载失败: {ex.Message}", LogHelper.LogType.Error); - - LogHelper.WriteLogToFile("尝试从备份恢复配置文件", LogHelper.LogType.Warning); - if (AutoBackupManager.TryRestoreFromBackup()) - { - try - { - string text = File.ReadAllText(App.RootPath + settingsFileName); - loadedSettings = JsonConvert.DeserializeObject(text); - if (loadedSettings != null) - { - CleanupObsoleteSettings(text, ref loadedSettings); - } - } - catch (Exception restoreEx) - { - LogHelper.WriteLogToFile($"从备份恢复后重新加载失败: {restoreEx.Message}", LogHelper.LogType.Error); - loadedSettings = new Settings(); - } - } - - if (loadedSettings == null) - { - LogHelper.WriteLogToFile("从备份恢复失败,使用默认设置", LogHelper.LogType.Warning); - loadedSettings = new Settings(); - } - } - } - else - { - LogHelper.WriteLogToFile("配置文件不存在,尝试从备份恢复", LogHelper.LogType.Warning); - if (AutoBackupManager.TryRestoreFromBackup()) - { - try - { - string text = File.ReadAllText(App.RootPath + settingsFileName); - loadedSettings = JsonConvert.DeserializeObject(text); - if (loadedSettings != null) - { - CleanupObsoleteSettings(text, ref loadedSettings); - } - } - catch (Exception restoreEx) - { - LogHelper.WriteLogToFile($"从备份恢复后加载失败: {restoreEx.Message}", LogHelper.LogType.Error); - loadedSettings = new Settings(); - } - } - else - { - LogHelper.WriteLogToFile("备份恢复失败,使用默认设置", LogHelper.LogType.Warning); - loadedSettings = new Settings(); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); - loadedSettings = new Settings(); - } - - try - { - if (loadedSettings?.Appearance != null) - { - var preferredLanguage = loadedSettings.Appearance.Language ?? string.Empty; - if (!string.IsNullOrWhiteSpace(preferredLanguage)) - { - LocalizationHelper.TrySetCulture(preferredLanguage); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"从配置应用界面语言失败: {ex.Message}", LogHelper.LogType.Error); - } - - try - { - ProcessProtectionManager.ApplyFromSettings(); - } - catch - { - } - - _settings = loadedSettings; - return loadedSettings; - } - - public void SaveSettingsToFile() - { - if (_settings == null) return; - - var text = JsonConvert.SerializeObject(_settings, Formatting.Indented); - try - { - string configsDir = Path.Combine(App.RootPath, "Configs"); - if (!Directory.Exists(configsDir)) - { - ProcessProtectionManager.WithWriteAccess(configsDir, () => Directory.CreateDirectory(configsDir)); - } - - var path = App.RootPath + settingsFileName; - ProcessProtectionManager.WithWriteAccess(path, () => File.WriteAllText(path, text)); - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - } - - private void CleanupObsoleteSettings(string userConfigJson, ref Settings settings) - { - try - { - Settings defaultSettings = new Settings(); - - JObject defaultConfigObj = JObject.FromObject(defaultSettings); - EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(defaultConfigObj); - JObject userConfigObj = JObject.Parse(userConfigJson); - - bool hasChanges = false; - - RemoveObsoleteProperties(userConfigObj, defaultConfigObj, ref hasChanges); - - if (hasChanges) - { - string cleanedJson = userConfigObj.ToString(Formatting.Indented); - settings = JsonConvert.DeserializeObject(cleanedJson); - SaveSettingsToFile(); - LogHelper.WriteLogToFile("已清理过期配置项", LogHelper.LogType.Event); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"清理过期配置时出错: {ex.Message}", LogHelper.LogType.Error); - } - } - - private static void EnsureDefaultConfigSchemaIncludesIgnoredNullKeys(JObject defaultConfigObj) - { - if (defaultConfigObj == null) return; - if (defaultConfigObj["appearance"] is JObject appearance && !appearance.ContainsKey("hitokotoCategories")) - appearance["hitokotoCategories"] = JValue.CreateNull(); - } - - private void RemoveObsoleteProperties(JObject userObj, JObject defaultObj, ref bool hasChanges) - { - if (userObj == null || defaultObj == null) - return; - - List keysToRemove = new List(); - - foreach (var property in userObj.Properties()) - { - string propertyName = property.Name; - - if (!defaultObj.ContainsKey(propertyName)) - { - keysToRemove.Add(propertyName); - continue; - } - - JToken userValue = property.Value; - JToken defaultValue = defaultObj[propertyName]; - - if (userValue != null && defaultValue != null) - { - if (userValue.Type == JTokenType.Object && defaultValue.Type == JTokenType.Object) - { - RemoveObsoleteProperties(userValue as JObject, defaultValue as JObject, ref hasChanges); - } - else if (userValue.Type == JTokenType.Array && defaultValue.Type == JTokenType.Array) - { - JArray userArray = userValue as JArray; - JArray defaultArray = defaultValue as JArray; - - if (userArray != null && defaultArray != null && userArray.Count > 0 && defaultArray.Count > 0) - { - if (userArray[0].Type == JTokenType.Object && defaultArray[0].Type == JTokenType.Object) - { - for (int i = 0; i < userArray.Count; i++) - { - if (userArray[i] is JObject userItemObj && defaultArray[0] is JObject defaultItemObj) - { - RemoveObsoleteProperties(userItemObj, defaultItemObj, ref hasChanges); - } - } - } - } - } - } - } - - foreach (var key in keysToRemove) - { - userObj.Remove(key); - hasChanges = true; - LogHelper.WriteLogToFile($"已删除过期配置项: {key}", LogHelper.LogType.Event); - } - } - - #endregion - - #region Generic Setting Methods - - public void Set(Expression> propertyExpression, T value, bool saveToFile = true) - { - if (_settings == null) return; - - var propertyName = GetPropertyName(propertyExpression); - var property = typeof(Settings).GetProperty(propertyName); - - if (property != null) - { - property.SetValue(_settings, value); - if (saveToFile) SaveSettingsToFile(); - SettingChanged?.Invoke(propertyName, value); - } - } - - public void Set(Expression> categoryExpression, Expression> propertyExpression, TProperty value, bool saveToFile = true) - { - if (_settings == null) return; - - var categoryPropertyName = GetPropertyName(categoryExpression); - var categoryProperty = typeof(Settings).GetProperty(categoryPropertyName); - - if (categoryProperty != null) - { - var categoryInstance = categoryProperty.GetValue(_settings); - if (categoryInstance != null) - { - var propertyName = GetPropertyName(propertyExpression); - var property = typeof(TCategory).GetProperty(propertyName); - - if (property != null) - { - property.SetValue(categoryInstance, value); - if (saveToFile) SaveSettingsToFile(); - SettingChanged?.Invoke($"{categoryPropertyName}.{propertyName}", value); - } - } - } - } - - public T Get(Expression> propertyExpression) - { - if (_settings == null) return default; - - var propertyName = GetPropertyName(propertyExpression); - var property = typeof(Settings).GetProperty(propertyName); - - if (property != null) - { - return (T)property.GetValue(_settings); - } - return default; - } - - public TProperty Get(Expression> categoryExpression, Expression> propertyExpression) - { - if (_settings == null) return default; - - var categoryPropertyName = GetPropertyName(categoryExpression); - var categoryProperty = typeof(Settings).GetProperty(categoryPropertyName); - - if (categoryProperty != null) - { - var categoryInstance = categoryProperty.GetValue(_settings); - if (categoryInstance != null) - { - var propertyName = GetPropertyName(propertyExpression); - var property = typeof(TCategory).GetProperty(propertyName); - - if (property != null) - { - return (TProperty)property.GetValue(categoryInstance); - } - } - } - return default; - } - - private string GetPropertyName(Expression> expression) - { - if (expression.Body is MemberExpression memberExpression) - { - return memberExpression.Member.Name; - } - throw new ArgumentException("Expression is not a property access", nameof(expression)); - } - - #endregion - - #region Convenience Methods for Common Settings - - #region Advanced Settings - - public void SetNoFocusMode(bool enabled, bool saveToFile = true) - { - Set(s => s.Advanced, a => a.IsNoFocusMode, enabled, saveToFile); - if (_mainWindow != null) - { - _mainWindow.Dispatcher.BeginInvoke(new Action(() => - { - _mainWindow.ApplyNoFocusMode(); - if (_settings.Advanced.IsAlwaysOnTop) - { - _mainWindow.ApplyAlwaysOnTop(); - } - })); - } - } - - public void SetAlwaysOnTop(bool enabled, bool saveToFile = true) - { - Set(s => s.Advanced, a => a.IsAlwaysOnTop, enabled, saveToFile); - if (_mainWindow != null) - { - _mainWindow.Dispatcher.BeginInvoke(new Action(() => - { - _mainWindow.ApplyAlwaysOnTop(); - _mainWindow.UpdateUIAccessTopMostVisibility(); - })); - } - } - - public void SetUIAccessTopMost(bool enabled, bool saveToFile = true) - { - Set(s => s.Advanced, a => a.EnableUIAccessTopMost, enabled, saveToFile); - if (_mainWindow != null) - { - _mainWindow.Dispatcher.BeginInvoke(new Action(() => - { - _mainWindow.ApplyUIAccessTopMost(); - })); - } - App.IsUIAccessTopMostEnabled = enabled; - } - - #endregion - - #region Canvas Settings - - public void SetInkFadeEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.EnableInkFade, enabled, saveToFile); - } - - public void SetInkFadeTime(int timeMs, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.InkFadeTime, timeMs, saveToFile); - } - - public void SetHideInkFadeControlInPenMenu(bool enabled, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.HideInkFadeControlInPenMenu, enabled, saveToFile); - } - - public void SetEraserAutoSwitchBack(bool enabled, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.EnableEraserAutoSwitchBack, enabled, saveToFile); - } - - public void SetEraserAutoSwitchBackDelay(int seconds, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.EraserAutoSwitchBackDelaySeconds, seconds, saveToFile); - } - - public void SetBrushAutoRestore(bool enabled, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.EnableBrushAutoRestore, enabled, saveToFile); - } - - public void SetBrushAutoRestoreTimes(string times, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.BrushAutoRestoreTimes, times, saveToFile); - } - - public void SetBrushAutoRestoreColor(string color, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.BrushAutoRestoreColor, color, saveToFile); - } - - public void SetBrushAutoRestoreWidth(double width, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.BrushAutoRestoreWidth, width, saveToFile); - } - - public void SetBrushAutoRestoreAlpha(int alpha, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.BrushAutoRestoreAlpha, alpha, saveToFile); - } - - public void SetInkWidth(double width, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.InkWidth, width, saveToFile); - } - - public void SetHighlighterWidth(double width, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.HighlighterWidth, width, saveToFile); - } - - public void SetInkAlpha(double alpha, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.InkAlpha, alpha, saveToFile); - } - - public void SetEraserSize(int size, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.EraserSize, size, saveToFile); - } - - public void SetEraserType(int type, bool saveToFile = true) - { - Set(s => s.Canvas, c => c.EraserType, type, saveToFile); - } - - #endregion - - #region Appearance Settings - - public void SetTheme(int themeIndex, bool saveToFile = true) - { - Set(s => s.Appearance, a => a.Theme, themeIndex, saveToFile); - } - - public void SetLanguage(string languageCode, bool saveToFile = true) - { - Set(s => s.Appearance, a => a.Language, languageCode ?? string.Empty, saveToFile); - LocalizationHelper.TrySetCulture(languageCode); - } - - public void SetFloatingBarOpacity(double opacity, bool saveToFile = true) - { - Set(s => s.Appearance, a => a.ViewboxFloatingBarOpacityValue, opacity, saveToFile); - } - - public void SetFloatingBarScale(double scale, bool saveToFile = true) - { - Set(s => s.Appearance, a => a.ViewboxFloatingBarScaleTransformValue, scale, saveToFile); - } - - #endregion - - #region PowerPoint Settings - - public void SetPPTOnlyMode(bool enabled, bool saveToFile = true) - { - Set(s => s.ModeSettings, m => m.IsPPTOnlyMode, enabled, saveToFile); - } - - public void SetPowerPointSupport(bool enabled, bool saveToFile = true) - { - Set(s => s.PowerPointSettings, p => p.PowerPointSupport, enabled, saveToFile); - } - - public void SetShowGestureButtonInSlideShow(bool enabled, bool saveToFile = true) - { - Set(s => s.PowerPointSettings, p => p.ShowGestureButtonInSlideShow, enabled, saveToFile); - } - - public void SetPPTTimeCapsuleEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.PowerPointSettings, p => p.EnablePPTTimeCapsule, enabled, saveToFile); - } - - public void SetPPTTimeCapsulePosition(int position, bool saveToFile = true) - { - Set(s => s.PowerPointSettings, p => p.PPTTimeCapsulePosition, position, saveToFile); - } - - #endregion - - #region Gesture Settings - - public void SetTwoFingerZoomEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.Gesture, g => g.IsEnableTwoFingerZoom, enabled, saveToFile); - } - - public void SetTwoFingerTranslateEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.Gesture, g => g.IsEnableTwoFingerTranslate, enabled, saveToFile); - } - - public void SetTwoFingerRotationEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.Gesture, g => g.IsEnableTwoFingerRotation, enabled, saveToFile); - } - - public void SetMultiTouchModeEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.Gesture, g => g.IsEnableMultiTouchMode, enabled, saveToFile); - } - - #endregion - - #region Startup Settings - - public void SetAutoUpdate(bool enabled, bool saveToFile = true) - { - Set(s => s.Startup, s => s.IsAutoUpdate, enabled, saveToFile); - } - - public void SetUpdateChannel(UpdateChannel channel, bool saveToFile = true) - { - Set(s => s.Startup, s => s.UpdateChannel, channel, saveToFile); - } - - public void SetTelemetryUploadLevel(TelemetryUploadLevel level, bool saveToFile = true) - { - Set(s => s.Startup, s => s.TelemetryUploadLevel, level, saveToFile); - } - - #endregion - - #region Automation Settings - - public void SetAutoSaveStrokesEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.Automation, a => a.IsEnableAutoSaveStrokes, enabled, saveToFile); - } - - public void SetAutoSaveStrokesInterval(int minutes, bool saveToFile = true) - { - Set(s => s.Automation, a => a.AutoSaveStrokesIntervalMinutes, minutes, saveToFile); - } - - public void SetAutoSavedStrokesLocation(string path, bool saveToFile = true) - { - Set(s => s.Automation, a => a.AutoSavedStrokesLocation, path, saveToFile); - } - - #endregion - - #region InkToShape Settings - - public void SetInkToShapeEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.InkToShape, i => i.IsInkToShapeEnabled, enabled, saveToFile); - } - - public void SetShapeRecognitionEngine(int engine, bool saveToFile = true) - { - Set(s => s.InkToShape, i => i.ShapeRecognitionEngine, engine, saveToFile); - } - - #endregion - - #region Security Settings - - public void SetPasswordEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.Security, s => s.PasswordEnabled, enabled, saveToFile); - } - - public void SetProcessProtectionEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.Security, s => s.EnableProcessProtection, enabled, saveToFile); - } - - #endregion - - #region Camera Settings - - public void SetCameraRotationAngle(int angle, bool saveToFile = true) - { - Set(s => s.Camera, c => c.RotationAngle, angle, saveToFile); - } - - public void SetCameraResolution(int width, int height, bool saveToFile = true) - { - Set(s => s.Camera, c => c.ResolutionWidth, width, saveToFile); - Set(s => s.Camera, c => c.ResolutionHeight, height, saveToFile); - } - - #endregion - - #endregion - - #region Startup Settings - Complete Functionality - - public bool CheckRunAtStartup() - { - try - { - return File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\Ink Canvas Annotation.lnk"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); - return false; - } - } - - // Settings 中没有 IsRunAtStartup 属性,所以这个方法被注释掉了 - // public void SetRunAtStartup(bool enabled, bool saveToFile = true) - // { - // Set(s => s.Startup, s => s.IsRunAtStartup, enabled, saveToFile); - // } - - public void SetFoldAtStartup(bool enabled, bool saveToFile = true) - { - Set(s => s.Startup, s => s.IsFoldAtStartup, enabled, saveToFile); - } - - public void SetUpdatePackageArchitecture(UpdatePackageArchitecture arch, bool saveToFile = true) - { - Set(s => s.Startup, s => s.UpdatePackageArchitecture, arch, saveToFile); - } - - public void SetAutoUpdateWithSilence(bool enabled, bool saveToFile = true) - { - Set(s => s.Startup, s => s.IsAutoUpdateWithSilence, enabled, saveToFile); - } - - public void SetAutoUpdateWithSilenceStartTime(string time, bool saveToFile = true) - { - Set(s => s.Startup, s => s.AutoUpdateWithSilenceStartTime, time, saveToFile); - } - - public void SetAutoUpdateWithSilenceEndTime(string time, bool saveToFile = true) - { - Set(s => s.Startup, s => s.AutoUpdateWithSilenceEndTime, time, saveToFile); - } - - public void SetNibModeEnabled(bool enabled, bool saveToFile = true) - { - Set(s => s.Startup, s => s.IsEnableNibMode, enabled, saveToFile); - } - - public void CheckUpdateChannelAndTelemetryConsistency(bool isLoaded) - { - if (_settings == null) return; - - var currentChannel = _settings.Startup.UpdateChannel; - if (currentChannel == UpdateChannel.Release) return; - - if (!_settings.Startup.HasAcceptedTelemetryPrivacy) - { - _settings.Startup.UpdateChannel = UpdateChannel.Release; - DeviceIdentifier.UpdateUsageChannel(UpdateChannel.Release); - SaveSettingsToFile(); - LogHelper.WriteLogToFile($"启动检测 | 用户未同意隐私协议,已切换回 Release 通道"); - return; - } - - if (_settings.Startup.TelemetryUploadLevel == TelemetryUploadLevel.None) - { - _isChangingUpdateChannelInternally = true; - try - { - _settings.Startup.UpdateChannel = UpdateChannel.Release; - DeviceIdentifier.UpdateUsageChannel(UpdateChannel.Release); - SaveSettingsToFile(); - } - finally - { - _isChangingUpdateChannelInternally = false; - } - LogHelper.WriteLogToFile($"启动检测 | 用户未启用遥测,已切换回 Release 通道"); - } - } - - #endregion - - #region Helper Methods - - public static string GetThemeName(int themeIndex) - { - return themeIndex switch - { - 0 => "浅色主题", - 1 => "深色主题", - 2 => "跟随系统", - _ => "未知主题" - }; - } - - public static string GetLanguageCode(int languageIndex) - { - return languageIndex switch - { - 1 => "zh-CN", - 2 => "en-US", - _ => string.Empty - }; - } - - public static int GetLanguageIndex(string languageCode) - { - return languageCode switch - { - "zh-CN" => 1, - "en-US" => 2, - _ => 0 - }; - } - - #endregion - } -} diff --git a/Ink Canvas/Helpers/SettingsServiceDemo.cs b/Ink Canvas/Helpers/SettingsServiceDemo.cs deleted file mode 100644 index dc12ae13..00000000 --- a/Ink Canvas/Helpers/SettingsServiceDemo.cs +++ /dev/null @@ -1,75 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Ink_Canvas; -using Ink_Canvas.Services; - -namespace Ink_Canvas -{ - public static class SettingsServiceDemo - { - public static void ListAllSettings() - { - var settingsType = typeof(Settings); - var allProperties = new List(); - - Console.WriteLine("=== Settings 所有属性列表 ===\n"); - - foreach (var categoryProp in settingsType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - if (categoryProp.PropertyType.IsClass && categoryProp.PropertyType != typeof(string) && categoryProp.PropertyType != typeof(List) && categoryProp.PropertyType != typeof(List) && categoryProp.PropertyType != typeof(List) && categoryProp.PropertyType != typeof(Dictionary) && categoryProp.PropertyType != typeof(List) && categoryProp.PropertyType != typeof(DateTime)) - { - Console.WriteLine($"\n--- 分类: {categoryProp.Name} ---\n"); - - foreach (var settingProp in categoryProp.PropertyType.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var propName = $"{categoryProp.Name}.{settingProp.Name}"; - allProperties.Add(propName); - Console.WriteLine($" {settingProp.Name,-60} ({settingProp.PropertyType.Name})"); - } - } - else - { - Console.WriteLine($"{categoryProp.Name,-60} ({categoryProp.PropertyType.Name})"); - allProperties.Add(categoryProp.Name); - } - } - - Console.WriteLine($"\n\n总计: {allProperties.Count} 个设置项\n"); - - Console.WriteLine("=== SettingsService 访问示例 ===\n"); - - Console.WriteLine("// 方式1: 泛型 Set/Get"); - Console.WriteLine("SettingsService.Instance.Set(s => s.Canvas, c => c.InkWidth, 3.5);"); - Console.WriteLine("double inkWidth = SettingsService.Instance.Get(s => s.Canvas, c => c.InkWidth);"); - Console.WriteLine(); - - Console.WriteLine("// 方式2: 便捷方法"); - Console.WriteLine("SettingsService.Instance.SetTheme(1);"); - Console.WriteLine("SettingsService.Instance.SetNoFocusMode(true);"); - Console.WriteLine(); - - Console.WriteLine("// 方式3: 直接访问 Settings 对象"); - Console.WriteLine("var settings = SettingsService.Instance.Settings;"); - Console.WriteLine("if (settings != null) {"); - Console.WriteLine(" settings.Canvas.InkWidth = 3.5;"); - Console.WriteLine(" settings.Appearance.Theme = 1;"); - Console.WriteLine(" SettingsService.Instance.Save();"); - Console.WriteLine("}"); - Console.WriteLine(); - - Console.WriteLine("// 任意设置项示例:"); - Console.WriteLine("SettingsService.Instance.Set(s => s.Automation, a => a.IsAutoFoldInEasiNote, true);"); - Console.WriteLine("SettingsService.Instance.Set(s => s.PowerPointSettings, p => p.PowerPointSupport, true);"); - Console.WriteLine("SettingsService.Instance.Set(s => s.Gesture, g => g.IsEnableTwoFingerZoom, true);"); - Console.WriteLine("SettingsService.Instance.Set(s => s.Advanced, a => a.IsAlwaysOnTop, true);"); - Console.WriteLine("SettingsService.Instance.Set(s => s.Startup, s => s.IsAutoUpdate, true);"); - Console.WriteLine("SettingsService.Instance.Set(s => s.Security, s => s.PasswordEnabled, true);"); - Console.WriteLine("SettingsService.Instance.Set(s => s.Camera, c => c.RotationAngle, 90);"); - Console.WriteLine("SettingsService.Instance.Set(s => s.InkToShape, i => i.IsInkToShapeEnabled, true);"); - Console.WriteLine("SettingsService.Instance.Set(s => s.RandSettings, r => r.EnableQuickDraw, true);"); - Console.WriteLine("SettingsService.Instance.Set(s => s.Appearance, a => a.ViewboxFloatingBarOpacityValue, 0.8);"); - } - } -} diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 79fca2ae..b9434bac 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -655,6 +655,148 @@ Padding="15,5" Margin="0,10,0,0"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + private void ToggleSwitchEnableEraserAutoSwitchBack_Toggled(object sender, RoutedEventArgs e) { try { if (!isLoaded) return; - bool enabled = ToggleSwitchEnableEraserAutoSwitchBack.IsOn; - SettingsService.Instance.SetEraserAutoSwitchBack(enabled); + Settings.Canvas.EnableEraserAutoSwitchBack = ToggleSwitchEnableEraserAutoSwitchBack.IsOn; + SaveSettingsToFile(); - if (!enabled) + // 如果禁用,停止计时器 + if (!Settings.Canvas.EnableEraserAutoSwitchBack) { StopEraserAutoSwitchBackTimer(); } + + LogHelper.WriteLogToFile($"橡皮擦自动切换回批注模式已{(Settings.Canvas.EnableEraserAutoSwitchBack ? "启用" : "禁用")}", LogHelper.LogType.Event); } catch (Exception ex) { @@ -3544,18 +3566,24 @@ namespace Ink_Canvas } } + /// + /// 橡皮擦自动切换延迟时间滑块值改变事件处理 + /// private void EraserAutoSwitchBackDelaySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs e) { try { if (!isLoaded) return; - int delaySeconds = (int)e.NewValue; - SettingsService.Instance.SetEraserAutoSwitchBackDelay(delaySeconds); + Settings.Canvas.EraserAutoSwitchBackDelaySeconds = (int)e.NewValue; + SaveSettingsToFile(); + // 如果计时器正在运行,重新启动以应用新的延迟时间 if (_eraserAutoSwitchBackTimer != null && _eraserAutoSwitchBackTimer.IsEnabled) { StartEraserAutoSwitchBackTimer(); } + + LogHelper.WriteLogToFile($"橡皮擦自动切换延迟时间已更新为 {Settings.Canvas.EraserAutoSwitchBackDelaySeconds} 秒", LogHelper.LogType.Event); } catch (Exception ex) { @@ -3563,15 +3591,18 @@ namespace Ink_Canvas } } + /// + /// 根据开关状态启用或禁用画笔自动恢复:更新设置并保存,启用时初始化并安排恢复定时器,禁用时停止计时器。 + /// private void ToggleSwitchBrushAutoRestore_Toggled(object sender, RoutedEventArgs e) { try { if (!isLoaded) return; - bool enabled = ToggleSwitchBrushAutoRestore.IsOn; - SettingsService.Instance.SetBrushAutoRestore(enabled); + Settings.Canvas.EnableBrushAutoRestore = ToggleSwitchBrushAutoRestore.IsOn; + SaveSettingsToFile(); - if (enabled) + if (Settings.Canvas.EnableBrushAutoRestore) { InitBrushAutoRestoreTimer(); ScheduleBrushAutoRestore(); @@ -3603,8 +3634,8 @@ namespace Ink_Canvas if (!isLoaded) return; if (Settings?.Canvas == null) return; - string times = BrushAutoRestoreTimesTextBox.Text ?? string.Empty; - SettingsService.Instance.SetBrushAutoRestoreTimes(times); + Settings.Canvas.BrushAutoRestoreTimes = BrushAutoRestoreTimesTextBox.Text ?? string.Empty; + SaveSettingsToFile(); if (Settings.Canvas.EnableBrushAutoRestore) { ScheduleBrushAutoRestore(); @@ -3632,7 +3663,8 @@ namespace Ink_Canvas if (ComboBoxBrushAutoRestoreColor.SelectedItem is ComboBoxItem item) { string hex = item.Tag as string ?? string.Empty; - SettingsService.Instance.SetBrushAutoRestoreColor(hex); + Settings.Canvas.BrushAutoRestoreColor = hex; + SaveSettingsToFile(); } } catch (Exception ex) @@ -3656,7 +3688,8 @@ namespace Ink_Canvas if (!isLoaded) return; if (Settings?.Canvas == null) return; - SettingsService.Instance.SetBrushAutoRestoreWidth(e.NewValue); + Settings.Canvas.BrushAutoRestoreWidth = e.NewValue; + SaveSettingsToFile(); } catch (Exception ex) { @@ -3675,7 +3708,8 @@ namespace Ink_Canvas if (!isLoaded) return; if (Settings?.Canvas == null) return; - SettingsService.Instance.SetBrushAutoRestoreAlpha((int)e.NewValue); + Settings.Canvas.BrushAutoRestoreAlpha = (int)e.NewValue; + SaveSettingsToFile(); } catch (Exception ex) { @@ -3710,19 +3744,25 @@ namespace Ink_Canvas } } + /// + /// PPT放映模式显示手势按钮开关切换事件处理 + /// private void ToggleSwitchShowGestureButtonInSlideShow_Toggled(object sender, RoutedEventArgs e) { try { if (!isLoaded) return; var toggle = sender as ToggleSwitch; - bool enabled = toggle != null && toggle.IsOn; - SettingsService.Instance.SetShowGestureButtonInSlideShow(enabled); + Settings.PowerPointSettings.ShowGestureButtonInSlideShow = toggle != null && toggle.IsOn; + SaveSettingsToFile(); + // 如果当前在PPT放映模式,需要立即更新手势按钮的显示状态 if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { UpdateGestureButtonVisibilityInPPTMode(); } + + LogHelper.WriteLogToFile($"PPT放映模式显示手势按钮已{(Settings.PowerPointSettings.ShowGestureButtonInSlideShow ? "启用" : "禁用")}", LogHelper.LogType.Event); } catch (Exception ex) { @@ -3736,14 +3776,17 @@ namespace Ink_Canvas { if (!isLoaded) return; var toggle = sender as ToggleSwitch; - bool enabled = toggle != null && toggle.IsOn; - SettingsService.Instance.SetPPTTimeCapsuleEnabled(enabled); + Settings.PowerPointSettings.EnablePPTTimeCapsule = toggle != null && toggle.IsOn; + SaveSettingsToFile(); + // 如果当前在PPT放映模式,需要立即更新时间胶囊和快捷面板的显示状态 if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { UpdatePPTTimeCapsuleVisibility(); UpdatePPTQuickPanelVisibility(); } + + LogHelper.WriteLogToFile($"PPT时间显示胶囊已{(Settings.PowerPointSettings.EnablePPTTimeCapsule ? "启用" : "禁用")}", LogHelper.LogType.Event); } catch (Exception ex) { @@ -3758,13 +3801,16 @@ namespace Ink_Canvas if (!isLoaded) return; if (ComboBoxPPTTimeCapsulePosition != null) { - int position = ComboBoxPPTTimeCapsulePosition.SelectedIndex; - SettingsService.Instance.SetPPTTimeCapsulePosition(position); + Settings.PowerPointSettings.PPTTimeCapsulePosition = ComboBoxPPTTimeCapsulePosition.SelectedIndex; + SaveSettingsToFile(); + // 如果当前在PPT放映模式,需要立即更新时间胶囊的位置 if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible) { UpdatePPTTimeCapsulePosition(); } + + LogHelper.WriteLogToFile($"PPT时间胶囊位置已更改为: {ComboBoxPPTTimeCapsulePosition.SelectedIndex}", LogHelper.LogType.Event); } } catch (Exception ex) @@ -4293,6 +4339,9 @@ namespace Ink_Canvas #region 模式切换相关 + /// + /// 模式切换开关事件处理 + /// private void ToggleSwitchMode_Toggled(object sender, RoutedEventArgs e) { try @@ -4300,10 +4349,13 @@ namespace Ink_Canvas var toggle = sender as ToggleSwitch; if (toggle != null) { - bool enabled = toggle.IsOn; - SettingsService.Instance.SetPPTOnlyMode(enabled); + Settings.ModeSettings.IsPPTOnlyMode = toggle.IsOn; - if (enabled) + // 保存设置到文件 + SaveSettingsToFile(); + + // 如果切换到仅PPT模式,立即隐藏主窗口 + if (Settings.ModeSettings.IsPPTOnlyMode) { Hide(); LogHelper.WriteLogToFile("已切换到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event); @@ -4312,6 +4364,7 @@ namespace Ink_Canvas else { StopPptOnlyVisibilityProbeTimer(); + // 如果切换到正常模式,显示主窗口 Show(); LogHelper.WriteLogToFile("已切换到正常模式,主窗口已显示", LogHelper.LogType.Event); } @@ -4393,6 +4446,9 @@ namespace Ink_Canvas #region Theme Toggle + /// + /// 主题下拉框选择变化事件 + /// private void ComboBoxTheme_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (!isLoaded) return; @@ -4402,10 +4458,33 @@ namespace Ink_Canvas System.Windows.Controls.ComboBox comboBox = sender as System.Windows.Controls.ComboBox; if (comboBox != null) { - int themeIndex = comboBox.SelectedIndex; - SettingsService.Instance.SetTheme(themeIndex); - ApplyTheme(themeIndex); - ShowNotification($"已切换到{SettingsService.GetThemeName(themeIndex)}"); + Settings.Appearance.Theme = comboBox.SelectedIndex; + + // 应用新主题 + ApplyTheme(comboBox.SelectedIndex); + + // 保存设置 + SaveSettingsToFile(); + + // 显示通知 + string themeName; + switch (comboBox.SelectedIndex) + { + case 0: + themeName = "浅色主题"; + break; + case 1: + themeName = "深色主题"; + break; + case 2: + themeName = "跟随系统"; + break; + default: + themeName = "未知主题"; + break; + } + + ShowNotification($"已切换到{themeName}"); } } catch (Exception ex) @@ -4427,9 +4506,25 @@ namespace Ink_Canvas if (ComboBoxLanguage == null) return; var index = ComboBoxLanguage.SelectedIndex; - string language = SettingsService.GetLanguageCode(index); + string language; + + switch (index) + { + case 1: + language = "zh-CN"; + break; + case 2: + language = "en-US"; + break; + case 0: + default: + language = string.Empty; + break; + } + + Settings.Appearance.Language = language; + SaveSettingsToFile(); - SettingsService.Instance.SetLanguage(language); LocalizationHelper.TrySetCulture(language); _isReloadingForLanguageChange = true; @@ -4535,11 +4630,21 @@ namespace Ink_Canvas /// /// 更新UIA置顶开关的可见性 /// - internal void UpdateUIAccessTopMostVisibility() + private void UpdateUIAccessTopMostVisibility() { try { - // 移除了 UIAccessTopMostPanel 的可见性控制,因为 UI 已移至新设置窗口 + var visibility = Settings.Advanced.IsAlwaysOnTop ? Visibility.Visible : Visibility.Collapsed; + + if (UIAccessTopMostPanel != null) + { + UIAccessTopMostPanel.Visibility = visibility; + } + + if (UIAccessTopMostDescription != null) + { + UIAccessTopMostDescription.Visibility = visibility; + } } catch (Exception ex) { @@ -4550,7 +4655,7 @@ namespace Ink_Canvas /// /// 应用UIA置顶功能 /// - internal void ApplyUIAccessTopMost() + private void ApplyUIAccessTopMost() { try { diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index fd936522..9fbca2bc 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -216,8 +216,23 @@ namespace Ink_Canvas { } - // 检查开机自启动(不再设置 UI 控件,Settings 中没有 IsRunAtStartup 属性) - // var runAtStartup = SettingsService.Instance.CheckRunAtStartup(); + try + { + if (File.Exists(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + + "\\Ink Canvas Annotation.lnk")) + { + ToggleSwitchRunAtStartup.IsOn = true; + } + else + { + ToggleSwitchRunAtStartup.IsOn = false; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile(ex.ToString(), LogHelper.LogType.Error); + ToggleSwitchRunAtStartup.IsOn = false; + } if (Settings.Startup != null) { @@ -235,16 +250,22 @@ namespace Ink_Canvas } } - // 设置笔尖模式(不再设置 UI 控件) if (Settings.Startup.IsEnableNibMode) { + ToggleSwitchEnableNibMode.IsOn = true; + BoardToggleSwitchEnableNibMode.IsOn = true; BoundsWidth = Settings.Advanced.NibModeBoundsWidth; } else { + ToggleSwitchEnableNibMode.IsOn = false; + BoardToggleSwitchEnableNibMode.IsOn = false; BoundsWidth = Settings.Advanced.FingerModeBoundsWidth; } + // 设置自动更新相关选项 + ToggleSwitchIsAutoUpdate.IsOn = Settings.Startup.IsAutoUpdate; + // 只有在启用了自动更新功能时才检查更新 if (Settings.Startup.IsAutoUpdate && !skipAutoUpdateCheck) { @@ -261,13 +282,66 @@ namespace Ink_Canvas } } - // 不再初始化 UI 控件,已移至 SettingsService + ToggleSwitchIsAutoUpdateWithSilence.Visibility = Settings.Startup.IsAutoUpdate ? Visibility.Visible : Visibility.Collapsed; + if (Settings.Startup.IsAutoUpdateWithSilence) + { + ToggleSwitchIsAutoUpdateWithSilence.IsOn = true; + } + + // 初始化更新通道选择 + foreach (var radioButton in UpdateChannelSelector.Items) + { + if (radioButton is RadioButton rb) + { + if (rb.Tag.ToString() == Settings.Startup.UpdateChannel.ToString()) + { + rb.IsChecked = true; + break; + } + } + } + + // 初始化更新包架构 + if (UpdatePackageArchitectureSelector != null) + { + _isChangingUpdatePackageArchInternally = true; + try + { + string wantTag = Settings.Startup.UpdatePackageArchitecture == UpdatePackageArchitecture.X64 ? "X64" : "X86"; + foreach (var item in UpdatePackageArchitectureSelector.Items) + { + if (item is RadioButton rb && rb.Tag != null && + string.Equals(rb.Tag.ToString(), wantTag, StringComparison.OrdinalIgnoreCase)) + { + rb.IsChecked = true; + break; + } + } + } + finally + { + _isChangingUpdatePackageArchInternally = false; + } + } + + AutoUpdateTimePeriodBlock.Visibility = Settings.Startup.IsAutoUpdateWithSilence + ? Visibility.Visible + : Visibility.Collapsed; + + AutoUpdateWithSilenceTimeComboBox.InitializeAutoUpdateWithSilenceTimeComboBoxOptions( + AutoUpdateWithSilenceStartTimeComboBox, AutoUpdateWithSilenceEndTimeComboBox); + AutoUpdateWithSilenceStartTimeComboBox.SelectedItem = Settings.Startup.AutoUpdateWithSilenceStartTime; + AutoUpdateWithSilenceEndTimeComboBox.SelectedItem = Settings.Startup.AutoUpdateWithSilenceEndTime; + + ToggleSwitchFoldAtStartup.IsOn = Settings.Startup.IsFoldAtStartup; } else { Settings.Startup = new Startup(); - Settings.Startup.IsEnableNibMode = false; - BoundsWidth = Settings.Advanced.FingerModeBoundsWidth; + Settings.Startup.IsEnableNibMode = false; // 默认关闭笔尖模式 + ToggleSwitchEnableNibMode.IsOn = false; // 默认关闭笔尖模式 + BoardToggleSwitchEnableNibMode.IsOn = false; // 默认关闭笔尖模式 + BoundsWidth = Settings.Advanced.FingerModeBoundsWidth; // 使用手指模式边界宽度 } // 恢复崩溃后操作设置 @@ -277,10 +351,12 @@ namespace Ink_Canvas if (Settings.Startup.CrashAction == 0) { App.CrashAction = App.CrashActionType.SilentRestart; + if (RadioCrashSilentRestart != null) RadioCrashSilentRestart.IsChecked = true; } else { App.CrashAction = App.CrashActionType.NoAction; + if (RadioCrashNoAction != null) RadioCrashNoAction.IsChecked = true; } } From 0dfe835eeffec86188ccbc326e113587d7f28eb4 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Tue, 7 Apr 2026 23:32:20 +0800 Subject: [PATCH 011/205] =?UTF-8?q?=20fix:=E8=A7=A6=E6=91=B8=E6=BB=91?= =?UTF-8?q?=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml | 6 +++--- Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml index 94ae1cb3..52409d3b 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml +++ b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml @@ -1,4 +1,4 @@ - - + 4 @@ -114,4 +114,4 @@ - + diff --git a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml.cs index 7f934766..9e8c4120 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml.cs @@ -1,6 +1,6 @@ using System; using System.Windows; -using System.Windows.Controls; +using iNKORE.UI.WPF.Modern.Controls; namespace Ink_Canvas.Windows.SettingsViews.Pages { From 73935e0f224d3b0b73932bb0479327da2aecc667 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Tue, 7 Apr 2026 23:46:26 +0800 Subject: [PATCH 012/205] =?UTF-8?q?fix:=E5=BC=80=E5=85=B3=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E8=8B=B1=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Windows/SettingsViews/Pages/StartupPage.xaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml index 52409d3b..7c5a6d97 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml +++ b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml @@ -9,7 +9,7 @@ xmlns:i18n="clr-namespace:Ink_Canvas.MarkupExtensions" xmlns:sys="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" - d:DesignHeight="950" d:DesignWidth="800" + Title="启动"> @@ -40,7 +40,7 @@ - + @@ -49,7 +49,7 @@ - + @@ -58,7 +58,7 @@ - + @@ -67,7 +67,7 @@ - + @@ -80,7 +80,7 @@ - + @@ -89,7 +89,7 @@ - + @@ -103,7 +103,7 @@ - + From c73123cd239bdfdb5ea6099e0325a9c4cf198e49 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Tue, 7 Apr 2026 23:58:17 +0800 Subject: [PATCH 013/205] =?UTF-8?q?=20fix:=E8=A7=A6=E6=91=B8=E6=BB=91?= =?UTF-8?q?=E5=8A=A8(=E5=B7=A6=E5=8F=B3=E4=B8=A4=E8=BE=B9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SettingsViews/Pages/StartupPage.xaml | 156 +++++++++--------- 1 file changed, 79 insertions(+), 77 deletions(-) diff --git a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml index 7c5a6d97..10db149b 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml +++ b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml @@ -12,8 +12,9 @@ Title="启动"> - - + + + 4 @@ -25,93 +26,94 @@ - - + + - - + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - + + - - - - - - - - - - + + + + + + + + + + - - + + - + + From 3f3a10de7d7d8c29980c627463df5bf76f9299d2 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Wed, 8 Apr 2026 00:00:32 +0800 Subject: [PATCH 014/205] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml.cs | 2 +- .../SettingsViews/MainWindowSettingsHelper.cs | 1 - .../Pages/AppearancePage.xaml.cs | 12 --------- .../SettingsViews/Pages/BasicPage.xaml.cs | 12 --------- .../SettingsViews/Pages/DesignPage.xaml.cs | 12 --------- .../SettingsViews/Pages/HomePage.xaml.cs | 12 --------- .../SettingsViews/Pages/Page2Page.xaml.cs | 13 --------- .../SettingsViews/Pages/StartupPage.xaml.cs | 2 +- .../SettingsViews/SettingsWindow.xaml.cs | 27 +++++++++---------- 9 files changed, 15 insertions(+), 78 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index af1e63d1..15174148 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -2822,7 +2822,7 @@ namespace Ink_Canvas settingsWindow.ShowDialog(); } } - + #endregion 新设置窗口 // 在MainWindow类中添加: diff --git a/Ink Canvas/Windows/SettingsViews/MainWindowSettingsHelper.cs b/Ink Canvas/Windows/SettingsViews/MainWindowSettingsHelper.cs index 214454bc..44500cff 100644 --- a/Ink Canvas/Windows/SettingsViews/MainWindowSettingsHelper.cs +++ b/Ink Canvas/Windows/SettingsViews/MainWindowSettingsHelper.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Windows; diff --git a/Ink Canvas/Windows/SettingsViews/Pages/AppearancePage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/AppearancePage.xaml.cs index 8999b7df..1e36520a 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/AppearancePage.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/Pages/AppearancePage.xaml.cs @@ -1,17 +1,5 @@ -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 Ink_Canvas.Windows.SettingsViews.Pages { diff --git a/Ink Canvas/Windows/SettingsViews/Pages/BasicPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/BasicPage.xaml.cs index 78b6e71b..f70e13b1 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/BasicPage.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/Pages/BasicPage.xaml.cs @@ -1,17 +1,5 @@ -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 Ink_Canvas.Windows.SettingsViews.Pages { diff --git a/Ink Canvas/Windows/SettingsViews/Pages/DesignPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/DesignPage.xaml.cs index 41c804f5..01d5e7c8 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/DesignPage.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/Pages/DesignPage.xaml.cs @@ -1,17 +1,5 @@ -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 Ink_Canvas.Windows.SettingsViews.Pages { diff --git a/Ink Canvas/Windows/SettingsViews/Pages/HomePage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/HomePage.xaml.cs index 6ac88f06..a0052272 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/HomePage.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/Pages/HomePage.xaml.cs @@ -1,17 +1,5 @@ -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 Ink_Canvas.Windows.SettingsViews.Pages { diff --git a/Ink Canvas/Windows/SettingsViews/Pages/Page2Page.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/Page2Page.xaml.cs index 26803bb3..d221d279 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/Page2Page.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/Pages/Page2Page.xaml.cs @@ -1,17 +1,4 @@ -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 Ink_Canvas.Windows.SettingsViews.Pages { diff --git a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml.cs index 9e8c4120..59436f5f 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml.cs @@ -1,6 +1,6 @@ +using iNKORE.UI.WPF.Modern.Controls; using System; using System.Windows; -using iNKORE.UI.WPF.Modern.Controls; namespace Ink_Canvas.Windows.SettingsViews.Pages { diff --git a/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs b/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs index a4b260b5..c13267e0 100644 --- a/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/SettingsWindow.xaml.cs @@ -2,11 +2,10 @@ using Ink_Canvas.Windows.SettingsViews.Pages; using iNKORE.UI.WPF.Modern.Controls; using System; using System.Collections.Generic; -using System.Windows; -using System.Windows.Navigation; -using System.Windows.Interop; -using System.Windows.Input; using System.Linq; +using System.Windows; +using System.Windows.Interop; +using System.Windows.Navigation; using MessageBox = System.Windows.MessageBox; using Screen = System.Windows.Forms.Screen; @@ -16,13 +15,13 @@ namespace Ink_Canvas.Windows.SettingsViews { private readonly Dictionary _pageTypes; private readonly Dictionary _pages = new Dictionary(); - + // 保存窗口原始位置和大小 private double _originalLeft; private double _originalTop; private double _originalWidth; private double _originalHeight; - + // 标记窗口是否曾经最大化过 private bool _wasMaximized = false; @@ -89,10 +88,10 @@ namespace Ink_Canvas.Windows.SettingsViews _originalTop = this.Top; _originalWidth = this.Width; _originalHeight = this.Height; - + // 标记窗口曾经最大化过 _wasMaximized = true; - + // 最大化时清除最大尺寸限制 this.MaxWidth = double.PositiveInfinity; this.MaxHeight = double.PositiveInfinity; @@ -104,10 +103,10 @@ namespace Ink_Canvas.Windows.SettingsViews this.Top = _originalTop; this.Width = _originalWidth; this.Height = _originalHeight; - + // 重置标记 _wasMaximized = false; - + // 只设置最大尺寸,不改变窗口位置 SetMaxSizeOnly(); } @@ -116,11 +115,11 @@ namespace Ink_Canvas.Windows.SettingsViews // 正常状态下只设置最大尺寸限制 SetMaxSizeOnly(); } - + // 窗口状态改变时更新标题栏显示 UpdateAppTitleBarMargin(); }; - + // 窗口大小改变时更新标题栏显示 this.SizeChanged += (sender, e) => { @@ -158,7 +157,7 @@ namespace Ink_Canvas.Windows.SettingsViews #endregion #region 高DPI/多屏自适应窗口控制 - + /// /// 获取当前窗口所在屏幕的工作区尺寸(DIP单位) /// @@ -341,7 +340,7 @@ namespace Ink_Canvas.Windows.SettingsViews if (sender.DisplayMode == NavigationViewDisplayMode.Minimal) { AppTitleBar.Margin = new Thickness((sender.CompactPaneLength * 2), currMargin.Top, currMargin.Right, currMargin.Bottom); - + // 当窗口宽度非常小时,隐藏图标和应用设置文字 if (this.ActualWidth < 400) { From 302ef307fedf3ac197f711f6934740271d3ba067 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Wed, 8 Apr 2026 00:51:28 +0800 Subject: [PATCH 015/205] =?UTF-8?q?=20add:=E5=9F=BA=E7=A1=80=E6=8F=92?= =?UTF-8?q?=E4=BB=B6=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/App.xaml.cs | 44 ++- Ink Canvas/InkCanvasForClass.csproj | 1 + Ink Canvas/Plugins/IPlugin.cs | 28 ++ Ink Canvas/Plugins/PluginBase.cs | 34 ++ Ink Canvas/Plugins/PluginManager.cs | 182 +++++++++ Ink Canvas/packages.lock.json | 589 ++++++++++++++++++++++++++++ 6 files changed, 871 insertions(+), 7 deletions(-) create mode 100644 Ink Canvas/Plugins/IPlugin.cs create mode 100644 Ink Canvas/Plugins/PluginBase.cs create mode 100644 Ink Canvas/Plugins/PluginManager.cs diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index 2238c408..c3acd77a 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -1,5 +1,6 @@ using H.NotifyIcon; using Ink_Canvas.Helpers; +using Ink_Canvas.Plugins; using Ink_Canvas.Properties; using iNKORE.UI.WPF.Modern.Controls; using Microsoft.Win32; @@ -1161,6 +1162,24 @@ namespace Ink_Canvas LogHelper.WriteLogToFile($"初始化上传帮助类时出错: {ex.Message}", LogHelper.LogType.Error); } + // 加载插件 + try + { + LogHelper.WriteLogToFile("开始加载插件"); + var pluginsDir = Path.Combine(RootPath, "Plugins"); + if (!Directory.Exists(pluginsDir)) + { + Directory.CreateDirectory(pluginsDir); + } + PluginManager.Instance.SetPluginsDirectory(pluginsDir); + await PluginManager.Instance.LoadAllAsync(); + LogHelper.WriteLogToFile($"插件加载完成,已加载 {PluginManager.Instance.Plugins.Count} 个插件"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"加载插件时出错: {ex.Message}", LogHelper.LogType.Error); + } + } private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) @@ -1456,14 +1475,25 @@ namespace Ink_Canvas } if (IsAppExitByUser) + { + // 写入退出信号文件,通知看门狗正常退出 + StartupCount.Reset(); + File.WriteAllText(watchdogExitSignalFile, "exit"); + if (watchdogProcess != null && !watchdogProcess.HasExited) { - // 写入退出信号文件,通知看门狗正常退出 - StartupCount.Reset(); - File.WriteAllText(watchdogExitSignalFile, "exit"); - if (watchdogProcess != null && !watchdogProcess.HasExited) - { - watchdogProcess.Kill(); - } + watchdogProcess.Kill(); + } + } + + // 卸载所有插件 + try + { + PluginManager.Instance.UnloadAll(); + LogHelper.WriteLogToFile("插件已全部卸载"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"卸载插件时出错: {ex.Message}", LogHelper.LogType.Error); } } catch (Exception ex) diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index db46b9b2..6024ccf3 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -142,6 +142,7 @@ + diff --git a/Ink Canvas/Plugins/IPlugin.cs b/Ink Canvas/Plugins/IPlugin.cs new file mode 100644 index 00000000..c35c679a --- /dev/null +++ b/Ink Canvas/Plugins/IPlugin.cs @@ -0,0 +1,28 @@ +using System; + +namespace Ink_Canvas.Plugins +{ + public interface IPlugin + { + string Id { get; } + string Name { get; } + string Version { get; } + string Description { get; } + string Author { get; } + int Order { get; } + + void Initialize(IPluginHost host); + void Shutdown(); + + object? GetSettingsView(); + object? GetMainView(); + } + + public interface IPluginHost + { + void Log(string message); + void LogError(string message, Exception? ex = null); + T? GetService() where T : class; + void RegisterService(T service) where T : class; + } +} \ No newline at end of file diff --git a/Ink Canvas/Plugins/PluginBase.cs b/Ink Canvas/Plugins/PluginBase.cs new file mode 100644 index 00000000..2f3f2ce2 --- /dev/null +++ b/Ink Canvas/Plugins/PluginBase.cs @@ -0,0 +1,34 @@ +using System; + +namespace Ink_Canvas.Plugins +{ + public abstract class PluginBase : IPlugin + { + public abstract string Id { get; } + public abstract string Name { get; } + public abstract string Version { get; } + public abstract string Description { get; } + public virtual string Author => "Unknown"; + public virtual int Order => 0; + + protected IPluginHost? Host { get; private set; } + + public virtual void Initialize(IPluginHost host) + { + Host = host; + Host.Log($"[Plugin:{Name}] Initialized"); + } + + public virtual void Shutdown() + { + Host?.Log($"[Plugin:{Name}] Shutdown"); + Host = null; + } + + public virtual object? GetSettingsView() => null; + public virtual object? GetMainView() => null; + + protected void Log(string message) => Host?.Log($"[Plugin:{Name}] {message}"); + protected void LogError(string message, Exception? ex = null) => Host?.LogError($"[Plugin:{Name}] {message}", ex); + } +} \ No newline at end of file diff --git a/Ink Canvas/Plugins/PluginManager.cs b/Ink Canvas/Plugins/PluginManager.cs new file mode 100644 index 00000000..0f042bae --- /dev/null +++ b/Ink Canvas/Plugins/PluginManager.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; + +namespace Ink_Canvas.Plugins +{ + public class PluginInfo + { + public string Id { get; set; } = ""; + public string Name { get; set; } = ""; + public string Version { get; set; } = ""; + public string FilePath { get; set; } = ""; + public bool IsLoaded { get; set; } + public IPlugin? Instance { get; set; } + public Assembly? Assembly { get; set; } + public Exception? LoadError { get; set; } + } + + public class PluginManager : IPluginHost + { + private static PluginManager? _instance; + public static PluginManager Instance => _instance ??= new PluginManager(); + + private readonly List _plugins = new(); + private readonly Dictionary _services = new(); + private string _pluginsDirectory; + + public IReadOnlyList Plugins => _plugins.AsReadOnly(); + public event EventHandler? PluginLoaded; + public event EventHandler? PluginUnloaded; + public event EventHandler? LogMessage; + + private PluginManager() + { + _pluginsDirectory = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, "Plugins"); + } + + public void SetPluginsDirectory(string path) + { + if (Directory.Exists(path)) + { + _pluginsDirectory = path; + } + } + + public async Task LoadAllAsync() + { + await Task.Run(() => LoadAll()); + } + + private void LoadAll() + { + if (!Directory.Exists(_pluginsDirectory)) + { + Directory.CreateDirectory(_pluginsDirectory); + Log("Plugins directory created"); + return; + } + + var dllFiles = Directory.GetFiles(_pluginsDirectory, "*.dll") + .Where(f => !f.EndsWith("InkCanvasForClass.dll") && + !f.EndsWith("Weikio.PluginFramework.dll")); + + foreach (var dll in dllFiles) + { + LoadPlugin(dll); + } + + Log($"Plugin loading complete. Loaded {_plugins.Count} plugins."); + } + + private void LoadPlugin(string dllPath) + { + if (!File.Exists(dllPath)) + { + Log($"Plugin file not found: {dllPath}"); + return; + } + + try + { + var assembly = Assembly.LoadFrom(dllPath); + var pluginTypes = assembly.GetTypes() + .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); + + foreach (var pluginType in pluginTypes) + { + try + { + var plugin = (IPlugin)Activator.CreateInstance(pluginType)!; + + var info = new PluginInfo + { + Id = plugin.Id, + Name = plugin.Name, + Version = plugin.Version, + FilePath = dllPath, + IsLoaded = true, + Instance = plugin, + Assembly = assembly + }; + + plugin.Initialize(this); + _plugins.Add(info); + PluginLoaded?.Invoke(this, info); + Log($"Plugin loaded: {info.Name} v{info.Version}"); + } + catch (Exception ex) + { + LogError($"Failed to create plugin instance from {dllPath}", ex); + } + } + } + catch (Exception ex) + { + LogError($"Failed to load plugin from {dllPath}", ex); + + var info = new PluginInfo + { + FilePath = dllPath, + LoadError = ex + }; + _plugins.Add(info); + } + } + + public void UnloadPlugin(PluginInfo plugin) + { + if (!plugin.IsLoaded || plugin.Instance == null) + return; + + try + { + plugin.Instance.Shutdown(); + plugin.Instance = null; + plugin.IsLoaded = false; + + _plugins.Remove(plugin); + PluginUnloaded?.Invoke(this, plugin); + Log($"Plugin unloaded: {plugin.Name}"); + } + catch (Exception ex) + { + LogError($"Failed to unload plugin {plugin.Name}", ex); + } + } + + public void UnloadAll() + { + foreach (var plugin in _plugins.ToList()) + { + UnloadPlugin(plugin); + } + } + + public void RegisterService(T service) where T : class + { + _services[typeof(T)] = service; + } + + public T? GetService() where T : class + { + return _services.TryGetValue(typeof(T), out var service) ? service as T : null; + } + + public void Log(string message) + { + LogMessage?.Invoke(this, message); + System.Diagnostics.Debug.WriteLine($"[PluginManager] {message}"); + } + + public void LogError(string message, Exception? ex = null) + { + var fullMessage = ex != null ? $"{message}: {ex.Message}\n{ex.StackTrace}" : message; + Log($"ERROR: {fullMessage}"); + } + } +} \ No newline at end of file diff --git a/Ink Canvas/packages.lock.json b/Ink Canvas/packages.lock.json index 09b35601..7535908c 100644 --- a/Ink Canvas/packages.lock.json +++ b/Ink Canvas/packages.lock.json @@ -185,6 +185,19 @@ "resolved": "2.9.0", "contentHash": "GLhd1tQAJeuVO1sj3Wm/dkg0GEVWxk+XGl6rdegMSMHenZuOaWQw4PifWDsjNEC1dtV1/C8JJfK0qfdkM+VIgA==" }, + "Weikio.PluginFramework": { + "type": "Direct", + "requested": "[1.5.1, )", + "resolved": "1.5.1", + "contentHash": "Sm+wTkoMeciqmNM+MIKzmxMs2N+I6RGbOQNwCV0wXFhG17W27/CHLShotDcEFVmF8pgVDlRORmCZrpzpBR37Zw==", + "dependencies": { + "Microsoft.CodeAnalysis.CSharp": "3.3.1", + "Microsoft.Extensions.Logging": "3.1.2", + "System.Reflection.MetadataLoadContext": "4.7.1", + "Weikio.PluginFramework.Abstractions": "1.5.1", + "Weikio.TypeGenerator": "1.4.1" + } + }, "AForge": { "type": "Transitive", "resolved": "2.2.5", @@ -221,11 +234,110 @@ "resolved": "1.27.0", "contentHash": "We7LtBdoukRg9mqTfa1f5n8z/GQPMKBRj3URk9DiMuqzIHkW1lTgK5njVPSScxsRt4YzW22423tSnLWNm2MJKg==" }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "2.9.4", + "contentHash": "alIJhS0VUg/7x5AsHEoovh/wRZ0RfCSS7k5pDSqpRLTyuMTtRgj6OJJPRApRhJHOGYYsLakf1hKeXFoDwKwNkg==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "3.3.1", + "contentHash": "N5yQdGy+M4kimVG7hwCeGTCfgYjK2o5b/Shumkb/rCC+/SAkvP1HUAYK+vxPFS7dLJNtXLRsmPHKj3fnyNWnrw==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "2.9.4", + "System.Collections.Immutable": "1.5.0", + "System.Memory": "4.5.3", + "System.Reflection.Metadata": "1.6.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.2", + "System.Text.Encoding.CodePages": "4.5.1", + "System.Threading.Tasks.Extensions": "4.5.3" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "3.3.1", + "contentHash": "WDUIhTHem38H6VJ98x2Ssq0fweakJHnHYl7vbG8ARnsAwLoJKCQCy78EeY1oRrCKG42j0v6JVljKkeqSDA28UA==", + "dependencies": { + "Microsoft.CodeAnalysis.Common": "[3.3.1]" + } + }, + "Microsoft.Extensions.Configuration": { + "type": "Transitive", + "resolved": "3.1.2", + "contentHash": "BxwRSBab309SYMCDCFyB6eSc7FnX5m9kOJQHw2IQIyb5PEtpfslhscTw63Gwhl3dPnaM1VGFXIyI0BVgpiLgOw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Abstractions": "3.1.2" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "3.1.2", + "contentHash": "xmfdVdazTslWJ8od7uNS9QSPqn1wBC84RLprPrFS20EdAqd3lV0g0IZAitYbCiiICpjktnhzbUb85aLHNZ3RQw==", + "dependencies": { + "Microsoft.Extensions.Primitives": "3.1.2" + } + }, + "Microsoft.Extensions.Configuration.Binder": { + "type": "Transitive", + "resolved": "3.1.2", + "contentHash": "IWrc9/voGki2pc5g8bRXIqs+P50tXOjNf47qgFKSu/pL50InRuXxh/nj5AG9Po8YRpvT/bYIUk3XQqHH7yUg5w==", + "dependencies": { + "Microsoft.Extensions.Configuration": "3.1.2" + } + }, + "Microsoft.Extensions.DependencyInjection": { + "type": "Transitive", + "resolved": "3.1.2", + "contentHash": "e+F6/wjQPOFHB/sGWTAqC8FX/C6+JZWWLpryXTAQYIS3tr+17lByADdP9Y6RtxfJ4kW/IPrU6RuxTNZNdAQz1A==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.2" + } + }, + "Microsoft.Extensions.DependencyInjection.Abstractions": { + "type": "Transitive", + "resolved": "3.1.2", + "contentHash": "/CZzCSCIm/3FFoXHfUpsfov/Elo268dcvlz/MMINT0vPgphqg2pAgdEn/EjCDyoAT3NAmsRmjfGwBumC1uYJtA==" + }, + "Microsoft.Extensions.Logging": { + "type": "Transitive", + "resolved": "3.1.2", + "contentHash": "AIIRgKamzEqJNLZsHd37VogFX9YpxgrBmf/b3dznD7S0qjxWQnAs498ulLV1n6AKJ8XVjTCBNzsvQiSwCa7dIw==", + "dependencies": { + "Microsoft.Extensions.Configuration.Binder": "3.1.2", + "Microsoft.Extensions.DependencyInjection": "3.1.2", + "Microsoft.Extensions.Logging.Abstractions": "3.1.2", + "Microsoft.Extensions.Options": "3.1.2" + } + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "3.1.2", + "contentHash": "cIXPw7VVX3fON4uuHwJFmCi0qDl8uY75xZMKB2oM3In0ZDEB1Ee+p9Ti1DSw92AwRtJ2Zh+QG1joTBednJMzvA==" + }, + "Microsoft.Extensions.Options": { + "type": "Transitive", + "resolved": "3.1.2", + "contentHash": "6F4anwt9yMlnQckac2etjrasRFyqZNIp46p+i9qVps0DXNsOLZIKRkqq4AY4FlxXxKeGkEJC7M77RQEkvd3p8Q==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.2", + "Microsoft.Extensions.Primitives": "3.1.2" + } + }, + "Microsoft.Extensions.Primitives": { + "type": "Transitive", + "resolved": "3.1.2", + "contentHash": "WGtoFWY9yc9HGMG6ObDNQPz9dBP+xz/GqFe2dKjdE/cSdXFEKxCFTyYCzL/e8kxVkc/Bq9qjOsXRWydvn0g9Uw==" + }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" }, + "Microsoft.NETCore.Targets": { + "type": "Transitive", + "resolved": "1.1.0", + "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" + }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "5.0.0", @@ -250,6 +362,11 @@ "resolved": "10.0.5", "contentHash": "hGZWDDJh1U6t7fy3iO4HlZYK1ur1fWE3sTqTNHkHk0Leh0JUcxYM//JtLBNG5g+6D2Lt0+aHH8rc7e5oIlNgCg==" }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "1.5.0", + "contentHash": "EXKiDFsChZW0RjrZ4FYHu9aW6+P4MCgEDCklsVseRfhoO0F+dXeMSsMRAlVXIo06kGJ/zv+2w1a2uc2+kxxSaQ==" + }, "System.Drawing.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -258,6 +375,35 @@ "Microsoft.Win32.SystemEvents": "8.0.0" } }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.3", + "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==" + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0" + } + }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.7.0", @@ -268,11 +414,45 @@ "resolved": "5.0.0", "contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ==" }, + "System.Reflection.MetadataLoadContext": { + "type": "Transitive", + "resolved": "4.7.1", + "contentHash": "OIc85e1sXjnvRlPXdpJrcbqQr2dP23rmvAnRlbghMo3btOCo/FBzN1XGi2mIxJdlrwzHyOpVcixbluRIzHwMiA==" + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, + "System.Runtime.Loader": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "DHMaRn8D8YCK2GG2pw+UzNxn/OHVfaWx7OTLBD/hPegHZZgcZh3H6seWegrC4BYwsfuGrywIuT+MQs+rPqRLTQ==", + "dependencies": { + "System.IO": "4.3.0", + "System.Reflection": "4.3.0", + "System.Runtime": "4.3.0" + } + }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -287,6 +467,25 @@ "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "4J2JQXbftjPMppIHJ7IC+VXQ9XfEagN92vZZNoG12i+zReYlim5dMoXFC1Zzg7tsnKDM7JPo5bYfFK4Jheq44w==", + "dependencies": { + "Microsoft.NETCore.Platforms": "2.1.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", @@ -304,10 +503,40 @@ "System.Text.Encodings.Web": "8.0.0" } }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.3", + "contentHash": "+MvhNtcvIbqmhANyKu91jQnvIRVSTiaOiFNfKWwXGHG48YAb4I/TyH8spsySiPYla7gKal5ZnF3teJqZAximyQ==" + }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + }, + "Weikio.PluginFramework.Abstractions": { + "type": "Transitive", + "resolved": "1.5.1", + "contentHash": "8ebyvrSp9JoMM6MxTiBTT6RgOWKSqvQRRqPo0J3dF1l08+E/pq1fUhhbybnCqscVjSn7v2LQtQZbIZEwQ9j4Hg==" + }, + "Weikio.TypeGenerator": { + "type": "Transitive", + "resolved": "1.4.1", + "contentHash": "GHSPO4IHAmhgt3o24JLke2+ZSDuSxur/+6Mp4BslvcEh0t/z2yPtZZO3gHhl67HHEnOTLD9BARbAl/vJQ9l43w==", + "dependencies": { + "Microsoft.CodeAnalysis.CSharp": "3.3.1", + "System.Reflection.MetadataLoadContext": "4.7.1", + "System.Runtime.Loader": "4.3.0" + } } }, "net6.0-windows10.0.19041/win-arm64": { @@ -325,6 +554,95 @@ "resolved": "8.0.0", "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, + "runtime.any.System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ==" + }, + "runtime.any.System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ==" + }, + "runtime.any.System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg==" + }, + "runtime.any.System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==", + "dependencies": { + "System.Private.Uri": "4.3.0" + } + }, + "runtime.any.System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ==" + }, + "runtime.any.System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w==" + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.any.System.IO": "4.3.0" + } + }, + "System.Private.Uri": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Reflection": "4.3.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Reflection.Primitives": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "runtime.any.System.Runtime": "4.3.0" + } + }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -339,6 +657,26 @@ "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Text.Encoding": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "4J2JQXbftjPMppIHJ7IC+VXQ9XfEagN92vZZNoG12i+zReYlim5dMoXFC1Zzg7tsnKDM7JPo5bYfFK4Jheq44w==", + "dependencies": { + "Microsoft.NETCore.Platforms": "2.1.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", @@ -346,6 +684,17 @@ "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Threading.Tasks": "4.3.0" + } } }, "net6.0-windows10.0.19041/win-x64": { @@ -363,6 +712,95 @@ "resolved": "8.0.0", "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, + "runtime.any.System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ==" + }, + "runtime.any.System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ==" + }, + "runtime.any.System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg==" + }, + "runtime.any.System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==", + "dependencies": { + "System.Private.Uri": "4.3.0" + } + }, + "runtime.any.System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ==" + }, + "runtime.any.System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w==" + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.any.System.IO": "4.3.0" + } + }, + "System.Private.Uri": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Reflection": "4.3.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Reflection.Primitives": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "runtime.any.System.Runtime": "4.3.0" + } + }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -377,6 +815,26 @@ "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Text.Encoding": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "4J2JQXbftjPMppIHJ7IC+VXQ9XfEagN92vZZNoG12i+zReYlim5dMoXFC1Zzg7tsnKDM7JPo5bYfFK4Jheq44w==", + "dependencies": { + "Microsoft.NETCore.Platforms": "2.1.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", @@ -384,6 +842,17 @@ "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Threading.Tasks": "4.3.0" + } } }, "net6.0-windows10.0.19041/win-x86": { @@ -401,6 +870,95 @@ "resolved": "8.0.0", "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, + "runtime.any.System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ==" + }, + "runtime.any.System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ==" + }, + "runtime.any.System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg==" + }, + "runtime.any.System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==", + "dependencies": { + "System.Private.Uri": "4.3.0" + } + }, + "runtime.any.System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ==" + }, + "runtime.any.System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w==" + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "System.Text.Encoding": "4.3.0", + "System.Threading.Tasks": "4.3.0", + "runtime.any.System.IO": "4.3.0" + } + }, + "System.Private.Uri": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0" + } + }, + "System.Reflection": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.IO": "4.3.0", + "System.Reflection.Primitives": "4.3.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Reflection": "4.3.0" + } + }, + "System.Reflection.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Reflection.Primitives": "4.3.0" + } + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "runtime.any.System.Runtime": "4.3.0" + } + }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -415,6 +973,26 @@ "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, + "System.Text.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Text.Encoding": "4.3.0" + } + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "4J2JQXbftjPMppIHJ7IC+VXQ9XfEagN92vZZNoG12i+zReYlim5dMoXFC1Zzg7tsnKDM7JPo5bYfFK4Jheq44w==", + "dependencies": { + "Microsoft.NETCore.Platforms": "2.1.2", + "System.Runtime.CompilerServices.Unsafe": "4.5.2" + } + }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", @@ -422,6 +1000,17 @@ "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } + }, + "System.Threading.Tasks": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", + "dependencies": { + "Microsoft.NETCore.Platforms": "1.1.0", + "Microsoft.NETCore.Targets": "1.1.0", + "System.Runtime": "4.3.0", + "runtime.any.System.Threading.Tasks": "4.3.0" + } } } } From ea55eb1738f6d43fd8f3159ad0ece8e7e02babd7 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Wed, 8 Apr 2026 12:41:56 +0800 Subject: [PATCH 016/205] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20PluginManager.cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/Plugins/PluginManager.cs | 81 ++++++++++++----------------- 1 file changed, 33 insertions(+), 48 deletions(-) diff --git a/Ink Canvas/Plugins/PluginManager.cs b/Ink Canvas/Plugins/PluginManager.cs index 0f042bae..8956148f 100644 --- a/Ink Canvas/Plugins/PluginManager.cs +++ b/Ink Canvas/Plugins/PluginManager.cs @@ -2,8 +2,10 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using System.Threading.Tasks; +using Weikio.PluginFramework; +using Weikio.PluginFramework.Abstractions; +using Weikio.PluginFramework.Catalogs; namespace Ink_Canvas.Plugins { @@ -15,7 +17,6 @@ namespace Ink_Canvas.Plugins public string FilePath { get; set; } = ""; public bool IsLoaded { get; set; } public IPlugin? Instance { get; set; } - public Assembly? Assembly { get; set; } public Exception? LoadError { get; set; } } @@ -27,6 +28,7 @@ namespace Ink_Canvas.Plugins private readonly List _plugins = new(); private readonly Dictionary _services = new(); private string _pluginsDirectory; + private FolderPluginCatalog? _catalog; public IReadOnlyList Plugins => _plugins.AsReadOnly(); public event EventHandler? PluginLoaded; @@ -48,11 +50,6 @@ namespace Ink_Canvas.Plugins } public async Task LoadAllAsync() - { - await Task.Run(() => LoadAll()); - } - - private void LoadAll() { if (!Directory.Exists(_pluginsDirectory)) { @@ -61,70 +58,58 @@ namespace Ink_Canvas.Plugins return; } - var dllFiles = Directory.GetFiles(_pluginsDirectory, "*.dll") - .Where(f => !f.EndsWith("InkCanvasForClass.dll") && - !f.EndsWith("Weikio.PluginFramework.dll")); - - foreach (var dll in dllFiles) - { - LoadPlugin(dll); - } - - Log($"Plugin loading complete. Loaded {_plugins.Count} plugins."); - } - - private void LoadPlugin(string dllPath) - { - if (!File.Exists(dllPath)) - { - Log($"Plugin file not found: {dllPath}"); - return; - } - try { - var assembly = Assembly.LoadFrom(dllPath); - var pluginTypes = assembly.GetTypes() - .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); + var options = new FolderPluginCatalogOptions + { + SearchPatterns = new[] { "*.dll" }, + IncludeSubfolders = false + }; - foreach (var pluginType in pluginTypes) + _catalog = new FolderPluginCatalog(_pluginsDirectory, options); + await _catalog.Initialize(); + + var weikioPlugins = _catalog.GetPlugins(); + + foreach (var weikioPlugin in weikioPlugins) { try { - var plugin = (IPlugin)Activator.CreateInstance(pluginType)!; + var pluginType = weikioPlugin.GetType(); + var instance = Activator.CreateInstance(pluginType) as IPlugin; + + if (instance == null) + { + Log($"Failed to create instance of plugin from {weikioPlugin.AssemblyPath}"); + continue; + } var info = new PluginInfo { - Id = plugin.Id, - Name = plugin.Name, - Version = plugin.Version, - FilePath = dllPath, + Id = instance.Id, + Name = instance.Name, + Version = instance.Version, + FilePath = weikioPlugin.AssemblyPath ?? "", IsLoaded = true, - Instance = plugin, - Assembly = assembly + Instance = instance }; - plugin.Initialize(this); + instance.Initialize(this); _plugins.Add(info); PluginLoaded?.Invoke(this, info); Log($"Plugin loaded: {info.Name} v{info.Version}"); } catch (Exception ex) { - LogError($"Failed to create plugin instance from {dllPath}", ex); + LogError($"Failed to load plugin from {weikioPlugin.AssemblyPath}", ex); } } + + Log($"Plugin loading complete. Loaded {_plugins.Count} plugins."); } catch (Exception ex) { - LogError($"Failed to load plugin from {dllPath}", ex); - - var info = new PluginInfo - { - FilePath = dllPath, - LoadError = ex - }; - _plugins.Add(info); + LogError("Failed to initialize plugin catalog", ex); } } From 77cf91e8024d2e487d15d4d5c33b2bd9bd950981 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Wed, 8 Apr 2026 12:43:55 +0800 Subject: [PATCH 017/205] =?UTF-8?q?Revert=20"=E6=9B=B4=E6=96=B0=20PluginMa?= =?UTF-8?q?nager.cs"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit ea55eb1738f6d43fd8f3159ad0ece8e7e02babd7. --- Ink Canvas/Plugins/PluginManager.cs | 81 +++++++++++++++++------------ 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/Ink Canvas/Plugins/PluginManager.cs b/Ink Canvas/Plugins/PluginManager.cs index 8956148f..0f042bae 100644 --- a/Ink Canvas/Plugins/PluginManager.cs +++ b/Ink Canvas/Plugins/PluginManager.cs @@ -2,10 +2,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; -using Weikio.PluginFramework; -using Weikio.PluginFramework.Abstractions; -using Weikio.PluginFramework.Catalogs; namespace Ink_Canvas.Plugins { @@ -17,6 +15,7 @@ namespace Ink_Canvas.Plugins public string FilePath { get; set; } = ""; public bool IsLoaded { get; set; } public IPlugin? Instance { get; set; } + public Assembly? Assembly { get; set; } public Exception? LoadError { get; set; } } @@ -28,7 +27,6 @@ namespace Ink_Canvas.Plugins private readonly List _plugins = new(); private readonly Dictionary _services = new(); private string _pluginsDirectory; - private FolderPluginCatalog? _catalog; public IReadOnlyList Plugins => _plugins.AsReadOnly(); public event EventHandler? PluginLoaded; @@ -50,6 +48,11 @@ namespace Ink_Canvas.Plugins } public async Task LoadAllAsync() + { + await Task.Run(() => LoadAll()); + } + + private void LoadAll() { if (!Directory.Exists(_pluginsDirectory)) { @@ -58,58 +61,70 @@ namespace Ink_Canvas.Plugins return; } + var dllFiles = Directory.GetFiles(_pluginsDirectory, "*.dll") + .Where(f => !f.EndsWith("InkCanvasForClass.dll") && + !f.EndsWith("Weikio.PluginFramework.dll")); + + foreach (var dll in dllFiles) + { + LoadPlugin(dll); + } + + Log($"Plugin loading complete. Loaded {_plugins.Count} plugins."); + } + + private void LoadPlugin(string dllPath) + { + if (!File.Exists(dllPath)) + { + Log($"Plugin file not found: {dllPath}"); + return; + } + try { - var options = new FolderPluginCatalogOptions - { - SearchPatterns = new[] { "*.dll" }, - IncludeSubfolders = false - }; + var assembly = Assembly.LoadFrom(dllPath); + var pluginTypes = assembly.GetTypes() + .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); - _catalog = new FolderPluginCatalog(_pluginsDirectory, options); - await _catalog.Initialize(); - - var weikioPlugins = _catalog.GetPlugins(); - - foreach (var weikioPlugin in weikioPlugins) + foreach (var pluginType in pluginTypes) { try { - var pluginType = weikioPlugin.GetType(); - var instance = Activator.CreateInstance(pluginType) as IPlugin; - - if (instance == null) - { - Log($"Failed to create instance of plugin from {weikioPlugin.AssemblyPath}"); - continue; - } + var plugin = (IPlugin)Activator.CreateInstance(pluginType)!; var info = new PluginInfo { - Id = instance.Id, - Name = instance.Name, - Version = instance.Version, - FilePath = weikioPlugin.AssemblyPath ?? "", + Id = plugin.Id, + Name = plugin.Name, + Version = plugin.Version, + FilePath = dllPath, IsLoaded = true, - Instance = instance + Instance = plugin, + Assembly = assembly }; - instance.Initialize(this); + plugin.Initialize(this); _plugins.Add(info); PluginLoaded?.Invoke(this, info); Log($"Plugin loaded: {info.Name} v{info.Version}"); } catch (Exception ex) { - LogError($"Failed to load plugin from {weikioPlugin.AssemblyPath}", ex); + LogError($"Failed to create plugin instance from {dllPath}", ex); } } - - Log($"Plugin loading complete. Loaded {_plugins.Count} plugins."); } catch (Exception ex) { - LogError("Failed to initialize plugin catalog", ex); + LogError($"Failed to load plugin from {dllPath}", ex); + + var info = new PluginInfo + { + FilePath = dllPath, + LoadError = ex + }; + _plugins.Add(info); } } From 256b3c08874a3478f8e1ac65f9fd4095f7adbfd3 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Wed, 8 Apr 2026 12:44:00 +0800 Subject: [PATCH 018/205] =?UTF-8?q?Revert=20"=20add:=E5=9F=BA=E7=A1=80?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E6=A1=86=E6=9E=B6"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 302ef307fedf3ac197f711f6934740271d3ba067. --- Ink Canvas/App.xaml.cs | 44 +-- Ink Canvas/InkCanvasForClass.csproj | 1 - Ink Canvas/Plugins/IPlugin.cs | 28 -- Ink Canvas/Plugins/PluginBase.cs | 34 -- Ink Canvas/Plugins/PluginManager.cs | 182 --------- Ink Canvas/packages.lock.json | 589 ---------------------------- 6 files changed, 7 insertions(+), 871 deletions(-) delete mode 100644 Ink Canvas/Plugins/IPlugin.cs delete mode 100644 Ink Canvas/Plugins/PluginBase.cs delete mode 100644 Ink Canvas/Plugins/PluginManager.cs diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index c3acd77a..2238c408 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -1,6 +1,5 @@ using H.NotifyIcon; using Ink_Canvas.Helpers; -using Ink_Canvas.Plugins; using Ink_Canvas.Properties; using iNKORE.UI.WPF.Modern.Controls; using Microsoft.Win32; @@ -1162,24 +1161,6 @@ namespace Ink_Canvas LogHelper.WriteLogToFile($"初始化上传帮助类时出错: {ex.Message}", LogHelper.LogType.Error); } - // 加载插件 - try - { - LogHelper.WriteLogToFile("开始加载插件"); - var pluginsDir = Path.Combine(RootPath, "Plugins"); - if (!Directory.Exists(pluginsDir)) - { - Directory.CreateDirectory(pluginsDir); - } - PluginManager.Instance.SetPluginsDirectory(pluginsDir); - await PluginManager.Instance.LoadAllAsync(); - LogHelper.WriteLogToFile($"插件加载完成,已加载 {PluginManager.Instance.Plugins.Count} 个插件"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"加载插件时出错: {ex.Message}", LogHelper.LogType.Error); - } - } private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) @@ -1475,25 +1456,14 @@ namespace Ink_Canvas } if (IsAppExitByUser) - { - // 写入退出信号文件,通知看门狗正常退出 - StartupCount.Reset(); - File.WriteAllText(watchdogExitSignalFile, "exit"); - if (watchdogProcess != null && !watchdogProcess.HasExited) { - watchdogProcess.Kill(); - } - } - - // 卸载所有插件 - try - { - PluginManager.Instance.UnloadAll(); - LogHelper.WriteLogToFile("插件已全部卸载"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"卸载插件时出错: {ex.Message}", LogHelper.LogType.Error); + // 写入退出信号文件,通知看门狗正常退出 + StartupCount.Reset(); + File.WriteAllText(watchdogExitSignalFile, "exit"); + if (watchdogProcess != null && !watchdogProcess.HasExited) + { + watchdogProcess.Kill(); + } } } catch (Exception ex) diff --git a/Ink Canvas/InkCanvasForClass.csproj b/Ink Canvas/InkCanvasForClass.csproj index 6024ccf3..db46b9b2 100644 --- a/Ink Canvas/InkCanvasForClass.csproj +++ b/Ink Canvas/InkCanvasForClass.csproj @@ -142,7 +142,6 @@ - diff --git a/Ink Canvas/Plugins/IPlugin.cs b/Ink Canvas/Plugins/IPlugin.cs deleted file mode 100644 index c35c679a..00000000 --- a/Ink Canvas/Plugins/IPlugin.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace Ink_Canvas.Plugins -{ - public interface IPlugin - { - string Id { get; } - string Name { get; } - string Version { get; } - string Description { get; } - string Author { get; } - int Order { get; } - - void Initialize(IPluginHost host); - void Shutdown(); - - object? GetSettingsView(); - object? GetMainView(); - } - - public interface IPluginHost - { - void Log(string message); - void LogError(string message, Exception? ex = null); - T? GetService() where T : class; - void RegisterService(T service) where T : class; - } -} \ No newline at end of file diff --git a/Ink Canvas/Plugins/PluginBase.cs b/Ink Canvas/Plugins/PluginBase.cs deleted file mode 100644 index 2f3f2ce2..00000000 --- a/Ink Canvas/Plugins/PluginBase.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; - -namespace Ink_Canvas.Plugins -{ - public abstract class PluginBase : IPlugin - { - public abstract string Id { get; } - public abstract string Name { get; } - public abstract string Version { get; } - public abstract string Description { get; } - public virtual string Author => "Unknown"; - public virtual int Order => 0; - - protected IPluginHost? Host { get; private set; } - - public virtual void Initialize(IPluginHost host) - { - Host = host; - Host.Log($"[Plugin:{Name}] Initialized"); - } - - public virtual void Shutdown() - { - Host?.Log($"[Plugin:{Name}] Shutdown"); - Host = null; - } - - public virtual object? GetSettingsView() => null; - public virtual object? GetMainView() => null; - - protected void Log(string message) => Host?.Log($"[Plugin:{Name}] {message}"); - protected void LogError(string message, Exception? ex = null) => Host?.LogError($"[Plugin:{Name}] {message}", ex); - } -} \ No newline at end of file diff --git a/Ink Canvas/Plugins/PluginManager.cs b/Ink Canvas/Plugins/PluginManager.cs deleted file mode 100644 index 0f042bae..00000000 --- a/Ink Canvas/Plugins/PluginManager.cs +++ /dev/null @@ -1,182 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; - -namespace Ink_Canvas.Plugins -{ - public class PluginInfo - { - public string Id { get; set; } = ""; - public string Name { get; set; } = ""; - public string Version { get; set; } = ""; - public string FilePath { get; set; } = ""; - public bool IsLoaded { get; set; } - public IPlugin? Instance { get; set; } - public Assembly? Assembly { get; set; } - public Exception? LoadError { get; set; } - } - - public class PluginManager : IPluginHost - { - private static PluginManager? _instance; - public static PluginManager Instance => _instance ??= new PluginManager(); - - private readonly List _plugins = new(); - private readonly Dictionary _services = new(); - private string _pluginsDirectory; - - public IReadOnlyList Plugins => _plugins.AsReadOnly(); - public event EventHandler? PluginLoaded; - public event EventHandler? PluginUnloaded; - public event EventHandler? LogMessage; - - private PluginManager() - { - _pluginsDirectory = Path.Combine( - AppDomain.CurrentDomain.BaseDirectory, "Plugins"); - } - - public void SetPluginsDirectory(string path) - { - if (Directory.Exists(path)) - { - _pluginsDirectory = path; - } - } - - public async Task LoadAllAsync() - { - await Task.Run(() => LoadAll()); - } - - private void LoadAll() - { - if (!Directory.Exists(_pluginsDirectory)) - { - Directory.CreateDirectory(_pluginsDirectory); - Log("Plugins directory created"); - return; - } - - var dllFiles = Directory.GetFiles(_pluginsDirectory, "*.dll") - .Where(f => !f.EndsWith("InkCanvasForClass.dll") && - !f.EndsWith("Weikio.PluginFramework.dll")); - - foreach (var dll in dllFiles) - { - LoadPlugin(dll); - } - - Log($"Plugin loading complete. Loaded {_plugins.Count} plugins."); - } - - private void LoadPlugin(string dllPath) - { - if (!File.Exists(dllPath)) - { - Log($"Plugin file not found: {dllPath}"); - return; - } - - try - { - var assembly = Assembly.LoadFrom(dllPath); - var pluginTypes = assembly.GetTypes() - .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsInterface && !t.IsAbstract); - - foreach (var pluginType in pluginTypes) - { - try - { - var plugin = (IPlugin)Activator.CreateInstance(pluginType)!; - - var info = new PluginInfo - { - Id = plugin.Id, - Name = plugin.Name, - Version = plugin.Version, - FilePath = dllPath, - IsLoaded = true, - Instance = plugin, - Assembly = assembly - }; - - plugin.Initialize(this); - _plugins.Add(info); - PluginLoaded?.Invoke(this, info); - Log($"Plugin loaded: {info.Name} v{info.Version}"); - } - catch (Exception ex) - { - LogError($"Failed to create plugin instance from {dllPath}", ex); - } - } - } - catch (Exception ex) - { - LogError($"Failed to load plugin from {dllPath}", ex); - - var info = new PluginInfo - { - FilePath = dllPath, - LoadError = ex - }; - _plugins.Add(info); - } - } - - public void UnloadPlugin(PluginInfo plugin) - { - if (!plugin.IsLoaded || plugin.Instance == null) - return; - - try - { - plugin.Instance.Shutdown(); - plugin.Instance = null; - plugin.IsLoaded = false; - - _plugins.Remove(plugin); - PluginUnloaded?.Invoke(this, plugin); - Log($"Plugin unloaded: {plugin.Name}"); - } - catch (Exception ex) - { - LogError($"Failed to unload plugin {plugin.Name}", ex); - } - } - - public void UnloadAll() - { - foreach (var plugin in _plugins.ToList()) - { - UnloadPlugin(plugin); - } - } - - public void RegisterService(T service) where T : class - { - _services[typeof(T)] = service; - } - - public T? GetService() where T : class - { - return _services.TryGetValue(typeof(T), out var service) ? service as T : null; - } - - public void Log(string message) - { - LogMessage?.Invoke(this, message); - System.Diagnostics.Debug.WriteLine($"[PluginManager] {message}"); - } - - public void LogError(string message, Exception? ex = null) - { - var fullMessage = ex != null ? $"{message}: {ex.Message}\n{ex.StackTrace}" : message; - Log($"ERROR: {fullMessage}"); - } - } -} \ No newline at end of file diff --git a/Ink Canvas/packages.lock.json b/Ink Canvas/packages.lock.json index 7535908c..09b35601 100644 --- a/Ink Canvas/packages.lock.json +++ b/Ink Canvas/packages.lock.json @@ -185,19 +185,6 @@ "resolved": "2.9.0", "contentHash": "GLhd1tQAJeuVO1sj3Wm/dkg0GEVWxk+XGl6rdegMSMHenZuOaWQw4PifWDsjNEC1dtV1/C8JJfK0qfdkM+VIgA==" }, - "Weikio.PluginFramework": { - "type": "Direct", - "requested": "[1.5.1, )", - "resolved": "1.5.1", - "contentHash": "Sm+wTkoMeciqmNM+MIKzmxMs2N+I6RGbOQNwCV0wXFhG17W27/CHLShotDcEFVmF8pgVDlRORmCZrpzpBR37Zw==", - "dependencies": { - "Microsoft.CodeAnalysis.CSharp": "3.3.1", - "Microsoft.Extensions.Logging": "3.1.2", - "System.Reflection.MetadataLoadContext": "4.7.1", - "Weikio.PluginFramework.Abstractions": "1.5.1", - "Weikio.TypeGenerator": "1.4.1" - } - }, "AForge": { "type": "Transitive", "resolved": "2.2.5", @@ -234,110 +221,11 @@ "resolved": "1.27.0", "contentHash": "We7LtBdoukRg9mqTfa1f5n8z/GQPMKBRj3URk9DiMuqzIHkW1lTgK5njVPSScxsRt4YzW22423tSnLWNm2MJKg==" }, - "Microsoft.CodeAnalysis.Analyzers": { - "type": "Transitive", - "resolved": "2.9.4", - "contentHash": "alIJhS0VUg/7x5AsHEoovh/wRZ0RfCSS7k5pDSqpRLTyuMTtRgj6OJJPRApRhJHOGYYsLakf1hKeXFoDwKwNkg==" - }, - "Microsoft.CodeAnalysis.Common": { - "type": "Transitive", - "resolved": "3.3.1", - "contentHash": "N5yQdGy+M4kimVG7hwCeGTCfgYjK2o5b/Shumkb/rCC+/SAkvP1HUAYK+vxPFS7dLJNtXLRsmPHKj3fnyNWnrw==", - "dependencies": { - "Microsoft.CodeAnalysis.Analyzers": "2.9.4", - "System.Collections.Immutable": "1.5.0", - "System.Memory": "4.5.3", - "System.Reflection.Metadata": "1.6.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.2", - "System.Text.Encoding.CodePages": "4.5.1", - "System.Threading.Tasks.Extensions": "4.5.3" - } - }, - "Microsoft.CodeAnalysis.CSharp": { - "type": "Transitive", - "resolved": "3.3.1", - "contentHash": "WDUIhTHem38H6VJ98x2Ssq0fweakJHnHYl7vbG8ARnsAwLoJKCQCy78EeY1oRrCKG42j0v6JVljKkeqSDA28UA==", - "dependencies": { - "Microsoft.CodeAnalysis.Common": "[3.3.1]" - } - }, - "Microsoft.Extensions.Configuration": { - "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "BxwRSBab309SYMCDCFyB6eSc7FnX5m9kOJQHw2IQIyb5PEtpfslhscTw63Gwhl3dPnaM1VGFXIyI0BVgpiLgOw==", - "dependencies": { - "Microsoft.Extensions.Configuration.Abstractions": "3.1.2" - } - }, - "Microsoft.Extensions.Configuration.Abstractions": { - "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "xmfdVdazTslWJ8od7uNS9QSPqn1wBC84RLprPrFS20EdAqd3lV0g0IZAitYbCiiICpjktnhzbUb85aLHNZ3RQw==", - "dependencies": { - "Microsoft.Extensions.Primitives": "3.1.2" - } - }, - "Microsoft.Extensions.Configuration.Binder": { - "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "IWrc9/voGki2pc5g8bRXIqs+P50tXOjNf47qgFKSu/pL50InRuXxh/nj5AG9Po8YRpvT/bYIUk3XQqHH7yUg5w==", - "dependencies": { - "Microsoft.Extensions.Configuration": "3.1.2" - } - }, - "Microsoft.Extensions.DependencyInjection": { - "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "e+F6/wjQPOFHB/sGWTAqC8FX/C6+JZWWLpryXTAQYIS3tr+17lByADdP9Y6RtxfJ4kW/IPrU6RuxTNZNdAQz1A==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.2" - } - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "/CZzCSCIm/3FFoXHfUpsfov/Elo268dcvlz/MMINT0vPgphqg2pAgdEn/EjCDyoAT3NAmsRmjfGwBumC1uYJtA==" - }, - "Microsoft.Extensions.Logging": { - "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "AIIRgKamzEqJNLZsHd37VogFX9YpxgrBmf/b3dznD7S0qjxWQnAs498ulLV1n6AKJ8XVjTCBNzsvQiSwCa7dIw==", - "dependencies": { - "Microsoft.Extensions.Configuration.Binder": "3.1.2", - "Microsoft.Extensions.DependencyInjection": "3.1.2", - "Microsoft.Extensions.Logging.Abstractions": "3.1.2", - "Microsoft.Extensions.Options": "3.1.2" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "cIXPw7VVX3fON4uuHwJFmCi0qDl8uY75xZMKB2oM3In0ZDEB1Ee+p9Ti1DSw92AwRtJ2Zh+QG1joTBednJMzvA==" - }, - "Microsoft.Extensions.Options": { - "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "6F4anwt9yMlnQckac2etjrasRFyqZNIp46p+i9qVps0DXNsOLZIKRkqq4AY4FlxXxKeGkEJC7M77RQEkvd3p8Q==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "3.1.2", - "Microsoft.Extensions.Primitives": "3.1.2" - } - }, - "Microsoft.Extensions.Primitives": { - "type": "Transitive", - "resolved": "3.1.2", - "contentHash": "WGtoFWY9yc9HGMG6ObDNQPz9dBP+xz/GqFe2dKjdE/cSdXFEKxCFTyYCzL/e8kxVkc/Bq9qjOsXRWydvn0g9Uw==" - }, "Microsoft.NETCore.Platforms": { "type": "Transitive", "resolved": "5.0.0", "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" }, - "Microsoft.NETCore.Targets": { - "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "aOZA3BWfz9RXjpzt0sRJJMjAscAUm3Hoa4UWAfceV9UTYxgwZ1lZt5nO2myFf+/jetYQo4uTP7zS8sJY67BBxg==" - }, "Microsoft.Win32.Registry": { "type": "Transitive", "resolved": "5.0.0", @@ -362,11 +250,6 @@ "resolved": "10.0.5", "contentHash": "hGZWDDJh1U6t7fy3iO4HlZYK1ur1fWE3sTqTNHkHk0Leh0JUcxYM//JtLBNG5g+6D2Lt0+aHH8rc7e5oIlNgCg==" }, - "System.Collections.Immutable": { - "type": "Transitive", - "resolved": "1.5.0", - "contentHash": "EXKiDFsChZW0RjrZ4FYHu9aW6+P4MCgEDCklsVseRfhoO0F+dXeMSsMRAlVXIo06kGJ/zv+2w1a2uc2+kxxSaQ==" - }, "System.Drawing.Common": { "type": "Transitive", "resolved": "8.0.0", @@ -375,35 +258,6 @@ "Microsoft.Win32.SystemEvents": "8.0.0" } }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0" - } - }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.3", - "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==" - }, - "System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0" - } - }, "System.Reflection.Emit": { "type": "Transitive", "resolved": "4.7.0", @@ -414,45 +268,11 @@ "resolved": "5.0.0", "contentHash": "5NecZgXktdGg34rh1OenY1rFNDCI8xSjFr+Z4OU4cU06AQHUdRnIIEeWENu3Wl4YowbzkymAIMvi3WyK9U53pQ==" }, - "System.Reflection.MetadataLoadContext": { - "type": "Transitive", - "resolved": "4.7.1", - "contentHash": "OIc85e1sXjnvRlPXdpJrcbqQr2dP23rmvAnRlbghMo3btOCo/FBzN1XGi2mIxJdlrwzHyOpVcixbluRIzHwMiA==" - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, - "System.Runtime.Loader": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "DHMaRn8D8YCK2GG2pw+UzNxn/OHVfaWx7OTLBD/hPegHZZgcZh3H6seWegrC4BYwsfuGrywIuT+MQs+rPqRLTQ==", - "dependencies": { - "System.IO": "4.3.0", - "System.Reflection": "4.3.0", - "System.Runtime": "4.3.0" - } - }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -467,25 +287,6 @@ "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Text.Encoding.CodePages": { - "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "4J2JQXbftjPMppIHJ7IC+VXQ9XfEagN92vZZNoG12i+zReYlim5dMoXFC1Zzg7tsnKDM7JPo5bYfFK4Jheq44w==", - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.2", - "System.Runtime.CompilerServices.Unsafe": "4.5.2" - } - }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", @@ -503,40 +304,10 @@ "System.Text.Encodings.Web": "8.0.0" } }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0" - } - }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.5.3", - "contentHash": "+MvhNtcvIbqmhANyKu91jQnvIRVSTiaOiFNfKWwXGHG48YAb4I/TyH8spsySiPYla7gKal5ZnF3teJqZAximyQ==" - }, "System.ValueTuple": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" - }, - "Weikio.PluginFramework.Abstractions": { - "type": "Transitive", - "resolved": "1.5.1", - "contentHash": "8ebyvrSp9JoMM6MxTiBTT6RgOWKSqvQRRqPo0J3dF1l08+E/pq1fUhhbybnCqscVjSn7v2LQtQZbIZEwQ9j4Hg==" - }, - "Weikio.TypeGenerator": { - "type": "Transitive", - "resolved": "1.4.1", - "contentHash": "GHSPO4IHAmhgt3o24JLke2+ZSDuSxur/+6Mp4BslvcEh0t/z2yPtZZO3gHhl67HHEnOTLD9BARbAl/vJQ9l43w==", - "dependencies": { - "Microsoft.CodeAnalysis.CSharp": "3.3.1", - "System.Reflection.MetadataLoadContext": "4.7.1", - "System.Runtime.Loader": "4.3.0" - } } }, "net6.0-windows10.0.19041/win-arm64": { @@ -554,95 +325,6 @@ "resolved": "8.0.0", "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, - "runtime.any.System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ==" - }, - "runtime.any.System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ==" - }, - "runtime.any.System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg==" - }, - "runtime.any.System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==", - "dependencies": { - "System.Private.Uri": "4.3.0" - } - }, - "runtime.any.System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ==" - }, - "runtime.any.System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w==" - }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.any.System.IO": "4.3.0" - } - }, - "System.Private.Uri": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Reflection": "4.3.0" - } - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Reflection.Primitives": "4.3.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "runtime.any.System.Runtime": "4.3.0" - } - }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -657,26 +339,6 @@ "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Text.Encoding": "4.3.0" - } - }, - "System.Text.Encoding.CodePages": { - "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "4J2JQXbftjPMppIHJ7IC+VXQ9XfEagN92vZZNoG12i+zReYlim5dMoXFC1Zzg7tsnKDM7JPo5bYfFK4Jheq44w==", - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.2", - "System.Runtime.CompilerServices.Unsafe": "4.5.2" - } - }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", @@ -684,17 +346,6 @@ "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } - }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Threading.Tasks": "4.3.0" - } } }, "net6.0-windows10.0.19041/win-x64": { @@ -712,95 +363,6 @@ "resolved": "8.0.0", "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, - "runtime.any.System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ==" - }, - "runtime.any.System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ==" - }, - "runtime.any.System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg==" - }, - "runtime.any.System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==", - "dependencies": { - "System.Private.Uri": "4.3.0" - } - }, - "runtime.any.System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ==" - }, - "runtime.any.System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w==" - }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.any.System.IO": "4.3.0" - } - }, - "System.Private.Uri": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Reflection": "4.3.0" - } - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Reflection.Primitives": "4.3.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "runtime.any.System.Runtime": "4.3.0" - } - }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -815,26 +377,6 @@ "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Text.Encoding": "4.3.0" - } - }, - "System.Text.Encoding.CodePages": { - "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "4J2JQXbftjPMppIHJ7IC+VXQ9XfEagN92vZZNoG12i+zReYlim5dMoXFC1Zzg7tsnKDM7JPo5bYfFK4Jheq44w==", - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.2", - "System.Runtime.CompilerServices.Unsafe": "4.5.2" - } - }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", @@ -842,17 +384,6 @@ "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } - }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Threading.Tasks": "4.3.0" - } } }, "net6.0-windows10.0.19041/win-x86": { @@ -870,95 +401,6 @@ "resolved": "8.0.0", "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw==" }, - "runtime.any.System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "SDZ5AD1DtyRoxYtEcqQ3HDlcrorMYXZeCt7ZhG9US9I5Vva+gpIWDGMkcwa5XiKL0ceQKRZIX2x0XEjLX7PDzQ==" - }, - "runtime.any.System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "hLC3A3rI8jipR5d9k7+f0MgRCW6texsAp0MWkN/ci18FMtQ9KH7E2vDn/DH2LkxsszlpJpOn9qy6Z6/69rH6eQ==" - }, - "runtime.any.System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Nrm1p3armp6TTf2xuvaa+jGTTmncALWFq22CpmwRvhDf6dE9ZmH40EbOswD4GnFLrMRS0Ki6Kx5aUPmKK/hZBg==" - }, - "runtime.any.System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "fRS7zJgaG9NkifaAxGGclDDoRn9HC7hXACl52Or06a/fxdzDajWb5wov3c6a+gVSlekRoexfjwQSK9sh5um5LQ==", - "dependencies": { - "System.Private.Uri": "4.3.0" - } - }, - "runtime.any.System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ==" - }, - "runtime.any.System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "OhBAVBQG5kFj1S+hCEQ3TUHBAEtZ3fbEMgZMRNdN8A0Pj4x+5nTELEqL59DU0TjKVE6II3dqKw4Dklb3szT65w==" - }, - "System.IO": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "System.Text.Encoding": "4.3.0", - "System.Threading.Tasks": "4.3.0", - "runtime.any.System.IO": "4.3.0" - } - }, - "System.Private.Uri": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "I4SwANiUGho1esj4V4oSlPllXjzCZDE+5XXso2P03LW2vOda2Enzh8DWOxwN6hnrJyp314c7KuVu31QYhRzOGg==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0" - } - }, - "System.Reflection": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "KMiAFoW7MfJGa9nDFNcfu+FpEdiHpWgTcS2HdMpDvt9saK3y/G4GwprPyzqjFH9NTaGPQeWNHU+iDlDILj96aQ==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.IO": "4.3.0", - "System.Reflection.Primitives": "4.3.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Reflection": "4.3.0" - } - }, - "System.Reflection.Primitives": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "5RXItQz5As4xN2/YUDxdpsEkMhvw3e6aNveFXUn4Hl/udNTCNhnKp8lT9fnc3MhvGKh1baak5CovpuQUXHAlIA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Reflection.Primitives": "4.3.0" - } - }, - "System.Runtime": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "runtime.any.System.Runtime": "4.3.0" - } - }, "System.Security.AccessControl": { "type": "Transitive", "resolved": "5.0.0", @@ -973,26 +415,6 @@ "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Text.Encoding": "4.3.0" - } - }, - "System.Text.Encoding.CodePages": { - "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "4J2JQXbftjPMppIHJ7IC+VXQ9XfEagN92vZZNoG12i+zReYlim5dMoXFC1Zzg7tsnKDM7JPo5bYfFK4Jheq44w==", - "dependencies": { - "Microsoft.NETCore.Platforms": "2.1.2", - "System.Runtime.CompilerServices.Unsafe": "4.5.2" - } - }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", @@ -1000,17 +422,6 @@ "dependencies": { "System.Runtime.CompilerServices.Unsafe": "6.0.0" } - }, - "System.Threading.Tasks": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "LbSxKEdOUhVe8BezB/9uOGGppt+nZf6e1VFyw6v3DN6lqitm0OSn2uXMOdtP0M3W4iMcqcivm2J6UgqiwwnXiA==", - "dependencies": { - "Microsoft.NETCore.Platforms": "1.1.0", - "Microsoft.NETCore.Targets": "1.1.0", - "System.Runtime": "4.3.0", - "runtime.any.System.Threading.Tasks": "4.3.0" - } } } } From 8b2bc352a6dd1cd56f4222abebb577ce5b7ab737 Mon Sep 17 00:00:00 2001 From: tayasui rainnya! <156585442+Tayasui-rainnya@users.noreply.github.com> Date: Thu, 9 Apr 2026 12:54:31 +0800 Subject: [PATCH 019/205] feat: add include-ink toggle for area screenshot selector --- Ink Canvas/MainWindow_cs/MW_ImageInsert.cs | 173 +++++++++++++++++- Ink Canvas/MainWindow_cs/MW_Screenshot.cs | 55 +++++- .../Windows/ScreenshotSelectorWindow.xaml | 18 ++ .../Windows/ScreenshotSelectorWindow.xaml.cs | 53 +++++- 4 files changed, 284 insertions(+), 15 deletions(-) diff --git a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs index bab3e578..69d58d29 100644 --- a/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs +++ b/Ink Canvas/MainWindow_cs/MW_ImageInsert.cs @@ -30,15 +30,19 @@ namespace Ink_Canvas public Bitmap CameraImage; public BitmapSource CameraBitmapSource; public bool AddToWhiteboard; + public bool IncludeInk; + public BitmapSource InkOverlayBitmapSource; public ScreenshotResult(Rectangle area, List path = null, Bitmap cameraImage = null, - BitmapSource cameraBitmapSource = null, bool addToWhiteboard = false) + BitmapSource cameraBitmapSource = null, bool addToWhiteboard = false, bool includeInk = false, BitmapSource inkOverlayBitmapSource = null) { Area = area; Path = path; CameraImage = cameraImage; CameraBitmapSource = cameraBitmapSource; AddToWhiteboard = addToWhiteboard; + IncludeInk = includeInk; + InkOverlayBitmapSource = inkOverlayBitmapSource; } } @@ -60,6 +64,8 @@ namespace Ink_Canvas { try { + var inkOverlayPreview = CreateInkOverlayPreviewBitmapSource(); + // 隐藏主窗口以避免截图包含窗口本身 var originalVisibility = Visibility; Visibility = Visibility.Hidden; @@ -68,7 +74,7 @@ namespace Ink_Canvas await Task.Delay(200); // 启动区域选择截图 - var screenshotResult = await ShowScreenshotSelector(); + var screenshotResult = await ShowScreenshotSelector(inkOverlayPreview); // 恢复窗口显示 Visibility = originalVisibility; @@ -104,11 +110,33 @@ namespace Ink_Canvas try { + if (screenshotResult.Value.IncludeInk && screenshotResult.Value.InkOverlayBitmapSource != null) + { + var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Value.Area, screenshotResult.Value.InkOverlayBitmapSource); + if (withInkBitmap != null && withInkBitmap != finalBitmap) + { + if (needDisposeFinalBitmap && finalBitmap != originalBitmap) + { + finalBitmap.Dispose(); + } + finalBitmap = withInkBitmap; + needDisposeFinalBitmap = true; + } + } + // 如果有路径信息,应用形状遮罩 if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0) { - finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area); - needDisposeFinalBitmap = true; // 标记需要释放新创建的位图 + var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area); + if (maskedBitmap != null && maskedBitmap != finalBitmap) + { + if (needDisposeFinalBitmap && finalBitmap != originalBitmap) + { + finalBitmap.Dispose(); + } + finalBitmap = maskedBitmap; + needDisposeFinalBitmap = true; // 标记需要释放新创建的位图 + } } // 将截图转换为WPF Image并插入到画布 @@ -199,7 +227,7 @@ namespace Ink_Canvas /// 2. 获取用户选择的区域或摄像头截图 /// 3. 返回截图结果 /// - private async Task ShowScreenshotSelector() + private async Task ShowScreenshotSelector(BitmapSource inkOverlayPreview = null) { ScreenshotResult? result = null; @@ -207,7 +235,7 @@ namespace Ink_Canvas { await Application.Current.Dispatcher.InvokeAsync(() => { - var selectorWindow = new ScreenshotSelectorWindow(); + var selectorWindow = new ScreenshotSelectorWindow(inkOverlayPreview); if (selectorWindow.ShowDialog() == true) { // 检查是否是摄像头截图 @@ -218,7 +246,9 @@ namespace Ink_Canvas null, // 摄像头截图不需要路径 null, // 不再使用Bitmap selectorWindow.CameraBitmapSource, // 摄像头BitmapSource - selectorWindow.ShouldAddToWhiteboard + selectorWindow.ShouldAddToWhiteboard, + false, + null ); } else if (selectorWindow.CameraImage != null) @@ -228,7 +258,9 @@ namespace Ink_Canvas null, // 摄像头截图不需要路径 selectorWindow.CameraImage, // 摄像头图像 null, - selectorWindow.ShouldAddToWhiteboard + selectorWindow.ShouldAddToWhiteboard, + false, + null ); } else @@ -238,7 +270,9 @@ namespace Ink_Canvas selectorWindow.SelectedPath, null, null, - selectorWindow.ShouldAddToWhiteboard + selectorWindow.ShouldAddToWhiteboard, + selectorWindow.IncludeInkInScreenshot, + selectorWindow.IncludeInkInScreenshot ? inkOverlayPreview : null ); } } @@ -304,6 +338,127 @@ namespace Ink_Canvas } } + private BitmapSource CreateInkOverlayPreviewBitmapSource() + { + try + { + if (inkCanvas == null || inkCanvas.Strokes == null || inkCanvas.Strokes.Count == 0) + { + return null; + } + + if (inkCanvas.ActualWidth <= 0 || inkCanvas.ActualHeight <= 0) + { + return null; + } + + var virtualScreen = SystemInformation.VirtualScreen; + var dpiScale = GetDpiScale(); + var virtualLeftDip = virtualScreen.Left / dpiScale; + var virtualTopDip = virtualScreen.Top / dpiScale; + + var inkTopLeftInWindow = inkCanvas.TranslatePoint(new Point(0, 0), this); + var inkRectDip = new Rect( + (Left + inkTopLeftInWindow.X) - virtualLeftDip, + (Top + inkTopLeftInWindow.Y) - virtualTopDip, + inkCanvas.ActualWidth, + inkCanvas.ActualHeight); + + var drawingVisual = new DrawingVisual(); + using (var dc = drawingVisual.RenderOpen()) + { + var visualBrush = new VisualBrush(inkCanvas) + { + Stretch = Stretch.Fill + }; + dc.DrawRectangle(visualBrush, null, inkRectDip); + } + + var rtb = new RenderTargetBitmap( + Math.Max(1, virtualScreen.Width), + Math.Max(1, virtualScreen.Height), + 96, + 96, + PixelFormats.Pbgra32); + rtb.Render(drawingVisual); + rtb.Freeze(); + return rtb; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"创建截图墨迹预览失败: {ex.Message}", LogHelper.LogType.Warning); + return null; + } + } + + private Bitmap OverlayInkOnCapturedBitmap(Bitmap capturedBitmap, Rectangle captureArea, BitmapSource inkOverlayBitmapSource) + { + if (capturedBitmap == null || inkOverlayBitmapSource == null) + { + return capturedBitmap; + } + + try + { + var virtualScreen = SystemInformation.VirtualScreen; + var sourceRect = new Rectangle( + captureArea.X - virtualScreen.X, + captureArea.Y - virtualScreen.Y, + captureArea.Width, + captureArea.Height); + + sourceRect.Intersect(new Rectangle(0, 0, inkOverlayBitmapSource.PixelWidth, inkOverlayBitmapSource.PixelHeight)); + if (sourceRect.Width <= 0 || sourceRect.Height <= 0) + { + return capturedBitmap; + } + + using (var inkOverlayBitmap = ConvertBitmapSourceToBitmap(inkOverlayBitmapSource)) + { + if (inkOverlayBitmap == null) + { + return capturedBitmap; + } + + var resultBitmap = new Bitmap(capturedBitmap.Width, capturedBitmap.Height, PixelFormat.Format32bppArgb); + using (var g = Graphics.FromImage(resultBitmap)) + { + g.DrawImage(capturedBitmap, 0, 0, capturedBitmap.Width, capturedBitmap.Height); + + var targetRect = new Rectangle(0, 0, Math.Min(sourceRect.Width, capturedBitmap.Width), Math.Min(sourceRect.Height, capturedBitmap.Height)); + g.DrawImage(inkOverlayBitmap, targetRect, sourceRect, GraphicsUnit.Pixel); + } + + return resultBitmap; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"叠加截图墨迹失败: {ex.Message}", LogHelper.LogType.Warning); + return capturedBitmap; + } + } + + private Bitmap ConvertBitmapSourceToBitmap(BitmapSource bitmapSource) + { + if (bitmapSource == null) + { + return null; + } + + using (var memoryStream = new MemoryStream()) + { + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(bitmapSource)); + encoder.Save(memoryStream); + memoryStream.Position = 0; + using (var tempBitmap = new Bitmap(memoryStream)) + { + return new Bitmap(tempBitmap); + } + } + } + /// /// 将截图插入到画布 /// diff --git a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs index 61632332..16e36382 100644 --- a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs +++ b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs @@ -162,10 +162,11 @@ namespace Ink_Canvas var originalVisibility = Visibility; try { + var inkOverlayPreview = CreateInkOverlayPreviewBitmapSource(); Visibility = Visibility.Hidden; await Task.Delay(200); - var screenshotResult = await ShowScreenshotSelector(); + var screenshotResult = await ShowScreenshotSelector(inkOverlayPreview); if (!screenshotResult.HasValue) { @@ -202,10 +203,32 @@ namespace Ink_Canvas try { + if (screenshotResult.Value.IncludeInk && screenshotResult.Value.InkOverlayBitmapSource != null) + { + var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Value.Area, screenshotResult.Value.InkOverlayBitmapSource); + if (withInkBitmap != null && withInkBitmap != finalBitmap) + { + if (needDisposeFinalBitmap && finalBitmap != originalBitmap) + { + finalBitmap.Dispose(); + } + finalBitmap = withInkBitmap; + needDisposeFinalBitmap = true; + } + } + if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0) { - finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area); - needDisposeFinalBitmap = true; + var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area); + if (maskedBitmap != null && maskedBitmap != finalBitmap) + { + if (needDisposeFinalBitmap && finalBitmap != originalBitmap) + { + finalBitmap.Dispose(); + } + finalBitmap = maskedBitmap; + needDisposeFinalBitmap = true; + } } var directory = Path.GetDirectoryName(desktopPath); @@ -275,10 +298,32 @@ namespace Ink_Canvas try { + if (screenshotResult.IncludeInk && screenshotResult.InkOverlayBitmapSource != null) + { + var withInkBitmap = OverlayInkOnCapturedBitmap(finalBitmap, screenshotResult.Area, screenshotResult.InkOverlayBitmapSource); + if (withInkBitmap != null && withInkBitmap != finalBitmap) + { + if (needDisposeFinalBitmap && finalBitmap != originalBitmap) + { + finalBitmap.Dispose(); + } + finalBitmap = withInkBitmap; + needDisposeFinalBitmap = true; + } + } + if (screenshotResult.Path != null && screenshotResult.Path.Count > 0) { - finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Path, screenshotResult.Area); - needDisposeFinalBitmap = true; + var maskedBitmap = ApplyShapeMask(finalBitmap, screenshotResult.Path, screenshotResult.Area); + if (maskedBitmap != null && maskedBitmap != finalBitmap) + { + if (needDisposeFinalBitmap && finalBitmap != originalBitmap) + { + finalBitmap.Dispose(); + } + finalBitmap = maskedBitmap; + needDisposeFinalBitmap = true; + } } bitmapSourceForClipboard = ConvertBitmapToBitmapSource(finalBitmap); diff --git a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml index 0e9584d8..86e0dfef 100644 --- a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml +++ b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml @@ -36,6 +36,13 @@ + + + @@ -141,6 +148,17 @@ Background="#404040" /> + + - - @@ -637,7 +637,7 @@ @@ -666,38 +666,38 @@ @@ -781,19 +781,19 @@ @@ -807,14 +807,14 @@ @@ -822,7 +822,7 @@ @@ -830,14 +830,14 @@ - - - - - - + + + + + @@ -848,7 +848,7 @@ FontSize="14" Margin="0,0,16,0" /> @@ -872,7 +872,7 @@ FontSize="14" Margin="0,0,8,0" Width="320" Style="{StaticResource AutoFitSettingsOptionLabel14}" /> @@ -880,7 +880,7 @@ FontSize="14" Margin="0,0,8,0" Width="320" Style="{StaticResource AutoFitSettingsOptionLabel14}" /> @@ -890,11 +890,11 @@ - - - + + + @@ -945,7 +945,7 @@ @@ -955,7 +955,7 @@ @@ -1005,7 +1005,7 @@ @@ -1054,7 +1054,7 @@ Width="320" Style="{StaticResource AutoFitSettingsOptionLabel14}" /> @@ -1108,7 +1108,7 @@ @@ -1116,7 +1116,7 @@ FontSize="14" Margin="0,0,16,0" /> + VerticalAlignment="Center"> @@ -1129,7 +1129,7 @@ VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" /> @@ -1150,7 +1150,7 @@ VerticalAlignment="Center" FontSize="14" Margin="0,0,16,0" /> @@ -1196,7 +1196,7 @@ @@ -1234,7 +1234,7 @@ - - - - + + + - - - - + + + - - - - - - - - + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + - @@ -5411,7 +5411,7 @@ + /> + /> + /> + /> @@ -5766,7 +5766,7 @@ VerticalAlignment="Center" /> + /> + /> + /> + /> + /> @@ -6773,7 +6773,7 @@ @@ -7490,7 +7490,7 @@ HorizontalAlignment="Right" Height="50" Margin="10" Visibility="Collapsed"> - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 21f3b998381e18d96b49eae68fc1dee5ded4307c Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Fri, 17 Apr 2026 00:14:08 +0800 Subject: [PATCH 063/205] =?UTF-8?q?fix:=E4=BF=AE=E5=A4=8D=E6=B5=AE?= =?UTF-8?q?=E5=8A=A8=E5=B7=A5=E5=85=B7=E6=A0=8F=E6=89=B9=E6=B3=A8=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E4=BD=8D=E7=BD=AE=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 这才是真正的精准定位 都给我学好了 --- Ink Canvas/MainWindow.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index a75cbda0..c80fdedb 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -6544,10 +6544,10 @@ - + + BorderThickness="1" CornerRadius="8" Margin="-96.5,-84"> Date: Fri, 17 Apr 2026 00:30:45 +0800 Subject: [PATCH 064/205] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20MainWindow.xaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml | 279 ++++++++++++++++++------------------- 1 file changed, 139 insertions(+), 140 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index c80fdedb..a8032a91 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -7184,150 +7184,149 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - + + + + + + + + + + + + + + + + + + + + + + + + Date: Fri, 17 Apr 2026 01:20:06 +0800 Subject: [PATCH 065/205] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml | 53 ++++++------------- Ink Canvas/MainWindow.xaml.cs | 2 +- Ink Canvas/MainWindow_cs/MW_AutoFold.cs | 6 +-- .../MainWindow_cs/MW_FloatingBarIcons.cs | 34 ++++++------ .../MainWindow_cs/MW_Save&OpenStrokes.cs | 4 +- Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs | 2 +- InkCanvas.Controls/BoardToolbarButton.xaml.cs | 28 ++++++++++ InkCanvas.Controls/QuickPanelButton.xaml.cs | 4 +- InkCanvas.Controls/ToolMenuButton.xaml.cs | 4 +- InkCanvas.Controls/ToolbarImageButton.xaml.cs | 4 +- 10 files changed, 73 insertions(+), 68 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index a8032a91..e8473a6a 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -5592,7 +5592,7 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index e4591fcd..696efd02 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -2884,7 +2884,7 @@ namespace Ink_Canvas ShowPage(currentPageIndex); } // 快速面板退出PPT放映按钮事件 - private void ExitPPTSlideShow_MouseUp(object sender, RoutedEventArgs e) + private void ExitPPTSlideShow_MouseUp(object sender, MouseButtonEventArgs e) { // 直接调用PPT放映结束按钮的逻辑 BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null); diff --git a/Ink Canvas/MainWindow_cs/MW_AutoFold.cs b/Ink Canvas/MainWindow_cs/MW_AutoFold.cs index 95fd7ec0..f1c95b23 100644 --- a/Ink Canvas/MainWindow_cs/MW_AutoFold.cs +++ b/Ink Canvas/MainWindow_cs/MW_AutoFold.cs @@ -65,7 +65,7 @@ namespace Ink_Canvas /// /// 事件发送者。 /// 路由事件参数。 - public async void FoldFloatingBar_MouseUp(object sender, RoutedEventArgs e) + public async void FoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e) { await FoldFloatingBar(sender); } @@ -315,7 +315,7 @@ namespace Ink_Canvas /// 1. 隐藏左侧快捷面板 /// 2. 隐藏右侧快捷面板 /// - private void HideQuickPanel_MouseUp(object sender, RoutedEventArgs e) + private void HideQuickPanel_MouseUp(object sender, MouseButtonEventArgs e) { HideLeftQuickPanel(); HideRightQuickPanel(); @@ -326,7 +326,7 @@ namespace Ink_Canvas /// /// 事件发送者。 /// 路由事件参数。 - public async void UnFoldFloatingBar_MouseUp(object sender, RoutedEventArgs e) + public async void UnFoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e) { await UnFoldFloatingBar(sender); } diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index 6617fb2d..989d82a0 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -626,7 +626,7 @@ namespace Ink_Canvas /// /// 发送者 /// 鼠标按钮事件参数 - internal void SymbolIconUndo_MouseUp(object sender, RoutedEventArgs e) + internal void SymbolIconUndo_MouseUp(object sender, MouseButtonEventArgs e) { if (!BtnUndo.IsEnabled) return; BtnUndo_Click(BtnUndo, null); @@ -659,7 +659,7 @@ namespace Ink_Canvas /// /// 发送者 /// 鼠标按钮事件参数 - internal void ImageBlackboard_MouseUp(object sender, RoutedEventArgs e) + internal void ImageBlackboard_MouseUp(object sender, MouseButtonEventArgs e) { LeftUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed; @@ -933,7 +933,7 @@ namespace Ink_Canvas /// /// 发送者 /// 鼠标按钮事件参数 - internal void SymbolIconDelete_MouseUp(object sender, RoutedEventArgs e) + internal void SymbolIconDelete_MouseUp(object sender, MouseButtonEventArgs e) { if (inkCanvas.GetSelectedStrokes().Count > 0) { @@ -979,7 +979,7 @@ namespace Ink_Canvas /// /// 发送者 /// 鼠标按钮事件参数 - internal void SymbolIconSelect_MouseUp(object sender, RoutedEventArgs e) + internal void SymbolIconSelect_MouseUp(object sender, MouseButtonEventArgs e) { if (lastBorderMouseDownObject is Panel panel) @@ -1098,7 +1098,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - private void SymbolIconSettings_Click(object sender, RoutedEventArgs e) + private void SymbolIconSettings_Click(object sender, MouseButtonEventArgs e) { if (isOpeningOrHidingSettingsPane) return; HideSubPanels(); @@ -1110,7 +1110,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - private async void SymbolIconScreenshot_MouseUp(object sender, RoutedEventArgs e) + private async void SymbolIconScreenshot_MouseUp(object sender, MouseButtonEventArgs e) { HideSubPanelsImmediately(); await Task.Delay(50); @@ -1131,7 +1131,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - private void ImageCountdownTimer_MouseUp(object sender, RoutedEventArgs e) + private void ImageCountdownTimer_MouseUp(object sender, MouseButtonEventArgs e) { LeftUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed; RightUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed; @@ -1185,7 +1185,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - private void OperatingGuideWindowIcon_MouseUp(object sender, RoutedEventArgs e) + private void OperatingGuideWindowIcon_MouseUp(object sender, MouseButtonEventArgs e) { AnimationsHelper.HideWithSlideAndFade(BorderTools); AnimationsHelper.HideWithSlideAndFade(BoardBorderTools); @@ -1199,7 +1199,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - private void SymbolIconRand_MouseUp(object sender, RoutedEventArgs e) + private void SymbolIconRand_MouseUp(object sender, MouseButtonEventArgs e) { // 如果控件被隐藏,不处理事件 if (BoardRandomDrawToolBtn.Visibility != Visibility.Visible) return; @@ -1332,7 +1332,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - private void SymbolIconRandOne_MouseUp(object sender, RoutedEventArgs e) + private void SymbolIconRandOne_MouseUp(object sender, MouseButtonEventArgs e) { // 如果控件被隐藏,不处理事件 if (BoardSingleDrawToolBtn.Visibility != Visibility.Visible) return; @@ -1407,7 +1407,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - private void GridInkReplayButton_MouseUp(object sender, RoutedEventArgs e) + private void GridInkReplayButton_MouseUp(object sender, MouseButtonEventArgs e) { //if (lastBorderMouseDownObject != sender) return; @@ -1711,7 +1711,7 @@ namespace Ink_Canvas /// /// 发送者 /// 鼠标按钮事件参数 - private void SymbolIconTools_MouseUp(object sender, RoutedEventArgs e) + private void SymbolIconTools_MouseUp(object sender, MouseButtonEventArgs e) { if (BorderTools.Visibility == Visibility.Visible) { @@ -2117,7 +2117,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - internal async void CursorIcon_Click(object sender, RoutedEventArgs e) + internal async void CursorIcon_Click(object sender, MouseButtonEventArgs e) { if (lastBorderMouseDownObject is Panel panel) panel.Background = new SolidColorBrush(Colors.Transparent); @@ -2232,7 +2232,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - internal void PenIcon_Click(object sender, RoutedEventArgs e) + internal void PenIcon_Click(object sender, MouseButtonEventArgs e) { if (lastBorderMouseDownObject is Panel panel) panel.Background = new SolidColorBrush(Colors.Transparent); @@ -2477,7 +2477,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - internal void EraserIcon_Click(object sender, RoutedEventArgs e) + internal void EraserIcon_Click(object sender, MouseButtonEventArgs e) { bool isAlreadyEraser = inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint; forceEraser = false; @@ -2590,7 +2590,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - private void EraserIconByStrokes_Click(object sender, RoutedEventArgs e) + private void EraserIconByStrokes_Click(object sender, MouseButtonEventArgs e) { // 禁用高级橡皮擦系统 DisableEraserOverlay(); @@ -2622,7 +2622,7 @@ namespace Ink_Canvas /// /// 发送者 /// 路由事件参数 - private void CursorWithDelIcon_Click(object sender, RoutedEventArgs e) + private void CursorWithDelIcon_Click(object sender, MouseButtonEventArgs e) { SymbolIconDelete_MouseUp(sender, null); CursorIcon_Click(null, null); diff --git a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs index e9892ceb..bb42d7c5 100644 --- a/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs +++ b/Ink Canvas/MainWindow_cs/MW_Save&OpenStrokes.cs @@ -94,7 +94,7 @@ namespace Ink_Canvas /// 3. 隐藏通知面板 /// 4. 调用SaveInkCanvasStrokes方法保存墨迹 /// - private void SymbolIconSaveStrokes_MouseUp(object sender, RoutedEventArgs e) + private void SymbolIconSaveStrokes_MouseUp(object sender, MouseButtonEventArgs e) { if (lastBorderMouseDownObject != sender || inkCanvas.Visibility != Visibility.Visible) return; @@ -901,7 +901,7 @@ namespace Ink_Canvas /// - 其他:处理单个墨迹文件(二进制格式) /// 5. 如果墨迹画布不可见,切换到鼠标模式 /// - private void SymbolIconOpenStrokes_MouseUp(object sender, RoutedEventArgs e) + private void SymbolIconOpenStrokes_MouseUp(object sender, MouseButtonEventArgs e) { if (lastBorderMouseDownObject != sender) return; AnimationsHelper.HideWithSlideAndFade(BorderTools); diff --git a/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs b/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs index 875c2df8..17d8fe4d 100644 --- a/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs +++ b/Ink Canvas/MainWindow_cs/MW_ShapeDrawing.cs @@ -33,7 +33,7 @@ namespace Ink_Canvas /// 3. 如果形状绘制面板可见,则隐藏它 /// 4. 如果形状绘制面板不可见,则显示它 /// - private void ImageDrawShape_MouseUp(object sender, RoutedEventArgs e) + private void ImageDrawShape_MouseUp(object sender, MouseButtonEventArgs e) { if (BorderDrawShape.Visibility == Visibility.Visible) { diff --git a/InkCanvas.Controls/BoardToolbarButton.xaml.cs b/InkCanvas.Controls/BoardToolbarButton.xaml.cs index 1ffb6a9d..7c715dc4 100644 --- a/InkCanvas.Controls/BoardToolbarButton.xaml.cs +++ b/InkCanvas.Controls/BoardToolbarButton.xaml.cs @@ -113,6 +113,34 @@ namespace Ink_Canvas.Controls set => LabelTextBlock.Foreground = value; } + public static readonly DependencyProperty IsEnabledBindingProperty = DependencyProperty.Register( + nameof(IsEnabledBinding), typeof(bool?), typeof(BoardToolbarButton), + new PropertyMetadata(null, OnIsEnabledBindingChanged)); + + private static void OnIsEnabledBindingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var button = (BoardToolbarButton)d; + if (e.NewValue is bool isEnabled) + { + button.IsEnabled = isEnabled; + button.UpdateIconOpacity(isEnabled); + } + } + + public bool? IsEnabledBinding + { + get => (bool?)GetValue(IsEnabledBindingProperty); + set => SetValue(IsEnabledBindingProperty, value); + } + + private void UpdateIconOpacity(bool isEnabled) + { + if (ButtonImage != null) + { + ButtonImage.Opacity = isEnabled ? 1.0 : 0.4; + } + } + public BoardToolbarButton() { InitializeComponent(); diff --git a/InkCanvas.Controls/QuickPanelButton.xaml.cs b/InkCanvas.Controls/QuickPanelButton.xaml.cs index f4815f5e..28319da1 100644 --- a/InkCanvas.Controls/QuickPanelButton.xaml.cs +++ b/InkCanvas.Controls/QuickPanelButton.xaml.cs @@ -65,7 +65,7 @@ namespace Ink_Canvas.Controls set => SetValue(LabelFontSizeProperty, value); } - public event RoutedEventHandler ButtonMouseUp; + public event MouseButtonEventHandler ButtonMouseUp; public QuickPanelButton() { @@ -74,7 +74,7 @@ namespace Ink_Canvas.Controls private void ButtonPanel_MouseUp(object sender, MouseButtonEventArgs e) { - ButtonMouseUp?.Invoke(this, new RoutedEventArgs(e.RoutedEvent, this)); + ButtonMouseUp?.Invoke(this, e); } } } diff --git a/InkCanvas.Controls/ToolMenuButton.xaml.cs b/InkCanvas.Controls/ToolMenuButton.xaml.cs index 5d3f455b..63ffe48e 100644 --- a/InkCanvas.Controls/ToolMenuButton.xaml.cs +++ b/InkCanvas.Controls/ToolMenuButton.xaml.cs @@ -84,7 +84,7 @@ namespace Ink_Canvas.Controls public event MouseButtonEventHandler ButtonMouseDown; public event MouseEventHandler ButtonMouseLeave; - public event RoutedEventHandler ButtonMouseUp; + public event MouseButtonEventHandler ButtonMouseUp; public ToolMenuButton() { @@ -115,7 +115,7 @@ namespace Ink_Canvas.Controls private void ButtonPanel_MouseUp(object sender, MouseButtonEventArgs e) { - ButtonMouseUp?.Invoke(this, new RoutedEventArgs(e.RoutedEvent, this)); + ButtonMouseUp?.Invoke(this, e); } } } diff --git a/InkCanvas.Controls/ToolbarImageButton.xaml.cs b/InkCanvas.Controls/ToolbarImageButton.xaml.cs index 17d7940b..ad02cd87 100644 --- a/InkCanvas.Controls/ToolbarImageButton.xaml.cs +++ b/InkCanvas.Controls/ToolbarImageButton.xaml.cs @@ -61,7 +61,7 @@ namespace Ink_Canvas.Controls public event MouseButtonEventHandler ButtonMouseDown; public event MouseEventHandler ButtonMouseLeave; - public event RoutedEventHandler ButtonMouseUp; + public event MouseButtonEventHandler ButtonMouseUp; public ToolbarImageButton() { @@ -104,7 +104,7 @@ namespace Ink_Canvas.Controls ButtonPanel.Background = Brushes.Transparent; _lastPressedButton = null; } - ButtonMouseUp?.Invoke(this, new RoutedEventArgs(e.RoutedEvent, this)); + ButtonMouseUp?.Invoke(this, e); } } } From 3916476af7acca6ebb1bf33bb6422dde026cf0d4 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Fri, 17 Apr 2026 01:43:18 +0800 Subject: [PATCH 066/205] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml | 33 ++++--------------- .../MainWindow_cs/MW_FloatingBarIcons.cs | 32 +++++++++--------- InkCanvas.Controls/BoardToolbarButton.xaml | 2 ++ InkCanvas.Controls/BoardToolbarButton.xaml.cs | 1 + 4 files changed, 25 insertions(+), 43 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index e8473a6a..b54e5346 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -4671,33 +4671,12 @@ - - - - - - - - - - - - - - - - - + /// 用於浮動工具欄的"手勢"按鈕和白板工具欄的"手勢"按鈕的點擊事件 /// - private void TwoFingerGestureBorder_MouseUp(object sender, RoutedEventArgs e) + private void TwoFingerGestureBorder_MouseUp(object sender, MouseButtonEventArgs e) { if (TwoFingerGestureBorder.Visibility == Visibility.Visible) { @@ -93,12 +93,12 @@ namespace Ink_Canvas new BitmapImage(new Uri(gestureIconPath, UriKind.Relative)); BoardGesture.Background = new SolidColorBrush(boardBgColor); - BoardGestureGeometry.Brush = new SolidColorBrush(boardIconColor); - BoardGestureGeometry2.Brush = new SolidColorBrush(boardIconColor); - BoardGestureLabel.Foreground = new SolidColorBrush(boardTextColor); + BoardGesture.IconGeometryDrawing.Brush = new SolidColorBrush(boardIconColor); + BoardGesture.IconGeometryDrawing2.Brush = new SolidColorBrush(boardIconColor); + BoardGesture.Foreground = new SolidColorBrush(boardTextColor); BoardGesture.BorderBrush = new SolidColorBrush(boardBorderColor); - BoardGestureGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DisabledGestureIcon); - BoardGestureGeometry2.Geometry = Geometry.Parse("F0 M24,24z M0,0z"); + BoardGesture.IconGeometryDrawing.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DisabledGestureIcon); + BoardGesture.IconGeometryDrawing2.Geometry = Geometry.Parse("F0 M24,24z M0,0z"); } else { @@ -110,12 +110,12 @@ namespace Ink_Canvas new BitmapImage(new Uri("/Resources/new-icons/gesture-enabled.png", UriKind.Relative)); BoardGesture.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235)); - BoardGestureGeometry.Brush = new SolidColorBrush(Colors.GhostWhite); - BoardGestureGeometry2.Brush = new SolidColorBrush(Colors.GhostWhite); - BoardGestureLabel.Foreground = new SolidColorBrush(Colors.GhostWhite); + BoardGesture.IconGeometryDrawing.Brush = new SolidColorBrush(Colors.GhostWhite); + BoardGesture.IconGeometryDrawing2.Brush = new SolidColorBrush(Colors.GhostWhite); + BoardGesture.Foreground = new SolidColorBrush(Colors.GhostWhite); BoardGesture.BorderBrush = new SolidColorBrush(Color.FromRgb(37, 99, 235)); - BoardGestureGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.EnabledGestureIcon); - BoardGestureGeometry2.Geometry = Geometry.Parse("F0 M24,24z M0,0z " + XamlGraphicsIconGeometries.EnabledGestureIconBadgeCheck); + BoardGesture.IconGeometryDrawing.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.EnabledGestureIcon); + BoardGesture.IconGeometryDrawing2.Geometry = Geometry.Parse("F0 M24,24z M0,0z " + XamlGraphicsIconGeometries.EnabledGestureIconBadgeCheck); } else { @@ -123,12 +123,12 @@ namespace Ink_Canvas new BitmapImage(new Uri(gestureIconPath, UriKind.Relative)); BoardGesture.Background = new SolidColorBrush(boardBgColor); - BoardGestureGeometry.Brush = new SolidColorBrush(boardIconColor); - BoardGestureGeometry2.Brush = new SolidColorBrush(boardIconColor); - BoardGestureLabel.Foreground = new SolidColorBrush(boardTextColor); + BoardGesture.IconGeometryDrawing.Brush = new SolidColorBrush(boardIconColor); + BoardGesture.IconGeometryDrawing2.Brush = new SolidColorBrush(boardIconColor); + BoardGesture.Foreground = new SolidColorBrush(boardTextColor); BoardGesture.BorderBrush = new SolidColorBrush(boardBorderColor); - BoardGestureGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DisabledGestureIcon); - BoardGestureGeometry2.Geometry = Geometry.Parse("F0 M24,24z M0,0z"); + BoardGesture.IconGeometryDrawing.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DisabledGestureIcon); + BoardGesture.IconGeometryDrawing2.Geometry = Geometry.Parse("F0 M24,24z M0,0z"); } } } diff --git a/InkCanvas.Controls/BoardToolbarButton.xaml b/InkCanvas.Controls/BoardToolbarButton.xaml index e120b1d1..0db8a3f1 100644 --- a/InkCanvas.Controls/BoardToolbarButton.xaml +++ b/InkCanvas.Controls/BoardToolbarButton.xaml @@ -25,6 +25,8 @@ + diff --git a/InkCanvas.Controls/BoardToolbarButton.xaml.cs b/InkCanvas.Controls/BoardToolbarButton.xaml.cs index 7c715dc4..64ad4ac1 100644 --- a/InkCanvas.Controls/BoardToolbarButton.xaml.cs +++ b/InkCanvas.Controls/BoardToolbarButton.xaml.cs @@ -90,6 +90,7 @@ namespace Ink_Canvas.Controls public event MouseButtonEventHandler ButtonMouseUp; public GeometryDrawing IconGeometryDrawing => IconGeometryInternal; + public GeometryDrawing IconGeometryDrawing2 => IconGeometryInternal2; public Border ButtonBorderControl => ButtonBorder; From fdf3633d8c16edbd4816e38ed36c82278e272ba9 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Fri, 17 Apr 2026 02:05:20 +0800 Subject: [PATCH 067/205] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index b54e5346..497fdb6c 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -4675,7 +4675,6 @@ Position="First" Label="{i18n:I18n Key=Board_Gesture}" IconGeometry="F0 M24,24z M0,0z M7.82154,10.0753L7.82154,3.74613C7.82154,3.06603 8.08946,2.40655 8.57377,1.92224 9.05808,1.43793 9.70726,1.17001 10.3977,1.17001 11.0881,1.17001 11.7372,1.43793 12.2216,1.92224 12.7059,2.40655 12.9738,3.05573 12.9738,3.74613L12.9738,6.37308C13.1415,6.33947 13.3139,6.32225 13.489,6.32225 14.1794,6.32225 14.8286,6.59016 15.3129,7.07447 15.4484,7.21001 15.567,7.35845 15.6675,7.5171 15.9551,7.40916 16.2634,7.35269 16.5803,7.35269 17.2707,7.35269 17.9199,7.62061 18.4042,8.10492 18.5461,8.24683 18.6695,8.4029 18.7729,8.57001 19.6856,8.26338 20.7674,8.45871 21.4647,9.15599 21.949,9.6403 22.2169,10.2998 22.2169,10.9799L22.2169,15.6169C22.2169,17.5438 21.4647,19.3574 20.1045,20.7176 18.7443,22.0778 16.9307,22.83 15.0038,22.83L13.149,22.83 13.1799,22.8094 12.8398,22.8094C11.7682,22.7579 10.7068,22.4694 9.75878,21.9541 8.70773,21.3874 7.81124,20.563 7.15175,19.5738L6.94566,19.2647C6.60562,18.7494 5.49273,16.8019 3.52458,13.3087 3.19484,12.7213 3.1021,12.0412 3.27727,11.3818 3.45245,10.7326 3.86463,10.1761 4.44168,9.83608 5.00842,9.49604 5.66791,9.35177 6.31709,9.43421 6.86548,9.50385 7.39181,9.7279 7.82154,10.0753z M10.037,3.38547C10.1297,3.28243 10.2637,3.23091 10.3977,3.23091 10.5316,3.23091 10.6656,3.29273 10.7583,3.38547 10.8614,3.47821 10.9129,3.61217 10.9129,3.74613L10.9129,11.4745C10.9129,12.0412 11.3766,12.5049 11.9433,12.5049 12.5101,12.5049 12.9738,12.0412 12.9738,11.4745L12.9738,8.89836C12.9738,8.7644 13.0356,8.63045 13.1283,8.53771 13.2211,8.43466 13.355,8.38314 13.489,8.38314 13.623,8.38314 13.7569,8.44497 13.8497,8.53771 13.9527,8.63045 14.0042,8.7644 14.0042,8.89836L14.0042,11.4745C14.0042,12.0412 14.4679,12.5049 15.0347,12.5049 15.6014,12.5049 16.0651,12.0412 16.0651,11.4745L16.0651,9.92881C16.0651,9.79485 16.1269,9.66089 16.2197,9.56815 16.3124,9.46511 16.4464,9.41359 16.5803,9.41359 16.7143,9.41359 16.8483,9.47541 16.941,9.56815 17.044,9.66089 17.0956,9.79485 17.0956,9.92881L17.0956,10.5869C17.0752,10.7163 17.0646,10.8477 17.0646,10.9799 17.0646,11.0661 17.0754,11.1499 17.0956,11.2301L17.0956,11.4745C17.0956,12.0412 17.5593,12.5049 18.126,12.5049 18.6928,12.5049 19.1565,12.0412 19.1565,11.4745L19.1565,10.8128C19.1834,10.7399 19.2266,10.6727 19.2801,10.6192 19.4759,10.4234 19.8159,10.4234 20.0117,10.6192 20.1148,10.712 20.1663,10.8459 20.1663,10.9799L20.1663,15.6169C20.1663,16.9977 19.6408,18.296 18.6618,19.2647 17.6829,20.2333 16.3949,20.7691 15.0141,20.7691L13.1593,20.7691C12.3143,20.7691 11.4796,20.5527 10.7274,20.1509 9.98548,19.749 9.3363,19.1616 8.8726,18.4506L8.66651,18.1415C8.35737,17.6675 7.23419,15.7096 5.31756,12.2988 5.24543,12.1752 5.23512,12.0412 5.26604,11.9073 5.30725,11.7733 5.38969,11.6703 5.50304,11.5981 5.66791,11.4951 5.874,11.4539 6.06978,11.4745 6.26557,11.5054 6.45105,11.5878 6.59531,11.7321L8.11007,13.2469C8.49419,13.631 9.10425,13.648 9.50833,13.2978 9.73651,13.1084 9.88244,12.8229 9.88244,12.5049L9.88244,3.74613C9.88244,3.61217,9.94426,3.47821,10.037,3.38547z M2.99905,6.31195L1.78313,4.65293 2.61779,4.04497C3.46275,3.4267,4.37985,2.89087,5.33817,2.46838L6.27587,2.0459 7.12084,3.93162 6.18313,4.3541C5.35878,4.72506,4.56533,5.17846,3.83372,5.71429L2.99905,6.32225 2.99905,6.31195z M18.2806,5.20935L19.1565,5.75549 20.259,4.01404 19.3831,3.4679C18.1157,2.67446,16.7452,2.0768,15.3026,1.68523L14.303,1.41731 13.7672,3.40607 14.7667,3.67399C16.0033,4.00373,17.1883,4.51895,18.2806,5.20935z" - ButtonMouseDown="Border_MouseDown" ButtonMouseUp="TwoFingerGestureBorder_MouseUp" /> @@ -4837,7 +4836,6 @@ Position="Last" Label="{i18n:I18n Key=Board_Background}" IconGeometry="F0 M24,24z M0,0z M4.71815,3.98345C6.64142,2.23541 9.19629,1.17001 12,1.17001 17.9812,1.17001 22.83,6.01877 22.83,12 22.83,17.9813 17.9812,22.83 12,22.83 11.6262,22.83 11.2568,22.8111 10.8927,22.7741 5.8167,22.2586 1.77699,18.2377 1.2325,13.1703 1.22536,13.1039 1.21882,13.0373 1.21289,12.9705 1.20871,12.9234 1.20483,12.8762 1.20125,12.8289 1.18054,12.5553 1.17,12.2789 1.17,12 1.17,9.41057 2.07878,7.03339 3.59479,5.17001 3.9391,4.74681 4.31473,4.35011 4.71815,3.98345z M12,20.83C16.8767,20.83 20.83,16.8767 20.83,12 20.83,7.12334 16.8767,3.17001 12,3.17001L12,20.83z" - ButtonMouseDown="Border_MouseDown" ButtonMouseUp="BoardChangeBackgroundColorBtn_MouseUp" /> @@ -4848,13 +4846,10 @@ Position="First" Label="{i18n:I18n Key=Board_Select}" IconGeometry="F1 M24,24z M0,0z M22.7989,10.1653L1.14304,1.14304 10.1653,22.7989 12.8305,14.9518 19.6892,21.8105 21.8105,19.6892 14.9518,12.8305 22.7989,10.1653z" - ButtonMouseDown="Border_MouseDown" ButtonMouseUp="SymbolIconSelect_MouseUp" /> @@ -5203,10 +5198,8 @@ @@ -5417,10 +5410,8 @@ @@ -5662,18 +5651,15 @@ From c38829f7d8a8eb4e7e47f489bff3fe22541fe4d1 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Fri, 17 Apr 2026 06:23:49 +0800 Subject: [PATCH 068/205] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml | 55 +++++++------------------------------- 1 file changed, 10 insertions(+), 45 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 497fdb6c..7313c3e2 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -5666,28 +5666,11 @@ - - - - - - - - - - - - - - - - + @@ -5750,29 +5733,11 @@ - - - - - - - - - - - - - - - - + From c4b5be783eb0543491ba3863df7fd29095e81fc0 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Fri, 17 Apr 2026 06:39:27 +0800 Subject: [PATCH 069/205] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml | 155 ++++--------------- Ink Canvas/MainWindow.xaml.cs | 8 +- Ink Canvas/MainWindow_cs/MW_BoardControls.cs | 34 ++-- 3 files changed, 50 insertions(+), 147 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 7313c3e2..5d5920df 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -4504,32 +4504,12 @@ - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - + diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index 696efd02..fcc309a3 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -1308,12 +1308,12 @@ namespace Ink_Canvas BlackBoardLeftSidePageListView.ItemsSource = blackBoardSidePageListViewObservableCollection; BlackBoardRightSidePageListView.ItemsSource = blackBoardSidePageListViewObservableCollection; - BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = + BtnLeftWhiteBoardSwitchPrevious.IconGeometryDrawing.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27)); - BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 0.5; - BtnRightWhiteBoardSwitchPreviousGeometry.Brush = + BtnLeftWhiteBoardSwitchPrevious.LabelTextBlockControl.Opacity = 0.5; + BtnRightWhiteBoardSwitchPrevious.IconGeometryDrawing.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27)); - BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 0.5; + BtnRightWhiteBoardSwitchPrevious.LabelTextBlockControl.Opacity = 0.5; // 应用颜色主题,这将考虑自定义背景色 CheckColorTheme(true); diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs index e5f6f717..b591a79f 100644 --- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs @@ -426,7 +426,7 @@ namespace Ink_Canvas /// /// 该方法在切换前会取消当前选中元素(同时保留并恢复编辑模式)、调用视频呈现器的离开页前钩子、保存当前页的笔迹与元素、清空画布;切换到前一页后恢复该页内容、调用视频呈现器的页已更改钩子并刷新页面索引显示。 /// - private void BtnWhiteBoardSwitchPrevious_Click(object sender, EventArgs e) + private void BtnWhiteBoardSwitchPrevious_Click(object sender, MouseButtonEventArgs e) { if (CurrentWhiteboardIndex <= 1) return; @@ -458,7 +458,7 @@ namespace Ink_Canvas /// /// 触发事件的源对象(通常为按钮)。 /// 事件参数。 - private void BtnWhiteBoardSwitchNext_Click(object sender, EventArgs e) + private void BtnWhiteBoardSwitchNext_Click(object sender, MouseButtonEventArgs e) { if (CurrentWhiteboardIndex < WhiteboardTotalCount && Settings.Automation.IsAutoSaveStrokesAtClear && @@ -505,7 +505,7 @@ namespace Ink_Canvas /// - 将当前页面的历史保存到时间轴并清空画布,然后在白板集合中插入一个空白页面(其历史为 null),随后恢复该页面并触发页面变更回调。 /// - 更新页码显示并在达到上限时禁用添加按钮;若侧边页列表可见,则刷新该列表。 /// - private void BtnWhiteBoardAdd_Click(object sender, EventArgs e) + private void BtnWhiteBoardAdd_Click(object sender, MouseButtonEventArgs e) { if (WhiteboardTotalCount >= 99) return; if (Settings.Automation.IsAutoSaveStrokesAtClear && @@ -652,8 +652,8 @@ namespace Ink_Canvas bool isMaxPage = WhiteboardTotalCount >= 99; // 设置按钮文本 - BtnLeftWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页"; - BtnRightWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页"; + BtnLeftWhiteBoardSwitchNext.LabelTextBlockControl.Text = isLastPage ? "新页面" : "下一页"; + BtnRightWhiteBoardSwitchNext.LabelTextBlockControl.Text = isLastPage ? "新页面" : "下一页"; if (isLastPage) { @@ -670,11 +670,11 @@ namespace Ink_Canvas // 设置下一页按钮颜色 if (iconForegroundBrush != null) { - BtnLeftWhiteBoardSwitchNextGeometry.Brush = iconForegroundBrush; - BtnRightWhiteBoardSwitchNextGeometry.Brush = iconForegroundBrush; + BtnLeftWhiteBoardSwitchNext.IconGeometryDrawing.Brush = iconForegroundBrush; + BtnRightWhiteBoardSwitchNext.IconGeometryDrawing.Brush = iconForegroundBrush; } - BtnLeftWhiteBoardSwitchNextLabel.Opacity = 1; - BtnRightWhiteBoardSwitchNextLabel.Opacity = 1; + BtnLeftWhiteBoardSwitchNext.LabelTextBlockControl.Opacity = 1; + BtnRightWhiteBoardSwitchNext.LabelTextBlockControl.Opacity = 1; BtnWhiteBoardSwitchPrevious.IsEnabled = true; @@ -684,21 +684,21 @@ namespace Ink_Canvas if (iconForegroundBrush != null) { var disabledBrush = new SolidColorBrush(Color.FromArgb(127, iconForegroundBrush.Color.R, iconForegroundBrush.Color.G, iconForegroundBrush.Color.B)); - BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = disabledBrush; - BtnRightWhiteBoardSwitchPreviousGeometry.Brush = disabledBrush; + BtnLeftWhiteBoardSwitchPrevious.IconGeometryDrawing.Brush = disabledBrush; + BtnRightWhiteBoardSwitchPrevious.IconGeometryDrawing.Brush = disabledBrush; } - BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 0.5; - BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 0.5; + BtnLeftWhiteBoardSwitchPrevious.LabelTextBlockControl.Opacity = 0.5; + BtnRightWhiteBoardSwitchPrevious.LabelTextBlockControl.Opacity = 0.5; } else { if (iconForegroundBrush != null) { - BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = iconForegroundBrush; - BtnRightWhiteBoardSwitchPreviousGeometry.Brush = iconForegroundBrush; + BtnLeftWhiteBoardSwitchPrevious.IconGeometryDrawing.Brush = iconForegroundBrush; + BtnRightWhiteBoardSwitchPrevious.IconGeometryDrawing.Brush = iconForegroundBrush; } - BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 1; - BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 1; + BtnLeftWhiteBoardSwitchPrevious.LabelTextBlockControl.Opacity = 1; + BtnRightWhiteBoardSwitchPrevious.LabelTextBlockControl.Opacity = 1; } BtnWhiteBoardDelete.IsEnabled = WhiteboardTotalCount != 1; From 7003bb84267e9df2dbcb0664c89f3b7d029d648e Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Fri, 17 Apr 2026 09:26:29 +0800 Subject: [PATCH 070/205] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml | 10 +++++----- Ink Canvas/MainWindow_cs/MW_BoardControls.cs | 16 +++++++++++++--- Ink Canvas/MainWindow_cs/MW_Screenshot.cs | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 5d5920df..961a8552 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -4509,7 +4509,7 @@ Label="上一页" IconGeometry="F1 M24,24z M0,0z M7.40091,10.456L14.5033,3.35357 12.3198,1.17001 1.48978,12 12.3198,22.83 14.5033,20.6465 7.40089,13.544 22.5102,13.544 22.5102,10.456 7.40091,10.456z" IsEnabledBinding="{Binding ElementName=BtnWhiteBoardSwitchPrevious, Path=IsEnabled}" - ButtonMouseUp="BtnWhiteBoardSwitchPrevious_Click" /> + ButtonMouseUp="BoardBtnWhiteBoardSwitchPrevious_MouseUp" /> + ButtonMouseUp="BoardBtnWhiteBoardSwitchNext_MouseUp" /> + ButtonMouseUp="BoardBtnWhiteBoardAdd_MouseUp" /> + ButtonMouseUp="BoardBtnWhiteBoardSwitchPrevious_MouseUp" /> + ButtonMouseUp="BoardBtnWhiteBoardSwitchNext_MouseUp" /> diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs index b591a79f..337bcc44 100644 --- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Ink; +using System.Windows.Input; using System.Windows.Media; using System.Windows.Threading; @@ -426,7 +427,10 @@ namespace Ink_Canvas /// /// 该方法在切换前会取消当前选中元素(同时保留并恢复编辑模式)、调用视频呈现器的离开页前钩子、保存当前页的笔迹与元素、清空画布;切换到前一页后恢复该页内容、调用视频呈现器的页已更改钩子并刷新页面索引显示。 /// - private void BtnWhiteBoardSwitchPrevious_Click(object sender, MouseButtonEventArgs e) + private void BoardBtnWhiteBoardSwitchPrevious_MouseUp(object sender, MouseButtonEventArgs e) + => BtnWhiteBoardSwitchPrevious_Click(sender, e); + + private void BtnWhiteBoardSwitchPrevious_Click(object sender, RoutedEventArgs e) { if (CurrentWhiteboardIndex <= 1) return; @@ -458,7 +462,10 @@ namespace Ink_Canvas /// /// 触发事件的源对象(通常为按钮)。 /// 事件参数。 - private void BtnWhiteBoardSwitchNext_Click(object sender, MouseButtonEventArgs e) + private void BoardBtnWhiteBoardSwitchNext_MouseUp(object sender, MouseButtonEventArgs e) + => BtnWhiteBoardSwitchNext_Click(sender, e); + + private void BtnWhiteBoardSwitchNext_Click(object sender, RoutedEventArgs e) { if (CurrentWhiteboardIndex < WhiteboardTotalCount && Settings.Automation.IsAutoSaveStrokesAtClear && @@ -505,7 +512,10 @@ namespace Ink_Canvas /// - 将当前页面的历史保存到时间轴并清空画布,然后在白板集合中插入一个空白页面(其历史为 null),随后恢复该页面并触发页面变更回调。 /// - 更新页码显示并在达到上限时禁用添加按钮;若侧边页列表可见,则刷新该列表。 /// - private void BtnWhiteBoardAdd_Click(object sender, MouseButtonEventArgs e) + private void BoardBtnWhiteBoardAdd_MouseUp(object sender, MouseButtonEventArgs e) + => BtnWhiteBoardAdd_Click(sender, e); + + private void BtnWhiteBoardAdd_Click(object sender, RoutedEventArgs e) { if (WhiteboardTotalCount >= 99) return; if (Settings.Automation.IsAutoSaveStrokesAtClear && diff --git a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs index 16e36382..3e083cbe 100644 --- a/Ink Canvas/MainWindow_cs/MW_Screenshot.cs +++ b/Ink Canvas/MainWindow_cs/MW_Screenshot.cs @@ -353,7 +353,7 @@ namespace Ink_Canvas await Task.Delay(150); } - BtnWhiteBoardAdd_Click(null, EventArgs.Empty); + BtnWhiteBoardAdd_Click(null, new RoutedEventArgs()); await InsertBitmapSourceToCanvas(bitmapSourceForClipboard); } From 231b850f747e5918277e8a425cfbea174cbc1e58 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Fri, 17 Apr 2026 13:14:23 +0800 Subject: [PATCH 071/205] =?UTF-8?q?fix:=E7=AA=97=E5=8F=A3=E7=BD=AE?= =?UTF-8?q?=E9=A1=B6=E5=BC=82=E5=B8=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml.cs | 3 + .../MainWindow_cs/MW_FloatingBarIcons.cs | 20 +++-- Ink Canvas/MainWindow_cs/MW_PPT.cs | 42 ++++++++-- Ink Canvas/Windows/RandWindow.xaml.cs | 4 +- .../Windows/YesOrNoNotificationWindow.xaml.cs | 78 ++++++++++++++++++- 5 files changed, 134 insertions(+), 13 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index fcc309a3..00d7e870 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -2896,6 +2896,7 @@ namespace Ink_Canvas BorderSettings.Visibility = Visibility.Hidden; BorderSettingsMask.Visibility = Visibility.Hidden; var win = new HistoryRollbackWindow(Settings.Startup.UpdateChannel); + win.Owner = this; win.ShowDialog(); // 可选:回滚窗口关闭后恢复设置面板显示 BorderSettings.Visibility = Visibility.Visible; @@ -3349,6 +3350,7 @@ namespace Ink_Canvas // 创建快捷键设置窗口 var hotkeySettingsWindow = new HotkeySettingsWindow(this, _globalHotkeyManager); + hotkeySettingsWindow.Owner = this; // 设置窗口关闭事件,用于在快捷键设置窗口关闭后恢复设置面板 hotkeySettingsWindow.Closed += (s, e) => @@ -4692,6 +4694,7 @@ namespace Ink_Canvas return; var quickDrawWindow = new QuickDrawWindow(); + quickDrawWindow.Owner = this; quickDrawWindow.ShowDialog(); } catch (Exception ex) diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index e51bcfb7..6c7c049f 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -1215,7 +1215,9 @@ namespace Ink_Canvas if (Settings.RandSettings.UseNewRollCallUI) { // 使用新点名UI - 随机抽模式 - new NewStyleRollCallWindow(Settings, false).ShowDialog(); + var rollCallWindow = new NewStyleRollCallWindow(Settings, false); + rollCallWindow.Owner = this; + rollCallWindow.ShowDialog(); } else { @@ -1378,11 +1380,15 @@ namespace Ink_Canvas // 调用失败时回退到相应的点名窗口 if (Settings.RandSettings.UseNewRollCallUI) { - new NewStyleRollCallWindow(Settings, true).ShowDialog(); // 单次抽模式 + var rollCallWindow = new NewStyleRollCallWindow(Settings, true); // 单次抽模式 + rollCallWindow.Owner = this; + rollCallWindow.ShowDialog(); } else { - new RandWindow(Settings, true).ShowDialog(); + var randWindow = new RandWindow(Settings, true); + randWindow.Owner = this; + randWindow.ShowDialog(); } } } @@ -1392,12 +1398,16 @@ namespace Ink_Canvas if (Settings.RandSettings.UseNewRollCallUI) { // 使用新点名UI - 单次抽模式 - new NewStyleRollCallWindow(Settings, true).ShowDialog(); + var rollCallWindow = new NewStyleRollCallWindow(Settings, true); + rollCallWindow.Owner = this; + rollCallWindow.ShowDialog(); } else { // 使用默认的随机点名窗口 - new RandWindow(Settings, true).ShowDialog(); + var randWindow = new RandWindow(Settings, true); + randWindow.Owner = this; + randWindow.ShowDialog(); } } } diff --git a/Ink Canvas/MainWindow_cs/MW_PPT.cs b/Ink Canvas/MainWindow_cs/MW_PPT.cs index e9fd850f..1437027e 100644 --- a/Ink Canvas/MainWindow_cs/MW_PPT.cs +++ b/Ink Canvas/MainWindow_cs/MW_PPT.cs @@ -1652,7 +1652,7 @@ namespace Ink_Canvas if (int.TryParse(File.ReadAllText(positionFile), out var page) && page > 0) { _lastPlaybackPage = page; - new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () => + var yesNoWindow = new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () => { try { @@ -1674,7 +1674,17 @@ namespace Ink_Canvas { LogHelper.WriteLogToFile($"跳转到第{page}页失败: {ex}", LogHelper.LogType.Error); } - }).ShowDialog(); + }); + yesNoWindow.Owner = this; + PauseTopmostMaintenance(); + try + { + yesNoWindow.ShowDialog(); + } + finally + { + ResumeTopmostMaintenance(); + } } } catch (Exception ex) @@ -1716,7 +1726,7 @@ namespace Ink_Canvas if (hasHiddenSlides && !IsShowingRestoreHiddenSlidesWindow) { IsShowingRestoreHiddenSlidesWindow = true; - new YesOrNoNotificationWindow("检测到此演示文档中包含隐藏的幻灯片,是否取消隐藏?", + var yesNoWindow = new YesOrNoNotificationWindow("检测到此演示文档中包含隐藏的幻灯片,是否取消隐藏?", () => { try @@ -1740,7 +1750,17 @@ namespace Ink_Canvas } }, () => { IsShowingRestoreHiddenSlidesWindow = false; }, - () => { IsShowingRestoreHiddenSlidesWindow = false; }).ShowDialog(); + () => { IsShowingRestoreHiddenSlidesWindow = false; }); + yesNoWindow.Owner = this; + PauseTopmostMaintenance(); + try + { + yesNoWindow.ShowDialog(); + } + finally + { + ResumeTopmostMaintenance(); + } } } catch (Exception ex) @@ -1786,7 +1806,7 @@ namespace Ink_Canvas if (hasSlideTimings && !IsShowingAutoplaySlidesWindow) { IsShowingAutoplaySlidesWindow = true; - new YesOrNoNotificationWindow("检测到此演示文档中自动播放或排练计时已经启用,可能导致幻灯片自动翻页,是否取消?", + var yesNoWindow = new YesOrNoNotificationWindow("检测到此演示文档中自动播放或排练计时已经启用,可能导致幻灯片自动翻页,是否取消?", () => { try @@ -1806,7 +1826,17 @@ namespace Ink_Canvas } }, () => { IsShowingAutoplaySlidesWindow = false; }, - () => { IsShowingAutoplaySlidesWindow = false; }).ShowDialog(); + () => { IsShowingAutoplaySlidesWindow = false; }); + yesNoWindow.Owner = this; + PauseTopmostMaintenance(); + try + { + yesNoWindow.ShowDialog(); + } + finally + { + ResumeTopmostMaintenance(); + } } } catch (Exception ex) diff --git a/Ink Canvas/Windows/RandWindow.xaml.cs b/Ink Canvas/Windows/RandWindow.xaml.cs index b49540fc..faa9a39f 100644 --- a/Ink Canvas/Windows/RandWindow.xaml.cs +++ b/Ink Canvas/Windows/RandWindow.xaml.cs @@ -401,7 +401,9 @@ namespace Ink_Canvas if (!ok) return; } - new NamesInputWindow().ShowDialog(); + var namesInputWindow = new NamesInputWindow(); + namesInputWindow.Owner = this; + namesInputWindow.ShowDialog(); Window_Loaded(this, null); } diff --git a/Ink Canvas/Windows/YesOrNoNotificationWindow.xaml.cs b/Ink Canvas/Windows/YesOrNoNotificationWindow.xaml.cs index 241884b9..533ec9d9 100644 --- a/Ink Canvas/Windows/YesOrNoNotificationWindow.xaml.cs +++ b/Ink Canvas/Windows/YesOrNoNotificationWindow.xaml.cs @@ -1,5 +1,8 @@ -using System; +using System; +using System.Runtime.InteropServices; using System.Windows; +using System.Windows.Interop; +using System.Windows.Threading; namespace Ink_Canvas { @@ -8,9 +11,26 @@ namespace Ink_Canvas /// public partial class YesOrNoNotificationWindow : Window { + [DllImport("user32.dll")] + private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); + + [DllImport("user32.dll")] + private static extern IntPtr GetForegroundWindow(); + + [DllImport("user32.dll")] + private static extern bool IsWindowVisible(IntPtr hWnd); + + private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1); + private const uint SWP_NOMOVE = 0x0002; + private const uint SWP_NOSIZE = 0x0001; + private const uint SWP_SHOWWINDOW = 0x0040; + private const uint SWP_NOOWNERZORDER = 0x0200; + private readonly Action _yesAction; private readonly Action _noAction; private readonly Action _windowClose; + private DispatcherTimer _topmostCheckTimer; + private IntPtr _hwnd; public YesOrNoNotificationWindow(string text, Action yesAction = null, Action noAction = null, Action windowClose = null) { @@ -19,6 +39,62 @@ namespace Ink_Canvas _windowClose = windowClose; InitializeComponent(); Label.Text = text; + + Loaded += YesOrNoNotificationWindow_Loaded; + Closed += YesOrNoNotificationWindow_Closed; + } + + private void YesOrNoNotificationWindow_Loaded(object sender, RoutedEventArgs e) + { + _hwnd = new WindowInteropHelper(this).Handle; + + Topmost = true; + Activate(); + Focus(); + + if (_hwnd != IntPtr.Zero) + { + SetWindowPos(_hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + } + + _topmostCheckTimer = new DispatcherTimer(); + _topmostCheckTimer.Interval = TimeSpan.FromMilliseconds(100); + _topmostCheckTimer.Tick += TopmostCheckTimer_Tick; + _topmostCheckTimer.Start(); + } + + private void TopmostCheckTimer_Tick(object sender, EventArgs e) + { + try + { + if (_hwnd == IntPtr.Zero) return; + + var foregroundWindow = GetForegroundWindow(); + + if (foregroundWindow != _hwnd && IsWindowVisible(_hwnd)) + { + Topmost = true; + Activate(); + Focus(); + + SetWindowPos(_hwnd, HWND_TOPMOST, 0, 0, 0, 0, + SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER); + } + } + catch (Exception) + { + } + } + + private void YesOrNoNotificationWindow_Closed(object sender, EventArgs e) + { + if (_topmostCheckTimer != null) + { + _topmostCheckTimer.Stop(); + _topmostCheckTimer.Tick -= TopmostCheckTimer_Tick; + _topmostCheckTimer = null; + } } private void ButtonYes_Click(object sender, RoutedEventArgs e) From 8b5797ac66b931c312f13bbf090086391a58a983 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Fri, 17 Apr 2026 13:41:18 +0800 Subject: [PATCH 072/205] =?UTF-8?q?fix:=E8=83=8C=E6=99=AF=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE=E8=8F=9C=E5=8D=95=E6=97=A0=E6=B3=95=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml | 142 +++++ Ink Canvas/MainWindow_cs/MW_BoardIcons.cs | 683 ++++------------------ 2 files changed, 266 insertions(+), 559 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 961a8552..666b573d 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -4798,6 +4798,148 @@ Label="{i18n:I18n Key=Board_Background}" IconGeometry="F0 M24,24z M0,0z M4.71815,3.98345C6.64142,2.23541 9.19629,1.17001 12,1.17001 17.9812,1.17001 22.83,6.01877 22.83,12 22.83,17.9813 17.9812,22.83 12,22.83 11.6262,22.83 11.2568,22.8111 10.8927,22.7741 5.8167,22.2586 1.77699,18.2377 1.2325,13.1703 1.22536,13.1039 1.21882,13.0373 1.21289,12.9705 1.20871,12.9234 1.20483,12.8762 1.20125,12.8289 1.18054,12.5553 1.17,12.2789 1.17,12 1.17,9.41057 2.07878,7.03339 3.59479,5.17001 3.9391,4.74681 4.31473,4.35011 4.71815,3.98345z M12,20.83C16.8767,20.83 20.83,16.8767 20.83,12 20.83,7.12334 16.8767,3.17001 12,3.17001L12,20.83z" ButtonMouseUp="BoardChangeBackgroundColorBtn_MouseUp" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - _boothResolutionWidth; - public int BoothResolutionHeight => _boothResolutionHeight; + private static Cursor _cachedPenCursor = null; private static readonly object _cursorLock = new object(); @@ -1726,24 +1723,6 @@ namespace Ink_Canvas { SystemEvents.DisplaySettingsChanged -= SystemEventsOnDisplaySettingsChanged; - try - { - // 清理视频展台资源 - if (_cameraService != null) - { - _cameraService.FrameReceived -= CameraService_FrameReceived; - _cameraService.ErrorOccurred -= CameraService_ErrorOccurred; - _cameraService.Dispose(); - _cameraService = null; - } - lock (_videoPresenterFrameLock) - { - _lastFrame?.Dispose(); - _lastFrame = null; - } - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - // 释放PPT管理器资源 DisposePPTManagers(); @@ -2969,62 +2948,6 @@ namespace Ink_Canvas } #endregion - #region 展台/白板分辨率切换 - private const int BoothResolutionTabCount = 4; - private static readonly (int w, int h)[] BoothResolutionValues = { (1280, 720), (1920, 1080), (2560, 1440), (3840, 2160) }; - - private void BoothResolutionTab_Click(object sender, RoutedEventArgs e) - { - if (sender is Button btn && btn.Tag is string tag) - { - var parts = tag.Split(','); - if (parts.Length == 2 && int.TryParse(parts[0].Trim(), out int w) && int.TryParse(parts[1].Trim(), out int h) && w > 0 && h > 0) - { - _boothResolutionWidth = w; - _boothResolutionHeight = h; - UpdateBoothResolutionTabState(); - SyncBoothResolutionToCameraService(); - } - } - } - - private void UpdateBoothResolutionTabState() - { - int index = 0; - for (int i = 0; i < BoothResolutionValues.Length; i++) - { - if (BoothResolutionValues[i].w == _boothResolutionWidth && BoothResolutionValues[i].h == _boothResolutionHeight) - { - index = i; - break; - } - } - - if (BoothResolutionTabIndicator != null) - { - BoothResolutionTabIndicator.Margin = new Thickness(index * 70, 0, 0, 0); - } - - var texts = new[] { BtnBoothResolution720?.Content as TextBlock, BtnBoothResolution1080?.Content as TextBlock, BtnBoothResolution2K?.Content as TextBlock, BtnBoothResolution4K?.Content as TextBlock }; - for (int i = 0; i < texts.Length && i < 4; i++) - { - if (texts[i] == null) continue; - if (i == index) - { - texts[i].FontWeight = FontWeights.Bold; - texts[i].Foreground = new SolidColorBrush(Colors.White); - texts[i].Opacity = 1.0; - } - else - { - texts[i].FontWeight = FontWeights.SemiBold; - texts[i].SetResourceReference(TextBlock.ForegroundProperty, "FloatBarForeground"); - texts[i].Opacity = 0.7; - } - } - } - #endregion - private void ToggleSwitchEnableInkToShape_Toggled(object sender, RoutedEventArgs e) { diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs index 337bcc44..26107e94 100644 --- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs @@ -54,7 +54,7 @@ namespace Ink_Canvas /// /// 为 true 时将导出结果保存到主备份槽(索引 0);为 false 时保存到当前白板索引。 /// - /// - 会提交画布上缺失于历史记录的 Image/MediaElement(但跳过 Tag 等于 VideoPresenterLiveFrameTag 的 Image)和缺失的墨迹; + /// - 会提交画布上缺失于历史记录的 Image/MediaElement 和缺失的墨迹; /// - 导出后把结果存入 TimeMachineHistories 的相应索引,并保存当前多指书写模式到 savedMultiTouchModeStates; /// - 导出后会清除时间机器的临时墨迹历史以释放内存。 /// - 此方法有副作用:修改 TimeMachineHistories、savedMultiTouchModeStates,并通过 timeMachine 的提交方法改变其内部历史状态。 @@ -85,10 +85,6 @@ namespace Ink_Canvas { if (child is Image || child is MediaElement || child is PdfEmbeddedView) { - if (child is Image img && img.Tag is string tag && tag == VideoPresenterLiveFrameTag) - { - continue; - } if (!elementsInHistory.Contains(child)) { timeMachine.CommitElementInsertHistory(child); @@ -445,14 +441,12 @@ namespace Ink_Canvas currentSelectedElement = null; } - VideoPresenter_BeforePageLeave(); SaveStrokes(); ClearStrokes(true); CurrentWhiteboardIndex--; RestoreStrokes(); - VideoPresenter_OnPageChanged(); UpdateIndexInfoDisplay(); } @@ -490,14 +484,12 @@ namespace Ink_Canvas currentSelectedElement = null; } - VideoPresenter_BeforePageLeave(); SaveStrokes(); ClearStrokes(true); CurrentWhiteboardIndex++; RestoreStrokes(); - VideoPresenter_OnPageChanged(); UpdateIndexInfoDisplay(); } @@ -533,7 +525,6 @@ namespace Ink_Canvas currentSelectedElement = null; } - VideoPresenter_BeforePageLeave(); SaveStrokes(); ClearStrokes(true); @@ -549,12 +540,9 @@ namespace Ink_Canvas } } - // 确保新页面的历史记录为空 TimeMachineHistories[CurrentWhiteboardIndex] = null; - // 恢复新页面(这会清空画布,因为历史记录为null) RestoreStrokes(); - VideoPresenter_OnPageChanged(); UpdateIndexInfoDisplay(); diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index 21019d38..68a91d6f 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -3391,7 +3391,6 @@ namespace Ink_Canvas switch (++currentMode % 2) { case 0: //屏幕模式 - VideoPresenter_OnExitWhiteboardMode(); currentMode = 0; GridBackgroundCover.Visibility = Visibility.Collapsed; AnimationsHelper.HideWithSlideAndFade(BlackboardLeftSide); diff --git a/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs b/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs index e276e9b5..a5f5c151 100644 --- a/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs +++ b/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs @@ -1,1052 +1,15 @@ -using AForge.Imaging; -using AForge.Imaging.Filters; -using AForge.Math.Geometry; using Ink_Canvas.Helpers; -using Ink_Canvas.Models; -using System; -using System.Collections.Generic; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; namespace Ink_Canvas { public partial class MainWindow : Window { - private static readonly SolidColorBrush BoothButtonHighlightBrush = new SolidColorBrush((System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#FF66CCFF")); - private bool _boothButtonPressHandlersAttached; - - // 标记:用于在保存/恢复白板内容时排除“展台实时上屏”画面 - private const string VideoPresenterLiveFrameTag = "__VideoPresenterLiveFrame"; - - private CameraService _cameraService; - private readonly object _videoPresenterFrameLock = new object(); - private Bitmap _lastFrame; - - private readonly List _capturedPhotos = new List(); - private const int MaxCapturedPhotos = 50; // 容量上限:比 UI 显示的 30 项多一些,避免频繁清理 - - // 按页绑定:每一页对应一个“实时画面”元素与布局/设备信息 - private readonly Dictionary _liveFrameImageByPage = new Dictionary(); - private readonly HashSet _liveEnabledPages = new HashSet(); - private readonly Dictionary _cameraIndexByPage = new Dictionary(); - private readonly Dictionary _liveFrameLayoutByPage = - new Dictionary(); - - private DateTime _lastCaptureTime = DateTime.MinValue; - private const int VideoPresenterCaptureCooldownMs = 1000; - - private const int CorrectedPaperHeight = 600; - - /// - /// 切换视频呈现侧边栏的显示状态(显示或隐藏)。 - /// - /// 触发事件的源对象。 - /// 鼠标按钮事件的参数。 - private void BtnToggleVideoPresenter_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) + private void BtnToggleVideoPresenter_Click(object sender, MouseButtonEventArgs e) { - if (Settings?.Canvas?.LaunchSeewoVideoShowcaseForWhiteboardBooth == true) - { - // 与主窗口「希沃视频展台」入口(BoardLaunchEasiCamera_MouseUp)一致:先走黑板/白板入口逻辑再启动 - ImageBlackboard_MouseUp(null, null); - SoftwareLauncher.LaunchEasiCamera("希沃视频展台"); - return; - } - - ToggleVideoPresenterSidebar(); - } - - /// - /// 切换视频演示侧栏的显示状态并在显示时初始化相关控件与状态。 - /// - /// - /// 当侧栏被显示时:确保摄像头服务已初始化、暂时禁用拍照按钮、刷新可用摄像头列表,并将“照片校正”和当前页面的“上屏(live on canvas)”开关同步为保存的设置或页面状态; - /// 当侧栏被隐藏时:将其折叠并停止进一步初始化操作。 - /// - private void ToggleVideoPresenterSidebar() - { - if (VideoPresenterSidebar == null) return; - - if (VideoPresenterSidebar.Visibility == Visibility.Visible) - { - VideoPresenterSidebar.Visibility = Visibility.Collapsed; - return; - } - - VideoPresenterSidebar.Visibility = Visibility.Visible; - EnsureCameraService(); - if (BtnCapturePhoto != null) BtnCapturePhoto.IsEnabled = false; - RefreshVideoPresenterDeviceList(); - UpdateBoothResolutionTabState(); - - if (ToggleBtnPhotoCorrection != null) - { - ToggleBtnPhotoCorrection.IsChecked = Settings?.Automation?.IsEnablePhotoCorrection ?? false; - } - - // 同步“上屏”按钮状态(按页绑定) - if (BtnToggleVideoPresenterLiveOnCanvas != null) - { - BtnToggleVideoPresenterLiveOnCanvas.IsChecked = _liveEnabledPages.Contains(GetCurrentPageIndex()); - } - - if (!_boothButtonPressHandlersAttached) - { - AttachBoothButtonPressHandlers(); - } - } - - private void AttachBoothButtonPressHandlers() - { - if (BtnCapturePhoto == null || BtnRotateImage == null) return; - BtnCapturePhoto.PreviewMouseDown += BoothButton_PreviewMouseDown; - BtnCapturePhoto.PreviewMouseUp += BoothButton_PreviewMouseUp; - BtnCapturePhoto.LostMouseCapture += BoothButton_PreviewMouseUp; - BtnRotateImage.PreviewMouseDown += BoothButton_PreviewMouseDown; - BtnRotateImage.PreviewMouseUp += BoothButton_PreviewMouseUp; - BtnRotateImage.LostMouseCapture += BoothButton_PreviewMouseUp; - _boothButtonPressHandlersAttached = true; - } - - private void BoothButton_PreviewMouseDown(object sender, MouseButtonEventArgs e) - { - if (sender is Control c) ApplyBoothButtonHighlight(c, true); - } - - private void BoothButton_PreviewMouseUp(object sender, EventArgs e) - { - if (sender is Control c) ApplyBoothButtonHighlight(c, false); - } - - private static void ApplyBoothButtonHighlight(Control control, bool highlight) - { - if (control == null) return; - if (highlight) - { - control.Background = BoothButtonHighlightBrush; - control.BorderBrush = BoothButtonHighlightBrush; - } - else - { - control.SetResourceReference(Control.BackgroundProperty, "FloatBarBackground"); - control.SetResourceReference(Control.BorderBrushProperty, "FloatBarBorderBrush"); - } - } - - /// - /// 关闭视频呈现侧边栏(将其可见性设为 Collapsed)。 - /// - private void BtnCloseVideoPresenter_Click(object sender, RoutedEventArgs e) - { - if (VideoPresenterSidebar != null) - { - VideoPresenterSidebar.Visibility = Visibility.Collapsed; - } - } - - private void CloseVideoPresenterSidebarAndReleaseResources() - { - if (VideoPresenterSidebar != null) - { - VideoPresenterSidebar.Visibility = Visibility.Collapsed; - } - - StopVideoPresenterPreviewAndFrameCache(clearPreviewImage: true); - } - - private void StopVideoPresenterPreviewAndFrameCache(bool clearPreviewImage) - { - if (BtnCapturePhoto != null) - { - BtnCapturePhoto.IsEnabled = false; - } - - if (clearPreviewImage && VideoPresenterPreviewImage != null) - { - VideoPresenterPreviewImage.Source = null; - } - - try { _cameraService?.StopPreview(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - lock (_videoPresenterFrameLock) - { - _lastFrame?.Dispose(); - _lastFrame = null; - } - } - - /// - /// 延迟初始化摄像头服务并订阅其帧和错误事件;如果服务已存在则不做任何操作。 - /// - private void EnsureCameraService() - { - if (_cameraService != null) return; - - _cameraService = new CameraService(); - _cameraService.FrameReceived += CameraService_FrameReceived; - _cameraService.ErrorOccurred += CameraService_ErrorOccurred; - SyncBoothResolutionToCameraService(); - } - - internal void SyncBoothResolutionToCameraService() - { - if (_cameraService == null) return; - _cameraService.ResolutionWidth = BoothResolutionWidth; - _cameraService.ResolutionHeight = BoothResolutionHeight; - } - - /// - /// 在相机服务发生错误时将错误信息写入错误日志文件。 - /// - /// 来自相机服务的错误描述,会被写入错误日志。 - private void CameraService_ErrorOccurred(object sender, string e) - { - try - { - LogHelper.WriteLogToFile($"视频展台摄像头错误: {e}", LogHelper.LogType.Error); - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - } - - /// - /// 处理来自摄像头的单帧图像,用于更新预览、缓存最新帧并刷新当前页的实时画面显示。 - /// - /// 来自摄像头的位图帧;为 null 时忽略。 - /// - /// 缓存该帧为最新帧,并通过 CameraService 提供的 BitmapSource 直接更新预览与实时上屏 - /// - private void CameraService_FrameReceived(object sender, Bitmap frame) - { - if (frame == null) return; - - try - { - Bitmap serviceCopy; - try - { - serviceCopy = (Bitmap)frame.Clone(); - } - catch - { - // 可能在下一帧到来时被 CameraService 释放,直接忽略这一帧 - return; - } - - lock (_videoPresenterFrameLock) - { - _lastFrame?.Dispose(); - _lastFrame = (Bitmap)serviceCopy.Clone(); - } - - var previewSource = _cameraService?.GetCurrentFrameAsBitmapSource(); - serviceCopy.Dispose(); - if (previewSource == null) return; - - Dispatcher.BeginInvoke(new Action(() => - { - if (VideoPresenterPreviewImage != null) - { - VideoPresenterPreviewImage.Source = previewSource; - } - - if (BtnCapturePhoto != null) - { - BtnCapturePhoto.IsEnabled = true; - } - - // 实时上屏:刷新当前页的画面元素 - TryUpdateLiveFrameOnCanvas(previewSource); - })); - } - catch - { - // 忽略预览刷新异常 - } - } - - /// - /// 获取当前白板页索引(确保返回值至少为 1)。 - /// - /// 当前白板页索引;如果内部索引小于 1,则返回 1。 - private int GetCurrentPageIndex() - { - return Math.Max(1, CurrentWhiteboardIndex); - } - - /// - /// 在当前白板页面(若已启用)将给定预览图像应用到页面上的实时摄像框元素,并确保该元素已添加到画布且可见。 - /// - /// - /// 如果当前页面未启用实时显示,或画布/对应图像元素不可用,则函数不执行任何操作。 - /// - private void TryUpdateLiveFrameOnCanvas(ImageSource preview) - { - try - { - if (preview == null) return; - - int page = GetCurrentPageIndex(); - if (!_liveEnabledPages.Contains(page)) return; - if (inkCanvas == null) return; - if (!_liveFrameImageByPage.TryGetValue(page, out var img) || img == null) return; - - if (!inkCanvas.Children.Contains(img)) - { - inkCanvas.Children.Add(img); - } - - img.Source = preview; - img.Visibility = Visibility.Visible; - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - } - - private const double VideoPresenterLiveFrameScreenRatio = 0.75; - - /// - /// 获取或创建并缓存用于指定白板页的实时视频帧 Image 元素。 - /// - /// 白板页索引(页面编号,用于在每页间区分并缓存元素)。 - /// 返回指定页对应的 Image 元素;若已存在则返回已缓存实例,否则创建新的 Image(根据画布大小设置默认宽高、标记为实时帧并初始化变换与交互绑定)并将其缓存后返回。 - private System.Windows.Controls.Image EnsureLiveFrameElementForPage(int page) - { - if (_liveFrameImageByPage.TryGetValue(page, out var existing) && existing != null) return existing; - - double canvasW = inkCanvas?.ActualWidth ?? 0; - double canvasH = inkCanvas?.ActualHeight ?? 0; - double w = canvasW > 10 && canvasH > 10 - ? canvasW * VideoPresenterLiveFrameScreenRatio - : 520; - double h = canvasW > 10 && canvasH > 10 - ? canvasH * VideoPresenterLiveFrameScreenRatio - : 390; - - var img = new System.Windows.Controls.Image - { - Tag = VideoPresenterLiveFrameTag, - Stretch = System.Windows.Media.Stretch.Uniform, - Width = w, - Height = h, - Visibility = Visibility.Visible, - Opacity = 1.0 - }; - try - { - InitializeElementTransform(img); - BindElementEvents(img); - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - - _liveFrameImageByPage[page] = img; - return img; - } - - /// - /// 将已保存的布局(或默认布局)应用到指定白板页面上的直播帧 Image 元素,设置其位置和尺寸并确保坐标有效。 - /// - /// 目标白板页面的索引。 - /// 要应用布局的 Image 元素;为 null 时不执行任何操作。 - /// - /// 如果存在为该页面保存的布局则使用其宽度和左/上坐标;否则将 Image 调整为画布尺寸的 75% 并居中。最终位置会限制为不小于 0 的坐标,且对无效计算结果使用合理的默认偏移。 - /// - private void ApplyLiveFrameLayoutForPage(int page, System.Windows.Controls.Image img) - { - if (img == null) return; - - if (_liveFrameLayoutByPage.TryGetValue(page, out var layout)) - { - if (!double.IsNaN(layout.width) && layout.width > 10) img.Width = layout.width; - InkCanvas.SetLeft(img, Math.Max(0, layout.left)); - InkCanvas.SetTop(img, Math.Max(0, layout.top)); - return; - } - - // 默认尺寸:画布宽高的 75%;位置居中 - double cw = inkCanvas?.ActualWidth ?? 0; - double ch = inkCanvas?.ActualHeight ?? 0; - if (cw > 10 && ch > 10) - { - img.Width = cw * VideoPresenterLiveFrameScreenRatio; - img.Height = ch * VideoPresenterLiveFrameScreenRatio; - } - double x = (inkCanvas?.ActualWidth ?? 0) / 2 - img.Width / 2; - double y = (inkCanvas?.ActualHeight ?? 0) / 2 - img.Height / 2; - if (double.IsNaN(x) || double.IsInfinity(x)) x = 100; - if (double.IsNaN(y) || double.IsInfinity(y)) y = 100; - InkCanvas.SetLeft(img, Math.Max(0, x)); - InkCanvas.SetTop(img, Math.Max(0, y)); - } - - /// - /// 刷新视频呈现器侧栏中的摄像头设备列表并在界面上显示可选项。 - /// - /// - /// 若未检测到摄像头,会在面板中显示提示文本;若存在设备,则为每个设备创建一个用于选择的单选按钮,选择某项会启动对应的摄像头预览。函数在列表生成后会尝试恢复并启动当前页面在 _cameraIndexByPage 中存储的摄像头索引,仅当没有保存的索引时才会选择并启动第一个可用设备。保存的每页选择优先于默认选择第一个设备。 - /// - private void RefreshVideoPresenterDeviceList() - { - if (_cameraService == null) return; - if (CameraDevicesStackPanel == null) return; - - _cameraService.RefreshCameraList(); - CameraDevicesStackPanel.Children.Clear(); - - if (_cameraService.AvailableCameras == null || _cameraService.AvailableCameras.Count == 0) - { - var tb = new TextBlock - { - Text = "未检测到摄像头设备", - FontSize = 12, - Margin = new Thickness(5), - HorizontalAlignment = HorizontalAlignment.Center - }; - tb.SetResourceReference(TextBlock.ForegroundProperty, "FloatBarForeground"); - CameraDevicesStackPanel.Children.Add(tb); - return; - } - - for (int i = 0; i < _cameraService.AvailableCameras.Count; i++) - { - int idx = i; - var dev = _cameraService.AvailableCameras[i]; - var rb = new RadioButton - { - Content = dev.Name, - Margin = new Thickness(0, 2, 0, 2), - FontSize = 12, - Tag = idx, - }; - rb.SetResourceReference(Control.ForegroundProperty, "FloatBarForeground"); - rb.Checked += (s, e) => StartVideoPresenterPreview(idx); - CameraDevicesStackPanel.Children.Add(rb); - } - - // 预选该页已保存的摄像头,否则使用第一个 - if (_cameraService.AvailableCameras.Count > 0) - { - int currentPage = GetCurrentPageIndex(); - int cameraToSelect = 0; - if (_cameraIndexByPage.TryGetValue(currentPage, out int savedIdx) && savedIdx >= 0 && savedIdx < _cameraService.AvailableCameras.Count) - { - cameraToSelect = savedIdx; - } - - if (cameraToSelect < CameraDevicesStackPanel.Children.Count && CameraDevicesStackPanel.Children[cameraToSelect] is RadioButton rb) - { - rb.IsChecked = true; - } - else - { - StartVideoPresenterPreview(cameraToSelect); - } - } - } - - /// - /// 为当前白板页开始指定摄像头的预览并保存该页的摄像头选择。 - /// - /// 要启动的摄像头在设备列表中的索引。 - /// 预览成功时会允许拍照按钮可用。 - private void StartVideoPresenterPreview(int cameraIndex) - { - try - { - EnsureCameraService(); - _cameraIndexByPage[GetCurrentPageIndex()] = cameraIndex; - if (_cameraService.StartPreview(cameraIndex)) - { - if (BtnCapturePhoto != null) BtnCapturePhoto.IsEnabled = true; - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启动视频展台预览失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 在当前白板页面启用“在画布上显示实时视频”功能并将对应的实时画面元素添加到画布上。 - /// - /// - /// 确保为当前页面创建并应用已保存的布局,将实时画面 Image 加入 inkCanvas(若尚未存在),并尝试切换编辑工具为选择模式。若侧栏预览已有帧,则立即用该预览刷新画布上的实时画面图像源。 - /// - private void BtnToggleVideoPresenterLiveOnCanvas_Checked(object sender, RoutedEventArgs e) - { - ApplyBoothButtonHighlight(BtnToggleVideoPresenterLiveOnCanvas, true); - int page = GetCurrentPageIndex(); - _liveEnabledPages.Add(page); - StartVideoPresenterPreviewForCurrentPageIfNeeded(); - - var img = EnsureLiveFrameElementForPage(page); - ApplyLiveFrameLayoutForPage(page, img); - - if (inkCanvas != null && !inkCanvas.Children.Contains(img)) - { - inkCanvas.Children.Add(img); - } - - try - { - SetCurrentToolMode(InkCanvasEditingMode.Select); - UpdateCurrentToolMode("select"); - HideSubPanels("select"); - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - - // 立即用侧栏预览刷新一次 - if (VideoPresenterPreviewImage?.Source is ImageSource src) - { - img.Source = src; - } - } - - /// - /// 在当前页面禁用画布上的实时视频覆盖并移除其视觉元素。 - /// - /// - /// 从记录已启用实时显示的集合中删除当前页面索引,并在存在对应的 Image 元素且已添加到 inkCanvas 时尝试将其移除。 - /// - private void BtnToggleVideoPresenterLiveOnCanvas_Unchecked(object sender, RoutedEventArgs e) - { - ApplyBoothButtonHighlight(BtnToggleVideoPresenterLiveOnCanvas, false); - int page = GetCurrentPageIndex(); - _liveEnabledPages.Remove(page); - - if (_liveFrameImageByPage.TryGetValue(page, out var img) && img != null) - { - try - { - if (inkCanvas != null && inkCanvas.Children.Contains(img)) - { - inkCanvas.Children.Remove(img); - } - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - } - - if (_liveEnabledPages.Count == 0) - { - StopVideoPresenterPreviewAndFrameCache(clearPreviewImage: false); - } - } - - private void StartVideoPresenterPreviewForCurrentPageIfNeeded() - { - try - { - EnsureCameraService(); - if (_cameraService == null || _cameraService.IsCapturing) return; - - int page = GetCurrentPageIndex(); - int idx = 0; - if (_cameraIndexByPage.TryGetValue(page, out int savedIdx)) - { - idx = savedIdx; - } - - if (_cameraService.AvailableCameras == null || _cameraService.AvailableCameras.Count == 0) - { - _cameraService.RefreshCameraList(); - } - - if (_cameraService.AvailableCameras == null || _cameraService.AvailableCameras.Count == 0) - { - return; - } - - idx = Math.Max(0, Math.Min(idx, _cameraService.AvailableCameras.Count - 1)); - _cameraService.StartPreview(idx); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"启动视频展台预览失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 在离开当前白板页之前保存该页实时视频画面在画布上的位置和宽度(按页索引进行存储)。 - /// - /// - /// 若画面元素的 Left 或 Top 为 NaN,则按 0 处理;保存的数据格式为 (left, top, width) 到页面布局映射中供后续恢复使用。 - /// - private void VideoPresenter_BeforePageLeave() - { - try - { - int page = GetCurrentPageIndex(); - if (!_liveFrameImageByPage.TryGetValue(page, out var img) || img == null) return; - - double left = InkCanvas.GetLeft(img); - double top = InkCanvas.GetTop(img); - if (double.IsNaN(left)) left = 0; - if (double.IsNaN(top)) top = 0; - - _liveFrameLayoutByPage[page] = (left, top, img.Width); - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - } - - /// - /// 在页面切换后恢复该页的实时画面状态并同步相关设备与 UI 控件状态。 - /// - /// - /// 同步“上屏”切换按钮状态;若当前页启用了在画布上显示实时画面,则确保并布局对应的 Image 元素并用当前预览图像填充其 Source;同时恢复该页保存的摄像头索引并启动对应摄像头预览。 - /// - private void VideoPresenter_OnPageChanged() - { - try - { - int page = GetCurrentPageIndex(); - - // 同步“上屏”按钮状态 - if (BtnToggleVideoPresenterLiveOnCanvas != null) - { - BtnToggleVideoPresenterLiveOnCanvas.IsChecked = _liveEnabledPages.Contains(page); - } - - // 若该页上屏,恢复画面元素(RestoreStrokes 会清空 inkCanvas.Children) - if (_liveEnabledPages.Contains(page)) - { - var img = EnsureLiveFrameElementForPage(page); - ApplyLiveFrameLayoutForPage(page, img); - if (inkCanvas != null && !inkCanvas.Children.Contains(img)) - { - inkCanvas.Children.Add(img); - } - - if (VideoPresenterPreviewImage?.Source is ImageSource src) - { - img.Source = src; - } - } - - // 按页摄像头索引:仅在展台侧栏可见时,切页后自动切回该页的摄像头 - if (VideoPresenterSidebar?.Visibility == Visibility.Visible - && _cameraIndexByPage.TryGetValue(page, out int idx)) - { - EnsureCameraService(); - _cameraService?.StartPreview(idx); - } - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - } - - /// - /// 处理“拍照”按钮的点击:捕获当前视频帧并将照片加入捕获列表,随后刷新捕获照片的显示。 - /// - /// - /// - 在拍照前会检查并强制执行最小冷却时间,防止短时间内重复拍照。 - /// - 如果用户已启用照片纠正,会尝试检测纸张轮廓并对照片做透视校正再保存。 - /// - 照片处理在后台线程完成,最终的列表更新和 UI 刷新在 UI 线程上执行。 - /// - 发生异常时会记录错误日志,不会向上抛出异常。 - /// - private void BtnCapturePhoto_Click(object sender, RoutedEventArgs e) - { - try - { - if ((DateTime.Now - _lastCaptureTime).TotalMilliseconds < VideoPresenterCaptureCooldownMs) return; - _lastCaptureTime = DateTime.Now; - - Bitmap frame; - lock (_videoPresenterFrameLock) - { - if (_lastFrame == null) return; - frame = (Bitmap)_lastFrame.Clone(); - } - - Task.Run(() => - { - try - { - using (frame) - { - Bitmap toSave = frame; - - if (Settings?.Automation?.IsEnablePhotoCorrection == true - && TryDetectPaperCorners(toSave, out List corners)) - { - var corrected = ApplyPerspectiveCorrection(toSave, corners); - if (corrected != null) toSave = corrected; - } - - var bmpImage = ConvertBitmapToBitmapImage(toSave); - if (!ReferenceEquals(toSave, frame)) - { - toSave.Dispose(); - } - - if (bmpImage == null) return; - - Dispatcher.BeginInvoke(new Action(() => - { - var ci = new CapturedImage(bmpImage); - _capturedPhotos.Insert(0, ci); - - while (_capturedPhotos.Count > MaxCapturedPhotos) - { - _capturedPhotos.RemoveAt(_capturedPhotos.Count - 1); - } - - UpdateCapturedPhotosDisplay(); - })); - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"视频展台拍照失败: {ex.Message}", LogHelper.LogType.Error); - } - }); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"视频展台拍照失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 将当前相机预览的显示角度顺时针旋转 90°(在四个方向间切换)。 - /// - /// - /// 更新内部 CameraService 的旋转状态以切换到下一个方向;在错误发生时会记录日志但不会抛出异常到调用者。 - /// - /// 触发该事件的控件(通常为旋转按钮)。 - /// 事件参数。 - private void BtnRotateImage_Click(object sender, RoutedEventArgs e) - { - try - { - EnsureCameraService(); - _cameraService.RotationAngle = (_cameraService.RotationAngle + 1) % 4; - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"视频展台旋转失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 在启用照片校正的切换按钮被选中时,将该偏好设置为开启并保存到设置文件。 - /// - private void ToggleBtnPhotoCorrection_Checked(object sender, RoutedEventArgs e) - { - ApplyBoothButtonHighlight(ToggleBtnPhotoCorrection, true); - if (Settings?.Automation == null) return; - Settings.Automation.IsEnablePhotoCorrection = true; - SaveSettingsToFile(); - } - - /// - /// 关闭“相片校正”设置并将变更持久化到设置文件。 - /// - private void ToggleBtnPhotoCorrection_Unchecked(object sender, RoutedEventArgs e) - { - ApplyBoothButtonHighlight(ToggleBtnPhotoCorrection, false); - if (Settings?.Automation == null) return; - Settings.Automation.IsEnablePhotoCorrection = false; - SaveSettingsToFile(); - } - - /// - /// 刷新并在 CapturedPhotosStackPanel 中显示最近捕获的照片缩略图,最多显示 30 张。 - /// - /// - /// 如果 CapturedPhotosStackPanel 为 null 则不执行任何操作。该方法会清空面板现有内容,并为每张照片创建一个包含缩略图的按钮;点击按钮会将对应照片插入画布。 - /// - private void UpdateCapturedPhotosDisplay() - { - if (CapturedPhotosStackPanel == null) return; - - CapturedPhotosStackPanel.Children.Clear(); - - const double PhotoListImageWidth = 310; - const double PhotoListImageHeight = 180; - - foreach (var photo in _capturedPhotos.Take(30)) - { - var btn = new Button - { - Margin = new Thickness(0, 0, 0, 6), - Padding = new Thickness(0), - BorderThickness = new Thickness(0), - Background = System.Windows.Media.Brushes.Transparent, - Tag = photo - }; - btn.Click += (s, e) => - { - if (btn.Tag is CapturedImage p) InsertPhotoToCanvas(p); - }; - - var img = new System.Windows.Controls.Image - { - Source = photo.Thumbnail, - Stretch = System.Windows.Media.Stretch.Uniform, - Width = PhotoListImageWidth, - Height = PhotoListImageHeight - }; - btn.Content = img; - CapturedPhotosStackPanel.Children.Add(btn); - } - } - - /// - /// 将选定的捕获图片作为图像元素插入到画布中央并切换到选择工具模式。 - /// - /// 要插入的捕获图片;若为 null 或其 Image 为 null,则不进行任何操作。 - /// - /// 在画布上创建并配置一个 Image 元素(设置 Source、Stretch、默认宽度及位置),初始化其变换与事件绑定,提交插入历史记录,添加到 inkCanvas,并将当前工具切换为“选择”同时隐藏相关子面板。方法内部捕获并记录异常,不会向外抛出。 - /// - private void InsertPhotoToCanvas(CapturedImage photo) - { - if (photo?.Image == null) return; - - try - { - var img = new System.Windows.Controls.Image - { - Source = photo.Image, - Stretch = System.Windows.Media.Stretch.Uniform, - Width = 500 - }; - - double x = (inkCanvas?.ActualWidth ?? 0) / 2 - img.Width / 2; - double y = (inkCanvas?.ActualHeight ?? 0) / 2 - 200; - if (double.IsNaN(x) || double.IsInfinity(x)) x = 100; - if (double.IsNaN(y) || double.IsInfinity(y)) y = 100; - - InkCanvas.SetLeft(img, Math.Max(0, x)); - InkCanvas.SetTop(img, Math.Max(0, y)); - InitializeElementTransform(img); - BindElementEvents(img); - timeMachine.CommitElementInsertHistory(img); - - inkCanvas?.Children.Add(img); - - SetCurrentToolMode(InkCanvasEditingMode.Select); - UpdateCurrentToolMode("select"); - HideSubPanels("select"); - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"插入展台照片失败: {ex.Message}", LogHelper.LogType.Error); - } - } - - /// - /// 在离开白板模式时关闭并清理视频呈现器相关的 UI 与运行状态。 - /// - /// - /// 隐藏视频呈现侧栏、将“在画布上显示实时帧”开关取消选中、从画布中移除并隐藏所有每页的实时帧图像实例,并尝试停止相机预览。该方法在执行过程中会吞并内部异常以避免抛出至调用方。 - /// - private void VideoPresenter_OnExitWhiteboardMode() - { - try - { - CloseVideoPresenterSidebarAndReleaseResources(); - - if (BtnToggleVideoPresenterLiveOnCanvas != null) - { - BtnToggleVideoPresenterLiveOnCanvas.IsChecked = false; - } - - if (inkCanvas != null) - { - foreach (var kv in _liveFrameImageByPage.ToList()) - { - var img = kv.Value; - if (img == null) continue; - try - { - if (inkCanvas.Children.Contains(img)) - { - inkCanvas.Children.Remove(img); - } - img.Visibility = Visibility.Collapsed; - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - } - } - - } - catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } - } - - /// - /// 将一个 System.Drawing.Bitmap 转换为可跨线程使用的 WPF BitmapImage。 - /// - /// 要转换的源位图;若为 则直接返回 。 - /// 转换得到的 ;若输入为 或转换失败则返回 - private static BitmapImage ConvertBitmapToBitmapImage(Bitmap bitmap) - { - try - { - if (bitmap == null) return null; - - using (var ms = new MemoryStream()) - { - bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png); - ms.Position = 0; - var bi = new BitmapImage(); - bi.BeginInit(); - bi.CacheOption = BitmapCacheOption.OnLoad; - bi.StreamSource = ms; - bi.EndInit(); - bi.Freeze(); - return bi; - } - } - catch - { - return null; - } - } - - /// - /// 在给定帧中尝试检测纸张(四边形)角点,并返回按原始帧坐标排列的四个点。 - /// - /// 要检测的输入位图帧。 - /// 检测到的四个角点(按顺序:左上、右上、左下、右下),坐标以输入帧的像素空间为准;检测失败时为 null。 - /// 如果成功检测到四个角点并填充 否则(包括输入为 null 或检测过程中发生错误)。 - private static bool TryDetectPaperCorners(Bitmap frame, out List cornersOut) - { - cornersOut = null; - try - { - if (frame == null) return false; - - int targetWidth = 640; - int ow = frame.Width; - int oh = frame.Height; - double scale = 1.0; - Bitmap work = frame; - if (ow > targetWidth) - { - int nh = (int)Math.Round(oh * (targetWidth / (double)ow)); - var resize = new ResizeBilinear(targetWidth, nh); - work = resize.Apply(frame); - scale = (double)ow / targetWidth; - } - - var gray = Grayscale.CommonAlgorithms.BT709.Apply(work); - var blur = new GaussianBlur(3, 3); - blur.ApplyInPlace(gray); - var canny = new CannyEdgeDetector(); - canny.ApplyInPlace(gray); - var dilate = new Dilatation3x3(); - dilate.ApplyInPlace(gray); - - var bc = new BlobCounter - { - FilterBlobs = true, - MinHeight = 50, - MinWidth = 50, - ObjectsOrder = ObjectsOrder.Size - }; - bc.ProcessImage(gray); - var blobs = bc.GetObjectsInformation(); - var sc = new SimpleShapeChecker(); - List best = null; - double bestArea = 0; - - foreach (var blob in blobs) - { - var edgePoints = bc.GetBlobsEdgePoints(blob); - if (edgePoints == null || edgePoints.Count < 4) continue; - if (sc.IsQuadrilateral(edgePoints, out List crn)) - { - double area = Math.Abs(PolygonArea(crn)); - if (area > bestArea) - { - bestArea = area; - best = crn; - } - } - } - - if (best != null) - { - var pts = best - .Select(p => new AForge.IntPoint((int)Math.Round(p.X * scale), (int)Math.Round(p.Y * scale))) - .ToList(); - pts.Sort((a, b) => a.Y.CompareTo(b.Y)); - if (pts[0].X > pts[1].X) (pts[0], pts[1]) = (pts[1], pts[0]); - if (pts[2].X > pts[3].X) (pts[2], pts[3]) = (pts[3], pts[2]); - cornersOut = pts; - if (!ReferenceEquals(work, frame)) work.Dispose(); - gray.Dispose(); - return true; - } - - if (!ReferenceEquals(work, frame)) work.Dispose(); - gray.Dispose(); - return false; - } - catch - { - return false; - } - } - - /// - /// 将源图像中由四个角点定义的纸张区域进行透视矫正并裁切为目标尺寸的位图,目标高度为 CorrectedPaperHeight,宽度按纸张比例计算。 - /// - /// 包含待矫正纸张的源位图。 - /// 纸张在源图像中的四个角点,按顺序提供:左上 (top-left)、右上 (top-right)、左下 (bottom-left)、右下 (bottom-right)。坐标为图像像素坐标系。 - /// 透视矫正并裁切后的位图;在输入无效或矫正失败时返回 - private static Bitmap ApplyPerspectiveCorrection(Bitmap frame, List corners) - { - try - { - if (frame == null || corners == null || corners.Count != 4) return null; - var tl = corners[0]; - var tr = corners[1]; - var bl = corners[2]; - var br = corners[3]; - - double topW = Math.Sqrt((tr.X - tl.X) * (tr.X - tl.X) + (tr.Y - tl.Y) * (tr.Y - tl.Y)); - double bottomW = Math.Sqrt((br.X - bl.X) * (br.X - bl.X) + (br.Y - bl.Y) * (br.Y - bl.Y)); - double leftH = Math.Sqrt((bl.X - tl.X) * (bl.X - tl.X) + (bl.Y - tl.Y) * (bl.Y - tl.Y)); - double rightH = Math.Sqrt((br.X - tr.X) * (br.X - tr.X) + (br.Y - tr.Y) * (br.Y - tr.Y)); - - double avgW = (topW + bottomW) / 2.0; - double avgH = (leftH + rightH) / 2.0; - if (avgH <= 0) avgH = 1; - double ratio = avgW / avgH; - - int targetH = CorrectedPaperHeight; - int targetW = Math.Max(1, (int)Math.Round(targetH * ratio)); - - var orderedCorners = new List { tl, tr, br, bl }; - var qtf = new QuadrilateralTransformation(orderedCorners, targetW, targetH); - return qtf.Apply(frame); - } - catch - { - return null; - } - } - - /// - /// 计算由给定顶点按顺序构成的多边形的有向面积(使用高斯面积/鞋带公式)。 - /// - /// 按顶点顺序排列的多边形顶点列表(至少应包含三个点以形成多边形)。 - /// 多边形的有向面积;当顶点顺时针时为负值,逆时针为正值;点数少于三时返回 0。 - private static double PolygonArea(List pts) - { - int n = pts.Count; - if (n < 3) return 0; - long sum = 0; - for (int i = 0; i < n; i++) - { - var p = pts[i]; - var q = pts[(i + 1) % n]; - sum += (long)p.X * q.Y - (long)p.Y * q.X; - } - return 0.5 * sum; + ImageBlackboard_MouseUp(null, null); + SoftwareLauncher.LaunchEasiCamera("希沃视频展台"); } } } diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index 4f3baec5..e0b93049 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -151,10 +151,6 @@ namespace Ink_Canvas [JsonProperty("enableVelocityBrushTip")] public bool EnableVelocityBrushTip { get; set; } - /// 为 true 时,白板工具栏「展台」按钮启动希沃视频展台(sweclauncher),否则使用内置展台。 - [JsonProperty("launchSeewoVideoShowcaseForWhiteboardBooth")] - public bool LaunchSeewoVideoShowcaseForWhiteboardBooth { get; set; } = false; - } public enum OptionalOperation diff --git a/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml index ef0c3239..d601c3d0 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml +++ b/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml @@ -96,13 +96,6 @@ SwitchName="ToggleSwitchCompressPicturesUploaded" Toggled="ToggleSwitchCompressPicturesUploaded_Toggled" /> - - diff --git a/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml.cs index fa4e1be8..2f69956a 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml.cs @@ -40,7 +40,6 @@ namespace Ink_Canvas.Windows.SettingsViews.Pages CardClearCanvasAndClearTimeMachine.IsOn = settings.Canvas.ClearCanvasAndClearTimeMachine; CardClearCanvasAlsoClearImages.IsOn = settings.Canvas.ClearCanvasAlsoClearImages; CardCompressPicturesUploaded.IsOn = settings.Canvas.IsCompressPicturesUploaded; - CardLaunchSeewoVideoShowcaseForWhiteboardBooth.IsOn = settings.Canvas.LaunchSeewoVideoShowcaseForWhiteboardBooth; ComboBoxHyperbolaAsymptoteOption.SelectedIndex = (int)settings.Canvas.HyperbolaAsymptoteOption; CardShowCircleCenter.IsOn = settings.Canvas.ShowCircleCenter; int curveMode = 0; @@ -176,13 +175,6 @@ namespace Ink_Canvas.Windows.SettingsViews.Pages SettingsManager.SaveSettingsToFile(); } - private void ToggleSwitchLaunchSeewoVideoShowcaseForWhiteboardBooth_Toggled(object sender, RoutedEventArgs e) - { - if (!_isLoaded) return; - SettingsManager.Settings.Canvas.LaunchSeewoVideoShowcaseForWhiteboardBooth = CardLaunchSeewoVideoShowcaseForWhiteboardBooth.IsOn; - SettingsManager.SaveSettingsToFile(); - } - private void ComboBoxHyperbolaAsymptoteOption_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (!_isLoaded) return; From b891cb6fe3de3e9337eff9349a64c995b8639495 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Thu, 23 Apr 2026 22:09:27 +0800 Subject: [PATCH 086/205] =?UTF-8?q?Revert=20"add:=E6=96=B0=E8=AE=BE?= =?UTF-8?q?=E7=BD=AE"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit b949b1651aa16642829894318df0d92f41a02722. --- Ink Canvas/App.xaml.cs | 11 + Ink Canvas/MainWindow.xaml | 2318 +++++++++++++++++ Ink Canvas/MainWindow.xaml.cs | 86 +- Ink Canvas/MainWindow_cs/MW_AutoTheme.cs | 4 + .../MW_FloatingWindowInterceptor.cs | 20 + Ink Canvas/MainWindow_cs/MW_PPT.cs | 12 +- Ink Canvas/MainWindow_cs/MW_Settings.cs | 836 +++++- Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs | 266 +- Ink Canvas/MainWindow_cs/MW_TouchEvents.cs | 6 +- Ink Canvas/Windows/CustomIconWindow.xaml.cs | 3 +- .../SettingsViews/Pages/AdvancedPage.xaml | 139 - .../SettingsViews/Pages/AdvancedPage.xaml.cs | 148 -- .../SettingsViews/Pages/AppearancePage.xaml | 327 +-- .../Pages/AppearancePage.xaml.cs | 334 +-- .../SettingsViews/Pages/AutomationPage.xaml | 76 - .../Pages/AutomationPage.xaml.cs | 127 - .../SettingsViews/Pages/GesturePage.xaml | 78 - .../SettingsViews/Pages/GesturePage.xaml.cs | 83 - .../Windows/SettingsViews/Pages/PPTPage.xaml | 169 -- .../SettingsViews/Pages/PPTPage.xaml.cs | 183 -- .../SettingsViews/Pages/StartupPage.xaml | 35 - .../SettingsViews/Pages/StartupPage.xaml.cs | 53 - .../Windows/SettingsViews/SettingsWindow.xaml | 45 - .../SettingsViews/SettingsWindow.xaml.cs | 5 - 24 files changed, 3526 insertions(+), 1838 deletions(-) delete mode 100644 Ink Canvas/Windows/SettingsViews/Pages/AdvancedPage.xaml delete mode 100644 Ink Canvas/Windows/SettingsViews/Pages/AdvancedPage.xaml.cs delete mode 100644 Ink Canvas/Windows/SettingsViews/Pages/AutomationPage.xaml delete mode 100644 Ink Canvas/Windows/SettingsViews/Pages/AutomationPage.xaml.cs delete mode 100644 Ink Canvas/Windows/SettingsViews/Pages/GesturePage.xaml delete mode 100644 Ink Canvas/Windows/SettingsViews/Pages/GesturePage.xaml.cs delete mode 100644 Ink Canvas/Windows/SettingsViews/Pages/PPTPage.xaml delete mode 100644 Ink Canvas/Windows/SettingsViews/Pages/PPTPage.xaml.cs diff --git a/Ink Canvas/App.xaml.cs b/Ink Canvas/App.xaml.cs index 6c3dc892..8b52712f 100644 --- a/Ink Canvas/App.xaml.cs +++ b/Ink Canvas/App.xaml.cs @@ -1113,6 +1113,17 @@ namespace Ink_Canvas LogHelper.WriteLogToFile($"Failed to register InkCanvasService: {ex.Message}", LogHelper.LogType.Error); } + try + { + var appRestartService = new Plugins.AppRestartService(); + Plugins.PluginManager.Instance.RegisterService(appRestartService); + LogHelper.WriteLogToFile("AppRestartService registered for plugins"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"Failed to register AppRestartService: {ex.Message}", LogHelper.LogType.Error); + } + // 主窗口加载完成后关闭启动画面 mainWindow.Loaded += (s, args) => { diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 11f9499e..42917682 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -649,8 +649,2326 @@ Padding="15,5" Margin="0,10,0,0"/> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _boothResolutionWidth; + public int BoothResolutionHeight => _boothResolutionHeight; private static Cursor _cachedPenCursor = null; private static readonly object _cursorLock = new object(); @@ -1763,6 +1766,24 @@ namespace Ink_Canvas { SystemEvents.DisplaySettingsChanged -= SystemEventsOnDisplaySettingsChanged; + try + { + // 清理视频展台资源 + if (_cameraService != null) + { + _cameraService.FrameReceived -= CameraService_FrameReceived; + _cameraService.ErrorOccurred -= CameraService_ErrorOccurred; + _cameraService.Dispose(); + _cameraService = null; + } + lock (_videoPresenterFrameLock) + { + _lastFrame?.Dispose(); + _lastFrame = null; + } + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + // 释放PPT管理器资源 DisposePPTManagers(); @@ -2997,6 +3018,62 @@ namespace Ink_Canvas } #endregion + #region 展台/白板分辨率切换 + private const int BoothResolutionTabCount = 4; + private static readonly (int w, int h)[] BoothResolutionValues = { (1280, 720), (1920, 1080), (2560, 1440), (3840, 2160) }; + + private void BoothResolutionTab_Click(object sender, RoutedEventArgs e) + { + if (sender is Button btn && btn.Tag is string tag) + { + var parts = tag.Split(','); + if (parts.Length == 2 && int.TryParse(parts[0].Trim(), out int w) && int.TryParse(parts[1].Trim(), out int h) && w > 0 && h > 0) + { + _boothResolutionWidth = w; + _boothResolutionHeight = h; + UpdateBoothResolutionTabState(); + SyncBoothResolutionToCameraService(); + } + } + } + + private void UpdateBoothResolutionTabState() + { + int index = 0; + for (int i = 0; i < BoothResolutionValues.Length; i++) + { + if (BoothResolutionValues[i].w == _boothResolutionWidth && BoothResolutionValues[i].h == _boothResolutionHeight) + { + index = i; + break; + } + } + + if (BoothResolutionTabIndicator != null) + { + BoothResolutionTabIndicator.Margin = new Thickness(index * 70, 0, 0, 0); + } + + var texts = new[] { BtnBoothResolution720?.Content as TextBlock, BtnBoothResolution1080?.Content as TextBlock, BtnBoothResolution2K?.Content as TextBlock, BtnBoothResolution4K?.Content as TextBlock }; + for (int i = 0; i < texts.Length && i < 4; i++) + { + if (texts[i] == null) continue; + if (i == index) + { + texts[i].FontWeight = FontWeights.Bold; + texts[i].Foreground = new SolidColorBrush(Colors.White); + texts[i].Opacity = 1.0; + } + else + { + texts[i].FontWeight = FontWeights.SemiBold; + texts[i].SetResourceReference(TextBlock.ForegroundProperty, "FloatBarForeground"); + texts[i].Opacity = 0.7; + } + } + } + #endregion + private void ToggleSwitchEnableInkToShape_Toggled(object sender, RoutedEventArgs e) { diff --git a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs index 26107e94..337bcc44 100644 --- a/Ink Canvas/MainWindow_cs/MW_BoardControls.cs +++ b/Ink Canvas/MainWindow_cs/MW_BoardControls.cs @@ -54,7 +54,7 @@ namespace Ink_Canvas /// /// 为 true 时将导出结果保存到主备份槽(索引 0);为 false 时保存到当前白板索引。 /// - /// - 会提交画布上缺失于历史记录的 Image/MediaElement 和缺失的墨迹; + /// - 会提交画布上缺失于历史记录的 Image/MediaElement(但跳过 Tag 等于 VideoPresenterLiveFrameTag 的 Image)和缺失的墨迹; /// - 导出后把结果存入 TimeMachineHistories 的相应索引,并保存当前多指书写模式到 savedMultiTouchModeStates; /// - 导出后会清除时间机器的临时墨迹历史以释放内存。 /// - 此方法有副作用:修改 TimeMachineHistories、savedMultiTouchModeStates,并通过 timeMachine 的提交方法改变其内部历史状态。 @@ -85,6 +85,10 @@ namespace Ink_Canvas { if (child is Image || child is MediaElement || child is PdfEmbeddedView) { + if (child is Image img && img.Tag is string tag && tag == VideoPresenterLiveFrameTag) + { + continue; + } if (!elementsInHistory.Contains(child)) { timeMachine.CommitElementInsertHistory(child); @@ -441,12 +445,14 @@ namespace Ink_Canvas currentSelectedElement = null; } + VideoPresenter_BeforePageLeave(); SaveStrokes(); ClearStrokes(true); CurrentWhiteboardIndex--; RestoreStrokes(); + VideoPresenter_OnPageChanged(); UpdateIndexInfoDisplay(); } @@ -484,12 +490,14 @@ namespace Ink_Canvas currentSelectedElement = null; } + VideoPresenter_BeforePageLeave(); SaveStrokes(); ClearStrokes(true); CurrentWhiteboardIndex++; RestoreStrokes(); + VideoPresenter_OnPageChanged(); UpdateIndexInfoDisplay(); } @@ -525,6 +533,7 @@ namespace Ink_Canvas currentSelectedElement = null; } + VideoPresenter_BeforePageLeave(); SaveStrokes(); ClearStrokes(true); @@ -540,9 +549,12 @@ namespace Ink_Canvas } } + // 确保新页面的历史记录为空 TimeMachineHistories[CurrentWhiteboardIndex] = null; + // 恢复新页面(这会清空画布,因为历史记录为null) RestoreStrokes(); + VideoPresenter_OnPageChanged(); UpdateIndexInfoDisplay(); diff --git a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs index 68a91d6f..21019d38 100644 --- a/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs +++ b/Ink Canvas/MainWindow_cs/MW_FloatingBarIcons.cs @@ -3391,6 +3391,7 @@ namespace Ink_Canvas switch (++currentMode % 2) { case 0: //屏幕模式 + VideoPresenter_OnExitWhiteboardMode(); currentMode = 0; GridBackgroundCover.Visibility = Visibility.Collapsed; AnimationsHelper.HideWithSlideAndFade(BlackboardLeftSide); diff --git a/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs b/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs index a5f5c151..e276e9b5 100644 --- a/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs +++ b/Ink Canvas/MainWindow_cs/MW_VideoPresenter.cs @@ -1,15 +1,1052 @@ +using AForge.Imaging; +using AForge.Imaging.Filters; +using AForge.Math.Geometry; using Ink_Canvas.Helpers; +using Ink_Canvas.Models; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Threading.Tasks; using System.Windows; +using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Imaging; namespace Ink_Canvas { public partial class MainWindow : Window { - private void BtnToggleVideoPresenter_Click(object sender, MouseButtonEventArgs e) + private static readonly SolidColorBrush BoothButtonHighlightBrush = new SolidColorBrush((System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#FF66CCFF")); + private bool _boothButtonPressHandlersAttached; + + // 标记:用于在保存/恢复白板内容时排除“展台实时上屏”画面 + private const string VideoPresenterLiveFrameTag = "__VideoPresenterLiveFrame"; + + private CameraService _cameraService; + private readonly object _videoPresenterFrameLock = new object(); + private Bitmap _lastFrame; + + private readonly List _capturedPhotos = new List(); + private const int MaxCapturedPhotos = 50; // 容量上限:比 UI 显示的 30 项多一些,避免频繁清理 + + // 按页绑定:每一页对应一个“实时画面”元素与布局/设备信息 + private readonly Dictionary _liveFrameImageByPage = new Dictionary(); + private readonly HashSet _liveEnabledPages = new HashSet(); + private readonly Dictionary _cameraIndexByPage = new Dictionary(); + private readonly Dictionary _liveFrameLayoutByPage = + new Dictionary(); + + private DateTime _lastCaptureTime = DateTime.MinValue; + private const int VideoPresenterCaptureCooldownMs = 1000; + + private const int CorrectedPaperHeight = 600; + + /// + /// 切换视频呈现侧边栏的显示状态(显示或隐藏)。 + /// + /// 触发事件的源对象。 + /// 鼠标按钮事件的参数。 + private void BtnToggleVideoPresenter_Click(object sender, System.Windows.Input.MouseButtonEventArgs e) { - ImageBlackboard_MouseUp(null, null); - SoftwareLauncher.LaunchEasiCamera("希沃视频展台"); + if (Settings?.Canvas?.LaunchSeewoVideoShowcaseForWhiteboardBooth == true) + { + // 与主窗口「希沃视频展台」入口(BoardLaunchEasiCamera_MouseUp)一致:先走黑板/白板入口逻辑再启动 + ImageBlackboard_MouseUp(null, null); + SoftwareLauncher.LaunchEasiCamera("希沃视频展台"); + return; + } + + ToggleVideoPresenterSidebar(); + } + + /// + /// 切换视频演示侧栏的显示状态并在显示时初始化相关控件与状态。 + /// + /// + /// 当侧栏被显示时:确保摄像头服务已初始化、暂时禁用拍照按钮、刷新可用摄像头列表,并将“照片校正”和当前页面的“上屏(live on canvas)”开关同步为保存的设置或页面状态; + /// 当侧栏被隐藏时:将其折叠并停止进一步初始化操作。 + /// + private void ToggleVideoPresenterSidebar() + { + if (VideoPresenterSidebar == null) return; + + if (VideoPresenterSidebar.Visibility == Visibility.Visible) + { + VideoPresenterSidebar.Visibility = Visibility.Collapsed; + return; + } + + VideoPresenterSidebar.Visibility = Visibility.Visible; + EnsureCameraService(); + if (BtnCapturePhoto != null) BtnCapturePhoto.IsEnabled = false; + RefreshVideoPresenterDeviceList(); + UpdateBoothResolutionTabState(); + + if (ToggleBtnPhotoCorrection != null) + { + ToggleBtnPhotoCorrection.IsChecked = Settings?.Automation?.IsEnablePhotoCorrection ?? false; + } + + // 同步“上屏”按钮状态(按页绑定) + if (BtnToggleVideoPresenterLiveOnCanvas != null) + { + BtnToggleVideoPresenterLiveOnCanvas.IsChecked = _liveEnabledPages.Contains(GetCurrentPageIndex()); + } + + if (!_boothButtonPressHandlersAttached) + { + AttachBoothButtonPressHandlers(); + } + } + + private void AttachBoothButtonPressHandlers() + { + if (BtnCapturePhoto == null || BtnRotateImage == null) return; + BtnCapturePhoto.PreviewMouseDown += BoothButton_PreviewMouseDown; + BtnCapturePhoto.PreviewMouseUp += BoothButton_PreviewMouseUp; + BtnCapturePhoto.LostMouseCapture += BoothButton_PreviewMouseUp; + BtnRotateImage.PreviewMouseDown += BoothButton_PreviewMouseDown; + BtnRotateImage.PreviewMouseUp += BoothButton_PreviewMouseUp; + BtnRotateImage.LostMouseCapture += BoothButton_PreviewMouseUp; + _boothButtonPressHandlersAttached = true; + } + + private void BoothButton_PreviewMouseDown(object sender, MouseButtonEventArgs e) + { + if (sender is Control c) ApplyBoothButtonHighlight(c, true); + } + + private void BoothButton_PreviewMouseUp(object sender, EventArgs e) + { + if (sender is Control c) ApplyBoothButtonHighlight(c, false); + } + + private static void ApplyBoothButtonHighlight(Control control, bool highlight) + { + if (control == null) return; + if (highlight) + { + control.Background = BoothButtonHighlightBrush; + control.BorderBrush = BoothButtonHighlightBrush; + } + else + { + control.SetResourceReference(Control.BackgroundProperty, "FloatBarBackground"); + control.SetResourceReference(Control.BorderBrushProperty, "FloatBarBorderBrush"); + } + } + + /// + /// 关闭视频呈现侧边栏(将其可见性设为 Collapsed)。 + /// + private void BtnCloseVideoPresenter_Click(object sender, RoutedEventArgs e) + { + if (VideoPresenterSidebar != null) + { + VideoPresenterSidebar.Visibility = Visibility.Collapsed; + } + } + + private void CloseVideoPresenterSidebarAndReleaseResources() + { + if (VideoPresenterSidebar != null) + { + VideoPresenterSidebar.Visibility = Visibility.Collapsed; + } + + StopVideoPresenterPreviewAndFrameCache(clearPreviewImage: true); + } + + private void StopVideoPresenterPreviewAndFrameCache(bool clearPreviewImage) + { + if (BtnCapturePhoto != null) + { + BtnCapturePhoto.IsEnabled = false; + } + + if (clearPreviewImage && VideoPresenterPreviewImage != null) + { + VideoPresenterPreviewImage.Source = null; + } + + try { _cameraService?.StopPreview(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + lock (_videoPresenterFrameLock) + { + _lastFrame?.Dispose(); + _lastFrame = null; + } + } + + /// + /// 延迟初始化摄像头服务并订阅其帧和错误事件;如果服务已存在则不做任何操作。 + /// + private void EnsureCameraService() + { + if (_cameraService != null) return; + + _cameraService = new CameraService(); + _cameraService.FrameReceived += CameraService_FrameReceived; + _cameraService.ErrorOccurred += CameraService_ErrorOccurred; + SyncBoothResolutionToCameraService(); + } + + internal void SyncBoothResolutionToCameraService() + { + if (_cameraService == null) return; + _cameraService.ResolutionWidth = BoothResolutionWidth; + _cameraService.ResolutionHeight = BoothResolutionHeight; + } + + /// + /// 在相机服务发生错误时将错误信息写入错误日志文件。 + /// + /// 来自相机服务的错误描述,会被写入错误日志。 + private void CameraService_ErrorOccurred(object sender, string e) + { + try + { + LogHelper.WriteLogToFile($"视频展台摄像头错误: {e}", LogHelper.LogType.Error); + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + } + + /// + /// 处理来自摄像头的单帧图像,用于更新预览、缓存最新帧并刷新当前页的实时画面显示。 + /// + /// 来自摄像头的位图帧;为 null 时忽略。 + /// + /// 缓存该帧为最新帧,并通过 CameraService 提供的 BitmapSource 直接更新预览与实时上屏 + /// + private void CameraService_FrameReceived(object sender, Bitmap frame) + { + if (frame == null) return; + + try + { + Bitmap serviceCopy; + try + { + serviceCopy = (Bitmap)frame.Clone(); + } + catch + { + // 可能在下一帧到来时被 CameraService 释放,直接忽略这一帧 + return; + } + + lock (_videoPresenterFrameLock) + { + _lastFrame?.Dispose(); + _lastFrame = (Bitmap)serviceCopy.Clone(); + } + + var previewSource = _cameraService?.GetCurrentFrameAsBitmapSource(); + serviceCopy.Dispose(); + if (previewSource == null) return; + + Dispatcher.BeginInvoke(new Action(() => + { + if (VideoPresenterPreviewImage != null) + { + VideoPresenterPreviewImage.Source = previewSource; + } + + if (BtnCapturePhoto != null) + { + BtnCapturePhoto.IsEnabled = true; + } + + // 实时上屏:刷新当前页的画面元素 + TryUpdateLiveFrameOnCanvas(previewSource); + })); + } + catch + { + // 忽略预览刷新异常 + } + } + + /// + /// 获取当前白板页索引(确保返回值至少为 1)。 + /// + /// 当前白板页索引;如果内部索引小于 1,则返回 1。 + private int GetCurrentPageIndex() + { + return Math.Max(1, CurrentWhiteboardIndex); + } + + /// + /// 在当前白板页面(若已启用)将给定预览图像应用到页面上的实时摄像框元素,并确保该元素已添加到画布且可见。 + /// + /// + /// 如果当前页面未启用实时显示,或画布/对应图像元素不可用,则函数不执行任何操作。 + /// + private void TryUpdateLiveFrameOnCanvas(ImageSource preview) + { + try + { + if (preview == null) return; + + int page = GetCurrentPageIndex(); + if (!_liveEnabledPages.Contains(page)) return; + if (inkCanvas == null) return; + if (!_liveFrameImageByPage.TryGetValue(page, out var img) || img == null) return; + + if (!inkCanvas.Children.Contains(img)) + { + inkCanvas.Children.Add(img); + } + + img.Source = preview; + img.Visibility = Visibility.Visible; + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + } + + private const double VideoPresenterLiveFrameScreenRatio = 0.75; + + /// + /// 获取或创建并缓存用于指定白板页的实时视频帧 Image 元素。 + /// + /// 白板页索引(页面编号,用于在每页间区分并缓存元素)。 + /// 返回指定页对应的 Image 元素;若已存在则返回已缓存实例,否则创建新的 Image(根据画布大小设置默认宽高、标记为实时帧并初始化变换与交互绑定)并将其缓存后返回。 + private System.Windows.Controls.Image EnsureLiveFrameElementForPage(int page) + { + if (_liveFrameImageByPage.TryGetValue(page, out var existing) && existing != null) return existing; + + double canvasW = inkCanvas?.ActualWidth ?? 0; + double canvasH = inkCanvas?.ActualHeight ?? 0; + double w = canvasW > 10 && canvasH > 10 + ? canvasW * VideoPresenterLiveFrameScreenRatio + : 520; + double h = canvasW > 10 && canvasH > 10 + ? canvasH * VideoPresenterLiveFrameScreenRatio + : 390; + + var img = new System.Windows.Controls.Image + { + Tag = VideoPresenterLiveFrameTag, + Stretch = System.Windows.Media.Stretch.Uniform, + Width = w, + Height = h, + Visibility = Visibility.Visible, + Opacity = 1.0 + }; + try + { + InitializeElementTransform(img); + BindElementEvents(img); + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + + _liveFrameImageByPage[page] = img; + return img; + } + + /// + /// 将已保存的布局(或默认布局)应用到指定白板页面上的直播帧 Image 元素,设置其位置和尺寸并确保坐标有效。 + /// + /// 目标白板页面的索引。 + /// 要应用布局的 Image 元素;为 null 时不执行任何操作。 + /// + /// 如果存在为该页面保存的布局则使用其宽度和左/上坐标;否则将 Image 调整为画布尺寸的 75% 并居中。最终位置会限制为不小于 0 的坐标,且对无效计算结果使用合理的默认偏移。 + /// + private void ApplyLiveFrameLayoutForPage(int page, System.Windows.Controls.Image img) + { + if (img == null) return; + + if (_liveFrameLayoutByPage.TryGetValue(page, out var layout)) + { + if (!double.IsNaN(layout.width) && layout.width > 10) img.Width = layout.width; + InkCanvas.SetLeft(img, Math.Max(0, layout.left)); + InkCanvas.SetTop(img, Math.Max(0, layout.top)); + return; + } + + // 默认尺寸:画布宽高的 75%;位置居中 + double cw = inkCanvas?.ActualWidth ?? 0; + double ch = inkCanvas?.ActualHeight ?? 0; + if (cw > 10 && ch > 10) + { + img.Width = cw * VideoPresenterLiveFrameScreenRatio; + img.Height = ch * VideoPresenterLiveFrameScreenRatio; + } + double x = (inkCanvas?.ActualWidth ?? 0) / 2 - img.Width / 2; + double y = (inkCanvas?.ActualHeight ?? 0) / 2 - img.Height / 2; + if (double.IsNaN(x) || double.IsInfinity(x)) x = 100; + if (double.IsNaN(y) || double.IsInfinity(y)) y = 100; + InkCanvas.SetLeft(img, Math.Max(0, x)); + InkCanvas.SetTop(img, Math.Max(0, y)); + } + + /// + /// 刷新视频呈现器侧栏中的摄像头设备列表并在界面上显示可选项。 + /// + /// + /// 若未检测到摄像头,会在面板中显示提示文本;若存在设备,则为每个设备创建一个用于选择的单选按钮,选择某项会启动对应的摄像头预览。函数在列表生成后会尝试恢复并启动当前页面在 _cameraIndexByPage 中存储的摄像头索引,仅当没有保存的索引时才会选择并启动第一个可用设备。保存的每页选择优先于默认选择第一个设备。 + /// + private void RefreshVideoPresenterDeviceList() + { + if (_cameraService == null) return; + if (CameraDevicesStackPanel == null) return; + + _cameraService.RefreshCameraList(); + CameraDevicesStackPanel.Children.Clear(); + + if (_cameraService.AvailableCameras == null || _cameraService.AvailableCameras.Count == 0) + { + var tb = new TextBlock + { + Text = "未检测到摄像头设备", + FontSize = 12, + Margin = new Thickness(5), + HorizontalAlignment = HorizontalAlignment.Center + }; + tb.SetResourceReference(TextBlock.ForegroundProperty, "FloatBarForeground"); + CameraDevicesStackPanel.Children.Add(tb); + return; + } + + for (int i = 0; i < _cameraService.AvailableCameras.Count; i++) + { + int idx = i; + var dev = _cameraService.AvailableCameras[i]; + var rb = new RadioButton + { + Content = dev.Name, + Margin = new Thickness(0, 2, 0, 2), + FontSize = 12, + Tag = idx, + }; + rb.SetResourceReference(Control.ForegroundProperty, "FloatBarForeground"); + rb.Checked += (s, e) => StartVideoPresenterPreview(idx); + CameraDevicesStackPanel.Children.Add(rb); + } + + // 预选该页已保存的摄像头,否则使用第一个 + if (_cameraService.AvailableCameras.Count > 0) + { + int currentPage = GetCurrentPageIndex(); + int cameraToSelect = 0; + if (_cameraIndexByPage.TryGetValue(currentPage, out int savedIdx) && savedIdx >= 0 && savedIdx < _cameraService.AvailableCameras.Count) + { + cameraToSelect = savedIdx; + } + + if (cameraToSelect < CameraDevicesStackPanel.Children.Count && CameraDevicesStackPanel.Children[cameraToSelect] is RadioButton rb) + { + rb.IsChecked = true; + } + else + { + StartVideoPresenterPreview(cameraToSelect); + } + } + } + + /// + /// 为当前白板页开始指定摄像头的预览并保存该页的摄像头选择。 + /// + /// 要启动的摄像头在设备列表中的索引。 + /// 预览成功时会允许拍照按钮可用。 + private void StartVideoPresenterPreview(int cameraIndex) + { + try + { + EnsureCameraService(); + _cameraIndexByPage[GetCurrentPageIndex()] = cameraIndex; + if (_cameraService.StartPreview(cameraIndex)) + { + if (BtnCapturePhoto != null) BtnCapturePhoto.IsEnabled = true; + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动视频展台预览失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 在当前白板页面启用“在画布上显示实时视频”功能并将对应的实时画面元素添加到画布上。 + /// + /// + /// 确保为当前页面创建并应用已保存的布局,将实时画面 Image 加入 inkCanvas(若尚未存在),并尝试切换编辑工具为选择模式。若侧栏预览已有帧,则立即用该预览刷新画布上的实时画面图像源。 + /// + private void BtnToggleVideoPresenterLiveOnCanvas_Checked(object sender, RoutedEventArgs e) + { + ApplyBoothButtonHighlight(BtnToggleVideoPresenterLiveOnCanvas, true); + int page = GetCurrentPageIndex(); + _liveEnabledPages.Add(page); + StartVideoPresenterPreviewForCurrentPageIfNeeded(); + + var img = EnsureLiveFrameElementForPage(page); + ApplyLiveFrameLayoutForPage(page, img); + + if (inkCanvas != null && !inkCanvas.Children.Contains(img)) + { + inkCanvas.Children.Add(img); + } + + try + { + SetCurrentToolMode(InkCanvasEditingMode.Select); + UpdateCurrentToolMode("select"); + HideSubPanels("select"); + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + + // 立即用侧栏预览刷新一次 + if (VideoPresenterPreviewImage?.Source is ImageSource src) + { + img.Source = src; + } + } + + /// + /// 在当前页面禁用画布上的实时视频覆盖并移除其视觉元素。 + /// + /// + /// 从记录已启用实时显示的集合中删除当前页面索引,并在存在对应的 Image 元素且已添加到 inkCanvas 时尝试将其移除。 + /// + private void BtnToggleVideoPresenterLiveOnCanvas_Unchecked(object sender, RoutedEventArgs e) + { + ApplyBoothButtonHighlight(BtnToggleVideoPresenterLiveOnCanvas, false); + int page = GetCurrentPageIndex(); + _liveEnabledPages.Remove(page); + + if (_liveFrameImageByPage.TryGetValue(page, out var img) && img != null) + { + try + { + if (inkCanvas != null && inkCanvas.Children.Contains(img)) + { + inkCanvas.Children.Remove(img); + } + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + } + + if (_liveEnabledPages.Count == 0) + { + StopVideoPresenterPreviewAndFrameCache(clearPreviewImage: false); + } + } + + private void StartVideoPresenterPreviewForCurrentPageIfNeeded() + { + try + { + EnsureCameraService(); + if (_cameraService == null || _cameraService.IsCapturing) return; + + int page = GetCurrentPageIndex(); + int idx = 0; + if (_cameraIndexByPage.TryGetValue(page, out int savedIdx)) + { + idx = savedIdx; + } + + if (_cameraService.AvailableCameras == null || _cameraService.AvailableCameras.Count == 0) + { + _cameraService.RefreshCameraList(); + } + + if (_cameraService.AvailableCameras == null || _cameraService.AvailableCameras.Count == 0) + { + return; + } + + idx = Math.Max(0, Math.Min(idx, _cameraService.AvailableCameras.Count - 1)); + _cameraService.StartPreview(idx); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"启动视频展台预览失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 在离开当前白板页之前保存该页实时视频画面在画布上的位置和宽度(按页索引进行存储)。 + /// + /// + /// 若画面元素的 Left 或 Top 为 NaN,则按 0 处理;保存的数据格式为 (left, top, width) 到页面布局映射中供后续恢复使用。 + /// + private void VideoPresenter_BeforePageLeave() + { + try + { + int page = GetCurrentPageIndex(); + if (!_liveFrameImageByPage.TryGetValue(page, out var img) || img == null) return; + + double left = InkCanvas.GetLeft(img); + double top = InkCanvas.GetTop(img); + if (double.IsNaN(left)) left = 0; + if (double.IsNaN(top)) top = 0; + + _liveFrameLayoutByPage[page] = (left, top, img.Width); + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + } + + /// + /// 在页面切换后恢复该页的实时画面状态并同步相关设备与 UI 控件状态。 + /// + /// + /// 同步“上屏”切换按钮状态;若当前页启用了在画布上显示实时画面,则确保并布局对应的 Image 元素并用当前预览图像填充其 Source;同时恢复该页保存的摄像头索引并启动对应摄像头预览。 + /// + private void VideoPresenter_OnPageChanged() + { + try + { + int page = GetCurrentPageIndex(); + + // 同步“上屏”按钮状态 + if (BtnToggleVideoPresenterLiveOnCanvas != null) + { + BtnToggleVideoPresenterLiveOnCanvas.IsChecked = _liveEnabledPages.Contains(page); + } + + // 若该页上屏,恢复画面元素(RestoreStrokes 会清空 inkCanvas.Children) + if (_liveEnabledPages.Contains(page)) + { + var img = EnsureLiveFrameElementForPage(page); + ApplyLiveFrameLayoutForPage(page, img); + if (inkCanvas != null && !inkCanvas.Children.Contains(img)) + { + inkCanvas.Children.Add(img); + } + + if (VideoPresenterPreviewImage?.Source is ImageSource src) + { + img.Source = src; + } + } + + // 按页摄像头索引:仅在展台侧栏可见时,切页后自动切回该页的摄像头 + if (VideoPresenterSidebar?.Visibility == Visibility.Visible + && _cameraIndexByPage.TryGetValue(page, out int idx)) + { + EnsureCameraService(); + _cameraService?.StartPreview(idx); + } + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + } + + /// + /// 处理“拍照”按钮的点击:捕获当前视频帧并将照片加入捕获列表,随后刷新捕获照片的显示。 + /// + /// + /// - 在拍照前会检查并强制执行最小冷却时间,防止短时间内重复拍照。 + /// - 如果用户已启用照片纠正,会尝试检测纸张轮廓并对照片做透视校正再保存。 + /// - 照片处理在后台线程完成,最终的列表更新和 UI 刷新在 UI 线程上执行。 + /// - 发生异常时会记录错误日志,不会向上抛出异常。 + /// + private void BtnCapturePhoto_Click(object sender, RoutedEventArgs e) + { + try + { + if ((DateTime.Now - _lastCaptureTime).TotalMilliseconds < VideoPresenterCaptureCooldownMs) return; + _lastCaptureTime = DateTime.Now; + + Bitmap frame; + lock (_videoPresenterFrameLock) + { + if (_lastFrame == null) return; + frame = (Bitmap)_lastFrame.Clone(); + } + + Task.Run(() => + { + try + { + using (frame) + { + Bitmap toSave = frame; + + if (Settings?.Automation?.IsEnablePhotoCorrection == true + && TryDetectPaperCorners(toSave, out List corners)) + { + var corrected = ApplyPerspectiveCorrection(toSave, corners); + if (corrected != null) toSave = corrected; + } + + var bmpImage = ConvertBitmapToBitmapImage(toSave); + if (!ReferenceEquals(toSave, frame)) + { + toSave.Dispose(); + } + + if (bmpImage == null) return; + + Dispatcher.BeginInvoke(new Action(() => + { + var ci = new CapturedImage(bmpImage); + _capturedPhotos.Insert(0, ci); + + while (_capturedPhotos.Count > MaxCapturedPhotos) + { + _capturedPhotos.RemoveAt(_capturedPhotos.Count - 1); + } + + UpdateCapturedPhotosDisplay(); + })); + } + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"视频展台拍照失败: {ex.Message}", LogHelper.LogType.Error); + } + }); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"视频展台拍照失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 将当前相机预览的显示角度顺时针旋转 90°(在四个方向间切换)。 + /// + /// + /// 更新内部 CameraService 的旋转状态以切换到下一个方向;在错误发生时会记录日志但不会抛出异常到调用者。 + /// + /// 触发该事件的控件(通常为旋转按钮)。 + /// 事件参数。 + private void BtnRotateImage_Click(object sender, RoutedEventArgs e) + { + try + { + EnsureCameraService(); + _cameraService.RotationAngle = (_cameraService.RotationAngle + 1) % 4; + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"视频展台旋转失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 在启用照片校正的切换按钮被选中时,将该偏好设置为开启并保存到设置文件。 + /// + private void ToggleBtnPhotoCorrection_Checked(object sender, RoutedEventArgs e) + { + ApplyBoothButtonHighlight(ToggleBtnPhotoCorrection, true); + if (Settings?.Automation == null) return; + Settings.Automation.IsEnablePhotoCorrection = true; + SaveSettingsToFile(); + } + + /// + /// 关闭“相片校正”设置并将变更持久化到设置文件。 + /// + private void ToggleBtnPhotoCorrection_Unchecked(object sender, RoutedEventArgs e) + { + ApplyBoothButtonHighlight(ToggleBtnPhotoCorrection, false); + if (Settings?.Automation == null) return; + Settings.Automation.IsEnablePhotoCorrection = false; + SaveSettingsToFile(); + } + + /// + /// 刷新并在 CapturedPhotosStackPanel 中显示最近捕获的照片缩略图,最多显示 30 张。 + /// + /// + /// 如果 CapturedPhotosStackPanel 为 null 则不执行任何操作。该方法会清空面板现有内容,并为每张照片创建一个包含缩略图的按钮;点击按钮会将对应照片插入画布。 + /// + private void UpdateCapturedPhotosDisplay() + { + if (CapturedPhotosStackPanel == null) return; + + CapturedPhotosStackPanel.Children.Clear(); + + const double PhotoListImageWidth = 310; + const double PhotoListImageHeight = 180; + + foreach (var photo in _capturedPhotos.Take(30)) + { + var btn = new Button + { + Margin = new Thickness(0, 0, 0, 6), + Padding = new Thickness(0), + BorderThickness = new Thickness(0), + Background = System.Windows.Media.Brushes.Transparent, + Tag = photo + }; + btn.Click += (s, e) => + { + if (btn.Tag is CapturedImage p) InsertPhotoToCanvas(p); + }; + + var img = new System.Windows.Controls.Image + { + Source = photo.Thumbnail, + Stretch = System.Windows.Media.Stretch.Uniform, + Width = PhotoListImageWidth, + Height = PhotoListImageHeight + }; + btn.Content = img; + CapturedPhotosStackPanel.Children.Add(btn); + } + } + + /// + /// 将选定的捕获图片作为图像元素插入到画布中央并切换到选择工具模式。 + /// + /// 要插入的捕获图片;若为 null 或其 Image 为 null,则不进行任何操作。 + /// + /// 在画布上创建并配置一个 Image 元素(设置 Source、Stretch、默认宽度及位置),初始化其变换与事件绑定,提交插入历史记录,添加到 inkCanvas,并将当前工具切换为“选择”同时隐藏相关子面板。方法内部捕获并记录异常,不会向外抛出。 + /// + private void InsertPhotoToCanvas(CapturedImage photo) + { + if (photo?.Image == null) return; + + try + { + var img = new System.Windows.Controls.Image + { + Source = photo.Image, + Stretch = System.Windows.Media.Stretch.Uniform, + Width = 500 + }; + + double x = (inkCanvas?.ActualWidth ?? 0) / 2 - img.Width / 2; + double y = (inkCanvas?.ActualHeight ?? 0) / 2 - 200; + if (double.IsNaN(x) || double.IsInfinity(x)) x = 100; + if (double.IsNaN(y) || double.IsInfinity(y)) y = 100; + + InkCanvas.SetLeft(img, Math.Max(0, x)); + InkCanvas.SetTop(img, Math.Max(0, y)); + InitializeElementTransform(img); + BindElementEvents(img); + timeMachine.CommitElementInsertHistory(img); + + inkCanvas?.Children.Add(img); + + SetCurrentToolMode(InkCanvasEditingMode.Select); + UpdateCurrentToolMode("select"); + HideSubPanels("select"); + } + catch (Exception ex) + { + LogHelper.WriteLogToFile($"插入展台照片失败: {ex.Message}", LogHelper.LogType.Error); + } + } + + /// + /// 在离开白板模式时关闭并清理视频呈现器相关的 UI 与运行状态。 + /// + /// + /// 隐藏视频呈现侧栏、将“在画布上显示实时帧”开关取消选中、从画布中移除并隐藏所有每页的实时帧图像实例,并尝试停止相机预览。该方法在执行过程中会吞并内部异常以避免抛出至调用方。 + /// + private void VideoPresenter_OnExitWhiteboardMode() + { + try + { + CloseVideoPresenterSidebarAndReleaseResources(); + + if (BtnToggleVideoPresenterLiveOnCanvas != null) + { + BtnToggleVideoPresenterLiveOnCanvas.IsChecked = false; + } + + if (inkCanvas != null) + { + foreach (var kv in _liveFrameImageByPage.ToList()) + { + var img = kv.Value; + if (img == null) continue; + try + { + if (inkCanvas.Children.Contains(img)) + { + inkCanvas.Children.Remove(img); + } + img.Visibility = Visibility.Collapsed; + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + } + } + + } + catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } + } + + /// + /// 将一个 System.Drawing.Bitmap 转换为可跨线程使用的 WPF BitmapImage。 + /// + /// 要转换的源位图;若为 则直接返回 。 + /// 转换得到的 ;若输入为 或转换失败则返回 + private static BitmapImage ConvertBitmapToBitmapImage(Bitmap bitmap) + { + try + { + if (bitmap == null) return null; + + using (var ms = new MemoryStream()) + { + bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + ms.Position = 0; + var bi = new BitmapImage(); + bi.BeginInit(); + bi.CacheOption = BitmapCacheOption.OnLoad; + bi.StreamSource = ms; + bi.EndInit(); + bi.Freeze(); + return bi; + } + } + catch + { + return null; + } + } + + /// + /// 在给定帧中尝试检测纸张(四边形)角点,并返回按原始帧坐标排列的四个点。 + /// + /// 要检测的输入位图帧。 + /// 检测到的四个角点(按顺序:左上、右上、左下、右下),坐标以输入帧的像素空间为准;检测失败时为 null。 + /// 如果成功检测到四个角点并填充 否则(包括输入为 null 或检测过程中发生错误)。 + private static bool TryDetectPaperCorners(Bitmap frame, out List cornersOut) + { + cornersOut = null; + try + { + if (frame == null) return false; + + int targetWidth = 640; + int ow = frame.Width; + int oh = frame.Height; + double scale = 1.0; + Bitmap work = frame; + if (ow > targetWidth) + { + int nh = (int)Math.Round(oh * (targetWidth / (double)ow)); + var resize = new ResizeBilinear(targetWidth, nh); + work = resize.Apply(frame); + scale = (double)ow / targetWidth; + } + + var gray = Grayscale.CommonAlgorithms.BT709.Apply(work); + var blur = new GaussianBlur(3, 3); + blur.ApplyInPlace(gray); + var canny = new CannyEdgeDetector(); + canny.ApplyInPlace(gray); + var dilate = new Dilatation3x3(); + dilate.ApplyInPlace(gray); + + var bc = new BlobCounter + { + FilterBlobs = true, + MinHeight = 50, + MinWidth = 50, + ObjectsOrder = ObjectsOrder.Size + }; + bc.ProcessImage(gray); + var blobs = bc.GetObjectsInformation(); + var sc = new SimpleShapeChecker(); + List best = null; + double bestArea = 0; + + foreach (var blob in blobs) + { + var edgePoints = bc.GetBlobsEdgePoints(blob); + if (edgePoints == null || edgePoints.Count < 4) continue; + if (sc.IsQuadrilateral(edgePoints, out List crn)) + { + double area = Math.Abs(PolygonArea(crn)); + if (area > bestArea) + { + bestArea = area; + best = crn; + } + } + } + + if (best != null) + { + var pts = best + .Select(p => new AForge.IntPoint((int)Math.Round(p.X * scale), (int)Math.Round(p.Y * scale))) + .ToList(); + pts.Sort((a, b) => a.Y.CompareTo(b.Y)); + if (pts[0].X > pts[1].X) (pts[0], pts[1]) = (pts[1], pts[0]); + if (pts[2].X > pts[3].X) (pts[2], pts[3]) = (pts[3], pts[2]); + cornersOut = pts; + if (!ReferenceEquals(work, frame)) work.Dispose(); + gray.Dispose(); + return true; + } + + if (!ReferenceEquals(work, frame)) work.Dispose(); + gray.Dispose(); + return false; + } + catch + { + return false; + } + } + + /// + /// 将源图像中由四个角点定义的纸张区域进行透视矫正并裁切为目标尺寸的位图,目标高度为 CorrectedPaperHeight,宽度按纸张比例计算。 + /// + /// 包含待矫正纸张的源位图。 + /// 纸张在源图像中的四个角点,按顺序提供:左上 (top-left)、右上 (top-right)、左下 (bottom-left)、右下 (bottom-right)。坐标为图像像素坐标系。 + /// 透视矫正并裁切后的位图;在输入无效或矫正失败时返回 + private static Bitmap ApplyPerspectiveCorrection(Bitmap frame, List corners) + { + try + { + if (frame == null || corners == null || corners.Count != 4) return null; + var tl = corners[0]; + var tr = corners[1]; + var bl = corners[2]; + var br = corners[3]; + + double topW = Math.Sqrt((tr.X - tl.X) * (tr.X - tl.X) + (tr.Y - tl.Y) * (tr.Y - tl.Y)); + double bottomW = Math.Sqrt((br.X - bl.X) * (br.X - bl.X) + (br.Y - bl.Y) * (br.Y - bl.Y)); + double leftH = Math.Sqrt((bl.X - tl.X) * (bl.X - tl.X) + (bl.Y - tl.Y) * (bl.Y - tl.Y)); + double rightH = Math.Sqrt((br.X - tr.X) * (br.X - tr.X) + (br.Y - tr.Y) * (br.Y - tr.Y)); + + double avgW = (topW + bottomW) / 2.0; + double avgH = (leftH + rightH) / 2.0; + if (avgH <= 0) avgH = 1; + double ratio = avgW / avgH; + + int targetH = CorrectedPaperHeight; + int targetW = Math.Max(1, (int)Math.Round(targetH * ratio)); + + var orderedCorners = new List { tl, tr, br, bl }; + var qtf = new QuadrilateralTransformation(orderedCorners, targetW, targetH); + return qtf.Apply(frame); + } + catch + { + return null; + } + } + + /// + /// 计算由给定顶点按顺序构成的多边形的有向面积(使用高斯面积/鞋带公式)。 + /// + /// 按顶点顺序排列的多边形顶点列表(至少应包含三个点以形成多边形)。 + /// 多边形的有向面积;当顶点顺时针时为负值,逆时针为正值;点数少于三时返回 0。 + private static double PolygonArea(List pts) + { + int n = pts.Count; + if (n < 3) return 0; + long sum = 0; + for (int i = 0; i < n; i++) + { + var p = pts[i]; + var q = pts[(i + 1) % n]; + sum += (long)p.X * q.Y - (long)p.Y * q.X; + } + return 0.5 * sum; } } } diff --git a/Ink Canvas/Resources/Settings.cs b/Ink Canvas/Resources/Settings.cs index e0b93049..4f3baec5 100644 --- a/Ink Canvas/Resources/Settings.cs +++ b/Ink Canvas/Resources/Settings.cs @@ -151,6 +151,10 @@ namespace Ink_Canvas [JsonProperty("enableVelocityBrushTip")] public bool EnableVelocityBrushTip { get; set; } + /// 为 true 时,白板工具栏「展台」按钮启动希沃视频展台(sweclauncher),否则使用内置展台。 + [JsonProperty("launchSeewoVideoShowcaseForWhiteboardBooth")] + public bool LaunchSeewoVideoShowcaseForWhiteboardBooth { get; set; } = false; + } public enum OptionalOperation diff --git a/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml index d601c3d0..ef0c3239 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml +++ b/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml @@ -96,6 +96,13 @@ SwitchName="ToggleSwitchCompressPicturesUploaded" Toggled="ToggleSwitchCompressPicturesUploaded_Toggled" /> + + diff --git a/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml.cs index 2f69956a..fa4e1be8 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml.cs +++ b/Ink Canvas/Windows/SettingsViews/Pages/CanvasPage.xaml.cs @@ -40,6 +40,7 @@ namespace Ink_Canvas.Windows.SettingsViews.Pages CardClearCanvasAndClearTimeMachine.IsOn = settings.Canvas.ClearCanvasAndClearTimeMachine; CardClearCanvasAlsoClearImages.IsOn = settings.Canvas.ClearCanvasAlsoClearImages; CardCompressPicturesUploaded.IsOn = settings.Canvas.IsCompressPicturesUploaded; + CardLaunchSeewoVideoShowcaseForWhiteboardBooth.IsOn = settings.Canvas.LaunchSeewoVideoShowcaseForWhiteboardBooth; ComboBoxHyperbolaAsymptoteOption.SelectedIndex = (int)settings.Canvas.HyperbolaAsymptoteOption; CardShowCircleCenter.IsOn = settings.Canvas.ShowCircleCenter; int curveMode = 0; @@ -175,6 +176,13 @@ namespace Ink_Canvas.Windows.SettingsViews.Pages SettingsManager.SaveSettingsToFile(); } + private void ToggleSwitchLaunchSeewoVideoShowcaseForWhiteboardBooth_Toggled(object sender, RoutedEventArgs e) + { + if (!_isLoaded) return; + SettingsManager.Settings.Canvas.LaunchSeewoVideoShowcaseForWhiteboardBooth = CardLaunchSeewoVideoShowcaseForWhiteboardBooth.IsOn; + SettingsManager.SaveSettingsToFile(); + } + private void ComboBoxHyperbolaAsymptoteOption_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (!_isLoaded) return; From f05062f9024ac959b30d81de875febc0054a9f20 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Thu, 23 Apr 2026 23:53:52 +0800 Subject: [PATCH 088/205] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml | 11 +----- Ink Canvas/MainWindow.xaml.cs | 35 ------------------- Ink Canvas/MainWindow_cs/MW_AutoStart.cs | 11 ------ Ink Canvas/MainWindow_cs/MW_Settings.cs | 12 ------- Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs | 21 ++++------- InkCanvas.Controls/LabeledSettingsCard.xaml | 1 - 6 files changed, 8 insertions(+), 83 deletions(-) delete mode 100644 Ink Canvas/MainWindow_cs/MW_AutoStart.cs diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index b3ec97a1..15ffeff0 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -626,16 +626,7 @@ - - - - - - - - - + diff --git a/Ink Canvas/MainWindow.xaml.cs b/Ink Canvas/MainWindow.xaml.cs index 12925040..023e785e 100644 --- a/Ink Canvas/MainWindow.xaml.cs +++ b/Ink Canvas/MainWindow.xaml.cs @@ -3755,42 +3755,7 @@ namespace Ink_Canvas #region 模式切换相关 - /// - /// 模式切换开关事件处理 - /// - private void ToggleSwitchMode_Toggled(object sender, RoutedEventArgs e) - { - try - { - var toggle = sender as ToggleSwitch; - if (toggle != null) - { - Settings.ModeSettings.IsPPTOnlyMode = toggle.IsOn; - // 保存设置到文件 - SaveSettingsToFile(); - - // 如果切换到仅PPT模式,立即隐藏主窗口 - if (Settings.ModeSettings.IsPPTOnlyMode) - { - Hide(); - LogHelper.WriteLogToFile("已切换到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event); - EnsurePptOnlyVisibilityProbeTimer(); - } - else - { - StopPptOnlyVisibilityProbeTimer(); - // 如果切换到正常模式,显示主窗口 - Show(); - LogHelper.WriteLogToFile("已切换到正常模式,主窗口已显示", LogHelper.LogType.Event); - } - } - } - catch (Exception ex) - { - LogHelper.WriteLogToFile($"切换模式时出错: {ex.Message}", LogHelper.LogType.Error); - } - } /// /// 检查是否应该显示主窗口(基于PPT模式和PPT放映状态) diff --git a/Ink Canvas/MainWindow_cs/MW_AutoStart.cs b/Ink Canvas/MainWindow_cs/MW_AutoStart.cs deleted file mode 100644 index 148c5ea3..00000000 --- a/Ink Canvas/MainWindow_cs/MW_AutoStart.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ink_Canvas.Windows.SettingsViews.Helpers; - -namespace Ink_Canvas -{ - public partial class MainWindow - { - public static bool StartAutomaticallyCreate(string exeName) => AutoStartHelper.StartAutomaticallyCreate(exeName); - - public static bool StartAutomaticallyDel(string exeName) => AutoStartHelper.StartAutomaticallyDel(exeName); - } -} diff --git a/Ink Canvas/MainWindow_cs/MW_Settings.cs b/Ink Canvas/MainWindow_cs/MW_Settings.cs index 3a6bbe69..6061f90e 100644 --- a/Ink Canvas/MainWindow_cs/MW_Settings.cs +++ b/Ink Canvas/MainWindow_cs/MW_Settings.cs @@ -3063,18 +3063,6 @@ namespace Ink_Canvas catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); } } - #endregion - - #region Ink To Shape - - - - - - - - - #endregion #region Advanced diff --git a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs index 8e8c967b..1a4cecd6 100644 --- a/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs +++ b/Ink Canvas/MainWindow_cs/MW_SettingsToLoad.cs @@ -1015,22 +1015,15 @@ namespace Ink_Canvas } // ModeSettings - if (Settings.ModeSettings != null) - { - ToggleSwitchMode.IsOn = Settings.ModeSettings.IsPPTOnlyMode; - - // 根据加载的配置状态执行相应的窗口显示/隐藏逻辑 - if (isStartup && Settings.ModeSettings.IsPPTOnlyMode) - { - // 启动时如果是仅PPT模式,隐藏主窗口 - Hide(); - LogHelper.WriteLogToFile("启动时检测到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event); - } - } - else + if (Settings.ModeSettings == null) { Settings.ModeSettings = new ModeSettings(); - ToggleSwitchMode.IsOn = false; + } + + if (isStartup && Settings.ModeSettings.IsPPTOnlyMode) + { + Hide(); + LogHelper.WriteLogToFile("启动时检测到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event); } // Automation diff --git a/InkCanvas.Controls/LabeledSettingsCard.xaml b/InkCanvas.Controls/LabeledSettingsCard.xaml index dcd3eac6..850e4b8e 100644 --- a/InkCanvas.Controls/LabeledSettingsCard.xaml +++ b/InkCanvas.Controls/LabeledSettingsCard.xaml @@ -10,7 +10,6 @@ OnContent="{DynamicResource Common_On}" OffContent="{DynamicResource Common_Off}" IsOn="{Binding IsOn, RelativeSource={RelativeSource AncestorType=UserControl}, Mode=TwoWay}" - FontWeight="Bold" Toggled="ToggleSwitch_Toggled"/> From 48b0e0927876cec3a96045ded466893a69d23b35 Mon Sep 17 00:00:00 2001 From: PrefacedCorg <1876568293@qq.com> Date: Fri, 24 Apr 2026 07:13:27 +0800 Subject: [PATCH 089/205] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Ink Canvas/MainWindow.xaml | 10 +- Ink Canvas/Windows/RandWindow.xaml | 2 +- .../Windows/ScreenshotSelectorWindow.xaml | 4 +- .../SettingsViews/Pages/BasicPage.xaml | 40 ++++--- .../SettingsViews/Pages/BasicPage.xaml.cs | 91 ++++++++++++++-- .../SettingsViews/Pages/CanvasPage.xaml | 4 +- .../Windows/SettingsViews/Pages/HomePage.xaml | 63 ++++------- .../SettingsViews/Pages/HomePage.xaml.cs | 103 ++++++++++++------ .../SettingsViews/Pages/UpdatePage.xaml | 8 +- .../SettingsViews/SettingsWindow.xaml.cs | 5 + 10 files changed, 215 insertions(+), 115 deletions(-) diff --git a/Ink Canvas/MainWindow.xaml b/Ink Canvas/MainWindow.xaml index 15ffeff0..ba26217a 100644 --- a/Ink Canvas/MainWindow.xaml +++ b/Ink Canvas/MainWindow.xaml @@ -686,7 +686,7 @@ Visibility="{Binding ElementName=ToggleSwitchEnablePalmEraser, Path=IsOn, Converter={StaticResource BooleanToVisibilityConverter}}"> - + @@ -2180,7 +2180,7 @@ - @@ -2208,7 +2208,7 @@ - @@ -2857,7 +2857,7 @@ - @@ -2996,7 +2996,7 @@ - diff --git a/Ink Canvas/Windows/RandWindow.xaml b/Ink Canvas/Windows/RandWindow.xaml index ff7bf4b6..31bc0c3d 100644 --- a/Ink Canvas/Windows/RandWindow.xaml +++ b/Ink Canvas/Windows/RandWindow.xaml @@ -106,7 +106,7 @@ - ClassIsland点名 diff --git a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml index d2654a16..4e868847 100644 --- a/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml +++ b/Ink Canvas/Windows/ScreenshotSelectorWindow.xaml @@ -1,4 +1,4 @@ - - diff --git a/Ink Canvas/Windows/SettingsViews/Pages/BasicPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/BasicPage.xaml index 89d58661..a95ed041 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/BasicPage.xaml +++ b/Ink Canvas/Windows/SettingsViews/Pages/BasicPage.xaml @@ -6,18 +6,14 @@ xmlns:local="clr-namespace:Ink_Canvas.Windows.SettingsViews.Pages" xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf" - xmlns:i18n="clr-namespace:Ink_Canvas.MarkupExtensions" xmlns:sys="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" Title="基本"> - - 4 - + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ink Canvas/Windows/SettingsViews/Pages/MainInterfacePage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/MainInterfacePage.xaml.cs new file mode 100644 index 00000000..ad450b68 --- /dev/null +++ b/Ink Canvas/Windows/SettingsViews/Pages/MainInterfacePage.xaml.cs @@ -0,0 +1,88 @@ +using iNKORE.UI.WPF.Modern.Controls; +using System.Collections.ObjectModel; +using System.Windows; +using SWC = System.Windows.Controls; + +namespace Ink_Canvas.Windows.SettingsViews.Pages +{ + public partial class MainInterfacePage : SWC.Page + { + private readonly ObservableCollection _subPageItems = new(); + + public MainInterfacePage() + { + InitializeComponent(); + SubPageItems.ItemsSource = _subPageItems; + Loaded += MainInterfacePage_Loaded; + } + + private void MainInterfacePage_Loaded(object sender, RoutedEventArgs e) + { + LoadSubPages(); + } + + private void LoadSubPages() + { + _subPageItems.Clear(); + + var settingsWindow = Window.GetWindow(this) as SettingsWindow; + if (settingsWindow == null) return; + + var navView = settingsWindow.GetNavigationView(); + if (navView == null) return; + + foreach (var item in navView.MenuItems) + { + if (item is NavigationViewItem navItem) + { + string tag = navItem.Tag as string; + if (tag == "MainInterfacePage" && navItem.MenuItems.Count > 0) + { + foreach (var child in navItem.MenuItems) + { + if (child is NavigationViewItem childItem) + { + string childTag = childItem.Tag as string; + if (!string.IsNullOrEmpty(childTag)) + { + string glyph = ExtractIconGlyph(childItem); + string description = SWC.ToolTipService.GetToolTip(childItem) as string + ?? $"点击跳转到{childItem.Content}"; + + _subPageItems.Add(new SubPageNavItem + { + Header = childItem.Content?.ToString() ?? "", + Description = description, + PageTag = childTag, + IconGlyph = glyph + }); + } + } + } + break; + } + } + } + } + + private string ExtractIconGlyph(NavigationViewItem navItem) + { + if (navItem.Icon is FontIcon fontIcon) + return fontIcon.Glyph ?? "\uE737"; + + if (navItem.Icon is SymbolIcon symbolIcon) + return char.ConvertFromUtf32((int)symbolIcon.Symbol); + + return "\uE737"; + } + + private void SubPageCard_Click(object sender, RoutedEventArgs e) + { + if (sender is FrameworkElement element && element.Tag is string pageTag) + { + var settingsWindow = Window.GetWindow(this) as SettingsWindow; + settingsWindow?.NavigateToPage(pageTag); + } + } + } +} diff --git a/Ink Canvas/Windows/SettingsViews/Pages/PrivacyPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/PrivacyPage.xaml new file mode 100644 index 00000000..7f422dd3 --- /dev/null +++ b/Ink Canvas/Windows/SettingsViews/Pages/PrivacyPage.xaml @@ -0,0 +1,64 @@ + + + + + + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Ink Canvas/Windows/SettingsViews/Pages/PrivacyPage.xaml.cs b/Ink Canvas/Windows/SettingsViews/Pages/PrivacyPage.xaml.cs new file mode 100644 index 00000000..b0934c99 --- /dev/null +++ b/Ink Canvas/Windows/SettingsViews/Pages/PrivacyPage.xaml.cs @@ -0,0 +1,279 @@ +using Ink_Canvas.Helpers; +using Ink_Canvas.Windows.SettingsViews.Helpers; +using System; +using System.IO; +using System.Reflection; +using System.Windows; +using System.Windows.Controls; + +namespace Ink_Canvas.Windows.SettingsViews.Pages +{ + public partial class PrivacyPage : iNKORE.UI.WPF.Modern.Controls.Page + { + private bool _isLoaded = false; + private bool _isChangingTelemetryInternally; + private bool _isChangingTelemetryPrivacyInternally; + + public PrivacyPage() + { + InitializeComponent(); + Loaded += PrivacyPage_Loaded; + } + + private void PrivacyPage_Loaded(object sender, RoutedEventArgs e) + { + LoadSettings(); + _isLoaded = true; + } + + private void LoadSettings() + { + _isLoaded = false; + + try + { + var settings = SettingsManager.Settings; + if (settings?.Startup != null) + { + int idx = 0; + switch (settings.Startup.TelemetryUploadLevel) + { + case TelemetryUploadLevel.None: + idx = 0; + break; + case TelemetryUploadLevel.Basic: + idx = 1; + break; + case TelemetryUploadLevel.Extended: + idx = 2; + break; + default: + idx = 0; + break; + } + ComboBoxTelemetryUploadLevel.SelectedIndex = idx; + CheckBoxTelemetryPrivacyAccepted.IsChecked = settings.Startup.HasAcceptedTelemetryPrivacy; + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"加载隐私页面设置时出错: {ex.Message}"); + } + + _isLoaded = true; + } + + private void ComboBoxTelemetryUploadLevel_SelectionChanged(object sender, RoutedEventArgs e) + { + if (!_isLoaded) return; + if (_isChangingTelemetryInternally) return; + var oldLevel = SettingsManager.Settings.Startup.TelemetryUploadLevel; + var item = ComboBoxTelemetryUploadLevel?.SelectedItem as ComboBoxItem; + if (item == null) return; + + var tag = item.Tag?.ToString() ?? "0"; + var newLevel = TelemetryUploadLevel.None; + switch (tag) + { + case "1": + newLevel = TelemetryUploadLevel.Basic; + break; + case "2": + newLevel = TelemetryUploadLevel.Extended; + break; + default: + newLevel = TelemetryUploadLevel.None; + break; + } + + if (newLevel == TelemetryUploadLevel.None && + oldLevel != TelemetryUploadLevel.None && + SettingsManager.Settings.Startup.UpdateChannel != UpdateChannel.Release) + { + var result = MessageBox.Show( + "关闭匿名使用数据上传后,将无法继续使用预览/测试通道,系统会自动切换回正式通道(Release)。\n\n是否确认关闭?", + "确认关闭遥测", + MessageBoxButton.YesNo, + MessageBoxImage.Warning); + + if (result != MessageBoxResult.Yes) + { + _isChangingTelemetryInternally = true; + try + { + int idx = 0; + switch (oldLevel) + { + case TelemetryUploadLevel.Basic: + idx = 1; + break; + case TelemetryUploadLevel.Extended: + idx = 2; + break; + default: + idx = 0; + break; + } + ComboBoxTelemetryUploadLevel.SelectedIndex = idx; + } + finally + { + _isChangingTelemetryInternally = false; + } + return; + } + + SettingsManager.Settings.Startup.UpdateChannel = UpdateChannel.Release; + DeviceIdentifier.UpdateUsageChannel(UpdateChannel.Release); + } + + if (newLevel != TelemetryUploadLevel.None && !SettingsManager.Settings.Startup.HasAcceptedTelemetryPrivacy) + { + MessageBox.Show( + "在开启匿名使用数据上传前,请先阅读并勾选上方的隐私说明。", + "需要同意隐私说明", + MessageBoxButton.OK, + MessageBoxImage.Warning); + + _isChangingTelemetryInternally = true; + try + { + SettingsManager.Settings.Startup.TelemetryUploadLevel = TelemetryUploadLevel.None; + if (ComboBoxTelemetryUploadLevel != null) + { + ComboBoxTelemetryUploadLevel.SelectedIndex = 0; + } + } + finally + { + _isChangingTelemetryInternally = false; + } + + return; + } + + SettingsManager.Settings.Startup.TelemetryUploadLevel = newLevel; + SettingsManager.SaveSettingsToFile(); + } + + private void CheckBoxTelemetryPrivacyAccepted_Checked(object sender, RoutedEventArgs e) + { + if (!_isLoaded) return; + if (_isChangingTelemetryPrivacyInternally) return; + + bool isChecked = CheckBoxTelemetryPrivacyAccepted.IsChecked == true; + + if (isChecked) + { + if (!PrivacyFileExists()) + { + MessageBox.Show( + "未找到隐私说明文件(privacy / privacy.txt),暂时无法启用匿名使用数据上传。", + "隐私说明缺失", + MessageBoxButton.OK, + MessageBoxImage.Warning); + + _isChangingTelemetryPrivacyInternally = true; + try + { + CheckBoxTelemetryPrivacyAccepted.IsChecked = false; + } + finally + { + _isChangingTelemetryPrivacyInternally = false; + } + + SettingsManager.Settings.Startup.HasAcceptedTelemetryPrivacy = false; + SettingsManager.SaveSettingsToFile(); + return; + } + + var privacyWindow = new PrivacyAgreementWindow(); + bool? dialogResult = privacyWindow.ShowDialog(); + + if (dialogResult == true && privacyWindow.UserAccepted) + { + SettingsManager.Settings.Startup.HasAcceptedTelemetryPrivacy = true; + SettingsManager.SaveSettingsToFile(); + } + else + { + _isChangingTelemetryPrivacyInternally = true; + try + { + CheckBoxTelemetryPrivacyAccepted.IsChecked = false; + } + finally + { + _isChangingTelemetryPrivacyInternally = false; + } + + SettingsManager.Settings.Startup.HasAcceptedTelemetryPrivacy = false; + SettingsManager.SaveSettingsToFile(); + } + } + else + { + var result = MessageBox.Show( + "取消同意隐私说明后,将关闭匿名使用数据上传,并切回正式通道(Release)。\n\n是否确认?", + "确认取消隐私同意", + MessageBoxButton.YesNo, + MessageBoxImage.Warning); + + if (result != MessageBoxResult.Yes) + { + _isChangingTelemetryPrivacyInternally = true; + try + { + CheckBoxTelemetryPrivacyAccepted.IsChecked = true; + } + finally + { + _isChangingTelemetryPrivacyInternally = false; + } + return; + } + + _isChangingTelemetryInternally = true; + try + { + SettingsManager.Settings.Startup.TelemetryUploadLevel = TelemetryUploadLevel.None; + if (ComboBoxTelemetryUploadLevel != null) + { + ComboBoxTelemetryUploadLevel.SelectedIndex = 0; + } + } + finally + { + _isChangingTelemetryInternally = false; + } + + if (SettingsManager.Settings.Startup.UpdateChannel != UpdateChannel.Release) + { + SettingsManager.Settings.Startup.UpdateChannel = UpdateChannel.Release; + DeviceIdentifier.UpdateUsageChannel(UpdateChannel.Release); + } + + SettingsManager.Settings.Startup.HasAcceptedTelemetryPrivacy = false; + SettingsManager.SaveSettingsToFile(); + } + } + + private static bool PrivacyFileExists() + { + try + { + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = "Ink_Canvas.privacy.txt"; + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + { + return stream != null; + } + } + catch + { + return false; + } + } + } +} diff --git a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml index 806f4023..7ac3c583 100644 --- a/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml +++ b/Ink Canvas/Windows/SettingsViews/Pages/StartupPage.xaml @@ -1,25 +1,22 @@ 4 - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +