Compare commits
363 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3a917d529d | |||
| e45e8fad43 | |||
| 8364dde2e3 | |||
| 6105f8759d | |||
| c8e3bceab2 | |||
| f825211987 | |||
| aea4c2ce3c | |||
| 35c8e980f8 | |||
| 09713f70bf | |||
| ed7157163c | |||
| 86fdc04616 | |||
| ed44a22edb | |||
| 2c4d6f124e | |||
| 2664ee812c | |||
| b8b07f90cd | |||
| f39d0c0f82 | |||
| 81b291f2e6 | |||
| a25ea95c19 | |||
| 16724fd0ae | |||
| 723c0b9cdc | |||
| 39e098221c | |||
| 005ba66bc2 | |||
| d15db2d8a9 | |||
| 191fd25c66 | |||
| 5f1f190613 | |||
| 7736d88657 | |||
| cc5ba1364c | |||
| fcbfb326c0 | |||
| d6c1a1fd71 | |||
| 8db0e8c95c | |||
| 860dc43c8d | |||
| be34eda535 | |||
| 2d9901791c | |||
| ef9b211677 | |||
| 53fda7e512 | |||
| bbb2981208 | |||
| e1aa003b77 | |||
| b7e88371ac | |||
| 2b8dcfdc86 | |||
| 9691263aa5 | |||
| 95ee002765 | |||
| 786945f6c8 | |||
| 7a7c5f266d | |||
| 15884c5901 | |||
| 16f53acf42 | |||
| 2e61777469 | |||
| 46f4a0523c | |||
| c8848f6cde | |||
| 1fe66790e6 | |||
| dada05aabb | |||
| a18d476415 | |||
| 94142ec8a5 | |||
| fff52aa282 | |||
| 07ebbfbd24 | |||
| 2a17ea1bd1 | |||
| e801394dbe | |||
| 9fb18e020e | |||
| 82ec0ad1cd | |||
| 3853fd31f4 | |||
| 41a59136c7 | |||
| 8c7eeb51d0 | |||
| 4f5f3ed8fe | |||
| 207321651d | |||
| 267d9b4450 | |||
| 31aee3bd9a | |||
| a8c9734a5a | |||
| 138484ac19 | |||
| 1217ef7ef9 | |||
| 02cf10ab13 | |||
| db181d994a | |||
| ee4e9032f2 | |||
| 54fd9bcde6 | |||
| 6fbd8b6fac | |||
| 8383002b5c | |||
| 8c37b91b5b | |||
| 5a387eef96 | |||
| c27759189d | |||
| a33a399989 | |||
| 6c7c76958f | |||
| 6980abe331 | |||
| 5fc92cdd10 | |||
| 4bcc39a7ae | |||
| a044a8bc21 | |||
| f15a293a69 | |||
| 448a695503 | |||
| 3915893ee6 | |||
| 914e6eea2e | |||
| 3434ab6227 | |||
| 90ba3f7fa6 | |||
| f05bcf6cc8 | |||
| 21d5ee25ea | |||
| 13c73fbfe4 | |||
| 97dbedfed5 | |||
| e5ef1c6472 | |||
| 17945a9298 | |||
| 17c0ecc0f5 | |||
| 8c2fc15d81 | |||
| ac399773d0 | |||
| 199674feca | |||
| cb6af3e21f | |||
| efe0bb6ae2 | |||
| 4a1403eed3 | |||
| 77a7628453 | |||
| 21186d1edb | |||
| 1cb77b4f48 | |||
| a6f92a883c | |||
| 2c0b09a9ad | |||
| 003fffca2d | |||
| 28b719e9db | |||
| 4d8fbef455 | |||
| e55f8e8ea1 | |||
| 8ad987a64b | |||
| b0aff1e378 | |||
| 6239ac0b88 | |||
| 875fa9c6f0 | |||
| a7b020b0ff | |||
| b614517728 | |||
| 329e9bd933 | |||
| 73f27a9423 | |||
| ffee3aa564 | |||
| 9268ebb580 | |||
| 40fc4e89e0 | |||
| 6fd4a4f036 | |||
| 483991b85c | |||
| a0d24ea6cc | |||
| 9613285513 | |||
| 649a939a57 | |||
| f20e360c0b | |||
| c1e599971e | |||
| 21c93d38c7 | |||
| 4902559cfa | |||
| 49b22dc184 | |||
| f1f75ff015 | |||
| 6e68fa9cfc | |||
| c68596b91e | |||
| 7d52573595 | |||
| 6ac34ba8aa | |||
| 9136f1dbe3 | |||
| 98cfdb53c3 | |||
| e17387c99e | |||
| d30ed9e726 | |||
| 988af60a30 | |||
| 8b53456b5d | |||
| d23193527e | |||
| b58376847e | |||
| d0551db70a | |||
| e416151a56 | |||
| b02b496b88 | |||
| e31dbbcedc | |||
| 09ad1bac86 | |||
| a2ac761f70 | |||
| 9e5ad7e2b4 | |||
| 4f015fb155 | |||
| b4526473fa | |||
| 3ab9fcc857 | |||
| 7ef231a4c7 | |||
| da3a1f7dea | |||
| 81001542b8 | |||
| aacf5b696c | |||
| 062c0feb65 | |||
| 92f97eab78 | |||
| 31120eab09 | |||
| 9b7d718f78 | |||
| 9132c9eb14 | |||
| adebf8ec38 | |||
| 854e14f9b0 | |||
| d7faac257c | |||
| 1b3c5294d9 | |||
| f7802ba4d0 | |||
| 8e84dce73f | |||
| 620da0e645 | |||
| c4657ebb86 | |||
| 24cbaf69ea | |||
| f3ef2f7aec | |||
| 77dff81217 | |||
| 004364c3a9 | |||
| 877d702978 | |||
| a8bc9f442b | |||
| f09f05ab3c | |||
| ef5377f85c | |||
| 5dbdd7bf12 | |||
| d73e3eb0ea | |||
| b7d03d694c | |||
| b7d4983af5 | |||
| 919cf8a736 | |||
| 8a7d2ddf30 | |||
| b1640f44c2 | |||
| 6e61538dec | |||
| f789a919e1 | |||
| b9651240df | |||
| 5266368f79 | |||
| 42854ff924 | |||
| 48b0e09278 | |||
| f05062f902 | |||
| 532aa03c56 | |||
| b891cb6fe3 | |||
| 0683779e09 | |||
| b949b1651a | |||
| 259ce3dd11 | |||
| cab703b98e | |||
| a57e0496ee | |||
| df66a49ba5 | |||
| 245a29f797 | |||
| 4e8f574fdb | |||
| a5d6135f0c | |||
| 12c7fb1713 | |||
| 4dd56a4e5d | |||
| abe5992d21 | |||
| 8d74b6ee30 | |||
| 8b5797ac66 | |||
| 231b850f74 | |||
| 7003bb8426 | |||
| c4b5be783e | |||
| c38829f7d8 | |||
| fdf3633d8c | |||
| 3916476af7 | |||
| edc94547ab | |||
| 8a8cf9a679 | |||
| 21f3b99838 | |||
| 3bfa0b1d85 | |||
| de55fd43a0 | |||
| 6e68e53b1e | |||
| 4b8d89854e | |||
| 8298f7d5bb | |||
| 816153a1db | |||
| 1e8ddf4754 | |||
| d73e87b980 | |||
| b64b0a3618 | |||
| d52ae90b56 | |||
| 0045f97569 | |||
| 41be1e901d | |||
| 0a14d96e10 | |||
| 57c3fe358b | |||
| c255db6ff3 | |||
| f6484f759e | |||
| b16636d06b | |||
| b50049c822 | |||
| 220a316d70 | |||
| dbac80509a | |||
| bb82eb4891 | |||
| c1d584e3e7 | |||
| f776dac04d | |||
| b56f99fff9 | |||
| 4151dfaf1f | |||
| 836e0b0fde | |||
| 6e7a0e36f4 | |||
| a7fe7937a5 | |||
| dba59b92ac | |||
| 0d6ffa6de6 | |||
| 9edf5ee36c | |||
| 585b712c4c | |||
| d6f20a365e | |||
| f7befe387c | |||
| b1a34b7a64 | |||
| d556cf7741 | |||
| 76a046cca6 | |||
| d6385aea1c | |||
| 31bf2b5af1 | |||
| de613c8a4b | |||
| 84b626d344 | |||
| 2ca7d2a337 | |||
| ea03e8e7c7 | |||
| 778894482f | |||
| 1035f7ef92 | |||
| 6e4f8bc982 | |||
| 36e47cec43 | |||
| 8b2bc352a6 | |||
| 256b3c0887 | |||
| 77cf91e802 | |||
| ea55eb1738 | |||
| 302ef307fe | |||
| 3f3a10de7d | |||
| c73123cd23 | |||
| 73935e0f22 | |||
| 0dfe835eef | |||
| 1ec07421b5 | |||
| 441e600b5d | |||
| bd4b4bd233 | |||
| f0343c98b5 | |||
| 36ff945384 | |||
| 057fb35d00 | |||
| bcbece5bd6 | |||
| 1b01e4a5c5 | |||
| 5aad206e0d | |||
| 74344c4782 | |||
| f280358f56 | |||
| 243201502b | |||
| 8c090218d1 | |||
| 6ca1c598d4 | |||
| 5eff424b2d | |||
| 0c078ef863 | |||
| fea6576dfb | |||
| 998d829783 | |||
| 226a6942dc | |||
| 52a3d184c7 | |||
| 44cdab063c | |||
| 24bc2cf138 | |||
| e6b707ea51 | |||
| 6183011952 | |||
| d80a59556e | |||
| ebbe018bae | |||
| bad05f77b5 | |||
| ea375a9ce6 | |||
| d3382d7856 | |||
| acf0c17d7a | |||
| 83568c8b33 | |||
| cc80529498 | |||
| 002970921d | |||
| ea74592e89 | |||
| fa38d3d664 | |||
| cffedb8cb7 | |||
| c77beb662e | |||
| 77dd83c2d6 | |||
| ceb99cea2b | |||
| 04a8224484 | |||
| 2f54e9d7b3 | |||
| 1165e5bbf2 | |||
| 24c6ca60a3 | |||
| 8f3b65c6d4 | |||
| 4835e3db50 | |||
| 7a30c93ff5 | |||
| 1fca17d557 | |||
| 67a70fd6e4 | |||
| 2e8660de26 | |||
| 069a478559 | |||
| c70d8e1c4e | |||
| 379d514bb5 | |||
| 25dc6a00d3 | |||
| eb2f65e6e5 | |||
| 16c86cd02d | |||
| 9279783fc3 | |||
| 742a62b6ff | |||
| 140e92eeda | |||
| 34c2dab82a | |||
| fa2366c373 | |||
| e2d898df14 | |||
| cacc67b11d | |||
| c890f95092 | |||
| 7c9a5f8265 | |||
| 7eafcb02cb | |||
| d4517d3c53 | |||
| 62a9a097aa | |||
| e7fa1caf6c | |||
| e347443a0a | |||
| b77e540863 | |||
| df4168e8fa | |||
| 576f86ce48 | |||
| c3335c0c24 | |||
| 26f79da2f9 | |||
| 11cb1815ad | |||
| a6fdb07e8b | |||
| ffdb3cd431 | |||
| 5f9b01ef04 | |||
| ca53624149 | |||
| 6484450ad3 | |||
| 7c97b683ea | |||
| 378b3e73f2 | |||
| a2f21357b6 | |||
| e0f35450e1 | |||
| cc9f58fb6a | |||
| e8be85141b | |||
| 1bf57ea2f5 | |||
| 20441543f0 |
+17
-4
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "https://www.schemastore.org/all-contributors.json",
|
||||
"projectName": "community",
|
||||
"projectOwner": "InkCanvasForClass",
|
||||
"files": [
|
||||
@@ -6,7 +7,7 @@
|
||||
],
|
||||
"commitType": "docs",
|
||||
"commitConvention": "angular",
|
||||
"contributorsPerLine": 7,
|
||||
"contributorsPerLine": 5,
|
||||
"contributors": [
|
||||
{
|
||||
"login": "CJKmkp",
|
||||
@@ -100,7 +101,8 @@
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/156585442?v=4",
|
||||
"profile": "https://github.com/Tayasui-rainnya",
|
||||
"contributions": [
|
||||
"design"
|
||||
"design",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -110,7 +112,8 @@
|
||||
"profile": "https://github.com/doudou0720",
|
||||
"contributions": [
|
||||
"code",
|
||||
"blog"
|
||||
"blog",
|
||||
"infra"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -140,6 +143,16 @@
|
||||
"infra",
|
||||
"blog"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Hao3288",
|
||||
"name": "NoobHao",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/119276078?v=4",
|
||||
"profile": "https://github.com/Hao3288",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
}
|
||||
]
|
||||
],
|
||||
"repoType": "github"
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ name: .NET Build & Package
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, beta ]
|
||||
branches: [ net6 ]
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
@@ -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()
|
||||
|
||||
@@ -3,7 +3,7 @@ name: PR Check
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
branches: [ main, beta ]
|
||||
branches: [ main, net6 ]
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
|
||||
@@ -43,7 +43,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
|
||||
@@ -71,7 +71,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
|
||||
|
||||
@@ -236,7 +236,7 @@ jobs:
|
||||
- name: Check if exe file is generated
|
||||
id: check-exe
|
||||
run: |
|
||||
$exePath = "Ink Canvas\bin\Release\${{ matrix.architecture }}\net472\InkCanvasForClass.exe"
|
||||
$exePath = "Ink Canvas\bin\Release\${{ matrix.architecture }}\net6.0-windows10.0.19041.0\InkCanvasForClass.exe"
|
||||
|
||||
if (Test-Path $exePath) {
|
||||
echo "build_success=true" >> $env:GITHUB_OUTPUT
|
||||
@@ -284,7 +284,7 @@ jobs:
|
||||
New-Item -ItemType Directory -Path "release" -Force
|
||||
|
||||
# 复制发布文件(使用架构特定的路径)
|
||||
Copy-Item "Ink Canvas\bin\Release\$architecture\net472\*" "release/" -Recurse -Force
|
||||
Copy-Item "Ink Canvas\bin\Release\$architecture\net6.0-windows10.0.19041.0\*" "release/" -Recurse -Force
|
||||
|
||||
# 创建压缩包
|
||||
Compress-Archive -Path "release/*" -DestinationPath $archiveName -Force
|
||||
@@ -305,6 +305,7 @@ jobs:
|
||||
$issContent = $issContent -replace '#define MyAppVersion ".*"', "#define MyAppVersion `"$version`""
|
||||
|
||||
# 替换源文件路径为相对路径(考虑到ISS文件在build目录下,需要返回上级目录)
|
||||
$issContent = $issContent -replace 'Source: "release\\\*";', 'Source: "../release/*";'
|
||||
$issContent = $issContent -replace 'Source: ".*\\{#MyAppExeName}";', 'Source: "../release/{#MyAppExeName}";'
|
||||
$issContent = $issContent -replace 'Source: ".*\\InkCanvasForClass.exe.config";', 'Source: "../release/InkCanvasForClass.exe.config";'
|
||||
|
||||
@@ -750,5 +751,5 @@ jobs:
|
||||
/repos/InkCanvasForClass/community/contents/AutomaticUpdateVersionControl.txt \
|
||||
-f message="Update AutomaticUpdateVersionControl.txt" \
|
||||
-f content="$CONTENT" \
|
||||
-f branch="beta" \
|
||||
-f branch="net6" \
|
||||
${SHA:+-f sha="$SHA"}
|
||||
|
||||
+2
-1
@@ -429,4 +429,5 @@ FodyWeavers.xsd
|
||||
|
||||
# Telemetry DSN configuration file (contains sensitive information)
|
||||
telemetry_dsn.txt
|
||||
**/telemetry_dsn.txt
|
||||
**/telemetry_dsn.txt
|
||||
.trae/skills/migrate-toggle-switch/SKILL.md
|
||||
|
||||
+69
-2
@@ -1,9 +1,16 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.33530.505
|
||||
# Visual Studio Version 18
|
||||
VisualStudioVersion = 18.4.11626.88 stable
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InkCanvasForClass", "Ink Canvas\InkCanvasForClass.csproj", "{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.PluginSdk", "InkCanvas.PluginSdk\InkCanvas.PluginSdk.csproj", "{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.Controls", "InkCanvas.Controls\InkCanvas.Controls.csproj", "{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvas.IACoreHelper", "InkCanvas.IACoreHelper\InkCanvas.IACoreHelper.csproj", "{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -38,6 +45,66 @@ Global
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6A0B1FE5-5D4A-EB5D-8C4F-A1F107FD7556}.Release|x86.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890}.Release|x86.Build.0 = Release|Any CPU
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|Any CPU.ActiveCfg = Debug|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|Any CPU.Build.0 = Debug|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM.ActiveCfg = Debug|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM.Build.0 = Debug|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM64.ActiveCfg = Debug|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|ARM64.Build.0 = Debug|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x64.ActiveCfg = Debug|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x64.Build.0 = Debug|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Debug|x86.Build.0 = Debug|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|Any CPU.ActiveCfg = Release|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|Any CPU.Build.0 = Release|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM.ActiveCfg = Release|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM.Build.0 = Release|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM64.ActiveCfg = Release|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|ARM64.Build.0 = Release|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x64.ActiveCfg = Release|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x64.Build.0 = Release|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x86.ActiveCfg = Release|x86
|
||||
{B1A2C3D4-E5F6-7890-ABCD-EF1234567891}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
+35
-6
@@ -1,16 +1,15 @@
|
||||
<Application x:Class="Ink_Canvas.App"
|
||||
<Application x:Class="Ink_Canvas.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
xmlns:tb="http://www.hardcodet.net/taskbar"
|
||||
xmlns:props="clr-namespace:Ink_Canvas.Properties"
|
||||
xmlns:tb="clr-namespace:H.NotifyIcon;assembly=H.NotifyIcon.Wpf"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
>
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<Style TargetType="ui:ScrollViewerEx">
|
||||
<EventSetter Event="PreviewMouseWheel" Handler="ScrollViewer_PreviewMouseWheel"/>
|
||||
</Style>
|
||||
<FontFamily x:Key="HarmonyOSFont">./Resources/Fonts/#HarmonyOS Sans SC</FontFamily>
|
||||
<ContextMenu Opened="SysTrayMenu_Opened" Closed="SysTrayMenu_Closed" x:Shared="false" x:Key="SysTrayMenu" Padding="6" ui:ThemeManager.RequestedTheme="Light">
|
||||
<MenuItem IsCheckable="True" IsChecked="False" Checked="HideICCMainWindowTrayIconMenuItem_Checked" Unchecked="HideICCMainWindowTrayIconMenuItem_UnChecked" Name="HideICCMainWindowTrayIconMenuItem">
|
||||
<MenuItem.Header>
|
||||
@@ -32,6 +31,36 @@
|
||||
</Image>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Name="TempShowMainWindowTrayIconMenuItem" Click="TempShowMainWindowTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="{x:Static props:Strings.Tray_TempShowMainWindow}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="28" Height="28" Margin="-2">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#27272a" Geometry="F0 M24,24z M0,0z M5,6C4.73478,6 4.48043,6.10536 4.29289,6.29289 4.10536,6.48043 4,6.73478 4,7L4,17C4,17.2652 4.10536,17.5196 4.29289,17.7071 4.48043,17.8946 4.73478,18 5,18L19,18C19.2652,18 19.5196,17.8946 19.7071,17.7071 19.8946,17.5196 20,17.2652 20,17L20,7C20,6.73478 19.8946,6.48043 19.7071,6.29289 19.5196,6.10536 19.2652,6 19,6L5,6z M2.87868,4.87868C3.44129,4.31607,4.20435,4,5,4L19,4C19.7957,4 20.5587,4.31607 21.1213,4.87868 21.6839,5.44129 22,6.20435 22,7L22,17C22,17.7957 21.6839,18.5587 21.1213,19.1213 20.5587,19.6839 19.7957,20 19,20L5,20C4.20435,20 3.44129,19.6839 2.87868,19.1213 2.31607,18.5587 2,17.7956 2,17L2,7C2,6.20435,2.31607,5.44129,2.87868,4.87868z M5,8C5,7.44772,5.44772,7,6,7L6.01,7C6.56228,7 7.01,7.44772 7.01,8 7.01,8.55228 6.56228,9 6.01,9L6,9C5.44772,9,5,8.55228,5,8z M9,7C8.44772,7 8,7.44772 8,8 8,8.55228 8.44772,9 9,9L9.01,9C9.56228,9 10.01,8.55228 10.01,8 10.01,7.44772 9.56228,7 9.01,7L9,7z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<MenuItem Name="OpenSettingsTrayIconMenuItem" Click="OpenSettingsTrayIconMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ikw:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="{x:Static props:Strings.Tray_OpenSettings}" />
|
||||
</ikw:SimpleStackPanel>
|
||||
</MenuItem.Header>
|
||||
<MenuItem.Icon>
|
||||
<Image Width="28" Height="28" Margin="-2" Source="/Resources/Icons-Fluent/ic_fluent_settings_24_regular.png" />
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator Margin="0,3" />
|
||||
<MenuItem Name="DisableAllHotkeysMenuItem" Click="DisableAllHotkeysMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
@@ -232,7 +261,7 @@
|
||||
ToolTipText="InkCanvasForClass"
|
||||
ContextMenu="{StaticResource SysTrayMenu}"
|
||||
IconSource="/Resources/icc.ico"/>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemeResources/>
|
||||
<ui:XamlControlsResources />
|
||||
<ResourceDictionary Source="Resources/SeewoImageDictionary.xaml"/>
|
||||
|
||||
+326
-150
@@ -1,5 +1,6 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using H.NotifyIcon;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Plugins;
|
||||
using Ink_Canvas.Properties;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using Microsoft.Win32;
|
||||
@@ -7,6 +8,7 @@ using Newtonsoft.Json;
|
||||
using Sentry;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@@ -17,6 +19,7 @@ using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Threading;
|
||||
using Application = System.Windows.Application;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
@@ -32,6 +35,20 @@ namespace Ink_Canvas
|
||||
{
|
||||
Mutex mutex;
|
||||
|
||||
public void ReleaseMutexForRestart()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (mutex != null)
|
||||
{
|
||||
mutex.ReleaseMutex();
|
||||
mutex.Dispose();
|
||||
mutex = null;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
public static string[] StartArgs;
|
||||
public static string RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
|
||||
|
||||
@@ -61,9 +78,16 @@ namespace Ink_Canvas
|
||||
private static string lastErrorMessage = string.Empty;
|
||||
// 新增:是否已初始化崩溃监听器
|
||||
private static bool crashListenersInitialized;
|
||||
private IntPtr processDestroyHook = IntPtr.Zero;
|
||||
private IntPtr monitoredMainWindowHandle = IntPtr.Zero;
|
||||
private bool mainWindowDestroyedLogged;
|
||||
private WinEventDelegate processDestroyHookCallback;
|
||||
// 新增:启动画面相关
|
||||
private static SplashScreen _splashScreen;
|
||||
private static bool _isSplashScreenShown = false;
|
||||
private static System.Resources.ResourceSet _pendingLocalizedResourceSet;
|
||||
private static readonly Stopwatch startupStopwatch = new Stopwatch();
|
||||
private static readonly Stopwatch splashStopwatch = new Stopwatch();
|
||||
|
||||
[DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern int SetCurrentProcessExplicitAppUserModelID(string appId);
|
||||
@@ -192,15 +216,13 @@ namespace Ink_Canvas
|
||||
// 尝试注册Windows关闭消息监听
|
||||
SetConsoleCtrlHandler(ConsoleCtrlHandler, true);
|
||||
|
||||
// 如果系统支持,添加Windows Management Instrumentation监听器
|
||||
try
|
||||
{
|
||||
// 使用反射动态加载和调用WMI
|
||||
TrySetupWmiMonitoring();
|
||||
TrySetupTerminationMonitoring();
|
||||
}
|
||||
catch (Exception wmiEx)
|
||||
catch (Exception monitorEx)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置WMI进程监控失败: {wmiEx.Message}", LogHelper.LogType.Warning);
|
||||
LogHelper.WriteLogToFile($"设置终止监控失败: {monitorEx.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
crashListenersInitialized = true;
|
||||
@@ -212,80 +234,114 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 动态加载WMI监控
|
||||
private void TrySetupWmiMonitoring()
|
||||
private void TrySetupTerminationMonitoring()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查System.Management程序集是否可用
|
||||
var assemblyName = "System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a";
|
||||
var assembly = Assembly.Load(assemblyName);
|
||||
if (assembly == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到System.Management程序集,跳过WMI监控", LogHelper.LogType.Warning);
|
||||
return;
|
||||
}
|
||||
processDestroyHookCallback = OnWinEventMainWindowDestroyed;
|
||||
|
||||
// 使用反射创建WMI查询
|
||||
var watcherType = assembly.GetType("System.Management.ManagementEventWatcher");
|
||||
if (watcherType == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到ManagementEventWatcher类型,跳过WMI监控", LogHelper.LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建WMI查询字符串
|
||||
string queryString = $"SELECT * FROM __InstanceDeletionEvent WITHIN 1 WHERE TargetInstance ISA 'Win32_Process' AND TargetInstance.ProcessId = {currentProcessId}";
|
||||
|
||||
// 创建ManagementEventWatcher实例
|
||||
object watcher = Activator.CreateInstance(watcherType, queryString);
|
||||
|
||||
// 获取EventArrived事件信息
|
||||
var eventInfo = watcherType.GetEvent("EventArrived");
|
||||
if (eventInfo == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到EventArrived事件,跳过WMI监控", LogHelper.LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建委托并订阅事件
|
||||
Type delegateType = eventInfo.EventHandlerType;
|
||||
var handler = Delegate.CreateDelegate(delegateType, this, GetType().GetMethod("WmiEventHandler", BindingFlags.NonPublic | BindingFlags.Instance));
|
||||
eventInfo.AddEventHandler(watcher, handler);
|
||||
|
||||
// 启动监听
|
||||
var startMethod = watcherType.GetMethod("Start");
|
||||
startMethod.Invoke(watcher, null);
|
||||
|
||||
LogHelper.WriteLogToFile("已成功启动WMI进程监控");
|
||||
// 等主窗口句柄可用后再开始监听
|
||||
Dispatcher.BeginInvoke(new Action(BindMainWindowLifecycle), DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"动态加载WMI监控失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
LogHelper.WriteLogToFile($"初始化终止监控失败: {ex.GetType().FullName}: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
// WMI事件处理方法
|
||||
private void WmiEventHandler(object sender, EventArgs e)
|
||||
private void BindMainWindowLifecycle()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 尝试从事件参数中提取信息
|
||||
dynamic eventArgs = e;
|
||||
dynamic newEvent = eventArgs.NewEvent;
|
||||
if (newEvent != null)
|
||||
if (Current?.MainWindow == null)
|
||||
{
|
||||
dynamic targetInstance = newEvent["TargetInstance"];
|
||||
if (targetInstance != null)
|
||||
{
|
||||
string processName = targetInstance["Name"]?.ToString() ?? "未知进程";
|
||||
WriteCrashLog($"WMI检测到进程{processName}(ID:{currentProcessId})已终止");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Current.MainWindow.SourceInitialized -= MainWindow_SourceInitialized;
|
||||
Current.MainWindow.SourceInitialized += MainWindow_SourceInitialized;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void MainWindow_SourceInitialized(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!(sender is Window window))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
monitoredMainWindowHandle = new WindowInteropHelper(window).Handle;
|
||||
if (monitoredMainWindowHandle == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterMainWindowDestroyHook();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void RegisterMainWindowDestroyHook()
|
||||
{
|
||||
if (processDestroyHook != IntPtr.Zero || monitoredMainWindowHandle == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
processDestroyHook = SetWinEventHook(
|
||||
EVENT_OBJECT_DESTROY,
|
||||
EVENT_OBJECT_DESTROY,
|
||||
IntPtr.Zero,
|
||||
processDestroyHookCallback,
|
||||
(uint)currentProcessId,
|
||||
0,
|
||||
WINEVENT_OUTOFCONTEXT);
|
||||
|
||||
if (processDestroyHook == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWinEventMainWindowDestroyed(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
|
||||
{
|
||||
if (eventType != EVENT_OBJECT_DESTROY || mainWindowDestroyedLogged)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (idObject != OBJID_WINDOW || idChild != CHILDID_SELF)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (hwnd != monitoredMainWindowHandle || hwnd == IntPtr.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
mainWindowDestroyedLogged = true;
|
||||
}
|
||||
|
||||
private void CleanupTerminationMonitoring()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (processDestroyHook != IntPtr.Zero)
|
||||
{
|
||||
UnhookWinEvent(processDestroyHook);
|
||||
processDestroyHook = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理WMI事件时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,6 +350,19 @@ namespace Ink_Canvas
|
||||
private static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);
|
||||
|
||||
private delegate bool ConsoleCtrlDelegate(int ctrlType);
|
||||
private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
|
||||
|
||||
private const uint EVENT_OBJECT_DESTROY = 0x8001;
|
||||
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
|
||||
private const int OBJID_WINDOW = 0;
|
||||
private const int CHILDID_SELF = 0;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
|
||||
|
||||
private static bool ConsoleCtrlHandler(int ctrlType)
|
||||
{
|
||||
@@ -428,6 +497,7 @@ namespace Ink_Canvas
|
||||
// 处理进程退出事件
|
||||
private void CurrentDomain_ProcessExit(object sender, EventArgs e)
|
||||
{
|
||||
CleanupTerminationMonitoring();
|
||||
TimeSpan runDuration = DateTime.Now - appStartTime;
|
||||
string durationText = FormatTimeSpan(runDuration);
|
||||
WriteCrashLog($"应用程序退出,运行时长: {durationText}");
|
||||
@@ -476,6 +546,7 @@ namespace Ink_Canvas
|
||||
_splashScreen.Show();
|
||||
_isSplashScreenShown = true;
|
||||
splashScreenStartTime = DateTime.Now;
|
||||
splashStopwatch.Restart();
|
||||
LogHelper.WriteLogToFile("启动画面已显示");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -695,20 +766,24 @@ namespace Ink_Canvas
|
||||
{
|
||||
appStartTime = DateTime.Now;
|
||||
appStartupStartTime = DateTime.Now;
|
||||
startupStopwatch.Restart();
|
||||
|
||||
TryApplyPreferredLanguageFromSettings();
|
||||
|
||||
_pendingLocalizedResourceSet = Strings.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
|
||||
|
||||
// 根据设置决定是否显示启动画面
|
||||
if (ShouldShowSplashScreen() && !IsLaunchByFileOrUri(e.Args))
|
||||
{
|
||||
ShowSplashScreen();
|
||||
SetSplashMessage(Strings.GetString("Splash_Starting"));
|
||||
SetSplashProgress(20);
|
||||
await Task.Delay(500);
|
||||
SetSplashProgress(25);
|
||||
|
||||
// 强制刷新UI,确保启动画面显示
|
||||
Application.Current.Dispatcher.Invoke(() => { }, DispatcherPriority.Render);
|
||||
}
|
||||
|
||||
await Task.Delay(500);
|
||||
await Task.Delay(100);
|
||||
RootPath = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
|
||||
|
||||
LogHelper.NewLog(string.Format("Ink Canvas Starting (Version: {0})", Assembly.GetExecutingAssembly().GetName().Version));
|
||||
@@ -739,64 +814,13 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile("App | 检测到最终应用启动(更新后的应用)");
|
||||
}
|
||||
|
||||
// 释放IACore相关DLL
|
||||
if (_isSplashScreenShown)
|
||||
{
|
||||
SetSplashMessage("正在初始化组件...");
|
||||
SetSplashProgress(40);
|
||||
await Task.Delay(500);
|
||||
}
|
||||
try
|
||||
{
|
||||
IACoreDllExtractor.ExtractIACoreDlls();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 释放UIAccess DLL
|
||||
if (_isSplashScreenShown)
|
||||
{
|
||||
SetSplashMessage("正在初始化组件...");
|
||||
SetSplashProgress(50);
|
||||
await Task.Delay(300);
|
||||
}
|
||||
try
|
||||
{
|
||||
UIAccessDllExtractor.ExtractUIAccessDlls();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放UIAccess DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 记录应用启动(设备标识符)
|
||||
if (_isSplashScreenShown)
|
||||
{
|
||||
SetSplashMessage("正在加载配置...");
|
||||
SetSplashProgress(60);
|
||||
await Task.Delay(500);
|
||||
SetSplashProgress(50);
|
||||
await Task.Delay(100);
|
||||
}
|
||||
DeviceIdentifier.RecordAppLaunch();
|
||||
try
|
||||
{
|
||||
var systemVersion = DeviceIdentifier.GetSystemVersion();
|
||||
if (!string.IsNullOrWhiteSpace(systemVersion))
|
||||
{
|
||||
SentrySdk.ConfigureScope(scope =>
|
||||
{
|
||||
scope.SetTag("system_version", systemVersion);
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"App | 初始化系统版本遥测标签失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
LogHelper.WriteLogToFile($"App | 设备ID: {DeviceIdentifier.GetDeviceId()}");
|
||||
LogHelper.WriteLogToFile($"App | 使用频率: {DeviceIdentifier.GetUsageFrequency()}");
|
||||
LogHelper.WriteLogToFile($"App | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}");
|
||||
|
||||
// 处理更新模式启动
|
||||
bool isUpdateMode = AutoUpdateHelper.HandleUpdateModeStartup(e.Args);
|
||||
@@ -831,7 +855,7 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"App | 清理更新标记文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1068,39 +1092,59 @@ namespace Ink_Canvas
|
||||
if (_isSplashScreenShown)
|
||||
{
|
||||
SetSplashMessage("正在初始化主界面...");
|
||||
SetSplashProgress(80);
|
||||
await Task.Delay(500);
|
||||
SetSplashProgress(75);
|
||||
}
|
||||
var mainWindow = new MainWindow();
|
||||
MainWindow = mainWindow;
|
||||
|
||||
// 注册 InkCanvas 服务供插件使用
|
||||
try
|
||||
{
|
||||
var inkCanvasService = new Plugins.InkCanvasService(mainWindow);
|
||||
Plugins.PluginManager.Instance.RegisterService<Plugins.IInkCanvasService>(inkCanvasService);
|
||||
LogHelper.WriteLogToFile("InkCanvasService registered for plugins");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Failed to register InkCanvasService: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var appRestartService = new Plugins.AppRestartService();
|
||||
Plugins.PluginManager.Instance.RegisterService<Plugins.IAppRestartService>(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) =>
|
||||
{
|
||||
isStartupComplete = true;
|
||||
startupCompleteHeartbeat = DateTime.Now;
|
||||
if (_isSplashScreenShown && splashScreenStartTime != DateTime.MinValue)
|
||||
if (_isSplashScreenShown && splashStopwatch.IsRunning)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动完成心跳已记录,启动画面显示时长: {(startupCompleteHeartbeat - splashScreenStartTime).TotalSeconds:F2}秒");
|
||||
LogHelper.WriteLogToFile($"启动完成心跳已记录,启动画面显示时长: {splashStopwatch.Elapsed.TotalSeconds:F2}秒");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动完成心跳已记录");
|
||||
}
|
||||
LogHelper.WriteLogToFile($"启动时长: {(startupCompleteHeartbeat - appStartupStartTime).TotalSeconds:F2}秒");
|
||||
LogHelper.WriteLogToFile($"启动时长: {startupStopwatch.Elapsed.TotalSeconds:F2}秒");
|
||||
|
||||
if (_isSplashScreenShown)
|
||||
{
|
||||
SetSplashMessage("完成初始化...");
|
||||
SetSplashProgress(80);
|
||||
Task.Delay(300).ContinueWith(_ =>
|
||||
SetSplashMessage("启动完成!");
|
||||
SetSplashProgress(100);
|
||||
Task.Delay(100).ContinueWith(_ =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
SetSplashMessage("启动完成!");
|
||||
SetSplashProgress(100);
|
||||
// 延迟关闭启动画面,让用户看到完成消息
|
||||
Task.Delay(500).ContinueWith(__ =>
|
||||
Task.Delay(100).ContinueWith(__ =>
|
||||
{
|
||||
Dispatcher.Invoke(() => CloseSplashScreen());
|
||||
});
|
||||
@@ -1110,6 +1154,19 @@ namespace Ink_Canvas
|
||||
};
|
||||
|
||||
mainWindow.Show();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(600);
|
||||
Dispatcher.Invoke(() => _taskbar?.ForceCreate());
|
||||
});
|
||||
_ = Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (_pendingLocalizedResourceSet != null)
|
||||
{
|
||||
LoadLocalizedResources(_pendingLocalizedResourceSet);
|
||||
_pendingLocalizedResourceSet = null;
|
||||
}
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
|
||||
// 处理启动时的URI参数
|
||||
string startupUriArg = e.Args.FirstOrDefault(a => a.StartsWith("icc:", StringComparison.OrdinalIgnoreCase));
|
||||
@@ -1117,7 +1174,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
LogHelper.WriteLogToFile($"App | 处理启动URI参数: {startupUriArg}", LogHelper.LogType.Event);
|
||||
// 延迟一点执行,确保窗口初始化完成
|
||||
Task.Delay(1000).ContinueWith(_ =>
|
||||
_ = Task.Delay(1000).ContinueWith(_ =>
|
||||
{
|
||||
mainWindow.Dispatcher.Invoke(() =>
|
||||
{
|
||||
@@ -1126,40 +1183,139 @@ namespace Ink_Canvas
|
||||
});
|
||||
}
|
||||
|
||||
// 注册.icstk文件关联
|
||||
_ = RunDeferredStartupTasksAsync();
|
||||
|
||||
}
|
||||
|
||||
private async Task RunDeferredStartupTasksAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("开始注册.icstk文件关联");
|
||||
FileAssociationManager.RegisterFileAssociation();
|
||||
FileAssociationManager.ShowFileAssociationStatus();
|
||||
await Task.Delay(1200);
|
||||
|
||||
try
|
||||
{
|
||||
IACoreDllExtractor.ExtractIACoreDlls();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var shapeMode = ShapeRecognitionRouter.FromSettingsInt(
|
||||
Ink_Canvas.Windows.SettingsViews.Helpers.SettingsManager.Settings?.InkToShape?.ShapeRecognitionEngine ?? 0);
|
||||
if (!ShapeRecognitionRouter.ResolveUseWinRt(shapeMode))
|
||||
{
|
||||
LogHelper.WriteLogToFile("启动 IACore IPC 辅助进程");
|
||||
bool ipcStarted = IpcIACoreClient.Instance.Start();
|
||||
LogHelper.WriteLogToFile($"IACore IPC 辅助进程{(ipcStarted ? "启动成功" : "启动失败")}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动 IACore IPC 辅助进程时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("开始注册.icstk文件关联");
|
||||
FileAssociationManager.RegisterFileAssociation();
|
||||
FileAssociationManager.ShowFileAssociationStatus();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("启动IPC监听器");
|
||||
FileAssociationManager.StartIpcListener();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动IPC监听器时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("初始化上传帮助类");
|
||||
Helpers.UploadHelper.Initialize();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化上传帮助类时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("开始加载插件");
|
||||
await PluginManager.Instance.LoadAllAsync();
|
||||
LogHelper.WriteLogToFile(string.Format("插件加载完成,共加载 {0} 个插件", PluginManager.Instance.Plugins.Count));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile(string.Format("加载插件时出错: {0}", ex.Message), LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(1500);
|
||||
DeviceIdentifier.RecordAppLaunch();
|
||||
var systemVersion = DeviceIdentifier.GetSystemVersion();
|
||||
if (!string.IsNullOrWhiteSpace(systemVersion))
|
||||
{
|
||||
SentrySdk.ConfigureScope(scope =>
|
||||
{
|
||||
scope.SetTag("system_version", systemVersion);
|
||||
});
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"App | 设备ID: {DeviceIdentifier.GetDeviceId()}");
|
||||
LogHelper.WriteLogToFile($"App | 使用频率: {DeviceIdentifier.GetUsageFrequency()}");
|
||||
LogHelper.WriteLogToFile($"App | 更新优先级: {DeviceIdentifier.GetUpdatePriority()}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"App | 初始化设备统计与遥测标签失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.WriteLogToFile($"启动阶段任务执行失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 启动IPC监听器
|
||||
private void TryApplyPreferredLanguageFromSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("启动IPC监听器");
|
||||
FileAssociationManager.StartIpcListener();
|
||||
var settingsPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Configs", "Settings.json");
|
||||
if (!File.Exists(settingsPath)) return;
|
||||
|
||||
var json = File.ReadAllText(settingsPath);
|
||||
dynamic obj = JsonConvert.DeserializeObject(json);
|
||||
string preferredLanguage = obj?["appearance"]?["language"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(preferredLanguage))
|
||||
{
|
||||
LocalizationHelper.TrySetCulture(preferredLanguage);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动IPC监听器时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.WriteLogToFile($"启动时预加载语言失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化上传帮助类
|
||||
try
|
||||
private void LoadLocalizedResources(System.Resources.ResourceSet resourceSet)
|
||||
{
|
||||
foreach (System.Collections.DictionaryEntry entry in resourceSet)
|
||||
{
|
||||
LogHelper.WriteLogToFile("初始化上传帮助类");
|
||||
Helpers.UploadHelper.Initialize();
|
||||
if (entry.Key is string key && entry.Value is string value)
|
||||
Current.Resources[key] = value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化上传帮助类时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
@@ -1436,6 +1592,26 @@ namespace Ink_Canvas
|
||||
|
||||
private void App_Exit(object sender, ExitEventArgs e)
|
||||
{
|
||||
CleanupTerminationMonitoring();
|
||||
|
||||
try
|
||||
{
|
||||
IpcIACoreClient.Instance.Dispose();
|
||||
}
|
||||
catch { }
|
||||
|
||||
// 卸载所有插件
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("正在卸载插件...");
|
||||
PluginManager.Instance.UnloadAll();
|
||||
LogHelper.WriteLogToFile("插件卸载完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"卸载插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 仅在软件内主动退出时关闭看门狗,并写入退出信号
|
||||
try
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
@@ -43,5 +44,5 @@ using System.Windows;
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.7.18.9")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.9")]
|
||||
[assembly: AssemblyVersion("1.7.18.10")]
|
||||
[assembly: AssemblyFileVersion("1.7.18.10")]
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<UserControl x:Class="Ink_Canvas.Controls.ImageSelectionOverlay"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
HorizontalAlignment="Left" VerticalAlignment="Top"
|
||||
Background="{x:Null}"
|
||||
IsHitTestVisible="True">
|
||||
<Canvas x:Name="OverlayRoot" Background="Transparent" IsHitTestVisible="True" ClipToBounds="False">
|
||||
<Rectangle x:Name="MoveSurface"
|
||||
Fill="Transparent"
|
||||
Cursor="SizeAll"
|
||||
IsHitTestVisible="True" />
|
||||
<Rectangle x:Name="FrameBorder"
|
||||
Stroke="#22D3A9"
|
||||
StrokeThickness="1.5"
|
||||
Fill="Transparent"
|
||||
IsHitTestVisible="False"
|
||||
SnapsToDevicePixels="True" />
|
||||
|
||||
<Line x:Name="RotationLine"
|
||||
Stroke="#22D3A9"
|
||||
StrokeThickness="1.5"
|
||||
IsHitTestVisible="False"
|
||||
SnapsToDevicePixels="True" />
|
||||
|
||||
<Ellipse x:Name="RotationHandle"
|
||||
Width="14" Height="14"
|
||||
Fill="#22D3A9"
|
||||
Stroke="White"
|
||||
StrokeThickness="2"
|
||||
Cursor="Hand" />
|
||||
|
||||
<Ellipse x:Name="TopLeftHandle"
|
||||
Width="12" Height="12"
|
||||
Fill="White"
|
||||
Stroke="#22D3A9"
|
||||
StrokeThickness="1.5"
|
||||
Cursor="SizeNWSE" />
|
||||
<Ellipse x:Name="TopRightHandle"
|
||||
Width="12" Height="12"
|
||||
Fill="White"
|
||||
Stroke="#22D3A9"
|
||||
StrokeThickness="1.5"
|
||||
Cursor="SizeNESW" />
|
||||
<Ellipse x:Name="BottomLeftHandle"
|
||||
Width="12" Height="12"
|
||||
Fill="White"
|
||||
Stroke="#22D3A9"
|
||||
StrokeThickness="1.5"
|
||||
Cursor="SizeNESW" />
|
||||
<Ellipse x:Name="BottomRightHandle"
|
||||
Width="12" Height="12"
|
||||
Fill="White"
|
||||
Stroke="#22D3A9"
|
||||
StrokeThickness="1.5"
|
||||
Cursor="SizeNWSE" />
|
||||
</Canvas>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,260 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
{
|
||||
public enum ImageResizeCorner
|
||||
{
|
||||
TopLeft,
|
||||
TopRight,
|
||||
BottomLeft,
|
||||
BottomRight
|
||||
}
|
||||
|
||||
public class ImageResizeDeltaEventArgs : EventArgs
|
||||
{
|
||||
public ImageResizeCorner Corner { get; }
|
||||
public Vector CanvasDelta { get; }
|
||||
public bool LockAspectRatio { get; }
|
||||
|
||||
public ImageResizeDeltaEventArgs(ImageResizeCorner corner, Vector canvasDelta, bool lockAspect)
|
||||
{
|
||||
Corner = corner;
|
||||
CanvasDelta = canvasDelta;
|
||||
LockAspectRatio = lockAspect;
|
||||
}
|
||||
}
|
||||
|
||||
public class ImageMoveDeltaEventArgs : EventArgs
|
||||
{
|
||||
public Vector CanvasDelta { get; }
|
||||
public ImageMoveDeltaEventArgs(Vector delta) { CanvasDelta = delta; }
|
||||
}
|
||||
|
||||
public class ImageRotateDeltaEventArgs : EventArgs
|
||||
{
|
||||
public double AngleDelta { get; }
|
||||
public ImageRotateDeltaEventArgs(double angleDelta) { AngleDelta = angleDelta; }
|
||||
}
|
||||
|
||||
public partial class ImageSelectionOverlay : UserControl
|
||||
{
|
||||
private const double HandleSize = 12;
|
||||
private const double RotationHandleSize = 14;
|
||||
private const double RotationHandleOffset = 28;
|
||||
|
||||
public event EventHandler<ImageResizeDeltaEventArgs> ResizeDelta;
|
||||
public event EventHandler<ImageMoveDeltaEventArgs> MoveDelta;
|
||||
public event EventHandler<ImageRotateDeltaEventArgs> RotateDelta;
|
||||
public event EventHandler InteractionStarted;
|
||||
public event EventHandler InteractionEnded;
|
||||
|
||||
public IInputElement CoordinateSource { get; set; }
|
||||
|
||||
private Point _rotationCenterCanvas;
|
||||
private readonly RotateTransform _overlayRotation = new RotateTransform(0);
|
||||
|
||||
private bool _isResizing;
|
||||
private bool _isRotating;
|
||||
private bool _isMoving;
|
||||
private ImageResizeCorner _activeCorner;
|
||||
private Point _lastPoint;
|
||||
private double _lastRotationAngle;
|
||||
|
||||
public ImageSelectionOverlay()
|
||||
{
|
||||
InitializeComponent();
|
||||
RenderTransform = _overlayRotation;
|
||||
|
||||
TopLeftHandle.MouseLeftButtonDown += (s, e) => BeginResize(ImageResizeCorner.TopLeft, e, TopLeftHandle);
|
||||
TopRightHandle.MouseLeftButtonDown += (s, e) => BeginResize(ImageResizeCorner.TopRight, e, TopRightHandle);
|
||||
BottomLeftHandle.MouseLeftButtonDown += (s, e) => BeginResize(ImageResizeCorner.BottomLeft, e, BottomLeftHandle);
|
||||
BottomRightHandle.MouseLeftButtonDown += (s, e) => BeginResize(ImageResizeCorner.BottomRight, e, BottomRightHandle);
|
||||
|
||||
TopLeftHandle.MouseMove += ResizeMove;
|
||||
TopRightHandle.MouseMove += ResizeMove;
|
||||
BottomLeftHandle.MouseMove += ResizeMove;
|
||||
BottomRightHandle.MouseMove += ResizeMove;
|
||||
|
||||
TopLeftHandle.MouseLeftButtonUp += EndResize;
|
||||
TopRightHandle.MouseLeftButtonUp += EndResize;
|
||||
BottomLeftHandle.MouseLeftButtonUp += EndResize;
|
||||
BottomRightHandle.MouseLeftButtonUp += EndResize;
|
||||
|
||||
RotationHandle.MouseLeftButtonDown += BeginRotate;
|
||||
RotationHandle.MouseMove += RotateMove;
|
||||
RotationHandle.MouseLeftButtonUp += EndRotate;
|
||||
|
||||
MoveSurface.MouseLeftButtonDown += BeginMove;
|
||||
MoveSurface.MouseMove += MoveMove;
|
||||
MoveSurface.MouseLeftButtonUp += EndMove;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Position overlay so its logical rect (width × height) is centered at centerCanvas,
|
||||
/// then rotated by rotationAngleDegrees around that center to match the target element.
|
||||
/// </summary>
|
||||
public void UpdateFrame(Point centerCanvas, double width, double height, double rotationAngleDegrees)
|
||||
{
|
||||
if (width <= 0 || height <= 0) return;
|
||||
|
||||
_rotationCenterCanvas = centerCanvas;
|
||||
|
||||
double left = centerCanvas.X - width / 2;
|
||||
double top = centerCanvas.Y - height / 2;
|
||||
Margin = new Thickness(left, top, 0, 0);
|
||||
Width = width;
|
||||
Height = height;
|
||||
|
||||
RenderTransformOrigin = new Point(0, 0);
|
||||
_overlayRotation.Angle = rotationAngleDegrees;
|
||||
_overlayRotation.CenterX = width / 2;
|
||||
_overlayRotation.CenterY = height / 2;
|
||||
|
||||
FrameBorder.Width = width;
|
||||
FrameBorder.Height = height;
|
||||
System.Windows.Controls.Canvas.SetLeft(FrameBorder, 0);
|
||||
System.Windows.Controls.Canvas.SetTop(FrameBorder, 0);
|
||||
|
||||
MoveSurface.Width = width;
|
||||
MoveSurface.Height = height;
|
||||
System.Windows.Controls.Canvas.SetLeft(MoveSurface, 0);
|
||||
System.Windows.Controls.Canvas.SetTop(MoveSurface, 0);
|
||||
|
||||
double h = HandleSize / 2;
|
||||
System.Windows.Controls.Canvas.SetLeft(TopLeftHandle, -h);
|
||||
System.Windows.Controls.Canvas.SetTop(TopLeftHandle, -h);
|
||||
System.Windows.Controls.Canvas.SetLeft(TopRightHandle, width - h);
|
||||
System.Windows.Controls.Canvas.SetTop(TopRightHandle, -h);
|
||||
System.Windows.Controls.Canvas.SetLeft(BottomLeftHandle, -h);
|
||||
System.Windows.Controls.Canvas.SetTop(BottomLeftHandle, height - h);
|
||||
System.Windows.Controls.Canvas.SetLeft(BottomRightHandle, width - h);
|
||||
System.Windows.Controls.Canvas.SetTop(BottomRightHandle, height - h);
|
||||
|
||||
double rh = RotationHandleSize / 2;
|
||||
double midX = width / 2;
|
||||
System.Windows.Controls.Canvas.SetLeft(RotationHandle, midX - rh);
|
||||
System.Windows.Controls.Canvas.SetTop(RotationHandle, -RotationHandleOffset - rh);
|
||||
|
||||
RotationLine.X1 = midX;
|
||||
RotationLine.Y1 = 0;
|
||||
RotationLine.X2 = midX;
|
||||
RotationLine.Y2 = -RotationHandleOffset;
|
||||
}
|
||||
|
||||
private IInputElement GetSource() => CoordinateSource ?? (IInputElement)Parent;
|
||||
|
||||
private void BeginResize(ImageResizeCorner corner, MouseButtonEventArgs e, Ellipse handle)
|
||||
{
|
||||
var source = GetSource();
|
||||
if (source == null) return;
|
||||
_isResizing = true;
|
||||
_activeCorner = corner;
|
||||
_lastPoint = e.GetPosition(source);
|
||||
handle.CaptureMouse();
|
||||
InteractionStarted?.Invoke(this, EventArgs.Empty);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ResizeMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!_isResizing || !(sender is Ellipse handle) || !handle.IsMouseCaptured) return;
|
||||
var source = GetSource();
|
||||
if (source == null) return;
|
||||
var current = e.GetPosition(source);
|
||||
var delta = current - _lastPoint;
|
||||
bool lockAspect = (Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift;
|
||||
ResizeDelta?.Invoke(this, new ImageResizeDeltaEventArgs(_activeCorner, delta, lockAspect));
|
||||
_lastPoint = current;
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void EndResize(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isResizing) return;
|
||||
if (sender is Ellipse handle) handle.ReleaseMouseCapture();
|
||||
_isResizing = false;
|
||||
InteractionEnded?.Invoke(this, EventArgs.Empty);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void BeginRotate(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var source = GetSource();
|
||||
if (source == null) return;
|
||||
_isRotating = true;
|
||||
var p = e.GetPosition(source);
|
||||
_lastRotationAngle = AngleFromCenter(p);
|
||||
RotationHandle.CaptureMouse();
|
||||
InteractionStarted?.Invoke(this, EventArgs.Empty);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void RotateMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!_isRotating || !RotationHandle.IsMouseCaptured) return;
|
||||
var source = GetSource();
|
||||
if (source == null) return;
|
||||
var p = e.GetPosition(source);
|
||||
double angle = AngleFromCenter(p);
|
||||
double delta = angle - _lastRotationAngle;
|
||||
if (delta > 180) delta -= 360;
|
||||
else if (delta < -180) delta += 360;
|
||||
_lastRotationAngle = angle;
|
||||
RotateDelta?.Invoke(this, new ImageRotateDeltaEventArgs(delta));
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void EndRotate(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isRotating) return;
|
||||
RotationHandle.ReleaseMouseCapture();
|
||||
_isRotating = false;
|
||||
InteractionEnded?.Invoke(this, EventArgs.Empty);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void BeginMove(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
var source = GetSource();
|
||||
if (source == null) return;
|
||||
_isMoving = true;
|
||||
_lastPoint = e.GetPosition(source);
|
||||
MoveSurface.CaptureMouse();
|
||||
InteractionStarted?.Invoke(this, EventArgs.Empty);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void MoveMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!_isMoving || !MoveSurface.IsMouseCaptured) return;
|
||||
var source = GetSource();
|
||||
if (source == null) return;
|
||||
var current = e.GetPosition(source);
|
||||
var delta = current - _lastPoint;
|
||||
_lastPoint = current;
|
||||
MoveDelta?.Invoke(this, new ImageMoveDeltaEventArgs(delta));
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void EndMove(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isMoving) return;
|
||||
MoveSurface.ReleaseMouseCapture();
|
||||
_isMoving = false;
|
||||
InteractionEnded?.Invoke(this, EventArgs.Empty);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private double AngleFromCenter(Point p)
|
||||
{
|
||||
double dx = p.X - _rotationCenterCanvas.X;
|
||||
double dy = p.Y - _rotationCenterCanvas.Y;
|
||||
return Math.Atan2(dy, dx) * 180.0 / Math.PI;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// 画布上的多页 PDF:仅显示当前页;翻页与页码由主窗口 PDF 侧栏控制(无 XAML 文件)。
|
||||
/// </summary>
|
||||
public class PdfEmbeddedView : UserControl
|
||||
{
|
||||
private readonly Image _pageImage;
|
||||
|
||||
private string _pdfPath;
|
||||
private uint _pageCount;
|
||||
private uint _currentIndex;
|
||||
private bool _compressLargePictures;
|
||||
private bool _isPagingBusy;
|
||||
private bool _layoutSizeCommitted;
|
||||
|
||||
/// <summary>页码或可翻页状态变化(用于更新侧栏)。</summary>
|
||||
public event EventHandler PageNavigationStateChanged;
|
||||
|
||||
public PdfEmbeddedView()
|
||||
{
|
||||
MinWidth = 80;
|
||||
MinHeight = 60;
|
||||
|
||||
var grid = new Grid { ClipToBounds = true };
|
||||
_pageImage = new Image
|
||||
{
|
||||
Stretch = Stretch.Uniform,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
grid.Children.Add(_pageImage);
|
||||
Content = grid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化并显示指定页;由 MainWindow 在 UI 线程创建后调用。
|
||||
/// </summary>
|
||||
/// <param name="initialPageIndex">从 0 开始的页码,超出范围时夹紧到合法区间。</param>
|
||||
public async Task InitializeAsync(string pdfFilePath, uint pageCount, bool compressLargePictures, uint initialPageIndex = 0)
|
||||
{
|
||||
_pdfPath = pdfFilePath ?? throw new ArgumentNullException(nameof(pdfFilePath));
|
||||
_pageCount = pageCount;
|
||||
_compressLargePictures = compressLargePictures;
|
||||
if (_pageCount == 0)
|
||||
_currentIndex = 0;
|
||||
else
|
||||
_currentIndex = initialPageIndex >= _pageCount ? _pageCount - 1 : initialPageIndex;
|
||||
|
||||
await ShowPageAsync(_currentIndex);
|
||||
}
|
||||
|
||||
public string PdfPath => _pdfPath;
|
||||
|
||||
public uint PageCount => _pageCount;
|
||||
|
||||
public uint CurrentPageIndex => _currentIndex;
|
||||
|
||||
public string PageLabelText => _pageCount == 0 ? "" : $"{_currentIndex + 1} / {_pageCount}";
|
||||
|
||||
public bool CanGoPrevious => !_isPagingBusy && _pageCount > 1 && _currentIndex > 0;
|
||||
|
||||
public bool CanGoNext => !_isPagingBusy && _pageCount > 1 && _currentIndex < _pageCount - 1;
|
||||
|
||||
public async Task GoToPreviousPageAsync()
|
||||
{
|
||||
await GoRelativeAsync(-1);
|
||||
}
|
||||
|
||||
public async Task GoToNextPageAsync()
|
||||
{
|
||||
await GoRelativeAsync(1);
|
||||
}
|
||||
|
||||
private void NotifyPageNavigationStateChanged()
|
||||
{
|
||||
PageNavigationStateChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private async Task GoRelativeAsync(int delta)
|
||||
{
|
||||
if (_isPagingBusy || _pageCount <= 1)
|
||||
return;
|
||||
int next = (int)_currentIndex + delta;
|
||||
if (next < 0 || next >= _pageCount)
|
||||
return;
|
||||
_currentIndex = (uint)next;
|
||||
await ShowPageAsync(_currentIndex);
|
||||
}
|
||||
|
||||
private async Task ShowPageAsync(uint pageIndex)
|
||||
{
|
||||
_isPagingBusy = true;
|
||||
NotifyPageNavigationStateChanged();
|
||||
try
|
||||
{
|
||||
BitmapSource raw = await PdfWinRtHelper.RenderPageToBitmapSourceAsync(_pdfPath, pageIndex);
|
||||
if (raw == null)
|
||||
return;
|
||||
|
||||
BitmapSource display = ApplyCompressionIfNeeded(raw);
|
||||
_pageImage.Source = display;
|
||||
if (!_layoutSizeCommitted)
|
||||
{
|
||||
bool callerSized = !double.IsNaN(Width) && Width > 0 && !double.IsNaN(Height) && Height > 0;
|
||||
if (!callerSized)
|
||||
{
|
||||
Width = display.PixelWidth;
|
||||
Height = display.PixelHeight;
|
||||
}
|
||||
|
||||
_layoutSizeCommitted = true;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isPagingBusy = false;
|
||||
NotifyPageNavigationStateChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private BitmapSource ApplyCompressionIfNeeded(BitmapSource rendered)
|
||||
{
|
||||
int width = rendered.PixelWidth;
|
||||
int height = rendered.PixelHeight;
|
||||
if (_compressLargePictures && (width > 1920 || height > 1080))
|
||||
{
|
||||
double scaleX = 1920.0 / width;
|
||||
double scaleY = 1080.0 / height;
|
||||
double scale = Math.Min(scaleX, scaleY);
|
||||
return new TransformedBitmap(rendered, new ScaleTransform(scale, scale));
|
||||
}
|
||||
|
||||
return rendered;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<UserControl x:Class="Ink_Canvas.Controls.PptNavBar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ikw="http://schemas.inkore.net/lib/ui/wpf"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Controls"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="50" d:DesignWidth="200">
|
||||
<UserControl.Resources>
|
||||
<SolidColorBrush x:Key="PptNavBarItemForeground" Color="#71717a"/>
|
||||
<DataTemplate x:Key="PptPreviewItemTemplate">
|
||||
<Grid Margin="2,4">
|
||||
<Border CornerRadius="8" Background="Transparent" Padding="6">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="28"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||
FontSize="12" FontWeight="SemiBold"
|
||||
Foreground="{DynamicResource PptNavBarItemForeground}"
|
||||
Text="{Binding SlideNumber}"/>
|
||||
<Image Grid.Column="1" Source="{Binding Thumbnail}" Stretch="Uniform"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
<Style x:Key="PptPreviewItemContainerStyle" TargetType="ListBoxItem">
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="0"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListBoxItem">
|
||||
<Border x:Name="Bd" CornerRadius="8" Background="Transparent"
|
||||
BorderBrush="Transparent" BorderThickness="2">
|
||||
<ContentPresenter/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="Bd" Property="Background" Value="#1affffff"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsSelected" Value="True">
|
||||
<Setter TargetName="Bd" Property="BorderBrush" Value="#66CCFF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Border x:Name="RootBorder" BorderThickness="1" BorderBrush="#a1a1aa"
|
||||
Background="#f4f4f5" Opacity="1" CornerRadius="6">
|
||||
<Border.Effect>
|
||||
<DropShadowEffect Color="#000000" BlurRadius="14" ShadowDepth="2" Opacity="0.16"/>
|
||||
</Border.Effect>
|
||||
<DockPanel x:Name="LayoutRoot" LastChildFill="True">
|
||||
<ListBox x:Name="PreviewList"
|
||||
Visibility="Collapsed"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
Padding="6,8,6,4"
|
||||
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ItemTemplate="{StaticResource PptPreviewItemTemplate}"
|
||||
ItemContainerStyle="{StaticResource PptPreviewItemContainerStyle}"
|
||||
MouseUp="PreviewList_MouseUp"/>
|
||||
<ikw:SimpleStackPanel x:Name="ButtonRow" Orientation="Horizontal"
|
||||
HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<Border x:Name="PreviousButtonBorder" Width="50" Height="50"
|
||||
MouseDown="PreviousButton_MouseDown"
|
||||
MouseUp="PreviousButton_MouseUp"
|
||||
MouseLeave="PreviousButton_MouseLeave"
|
||||
CornerRadius="5 5 0 0" Background="Transparent">
|
||||
<Grid>
|
||||
<Border x:Name="PreviousButtonFeedbackBorder" Margin="3" CornerRadius="5"
|
||||
Background="#18181b" Opacity="0"/>
|
||||
<Image Height="28" Width="28">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing x:Name="PreviousButtonGeometry" Brush="#27272a"/>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border x:Name="PageButtonBorder" Visibility="Visible" TextBlock.Foreground="#171717"
|
||||
MouseDown="PageButton_MouseDown"
|
||||
MouseUp="PageButton_MouseUp"
|
||||
MouseLeave="PageButton_MouseLeave"
|
||||
MinWidth="50" MinHeight="50" Background="Transparent">
|
||||
<Grid>
|
||||
<Border x:Name="PageButtonFeedbackBorder" Margin="0,3" CornerRadius="5"
|
||||
Background="#18181b" Opacity="0"/>
|
||||
<ikw:SimpleStackPanel VerticalAlignment="Center" Orientation="Vertical"
|
||||
Spacing="0.5" Margin="12,0">
|
||||
<TextBlock x:Name="PageNowText" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="17" FontWeight="Bold" Text="?"/>
|
||||
<TextBlock x:Name="PageTotalText" HorizontalAlignment="Center" VerticalAlignment="Center"
|
||||
FontSize="10" Text="/ ?"/>
|
||||
</ikw:SimpleStackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
<Border x:Name="NextButtonBorder" Width="50" Height="50"
|
||||
MouseDown="NextButton_MouseDown"
|
||||
MouseUp="NextButton_MouseUp"
|
||||
MouseLeave="NextButton_MouseLeave"
|
||||
CornerRadius="0 0 5 5" Background="Transparent">
|
||||
<Grid>
|
||||
<Border x:Name="NextButtonFeedbackBorder" Margin="3" CornerRadius="5"
|
||||
Background="#18181b" Opacity="0"/>
|
||||
<Image VerticalAlignment="Center" HorizontalAlignment="Center" Height="28" Width="28">
|
||||
<Image.Source>
|
||||
<DrawingImage>
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing x:Name="NextButtonGeometry" Brush="#27272a"/>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</Image.Source>
|
||||
</Image>
|
||||
</Grid>
|
||||
</Border>
|
||||
</ikw:SimpleStackPanel>
|
||||
</DockPanel>
|
||||
</Border>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,392 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas.Controls
|
||||
{
|
||||
/// <summary>
|
||||
/// PPT 翻页 + 增强预览一体化控件。
|
||||
/// 通过 <see cref="Direction"/> 切换底部条 (LB/RB) 与侧边条 (LS/RS) 布局,
|
||||
/// 预览列表内嵌于同一个 Border,展开时占据按钮组之外的剩余空间。
|
||||
/// </summary>
|
||||
public partial class PptNavBar : UserControl
|
||||
{
|
||||
public sealed class PreviewItem
|
||||
{
|
||||
public int SlideNumber { get; set; }
|
||||
public BitmapImage Thumbnail { get; set; }
|
||||
}
|
||||
|
||||
public enum NavDirection
|
||||
{
|
||||
LeftBottom,
|
||||
RightBottom,
|
||||
LeftSide,
|
||||
RightSide
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DirectionProperty = DependencyProperty.Register(
|
||||
nameof(Direction), typeof(NavDirection), typeof(PptNavBar),
|
||||
new PropertyMetadata(NavDirection.LeftBottom, OnDirectionChanged));
|
||||
|
||||
public static readonly DependencyProperty CurrentSlideProperty = DependencyProperty.Register(
|
||||
nameof(CurrentSlide), typeof(int), typeof(PptNavBar),
|
||||
new PropertyMetadata(0, OnPageChanged));
|
||||
|
||||
public static readonly DependencyProperty TotalSlidesProperty = DependencyProperty.Register(
|
||||
nameof(TotalSlides), typeof(int), typeof(PptNavBar),
|
||||
new PropertyMetadata(0, OnPageChanged));
|
||||
|
||||
public static readonly DependencyProperty PreviewItemsProperty = DependencyProperty.Register(
|
||||
nameof(PreviewItems), typeof(IList<PreviewItem>), typeof(PptNavBar),
|
||||
new PropertyMetadata(null, OnPreviewItemsChanged));
|
||||
|
||||
public static readonly DependencyProperty IsPreviewExpandedProperty = DependencyProperty.Register(
|
||||
nameof(IsPreviewExpanded), typeof(bool), typeof(PptNavBar),
|
||||
new PropertyMetadata(false, OnIsPreviewExpandedChanged));
|
||||
|
||||
public NavDirection Direction
|
||||
{
|
||||
get => (NavDirection)GetValue(DirectionProperty);
|
||||
set => SetValue(DirectionProperty, value);
|
||||
}
|
||||
|
||||
public int CurrentSlide
|
||||
{
|
||||
get => (int)GetValue(CurrentSlideProperty);
|
||||
set => SetValue(CurrentSlideProperty, value);
|
||||
}
|
||||
|
||||
public int TotalSlides
|
||||
{
|
||||
get => (int)GetValue(TotalSlidesProperty);
|
||||
set => SetValue(TotalSlidesProperty, value);
|
||||
}
|
||||
|
||||
public IList<PreviewItem> PreviewItems
|
||||
{
|
||||
get => (IList<PreviewItem>)GetValue(PreviewItemsProperty);
|
||||
set => SetValue(PreviewItemsProperty, value);
|
||||
}
|
||||
|
||||
public bool IsPreviewExpanded
|
||||
{
|
||||
get => (bool)GetValue(IsPreviewExpandedProperty);
|
||||
set => SetValue(IsPreviewExpandedProperty, value);
|
||||
}
|
||||
|
||||
public event EventHandler PreviousClick;
|
||||
public event EventHandler NextClick;
|
||||
public event EventHandler PageClick;
|
||||
public event EventHandler<int> SlideSelected;
|
||||
public event EventHandler PreviousPressedDown;
|
||||
public event EventHandler NextPressedDown;
|
||||
public event EventHandler PressEnded;
|
||||
public event EventHandler<bool> PreviewExpandedChanged;
|
||||
|
||||
// 静态几何(左下/右下:水平箭头;左侧/右侧:垂直箭头)
|
||||
private static readonly Geometry HArrowLeft = Geometry.Parse("F0 M24,24z M0,0z M3.3994,12.9642C2.86687,12.4317,2.86687,11.5683,3.3994,11.0358L9.94485,4.49031C10.4774,3.95777 11.3408,3.95777 11.8733,4.49031 12.4059,5.02284 12.4059,5.88625 11.8733,6.41878L7.65575,10.6364 19.6364,10.6364C20.3895,10.6364 21,11.2469 21,12 21,12.7531 20.3895,13.3636 19.6364,13.3636L7.65575,13.3636 11.8733,17.5812C12.4059,18.1137 12.4059,18.9772 11.8733,19.5097 11.3408,20.0422 10.4774,20.0422 9.94485,19.5097L3.3994,12.9642z");
|
||||
private static readonly Geometry HArrowRight = Geometry.Parse("F0 M24,24z M0,0z M20.6006,12.9642C21.1331,12.4317,21.1331,11.5683,20.6006,11.0358L14.0551,4.49031C13.5226,3.95777 12.6592,3.95777 12.1267,4.49031 11.5941,5.02284 11.5941,5.88625 12.1267,6.41878L16.3443,10.6364 4.36364,10.6364C3.61052,10.6364 3,11.2469 3,12 3,12.7531 3.61052,13.3636 4.36364,13.3636L16.3443,13.3636 12.1267,17.5812C11.5941,18.1137 11.5941,18.9772 12.1267,19.5097 12.6592,20.0422 13.5226,20.0422 14.0551,19.5097L20.6006,12.9642z");
|
||||
private static readonly Geometry VArrowUp = Geometry.Parse("F0 M24,24z M0,0z M11.0357,3.3994C11.5682,2.86687,12.4316,2.86687,12.9641,3.3994L19.5096,9.94485C20.0421,10.4774 20.0421,11.3408 19.5096,11.8733 18.9771,12.4059 18.1137,12.4059 17.5811,11.8733L13.3635,7.65575 13.3635,19.6364C13.3635,20.3895 12.753,21 11.9999,21 11.2468,21 10.6363,20.3895 10.6363,19.6364L10.6363,7.65575 6.41869,11.8733C5.88616,12.4059 5.02275,12.4059 4.49022,11.8733 3.95769,11.3408 3.95769,10.4774 4.49022,9.94485L11.0357,3.3994z");
|
||||
private static readonly Geometry VArrowDown = Geometry.Parse("F0 M24,24z M0,0z M11.0357,20.6006C11.5682,21.1331,12.4316,21.1331,12.9641,20.6006L19.5096,14.0551C20.0421,13.5226 20.0421,12.6592 19.5096,12.1267 18.9771,11.5941 18.1137,11.5941 17.5811,12.1267L13.3635,16.3443 13.3635,4.36364C13.3635,3.61052 12.753,3 11.9999,3 11.2468,3 10.6363,3.61052 10.6363,4.36364L10.6363,16.3443 6.41869,12.1267C5.88616,11.5941 5.02275,11.5941 4.49022,12.1267 3.95769,12.6592 3.95769,13.5226 4.49022,14.0551L11.0357,20.6006z");
|
||||
|
||||
public PptNavBar()
|
||||
{
|
||||
InitializeComponent();
|
||||
ApplyDirection(Direction);
|
||||
}
|
||||
|
||||
private static void OnDirectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is PptNavBar bar) bar.ApplyDirection((NavDirection)e.NewValue);
|
||||
}
|
||||
|
||||
private static void OnPageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is PptNavBar bar) bar.RefreshPageText();
|
||||
}
|
||||
|
||||
private static void OnPreviewItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is PptNavBar bar)
|
||||
{
|
||||
bar.PreviewList.ItemsSource = e.NewValue as IList<PreviewItem>;
|
||||
bar.SyncPreviewSelection();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnIsPreviewExpandedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is PptNavBar bar)
|
||||
{
|
||||
bool expanded = (bool)e.NewValue;
|
||||
bar.PreviewList.Visibility = expanded ? Visibility.Visible : Visibility.Collapsed;
|
||||
bar.ApplyLayout();
|
||||
if (expanded)
|
||||
{
|
||||
bar.SyncPreviewSelection();
|
||||
bar.HookOutsideClick();
|
||||
}
|
||||
else
|
||||
{
|
||||
bar.UnhookOutsideClick();
|
||||
}
|
||||
bar.PreviewExpandedChanged?.Invoke(bar, expanded);
|
||||
}
|
||||
}
|
||||
|
||||
private Window _hookedWindow;
|
||||
private void HookOutsideClick()
|
||||
{
|
||||
if (_hookedWindow != null) return;
|
||||
_hookedWindow = Window.GetWindow(this);
|
||||
if (_hookedWindow != null)
|
||||
{
|
||||
_hookedWindow.PreviewMouseDown += OnWindowPreviewMouseDown;
|
||||
}
|
||||
}
|
||||
private void UnhookOutsideClick()
|
||||
{
|
||||
if (_hookedWindow != null)
|
||||
{
|
||||
_hookedWindow.PreviewMouseDown -= OnWindowPreviewMouseDown;
|
||||
_hookedWindow = null;
|
||||
}
|
||||
}
|
||||
private void OnWindowPreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.OriginalSource is DependencyObject d && !IsDescendantOf(d, this))
|
||||
{
|
||||
IsPreviewExpanded = false;
|
||||
}
|
||||
}
|
||||
private static bool IsDescendantOf(DependencyObject child, DependencyObject ancestor)
|
||||
{
|
||||
while (child != null)
|
||||
{
|
||||
if (ReferenceEquals(child, ancestor)) return true;
|
||||
child = System.Windows.Media.VisualTreeHelper.GetParent(child)
|
||||
?? System.Windows.LogicalTreeHelper.GetParent(child);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ApplyDirection(NavDirection dir) => ApplyLayout();
|
||||
|
||||
private void ApplyLayout()
|
||||
{
|
||||
var dir = Direction;
|
||||
bool expanded = IsPreviewExpanded;
|
||||
|
||||
// 重置可能在不同状态下被设置的属性
|
||||
ButtonRow.ClearValue(WidthProperty);
|
||||
ButtonRow.ClearValue(HeightProperty);
|
||||
ButtonRow.ClearValue(HorizontalAlignmentProperty);
|
||||
PreviewList.ClearValue(WidthProperty);
|
||||
PreviewList.ClearValue(HeightProperty);
|
||||
PreviewList.ClearValue(MaxHeightProperty);
|
||||
PreviewList.ClearValue(MaxWidthProperty);
|
||||
PreviewList.ClearValue(HorizontalAlignmentProperty);
|
||||
ClearValue(HeightProperty);
|
||||
ClearValue(MaxHeightProperty);
|
||||
|
||||
double availableHeight = ComputeAvailableHeight();
|
||||
|
||||
switch (dir)
|
||||
{
|
||||
case NavDirection.LeftBottom:
|
||||
case NavDirection.RightBottom:
|
||||
DockPanel.SetDock(PreviewList, Dock.Top);
|
||||
DockPanel.SetDock(ButtonRow, Dock.Bottom);
|
||||
ButtonRow.Orientation = Orientation.Horizontal;
|
||||
ButtonRow.Height = 50;
|
||||
if (expanded)
|
||||
{
|
||||
// 预览面板拉宽到 280,贴向同侧角落
|
||||
PreviewList.Width = 280;
|
||||
PreviewList.MaxHeight = Math.Max(200, availableHeight - 50);
|
||||
PreviewList.HorizontalAlignment = dir == NavDirection.LeftBottom
|
||||
? HorizontalAlignment.Left
|
||||
: HorizontalAlignment.Right;
|
||||
// 按钮组宽度限制为原始内容宽度,并贴向同侧,保持按钮位置不变
|
||||
ButtonRow.HorizontalAlignment = dir == NavDirection.LeftBottom
|
||||
? HorizontalAlignment.Left
|
||||
: HorizontalAlignment.Right;
|
||||
}
|
||||
else
|
||||
{
|
||||
PreviewList.SetBinding(WidthProperty, new System.Windows.Data.Binding(nameof(ButtonRow.ActualWidth)) { Source = ButtonRow });
|
||||
PreviewList.MaxHeight = 380;
|
||||
}
|
||||
PreviousButtonGeometry.Geometry = HArrowLeft;
|
||||
NextButtonGeometry.Geometry = HArrowRight;
|
||||
break;
|
||||
|
||||
case NavDirection.LeftSide:
|
||||
DockPanel.SetDock(PreviewList, Dock.Right);
|
||||
DockPanel.SetDock(ButtonRow, Dock.Left);
|
||||
ButtonRow.Orientation = Orientation.Vertical;
|
||||
ButtonRow.Width = 50;
|
||||
PreviewList.Width = 240;
|
||||
PreviewList.MaxHeight = 480;
|
||||
PreviousButtonGeometry.Geometry = VArrowUp;
|
||||
NextButtonGeometry.Geometry = VArrowDown;
|
||||
break;
|
||||
case NavDirection.RightSide:
|
||||
DockPanel.SetDock(PreviewList, Dock.Left);
|
||||
DockPanel.SetDock(ButtonRow, Dock.Right);
|
||||
ButtonRow.Orientation = Orientation.Vertical;
|
||||
ButtonRow.Width = 50;
|
||||
PreviewList.Width = 240;
|
||||
PreviewList.MaxHeight = 480;
|
||||
PreviousButtonGeometry.Geometry = VArrowUp;
|
||||
NextButtonGeometry.Geometry = VArrowDown;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private double ComputeAvailableHeight()
|
||||
{
|
||||
var window = Window.GetWindow(this);
|
||||
double h = window != null ? window.ActualHeight : SystemParameters.PrimaryScreenHeight;
|
||||
return Math.Max(240, h - 12);
|
||||
}
|
||||
|
||||
private void RefreshPageText()
|
||||
{
|
||||
if (CurrentSlide > 0 && TotalSlides > 0)
|
||||
{
|
||||
PageNowText.Text = CurrentSlide.ToString();
|
||||
PageTotalText.Text = $"/ {TotalSlides}";
|
||||
}
|
||||
else
|
||||
{
|
||||
PageNowText.Text = "?";
|
||||
PageTotalText.Text = "/ ?";
|
||||
}
|
||||
SyncPreviewSelection();
|
||||
}
|
||||
|
||||
private void SyncPreviewSelection()
|
||||
{
|
||||
if (PreviewItems == null || CurrentSlide <= 0) return;
|
||||
foreach (var item in PreviewItems)
|
||||
{
|
||||
if (item.SlideNumber == CurrentSlide)
|
||||
{
|
||||
PreviewList.SelectedItem = item;
|
||||
PreviewList.ScrollIntoView(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetFeedback(Border feedback, double opacity) => feedback.Opacity = opacity;
|
||||
|
||||
private object _lastDown;
|
||||
|
||||
private void PreviousButton_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_lastDown = sender;
|
||||
SetFeedback(PreviousButtonFeedbackBorder, 0.15);
|
||||
PreviousPressedDown?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void PreviousButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
SetFeedback(PreviousButtonFeedbackBorder, 0);
|
||||
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||
if (_lastDown != sender) return;
|
||||
_lastDown = null;
|
||||
PreviousClick?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void PreviousButton_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
SetFeedback(PreviousButtonFeedbackBorder, 0);
|
||||
_lastDown = null;
|
||||
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void NextButton_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_lastDown = sender;
|
||||
SetFeedback(NextButtonFeedbackBorder, 0.15);
|
||||
NextPressedDown?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void NextButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
SetFeedback(NextButtonFeedbackBorder, 0);
|
||||
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||
if (_lastDown != sender) return;
|
||||
_lastDown = null;
|
||||
NextClick?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void NextButton_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
SetFeedback(NextButtonFeedbackBorder, 0);
|
||||
_lastDown = null;
|
||||
PressEnded?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void PageButton_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
_lastDown = sender;
|
||||
SetFeedback(PageButtonFeedbackBorder, 0.15);
|
||||
}
|
||||
|
||||
private void PageButton_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
SetFeedback(PageButtonFeedbackBorder, 0);
|
||||
if (_lastDown != sender) return;
|
||||
_lastDown = null;
|
||||
PageClick?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void PageButton_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
SetFeedback(PageButtonFeedbackBorder, 0);
|
||||
_lastDown = null;
|
||||
}
|
||||
|
||||
private void PreviewList_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (PreviewList.SelectedItem is PreviewItem item)
|
||||
{
|
||||
SlideSelected?.Invoke(this, item.SlideNumber);
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyTheme(bool isDark)
|
||||
{
|
||||
var fgBrush = isDark ? Brushes.White : new SolidColorBrush(Color.FromRgb(39, 39, 42));
|
||||
var feedbackBrush = isDark ? Brushes.White : new SolidColorBrush(Color.FromRgb(24, 24, 27));
|
||||
var bgBrush = isDark
|
||||
? new SolidColorBrush(Color.FromRgb(39, 39, 42))
|
||||
: new SolidColorBrush(Color.FromRgb(244, 244, 245));
|
||||
var borderBrush = isDark
|
||||
? new SolidColorBrush(Color.FromRgb(82, 82, 91))
|
||||
: new SolidColorBrush(Color.FromRgb(161, 161, 170));
|
||||
|
||||
PreviousButtonGeometry.Brush = fgBrush;
|
||||
NextButtonGeometry.Brush = fgBrush;
|
||||
PreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
NextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
PageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
PageNowText.Foreground = fgBrush;
|
||||
PageTotalText.Foreground = fgBrush;
|
||||
RootBorder.Background = bgBrush;
|
||||
RootBorder.BorderBrush = borderBrush;
|
||||
Resources["PptNavBarItemForeground"] = fgBrush;
|
||||
}
|
||||
|
||||
public void SetPageButtonVisibility(Visibility v) => PageButtonBorder.Visibility = v;
|
||||
public void SetBarOpacity(double opacity) => RootBorder.Opacity = opacity;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
/// <summary>
|
||||
/// 工具栏按钮插件与宿主之间的桥梁。Phase 1 粗粒度暴露 MainWindow,后续收窄。
|
||||
/// </summary>
|
||||
public interface IToolbarHost
|
||||
{
|
||||
MainWindow Window { get; }
|
||||
|
||||
/// <summary>按 id 登记按钮的 view 实例(供 MainWindow 字段回填和互相查找)。</summary>
|
||||
void RegisterView(string id, FrameworkElement view);
|
||||
|
||||
/// <summary>按 id 获取之前注册的 view。不存在返回 null。</summary>
|
||||
FrameworkElement FindView(string id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
public interface IToolbarItem
|
||||
{
|
||||
string Id { get; }
|
||||
|
||||
ToolbarSlot DefaultSlot { get; }
|
||||
|
||||
int DefaultOrder { get; }
|
||||
|
||||
bool DefaultVisible { get; }
|
||||
|
||||
ToolbarInsertPosition DefaultPosition { get; }
|
||||
|
||||
string DefaultAnchorName { get; }
|
||||
|
||||
string DisplayName { get; }
|
||||
|
||||
string MenuPanelName { get; }
|
||||
|
||||
FrameworkElement BuildView(IToolbarHost host);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// 清空按钮。位置:夹在颜色面板与 StackPanelCanvasControls 之间,
|
||||
/// 所以用 BeforeAnchor 锚到 StackPanelCanvasControls。
|
||||
/// </summary>
|
||||
internal sealed class ClearToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.clear";
|
||||
public override string LocalizationKey => "FloatingBar_Clear";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarMain;
|
||||
public override int DefaultOrder => 0;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.BeforeAnchor;
|
||||
public override string DefaultAnchorName => "StackPanelCanvasControls";
|
||||
|
||||
protected override string IconBrushResourceKey => "RedBrush";
|
||||
protected override string LabelBrushResourceKey => "RedBrush";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.SymbolIconDelete_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachSymbolIconDelete(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class CursorToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.cursor";
|
||||
public override string LocalizationKey => "FloatingBar_Mouse";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarMain;
|
||||
public override int DefaultOrder => 100;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.CursorIcon_Click(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachCursorIconView(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class CursorWithDelToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.cursorWithDel";
|
||||
public override string LocalizationKey => "FloatingBar_ClearAndMouse";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 320;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Append;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.CursorWithDelIcon_Click(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachCursorWithDelBtn(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class EraserByStrokesToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.eraserByStrokes";
|
||||
public override string LocalizationKey => "FloatingBar_StrokeEraser";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 110;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.EraserIconByStrokes_Click(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachEraserByStrokesIcon(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class EraserToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.eraser";
|
||||
public override string LocalizationKey => "FloatingBar_AreaEraser";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 100;
|
||||
public override string MenuPanelName => "EraserSizePanel";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.EraserIcon_Click(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachEraserIcon(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class FoldToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.fold";
|
||||
public override string LocalizationKey => "FloatingBar_Hide";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarEnd;
|
||||
public override int DefaultOrder => 120;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.AfterAnchor;
|
||||
public override string DefaultAnchorName => "FloatingBarEndSeparator";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.FoldFloatingBar_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachFoldIcon(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class PenToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.pen";
|
||||
public override string LocalizationKey => "FloatingBar_Annotate";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarMain;
|
||||
public override int DefaultOrder => 110;
|
||||
public override string MenuPanelName => "PenPalette";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.PenIcon_Click(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachPenIconView(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class RedoToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.redo";
|
||||
public override string LocalizationKey => "Board_Redo";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 310;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Append;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.SymbolIconRedo_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
{
|
||||
host.Window.AttachSymbolIconRedo(view);
|
||||
view.SetBinding(System.Windows.UIElement.IsEnabledProperty,
|
||||
new System.Windows.Data.Binding("IsEnabled") { ElementName = "BtnRedo" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class SelectToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.select";
|
||||
public override string LocalizationKey => "FloatingBar_LassoSelect";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 120;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.SymbolIconSelect_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachSymbolIconSelect(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class ShapeDrawToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.shapeDraw";
|
||||
public override string LocalizationKey => "FloatingBar_Geometry";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 130;
|
||||
public override string MenuPanelName => "BorderDrawShape";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.ImageDrawShape_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachShapeDrawBtn(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using Ink_Canvas.Properties;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal abstract class ToolbarImageButtonItemBase : IToolbarItem
|
||||
{
|
||||
public abstract string Id { get; }
|
||||
public abstract string LocalizationKey { get; }
|
||||
public abstract ToolbarSlot DefaultSlot { get; }
|
||||
public abstract int DefaultOrder { get; }
|
||||
public virtual bool DefaultVisible => true;
|
||||
public virtual ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Prepend;
|
||||
public virtual string DefaultAnchorName => null;
|
||||
|
||||
public string DisplayName => Strings.GetString(LocalizationKey) ?? LocalizationKey;
|
||||
public virtual string MenuPanelName => null;
|
||||
|
||||
protected virtual string IconBrushResourceKey => null;
|
||||
protected virtual string LabelBrushResourceKey => null;
|
||||
|
||||
protected abstract void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e);
|
||||
|
||||
protected virtual void AfterBuild(IToolbarHost host, ToolbarImageButton view) { }
|
||||
|
||||
public FrameworkElement BuildView(IToolbarHost host)
|
||||
{
|
||||
var btn = new ToolbarImageButton
|
||||
{
|
||||
Label = Strings.GetString(LocalizationKey) ?? LocalizationKey,
|
||||
Tag = "ToolbarRegistryInjected"
|
||||
};
|
||||
if (!string.IsNullOrEmpty(IconBrushResourceKey))
|
||||
{
|
||||
if (btn.TryFindResource(IconBrushResourceKey) is Brush brush) btn.IconBrush = brush;
|
||||
else btn.SetResourceReference(ToolbarImageButton.IconBrushProperty, IconBrushResourceKey);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(LabelBrushResourceKey))
|
||||
{
|
||||
if (btn.TryFindResource(LabelBrushResourceKey) is Brush brush) btn.LabelBrush = brush;
|
||||
else btn.SetResourceReference(ToolbarImageButton.LabelBrushProperty, LabelBrushResourceKey);
|
||||
}
|
||||
btn.ButtonMouseUp += (s, e) => OnClick(host, s, e);
|
||||
AfterBuild(host, btn);
|
||||
return btn;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class ToolsToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.tools";
|
||||
public override string LocalizationKey => "Board_Tools";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarEnd;
|
||||
public override int DefaultOrder => 110;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.AfterAnchor;
|
||||
public override string DefaultAnchorName => "FloatingBarEndSeparator";
|
||||
public override string MenuPanelName => "BorderTools";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.SymbolIconTools_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachToolsBtn(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class UndoToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.undo";
|
||||
public override string LocalizationKey => "Board_Undo";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarCanvasControls;
|
||||
public override int DefaultOrder => 300;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.Append;
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.SymbolIconUndo_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
{
|
||||
host.Window.AttachSymbolIconUndo(view);
|
||||
view.SetBinding(System.Windows.UIElement.IsEnabledProperty,
|
||||
new System.Windows.Data.Binding("IsEnabled") { ElementName = "BtnUndo" });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar.Items
|
||||
{
|
||||
internal sealed class WhiteboardToolItem : ToolbarImageButtonItemBase
|
||||
{
|
||||
public override string Id => "builtin.whiteboard";
|
||||
public override string LocalizationKey => "FloatingBar_Whiteboard";
|
||||
public override ToolbarSlot DefaultSlot => ToolbarSlot.FloatingBarEnd;
|
||||
public override int DefaultOrder => 100;
|
||||
public override ToolbarInsertPosition DefaultPosition => ToolbarInsertPosition.AfterAnchor;
|
||||
public override string DefaultAnchorName => "FloatingBarEndSeparator";
|
||||
|
||||
protected override void OnClick(IToolbarHost host, object sender, MouseButtonEventArgs e)
|
||||
=> host.Window.ImageBlackboard_MouseUp(sender, e);
|
||||
|
||||
protected override void AfterBuild(IToolbarHost host, ToolbarImageButton view)
|
||||
=> host.Window.AttachWhiteboardBtn(view);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
/// <summary>
|
||||
/// MainWindow 版的 IToolbarHost 实现。Phase 1 直接把 MainWindow 引用暴露给插件,
|
||||
/// 插件可通过 host.Window 访问私有/内部成员(partial class 扩展或 internal 字段)。
|
||||
/// 后续阶段逐步把具体行为抽成 Host 上的方法/事件,收窄这个接口。
|
||||
/// </summary>
|
||||
public sealed class ToolbarHost : IToolbarHost
|
||||
{
|
||||
private readonly Dictionary<string, FrameworkElement> _views = new Dictionary<string, FrameworkElement>();
|
||||
|
||||
public ToolbarHost(MainWindow window)
|
||||
{
|
||||
Window = window;
|
||||
}
|
||||
|
||||
public MainWindow Window { get; }
|
||||
|
||||
public void RegisterView(string id, FrameworkElement view)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id) || view == null) return;
|
||||
_views[id] = view;
|
||||
}
|
||||
|
||||
public FrameworkElement FindView(string id)
|
||||
{
|
||||
if (string.IsNullOrEmpty(id)) return null;
|
||||
return _views.TryGetValue(id, out var v) ? v : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
public enum ToolbarInsertPosition
|
||||
{
|
||||
/// <summary>从容器头部依次插入;Order 小的在前。</summary>
|
||||
Prepend,
|
||||
/// <summary>追加到容器末尾。</summary>
|
||||
Append,
|
||||
/// <summary>插入到由 AnchorName 指定的已有元素之前。</summary>
|
||||
BeforeAnchor,
|
||||
/// <summary>插入到由 AnchorName 指定的已有元素之后(同一锚点多项按 Order 依次排列)。</summary>
|
||||
AfterAnchor
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个工具栏按钮的用户配置(可见性、顺序、所属 slot、插入位置)。
|
||||
/// 由 Settings.Toolbar 持久化。
|
||||
/// </summary>
|
||||
public class ToolbarItemConfig
|
||||
{
|
||||
[JsonProperty("visible")]
|
||||
public bool Visible { get; set; } = true;
|
||||
|
||||
[JsonProperty("order")]
|
||||
public int Order { get; set; }
|
||||
|
||||
[JsonProperty("slot")]
|
||||
public ToolbarSlot Slot { get; set; } = ToolbarSlot.FloatingBarMain;
|
||||
|
||||
[JsonProperty("position")]
|
||||
public ToolbarInsertPosition Position { get; set; } = ToolbarInsertPosition.Prepend;
|
||||
|
||||
[JsonProperty("anchorName")]
|
||||
public string AnchorName { get; set; }
|
||||
}
|
||||
|
||||
public class ToolbarLayoutSettings
|
||||
{
|
||||
[JsonProperty("items")]
|
||||
public Dictionary<string, ToolbarItemConfig> Items { get; set; } = new Dictionary<string, ToolbarItemConfig>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,209 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
public static class ToolbarRegistry
|
||||
{
|
||||
private static List<IToolbarItem> _items;
|
||||
internal const string InjectedTag = "ToolbarRegistryInjected";
|
||||
|
||||
public static IReadOnlyList<IToolbarItem> Discover()
|
||||
{
|
||||
if (_items != null) return _items;
|
||||
|
||||
var itemType = typeof(IToolbarItem);
|
||||
_items = Assembly.GetExecutingAssembly()
|
||||
.GetTypes()
|
||||
.Where(t => !t.IsAbstract && !t.IsInterface && itemType.IsAssignableFrom(t))
|
||||
.Select(t =>
|
||||
{
|
||||
try { return (IToolbarItem)Activator.CreateInstance(t); }
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 实例化 {t.FullName} 失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.Where(i => i != null)
|
||||
.ToList();
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: Discover 完成, 发现 {_items.Count} 个条目", LogHelper.LogType.Info);
|
||||
return _items;
|
||||
}
|
||||
|
||||
public static void ClearInjected(Panel container)
|
||||
{
|
||||
if (container == null) return;
|
||||
var toRemove = container.Children.OfType<FrameworkElement>()
|
||||
.Where(e => e.Tag as string == InjectedTag)
|
||||
.ToList();
|
||||
foreach (var element in toRemove)
|
||||
container.Children.Remove(element);
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: ClearInjected 清除 {toRemove.Count} 个元素 [{container.Name}]", LogHelper.LogType.Info);
|
||||
}
|
||||
|
||||
public static void Populate(IToolbarHost host, IDictionary<ToolbarSlot, Panel> slots, ToolbarLayoutSettings layout)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: Populate 开始", LogHelper.LogType.Info);
|
||||
if (host == null || slots == null) { LogHelper.WriteLogToFile("ToolbarRegistry: Populate host 或 slots 为空", LogHelper.LogType.Warning); return; }
|
||||
layout = layout ?? new ToolbarLayoutSettings();
|
||||
|
||||
var grouped = new Dictionary<ToolbarSlot, List<(IToolbarItem item, ToolbarItemConfig cfg)>>();
|
||||
foreach (var item in Discover())
|
||||
{
|
||||
if (!layout.Items.TryGetValue(item.Id, out var cfg))
|
||||
{
|
||||
cfg = new ToolbarItemConfig
|
||||
{
|
||||
Visible = item.DefaultVisible,
|
||||
Order = item.DefaultOrder,
|
||||
Slot = item.DefaultSlot,
|
||||
Position = item.DefaultPosition,
|
||||
AnchorName = item.DefaultAnchorName
|
||||
};
|
||||
}
|
||||
if (!cfg.Visible) continue;
|
||||
if (!grouped.TryGetValue(cfg.Slot, out var list))
|
||||
{
|
||||
list = new List<(IToolbarItem, ToolbarItemConfig)>();
|
||||
grouped[cfg.Slot] = list;
|
||||
}
|
||||
list.Add((item, cfg));
|
||||
}
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 分组完成, {grouped.Count} 个 slot 有可见条目", LogHelper.LogType.Info);
|
||||
|
||||
foreach (var kv in grouped)
|
||||
{
|
||||
if (!slots.TryGetValue(kv.Key, out var container) || container == null) continue;
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 注入到 {kv.Key}, 条目数={kv.Value.Count}", LogHelper.LogType.Info);
|
||||
InjectIntoContainer(host, container, kv.Value);
|
||||
}
|
||||
|
||||
ApplyMenuVisibility(host, layout);
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: Populate 完成", LogHelper.LogType.Info);
|
||||
}
|
||||
|
||||
public static void ApplyMenuVisibility(IToolbarHost host, ToolbarLayoutSettings layout)
|
||||
{
|
||||
if (host == null || layout == null) return;
|
||||
foreach (var item in Discover())
|
||||
{
|
||||
if (string.IsNullOrEmpty(item.MenuPanelName)) continue;
|
||||
bool visible = true;
|
||||
if (layout.Items.TryGetValue(item.Id, out var cfg))
|
||||
visible = cfg.Visible;
|
||||
try
|
||||
{
|
||||
var menuElement = host.Window.FindName(item.MenuPanelName);
|
||||
if (menuElement is System.Windows.Controls.Primitives.Popup popup)
|
||||
{
|
||||
popup.IsOpen = visible;
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 菜单 Popup [{item.MenuPanelName}] -> {(visible ? "Open" : "Closed")}", LogHelper.LogType.Info);
|
||||
}
|
||||
else if (menuElement is FrameworkElement fe)
|
||||
{
|
||||
fe.Visibility = visible ? Visibility.Visible : Visibility.Collapsed;
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 菜单 [{item.MenuPanelName}] -> {(visible ? "Visible" : "Collapsed")}", LogHelper.LogType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 找不到菜单面板 [{item.MenuPanelName}]", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 设置菜单可见性异常 [{item.MenuPanelName}]: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void InjectIntoContainer(IToolbarHost host, Panel container,
|
||||
List<(IToolbarItem item, ToolbarItemConfig cfg)> entries)
|
||||
{
|
||||
var prepend = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.Prepend).OrderBy(e => e.cfg.Order).ToList();
|
||||
var append = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.Append).OrderBy(e => e.cfg.Order).ToList();
|
||||
var before = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.BeforeAnchor).ToList();
|
||||
var after = entries.Where(e => e.cfg.Position == ToolbarInsertPosition.AfterAnchor).ToList();
|
||||
|
||||
var prependIndex = 0;
|
||||
foreach (var entry in prepend)
|
||||
{
|
||||
var view = BuildAndRegister(host, entry.item);
|
||||
if (view == null) continue;
|
||||
container.Children.Insert(prependIndex++, view);
|
||||
}
|
||||
|
||||
foreach (var entry in append)
|
||||
{
|
||||
var view = BuildAndRegister(host, entry.item);
|
||||
if (view == null) continue;
|
||||
container.Children.Add(view);
|
||||
}
|
||||
|
||||
foreach (var group in before.GroupBy(e => e.cfg.AnchorName))
|
||||
{
|
||||
var anchor = FindNamedChild(container, group.Key);
|
||||
if (anchor == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 未找到锚点 '{group.Key}' (BeforeAnchor)", LogHelper.LogType.Warning);
|
||||
continue;
|
||||
}
|
||||
var idx = container.Children.IndexOf(anchor);
|
||||
foreach (var entry in group.OrderBy(e => e.cfg.Order))
|
||||
{
|
||||
var view = BuildAndRegister(host, entry.item);
|
||||
if (view == null) continue;
|
||||
container.Children.Insert(idx++, view);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var group in after.GroupBy(e => e.cfg.AnchorName))
|
||||
{
|
||||
var anchor = FindNamedChild(container, group.Key);
|
||||
if (anchor == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 未找到锚点 '{group.Key}' (AfterAnchor)", LogHelper.LogType.Warning);
|
||||
continue;
|
||||
}
|
||||
var idx = container.Children.IndexOf(anchor) + 1;
|
||||
foreach (var entry in group.OrderBy(e => e.cfg.Order))
|
||||
{
|
||||
var view = BuildAndRegister(host, entry.item);
|
||||
if (view == null) continue;
|
||||
container.Children.Insert(idx++, view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static UIElement FindNamedChild(Panel container, string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return null;
|
||||
foreach (UIElement child in container.Children)
|
||||
{
|
||||
if (child is FrameworkElement fe && fe.Name == name) return child;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static FrameworkElement BuildAndRegister(IToolbarHost host, IToolbarItem item)
|
||||
{
|
||||
try
|
||||
{
|
||||
var view = item.BuildView(host);
|
||||
if (view == null) return null;
|
||||
host.RegisterView(item.Id, view);
|
||||
return view;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ToolbarRegistry: 构建 {item.Id} 失败: {ex.GetType().Name}: {ex.Message}\n{ex.StackTrace}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Ink_Canvas.Controls.Toolbar
|
||||
{
|
||||
public enum ToolbarSlot
|
||||
{
|
||||
FloatingBarMain,
|
||||
FloatingBarCanvasControls,
|
||||
FloatingBarEnd,
|
||||
BlackboardLeft,
|
||||
BlackboardRight
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
@@ -7,6 +10,138 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class AnimationsHelper
|
||||
{
|
||||
#region Win32 API - 用于提升 Popup 层级和刷新位置
|
||||
|
||||
[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 bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
public int Bottom;
|
||||
}
|
||||
|
||||
private const uint GW_HWNDPREV = 3;
|
||||
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
||||
private static readonly IntPtr HWND_TOP = new IntPtr(0);
|
||||
private const uint SWP_NOMOVE = 0x0002;
|
||||
private const uint SWP_NOSIZE = 0x0001;
|
||||
private const uint SWP_NOACTIVATE = 0x0010;
|
||||
private const uint SWP_SHOWWINDOW = 0x0040;
|
||||
|
||||
/// <summary>
|
||||
/// 强制刷新 Popup 的实际窗口位置(终极方案)
|
||||
/// 通过 Win32 API 直接操作窗口句柄
|
||||
/// </summary>
|
||||
public static void ForceRefreshPopupPosition(Popup popup)
|
||||
{
|
||||
if (popup?.Child == null || !popup.IsOpen) return;
|
||||
|
||||
try
|
||||
{
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var source = PresentationSource.FromVisual(popup.Child) as HwndSource;
|
||||
if (source?.Handle == null) return;
|
||||
|
||||
var hwnd = source.Handle;
|
||||
|
||||
// 获取当前窗口位置
|
||||
if (GetWindowRect(hwnd, out RECT rect))
|
||||
{
|
||||
// 使用相同的参数调用 SetWindowPos,但加上 SWP_SHOWWINDOW
|
||||
// 这会强制窗口管理器重新评估并更新窗口位置
|
||||
SetWindowPos(
|
||||
hwnd,
|
||||
HWND_TOP,
|
||||
rect.Left, rect.Top,
|
||||
rect.Right - rect.Left,
|
||||
rect.Bottom - rect.Top,
|
||||
SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] Force refreshed position: ({rect.Left}, {rect.Top})");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] ForceRefreshPopupPosition failed: {ex.Message}");
|
||||
}
|
||||
}), System.Windows.Threading.DispatcherPriority.Render);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] ForceRefreshPopupPosition error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将 Popup 窗口提升到最顶层,确保不被其他控件遮挡
|
||||
/// 采用多重策略确保置顶生效
|
||||
/// </summary>
|
||||
private static void BringPopupToFront(Popup popup)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (popup?.Child == null) return;
|
||||
|
||||
Action bringToTopAction = () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var source = PresentationSource.FromVisual(popup.Child) as HwndSource;
|
||||
if (source?.Handle == null) return;
|
||||
|
||||
var hwnd = source.Handle;
|
||||
|
||||
// 策略1:直接设置为 TOPMOST(最高优先级)
|
||||
SetWindowPos(hwnd, HWND_TOPMOST,
|
||||
0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] Set TOPMOST for popup");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] BringPopupToFront failed: {ex.Message}");
|
||||
}
|
||||
};
|
||||
|
||||
// 立即执行第一次
|
||||
Application.Current.Dispatcher.BeginInvoke(bringToTopAction,
|
||||
System.Windows.Threading.DispatcherPriority.Render);
|
||||
|
||||
// 延迟 50ms 后再次执行(确保在其他窗口操作之后)
|
||||
Application.Current.Dispatcher.BeginInvoke(bringToTopAction,
|
||||
System.Windows.Threading.DispatcherPriority.Normal);
|
||||
|
||||
// 延迟 100ms 后第三次执行(最终确认)
|
||||
Application.Current.Dispatcher.BeginInvoke(bringToTopAction,
|
||||
System.Windows.Threading.DispatcherPriority.Background);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[PopupZOrder] BringPopupToFront error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static UIElement ResolveAnimationTarget(UIElement element)
|
||||
{
|
||||
return element;
|
||||
}
|
||||
|
||||
public static void ShowWithFadeIn(UIElement element, double duration = 0.15)
|
||||
{
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
@@ -36,14 +171,17 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
if (element.Visibility == Visibility.Visible) return;
|
||||
|
||||
element.Visibility = Visibility.Visible;
|
||||
|
||||
var target = ResolveAnimationTarget(element);
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 渐变动画
|
||||
var fadeInAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0.5,
|
||||
@@ -54,10 +192,9 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
// 滑动动画
|
||||
var slideAnimation = new DoubleAnimation
|
||||
{
|
||||
From = element.RenderTransform.Value.OffsetY + 10, // 滑动距离
|
||||
From = 10,
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
@@ -68,10 +205,9 @@ namespace Ink_Canvas.Helpers
|
||||
sb.Children.Add(fadeInAnimation);
|
||||
sb.Children.Add(slideAnimation);
|
||||
|
||||
element.Visibility = Visibility.Visible;
|
||||
element.RenderTransform = new TranslateTransform();
|
||||
target.RenderTransform = new TranslateTransform();
|
||||
|
||||
sb.Begin((FrameworkElement)element);
|
||||
sb.Begin((FrameworkElement)target);
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
@@ -207,14 +343,15 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
if (element.Visibility == Visibility.Collapsed) return;
|
||||
|
||||
if (element == null)
|
||||
throw new ArgumentNullException(nameof(element));
|
||||
|
||||
if (element.Visibility == Visibility.Collapsed) return;
|
||||
|
||||
var target = ResolveAnimationTarget(element);
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 渐变动画
|
||||
var fadeOutAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 1,
|
||||
@@ -224,11 +361,10 @@ namespace Ink_Canvas.Helpers
|
||||
fadeOutAnimation.EasingFunction = new CubicEase();
|
||||
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
// 滑动动画
|
||||
var slideAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = element.RenderTransform.Value.OffsetY + 10, // 滑动距离
|
||||
To = 10,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
slideAnimation.EasingFunction = new CubicEase();
|
||||
@@ -243,8 +379,8 @@ namespace Ink_Canvas.Helpers
|
||||
element.Visibility = Visibility.Collapsed;
|
||||
};
|
||||
|
||||
element.RenderTransform = new TranslateTransform();
|
||||
sb.Begin((FrameworkElement)element);
|
||||
target.RenderTransform = new TranslateTransform();
|
||||
sb.Begin((FrameworkElement)target);
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
@@ -258,7 +394,6 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
// 渐变动画
|
||||
var fadeOutAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 1,
|
||||
@@ -277,5 +412,110 @@ namespace Ink_Canvas.Helpers
|
||||
sb.Begin((FrameworkElement)element);
|
||||
}
|
||||
|
||||
public static void ShowPopupWithSlideAndFade(Popup popup, double duration = 0.15)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (popup == null)
|
||||
throw new ArgumentNullException(nameof(popup));
|
||||
|
||||
if (popup.IsOpen) return;
|
||||
|
||||
var child = popup.Child as FrameworkElement;
|
||||
if (child == null)
|
||||
{
|
||||
popup.IsOpen = true;
|
||||
BringPopupToFront(popup);
|
||||
return;
|
||||
}
|
||||
|
||||
child.Opacity = 0.5;
|
||||
child.RenderTransform = new TranslateTransform(0, 10);
|
||||
|
||||
popup.IsOpen = true;
|
||||
|
||||
// 提升 Popup 到最顶层
|
||||
BringPopupToFront(popup);
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
var fadeInAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0.5,
|
||||
To = 1,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
fadeInAnimation.EasingFunction = new CubicEase();
|
||||
Storyboard.SetTargetProperty(fadeInAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
var slideAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 10,
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
slideAnimation.EasingFunction = new CubicEase();
|
||||
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
|
||||
|
||||
sb.Children.Add(fadeInAnimation);
|
||||
sb.Children.Add(slideAnimation);
|
||||
|
||||
sb.Begin(child);
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
public static void HidePopupWithSlideAndFade(Popup popup, double duration = 0.15)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (popup == null)
|
||||
throw new ArgumentNullException(nameof(popup));
|
||||
|
||||
if (!popup.IsOpen) return;
|
||||
|
||||
var child = popup.Child as FrameworkElement;
|
||||
if (child == null)
|
||||
{
|
||||
popup.IsOpen = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var sb = new Storyboard();
|
||||
|
||||
var fadeOutAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 1,
|
||||
To = 0,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
fadeOutAnimation.EasingFunction = new CubicEase();
|
||||
Storyboard.SetTargetProperty(fadeOutAnimation, new PropertyPath(UIElement.OpacityProperty));
|
||||
|
||||
var slideAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 0,
|
||||
To = 10,
|
||||
Duration = TimeSpan.FromSeconds(duration)
|
||||
};
|
||||
slideAnimation.EasingFunction = new CubicEase();
|
||||
Storyboard.SetTargetProperty(slideAnimation, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.Y)"));
|
||||
|
||||
sb.Children.Add(fadeOutAnimation);
|
||||
sb.Children.Add(slideAnimation);
|
||||
|
||||
sb.Completed += (s, e) =>
|
||||
{
|
||||
popup.IsOpen = false;
|
||||
child.Opacity = 1;
|
||||
child.RenderTransform = new TranslateTransform();
|
||||
};
|
||||
|
||||
child.RenderTransform = new TranslateTransform();
|
||||
sb.Begin(child);
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
using Ink_Canvas.Windows.SettingsViews.Helpers;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Security.Principal;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public static class AppRestartHelper
|
||||
{
|
||||
public static bool IsRunningAsAdmin()
|
||||
{
|
||||
try
|
||||
{
|
||||
var identity = WindowsIdentity.GetCurrent();
|
||||
var principal = new WindowsPrincipal(identity);
|
||||
return principal.IsInRole(WindowsBuiltInRole.Administrator);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RestartApp(bool asAdmin)
|
||||
{
|
||||
try
|
||||
{
|
||||
App.IsAppExitByUser = true;
|
||||
|
||||
(Application.Current as App)?.ReleaseMutexForRestart();
|
||||
|
||||
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
|
||||
if (asAdmin)
|
||||
{
|
||||
var psi = new ProcessStartInfo(exePath) { UseShellExecute = true, Verb = "runas" };
|
||||
Process.Start(psi);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 当前已是管理员时,直接通过用户令牌降权启动,避免经由 explorer 中转的延迟
|
||||
if (IsRunningAsAdmin() && UIAccessHelper.RestartAsNormalUser())
|
||||
{
|
||||
Application.Current.Shutdown();
|
||||
return;
|
||||
}
|
||||
|
||||
Process.Start("explorer.exe", "\"" + exePath + "\"");
|
||||
}
|
||||
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"重启应用时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void RestartWithCurrentPrivileges()
|
||||
{
|
||||
RestartApp(IsRunningAsAdmin());
|
||||
}
|
||||
|
||||
public static void RestartAsAdmin()
|
||||
{
|
||||
RestartApp(true);
|
||||
}
|
||||
|
||||
public static void RestartAsNormal()
|
||||
{
|
||||
RestartApp(false);
|
||||
}
|
||||
|
||||
public static void SwitchToUIATopMostAndRestart()
|
||||
{
|
||||
try
|
||||
{
|
||||
SettingsManager.Settings.Advanced.EnableUIAccessTopMost = true;
|
||||
|
||||
if (!SettingsManager.Settings.Advanced.IsAlwaysOnTop)
|
||||
{
|
||||
SettingsManager.Settings.Advanced.IsAlwaysOnTop = true;
|
||||
}
|
||||
|
||||
SettingsManager.SaveSettingsToFile();
|
||||
|
||||
App.IsUIAccessTopMostEnabled = true;
|
||||
RestartApp(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"切换到UIA置顶模式时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void SwitchToNormalTopMostAndRestart()
|
||||
{
|
||||
try
|
||||
{
|
||||
SettingsManager.Settings.Advanced.EnableUIAccessTopMost = false;
|
||||
SettingsManager.SaveSettingsToFile();
|
||||
|
||||
App.IsUIAccessTopMostEnabled = false;
|
||||
RestartApp(IsRunningAsAdmin());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"切换到普通置顶模式时出错: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,37 @@ namespace Ink_Canvas.Helpers
|
||||
private static readonly string updatesFolderPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "AutoUpdate");
|
||||
private static string statusFilePath;
|
||||
|
||||
// 全局下载取消令牌;UI 通过 RequestCancelDownload 取消当前下载
|
||||
private static CancellationTokenSource _activeDownloadCts;
|
||||
private static readonly object _activeDownloadLock = new object();
|
||||
|
||||
public static void RequestCancelDownload()
|
||||
{
|
||||
lock (_activeDownloadLock)
|
||||
{
|
||||
try { _activeDownloadCts?.Cancel(); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
private static CancellationTokenSource BeginDownloadSession()
|
||||
{
|
||||
lock (_activeDownloadLock)
|
||||
{
|
||||
try { _activeDownloadCts?.Cancel(); } catch { }
|
||||
_activeDownloadCts = new CancellationTokenSource();
|
||||
return _activeDownloadCts;
|
||||
}
|
||||
}
|
||||
|
||||
private static void EndDownloadSession(CancellationTokenSource cts)
|
||||
{
|
||||
lock (_activeDownloadLock)
|
||||
{
|
||||
if (ReferenceEquals(_activeDownloadCts, cts)) _activeDownloadCts = null;
|
||||
}
|
||||
try { cts?.Dispose(); } catch { }
|
||||
}
|
||||
|
||||
public static bool IsX64UpdatePackageSelected()
|
||||
{
|
||||
try
|
||||
@@ -383,6 +414,8 @@ namespace Ink_Canvas.Helpers
|
||||
// 获取所有可用线路组,按延迟排序
|
||||
public static async Task<List<UpdateLineGroup>> GetAvailableLineGroupsOrdered(UpdateChannel channel)
|
||||
{
|
||||
var cached = TryGetCachedOrderedGroups(channel);
|
||||
if (cached != null) return cached;
|
||||
var groups = ChannelLineGroups[channel];
|
||||
var availableGroups = new List<(UpdateLineGroup group, long delay)>();
|
||||
|
||||
@@ -468,9 +501,46 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 所有线路组均不可用", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
CacheOrderedGroups(channel, orderedGroups);
|
||||
return orderedGroups;
|
||||
}
|
||||
|
||||
// 缓存按延迟排序后的线路组,避免短时间内重复测速
|
||||
private static readonly Dictionary<UpdateChannel, (List<UpdateLineGroup> groups, DateTime cachedAt)> _orderedGroupsCache
|
||||
= new Dictionary<UpdateChannel, (List<UpdateLineGroup>, DateTime)>();
|
||||
private static readonly TimeSpan _orderedGroupsCacheTtl = TimeSpan.FromMinutes(15);
|
||||
|
||||
private static List<UpdateLineGroup> TryGetCachedOrderedGroups(UpdateChannel channel)
|
||||
{
|
||||
lock (_orderedGroupsCache)
|
||||
{
|
||||
if (_orderedGroupsCache.TryGetValue(channel, out var entry) &&
|
||||
entry.groups != null && entry.groups.Count > 0 &&
|
||||
DateTime.UtcNow - entry.cachedAt < _orderedGroupsCacheTtl)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 复用线路组延迟检测缓存({entry.groups.Count} 个)");
|
||||
return new List<UpdateLineGroup>(entry.groups);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static void CacheOrderedGroups(UpdateChannel channel, List<UpdateLineGroup> groups)
|
||||
{
|
||||
lock (_orderedGroupsCache)
|
||||
{
|
||||
_orderedGroupsCache[channel] = (new List<UpdateLineGroup>(groups), DateTime.UtcNow);
|
||||
}
|
||||
}
|
||||
|
||||
public static void InvalidateOrderedGroupsCache()
|
||||
{
|
||||
lock (_orderedGroupsCache)
|
||||
{
|
||||
_orderedGroupsCache.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<long> GetDownloadUrlDelay(string url)
|
||||
{
|
||||
try
|
||||
@@ -945,6 +1015,7 @@ namespace Ink_Canvas.Helpers
|
||||
// 使用多线路组下载新版(支持自动切换)
|
||||
public static async Task<bool> DownloadSetupFileWithFallback(string version, List<UpdateLineGroup> groups, Action<double, string> progressCallback = null)
|
||||
{
|
||||
var session = BeginDownloadSession();
|
||||
try
|
||||
{
|
||||
version = NormalizeVersionForUpdate(version);
|
||||
@@ -979,8 +1050,19 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
// 依次尝试每个线路组
|
||||
CancellationToken groupLoopToken;
|
||||
lock (_activeDownloadLock)
|
||||
{
|
||||
groupLoopToken = _activeDownloadCts?.Token ?? CancellationToken.None;
|
||||
}
|
||||
foreach (var group in groups)
|
||||
{
|
||||
if (groupLoopToken.IsCancellationRequested)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 用户已取消,停止尝试后续线路组");
|
||||
break;
|
||||
}
|
||||
|
||||
string url = string.Format(group.DownloadUrlFormat, version);
|
||||
url = AppendX64SuffixBeforeZipExtension(url);
|
||||
// 智教联盟需要先获取真实下载地址
|
||||
@@ -1006,6 +1088,12 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
bool downloadSuccess = await DownloadFile(url, zipFilePath, progressCallback);
|
||||
|
||||
if (groupLoopToken.IsCancellationRequested)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 用户已取消,停止尝试后续线路组");
|
||||
break;
|
||||
}
|
||||
|
||||
if (downloadSuccess)
|
||||
{
|
||||
SaveDownloadStatus(true);
|
||||
@@ -1021,6 +1109,13 @@ namespace Ink_Canvas.Helpers
|
||||
progressCallback?.Invoke(0, "所有线路组下载均失败");
|
||||
return false;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 下载已被用户取消", LogHelper.LogType.Warning);
|
||||
SaveDownloadStatus(false);
|
||||
progressCallback?.Invoke(0, "下载已取消");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 下载更新时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
@@ -1033,6 +1128,10 @@ namespace Ink_Canvas.Helpers
|
||||
progressCallback?.Invoke(0, $"下载异常: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
EndDownloadSession(session);
|
||||
}
|
||||
}
|
||||
|
||||
// 下载文件的具体实现
|
||||
@@ -1043,6 +1142,12 @@ namespace Ink_Canvas.Helpers
|
||||
// 降低并发数,减少网络压力
|
||||
int[] threadOptions = { 32, 16, 8, 4, 1 };
|
||||
|
||||
CancellationToken externalToken;
|
||||
lock (_activeDownloadLock)
|
||||
{
|
||||
externalToken = _activeDownloadCts?.Token ?? CancellationToken.None;
|
||||
}
|
||||
|
||||
// 检查服务器是否支持Range分块下载
|
||||
bool supportRange = false;
|
||||
long totalSize = -1;
|
||||
@@ -1146,7 +1251,7 @@ namespace Ink_Canvas.Helpers
|
||||
// 增加连接超时设置
|
||||
client.Timeout = TimeSpan.FromSeconds(30);
|
||||
|
||||
var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token);
|
||||
var downloadCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, externalToken);
|
||||
var lastReadTime = DateTime.UtcNow;
|
||||
bool dataReceived = false;
|
||||
|
||||
@@ -1206,8 +1311,20 @@ namespace Ink_Canvas.Helpers
|
||||
success = true;
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载成功");
|
||||
}
|
||||
catch (Exception ex) when (ex is HttpRequestException || ex is IOException || ex is TaskCanceledException)
|
||||
catch (Exception ex) when (ex is HttpRequestException || ex is IOException || ex is TaskCanceledException || ex is OperationCanceledException)
|
||||
{
|
||||
// 用户主动取消:不再重试
|
||||
if (externalToken.IsCancellationRequested)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载已被用户取消", LogHelper.LogType.Warning);
|
||||
if (File.Exists(tempPath))
|
||||
{
|
||||
try { File.Delete(tempPath); } catch { }
|
||||
}
|
||||
cts.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}", LogHelper.LogType.Warning);
|
||||
progressCallback?.Invoke(0, $"分块{block.Index}下载失败,第{retry + 1}次: {ex.Message}");
|
||||
|
||||
@@ -1218,7 +1335,8 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
// 增加重试间隔,避免频繁重试
|
||||
await Task.Delay(2000 * (retry + 1));
|
||||
try { await Task.Delay(2000 * (retry + 1), externalToken); }
|
||||
catch (OperationCanceledException) { cts.Cancel(); return; }
|
||||
}
|
||||
}
|
||||
if (success)
|
||||
@@ -1339,12 +1457,18 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 开始单线程下载: {fileUrl}");
|
||||
progressCallback?.Invoke(0, "开始单线程下载");
|
||||
|
||||
CancellationToken token;
|
||||
lock (_activeDownloadLock)
|
||||
{
|
||||
token = _activeDownloadCts?.Token ?? CancellationToken.None;
|
||||
}
|
||||
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
client.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||
client.Timeout = TimeSpan.FromMinutes(10); // 单线程下载设置更长的超时时间
|
||||
|
||||
using (var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead))
|
||||
using (var resp = await client.GetAsync(fileUrl, HttpCompletionOption.ResponseHeadersRead, token))
|
||||
{
|
||||
resp.EnsureSuccessStatusCode();
|
||||
using (var fs = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
|
||||
@@ -1355,9 +1479,9 @@ namespace Ink_Canvas.Helpers
|
||||
long downloaded = 0;
|
||||
var lastProgressUpdate = DateTime.UtcNow;
|
||||
|
||||
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
|
||||
while ((read = await stream.ReadAsync(buffer, 0, buffer.Length, token)) > 0)
|
||||
{
|
||||
await fs.WriteAsync(buffer, 0, read);
|
||||
await fs.WriteAsync(buffer, 0, read, token);
|
||||
downloaded += read;
|
||||
|
||||
// 限制进度更新频率,避免UI卡顿
|
||||
@@ -1379,6 +1503,13 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 单线程下载完成");
|
||||
return true;
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 单线程下载已被取消", LogHelper.LogType.Warning);
|
||||
progressCallback?.Invoke(0, "下载已取消");
|
||||
try { if (File.Exists(destinationPath)) File.Delete(destinationPath); } catch { }
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 单线程下载失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
@@ -2201,9 +2332,9 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 开始修复版本,通道: {channel}");
|
||||
|
||||
// 获取远程版本号(自动选择最快线路组,始终下载远程版本,版本修复模式)
|
||||
var (remoteVersion, group, _) = await CheckForUpdates(channel, true, true);
|
||||
if (string.IsNullOrEmpty(remoteVersion) || group == null)
|
||||
// 获取远程版本号(始终下载远程版本,版本修复模式)
|
||||
var (remoteVersion, preferredGroup, _) = await CheckForUpdates(channel, true, true);
|
||||
if (string.IsNullOrEmpty(remoteVersion))
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时获取远程版本失败", LogHelper.LogType.Error);
|
||||
return false;
|
||||
@@ -2211,8 +2342,22 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 修复版本远程版本: {remoteVersion}");
|
||||
|
||||
var availableGroups = await GetAvailableLineGroupsOrdered(channel);
|
||||
if (availableGroups.Count == 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时无可用线路组", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (preferredGroup != null)
|
||||
{
|
||||
availableGroups.RemoveAll(g => g.GroupName == preferredGroup.GroupName);
|
||||
availableGroups.Insert(0, preferredGroup);
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | 修复版本下载优先使用线路组: {preferredGroup.GroupName}");
|
||||
}
|
||||
|
||||
// 无论版本是否为最新,都下载远程版本
|
||||
bool downloadResult = await DownloadSetupFile(remoteVersion, group);
|
||||
bool downloadResult = await DownloadSetupFileWithFallback(remoteVersion, availableGroups);
|
||||
if (!downloadResult)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 修复版本时下载更新失败", LogHelper.LogType.Error);
|
||||
|
||||
@@ -519,7 +519,7 @@ namespace Ink_Canvas.Helpers
|
||||
/// <summary>
|
||||
/// 异步上传文件
|
||||
/// </summary>
|
||||
public async Task<bool> UploadFileAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
public Task<bool> UploadFileAsync(string filePath, CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -528,19 +528,19 @@ namespace Ink_Canvas.Helpers
|
||||
// 检查是否启用
|
||||
if (!IsUploadEnabled())
|
||||
{
|
||||
return false;
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
// 基本验证
|
||||
if (!File.Exists(filePath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 上传失败:文件不存在 - {filePath}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
if (!IsValidFile(filePath))
|
||||
{
|
||||
return false;
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
// 确保队列已初始化
|
||||
@@ -552,7 +552,7 @@ namespace Ink_Canvas.Helpers
|
||||
// 加入队列
|
||||
EnqueueFile(filePath, 0, cancellationToken);
|
||||
|
||||
return true;
|
||||
return Task.FromResult(true);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -562,7 +562,7 @@ namespace Ink_Canvas.Helpers
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"[{GetType().Name}] 加入上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Converter
|
||||
{
|
||||
@@ -152,4 +153,27 @@ namespace Ink_Canvas.Converter
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public class StringToGeometryConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (value is string geometryString && !string.IsNullOrEmpty(geometryString))
|
||||
{
|
||||
return Geometry.Parse(geometryString);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public static class DebugConsoleManager
|
||||
{
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool AllocConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern bool FreeConsole();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr GetConsoleWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern bool SetConsoleTitle(string lpConsoleTitle);
|
||||
|
||||
private const int SW_HIDE = 0;
|
||||
private const int SW_SHOW = 5;
|
||||
private const uint SC_CLOSE = 0xF060;
|
||||
private const uint MF_BYCOMMAND = 0x00000000;
|
||||
|
||||
private static bool _allocated;
|
||||
|
||||
public static bool IsVisible { get; private set; }
|
||||
|
||||
public static void Show()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_allocated)
|
||||
{
|
||||
if (GetConsoleWindow() == IntPtr.Zero)
|
||||
{
|
||||
if (!AllocConsole()) return;
|
||||
}
|
||||
_allocated = true;
|
||||
|
||||
Console.OutputEncoding = Encoding.UTF8;
|
||||
SetConsoleTitle("InkCanvasForClass - Debug Console");
|
||||
|
||||
// 移除关闭菜单,避免用户点 X 时直接结束进程
|
||||
var hWnd = GetConsoleWindow();
|
||||
if (hWnd != IntPtr.Zero)
|
||||
{
|
||||
var hMenu = GetSystemMenu(hWnd, false);
|
||||
if (hMenu != IntPtr.Zero) DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var hWnd = GetConsoleWindow();
|
||||
if (hWnd != IntPtr.Zero) ShowWindow(hWnd, SW_SHOW);
|
||||
}
|
||||
IsVisible = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[DebugConsoleManager] Show failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Hide()
|
||||
{
|
||||
try
|
||||
{
|
||||
var hWnd = GetConsoleWindow();
|
||||
if (hWnd != IntPtr.Zero) ShowWindow(hWnd, SW_HIDE);
|
||||
IsVisible = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"[DebugConsoleManager] Hide failed: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void WriteLine(string line)
|
||||
{
|
||||
if (!IsVisible) return;
|
||||
try { Console.WriteLine(line); }
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
@@ -22,6 +23,9 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
private static readonly string DeviceId;
|
||||
private static readonly object fileLock = new object();
|
||||
private static UsageStats usageStatsCache;
|
||||
private static DateTime usageStatsCacheTime;
|
||||
private static readonly TimeSpan UsageStatsCacheDuration = TimeSpan.FromMinutes(2);
|
||||
|
||||
static DeviceIdentifier()
|
||||
{
|
||||
@@ -116,114 +120,26 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
private static string GenerateHardwareFingerprint()
|
||||
{
|
||||
// 收集硬件信息
|
||||
var hardwareInfo = new StringBuilder();
|
||||
AppendFingerprintPart(hardwareInfo, "CPU",
|
||||
GetWmiProperty("SELECT ProcessorId FROM Win32_Processor", "ProcessorId"),
|
||||
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0", "ProcessorNameString"),
|
||||
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\CentralProcessor\0", "Identifier"));
|
||||
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.Load("System.Management");
|
||||
if (assembly != null)
|
||||
{
|
||||
// CPU信息
|
||||
try
|
||||
{
|
||||
var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher");
|
||||
var searcher = Activator.CreateInstance(searcherType, "SELECT ProcessorId FROM Win32_Processor");
|
||||
var getMethod = searcherType.GetMethod("Get");
|
||||
var enumerator = getMethod.Invoke(searcher, null);
|
||||
AppendFingerprintPart(hardwareInfo, "BOARD",
|
||||
GetWmiProperty("SELECT SerialNumber FROM Win32_BaseBoard", "SerialNumber"),
|
||||
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\BIOS", "BaseBoardSerialNumber"),
|
||||
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\BIOS", "BaseBoardProduct"));
|
||||
|
||||
var moveNextMethod = enumerator.GetType().GetMethod("MoveNext");
|
||||
var currentProperty = enumerator.GetType().GetProperty("Current");
|
||||
AppendFingerprintPart(hardwareInfo, "BIOS",
|
||||
GetWmiProperty("SELECT SerialNumber FROM Win32_BIOS", "SerialNumber"),
|
||||
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\BIOS", "BIOSVersion"),
|
||||
GetRegistryValue(@"HARDWARE\DESCRIPTION\System\BIOS", "BIOSVendor"));
|
||||
|
||||
if ((bool)moveNextMethod.Invoke(enumerator, null))
|
||||
{
|
||||
var obj = currentProperty.GetValue(enumerator);
|
||||
var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) });
|
||||
var processorId = indexer.GetValue(obj, new object[] { "ProcessorId" });
|
||||
hardwareInfo.Append(processorId?.ToString() ?? "");
|
||||
}
|
||||
|
||||
var disposeMethod = searcher.GetType().GetMethod("Dispose");
|
||||
disposeMethod?.Invoke(searcher, null);
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
|
||||
// 主板序列号
|
||||
try
|
||||
{
|
||||
var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher");
|
||||
var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_BaseBoard");
|
||||
var getMethod = searcherType.GetMethod("Get");
|
||||
var enumerator = getMethod.Invoke(searcher, null);
|
||||
|
||||
var moveNextMethod = enumerator.GetType().GetMethod("MoveNext");
|
||||
var currentProperty = enumerator.GetType().GetProperty("Current");
|
||||
|
||||
if ((bool)moveNextMethod.Invoke(enumerator, null))
|
||||
{
|
||||
var obj = currentProperty.GetValue(enumerator);
|
||||
var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) });
|
||||
var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" });
|
||||
hardwareInfo.Append(serialNumber?.ToString() ?? "");
|
||||
}
|
||||
|
||||
var disposeMethod = searcher.GetType().GetMethod("Dispose");
|
||||
disposeMethod?.Invoke(searcher, null);
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
|
||||
// BIOS序列号
|
||||
try
|
||||
{
|
||||
var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher");
|
||||
var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_BIOS");
|
||||
var getMethod = searcherType.GetMethod("Get");
|
||||
var enumerator = getMethod.Invoke(searcher, null);
|
||||
|
||||
var moveNextMethod = enumerator.GetType().GetMethod("MoveNext");
|
||||
var currentProperty = enumerator.GetType().GetProperty("Current");
|
||||
|
||||
if ((bool)moveNextMethod.Invoke(enumerator, null))
|
||||
{
|
||||
var obj = currentProperty.GetValue(enumerator);
|
||||
var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) });
|
||||
var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" });
|
||||
hardwareInfo.Append(serialNumber?.ToString() ?? "");
|
||||
}
|
||||
|
||||
var disposeMethod = searcher.GetType().GetMethod("Dispose");
|
||||
disposeMethod?.Invoke(searcher, null);
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
|
||||
// 主硬盘序列号
|
||||
try
|
||||
{
|
||||
var searcherType = assembly.GetType("System.Management.ManagementObjectSearcher");
|
||||
var searcher = Activator.CreateInstance(searcherType, "SELECT SerialNumber FROM Win32_DiskDrive WHERE MediaType='Fixed hard disk media'");
|
||||
var getMethod = searcherType.GetMethod("Get");
|
||||
var enumerator = getMethod.Invoke(searcher, null);
|
||||
|
||||
var moveNextMethod = enumerator.GetType().GetMethod("MoveNext");
|
||||
var currentProperty = enumerator.GetType().GetProperty("Current");
|
||||
|
||||
if ((bool)moveNextMethod.Invoke(enumerator, null))
|
||||
{
|
||||
var obj = currentProperty.GetValue(enumerator);
|
||||
var indexer = obj.GetType().GetProperty("Item", new[] { typeof(string) });
|
||||
var serialNumber = indexer.GetValue(obj, new object[] { "SerialNumber" });
|
||||
hardwareInfo.Append(serialNumber?.ToString() ?? "");
|
||||
}
|
||||
|
||||
var disposeMethod = searcher.GetType().GetMethod("Dispose");
|
||||
disposeMethod?.Invoke(searcher, null);
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
AppendFingerprintPart(hardwareInfo, "DISK",
|
||||
GetWmiProperty("SELECT SerialNumber FROM Win32_DiskDrive WHERE MediaType='Fixed hard disk media'", "SerialNumber"),
|
||||
GetSystemDriveVolumeSerial(),
|
||||
GetRegistryValue(@"SOFTWARE\Microsoft\Cryptography", "MachineGuid"));
|
||||
|
||||
if (hardwareInfo.Length < 10)
|
||||
{
|
||||
@@ -235,6 +151,108 @@ namespace Ink_Canvas.Helpers
|
||||
return hardwareInfo.ToString();
|
||||
}
|
||||
|
||||
private static void AppendFingerprintPart(StringBuilder hardwareInfo, string key, params string[] candidates)
|
||||
{
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(candidate))
|
||||
{
|
||||
hardwareInfo.Append(key).Append(':').Append(candidate.Trim()).Append(';');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetWmiProperty(string query, string propertyName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.Load("System.Management");
|
||||
var searcherType = assembly?.GetType("System.Management.ManagementObjectSearcher");
|
||||
if (searcherType == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var searcher = Activator.CreateInstance(searcherType, query);
|
||||
var getMethod = searcherType.GetMethod("Get");
|
||||
var resultCollection = getMethod?.Invoke(searcher, null);
|
||||
if (resultCollection == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var enumerator = resultCollection.GetType().GetMethod("GetEnumerator")?.Invoke(resultCollection, null);
|
||||
var moveNextMethod = enumerator?.GetType().GetMethod("MoveNext");
|
||||
var currentProperty = enumerator?.GetType().GetProperty("Current");
|
||||
if (enumerator == null || moveNextMethod == null || currentProperty == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!(bool)moveNextMethod.Invoke(enumerator, null))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var currentObject = currentProperty.GetValue(enumerator);
|
||||
var indexer = currentObject?.GetType().GetProperty("Item", new[] { typeof(string) });
|
||||
var result = indexer?.GetValue(currentObject, new object[] { propertyName })?.ToString();
|
||||
|
||||
searcher?.GetType().GetMethod("Dispose")?.Invoke(searcher, null);
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetRegistryValue(string subKey, string valueName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return Microsoft.Win32.Registry.GetValue($@"HKEY_LOCAL_MACHINE\{subKey}", valueName, null)?.ToString();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static string GetSystemDriveVolumeSerial()
|
||||
{
|
||||
try
|
||||
{
|
||||
var rootPath = Path.GetPathRoot(Environment.SystemDirectory);
|
||||
if (string.IsNullOrWhiteSpace(rootPath))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (GetVolumeInformation(rootPath, null, 0, out uint serialNumber, out _, out _, null, 0))
|
||||
{
|
||||
return serialNumber.ToString("X8");
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
||||
private static extern bool GetVolumeInformation(
|
||||
string rootPathName,
|
||||
StringBuilder volumeNameBuffer,
|
||||
uint volumeNameSize,
|
||||
out uint volumeSerialNumber,
|
||||
out uint maximumComponentLength,
|
||||
out uint fileSystemFlags,
|
||||
StringBuilder fileSystemNameBuffer,
|
||||
uint nFileSystemNameSize);
|
||||
|
||||
/// <summary>
|
||||
/// 基于硬件指纹生成25字符的设备ID
|
||||
/// </summary>
|
||||
@@ -654,7 +672,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = LoadUsageStats();
|
||||
var stats = GetUsageStatsCached();
|
||||
return stats.SystemVersion;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -773,7 +791,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = LoadUsageStats();
|
||||
var stats = GetUsageStatsCached();
|
||||
return stats.UpdatePriority;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -790,7 +808,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
var stats = LoadUsageStats();
|
||||
var stats = GetUsageStatsCached();
|
||||
return stats.UsageFrequency;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -892,6 +910,23 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private static UsageStats GetUsageStatsCached(bool forceRefresh = false)
|
||||
{
|
||||
lock (fileLock)
|
||||
{
|
||||
if (!forceRefresh
|
||||
&& usageStatsCache != null
|
||||
&& (DateTime.Now - usageStatsCacheTime) < UsageStatsCacheDuration)
|
||||
{
|
||||
return usageStatsCache;
|
||||
}
|
||||
|
||||
usageStatsCache = LoadUsageStats();
|
||||
usageStatsCacheTime = DateTime.Now;
|
||||
return usageStatsCache;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存使用统计
|
||||
/// </summary>
|
||||
@@ -902,6 +937,9 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
// 保存到备份文件
|
||||
SaveUsageStatsToFile(UsageStatsBackupPath, stats);
|
||||
|
||||
usageStatsCache = stats;
|
||||
usageStatsCacheTime = DateTime.Now;
|
||||
}
|
||||
|
||||
|
||||
@@ -1242,15 +1280,20 @@ namespace Ink_Canvas.Helpers
|
||||
int versionDiff = CalculateVersionGenerationDifference(localVersion, updateVersion);
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 无法获取版本发布时间,使用版本号差异判断 - 本地版本: {localVersion}, 远程版本: {updateVersion}, 代数差异: {versionDiff}");
|
||||
|
||||
if (versionDiff >= 1)
|
||||
if (versionDiff <= 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本号代数差异({versionDiff})>=1,允许更新");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本号代数差异({versionDiff})<1,可能是相同版本或降级,暂不更新");
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本号代数差异({versionDiff})<=0,可能是相同版本或降级,暂不更新");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 当代数差异较大(>=3)时直接放行,避免被分级策略卡住
|
||||
if (versionDiff >= 3)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本号代数差异({versionDiff})>=3,跳过分级策略直接推送");
|
||||
return true;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本号代数差异({versionDiff})>=1,进入分级策略判断");
|
||||
}
|
||||
|
||||
// 计算最近活跃度(最后一次使用距今的天数)
|
||||
@@ -1466,32 +1509,22 @@ namespace Ink_Canvas.Helpers
|
||||
int.TryParse(remoteParts[2], out int remoteBuild) &&
|
||||
int.TryParse(remoteParts[3], out int remoteRevision))
|
||||
{
|
||||
// 计算代数差异:主版本号差异 * 1000 + 次版本号差异 * 100 + 构建号差异 * 10 + 修订号差异
|
||||
int majorDiff = remoteMajor - localMajor;
|
||||
int minorDiff = remoteMinor - localMinor;
|
||||
int buildDiff = remoteBuild - localBuild;
|
||||
int revisionDiff = remoteRevision - localRevision;
|
||||
var localSemver = new Version(localMajor, localMinor, localBuild, localRevision);
|
||||
var remoteSemver = new Version(remoteMajor, remoteMinor, remoteBuild, remoteRevision);
|
||||
int direction = remoteSemver.CompareTo(localSemver);
|
||||
if (direction == 0) return 0;
|
||||
int sign = direction > 0 ? 1 : -1;
|
||||
|
||||
// 如果主版本号不同,则代数差异很大
|
||||
if (majorDiff != 0)
|
||||
{
|
||||
return majorDiff * 1000 + minorDiff * 100 + buildDiff * 10 + revisionDiff;
|
||||
}
|
||||
int majorDiff = Math.Abs(remoteMajor - localMajor);
|
||||
if (majorDiff != 0) return sign * (majorDiff * 1000);
|
||||
|
||||
// 如果次版本号不同,则代数差异中等
|
||||
if (minorDiff != 0)
|
||||
{
|
||||
return minorDiff * 100 + buildDiff * 10 + revisionDiff;
|
||||
}
|
||||
int minorDiff = Math.Abs(remoteMinor - localMinor);
|
||||
if (minorDiff != 0) return sign * (minorDiff * 100);
|
||||
|
||||
// 如果构建号不同,则代数差异较小
|
||||
if (buildDiff != 0)
|
||||
{
|
||||
return buildDiff * 10 + revisionDiff;
|
||||
}
|
||||
int buildDiff = Math.Abs(remoteBuild - localBuild);
|
||||
if (buildDiff != 0) return sign * (buildDiff * 10);
|
||||
|
||||
// 只有修订号不同,代数差异最小
|
||||
return revisionDiff;
|
||||
return sign * Math.Abs(remoteRevision - localRevision);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
@@ -54,6 +54,11 @@ namespace Ink_Canvas.Helpers
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr MonitorFromRect(ref RECT lprc, uint dwFlags);
|
||||
|
||||
public static IntPtr GetForegroundWindowHandle()
|
||||
{
|
||||
return GetForegroundWindow();
|
||||
}
|
||||
|
||||
public static string WindowTitle()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
@@ -189,9 +188,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
/// <summary>
|
||||
/// 确保窗口全屏的Hook
|
||||
/// 使用HandleProcessCorruptedStateExceptions,防止访问内存过程中因为一些致命异常导致程序崩溃
|
||||
/// </summary>
|
||||
[HandleProcessCorruptedStateExceptions]
|
||||
private static IntPtr KeepFullScreenHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
//处理WM_WINDOWPOSCHANGING消息
|
||||
|
||||
@@ -285,6 +285,7 @@ namespace Ink_Canvas.Helpers
|
||||
// 功能快捷键
|
||||
RegisterHotkey("DrawLine", Key.L, ModifierKeys.Alt, () => _mainWindow.BtnDrawLine_Click(null, null));
|
||||
RegisterHotkey("Screenshot", Key.C, ModifierKeys.Alt, () => _mainWindow.SaveScreenShotToDesktop());
|
||||
RegisterHotkey("QuickDraw", Key.K, ModifierKeys.Alt, () => _mainWindow.OpenQuickDrawFromHotkey());
|
||||
RegisterHotkey("Hide", Key.V, ModifierKeys.Alt, () => _mainWindow.SymbolIconEmoji_MouseUp(null, null));
|
||||
|
||||
// 退出快捷键
|
||||
@@ -566,6 +567,36 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新多屏相关设置(开关和跟随鼠标策略)。
|
||||
/// </summary>
|
||||
public void RefreshMultiScreenSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
var advanced = MainWindow.Settings.Advanced;
|
||||
_isMultiScreenMode = advanced.EnableMultiScreenSupport && ScreenDetectionHelper.HasMultipleScreens();
|
||||
_enableScreenSpecificHotkeys = _isMultiScreenMode;
|
||||
|
||||
if (_isMultiScreenMode)
|
||||
{
|
||||
_currentScreen = advanced.FollowMouseForScreenSelection
|
||||
? Screen.FromPoint(Control.MousePosition)
|
||||
: ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentScreen = ScreenDetectionHelper.GetPrimaryScreen();
|
||||
}
|
||||
|
||||
RefreshHotkeysForCurrentScreen();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"刷新多屏设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前屏幕信息
|
||||
/// </summary>
|
||||
@@ -623,13 +654,15 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检测是否有多个屏幕
|
||||
_isMultiScreenMode = ScreenDetectionHelper.HasMultipleScreens();
|
||||
var advanced = MainWindow.Settings.Advanced;
|
||||
_isMultiScreenMode = advanced.EnableMultiScreenSupport && ScreenDetectionHelper.HasMultipleScreens();
|
||||
_enableScreenSpecificHotkeys = _isMultiScreenMode;
|
||||
|
||||
if (_isMultiScreenMode)
|
||||
{
|
||||
// 获取当前窗口所在的屏幕
|
||||
_currentScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
||||
_currentScreen = advanced.FollowMouseForScreenSelection
|
||||
? Screen.FromPoint(Control.MousePosition)
|
||||
: ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
||||
|
||||
// 监听窗口位置变化事件
|
||||
_mainWindow.LocationChanged += OnWindowLocationChanged;
|
||||
@@ -687,6 +720,9 @@ namespace Ink_Canvas.Helpers
|
||||
if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys)
|
||||
return;
|
||||
|
||||
if (MainWindow.Settings.Advanced.FollowMouseForScreenSelection)
|
||||
return;
|
||||
|
||||
var newScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
||||
if (newScreen != null && newScreen != _currentScreen)
|
||||
{
|
||||
@@ -799,9 +835,16 @@ namespace Ink_Canvas.Helpers
|
||||
if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys)
|
||||
return;
|
||||
|
||||
// 检查鼠标是否在当前窗口所在的屏幕上
|
||||
var mousePosition = Control.MousePosition;
|
||||
var currentScreen = Screen.FromPoint(mousePosition);
|
||||
var mouseScreen = Screen.FromPoint(mousePosition);
|
||||
|
||||
if (MainWindow.Settings.Advanced.FollowMouseForScreenSelection &&
|
||||
mouseScreen != null &&
|
||||
mouseScreen != _currentScreen)
|
||||
{
|
||||
_currentScreen = mouseScreen;
|
||||
RefreshHotkeysForCurrentScreen();
|
||||
}
|
||||
|
||||
// 无论屏幕是否变化,都检查热键状态
|
||||
// 这样可以确保热键状态始终与当前上下文保持一致
|
||||
@@ -1033,6 +1076,7 @@ namespace Ink_Canvas.Helpers
|
||||
new HotkeyConfigItem { Name = "Pen5", Key = Key.D5, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "DrawLine", Key = Key.L, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Screenshot", Key = Key.C, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "QuickDraw", Key = Key.K, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Hide", Key = Key.V, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Exit", Key = Key.Escape, Modifiers = ModifierKeys.None }
|
||||
});
|
||||
@@ -1111,6 +1155,14 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
// 旧版 HotkeyConfig.json 无「快抽」项时补注册默认组合,避免升级后无快捷键
|
||||
if (successCount > 0 && !IsHotkeyRegistered("QuickDraw"))
|
||||
{
|
||||
var quickDrawAction = GetActionByName("QuickDraw");
|
||||
if (quickDrawAction != null && RegisterHotkey("QuickDraw", Key.K, ModifierKeys.Alt, quickDrawAction))
|
||||
successCount++;
|
||||
}
|
||||
|
||||
if (successCount > 0)
|
||||
{
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
@@ -1221,6 +1273,8 @@ namespace Ink_Canvas.Helpers
|
||||
return () => _mainWindow.BtnDrawLine_Click(null, null);
|
||||
case "Screenshot":
|
||||
return () => _mainWindow.SaveScreenShotToDesktop();
|
||||
case "QuickDraw":
|
||||
return () => _mainWindow.OpenQuickDrawFromHotkey();
|
||||
case "Hide":
|
||||
return () => _mainWindow.SymbolIconEmoji_MouseUp(null, null);
|
||||
case "Exit":
|
||||
|
||||
@@ -8,9 +8,9 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
private static InkRecognitionManager _instance;
|
||||
private static readonly object _lock = new object();
|
||||
private readonly object _initSync = new object();
|
||||
|
||||
private ModernInkProcessor _modernProcessor;
|
||||
private ModernInkAnalyzer _modernAnalyzer;
|
||||
private bool _isModernSystemAvailable;
|
||||
private bool _isInitialized;
|
||||
|
||||
@@ -31,35 +31,16 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private InkRecognitionManager()
|
||||
{
|
||||
Initialize();
|
||||
}
|
||||
private InkRecognitionManager() { }
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
var tryModern = WinRtInkShapeRecognizer.IsApiAvailable && Environment.Is64BitProcess;
|
||||
|
||||
_isModernSystemAvailable = false;
|
||||
if (tryModern)
|
||||
{
|
||||
try
|
||||
{
|
||||
_modernProcessor = new ModernInkProcessor();
|
||||
_modernAnalyzer = new ModernInkAnalyzer();
|
||||
_isModernSystemAvailable = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("WinRT 墨迹初始化失败: " + ex.Message, LogHelper.LogType.Warning);
|
||||
_isModernSystemAvailable = false;
|
||||
_modernProcessor = null;
|
||||
_modernAnalyzer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 启动阶段只做能力探测,不做 WinRT 组件实例化(避免冷启动延迟)
|
||||
_isModernSystemAvailable = WinRtInkShapeRecognizer.IsApiAvailable;
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -69,10 +50,41 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
lock (_initSync)
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
Initialize();
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureModernAnalyzerInitialized()
|
||||
{
|
||||
if (_modernProcessor != null || !_isModernSystemAvailable) return;
|
||||
|
||||
lock (_initSync)
|
||||
{
|
||||
if (_modernProcessor != null || !_isModernSystemAvailable) return;
|
||||
try
|
||||
{
|
||||
_modernProcessor ??= new ModernInkProcessor();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("WinRT 墨迹模块懒加载失败: " + ex.Message, LogHelper.LogType.Warning);
|
||||
_isModernSystemAvailable = false;
|
||||
_modernProcessor = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Task<InkShapeRecognitionResult> RecognizeShapeAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode)
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (!_isInitialized || strokes == null || strokes.Count == 0)
|
||||
return Task.FromResult(InkShapeRecognitionResult.Empty);
|
||||
|
||||
@@ -84,8 +96,10 @@ namespace Ink_Canvas.Helpers
|
||||
return RecognizeShapeWinRtOnDispatcherContext(strokes);
|
||||
}
|
||||
|
||||
var legacy = InkRecognizeHelper.RecognizeShapeIACore(strokes);
|
||||
return Task.FromResult(InkRecognizeHelper.FromIACoreOrEmpty(legacy));
|
||||
// IACore 必须走 IPC 辅助进程(x86/.NET 4.7.2)。
|
||||
// 在 .NET 6 x64 主进程中本地加载 IAWinFX 会失败,故不再本地回退。
|
||||
var ipcResult = IpcIACoreClient.Instance.Recognize(strokes);
|
||||
return Task.FromResult(ipcResult);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -108,6 +122,7 @@ namespace Ink_Canvas.Helpers
|
||||
bool applyHandwritingBeautify = false,
|
||||
string handwritingFontFamilyList = null)
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (!_isInitialized)
|
||||
{
|
||||
LogHelper.WriteLogToFile("[手写体] CorrectInkAsync 跳过:InkRecognitionManager 未初始化。", LogHelper.LogType.Info);
|
||||
@@ -140,18 +155,11 @@ namespace Ink_Canvas.Helpers
|
||||
return Task.FromResult(strokes);
|
||||
}
|
||||
|
||||
if (!Environment.Is64BitProcess)
|
||||
EnsureModernAnalyzerInitialized();
|
||||
if (_modernProcessor == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile(
|
||||
"[手写体] CorrectInkAsync 跳过:非 64 位进程,WinRT 手写体替换不可用。笔画数=" + strokes.Count,
|
||||
LogHelper.LogType.Info);
|
||||
return Task.FromResult(strokes);
|
||||
}
|
||||
|
||||
if (_modernAnalyzer == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile(
|
||||
"[手写体] CorrectInkAsync 跳过:ModernInkAnalyzer 未就绪(WinRT 初始化失败?)。笔画数=" +
|
||||
"[手写体] CorrectInkAsync 跳过:ModernInkProcessor 未就绪(WinRT 初始化失败?)。笔画数=" +
|
||||
strokes.Count,
|
||||
LogHelper.LogType.Warning);
|
||||
return Task.FromResult(strokes);
|
||||
@@ -161,7 +169,7 @@ namespace Ink_Canvas.Helpers
|
||||
"[手写体] CorrectInkAsync 开始:笔画数=" + strokes.Count +
|
||||
",字体=" + (string.IsNullOrWhiteSpace(handwritingFontFamilyList) ? "(默认)" : handwritingFontFamilyList.Trim()),
|
||||
LogHelper.LogType.Info);
|
||||
return _modernAnalyzer.AnalyzeAndCorrectAsync(strokes, handwritingFontFamilyList);
|
||||
return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(strokes, handwritingFontFamilyList);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -171,19 +179,19 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// WinRT 手写体识别(需 64 位进程、Windows 10+ 及系统手写识别组件)。返回分词候选与包围框,供剪贴板或插件使用。
|
||||
/// WinRT 手写体识别(需 Windows 10+ 及系统手写识别组件)。返回分词候选与包围框,供剪贴板或插件使用。
|
||||
/// </summary>
|
||||
public Task<HandwritingRecognitionResult> RecognizeHandwritingAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode)
|
||||
{
|
||||
EnsureInitialized();
|
||||
if (!_isInitialized || strokes == null || strokes.Count == 0)
|
||||
return Task.FromResult(HandwritingRecognitionResult.Empty);
|
||||
|
||||
try
|
||||
{
|
||||
if (!Environment.Is64BitProcess
|
||||
|| !ShapeRecognitionRouter.ResolveUseWinRt(mode)
|
||||
if (!ShapeRecognitionRouter.ResolveUseWinRt(mode)
|
||||
|| !WinRtHandwritingRecognizer.IsApiAvailable)
|
||||
return Task.FromResult(HandwritingRecognitionResult.Empty);
|
||||
|
||||
@@ -208,15 +216,16 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
public string GetSystemInfo()
|
||||
{
|
||||
return _isModernSystemAvailable
|
||||
? $"现代化64位墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}"
|
||||
: $"传统墨迹识别系统 (IACore) - 进程架构: {Environment.Is64BitProcess}";
|
||||
if (_isModernSystemAvailable)
|
||||
return $"现代化墨迹识别系统 (Windows Runtime API) - 进程架构: {Environment.Is64BitProcess}";
|
||||
if (IpcIACoreClient.Instance.IsAvailable)
|
||||
return $"传统墨迹识别系统 (IACore via IPC) - 进程架构: {Environment.Is64BitProcess}";
|
||||
return $"传统墨迹识别系统 (IACore 本地) - 进程架构: {Environment.Is64BitProcess}";
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_modernProcessor?.Dispose();
|
||||
_modernAnalyzer?.Dispose();
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
@@ -238,20 +247,4 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class ModernInkAnalyzer : IDisposable
|
||||
{
|
||||
public Task<StrokeCollection> AnalyzeAndCorrectAsync(
|
||||
StrokeCollection strokes,
|
||||
string handwritingFontFamilyList)
|
||||
{
|
||||
return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(
|
||||
strokes,
|
||||
handwritingFontFamilyList);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,76 +1,15 @@
|
||||
using Ink_Canvas;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 墨迹形状/手写识别的对外门面。
|
||||
/// IACore 路径通过 IPC 调用 x86 辅助进程;WinRT 路径在主进程内直接调用。
|
||||
/// 主进程 (.NET 6 x64) 不再直接引用 IAWinFX 类型。
|
||||
/// </summary>
|
||||
public class InkRecognizeHelper
|
||||
{
|
||||
/// <summary>IACore / IAWinFX 形状识别(典型用于 32 位进程)。</summary>
|
||||
public static ShapeRecognizeResult RecognizeShapeIACore(StrokeCollection strokes)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
return default;
|
||||
|
||||
var analyzer = new InkAnalyzer();
|
||||
analyzer.AddStrokes(strokes);
|
||||
analyzer.SetStrokesType(strokes, StrokeType.Drawing);
|
||||
|
||||
AnalysisAlternate analysisAlternate = null;
|
||||
int strokesCount = strokes.Count;
|
||||
var sfsaf = analyzer.Analyze();
|
||||
if (sfsaf.Successful)
|
||||
{
|
||||
var alternates = analyzer.GetAlternates();
|
||||
if (alternates.Count > 0)
|
||||
{
|
||||
while (strokesCount >= 2)
|
||||
{
|
||||
var alt0 = alternates[0];
|
||||
if (alt0?.AlternateNodes == null || alt0.AlternateNodes.Count == 0)
|
||||
break;
|
||||
var drawNode = alt0.AlternateNodes[0] as InkDrawingNode;
|
||||
if (drawNode == null)
|
||||
break;
|
||||
var shapeOk = IsContainShapeType(drawNode.GetShapeName());
|
||||
if (alt0.Strokes.Contains(strokes.Last()) && shapeOk)
|
||||
break;
|
||||
analyzer.RemoveStroke(strokes[strokes.Count - strokesCount]);
|
||||
strokesCount--;
|
||||
sfsaf = analyzer.Analyze();
|
||||
if (sfsaf.Successful)
|
||||
alternates = analyzer.GetAlternates();
|
||||
else
|
||||
break;
|
||||
if (alternates.Count == 0)
|
||||
break;
|
||||
}
|
||||
if (alternates.Count > 0)
|
||||
analysisAlternate = alternates[0];
|
||||
}
|
||||
}
|
||||
|
||||
analyzer.Dispose();
|
||||
|
||||
if (analysisAlternate != null && analysisAlternate.AlternateNodes.Count > 0)
|
||||
{
|
||||
var node = analysisAlternate.AlternateNodes[0] as InkDrawingNode;
|
||||
if (node == null)
|
||||
return default;
|
||||
return new ShapeRecognizeResult(node.Centroid, node.HotPoints, analysisAlternate, node);
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
/// <summary>兼容旧调用:等价于 <see cref="RecognizeShapeIACore"/>。</summary>
|
||||
public static ShapeRecognizeResult RecognizeShape(StrokeCollection strokes) =>
|
||||
RecognizeShapeIACore(strokes);
|
||||
|
||||
/// <summary>按设置选择 WinRT(<see cref="InkRecognitionManager"/>)或 IACore;WinRT 请用 <see cref="RecognizeShapeUnifiedAsync"/>。</summary>
|
||||
public static InkShapeRecognitionResult RecognizeShapeUnified(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode)
|
||||
@@ -81,11 +20,9 @@ namespace Ink_Canvas.Helpers
|
||||
if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
var legacy = RecognizeShapeIACore(strokes);
|
||||
return FromIACoreOrEmpty(legacy);
|
||||
return IpcIACoreClient.Instance.Recognize(strokes);
|
||||
}
|
||||
|
||||
/// <summary>与 CE 反编译版 <c>InkRecognitionManager.RecognizeShapeAsync</c> 对齐的统一入口。</summary>
|
||||
public static Task<InkShapeRecognitionResult> RecognizeShapeUnifiedAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode)
|
||||
@@ -100,14 +37,15 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
_ = InkRecognitionManager.Instance;
|
||||
if (ShapeRecognitionRouter.ResolveUseWinRt(mode))
|
||||
{
|
||||
WinRtInkShapeRecognizer.Warmup();
|
||||
WinRtHandwritingRecognizer.Warmup();
|
||||
}
|
||||
else
|
||||
RecognizeShapeIACore(new StrokeCollection());
|
||||
{
|
||||
IpcIACoreClient.Instance.Start();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -115,13 +53,11 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>WinRT 手写识别(64 位 + Windows 10+)。</summary>
|
||||
public static Task<HandwritingRecognitionResult> RecognizeHandwritingUnifiedAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode) =>
|
||||
InkRecognitionManager.Instance.RecognizeHandwritingAsync(strokes, mode);
|
||||
|
||||
/// <summary>WinRT 下将识别成功的词替换为手写体字形墨迹;是否应用由设置「WinRT 识别转手写体字形」控制。</summary>
|
||||
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode) =>
|
||||
@@ -131,7 +67,6 @@ namespace Ink_Canvas.Helpers
|
||||
MainWindow.Settings?.InkToShape?.EnableWinRtHandwritingStrokeBeautify ?? false,
|
||||
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
||||
|
||||
/// <summary>显式指定是否应用手写体字形替换(忽略开关);字体仍从设置读取。</summary>
|
||||
public static Task<StrokeCollection> CorrectHandwritingStrokesUnifiedAsync(
|
||||
StrokeCollection strokes,
|
||||
ShapeRecognitionEngineMode mode,
|
||||
@@ -142,47 +77,18 @@ namespace Ink_Canvas.Helpers
|
||||
applyHandwritingBeautify,
|
||||
MainWindow.Settings?.InkToShape?.HandwritingCorrectionFontFamily);
|
||||
|
||||
internal static InkShapeRecognitionResult FromIACoreOrEmpty(ShapeRecognizeResult legacy)
|
||||
{
|
||||
if (legacy?.InkDrawingNode == null)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
var node = legacy.InkDrawingNode;
|
||||
var shape = node.GetShape();
|
||||
var hot = ClonePointCollection(node.HotPoints);
|
||||
return new InkShapeRecognitionResult(
|
||||
node.GetShapeName(),
|
||||
legacy.Centroid,
|
||||
hot,
|
||||
shape.Width,
|
||||
shape.Height,
|
||||
node.Strokes);
|
||||
}
|
||||
|
||||
private static PointCollection ClonePointCollection(PointCollection src)
|
||||
{
|
||||
var dst = new PointCollection();
|
||||
if (src == null) return dst;
|
||||
foreach (System.Windows.Point p in src)
|
||||
dst.Add(p);
|
||||
return dst;
|
||||
}
|
||||
|
||||
public static bool IsContainShapeType(string name)
|
||||
{
|
||||
if (name.Contains("Triangle") || name.Contains("Circle") ||
|
||||
name.Contains("Rectangle") || name.Contains("Diamond") ||
|
||||
name.Contains("Parallelogram") || name.Contains("Square")
|
||||
|| name.Contains("Ellipse"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return false;
|
||||
|
||||
return name.Contains("Triangle") || name.Contains("Circle") ||
|
||||
name.Contains("Rectangle") || name.Contains("Diamond") ||
|
||||
name.Contains("Parallelogram") || name.Contains("Square") ||
|
||||
name.Contains("Ellipse");
|
||||
}
|
||||
}
|
||||
|
||||
//Recognizer 的实现
|
||||
|
||||
public enum RecognizeLanguage
|
||||
{
|
||||
SimplifiedChinese = 0x0804,
|
||||
@@ -190,127 +96,17 @@ namespace Ink_Canvas.Helpers
|
||||
English = 0x0809
|
||||
}
|
||||
|
||||
public class ShapeRecognizeResult
|
||||
{
|
||||
public ShapeRecognizeResult(Point centroid, PointCollection hotPoints, AnalysisAlternate analysisAlternate, InkDrawingNode node)
|
||||
{
|
||||
Centroid = centroid;
|
||||
HotPoints = hotPoints;
|
||||
AnalysisAlternate = analysisAlternate;
|
||||
InkDrawingNode = node;
|
||||
}
|
||||
|
||||
public AnalysisAlternate AnalysisAlternate { get; }
|
||||
|
||||
public Point Centroid { get; set; }
|
||||
|
||||
public PointCollection HotPoints { get; }
|
||||
|
||||
public InkDrawingNode InkDrawingNode { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图形识别类
|
||||
/// </summary>
|
||||
//public class ShapeRecogniser
|
||||
//{
|
||||
// public InkAnalyzer _inkAnalyzer = null;
|
||||
|
||||
// private ShapeRecogniser()
|
||||
// {
|
||||
// this._inkAnalyzer = new InkAnalyzer
|
||||
// {
|
||||
// AnalysisModes = AnalysisModes.AutomaticReconciliationEnabled
|
||||
// };
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 根据笔迹集合返回图形名称字符串
|
||||
// /// </summary>
|
||||
// /// <param name="strokeCollection"></param>
|
||||
// /// <returns></returns>
|
||||
// public InkDrawingNode Recognition(StrokeCollection strokeCollection)
|
||||
// {
|
||||
// if (strokeCollection == null)
|
||||
// {
|
||||
// //MessageBox.Show("dddddd");
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// InkDrawingNode result = null;
|
||||
// try
|
||||
// {
|
||||
// this._inkAnalyzer.AddStrokes(strokeCollection);
|
||||
// if (this._inkAnalyzer.Analyze().Successful)
|
||||
// {
|
||||
// result = _internalAnalyzer(this._inkAnalyzer);
|
||||
// this._inkAnalyzer.RemoveStrokes(strokeCollection);
|
||||
// }
|
||||
// }
|
||||
// catch (System.Exception ex)
|
||||
// {
|
||||
// //result = ex.Message;
|
||||
// System.Diagnostics.Debug.WriteLine(ex.Message);
|
||||
// }
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// /// <summary>
|
||||
// /// 实现笔迹的分析,返回图形对应的字符串
|
||||
// /// 你在实际的应用中根据返回的字符串来生成对应的Shape
|
||||
// /// </summary>
|
||||
// /// <param name="ink"></param>
|
||||
// /// <returns></returns>
|
||||
// private InkDrawingNode _internalAnalyzer(InkAnalyzer ink)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// ContextNodeCollection nodecollections = ink.FindNodesOfType(ContextNodeType.InkDrawing);
|
||||
// foreach (ContextNode node in nodecollections)
|
||||
// {
|
||||
// InkDrawingNode drawingNode = node as InkDrawingNode;
|
||||
// if (drawingNode != null)
|
||||
// {
|
||||
// return drawingNode;//.GetShapeName();
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// catch (System.Exception ex)
|
||||
// {
|
||||
// System.Diagnostics.Debug.WriteLine(ex.Message);
|
||||
// }
|
||||
|
||||
// return null;
|
||||
// }
|
||||
|
||||
|
||||
// private static ShapeRecogniser instance = null;
|
||||
// public static ShapeRecogniser Instance
|
||||
// {
|
||||
// get
|
||||
// {
|
||||
// return instance == null ? (instance = new ShapeRecogniser()) : instance;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
//用于自动控制其他形状相对于圆的位置
|
||||
|
||||
public class Circle
|
||||
{
|
||||
public Circle(Point centroid, double r, Stroke stroke)
|
||||
public Circle(System.Windows.Point centroid, double r, Stroke stroke)
|
||||
{
|
||||
Centroid = centroid;
|
||||
R = r;
|
||||
Stroke = stroke;
|
||||
}
|
||||
|
||||
public Point Centroid { get; set; }
|
||||
|
||||
public System.Windows.Point Centroid { get; set; }
|
||||
public double R { get; set; }
|
||||
|
||||
public Stroke Stroke { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using OSVersionExtension;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
@@ -17,13 +16,13 @@ namespace Ink_Canvas.Helpers
|
||||
public static class ShapeRecognitionRouter
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动模式:按当前进程位数选择——<c>64</c> 位进程用 WinRT,<c>32</c> 位进程(含 x86 目标在 WOW64 下运行)用 IACore。
|
||||
/// 自动模式:在 Windows 10 及以上系统默认使用 WinRT,否则使用 IACore。
|
||||
/// </summary>
|
||||
public static bool ResolveUseWinRt(ShapeRecognitionEngineMode mode)
|
||||
{
|
||||
if (mode == ShapeRecognitionEngineMode.WinRT) return true;
|
||||
if (mode == ShapeRecognitionEngineMode.IACore) return false;
|
||||
return Environment.Is64BitProcess;
|
||||
return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
||||
}
|
||||
|
||||
public static bool ShouldRunShapeRecognition(bool inkToShapeEnabled, ShapeRecognitionEngineMode mode)
|
||||
@@ -31,7 +30,7 @@ namespace Ink_Canvas.Helpers
|
||||
if (!inkToShapeEnabled) return false;
|
||||
if (ResolveUseWinRt(mode))
|
||||
return OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
||||
return !Environment.Is64BitProcess;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static ShapeRecognitionEngineMode FromSettingsInt(int value)
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Pipes;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public sealed class IpcIACoreClient : IDisposable
|
||||
{
|
||||
private static IpcIACoreClient _instance;
|
||||
private static readonly object _instanceLock = new object();
|
||||
|
||||
public static IpcIACoreClient Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
lock (_instanceLock)
|
||||
if (_instance == null)
|
||||
_instance = new IpcIACoreClient();
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private Process _helperProcess;
|
||||
private readonly object _pipeLock = new object();
|
||||
private bool _disposed;
|
||||
private bool _available;
|
||||
|
||||
private static string HelperExePath =>
|
||||
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "InkCanvas.IACoreHelper.exe");
|
||||
|
||||
private string PipeName =>
|
||||
string.Format("ICC_IACoreHelper_{0}", Process.GetCurrentProcess().Id);
|
||||
|
||||
private IpcIACoreClient() { }
|
||||
|
||||
public bool Start()
|
||||
{
|
||||
if (_disposed) return false;
|
||||
if (IsAvailable) return true;
|
||||
|
||||
if (!File.Exists(HelperExePath))
|
||||
{
|
||||
_available = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return LaunchHelper();
|
||||
}
|
||||
|
||||
public bool IsAvailable => _available && _helperProcess != null && !_helperProcess.HasExited;
|
||||
|
||||
public InkShapeRecognitionResult Recognize(StrokeCollection strokes)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
EnsureHelperAlive();
|
||||
if (!IsAvailable)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
lock (_pipeLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
return SendRecognizeRequest(strokes);
|
||||
}
|
||||
catch
|
||||
{
|
||||
KillHelper();
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool LaunchHelper()
|
||||
{
|
||||
try
|
||||
{
|
||||
KillHelper();
|
||||
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = HelperExePath,
|
||||
Arguments = Process.GetCurrentProcess().Id.ToString(),
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory
|
||||
};
|
||||
_helperProcess = Process.Start(psi);
|
||||
if (_helperProcess == null)
|
||||
{
|
||||
_available = false;
|
||||
return false;
|
||||
}
|
||||
_helperProcess.EnableRaisingEvents = true;
|
||||
_helperProcess.Exited += OnHelperExited;
|
||||
|
||||
bool pipeReady = WaitForPipe(3000);
|
||||
_available = pipeReady;
|
||||
return pipeReady;
|
||||
}
|
||||
catch
|
||||
{
|
||||
_available = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool WaitForPipe(int timeoutMs)
|
||||
{
|
||||
int elapsed = 0;
|
||||
while (elapsed < timeoutMs)
|
||||
{
|
||||
if (_helperProcess == null || _helperProcess.HasExited)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
using (var probe = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut))
|
||||
{
|
||||
probe.Connect(200);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
Thread.Sleep(100);
|
||||
elapsed += 300;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private InkShapeRecognitionResult SendRecognizeRequest(StrokeCollection strokes)
|
||||
{
|
||||
using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut))
|
||||
{
|
||||
client.Connect(IpcTimeoutMs);
|
||||
|
||||
using (var writer = new BinaryWriter(client, System.Text.Encoding.UTF8, leaveOpen: true))
|
||||
using (var reader = new BinaryReader(client, System.Text.Encoding.UTF8, leaveOpen: true))
|
||||
{
|
||||
writer.Write(CmdRecognize);
|
||||
writer.Write(strokes.Count);
|
||||
foreach (var stroke in strokes)
|
||||
{
|
||||
var pts = stroke.StylusPoints;
|
||||
writer.Write(pts.Count);
|
||||
foreach (var pt in pts)
|
||||
{
|
||||
writer.Write((float)pt.X);
|
||||
writer.Write((float)pt.Y);
|
||||
writer.Write(pt.PressureFactor);
|
||||
}
|
||||
}
|
||||
writer.Flush();
|
||||
|
||||
bool success = reader.ReadBoolean();
|
||||
string shape = reader.ReadString();
|
||||
float cx = reader.ReadSingle();
|
||||
float cy = reader.ReadSingle();
|
||||
float width = reader.ReadSingle();
|
||||
float height = reader.ReadSingle();
|
||||
|
||||
int hotLen = reader.ReadInt32();
|
||||
var hotPoints = new PointCollection();
|
||||
for (int i = 0; i < hotLen; i++)
|
||||
hotPoints.Add(new Point(reader.ReadSingle(), reader.ReadSingle()));
|
||||
|
||||
int idxLen = reader.ReadInt32();
|
||||
var indices = new int[idxLen];
|
||||
for (int i = 0; i < idxLen; i++)
|
||||
indices[i] = reader.ReadInt32();
|
||||
|
||||
if (!success || string.IsNullOrEmpty(shape))
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
var recognized = new StrokeCollection();
|
||||
foreach (int idx in indices)
|
||||
if (idx >= 0 && idx < strokes.Count)
|
||||
recognized.Add(strokes[idx]);
|
||||
|
||||
return new InkShapeRecognitionResult(
|
||||
shape,
|
||||
new Point(cx, cy),
|
||||
hotPoints,
|
||||
width,
|
||||
height,
|
||||
recognized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureHelperAlive()
|
||||
{
|
||||
if (!IsAvailable)
|
||||
LaunchHelper();
|
||||
}
|
||||
|
||||
private void OnHelperExited(object sender, EventArgs e)
|
||||
{
|
||||
_available = false;
|
||||
}
|
||||
|
||||
private void KillHelper()
|
||||
{
|
||||
if (_helperProcess == null) return;
|
||||
try
|
||||
{
|
||||
try { _helperProcess.Exited -= OnHelperExited; } catch { }
|
||||
|
||||
if (!_helperProcess.HasExited)
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var client = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut))
|
||||
{
|
||||
client.Connect(500);
|
||||
using (var w = new BinaryWriter(client))
|
||||
w.Write(CmdShutdown);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (!_helperProcess.WaitForExit(800))
|
||||
_helperProcess.Kill();
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_helperProcess?.Dispose();
|
||||
_helperProcess = null;
|
||||
_available = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
KillHelper();
|
||||
}
|
||||
|
||||
private const int IpcTimeoutMs = 5000;
|
||||
private const byte CmdRecognize = 0x01;
|
||||
private const byte CmdShutdown = 0xFF;
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
string logLine = string.Format("{0} [T{1}] [{2}] [{3}] {4}", DateTime.Now.ToString("O"), threadId, strLogType, callerInfo, str);
|
||||
DebugConsoleManager.WriteLine(logLine);
|
||||
ProcessProtectionManager.WithWriteAccess(file, () =>
|
||||
{
|
||||
using (StreamWriter sw = new StreamWriter(file, true))
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// .NET Core / 5+ 未提供 <see cref="Marshal.GetActiveObject"/>,通过 OLE 实现等效行为。
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -460,7 +460,6 @@ namespace Ink_Canvas.Helpers
|
||||
_memoryStreams = new MemoryStream[_maxSlides + 2];
|
||||
}
|
||||
CurrentStrokes?.Clear();
|
||||
LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
@@ -390,11 +390,15 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
PPTApplication.PresentationOpen -= OnPresentationOpen;
|
||||
PPTApplication.PresentationClose -= OnPresentationClose;
|
||||
PPTApplication.SlideShowBegin -= OnSlideShowBegin;
|
||||
PPTApplication.SlideShowNextSlide -= OnSlideShowNextSlide;
|
||||
PPTApplication.SlideShowEnd -= OnSlideShowEnd;
|
||||
// 再次检查PPTApplication是否为null,因为可能在异步操作期间被修改
|
||||
if (PPTApplication != null && Marshal.IsComObject(PPTApplication))
|
||||
{
|
||||
PPTApplication.PresentationOpen -= OnPresentationOpen;
|
||||
PPTApplication.PresentationClose -= OnPresentationClose;
|
||||
PPTApplication.SlideShowBegin -= OnSlideShowBegin;
|
||||
PPTApplication.SlideShowNextSlide -= OnSlideShowNextSlide;
|
||||
PPTApplication.SlideShowEnd -= OnSlideShowEnd;
|
||||
}
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
@@ -406,6 +410,15 @@ namespace Ink_Canvas.Helpers
|
||||
// COM对象类型转换失败,通常是因为对象已经被释放
|
||||
LogHelper.WriteLogToFile("PPT COM对象已被释放,跳过事件注册取消", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (System.Reflection.TargetInvocationException tie) when (tie.InnerException is InvalidComObjectException)
|
||||
{
|
||||
// RCW 已分离:Office Interop 内部通过反射创建 EventProvider 时抛出,是正常情况
|
||||
LogHelper.WriteLogToFile("PPT COM对象RCW已分离,跳过事件注册取消", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (InvalidComObjectException)
|
||||
{
|
||||
LogHelper.WriteLogToFile("PPT COM对象RCW已分离,跳过事件注册取消", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"取消PPT事件注册时发生异常: {ex}", LogHelper.LogType.Warning);
|
||||
@@ -1251,7 +1264,6 @@ namespace Ink_Canvas.Helpers
|
||||
object slideNavigation = null;
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile($"尝试显示幻灯片导航 - 连接状态: {IsConnected}, 放映状态: {IsInSlideShow}", LogHelper.LogType.Trace);
|
||||
|
||||
if (!IsConnected || !IsInSlideShow || PPTApplication == null)
|
||||
{
|
||||
@@ -1284,7 +1296,6 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
dynamic sn = slideNavigation;
|
||||
sn.Visible = true;
|
||||
LogHelper.WriteLogToFile("成功显示幻灯片导航(PowerPoint模式)", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ namespace Ink_Canvas.Helpers
|
||||
[DllImport("ole32.dll")]
|
||||
private static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);
|
||||
|
||||
[DllImport("ole32.dll", CharSet = CharSet.Unicode)]
|
||||
private static extern int CLSIDFromProgID(string lpszProgID, out Guid pclsid);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
@@ -104,7 +107,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 +127,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
|
||||
@@ -189,6 +192,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
IMoniker[] moniker = new IMoniker[1];
|
||||
IntPtr fetched = IntPtr.Zero;
|
||||
string[] applicationMonikersFromProgIds = GetApplicationMonikersFromProgIds();
|
||||
|
||||
while (enumMoniker.Next(1, moniker, fetched) == 0)
|
||||
{
|
||||
@@ -205,17 +209,32 @@ namespace Ink_Canvas.Helpers
|
||||
CreateBindCtx(0, out bindCtx);
|
||||
moniker[0].GetDisplayName(bindCtx, null, out displayName);
|
||||
|
||||
if (LooksLikePresentationFile(displayName) || displayName == "!{91493441-5A91-11CF-8700-00AA0060263B}")
|
||||
bool looksLikePresentationFile = LooksLikePresentationFile(displayName);
|
||||
bool isApplicationMoniker = ContainsMoniker(applicationMonikersFromProgIds, displayName);
|
||||
if (!isApplicationMoniker)
|
||||
{
|
||||
isApplicationMoniker = IsFallbackApplicationMoniker(displayName);
|
||||
}
|
||||
|
||||
if (looksLikePresentationFile || isApplicationMoniker)
|
||||
{
|
||||
rot.GetObject(moniker[0], out comObject);
|
||||
if (comObject != null)
|
||||
{
|
||||
try
|
||||
if (isApplicationMoniker)
|
||||
{
|
||||
object appObj = comObject.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, comObject, null);
|
||||
candidateApp = appObj;
|
||||
candidateApp = comObject;
|
||||
comObject = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
object appObj = comObject.GetType().InvokeMember("Application", BindingFlags.GetProperty, null, comObject, null);
|
||||
candidateApp = appObj;
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
}
|
||||
}
|
||||
bool isDuplicate = false;
|
||||
@@ -398,6 +417,59 @@ namespace Ink_Canvas.Helpers
|
||||
return false;
|
||||
}
|
||||
|
||||
private static string[] GetApplicationMonikersFromProgIds()
|
||||
{
|
||||
string[] ApplicationProgIds = new[]
|
||||
{
|
||||
"PowerPoint.Application",
|
||||
"KWPP.Application",
|
||||
"Wpp.Application",
|
||||
"WPP.Application",
|
||||
};
|
||||
|
||||
List<string> monikers = new List<string>();
|
||||
|
||||
foreach (string progId in ApplicationProgIds)
|
||||
{
|
||||
Guid clsid;
|
||||
if (CLSIDFromProgID(progId, out clsid) == 0 && clsid != Guid.Empty)
|
||||
{
|
||||
string moniker = "!" + clsid.ToString("B").ToUpperInvariant();
|
||||
if (!ContainsMoniker(monikers, moniker))
|
||||
{
|
||||
monikers.Add(moniker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return monikers.ToArray();
|
||||
}
|
||||
|
||||
private static bool ContainsMoniker(IEnumerable<string> monikers, string displayName)
|
||||
{
|
||||
if (monikers == null || string.IsNullOrEmpty(displayName))
|
||||
return false;
|
||||
|
||||
foreach (string moniker in monikers)
|
||||
{
|
||||
if (string.Equals(displayName, moniker, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsFallbackApplicationMoniker(string displayName)
|
||||
{
|
||||
string[] FallbackApplicationMonikers = new[]
|
||||
{
|
||||
"!{91493441-5A91-11CF-8700-00AA0060263B}",
|
||||
"!{44720441-94BF-4940-926D-4F38FECF2A48}",
|
||||
};
|
||||
|
||||
return ContainsMoniker(FallbackApplicationMonikers, displayName);
|
||||
}
|
||||
|
||||
public static bool IsSlideShowWindowActive(object sswObj)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
@@ -86,18 +84,8 @@ namespace Ink_Canvas.Helpers
|
||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Visible;
|
||||
|
||||
// 只有在页数有效时才更新页码显示
|
||||
if (currentSlide > 0 && totalSlides > 0)
|
||||
{
|
||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 页数无效时清空页码显示
|
||||
_mainWindow.PPTBtnPageNow.Text = "?";
|
||||
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
|
||||
}
|
||||
// 同步页码到所有翻页条 + 兼容旧绑定的隐藏 placeholder
|
||||
SetPageNumberOnAllBars(currentSlide, totalSlides);
|
||||
|
||||
UpdateNavigationPanelsVisibility();
|
||||
UpdateNavigationButtonStyles();
|
||||
@@ -112,6 +100,11 @@ namespace Ink_Canvas.Helpers
|
||||
MainWindow.MoveWindow(new WindowInteropHelper(_mainWindow).Handle, 0, 0,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, true);
|
||||
|
||||
// MoveWindow 触发的 WM_WINDOWPOSCHANGING + 重绘会打断面板的 ShowWithFadeIn 动画,
|
||||
// 在窗口尺寸最终确定后重新评估一次翻页面板的可见性。
|
||||
UpdateNavigationPanelsVisibility();
|
||||
UpdateNavigationButtonStyles();
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
|
||||
_mainWindow.isFullScreenApplied = true; // 标记已应用全屏处理
|
||||
@@ -158,18 +151,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有在页数有效时才更新页码显示
|
||||
if (currentSlide > 0 && totalSlides > 0)
|
||||
{
|
||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 页数无效时清空页码显示
|
||||
_mainWindow.PPTBtnPageNow.Text = "?";
|
||||
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
|
||||
}
|
||||
SetPageNumberOnAllBars(currentSlide, totalSlides);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -178,6 +160,34 @@ namespace Ink_Canvas.Helpers
|
||||
});
|
||||
}
|
||||
|
||||
private void SetPageNumberOnAllBars(int currentSlide, int totalSlides)
|
||||
{
|
||||
var bars = new[]
|
||||
{
|
||||
_mainWindow.LeftBottomPanelForPPTNavigation,
|
||||
_mainWindow.RightBottomPanelForPPTNavigation,
|
||||
_mainWindow.LeftSidePanelForPPTNavigation,
|
||||
_mainWindow.RightSidePanelForPPTNavigation,
|
||||
};
|
||||
foreach (var bar in bars)
|
||||
{
|
||||
if (bar == null) continue;
|
||||
bar.CurrentSlide = currentSlide;
|
||||
bar.TotalSlides = totalSlides;
|
||||
}
|
||||
// 兼容旧绑定(其它界面通过 ElementName 引用 PPTBtnPageNow / PPTBtnPageTotal)
|
||||
if (currentSlide > 0 && totalSlides > 0)
|
||||
{
|
||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
||||
}
|
||||
else
|
||||
{
|
||||
_mainWindow.PPTBtnPageNow.Text = "?";
|
||||
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PPT放映状态变化
|
||||
/// </summary>
|
||||
@@ -386,16 +396,17 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
// 页码按钮显示
|
||||
var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
|
||||
_mainWindow.PPTLSPageButton.Visibility = pageButtonVisibility;
|
||||
_mainWindow.PPTRSPageButton.Visibility = pageButtonVisibility;
|
||||
_mainWindow.LeftSidePanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility);
|
||||
_mainWindow.RightSidePanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility);
|
||||
|
||||
// 透明度设置 - 直接使用用户设置的透明度值
|
||||
_mainWindow.PPTBtnLSBorder.Opacity = PPTLSButtonOpacity;
|
||||
_mainWindow.PPTBtnRSBorder.Opacity = PPTRSButtonOpacity;
|
||||
// 透明度
|
||||
_mainWindow.LeftSidePanelForPPTNavigation.SetBarOpacity(PPTLSButtonOpacity);
|
||||
_mainWindow.RightSidePanelForPPTNavigation.SetBarOpacity(PPTRSButtonOpacity);
|
||||
|
||||
// 颜色主题
|
||||
bool isDarkTheme = options[2] == '2';
|
||||
ApplyButtonTheme(_mainWindow.PPTBtnLSBorder, _mainWindow.PPTBtnRSBorder, isDarkTheme, true);
|
||||
_mainWindow.LeftSidePanelForPPTNavigation.ApplyTheme(isDarkTheme);
|
||||
_mainWindow.RightSidePanelForPPTNavigation.ApplyTheme(isDarkTheme);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -414,113 +425,23 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
// 页码按钮显示
|
||||
var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
|
||||
_mainWindow.PPTLBPageButton.Visibility = pageButtonVisibility;
|
||||
_mainWindow.PPTRBPageButton.Visibility = pageButtonVisibility;
|
||||
_mainWindow.LeftBottomPanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility);
|
||||
_mainWindow.RightBottomPanelForPPTNavigation.SetPageButtonVisibility(pageButtonVisibility);
|
||||
|
||||
// 透明度设置 - 直接使用用户设置的透明度值
|
||||
_mainWindow.PPTBtnLBBorder.Opacity = PPTLBButtonOpacity;
|
||||
_mainWindow.PPTBtnRBBorder.Opacity = PPTRBButtonOpacity;
|
||||
// 透明度
|
||||
_mainWindow.LeftBottomPanelForPPTNavigation.SetBarOpacity(PPTLBButtonOpacity);
|
||||
_mainWindow.RightBottomPanelForPPTNavigation.SetBarOpacity(PPTRBButtonOpacity);
|
||||
|
||||
// 颜色主题
|
||||
bool isDarkTheme = options[2] == '2';
|
||||
ApplyButtonTheme(_mainWindow.PPTBtnLBBorder, _mainWindow.PPTBtnRBBorder, isDarkTheme, false);
|
||||
_mainWindow.LeftBottomPanelForPPTNavigation.ApplyTheme(isDarkTheme);
|
||||
_mainWindow.RightBottomPanelForPPTNavigation.ApplyTheme(isDarkTheme);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新底部按钮样式失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyButtonTheme(Border leftBorder, Border rightBorder, bool isDarkTheme, bool isSideButton)
|
||||
{
|
||||
try
|
||||
{
|
||||
Color backgroundColor, borderColor, foregroundColor, feedbackColor;
|
||||
|
||||
if (isDarkTheme)
|
||||
{
|
||||
backgroundColor = Color.FromRgb(39, 39, 42);
|
||||
borderColor = Color.FromRgb(82, 82, 91);
|
||||
foregroundColor = Colors.White;
|
||||
feedbackColor = Colors.White;
|
||||
}
|
||||
else
|
||||
{
|
||||
backgroundColor = Color.FromRgb(244, 244, 245);
|
||||
borderColor = Color.FromRgb(161, 161, 170);
|
||||
foregroundColor = Color.FromRgb(39, 39, 42);
|
||||
feedbackColor = Color.FromRgb(24, 24, 27);
|
||||
}
|
||||
|
||||
// 应用背景和边框颜色
|
||||
var backgroundBrush = new SolidColorBrush(backgroundColor);
|
||||
var borderBrush = new SolidColorBrush(borderColor);
|
||||
|
||||
leftBorder.Background = backgroundBrush;
|
||||
leftBorder.BorderBrush = borderBrush;
|
||||
rightBorder.Background = backgroundBrush;
|
||||
rightBorder.BorderBrush = borderBrush;
|
||||
|
||||
// 应用图标和文字颜色
|
||||
var foregroundBrush = new SolidColorBrush(foregroundColor);
|
||||
var feedbackBrush = new SolidColorBrush(feedbackColor);
|
||||
|
||||
if (isSideButton)
|
||||
{
|
||||
ApplySideButtonColors(foregroundBrush, feedbackBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyBottomButtonColors(foregroundBrush, feedbackBrush);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用按钮主题失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySideButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush)
|
||||
{
|
||||
// 图标颜色
|
||||
_mainWindow.PPTLSPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRSPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTLSNextButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRSNextButtonGeometry.Brush = foregroundBrush;
|
||||
|
||||
// 反馈背景颜色
|
||||
_mainWindow.PPTLSPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRSPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLSPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRSPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLSNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRSNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
|
||||
// 文字颜色
|
||||
TextBlock.SetForeground(_mainWindow.PPTLSPageButton, foregroundBrush);
|
||||
TextBlock.SetForeground(_mainWindow.PPTRSPageButton, foregroundBrush);
|
||||
}
|
||||
|
||||
private void ApplyBottomButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush)
|
||||
{
|
||||
// 图标颜色
|
||||
_mainWindow.PPTLBPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRBPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTLBNextButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRBNextButtonGeometry.Brush = foregroundBrush;
|
||||
|
||||
// 反馈背景颜色
|
||||
_mainWindow.PPTLBPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRBPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLBPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRBPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLBNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRBNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
|
||||
// 文字颜色
|
||||
TextBlock.SetForeground(_mainWindow.PPTLBPageButton, foregroundBrush);
|
||||
TextBlock.SetForeground(_mainWindow.PPTRBPageButton, foregroundBrush);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Windows.Data.Pdf;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用 Windows.Data.Pdf(WinRT)将 PDF 页渲染为 WPF 可用的位图。
|
||||
/// </summary>
|
||||
internal static class PdfWinRtHelper
|
||||
{
|
||||
public static async Task<uint> GetPageCountAsync(string pdfPath)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pdfPath) || !File.Exists(pdfPath))
|
||||
return 0;
|
||||
|
||||
var file = await StorageFile.GetFileFromPathAsync(pdfPath).AsTask();
|
||||
var doc = await PdfDocument.LoadFromFileAsync(file).AsTask();
|
||||
if (doc.IsPasswordProtected)
|
||||
return 0;
|
||||
return doc.PageCount;
|
||||
}
|
||||
|
||||
public static async Task<BitmapSource> RenderPageToBitmapSourceAsync(string pdfPath, uint pageIndex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pdfPath) || !File.Exists(pdfPath))
|
||||
return null;
|
||||
|
||||
var file = await StorageFile.GetFileFromPathAsync(pdfPath).AsTask();
|
||||
var doc = await PdfDocument.LoadFromFileAsync(file).AsTask();
|
||||
if (doc.IsPasswordProtected)
|
||||
return null;
|
||||
if (pageIndex >= doc.PageCount)
|
||||
return null;
|
||||
|
||||
var page = doc.GetPage(pageIndex);
|
||||
try
|
||||
{
|
||||
using (var ras = new InMemoryRandomAccessStream())
|
||||
{
|
||||
await page.RenderToStreamAsync(ras).AsTask();
|
||||
ras.Seek(0);
|
||||
|
||||
var ms = new MemoryStream();
|
||||
using (var netStream = ras.AsStreamForRead())
|
||||
netStream.CopyTo(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
try
|
||||
{
|
||||
return await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var bi = new BitmapImage();
|
||||
bi.BeginInit();
|
||||
bi.StreamSource = ms;
|
||||
bi.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bi.EndInit();
|
||||
bi.Freeze();
|
||||
return (BitmapSource)bi;
|
||||
});
|
||||
}
|
||||
finally
|
||||
{
|
||||
ms.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
(page as IDisposable)?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,274 +0,0 @@
|
||||
using iNKORE.UI.WPF.Controls;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮控件
|
||||
/// </summary>
|
||||
public class LauncherButton
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 实际按钮控件
|
||||
/// </summary>
|
||||
private readonly SimpleStackPanel _panel;
|
||||
|
||||
/// <summary>
|
||||
/// 获取按钮UI元素
|
||||
/// </summary>
|
||||
public UIElement Element => _panel;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="plugin">父插件</param>
|
||||
public LauncherButton(SuperLauncherPlugin plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
_plugin = plugin;
|
||||
LogHelper.WriteLogToFile("开始创建启动台按钮");
|
||||
|
||||
// 创建SimpleStackPanel
|
||||
_panel = new SimpleStackPanel
|
||||
{
|
||||
Name = "Launcher_Icon",
|
||||
Orientation = Orientation.Vertical,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Width = 28,
|
||||
Margin = new Thickness(0, -2, 0, 0),
|
||||
Background = Brushes.Transparent
|
||||
};
|
||||
|
||||
LogHelper.WriteLogToFile("创建SimpleStackPanel完成");
|
||||
|
||||
// 添加图标
|
||||
var image = CreateIconImage();
|
||||
_panel.Children.Add(image);
|
||||
|
||||
// 添加文本
|
||||
TextBlock textBlock = new TextBlock
|
||||
{
|
||||
Text = "启动台",
|
||||
Foreground = Brushes.Black,
|
||||
FontSize = 8,
|
||||
Margin = new Thickness(0, 1, 0, 0),
|
||||
TextAlignment = TextAlignment.Center
|
||||
};
|
||||
_panel.Children.Add(textBlock);
|
||||
|
||||
// 设置鼠标事件
|
||||
_panel.MouseDown += Panel_MouseDown;
|
||||
_panel.MouseUp += Panel_MouseUp;
|
||||
_panel.MouseLeave += Panel_MouseLeave;
|
||||
|
||||
// 右键菜单支持
|
||||
_panel.ContextMenu = CreateContextMenu();
|
||||
|
||||
// 设置工具提示
|
||||
_panel.ToolTip = "启动台";
|
||||
|
||||
LogHelper.WriteLogToFile("启动台按钮创建完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建右键菜单
|
||||
/// </summary>
|
||||
private ContextMenu CreateContextMenu()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建菜单
|
||||
ContextMenu menu = new ContextMenu();
|
||||
|
||||
// 创建位置切换菜单项
|
||||
MenuItem positionMenuItem = new MenuItem();
|
||||
positionMenuItem.Header = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ?
|
||||
"移至右侧" : "移至左侧";
|
||||
positionMenuItem.Click += (s, e) =>
|
||||
{
|
||||
// 切换位置
|
||||
_plugin.Config.ButtonPosition = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ?
|
||||
LauncherButtonPosition.Right : LauncherButtonPosition.Left;
|
||||
|
||||
// 更新按钮位置
|
||||
_plugin.UpdateButtonPosition();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile($"通过右键菜单切换启动台按钮位置为: {_plugin.Config.ButtonPosition}");
|
||||
};
|
||||
menu.Items.Add(positionMenuItem);
|
||||
|
||||
// 添加设置菜单项
|
||||
MenuItem settingsMenuItem = new MenuItem();
|
||||
settingsMenuItem.Header = "打开设置";
|
||||
settingsMenuItem.Click += (s, e) =>
|
||||
{
|
||||
// 打开插件设置窗口
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用反射调用主窗口的ShowPluginSettings方法
|
||||
var method = mainWindow.GetType().GetMethod("ShowPluginSettings");
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(mainWindow, null);
|
||||
LogHelper.WriteLogToFile("已打开插件设置窗口");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开插件设置窗口失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
};
|
||||
menu.Items.Add(settingsMenuItem);
|
||||
|
||||
return menu;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建右键菜单时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实际的UI元素
|
||||
/// </summary>
|
||||
[Obsolete("使用Element属性代替")]
|
||||
public UIElement GetUIElement()
|
||||
{
|
||||
return _panel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建图标图像
|
||||
/// </summary>
|
||||
private Image CreateIconImage()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建图像
|
||||
Image image = new Image
|
||||
{
|
||||
Height = 17,
|
||||
Margin = new Thickness(0, 3, 0, 0)
|
||||
};
|
||||
|
||||
// 设置位图缩放模式
|
||||
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
|
||||
|
||||
// 创建绘图图像
|
||||
DrawingImage drawingImage = new DrawingImage();
|
||||
DrawingGroup drawingGroup = new DrawingGroup();
|
||||
drawingGroup.ClipGeometry = Geometry.Parse("M0,0 V24 H24 V0 H0 Z");
|
||||
|
||||
// 使用提供的应用网格图标
|
||||
GeometryDrawing geometryDrawing = new GeometryDrawing
|
||||
{
|
||||
Brush = new SolidColorBrush(Color.FromRgb(0x1B, 0x1B, 0x1B)),
|
||||
Geometry = Geometry.Parse("F0 M24,24z M0,0z M4.41721,4.29873C4.35178,4.29873,4.29873,4.35178,4.29873,4.41721L4.29873,9.15646C4.29873,9.22189,4.35178,9.27494,4.41721,9.27494L9.15646,9.27494C9.22189,9.27494,9.27494,9.22189,9.27494,9.15646L9.27494,4.41721C9.27494,4.35178,9.22189,4.29873,9.15646,4.29873L4.41721,4.29873z M2.64,4.41721C2.64,3.43569,3.43569,2.64,4.41721,2.64L9.15646,2.64C10.138,2.64,10.9337,3.43569,10.9337,4.41721L10.9337,9.15646C10.9337,10.138,10.138,10.9337,9.15646,10.9337L4.41721,10.9337C3.43569,10.9337,2.64,10.138,2.64,9.15646L2.64,4.41721z M14.8435,4.29873C14.7781,4.29873,14.7251,4.35178,14.7251,4.41721L14.7251,9.15646C14.7251,9.22189,14.7781,9.27494,14.8435,9.27494L19.5828,9.27494C19.6482,9.27494,19.7013,9.22189,19.7013,9.15646L19.7013,4.41721C19.7013,4.35178,19.6482,4.29873,19.5828,4.29873L14.8435,4.29873z M13.0663,4.41721C13.0663,3.43569,13.862,2.64,14.8435,2.64L19.5828,2.64C20.5643,2.64,21.36,3.43569,21.36,4.41721L21.36,9.15646C21.36,10.138,20.5643,10.9337,19.5828,10.9337L14.8435,10.9337C13.862,10.9337,13.0663,10.138,13.0663,9.15646L13.0663,4.41721z M14.8435,14.7251C14.7781,14.7251,14.7251,14.7781,14.7251,14.8435L14.7251,19.5828C14.7251,19.6482,14.7781,19.7013,14.8435,19.7013L19.5828,19.7013C19.6482,19.7013,19.7013,19.6482,19.7013,19.5828L19.7013,14.8435C19.7013,14.7781,19.6482,14.7251,19.5828,14.7251L14.8435,14.7251z M13.0663,14.8435C13.0663,13.862,13.862,13.0663,14.8435,13.0663L19.5828,13.0663C20.5643,13.0663,21.36,13.862,21.36,14.8435L21.36,19.5828C21.36,20.5643,20.5643,21.36,19.5828,21.36L14.8435,21.36C13.862,21.36,13.0663,20.5643,13.0663,19.5828L13.0663,14.8435z M4.41721,14.7251C4.35178,14.7251,4.29873,14.7781,4.29873,14.8435L4.29873,19.5828C4.29873,19.6482,4.35178,19.7013,4.41721,19.7013L9.15646,19.7013C9.22189,19.7013,9.27494,19.6482,9.27494,19.5828L9.27494,14.8435C9.27494,14.7781,9.22189,14.7251,9.15646,14.7251L4.41721,14.7251z M2.64,14.8435C2.64,13.862,3.43569,13.0663,4.41721,13.0663L9.15646,13.0663C10.138,13.0663,10.9337,13.862,10.9337,14.8435L10.9337,19.5828C10.9337,20.5643,10.138,21.36,9.15646,21.36L4.41721,21.36C3.43569,21.36,2.64,20.5643,2.64,19.5828L2.64,14.8435z")
|
||||
};
|
||||
|
||||
drawingGroup.Children.Add(geometryDrawing);
|
||||
|
||||
// 设置图像源
|
||||
drawingImage.Drawing = drawingGroup;
|
||||
image.Source = drawingImage;
|
||||
|
||||
return image;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建图标图像时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
|
||||
// 返回一个空图像
|
||||
return new Image();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标按下事件
|
||||
/// </summary>
|
||||
private void Panel_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 提供反馈
|
||||
_panel.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
|
||||
LogHelper.WriteLogToFile("启动台按钮鼠标按下");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标按下事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标抬起事件
|
||||
/// </summary>
|
||||
private void Panel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有左键点击才显示启动台窗口
|
||||
if (e.ChangedButton != MouseButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 恢复背景
|
||||
_panel.Background = Brushes.Transparent;
|
||||
LogHelper.WriteLogToFile("启动台按钮鼠标抬起,准备显示启动台窗口");
|
||||
|
||||
// 获取按钮在屏幕上的位置
|
||||
Point buttonPosition = _panel.PointToScreen(new Point(_panel.ActualWidth / 2, 0));
|
||||
|
||||
// 显示启动台窗口
|
||||
_plugin.ShowLauncherWindow(buttonPosition);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标抬起事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标离开事件
|
||||
/// </summary>
|
||||
private void Panel_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 恢复背景
|
||||
_panel.Background = Brushes.Transparent;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标离开事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,332 +0,0 @@
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮位置
|
||||
/// </summary>
|
||||
public enum LauncherButtonPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// 左侧
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// 右侧
|
||||
/// </summary>
|
||||
Right
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动台配置
|
||||
/// </summary>
|
||||
public class LauncherConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮位置
|
||||
/// </summary>
|
||||
public LauncherButtonPosition ButtonPosition { get; set; } = LauncherButtonPosition.Right;
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用程序列表
|
||||
/// </summary>
|
||||
public List<LauncherItem> Items { get; set; } = new List<LauncherItem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用项
|
||||
/// </summary>
|
||||
public class LauncherItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用程序名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序路径
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可见
|
||||
/// </summary>
|
||||
public bool IsVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 在启动台中的位置(0-39)
|
||||
/// </summary>
|
||||
public int Position { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已固定位置
|
||||
/// </summary>
|
||||
public bool IsPositionFixed { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 图标缓存
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
private ImageSource _iconCache;
|
||||
|
||||
/// <summary>
|
||||
/// 获取应用程序图标
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ImageSource Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconCache != null)
|
||||
{
|
||||
return _iconCache;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(Path))
|
||||
{
|
||||
// 从文件中获取图标
|
||||
Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(Path);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从注册表中获取文件类型关联图标
|
||||
string extension = System.IO.Path.GetExtension(Path);
|
||||
if (!string.IsNullOrEmpty(extension))
|
||||
{
|
||||
string fileType = Registry.ClassesRoot.OpenSubKey(extension)?.GetValue(string.Empty) as string;
|
||||
if (!string.IsNullOrEmpty(fileType))
|
||||
{
|
||||
string iconPath = Registry.ClassesRoot.OpenSubKey(fileType + "\\DefaultIcon")?.GetValue(string.Empty) as string;
|
||||
if (!string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
string[] parts = iconPath.Split(',');
|
||||
string iconFile = parts[0].Trim('"');
|
||||
int iconIndex = parts.Length > 1 ? Convert.ToInt32(parts[1]) : 0;
|
||||
|
||||
if (File.Exists(iconFile))
|
||||
{
|
||||
Icon icon = IconExtractor.Extract(iconFile, iconIndex, true);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取应用图标时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 返回默认图标
|
||||
return GetDefaultIcon();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取默认图标
|
||||
/// </summary>
|
||||
private ImageSource GetDefaultIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 对于资源管理器,使用特定图标
|
||||
if (Path.EndsWith("explorer.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 直接从C:\Windows\explorer.exe获取图标
|
||||
string explorerPath = @"C:\Windows\explorer.exe";
|
||||
if (File.Exists(explorerPath))
|
||||
{
|
||||
Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(explorerPath);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取资源管理器图标时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
// 如果获取Windows图标失败,回退到默认图标
|
||||
}
|
||||
|
||||
// 回退到备用图标
|
||||
string explorerIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_folder_24_regular.png");
|
||||
if (File.Exists(explorerIconPath))
|
||||
{
|
||||
Uri uri = new Uri(explorerIconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回一个简单的默认图标
|
||||
string iconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-png", "icc.png");
|
||||
if (File.Exists(iconPath))
|
||||
{
|
||||
Uri uri = new Uri(iconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
|
||||
// 如果还是没有找到,尝试使用应用程序图标
|
||||
string appIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_apps_24_regular.png");
|
||||
if (File.Exists(appIconPath))
|
||||
{
|
||||
Uri uri = new Uri(appIconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取默认图标时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动应用程序
|
||||
/// </summary>
|
||||
public void Launch()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path))
|
||||
{
|
||||
LogHelper.WriteLogToFile("无法启动应用程序:路径为空", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!File.Exists(Path) && !Path.Contains(":\\"))
|
||||
{
|
||||
// 可能是系统命令,如explorer.exe
|
||||
ProcessStartInfo psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用Process.Start启动应用程序
|
||||
ProcessStartInfo psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已启动应用程序: {Path}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动应用程序时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"启动应用程序时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图标提取工具类
|
||||
/// </summary>
|
||||
public static class IconExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// 从文件中提取图标
|
||||
/// </summary>
|
||||
/// <param name="file">文件路径</param>
|
||||
/// <param name="index">图标索引</param>
|
||||
/// <param name="largeIcon">是否提取大图标</param>
|
||||
/// <returns>提取的图标</returns>
|
||||
public static Icon Extract(string file, int index, bool largeIcon)
|
||||
{
|
||||
try
|
||||
{
|
||||
IntPtr large;
|
||||
IntPtr small;
|
||||
ExtractIconEx(file, index, out large, out small, 1);
|
||||
|
||||
try
|
||||
{
|
||||
return Icon.FromHandle(largeIcon ? large : small);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (large != IntPtr.Zero)
|
||||
DestroyIcon(large);
|
||||
|
||||
if (small != IntPtr.Zero)
|
||||
DestroyIcon(small);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("Shell32.dll", EntryPoint = "ExtractIconEx")]
|
||||
private static extern int ExtractIconEx(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string lpszFile,
|
||||
int nIconIndex,
|
||||
out IntPtr phiconLarge,
|
||||
out IntPtr phiconSmall,
|
||||
int nIcons);
|
||||
|
||||
[DllImport("User32.dll")]
|
||||
private static extern int DestroyIcon(IntPtr hIcon);
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
<UserControl x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherSettingsControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="500" d:DesignWidth="600">
|
||||
|
||||
<UserControl.Resources>
|
||||
<!-- 自定义按钮样式 -->
|
||||
<Style x:Key="DefaultButtonStyle" TargetType="Button">
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextElement.Foreground="{TemplateBinding Foreground}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.8"/>
|
||||
<Setter Property="Effect">
|
||||
<Setter.Value>
|
||||
<DropShadowEffect Color="Black" Direction="270" ShadowDepth="2" Opacity="0.3" BlurRadius="4"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.6"/>
|
||||
<Setter Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<ScaleTransform ScaleX="0.95" ScaleY="0.95"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.4"/>
|
||||
<Setter Property="Cursor" Value="Arrow"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题 -->
|
||||
<TextBlock Grid.Row="0" Text="超级启动台设置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15" Foreground="Black"/>
|
||||
|
||||
<!-- 基本设置 -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,15">
|
||||
<TextBlock Text="基本设置" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
|
||||
<Grid Margin="10,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 按钮位置 -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="按钮位置:" VerticalAlignment="Center" Foreground="Black"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Margin="0,5">
|
||||
<RadioButton x:Name="RbtnLeft" Content="浮动栏左侧" Margin="0,0,20,0" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
||||
<RadioButton x:Name="RbtnRight" Content="浮动栏右侧" IsChecked="True" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 应用管理 -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="应用管理" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
|
||||
<Border Grid.Row="1" BorderThickness="1" BorderBrush="#CCCCCC" CornerRadius="5">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 应用列表 -->
|
||||
<DataGrid Grid.Row="0" x:Name="DgApps" AutoGenerateColumns="False" Margin="5"
|
||||
CanUserAddRows="False" CanUserDeleteRows="False"
|
||||
HeadersVisibility="Column" SelectionMode="Single"
|
||||
SelectionChanged="DgApps_SelectionChanged">
|
||||
<DataGrid.Columns>
|
||||
<DataGridCheckBoxColumn Header="显示" Binding="{Binding IsVisible}" Width="50"/>
|
||||
<DataGridTextColumn Header="名称" Binding="{Binding Name}" Width="150"/>
|
||||
<DataGridTextColumn Header="路径" Binding="{Binding Path}" Width="*"/>
|
||||
<DataGridTextColumn Header="位置" Binding="{Binding Position}" Width="50"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5">
|
||||
<Button x:Name="BtnAdd" Content="添加" Padding="10,5" Margin="0,5,5,5" Click="BtnAdd_Click"
|
||||
Background="#FF007ACC" Foreground="White" BorderBrush="#FF005A9B" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
<Button x:Name="BtnEdit" Content="编辑" Padding="10,5" Margin="5" Click="BtnEdit_Click"
|
||||
Background="#FF6C757D" Foreground="White" BorderBrush="#FF5A6268" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
<Button x:Name="BtnDelete" Content="删除" Padding="10,5" Margin="5" Click="BtnDelete_Click"
|
||||
Background="#FFDC3545" Foreground="White" BorderBrush="#FFBD2130" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
|
||||
<Button x:Name="BtnSave" Content="保存设置" Padding="15,5" Click="BtnSave_Click"
|
||||
Background="#FF28A745" Foreground="White" BorderBrush="#FF1E7E34" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -1,396 +0,0 @@
|
||||
using Ink_Canvas.Windows;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// LauncherSettingsControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LauncherSettingsControl : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="plugin">父插件</param>
|
||||
public LauncherSettingsControl(SuperLauncherPlugin plugin)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_plugin = plugin;
|
||||
|
||||
// 设置按钮位置
|
||||
RbtnLeft.IsChecked = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left;
|
||||
RbtnRight.IsChecked = _plugin.Config.ButtonPosition == LauncherButtonPosition.Right;
|
||||
|
||||
// 绑定应用列表
|
||||
DgApps.ItemsSource = _plugin.LauncherItems;
|
||||
|
||||
// 初始化按钮状态
|
||||
UpdateButtonStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新按钮状态
|
||||
/// </summary>
|
||||
private void UpdateButtonStates()
|
||||
{
|
||||
bool hasSelection = DgApps.SelectedItem != null;
|
||||
BtnEdit.IsEnabled = hasSelection;
|
||||
BtnDelete.IsEnabled = hasSelection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 位置单选按钮选择事件
|
||||
/// </summary>
|
||||
private void RbtnPosition_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
|
||||
LauncherButtonPosition oldPosition = _plugin.Config.ButtonPosition;
|
||||
|
||||
if (sender == RbtnLeft)
|
||||
{
|
||||
_plugin.Config.ButtonPosition = LauncherButtonPosition.Left;
|
||||
}
|
||||
else if (sender == RbtnRight)
|
||||
{
|
||||
_plugin.Config.ButtonPosition = LauncherButtonPosition.Right;
|
||||
}
|
||||
|
||||
// 如果位置发生变化,更新按钮位置
|
||||
if (oldPosition != _plugin.Config.ButtonPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 更新按钮位置
|
||||
_plugin.UpdateButtonPosition();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile($"启动台按钮位置已更改为: {_plugin.Config.ButtonPosition}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"更新启动台按钮位置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建新的启动项
|
||||
LauncherItem item = new LauncherItem
|
||||
{
|
||||
Name = "",
|
||||
Path = "",
|
||||
IsVisible = true,
|
||||
Position = -1 // 让插件管理器分配位置
|
||||
};
|
||||
|
||||
// 直接显示编辑对话框
|
||||
EditLauncherItem(item, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加启动项时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"添加启动项时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑应用按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DgApps.SelectedItem is LauncherItem item)
|
||||
{
|
||||
EditLauncherItem(item, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除应用按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnDelete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DgApps.SelectedItem is LauncherItem item)
|
||||
{
|
||||
// 确认删除
|
||||
MessageBoxResult result = MessageBox.Show(
|
||||
$"确定要删除 {item.Name} 吗?",
|
||||
"删除确认",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
// 从集合中移除
|
||||
_plugin.LauncherItems.Remove(item);
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
// 如果插件已启用,重新加载启动台按钮
|
||||
if (_plugin.IsEnabled)
|
||||
{
|
||||
_plugin.Disable();
|
||||
_plugin.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果插件未启用,则启用它
|
||||
_plugin.Enable();
|
||||
|
||||
// 通知PluginSettingsWindow刷新插件列表
|
||||
var window = Window.GetWindow(this);
|
||||
if (window is PluginSettingsWindow pluginSettingsWindow)
|
||||
{
|
||||
// 触发刷新
|
||||
pluginSettingsWindow.RefreshPluginList();
|
||||
}
|
||||
}
|
||||
|
||||
MessageBox.Show("设置已保存并应用!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"保存设置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用项选择变更事件
|
||||
/// </summary>
|
||||
private void DgApps_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
UpdateButtonStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑启动项
|
||||
/// </summary>
|
||||
/// <param name="item">启动项</param>
|
||||
/// <param name="isNew">是否为新建</param>
|
||||
private void EditLauncherItem(LauncherItem item, bool isNew)
|
||||
{
|
||||
// 创建简单的编辑窗口
|
||||
Window editWindow = new Window
|
||||
{
|
||||
Title = isNew ? "添加" : "编辑应用",
|
||||
Width = 400,
|
||||
Height = 200,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
ResizeMode = ResizeMode.NoResize
|
||||
};
|
||||
|
||||
// 创建编辑表单
|
||||
Grid grid = new Grid
|
||||
{
|
||||
Margin = new Thickness(20)
|
||||
};
|
||||
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(80) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
// 名称输入框
|
||||
TextBlock nameLabel = new TextBlock
|
||||
{
|
||||
Text = "名称:",
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
TextBox nameTextBox = new TextBox
|
||||
{
|
||||
Text = item.Name,
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
};
|
||||
|
||||
Grid.SetRow(nameLabel, 0);
|
||||
Grid.SetColumn(nameLabel, 0);
|
||||
Grid.SetRow(nameTextBox, 0);
|
||||
Grid.SetColumn(nameTextBox, 1);
|
||||
|
||||
grid.Children.Add(nameLabel);
|
||||
grid.Children.Add(nameTextBox);
|
||||
|
||||
// 路径输入框
|
||||
TextBlock pathLabel = new TextBlock
|
||||
{
|
||||
Text = "路径:",
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
Grid pathGrid = new Grid();
|
||||
pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength() });
|
||||
|
||||
TextBox pathTextBox = new TextBox
|
||||
{
|
||||
Text = item.Path,
|
||||
Margin = new Thickness(0, 5, 5, 5)
|
||||
};
|
||||
Button browseButton = new Button
|
||||
{
|
||||
Content = "浏览",
|
||||
Padding = new Thickness(5, 0, 5, 0),
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
};
|
||||
|
||||
browseButton.Click += (s, e) =>
|
||||
{
|
||||
OpenFileDialog dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "选择应用程序",
|
||||
Filter = "应用程序 (*.exe)|*.exe|所有文件 (*.*)|*.*",
|
||||
Multiselect = false,
|
||||
FileName = pathTextBox.Text
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
pathTextBox.Text = dialog.FileName;
|
||||
|
||||
// 如果选择的是.exe文件,自动获取文件名填入名称字段
|
||||
if (Path.GetExtension(dialog.FileName).ToLower() == ".exe")
|
||||
{
|
||||
string fileName = Path.GetFileNameWithoutExtension(dialog.FileName);
|
||||
// 只有在名称字段为空或者是新建项目时才自动填入
|
||||
if (string.IsNullOrWhiteSpace(nameTextBox.Text) || isNew)
|
||||
{
|
||||
nameTextBox.Text = fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Grid.SetColumn(pathTextBox, 0);
|
||||
Grid.SetColumn(browseButton, 1);
|
||||
pathGrid.Children.Add(pathTextBox);
|
||||
pathGrid.Children.Add(browseButton);
|
||||
|
||||
Grid.SetRow(pathLabel, 1);
|
||||
Grid.SetColumn(pathLabel, 0);
|
||||
Grid.SetRow(pathGrid, 1);
|
||||
Grid.SetColumn(pathGrid, 1);
|
||||
|
||||
grid.Children.Add(pathLabel);
|
||||
grid.Children.Add(pathGrid);
|
||||
|
||||
// 确认和取消按钮
|
||||
StackPanel buttonPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Thickness(0, 10, 0, 0)
|
||||
};
|
||||
|
||||
Button okButton = new Button
|
||||
{
|
||||
Content = "确定",
|
||||
Padding = new Thickness(15, 5, 15, 5),
|
||||
Margin = new Thickness(0, 0, 10, 0),
|
||||
IsDefault = true
|
||||
};
|
||||
|
||||
Button cancelButton = new Button
|
||||
{
|
||||
Content = "取消",
|
||||
Padding = new Thickness(15, 5, 15, 5),
|
||||
IsCancel = true
|
||||
};
|
||||
|
||||
okButton.Click += (s, e) =>
|
||||
{
|
||||
// 验证输入
|
||||
if (string.IsNullOrWhiteSpace(nameTextBox.Text))
|
||||
{
|
||||
MessageBox.Show("请输入应用名称!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pathTextBox.Text))
|
||||
{
|
||||
MessageBox.Show("请输入应用路径!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新项目
|
||||
item.Name = nameTextBox.Text;
|
||||
item.Path = pathTextBox.Text;
|
||||
|
||||
// 如果是新建,添加到集合
|
||||
if (isNew)
|
||||
{
|
||||
_plugin.AddLauncherItem(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 触发属性变更通知,刷新DataGrid
|
||||
if (DgApps.ItemsSource is ICollectionView view)
|
||||
{
|
||||
view.Refresh();
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
|
||||
editWindow.DialogResult = true;
|
||||
editWindow.Close();
|
||||
};
|
||||
|
||||
cancelButton.Click += (s, e) =>
|
||||
{
|
||||
editWindow.DialogResult = false;
|
||||
editWindow.Close();
|
||||
};
|
||||
|
||||
buttonPanel.Children.Add(okButton);
|
||||
buttonPanel.Children.Add(cancelButton);
|
||||
|
||||
Grid.SetRow(buttonPanel, 2);
|
||||
Grid.SetColumnSpan(buttonPanel, 2);
|
||||
|
||||
grid.Children.Add(buttonPanel);
|
||||
|
||||
// 设置窗口内容
|
||||
editWindow.Content = grid;
|
||||
|
||||
// 显示窗口
|
||||
editWindow.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
<Window x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher"
|
||||
mc:Ignorable="d"
|
||||
Title="启动台"
|
||||
Width="400"
|
||||
Height="300"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="#80000000"
|
||||
ResizeMode="NoResize"
|
||||
Topmost="True"
|
||||
Deactivated="Window_Deactivated"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<Window.Resources>
|
||||
<!-- 应用项样式 -->
|
||||
<Style x:Key="LauncherItemStyle" TargetType="Button">
|
||||
<Setter Property="Width" Value="80"/>
|
||||
<Setter Property="Height" Value="80"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
<Setter Property="Background" Value="#40FFFFFF"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border" Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Image Grid.Row="0" Source="{Binding Icon}" Width="32" Height="32" Margin="0,10,0,5"/>
|
||||
<TextBlock Grid.Row="1" Text="{Binding Name}" TextWrapping="Wrap" TextAlignment="Center"
|
||||
Margin="2,0,2,8" FontSize="11" Foreground="White"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#80FFFFFF"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#C0FFFFFF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Border CornerRadius="15" Background="#80000000" BorderThickness="1" BorderBrush="#40FFFFFF">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题栏 -->
|
||||
<Grid Grid.Row="0" Height="40">
|
||||
<TextBlock Text="启动台" Foreground="White" FontSize="18" FontWeight="Bold"
|
||||
VerticalAlignment="Center" Margin="15,0,0,0"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,10,0">
|
||||
<Button x:Name="BtnFixMode" Click="BtnFixMode_Click" Width="30" Height="30"
|
||||
Margin="5,0" Background="Transparent" BorderThickness="0"
|
||||
ToolTip="切换固定模式">
|
||||
<Path x:Name="FixModeIcon" Data="M7,2V13H10V22L17,10H13L17,2H7Z" Fill="White" Stretch="Uniform" Width="16" Height="16"/>
|
||||
</Button>
|
||||
<Button x:Name="BtnClose" Click="BtnClose_Click" Width="30" Height="30"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
ToolTip="关闭">
|
||||
<Path Data="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
|
||||
Fill="White" Stretch="Uniform" Width="16" Height="16"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 应用网格 -->
|
||||
<ScrollViewer Grid.Row="1" Margin="10" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<WrapPanel x:Name="AppPanel" Orientation="Horizontal" HorizontalAlignment="Center"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -1,466 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// LauncherWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LauncherWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 是否处于固定模式
|
||||
/// </summary>
|
||||
private bool _isFixMode;
|
||||
|
||||
/// <summary>
|
||||
/// 应用项按钮列表
|
||||
/// </summary>
|
||||
private readonly Dictionary<Button, LauncherItem> _appButtons = new Dictionary<Button, LauncherItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 拖拽中的按钮
|
||||
/// </summary>
|
||||
private Button _draggingButton;
|
||||
|
||||
/// <summary>
|
||||
/// 拖拽开始位置
|
||||
/// </summary>
|
||||
private Point _dragStartPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public LauncherWindow(SuperLauncherPlugin plugin)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_plugin = plugin;
|
||||
|
||||
// 加载应用项
|
||||
LoadLauncherItems();
|
||||
|
||||
// 添加鼠标按下事件(用于拖动窗口)
|
||||
MouseDown += (s, e) =>
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left && e.ButtonState == MouseButtonState.Pressed)
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
};
|
||||
|
||||
// 根据应用数量调整窗口大小
|
||||
AdjustWindowSize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载启动台应用项
|
||||
/// </summary>
|
||||
private void LoadLauncherItems()
|
||||
{
|
||||
// 清空现有应用项
|
||||
AppPanel.Children.Clear();
|
||||
_appButtons.Clear();
|
||||
|
||||
// 获取显示的应用项
|
||||
var visibleItems = _plugin.LauncherItems
|
||||
.Where(item => item.IsVisible)
|
||||
.OrderBy(item => item.Position)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in visibleItems)
|
||||
{
|
||||
// 创建应用按钮
|
||||
Button appButton = new Button
|
||||
{
|
||||
Style = (Style)FindResource("LauncherItemStyle"),
|
||||
DataContext = item,
|
||||
Tag = item.Position
|
||||
};
|
||||
|
||||
// 添加点击事件
|
||||
appButton.Click += AppButton_Click;
|
||||
|
||||
// 在固定模式下,添加拖拽事件
|
||||
appButton.PreviewMouseDown += AppButton_PreviewMouseDown;
|
||||
appButton.PreviewMouseMove += AppButton_PreviewMouseMove;
|
||||
appButton.PreviewMouseUp += AppButton_PreviewMouseUp;
|
||||
|
||||
// 记录按钮和项目的对应关系
|
||||
_appButtons.Add(appButton, item);
|
||||
|
||||
// 添加到面板
|
||||
AppPanel.Children.Add(appButton);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据应用数量调整窗口大小
|
||||
/// </summary>
|
||||
private void AdjustWindowSize()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 每行最多显示4个应用
|
||||
const int appsPerRow = 4;
|
||||
|
||||
// 计算行数
|
||||
int visibleCount = _appButtons.Count;
|
||||
int rowCount = (int)Math.Ceiling(visibleCount / (double)appsPerRow);
|
||||
|
||||
// 设置窗口宽度(每个应用90像素宽 = 80 + 5*2)
|
||||
Width = Math.Min(appsPerRow * 90 + 40, 400); // 最大宽度400
|
||||
|
||||
// 设置窗口高度(每个应用90像素高 = 80 + 5*2)
|
||||
Height = Math.Min(rowCount * 90 + 60, 600); // 最大高度600,标题栏40 + 边距20
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"调整启动台窗口大小时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮点击事件
|
||||
/// </summary>
|
||||
private void AppButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isFixMode) return; // 在固定模式下,不响应点击事件
|
||||
|
||||
if (sender is Button button && _appButtons.TryGetValue(button, out LauncherItem item))
|
||||
{
|
||||
// 获取应用路径和名称,用于后续启动
|
||||
string appPath = item.Path;
|
||||
string appName = item.Name;
|
||||
|
||||
LogHelper.WriteLogToFile($"点击启动应用: {appName}, 路径: {appPath}");
|
||||
|
||||
// 首先标记窗口正在关闭
|
||||
IsClosing = true;
|
||||
|
||||
// 创建一个应用启动任务
|
||||
var launchTask = new Task(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 等待一段时间,确保窗口关闭流程已经开始
|
||||
Thread.Sleep(200);
|
||||
|
||||
// 使用UI线程启动应用
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查应用路径是否存在
|
||||
if (File.Exists(appPath) || !appPath.Contains(":\\"))
|
||||
{
|
||||
// 创建进程启动信息
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = appPath,
|
||||
UseShellExecute = true,
|
||||
};
|
||||
|
||||
// 启动应用程序
|
||||
var process = Process.Start(psi);
|
||||
LogHelper.WriteLogToFile($"应用程序 {appName} 已启动");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用路径不存在: {appPath}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"找不到应用程序: {appPath}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动应用程序失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"启动应用程序失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用启动任务出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭窗口
|
||||
try
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try { Close(); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
|
||||
// 启动应用程序任务
|
||||
launchTask.Start();
|
||||
}), DispatcherPriority.Background);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"关闭窗口或启动任务时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
// 如果无法通过UI关闭窗口,直接启动任务
|
||||
launchTask.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用按钮点击事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
try { IsClosing = true; Close(); } catch (Exception innerEx) { System.Diagnostics.Debug.WriteLine(innerEx); }
|
||||
}
|
||||
}
|
||||
|
||||
#region 固定模式拖拽事件
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标按下事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isFixMode) return;
|
||||
|
||||
if (e.ChangedButton == MouseButton.Left && sender is Button button)
|
||||
{
|
||||
_draggingButton = button;
|
||||
_dragStartPoint = e.GetPosition(AppPanel);
|
||||
button.CaptureMouse();
|
||||
button.Opacity = 0.7;
|
||||
|
||||
// 阻止事件冒泡,以避免触发按钮点击
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标移动事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!_isFixMode || _draggingButton == null) return;
|
||||
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Point currentPosition = e.GetPosition(AppPanel);
|
||||
|
||||
// 移动按钮
|
||||
System.Windows.Controls.Canvas.SetLeft(_draggingButton, currentPosition.X - _draggingButton.ActualWidth / 2);
|
||||
System.Windows.Controls.Canvas.SetTop(_draggingButton, currentPosition.Y - _draggingButton.ActualHeight / 2);
|
||||
|
||||
// 将按钮移到最上层
|
||||
Panel.SetZIndex(_draggingButton, 100);
|
||||
|
||||
// 阻止事件冒泡
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标释放事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isFixMode || _draggingButton == null) return;
|
||||
|
||||
// 释放鼠标捕获
|
||||
_draggingButton.ReleaseMouseCapture();
|
||||
|
||||
// 计算新位置
|
||||
Point releasePoint = e.GetPosition(AppPanel);
|
||||
int newPosition = CalculateGridPosition(releasePoint);
|
||||
|
||||
// 获取当前项目
|
||||
LauncherItem currentItem = _appButtons[_draggingButton];
|
||||
|
||||
// 重新排序
|
||||
ReorderItems(currentItem, newPosition);
|
||||
|
||||
// 重新加载应用项
|
||||
LoadLauncherItems();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
// 清除拖拽状态
|
||||
_draggingButton.Opacity = 1;
|
||||
Panel.SetZIndex(_draggingButton, 0);
|
||||
_draggingButton = null;
|
||||
|
||||
// 阻止事件冒泡
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算网格位置
|
||||
/// </summary>
|
||||
private int CalculateGridPosition(Point point)
|
||||
{
|
||||
// 计算行和列
|
||||
int columnCount = 4; // 每行最多4个应用
|
||||
int columnWidth = 90; // 应用宽度(包括边距)
|
||||
int rowHeight = 90; // 应用高度(包括边距)
|
||||
|
||||
int column = (int)(point.X / columnWidth);
|
||||
int row = (int)(point.Y / rowHeight);
|
||||
|
||||
// 确保在有效范围内
|
||||
column = Math.Max(0, Math.Min(column, columnCount - 1));
|
||||
row = Math.Max(0, row);
|
||||
|
||||
// 计算位置索引
|
||||
return row * columnCount + column;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新排序应用项
|
||||
/// </summary>
|
||||
private void ReorderItems(LauncherItem item, int newPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 设置项目为固定位置
|
||||
item.IsPositionFixed = true;
|
||||
|
||||
// 如果位置相同,无需调整
|
||||
if (item.Position == newPosition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有可见项目
|
||||
var visibleItems = _plugin.LauncherItems
|
||||
.Where(i => i.IsVisible)
|
||||
.OrderBy(i => i.Position)
|
||||
.ToList();
|
||||
|
||||
// 移除当前项目
|
||||
visibleItems.Remove(item);
|
||||
|
||||
// 查找插入位置
|
||||
int insertIndex = 0;
|
||||
for (int i = 0; i < visibleItems.Count; i++)
|
||||
{
|
||||
if (visibleItems[i].Position >= newPosition)
|
||||
{
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
insertIndex = i + 1;
|
||||
}
|
||||
|
||||
// 插入项目
|
||||
visibleItems.Insert(insertIndex, item);
|
||||
|
||||
// 重新分配位置
|
||||
for (int i = 0; i < visibleItems.Count; i++)
|
||||
{
|
||||
visibleItems[i].Position = i;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"重新排序应用项时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口事件处理
|
||||
|
||||
/// <summary>
|
||||
/// 窗口失去焦点事件
|
||||
/// </summary>
|
||||
private void Window_Deactivated(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有在非固定模式、窗口已加载、未处于关闭状态且IsLoaded=true时关闭窗口
|
||||
if (!_isFixMode && IsLoaded && !IsClosing)
|
||||
{
|
||||
// 标记为正在关闭
|
||||
IsClosing = true;
|
||||
|
||||
// 使用Dispatcher.BeginInvoke而不是直接调用Close,避免冲突
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 再次检查窗口状态
|
||||
if (IsLoaded && !IsClosing)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"延迟关闭窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}), DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"窗口失去焦点关闭时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口是否正在关闭
|
||||
/// </summary>
|
||||
private bool IsClosing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重写OnClosing方法,标记窗口正在关闭
|
||||
/// </summary>
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
IsClosing = true;
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnClose_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 固定模式按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnFixMode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 切换固定模式
|
||||
_isFixMode = !_isFixMode;
|
||||
|
||||
// 更新固定模式按钮图标颜色
|
||||
FixModeIcon.Fill = _isFixMode ? Brushes.Yellow : Brushes.White;
|
||||
|
||||
// 显示提示
|
||||
if (_isFixMode)
|
||||
{
|
||||
MessageBox.Show("已进入固定模式,您可以拖动应用图标调整位置。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,589 +0,0 @@
|
||||
using Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn
|
||||
{
|
||||
/// <summary>
|
||||
/// 超级启动台插件
|
||||
/// </summary>
|
||||
public class SuperLauncherPlugin : PluginBase
|
||||
{
|
||||
#region 插件基本信息
|
||||
|
||||
public override string Name => "超级启动台";
|
||||
|
||||
public override string Description => "在浮动栏添加一个启动台按钮,可快速启动常用应用程序。";
|
||||
|
||||
public override Version Version => new Version(1, 0, 1);
|
||||
|
||||
public override string Author => "ICC CE 团队";
|
||||
|
||||
public override bool IsBuiltIn => true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件属性和字段
|
||||
|
||||
/// <summary>
|
||||
/// 启动台配置
|
||||
/// </summary>
|
||||
public LauncherConfig Config { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用程序列表
|
||||
/// </summary>
|
||||
public ObservableCollection<LauncherItem> LauncherItems { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动台按钮
|
||||
/// </summary>
|
||||
private LauncherButton _launcherButton;
|
||||
|
||||
/// <summary>
|
||||
/// 启动台窗口
|
||||
/// </summary>
|
||||
private LauncherWindow _launcherWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 配置文件路径
|
||||
/// </summary>
|
||||
private readonly string _configPath = Path.Combine(App.RootPath, "PluginConfigs", "SuperLauncher.json");
|
||||
|
||||
/// <summary>
|
||||
/// 标记是否已添加到浮动栏
|
||||
/// </summary>
|
||||
private bool _isAddedToFloatingBar;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件生命周期
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// 创建配置目录
|
||||
string configDir = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
if (!Directory.Exists(configDir))
|
||||
{
|
||||
Directory.CreateDirectory(configDir);
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
LoadConfig();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已初始化");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Enable()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsEnabled) return; // 防止重复启用
|
||||
|
||||
// 创建启动台按钮
|
||||
if (_launcherButton == null)
|
||||
{
|
||||
_launcherButton = new LauncherButton(this);
|
||||
LogHelper.WriteLogToFile("超级启动台按钮已创建");
|
||||
}
|
||||
|
||||
// 添加启动台按钮到浮动栏
|
||||
AddLauncherButtonToFloatingBar();
|
||||
|
||||
// 设置启用状态
|
||||
base.Enable();
|
||||
|
||||
// 保存插件配置
|
||||
SavePluginSettings();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已启用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Disable()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsEnabled) return; // 防止重复禁用
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
|
||||
// 如果启动台窗口打开,则关闭
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
}
|
||||
|
||||
// 设置禁用状态
|
||||
base.Disable();
|
||||
|
||||
// 保存插件配置
|
||||
SavePluginSettings();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已禁用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override UserControl GetSettingsView()
|
||||
{
|
||||
return new LauncherSettingsControl(this);
|
||||
}
|
||||
|
||||
public override void Cleanup()
|
||||
{
|
||||
// 保存配置
|
||||
SaveConfig();
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
|
||||
// 如果启动台窗口打开,则关闭
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
}
|
||||
|
||||
base.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件设置
|
||||
/// </summary>
|
||||
public override void SavePluginSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保配置已加载
|
||||
if (Config == null)
|
||||
{
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
// 更新其他设置,但不更改插件启用状态
|
||||
|
||||
// 保存配置
|
||||
SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件设置已保存");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存超级启动台插件设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 配置管理
|
||||
|
||||
/// <summary>
|
||||
/// 加载配置
|
||||
/// </summary>
|
||||
private void LoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_configPath))
|
||||
{
|
||||
string json = File.ReadAllText(_configPath);
|
||||
Config = JsonConvert.DeserializeObject<LauncherConfig>(json) ?? CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items ?? new List<LauncherItem>());
|
||||
|
||||
// 注意:不再根据配置更改插件启用状态
|
||||
// 插件状态由PluginManager统一管理
|
||||
}
|
||||
else
|
||||
{
|
||||
Config = CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items);
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
Config = CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存配置
|
||||
/// </summary>
|
||||
public void SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 同步LauncherItems到Config
|
||||
Config.Items = new List<LauncherItem>(LauncherItems);
|
||||
|
||||
// 序列化并保存配置
|
||||
string json = JsonConvert.SerializeObject(Config, Formatting.Indented);
|
||||
File.WriteAllText(_configPath, json);
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台配置已保存");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建默认配置
|
||||
/// </summary>
|
||||
private LauncherConfig CreateDefaultConfig()
|
||||
{
|
||||
var config = new LauncherConfig
|
||||
{
|
||||
ButtonPosition = LauncherButtonPosition.Right,
|
||||
// 不再使用IsEnabled,插件状态由PluginManager管理
|
||||
Items = new List<LauncherItem>
|
||||
{
|
||||
new LauncherItem
|
||||
{
|
||||
Name = "资源管理器",
|
||||
Path = @"C:\Windows\explorer.exe",
|
||||
IsVisible = true,
|
||||
Position = 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 启动台按钮管理
|
||||
|
||||
/// <summary>
|
||||
/// 将启动台按钮添加到浮动栏
|
||||
/// </summary>
|
||||
private void AddLauncherButtonToFloatingBar()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果已经添加,先移除
|
||||
if (_isAddedToFloatingBar)
|
||||
{
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
_isAddedToFloatingBar = false;
|
||||
}
|
||||
|
||||
// 获取主窗口实例
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到主窗口实例,无法添加启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建启动台按钮
|
||||
_launcherButton = new LauncherButton(this);
|
||||
var buttonElement = _launcherButton.Element;
|
||||
|
||||
// 查找浮动栏
|
||||
var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel;
|
||||
if (floatingBar == null)
|
||||
{
|
||||
// 如果直接查找失败,则尝试遍历可视树查找
|
||||
Panel floatingBarPanelFromTree = null;
|
||||
FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree);
|
||||
floatingBar = floatingBarPanelFromTree;
|
||||
}
|
||||
|
||||
if (floatingBar == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到浮动栏,无法添加启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加启动台按钮到浮动栏
|
||||
if (Config.ButtonPosition == LauncherButtonPosition.Left)
|
||||
{
|
||||
floatingBar.Children.Insert(0, buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏左侧");
|
||||
}
|
||||
else
|
||||
{
|
||||
floatingBar.Children.Add(buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏右侧");
|
||||
}
|
||||
|
||||
_isAddedToFloatingBar = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加启动台按钮到浮动栏时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归查找StackPanelFloatingBar
|
||||
/// </summary>
|
||||
private void FindStackPanelFloatingBar(DependencyObject parent, ref Panel result)
|
||||
{
|
||||
if (parent == null || result != null) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 检查当前对象是否为我们要找的面板
|
||||
if (parent is Panel panel && panel.Name == "StackPanelFloatingBar")
|
||||
{
|
||||
result = panel;
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取子元素数量
|
||||
int childCount = VisualTreeHelper.GetChildrenCount(parent);
|
||||
|
||||
// 遍历所有子元素
|
||||
for (int i = 0; i < childCount; i++)
|
||||
{
|
||||
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
|
||||
FindStackPanelFloatingBar(child, ref result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"查找StackPanelFloatingBar时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从浮动栏移除启动台按钮
|
||||
/// </summary>
|
||||
private void RemoveLauncherButtonFromFloatingBar()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_isAddedToFloatingBar || _launcherButton == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取主窗口实例
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到主窗口实例,无法移除启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取按钮元素
|
||||
var buttonElement = _launcherButton.Element;
|
||||
|
||||
// 查找浮动栏
|
||||
var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel;
|
||||
if (floatingBar == null)
|
||||
{
|
||||
// 如果直接查找失败,则尝试遍历可视树查找
|
||||
Panel floatingBarPanelFromTree = null;
|
||||
FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree);
|
||||
floatingBar = floatingBarPanelFromTree;
|
||||
}
|
||||
|
||||
if (floatingBar == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到浮动栏,无法移除启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
if (floatingBar.Children.Contains(buttonElement))
|
||||
{
|
||||
floatingBar.Children.Remove(buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已从浮动栏移除");
|
||||
}
|
||||
|
||||
_isAddedToFloatingBar = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"移除启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新启动台按钮位置
|
||||
/// </summary>
|
||||
public void UpdateButtonPosition()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果按钮已添加到浮动栏,重新添加以更新位置
|
||||
if (_isAddedToFloatingBar)
|
||||
{
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
AddLauncherButtonToFloatingBar();
|
||||
LogHelper.WriteLogToFile($"启动台按钮位置已更新为: {Config.ButtonPosition}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 启动台功能
|
||||
|
||||
/// <summary>
|
||||
/// 显示启动台窗口
|
||||
/// </summary>
|
||||
/// <param name="buttonPosition">按钮在屏幕上的位置</param>
|
||||
public void ShowLauncherWindow(Point buttonPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果窗口已存在,关闭它
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建新的启动台窗口
|
||||
_launcherWindow = new LauncherWindow(this);
|
||||
|
||||
// 计算窗口位置,使其位于按钮上方
|
||||
PositionLauncherWindow(_launcherWindow, buttonPosition);
|
||||
|
||||
// 显示窗口
|
||||
_launcherWindow.Show();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示启动台窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置启动台窗口位置
|
||||
/// </summary>
|
||||
/// <param name="window">启动台窗口</param>
|
||||
/// <param name="buttonPosition">按钮在屏幕上的位置</param>
|
||||
private void PositionLauncherWindow(LauncherWindow window, Point buttonPosition)
|
||||
{
|
||||
// 确保窗口已加载
|
||||
if (window.ActualWidth == 0 || window.ActualHeight == 0)
|
||||
{
|
||||
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
|
||||
// 设置窗口加载完成后的位置
|
||||
window.Loaded += (s, e) =>
|
||||
{
|
||||
// 窗口位于按钮上方居中
|
||||
double left = buttonPosition.X - (window.ActualWidth / 2);
|
||||
double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距
|
||||
|
||||
// 确保窗口在屏幕内
|
||||
left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth));
|
||||
top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight));
|
||||
|
||||
window.Left = left;
|
||||
window.Top = top;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 窗口位于按钮上方居中
|
||||
double left = buttonPosition.X - (window.ActualWidth / 2);
|
||||
double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距
|
||||
|
||||
// 确保窗口在屏幕内
|
||||
left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth));
|
||||
top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight));
|
||||
|
||||
window.Left = left;
|
||||
window.Top = top;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加应用到启动台
|
||||
/// </summary>
|
||||
/// <param name="item">启动台项</param>
|
||||
public void AddLauncherItem(LauncherItem item)
|
||||
{
|
||||
// 如果项目数量已达上限,则不添加
|
||||
if (LauncherItems.Count >= 40)
|
||||
{
|
||||
MessageBox.Show("启动台项目数量已达上限(40个)!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
// 寻找合适的位置
|
||||
if (item.Position < 0)
|
||||
{
|
||||
item.Position = FindNextAvailablePosition();
|
||||
}
|
||||
|
||||
// 添加项目并保存配置
|
||||
LauncherItems.Add(item);
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找下一个可用位置
|
||||
/// </summary>
|
||||
private int FindNextAvailablePosition()
|
||||
{
|
||||
// 获取已使用的位置列表
|
||||
var usedPositions = new HashSet<int>();
|
||||
foreach (var item in LauncherItems)
|
||||
{
|
||||
usedPositions.Add(item.Position);
|
||||
}
|
||||
|
||||
// 查找第一个可用位置
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
if (!usedPositions.Contains(i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有位置都已使用,则返回0
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件基类,提供对插件服务的访问和基本实现
|
||||
/// </summary>
|
||||
public abstract class EnhancedPluginBase : PluginBase, IEnhancedPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务实例
|
||||
/// </summary>
|
||||
public IPluginService PluginService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
protected EnhancedPluginBase()
|
||||
{
|
||||
PluginService = PluginServiceManager.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
public virtual void OnStartup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
public virtual void OnShutdown()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
public virtual MenuItem[] GetMenuItems()
|
||||
{
|
||||
return new MenuItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
public virtual Button[] GetToolbarButtons()
|
||||
{
|
||||
return new Button[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
public virtual string GetStatusBarInfo()
|
||||
{
|
||||
return $"{Name} v{Version} - {(IsEnabled ? "已启用" : "已禁用")}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
public virtual void OnConfigurationChanged()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写初始化方法,调用OnStartup
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
OnStartup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写清理方法,调用OnShutdown
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
OnShutdown();
|
||||
base.Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,241 +0,0 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件基类 V2,提供对三个专门服务接口的访问
|
||||
/// 插件开发者可以根据需要选择性地使用这些服务
|
||||
/// </summary>
|
||||
public abstract class EnhancedPluginBaseV2 : PluginBase, IEnhancedPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取服务实例
|
||||
/// </summary>
|
||||
public IGetService GetService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 窗口服务实例
|
||||
/// </summary>
|
||||
public IWindowService WindowService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作服务实例
|
||||
/// </summary>
|
||||
public IActionService ActionService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件服务实例(兼容性)
|
||||
/// </summary>
|
||||
public IPluginService PluginService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
protected EnhancedPluginBaseV2()
|
||||
{
|
||||
// 初始化所有服务实例
|
||||
PluginService = PluginServiceManager.Instance;
|
||||
GetService = PluginServiceManager.Instance;
|
||||
WindowService = PluginServiceManager.Instance;
|
||||
ActionService = PluginServiceManager.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
public virtual void OnStartup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
public virtual void OnShutdown()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
public virtual MenuItem[] GetMenuItems()
|
||||
{
|
||||
return new MenuItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
public virtual Button[] GetToolbarButtons()
|
||||
{
|
||||
return new Button[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
public virtual string GetStatusBarInfo()
|
||||
{
|
||||
return $"{Name} v{Version} - {(IsEnabled ? "已启用" : "已禁用")}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
public virtual void OnConfigurationChanged()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
|
||||
}
|
||||
|
||||
#region 便捷方法
|
||||
|
||||
/// <summary>
|
||||
/// 显示通知消息
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
protected void ShowNotification(string message, NotificationType type = NotificationType.Info)
|
||||
{
|
||||
WindowService.ShowNotification(message, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示确认对话框
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <returns>用户选择结果</returns>
|
||||
protected bool ShowConfirmDialog(string message, string title = "确认")
|
||||
{
|
||||
return WindowService.ShowConfirmDialog(message, title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示输入对话框
|
||||
/// </summary>
|
||||
/// <param name="message">提示消息</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>用户输入内容</returns>
|
||||
protected string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
|
||||
{
|
||||
return WindowService.ShowInputDialog(message, title, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>设置值</returns>
|
||||
protected T GetSetting<T>(string key, T defaultValue = default(T))
|
||||
{
|
||||
return GetService.GetSetting(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="value">设置值</param>
|
||||
protected void SetSetting<T>(string key, T value)
|
||||
{
|
||||
ActionService.SetSetting(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置
|
||||
/// </summary>
|
||||
protected void SaveSettings()
|
||||
{
|
||||
ActionService.SaveSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除当前画布
|
||||
/// </summary>
|
||||
protected void ClearCanvas()
|
||||
{
|
||||
ActionService.ClearCanvas();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 撤销操作
|
||||
/// </summary>
|
||||
protected void Undo()
|
||||
{
|
||||
ActionService.Undo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重做操作
|
||||
/// </summary>
|
||||
protected void Redo()
|
||||
{
|
||||
ActionService.Redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以撤销
|
||||
/// </summary>
|
||||
protected bool CanUndo => GetService.CanUndo;
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以重做
|
||||
/// </summary>
|
||||
protected bool CanRedo => GetService.CanRedo;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前绘制模式
|
||||
/// </summary>
|
||||
protected int CurrentDrawingMode => GetService.CurrentDrawingMode;
|
||||
|
||||
/// <summary>
|
||||
/// 设置绘制模式
|
||||
/// </summary>
|
||||
/// <param name="mode">绘制模式</param>
|
||||
protected void SetDrawingMode(int mode)
|
||||
{
|
||||
ActionService.SetDrawingMode(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
protected void RegisterEventHandler(string eventName, System.EventHandler handler)
|
||||
{
|
||||
ActionService.RegisterEventHandler(eventName, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
protected void UnregisterEventHandler(string eventName, System.EventHandler handler)
|
||||
{
|
||||
ActionService.UnregisterEventHandler(eventName, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发事件
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="args">事件参数</param>
|
||||
protected void TriggerEvent(string eventName, object sender, System.EventArgs args)
|
||||
{
|
||||
ActionService.TriggerEvent(eventName, sender, args);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,296 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作服务接口,统一所有执行操作相关的方法
|
||||
/// </summary>
|
||||
public interface IActionService
|
||||
{
|
||||
#region 画布操作
|
||||
|
||||
/// <summary>
|
||||
/// 清除当前画布
|
||||
/// </summary>
|
||||
void ClearCanvas();
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有画布
|
||||
/// </summary>
|
||||
void ClearAllCanvases();
|
||||
|
||||
/// <summary>
|
||||
/// 添加新页面
|
||||
/// </summary>
|
||||
void AddNewPage();
|
||||
|
||||
/// <summary>
|
||||
/// 删除当前页面
|
||||
/// </summary>
|
||||
void DeleteCurrentPage();
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定页面
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页面索引</param>
|
||||
void SwitchToPage(int pageIndex);
|
||||
|
||||
/// <summary>
|
||||
/// 切换到下一页
|
||||
/// </summary>
|
||||
void NextPage();
|
||||
|
||||
/// <summary>
|
||||
/// 切换到上一页
|
||||
/// </summary>
|
||||
void PreviousPage();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制操作
|
||||
|
||||
/// <summary>
|
||||
/// 设置绘制模式
|
||||
/// </summary>
|
||||
/// <param name="mode">绘制模式</param>
|
||||
void SetDrawingMode(int mode);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触宽度
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
void SetInkWidth(double width);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触颜色
|
||||
/// </summary>
|
||||
/// <param name="color">颜色</param>
|
||||
void SetInkColor(Color color);
|
||||
|
||||
/// <summary>
|
||||
/// 设置高亮笔宽度
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
void SetHighlighterWidth(double width);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦大小
|
||||
/// </summary>
|
||||
/// <param name="size">大小</param>
|
||||
void SetEraserSize(int size);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦类型
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
void SetEraserType(int type);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦形状
|
||||
/// </summary>
|
||||
/// <param name="shape">形状</param>
|
||||
void SetEraserShape(int shape);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触透明度
|
||||
/// </summary>
|
||||
/// <param name="alpha">透明度</param>
|
||||
void SetInkAlpha(double alpha);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触样式
|
||||
/// </summary>
|
||||
/// <param name="style">样式</param>
|
||||
void SetInkStyle(int style);
|
||||
|
||||
/// <summary>
|
||||
/// 设置背景颜色
|
||||
/// </summary>
|
||||
/// <param name="color">颜色</param>
|
||||
void SetBackgroundColor(string color);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件操作
|
||||
|
||||
/// <summary>
|
||||
/// 保存画布内容
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void SaveCanvas(string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// 加载画布内容
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void LoadCanvas(string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// 导出为图片
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="format">图片格式</param>
|
||||
void ExportAsImage(string filePath, string format);
|
||||
|
||||
/// <summary>
|
||||
/// 导出为PDF
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void ExportAsPDF(string filePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 撤销重做操作
|
||||
|
||||
/// <summary>
|
||||
/// 撤销操作
|
||||
/// </summary>
|
||||
void Undo();
|
||||
|
||||
/// <summary>
|
||||
/// 重做操作
|
||||
/// </summary>
|
||||
void Redo();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 选择操作
|
||||
|
||||
/// <summary>
|
||||
/// 全选
|
||||
/// </summary>
|
||||
void SelectAll();
|
||||
|
||||
/// <summary>
|
||||
/// 取消选择
|
||||
/// </summary>
|
||||
void DeselectAll();
|
||||
|
||||
/// <summary>
|
||||
/// 删除选中内容
|
||||
/// </summary>
|
||||
void DeleteSelected();
|
||||
|
||||
/// <summary>
|
||||
/// 复制选中内容
|
||||
/// </summary>
|
||||
void CopySelected();
|
||||
|
||||
/// <summary>
|
||||
/// 剪切选中内容
|
||||
/// </summary>
|
||||
void CutSelected();
|
||||
|
||||
/// <summary>
|
||||
/// 粘贴内容
|
||||
/// </summary>
|
||||
void Paste();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统设置操作
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="value">设置值</param>
|
||||
void SetSetting<T>(string key, T value);
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置到文件
|
||||
/// </summary>
|
||||
void SaveSettings();
|
||||
|
||||
/// <summary>
|
||||
/// 从文件加载设置
|
||||
/// </summary>
|
||||
void LoadSettings();
|
||||
|
||||
/// <summary>
|
||||
/// 重置设置为默认值
|
||||
/// </summary>
|
||||
void ResetSettings();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件管理操作
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void EnablePlugin(string pluginName);
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void DisablePlugin(string pluginName);
|
||||
|
||||
/// <summary>
|
||||
/// 卸载插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void UnloadPlugin(string pluginName);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件系统操作
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
void RegisterEventHandler(string eventName, EventHandler handler);
|
||||
|
||||
/// <summary>
|
||||
/// 注销事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
void UnregisterEventHandler(string eventName, EventHandler handler);
|
||||
|
||||
/// <summary>
|
||||
/// 触发事件
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="args">事件参数</param>
|
||||
void TriggerEvent(string eventName, object sender, EventArgs args);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用程序操作
|
||||
|
||||
/// <summary>
|
||||
/// 重启应用程序
|
||||
/// </summary>
|
||||
void RestartApplication();
|
||||
|
||||
/// <summary>
|
||||
/// 退出应用程序
|
||||
/// </summary>
|
||||
void ExitApplication();
|
||||
|
||||
/// <summary>
|
||||
/// 检查更新
|
||||
/// </summary>
|
||||
void CheckForUpdates();
|
||||
|
||||
/// <summary>
|
||||
/// 打开帮助文档
|
||||
/// </summary>
|
||||
void OpenHelpDocument();
|
||||
|
||||
/// <summary>
|
||||
/// 打开关于页面
|
||||
/// </summary>
|
||||
void OpenAboutPage();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,178 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// ICCPP 插件适配器,用于加载和管理 .iccpp 格式的插件
|
||||
/// </summary>
|
||||
public class ICCPPPluginAdapter : PluginBase
|
||||
{
|
||||
private readonly byte[] _pluginData;
|
||||
private readonly string _pluginPath;
|
||||
private readonly string _pluginName;
|
||||
private readonly Version _pluginVersion;
|
||||
private bool _isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 创建 ICCPP 插件适配器
|
||||
/// </summary>
|
||||
/// <param name="pluginPath">插件文件路径</param>
|
||||
/// <param name="pluginData">插件文件数据</param>
|
||||
public ICCPPPluginAdapter(string pluginPath, byte[] pluginData)
|
||||
{
|
||||
_pluginPath = pluginPath;
|
||||
_pluginData = pluginData;
|
||||
PluginPath = pluginPath;
|
||||
|
||||
// 从文件名获取插件名称
|
||||
_pluginName = Path.GetFileNameWithoutExtension(pluginPath);
|
||||
_pluginVersion = new Version(1, 0, 0); // 默认版本
|
||||
|
||||
// 尝试从插件数据中读取更多信息
|
||||
TryReadPluginMetadata();
|
||||
}
|
||||
|
||||
public ICCPPPluginAdapter()
|
||||
{
|
||||
_pluginPath = string.Empty;
|
||||
_pluginData = new byte[0];
|
||||
PluginPath = string.Empty;
|
||||
_pluginName = "ICCPPPlugin";
|
||||
_pluginVersion = new Version(1, 0, 0);
|
||||
// 可选:初始化其他字段
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从插件数据中读取元数据
|
||||
/// </summary>
|
||||
private void TryReadPluginMetadata()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以根据 .iccpp 文件的实际格式解析元数据
|
||||
// 例如,如果文件有特定的头部结构,可以在这里解析
|
||||
|
||||
// 示例:如果前100字节包含元数据
|
||||
if (_pluginData.Length > 100)
|
||||
{
|
||||
// 解析元数据的代码...
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"解析插件 {_pluginName} 元数据时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#region IPlugin 接口实现
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public override string Name => _pluginName;
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public override string Description => $"{_pluginName} (ICCPP 格式插件)";
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public override Version Version => _pluginVersion;
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public override string Author => "未知";
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
public override bool IsBuiltIn => false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的初始化逻辑
|
||||
// 例如,根据文件格式加载特定资源
|
||||
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已初始化");
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
public override void Enable()
|
||||
{
|
||||
if (IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的启用逻辑
|
||||
// 例如,加载动态库、注册事件等
|
||||
|
||||
base.Enable(); // 设置启用状态并触发事件
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已启用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
public override void Disable()
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的禁用逻辑
|
||||
// 例如,卸载动态库、注销事件等
|
||||
|
||||
base.Disable(); // 设置禁用状态并触发事件
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已禁用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理插件资源
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的清理逻辑
|
||||
// 例如,释放资源等
|
||||
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已清理资源");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理 ICCPP 插件 {Name} 资源时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件接口,提供对插件服务的访问
|
||||
/// </summary>
|
||||
public interface IEnhancedPlugin : IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取插件服务实例
|
||||
/// </summary>
|
||||
IPluginService PluginService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
void OnStartup();
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
void OnShutdown();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
MenuItem[] GetMenuItems();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
Button[] GetToolbarButtons();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
string GetStatusBarInfo();
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
void OnConfigurationChanged();
|
||||
}
|
||||
}
|
||||
@@ -1,214 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取服务接口,统一所有获取类的方法
|
||||
/// </summary>
|
||||
public interface IGetService
|
||||
{
|
||||
#region 窗口和UI获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取主窗口引用
|
||||
/// </summary>
|
||||
Window MainWindow { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前画布
|
||||
/// </summary>
|
||||
InkCanvas CurrentCanvas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有画布页面
|
||||
/// </summary>
|
||||
List<Canvas> AllCanvasPages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前页面索引
|
||||
/// </summary>
|
||||
int CurrentPageIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前页面数量
|
||||
/// </summary>
|
||||
int TotalPageCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取浮动工具栏
|
||||
/// </summary>
|
||||
FrameworkElement FloatingToolBar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取左侧面板
|
||||
/// </summary>
|
||||
FrameworkElement LeftPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取右侧面板
|
||||
/// </summary>
|
||||
FrameworkElement RightPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取顶部面板
|
||||
/// </summary>
|
||||
FrameworkElement TopPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取底部面板
|
||||
/// </summary>
|
||||
FrameworkElement BottomPanel { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制工具状态获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前绘制模式
|
||||
/// </summary>
|
||||
int CurrentDrawingMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触宽度
|
||||
/// </summary>
|
||||
double CurrentInkWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触颜色
|
||||
/// </summary>
|
||||
Color CurrentInkColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前高亮笔宽度
|
||||
/// </summary>
|
||||
double CurrentHighlighterWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦大小
|
||||
/// </summary>
|
||||
int CurrentEraserSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦类型
|
||||
/// </summary>
|
||||
int CurrentEraserType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦形状
|
||||
/// </summary>
|
||||
int CurrentEraserShape { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触透明度
|
||||
/// </summary>
|
||||
double CurrentInkAlpha { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触样式
|
||||
/// </summary>
|
||||
int CurrentInkStyle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前背景颜色
|
||||
/// </summary>
|
||||
string CurrentBackgroundColor { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用状态获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前主题模式
|
||||
/// </summary>
|
||||
bool IsDarkTheme { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为白板模式
|
||||
/// </summary>
|
||||
bool IsWhiteboardMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为PPT模式
|
||||
/// </summary>
|
||||
bool IsPPTMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为全屏模式
|
||||
/// </summary>
|
||||
bool IsFullScreenMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为画板模式
|
||||
/// </summary>
|
||||
bool IsCanvasMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为选择模式
|
||||
/// </summary>
|
||||
bool IsSelectionMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为擦除模式
|
||||
/// </summary>
|
||||
bool IsEraserMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为形状绘制模式
|
||||
/// </summary>
|
||||
bool IsShapeDrawingMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为高亮模式
|
||||
/// </summary>
|
||||
bool IsHighlighterMode { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 撤销重做状态获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否可以撤销
|
||||
/// </summary>
|
||||
bool CanUndo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否可以重做
|
||||
/// </summary>
|
||||
bool CanRedo { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统设置获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>设置值</returns>
|
||||
T GetSetting<T>(string key, T defaultValue = default(T));
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件信息获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已加载的插件
|
||||
/// </summary>
|
||||
/// <returns>插件列表</returns>
|
||||
List<IPlugin> GetAllPlugins();
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <returns>插件实例</returns>
|
||||
IPlugin GetPlugin(string pluginName);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义插件的基本接口
|
||||
/// </summary>
|
||||
public interface IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
string Author { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
bool IsBuiltIn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// 此方法在插件加载时被调用,用于执行一些初始化工作
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// 此方法在插件被用户或系统启用时调用,激活插件功能
|
||||
/// </summary>
|
||||
void Enable();
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// 此方法在插件被用户或系统禁用时调用,停用插件功能
|
||||
/// </summary>
|
||||
void Disable();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// 此方法返回插件的设置界面控件,用于展示在设置窗口
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
UserControl GetSettingsView();
|
||||
|
||||
/// <summary>
|
||||
/// 插件卸载时的清理工作
|
||||
/// 此方法在插件被卸载前调用,用于释放资源和执行清理
|
||||
/// </summary>
|
||||
void Cleanup();
|
||||
}
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务接口,提供对软件内部功能的访问
|
||||
/// 继承自三个专门的服务接口:获取服务、窗口服务、操作服务
|
||||
/// </summary>
|
||||
public interface IPluginService : IGetService, IWindowService, IActionService
|
||||
{
|
||||
// 这个接口现在继承自三个专门的服务接口
|
||||
// 所有方法都在子接口中定义,这里不需要重复定义
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知类型枚举
|
||||
/// </summary>
|
||||
public enum NotificationType
|
||||
{
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗口服务接口,统一所有窗口操作相关的方法
|
||||
/// </summary>
|
||||
public interface IWindowService
|
||||
{
|
||||
#region 窗口显示和隐藏
|
||||
|
||||
/// <summary>
|
||||
/// 显示设置窗口
|
||||
/// </summary>
|
||||
void ShowSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏设置窗口
|
||||
/// </summary>
|
||||
void HideSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示插件设置窗口
|
||||
/// </summary>
|
||||
void ShowPluginSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏插件设置窗口
|
||||
/// </summary>
|
||||
void HidePluginSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示帮助窗口
|
||||
/// </summary>
|
||||
void ShowHelpWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏帮助窗口
|
||||
/// </summary>
|
||||
void HideHelpWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示关于窗口
|
||||
/// </summary>
|
||||
void ShowAboutWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏关于窗口
|
||||
/// </summary>
|
||||
void HideAboutWindow();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 对话框和通知
|
||||
|
||||
/// <summary>
|
||||
/// 显示通知消息
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
void ShowNotification(string message, NotificationType type = NotificationType.Info);
|
||||
|
||||
/// <summary>
|
||||
/// 显示确认对话框
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <returns>用户选择结果</returns>
|
||||
bool ShowConfirmDialog(string message, string title = "确认");
|
||||
|
||||
/// <summary>
|
||||
/// 显示输入对话框
|
||||
/// </summary>
|
||||
/// <param name="message">提示消息</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>用户输入内容</returns>
|
||||
string ShowInputDialog(string message, string title = "输入", string defaultValue = "");
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口状态控制
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口全屏状态
|
||||
/// </summary>
|
||||
/// <param name="isFullScreen">是否全屏</param>
|
||||
void SetFullScreen(bool isFullScreen);
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口置顶状态
|
||||
/// </summary>
|
||||
/// <param name="isTopMost">是否置顶</param>
|
||||
void SetTopMost(bool isTopMost);
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口可见性
|
||||
/// </summary>
|
||||
/// <param name="isVisible">是否可见</param>
|
||||
void SetWindowVisibility(bool isVisible);
|
||||
|
||||
/// <summary>
|
||||
/// 最小化窗口
|
||||
/// </summary>
|
||||
void MinimizeWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 最大化窗口
|
||||
/// </summary>
|
||||
void MaximizeWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 恢复窗口
|
||||
/// </summary>
|
||||
void RestoreWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 关闭窗口
|
||||
/// </summary>
|
||||
void CloseWindow();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口位置和大小
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口位置
|
||||
/// </summary>
|
||||
/// <param name="x">X坐标</param>
|
||||
/// <param name="y">Y坐标</param>
|
||||
void SetWindowPosition(double x, double y);
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口大小
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
/// <param name="height">高度</param>
|
||||
void SetWindowSize(double width, double height);
|
||||
|
||||
/// <summary>
|
||||
/// 获取窗口位置
|
||||
/// </summary>
|
||||
/// <returns>窗口位置</returns>
|
||||
(double x, double y) GetWindowPosition();
|
||||
|
||||
/// <summary>
|
||||
/// 获取窗口大小
|
||||
/// </summary>
|
||||
/// <returns>窗口大小</returns>
|
||||
(double width, double height) GetWindowSize();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件基类,提供基本实现
|
||||
/// </summary>
|
||||
public abstract class PluginBase : IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件状态(私有字段)
|
||||
/// </summary>
|
||||
private bool _isEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// 插件状态(公共属性)
|
||||
/// </summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
protected set
|
||||
{
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
OnEnabledStateChanged(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件ID
|
||||
/// </summary>
|
||||
public string Id { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件路径
|
||||
/// </summary>
|
||||
public string PluginPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public abstract Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public abstract string Author { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
public virtual bool IsBuiltIn => false;
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更事件
|
||||
/// </summary>
|
||||
public event EventHandler<bool> EnabledStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
Id = GetType().FullName;
|
||||
|
||||
// 添加日志,记录插件名称
|
||||
try
|
||||
{
|
||||
string name = Name;
|
||||
LogHelper.WriteLogToFile($"初始化插件: ID={Id}, 名称={name ?? "未命名"}");
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"警告: 插件 {Id} 的名称为空", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取插件名称时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已初始化");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
public virtual void Enable()
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
IsEnabled = true;
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
public virtual void Disable()
|
||||
{
|
||||
if (IsEnabled)
|
||||
{
|
||||
IsEnabled = false;
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
public virtual UserControl GetSettingsView()
|
||||
{
|
||||
// 默认返回空设置页面
|
||||
return new UserControl();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件卸载时的清理工作
|
||||
/// </summary>
|
||||
public virtual void Cleanup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已卸载");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件自身的设置
|
||||
/// 注意:此方法仅用于保存插件的特定设置,不应影响插件启用/禁用状态
|
||||
/// 插件启用状态由PluginManager统一管理
|
||||
/// </summary>
|
||||
public virtual void SavePluginSettings()
|
||||
{
|
||||
// 默认实现不做任何事情
|
||||
// 子类可以重写此方法,将自身设置保存到配置文件中
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 设置已保存", LogHelper.LogType.Event);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发状态变更事件
|
||||
/// </summary>
|
||||
/// <param name="isEnabled">是否启用</param>
|
||||
protected virtual void OnEnabledStateChanged(bool isEnabled)
|
||||
{
|
||||
EnabledStateChanged?.Invoke(this, isEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,273 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件配置管理器,允许插件管理自己的配置
|
||||
/// </summary>
|
||||
public class PluginConfigurationManager
|
||||
{
|
||||
private static readonly string PluginConfigDirectory = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
private static readonly Dictionary<string, Dictionary<string, object>> _pluginConfigs = new Dictionary<string, Dictionary<string, object>>();
|
||||
private static readonly object _lockObject = new object();
|
||||
|
||||
static PluginConfigurationManager()
|
||||
{
|
||||
// 确保配置目录存在
|
||||
if (!Directory.Exists(PluginConfigDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(PluginConfigDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">配置值类型</typeparam>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>配置值</returns>
|
||||
public static T GetConfiguration<T>(string pluginName, string key, T defaultValue = default(T))
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
if (pluginConfig.TryGetValue(key, out var value))
|
||||
{
|
||||
if (value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
// 尝试类型转换
|
||||
try
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置插件配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">配置值类型</typeparam>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="value">配置值</param>
|
||||
public static void SetConfiguration<T>(string pluginName, string key, T value)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_pluginConfigs.ContainsKey(pluginName))
|
||||
{
|
||||
_pluginConfigs[pluginName] = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
_pluginConfigs[pluginName][key] = value;
|
||||
|
||||
// 异步保存配置
|
||||
Task.Run(() => SavePluginConfiguration(pluginName));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
public static void RemoveConfiguration(string pluginName, string key)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
if (pluginConfig.Remove(key))
|
||||
{
|
||||
// 异步保存配置
|
||||
Task.Run(() => SavePluginConfiguration(pluginName));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"删除插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的所有配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <returns>配置字典</returns>
|
||||
public static Dictionary<string, object> GetAllConfigurations(string pluginName)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
return new Dictionary<string, object>(pluginConfig);
|
||||
}
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除插件的所有配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
public static void ClearAllConfigurations(string pluginName)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.Remove(pluginName))
|
||||
{
|
||||
// 删除配置文件
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
File.Delete(configFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除插件 {pluginName} 所有配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
public static void LoadPluginConfiguration(string pluginName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
string json = File.ReadAllText(configFile);
|
||||
var config = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
_pluginConfigs[pluginName] = config ?? new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已加载插件 {pluginName} 的配置");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
private static void SavePluginConfiguration(string pluginName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<string, object> pluginConfig;
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (!_pluginConfigs.TryGetValue(pluginName, out pluginConfig))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
string json = JsonConvert.SerializeObject(pluginConfig, Formatting.Indented);
|
||||
File.WriteAllText(configFile, json);
|
||||
|
||||
LogHelper.WriteLogToFile($"已保存插件 {pluginName} 的配置");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载所有插件的配置
|
||||
/// </summary>
|
||||
public static void LoadAllPluginConfigurations()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(PluginConfigDirectory))
|
||||
{
|
||||
string[] configFiles = Directory.GetFiles(PluginConfigDirectory, "*.json");
|
||||
foreach (string configFile in configFiles)
|
||||
{
|
||||
string pluginName = Path.GetFileNameWithoutExtension(configFile);
|
||||
LoadPluginConfiguration(pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存所有插件的配置
|
||||
/// </summary>
|
||||
public static void SaveAllPluginConfigurations()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
foreach (string pluginName in _pluginConfigs.Keys)
|
||||
{
|
||||
SavePluginConfiguration(pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,509 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务管理器,实现IPluginService接口,提供对软件内部功能的访问
|
||||
/// </summary>
|
||||
public class PluginServiceManager : IPluginService
|
||||
{
|
||||
private static PluginServiceManager _instance;
|
||||
private MainWindow _mainWindow;
|
||||
private Dictionary<string, EventHandler> _eventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// 单例实例
|
||||
/// </summary>
|
||||
public static PluginServiceManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new PluginServiceManager();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private PluginServiceManager()
|
||||
{
|
||||
_eventHandlers = new Dictionary<string, EventHandler>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置主窗口引用
|
||||
/// </summary>
|
||||
/// <param name="mainWindow">主窗口实例</param>
|
||||
public void SetMainWindow(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
#region 窗口和UI访问
|
||||
|
||||
public Window MainWindow => _mainWindow;
|
||||
|
||||
public InkCanvas CurrentCanvas => null; // 暂时返回null,避免访问权限问题
|
||||
|
||||
public List<Canvas> AllCanvasPages => new List<Canvas>(); // 暂时返回空列表
|
||||
|
||||
public int CurrentPageIndex => 0; // 暂时返回0
|
||||
|
||||
public int TotalPageCount => 0; // 暂时返回0
|
||||
|
||||
public FrameworkElement FloatingToolBar => _mainWindow?.ViewboxFloatingBar;
|
||||
|
||||
public FrameworkElement LeftPanel => _mainWindow?.BlackboardLeftSide;
|
||||
|
||||
public FrameworkElement RightPanel => _mainWindow?.BlackboardRightSide;
|
||||
|
||||
public FrameworkElement TopPanel => _mainWindow?.BorderTools;
|
||||
|
||||
public FrameworkElement BottomPanel => _mainWindow?.BorderSettings;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制工具状态
|
||||
|
||||
public int CurrentDrawingMode => 0; // 暂时返回0
|
||||
|
||||
public double CurrentInkWidth => 2.5; // 暂时返回默认值
|
||||
|
||||
public Color CurrentInkColor => Colors.Black; // 暂时返回默认值
|
||||
|
||||
public double CurrentHighlighterWidth => 20.0; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserSize => 2; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserType => 0; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserShape => 0; // 暂时返回默认值
|
||||
|
||||
public double CurrentInkAlpha => 255.0; // 暂时返回默认值
|
||||
|
||||
public int CurrentInkStyle => 0; // 暂时返回默认值
|
||||
|
||||
public string CurrentBackgroundColor => "#162924"; // 暂时返回默认值
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用状态
|
||||
|
||||
public bool IsDarkTheme => false; // 暂时返回默认值
|
||||
|
||||
public bool IsWhiteboardMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsPPTMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsFullScreenMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsCanvasMode => true; // 暂时返回默认值
|
||||
|
||||
public bool IsSelectionMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsEraserMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsShapeDrawingMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsHighlighterMode => false; // 暂时返回默认值
|
||||
|
||||
#endregion
|
||||
|
||||
#region IGetService 实现
|
||||
|
||||
public bool CanUndo => false; // 暂时返回默认值
|
||||
|
||||
public bool CanRedo => false; // 暂时返回默认值
|
||||
|
||||
public T GetSetting<T>(string key, T defaultValue = default(T))
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public List<IPlugin> GetAllPlugins()
|
||||
{
|
||||
return new List<IPlugin>(PluginManager.Instance.Plugins);
|
||||
}
|
||||
|
||||
public IPlugin GetPlugin(string pluginName)
|
||||
{
|
||||
return PluginManager.Instance.Plugins.FirstOrDefault(p => p.Name == pluginName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWindowService 实现
|
||||
|
||||
public void ShowSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowPluginSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HidePluginSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowHelpWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideHelpWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowAboutWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideAboutWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowNotification(string message, NotificationType type = NotificationType.Info)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public bool ShowConfirmDialog(string message, string title = "确认")
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return false;
|
||||
}
|
||||
|
||||
public string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public void SetFullScreen(bool isFullScreen)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetTopMost(bool isTopMost)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetWindowVisibility(bool isVisible)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void MinimizeWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void MaximizeWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void RestoreWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CloseWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetWindowPosition(double x, double y)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetWindowSize(double width, double height)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public (double x, double y) GetWindowPosition()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
public (double width, double height) GetWindowSize()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return (800, 600);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IActionService 实现
|
||||
|
||||
public void ClearCanvas()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ClearAllCanvases()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void AddNewPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeleteCurrentPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SwitchToPage(int pageIndex)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void NextPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void PreviousPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetDrawingMode(int mode)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkWidth(double width)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkColor(Color color)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetHighlighterWidth(double width)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserSize(int size)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserType(int type)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserShape(int shape)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkAlpha(double alpha)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkStyle(int style)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetBackgroundColor(string color)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SaveCanvas(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void LoadCanvas(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExportAsImage(string filePath, string format)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExportAsPDF(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Redo()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SelectAll()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeselectAll()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeleteSelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CopySelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CutSelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Paste()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetSetting<T>(string key, T value)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void LoadSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ResetSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void EnablePlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.TogglePlugin(plugin, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisablePlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.TogglePlugin(plugin, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnloadPlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.UnloadPlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterEventHandler(string eventName, EventHandler handler)
|
||||
{
|
||||
if (!_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName] = handler;
|
||||
}
|
||||
else
|
||||
{
|
||||
_eventHandlers[eventName] += handler;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterEventHandler(string eventName, EventHandler handler)
|
||||
{
|
||||
if (_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName] -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerEvent(string eventName, object sender, EventArgs args)
|
||||
{
|
||||
if (_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName]?.Invoke(sender, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void RestartApplication()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExitApplication()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CheckForUpdates()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void OpenHelpDocument()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void OpenAboutPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件模板,用于开发者参考
|
||||
/// 注意:实际开发时,请将此类移到单独的程序集中
|
||||
/// </summary>
|
||||
public class PluginTemplate : PluginBase
|
||||
{
|
||||
#region 插件基本信息
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public override string Name => "插件模板";
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public override string Description => "这是一个插件开发模板,用于开发者参考。";
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public override Version Version => new Version(1, 0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public override string Author => "Your Name";
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件(外部插件请返回false)
|
||||
/// </summary>
|
||||
public override bool IsBuiltIn => false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件生命周期
|
||||
|
||||
/// <summary>
|
||||
/// 插件初始化
|
||||
/// 在这里进行插件的初始化工作,如加载配置、注册事件等
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件ID和记录日志
|
||||
base.Initialize();
|
||||
|
||||
// TODO: 在这里进行插件初始化工作
|
||||
|
||||
// 示例:记录初始化信息
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 开始初始化");
|
||||
|
||||
// 示例:加载配置
|
||||
LoadConfig();
|
||||
|
||||
// 示例:注册自定义事件
|
||||
// MainWindow.Instance.SomeEvent += OnSomeEvent;
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 初始化完成");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// 在这里激活插件功能
|
||||
/// </summary>
|
||||
public override void Enable()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件状态和记录日志
|
||||
base.Enable();
|
||||
|
||||
// TODO: 在这里启用插件功能
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// 在这里停用插件功能
|
||||
/// </summary>
|
||||
public override void Disable()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件状态和记录日志
|
||||
base.Disable();
|
||||
|
||||
// TODO: 在这里禁用插件功能
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理资源
|
||||
/// 在插件卸载时调用,清理资源
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
// TODO: 在这里清理插件资源
|
||||
|
||||
// 示例:取消注册事件
|
||||
// MainWindow.Instance.SomeEvent -= OnSomeEvent;
|
||||
|
||||
// 示例:保存配置
|
||||
SaveConfig();
|
||||
|
||||
// 最后调用基类方法
|
||||
base.Cleanup();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件配置
|
||||
|
||||
/// <summary>
|
||||
/// 加载插件配置
|
||||
/// </summary>
|
||||
private void LoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 从文件或其他位置加载配置
|
||||
// 示例:
|
||||
// string configPath = Path.Combine(App.RootPath, "PluginConfigs", "YourPluginName.json");
|
||||
// if (File.Exists(configPath))
|
||||
// {
|
||||
// string json = File.ReadAllText(configPath);
|
||||
// YourConfig = Newtonsoft.Json.JsonConvert.DeserializeObject<YourConfigClass>(json);
|
||||
// }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件配置
|
||||
/// </summary>
|
||||
private void SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 保存配置到文件或其他位置
|
||||
// 示例:
|
||||
// string configDir = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
// if (!Directory.Exists(configDir))
|
||||
// {
|
||||
// Directory.CreateDirectory(configDir);
|
||||
// }
|
||||
// string configPath = Path.Combine(configDir, "YourPluginName.json");
|
||||
// string json = Newtonsoft.Json.JsonConvert.SerializeObject(YourConfig, Newtonsoft.Json.Formatting.Indented);
|
||||
// File.WriteAllText(configPath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件设置界面
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
public override UserControl GetSettingsView()
|
||||
{
|
||||
// 创建插件设置界面
|
||||
return new PluginTemplateSettingsControl();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件功能方法
|
||||
|
||||
// TODO: 在这里添加插件的具体功能方法
|
||||
|
||||
/// <summary>
|
||||
/// 示例方法:执行一些功能
|
||||
/// </summary>
|
||||
public void DoSomething()
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: 实现你的功能
|
||||
MessageBox.Show("插件功能执行示例", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"执行插件功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件设置控件
|
||||
/// </summary>
|
||||
public class PluginTemplateSettingsControl : UserControl
|
||||
{
|
||||
public PluginTemplateSettingsControl()
|
||||
{
|
||||
// 创建设置界面布局
|
||||
var panel = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(10)
|
||||
};
|
||||
|
||||
// 添加标题
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "插件模板设置",
|
||||
FontSize = 16,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
});
|
||||
|
||||
// 添加说明文字
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "这是一个示例设置界面,你可以在这里添加自己的设置控件。",
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(0, 0, 0, 15)
|
||||
});
|
||||
|
||||
// 添加示例设置选项
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
Content = "启用某项功能",
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
};
|
||||
panel.Children.Add(checkBox);
|
||||
|
||||
// 添加文本输入框
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "设置项:",
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
});
|
||||
|
||||
panel.Children.Add(new TextBox
|
||||
{
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
Width = 200,
|
||||
HorizontalAlignment = HorizontalAlignment.Left
|
||||
});
|
||||
|
||||
// 添加按钮
|
||||
var button = new Button
|
||||
{
|
||||
Content = "保存设置",
|
||||
Padding = new Thickness(10, 5, 10, 5),
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
HorizontalAlignment = HorizontalAlignment.Left
|
||||
};
|
||||
|
||||
button.Click += (sender, e) =>
|
||||
{
|
||||
MessageBox.Show("设置已保存!", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
};
|
||||
|
||||
panel.Children.Add(button);
|
||||
|
||||
// 设置控件内容
|
||||
Content = panel;
|
||||
}
|
||||
}
|
||||
}
|
||||
+206
-768
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 渲染保存文件名模板。支持占位符: {date} {time} {datetime} {mode} {page} {count} {type}。
|
||||
/// 当模板为空、渲染结果非法或仅含分隔符时,回退到默认时间戳命名。
|
||||
/// </summary>
|
||||
public static class SaveFileNameHelper
|
||||
{
|
||||
private const string DefaultDateTime = "yyyy-MM-dd HH-mm-ss-fff";
|
||||
|
||||
// Windows 保留设备名(不区分大小写)。这些名称无论是否带扩展名,CreateFile 都会失败。
|
||||
private static readonly HashSet<string> ReservedNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
"CON", "PRN", "AUX", "NUL",
|
||||
"COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
|
||||
"LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9",
|
||||
};
|
||||
|
||||
public static string Render(string template, SaveFileNameContext ctx)
|
||||
{
|
||||
if (ctx == null) ctx = new SaveFileNameContext();
|
||||
var now = ctx.Time ?? DateTime.Now;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(template))
|
||||
return now.ToString(DefaultDateTime);
|
||||
|
||||
try
|
||||
{
|
||||
string result = template
|
||||
.Replace("{date}", now.ToString("yyyy-MM-dd"))
|
||||
.Replace("{time}", now.ToString("HH-mm-ss"))
|
||||
.Replace("{datetime}", now.ToString(DefaultDateTime))
|
||||
.Replace("{mode}", ctx.Mode ?? "")
|
||||
.Replace("{page}", ctx.Page?.ToString() ?? "")
|
||||
.Replace("{count}", ctx.Count?.ToString() ?? "")
|
||||
.Replace("{type}", ctx.Type ?? "");
|
||||
|
||||
result = SanitizeFileName(result);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(result) || Regex.IsMatch(result, @"^[\s\-_]+$"))
|
||||
return now.ToString(DefaultDateTime);
|
||||
|
||||
return result;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return now.ToString(DefaultDateTime);
|
||||
}
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name)) return name;
|
||||
foreach (var c in Path.GetInvalidFileNameChars())
|
||||
{
|
||||
name = name.Replace(c, '_');
|
||||
}
|
||||
|
||||
// Windows 禁止文件名以点号或空格结尾(会被静默截断甚至创建失败)。
|
||||
name = name.Trim().TrimEnd('.', ' ');
|
||||
|
||||
if (string.IsNullOrEmpty(name)) return name;
|
||||
|
||||
// 保留设备名:比较时忽略扩展名,命中则加下划线前缀以规避。
|
||||
var stem = Path.GetFileNameWithoutExtension(name);
|
||||
if (!string.IsNullOrEmpty(stem) && ReservedNames.Contains(stem))
|
||||
{
|
||||
name = "_" + name;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public class SaveFileNameContext
|
||||
{
|
||||
public DateTime? Time { get; set; }
|
||||
/// <summary>"Annotation" or "BlackBoard" or "Screenshot" etc.</summary>
|
||||
public string Mode { get; set; }
|
||||
/// <summary>"User" or "Auto"</summary>
|
||||
public string Type { get; set; }
|
||||
public int? Page { get; set; }
|
||||
public int? Count { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -49,16 +49,13 @@ namespace Ink_Canvas.Helpers
|
||||
if (string.IsNullOrWhiteSpace(softwareName))
|
||||
return null;
|
||||
|
||||
// 64 位进程默认只枚举 64 位注册表视图;32 位希沃常写在 WOW6432Node 下,需一并扫描。
|
||||
string[] uninstallRoots =
|
||||
// 须用 OpenBaseKey + RegistryView 显式指定视图:Registry.LocalMachine.OpenSubKey 跟随进程位数,
|
||||
// 32 位进程下无法靠拼接 WOW6432Node 路径进入 64 位视图,会找不到 64 位安装的展台。
|
||||
const string uninstallSubKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
|
||||
foreach (RegistryView view in new[] { RegistryView.Registry64, RegistryView.Registry32 })
|
||||
{
|
||||
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
|
||||
@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall",
|
||||
};
|
||||
|
||||
foreach (string root in uninstallRoots)
|
||||
{
|
||||
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(root))
|
||||
using (RegistryKey baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, view))
|
||||
using (RegistryKey key = baseKey.OpenSubKey(uninstallSubKey))
|
||||
{
|
||||
if (key == null) continue;
|
||||
string found = FindInUninstallKey(key, softwareName);
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public static class ThemeHelper
|
||||
{
|
||||
public static bool IsSystemThemeLight()
|
||||
{
|
||||
try
|
||||
{
|
||||
var registryKey = Registry.CurrentUser;
|
||||
var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
|
||||
if (themeKey != null)
|
||||
{
|
||||
var value = themeKey.GetValue("AppsUseLightTheme");
|
||||
if (value != null)
|
||||
{
|
||||
bool result = (int)value == 1;
|
||||
themeKey.Close();
|
||||
return result;
|
||||
}
|
||||
themeKey.Close();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool IsSystemThemeLightLegacy()
|
||||
{
|
||||
try
|
||||
{
|
||||
var registryKey = Registry.CurrentUser;
|
||||
var themeKey = registryKey.OpenSubKey(@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize");
|
||||
if (themeKey != null)
|
||||
{
|
||||
int keyValue = (int)themeKey.GetValue("SystemUsesLightTheme");
|
||||
themeKey.Close();
|
||||
return keyValue == 1;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine(ex);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static ElementTheme GetEffectiveTheme(Settings settings)
|
||||
{
|
||||
if (settings.Appearance.Theme == 0)
|
||||
return ElementTheme.Light;
|
||||
if (settings.Appearance.Theme == 1)
|
||||
return ElementTheme.Dark;
|
||||
|
||||
return IsSystemThemeLight() ? ElementTheme.Light : ElementTheme.Dark;
|
||||
}
|
||||
|
||||
public static void ApplyTheme(FrameworkElement element, Settings settings)
|
||||
{
|
||||
if (element == null || settings == null) return;
|
||||
try
|
||||
{
|
||||
ThemeManager.SetRequestedTheme(element, GetEffectiveTheme(settings));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用主题失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ApplyTheme(FrameworkElement element, Settings settings, Action<string> onThemeApplied)
|
||||
{
|
||||
if (element == null || settings == null) return;
|
||||
try
|
||||
{
|
||||
var theme = GetEffectiveTheme(settings);
|
||||
ThemeManager.SetRequestedTheme(element, theme);
|
||||
onThemeApplied?.Invoke(theme == ElementTheme.Dark ? "Dark" : "Light");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用主题失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
@@ -100,8 +100,8 @@ namespace Ink_Canvas.Helpers
|
||||
var item = _currentStrokeHistory[_currentIndex];
|
||||
item.StrokeHasBeenCleared = !item.StrokeHasBeenCleared;
|
||||
_currentIndex--;
|
||||
OnUndoStateChanged?.Invoke(_currentIndex > -1);
|
||||
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
|
||||
OnUndoStateChanged?.Invoke(CanUndo);
|
||||
OnRedoStateChanged?.Invoke(CanRedo);
|
||||
return item;
|
||||
}
|
||||
|
||||
@@ -137,9 +137,15 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
private void NotifyUndoRedoState()
|
||||
{
|
||||
OnUndoStateChanged?.Invoke(_currentIndex > -1);
|
||||
OnRedoStateChanged?.Invoke(_currentStrokeHistory.Count - _currentIndex - 1 > 0);
|
||||
OnUndoStateChanged?.Invoke(CanUndo);
|
||||
OnRedoStateChanged?.Invoke(CanRedo);
|
||||
}
|
||||
|
||||
/// <summary>当前历史是否允许撤销。</summary>
|
||||
public bool CanUndo => _currentIndex > -1;
|
||||
|
||||
/// <summary>当前历史是否允许重做。</summary>
|
||||
public bool CanRedo => _currentStrokeHistory.Count > 0 && _currentStrokeHistory.Count - _currentIndex - 1 > 0;
|
||||
}
|
||||
|
||||
public class TimeMachineHistory
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// UIAccess DLL释放器
|
||||
/// </summary>
|
||||
public static class UIAccessDllExtractor
|
||||
{
|
||||
private static readonly string[] RequiredDlls = {
|
||||
"UIAccessDLL_x64.dll",
|
||||
"UIAccessDLL_x86.dll"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 在应用启动时释放UIAccess相关DLL
|
||||
/// </summary>
|
||||
public static void ExtractUIAccessDlls()
|
||||
{
|
||||
try
|
||||
{
|
||||
string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
LogHelper.WriteLogToFile("开始检查并释放UIAccess相关DLL文件");
|
||||
|
||||
foreach (string dllName in RequiredDlls)
|
||||
{
|
||||
string targetPath = Path.Combine(appDirectory, dllName);
|
||||
|
||||
// 检查文件是否已存在且有效
|
||||
if (File.Exists(targetPath) && IsValidDll(targetPath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"{dllName} 已存在且有效,跳过释放");
|
||||
continue;
|
||||
}
|
||||
|
||||
// 从嵌入资源中释放DLL
|
||||
if (ExtractDllFromResource(dllName, targetPath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"成功释放 {dllName} 到 {targetPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"警告:无法释放 {dllName},可能影响UIA置顶功能", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("UIAccess DLL释放检查完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放UIAccess DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从嵌入资源中提取DLL文件
|
||||
/// </summary>
|
||||
private static bool ExtractDllFromResource(string dllName, string targetPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
Assembly assembly = Assembly.GetExecutingAssembly();
|
||||
string resourceName = $"Ink_Canvas.{dllName}";
|
||||
|
||||
using (Stream resourceStream = assembly.GetManifestResourceStream(resourceName))
|
||||
{
|
||||
if (resourceStream == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"未找到嵌入资源: {resourceName}", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 确保目标目录存在
|
||||
string targetDirectory = Path.GetDirectoryName(targetPath);
|
||||
if (!Directory.Exists(targetDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(targetDirectory);
|
||||
}
|
||||
|
||||
// 写入文件
|
||||
using (FileStream fileStream = new FileStream(targetPath, FileMode.Create, FileAccess.Write))
|
||||
{
|
||||
resourceStream.CopyTo(fileStream);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从资源提取 {dllName} 失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查DLL文件是否有效
|
||||
/// </summary>
|
||||
private static bool IsValidDll(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
return false;
|
||||
|
||||
FileInfo fileInfo = new FileInfo(filePath);
|
||||
|
||||
// 检查文件大小(空文件或过小的文件可能无效)
|
||||
if (fileInfo.Length < 1024) // 小于1KB可能无效
|
||||
return false;
|
||||
|
||||
// 简单检查PE头(DLL文件应该以MZ开头)
|
||||
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read))
|
||||
{
|
||||
byte[] buffer = new byte[2];
|
||||
if (fs.Read(buffer, 0, 2) == 2)
|
||||
{
|
||||
return buffer[0] == 0x4D && buffer[1] == 0x5A; // "MZ"
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理释放的DLL文件(可选,在应用退出时调用)
|
||||
/// </summary>
|
||||
public static void CleanupExtractedDlls()
|
||||
{
|
||||
try
|
||||
{
|
||||
string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
|
||||
foreach (string dllName in RequiredDlls)
|
||||
{
|
||||
string filePath = Path.Combine(appDirectory, dllName);
|
||||
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(filePath);
|
||||
LogHelper.WriteLogToFile($"已清理 {dllName}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理 {dllName} 失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理UIAccess DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,710 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 通过 Winlogon 令牌模拟实现 UIAccess 提权重启。
|
||||
/// 1. 找到当前会话中 winlogon.exe 的令牌,复制为模拟令牌;
|
||||
/// 2. SetThreadToken 暂时模拟 winlogon(拥有 TCB 权限);
|
||||
/// 3. 在自身令牌副本上 SetTokenInformation(TokenUIAccess, TRUE);
|
||||
/// 4. RevertToSelf 后用 CreateProcessWithTokenW 启动新进程;
|
||||
/// 5. 新进程具有 UIAccess 权限,可置顶于 UAC 提示之上。
|
||||
/// </summary>
|
||||
public static class UIAccessHelper
|
||||
{
|
||||
#region Constants
|
||||
|
||||
private const uint TOKEN_QUERY = 0x0008;
|
||||
private const uint TOKEN_DUPLICATE = 0x0002;
|
||||
private const uint TOKEN_IMPERSONATE = 0x0004;
|
||||
private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
|
||||
private const uint TOKEN_ADJUST_DEFAULT = 0x0080;
|
||||
private const uint TOKEN_ADJUST_SESSIONID = 0x0100;
|
||||
private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
|
||||
|
||||
private const int SecurityAnonymous = 0;
|
||||
private const int SecurityImpersonation = 2;
|
||||
private const int TokenPrimary = 1;
|
||||
private const int TokenImpersonation = 2;
|
||||
|
||||
// TOKEN_INFORMATION_CLASS
|
||||
private const int TokenSessionId = 12;
|
||||
private const int TokenElevationType = 18;
|
||||
private const int TokenUIAccess = 26;
|
||||
|
||||
// TOKEN_ELEVATION_TYPE
|
||||
private const int TokenElevationTypeDefault = 1;
|
||||
private const int TokenElevationTypeFull = 2;
|
||||
private const int TokenElevationTypeLimited = 3;
|
||||
|
||||
private const uint PROCESS_QUERY_LIMITED_INFORMATION = 0x1000;
|
||||
private const uint TH32CS_SNAPPROCESS = 0x00000002;
|
||||
|
||||
private const uint LOGON_WITH_PROFILE = 0x00000001;
|
||||
private const uint CREATE_NEW_CONSOLE = 0x00000010;
|
||||
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
|
||||
|
||||
private const uint SE_PRIVILEGE_ENABLED = 0x00000002;
|
||||
private const string SE_ASSIGNPRIMARYTOKEN_NAME = "SeAssignPrimaryTokenPrivilege";
|
||||
|
||||
private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Structs
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct LUID
|
||||
{
|
||||
public uint LowPart;
|
||||
public int HighPart;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct LUID_AND_ATTRIBUTES
|
||||
{
|
||||
public LUID Luid;
|
||||
public uint Attributes;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct TOKEN_PRIVILEGES
|
||||
{
|
||||
public uint PrivilegeCount;
|
||||
public LUID_AND_ATTRIBUTES Privilege;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct PROCESSENTRY32W
|
||||
{
|
||||
public uint dwSize;
|
||||
public uint cntUsage;
|
||||
public uint th32ProcessID;
|
||||
public IntPtr th32DefaultHeapID;
|
||||
public uint th32ModuleID;
|
||||
public uint cntThreads;
|
||||
public uint th32ParentProcessID;
|
||||
public int pcPriClassBase;
|
||||
public uint dwFlags;
|
||||
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
|
||||
public string szExeFile;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
|
||||
private struct STARTUPINFOW
|
||||
{
|
||||
public uint cb;
|
||||
public IntPtr lpReserved;
|
||||
public IntPtr lpDesktop;
|
||||
public IntPtr lpTitle;
|
||||
public uint dwX, dwY, dwXSize, dwYSize;
|
||||
public uint dwXCountChars, dwYCountChars;
|
||||
public uint dwFillAttribute;
|
||||
public uint dwFlags;
|
||||
public ushort wShowWindow;
|
||||
public ushort cbReserved2;
|
||||
public IntPtr lpReserved2;
|
||||
public IntPtr hStdInput, hStdOutput, hStdError;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
private struct PROCESS_INFORMATION
|
||||
{
|
||||
public IntPtr hProcess;
|
||||
public IntPtr hThread;
|
||||
public uint dwProcessId;
|
||||
public uint dwThreadId;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region P/Invoke
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern IntPtr GetCurrentProcess();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CloseHandle(IntPtr hObject);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool DuplicateTokenEx(
|
||||
IntPtr hExistingToken,
|
||||
uint dwDesiredAccess,
|
||||
IntPtr lpTokenAttributes,
|
||||
int ImpersonationLevel,
|
||||
int TokenType,
|
||||
out IntPtr phNewToken);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool GetTokenInformation(
|
||||
IntPtr TokenHandle,
|
||||
int TokenInformationClass,
|
||||
IntPtr TokenInformation,
|
||||
uint TokenInformationLength,
|
||||
out uint ReturnLength);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool SetTokenInformation(
|
||||
IntPtr TokenHandle,
|
||||
int TokenInformationClass,
|
||||
IntPtr TokenInformation,
|
||||
uint TokenInformationLength);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool SetThreadToken(IntPtr Thread, IntPtr Token);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool RevertToSelf();
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
private static extern IntPtr CreateToolhelp32Snapshot(uint dwFlags, uint th32ProcessID);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool Process32FirstW(IntPtr hSnapshot, ref PROCESSENTRY32W lppe);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool Process32NextW(IntPtr hSnapshot, ref PROCESSENTRY32W lppe);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool LookupPrivilegeValueW(string lpSystemName, string lpName, out LUID lpLuid);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool AdjustTokenPrivileges(
|
||||
IntPtr TokenHandle,
|
||||
[MarshalAs(UnmanagedType.Bool)] bool DisableAllPrivileges,
|
||||
ref TOKEN_PRIVILEGES NewState,
|
||||
uint BufferLength,
|
||||
IntPtr PreviousState,
|
||||
IntPtr ReturnLength);
|
||||
|
||||
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
private static extern bool CreateProcessWithTokenW(
|
||||
IntPtr hToken,
|
||||
uint dwLogonFlags,
|
||||
string lpApplicationName,
|
||||
StringBuilder lpCommandLine,
|
||||
uint dwCreationFlags,
|
||||
IntPtr lpEnvironment,
|
||||
string lpCurrentDirectory,
|
||||
ref STARTUPINFOW lpStartupInfo,
|
||||
out PROCESS_INFORMATION lpProcessInformation);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
|
||||
private static extern void GetStartupInfoW(ref STARTUPINFOW lpStartupInfo);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Public API
|
||||
|
||||
/// <summary>
|
||||
/// 检查当前进程是否已具有 UIAccess 标志。
|
||||
/// </summary>
|
||||
public static bool HasUIAccess()
|
||||
{
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr hToken))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
IntPtr buf = Marshal.AllocHGlobal(sizeof(uint));
|
||||
try
|
||||
{
|
||||
Marshal.WriteInt32(buf, 0);
|
||||
if (!GetTokenInformation(hToken, TokenUIAccess, buf, sizeof(uint), out _))
|
||||
return false;
|
||||
return Marshal.ReadInt32(buf) != 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
Marshal.FreeHGlobal(buf);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(hToken);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以 UIAccess 令牌重启自身。当前进程必须已经以管理员身份运行。
|
||||
/// 成功时新进程已启动,调用方应立即退出当前进程。
|
||||
/// </summary>
|
||||
/// <param name="extraArgs">追加到新进程的额外命令行参数(例如 --skip-mutex-check)。</param>
|
||||
public static bool RestartWithUIAccess(string extraArgs = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (HasUIAccess())
|
||||
{
|
||||
LogHelper.WriteLogToFile("UIAccess | 当前进程已具有 UIAccess,跳过重启");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!CreateUIAccessToken(out IntPtr uiaToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | 创建 UIAccess 令牌失败 (LastError={Marshal.GetLastWin32Error()})", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return LaunchWithToken(uiaToken, extraArgs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(uiaToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | RestartWithUIAccess 异常: {ex}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 以普通用户权限(非提升)重启自身。
|
||||
/// 通过获取 explorer.exe / ctfmon.exe 的非特权令牌,再用 CreateProcessWithTokenW 启动新进程,
|
||||
/// 避免经由 explorer.exe 中转可能产生的 UAC 提示或丢失参数问题。
|
||||
/// 成功时调用方应立即退出当前进程。
|
||||
/// </summary>
|
||||
/// <param name="extraArgs">追加到新进程的额外命令行参数。</param>
|
||||
public static bool RestartAsNormalUser(string extraArgs = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!GetCurrentProcessSessionId(out uint sessionId))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | 获取当前会话 ID 失败 (LastError={Marshal.GetLastWin32Error()})", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GetUserPrimaryToken(sessionId, out IntPtr userToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | 获取用户令牌失败 (LastError={Marshal.GetLastWin32Error()})", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return LaunchWithToken(userToken, extraArgs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(userToken);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | RestartAsNormalUser 异常: {ex}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Token Manipulation
|
||||
|
||||
private static bool CreateUIAccessToken(out IntPtr uiaToken)
|
||||
{
|
||||
uiaToken = IntPtr.Zero;
|
||||
|
||||
// 1. 获取当前进程的 session id
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr hSelfQuery))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | OpenProcessToken(query) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint sessionId;
|
||||
try
|
||||
{
|
||||
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
|
||||
try
|
||||
{
|
||||
if (!GetTokenInformation(hSelfQuery, TokenSessionId, sesBuf, sizeof(uint), out _))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | GetTokenInformation(SessionId) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
sessionId = (uint)Marshal.ReadInt32(sesBuf);
|
||||
}
|
||||
finally { Marshal.FreeHGlobal(sesBuf); }
|
||||
}
|
||||
finally { CloseHandle(hSelfQuery); }
|
||||
|
||||
// 2. 找到同一会话的 winlogon 模拟令牌
|
||||
if (!GetWinlogonImpersonationToken(sessionId, out IntPtr winlogonToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile("UIAccess | 未能获取 winlogon 模拟令牌(需要管理员权限)", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 3. 模拟 winlogon
|
||||
if (!SetThreadToken(IntPtr.Zero, winlogonToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | SetThreadToken(winlogon) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 4. 复制自身令牌为主令牌
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_DUPLICATE, out IntPtr hSelfDup))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | OpenProcessToken(dup) 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
IntPtr dupToken;
|
||||
try
|
||||
{
|
||||
bool ok = DuplicateTokenEx(
|
||||
hSelfDup,
|
||||
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
|
||||
IntPtr.Zero,
|
||||
SecurityAnonymous,
|
||||
TokenPrimary,
|
||||
out dupToken);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | DuplicateTokenEx 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally { CloseHandle(hSelfDup); }
|
||||
|
||||
// 5. 在副本上设置 UIAccess = TRUE
|
||||
IntPtr uiBuf = Marshal.AllocHGlobal(sizeof(uint));
|
||||
try
|
||||
{
|
||||
Marshal.WriteInt32(uiBuf, 1);
|
||||
if (!SetTokenInformation(dupToken, TokenUIAccess, uiBuf, sizeof(uint)))
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
LogHelper.WriteLogToFile($"UIAccess | SetTokenInformation(UIAccess) 失败: {err}", LogHelper.LogType.Error);
|
||||
CloseHandle(dupToken);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
finally { Marshal.FreeHGlobal(uiBuf); }
|
||||
|
||||
uiaToken = dupToken;
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
RevertToSelf();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
CloseHandle(winlogonToken);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool GetWinlogonImpersonationToken(uint sessionId, out IntPtr winlogonToken)
|
||||
{
|
||||
winlogonToken = IntPtr.Zero;
|
||||
|
||||
IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snapshot == INVALID_HANDLE_VALUE || snapshot == IntPtr.Zero)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | CreateToolhelp32Snapshot 失败: {Marshal.GetLastWin32Error()}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var pe = new PROCESSENTRY32W { dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32W)) };
|
||||
bool more = Process32FirstW(snapshot, ref pe);
|
||||
|
||||
while (more)
|
||||
{
|
||||
if (string.Equals(pe.szExeFile, "winlogon.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (TryDuplicateWinlogonToken(pe.th32ProcessID, sessionId, out winlogonToken))
|
||||
return true;
|
||||
}
|
||||
more = Process32NextW(snapshot, ref pe);
|
||||
}
|
||||
}
|
||||
finally { CloseHandle(snapshot); }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryDuplicateWinlogonToken(uint pid, uint sessionId, out IntPtr dupToken)
|
||||
{
|
||||
dupToken = IntPtr.Zero;
|
||||
|
||||
IntPtr hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
||||
if (hProc == IntPtr.Zero) return false;
|
||||
|
||||
try
|
||||
{
|
||||
if (!OpenProcessToken(hProc, TOKEN_QUERY | TOKEN_DUPLICATE, out IntPtr hToken))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// 检查 session id 匹配
|
||||
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
|
||||
try
|
||||
{
|
||||
if (!GetTokenInformation(hToken, TokenSessionId, sesBuf, sizeof(uint), out _))
|
||||
return false;
|
||||
if ((uint)Marshal.ReadInt32(sesBuf) != sessionId)
|
||||
return false;
|
||||
}
|
||||
finally { Marshal.FreeHGlobal(sesBuf); }
|
||||
|
||||
if (!DuplicateTokenEx(
|
||||
hToken,
|
||||
TOKEN_IMPERSONATE | TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY | TOKEN_DUPLICATE,
|
||||
IntPtr.Zero,
|
||||
SecurityImpersonation,
|
||||
TokenImpersonation,
|
||||
out dupToken))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 启用 SeAssignPrimaryTokenPrivilege(Inkeys 行为)
|
||||
var tkp = new TOKEN_PRIVILEGES
|
||||
{
|
||||
PrivilegeCount = 1,
|
||||
Privilege = new LUID_AND_ATTRIBUTES { Attributes = SE_PRIVILEGE_ENABLED }
|
||||
};
|
||||
if (LookupPrivilegeValueW(null, SE_ASSIGNPRIMARYTOKEN_NAME, out tkp.Privilege.Luid))
|
||||
{
|
||||
AdjustTokenPrivileges(dupToken, false, ref tkp, (uint)Marshal.SizeOf(tkp), IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
finally { CloseHandle(hToken); }
|
||||
}
|
||||
finally { CloseHandle(hProc); }
|
||||
}
|
||||
|
||||
private static bool GetCurrentProcessSessionId(out uint sessionId)
|
||||
{
|
||||
sessionId = 0;
|
||||
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, out IntPtr hSelfQuery))
|
||||
return false;
|
||||
try
|
||||
{
|
||||
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
|
||||
try
|
||||
{
|
||||
if (!GetTokenInformation(hSelfQuery, TokenSessionId, sesBuf, sizeof(uint), out _))
|
||||
return false;
|
||||
sessionId = (uint)Marshal.ReadInt32(sesBuf);
|
||||
return true;
|
||||
}
|
||||
finally { Marshal.FreeHGlobal(sesBuf); }
|
||||
}
|
||||
finally { CloseHandle(hSelfQuery); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从 explorer.exe / ctfmon.exe 取得普通用户(非提升)令牌的主令牌副本,用于降权启动。
|
||||
/// 仅当当前进程为管理员时才能成功。
|
||||
/// </summary>
|
||||
private static bool GetUserPrimaryToken(uint sessionId, out IntPtr userToken)
|
||||
{
|
||||
userToken = IntPtr.Zero;
|
||||
|
||||
string[] candidates = { "explorer.exe", "ctfmon.exe" };
|
||||
foreach (var name in candidates)
|
||||
{
|
||||
IntPtr snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
||||
if (snapshot == INVALID_HANDLE_VALUE || snapshot == IntPtr.Zero) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var pe = new PROCESSENTRY32W { dwSize = (uint)Marshal.SizeOf(typeof(PROCESSENTRY32W)) };
|
||||
bool more = Process32FirstW(snapshot, ref pe);
|
||||
while (more)
|
||||
{
|
||||
if (string.Equals(pe.szExeFile, name, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (TryDuplicateUserPrimaryToken(pe.th32ProcessID, sessionId, out userToken))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"UIAccess | 已从 {name} (PID={pe.th32ProcessID}, Session={sessionId}) 取得用户令牌");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
more = Process32NextW(snapshot, ref pe);
|
||||
}
|
||||
}
|
||||
finally { CloseHandle(snapshot); }
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool TryDuplicateUserPrimaryToken(uint pid, uint sessionId, out IntPtr dupToken)
|
||||
{
|
||||
dupToken = IntPtr.Zero;
|
||||
|
||||
IntPtr hProc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
|
||||
if (hProc == IntPtr.Zero) return false;
|
||||
|
||||
try
|
||||
{
|
||||
if (!OpenProcessToken(hProc, TOKEN_QUERY | TOKEN_DUPLICATE, out IntPtr hToken))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
// 会话隔离:拒绝来自其他登录会话(RDP / 终端服务 / 快速用户切换)的令牌,
|
||||
// 否则降权后进程会落到错误用户的桌面上下文中。
|
||||
IntPtr sesBuf = Marshal.AllocHGlobal(sizeof(uint));
|
||||
try
|
||||
{
|
||||
if (!GetTokenInformation(hToken, TokenSessionId, sesBuf, sizeof(uint), out _))
|
||||
return false;
|
||||
if ((uint)Marshal.ReadInt32(sesBuf) != sessionId)
|
||||
return false;
|
||||
}
|
||||
finally { Marshal.FreeHGlobal(sesBuf); }
|
||||
|
||||
// 仅接受非提升令牌(否则降权失败)
|
||||
IntPtr elevBuf = Marshal.AllocHGlobal(sizeof(int));
|
||||
try
|
||||
{
|
||||
if (!GetTokenInformation(hToken, TokenElevationType, elevBuf, sizeof(int), out _))
|
||||
return false;
|
||||
int elev = Marshal.ReadInt32(elevBuf);
|
||||
if (elev == TokenElevationTypeFull)
|
||||
return false; // 该进程是提升令牌,跳过
|
||||
}
|
||||
finally { Marshal.FreeHGlobal(elevBuf); }
|
||||
|
||||
return DuplicateTokenEx(
|
||||
hToken,
|
||||
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
|
||||
IntPtr.Zero,
|
||||
SecurityAnonymous,
|
||||
TokenPrimary,
|
||||
out dupToken);
|
||||
}
|
||||
finally { CloseHandle(hToken); }
|
||||
}
|
||||
finally { CloseHandle(hProc); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Process Launch
|
||||
|
||||
private static bool LaunchWithToken(IntPtr token, string extraArgs)
|
||||
{
|
||||
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
string workDir = System.IO.Path.GetDirectoryName(exePath);
|
||||
|
||||
// 重建命令行:保留原始参数,追加 --skip-mutex-check 防止单实例阻塞
|
||||
var cmdBuilder = new StringBuilder(32768);
|
||||
cmdBuilder.Append('"').Append(exePath).Append('"');
|
||||
|
||||
string[] args = Environment.GetCommandLineArgs();
|
||||
for (int i = 1; i < args.Length; i++)
|
||||
{
|
||||
cmdBuilder.Append(' ');
|
||||
AppendQuoted(cmdBuilder, args[i]);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(extraArgs))
|
||||
cmdBuilder.Append(' ').Append(extraArgs);
|
||||
|
||||
// 防止单实例 Mutex 阻塞新进程
|
||||
if (Array.IndexOf(args, "--skip-mutex-check") < 0
|
||||
&& (extraArgs == null || extraArgs.IndexOf("--skip-mutex-check", StringComparison.Ordinal) < 0))
|
||||
{
|
||||
cmdBuilder.Append(" --skip-mutex-check");
|
||||
}
|
||||
|
||||
var si = new STARTUPINFOW { cb = (uint)Marshal.SizeOf(typeof(STARTUPINFOW)) };
|
||||
GetStartupInfoW(ref si);
|
||||
|
||||
bool ok = CreateProcessWithTokenW(
|
||||
token,
|
||||
LOGON_WITH_PROFILE,
|
||||
null,
|
||||
cmdBuilder,
|
||||
CREATE_UNICODE_ENVIRONMENT,
|
||||
IntPtr.Zero,
|
||||
workDir,
|
||||
ref si,
|
||||
out PROCESS_INFORMATION pi);
|
||||
|
||||
if (!ok)
|
||||
{
|
||||
int err = Marshal.GetLastWin32Error();
|
||||
LogHelper.WriteLogToFile($"UIAccess | CreateProcessWithTokenW 失败: {err}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
LogHelper.WriteLogToFile($"UIAccess | 已使用 UIAccess 令牌启动新进程 (PID={pi.dwProcessId})");
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void AppendQuoted(StringBuilder sb, string arg)
|
||||
{
|
||||
if (arg == null) { sb.Append("\"\""); return; }
|
||||
|
||||
bool needQuote = arg.Length == 0 || arg.IndexOfAny(new[] { ' ', '\t', '"' }) >= 0;
|
||||
if (!needQuote) { sb.Append(arg); return; }
|
||||
|
||||
sb.Append('"');
|
||||
int backslashes = 0;
|
||||
foreach (char c in arg)
|
||||
{
|
||||
if (c == '\\') { backslashes++; continue; }
|
||||
if (c == '"')
|
||||
{
|
||||
sb.Append('\\', backslashes * 2 + 1);
|
||||
sb.Append('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append('\\', backslashes);
|
||||
sb.Append(c);
|
||||
}
|
||||
backslashes = 0;
|
||||
}
|
||||
sb.Append('\\', backslashes * 2);
|
||||
sb.Append('"');
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
@@ -19,6 +18,9 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
internal static class WinRtHandwritingRecognizer
|
||||
{
|
||||
private static WinRtInk.InkRecognizer _preferredHandwritingRecognizer;
|
||||
private static bool _preferredHandwritingRecognizerResolved;
|
||||
|
||||
private static void LogHandwriting(string message, LogHelper.LogType logType = LogHelper.LogType.Info)
|
||||
{
|
||||
LogHelper.WriteLogToFile("[手写体] " + message, logType);
|
||||
@@ -27,29 +29,13 @@ namespace Ink_Canvas.Helpers
|
||||
public static bool IsApiAvailable =>
|
||||
OSVersion.GetOperatingSystem() >= OSVersionExtension.OperatingSystem.Windows10;
|
||||
|
||||
/// <summary>
|
||||
/// 启动阶段不再预热线程内 WinRT 手写管线。历史上曾用 <see cref="WinRtInkShapeRecognizer.CreateMinimalWarmupStrokeCollection"/> 跑全链路,
|
||||
/// 会显著拖慢启动;与更早的「空 <see cref="StrokeCollection"/>」一样,此处不再在 Idle 上做任何工作。
|
||||
/// 首次真正需要手写识别时由 <see cref="RecognizeHandwritingAsync"/> 承担冷启动成本。
|
||||
/// </summary>
|
||||
public static void Warmup()
|
||||
{
|
||||
if (!IsApiAvailable || !Environment.Is64BitProcess) return;
|
||||
try
|
||||
{
|
||||
var d = Application.Current?.Dispatcher;
|
||||
if (d == null) return;
|
||||
d.BeginInvoke(new Action(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await RecognizeHandwritingAsync(new StrokeCollection()).ConfigureAwait(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -57,15 +43,21 @@ namespace Ink_Canvas.Helpers
|
||||
/// 再对每一分词用 <see cref="WinRtInk.InkRecognizerContainer"/> 取 <c>GetTextCandidates</c>(与当前 SDK 中部分版本的
|
||||
/// <see cref="WinRtInk.InkRecognitionResult"/> 未暴露笔画映射的局限兼容)。
|
||||
/// </summary>
|
||||
public static async Task<HandwritingRecognitionResult> RecognizeHandwritingAsync(StrokeCollection strokes)
|
||||
/// <param name="verboseTrace">为 false 时跳过详细识别日志(用于 <see cref="Warmup"/> 等)。</param>
|
||||
public static async Task<HandwritingRecognitionResult> RecognizeHandwritingAsync(
|
||||
StrokeCollection strokes,
|
||||
bool verboseTrace = true)
|
||||
{
|
||||
if (!IsApiAvailable || strokes == null || strokes.Count == 0)
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
|
||||
var traceRecognition = strokes.Count > 0;
|
||||
var traceRecognition = verboseTrace;
|
||||
|
||||
try
|
||||
{
|
||||
var recognizer = new WinRtInk.InkRecognizerContainer();
|
||||
TryApplyPreferredHandwritingRecognizer(recognizer, traceRecognition);
|
||||
|
||||
var analyzer = new WinAnalysis.InkAnalyzer();
|
||||
var idToWpf = new Dictionary<uint, Stroke>();
|
||||
|
||||
@@ -92,19 +84,21 @@ namespace Ink_Canvas.Helpers
|
||||
LogHandwriting(
|
||||
"识别:AnalyzeAsync 未得到 Updated,Status=" +
|
||||
(analysisResult == null ? "null" : analysisResult.Status.ToString()) +
|
||||
",有效笔画数=" + idToWpf.Count);
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
",有效笔画数=" + idToWpf.Count +
|
||||
",尝试整批 RecognizeAsync 回退。");
|
||||
return await RecognizeHandwritingWholeInkAsync(strokes, traceRecognition).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
var wordNodes = analyzer.AnalysisRoot?.FindNodes(WinAnalysis.InkAnalysisNodeKind.InkWord);
|
||||
if (wordNodes == null || wordNodes.Count == 0)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("识别:未找到 InkWord 节点(可能被判为绘图或非书写),有效笔画数=" + idToWpf.Count);
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
LogHandwriting(
|
||||
"识别:未找到 InkWord 节点(墨迹分析常将非横平笔划判为绘图),有效笔画数=" + idToWpf.Count +
|
||||
",改用整批 RecognizeAsync 回退。");
|
||||
return await RecognizeHandwritingWholeInkAsync(strokes, traceRecognition).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
var recognizer = new WinRtInk.InkRecognizerContainer();
|
||||
var segments = new List<HandwritingWordSegment>();
|
||||
|
||||
foreach (var node in wordNodes)
|
||||
@@ -232,6 +226,243 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryApplyPreferredHandwritingRecognizer(
|
||||
WinRtInk.InkRecognizerContainer container,
|
||||
bool logDetail)
|
||||
{
|
||||
if (container == null)
|
||||
return;
|
||||
try
|
||||
{
|
||||
if (!_preferredHandwritingRecognizerResolved)
|
||||
{
|
||||
_preferredHandwritingRecognizerResolved = true;
|
||||
var all = container.GetRecognizers();
|
||||
_preferredHandwritingRecognizer = SelectBestInkRecognizer(all);
|
||||
if (logDetail)
|
||||
{
|
||||
if (_preferredHandwritingRecognizer != null)
|
||||
LogHandwriting("识别器:已选用 \"" + _preferredHandwritingRecognizer.Name + "\"。");
|
||||
else if (all != null && all.Count > 0)
|
||||
LogHandwriting("识别器:未匹配到与 UI/区域语言对应的引擎,使用系统默认(共 " + all.Count + " 个)。");
|
||||
}
|
||||
}
|
||||
|
||||
if (_preferredHandwritingRecognizer != null)
|
||||
container.SetDefaultRecognizer(_preferredHandwritingRecognizer);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("[手写体] 设置默认手写识别器失败: " + ex.Message, LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private static WinRtInk.InkRecognizer SelectBestInkRecognizer(
|
||||
IReadOnlyList<WinRtInk.InkRecognizer> list)
|
||||
{
|
||||
if (list == null || list.Count == 0)
|
||||
return null;
|
||||
|
||||
var culture = PrimaryHandwritingCulture();
|
||||
var lang = (culture?.TwoLetterISOLanguageName ?? string.Empty).ToLowerInvariant();
|
||||
var name = culture?.Name ?? string.Empty;
|
||||
|
||||
bool wantZhHans = lang == "zh" &&
|
||||
(name.IndexOf("hans", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
name.Equals("zh-cn", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.Equals("zh-sg", StringComparison.OrdinalIgnoreCase) ||
|
||||
(name.IndexOf("hant", StringComparison.OrdinalIgnoreCase) < 0 &&
|
||||
!name.Equals("zh-tw", StringComparison.OrdinalIgnoreCase) &&
|
||||
!name.Equals("zh-hk", StringComparison.OrdinalIgnoreCase) &&
|
||||
!name.Equals("zh-mo", StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
bool wantZhHant = lang == "zh" &&
|
||||
(name.IndexOf("hant", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
name.Equals("zh-tw", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.Equals("zh-hk", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.Equals("zh-mo", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
WinRtInk.InkRecognizer Pick(Func<string, bool> match)
|
||||
{
|
||||
foreach (var r in list)
|
||||
{
|
||||
var n = r?.Name;
|
||||
if (string.IsNullOrEmpty(n))
|
||||
continue;
|
||||
if (match(n))
|
||||
return r;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (wantZhHans)
|
||||
{
|
||||
var r = Pick(n =>
|
||||
n.IndexOf("简体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("簡體", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
(n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||
(n.IndexOf("简体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("簡體", StringComparison.OrdinalIgnoreCase) >= 0)) ||
|
||||
(n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||
(n.IndexOf("Simplified", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Hans", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("PRC", StringComparison.OrdinalIgnoreCase) >= 0)));
|
||||
if (r != null)
|
||||
return r;
|
||||
r = Pick(n =>
|
||||
n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
else if (wantZhHant)
|
||||
{
|
||||
var r = Pick(n =>
|
||||
n.IndexOf("繁体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("繁體", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
(n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||
(n.IndexOf("繁体", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("繁體", StringComparison.OrdinalIgnoreCase) >= 0)) ||
|
||||
(n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0 &&
|
||||
(n.IndexOf("Traditional", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Hant", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Taiwan", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Hong Kong", StringComparison.OrdinalIgnoreCase) >= 0)));
|
||||
if (r != null)
|
||||
return r;
|
||||
r = Pick(n =>
|
||||
n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
else if (lang == "ja")
|
||||
{
|
||||
var r = Pick(n =>
|
||||
n.IndexOf("Japanese", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("日本語", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("日语", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
else if (lang == "en")
|
||||
{
|
||||
var r = Pick(n => n.IndexOf("English", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
|
||||
if (lang == "zh")
|
||||
{
|
||||
var r = Pick(n =>
|
||||
n.IndexOf("中文", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
n.IndexOf("Chinese", StringComparison.OrdinalIgnoreCase) >= 0);
|
||||
if (r != null)
|
||||
return r;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static CultureInfo PrimaryHandwritingCulture()
|
||||
{
|
||||
var ui = CultureInfo.CurrentUICulture;
|
||||
var ct = CultureInfo.CurrentCulture;
|
||||
if (string.Equals(ui.TwoLetterISOLanguageName, "zh", StringComparison.OrdinalIgnoreCase))
|
||||
return ui;
|
||||
if (string.Equals(ct.TwoLetterISOLanguageName, "zh", StringComparison.OrdinalIgnoreCase))
|
||||
return ct;
|
||||
return ui;
|
||||
}
|
||||
|
||||
private static async Task<HandwritingRecognitionResult> RecognizeHandwritingWholeInkAsync(
|
||||
StrokeCollection strokes,
|
||||
bool traceRecognition)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
|
||||
var container = new WinRtInk.InkStrokeContainer();
|
||||
foreach (Stroke s in strokes)
|
||||
{
|
||||
var ink = WinRtInkShapeRecognizer.CreateInkStrokeFromWpf(s);
|
||||
if (ink != null)
|
||||
container.AddStroke(ink);
|
||||
}
|
||||
|
||||
var winStrokes = container.GetStrokes();
|
||||
if (winStrokes == null || winStrokes.Count == 0)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("整批回退:无有效 WinRT 笔画。");
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
|
||||
var reco = new WinRtInk.InkRecognizerContainer();
|
||||
TryApplyPreferredHandwritingRecognizer(reco, false);
|
||||
|
||||
IReadOnlyList<WinRtInk.InkRecognitionResult> rr;
|
||||
try
|
||||
{
|
||||
rr = await reco
|
||||
.RecognizeAsync(container, WinRtInk.InkRecognitionTarget.All)
|
||||
.AsTask()
|
||||
.ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("整批回退:RecognizeAsync 异常:" + ex.Message);
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
|
||||
if (rr == null || rr.Count == 0 || rr[0] == null)
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("整批回退:RecognizeAsync 无结果。");
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
|
||||
var cands = rr[0].GetTextCandidates();
|
||||
var primary = (cands != null && cands.Count > 0) ? cands[0] : string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(primary))
|
||||
{
|
||||
if (traceRecognition)
|
||||
LogHandwriting("整批回退:候选文本为空。");
|
||||
return HandwritingRecognitionResult.Empty;
|
||||
}
|
||||
|
||||
var merged = new List<string>();
|
||||
if (cands != null)
|
||||
{
|
||||
foreach (var c in cands)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(c) && !merged.Contains(c))
|
||||
merged.Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
var bounds = UnionStrokeBounds(strokes);
|
||||
var group = new List<Stroke>();
|
||||
foreach (Stroke s in strokes)
|
||||
group.Add(s);
|
||||
|
||||
var seg = new HandwritingWordSegment(primary, merged, bounds, group);
|
||||
return new HandwritingRecognitionResult(new List<HandwritingWordSegment> { seg });
|
||||
}
|
||||
|
||||
private static Rect UnionStrokeBounds(StrokeCollection strokes)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
return Rect.Empty;
|
||||
|
||||
var r = strokes[0].GetBounds();
|
||||
for (var i = 1; i < strokes.Count; i++)
|
||||
r = Rect.Union(r, strokes[i].GetBounds());
|
||||
return r;
|
||||
}
|
||||
|
||||
private const string DefaultHandwritingFontFamilyList = "Ink Free,KaiTi,Segoe Script";
|
||||
|
||||
/// <summary>
|
||||
@@ -491,64 +722,60 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
var m = new Matrix(scale, 0, 0, scale, tx, ty);
|
||||
geom.Transform = new MatrixTransform(m);
|
||||
return StrokesFromOutlinedGeometry(geom, templateDa, 0.35);
|
||||
|
||||
var filled = FilledGlyphStroke.TryCreate(geom, templateDa);
|
||||
if (filled == null)
|
||||
return list;
|
||||
|
||||
list.Add(filled);
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 把字形几何作为「实心填充」绘制的笔画。仍是 WPF <see cref="Stroke"/>,可被 InkCanvas 选择/移动/删除,
|
||||
/// 但渲染时直接 DrawGeometry(brush, null, geom),不再走 StylusPoints 描边路径。
|
||||
/// </summary>
|
||||
internal sealed class FilledGlyphStroke : Stroke
|
||||
{
|
||||
private readonly Geometry _geometry;
|
||||
|
||||
private FilledGlyphStroke(StylusPointCollection pts, Geometry geometry, DrawingAttributes da)
|
||||
: base(pts)
|
||||
{
|
||||
_geometry = geometry;
|
||||
if (da != null)
|
||||
DrawingAttributes = da.Clone();
|
||||
}
|
||||
|
||||
private static List<Stroke> StrokesFromOutlinedGeometry(Geometry geometry, DrawingAttributes da, double tolerance)
|
||||
public static FilledGlyphStroke TryCreate(Geometry geometry, DrawingAttributes templateDa)
|
||||
{
|
||||
var list = new List<Stroke>();
|
||||
if (geometry == null || geometry.IsEmpty() || da == null)
|
||||
return list;
|
||||
if (geometry == null || geometry.IsEmpty())
|
||||
return null;
|
||||
|
||||
Geometry outlined;
|
||||
try
|
||||
var b = geometry.Bounds;
|
||||
if (b.IsEmpty || b.Width < 0.5 || b.Height < 0.5)
|
||||
return null;
|
||||
|
||||
// StylusPoints 用 bounds 四角,保证命中测试 / 选区 / 包围盒计算正常。
|
||||
var pts = new StylusPointCollection
|
||||
{
|
||||
outlined = geometry.GetOutlinedPathGeometry(tolerance, ToleranceType.Absolute);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return list;
|
||||
}
|
||||
new StylusPoint(b.Left, b.Top, 0.5f),
|
||||
new StylusPoint(b.Right, b.Top, 0.5f),
|
||||
new StylusPoint(b.Right, b.Bottom, 0.5f),
|
||||
new StylusPoint(b.Left, b.Bottom, 0.5f),
|
||||
};
|
||||
|
||||
if (outlined == null || outlined.IsEmpty())
|
||||
return list;
|
||||
return new FilledGlyphStroke(pts, geometry, templateDa);
|
||||
}
|
||||
|
||||
Geometry flat;
|
||||
try
|
||||
{
|
||||
flat = outlined.GetFlattenedPathGeometry(tolerance, ToleranceType.Absolute);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return list;
|
||||
}
|
||||
protected override void DrawCore(DrawingContext drawingContext, DrawingAttributes drawingAttributes)
|
||||
{
|
||||
if (drawingContext == null || _geometry == null)
|
||||
return;
|
||||
|
||||
if (!(flat is PathGeometry pg))
|
||||
return list;
|
||||
|
||||
foreach (var fig in pg.Figures)
|
||||
{
|
||||
var pts = new StylusPointCollection();
|
||||
pts.Add(new StylusPoint(fig.StartPoint.X, fig.StartPoint.Y, 0.5f));
|
||||
foreach (var seg in fig.Segments)
|
||||
{
|
||||
switch (seg)
|
||||
{
|
||||
case LineSegment ls:
|
||||
pts.Add(new StylusPoint(ls.Point.X, ls.Point.Y, 0.5f));
|
||||
break;
|
||||
case PolyLineSegment pls:
|
||||
foreach (var p in pls.Points)
|
||||
pts.Add(new StylusPoint(p.X, p.Y, 0.5f));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (pts.Count >= 2)
|
||||
list.Add(new Stroke(pts) { DrawingAttributes = da.Clone() });
|
||||
}
|
||||
|
||||
return list;
|
||||
var color = drawingAttributes != null ? drawingAttributes.Color : Colors.Black;
|
||||
drawingContext.DrawGeometry(new SolidColorBrush(color), null, _geometry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using OSVersionExtension;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
@@ -11,6 +12,128 @@ using WinRtInkAnalyzer = global::Windows.UI.Input.Inking.Analysis.InkAnalyzer;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class ModernInkAnalyzer : IDisposable
|
||||
{
|
||||
public static readonly Guid ShapeStrokePropertyGuid = new Guid("11111111-2222-3333-4444-555555555555");
|
||||
|
||||
private global::Windows.UI.Input.Inking.Analysis.InkAnalyzer _internalAnalyzer;
|
||||
private readonly Dictionary<Stroke, uint> _strokeIdMap = new Dictionary<Stroke, uint>();
|
||||
private readonly Dictionary<uint, Stroke> _reverseIdMap = new Dictionary<uint, Stroke>();
|
||||
private readonly object _syncLock = new object();
|
||||
|
||||
public ModernInkAnalyzer()
|
||||
{
|
||||
if (!WinRtInkShapeRecognizer.IsApiAvailable)
|
||||
return;
|
||||
|
||||
_internalAnalyzer = new global::Windows.UI.Input.Inking.Analysis.InkAnalyzer();
|
||||
}
|
||||
|
||||
private void AddStrokeInternal(Stroke stroke)
|
||||
{
|
||||
if (stroke.ContainsPropertyData(ShapeStrokePropertyGuid))
|
||||
return;
|
||||
|
||||
var inkStroke = WinRtInkShapeRecognizer.CreateInkStrokeFromWpf(stroke);
|
||||
if (inkStroke == null) return;
|
||||
|
||||
_internalAnalyzer.AddDataForStroke(inkStroke);
|
||||
_internalAnalyzer.SetStrokeDataKind(
|
||||
inkStroke.Id,
|
||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisStrokeKind.Drawing);
|
||||
|
||||
_strokeIdMap[stroke] = inkStroke.Id;
|
||||
_reverseIdMap[inkStroke.Id] = stroke;
|
||||
}
|
||||
|
||||
private CancellationTokenSource _cts;
|
||||
|
||||
public async Task<InkShapeRecognitionResult> AnalyzeAsync(StrokeCollection strokes)
|
||||
{
|
||||
if (_internalAnalyzer == null || strokes == null || strokes.Count == 0)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
_cts?.Cancel();
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
|
||||
try
|
||||
{
|
||||
lock (_syncLock)
|
||||
{
|
||||
_internalAnalyzer.ClearDataForAllStrokes();
|
||||
_strokeIdMap.Clear();
|
||||
_reverseIdMap.Clear();
|
||||
|
||||
foreach (var stroke in strokes)
|
||||
{
|
||||
AddStrokeInternal(stroke);
|
||||
}
|
||||
}
|
||||
|
||||
if (_strokeIdMap.Count == 0)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
var result = await _internalAnalyzer.AnalyzeAsync().AsTask(token).ConfigureAwait(true);
|
||||
|
||||
if (token.IsCancellationRequested) return InkShapeRecognitionResult.Empty;
|
||||
|
||||
// Use the internal method from WinRtInkShapeRecognizer to find the primary drawing
|
||||
var drawing = WinRtInkShapeRecognizer.FindPrimaryDrawing(_internalAnalyzer);
|
||||
if (drawing == null)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
if (drawing.DrawingKind == global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind.Drawing)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
var name = WinRtInkShapeRecognizer.MapDrawingKindToShapeName(drawing.DrawingKind);
|
||||
if (string.IsNullOrEmpty(name) || name == "Drawing")
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
var winPts = WinRtInkShapeRecognizer.CopyWinRtPoints(drawing);
|
||||
var hot = WinRtInkShapeRecognizer.ToWpfPointCollection(winPts);
|
||||
var c = drawing.Center;
|
||||
var centroid = new SysPoint(c.X, c.Y);
|
||||
WinRtInkShapeRecognizer.BoundsFromPoints(winPts, out double w, out double h);
|
||||
|
||||
var toRemove = new StrokeCollection();
|
||||
lock (_syncLock)
|
||||
{
|
||||
foreach (var id in drawing.GetStrokeIds())
|
||||
{
|
||||
if (_reverseIdMap.TryGetValue(id, out var stroke))
|
||||
{
|
||||
toRemove.Add(stroke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (toRemove.Count == 0)
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
|
||||
return new InkShapeRecognitionResult(name, centroid, hot, w, h, toRemove);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return InkShapeRecognitionResult.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public Task<StrokeCollection> AnalyzeAndCorrectAsync(
|
||||
StrokeCollection strokes,
|
||||
string handwritingFontFamilyList)
|
||||
{
|
||||
return WinRtHandwritingRecognizer.ConvertRecognizedTextToHandwritingInkAsync(
|
||||
strokes,
|
||||
handwritingFontFamilyList);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_internalAnalyzer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>基于 Windows.UI.Input.Inking.Analysis 的形状识别(适用于 64 位进程等场景)。</summary>
|
||||
internal static class WinRtInkShapeRecognizer
|
||||
{
|
||||
@@ -28,7 +151,8 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
await RecognizeShapeAsync(new StrokeCollection());
|
||||
// 空 StrokeCollection 在 RecognizeShapeAsync 入口会直接返回,无法预热 WinRT InkAnalyzer。
|
||||
await RecognizeShapeAsync(CreateMinimalWarmupStrokeCollection()).ConfigureAwait(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -99,6 +223,23 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 极短合成笔画,供 <see cref="Warmup"/> 等场景走完整 WinRT 转换与分析管线(空集合在入口处会被直接返回)。
|
||||
/// </summary>
|
||||
internal static StrokeCollection CreateMinimalWarmupStrokeCollection()
|
||||
{
|
||||
var da = new DrawingAttributes { Color = Colors.Black, Width = 2, Height = 2 };
|
||||
var pts = new StylusPointCollection
|
||||
{
|
||||
new StylusPoint(8, 8),
|
||||
new StylusPoint(14, 10),
|
||||
new StylusPoint(20, 8),
|
||||
};
|
||||
var col = new StrokeCollection();
|
||||
col.Add(new Stroke(pts, da));
|
||||
return col;
|
||||
}
|
||||
|
||||
/// <summary>供 WinRT 手写等模块复用:将 WPF <see cref="Stroke"/> 转为 WinRT <see cref="global::Windows.UI.Input.Inking.InkStroke"/>。</summary>
|
||||
internal static global::Windows.UI.Input.Inking.InkStroke CreateInkStrokeFromWpf(Stroke stroke)
|
||||
{
|
||||
@@ -106,6 +247,9 @@ namespace Ink_Canvas.Helpers
|
||||
return null;
|
||||
|
||||
var da = stroke.DrawingAttributes;
|
||||
if (da == null)
|
||||
return null;
|
||||
|
||||
var wda = new global::Windows.UI.Input.Inking.InkDrawingAttributes
|
||||
{
|
||||
PenTip = global::Windows.UI.Input.Inking.PenTipShape.Circle,
|
||||
@@ -129,8 +273,8 @@ namespace Ink_Canvas.Helpers
|
||||
return builder.CreateStroke(points);
|
||||
}
|
||||
|
||||
private static global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing FindPrimaryDrawing(
|
||||
WinRtInkAnalyzer analyzer)
|
||||
internal static global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing FindPrimaryDrawing(
|
||||
global::Windows.UI.Input.Inking.Analysis.InkAnalyzer analyzer)
|
||||
{
|
||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing best = null;
|
||||
double bestArea = -1;
|
||||
@@ -169,7 +313,7 @@ namespace Ink_Canvas.Helpers
|
||||
return w * h;
|
||||
}
|
||||
|
||||
private static global::Windows.Foundation.Point[] CopyWinRtPoints(
|
||||
internal static global::Windows.Foundation.Point[] CopyWinRtPoints(
|
||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisInkDrawing drawing)
|
||||
{
|
||||
var src = drawing?.Points;
|
||||
@@ -186,7 +330,7 @@ namespace Ink_Canvas.Helpers
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static void BoundsFromPoints(
|
||||
internal static void BoundsFromPoints(
|
||||
System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points,
|
||||
out double w,
|
||||
out double h)
|
||||
@@ -211,7 +355,7 @@ namespace Ink_Canvas.Helpers
|
||||
h = Math.Max(0, maxY - minY);
|
||||
}
|
||||
|
||||
private static PointCollection ToWpfPointCollection(
|
||||
internal static PointCollection ToWpfPointCollection(
|
||||
System.Collections.Generic.IReadOnlyList<global::Windows.Foundation.Point> points)
|
||||
{
|
||||
var hot = new PointCollection();
|
||||
@@ -225,7 +369,7 @@ namespace Ink_Canvas.Helpers
|
||||
return hot;
|
||||
}
|
||||
|
||||
private static string MapDrawingKindToShapeName(
|
||||
internal static string MapDrawingKindToShapeName(
|
||||
global::Windows.UI.Input.Inking.Analysis.InkAnalysisDrawingKind kind)
|
||||
{
|
||||
switch (kind)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using H.NotifyIcon;
|
||||
using Microsoft.Toolkit.Uwp.Notifications;
|
||||
using System;
|
||||
using System.Windows;
|
||||
@@ -40,10 +40,9 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
taskbar.Visibility = Visibility.Visible;
|
||||
|
||||
taskbar.ShowBalloonTip(
|
||||
taskbar.ShowNotification(
|
||||
"InkCanvasForClass CE",
|
||||
$"发现新版本!:{version}",
|
||||
BalloonIcon.Info);
|
||||
$"发现新版本!:{version}");
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<RuntimeIdentifiers>win;win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>Ink_Canvas</RootNamespace>
|
||||
<AssemblyName>InkCanvasForClass</AssemblyName>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<TargetFramework>net6.0-windows10.0.19041.0</TargetFramework>
|
||||
<Nullable>disable</Nullable>
|
||||
<ImplicitUsings>disable</ImplicitUsings>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
@@ -25,8 +27,15 @@
|
||||
<BootstrapperEnabled>false</BootstrapperEnabled>
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<UseWPF>true</UseWPF>
|
||||
<UseWindowsForms>true</UseWindowsForms>
|
||||
<EnableWindowsTargeting>true</EnableWindowsTargeting>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>AnyCPU;x86;x64;ARM64</Platforms>
|
||||
<Platforms>AnyCPU;x86;x64</Platforms>
|
||||
<LangVersion>10</LangVersion>
|
||||
<NoWarn>$(NoWarn);CA1416;NU1701;MSB3270;CS8012;NETSDK1138</NoWarn>
|
||||
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
|
||||
<ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>None</ResolveAssemblyWarnOrErrorOnTargetArchitectureMismatch>
|
||||
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugType>embedded</DebugType>
|
||||
@@ -46,14 +55,12 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>embedded</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>embedded</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
@@ -71,14 +78,12 @@
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<PlatformTarget>ARM64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
@@ -86,7 +91,6 @@
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
@@ -94,40 +98,9 @@
|
||||
<OutputPath>bin\$(Configuration)\$(Platform)\</OutputPath>
|
||||
<DebugType>none</DebugType>
|
||||
<DebugSymbols>false</DebugSymbols>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<Prefer32Bit>false</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="IACore">
|
||||
<HintPath>.\IACore.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="IALoader">
|
||||
<HintPath>.\IALoader.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="IAWinFX">
|
||||
<HintPath>.\IAWinFX.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualBasic" />
|
||||
<Reference Include="netstandard" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="UIAutomationClient" />
|
||||
<Reference Include="UIAutomationTypes" />
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="WindowsFormsIntegration" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.manifest" />
|
||||
</ItemGroup>
|
||||
@@ -135,19 +108,23 @@
|
||||
<PackageReference Include="Costura.Fody" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="2.0.1" />
|
||||
<PackageReference Include="gong-wpf-dragdrop" Version="4.0.0" />
|
||||
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.0.131" />
|
||||
<PackageReference Include="iNKORE.UI.WPF.Modern" Version="0.10.2.1" />
|
||||
<PackageReference Include="iNKORE.UI.WPF" Version="1.2.8" />
|
||||
<PackageReference Include="MdXaml" Version="1.27.0" />
|
||||
<PackageReference Include="Microsoft.Office.Interop.PowerPoint" Version="15.0.4420.1018" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.Contracts" Version="10.0.19041.2" />
|
||||
<PackageReference Include="MicrosoftOfficeCore" Version="15.0.0" />
|
||||
<PackageReference Include="Microsoft.Toolkit.Uwp.Notifications" Version="7.1.3" />
|
||||
<PackageReference Include="Microsoft.International.Converters.PinYinConverter" Version="1.0.0" />
|
||||
<PackageReference Include="Sentry" Version="6.2.0" />
|
||||
<PackageReference Include="Sentry" Version="6.3.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" PrivateAssets="All" />
|
||||
<PackageReference Include="Microsoft.VisualBasic" Version="10.3.0" />
|
||||
<PackageReference Include="System.ComponentModel.Composition" Version="10.0.5" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" Version="10.0.5" />
|
||||
<PackageReference Include="System.Data.DataSetExtensions" Version="4.5.0" />
|
||||
<PackageReference Include="System.Management" Version="10.0.5" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
|
||||
<PackageReference Include="NHotkey.Wpf" Version="4.0.0" />
|
||||
<PackageReference Include="OSVersionExt" Version="4.1.0" />
|
||||
@@ -155,7 +132,17 @@
|
||||
<PackageReference Include="AForge.Video.DirectShow" Version="2.2.5" />
|
||||
<PackageReference Include="AForge.Imaging" Version="2.2.5" />
|
||||
<PackageReference Include="AForge.Math" Version="2.2.5" />
|
||||
<PackageReference Include="System.Private.Uri" Version="4.3.2" />
|
||||
<PackageReference Include="WebDav.Client" Version="2.9.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\InkCanvas.PluginSdk\InkCanvas.PluginSdk.csproj" />
|
||||
<ProjectReference Include="..\InkCanvas.Controls\InkCanvas.Controls.csproj" />
|
||||
<ProjectReference Include="..\InkCanvas.IACoreHelper\InkCanvas.IACoreHelper.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
<Private>false</Private>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(MSBuildRuntimeType)' == 'Full'">
|
||||
<COMReference Include="IWshRuntimeLibrary">
|
||||
@@ -197,8 +184,6 @@
|
||||
<EmbeddedResource Include="Resources\IACore\IACore.dll" />
|
||||
<EmbeddedResource Include="Resources\IACore\IALoader.dll" />
|
||||
<EmbeddedResource Include="Resources\IACore\IAWinFX.dll" />
|
||||
<EmbeddedResource Include="UIAccessDLL_x64.dll" />
|
||||
<EmbeddedResource Include="UIAccessDLL_x86.dll" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Cursors\Cursor.cur" />
|
||||
@@ -563,14 +548,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Remove="AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="Properties\Strings.en-US.resx" />
|
||||
<EmbeddedResource Remove="**\Strings.en-US.resx" />
|
||||
<None Include="Properties\Strings.en-US.resx" />
|
||||
<EmbeddedResource Include="Properties\Strings.enUS.xml">
|
||||
<LogicalName>Ink_Canvas.Properties.Strings.enUS.xml</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="MainWindow.xaml~RF6c3144.TMP" />
|
||||
<None Remove="Resources\Cursors\Cursor.cur" />
|
||||
@@ -636,6 +614,12 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Fonts\LXGWWenKaiTC-Regular.ttf" />
|
||||
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Black.ttf" />
|
||||
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Bold.ttf" />
|
||||
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Light.ttf" />
|
||||
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Medium.ttf" />
|
||||
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Regular.ttf" />
|
||||
<Resource Include="Resources\Fonts\HarmonyOS_SansSC_Thin.ttf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\HiteAnnotation.png" />
|
||||
@@ -680,4 +664,11 @@
|
||||
<Target Name="CleanTelemetryDsn" AfterTargets="Build;Clean" Condition="Exists('$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt')">
|
||||
<Delete Files="$(MSBuildProjectDirectory)\telemetry_dsn.generated.txt" />
|
||||
</Target>
|
||||
|
||||
<Target Name="CopyIACoreHelper" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<IACoreHelperFiles Include="$(MSBuildProjectDirectory)\..\InkCanvas.IACoreHelper\bin\$(Configuration)\*.*" />
|
||||
</ItemGroup>
|
||||
<Copy SourceFiles="@(IACoreHelperFiles)" DestinationFolder="$(OutDir)" SkipUnchangedFiles="true" Condition="'@(IACoreHelperFiles)' != ''" />
|
||||
</Target>
|
||||
</Project>
|
||||
|
||||
+1251
-8584
File diff suppressed because it is too large
Load Diff
+342
-1771
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,6 @@ using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
@@ -64,7 +63,7 @@ namespace Ink_Canvas
|
||||
/// 处理折叠浮动栏的鼠标点击事件。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <param name="e">鼠标按钮事件参数。</param>
|
||||
/// <param name="e">路由事件参数。</param>
|
||||
public async void FoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
await FoldFloatingBar(sender);
|
||||
@@ -91,19 +90,6 @@ namespace Ink_Canvas
|
||||
/// </remarks>
|
||||
public async Task FoldFloatingBar(object sender, bool isAutoFoldCommand = false)
|
||||
{
|
||||
var isShouldRejectAction = false;
|
||||
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == Fold_Icon && lastBorderMouseDownObject != Fold_Icon) isShouldRejectAction = true;
|
||||
});
|
||||
|
||||
if (isShouldRejectAction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// FloatingBarIcons_MouseUp_New(sender);
|
||||
if (sender == null)
|
||||
@@ -338,7 +324,7 @@ namespace Ink_Canvas
|
||||
/// 处理展开浮动栏的鼠标点击事件。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <param name="e">鼠标按钮事件参数。</param>
|
||||
/// <param name="e">路由事件参数。</param>
|
||||
public async void UnFoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
await UnFoldFloatingBar(sender);
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
using IWshRuntimeLibrary;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Application = System.Windows.Forms.Application;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建开机自启动快捷方式。
|
||||
/// </summary>
|
||||
/// <param name="exeName">可执行文件名,用于命名快捷方式。</param>
|
||||
/// <returns>创建成功返回true,失败返回false。</returns>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 创建Windows Shell对象
|
||||
/// 2. 在启动文件夹中创建快捷方式
|
||||
/// 3. 设置快捷方式的目标路径为当前可执行文件路径
|
||||
/// 4. 设置工作目录为当前目录
|
||||
/// 5. 设置窗口样式为普通窗口
|
||||
/// 6. 设置快捷方式描述
|
||||
/// 7. 保存快捷方式
|
||||
/// 8. 捕获可能的异常,确保方法不会因异常而崩溃
|
||||
/// </remarks>
|
||||
public static bool StartAutomaticallyCreate(string exeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var shell = new WshShell();
|
||||
var shortcut = (IWshShortcut)shell.CreateShortcut(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName + ".lnk");
|
||||
//设置快捷方式的目标所在的位置(源程序完整路径)
|
||||
shortcut.TargetPath = Application.ExecutablePath;
|
||||
//应用程序的工作目录
|
||||
//当用户没有指定一个具体的目录时,快捷方式的目标应用程序将使用该属性所指定的目录来装载或保存文件。
|
||||
shortcut.WorkingDirectory = Environment.CurrentDirectory;
|
||||
//目标应用程序窗口类型(1.Normal window普通窗口,3.Maximized最大化窗口,7.Minimized最小化)
|
||||
shortcut.WindowStyle = 1;
|
||||
//快捷方式的描述
|
||||
shortcut.Description = exeName + "_Ink";
|
||||
//设置快捷键(如果有必要的话.)
|
||||
//shortcut.Hotkey = "CTRL+ALT+D";
|
||||
shortcut.Save();
|
||||
return true;
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除开机自启动快捷方式。
|
||||
/// </summary>
|
||||
/// <param name="exeName">可执行文件名,用于定位要删除的快捷方式。</param>
|
||||
/// <returns>删除成功返回true,失败返回false。</returns>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 在启动文件夹中删除指定名称的快捷方式
|
||||
/// 2. 捕获可能的异常,确保方法不会因异常而崩溃
|
||||
/// </remarks>
|
||||
public static bool StartAutomaticallyDel(string exeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName +
|
||||
".lnk");
|
||||
return true;
|
||||
}
|
||||
catch (Exception) { }
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
@@ -14,19 +15,18 @@ namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 浮动栏前景色,根据当前主题动态更新。
|
||||
/// </summary>
|
||||
private const string ThemeLight = "Light";
|
||||
private const string ThemeDark = "Dark";
|
||||
private const string LightThemePath = "Resources/Styles/Light.xaml";
|
||||
private const string DarkThemePath = "Resources/Styles/Dark.xaml";
|
||||
private const string DrawShapeImagePath = "Resources/DrawShapeImageDictionary.xaml";
|
||||
private const string SeewoImagePath = "Resources/SeewoImageDictionary.xaml";
|
||||
private const string IconImagePath = "Resources/IconImageDictionary.xaml";
|
||||
|
||||
private Color FloatBarForegroundColor;
|
||||
|
||||
/// <summary>
|
||||
/// 应用并切换到指定的主题("Light" 或 "Dark"),更新主题资源并刷新相关 UI 元素以反映主题变化。
|
||||
/// </summary>
|
||||
/// <param name="theme">主题标识,支持 "Light" 或 "Dark"(区分大小写)。</param>
|
||||
/// <param name="autoSwitchIcon">若为 true,则根据主题自动切换并保存浮动工具栏的图标设置。</param>
|
||||
private void SetTheme(string theme, bool autoSwitchIcon = false)
|
||||
{
|
||||
// 清理现有的主题资源
|
||||
var resourcesToRemove = new List<ResourceDictionary>();
|
||||
foreach (var dict in Application.Current.Resources.MergedDictionaries)
|
||||
{
|
||||
@@ -43,195 +43,85 @@ namespace Ink_Canvas
|
||||
Application.Current.Resources.MergedDictionaries.Remove(dict);
|
||||
}
|
||||
|
||||
if (theme == "Light")
|
||||
var isLightTheme = theme == ThemeLight;
|
||||
var themePath = isLightTheme ? LightThemePath : DarkThemePath;
|
||||
var elementTheme = isLightTheme ? ElementTheme.Light : ElementTheme.Dark;
|
||||
|
||||
var rd1 = new ResourceDictionary { Source = new Uri(themePath, UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd1);
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
// 先加载主题
|
||||
var rd1 = new ResourceDictionary
|
||||
await Task.Delay(100);
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
Source = new Uri("Resources/Styles/Light.xaml", UriKind.Relative)
|
||||
};
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd1);
|
||||
|
||||
// 异步加载图形资源,避免阻塞启动
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(100);
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
var rd2 = new ResourceDictionary
|
||||
{
|
||||
Source = new Uri("Resources/DrawShapeImageDictionary.xaml", UriKind.Relative)
|
||||
};
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd2);
|
||||
|
||||
var rd3 = new ResourceDictionary
|
||||
{
|
||||
Source = new Uri("Resources/SeewoImageDictionary.xaml", UriKind.Relative)
|
||||
};
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd3);
|
||||
|
||||
var rd4 = new ResourceDictionary
|
||||
{
|
||||
Source = new Uri("Resources/IconImageDictionary.xaml", UriKind.Relative)
|
||||
};
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd4);
|
||||
});
|
||||
LoadImageResourceDictionary(DrawShapeImagePath);
|
||||
LoadImageResourceDictionary(SeewoImagePath);
|
||||
LoadImageResourceDictionary(IconImagePath);
|
||||
});
|
||||
});
|
||||
|
||||
ThemeManager.SetRequestedTheme(window, ElementTheme.Light);
|
||||
ThemeManager.SetRequestedTheme(window, elementTheme);
|
||||
|
||||
InitializeFloatBarForegroundColor();
|
||||
InitializeFloatBarForegroundColor();
|
||||
RefreshQuickPanelIcons();
|
||||
RefreshStrokeSelectionIcons();
|
||||
RefreshImageSelectionIcons();
|
||||
RefreshGestureButtonIcon();
|
||||
RefreshFloatingBarHighlightColors();
|
||||
|
||||
// 刷新快速面板图标
|
||||
RefreshQuickPanelIcons();
|
||||
|
||||
// 刷新墨迹选中栏图标
|
||||
RefreshStrokeSelectionIcons();
|
||||
|
||||
// 刷新图片选中栏图标
|
||||
RefreshImageSelectionIcons();
|
||||
|
||||
// 刷新手势按钮图标
|
||||
RefreshGestureButtonIcon();
|
||||
|
||||
RefreshFloatingBarHighlightColors();
|
||||
|
||||
if (autoSwitchIcon)
|
||||
{
|
||||
AutoSwitchFloatingBarIconForTheme("Light");
|
||||
}
|
||||
|
||||
// 强制刷新UI
|
||||
window.InvalidateVisual();
|
||||
|
||||
// 通知其他窗口刷新主题
|
||||
RefreshOtherWindowsTheme();
|
||||
}
|
||||
else if (theme == "Dark")
|
||||
if (autoSwitchIcon)
|
||||
{
|
||||
// 先加载主题
|
||||
var rd1 = new ResourceDictionary { Source = new Uri("Resources/Styles/Dark.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd1);
|
||||
|
||||
// 异步加载图形资源,避免阻塞启动
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(100);
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
var rd2 = new ResourceDictionary
|
||||
{
|
||||
Source = new Uri("Resources/DrawShapeImageDictionary.xaml", UriKind.Relative)
|
||||
};
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd2);
|
||||
|
||||
var rd3 = new ResourceDictionary
|
||||
{
|
||||
Source = new Uri("Resources/SeewoImageDictionary.xaml", UriKind.Relative)
|
||||
};
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd3);
|
||||
|
||||
var rd4 = new ResourceDictionary
|
||||
{
|
||||
Source = new Uri("Resources/IconImageDictionary.xaml", UriKind.Relative)
|
||||
};
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd4);
|
||||
});
|
||||
});
|
||||
|
||||
ThemeManager.SetRequestedTheme(window, ElementTheme.Dark);
|
||||
|
||||
InitializeFloatBarForegroundColor();
|
||||
|
||||
// 刷新快速面板图标
|
||||
RefreshQuickPanelIcons();
|
||||
|
||||
// 刷新墨迹选中栏图标
|
||||
RefreshStrokeSelectionIcons();
|
||||
|
||||
// 刷新图片选中栏图标
|
||||
RefreshImageSelectionIcons();
|
||||
|
||||
// 刷新手势按钮图标
|
||||
RefreshGestureButtonIcon();
|
||||
|
||||
RefreshFloatingBarHighlightColors();
|
||||
|
||||
if (autoSwitchIcon)
|
||||
{
|
||||
AutoSwitchFloatingBarIconForTheme("Dark");
|
||||
}
|
||||
|
||||
// 强制刷新UI
|
||||
window.InvalidateVisual();
|
||||
|
||||
// 通知其他窗口刷新主题
|
||||
RefreshOtherWindowsTheme();
|
||||
AutoSwitchFloatingBarIconForTheme(theme);
|
||||
}
|
||||
|
||||
window.InvalidateVisual();
|
||||
RefreshOtherWindowsTheme();
|
||||
}
|
||||
|
||||
void LoadImageResourceDictionary(string path)
|
||||
{
|
||||
var rd = new ResourceDictionary { Source = new Uri(path, UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化FloatBarForegroundColor,从当前主题资源中加载颜色
|
||||
/// </summary>
|
||||
private void InitializeFloatBarForegroundColor()
|
||||
{
|
||||
try
|
||||
{
|
||||
FloatBarForegroundColor = (Color)Application.Current.FindResource("FloatBarForegroundColor");
|
||||
|
||||
// 强制刷新浮动工具栏按钮颜色
|
||||
RefreshFloatingBarButtonColors();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 如果无法从资源中加载,使用默认颜色
|
||||
FloatBarForegroundColor = Color.FromRgb(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新快速面板图标
|
||||
/// </summary>
|
||||
private void RefreshQuickPanelIcons()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (LeftUnFoldButtonQuickPanel != null)
|
||||
{
|
||||
LeftUnFoldButtonQuickPanel.InvalidateVisual();
|
||||
}
|
||||
if (RightUnFoldButtonQuickPanel != null)
|
||||
{
|
||||
RightUnFoldButtonQuickPanel.InvalidateVisual();
|
||||
}
|
||||
if (LeftSidePanel != null)
|
||||
{
|
||||
LeftSidePanel.InvalidateVisual();
|
||||
}
|
||||
if (RightSidePanel != null)
|
||||
{
|
||||
RightSidePanel.InvalidateVisual();
|
||||
}
|
||||
LeftUnFoldButtonQuickPanel?.InvalidateVisual();
|
||||
RightUnFoldButtonQuickPanel?.InvalidateVisual();
|
||||
LeftSidePanel?.InvalidateVisual();
|
||||
RightSidePanel?.InvalidateVisual();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新浮动栏高光条颜色
|
||||
/// </summary>
|
||||
private void RefreshFloatingBarHighlightColors()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (FloatingbarSelectionBG != null && FloatingbarSelectionBG.Visibility == Visibility.Visible)
|
||||
{
|
||||
// 根据主题设置高光颜色
|
||||
bool isDarkTheme = IsCurrentThemeDark();
|
||||
|
||||
Color highlightBackgroundColor;
|
||||
Color highlightBarColor;
|
||||
bool isDarkTheme = Settings.Appearance.Theme == 1 ||
|
||||
(Settings.Appearance.Theme == 2 && !IsSystemThemeLight());
|
||||
|
||||
if (isDarkTheme)
|
||||
{
|
||||
@@ -244,7 +134,6 @@ namespace Ink_Canvas
|
||||
highlightBarColor = Color.FromRgb(37, 99, 235);
|
||||
}
|
||||
|
||||
// 设置高光背景颜色
|
||||
FloatingbarSelectionBG.Background = new SolidColorBrush(highlightBackgroundColor);
|
||||
if (FloatingbarSelectionBG.Child is System.Windows.Controls.Canvas canvas && canvas.Children.Count > 0)
|
||||
{
|
||||
@@ -261,73 +150,67 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新浮动工具栏按钮颜色
|
||||
/// </summary>
|
||||
private bool IsCurrentThemeDark()
|
||||
{
|
||||
return Settings.Appearance.Theme == 1 ||
|
||||
(Settings.Appearance.Theme == 2 && !ThemeHelper.IsSystemThemeLight());
|
||||
}
|
||||
|
||||
private void RefreshFloatingBarButtonColors()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 根据主题选择高光颜色
|
||||
Color selectedColor;
|
||||
bool isDarkTheme = Settings.Appearance.Theme == 1 ||
|
||||
(Settings.Appearance.Theme == 2 && !IsSystemThemeLight());
|
||||
SymbolIconDelete.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.DeleteIcon);
|
||||
ShapeDrawFloatingBarBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ShapesIcon);
|
||||
SymbolIconUndo.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.UndoIcon);
|
||||
SymbolIconRedo.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.RedoIcon);
|
||||
CursorWithDelFloatingBarBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.CursorWithDelFloatingBarBtnIcon);
|
||||
WhiteboardFloatingBarBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.WhiteboardFloatingBarBtnIcon);
|
||||
ToolsFloatingBarBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ToolsFloatingBarBtnIcon);
|
||||
Fold_Icon.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.FoldIcon);
|
||||
|
||||
if (isDarkTheme)
|
||||
{
|
||||
selectedColor = Color.FromRgb(102, 204, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedColor = Color.FromRgb(30, 58, 138);
|
||||
}
|
||||
TimerToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.TimerIconGeometry);
|
||||
RandomDrawToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.RandomDrawIconGeometry);
|
||||
SingleDrawToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SingleDrawIconGeometry);
|
||||
SaveToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SaveIconGeometry);
|
||||
OpenToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.OpenIconGeometry);
|
||||
ReplayToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ReplayIconGeometry);
|
||||
ScreenshotToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ScreenshotIconGeometry);
|
||||
ManualToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ManualIconGeometry);
|
||||
SettingsToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SettingsIconGeometry);
|
||||
|
||||
BoardTimerToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.TimerIconGeometry);
|
||||
BoardRandomDrawToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.RandomDrawIconGeometry);
|
||||
BoardSingleDrawToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SingleDrawIconGeometry);
|
||||
BoardSaveToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SaveIconGeometry);
|
||||
BoardOpenToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.OpenIconGeometry);
|
||||
BoardReplayToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ReplayIconGeometry);
|
||||
BoardScreenshotToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ScreenshotIconGeometry);
|
||||
BoardManualToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.ManualIconGeometry);
|
||||
BoardSettingsToolBtn.Icon.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.SettingsIconGeometry);
|
||||
|
||||
bool isDarkTheme = IsCurrentThemeDark();
|
||||
Color selectedColor = isDarkTheme ? Color.FromRgb(102, 204, 255) : Color.FromRgb(30, 58, 138);
|
||||
|
||||
SetAllFloatingBarButtonsToColor(FloatBarForegroundColor);
|
||||
|
||||
// 根据当前模式设置按钮颜色
|
||||
switch (_currentToolMode)
|
||||
{
|
||||
case "cursor":
|
||||
CursorIconGeometry.Brush = new SolidColorBrush(selectedColor);
|
||||
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
Cursor_Icon.Icon.Brush = new SolidColorBrush(selectedColor);
|
||||
break;
|
||||
case "pen":
|
||||
case "color":
|
||||
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
PenIconGeometry.Brush = new SolidColorBrush(selectedColor);
|
||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
Pen_Icon.Icon.Brush = new SolidColorBrush(selectedColor);
|
||||
break;
|
||||
case "eraser":
|
||||
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(selectedColor);
|
||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
Eraser_Icon.Icon.Brush = new SolidColorBrush(selectedColor);
|
||||
break;
|
||||
case "eraserByStrokes":
|
||||
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(selectedColor);
|
||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
EraserByStrokes_Icon.Icon.Brush = new SolidColorBrush(selectedColor);
|
||||
break;
|
||||
case "select":
|
||||
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(selectedColor);
|
||||
break;
|
||||
default:
|
||||
// 默认情况,所有按钮都使用主题颜色
|
||||
CursorIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
PenIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
StrokeEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
CircleEraserIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
LassoSelectIconGeometry.Brush = new SolidColorBrush(FloatBarForegroundColor);
|
||||
SymbolIconSelect.Icon.Brush = new SolidColorBrush(selectedColor);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -336,79 +219,46 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理系统主题偏好变化事件,根据当前设置更新应用主题。
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者。</param>
|
||||
/// <param name="e">用户偏好变化事件参数。</param>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 根据当前主题设置(Settings.Appearance.Theme)决定使用哪种主题
|
||||
/// 2. 如果设置为0(浅色主题),则设置为Light主题
|
||||
/// 3. 如果设置为1(深色主题),则设置为Dark主题
|
||||
/// 4. 如果设置为2(跟随系统主题),则根据系统主题设置应用相应的主题
|
||||
/// </remarks>
|
||||
void SetAllFloatingBarButtonsToColor(Color color)
|
||||
{
|
||||
var brush = new SolidColorBrush(color);
|
||||
Cursor_Icon.Icon.Brush = brush;
|
||||
Pen_Icon.Icon.Brush = brush;
|
||||
EraserByStrokes_Icon.Icon.Brush = brush;
|
||||
Eraser_Icon.Icon.Brush = brush;
|
||||
SymbolIconSelect.Icon.Brush = brush;
|
||||
ShapeDrawFloatingBarBtn.Icon.Brush = brush;
|
||||
SymbolIconUndo.Icon.Brush = brush;
|
||||
SymbolIconRedo.Icon.Brush = brush;
|
||||
CursorWithDelFloatingBarBtn.Icon.Brush = brush;
|
||||
WhiteboardFloatingBarBtn.Icon.Brush = brush;
|
||||
ToolsFloatingBarBtn.Icon.Brush = brush;
|
||||
Fold_Icon.Icon.Brush = brush;
|
||||
}
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
switch (Settings.Appearance.Theme)
|
||||
{
|
||||
case 0:
|
||||
SetTheme("Light");
|
||||
SetTheme(ThemeLight);
|
||||
break;
|
||||
case 1:
|
||||
SetTheme("Dark");
|
||||
SetTheme(ThemeDark);
|
||||
break;
|
||||
case 2:
|
||||
if (IsSystemThemeLight()) SetTheme("Light");
|
||||
else SetTheme("Dark");
|
||||
// 与 IsCurrentThemeDark / GetEffectiveTheme / 浮动栏一致,统一读 AppsUseLightTheme,
|
||||
// 否则 SystemUsesLightTheme 与 AppsUseLightTheme 可独立取值时主题会混搭
|
||||
SetTheme(ThemeHelper.IsSystemThemeLight() ? ThemeLight : ThemeDark);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查系统主题是否为浅色主题。
|
||||
/// </summary>
|
||||
/// <returns>系统主题为浅色返回true,深色返回false。</returns>
|
||||
/// <remarks>
|
||||
/// 操作包括:
|
||||
/// 1. 从注册表中读取系统主题设置
|
||||
/// 2. 检查"SystemUsesLightTheme"键的值
|
||||
/// 3. 如果值为1,则表示系统使用浅色主题
|
||||
/// 4. 捕获可能的异常,确保方法不会因异常而崩溃
|
||||
/// </remarks>
|
||||
private bool IsSystemThemeLight()
|
||||
{
|
||||
var light = false;
|
||||
try
|
||||
{
|
||||
var registryKey = Registry.CurrentUser;
|
||||
var themeKey =
|
||||
registryKey.OpenSubKey("software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
|
||||
var keyValue = 0;
|
||||
if (themeKey != null) keyValue = (int)themeKey.GetValue("SystemUsesLightTheme");
|
||||
if (keyValue == 1) light = true;
|
||||
}
|
||||
catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex); }
|
||||
|
||||
return light;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据主题自动切换浮动栏图标
|
||||
/// </summary>
|
||||
private void AutoSwitchFloatingBarIconForTheme(string theme)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (theme == "Light")
|
||||
{
|
||||
Settings.Appearance.FloatingBarImg = 0;
|
||||
}
|
||||
else if (theme == "Dark")
|
||||
{
|
||||
Settings.Appearance.FloatingBarImg = 3;
|
||||
}
|
||||
|
||||
Settings.Appearance.FloatingBarImg = theme == ThemeLight ? 0 : 3;
|
||||
UpdateFloatingBarIcon();
|
||||
UpdateFloatingBarIconComboBox();
|
||||
}
|
||||
@@ -417,111 +267,49 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新设置界面中的浮动栏图标选择下拉框显示
|
||||
/// </summary>
|
||||
private void UpdateFloatingBarIconComboBox()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ComboBoxFloatingBarImg != null)
|
||||
{
|
||||
ComboBoxFloatingBarImg.SelectedIndex = Settings.Appearance.FloatingBarImg;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新墨迹选中栏图标
|
||||
/// </summary>
|
||||
private void RefreshStrokeSelectionIcons()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (BorderStrokeSelectionControl != null)
|
||||
{
|
||||
// 强制刷新墨迹选中栏的视觉状态
|
||||
BorderStrokeSelectionControl.InvalidateVisual();
|
||||
|
||||
// 刷新墨迹选中栏内的所有图标
|
||||
var viewbox = BorderStrokeSelectionControl.Child as Viewbox;
|
||||
if (viewbox?.Child is ui.SimpleStackPanel stackPanel)
|
||||
{
|
||||
RefreshStrokeSelectionIconsRecursive(stackPanel);
|
||||
RefreshIconsRecursive(stackPanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 忽略异常,确保主题切换不会因为图标刷新失败而中断
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归刷新墨迹选中栏内的图标
|
||||
/// </summary>
|
||||
private void RefreshStrokeSelectionIconsRecursive(System.Windows.Controls.Panel panel)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var child in panel.Children)
|
||||
{
|
||||
if (child is Image image)
|
||||
{
|
||||
// 强制刷新图像
|
||||
image.InvalidateVisual();
|
||||
}
|
||||
else if (child is System.Windows.Controls.Panel childPanel)
|
||||
{
|
||||
// 递归处理子面板
|
||||
RefreshStrokeSelectionIconsRecursive(childPanel);
|
||||
}
|
||||
else if (child is Border border && border.Child is System.Windows.Controls.Panel borderPanel)
|
||||
{
|
||||
// 处理Border内的面板
|
||||
RefreshStrokeSelectionIconsRecursive(borderPanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 忽略异常
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新图片选中栏图标
|
||||
/// </summary>
|
||||
private void RefreshImageSelectionIcons()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (BorderImageSelectionControl != null)
|
||||
{
|
||||
// 强制刷新图片选中栏的视觉状态
|
||||
BorderImageSelectionControl.InvalidateVisual();
|
||||
|
||||
// 刷新图片选中栏内的所有图标
|
||||
var viewbox = BorderImageSelectionControl.Child as Viewbox;
|
||||
if (viewbox?.Child is ui.SimpleStackPanel stackPanel)
|
||||
{
|
||||
RefreshImageSelectionIconsRecursive(stackPanel);
|
||||
RefreshIconsRecursive(stackPanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 忽略异常,确保主题切换不会因为图标刷新失败而中断
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归刷新图片选中栏内的图标
|
||||
/// </summary>
|
||||
private void RefreshImageSelectionIconsRecursive(System.Windows.Controls.Panel panel)
|
||||
private void RefreshIconsRecursive(System.Windows.Controls.Panel panel)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -529,22 +317,18 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (child is Image image)
|
||||
{
|
||||
// 强制刷新图像
|
||||
image.InvalidateVisual();
|
||||
}
|
||||
else if (child is System.Windows.Controls.Panel childPanel)
|
||||
{
|
||||
// 递归处理子面板
|
||||
RefreshImageSelectionIconsRecursive(childPanel);
|
||||
RefreshIconsRecursive(childPanel);
|
||||
}
|
||||
else if (child is Border border && border.Child is System.Windows.Controls.Panel borderPanel)
|
||||
{
|
||||
// 处理Border内的面板
|
||||
RefreshImageSelectionIconsRecursive(borderPanel);
|
||||
RefreshIconsRecursive(borderPanel);
|
||||
}
|
||||
else if (child is Grid grid)
|
||||
{
|
||||
// 处理Grid内的子元素
|
||||
foreach (var gridChild in grid.Children)
|
||||
{
|
||||
if (gridChild is Image gridImage)
|
||||
@@ -557,18 +341,13 @@ namespace Ink_Canvas
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 忽略异常
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新手势按钮图标
|
||||
/// </summary>
|
||||
private void RefreshGestureButtonIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 调用手势按钮颜色和图标更新方法,该方法会根据当前主题和手势状态设置正确的图标
|
||||
CheckEnableTwoFingerGestureBtnColorPrompt();
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -576,14 +355,10 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新其他窗口的主题
|
||||
/// </summary>
|
||||
private void RefreshOtherWindowsTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 刷新所有打开的窗口
|
||||
foreach (Window window in Application.Current.Windows)
|
||||
{
|
||||
if (window is CountdownTimerWindow timerWindow)
|
||||
@@ -598,22 +373,18 @@ namespace Ink_Canvas
|
||||
{
|
||||
operatingGuideWindow.RefreshTheme();
|
||||
}
|
||||
else if (window is Windows.SettingsViews.SettingsWindow settingsWindow)
|
||||
{
|
||||
settingsWindow.RefreshTheme();
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新计时器控件
|
||||
if (TimerControl != null)
|
||||
{
|
||||
TimerControl.RefreshTheme();
|
||||
}
|
||||
|
||||
if (MinimizedTimerControl != null)
|
||||
{
|
||||
MinimizedTimerControl.RefreshTheme();
|
||||
}
|
||||
TimerControl?.RefreshTheme();
|
||||
MinimizedTimerControl?.RefreshTheme();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ink_Canvas.Controls;
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -5,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;
|
||||
|
||||
@@ -81,7 +83,7 @@ namespace Ink_Canvas
|
||||
var missingElements = 0;
|
||||
foreach (UIElement child in inkCanvas.Children)
|
||||
{
|
||||
if (child is Image || child is MediaElement)
|
||||
if (child is Image || child is MediaElement || child is PdfEmbeddedView)
|
||||
{
|
||||
if (child is Image img && img.Tag is string tag && tag == VideoPresenterLiveFrameTag)
|
||||
{
|
||||
@@ -225,6 +227,7 @@ namespace Ink_Canvas
|
||||
if (TimeMachineHistories[targetIndex] == null)
|
||||
{
|
||||
timeMachine.ClearStrokeHistory();
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -281,7 +284,11 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
private void ProcessElementsAfterRestore(List<UIElement> elements)
|
||||
{
|
||||
if (elements == null || elements.Count == 0) return;
|
||||
if (elements == null || elements.Count == 0)
|
||||
{
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用低优先级异步处理,让 UI 先响应,图片位置和事件绑定稍后完成
|
||||
Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
|
||||
@@ -310,7 +317,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
BindElementEvents(media);
|
||||
}
|
||||
else if (element is PdfEmbeddedView pdf)
|
||||
{
|
||||
double left = InkCanvas.GetLeft(pdf);
|
||||
double top = InkCanvas.GetTop(pdf);
|
||||
if (double.IsNaN(left) || double.IsNaN(top))
|
||||
{
|
||||
CenterAndScaleElement(pdf);
|
||||
}
|
||||
BindElementEvents(pdf);
|
||||
}
|
||||
}
|
||||
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -408,7 +427,10 @@ namespace Ink_Canvas
|
||||
/// <remarks>
|
||||
/// 该方法在切换前会取消当前选中元素(同时保留并恢复编辑模式)、调用视频呈现器的离开页前钩子、保存当前页的笔迹与元素、清空画布;切换到前一页后恢复该页内容、调用视频呈现器的页已更改钩子并刷新页面索引显示。
|
||||
/// </remarks>
|
||||
private void BtnWhiteBoardSwitchPrevious_Click(object sender, EventArgs 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;
|
||||
|
||||
@@ -440,7 +462,10 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
/// <param name="sender">触发事件的源对象(通常为按钮)。</param>
|
||||
/// <param name="e">事件参数。</param>
|
||||
private void BtnWhiteBoardSwitchNext_Click(object sender, EventArgs 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 &&
|
||||
@@ -487,7 +512,10 @@ namespace Ink_Canvas
|
||||
/// - 将当前页面的历史保存到时间轴并清空画布,然后在白板集合中插入一个空白页面(其历史为 null),随后恢复该页面并触发页面变更回调。
|
||||
/// - 更新页码显示并在达到上限时禁用添加按钮;若侧边页列表可见,则刷新该列表。
|
||||
/// </remarks>
|
||||
private void BtnWhiteBoardAdd_Click(object sender, EventArgs 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 &&
|
||||
@@ -634,8 +662,8 @@ namespace Ink_Canvas
|
||||
bool isMaxPage = WhiteboardTotalCount >= 99;
|
||||
|
||||
// 设置按钮文本
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
||||
BtnRightWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
||||
BtnLeftWhiteBoardSwitchNext.LabelTextBlockControl.Text = isLastPage ? "新页面" : "下一页";
|
||||
BtnRightWhiteBoardSwitchNext.LabelTextBlockControl.Text = isLastPage ? "新页面" : "下一页";
|
||||
|
||||
if (isLastPage)
|
||||
{
|
||||
@@ -652,11 +680,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;
|
||||
|
||||
@@ -666,21 +694,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;
|
||||
|
||||
@@ -3,10 +3,8 @@ using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -29,594 +27,159 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 创建背景选项面板(如果不存在)
|
||||
if (BackgroundPalette == null)
|
||||
if (BackgroundPalette.Visibility == Visibility.Visible)
|
||||
{
|
||||
CreateBackgroundPalette();
|
||||
}
|
||||
|
||||
// 显示或隐藏背景选项面板
|
||||
if (BackgroundPalette != null)
|
||||
{
|
||||
if (BackgroundPalette.Visibility == Visibility.Visible)
|
||||
{
|
||||
// 如果面板已经显示,则隐藏它
|
||||
AnimationsHelper.HideWithSlideAndFade(BackgroundPalette);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 隐藏其他可能显示的面板
|
||||
AnimationsHelper.HideWithSlideAndFade(EraserSizePanel);
|
||||
AnimationsHelper.HideWithSlideAndFade(BorderTools);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
|
||||
AnimationsHelper.HideWithSlideAndFade(PenPalette);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardPenPalette);
|
||||
AnimationsHelper.HideWithSlideAndFade(BorderDrawShape);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderDrawShape);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardEraserSizePanel);
|
||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
|
||||
|
||||
// 显示背景选项面板
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BackgroundPalette);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 原有的背景切换代码
|
||||
Settings.Canvas.UsingWhiteboard = !Settings.Canvas.UsingWhiteboard;
|
||||
SaveSettingsToFile();
|
||||
if (Settings.Canvas.UsingWhiteboard)
|
||||
{
|
||||
if (inkColor == 5) lastBoardInkColor = 0;
|
||||
ICCWaterMarkDark.Visibility = Visibility.Visible;
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 设置为白板默认背景色
|
||||
Color defaultWhiteboardColor = Color.FromRgb(255, 255, 255);
|
||||
|
||||
if (currentMode == 1) // 白板模式
|
||||
{
|
||||
// 设置背景为默认白板背景色
|
||||
GridBackgroundCover.Background = new SolidColorBrush(defaultWhiteboardColor);
|
||||
|
||||
// 更新RGB滑块的值为默认白板背景色
|
||||
if (BackgroundPalette != null && BackgroundPalette.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateRGBSliders(defaultWhiteboardColor);
|
||||
}
|
||||
|
||||
// 更新自定义背景色为默认白板背景色
|
||||
CustomBackgroundColor = defaultWhiteboardColor;
|
||||
|
||||
// 保存到设置
|
||||
string colorHex = $"#{defaultWhiteboardColor.R:X2}{defaultWhiteboardColor.G:X2}{defaultWhiteboardColor.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 设置墨迹颜色为黑色
|
||||
CheckLastColor(0);
|
||||
forceEraser = false;
|
||||
AnimationsHelper.HideWithSlideAndFade(BackgroundPalette);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inkColor == 0) lastBoardInkColor = 5;
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Visible;
|
||||
ICCWaterMarkDark.Visibility = Visibility.Collapsed;
|
||||
AnimationsHelper.HideWithSlideAndFade(EraserSizePanel);
|
||||
AnimationsHelper.HidePopupWithSlideAndFade(BorderTools);
|
||||
AnimationsHelper.HidePopupWithSlideAndFade(BoardBorderToolsPopup);
|
||||
AnimationsHelper.HideWithSlideAndFade(PenPalette);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardPenPalette);
|
||||
AnimationsHelper.HideWithSlideAndFade(BorderDrawShape);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderDrawShape);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardEraserSizePanel);
|
||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
|
||||
|
||||
// 设置为黑板默认背景色
|
||||
Color defaultBlackboardColor = Color.FromRgb(22, 41, 36);
|
||||
|
||||
if (currentMode == 1) // 黑板模式
|
||||
{
|
||||
// 设置背景为默认黑板背景色
|
||||
GridBackgroundCover.Background = new SolidColorBrush(defaultBlackboardColor);
|
||||
|
||||
// 更新RGB滑块的值为默认黑板背景色
|
||||
if (BackgroundPalette != null && BackgroundPalette.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateRGBSliders(defaultBlackboardColor);
|
||||
}
|
||||
|
||||
// 更新自定义背景色为默认黑板背景色
|
||||
CustomBackgroundColor = defaultBlackboardColor;
|
||||
|
||||
// 保存到设置
|
||||
string colorHex = $"#{defaultBlackboardColor.R:X2}{defaultBlackboardColor.G:X2}{defaultBlackboardColor.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 设置墨迹颜色为白色
|
||||
CheckLastColor(5);
|
||||
forceEraser = false;
|
||||
LoadCustomBackgroundColor();
|
||||
UpdateBackgroundButtonsState();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BackgroundPalette);
|
||||
}
|
||||
|
||||
CheckColorTheme(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建背景颜色选项面板
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 加载自定义背景色
|
||||
/// - 创建背景选项面板UI
|
||||
/// - 添加标题栏和关闭按钮
|
||||
/// - 添加白板/黑板模式选择按钮
|
||||
/// - 添加RGB颜色选择器
|
||||
/// - 添加颜色预览和应用按钮
|
||||
/// - 将面板添加到主网格
|
||||
/// </remarks>
|
||||
private void CreateBackgroundPalette()
|
||||
private void WhiteboardModeBtn_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// 确保加载自定义背景色
|
||||
LoadCustomBackgroundColor();
|
||||
Settings.Canvas.UsingWhiteboard = true;
|
||||
SaveSettingsToFile();
|
||||
ICCWaterMarkDark.Visibility = Visibility.Visible;
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 创建一个类似于PenPalette的面板
|
||||
BackgroundPalette = new Border
|
||||
Color defaultWhiteboardColor = Color.FromRgb(255, 255, 255);
|
||||
|
||||
if (currentMode == 1)
|
||||
{
|
||||
Name = "BackgroundPalette",
|
||||
Visibility = Visibility.Collapsed,
|
||||
Background = (SolidColorBrush)Application.Current.FindResource("SettingsPageBackground"),
|
||||
Opacity = 1,
|
||||
BorderBrush = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Width = 300,
|
||||
MaxHeight = 400
|
||||
};
|
||||
|
||||
// 确保面板显示在顶层
|
||||
Panel.SetZIndex(BackgroundPalette, 1000);
|
||||
|
||||
// 创建面板内容
|
||||
var stackPanel = new StackPanel();
|
||||
|
||||
// 创建标题栏
|
||||
var titleBorder = new Border
|
||||
{
|
||||
BorderBrush = new SolidColorBrush(Color.FromRgb(0x1e, 0x3a, 0x8a)),
|
||||
Height = 32,
|
||||
BorderThickness = new Thickness(0, 0, 0, 1),
|
||||
CornerRadius = new CornerRadius(8, 8, 0, 0),
|
||||
Background = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)),
|
||||
Margin = new Thickness(-1, -1, -1, 0),
|
||||
Padding = new Thickness(1, 1, 1, 0)
|
||||
};
|
||||
|
||||
var titleCanvas = new System.Windows.Controls.Canvas { Height = 24, ClipToBounds = true };
|
||||
var titleText = new TextBlock
|
||||
{
|
||||
Text = "背景设置",
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("FloatBarForeground"),
|
||||
Padding = new Thickness(0, 5, 0, 0),
|
||||
FontSize = 11,
|
||||
FontWeight = FontWeights.Bold,
|
||||
TextAlignment = TextAlignment.Center
|
||||
};
|
||||
System.Windows.Controls.Canvas.SetLeft(titleText, 8);
|
||||
titleCanvas.Children.Add(titleText);
|
||||
|
||||
// 关闭按钮
|
||||
var closeImage = new Image
|
||||
{
|
||||
Source = new BitmapImage(new Uri("/Resources/new-icons/close-white.png", UriKind.Relative)),
|
||||
Height = 16,
|
||||
Width = 16
|
||||
};
|
||||
RenderOptions.SetBitmapScalingMode(closeImage, BitmapScalingMode.HighQuality);
|
||||
closeImage.MouseUp += CloseBordertools_MouseUp;
|
||||
System.Windows.Controls.Canvas.SetRight(closeImage, 8);
|
||||
System.Windows.Controls.Canvas.SetTop(closeImage, 4);
|
||||
titleCanvas.Children.Add(closeImage);
|
||||
|
||||
titleBorder.Child = titleCanvas;
|
||||
stackPanel.Children.Add(titleBorder);
|
||||
|
||||
// 创建背景选项内容区域
|
||||
var contentPanel = new StackPanel { Margin = new Thickness(8) };
|
||||
|
||||
// 黑板/白板选择
|
||||
var modeTitle = new TextBlock
|
||||
{
|
||||
Text = "白板模式",
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground"),
|
||||
FontSize = 10,
|
||||
FontWeight = FontWeights.Bold,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Margin = new Thickness(0, 4, 0, 8)
|
||||
};
|
||||
contentPanel.Children.Add(modeTitle);
|
||||
|
||||
var modePanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
|
||||
|
||||
// 白板按钮
|
||||
var whiteboardButton = new Border
|
||||
{
|
||||
Width = 60,
|
||||
Height = 30,
|
||||
Background = Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) : new SolidColorBrush(Colors.LightGray),
|
||||
CornerRadius = new CornerRadius(4),
|
||||
Margin = new Thickness(0, 0, 8, 0)
|
||||
};
|
||||
var whiteboardText = new TextBlock
|
||||
{
|
||||
Text = "白板",
|
||||
Foreground = Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.Black),
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
whiteboardButton.Child = whiteboardText;
|
||||
whiteboardButton.MouseUp += (s, args) =>
|
||||
{
|
||||
Settings.Canvas.UsingWhiteboard = true;
|
||||
GridBackgroundCover.Background = new SolidColorBrush(defaultWhiteboardColor);
|
||||
UpdateRGBSliders(defaultWhiteboardColor);
|
||||
CustomBackgroundColor = defaultWhiteboardColor;
|
||||
string colorHex = $"#{defaultWhiteboardColor.R:X2}{defaultWhiteboardColor.G:X2}{defaultWhiteboardColor.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
SaveSettingsToFile();
|
||||
ICCWaterMarkDark.Visibility = Visibility.Visible;
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 设置为白板默认背景色
|
||||
Color defaultWhiteboardColor = Color.FromRgb(255, 255, 255);
|
||||
|
||||
if (currentMode == 1) // 白板模式
|
||||
{
|
||||
// 设置背景为默认白板背景色
|
||||
GridBackgroundCover.Background = new SolidColorBrush(defaultWhiteboardColor);
|
||||
|
||||
// 更新RGB滑块的值为默认白板背景色
|
||||
UpdateRGBSliders(defaultWhiteboardColor);
|
||||
|
||||
// 更新自定义背景色为默认白板背景色
|
||||
CustomBackgroundColor = defaultWhiteboardColor;
|
||||
|
||||
// 保存到设置
|
||||
string colorHex = $"#{defaultWhiteboardColor.R:X2}{defaultWhiteboardColor.G:X2}{defaultWhiteboardColor.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 设置墨迹颜色为黑色
|
||||
CheckLastColor(0);
|
||||
forceEraser = false;
|
||||
|
||||
CheckColorTheme(true);
|
||||
UpdateBackgroundButtonsState();
|
||||
};
|
||||
modePanel.Children.Add(whiteboardButton);
|
||||
|
||||
// 黑板按钮
|
||||
var blackboardButton = new Border
|
||||
{
|
||||
Width = 60,
|
||||
Height = 30,
|
||||
Background = !Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) : new SolidColorBrush(Colors.LightGray),
|
||||
CornerRadius = new CornerRadius(4)
|
||||
};
|
||||
var blackboardText = new TextBlock
|
||||
{
|
||||
Text = "黑板",
|
||||
Foreground = !Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.Black),
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
blackboardButton.Child = blackboardText;
|
||||
blackboardButton.MouseUp += (s, args) =>
|
||||
{
|
||||
Settings.Canvas.UsingWhiteboard = false;
|
||||
SaveSettingsToFile();
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Visible;
|
||||
ICCWaterMarkDark.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 设置为黑板默认背景色
|
||||
Color defaultBlackboardColor = Color.FromRgb(22, 41, 36);
|
||||
|
||||
if (currentMode == 1) // 黑板模式
|
||||
{
|
||||
// 设置背景为默认黑板背景色
|
||||
GridBackgroundCover.Background = new SolidColorBrush(defaultBlackboardColor);
|
||||
|
||||
// 更新RGB滑块的值为默认黑板背景色
|
||||
UpdateRGBSliders(defaultBlackboardColor);
|
||||
|
||||
// 更新自定义背景色为默认黑板背景色
|
||||
CustomBackgroundColor = defaultBlackboardColor;
|
||||
|
||||
// 保存到设置
|
||||
string colorHex = $"#{defaultBlackboardColor.R:X2}{defaultBlackboardColor.G:X2}{defaultBlackboardColor.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 设置墨迹颜色为白色
|
||||
CheckLastColor(5);
|
||||
forceEraser = false;
|
||||
|
||||
CheckColorTheme(true);
|
||||
UpdateBackgroundButtonsState();
|
||||
};
|
||||
modePanel.Children.Add(blackboardButton);
|
||||
|
||||
contentPanel.Children.Add(modePanel);
|
||||
|
||||
// 添加一条分隔线
|
||||
var separator = new Border
|
||||
{
|
||||
Height = 1,
|
||||
Background = (SolidColorBrush)Application.Current.FindResource("SettingsPageBorderBrush"),
|
||||
Margin = new Thickness(0, 12, 0, 12)
|
||||
};
|
||||
contentPanel.Children.Add(separator);
|
||||
|
||||
// 添加RGB颜色选择器部分
|
||||
var colorTitle = new TextBlock
|
||||
{
|
||||
Text = "背景颜色",
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground"),
|
||||
FontSize = 10,
|
||||
FontWeight = FontWeights.Bold,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Margin = new Thickness(0, 4, 0, 8)
|
||||
};
|
||||
contentPanel.Children.Add(colorTitle);
|
||||
|
||||
// 创建颜色预览
|
||||
Border colorPreview = new Border
|
||||
{
|
||||
Width = 100,
|
||||
Height = 40,
|
||||
BorderThickness = new Thickness(1),
|
||||
BorderBrush = (SolidColorBrush)Application.Current.FindResource("SettingsPageBorderBrush"),
|
||||
Background = new SolidColorBrush(Colors.White),
|
||||
CornerRadius = new CornerRadius(4),
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
contentPanel.Children.Add(colorPreview);
|
||||
|
||||
// 获取当前背景颜色
|
||||
Color currentBackgroundColor;
|
||||
if (currentMode == 1) // 白板或黑板模式
|
||||
{
|
||||
if (GridBackgroundCover.Background is SolidColorBrush brush)
|
||||
{
|
||||
currentBackgroundColor = brush.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 默认颜色
|
||||
currentBackgroundColor = Settings.Canvas.UsingWhiteboard ?
|
||||
Color.FromRgb(234, 235, 237) : // 白板默认颜色
|
||||
Color.FromRgb(22, 41, 36); // 黑板默认颜色
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 默认白色
|
||||
currentBackgroundColor = Colors.White;
|
||||
}
|
||||
|
||||
// 更新颜色预览
|
||||
colorPreview.Background = new SolidColorBrush(currentBackgroundColor);
|
||||
CheckLastColor(0);
|
||||
forceEraser = false;
|
||||
CheckColorTheme(true);
|
||||
UpdateBackgroundButtonsState();
|
||||
}
|
||||
|
||||
// 先创建所有滑块控件
|
||||
// R滑块和文本框
|
||||
var rPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) };
|
||||
var rLabel = new TextBlock { Text = "R:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
||||
var rSlider = new Slider
|
||||
private void BlackboardModeBtn_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Settings.Canvas.UsingWhiteboard = false;
|
||||
SaveSettingsToFile();
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Visible;
|
||||
ICCWaterMarkDark.Visibility = Visibility.Collapsed;
|
||||
|
||||
Color defaultBlackboardColor = Color.FromRgb(22, 41, 36);
|
||||
|
||||
if (currentMode == 1)
|
||||
{
|
||||
Minimum = 0,
|
||||
Maximum = 255,
|
||||
Value = currentBackgroundColor.R,
|
||||
Width = 150,
|
||||
Margin = new Thickness(5, 0, 5, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
var rValueText = new TextBlock
|
||||
GridBackgroundCover.Background = new SolidColorBrush(defaultBlackboardColor);
|
||||
UpdateRGBSliders(defaultBlackboardColor);
|
||||
CustomBackgroundColor = defaultBlackboardColor;
|
||||
string colorHex = $"#{defaultBlackboardColor.R:X2}{defaultBlackboardColor.G:X2}{defaultBlackboardColor.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
CheckLastColor(5);
|
||||
forceEraser = false;
|
||||
CheckColorTheme(true);
|
||||
UpdateBackgroundButtonsState();
|
||||
}
|
||||
|
||||
private void BackgroundRSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (BackgroundRValue != null)
|
||||
{
|
||||
Text = currentBackgroundColor.R.ToString(),
|
||||
Width = 30,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextAlignment = TextAlignment.Right,
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground")
|
||||
};
|
||||
|
||||
// G滑块和文本框
|
||||
var gPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) };
|
||||
var gLabel = new TextBlock { Text = "G:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
||||
var gSlider = new Slider
|
||||
{
|
||||
Minimum = 0,
|
||||
Maximum = 255,
|
||||
Value = currentBackgroundColor.G,
|
||||
Width = 150,
|
||||
Margin = new Thickness(5, 0, 5, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
var gValueText = new TextBlock
|
||||
{
|
||||
Text = currentBackgroundColor.G.ToString(),
|
||||
Width = 30,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextAlignment = TextAlignment.Right,
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground")
|
||||
};
|
||||
|
||||
// B滑块和文本框
|
||||
var bPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) };
|
||||
var bLabel = new TextBlock { Text = "B:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
||||
var bSlider = new Slider
|
||||
{
|
||||
Minimum = 0,
|
||||
Maximum = 255,
|
||||
Value = currentBackgroundColor.B,
|
||||
Width = 150,
|
||||
Margin = new Thickness(5, 0, 5, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
var bValueText = new TextBlock
|
||||
{
|
||||
Text = currentBackgroundColor.B.ToString(),
|
||||
Width = 30,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextAlignment = TextAlignment.Right,
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground")
|
||||
};
|
||||
|
||||
// 现在添加事件处理程序
|
||||
rSlider.ValueChanged += (s, e) =>
|
||||
{
|
||||
int value = (int)e.NewValue;
|
||||
rValueText.Text = value.ToString();
|
||||
UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider);
|
||||
};
|
||||
|
||||
gSlider.ValueChanged += (s, e) =>
|
||||
{
|
||||
int value = (int)e.NewValue;
|
||||
gValueText.Text = value.ToString();
|
||||
UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider);
|
||||
};
|
||||
|
||||
bSlider.ValueChanged += (s, e) =>
|
||||
{
|
||||
int value = (int)e.NewValue;
|
||||
bValueText.Text = value.ToString();
|
||||
UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider);
|
||||
};
|
||||
|
||||
// 添加控件到面板
|
||||
rPanel.Children.Add(rLabel);
|
||||
rPanel.Children.Add(rSlider);
|
||||
rPanel.Children.Add(rValueText);
|
||||
contentPanel.Children.Add(rPanel);
|
||||
|
||||
gPanel.Children.Add(gLabel);
|
||||
gPanel.Children.Add(gSlider);
|
||||
gPanel.Children.Add(gValueText);
|
||||
contentPanel.Children.Add(gPanel);
|
||||
|
||||
bPanel.Children.Add(bLabel);
|
||||
bPanel.Children.Add(bSlider);
|
||||
bPanel.Children.Add(bValueText);
|
||||
contentPanel.Children.Add(bPanel);
|
||||
|
||||
// 应用按钮
|
||||
var applyButton = new Button
|
||||
{
|
||||
Content = "应用颜色",
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
Padding = new Thickness(10, 5, 10, 5),
|
||||
Background = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)),
|
||||
Foreground = new SolidColorBrush(Colors.White),
|
||||
BorderThickness = new Thickness(0),
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
|
||||
applyButton.Click += (s, e) =>
|
||||
{
|
||||
Color selectedColor = Color.FromRgb(
|
||||
(byte)rSlider.Value,
|
||||
(byte)gSlider.Value,
|
||||
(byte)bSlider.Value
|
||||
);
|
||||
ApplyCustomBackgroundColor(selectedColor);
|
||||
};
|
||||
|
||||
contentPanel.Children.Add(applyButton);
|
||||
|
||||
stackPanel.Children.Add(contentPanel);
|
||||
|
||||
// 将面板添加到父容器
|
||||
BackgroundPalette.Child = stackPanel;
|
||||
|
||||
// 获取主窗口中的根网格,确保面板添加到顶层
|
||||
Grid mainGrid = FindName("Main_Grid") as Grid;
|
||||
if (mainGrid != null)
|
||||
{
|
||||
// 删除可能已存在的BackgroundPalette
|
||||
foreach (UIElement element in mainGrid.Children)
|
||||
{
|
||||
if (element is Border border && border.Name == "BackgroundPalette")
|
||||
{
|
||||
mainGrid.Children.Remove(border);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 重新定位面板
|
||||
BackgroundPalette.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
BackgroundPalette.VerticalAlignment = VerticalAlignment.Center;
|
||||
BackgroundPalette.Margin = new Thickness(0, 0, 0, 0);
|
||||
|
||||
// 添加到主网格
|
||||
mainGrid.Children.Add(BackgroundPalette);
|
||||
|
||||
// 设置面板位置
|
||||
var clickElement = FindName("BoardChangeBackgroundColorBtn") as FrameworkElement;
|
||||
if (clickElement != null)
|
||||
{
|
||||
Point position = clickElement.TranslatePoint(new Point(0, 0), mainGrid);
|
||||
BackgroundPalette.Margin = new Thickness(
|
||||
position.X - 150,
|
||||
position.Y + clickElement.ActualHeight + 5,
|
||||
0, 0);
|
||||
BackgroundPalette.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
BackgroundPalette.VerticalAlignment = VerticalAlignment.Top;
|
||||
}
|
||||
BackgroundRValue.Text = ((int)e.NewValue).ToString();
|
||||
UpdateColorPreviewFromSliders();
|
||||
}
|
||||
}
|
||||
|
||||
private void BackgroundGSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (BackgroundGValue != null)
|
||||
{
|
||||
BackgroundGValue.Text = ((int)e.NewValue).ToString();
|
||||
UpdateColorPreviewFromSliders();
|
||||
}
|
||||
}
|
||||
|
||||
private void BackgroundBSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (BackgroundBValue != null)
|
||||
{
|
||||
BackgroundBValue.Text = ((int)e.NewValue).ToString();
|
||||
UpdateColorPreviewFromSliders();
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyBackgroundColorBtn_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Color selectedColor = Color.FromRgb(
|
||||
(byte)BackgroundRSlider.Value,
|
||||
(byte)BackgroundGSlider.Value,
|
||||
(byte)BackgroundBSlider.Value
|
||||
);
|
||||
ApplyCustomBackgroundColor(selectedColor);
|
||||
}
|
||||
|
||||
private void UpdateColorPreviewFromSliders()
|
||||
{
|
||||
if (BackgroundColorPreview != null)
|
||||
{
|
||||
Color previewColor = Color.FromRgb(
|
||||
(byte)BackgroundRSlider.Value,
|
||||
(byte)BackgroundGSlider.Value,
|
||||
(byte)BackgroundBSlider.Value
|
||||
);
|
||||
BackgroundColorPreview.Background = new SolidColorBrush(previewColor);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新背景颜色选项面板中的按钮状态
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// - 更新白板和黑板按钮的背景和前景色
|
||||
/// - 根据当前使用的模式设置按钮状态
|
||||
/// </remarks>
|
||||
private void UpdateBackgroundButtonsState()
|
||||
{
|
||||
if (BackgroundPalette != null && BackgroundPalette.Child is StackPanel stackPanel)
|
||||
if (WhiteboardModeBtn != null)
|
||||
{
|
||||
if (stackPanel.Children.Count > 1 && stackPanel.Children[1] is StackPanel contentPanel)
|
||||
WhiteboardModeBtn.Background = Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) :
|
||||
new SolidColorBrush(Colors.LightGray);
|
||||
if (WhiteboardModeBtn.Child is TextBlock whiteboardText)
|
||||
{
|
||||
if (contentPanel.Children.Count > 1 && contentPanel.Children[1] is StackPanel modePanel)
|
||||
{
|
||||
if (modePanel.Children.Count > 1)
|
||||
{
|
||||
var whiteboardButton = modePanel.Children[0] as Border;
|
||||
var blackboardButton = modePanel.Children[1] as Border;
|
||||
whiteboardText.Foreground = Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Colors.White) :
|
||||
new SolidColorBrush(Colors.Black);
|
||||
}
|
||||
}
|
||||
|
||||
if (whiteboardButton != null && whiteboardButton.Child is TextBlock whiteboardText)
|
||||
{
|
||||
whiteboardButton.Background = Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) :
|
||||
new SolidColorBrush(Colors.LightGray);
|
||||
whiteboardText.Foreground = Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Colors.White) :
|
||||
new SolidColorBrush(Colors.Black);
|
||||
}
|
||||
|
||||
if (blackboardButton != null && blackboardButton.Child is TextBlock blackboardText)
|
||||
{
|
||||
blackboardButton.Background = !Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) :
|
||||
new SolidColorBrush(Colors.LightGray);
|
||||
blackboardText.Foreground = !Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Colors.White) :
|
||||
new SolidColorBrush(Colors.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (BlackboardModeBtn != null)
|
||||
{
|
||||
BlackboardModeBtn.Background = !Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) :
|
||||
new SolidColorBrush(Colors.LightGray);
|
||||
if (BlackboardModeBtn.Child is TextBlock blackboardText)
|
||||
{
|
||||
blackboardText.Foreground = !Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Colors.White) :
|
||||
new SolidColorBrush(Colors.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 背景颜色选项面板
|
||||
/// </summary>
|
||||
private Border BackgroundPalette { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 当前自定义背景色
|
||||
/// </summary>
|
||||
@@ -767,41 +330,7 @@ namespace Ink_Canvas
|
||||
/// - 启用橡皮擦模式
|
||||
/// - 设置橡皮擦形状为圆形
|
||||
/// - 设置当前工具模式为按笔画擦除
|
||||
/// - 禁用形状绘制模式
|
||||
/// - 重置钢笔类型和属性
|
||||
/// - 触发编辑模式变更事件
|
||||
/// - 取消单指拖动模式
|
||||
/// - 隐藏子面板
|
||||
/// </remarks>
|
||||
private void BoardEraserIconByStrokes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
//if (BoardEraserByStrokes.Background.ToString() == "#FF679CF4") {
|
||||
// AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardDeleteIcon);
|
||||
//}
|
||||
//else {
|
||||
// 禁用高级橡皮擦系统
|
||||
DisableEraserOverlay();
|
||||
|
||||
forceEraser = true;
|
||||
forcePointEraser = false;
|
||||
|
||||
inkCanvas.EraserShape = new EllipseStylusShape(5, 5);
|
||||
// 使用集中化的工具模式切换方法
|
||||
SetCurrentToolMode(InkCanvasEditingMode.EraseByStroke);
|
||||
drawingShapeMode = 0;
|
||||
|
||||
penType = 0;
|
||||
drawingAttributes.IsHighlighter = false;
|
||||
drawingAttributes.StylusTip = StylusTip.Ellipse;
|
||||
|
||||
inkCanvas_EditingModeChanged(inkCanvas, null);
|
||||
CancelSingleFingerDragMode();
|
||||
|
||||
HideSubPanels("eraserByStrokes");
|
||||
//}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理删除图标点击事件,清空画布内容
|
||||
/// </summary>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
@@ -913,53 +442,9 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
private void UpdateRGBSliders(Color color)
|
||||
{
|
||||
if (BackgroundPalette != null && BackgroundPalette.Child is StackPanel stackPanel)
|
||||
{
|
||||
if (stackPanel.Children.Count > 1 && stackPanel.Children[1] is StackPanel contentPanel)
|
||||
{
|
||||
// 查找RGB滑块
|
||||
Slider rSlider = null;
|
||||
Slider gSlider = null;
|
||||
Slider bSlider = null;
|
||||
|
||||
// 遍历面板查找RGB滑块
|
||||
foreach (var child in contentPanel.Children)
|
||||
{
|
||||
if (child is StackPanel panel && panel.Orientation == Orientation.Horizontal)
|
||||
{
|
||||
foreach (var panelChild in panel.Children)
|
||||
{
|
||||
if (panelChild is Slider slider)
|
||||
{
|
||||
if (panel.Children.Count > 0 && panel.Children[0] is TextBlock label)
|
||||
{
|
||||
if (label.Text == "R:")
|
||||
{
|
||||
rSlider = slider;
|
||||
}
|
||||
else if (label.Text == "G:")
|
||||
{
|
||||
gSlider = slider;
|
||||
}
|
||||
else if (label.Text == "B:")
|
||||
{
|
||||
bSlider = slider;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新滑块值
|
||||
if (rSlider != null && gSlider != null && bSlider != null)
|
||||
{
|
||||
rSlider.Value = color.R;
|
||||
gSlider.Value = color.G;
|
||||
bSlider.Value = color.B;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (BackgroundRSlider != null) BackgroundRSlider.Value = color.R;
|
||||
if (BackgroundGSlider != null) BackgroundGSlider.Value = color.G;
|
||||
if (BackgroundBSlider != null) BackgroundBSlider.Value = color.B;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,21 +272,21 @@ namespace Ink_Canvas
|
||||
/// - 显示通知
|
||||
/// - 包含异常处理
|
||||
/// </remarks>
|
||||
private async Task PasteImageFromClipboard(Point? position = null)
|
||||
private Task PasteImageFromClipboard(Point? position = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Clipboard.ContainsImage())
|
||||
{
|
||||
ShowNotification("剪贴板中没有图片");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
var clipboardImage = Clipboard.GetImage();
|
||||
if (clipboardImage == null)
|
||||
{
|
||||
ShowNotification("无法获取剪贴板图片");
|
||||
return;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
// 创建Image控件
|
||||
@@ -383,6 +383,7 @@ namespace Ink_Canvas
|
||||
ShowNotification($"粘贴图片失败: {ex.Message}");
|
||||
LogHelper.WriteLogToFile($"粘贴图片失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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.Media.Animation;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas
|
||||
@@ -52,6 +50,7 @@ namespace Ink_Canvas
|
||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
|
||||
SyncPdfPageSidebarWithCanvas();
|
||||
}
|
||||
|
||||
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
|
||||
@@ -282,26 +281,26 @@ namespace Ink_Canvas
|
||||
{
|
||||
// 亮系
|
||||
// 亮色的红色
|
||||
BorderPenColorRed.Background = new SolidColorBrush(Color.FromRgb(239, 68, 68));
|
||||
BoardBorderPenColorRed.Background = new SolidColorBrush(Color.FromRgb(239, 68, 68));
|
||||
BorderPenColorRed.Color = Color.FromRgb(239, 68, 68);
|
||||
BoardBorderPenColorRed.Color = Color.FromRgb(239, 68, 68);
|
||||
// 亮色的绿色
|
||||
BorderPenColorGreen.Background = new SolidColorBrush(Color.FromRgb(34, 197, 94));
|
||||
BoardBorderPenColorGreen.Background = new SolidColorBrush(Color.FromRgb(34, 197, 94));
|
||||
BorderPenColorGreen.Color = Color.FromRgb(34, 197, 94);
|
||||
BoardBorderPenColorGreen.Color = Color.FromRgb(34, 197, 94);
|
||||
// 亮色的蓝色
|
||||
BorderPenColorBlue.Background = new SolidColorBrush(Color.FromRgb(59, 130, 246));
|
||||
BoardBorderPenColorBlue.Background = new SolidColorBrush(Color.FromRgb(59, 130, 246));
|
||||
BorderPenColorBlue.Color = Color.FromRgb(59, 130, 246);
|
||||
BoardBorderPenColorBlue.Color = Color.FromRgb(59, 130, 246);
|
||||
// 亮色的黄色
|
||||
BorderPenColorYellow.Background = new SolidColorBrush(Color.FromRgb(250, 204, 21));
|
||||
BoardBorderPenColorYellow.Background = new SolidColorBrush(Color.FromRgb(250, 204, 21));
|
||||
BorderPenColorYellow.Color = Color.FromRgb(250, 204, 21);
|
||||
BoardBorderPenColorYellow.Color = Color.FromRgb(250, 204, 21);
|
||||
// 亮色的粉色
|
||||
BorderPenColorPink.Background = new SolidColorBrush(Color.FromRgb(236, 72, 153));
|
||||
BoardBorderPenColorPink.Background = new SolidColorBrush(Color.FromRgb(236, 72, 153));
|
||||
BorderPenColorPink.Color = Color.FromRgb(236, 72, 153);
|
||||
BoardBorderPenColorPink.Color = Color.FromRgb(236, 72, 153);
|
||||
// 亮色的Teal
|
||||
BorderPenColorTeal.Background = new SolidColorBrush(Color.FromRgb(20, 184, 166));
|
||||
BoardBorderPenColorTeal.Background = new SolidColorBrush(Color.FromRgb(20, 184, 166));
|
||||
BorderPenColorTeal.Color = Color.FromRgb(20, 184, 166);
|
||||
BoardBorderPenColorTeal.Color = Color.FromRgb(20, 184, 166);
|
||||
// 亮色的Orange
|
||||
BorderPenColorOrange.Background = new SolidColorBrush(Color.FromRgb(249, 115, 22));
|
||||
BoardBorderPenColorOrange.Background = new SolidColorBrush(Color.FromRgb(249, 115, 22));
|
||||
BorderPenColorOrange.Color = Color.FromRgb(249, 115, 22);
|
||||
BoardBorderPenColorOrange.Color = Color.FromRgb(249, 115, 22);
|
||||
|
||||
var newImageSource = new BitmapImage();
|
||||
newImageSource.BeginInit();
|
||||
@@ -318,26 +317,26 @@ namespace Ink_Canvas
|
||||
{
|
||||
// 暗系
|
||||
// 暗色的红色
|
||||
BorderPenColorRed.Background = new SolidColorBrush(Color.FromRgb(220, 38, 38));
|
||||
BoardBorderPenColorRed.Background = new SolidColorBrush(Color.FromRgb(220, 38, 38));
|
||||
BorderPenColorRed.Color = Color.FromRgb(220, 38, 38);
|
||||
BoardBorderPenColorRed.Color = Color.FromRgb(220, 38, 38);
|
||||
// 暗色的绿色
|
||||
BorderPenColorGreen.Background = new SolidColorBrush(Color.FromRgb(22, 163, 74));
|
||||
BoardBorderPenColorGreen.Background = new SolidColorBrush(Color.FromRgb(22, 163, 74));
|
||||
BorderPenColorGreen.Color = Color.FromRgb(22, 163, 74);
|
||||
BoardBorderPenColorGreen.Color = Color.FromRgb(22, 163, 74);
|
||||
// 暗色的蓝色
|
||||
BorderPenColorBlue.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235));
|
||||
BoardBorderPenColorBlue.Background = new SolidColorBrush(Color.FromRgb(37, 99, 235));
|
||||
BorderPenColorBlue.Color = Color.FromRgb(37, 99, 235);
|
||||
BoardBorderPenColorBlue.Color = Color.FromRgb(37, 99, 235);
|
||||
// 暗色的黄色
|
||||
BorderPenColorYellow.Background = new SolidColorBrush(Color.FromRgb(234, 179, 8));
|
||||
BoardBorderPenColorYellow.Background = new SolidColorBrush(Color.FromRgb(234, 179, 8));
|
||||
BorderPenColorYellow.Color = Color.FromRgb(234, 179, 8);
|
||||
BoardBorderPenColorYellow.Color = Color.FromRgb(234, 179, 8);
|
||||
// 暗色的紫色对应亮色的粉色
|
||||
BorderPenColorPink.Background = new SolidColorBrush(Color.FromRgb(147, 51, 234));
|
||||
BoardBorderPenColorPink.Background = new SolidColorBrush(Color.FromRgb(147, 51, 234));
|
||||
BorderPenColorPink.Color = Color.FromRgb(147, 51, 234);
|
||||
BoardBorderPenColorPink.Color = Color.FromRgb(147, 51, 234);
|
||||
// 暗色的Teal
|
||||
BorderPenColorTeal.Background = new SolidColorBrush(Color.FromRgb(13, 148, 136));
|
||||
BoardBorderPenColorTeal.Background = new SolidColorBrush(Color.FromRgb(13, 148, 136));
|
||||
BorderPenColorTeal.Color = Color.FromRgb(13, 148, 136);
|
||||
BoardBorderPenColorTeal.Color = Color.FromRgb(13, 148, 136);
|
||||
// 暗色的Orange
|
||||
BorderPenColorOrange.Background = new SolidColorBrush(Color.FromRgb(234, 88, 12));
|
||||
BoardBorderPenColorOrange.Background = new SolidColorBrush(Color.FromRgb(234, 88, 12));
|
||||
BorderPenColorOrange.Color = Color.FromRgb(234, 88, 12);
|
||||
BoardBorderPenColorOrange.Color = Color.FromRgb(234, 88, 12);
|
||||
|
||||
var newImageSource = new BitmapImage();
|
||||
newImageSource.BeginInit();
|
||||
@@ -352,127 +351,129 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 改变选中提示
|
||||
ViewboxBtnColorBlackContent.Visibility = Visibility.Collapsed;
|
||||
ViewboxBtnColorBlueContent.Visibility = Visibility.Collapsed;
|
||||
ViewboxBtnColorGreenContent.Visibility = Visibility.Collapsed;
|
||||
ViewboxBtnColorRedContent.Visibility = Visibility.Collapsed;
|
||||
ViewboxBtnColorYellowContent.Visibility = Visibility.Collapsed;
|
||||
ViewboxBtnColorWhiteContent.Visibility = Visibility.Collapsed;
|
||||
ViewboxBtnColorPinkContent.Visibility = Visibility.Collapsed;
|
||||
ViewboxBtnColorTealContent.Visibility = Visibility.Collapsed;
|
||||
ViewboxBtnColorOrangeContent.Visibility = Visibility.Collapsed;
|
||||
BorderPenColorBlack.IsChecked = false;
|
||||
BorderPenColorBlue.IsChecked = false;
|
||||
BorderPenColorGreen.IsChecked = false;
|
||||
BorderPenColorRed.IsChecked = false;
|
||||
BorderPenColorYellow.IsChecked = false;
|
||||
BorderPenColorWhite.IsChecked = false;
|
||||
BorderPenColorPink.IsChecked = false;
|
||||
BorderPenColorTeal.IsChecked = false;
|
||||
BorderPenColorOrange.IsChecked = false;
|
||||
|
||||
BoardViewboxBtnColorBlackContent.Visibility = Visibility.Collapsed;
|
||||
BoardViewboxBtnColorBlueContent.Visibility = Visibility.Collapsed;
|
||||
BoardViewboxBtnColorGreenContent.Visibility = Visibility.Collapsed;
|
||||
BoardViewboxBtnColorRedContent.Visibility = Visibility.Collapsed;
|
||||
BoardViewboxBtnColorYellowContent.Visibility = Visibility.Collapsed;
|
||||
BoardViewboxBtnColorWhiteContent.Visibility = Visibility.Collapsed;
|
||||
BoardViewboxBtnColorPinkContent.Visibility = Visibility.Collapsed;
|
||||
BoardViewboxBtnColorTealContent.Visibility = Visibility.Collapsed;
|
||||
BoardViewboxBtnColorOrangeContent.Visibility = Visibility.Collapsed;
|
||||
BoardBorderPenColorBlack.IsChecked = false;
|
||||
BoardBorderPenColorBlue.IsChecked = false;
|
||||
BoardBorderPenColorGreen.IsChecked = false;
|
||||
BoardBorderPenColorRed.IsChecked = false;
|
||||
BoardBorderPenColorYellow.IsChecked = false;
|
||||
BoardBorderPenColorWhite.IsChecked = false;
|
||||
BoardBorderPenColorPink.IsChecked = false;
|
||||
BoardBorderPenColorTeal.IsChecked = false;
|
||||
BoardBorderPenColorOrange.IsChecked = false;
|
||||
|
||||
HighlighterPenViewboxBtnColorBlackContent.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenViewboxBtnColorBlueContent.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenViewboxBtnColorGreenContent.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenViewboxBtnColorOrangeContent.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenViewboxBtnColorPurpleContent.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenViewboxBtnColorRedContent.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenViewboxBtnColorTealContent.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenViewboxBtnColorWhiteContent.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenViewboxBtnColorYellowContent.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenViewboxBtnColorZincContent.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenColorBlack.IsChecked = false;
|
||||
HighlighterPenColorBlue.IsChecked = false;
|
||||
HighlighterPenColorGreen.IsChecked = false;
|
||||
HighlighterPenColorOrange.IsChecked = false;
|
||||
HighlighterPenPenColorPurple.IsChecked = false;
|
||||
HighlighterPenColorRed.IsChecked = false;
|
||||
HighlighterPenColorTeal.IsChecked = false;
|
||||
HighlighterPenColorWhite.IsChecked = false;
|
||||
HighlighterPenColorYellow.IsChecked = false;
|
||||
HighlighterPenColorZinc.IsChecked = false;
|
||||
|
||||
BoardHighlighterPenViewboxBtnColorBlackContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenViewboxBtnColorBlueContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenViewboxBtnColorGreenContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenViewboxBtnColorOrangeContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenViewboxBtnColorPurpleContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenViewboxBtnColorRedContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenViewboxBtnColorTealContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenViewboxBtnColorWhiteContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenViewboxBtnColorYellowContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenViewboxBtnColorZincContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenColorBlack.IsChecked = false;
|
||||
BoardHighlighterPenColorBlue.IsChecked = false;
|
||||
BoardHighlighterPenColorGreen.IsChecked = false;
|
||||
BoardHighlighterPenColorOrange.IsChecked = false;
|
||||
BoardHighlighterPenPenColorPurple.IsChecked = false;
|
||||
BoardHighlighterPenColorRed.IsChecked = false;
|
||||
BoardHighlighterPenColorTeal.IsChecked = false;
|
||||
BoardHighlighterPenColorWhite.IsChecked = false;
|
||||
BoardHighlighterPenColorYellow.IsChecked = false;
|
||||
BoardHighlighterPenColorZinc.IsChecked = false;
|
||||
|
||||
switch (inkColor)
|
||||
{
|
||||
case 0:
|
||||
ViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
||||
BoardViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
||||
BorderPenColorBlack.IsChecked = true;
|
||||
BoardBorderPenColorBlack.IsChecked = true;
|
||||
break;
|
||||
case 1:
|
||||
ViewboxBtnColorRedContent.Visibility = Visibility.Visible;
|
||||
BoardViewboxBtnColorRedContent.Visibility = Visibility.Visible;
|
||||
BorderPenColorRed.IsChecked = true;
|
||||
BoardBorderPenColorRed.IsChecked = true;
|
||||
break;
|
||||
case 2:
|
||||
ViewboxBtnColorGreenContent.Visibility = Visibility.Visible;
|
||||
BoardViewboxBtnColorGreenContent.Visibility = Visibility.Visible;
|
||||
BorderPenColorGreen.IsChecked = true;
|
||||
BoardBorderPenColorGreen.IsChecked = true;
|
||||
break;
|
||||
case 3:
|
||||
ViewboxBtnColorBlueContent.Visibility = Visibility.Visible;
|
||||
BoardViewboxBtnColorBlueContent.Visibility = Visibility.Visible;
|
||||
BorderPenColorBlue.IsChecked = true;
|
||||
BoardBorderPenColorBlue.IsChecked = true;
|
||||
break;
|
||||
case 4:
|
||||
ViewboxBtnColorYellowContent.Visibility = Visibility.Visible;
|
||||
BoardViewboxBtnColorYellowContent.Visibility = Visibility.Visible;
|
||||
BorderPenColorYellow.IsChecked = true;
|
||||
BoardBorderPenColorYellow.IsChecked = true;
|
||||
break;
|
||||
case 5:
|
||||
ViewboxBtnColorWhiteContent.Visibility = Visibility.Visible;
|
||||
BoardViewboxBtnColorWhiteContent.Visibility = Visibility.Visible;
|
||||
BorderPenColorWhite.IsChecked = true;
|
||||
BoardBorderPenColorWhite.IsChecked = true;
|
||||
break;
|
||||
case 6:
|
||||
ViewboxBtnColorPinkContent.Visibility = Visibility.Visible;
|
||||
BoardViewboxBtnColorPinkContent.Visibility = Visibility.Visible;
|
||||
BorderPenColorPink.IsChecked = true;
|
||||
BoardBorderPenColorPink.IsChecked = true;
|
||||
break;
|
||||
case 7:
|
||||
ViewboxBtnColorTealContent.Visibility = Visibility.Visible;
|
||||
BorderPenColorTeal.IsChecked = true;
|
||||
BoardBorderPenColorTeal.IsChecked = true;
|
||||
break;
|
||||
case 8:
|
||||
ViewboxBtnColorOrangeContent.Visibility = Visibility.Visible;
|
||||
BorderPenColorOrange.IsChecked = true;
|
||||
BoardBorderPenColorOrange.IsChecked = true;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (highlighterColor)
|
||||
{
|
||||
case 100:
|
||||
HighlighterPenViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
||||
HighlighterPenColorBlack.IsChecked = true;
|
||||
BoardHighlighterPenColorBlack.IsChecked = true;
|
||||
break;
|
||||
case 101:
|
||||
HighlighterPenViewboxBtnColorWhiteContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorWhiteContent.Visibility = Visibility.Visible;
|
||||
HighlighterPenColorWhite.IsChecked = true;
|
||||
BoardHighlighterPenColorWhite.IsChecked = true;
|
||||
break;
|
||||
case 102:
|
||||
HighlighterPenViewboxBtnColorRedContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorRedContent.Visibility = Visibility.Visible;
|
||||
HighlighterPenColorRed.IsChecked = true;
|
||||
BoardHighlighterPenColorRed.IsChecked = true;
|
||||
break;
|
||||
case 103:
|
||||
HighlighterPenViewboxBtnColorYellowContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorYellowContent.Visibility = Visibility.Visible;
|
||||
HighlighterPenColorYellow.IsChecked = true;
|
||||
BoardHighlighterPenColorYellow.IsChecked = true;
|
||||
break;
|
||||
case 104:
|
||||
HighlighterPenViewboxBtnColorGreenContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorGreenContent.Visibility = Visibility.Visible;
|
||||
HighlighterPenColorGreen.IsChecked = true;
|
||||
BoardHighlighterPenColorGreen.IsChecked = true;
|
||||
break;
|
||||
case 105:
|
||||
HighlighterPenViewboxBtnColorZincContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorZincContent.Visibility = Visibility.Visible;
|
||||
HighlighterPenColorZinc.IsChecked = true;
|
||||
BoardHighlighterPenColorZinc.IsChecked = true;
|
||||
break;
|
||||
case 106:
|
||||
HighlighterPenViewboxBtnColorBlueContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorBlueContent.Visibility = Visibility.Visible;
|
||||
HighlighterPenColorBlue.IsChecked = true;
|
||||
BoardHighlighterPenColorBlue.IsChecked = true;
|
||||
break;
|
||||
case 107:
|
||||
HighlighterPenViewboxBtnColorPurpleContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorPurpleContent.Visibility = Visibility.Visible;
|
||||
HighlighterPenPenColorPurple.IsChecked = true;
|
||||
BoardHighlighterPenPenColorPurple.IsChecked = true;
|
||||
break;
|
||||
case 108:
|
||||
HighlighterPenViewboxBtnColorTealContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorTealContent.Visibility = Visibility.Visible;
|
||||
HighlighterPenColorTeal.IsChecked = true;
|
||||
BoardHighlighterPenColorTeal.IsChecked = true;
|
||||
break;
|
||||
case 109:
|
||||
HighlighterPenViewboxBtnColorOrangeContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorOrangeContent.Visibility = Visibility.Visible;
|
||||
HighlighterPenColorOrange.IsChecked = true;
|
||||
BoardHighlighterPenColorOrange.IsChecked = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -553,37 +554,15 @@ namespace Ink_Canvas
|
||||
BoardHighlightPenTabButton.Background = new SolidColorBrush(Colors.Transparent);
|
||||
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Collapsed;
|
||||
|
||||
// PenPalette.Margin = new Thickness(-160, -200, -33, 32);
|
||||
// 动态计算面板位置,使其对齐笔按钮(考虑快捷调色盘等动态宽度)
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = PenPalette.Margin,
|
||||
To = new Thickness(-160, -200, -33, 32),
|
||||
EasingFunction = new CubicEase()
|
||||
};
|
||||
PenPalette.BeginAnimation(MarginProperty, marginAnimation);
|
||||
PenPalette.BeginAnimation(MarginProperty, null);
|
||||
var currentMargin = PenPalette.Margin;
|
||||
// 先设置正确的Top/Bottom,保持当前Left/Right
|
||||
PenPalette.Margin = new Thickness(currentMargin.Left, -200, currentMargin.Right, 32);
|
||||
UpdatePenPalettePosition();
|
||||
});
|
||||
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = PenPalette.Margin,
|
||||
To = new Thickness(-160, -200, -33, 50),
|
||||
EasingFunction = new CubicEase()
|
||||
};
|
||||
BoardPenPaletteGrid.BeginAnimation(MarginProperty, marginAnimation);
|
||||
});
|
||||
|
||||
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => { PenPalette.Margin = new Thickness(-160, -200, -33, 32); });
|
||||
|
||||
await Dispatcher.InvokeAsync(() => { BoardPenPaletteGrid.Margin = new Thickness(-160, -200, -33, 50); });
|
||||
}
|
||||
else if (penType == 1)
|
||||
{
|
||||
@@ -621,36 +600,15 @@ namespace Ink_Canvas
|
||||
BoardHighlightPenTabButton.Background = new SolidColorBrush(Color.FromArgb(72, 219, 234, 254));
|
||||
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Visible;
|
||||
|
||||
// PenPalette.Margin = new Thickness(-160, -157, -33, 32);
|
||||
// 动态计算面板位置,使其对齐笔按钮(考虑快捷调色盘等动态宽度)
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = PenPalette.Margin,
|
||||
To = new Thickness(-160, -157, -33, 32),
|
||||
EasingFunction = new CubicEase()
|
||||
};
|
||||
PenPalette.BeginAnimation(MarginProperty, marginAnimation);
|
||||
PenPalette.BeginAnimation(MarginProperty, null);
|
||||
var currentMargin = PenPalette.Margin;
|
||||
// 荧光笔模式面板稍小,使用不同的Top/Bottom
|
||||
PenPalette.Margin = new Thickness(currentMargin.Left, -157, currentMargin.Right, 32);
|
||||
UpdatePenPalettePosition();
|
||||
});
|
||||
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = PenPalette.Margin,
|
||||
To = new Thickness(-160, -154, -33, 50),
|
||||
EasingFunction = new CubicEase()
|
||||
};
|
||||
BoardPenPaletteGrid.BeginAnimation(MarginProperty, marginAnimation);
|
||||
});
|
||||
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => { PenPalette.Margin = new Thickness(-160, -157, -33, 32); });
|
||||
|
||||
await Dispatcher.InvokeAsync(() => { BoardPenPaletteGrid.Margin = new Thickness(-160, -154, -33, 50); });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas
|
||||
@@ -10,25 +8,14 @@ namespace Ink_Canvas
|
||||
{
|
||||
#region 悬浮窗拦截功能
|
||||
|
||||
/// <summary>
|
||||
/// 初始化悬浮窗拦截管理器
|
||||
/// </summary>
|
||||
private void InitializeFloatingWindowInterceptor()
|
||||
{
|
||||
try
|
||||
{
|
||||
_floatingWindowInterceptorManager = new FloatingWindowInterceptorManager();
|
||||
|
||||
// 订阅事件
|
||||
_floatingWindowInterceptorManager.WindowIntercepted += OnFloatingWindowIntercepted;
|
||||
_floatingWindowInterceptorManager.WindowRestored += OnFloatingWindowRestored;
|
||||
|
||||
// 初始化拦截器
|
||||
_floatingWindowInterceptorManager.Initialize(Settings.Automation.FloatingWindowInterceptor);
|
||||
|
||||
// 加载UI状态
|
||||
LoadFloatingWindowInterceptorUI();
|
||||
|
||||
LogHelper.WriteLogToFile("悬浮窗拦截管理器初始化完成", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -37,83 +24,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载悬浮窗拦截UI状态
|
||||
/// </summary>
|
||||
private void LoadFloatingWindowInterceptorUI()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 设置主开关状态
|
||||
ToggleSwitchFloatingWindowInterceptorEnabled.IsOn = Settings.Automation.FloatingWindowInterceptor.IsEnabled;
|
||||
|
||||
// 设置各个拦截规则的状态
|
||||
foreach (var kvp in Settings.Automation.FloatingWindowInterceptor.InterceptRules)
|
||||
{
|
||||
var toggleName = $"ToggleSwitch{kvp.Key}";
|
||||
var toggle = FindName(toggleName) as ToggleSwitch;
|
||||
if (toggle != null)
|
||||
{
|
||||
toggle.IsOn = kvp.Value;
|
||||
}
|
||||
}
|
||||
|
||||
// 更新UI可见性
|
||||
UpdateFloatingWindowInterceptorUI();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载悬浮窗拦截UI状态失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新悬浮窗拦截UI
|
||||
/// </summary>
|
||||
private void UpdateFloatingWindowInterceptorUI()
|
||||
{
|
||||
try
|
||||
{
|
||||
var isEnabled = Settings.Automation.FloatingWindowInterceptor.IsEnabled;
|
||||
FloatingWindowInterceptorGrid.Visibility = isEnabled ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
// 计算启用的规则数量
|
||||
var enabledRulesCount = Settings.Automation.FloatingWindowInterceptor.InterceptRules.Where(kvp => kvp.Value).Count();
|
||||
var totalRulesCount = Settings.Automation.FloatingWindowInterceptor.InterceptRules.Count;
|
||||
|
||||
// 更新状态文本
|
||||
if (_floatingWindowInterceptorManager != null)
|
||||
{
|
||||
var stats = _floatingWindowInterceptorManager.GetStatistics();
|
||||
TextBlockFloatingWindowInterceptorStatus.Text = stats.IsRunning
|
||||
? $"拦截器运行中 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则"
|
||||
: $"拦截器未启动 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则";
|
||||
}
|
||||
else
|
||||
{
|
||||
TextBlockFloatingWindowInterceptorStatus.Text = $"拦截器未初始化 - 已启用 {enabledRulesCount}/{totalRulesCount} 个规则";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新悬浮窗拦截UI失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口被拦截事件处理
|
||||
/// </summary>
|
||||
private void OnFloatingWindowIntercepted(object sender, FloatingWindowInterceptor.WindowInterceptedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 在UI线程中更新状态
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
UpdateFloatingWindowInterceptorUI();
|
||||
}));
|
||||
Dispatcher.BeginInvoke(new Action(() => { }));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -121,18 +36,11 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口被恢复事件处理
|
||||
/// </summary>
|
||||
private void OnFloatingWindowRestored(object sender, FloatingWindowInterceptor.WindowRestoredEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 在UI线程中更新状态
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
UpdateFloatingWindowInterceptorUI();
|
||||
}));
|
||||
Dispatcher.BeginInvoke(new Action(() => { }));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -144,30 +52,24 @@ namespace Ink_Canvas
|
||||
|
||||
#region 悬浮窗拦截事件处理
|
||||
|
||||
/// <summary>
|
||||
/// 主开关切换事件
|
||||
/// </summary>
|
||||
private void ToggleSwitchFloatingWindowInterceptorEnabled_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
try
|
||||
{
|
||||
Settings.Automation.FloatingWindowInterceptor.IsEnabled = ToggleSwitchFloatingWindowInterceptorEnabled.IsOn;
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null)
|
||||
Settings.Automation.FloatingWindowInterceptor.IsEnabled = toggle.IsOn;
|
||||
|
||||
if (_floatingWindowInterceptorManager != null)
|
||||
{
|
||||
if (Settings.Automation.FloatingWindowInterceptor.IsEnabled)
|
||||
{
|
||||
_floatingWindowInterceptorManager.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_floatingWindowInterceptorManager.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateFloatingWindowInterceptorUI();
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -176,129 +78,98 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃白板3拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoWhiteboard3Floating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard3Floating, ToggleSwitchSeewoWhiteboard3Floating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard3Floating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃白板5拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoWhiteboard5Floating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5Floating, ToggleSwitchSeewoWhiteboard5Floating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5Floating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃白板5C拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoWhiteboard5CFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5CFloating, ToggleSwitchSeewoWhiteboard5CFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5CFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃品课侧栏拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoPincoSideBarFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoSideBarFloating, ToggleSwitchSeewoPincoSideBarFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoSideBarFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃品课画笔拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoPincoDrawingFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoDrawingFloating, ToggleSwitchSeewoPincoDrawingFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoDrawingFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃PPT小工具拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoPPTFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPPTFloating, ToggleSwitchSeewoPPTFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPPTFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AiClass拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchAiClassFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.AiClassFloating, ToggleSwitchAiClassFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.AiClassFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鸿合屏幕书写拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchHiteAnnotationFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.HiteAnnotationFloating, ToggleSwitchHiteAnnotationFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.HiteAnnotationFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 畅言智慧课堂拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchChangYanFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanFloating, ToggleSwitchChangYanFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 畅言PPT拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchChangYanPptFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanPptFloating, ToggleSwitchChangYanPptFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanPptFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 天喻教育云拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchIntelligentClassFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.IntelligentClassFloating, ToggleSwitchIntelligentClassFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.IntelligentClassFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃桌面画笔拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoDesktopAnnotationFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopAnnotationFloating, ToggleSwitchSeewoDesktopAnnotationFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopAnnotationFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃桌面侧栏拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoDesktopSideBarFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopSideBarFloating, ToggleSwitchSeewoDesktopSideBarFloating.IsOn);
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
if (toggle != null) SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopSideBarFloating, toggle.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置拦截规则
|
||||
/// </summary>
|
||||
/// <param name="type">拦截类型</param>
|
||||
/// <param name="enabled">是否启用拦截</param>
|
||||
private void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled)
|
||||
public void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -307,18 +178,15 @@ namespace Ink_Canvas
|
||||
_floatingWindowInterceptorManager.SetInterceptRule(type, enabled);
|
||||
}
|
||||
|
||||
// 更新设置
|
||||
var ruleName = type.ToString();
|
||||
if (Settings.Automation.FloatingWindowInterceptor.InterceptRules.ContainsKey(ruleName))
|
||||
{
|
||||
Settings.Automation.FloatingWindowInterceptor.InterceptRules[ruleName] = enabled;
|
||||
}
|
||||
|
||||
// 获取规则信息以处理父子关系
|
||||
var rule = _floatingWindowInterceptorManager?.GetInterceptRule(type);
|
||||
if (rule != null)
|
||||
{
|
||||
// 如果是父规则,更新所有子规则的设置
|
||||
if (rule.ChildTypes.Count > 0)
|
||||
{
|
||||
foreach (var childType in rule.ChildTypes)
|
||||
@@ -330,7 +198,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果是子规则,更新父规则的设置
|
||||
else if (rule.ParentType.HasValue)
|
||||
{
|
||||
var parentRule = _floatingWindowInterceptorManager?.GetInterceptRule(rule.ParentType.Value);
|
||||
@@ -345,9 +212,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 更新UI显示
|
||||
UpdateFloatingWindowInterceptorUI();
|
||||
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -357,4 +221,4 @@ namespace Ink_Canvas
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user