Compare commits
453 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 991a823700 | |||
| 61dbcf762c | |||
| 60b0149a9c | |||
| 501c034cfa | |||
| 11593db23c | |||
| a9ae2a004f | |||
| 7b2a31781c | |||
| ab73eb9632 | |||
| 8ade170b4e | |||
| 91c3d1161d | |||
| bb63805e87 | |||
| 8caf04990c | |||
| f28dd0a965 | |||
| b5d83268e5 | |||
| 19adc42122 | |||
| 8afee913be | |||
| 82d101365f | |||
| 73e679f268 | |||
| 372a8a1de1 | |||
| 171cc34c91 | |||
| fbd217d674 | |||
| f24960ab26 | |||
| d1a871c8f6 | |||
| 086b97906b | |||
| db76a11347 | |||
| 729b10cce8 | |||
| 4cbd25ccb3 | |||
| 0ef7b738a9 | |||
| 33b5563601 | |||
| f6f799f534 | |||
| 8c3f1360e1 | |||
| fafbabd603 | |||
| 254e38895c | |||
| aa0bb22cdd | |||
| 5bfd0c7b2f | |||
| c3885c170c | |||
| 88a7cce269 | |||
| a1fccc2905 | |||
| a889041896 | |||
| 8d778aba2c | |||
| ba5db63e0b | |||
| 0259d83429 | |||
| b6999e57ae | |||
| 04184cf731 | |||
| 04f98eb9e7 | |||
| 869dd045af | |||
| 339ebb862e | |||
| 7b8598bf9f | |||
| ab1c460225 | |||
| 298164d6ed | |||
| 703a8a4e0d | |||
| deaf5fcbf6 | |||
| 18b46689f1 | |||
| edbe3f8311 | |||
| 94c4c3e2d4 | |||
| 077f72737d | |||
| ae089a9390 | |||
| 0f087c2aa6 | |||
| 19d2ffb48e | |||
| a9bb6f73de | |||
| ca124732fa | |||
| 11da14aeab | |||
| 7d5f037c85 | |||
| f62b415227 | |||
| f2b8d4014e | |||
| 3ef047fb41 | |||
| aa3be4ab0d | |||
| 7500049ea2 | |||
| 44600df75c | |||
| dec2a15773 | |||
| 4da78a04cb | |||
| 98ec204bab | |||
| cd9499b064 | |||
| f5e824be86 | |||
| c76254f4f9 | |||
| 77ac6f88ca | |||
| 7e10911991 | |||
| 161b67b09d | |||
| 9614536a29 | |||
| 8ba7aab468 | |||
| 4ea9f79de1 | |||
| b7f7025d97 | |||
| 20d5dd2668 | |||
| adc2d02fbb | |||
| ef2dbdc93b | |||
| 9721ec1f0b | |||
| 4d069d87d7 | |||
| 0308f9ce65 | |||
| 6bcd3cb217 | |||
| 7f83c490db | |||
| bbf9c895b8 | |||
| ffa2063c52 | |||
| d464b1f78e | |||
| 92cb071408 | |||
| 9141b60d03 | |||
| df0a196931 | |||
| a8cb1dd495 | |||
| adc4966d49 | |||
| 1b2ea8c522 | |||
| 7c8bdb489b | |||
| 16458fbb42 | |||
| c72839cdcb | |||
| a31ad5803c | |||
| cf800cbd36 | |||
| 3c06ef0b1a | |||
| 2c3b921f09 | |||
| 402ecc66ae | |||
| e25f56a9b5 | |||
| b28fa887a2 | |||
| f83a02e619 | |||
| eaad089d68 | |||
| a2fda16df9 | |||
| e9e8ff57ae | |||
| 1fd95a2f2e | |||
| 228584ee48 | |||
| c651df0f6e | |||
| 5cced9baf2 | |||
| 5dd26e554b | |||
| 696fd3e8cd | |||
| 1d19b705d3 | |||
| d54074cb57 | |||
| cc054aeb75 | |||
| c32eaed534 | |||
| fa23f73ec4 | |||
| fbf6a13f92 | |||
| 3eba662772 | |||
| e007ee271f | |||
| 59a8d65a89 | |||
| 793519ae1b | |||
| 5c44062aa2 | |||
| fe03a2c2c0 | |||
| d1d74a3770 | |||
| 16108bf779 | |||
| 5bebf077e4 | |||
| efcc01ad6b | |||
| e8a4b45446 | |||
| 6cba297d77 | |||
| b149dc3cb9 | |||
| 1ee4f934b9 | |||
| f7b4adb85d | |||
| da3c41567d | |||
| 5f73795220 | |||
| 36bf1122c6 | |||
| 402f8bb9f9 | |||
| eef2a915fa | |||
| 26ee4d172f | |||
| 08eb450446 | |||
| ecae7d818c | |||
| 47ffccff68 | |||
| 0ba5286c94 | |||
| db8e1e3589 | |||
| d0764a6a77 | |||
| 7b6c347d6b | |||
| d58fec180c | |||
| ac4e4877a1 | |||
| ddea61245d | |||
| 98915bcff2 | |||
| 8e0f0450df | |||
| 1d9a669829 | |||
| 97bcf168b7 | |||
| e81198165b | |||
| b03b8da586 | |||
| d497e07187 | |||
| f284a99194 | |||
| 7d6dd6f805 | |||
| d70e28d198 | |||
| 4c6f138e5c | |||
| 5974841a7b | |||
| 5df54439ba | |||
| 8f627c6b7f | |||
| 3f28bc5c6c | |||
| 903fa699ca | |||
| d9e8f64699 | |||
| 8ad6ca8d41 | |||
| 4017efc65e | |||
| 6ed15d52de | |||
| 5246a2f79b | |||
| b670932dd7 | |||
| ddf30bd96b | |||
| 7e92428485 | |||
| 4c16050c85 | |||
| 2ed035525a | |||
| 1b89b0d7b6 | |||
| 0364821f0b | |||
| fa9d6f37ae | |||
| a4777904b9 | |||
| 0b3c2f95c5 | |||
| 8f54955432 | |||
| 034db2fc27 | |||
| 59141b0241 | |||
| 2e43b96a2c | |||
| edff96299c | |||
| 8d9587b790 | |||
| ad0cf2a849 | |||
| f928946c61 | |||
| 6cc9d0bee2 | |||
| 287e6bb91f | |||
| 3509036d85 | |||
| 752901dbb9 | |||
| e3add94546 | |||
| 8d31c74b39 | |||
| e6f608d206 | |||
| d80b9e29a7 | |||
| 092674465d | |||
| b2e3a5bb18 | |||
| 147a2f957e | |||
| ce56f2919c | |||
| 18059102a3 | |||
| 4542fcccbb | |||
| d12009d30c | |||
| d85e0fbd13 | |||
| f7a5f9ae6b | |||
| 3d4c3d0acc | |||
| c106c41048 | |||
| cea777e8b2 | |||
| 6f069b73da | |||
| c80af8c984 | |||
| d51cbd0682 | |||
| 7e94c945ff | |||
| bd9095b4c2 | |||
| c135bf8fb8 | |||
| f6aebb15b4 | |||
| 2b4f88becd | |||
| 7fbd3639b6 | |||
| 9e52c2c31d | |||
| 46a1e5ff14 | |||
| fa7c1fd646 | |||
| c347809eea | |||
| dd09a42f02 | |||
| db8ffd05ea | |||
| 045c29ca20 | |||
| 4ea6d19602 | |||
| df00447c41 | |||
| db5b1caea7 | |||
| 8432954b8d | |||
| 4a000e23e9 | |||
| b42db51346 | |||
| 1ef968ea93 | |||
| 5b50537333 | |||
| 8c624b48fb | |||
| ad329fc2c8 | |||
| f045b8f659 | |||
| 00a9da45bb | |||
| b7ecd12f8c | |||
| a8a3164ee7 | |||
| b1aec69e98 | |||
| d713a8c5a4 | |||
| 3895faf941 | |||
| bf336fdb10 | |||
| c96c26288c | |||
| 83558c089d | |||
| 26acb72e17 | |||
| 4724a432ab | |||
| 5b9c1627c0 | |||
| d06c2585cf | |||
| 3e96e51efd | |||
| 9afa7191c4 | |||
| dfddec5586 | |||
| 93022424b3 | |||
| aed77a187f | |||
| 583f47c98b | |||
| ff528b3f9d | |||
| 6d98d96ccb | |||
| 6f8e08c21a | |||
| 3a7417f493 | |||
| 66164a0c33 | |||
| 0d7a478e16 | |||
| 9caef310df | |||
| 688f742c16 | |||
| 38dd083cdc | |||
| 851bd1c3c6 | |||
| c91b8a1a7a | |||
| 69c45764b2 | |||
| 07a62d2f78 | |||
| 46b064b0a8 | |||
| 1d3b96bb65 | |||
| 916425b7f5 | |||
| 1c570ca242 | |||
| ae41108971 | |||
| 395e7609a4 | |||
| 185c9dc284 | |||
| a9b0ac0595 | |||
| d9e3524211 | |||
| f40ef15a24 | |||
| 4d7544eefb | |||
| 92bb458345 | |||
| 92e695ef7c | |||
| 45ff3fbb9b | |||
| ba252e92d3 | |||
| 4778f459e3 | |||
| c997854ae0 | |||
| 65b5322ad5 | |||
| dd43bf0476 | |||
| 15c808eebd | |||
| 28748a99ca | |||
| fb37d3b9e6 | |||
| e1f10e054c | |||
| c670357c01 | |||
| 92dce9b36e | |||
| aa2b62e8da | |||
| a34a65f354 | |||
| eea5f8496c | |||
| 091a256bcc | |||
| 14c9ce3ce1 | |||
| fd1e5e13fe | |||
| 19685b8f95 | |||
| a9da8dc10c | |||
| fcfba7a978 | |||
| 2c8c4351dd | |||
| 79264b187d | |||
| 52eba9117b | |||
| 37a69032f6 | |||
| dd16b4f5a1 | |||
| e9c20255a6 | |||
| 630b4edf91 | |||
| 04ef638e17 | |||
| 35bad240c2 | |||
| 0cb4749cf3 | |||
| 7c8281eb00 | |||
| 1957288219 | |||
| c4d2b15c48 | |||
| beebfb0dae | |||
| d67172b795 | |||
| 2b012bc042 | |||
| 84da68f950 | |||
| ad97fd13bf | |||
| ebf6d0d5f7 | |||
| 0f3b4b4384 | |||
| 226cf46d6b | |||
| 5689e33541 | |||
| 15e59e7117 | |||
| 6b538f6662 | |||
| ad6808b696 | |||
| 8347b18efc | |||
| f92b132f0a | |||
| 79057d6757 | |||
| a9b5ee8f62 | |||
| 65da2c449e | |||
| 0ef8c12650 | |||
| 1dfa48aecb | |||
| b5d1713b43 | |||
| 102421997d | |||
| 43370bb8d1 | |||
| 310f50fcde | |||
| 9b5d157333 | |||
| 53fbb7a0bd | |||
| fe92117c4e | |||
| 4a0d13457a | |||
| 1ad6405003 | |||
| 75e7e36011 | |||
| 933c695b8c | |||
| b34f7142f6 | |||
| 7392fa8165 | |||
| 349d417869 | |||
| b8e94cac9c | |||
| cd90490b8d | |||
| a9cc94ccb6 | |||
| 17f137af09 | |||
| ea7d0bbf71 | |||
| 176f1cf405 | |||
| 2ee93bbcc1 | |||
| ba0629000e | |||
| 313049f873 | |||
| d1a1b17d29 | |||
| eb91d2ce0a | |||
| 71c77da898 | |||
| 1b96c70386 | |||
| 703cecbd4f | |||
| e20bf41cc7 | |||
| 0f7b9524f9 | |||
| 53a9e901ec | |||
| 9a79fc71ed | |||
| e7f7a8038f | |||
| 5361f8ae6f | |||
| ec3bcddc9d | |||
| 326a9f1d75 | |||
| 8c07e9b8a3 | |||
| 406d01febf | |||
| 98663422b1 | |||
| 879dfcea28 | |||
| 40edb4c8de | |||
| 07b6d142ad | |||
| 5716b3e3cf | |||
| 1b242a07e1 | |||
| 1516a73228 | |||
| eef00eb28f | |||
| a5ac282ab6 | |||
| dbb88d4999 | |||
| 39addad3a1 | |||
| 87fd9a4470 | |||
| 7a9156449f | |||
| 6e2938e9c1 | |||
| 39e1498b26 | |||
| cc2dc9c1a7 | |||
| fa87198131 | |||
| 9b70b952f6 | |||
| b29fca1cdb | |||
| 501af800ce | |||
| 979be117c6 | |||
| 13594371bb | |||
| 9628d1b27f | |||
| fd0bd0b343 | |||
| 9ea58bfdad | |||
| c54d140107 | |||
| 98d4a4213c | |||
| a7d1de5ee3 | |||
| fab9e4b265 | |||
| d5fa46033e | |||
| 32c179bbf9 | |||
| 99f9886876 | |||
| c1fcaff28a | |||
| 2da5e8d71b | |||
| e80e64a287 | |||
| 0344e51ef7 | |||
| 9fd31b0584 | |||
| c6a48f79da | |||
| 110d050cc6 | |||
| 18188ef235 | |||
| 3e1c397132 | |||
| cda1f0b77d | |||
| afd49f154c | |||
| 505c620103 | |||
| 5d9e340d6d | |||
| f705c4575c | |||
| 1508bd806f | |||
| 1c542e0615 | |||
| ed5bcbde18 | |||
| 084cbcd362 | |||
| ad8369cfe9 | |||
| 7d36b3993e | |||
| ead0854c8e | |||
| 60b5655574 | |||
| 9037630990 | |||
| ba23fc9506 | |||
| 938ca7c0ea | |||
| a034f7a9a0 | |||
| d4c4fcfd74 | |||
| 5b457f2a8a | |||
| adc22441fc | |||
| 1ba59136c6 | |||
| 77702b2ac9 | |||
| 0d6c814908 | |||
| 42ce58db60 | |||
| 1ef56033fb | |||
| 8c1d3c0248 | |||
| a725a12d25 | |||
| f67db9beed | |||
| f22fd7b5d1 | |||
| 060664260b | |||
| 9da5ec7413 | |||
| 076240f7cd | |||
| d97a4ad243 | |||
| 56209d8491 |
@@ -19,15 +19,6 @@
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Hydro11451",
|
||||
"name": "Hydrogen",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/214308559?v=4",
|
||||
"profile": "http://hydro11451.qzz.io",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "CreeperAWA",
|
||||
"name": "CreeperAWA",
|
||||
@@ -98,6 +89,15 @@
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Tayasui-rainnya",
|
||||
"name": "tayasui rainnya!",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/156585442?v=4",
|
||||
"profile": "https://github.com/Tayasui-rainnya",
|
||||
"contributions": [
|
||||
"design"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -39,14 +39,14 @@ body:
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 期望结果 | Expected Behavior
|
||||
description: 你期望的正确行为或结果 | What did you expect to happen?
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
- type: textarea
|
||||
id: extra
|
||||
attributes:
|
||||
|
||||
@@ -20,7 +20,7 @@ body:
|
||||
label: 需求动机 | Motivation
|
||||
description: 为什么需要这个功能?| Why do you need this feature?
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
- type: textarea
|
||||
id: design
|
||||
attributes:
|
||||
|
||||
@@ -26,10 +26,10 @@ jobs:
|
||||
- name: Build the Solution
|
||||
run: |
|
||||
msbuild -t:restore /p:GitFlow="Github Action"
|
||||
msbuild /p:platform="Any CPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj"
|
||||
msbuild /p:platform="AnyCPU" /p:configuration="Debug" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj"
|
||||
|
||||
- name: Upload to artifact
|
||||
uses: actions/upload-artifact@v4.5.0
|
||||
with:
|
||||
name: InkCanvasForClass
|
||||
path: "Ink Canvas/bin/Any CPU/Release/net472/"
|
||||
path: "Ink Canvas/bin/Debug/net472"
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
name: Pre-release and Changelog
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_type:
|
||||
description: 'Version bump type'
|
||||
required: true
|
||||
default: 'patch'
|
||||
type: choice
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
prerelease:
|
||||
description: 'Create as pre-release'
|
||||
required: true
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
prerelease:
|
||||
if: github.ref == 'refs/heads/main'
|
||||
runs-on: windows-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4.2.2
|
||||
with:
|
||||
fetch-depth: 0 # 获取所有历史记录用于生成changelog
|
||||
|
||||
- name: Setup MSbuild
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Setup NuGet
|
||||
uses: NuGet/setup-nuget@v2.0.1
|
||||
|
||||
- name: Restore NuGet Packages
|
||||
run: nuget restore "Ink Canvas.sln"
|
||||
|
||||
- name: Get current version from Git tag
|
||||
id: get_version
|
||||
run: |
|
||||
# 获取最新的tag
|
||||
$latestTag = git describe --tags --abbrev=0 2>$null
|
||||
if ($latestTag) {
|
||||
# 移除v前缀(如果有的话)
|
||||
$version = $latestTag -replace "^v", ""
|
||||
echo "Found latest tag: $latestTag"
|
||||
} else {
|
||||
# 如果没有tag,使用默认版本
|
||||
$version = "1.0.0"
|
||||
echo "No tags found, using default version"
|
||||
}
|
||||
echo "current_version=$version" >> $env:GITHUB_OUTPUT
|
||||
echo "Current version: $version"
|
||||
|
||||
- name: Calculate new version
|
||||
id: calc_version
|
||||
run: |
|
||||
$currentVersion = "${{ steps.get_version.outputs.current_version }}"
|
||||
$versionParts = $currentVersion.Split('.')
|
||||
|
||||
# 确保版本号格式正确(至少3部分)
|
||||
if ($versionParts.Length -ge 3) {
|
||||
$major = [int]$versionParts[0]
|
||||
$minor = [int]$versionParts[1]
|
||||
$patch = [int]$versionParts[2]
|
||||
} else {
|
||||
# 如果版本号格式不正确,使用默认值
|
||||
$major = 1
|
||||
$minor = 0
|
||||
$patch = 0
|
||||
}
|
||||
|
||||
$versionType = "${{ github.event.inputs.version_type }}"
|
||||
|
||||
switch ($versionType) {
|
||||
"major" {
|
||||
$major++
|
||||
$minor = 0
|
||||
$patch = 0
|
||||
}
|
||||
"minor" {
|
||||
$minor++
|
||||
$patch = 0
|
||||
}
|
||||
"patch" {
|
||||
$patch++
|
||||
}
|
||||
}
|
||||
|
||||
# 生成新版本号(保持3位格式,如1.7.13)
|
||||
$newVersion = "$major.$minor.$patch"
|
||||
echo "new_version=$newVersion" >> $env:GITHUB_OUTPUT
|
||||
echo "New version: $newVersion"
|
||||
|
||||
- name: Generate Changelog
|
||||
id: changelog
|
||||
run: |
|
||||
# 获取上次tag到现在的所有commit
|
||||
$lastTag = git describe --tags --abbrev=0 2>$null
|
||||
if ($lastTag) {
|
||||
$commits = git log --pretty=format:"%h|%s|%an|%ad" --date=short "$lastTag..HEAD"
|
||||
} else {
|
||||
$commits = git log --pretty=format:"%h|%s|%an|%ad" --date=short
|
||||
}
|
||||
|
||||
# 初始化分类数组
|
||||
$fixes = @()
|
||||
$improvements = @()
|
||||
$additions = @()
|
||||
$deletions = @()
|
||||
$versionChanges = @()
|
||||
$others = @()
|
||||
|
||||
# 解析每个commit
|
||||
foreach ($commit in $commits) {
|
||||
if ($commit -match "^([^|]+)\|([^|]+)\|([^|]+)\|([^|]+)$") {
|
||||
$hash = $matches[1]
|
||||
$message = $matches[2]
|
||||
$author = $matches[3]
|
||||
$date = $matches[4]
|
||||
|
||||
$commitInfo = @{
|
||||
Hash = $hash
|
||||
Message = $message
|
||||
Author = $author
|
||||
Date = $date
|
||||
}
|
||||
|
||||
# 根据commit消息分类
|
||||
if ($message -match "^(fix|修复)") {
|
||||
$fixes += $commitInfo
|
||||
} elseif ($message -match "^(improve|改进|优化)") {
|
||||
$improvements += $commitInfo
|
||||
} elseif ($message -match "^(add|新增|添加)") {
|
||||
$additions += $commitInfo
|
||||
} elseif ($message -match "^(delete|删除|移除)") {
|
||||
$deletions += $commitInfo
|
||||
} elseif ($message -match "(版本|version|更新版本号)") {
|
||||
$versionChanges += $commitInfo
|
||||
} else {
|
||||
$others += $commitInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 生成changelog内容
|
||||
$version = "${{ steps.calc_version.outputs.new_version }}"
|
||||
$changelog = "# ICC CE $version.0 更新日志`n`n## 修复 (Fixes)"
|
||||
|
||||
if ($fixes.Count -gt 0) {
|
||||
foreach ($fix in $fixes) {
|
||||
$changelog += "`n- $($fix.Message) ($($fix.Author), $($fix.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n## 改进 (Improvements)"
|
||||
|
||||
if ($improvements.Count -gt 0) {
|
||||
foreach ($improvement in $improvements) {
|
||||
$changelog += "`n- $($improvement.Message) ($($improvement.Author), $($improvement.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n## 新增功能 (New Features)"
|
||||
|
||||
if ($additions.Count -gt 0) {
|
||||
foreach ($addition in $additions) {
|
||||
$changelog += "`n- $($addition.Message) ($($addition.Author), $($addition.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n## 删除功能 (Removed Features)"
|
||||
|
||||
if ($deletions.Count -gt 0) {
|
||||
foreach ($deletion in $deletions) {
|
||||
$changelog += "`n- $($deletion.Message) ($($deletion.Author), $($deletion.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n## 版本更新 (Version Updates)"
|
||||
|
||||
if ($versionChanges.Count -gt 0) {
|
||||
foreach ($versionChange in $versionChanges) {
|
||||
$changelog += "`n- $($versionChange.Message) ($($versionChange.Author), $($versionChange.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n## 其他更改 (Other Changes)"
|
||||
|
||||
if ($others.Count -gt 0) {
|
||||
foreach ($other in $others) {
|
||||
$changelog += "`n- $($other.Message) ($($other.Author), $($other.Date))"
|
||||
}
|
||||
} else {
|
||||
$changelog += "`n- 无"
|
||||
}
|
||||
|
||||
$changelog += "`n`n---`n*此更新日志由GitHub Actions自动生成*"
|
||||
|
||||
# 保存changelog到文件
|
||||
$changelog | Out-File -FilePath "CHANGELOG_${{ steps.calc_version.outputs.new_version }}.md" -Encoding UTF8
|
||||
|
||||
# 输出changelog内容到步骤输出
|
||||
echo "changelog<<EOF" >> $env:GITHUB_OUTPUT
|
||||
echo $changelog >> $env:GITHUB_OUTPUT
|
||||
echo "EOF" >> $env:GITHUB_OUTPUT
|
||||
|
||||
echo "Changelog generated successfully"
|
||||
|
||||
- name: Display version info
|
||||
run: |
|
||||
echo "Current version: ${{ steps.get_version.outputs.current_version }}"
|
||||
echo "New version: ${{ steps.calc_version.outputs.new_version }}"
|
||||
echo "Note: Version will not be automatically updated in repository files"
|
||||
|
||||
- name: Build the Solution
|
||||
run: |
|
||||
msbuild -t:restore /p:GitFlow="Github Action"
|
||||
msbuild /p:platform="AnyCPU" /p:configuration="Release" /p:GitFlow="Github Action" "Ink Canvas/InkCanvasForClass.csproj"
|
||||
|
||||
- name: Create Release Archive
|
||||
run: |
|
||||
$version = "${{ steps.calc_version.outputs.new_version }}"
|
||||
$archiveName = "InkCanvasForClass.CE.$version.0.zip"
|
||||
|
||||
# 创建发布目录
|
||||
New-Item -ItemType Directory -Path "release" -Force
|
||||
|
||||
# 复制发布文件
|
||||
Copy-Item "Ink Canvas\bin\Release\net472\*" "release\" -Recurse -Force
|
||||
|
||||
# 创建压缩包
|
||||
Compress-Archive -Path "release\*" -DestinationPath $archiveName -Force
|
||||
|
||||
echo "archive_name=$archiveName" >> $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Upload Release Assets
|
||||
uses: actions/upload-artifact@v4.5.0
|
||||
with:
|
||||
name: release-files-${{ steps.calc_version.outputs.new_version }}
|
||||
path: |
|
||||
InkCanvasForClass.CE.${{ steps.calc_version.outputs.new_version }}.0.zip
|
||||
CHANGELOG_${{ steps.calc_version.outputs.new_version }}.md
|
||||
|
||||
- name: Prepare Release Info
|
||||
run: |
|
||||
$version = "${{ steps.calc_version.outputs.new_version }}"
|
||||
echo "Preparing release for version: $version"
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
tag_name: ${{ steps.calc_version.outputs.new_version }}.0
|
||||
name: ICC CE ${{ steps.calc_version.outputs.new_version }}.0
|
||||
body: |
|
||||
${{ steps.changelog.outputs.changelog }}
|
||||
draft: false
|
||||
prerelease: ${{ github.event.inputs.prerelease }}
|
||||
files: |
|
||||
InkCanvasForClass.CE.${{ steps.calc_version.outputs.new_version }}.0.zip
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Generate UpdateLog preview
|
||||
run: |
|
||||
$version = "${{ steps.calc_version.outputs.new_version }}"
|
||||
$changelogFile = "CHANGELOG_$version.md"
|
||||
|
||||
# 读取生成的changelog
|
||||
$changelogContent = Get-Content $changelogFile -Raw
|
||||
|
||||
# 生成预览内容
|
||||
$previewContent = "ICC CE $version.0 更新日志`n" + $changelogContent
|
||||
|
||||
echo "UpdateLog preview generated (not written to file):"
|
||||
echo $previewContent
|
||||
|
||||
- name: Display Summary
|
||||
run: |
|
||||
echo "=== Release Summary ==="
|
||||
echo "Version: ${{ steps.calc_version.outputs.new_version }}"
|
||||
echo "Pre-release: ${{ github.event.inputs.prerelease }}"
|
||||
echo "Changelog: Generated and attached to release"
|
||||
echo "Archive: ICC_CE_${{ steps.calc_version.outputs.new_version }}.zip"
|
||||
echo ""
|
||||
echo "Note: No repository files were modified"
|
||||
echo "You can manually update version numbers and changelog as needed"
|
||||
@@ -1,4 +1,428 @@
|
||||
obj/
|
||||
bin/
|
||||
## Ignore Visual Studio temporary files, build results, and
|
||||
## files generated by popular Visual Studio add-ons.
|
||||
##
|
||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
*.user
|
||||
*.userosscache
|
||||
*.sln.docstates
|
||||
*.env
|
||||
|
||||
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||
*.userprefs
|
||||
|
||||
# Mono auto generated files
|
||||
mono_crash.*
|
||||
|
||||
# Build results
|
||||
[Dd]ebug/
|
||||
[Dd]ebugPublic/
|
||||
[Rr]elease/
|
||||
[Rr]eleases/
|
||||
|
||||
[Dd]ebug/x64/
|
||||
[Dd]ebugPublic/x64/
|
||||
[Rr]elease/x64/
|
||||
[Rr]eleases/x64/
|
||||
bin/x64/
|
||||
obj/x64/
|
||||
|
||||
[Dd]ebug/x86/
|
||||
[Dd]ebugPublic/x86/
|
||||
[Rr]elease/x86/
|
||||
[Rr]eleases/x86/
|
||||
bin/x86/
|
||||
obj/x86/
|
||||
|
||||
[Ww][Ii][Nn]32/
|
||||
[Aa][Rr][Mm]/
|
||||
[Aa][Rr][Mm]64/
|
||||
[Aa][Rr][Mm]64[Ee][Cc]/
|
||||
bld/
|
||||
[Oo]bj/
|
||||
[Oo]ut/
|
||||
[Ll]og/
|
||||
[Ll]ogs/
|
||||
|
||||
# Build results on 'Bin' directories
|
||||
**/[Bb]in/*
|
||||
# Uncomment if you have tasks that rely on *.refresh files to move binaries
|
||||
# (https://github.com/github/gitignore/pull/3736)
|
||||
#!**/[Bb]in/*.refresh
|
||||
|
||||
# Visual Studio 2015/2017 cache/options directory
|
||||
.vs/
|
||||
/Ink Canvas/obj
|
||||
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||
#wwwroot/
|
||||
|
||||
# Visual Studio 2017 auto generated files
|
||||
Generated\ Files/
|
||||
|
||||
# MSTest test Results
|
||||
[Tt]est[Rr]esult*/
|
||||
[Bb]uild[Ll]og.*
|
||||
*.trx
|
||||
|
||||
# NUnit
|
||||
*.VisualState.xml
|
||||
TestResult.xml
|
||||
nunit-*.xml
|
||||
|
||||
# Approval Tests result files
|
||||
*.received.*
|
||||
|
||||
# Build Results of an ATL Project
|
||||
[Dd]ebugPS/
|
||||
[Rr]eleasePS/
|
||||
dlldata.c
|
||||
|
||||
# Benchmark Results
|
||||
BenchmarkDotNet.Artifacts/
|
||||
|
||||
# .NET Core
|
||||
project.lock.json
|
||||
project.fragment.lock.json
|
||||
artifacts/
|
||||
|
||||
# ASP.NET Scaffolding
|
||||
ScaffoldingReadMe.txt
|
||||
|
||||
# StyleCop
|
||||
StyleCopReport.xml
|
||||
|
||||
# Files built by Visual Studio
|
||||
*_i.c
|
||||
*_p.c
|
||||
*_h.h
|
||||
*.ilk
|
||||
*.meta
|
||||
*.obj
|
||||
*.idb
|
||||
*.iobj
|
||||
*.pch
|
||||
*.pdb
|
||||
*.ipdb
|
||||
*.pgc
|
||||
*.pgd
|
||||
*.rsp
|
||||
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||
!Directory.Build.rsp
|
||||
*.sbr
|
||||
*.tlb
|
||||
*.tli
|
||||
*.tlh
|
||||
*.tmp
|
||||
*.tmp_proj
|
||||
*_wpftmp.csproj
|
||||
*.log
|
||||
*.tlog
|
||||
*.vspscc
|
||||
*.vssscc
|
||||
.builds
|
||||
*.pidb
|
||||
*.svclog
|
||||
*.scc
|
||||
|
||||
# Chutzpah Test files
|
||||
_Chutzpah*
|
||||
|
||||
# Visual C++ cache files
|
||||
ipch/
|
||||
*.aps
|
||||
*.ncb
|
||||
*.opendb
|
||||
*.opensdf
|
||||
*.sdf
|
||||
*.cachefile
|
||||
*.VC.db
|
||||
*.VC.VC.opendb
|
||||
|
||||
# Visual Studio profiler
|
||||
*.psess
|
||||
*.vsp
|
||||
*.vspx
|
||||
*.sap
|
||||
|
||||
# Visual Studio Trace Files
|
||||
*.e2e
|
||||
|
||||
# TFS 2012 Local Workspace
|
||||
$tf/
|
||||
|
||||
# Guidance Automation Toolkit
|
||||
*.gpState
|
||||
|
||||
# ReSharper is a .NET coding add-in
|
||||
_ReSharper*/
|
||||
*.[Rr]e[Ss]harper
|
||||
*.DotSettings.user
|
||||
|
||||
# TeamCity is a build add-in
|
||||
_TeamCity*
|
||||
|
||||
# DotCover is a Code Coverage Tool
|
||||
*.dotCover
|
||||
|
||||
# AxoCover is a Code Coverage Tool
|
||||
.axoCover/*
|
||||
!.axoCover/settings.json
|
||||
|
||||
# Coverlet is a free, cross platform Code Coverage Tool
|
||||
coverage*.json
|
||||
coverage*.xml
|
||||
coverage*.info
|
||||
|
||||
# Visual Studio code coverage results
|
||||
*.coverage
|
||||
*.coveragexml
|
||||
|
||||
# NCrunch
|
||||
_NCrunch_*
|
||||
.NCrunch_*
|
||||
.*crunch*.local.xml
|
||||
nCrunchTemp_*
|
||||
|
||||
# MightyMoose
|
||||
*.mm.*
|
||||
AutoTest.Net/
|
||||
|
||||
# Web workbench (sass)
|
||||
.sass-cache/
|
||||
|
||||
# Installshield output folder
|
||||
[Ee]xpress/
|
||||
|
||||
# DocProject is a documentation generator add-in
|
||||
DocProject/buildhelp/
|
||||
DocProject/Help/*.HxT
|
||||
DocProject/Help/*.HxC
|
||||
DocProject/Help/*.hhc
|
||||
DocProject/Help/*.hhk
|
||||
DocProject/Help/*.hhp
|
||||
DocProject/Help/Html2
|
||||
DocProject/Help/html
|
||||
|
||||
# Click-Once directory
|
||||
publish/
|
||||
|
||||
# Publish Web Output
|
||||
*.[Pp]ublish.xml
|
||||
*.azurePubxml
|
||||
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||
# but database connection strings (with potential passwords) will be unencrypted
|
||||
*.pubxml
|
||||
*.publishproj
|
||||
|
||||
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||
# in these scripts will be unencrypted
|
||||
PublishScripts/
|
||||
|
||||
# NuGet Packages
|
||||
*.nupkg
|
||||
# NuGet Symbol Packages
|
||||
*.snupkg
|
||||
# The packages folder can be ignored because of Package Restore
|
||||
**/[Pp]ackages/*
|
||||
# except build/, which is used as an MSBuild target.
|
||||
!**/[Pp]ackages/build/
|
||||
# Uncomment if necessary however generally it will be regenerated when needed
|
||||
#!**/[Pp]ackages/repositories.config
|
||||
# NuGet v3's project.json files produces more ignorable files
|
||||
*.nuget.props
|
||||
*.nuget.targets
|
||||
|
||||
# Microsoft Azure Build Output
|
||||
csx/
|
||||
*.build.csdef
|
||||
|
||||
# Microsoft Azure Emulator
|
||||
ecf/
|
||||
rcf/
|
||||
|
||||
# Windows Store app package directories and files
|
||||
AppPackages/
|
||||
BundleArtifacts/
|
||||
Package.StoreAssociation.xml
|
||||
_pkginfo.txt
|
||||
*.appx
|
||||
*.appxbundle
|
||||
*.appxupload
|
||||
|
||||
# Visual Studio cache files
|
||||
# files ending in .cache can be ignored
|
||||
*.[Cc]ache
|
||||
# but keep track of directories ending in .cache
|
||||
!?*.[Cc]ache/
|
||||
|
||||
# Others
|
||||
ClientBin/
|
||||
~$*
|
||||
*~
|
||||
*.dbmdl
|
||||
*.dbproj.schemaview
|
||||
*.jfm
|
||||
*.pfx
|
||||
*.publishsettings
|
||||
orleans.codegen.cs
|
||||
|
||||
# Including strong name files can present a security risk
|
||||
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||
#*.snk
|
||||
|
||||
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||
#bower_components/
|
||||
|
||||
# RIA/Silverlight projects
|
||||
Generated_Code/
|
||||
|
||||
# Backup & report files from converting an old project file
|
||||
# to a newer Visual Studio version. Backup files are not needed,
|
||||
# because we have git ;-)
|
||||
_UpgradeReport_Files/
|
||||
Backup*/
|
||||
UpgradeLog*.XML
|
||||
UpgradeLog*.htm
|
||||
ServiceFabricBackup/
|
||||
*.rptproj.bak
|
||||
|
||||
# SQL Server files
|
||||
*.mdf
|
||||
*.ldf
|
||||
*.ndf
|
||||
|
||||
# Business Intelligence projects
|
||||
*.rdl.data
|
||||
*.bim.layout
|
||||
*.bim_*.settings
|
||||
*.rptproj.rsuser
|
||||
*- [Bb]ackup.rdl
|
||||
*- [Bb]ackup ([0-9]).rdl
|
||||
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||
|
||||
# Microsoft Fakes
|
||||
FakesAssemblies/
|
||||
|
||||
# GhostDoc plugin setting file
|
||||
*.GhostDoc.xml
|
||||
|
||||
# Node.js Tools for Visual Studio
|
||||
.ntvs_analysis.dat
|
||||
node_modules/
|
||||
|
||||
# Visual Studio 6 build log
|
||||
*.plg
|
||||
|
||||
# Visual Studio 6 workspace options file
|
||||
*.opt
|
||||
|
||||
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||
*.vbw
|
||||
|
||||
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||
*.dsw
|
||||
*.dsp
|
||||
|
||||
# Visual Studio 6 technical files
|
||||
*.ncb
|
||||
*.aps
|
||||
|
||||
# Visual Studio LightSwitch build output
|
||||
**/*.HTMLClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/GeneratedArtifacts
|
||||
**/*.DesktopClient/ModelManifest.xml
|
||||
**/*.Server/GeneratedArtifacts
|
||||
**/*.Server/ModelManifest.xml
|
||||
_Pvt_Extensions
|
||||
|
||||
# Paket dependency manager
|
||||
**/.paket/paket.exe
|
||||
paket-files/
|
||||
|
||||
# FAKE - F# Make
|
||||
**/.fake/
|
||||
|
||||
# CodeRush personal settings
|
||||
**/.cr/personal
|
||||
|
||||
# Python Tools for Visual Studio (PTVS)
|
||||
**/__pycache__/
|
||||
*.pyc
|
||||
|
||||
# Cake - Uncomment if you are using it
|
||||
#tools/**
|
||||
#!tools/packages.config
|
||||
|
||||
# Tabs Studio
|
||||
*.tss
|
||||
|
||||
# Telerik's JustMock configuration file
|
||||
*.jmconfig
|
||||
|
||||
# BizTalk build output
|
||||
*.btp.cs
|
||||
*.btm.cs
|
||||
*.odx.cs
|
||||
*.xsd.cs
|
||||
|
||||
# OpenCover UI analysis results
|
||||
OpenCover/
|
||||
|
||||
# Azure Stream Analytics local run output
|
||||
ASALocalRun/
|
||||
|
||||
# MSBuild Binary and Structured Log
|
||||
*.binlog
|
||||
MSBuild_Logs/
|
||||
|
||||
# AWS SAM Build and Temporary Artifacts folder
|
||||
.aws-sam
|
||||
|
||||
# NVidia Nsight GPU debugger configuration file
|
||||
*.nvuser
|
||||
|
||||
# MFractors (Xamarin productivity tool) working folder
|
||||
**/.mfractor/
|
||||
|
||||
# Local History for Visual Studio
|
||||
**/.localhistory/
|
||||
|
||||
# Visual Studio History (VSHistory) files
|
||||
.vshistory/
|
||||
|
||||
# BeatPulse healthcheck temp database
|
||||
healthchecksdb
|
||||
|
||||
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||
MigrationBackup/
|
||||
|
||||
# Ionide (cross platform F# VS Code tools) working folder
|
||||
**/.ionide/
|
||||
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
# VS Code files for those working on multiple tools
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
!.vscode/*.code-snippets
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Built Visual Studio Code Extensions
|
||||
*.vsix
|
||||
|
||||
# Windows Installer files from build outputs
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
@@ -1 +1 @@
|
||||
1.7.10.0
|
||||
1.7.16.0
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:String x:Key="/Default/CodeInspection/PencilsConfiguration/ActualSeverity/@EntryValue">WARNING</s:String>
|
||||
<s:String x:Key="/Default/Environment/Hierarchy/Build/BuildTool/CustomBuildToolPath/@EntryValue">C:\Program Files\Microsoft Visual Studio\2022\Community\MSBuild\Current\Bin\amd64\MSBuild.exe</s:String>
|
||||
<s:Int64 x:Key="/Default/Environment/Hierarchy/Build/BuildTool/MsbuildVersion/@EntryValue">1114112</s:Int64></wpf:ResourceDictionary>
|
||||
@@ -32,7 +32,7 @@
|
||||
</MenuItem.Icon>
|
||||
</MenuItem>
|
||||
<Separator Margin="0,3" />
|
||||
<MenuItem>
|
||||
<MenuItem Name="DisableAllHotkeysMenuItem" Click="DisableAllHotkeysMenuItem_Clicked">
|
||||
<MenuItem.Header>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
<TextBlock FontSize="14" VerticalAlignment="Center" Foreground="#18181b" Text="禁用所有快捷键" />
|
||||
@@ -232,12 +232,11 @@
|
||||
ContextMenu="{StaticResource SysTrayMenu}"
|
||||
IconSource="/Resources/icc.ico"/>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ui:ThemeResources RequestedTheme="Light"/>
|
||||
<ui:ThemeResources/>
|
||||
<ui:XamlControlsResources />
|
||||
<ResourceDictionary Source="Resources/SeewoImageDictionary.xaml"/>
|
||||
<ResourceDictionary Source="Resources/DrawShapeImageDictionary.xaml"/>
|
||||
<ResourceDictionary Source="Resources/IconImageDictionary.xaml"/>
|
||||
<ResourceDictionary Source="Resources/Styles/Light.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
|
||||
@@ -10,7 +10,7 @@ using System.Windows;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("CJK_mkp")]
|
||||
[assembly: AssemblyProduct("InkCanvasForClass")]
|
||||
[assembly: AssemblyCopyright("Copyright © HARKOTEK Studio 2024")]
|
||||
[assembly: AssemblyCopyright("Copyright © CJK_mkp 2025")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -49,5 +49,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.10.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.10.0")]
|
||||
[assembly: AssemblyVersion("1.7.16.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.16.0")]
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Ink_Canvas.Converters
|
||||
{
|
||||
/// <summary>
|
||||
/// 位置计算转换器
|
||||
/// </summary>
|
||||
public class PositionConverters
|
||||
{
|
||||
/// <summary>
|
||||
/// 减法转换器
|
||||
/// </summary>
|
||||
public class SubtractConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is double baseValue && parameter is string paramStr)
|
||||
{
|
||||
if (double.TryParse(paramStr, out double subtractValue))
|
||||
{
|
||||
return baseValue - subtractValue;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
||||
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
|
||||
<xs:element name="Weavers">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
|
||||
<xs:complexType>
|
||||
<xs:all>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX86Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinArm64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCompression" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableCleanup" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="DisableEventSubscription" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UnmanagedWinX86Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UnmanagedWinX64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="UnmanagedWinArm64Assemblies" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="PreloadOrder" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:all>
|
||||
<xs:attribute name="VerifyAssembly" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
|
||||
<xs:annotation>
|
||||
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
<xs:attribute name="GenerateXsd" type="xs:boolean">
|
||||
<xs:annotation>
|
||||
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
|
||||
</xs:annotation>
|
||||
</xs:attribute>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
</xs:schema>
|
||||
@@ -95,18 +95,22 @@ namespace Ink_Canvas.Helpers
|
||||
// 使用改进的贝塞尔曲线拟合
|
||||
var smoothedPoints = ApplyImprovedBezierSmoothing(originalPoints);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 原始点数={originalPoints.Length}, 平滑后点数={smoothedPoints.Length}");
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// 严格控制点数,避免产生过多点
|
||||
if (smoothedPoints.Length > originalPoints.Length * 2)
|
||||
// 放宽点数限制
|
||||
if (smoothedPoints.Length > originalPoints.Length * 3.0)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 点数过多,进行重采样");
|
||||
// 如果点数增加太多,进行重采样
|
||||
smoothedPoints = ResampleEquidistantOptimized(smoothedPoints, ResampleInterval);
|
||||
}
|
||||
|
||||
// 最终检查:确保点数不会过多
|
||||
if (smoothedPoints.Length > originalPoints.Length * 1.5)
|
||||
// 进一步放宽最终检查
|
||||
if (smoothedPoints.Length > originalPoints.Length * 2.5)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 重采样后点数仍然过多,返回原始笔画");
|
||||
// 如果仍然太多点,使用原始笔画
|
||||
return stroke;
|
||||
}
|
||||
@@ -117,6 +121,7 @@ namespace Ink_Canvas.Helpers
|
||||
DrawingAttributes = stroke.DrawingAttributes.Clone()
|
||||
};
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"AsyncAdvancedBezierSmoothing: 成功创建平滑笔画");
|
||||
return smoothedStroke;
|
||||
}
|
||||
|
||||
@@ -125,33 +130,42 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
private StylusPoint[] ApplyImprovedBezierSmoothing(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 4) return points;
|
||||
if (points.Length < 6) return points; // 5次贝塞尔需要6个点
|
||||
|
||||
var result = new List<StylusPoint>();
|
||||
|
||||
// 添加第一个点
|
||||
result.Add(points[0]);
|
||||
|
||||
// 使用非重叠的窗口进行贝塞尔曲线拟合
|
||||
for (int i = 0; i < points.Length - 3; i += 3) // 每次移动3个点,避免重叠
|
||||
// 使用5次贝塞尔曲线,每次移动1个点确保连续性
|
||||
for (int i = 0; i < points.Length - 5; i++)
|
||||
{
|
||||
var p0 = points[i];
|
||||
var p1 = points[Math.Min(i + 1, points.Length - 1)];
|
||||
var p2 = points[Math.Min(i + 2, points.Length - 1)];
|
||||
var p3 = points[Math.Min(i + 3, points.Length - 1)];
|
||||
var p1 = points[i + 1];
|
||||
var p2 = points[i + 2];
|
||||
var p3 = points[i + 3];
|
||||
var p4 = points[i + 4];
|
||||
var p5 = points[i + 5];
|
||||
|
||||
// 计算改进的控制点
|
||||
var controlPoints = CalculateImprovedControlPoints(p0, p1, p2, p3);
|
||||
// 计算5次贝塞尔的控制点
|
||||
var controlPoints = CalculateQuinticControlPoints(p0, p1, p2, p3, p4, p5);
|
||||
|
||||
// 限制插值步数,避免点数爆炸
|
||||
int steps = Math.Min(UseAdaptiveInterpolation ?
|
||||
CalculateAdaptiveSteps(p0, p1, p2, p3) : InterpolationSteps, 16);
|
||||
|
||||
// 生成贝塞尔曲线点,但跳过第一个点避免重复
|
||||
for (int j = 1; j <= steps; j++)
|
||||
// 生成插值点
|
||||
if (i == 0)
|
||||
{
|
||||
double t = (double)j / steps;
|
||||
var bezierPoint = CubicBezierWithControlPoints(controlPoints, t, p0, p3);
|
||||
// 第一个窗口:生成更多插值点
|
||||
for (int j = 1; j <= 4; j++)
|
||||
{
|
||||
double t = (double)j / 5;
|
||||
var bezierPoint = CalculateQuinticBezierPoint(p0, controlPoints, p5, t);
|
||||
result.Add(bezierPoint);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 后续窗口:只生成最后一个插值点,避免重复
|
||||
double t = 4.0 / 5.0; // 只取最后一个插值点
|
||||
var bezierPoint = CalculateQuinticBezierPoint(p0, controlPoints, p5, t);
|
||||
result.Add(bezierPoint);
|
||||
}
|
||||
}
|
||||
@@ -159,8 +173,209 @@ namespace Ink_Canvas.Helpers
|
||||
// 添加最后一个点
|
||||
result.Add(points[points.Length - 1]);
|
||||
|
||||
// 去重和优化点数
|
||||
return RemoveDuplicatePoints(result.ToArray());
|
||||
System.Diagnostics.Debug.WriteLine($"ApplyImprovedBezierSmoothing: 原始点数={points.Length}, 生成点数={result.Count}");
|
||||
|
||||
// 使用更宽松的去重
|
||||
return RemoveDuplicatePointsLoose(result.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 5次贝塞尔曲线控制点计算
|
||||
/// </summary>
|
||||
private (Point cp1, Point cp2, Point cp3, Point cp4) CalculateQuinticControlPoints(
|
||||
StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, StylusPoint p4, StylusPoint p5)
|
||||
{
|
||||
// 计算控制点距离(基于相邻点距离)
|
||||
double dist1 = Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
|
||||
double dist2 = Math.Sqrt((p2.X - p1.X) * (p2.X - p1.X) + (p2.Y - p1.Y) * (p2.Y - p1.Y));
|
||||
double dist3 = Math.Sqrt((p4.X - p3.X) * (p4.X - p3.X) + (p4.Y - p3.Y) * (p4.Y - p3.Y));
|
||||
double dist4 = Math.Sqrt((p5.X - p4.X) * (p5.X - p4.X) + (p5.Y - p4.Y) * (p5.Y - p4.Y));
|
||||
|
||||
// 使用更小的控制点距离,产生超平滑的曲线
|
||||
double controlDist1 = dist1 * 0.15;
|
||||
double controlDist2 = dist2 * 0.15;
|
||||
double controlDist3 = dist3 * 0.15;
|
||||
double controlDist4 = dist4 * 0.15;
|
||||
|
||||
// 计算控制点方向 - 使用更平滑的方向计算
|
||||
double dir1X = p2.X - p0.X;
|
||||
double dir1Y = p2.Y - p0.Y;
|
||||
double dir2X = p3.X - p1.X;
|
||||
double dir2Y = p3.Y - p1.Y;
|
||||
double dir3X = p4.X - p2.X;
|
||||
double dir3Y = p4.Y - p2.Y;
|
||||
double dir4X = p5.X - p3.X;
|
||||
double dir4Y = p5.Y - p3.Y;
|
||||
|
||||
// 归一化方向
|
||||
NormalizeVector(ref dir1X, ref dir1Y);
|
||||
NormalizeVector(ref dir2X, ref dir2Y);
|
||||
NormalizeVector(ref dir3X, ref dir3Y);
|
||||
NormalizeVector(ref dir4X, ref dir4Y);
|
||||
|
||||
// 计算控制点
|
||||
var cp1 = new Point(p1.X + dir1X * controlDist1, p1.Y + dir1Y * controlDist1);
|
||||
var cp2 = new Point(p2.X + dir2X * controlDist2, p2.Y + dir2Y * controlDist2);
|
||||
var cp3 = new Point(p3.X - dir3X * controlDist3, p3.Y - dir3Y * controlDist3);
|
||||
var cp4 = new Point(p4.X - dir4X * controlDist4, p4.Y - dir4Y * controlDist4);
|
||||
|
||||
return (cp1, cp2, cp3, cp4);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 归一化向量
|
||||
/// </summary>
|
||||
private void NormalizeVector(ref double x, ref double y)
|
||||
{
|
||||
double length = Math.Sqrt(x * x + y * y);
|
||||
if (length > 0)
|
||||
{
|
||||
x /= length;
|
||||
y /= length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 5次贝塞尔曲线点计算
|
||||
/// </summary>
|
||||
private StylusPoint CalculateQuinticBezierPoint(StylusPoint p0, (Point cp1, Point cp2, Point cp3, Point cp4) controlPoints, StylusPoint p5, double t)
|
||||
{
|
||||
double oneMinusT = 1 - t;
|
||||
double oneMinusT2 = oneMinusT * oneMinusT;
|
||||
double oneMinusT3 = oneMinusT2 * oneMinusT;
|
||||
double oneMinusT4 = oneMinusT3 * oneMinusT;
|
||||
double oneMinusT5 = oneMinusT4 * oneMinusT;
|
||||
|
||||
double t2 = t * t;
|
||||
double t3 = t2 * t;
|
||||
double t4 = t3 * t;
|
||||
double t5 = t4 * t;
|
||||
|
||||
// 5次贝塞尔曲线公式
|
||||
double x = oneMinusT5 * p0.X +
|
||||
5 * oneMinusT4 * t * controlPoints.cp1.X +
|
||||
10 * oneMinusT3 * t2 * controlPoints.cp2.X +
|
||||
10 * oneMinusT2 * t3 * controlPoints.cp3.X +
|
||||
5 * oneMinusT * t4 * controlPoints.cp4.X +
|
||||
t5 * p5.X;
|
||||
|
||||
double y = oneMinusT5 * p0.Y +
|
||||
5 * oneMinusT4 * t * controlPoints.cp1.Y +
|
||||
10 * oneMinusT3 * t2 * controlPoints.cp2.Y +
|
||||
10 * oneMinusT2 * t3 * controlPoints.cp3.Y +
|
||||
5 * oneMinusT * t4 * controlPoints.cp4.Y +
|
||||
t5 * p5.Y;
|
||||
|
||||
// 压力插值 - 使用线性插值
|
||||
float pressure = (float)((1 - t) * p0.PressureFactor + t * p5.PressureFactor);
|
||||
|
||||
return new StylusPoint(x, y, Math.Max(pressure, 0.1f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简化的控制点计算
|
||||
/// </summary>
|
||||
private (Point cp1, Point cp2) CalculateSimpleControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
// 计算控制点距离(基于线段长度)
|
||||
double dist1 = Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
|
||||
double dist2 = Math.Sqrt((p3.X - p2.X) * (p3.X - p2.X) + (p3.Y - p2.Y) * (p3.Y - p2.Y));
|
||||
|
||||
// 使用更小的控制点距离,产生更平滑的曲线
|
||||
double controlDist1 = dist1 * 0.2; // 进一步减少控制点影响
|
||||
double controlDist2 = dist2 * 0.2;
|
||||
|
||||
// 计算控制点方向 - 使用更平滑的方向计算
|
||||
double dir1X = p2.X - p0.X; // 使用更远的点计算方向
|
||||
double dir1Y = p2.Y - p0.Y;
|
||||
double dir2X = p3.X - p1.X;
|
||||
double dir2Y = p3.Y - p1.Y;
|
||||
|
||||
// 归一化方向
|
||||
double len1 = Math.Sqrt(dir1X * dir1X + dir1Y * dir1Y);
|
||||
double len2 = Math.Sqrt(dir2X * dir2X + dir2Y * dir2Y);
|
||||
|
||||
if (len1 > 0)
|
||||
{
|
||||
dir1X /= len1;
|
||||
dir1Y /= len1;
|
||||
}
|
||||
|
||||
if (len2 > 0)
|
||||
{
|
||||
dir2X /= len2;
|
||||
dir2Y /= len2;
|
||||
}
|
||||
|
||||
// 计算控制点
|
||||
var cp1 = new Point(
|
||||
p1.X + dir1X * controlDist1,
|
||||
p1.Y + dir1Y * controlDist1
|
||||
);
|
||||
|
||||
var cp2 = new Point(
|
||||
p2.X - dir2X * controlDist2,
|
||||
p2.Y - dir2Y * controlDist2
|
||||
);
|
||||
|
||||
return (cp1, cp2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 宽松的去重算法
|
||||
/// </summary>
|
||||
private StylusPoint[] RemoveDuplicatePointsLoose(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 2) return points;
|
||||
|
||||
var result = new List<StylusPoint>();
|
||||
result.Add(points[0]);
|
||||
|
||||
double minDistance = 0.1; // 非常小的距离阈值,几乎不去重
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var lastPoint = result[result.Count - 1];
|
||||
var currentPoint = points[i];
|
||||
|
||||
// 计算距离
|
||||
double distance = Math.Sqrt(
|
||||
(currentPoint.X - lastPoint.X) * (currentPoint.X - lastPoint.X) +
|
||||
(currentPoint.Y - lastPoint.Y) * (currentPoint.Y - lastPoint.Y));
|
||||
|
||||
// 如果距离足够大,添加这个点
|
||||
if (distance >= minDistance)
|
||||
{
|
||||
result.Add(currentPoint);
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"RemoveDuplicatePointsLoose: 输入点数={points.Length}, 输出点数={result.Count}");
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算贝塞尔曲线上的点
|
||||
/// </summary>
|
||||
private StylusPoint CalculateBezierPoint(StylusPoint p0, Point cp1, Point cp2, StylusPoint p3, double t)
|
||||
{
|
||||
double x = Math.Pow(1 - t, 3) * p0.X +
|
||||
3 * Math.Pow(1 - t, 2) * t * cp1.X +
|
||||
3 * (1 - t) * Math.Pow(t, 2) * cp2.X +
|
||||
Math.Pow(t, 3) * p3.X;
|
||||
|
||||
double y = Math.Pow(1 - t, 3) * p0.Y +
|
||||
3 * Math.Pow(1 - t, 2) * t * cp1.Y +
|
||||
3 * (1 - t) * Math.Pow(t, 2) * cp2.Y +
|
||||
Math.Pow(t, 3) * p3.Y;
|
||||
|
||||
// 压力插值
|
||||
float pressure = (float)(Math.Pow(1 - t, 3) * p0.PressureFactor +
|
||||
3 * Math.Pow(1 - t, 2) * t * p0.PressureFactor +
|
||||
3 * (1 - t) * Math.Pow(t, 2) * p3.PressureFactor +
|
||||
Math.Pow(t, 3) * p3.PressureFactor);
|
||||
|
||||
return new StylusPoint(x, y, Math.Max(pressure, 0.1f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -251,7 +466,7 @@ namespace Ink_Canvas.Helpers
|
||||
var result = new List<StylusPoint>();
|
||||
result.Add(points[0]);
|
||||
|
||||
double minDistance = ResampleInterval * 0.5; // 最小距离阈值
|
||||
double minDistance = 0.3; // 进一步减少最小距离阈值,保留更多平滑点
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
@@ -270,6 +485,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"RemoveDuplicatePoints: 输入点数={points.Length}, 输出点数={result.Count}");
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
@@ -516,23 +732,31 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public class AdvancedBezierSmoothing
|
||||
{
|
||||
public double SmoothingStrength { get; set; } = 0.3;
|
||||
public double ResampleInterval { get; set; } = 3.0;
|
||||
public int InterpolationSteps { get; set; } = 8;
|
||||
public double SmoothingStrength { get; set; } = 0.6;
|
||||
public double ResampleInterval { get; set; } = 2.0;
|
||||
public int InterpolationSteps { get; set; } = 12;
|
||||
|
||||
public Stroke SmoothStroke(Stroke stroke)
|
||||
{
|
||||
if (stroke == null || stroke.StylusPoints.Count < 3)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 笔画点数不足,跳过平滑 (点数: {stroke?.StylusPoints.Count ?? 0})");
|
||||
return stroke;
|
||||
}
|
||||
|
||||
var originalPoints = stroke.StylusPoints.ToList();
|
||||
System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 开始平滑处理,原始点数: {stroke.StylusPoints.Count}");
|
||||
|
||||
// 简化处理:只进行轻度平滑
|
||||
var smoothedPoints = ApplyLightExponentialSmoothing(originalPoints, 0.2); // 很轻的平滑
|
||||
var originalPoints = stroke.StylusPoints.ToArray();
|
||||
|
||||
// 使用真正的贝塞尔曲线平滑
|
||||
var smoothedPoints = ApplyCubicBezierSmoothing(originalPoints);
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 平滑完成,平滑后点数: {smoothedPoints.Length}");
|
||||
|
||||
// 检查点数是否合理
|
||||
if (smoothedPoints.Count > originalPoints.Count * 1.5)
|
||||
if (smoothedPoints.Length > originalPoints.Length * 10.0)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 点数增加过多,返回原始笔画 (原始:{originalPoints.Length}, 平滑后:{smoothedPoints.Length})");
|
||||
return stroke; // 如果点数增加太多,返回原始笔画
|
||||
}
|
||||
|
||||
@@ -540,9 +764,140 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
DrawingAttributes = stroke.DrawingAttributes.Clone()
|
||||
};
|
||||
|
||||
System.Diagnostics.Debug.WriteLine($"AdvancedBezierSmoothing: 创建平滑笔画成功");
|
||||
return smoothedStroke;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 三次贝塞尔曲线平滑
|
||||
/// </summary>
|
||||
private StylusPoint[] ApplyCubicBezierSmoothing(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 4) return points;
|
||||
|
||||
var result = new List<StylusPoint>();
|
||||
result.Add(points[0]);
|
||||
|
||||
// 使用更保守的窗口大小和插值
|
||||
int windowSize = Math.Min(4, points.Length);
|
||||
int stepSize = Math.Max(1, points.Length / 10); // 根据点数动态调整步长
|
||||
|
||||
for (int i = 0; i <= points.Length - windowSize; i += stepSize)
|
||||
{
|
||||
if (i + windowSize - 1 >= points.Length) break;
|
||||
|
||||
var p0 = points[i];
|
||||
var p1 = points[Math.Min(i + 1, points.Length - 1)];
|
||||
var p2 = points[Math.Min(i + 2, points.Length - 1)];
|
||||
var p3 = points[Math.Min(i + windowSize - 1, points.Length - 1)];
|
||||
|
||||
// 计算控制点
|
||||
var controlPoints = CalculateControlPoints(p0, p1, p2, p3);
|
||||
|
||||
// 只生成2-3个插值点
|
||||
int steps = 2;
|
||||
|
||||
// 生成贝塞尔曲线点
|
||||
for (int j = 1; j <= steps; j++)
|
||||
{
|
||||
double t = (double)j / steps;
|
||||
var bezierPoint = CalculateBezierPoint(p0, controlPoints.cp1, controlPoints.cp2, p3, t);
|
||||
result.Add(bezierPoint);
|
||||
}
|
||||
}
|
||||
|
||||
result.Add(points[points.Length - 1]);
|
||||
|
||||
// 去重处理
|
||||
return RemoveDuplicatePoints(result.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去除重复点
|
||||
/// </summary>
|
||||
private StylusPoint[] RemoveDuplicatePoints(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length <= 1) return points;
|
||||
|
||||
var result = new List<StylusPoint> { points[0] };
|
||||
double minDistance = 1.0; // 最小距离阈值
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var lastPoint = result[result.Count - 1];
|
||||
var currentPoint = points[i];
|
||||
|
||||
double distance = Math.Sqrt(Math.Pow(currentPoint.X - lastPoint.X, 2) +
|
||||
Math.Pow(currentPoint.Y - lastPoint.Y, 2));
|
||||
|
||||
if (distance > minDistance)
|
||||
{
|
||||
result.Add(currentPoint);
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算控制点
|
||||
/// </summary>
|
||||
private (Point cp1, Point cp2) CalculateControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
// 计算切线方向
|
||||
var tangent1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||
var tangent2 = new Vector(p3.X - p2.X, p3.Y - p2.Y);
|
||||
|
||||
// 归一化切线
|
||||
if (tangent1.Length > 0) tangent1.Normalize();
|
||||
if (tangent2.Length > 0) tangent2.Normalize();
|
||||
|
||||
// 计算控制点距离
|
||||
double dist1 = Math.Sqrt((p1.X - p0.X) * (p1.X - p0.X) + (p1.Y - p0.Y) * (p1.Y - p0.Y));
|
||||
double dist2 = Math.Sqrt((p3.X - p2.X) * (p3.X - p2.X) + (p3.Y - p2.Y) * (p3.Y - p2.Y));
|
||||
|
||||
double controlDist1 = dist1 * SmoothingStrength;
|
||||
double controlDist2 = dist2 * SmoothingStrength;
|
||||
|
||||
// 计算控制点
|
||||
var cp1 = new Point(
|
||||
p1.X + tangent1.X * controlDist1,
|
||||
p1.Y + tangent1.Y * controlDist1
|
||||
);
|
||||
|
||||
var cp2 = new Point(
|
||||
p2.X - tangent2.X * controlDist2,
|
||||
p2.Y - tangent2.Y * controlDist2
|
||||
);
|
||||
|
||||
return (cp1, cp2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算贝塞尔曲线上的点
|
||||
/// </summary>
|
||||
private StylusPoint CalculateBezierPoint(StylusPoint p0, Point cp1, Point cp2, StylusPoint p3, double t)
|
||||
{
|
||||
double x = Math.Pow(1 - t, 3) * p0.X +
|
||||
3 * Math.Pow(1 - t, 2) * t * cp1.X +
|
||||
3 * (1 - t) * Math.Pow(t, 2) * cp2.X +
|
||||
Math.Pow(t, 3) * p3.X;
|
||||
|
||||
double y = Math.Pow(1 - t, 3) * p0.Y +
|
||||
3 * Math.Pow(1 - t, 2) * t * cp1.Y +
|
||||
3 * (1 - t) * Math.Pow(t, 2) * cp2.Y +
|
||||
Math.Pow(t, 3) * p3.Y;
|
||||
|
||||
// 压力插值
|
||||
float pressure = (float)(Math.Pow(1 - t, 3) * p0.PressureFactor +
|
||||
3 * Math.Pow(1 - t, 2) * t * p0.PressureFactor +
|
||||
3 * (1 - t) * Math.Pow(t, 2) * p3.PressureFactor +
|
||||
Math.Pow(t, 3) * p3.PressureFactor);
|
||||
|
||||
return new StylusPoint(x, y, Math.Max(pressure, 0.1f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 轻度指数平滑
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 自动备份管理器
|
||||
/// 负责管理配置文件的自动备份功能
|
||||
/// </summary>
|
||||
public static class AutoBackupManager
|
||||
{
|
||||
private static readonly string BackupDir = Path.Combine(App.RootPath, "Backups");
|
||||
private static readonly string SettingsFile = Path.Combine(App.RootPath, "Configs", "Settings.json");
|
||||
private static readonly string BackupPrefix = "Settings_AutoBackup_";
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否需要执行自动备份
|
||||
/// </summary>
|
||||
/// <param name="settings">设置对象</param>
|
||||
/// <returns>如果需要备份返回true,否则返回false</returns>
|
||||
public static bool ShouldPerformAutoBackup(Settings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果自动备份功能未启用,不执行备份
|
||||
if (!settings.Advanced.IsAutoBackupEnabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果从未备份过,需要创建首次备份
|
||||
if (settings.Advanced.LastAutoBackupTime == DateTime.MinValue)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查是否已超过备份间隔
|
||||
var daysSinceLastBackup = (DateTime.Now - settings.Advanced.LastAutoBackupTime).TotalDays;
|
||||
return daysSinceLastBackup >= settings.Advanced.AutoBackupIntervalDays;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检查自动备份条件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行自动备份
|
||||
/// </summary>
|
||||
/// <param name="settings">设置对象</param>
|
||||
/// <returns>备份是否成功</returns>
|
||||
public static bool PerformAutoBackup(Settings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保备份目录存在
|
||||
if (!Directory.Exists(BackupDir))
|
||||
{
|
||||
Directory.CreateDirectory(BackupDir);
|
||||
}
|
||||
|
||||
// 检查主配置文件是否存在
|
||||
if (!File.Exists(SettingsFile))
|
||||
{
|
||||
LogHelper.WriteLogToFile("主配置文件不存在,跳过自动备份", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建备份文件名(使用当前日期时间)
|
||||
string backupFileName = $"{BackupPrefix}{DateTime.Now:yyyyMMdd_HHmmss}.json";
|
||||
string backupPath = Path.Combine(BackupDir, backupFileName);
|
||||
|
||||
// 复制主配置文件到备份位置
|
||||
File.Copy(SettingsFile, backupPath, true);
|
||||
|
||||
// 更新最后备份时间
|
||||
settings.Advanced.LastAutoBackupTime = DateTime.Now;
|
||||
MainWindow.SaveSettingsToFile();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"执行自动备份时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从备份恢复配置文件
|
||||
/// </summary>
|
||||
/// <returns>恢复是否成功</returns>
|
||||
public static bool TryRestoreFromBackup()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保备份目录存在
|
||||
if (!Directory.Exists(BackupDir))
|
||||
{
|
||||
LogHelper.WriteLogToFile("备份目录不存在,无法从备份恢复", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 查找最新的备份文件
|
||||
var backupFiles = Directory.GetFiles(BackupDir, $"{BackupPrefix}*.json")
|
||||
.OrderByDescending(f => File.GetCreationTime(f))
|
||||
.ToArray();
|
||||
|
||||
if (backupFiles.Length == 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile("没有找到可用的备份文件", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 尝试使用最新的备份文件
|
||||
string latestBackup = backupFiles[0];
|
||||
|
||||
// 验证备份文件是否有效
|
||||
try
|
||||
{
|
||||
string backupJson = File.ReadAllText(latestBackup);
|
||||
var testSettings = JsonConvert.DeserializeObject<Settings>(backupJson);
|
||||
if (testSettings == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("备份文件内容无效,无法恢复", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"备份文件验证失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 备份当前损坏的配置文件(如果存在)
|
||||
if (File.Exists(SettingsFile))
|
||||
{
|
||||
string corruptedBackup = Path.Combine(BackupDir, $"Settings_Corrupted_{DateTime.Now:yyyyMMdd_HHmmss}.json");
|
||||
File.Copy(SettingsFile, corruptedBackup, true);
|
||||
}
|
||||
|
||||
// 从备份恢复配置文件
|
||||
File.Copy(latestBackup, SettingsFile, true);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从备份恢复配置文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理过期的备份文件
|
||||
/// 保留最近30天的备份文件
|
||||
/// </summary>
|
||||
public static void CleanupOldBackups()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(BackupDir))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var cutoffDate = DateTime.Now.AddDays(-30);
|
||||
var backupFiles = Directory.GetFiles(BackupDir, $"{BackupPrefix}*.json");
|
||||
|
||||
int deletedCount = 0;
|
||||
foreach (var file in backupFiles)
|
||||
{
|
||||
if (File.GetCreationTime(file) < cutoffDate)
|
||||
{
|
||||
File.Delete(file);
|
||||
deletedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (deletedCount > 0)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理过期备份文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化自动备份功能
|
||||
/// 在应用程序启动时调用
|
||||
/// </summary>
|
||||
/// <param name="settings">设置对象</param>
|
||||
public static void Initialize(Settings settings)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否需要执行自动备份
|
||||
if (ShouldPerformAutoBackup(settings))
|
||||
{
|
||||
PerformAutoBackup(settings);
|
||||
}
|
||||
|
||||
// 清理过期备份
|
||||
CleanupOldBackups();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化自动备份功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,20 +213,20 @@ namespace Ink_Canvas.Helpers
|
||||
.Select(x => x.group)
|
||||
.ToList();
|
||||
|
||||
// 将"智教联盟"线路组插入到最前面(如果存在)
|
||||
var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟");
|
||||
if (zhiJiaoGroup != null)
|
||||
{
|
||||
orderedGroups.Insert(0, zhiJiaoGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 智教联盟线路组已插入到首位");
|
||||
}
|
||||
|
||||
// 将"inkeys"线路组插入到第二位(如果存在)
|
||||
// 将"inkeys"线路组插入到最前面(如果存在)
|
||||
var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys");
|
||||
if (inkeysGroup != null)
|
||||
{
|
||||
orderedGroups.Insert(1, inkeysGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | inkeys线路组已插入到第二位");
|
||||
orderedGroups.Insert(0, inkeysGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | inkeys线路组已插入到首位");
|
||||
}
|
||||
|
||||
// 将"智教联盟"线路组插入到第二位(如果存在)
|
||||
var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟");
|
||||
if (zhiJiaoGroup != null)
|
||||
{
|
||||
orderedGroups.Insert(1, zhiJiaoGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 智教联盟线路组已插入到第二位");
|
||||
}
|
||||
|
||||
if (orderedGroups.Count > 0)
|
||||
@@ -671,22 +671,22 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
SaveDownloadStatus(false);
|
||||
|
||||
// 优先尝试“智教联盟”线路组
|
||||
// 优先尝试"inkeys"线路组和"智教联盟"线路组
|
||||
var zhiJiaoGroup = groups.FirstOrDefault(g => g.GroupName == "智教联盟");
|
||||
var inkeysGroup = groups.FirstOrDefault(g => g.GroupName == "inkeys");
|
||||
if (zhiJiaoGroup != null || inkeysGroup != null)
|
||||
if (inkeysGroup != null || zhiJiaoGroup != null)
|
||||
{
|
||||
var priorityGroups = new List<UpdateLineGroup>();
|
||||
if (zhiJiaoGroup != null)
|
||||
{
|
||||
priorityGroups.Add(zhiJiaoGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 下载时优先尝试智教联盟线路组");
|
||||
}
|
||||
if (inkeysGroup != null)
|
||||
{
|
||||
priorityGroups.Add(inkeysGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 下载时优先尝试inkeys线路组");
|
||||
}
|
||||
if (zhiJiaoGroup != null)
|
||||
{
|
||||
priorityGroups.Add(zhiJiaoGroup);
|
||||
LogHelper.WriteLogToFile("AutoUpdate | 下载时优先尝试智教联盟线路组");
|
||||
}
|
||||
groups = priorityGroups.Concat(groups.Where(g => g.GroupName != "智教联盟" && g.GroupName != "inkeys")).ToList();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
using AForge.Video;
|
||||
using AForge.Video.DirectShow;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Drawing.Imaging;
|
||||
using System.Linq;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class CameraService : IDisposable
|
||||
{
|
||||
private VideoCaptureDevice _videoSource;
|
||||
private bool _isCapturing;
|
||||
private Bitmap _currentFrame;
|
||||
private readonly object _frameLock = new object();
|
||||
private Dispatcher _dispatcher;
|
||||
|
||||
// 新增属性
|
||||
private int _rotationAngle = 0; // 0=0度,1=90度,2=180度,3=270度
|
||||
private int _resolutionWidth = 640;
|
||||
private int _resolutionHeight = 480;
|
||||
|
||||
public event EventHandler<Bitmap> FrameReceived;
|
||||
public event EventHandler<string> ErrorOccurred;
|
||||
|
||||
public bool IsCapturing => _isCapturing;
|
||||
public List<FilterInfo> AvailableCameras { get; private set; }
|
||||
public FilterInfo CurrentCamera { get; private set; }
|
||||
|
||||
// 新增属性
|
||||
public int RotationAngle
|
||||
{
|
||||
get => _rotationAngle;
|
||||
set => _rotationAngle = Math.Max(0, Math.Min(3, value));
|
||||
}
|
||||
|
||||
public int ResolutionWidth
|
||||
{
|
||||
get => _resolutionWidth;
|
||||
set => _resolutionWidth = Math.Max(320, Math.Min(1920, value));
|
||||
}
|
||||
|
||||
public int ResolutionHeight
|
||||
{
|
||||
get => _resolutionHeight;
|
||||
set => _resolutionHeight = Math.Max(240, Math.Min(1080, value));
|
||||
}
|
||||
|
||||
public CameraService()
|
||||
{
|
||||
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||
AvailableCameras = new List<FilterInfo>();
|
||||
RefreshCameraList();
|
||||
}
|
||||
|
||||
public CameraService(int rotationAngle, int resolutionWidth, int resolutionHeight)
|
||||
{
|
||||
_dispatcher = Dispatcher.CurrentDispatcher;
|
||||
AvailableCameras = new List<FilterInfo>();
|
||||
_rotationAngle = rotationAngle;
|
||||
_resolutionWidth = resolutionWidth;
|
||||
_resolutionHeight = resolutionHeight;
|
||||
RefreshCameraList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新可用摄像头列表
|
||||
/// </summary>
|
||||
public void RefreshCameraList()
|
||||
{
|
||||
try
|
||||
{
|
||||
AvailableCameras.Clear();
|
||||
var videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
|
||||
|
||||
foreach (FilterInfo device in videoDevices)
|
||||
{
|
||||
AvailableCameras.Add(device);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"刷新摄像头列表失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
ErrorOccurred?.Invoke(this, $"刷新摄像头列表失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始摄像头预览
|
||||
/// </summary>
|
||||
/// <param name="cameraIndex">摄像头索引</param>
|
||||
public bool StartPreview(int cameraIndex = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (AvailableCameras.Count == 0)
|
||||
{
|
||||
RefreshCameraList();
|
||||
if (AvailableCameras.Count == 0)
|
||||
{
|
||||
ErrorOccurred?.Invoke(this, "未找到可用的摄像头设备");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count)
|
||||
{
|
||||
ErrorOccurred?.Invoke(this, "摄像头索引超出范围");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 停止当前预览
|
||||
StopPreview();
|
||||
|
||||
CurrentCamera = AvailableCameras[cameraIndex];
|
||||
_videoSource = new VideoCaptureDevice(CurrentCamera.MonikerString);
|
||||
|
||||
// 设置视频源事件处理
|
||||
_videoSource.NewFrame += VideoSource_NewFrame;
|
||||
|
||||
// 启动视频源
|
||||
_videoSource.Start();
|
||||
|
||||
_isCapturing = true;
|
||||
LogHelper.WriteLogToFile($"开始摄像头预览: {CurrentCamera.Name}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动摄像头预览失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
ErrorOccurred?.Invoke(this, $"启动摄像头预览失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止摄像头预览
|
||||
/// </summary>
|
||||
public void StopPreview()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_videoSource != null && _videoSource.IsRunning)
|
||||
{
|
||||
_videoSource.SignalToStop();
|
||||
_videoSource.WaitForStop();
|
||||
_videoSource.NewFrame -= VideoSource_NewFrame;
|
||||
_videoSource = null;
|
||||
}
|
||||
|
||||
_isCapturing = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"停止摄像头预览失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定摄像头
|
||||
/// </summary>
|
||||
/// <param name="cameraIndex">摄像头索引</param>
|
||||
public bool SwitchCamera(int cameraIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (cameraIndex < 0 || cameraIndex >= AvailableCameras.Count)
|
||||
{
|
||||
ErrorOccurred?.Invoke(this, "摄像头索引超出范围");
|
||||
return false;
|
||||
}
|
||||
|
||||
return StartPreview(cameraIndex);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换摄像头失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
ErrorOccurred?.Invoke(this, $"切换摄像头失败: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前帧的BitmapSource(WPF格式),直接返回可用的WPF位图
|
||||
/// </summary>
|
||||
public BitmapSource GetCurrentFrameAsBitmapSource()
|
||||
{
|
||||
lock (_frameLock)
|
||||
{
|
||||
if (_currentFrame == null)
|
||||
return null;
|
||||
|
||||
try
|
||||
{
|
||||
// 验证位图有效性
|
||||
if (_currentFrame.Width <= 0 || _currentFrame.Height <= 0)
|
||||
return null;
|
||||
|
||||
// 使用更安全的方法转换位图
|
||||
var bitmapData = _currentFrame.LockBits(
|
||||
new Rectangle(0, 0, _currentFrame.Width, _currentFrame.Height),
|
||||
ImageLockMode.ReadOnly,
|
||||
_currentFrame.PixelFormat);
|
||||
|
||||
try
|
||||
{
|
||||
// 根据像素格式选择合适的WPF像素格式
|
||||
System.Windows.Media.PixelFormat wpfPixelFormat;
|
||||
switch (_currentFrame.PixelFormat)
|
||||
{
|
||||
case PixelFormat.Format24bppRgb:
|
||||
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr24;
|
||||
break;
|
||||
case PixelFormat.Format32bppArgb:
|
||||
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgra32;
|
||||
break;
|
||||
case PixelFormat.Format32bppRgb:
|
||||
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr32;
|
||||
break;
|
||||
default:
|
||||
wpfPixelFormat = System.Windows.Media.PixelFormats.Bgr24;
|
||||
break;
|
||||
}
|
||||
|
||||
var bitmapSource = BitmapSource.Create(
|
||||
bitmapData.Width,
|
||||
bitmapData.Height,
|
||||
_currentFrame.HorizontalResolution,
|
||||
_currentFrame.VerticalResolution,
|
||||
wpfPixelFormat,
|
||||
null,
|
||||
bitmapData.Scan0,
|
||||
bitmapData.Stride * bitmapData.Height,
|
||||
bitmapData.Stride);
|
||||
|
||||
bitmapSource.Freeze();
|
||||
return bitmapSource;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_currentFrame.UnlockBits(bitmapData);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"转换帧为BitmapSource失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 视频源新帧事件处理
|
||||
/// </summary>
|
||||
private void VideoSource_NewFrame(object sender, NewFrameEventArgs eventArgs)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_frameLock)
|
||||
{
|
||||
// 释放之前的帧
|
||||
_currentFrame?.Dispose();
|
||||
|
||||
// 创建新的位图,避免Clone的问题
|
||||
var sourceFrame = eventArgs.Frame;
|
||||
|
||||
if (sourceFrame != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var width = sourceFrame.Width;
|
||||
var height = sourceFrame.Height;
|
||||
|
||||
if (width > 0 && height > 0)
|
||||
{
|
||||
// 应用旋转
|
||||
Bitmap rotatedFrame = ApplyRotation(sourceFrame);
|
||||
|
||||
// 应用分辨率调整
|
||||
_currentFrame = ResizeImage(rotatedFrame, _resolutionWidth, _resolutionHeight);
|
||||
|
||||
rotatedFrame?.Dispose();
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentFrame = null;
|
||||
}
|
||||
}
|
||||
catch (Exception frameEx)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理源帧失败: {frameEx.Message}", LogHelper.LogType.Error);
|
||||
_currentFrame = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentFrame = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 在UI线程中触发事件
|
||||
_dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
FrameReceived?.Invoke(this, _currentFrame);
|
||||
}));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理新帧失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
ErrorOccurred?.Invoke(this, $"处理新帧失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取摄像头名称列表
|
||||
/// </summary>
|
||||
public List<string> GetCameraNames()
|
||||
{
|
||||
return AvailableCameras.Select(camera => camera.Name).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有可用摄像头
|
||||
/// </summary>
|
||||
public bool HasAvailableCameras()
|
||||
{
|
||||
if (AvailableCameras.Count == 0)
|
||||
{
|
||||
RefreshCameraList();
|
||||
}
|
||||
return AvailableCameras.Count > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用旋转到图像
|
||||
/// </summary>
|
||||
private Bitmap ApplyRotation(Bitmap source)
|
||||
{
|
||||
if (_rotationAngle == 0)
|
||||
return new Bitmap(source);
|
||||
|
||||
var rotationType = RotateFlipType.RotateNoneFlipNone;
|
||||
switch (_rotationAngle)
|
||||
{
|
||||
case 1: rotationType = RotateFlipType.Rotate90FlipNone; break;
|
||||
case 2: rotationType = RotateFlipType.Rotate180FlipNone; break;
|
||||
case 3: rotationType = RotateFlipType.Rotate270FlipNone; break;
|
||||
}
|
||||
|
||||
var rotated = new Bitmap(source);
|
||||
rotated.RotateFlip(rotationType);
|
||||
return rotated;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 调整图像大小
|
||||
/// </summary>
|
||||
private Bitmap ResizeImage(Bitmap source, int width, int height)
|
||||
{
|
||||
if (source.Width == width && source.Height == height)
|
||||
return new Bitmap(source);
|
||||
|
||||
var resized = new Bitmap(width, height, PixelFormat.Format24bppRgb);
|
||||
using (var graphics = Graphics.FromImage(resized))
|
||||
{
|
||||
graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
|
||||
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
|
||||
graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
|
||||
graphics.DrawImage(source, 0, 0, width, height);
|
||||
}
|
||||
return resized;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StopPreview();
|
||||
|
||||
lock (_frameLock)
|
||||
{
|
||||
_currentFrame?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -112,4 +112,27 @@ namespace Ink_Canvas.Converter
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public class InverseBooleanToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Ink_Canvas.Helpers
|
||||
// 文件路径策略
|
||||
private static readonly string DeviceIdFilePath = Path.Combine(App.RootPath, "device_id.dat");
|
||||
private static readonly string UsageStatsFilePath = Path.Combine(App.RootPath, "usage_stats.enc");
|
||||
private static readonly string UsageStatsBackupPath = Path.Combine(App.RootPath, "saves", "usage_stats_backup.enc");
|
||||
private static readonly string UsageStatsBackupPath = Path.Combine(App.RootPath, "Saves", "usage_stats_backup.enc");
|
||||
|
||||
private static readonly string DeviceId;
|
||||
private static readonly object fileLock = new object();
|
||||
@@ -1065,7 +1065,6 @@ namespace Ink_Canvas.Helpers
|
||||
// 如果不是自动更新(即版本修复),则应用不同的策略
|
||||
if (!isAutoUpdate)
|
||||
{
|
||||
// 版本修复:立即允许,不受分级策略影响
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 版本修复 - 版本: {updateVersion}, 类型: {updateType}, 结果: 允许");
|
||||
return true;
|
||||
}
|
||||
@@ -1092,7 +1091,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
int versionDiff = CalculateVersionGenerationDifference(localVersion, updateVersion);
|
||||
LogHelper.WriteLogToFile($"DeviceIdentifier | 无法获取版本发布时间,使用版本号差异判断 - 本地版本: {localVersion}, 远程版本: {updateVersion}, 代数差异: {versionDiff}");
|
||||
|
||||
|
||||
// 当版本号代数差异大于3时自动更新
|
||||
if (versionDiff > 3)
|
||||
{
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Windows;
|
||||
using Microsoft.Win32;
|
||||
using System.Threading;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
@@ -19,11 +19,13 @@ namespace Ink_Canvas.Helpers
|
||||
private const string FileTypeName = "InkCanvasStrokesFile";
|
||||
private const string AppName = "Ink Canvas";
|
||||
private const string AppDescription = "Ink Canvas Strokes File";
|
||||
|
||||
|
||||
// IPC相关常量
|
||||
private const string IpcMutexName = "InkCanvasFileAssociationIpc";
|
||||
private const string IpcEventName = "InkCanvasFileAssociationEvent";
|
||||
private const string IpcFilePrefix = "InkCanvasFileAssociation_";
|
||||
private const string IpcBoardModePrefix = "InkCanvasBoardMode_";
|
||||
private const string IpcShowModePrefix = "InkCanvasShowMode_";
|
||||
private const int IpcTimeout = 5000; // 5秒超时
|
||||
|
||||
/// <summary>
|
||||
@@ -34,19 +36,19 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
string exePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
|
||||
|
||||
// 注册文件类型
|
||||
using (RegistryKey fileTypeKey = Registry.ClassesRoot.CreateSubKey(FileTypeName))
|
||||
{
|
||||
fileTypeKey.SetValue("", AppDescription);
|
||||
fileTypeKey.SetValue("FriendlyTypeName", AppDescription);
|
||||
|
||||
|
||||
// 设置默认图标
|
||||
using (RegistryKey defaultIconKey = fileTypeKey.CreateSubKey("DefaultIcon"))
|
||||
{
|
||||
defaultIconKey.SetValue("", $"\"{exePath}\",0");
|
||||
}
|
||||
|
||||
|
||||
// 设置打开命令
|
||||
using (RegistryKey shellKey = fileTypeKey.CreateSubKey("shell"))
|
||||
using (RegistryKey openKey = shellKey.CreateSubKey("open"))
|
||||
@@ -55,16 +57,16 @@ namespace Ink_Canvas.Helpers
|
||||
commandKey.SetValue("", $"\"{exePath}\" \"%1\"");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 注册文件扩展名
|
||||
using (RegistryKey extensionKey = Registry.ClassesRoot.CreateSubKey(FileExtension))
|
||||
{
|
||||
extensionKey.SetValue("", FileTypeName);
|
||||
}
|
||||
|
||||
|
||||
// 刷新系统文件关联缓存
|
||||
RefreshSystemFileAssociations();
|
||||
|
||||
|
||||
LogHelper.WriteLogToFile($"成功注册{FileExtension}文件关联", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
@@ -94,13 +96,13 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 删除文件扩展名关联
|
||||
Registry.ClassesRoot.DeleteSubKeyTree(FileExtension, false);
|
||||
|
||||
|
||||
// 删除文件类型定义
|
||||
Registry.ClassesRoot.DeleteSubKeyTree(FileTypeName, false);
|
||||
|
||||
|
||||
// 刷新系统文件关联缓存
|
||||
RefreshSystemFileAssociations();
|
||||
|
||||
|
||||
LogHelper.WriteLogToFile($"成功注销{FileExtension}文件关联", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
@@ -121,21 +123,21 @@ namespace Ink_Canvas.Helpers
|
||||
using (RegistryKey extensionKey = Registry.ClassesRoot.OpenSubKey(FileExtension))
|
||||
{
|
||||
if (extensionKey == null) return false;
|
||||
|
||||
|
||||
string fileType = extensionKey.GetValue("") as string;
|
||||
if (string.IsNullOrEmpty(fileType)) return false;
|
||||
|
||||
|
||||
using (RegistryKey fileTypeKey = Registry.ClassesRoot.OpenSubKey(fileType))
|
||||
{
|
||||
if (fileTypeKey == null) return false;
|
||||
|
||||
|
||||
using (RegistryKey shellKey = fileTypeKey.OpenSubKey("shell\\open\\command"))
|
||||
{
|
||||
if (shellKey == null) return false;
|
||||
|
||||
|
||||
string command = shellKey.GetValue("") as string;
|
||||
if (string.IsNullOrEmpty(command)) return false;
|
||||
|
||||
|
||||
// 检查命令是否指向当前应用程序
|
||||
string currentExePath = Process.GetCurrentProcess().MainModule.FileName;
|
||||
return command.Contains(currentExePath);
|
||||
@@ -147,7 +149,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检查文件关联状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -184,11 +186,11 @@ namespace Ink_Canvas.Helpers
|
||||
public static string GetIcstkFileFromArgs(string[] args)
|
||||
{
|
||||
if (args == null || args.Length == 0) return null;
|
||||
|
||||
|
||||
foreach (string arg in args)
|
||||
{
|
||||
if (string.IsNullOrEmpty(arg)) continue;
|
||||
|
||||
|
||||
// 检查是否为.icstk文件
|
||||
if (Path.GetExtension(arg).Equals(FileExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -204,7 +206,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -218,24 +220,24 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile($"尝试通过IPC发送文件路径给已运行实例: {filePath}", LogHelper.LogType.Event);
|
||||
|
||||
|
||||
// 创建IPC文件
|
||||
string tempDir = Path.GetTempPath();
|
||||
string ipcFileName = IpcFilePrefix + Guid.NewGuid().ToString("N") + ".tmp";
|
||||
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
|
||||
|
||||
|
||||
// 写入文件路径到IPC文件
|
||||
File.WriteAllText(ipcFilePath, filePath, Encoding.UTF8);
|
||||
|
||||
|
||||
// 创建事件通知已运行实例
|
||||
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
|
||||
{
|
||||
ipcEvent.Set();
|
||||
}
|
||||
|
||||
|
||||
// 等待一段时间让已运行实例处理文件
|
||||
Thread.Sleep(1000);
|
||||
|
||||
|
||||
// 清理IPC文件
|
||||
try
|
||||
{
|
||||
@@ -248,7 +250,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
|
||||
LogHelper.WriteLogToFile("IPC文件路径发送完成", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
@@ -259,6 +261,106 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过IPC将白板模式命令发送给已运行的实例
|
||||
/// </summary>
|
||||
/// <returns>是否成功发送</returns>
|
||||
public static bool TrySendBoardModeCommandToExistingInstance()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("尝试通过IPC发送白板模式命令给已运行实例", LogHelper.LogType.Event);
|
||||
|
||||
// 创建IPC文件
|
||||
string tempDir = Path.GetTempPath();
|
||||
string ipcFileName = IpcBoardModePrefix + Guid.NewGuid().ToString("N") + ".tmp";
|
||||
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
|
||||
|
||||
// 写入白板模式命令到IPC文件
|
||||
File.WriteAllText(ipcFilePath, "BOARD_MODE", Encoding.UTF8);
|
||||
|
||||
// 创建事件通知已运行实例
|
||||
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
|
||||
{
|
||||
ipcEvent.Set();
|
||||
}
|
||||
|
||||
// 等待一段时间让已运行实例处理命令
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// 清理IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFilePath))
|
||||
{
|
||||
File.Delete(ipcFilePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("IPC白板模式命令发送完成", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"通过IPC发送白板模式命令失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过IPC将展开浮动栏命令发送给已运行的实例
|
||||
/// </summary>
|
||||
/// <returns>是否成功发送</returns>
|
||||
public static bool TrySendShowModeCommandToExistingInstance()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("尝试通过IPC发送展开浮动栏命令给已运行实例", LogHelper.LogType.Event);
|
||||
|
||||
// 创建IPC文件
|
||||
string tempDir = Path.GetTempPath();
|
||||
string ipcFileName = IpcShowModePrefix + Guid.NewGuid().ToString("N") + ".tmp";
|
||||
string ipcFilePath = Path.Combine(tempDir, ipcFileName);
|
||||
|
||||
// 写入展开浮动栏命令到IPC文件
|
||||
File.WriteAllText(ipcFilePath, "SHOW_MODE", Encoding.UTF8);
|
||||
|
||||
// 创建事件通知已运行实例
|
||||
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
|
||||
{
|
||||
ipcEvent.Set();
|
||||
}
|
||||
|
||||
// 等待一段时间让已运行实例处理命令
|
||||
Thread.Sleep(1000);
|
||||
|
||||
// 清理IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFilePath))
|
||||
{
|
||||
File.Delete(ipcFilePath);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("IPC展开浮动栏命令发送完成", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"通过IPC发送展开浮动栏命令失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动IPC监听器,等待其他实例发送文件路径
|
||||
/// </summary>
|
||||
@@ -271,7 +373,7 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("启动IPC监听器", LogHelper.LogType.Event);
|
||||
|
||||
|
||||
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
|
||||
{
|
||||
while (true)
|
||||
@@ -281,11 +383,11 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 处理IPC文件
|
||||
ProcessIpcFiles();
|
||||
|
||||
|
||||
// 重置事件
|
||||
ipcEvent.Reset();
|
||||
}
|
||||
|
||||
|
||||
// 检查应用是否还在运行
|
||||
if (Application.Current == null || Application.Current.Dispatcher == null)
|
||||
{
|
||||
@@ -299,7 +401,7 @@ namespace Ink_Canvas.Helpers
|
||||
LogHelper.WriteLogToFile($"IPC监听器出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ipcThread.IsBackground = true;
|
||||
ipcThread.Start();
|
||||
}
|
||||
@@ -317,19 +419,20 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
string tempDir = Path.GetTempPath();
|
||||
|
||||
// 处理文件路径IPC文件
|
||||
string[] ipcFiles = Directory.GetFiles(tempDir, IpcFilePrefix + "*.tmp");
|
||||
|
||||
foreach (string ipcFile in ipcFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 读取文件路径
|
||||
string filePath = File.ReadAllText(ipcFile, Encoding.UTF8);
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(filePath) && File.Exists(filePath))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC接收到文件路径: {filePath}", LogHelper.LogType.Event);
|
||||
|
||||
|
||||
// 在UI线程中处理文件打开
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
@@ -348,14 +451,120 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
// 删除IPC文件
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
|
||||
|
||||
// 尝试删除损坏的IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFile))
|
||||
{
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
// 处理白板模式命令IPC文件
|
||||
string[] boardModeFiles = Directory.GetFiles(tempDir, IpcBoardModePrefix + "*.tmp");
|
||||
foreach (string ipcFile in boardModeFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 读取命令内容
|
||||
string command = File.ReadAllText(ipcFile, Encoding.UTF8);
|
||||
|
||||
if (command == "BOARD_MODE")
|
||||
{
|
||||
LogHelper.WriteLogToFile("IPC接收到白板模式命令", LogHelper.LogType.Event);
|
||||
|
||||
// 在UI线程中处理白板模式切换
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取主窗口并切换到白板模式
|
||||
if (Application.Current.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
mainWindow.SwitchToBoardMode();
|
||||
mainWindow.ShowNotification("已切换到白板模式");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC处理白板模式切换失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// 删除IPC文件
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理白板模式IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
|
||||
// 尝试删除损坏的IPC文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(ipcFile))
|
||||
{
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
// 处理展开浮动栏命令IPC文件
|
||||
string[] showModeFiles = Directory.GetFiles(tempDir, IpcShowModePrefix + "*.tmp");
|
||||
foreach (string ipcFile in showModeFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 读取命令内容
|
||||
string command = File.ReadAllText(ipcFile, Encoding.UTF8);
|
||||
|
||||
if (command == "SHOW_MODE")
|
||||
{
|
||||
LogHelper.WriteLogToFile("IPC接收到展开浮动栏命令", LogHelper.LogType.Event);
|
||||
|
||||
// 在UI线程中处理展开浮动栏
|
||||
Application.Current.Dispatcher.BeginInvoke(new Action(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取主窗口并展开浮动栏
|
||||
if (Application.Current.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
// 如果当前处于收纳模式,则展开浮动栏
|
||||
if (mainWindow.isFloatingBarFolded)
|
||||
{
|
||||
await mainWindow.UnFoldFloatingBar(new object());
|
||||
}
|
||||
mainWindow.ShowNotification("已退出收纳模式并恢复浮动栏");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC处理展开浮动栏失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
// 删除IPC文件
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理展开浮动栏IPC文件失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
|
||||
// 尝试删除损坏的IPC文件
|
||||
try
|
||||
{
|
||||
@@ -377,4 +586,4 @@ namespace Ink_Canvas.Helpers
|
||||
[DllImport("shell32.dll")]
|
||||
private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
/// <summary>
|
||||
/// 悬浮窗拦截管理器
|
||||
/// </summary>
|
||||
public class FloatingWindowInterceptorManager : IDisposable
|
||||
{
|
||||
#region 私有字段
|
||||
|
||||
private FloatingWindowInterceptor _interceptor;
|
||||
private bool _isInitialized;
|
||||
private bool _disposed;
|
||||
private FloatingWindowInterceptorSettings _settings;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件
|
||||
|
||||
public event EventHandler<FloatingWindowInterceptor.WindowInterceptedEventArgs> WindowIntercepted;
|
||||
public event EventHandler<FloatingWindowInterceptor.WindowRestoredEventArgs> WindowRestored;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公共属性
|
||||
|
||||
public bool IsEnabled => _interceptor != null && _settings != null && _settings.IsEnabled;
|
||||
public bool IsRunning => _interceptor != null && _interceptor.IsRunning;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 公共方法
|
||||
|
||||
/// <summary>
|
||||
/// 初始化拦截器
|
||||
/// </summary>
|
||||
public void Initialize(FloatingWindowInterceptorSettings settings)
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
_settings = settings ?? new FloatingWindowInterceptorSettings();
|
||||
_interceptor = new FloatingWindowInterceptor();
|
||||
|
||||
// 订阅事件
|
||||
_interceptor.WindowIntercepted += OnWindowIntercepted;
|
||||
_interceptor.WindowRestored += OnWindowRestored;
|
||||
|
||||
// 应用配置
|
||||
ApplySettings();
|
||||
|
||||
_isInitialized = true;
|
||||
|
||||
// 如果设置了自动启动,则启动拦截器
|
||||
if (_settings.AutoStart && _settings.IsEnabled)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动拦截器
|
||||
/// </summary>
|
||||
public void Start()
|
||||
{
|
||||
if (!_isInitialized || _settings == null) return;
|
||||
|
||||
if (_interceptor == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
_interceptor.Start(_settings.ScanIntervalMs);
|
||||
LogHelper.WriteLogToFile("悬浮窗拦截器已启动", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止拦截器
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (_interceptor == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
_interceptor.Stop();
|
||||
LogHelper.WriteLogToFile("悬浮窗拦截器已停止", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"停止悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置拦截规则
|
||||
/// </summary>
|
||||
public void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled)
|
||||
{
|
||||
if (_interceptor == null || _settings == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
_interceptor.SetInterceptRule(type, enabled);
|
||||
|
||||
// 更新设置
|
||||
var ruleName = type.ToString();
|
||||
if (_settings.InterceptRules.ContainsKey(ruleName))
|
||||
{
|
||||
_settings.InterceptRules[ruleName] = enabled;
|
||||
}
|
||||
|
||||
// 获取规则信息以处理父子关系
|
||||
var rule = _interceptor.GetInterceptRule(type);
|
||||
if (rule != null)
|
||||
{
|
||||
// 如果是父规则,更新所有子规则的设置
|
||||
if (rule.ChildTypes.Count > 0)
|
||||
{
|
||||
foreach (var childType in rule.ChildTypes)
|
||||
{
|
||||
var childRuleName = childType.ToString();
|
||||
if (_settings.InterceptRules.ContainsKey(childRuleName))
|
||||
{
|
||||
_settings.InterceptRules[childRuleName] = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果是子规则,更新父规则的设置
|
||||
else if (rule.ParentType.HasValue)
|
||||
{
|
||||
var parentRule = _interceptor.GetInterceptRule(rule.ParentType.Value);
|
||||
if (parentRule != null)
|
||||
{
|
||||
var parentRuleName = rule.ParentType.Value.ToString();
|
||||
if (_settings.InterceptRules.ContainsKey(parentRuleName))
|
||||
{
|
||||
_settings.InterceptRules[parentRuleName] = parentRule.IsEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置拦截规则失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取拦截规则
|
||||
/// </summary>
|
||||
public FloatingWindowInterceptor.InterceptRule GetInterceptRule(FloatingWindowInterceptor.InterceptType type)
|
||||
{
|
||||
return _interceptor?.GetInterceptRule(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有拦截规则
|
||||
/// </summary>
|
||||
public Dictionary<FloatingWindowInterceptor.InterceptType, FloatingWindowInterceptor.InterceptRule> GetAllRules()
|
||||
{
|
||||
return _interceptor?.GetAllRules() ?? new Dictionary<FloatingWindowInterceptor.InterceptType, FloatingWindowInterceptor.InterceptRule>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动扫描一次
|
||||
/// </summary>
|
||||
public void ScanOnce()
|
||||
{
|
||||
if (_interceptor == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
_interceptor.ScanOnce();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"手动扫描失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 恢复所有被拦截的窗口
|
||||
/// </summary>
|
||||
public void RestoreAllWindows()
|
||||
{
|
||||
if (_interceptor == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
_interceptor.RestoreAllWindows();
|
||||
LogHelper.WriteLogToFile("已恢复所有被拦截的窗口", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"恢复窗口失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用设置
|
||||
/// </summary>
|
||||
public void ApplySettings()
|
||||
{
|
||||
if (_interceptor == null || _settings == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 应用拦截规则设置
|
||||
foreach (var kvp in _settings.InterceptRules)
|
||||
{
|
||||
if (Enum.TryParse<FloatingWindowInterceptor.InterceptType>(kvp.Key, out var type))
|
||||
{
|
||||
_interceptor.SetInterceptRule(type, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果启用了拦截器,则启动
|
||||
if (_settings.IsEnabled && !IsRunning)
|
||||
{
|
||||
Start();
|
||||
}
|
||||
// 如果禁用了拦截器,则停止
|
||||
else if (!_settings.IsEnabled && IsRunning)
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用设置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新扫描间隔
|
||||
/// </summary>
|
||||
public void UpdateScanInterval(int intervalMs)
|
||||
{
|
||||
if (_interceptor == null || _settings == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
_settings.ScanIntervalMs = intervalMs;
|
||||
|
||||
// 如果正在运行,重启以应用新间隔
|
||||
if (IsRunning)
|
||||
{
|
||||
Stop();
|
||||
Start();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新扫描间隔失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取拦截统计信息
|
||||
/// </summary>
|
||||
public InterceptStatistics GetStatistics()
|
||||
{
|
||||
if (_interceptor == null || _settings == null) return new InterceptStatistics();
|
||||
|
||||
try
|
||||
{
|
||||
var rules = GetAllRules();
|
||||
var enabledRules = rules.Count(r => r.Value.IsEnabled);
|
||||
var totalRules = rules.Count;
|
||||
|
||||
return new InterceptStatistics
|
||||
{
|
||||
TotalRules = totalRules,
|
||||
EnabledRules = enabledRules,
|
||||
IsRunning = IsRunning,
|
||||
ScanIntervalMs = _settings.ScanIntervalMs
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取统计信息失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
return new InterceptStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 私有方法
|
||||
|
||||
private void OnWindowIntercepted(object sender, FloatingWindowInterceptor.WindowInterceptedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 记录日志
|
||||
LogHelper.WriteLogToFile($"拦截窗口: {e.WindowTitle} ({e.InterceptType})", LogHelper.LogType.Event);
|
||||
|
||||
// 显示通知(如果启用)
|
||||
if (_settings != null && _settings.ShowNotifications)
|
||||
{
|
||||
ShowNotification($"已拦截悬浮窗: {e.Rule.Description}");
|
||||
}
|
||||
|
||||
// 触发事件
|
||||
WindowIntercepted?.Invoke(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理窗口拦截事件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnWindowRestored(object sender, FloatingWindowInterceptor.WindowRestoredEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 记录日志
|
||||
LogHelper.WriteLogToFile($"恢复窗口: {e.InterceptType}", LogHelper.LogType.Event);
|
||||
|
||||
// 触发事件
|
||||
WindowRestored?.Invoke(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理窗口恢复事件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowNotification(string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以集成系统通知或自定义通知
|
||||
// 暂时使用调试输出
|
||||
System.Diagnostics.Debug.WriteLine($"通知: {message}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示通知失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 辅助类
|
||||
|
||||
public class InterceptStatistics
|
||||
{
|
||||
public int TotalRules { get; set; }
|
||||
public int EnabledRules { get; set; }
|
||||
public bool IsRunning { get; set; }
|
||||
public int ScanIntervalMs { get; set; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed) return;
|
||||
|
||||
try
|
||||
{
|
||||
Stop();
|
||||
_interceptor?.Dispose();
|
||||
_interceptor = null;
|
||||
_isInitialized = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放悬浮窗拦截器失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,8 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
@@ -20,8 +22,18 @@ namespace Ink_Canvas.Helpers
|
||||
private bool _isDisposed;
|
||||
private bool _hotkeysShouldBeRegistered = true; // 启动时注册热键
|
||||
|
||||
// 多屏幕支持相关字段
|
||||
private Screen _currentScreen;
|
||||
private bool _isMultiScreenMode = false;
|
||||
private bool _enableScreenSpecificHotkeys = true; // 是否启用基于屏幕的热键注册
|
||||
|
||||
// 智能热键管理相关字段
|
||||
private bool _isWindowFocused = false;
|
||||
private bool _isMouseOverWindow = false;
|
||||
private System.Windows.Threading.DispatcherTimer _mousePositionTimer;
|
||||
|
||||
// 配置文件路径
|
||||
private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "HotkeyConfig.json");
|
||||
private static readonly string HotkeyConfigFile = Path.Combine(App.RootPath, "Configs", "HotkeyConfig.json");
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -30,6 +42,12 @@ namespace Ink_Canvas.Helpers
|
||||
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
|
||||
_registeredHotkeys = new Dictionary<string, HotkeyInfo>();
|
||||
_hotkeysShouldBeRegistered = true; // 启动时注册热键
|
||||
|
||||
// 初始化多屏幕支持
|
||||
InitializeMultiScreenSupport();
|
||||
|
||||
// 启动时确保配置文件存在
|
||||
EnsureConfigFileExists();
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -49,6 +67,12 @@ namespace Ink_Canvas.Helpers
|
||||
if (_isDisposed)
|
||||
return false;
|
||||
|
||||
// 检查是否应该注册热键(基于屏幕和模式)
|
||||
if (!ShouldRegisterHotkeys())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果快捷键已存在,先注销
|
||||
if (_registeredHotkeys.ContainsKey(hotkeyName))
|
||||
{
|
||||
@@ -82,7 +106,10 @@ namespace Ink_Canvas.Helpers
|
||||
});
|
||||
|
||||
_registeredHotkeys[hotkeyName] = hotkeyInfo;
|
||||
// 成功注册全局快捷键
|
||||
|
||||
// 记录注册信息
|
||||
var screenInfo = _isMultiScreenMode ? $" (屏幕: {_currentScreen?.DeviceName})" : "";
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -176,7 +203,6 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
if (!File.Exists(HotkeyConfigFile))
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键配置文件不存在");
|
||||
return new List<HotkeyInfo>();
|
||||
}
|
||||
|
||||
@@ -209,7 +235,6 @@ namespace Ink_Canvas.Helpers
|
||||
});
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"从配置文件读取到 {hotkeyList.Count} 个快捷键信息");
|
||||
return hotkeyList;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -280,28 +305,27 @@ namespace Ink_Canvas.Helpers
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果配置文件不存在,先创建默认配置文件
|
||||
if (!File.Exists(HotkeyConfigFile))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"快捷键配置文件不存在: {HotkeyConfigFile}", LogHelper.LogType.Warning);
|
||||
CreateDefaultConfigFile();
|
||||
RegisterDefaultHotkeys();
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// 尝试从配置文件加载
|
||||
if (LoadHotkeysFromConfigFile())
|
||||
{
|
||||
// 成功从配置文件加载快捷键设置
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
LogHelper.WriteLogToFile("成功从配置文件加载快捷键设置");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果配置文件不存在或加载失败,使用默认快捷键
|
||||
if (!File.Exists(HotkeyConfigFile))
|
||||
{
|
||||
LogHelper.WriteLogToFile("配置文件不存在,注册默认快捷键");
|
||||
RegisterDefaultHotkeys();
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("配置文件存在但加载失败,回退到默认快捷键", LogHelper.LogType.Warning);
|
||||
RegisterDefaultHotkeys();
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
}
|
||||
LogHelper.WriteLogToFile("配置文件存在但加载失败,回退到默认快捷键", LogHelper.LogType.Warning);
|
||||
RegisterDefaultHotkeys();
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -318,11 +342,9 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("开始保存快捷键配置到配置文件", LogHelper.LogType.Event);
|
||||
|
||||
if (SaveHotkeysToConfigFile())
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键配置已成功保存到配置文件", LogHelper.LogType.Event);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -346,16 +368,21 @@ namespace Ink_Canvas.Helpers
|
||||
if (!_hotkeysShouldBeRegistered)
|
||||
{
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
LogHelper.WriteLogToFile("启用快捷键注册功能");
|
||||
|
||||
// 立即加载快捷键设置
|
||||
LoadHotkeysFromSettings();
|
||||
// 启动鼠标位置监控定时器
|
||||
if (_isMultiScreenMode && _enableScreenSpecificHotkeys && _mousePositionTimer != null)
|
||||
{
|
||||
_mousePositionTimer.Start();
|
||||
}
|
||||
|
||||
// 根据上下文决定是否立即加载快捷键
|
||||
if (ShouldEnableHotkeysBasedOnContext())
|
||||
{
|
||||
LoadHotkeysFromSettings();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键注册功能已经启用,重新加载快捷键设置");
|
||||
// 即使已经启用,也要重新加载快捷键设置以确保快捷键正常工作
|
||||
LoadHotkeysFromSettings();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -375,14 +402,18 @@ namespace Ink_Canvas.Helpers
|
||||
if (_hotkeysShouldBeRegistered)
|
||||
{
|
||||
_hotkeysShouldBeRegistered = false;
|
||||
LogHelper.WriteLogToFile("禁用快捷键注册功能");
|
||||
|
||||
// 停止鼠标位置监控定时器
|
||||
if (_mousePositionTimer != null && _mousePositionTimer.IsEnabled)
|
||||
{
|
||||
_mousePositionTimer.Stop();
|
||||
}
|
||||
|
||||
// 注销所有快捷键
|
||||
UnregisterAllHotkeys();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("快捷键注册功能已经禁用");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -407,20 +438,17 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 如果设置允许,则在鼠标模式下也启用快捷键
|
||||
EnableHotkeyRegistration();
|
||||
LogHelper.WriteLogToFile("切换到鼠标模式,但根据设置保持快捷键启用");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 鼠标模式下禁用快捷键,让键盘操作放行
|
||||
DisableHotkeyRegistration();
|
||||
LogHelper.WriteLogToFile("切换到鼠标模式,禁用快捷键以放行键盘操作");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非鼠标模式下启用快捷键
|
||||
EnableHotkeyRegistration();
|
||||
LogHelper.WriteLogToFile("切换到非鼠标模式,启用快捷键");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -457,7 +485,6 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
if (success)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"成功更新快捷键 {hotkeyName}: {modifiers}+{key}", LogHelper.LogType.Event);
|
||||
// 自动保存配置
|
||||
SaveHotkeysToSettings();
|
||||
}
|
||||
@@ -470,9 +497,424 @@ namespace Ink_Canvas.Helpers
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用基于屏幕的热键注册
|
||||
/// </summary>
|
||||
public void EnableScreenSpecificHotkeys()
|
||||
{
|
||||
try
|
||||
{
|
||||
_enableScreenSpecificHotkeys = true;
|
||||
|
||||
// 如果当前在多屏幕环境下,刷新热键注册
|
||||
if (_isMultiScreenMode)
|
||||
{
|
||||
RefreshHotkeysForCurrentScreen();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用基于屏幕的热键注册时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用基于屏幕的热键注册
|
||||
/// </summary>
|
||||
public void DisableScreenSpecificHotkeys()
|
||||
{
|
||||
try
|
||||
{
|
||||
_enableScreenSpecificHotkeys = false;
|
||||
|
||||
// 重新注册热键(全局模式)
|
||||
if (_hotkeysShouldBeRegistered)
|
||||
{
|
||||
RefreshHotkeysForCurrentScreen();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用基于屏幕的热键注册时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前屏幕信息
|
||||
/// </summary>
|
||||
/// <returns>当前屏幕信息</returns>
|
||||
public string GetCurrentScreenInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isMultiScreenMode && _currentScreen != null)
|
||||
{
|
||||
return $"多屏幕环境 - 当前屏幕: {_currentScreen.DeviceName} ({_currentScreen.Bounds.Width}x{_currentScreen.Bounds.Height})";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "单屏幕环境";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取当前屏幕信息时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return "无法获取屏幕信息";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否启用了基于屏幕的热键注册
|
||||
/// </summary>
|
||||
/// <returns>是否启用</returns>
|
||||
public bool IsScreenSpecificHotkeysEnabled()
|
||||
{
|
||||
return _enableScreenSpecificHotkeys && _isMultiScreenMode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动刷新当前屏幕的热键注册
|
||||
/// </summary>
|
||||
public void RefreshCurrentScreenHotkeys()
|
||||
{
|
||||
try
|
||||
{
|
||||
RefreshHotkeysForCurrentScreen();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"刷新当前屏幕热键时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Helper Methods
|
||||
/// <summary>
|
||||
/// 初始化多屏幕支持
|
||||
/// </summary>
|
||||
private void InitializeMultiScreenSupport()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检测是否有多个屏幕
|
||||
_isMultiScreenMode = ScreenDetectionHelper.HasMultipleScreens();
|
||||
|
||||
if (_isMultiScreenMode)
|
||||
{
|
||||
// 获取当前窗口所在的屏幕
|
||||
_currentScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
||||
|
||||
// 监听窗口位置变化事件
|
||||
_mainWindow.LocationChanged += OnWindowLocationChanged;
|
||||
|
||||
// 初始化智能热键管理
|
||||
InitializeSmartHotkeyManagement();
|
||||
}
|
||||
else
|
||||
{
|
||||
_currentScreen = ScreenDetectionHelper.GetPrimaryScreen();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化多屏幕支持时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
_isMultiScreenMode = false;
|
||||
_currentScreen = ScreenDetectionHelper.GetPrimaryScreen();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 初始化智能热键管理
|
||||
/// </summary>
|
||||
private void InitializeSmartHotkeyManagement()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 监听窗口焦点事件
|
||||
_mainWindow.GotFocus += OnWindowGotFocus;
|
||||
_mainWindow.LostFocus += OnWindowLostFocus;
|
||||
|
||||
// 监听鼠标进入/离开事件
|
||||
_mainWindow.MouseEnter += OnMouseEnterWindow;
|
||||
_mainWindow.MouseLeave += OnMouseLeaveWindow;
|
||||
|
||||
// 初始化鼠标位置监控定时器
|
||||
_mousePositionTimer = new System.Windows.Threading.DispatcherTimer();
|
||||
_mousePositionTimer.Interval = TimeSpan.FromMilliseconds(500); // 每500ms检查一次
|
||||
_mousePositionTimer.Tick += OnMousePositionTimerTick;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化热键管理时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口位置变化事件处理
|
||||
/// </summary>
|
||||
private void OnWindowLocationChanged(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys)
|
||||
return;
|
||||
|
||||
var newScreen = ScreenDetectionHelper.GetWindowScreen(_mainWindow);
|
||||
if (newScreen != null && newScreen != _currentScreen)
|
||||
{
|
||||
_currentScreen = newScreen;
|
||||
|
||||
// 重新注册热键以适应新屏幕
|
||||
RefreshHotkeysForCurrentScreen();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理窗口位置变化时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为当前屏幕刷新热键注册
|
||||
/// </summary>
|
||||
private void RefreshHotkeysForCurrentScreen()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_hotkeysShouldBeRegistered)
|
||||
return;
|
||||
|
||||
// 注销所有现有热键
|
||||
UnregisterAllHotkeys();
|
||||
|
||||
// 重新注册热键
|
||||
LoadHotkeysFromSettings();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"刷新当前屏幕热键时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口获得焦点事件处理
|
||||
/// </summary>
|
||||
private void OnWindowGotFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_isWindowFocused = true;
|
||||
UpdateHotkeyStateBasedOnContext();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理窗口获得焦点事件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口失去焦点事件处理
|
||||
/// </summary>
|
||||
private void OnWindowLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_isWindowFocused = false;
|
||||
UpdateHotkeyStateBasedOnContext();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理窗口失去焦点事件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标进入窗口事件处理
|
||||
/// </summary>
|
||||
private void OnMouseEnterWindow(object sender, System.Windows.Input.MouseEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_isMouseOverWindow = true;
|
||||
UpdateHotkeyStateBasedOnContext();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理鼠标进入窗口事件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标离开窗口事件处理
|
||||
/// </summary>
|
||||
private void OnMouseLeaveWindow(object sender, System.Windows.Input.MouseEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
_isMouseOverWindow = false;
|
||||
UpdateHotkeyStateBasedOnContext();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理鼠标离开窗口事件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标位置定时器事件处理
|
||||
/// </summary>
|
||||
private void OnMousePositionTimerTick(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_isMultiScreenMode || !_enableScreenSpecificHotkeys)
|
||||
return;
|
||||
|
||||
// 检查鼠标是否在当前窗口所在的屏幕上
|
||||
var mousePosition = Control.MousePosition;
|
||||
var currentScreen = Screen.FromPoint(mousePosition);
|
||||
|
||||
// 无论屏幕是否变化,都检查热键状态
|
||||
// 这样可以确保热键状态始终与当前上下文保持一致
|
||||
bool shouldEnableHotkeys = ShouldEnableHotkeysBasedOnContext();
|
||||
bool currentlyHasHotkeys = _registeredHotkeys.Count > 0;
|
||||
|
||||
if (shouldEnableHotkeys && !currentlyHasHotkeys)
|
||||
{
|
||||
UpdateHotkeyStateBasedOnContext();
|
||||
}
|
||||
else if (!shouldEnableHotkeys && currentlyHasHotkeys)
|
||||
{
|
||||
UpdateHotkeyStateBasedOnContext();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理鼠标位置定时器事件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据上下文更新热键状态
|
||||
/// </summary>
|
||||
private void UpdateHotkeyStateBasedOnContext()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_hotkeysShouldBeRegistered)
|
||||
return;
|
||||
|
||||
bool shouldEnableHotkeys = ShouldEnableHotkeysBasedOnContext();
|
||||
bool currentlyHasHotkeys = _registeredHotkeys.Count > 0;
|
||||
|
||||
if (shouldEnableHotkeys && !currentlyHasHotkeys)
|
||||
{
|
||||
// 需要注册快捷键
|
||||
LoadHotkeysFromSettings();
|
||||
}
|
||||
else if (!shouldEnableHotkeys && currentlyHasHotkeys)
|
||||
{
|
||||
// 需要注销快捷键
|
||||
UnregisterAllHotkeys();
|
||||
}
|
||||
// 如果状态没有变化,则不进行任何操作
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"根据上下文更新热键状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否应该注册热键(基于屏幕和模式)
|
||||
/// </summary>
|
||||
/// <returns>是否应该注册热键</returns>
|
||||
private bool ShouldRegisterHotkeys()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果禁用热键注册,则不注册
|
||||
if (!_hotkeysShouldBeRegistered)
|
||||
return false;
|
||||
|
||||
// 如果启用基于屏幕的热键注册
|
||||
if (_enableScreenSpecificHotkeys && _isMultiScreenMode)
|
||||
{
|
||||
return ShouldEnableHotkeysBasedOnContext();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检查是否应该注册热键时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return true; // 出错时默认注册
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据上下文检查是否应该启用热键
|
||||
/// </summary>
|
||||
/// <returns>是否应该启用热键</returns>
|
||||
private bool ShouldEnableHotkeysBasedOnContext()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查当前是否处于鼠标模式
|
||||
bool isMouseMode = IsInSelectMode();
|
||||
|
||||
if (isMouseMode)
|
||||
{
|
||||
// 鼠标模式下,根据设置决定是否启用快捷键
|
||||
return MainWindow.Settings.Appearance.EnableHotkeysInMouseMode;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非鼠标模式下,需要检查焦点和屏幕位置
|
||||
|
||||
// 策略1:鼠标在窗口上时启用热键(最高优先级)
|
||||
if (_isMouseOverWindow)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 策略2:在多屏幕环境下,检查鼠标是否在当前窗口所在的屏幕上
|
||||
if (_isMultiScreenMode && _enableScreenSpecificHotkeys)
|
||||
{
|
||||
var mousePosition = Control.MousePosition;
|
||||
var mouseScreen = Screen.FromPoint(mousePosition);
|
||||
|
||||
if (mouseScreen == _currentScreen)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 策略3:单屏幕环境下,窗口有焦点时启用热键
|
||||
if (_isWindowFocused)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 策略4:如果以上都不满足,但在非鼠标模式下,仍然启用快捷键
|
||||
// 这样可以确保在批注模式下快捷键始终可用
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检查是否应该启用热键时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return true; // 出错时默认启用
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定笔类型
|
||||
/// </summary>
|
||||
@@ -505,6 +947,88 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 确保配置文件存在,如果不存在则创建
|
||||
/// </summary>
|
||||
private void EnsureConfigFileExists()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果配置文件不存在,创建默认配置文件
|
||||
if (!File.Exists(HotkeyConfigFile))
|
||||
{
|
||||
CreateDefaultConfigFile();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"确保快捷键配置文件存在时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建默认的快捷键配置文件
|
||||
/// </summary>
|
||||
private void CreateDefaultConfigFile()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保配置目录存在
|
||||
string configDir = Path.GetDirectoryName(HotkeyConfigFile);
|
||||
if (!Directory.Exists(configDir))
|
||||
{
|
||||
Directory.CreateDirectory(configDir);
|
||||
}
|
||||
|
||||
// 创建默认配置对象
|
||||
var config = new HotkeyConfig
|
||||
{
|
||||
Version = "1.0",
|
||||
LastModified = DateTime.Now,
|
||||
Hotkeys = new List<HotkeyConfigItem>()
|
||||
};
|
||||
|
||||
// 添加默认快捷键配置
|
||||
config.Hotkeys.AddRange(new[]
|
||||
{
|
||||
new HotkeyConfigItem { Name = "Undo", Key = Key.Z, Modifiers = ModifierKeys.Control },
|
||||
new HotkeyConfigItem { Name = "Redo", Key = Key.Y, Modifiers = ModifierKeys.Control },
|
||||
new HotkeyConfigItem { Name = "Clear", Key = Key.E, Modifiers = ModifierKeys.Control },
|
||||
new HotkeyConfigItem { Name = "Paste", Key = Key.V, Modifiers = ModifierKeys.Control },
|
||||
new HotkeyConfigItem { Name = "SelectTool", Key = Key.S, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "DrawTool", Key = Key.D, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "EraserTool", Key = Key.E, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "BlackboardTool", Key = Key.B, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "QuitDrawTool", Key = Key.Q, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Pen1", Key = Key.D1, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Pen2", Key = Key.D2, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Pen3", Key = Key.D3, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Pen4", Key = Key.D4, Modifiers = ModifierKeys.Alt },
|
||||
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 = "Hide", Key = Key.V, Modifiers = ModifierKeys.Alt },
|
||||
new HotkeyConfigItem { Name = "Exit", Key = Key.Escape, Modifiers = ModifierKeys.None }
|
||||
});
|
||||
|
||||
// 序列化为JSON
|
||||
var settings = new JsonSerializerSettings
|
||||
{
|
||||
Formatting = Formatting.Indented
|
||||
};
|
||||
|
||||
string jsonContent = JsonConvert.SerializeObject(config, settings);
|
||||
|
||||
// 写入配置文件
|
||||
File.WriteAllText(HotkeyConfigFile, jsonContent, Encoding.UTF8);
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建默认快捷键配置文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从配置文件加载快捷键设置
|
||||
/// </summary>
|
||||
@@ -561,7 +1085,6 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"成功加载 {successCount}/{config.Hotkeys.Count} 个快捷键配置", LogHelper.LogType.Event);
|
||||
if (successCount > 0)
|
||||
{
|
||||
_hotkeysShouldBeRegistered = true;
|
||||
@@ -620,7 +1143,6 @@ namespace Ink_Canvas.Helpers
|
||||
// 直接写入原文件,覆盖原有内容
|
||||
File.WriteAllText(HotkeyConfigFile, jsonContent, Encoding.UTF8);
|
||||
|
||||
LogHelper.WriteLogToFile($"快捷键配置已保存到: {HotkeyConfigFile}", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -826,6 +1348,29 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
if (!_isDisposed)
|
||||
{
|
||||
// 注销所有快捷键
|
||||
UnregisterAllHotkeys();
|
||||
|
||||
// 停止定时器
|
||||
if (_mousePositionTimer != null)
|
||||
{
|
||||
_mousePositionTimer.Stop();
|
||||
_mousePositionTimer = null;
|
||||
}
|
||||
|
||||
// 移除事件监听器
|
||||
if (_mainWindow != null)
|
||||
{
|
||||
if (_isMultiScreenMode)
|
||||
{
|
||||
_mainWindow.LocationChanged -= OnWindowLocationChanged;
|
||||
}
|
||||
|
||||
_mainWindow.GotFocus -= OnWindowGotFocus;
|
||||
_mainWindow.LostFocus -= OnWindowLostFocus;
|
||||
_mainWindow.MouseEnter -= OnMouseEnterWindow;
|
||||
_mainWindow.MouseLeave -= OnMouseLeaveWindow;
|
||||
}
|
||||
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
try
|
||||
{
|
||||
// 确保主窗口的InkCanvas保持Ink编辑模式,防止墨迹渐隐时切换到鼠标模式
|
||||
if (_mainWindow.inkCanvas.EditingMode != InkCanvasEditingMode.Ink)
|
||||
{
|
||||
_mainWindow.inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
// 记录墨迹的起点和终点
|
||||
_strokeStartPoints[stroke] = startPoint;
|
||||
|
||||
@@ -71,37 +71,46 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用质量设置
|
||||
/// 应用质量设置
|
||||
/// </summary>
|
||||
public void ApplyQualitySettings()
|
||||
{
|
||||
// 保存用户设置的异步处理偏好
|
||||
bool userAsyncPreference = UseAsyncProcessing;
|
||||
|
||||
switch (Quality)
|
||||
{
|
||||
case SmoothingQuality.Performance:
|
||||
SmoothingStrength = 0.2;
|
||||
ResampleInterval = 4.0;
|
||||
InterpolationSteps = 6;
|
||||
SmoothingStrength = 0.15;
|
||||
ResampleInterval = 5.0;
|
||||
InterpolationSteps = 4;
|
||||
UseAdaptiveInterpolation = false;
|
||||
CurveTension = 0.2;
|
||||
CurveTension = 0.15;
|
||||
MaxConcurrentTasks = Math.Max(1, Environment.ProcessorCount / 2);
|
||||
UseHardwareAcceleration = true;
|
||||
UseAsyncProcessing = userAsyncPreference;
|
||||
break;
|
||||
|
||||
case SmoothingQuality.Balanced:
|
||||
SmoothingStrength = 0.4;
|
||||
ResampleInterval = 2.5;
|
||||
InterpolationSteps = 12;
|
||||
SmoothingStrength = 0.3;
|
||||
ResampleInterval = 3.0;
|
||||
InterpolationSteps = 8;
|
||||
UseAdaptiveInterpolation = true;
|
||||
CurveTension = 0.3;
|
||||
CurveTension = 0.25;
|
||||
MaxConcurrentTasks = Environment.ProcessorCount;
|
||||
UseHardwareAcceleration = true;
|
||||
UseAsyncProcessing = userAsyncPreference;
|
||||
break;
|
||||
|
||||
case SmoothingQuality.Quality:
|
||||
SmoothingStrength = 0.6;
|
||||
ResampleInterval = 1.5;
|
||||
InterpolationSteps = 20;
|
||||
SmoothingStrength = 0.5;
|
||||
ResampleInterval = 2.0;
|
||||
InterpolationSteps = 15;
|
||||
UseAdaptiveInterpolation = true;
|
||||
CurveTension = 0.4;
|
||||
CurveTension = 0.35;
|
||||
MaxConcurrentTasks = Environment.ProcessorCount;
|
||||
UseHardwareAcceleration = true;
|
||||
UseAsyncProcessing = userAsyncPreference;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,813 @@
|
||||
using Microsoft.Office.Interop.PowerPoint;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Windows.Ink;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 多PPT墨迹管理器 - 支持多个PPT窗口分别管理墨迹
|
||||
/// </summary>
|
||||
public class MultiPPTInkManager : IDisposable
|
||||
{
|
||||
#region Properties
|
||||
public bool IsAutoSaveEnabled { get; set; } = true;
|
||||
public string AutoSaveLocation { get; set; } = "";
|
||||
public PPTManager PPTManager { get; set; }
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private readonly Dictionary<string, PPTInkManager> _presentationManagers;
|
||||
private readonly Dictionary<string, PresentationInfo> _presentationInfos;
|
||||
private readonly object _lockObject = new object();
|
||||
private bool _disposed;
|
||||
private string _currentActivePresentationId = "";
|
||||
|
||||
// 墨迹备份机制
|
||||
private readonly Dictionary<string, Dictionary<int, StrokeCollection>> _strokeBackups;
|
||||
private DateTime _lastBackupTime = DateTime.MinValue;
|
||||
private const int BackupIntervalMinutes = 2; // 每2分钟备份一次
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public MultiPPTInkManager()
|
||||
{
|
||||
_presentationManagers = new Dictionary<string, PPTInkManager>();
|
||||
_presentationInfos = new Dictionary<string, PresentationInfo>();
|
||||
_strokeBackups = new Dictionary<string, Dictionary<int, StrokeCollection>>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 初始化新的演示文稿
|
||||
/// </summary>
|
||||
public void InitializePresentation(Presentation presentation)
|
||||
{
|
||||
if (presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
|
||||
// 如果已存在该演示文稿的管理器,先清理
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationManagers[presentationId].Dispose();
|
||||
_presentationManagers.Remove(presentationId);
|
||||
}
|
||||
|
||||
// 创建新的墨迹管理器
|
||||
var inkManager = new PPTInkManager();
|
||||
inkManager.IsAutoSaveEnabled = IsAutoSaveEnabled;
|
||||
inkManager.AutoSaveLocation = AutoSaveLocation;
|
||||
inkManager.InitializePresentation(presentation);
|
||||
|
||||
// 保存管理器和演示文稿信息
|
||||
_presentationManagers[presentationId] = inkManager;
|
||||
_presentationInfos[presentationId] = new PresentationInfo
|
||||
{
|
||||
Id = presentationId,
|
||||
Name = presentation.Name,
|
||||
FullName = presentation.FullName,
|
||||
SlideCount = presentation.Slides.Count,
|
||||
CreatedTime = DateTime.Now,
|
||||
LastAccessTime = DateTime.Now
|
||||
};
|
||||
|
||||
// 设置为当前活跃的演示文稿
|
||||
_currentActivePresentationId = presentationId;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化多PPT墨迹管理失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定的演示文稿
|
||||
/// </summary>
|
||||
public bool SwitchToPresentation(Presentation presentation)
|
||||
{
|
||||
if (presentation == null) return false;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
// 如果切换的是不同的演示文稿,先保存当前活跃演示文稿的墨迹
|
||||
if (!string.IsNullOrEmpty(_currentActivePresentationId) &&
|
||||
_currentActivePresentationId != presentationId)
|
||||
{
|
||||
var currentManager = GetCurrentManager();
|
||||
if (currentManager != null)
|
||||
{
|
||||
// 获取当前活跃的演示文稿并保存墨迹
|
||||
var currentPresentation = GetCurrentActivePresentation();
|
||||
if (currentPresentation != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
currentManager.SaveAllStrokesToFile(currentPresentation);
|
||||
LogHelper.WriteLogToFile($"已保存当前演示文稿墨迹: {currentPresentation.Name}", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存当前演示文稿墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_currentActivePresentationId = presentationId;
|
||||
|
||||
// 更新最后访问时间
|
||||
if (_presentationInfos.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationInfos[presentationId].LastAccessTime = DateTime.Now;
|
||||
}
|
||||
|
||||
if (_currentActivePresentationId != presentationId)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"已切换到演示文稿: {presentation.Name}", LogHelper.LogType.Trace);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不存在,尝试初始化
|
||||
InitializePresentation(presentation);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换到演示文稿失败: {ex}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前页面的墨迹
|
||||
/// </summary>
|
||||
public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
if (slideIndex <= 0 || strokes == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
// 保存到管理器
|
||||
manager.SaveCurrentSlideStrokes(slideIndex, strokes);
|
||||
|
||||
// 只有在保存成功后才创建备份
|
||||
if (!string.IsNullOrEmpty(_currentActivePresentationId))
|
||||
{
|
||||
CreateStrokeBackup(_currentActivePresentationId, slideIndex, strokes);
|
||||
}
|
||||
|
||||
// 检查是否需要执行定期备份
|
||||
CheckAndPerformBackup();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存当前页面墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制保存指定页面的墨迹(忽略锁定状态)
|
||||
/// </summary>
|
||||
public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
if (slideIndex <= 0 || strokes == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
manager.ForceSaveSlideStrokes(slideIndex, strokes);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"强制保存页面墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载指定页面的墨迹
|
||||
/// </summary>
|
||||
public StrokeCollection LoadSlideStrokes(int slideIndex)
|
||||
{
|
||||
if (slideIndex <= 0) return new StrokeCollection();
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
var strokes = manager.LoadSlideStrokes(slideIndex);
|
||||
|
||||
// 如果从管理器加载失败,尝试从备份恢复
|
||||
if (strokes == null || strokes.Count == 0)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_currentActivePresentationId))
|
||||
{
|
||||
strokes = RestoreStrokeFromBackup(_currentActivePresentationId, slideIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return strokes ?? new StrokeCollection();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载页面墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
|
||||
// 尝试从备份恢复
|
||||
if (!string.IsNullOrEmpty(_currentActivePresentationId))
|
||||
{
|
||||
return RestoreStrokeFromBackup(_currentActivePresentationId, slideIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new StrokeCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定页面并加载墨迹
|
||||
/// </summary>
|
||||
public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
return manager.SwitchToSlide(slideIndex, currentStrokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"无法获取当前墨迹管理器,页面切换失败: {slideIndex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换页面墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
return new StrokeCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存所有墨迹到文件
|
||||
/// </summary>
|
||||
public void SaveAllStrokesToFile(Presentation presentation)
|
||||
{
|
||||
if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationManagers[presentationId].SaveAllStrokesToFile(presentation);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存所有墨迹到文件失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件加载已保存的墨迹
|
||||
/// </summary>
|
||||
public void LoadSavedStrokes(Presentation presentation)
|
||||
{
|
||||
if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation) || presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationManagers[presentationId].LoadSavedStrokes();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从文件加载墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除指定演示文稿的所有墨迹
|
||||
/// </summary>
|
||||
public void ClearPresentationStrokes(Presentation presentation)
|
||||
{
|
||||
if (presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationManagers[presentationId].ClearAllStrokes();
|
||||
LogHelper.WriteLogToFile($"已清除演示文稿墨迹: {presentation.Name}", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除演示文稿墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有演示文稿的墨迹
|
||||
/// </summary>
|
||||
public void ClearAllStrokes()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var manager in _presentationManagers.Values)
|
||||
{
|
||||
manager?.ClearAllStrokes();
|
||||
}
|
||||
LogHelper.WriteLogToFile("已清除所有演示文稿墨迹", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除所有墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 翻页后锁定墨迹写入
|
||||
/// </summary>
|
||||
public void LockInkForSlide(int slideIndex)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
manager.LockInkForSlide(slideIndex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"锁定墨迹写入失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以写入墨迹
|
||||
/// </summary>
|
||||
public bool CanWriteInk(int currentSlideIndex)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
return manager.CanWriteInk(currentSlideIndex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检查墨迹写入权限失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置当前演示文稿的墨迹锁定状态
|
||||
/// </summary>
|
||||
public void ResetCurrentPresentationLockState()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var manager = GetCurrentManager();
|
||||
if (manager != null)
|
||||
{
|
||||
manager.ResetLockState();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"重置墨迹锁定状态失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除演示文稿管理器
|
||||
/// </summary>
|
||||
public void RemovePresentation(Presentation presentation)
|
||||
{
|
||||
if (presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationId = GeneratePresentationId(presentation);
|
||||
|
||||
if (_presentationManagers.ContainsKey(presentationId))
|
||||
{
|
||||
// 保存墨迹到文件
|
||||
_presentationManagers[presentationId].SaveAllStrokesToFile(presentation);
|
||||
|
||||
// 释放资源
|
||||
_presentationManagers[presentationId].Dispose();
|
||||
_presentationManagers.Remove(presentationId);
|
||||
}
|
||||
|
||||
if (_presentationInfos.ContainsKey(presentationId))
|
||||
{
|
||||
_presentationInfos.Remove(presentationId);
|
||||
}
|
||||
|
||||
// 如果移除的是当前活跃的演示文稿,重置活跃ID
|
||||
if (_currentActivePresentationId == presentationId)
|
||||
{
|
||||
_currentActivePresentationId = "";
|
||||
}
|
||||
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前管理的演示文稿数量
|
||||
/// </summary>
|
||||
public int GetPresentationCount()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _presentationManagers.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有演示文稿信息
|
||||
/// </summary>
|
||||
public List<PresentationInfo> GetAllPresentationInfos()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _presentationInfos.Values.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理长时间未访问的演示文稿管理器
|
||||
/// </summary>
|
||||
public void CleanupInactivePresentations(TimeSpan inactiveThreshold)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var inactiveIds = new List<string>();
|
||||
var cutoffTime = DateTime.Now - inactiveThreshold;
|
||||
|
||||
foreach (var info in _presentationInfos.Values)
|
||||
{
|
||||
if (info.LastAccessTime < cutoffTime && info.Id != _currentActivePresentationId)
|
||||
{
|
||||
inactiveIds.Add(info.Id);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var id in inactiveIds)
|
||||
{
|
||||
if (_presentationManagers.ContainsKey(id))
|
||||
{
|
||||
_presentationManagers[id].Dispose();
|
||||
_presentationManagers.Remove(id);
|
||||
}
|
||||
_presentationInfos.Remove(id);
|
||||
|
||||
// 清理备份数据
|
||||
if (_strokeBackups.ContainsKey(id))
|
||||
{
|
||||
_strokeBackups.Remove(id);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已清理非活跃演示文稿: {id}", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理非活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建墨迹备份
|
||||
/// </summary>
|
||||
private void CreateStrokeBackup(string presentationId, int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0) return;
|
||||
|
||||
if (!_strokeBackups.ContainsKey(presentationId))
|
||||
{
|
||||
_strokeBackups[presentationId] = new Dictionary<int, StrokeCollection>();
|
||||
}
|
||||
|
||||
// 释放旧的备份
|
||||
if (_strokeBackups[presentationId].ContainsKey(slideIndex))
|
||||
{
|
||||
_strokeBackups[presentationId][slideIndex] = null;
|
||||
}
|
||||
|
||||
// 创建新的备份
|
||||
_strokeBackups[presentationId][slideIndex] = strokes.Clone();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建墨迹备份失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从备份恢复墨迹
|
||||
/// </summary>
|
||||
private StrokeCollection RestoreStrokeFromBackup(string presentationId, int slideIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_strokeBackups.ContainsKey(presentationId) &&
|
||||
_strokeBackups[presentationId].ContainsKey(slideIndex))
|
||||
{
|
||||
var backup = _strokeBackups[presentationId][slideIndex];
|
||||
if (backup != null)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从备份恢复第{slideIndex}页墨迹", LogHelper.LogType.Trace);
|
||||
return backup.Clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从备份恢复墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return new StrokeCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查并执行定期备份
|
||||
/// </summary>
|
||||
private void CheckAndPerformBackup()
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 检查是否需要执行备份
|
||||
if (now - _lastBackupTime < TimeSpan.FromMinutes(BackupIntervalMinutes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 备份当前活跃演示文稿的所有墨迹
|
||||
if (!string.IsNullOrEmpty(_currentActivePresentationId) &&
|
||||
_presentationManagers.ContainsKey(_currentActivePresentationId))
|
||||
{
|
||||
var manager = _presentationManagers[_currentActivePresentationId];
|
||||
if (manager != null)
|
||||
{
|
||||
// 这里可以添加更详细的备份逻辑
|
||||
}
|
||||
}
|
||||
|
||||
_lastBackupTime = now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"定期备份检查失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
private PPTInkManager GetCurrentManager()
|
||||
{
|
||||
if (string.IsNullOrEmpty(_currentActivePresentationId) ||
|
||||
!_presentationManagers.ContainsKey(_currentActivePresentationId))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _presentationManagers[_currentActivePresentationId];
|
||||
}
|
||||
|
||||
private Presentation GetCurrentActivePresentation()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 通过PPTManager获取当前活跃的演示文稿
|
||||
return PPTManager?.GetCurrentActivePresentation();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private string GeneratePresentationId(Presentation presentation)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查COM对象是否仍然有效
|
||||
if (presentation == null)
|
||||
{
|
||||
return $"invalid_{DateTime.Now.Ticks}";
|
||||
}
|
||||
|
||||
var presentationPath = presentation.FullName;
|
||||
var fileHash = GetFileHash(presentationPath);
|
||||
var processId = GetProcessId(presentation);
|
||||
return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}_{processId}";
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
|
||||
{
|
||||
return $"disconnected_{DateTime.Now.Ticks}";
|
||||
}
|
||||
return $"error_{DateTime.Now.Ticks}";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return $"unknown_{DateTime.Now.Ticks}";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetFileHash(string filePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath)) return "unknown";
|
||||
|
||||
using (var md5 = MD5.Create())
|
||||
{
|
||||
byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(filePath));
|
||||
return BitConverter.ToString(hashBytes).Replace("-", "").Substring(0, 8);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 所有异常都静默处理,避免日志噪音
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetProcessId(Presentation presentation)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 尝试获取PowerPoint应用程序的进程ID
|
||||
if (presentation.Application != null)
|
||||
{
|
||||
// 通过COM对象获取进程信息
|
||||
var hwnd = presentation.Application.HWND;
|
||||
if (hwnd != 0)
|
||||
{
|
||||
return hwnd.ToString();
|
||||
}
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
// COM对象已失效,这是正常情况,完全静默处理
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
|
||||
{
|
||||
return "disconnected";
|
||||
}
|
||||
return "error";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Dispose
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
// 释放所有管理器
|
||||
foreach (var manager in _presentationManagers.Values)
|
||||
{
|
||||
manager?.Dispose();
|
||||
}
|
||||
_presentationManagers.Clear();
|
||||
_presentationInfos.Clear();
|
||||
|
||||
// 清理备份数据
|
||||
foreach (var backupDict in _strokeBackups.Values)
|
||||
{
|
||||
foreach (var backup in backupDict.Values)
|
||||
{
|
||||
backup?.Clear();
|
||||
}
|
||||
backupDict.Clear();
|
||||
}
|
||||
_strokeBackups.Clear();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 演示文稿信息
|
||||
/// </summary>
|
||||
public class PresentationInfo
|
||||
{
|
||||
public string Id { get; set; }
|
||||
public string Name { get; set; }
|
||||
public string FullName { get; set; }
|
||||
public int SlideCount { get; set; }
|
||||
public DateTime CreatedTime { get; set; }
|
||||
public DateTime LastAccessTime { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -25,10 +25,14 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用于显示笔迹的类
|
||||
/// 用于显示笔迹的类
|
||||
/// </summary>
|
||||
public class StrokeVisual : DrawingVisual
|
||||
{
|
||||
private bool _needsRedraw = true;
|
||||
private int _lastPointCount = 0;
|
||||
private const int REDRAW_THRESHOLD = 3;
|
||||
|
||||
/// <summary>
|
||||
/// 创建显示笔迹的类
|
||||
/// </summary>
|
||||
@@ -49,15 +53,20 @@ namespace Ink_Canvas.Helpers
|
||||
public StrokeVisual(DrawingAttributes drawingAttributes)
|
||||
{
|
||||
_drawingAttributes = drawingAttributes;
|
||||
|
||||
// 启用硬件加速
|
||||
RenderOptions.SetBitmapScalingMode(this, BitmapScalingMode.HighQuality);
|
||||
RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
|
||||
RenderOptions.SetCachingHint(this, CachingHint.Cache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置或获取显示的笔迹
|
||||
/// 设置或获取显示的笔迹
|
||||
/// </summary>
|
||||
public Stroke Stroke { set; get; }
|
||||
|
||||
/// <summary>
|
||||
/// 在笔迹中添加点
|
||||
/// 在笔迹中添加点
|
||||
/// </summary>
|
||||
/// <param name="point"></param>
|
||||
public void Add(StylusPoint point)
|
||||
@@ -66,28 +75,50 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
var collection = new StylusPointCollection { point };
|
||||
Stroke = new Stroke(collection) { DrawingAttributes = _drawingAttributes };
|
||||
_lastPointCount = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Stroke.StylusPoints.Add(point);
|
||||
_lastPointCount++;
|
||||
}
|
||||
|
||||
// 标记需要重绘
|
||||
_needsRedraw = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新画出笔迹
|
||||
/// 重新画出笔迹
|
||||
/// </summary>
|
||||
public void Redraw()
|
||||
{
|
||||
if (!_needsRedraw || Stroke == null) return;
|
||||
|
||||
if (_lastPointCount % REDRAW_THRESHOLD != 0 && _lastPointCount > REDRAW_THRESHOLD)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
using (var dc = RenderOpen())
|
||||
{
|
||||
Stroke.Draw(dc);
|
||||
}
|
||||
_needsRedraw = false;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制重绘
|
||||
/// </summary>
|
||||
public void ForceRedraw()
|
||||
{
|
||||
_needsRedraw = true;
|
||||
Redraw();
|
||||
}
|
||||
|
||||
private readonly DrawingAttributes _drawingAttributes;
|
||||
|
||||
public static implicit operator Stroke(StrokeVisual v)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Office.Interop.PowerPoint;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Windows.Ink;
|
||||
@@ -29,6 +30,17 @@ namespace Ink_Canvas.Helpers
|
||||
private DateTime _inkLockUntil = DateTime.MinValue;
|
||||
private int _lockedSlideIndex = -1;
|
||||
private const int InkLockMilliseconds = 500;
|
||||
|
||||
// 添加快速切换保护机制
|
||||
private DateTime _lastSwitchTime = DateTime.MinValue;
|
||||
private int _lastSwitchSlideIndex = -1;
|
||||
private const int MinSwitchIntervalMs = 100; // 最小切换间隔100毫秒
|
||||
|
||||
// 内存管理相关字段
|
||||
private long _totalMemoryUsage = 0;
|
||||
private const long MaxMemoryUsageBytes = 100 * 1024 * 1024; // 100MB限制
|
||||
private DateTime _lastMemoryCleanup = DateTime.MinValue;
|
||||
private const int MemoryCleanupIntervalMinutes = 5; // 5分钟清理一次
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
@@ -66,7 +78,20 @@ namespace Ink_Canvas.Helpers
|
||||
_currentPresentationId = GeneratePresentationId(presentation);
|
||||
|
||||
// 重新初始化内存流数组
|
||||
var slideCount = presentation.Slides.Count;
|
||||
int slideCount = 0;
|
||||
try
|
||||
{
|
||||
slideCount = presentation.Slides.Count;
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x80048010)
|
||||
{
|
||||
return;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
_memoryStreams = new MemoryStream[slideCount + 2];
|
||||
|
||||
// 如果启用自动保存,尝试加载已保存的墨迹
|
||||
@@ -75,7 +100,6 @@ namespace Ink_Canvas.Helpers
|
||||
LoadSavedStrokes();
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已初始化演示文稿墨迹管理: {presentation.Name}, 幻灯片数量: {slideCount}", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -95,24 +119,39 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查墨迹锁定
|
||||
// 检查墨迹锁定
|
||||
if (!CanWriteInk(slideIndex))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"墨迹写入被锁定,当前页:{slideIndex},锁定页:{_lockedSlideIndex}", LogHelper.LogType.Warning);
|
||||
if (DateTime.Now < _inkLockUntil)
|
||||
{
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (slideIndex < _memoryStreams.Length)
|
||||
{
|
||||
// 先释放旧的内存流,防止内存泄漏
|
||||
try
|
||||
{
|
||||
_memoryStreams[slideIndex]?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放旧内存流失败: {ex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
// 创建新的内存流
|
||||
var ms = new MemoryStream();
|
||||
strokes.Save(ms);
|
||||
ms.Position = 0;
|
||||
|
||||
// 释放旧的内存流
|
||||
_memoryStreams[slideIndex]?.Dispose();
|
||||
_memoryStreams[slideIndex] = ms;
|
||||
|
||||
LogHelper.WriteLogToFile($"已保存第{slideIndex}页墨迹,大小: {ms.Length} bytes", LogHelper.LogType.Trace);
|
||||
if (ms.Length > 0)
|
||||
{
|
||||
}
|
||||
|
||||
// 检查内存使用情况
|
||||
CheckAndPerformMemoryCleanup();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -122,6 +161,45 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制保存指定页面的墨迹
|
||||
/// </summary>
|
||||
public void ForceSaveSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
if (slideIndex <= 0 || strokes == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (slideIndex < _memoryStreams.Length)
|
||||
{
|
||||
// 先释放旧的内存流,防止内存泄漏
|
||||
try
|
||||
{
|
||||
_memoryStreams[slideIndex]?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放旧内存流失败: {ex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
// 创建新的内存流
|
||||
var ms = new MemoryStream();
|
||||
strokes.Save(ms);
|
||||
ms.Position = 0;
|
||||
_memoryStreams[slideIndex] = ms;
|
||||
|
||||
LogHelper.WriteLogToFile($"已强制保存第{slideIndex}页墨迹,大小: {ms.Length} bytes", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"强制保存第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载指定页面的墨迹
|
||||
/// </summary>
|
||||
@@ -137,7 +215,6 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
_memoryStreams[slideIndex].Position = 0;
|
||||
var strokes = new StrokeCollection(_memoryStreams[slideIndex]);
|
||||
LogHelper.WriteLogToFile($"已加载第{slideIndex}页墨迹,笔画数量: {strokes.Count}", LogHelper.LogType.Trace);
|
||||
return strokes;
|
||||
}
|
||||
}
|
||||
@@ -159,26 +236,29 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果有当前墨迹,先保存到正确的页面
|
||||
if (currentStrokes != null && currentStrokes.Count > 0)
|
||||
// 检查快速切换保护
|
||||
var now = DateTime.Now;
|
||||
if (now - _lastSwitchTime < TimeSpan.FromMilliseconds(MinSwitchIntervalMs) &&
|
||||
_lastSwitchSlideIndex == slideIndex)
|
||||
{
|
||||
// 确定要保存的页面索引
|
||||
int saveToSlideIndex = _lockedSlideIndex > 0 ? _lockedSlideIndex : slideIndex;
|
||||
|
||||
// 确保页面索引有效
|
||||
if (saveToSlideIndex > 0 && saveToSlideIndex < _memoryStreams.Length)
|
||||
{
|
||||
SaveCurrentSlideStrokes(saveToSlideIndex, currentStrokes);
|
||||
LogHelper.WriteLogToFile($"已保存第{saveToSlideIndex}页墨迹,墨迹数量: {currentStrokes.Count}", LogHelper.LogType.Trace);
|
||||
}
|
||||
LogHelper.WriteLogToFile($"快速切换保护:忽略重复的页面切换请求 {slideIndex}", LogHelper.LogType.Warning);
|
||||
return LoadSlideStrokes(slideIndex);
|
||||
}
|
||||
|
||||
|
||||
// 设置墨迹锁定
|
||||
LockInkForSlide(slideIndex);
|
||||
|
||||
// 加载新页面的墨迹
|
||||
var newStrokes = LoadSlideStrokes(slideIndex);
|
||||
LogHelper.WriteLogToFile($"已切换到第{slideIndex}页,加载墨迹数量: {newStrokes.Count}", LogHelper.LogType.Trace);
|
||||
|
||||
// 更新切换记录
|
||||
_lastSwitchTime = now;
|
||||
_lastSwitchSlideIndex = slideIndex;
|
||||
|
||||
if (newStrokes.Count > 0)
|
||||
{
|
||||
}
|
||||
|
||||
return newStrokes;
|
||||
}
|
||||
@@ -219,7 +299,23 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
// 保存所有页面的墨迹
|
||||
int savedCount = 0;
|
||||
for (int i = 1; i <= presentation.Slides.Count && i < _memoryStreams.Length; i++)
|
||||
int slideCount = 0;
|
||||
|
||||
try
|
||||
{
|
||||
slideCount = presentation.Slides.Count;
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x80048010)
|
||||
{
|
||||
return;
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
for (int i = 1; i <= slideCount && i < _memoryStreams.Length; i++)
|
||||
{
|
||||
if (_memoryStreams[i] != null)
|
||||
{
|
||||
@@ -235,7 +331,6 @@ namespace Ink_Canvas.Helpers
|
||||
File.WriteAllBytes(filePath, srcBuf);
|
||||
savedCount++;
|
||||
|
||||
LogHelper.WriteLogToFile($"已保存第{i}页墨迹,大小: {byteLength} bytes", LogHelper.LogType.Trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -254,7 +349,6 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已保存{savedCount}页墨迹到文件", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -301,7 +395,6 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已从文件加载{loadedCount}页墨迹", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -319,13 +412,30 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < _memoryStreams.Length; i++)
|
||||
// 安全释放所有内存流
|
||||
if (_memoryStreams != null)
|
||||
{
|
||||
_memoryStreams[i]?.Dispose();
|
||||
_memoryStreams[i] = null;
|
||||
for (int i = 0; i < _memoryStreams.Length; i++)
|
||||
{
|
||||
try
|
||||
{
|
||||
_memoryStreams[i]?.Dispose();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放内存流{i}失败: {ex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_memoryStreams[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 重新初始化数组
|
||||
_memoryStreams = new MemoryStream[_maxSlides + 2];
|
||||
}
|
||||
|
||||
CurrentStrokes.Clear();
|
||||
CurrentStrokes?.Clear();
|
||||
LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -349,9 +459,145 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public bool CanWriteInk(int currentSlideIndex)
|
||||
{
|
||||
if (DateTime.Now < _inkLockUntil) return false;
|
||||
if (currentSlideIndex != _lockedSlideIndex && _lockedSlideIndex > 0) return false;
|
||||
return true;
|
||||
// 如果锁定时间已过,允许写入
|
||||
if (DateTime.Now >= _inkLockUntil)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果当前页面与锁定页面相同,允许写入(用户在当前页面绘制)
|
||||
if (currentSlideIndex == _lockedSlideIndex)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 如果当前页面不是锁定页面,但锁定时间很短(小于50ms),允许写入
|
||||
// 这样可以确保旧页面的墨迹能够及时保存
|
||||
if (DateTime.Now - (_inkLockUntil.AddMilliseconds(-InkLockMilliseconds)) < TimeSpan.FromMilliseconds(50))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 只有在快速切换且页面不同时才锁定
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置墨迹锁定状态
|
||||
/// </summary>
|
||||
public void ResetLockState()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
_inkLockUntil = DateTime.MinValue;
|
||||
_lockedSlideIndex = -1;
|
||||
_lastSwitchTime = DateTime.MinValue;
|
||||
_lastSwitchSlideIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查并执行内存清理
|
||||
/// </summary>
|
||||
private void CheckAndPerformMemoryCleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 检查是否需要执行内存清理
|
||||
if (now - _lastMemoryCleanup < TimeSpan.FromMinutes(MemoryCleanupIntervalMinutes))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算当前内存使用量
|
||||
long currentMemoryUsage = 0;
|
||||
if (_memoryStreams != null)
|
||||
{
|
||||
for (int i = 0; i < _memoryStreams.Length; i++)
|
||||
{
|
||||
if (_memoryStreams[i] != null)
|
||||
{
|
||||
currentMemoryUsage += _memoryStreams[i].Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_totalMemoryUsage = currentMemoryUsage;
|
||||
|
||||
// 如果内存使用量超过限制,执行清理
|
||||
if (currentMemoryUsage > MaxMemoryUsageBytes)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"内存使用量超限 ({currentMemoryUsage / 1024 / 1024}MB),开始清理", LogHelper.LogType.Warning);
|
||||
|
||||
// 清理非当前页面的墨迹
|
||||
CleanupInactiveSlideStrokes();
|
||||
|
||||
_lastMemoryCleanup = now;
|
||||
LogHelper.WriteLogToFile($"内存清理完成,当前使用量: {_totalMemoryUsage / 1024 / 1024}MB", LogHelper.LogType.Trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
_lastMemoryCleanup = now;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"内存清理检查失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理非活跃页面的墨迹
|
||||
/// </summary>
|
||||
private void CleanupInactiveSlideStrokes()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_memoryStreams == null) return;
|
||||
|
||||
int cleanedCount = 0;
|
||||
long freedMemory = 0;
|
||||
|
||||
for (int i = 0; i < _memoryStreams.Length; i++)
|
||||
{
|
||||
// 保留当前锁定页面和最近访问的页面
|
||||
if (i == _lockedSlideIndex || i == _lastSwitchSlideIndex)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_memoryStreams[i] != null)
|
||||
{
|
||||
long memorySize = _memoryStreams[i].Length;
|
||||
|
||||
try
|
||||
{
|
||||
_memoryStreams[i].Dispose();
|
||||
freedMemory += memorySize;
|
||||
cleanedCount++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理页面{i}墨迹失败: {ex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_memoryStreams[i] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cleanedCount > 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"已清理{cleanedCount}个页面的墨迹,释放内存: {freedMemory / 1024}KB", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理非活跃页面墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -70,17 +70,17 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
if (PPTApplication == null || !Marshal.IsComObject(PPTApplication)) return false;
|
||||
|
||||
|
||||
// 检查是否有放映窗口
|
||||
var slideShowWindows = PPTApplication.SlideShowWindows;
|
||||
if (slideShowWindows == null || slideShowWindows.Count == 0) return false;
|
||||
|
||||
|
||||
// 验证放映窗口是否真正有效
|
||||
try
|
||||
{
|
||||
var slideShowWindow = slideShowWindows[1];
|
||||
if (slideShowWindow == null) return false;
|
||||
|
||||
|
||||
// 尝试访问放映窗口的属性来验证其有效性
|
||||
var _ = slideShowWindow.View;
|
||||
return true;
|
||||
@@ -93,7 +93,6 @@ namespace Ink_Canvas.Helpers
|
||||
// COM对象已失效,触发断开连接
|
||||
DisconnectFromPPT();
|
||||
}
|
||||
LogHelper.WriteLogToFile($"验证PPT放映窗口失败: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -249,7 +248,6 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
if (!currentSlideShowState)
|
||||
{
|
||||
LogHelper.WriteLogToFile("检测到PPT放映已结束", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,16 +275,6 @@ namespace Ink_Canvas.Helpers
|
||||
catch (COMException ex)
|
||||
{
|
||||
var hr = (uint)ex.HResult;
|
||||
// 忽略常见的PowerPoint连接错误:
|
||||
// 0x800401E3: 操作无法使用
|
||||
// 0x80004005: 未指定错误
|
||||
// 0x800706B5: RPC服务器不可用
|
||||
// 0x8001010E: 应用程序调用一个已为另一线程整理的接口
|
||||
// 0x800401F3: 无效的类字符串(PowerPoint未安装或COM组件未注册)
|
||||
if (hr != 0x800401E3 && hr != 0x80004005 && hr != 0x800706B5 && hr != 0x8001010E && hr != 0x800401F3)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"连接PowerPoint失败: {ex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
@@ -294,9 +282,8 @@ namespace Ink_Canvas.Helpers
|
||||
// COM对象类型转换失败
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (Exception)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"连接PowerPoint时发生意外错误: {ex}", LogHelper.LogType.Warning);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -422,27 +409,32 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
// COM对象已经被释放或在错误的线程中,忽略这些错误
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr != 0x8001010E && hr != 0x80004005 && hr != 0x800706B5)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"取消PPT事件注册COM异常: {comEx}", LogHelper.LogType.Warning);
|
||||
}
|
||||
LogHelper.WriteLogToFile($"取消PPT事件注册时COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
|
||||
}
|
||||
catch (InvalidCastException)
|
||||
{
|
||||
// COM对象类型转换失败,通常是因为对象已经被释放
|
||||
LogHelper.WriteLogToFile("PPT COM对象已被释放,跳过事件注册取消", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"取消PPT事件注册时发生异常: {ex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}, DispatcherPriority.Normal, CancellationToken.None, TimeSpan.FromSeconds(1));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录但不抛出异常,确保清理过程能够继续
|
||||
LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex.GetType().Name} - {ex.Message}", LogHelper.LogType.Warning);
|
||||
LogHelper.WriteLogToFile($"取消PPT事件注册失败: {ex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
// 安全释放COM对象
|
||||
SafeReleaseComObject(CurrentSlide, "CurrentSlide");
|
||||
SafeReleaseComObject(CurrentSlides, "CurrentSlides");
|
||||
SafeReleaseComObject(CurrentPresentation, "CurrentPresentation");
|
||||
SafeReleaseComObject(PPTApplication, "PPTApplication");
|
||||
|
||||
// 清理引用
|
||||
PPTApplication = null;
|
||||
CurrentPresentation = null;
|
||||
@@ -465,6 +457,30 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全释放COM对象
|
||||
/// </summary>
|
||||
private void SafeReleaseComObject(object comObject, string objectName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (comObject != null && Marshal.IsComObject(comObject))
|
||||
{
|
||||
int refCount = Marshal.ReleaseComObject(comObject);
|
||||
LogHelper.WriteLogToFile($"已释放COM对象 {objectName},引用计数: {refCount}", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时COM异常: {comEx.Message} (HR: 0x{hr:X8})", LogHelper.LogType.Warning);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放COM对象 {objectName} 时发生异常: {ex}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateCurrentPresentationInfo()
|
||||
{
|
||||
try
|
||||
@@ -479,7 +495,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
CurrentPresentation = activePresentation;
|
||||
CurrentSlides = CurrentPresentation.Slides;
|
||||
|
||||
|
||||
// 验证页数读取是否成功
|
||||
try
|
||||
{
|
||||
@@ -487,7 +503,6 @@ namespace Ink_Canvas.Helpers
|
||||
if (slideCount > 0)
|
||||
{
|
||||
SlidesCount = slideCount;
|
||||
LogHelper.WriteLogToFile($"成功读取PPT页数: {slideCount}", LogHelper.LogType.Trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -603,7 +618,6 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
PresentationClose?.Invoke(pres);
|
||||
LogHelper.WriteLogToFile($"演示文稿已关闭: {pres?.Name}", LogHelper.LogType.Event);
|
||||
|
||||
// 重新启动连接检查
|
||||
_connectionCheckTimer?.Start();
|
||||
@@ -620,7 +634,6 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
UpdateCurrentPresentationInfo();
|
||||
SlideShowBegin?.Invoke(wn);
|
||||
LogHelper.WriteLogToFile("幻灯片放映开始", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -634,7 +647,6 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
UpdateCurrentPresentationInfo();
|
||||
SlideShowNextSlide?.Invoke(wn);
|
||||
LogHelper.WriteLogToFile($"幻灯片切换到第{wn?.View?.CurrentShowPosition}页", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -653,7 +665,6 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
SlideShowEnd?.Invoke(pres);
|
||||
LogHelper.WriteLogToFile("幻灯片放映结束", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -667,14 +678,25 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsConnected || !IsInSlideShow || PPTApplication == null) return false;
|
||||
if (!IsConnected || PPTApplication == null) return false;
|
||||
if (!Marshal.IsComObject(PPTApplication)) return false;
|
||||
|
||||
var slideShowWindow = PPTApplication.SlideShowWindows[1];
|
||||
if (slideShowWindow?.View != null)
|
||||
if (IsInSlideShow && PPTApplication.SlideShowWindows.Count >= 1)
|
||||
{
|
||||
slideShowWindow.View.GotoSlide(slideNumber);
|
||||
return true;
|
||||
var slideShowWindow = PPTApplication.SlideShowWindows[1];
|
||||
if (slideShowWindow?.View != null)
|
||||
{
|
||||
slideShowWindow.View.GotoSlide(slideNumber);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (CurrentPresentation != null)
|
||||
{
|
||||
if (CurrentPresentation.Windows?.Count >= 1)
|
||||
{
|
||||
CurrentPresentation.Windows[1].View.GotoSlide(slideNumber);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -825,14 +847,47 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
}
|
||||
|
||||
public int GetCurrentSlideNumber()
|
||||
/// <summary>
|
||||
/// 获取当前活跃的演示文稿(用于多窗口墨迹分离)
|
||||
/// </summary>
|
||||
public Presentation GetCurrentActivePresentation()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsConnected || !IsInSlideShow || PPTApplication == null) return 0;
|
||||
if (!Marshal.IsComObject(PPTApplication)) return 0;
|
||||
if (!IsConnected || PPTApplication == null) return null;
|
||||
if (!Marshal.IsComObject(PPTApplication)) return null;
|
||||
|
||||
return PPTApplication.SlideShowWindows[1]?.View?.CurrentShowPosition ?? 0;
|
||||
// 如果在放映模式,获取放映窗口的演示文稿
|
||||
if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var slideShowWindow = PPTApplication.SlideShowWindows[1];
|
||||
if (slideShowWindow?.View != null)
|
||||
{
|
||||
return (Presentation)slideShowWindow.View.Slide.Parent;
|
||||
}
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x80048240) // Integer out of range
|
||||
{
|
||||
// 放映窗口已不存在,返回null
|
||||
return null;
|
||||
}
|
||||
throw; // 重新抛出其他COM异常
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不在放映模式,获取活动窗口的演示文稿
|
||||
if (PPTApplication.ActiveWindow?.Presentation != null)
|
||||
{
|
||||
return PPTApplication.ActiveWindow.Presentation;
|
||||
}
|
||||
|
||||
// 如果没有活动窗口,返回当前演示文稿
|
||||
return CurrentPresentation;
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
@@ -842,12 +897,62 @@ namespace Ink_Canvas.Helpers
|
||||
// COM对象已失效,触发断开连接
|
||||
DisconnectFromPPT();
|
||||
}
|
||||
LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {comEx.Message}", LogHelper.LogType.Warning);
|
||||
return 0;
|
||||
LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {comEx.Message}", LogHelper.LogType.Warning);
|
||||
return CurrentPresentation;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取当前幻灯片编号失败: {ex}", LogHelper.LogType.Error);
|
||||
LogHelper.WriteLogToFile($"获取当前活跃演示文稿失败: {ex}", LogHelper.LogType.Error);
|
||||
return CurrentPresentation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前幻灯片编号
|
||||
/// </summary>
|
||||
public int GetCurrentSlideNumber()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsConnected || PPTApplication == null) return 0;
|
||||
if (!Marshal.IsComObject(PPTApplication)) return 0;
|
||||
|
||||
// 如果在放映模式,获取放映窗口的当前幻灯片编号
|
||||
if (IsInSlideShow && PPTApplication.SlideShowWindows.Count > 0)
|
||||
{
|
||||
var slideShowWindow = PPTApplication.SlideShowWindows[1];
|
||||
if (slideShowWindow?.View != null)
|
||||
{
|
||||
return slideShowWindow.View.CurrentShowPosition;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果不在放映模式,获取活动窗口的当前幻灯片编号
|
||||
if (PPTApplication.ActiveWindow?.Selection?.SlideRange?.SlideNumber > 0)
|
||||
{
|
||||
return PPTApplication.ActiveWindow.Selection.SlideRange.SlideNumber;
|
||||
}
|
||||
|
||||
// 如果CurrentSlide存在,尝试获取其编号
|
||||
if (CurrentSlide != null && Marshal.IsComObject(CurrentSlide))
|
||||
{
|
||||
return CurrentSlide.SlideNumber;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
catch (COMException comEx)
|
||||
{
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x8001010E || hr == 0x80004005)
|
||||
{
|
||||
// COM对象已失效,触发断开连接
|
||||
DisconnectFromPPT();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -1042,7 +1147,7 @@ namespace Ink_Canvas.Helpers
|
||||
_wpsProcessCheckTimer.Dispose();
|
||||
}
|
||||
|
||||
// 优化:增加检查间隔到2秒,减少性能开销
|
||||
// 增加检查间隔到2秒,减少性能开销
|
||||
_wpsProcessCheckTimer = new Timer(2000);
|
||||
_wpsProcessCheckTimer.Elapsed += OnWpsProcessCheckTimerElapsed;
|
||||
_wpsProcessCheckTimer.Start();
|
||||
@@ -1076,7 +1181,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
|
||||
// 检查前台WPS窗口是否存在(优化版)
|
||||
// 检查前台WPS窗口是否存在
|
||||
bool isForegroundWpsWindowActive = IsForegroundWpsWindowStillActiveOptimized();
|
||||
|
||||
if (isForegroundWpsWindowActive)
|
||||
@@ -1088,7 +1193,7 @@ namespace Ink_Canvas.Helpers
|
||||
return;
|
||||
}
|
||||
|
||||
// 优化:多重验证确保准确性
|
||||
// 多重验证确保准确性
|
||||
if (!PerformMultipleWpsWindowChecks())
|
||||
{
|
||||
LogHelper.WriteLogToFile("多重验证显示WPS窗口仍然存在,跳过查杀", LogHelper.LogType.Trace);
|
||||
@@ -1109,7 +1214,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优化版的前台WPS窗口检测,减少性能开销
|
||||
/// 前台WPS窗口检测
|
||||
/// </summary>
|
||||
private bool IsForegroundWpsWindowStillActiveOptimized()
|
||||
{
|
||||
@@ -1135,7 +1240,7 @@ namespace Ink_Canvas.Helpers
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"优化版WPS窗口检测失败: {ex}", LogHelper.LogType.Error);
|
||||
LogHelper.WriteLogToFile($"WPS窗口检测失败: {ex}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
@@ -86,24 +87,47 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
||||
LogHelper.WriteLogToFile($"更新PPT页码显示: {currentSlide}/{totalSlides}", LogHelper.LogType.Trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 页数无效时清空页码显示
|
||||
_mainWindow.PPTBtnPageNow.Text = "?";
|
||||
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
|
||||
LogHelper.WriteLogToFile($"PPT页数无效,清空页码显示: 当前页={currentSlide}, 总页数={totalSlides}", LogHelper.LogType.Warning);
|
||||
}
|
||||
|
||||
UpdateNavigationPanelsVisibility();
|
||||
UpdateNavigationButtonStyles();
|
||||
if (MainWindow.Settings.Advanced.IsEnableAvoidFullScreenHelper)
|
||||
{
|
||||
// 设置为画板模式,允许全屏操作
|
||||
AvoidFullScreenHelper.SetBoardMode(true);
|
||||
_dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
MainWindow.MoveWindow(new WindowInteropHelper(_mainWindow).Handle, 0, 0,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Width,
|
||||
System.Windows.Forms.Screen.PrimaryScreen.Bounds.Height, true);
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible;
|
||||
_mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
|
||||
HideAllNavigationPanels();
|
||||
if (MainWindow.Settings.Advanced.IsEnableAvoidFullScreenHelper)
|
||||
{
|
||||
// 恢复为非画板模式,重新启用全屏限制
|
||||
AvoidFullScreenHelper.SetBoardMode(false);
|
||||
|
||||
_dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
// 退出PPT放映模式,恢复到工作区域大小
|
||||
var workingArea = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea;
|
||||
MainWindow.MoveWindow(new WindowInteropHelper(_mainWindow).Handle,
|
||||
workingArea.X, workingArea.Y,
|
||||
workingArea.Width, workingArea.Height, true);
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -127,14 +151,12 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
||||
LogHelper.WriteLogToFile($"更新PPT页码显示: {currentSlide}/{totalSlides}", LogHelper.LogType.Trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 页数无效时清空页码显示
|
||||
_mainWindow.PPTBtnPageNow.Text = "?";
|
||||
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
|
||||
LogHelper.WriteLogToFile($"PPT页数无效,清空页码显示: 当前页={currentSlide}, 总页数={totalSlides}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -157,7 +179,6 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
// 如果不在放映模式,隐藏所有导航面板
|
||||
HideAllNavigationPanels();
|
||||
LogHelper.WriteLogToFile("PPT放映状态变化:隐藏导航面板", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -181,21 +202,19 @@ namespace Ink_Canvas.Helpers
|
||||
bool isInSlideShow = _mainWindow.PPTManager?.IsInSlideShow == true;
|
||||
int slidesCount = _mainWindow.PPTManager?.SlidesCount ?? 0;
|
||||
bool hasValidPageCount = slidesCount > 0;
|
||||
|
||||
|
||||
bool shouldShowButtons = ShowPPTButton &&
|
||||
_mainWindow.BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
|
||||
isInSlideShow &&
|
||||
hasValidPageCount;
|
||||
hasValidPageCount &&
|
||||
!MainWindow.Settings.Automation.IsAutoFoldInPPTSlideShow;
|
||||
|
||||
if (!shouldShowButtons)
|
||||
{
|
||||
HideAllNavigationPanels();
|
||||
LogHelper.WriteLogToFile($"隐藏PPT导航面板 - 放映状态: {isInSlideShow}, 页数: {slidesCount}, 按钮设置: {ShowPPTButton}", LogHelper.LogType.Trace);
|
||||
return;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"显示PPT导航面板 - 放映状态: {isInSlideShow}, 页数: {slidesCount}", LogHelper.LogType.Trace);
|
||||
|
||||
// 设置侧边按钮位置
|
||||
_mainWindow.LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTLSButtonPosition * 2);
|
||||
_mainWindow.RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTRSButtonPosition * 2);
|
||||
|
||||
@@ -238,4 +238,4 @@ namespace Ink_Canvas.Helpers.Plugins
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -293,4 +293,4 @@ namespace Ink_Canvas.Helpers.Plugins
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -212,4 +211,4 @@ namespace Ink_Canvas.Helpers.Plugins
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,3 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
@@ -151,4 +149,4 @@ namespace Ink_Canvas.Helpers.Plugins
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,8 @@ namespace Ink_Canvas.Helpers.Plugins
|
||||
public class PluginManager
|
||||
{
|
||||
private static readonly string PluginsDirectory = Path.Combine(App.RootPath, "Plugins");
|
||||
private static readonly string PluginConfigFile = Path.Combine(App.RootPath, "PluginConfig.json");
|
||||
private static readonly string PluginConfigBackupFile = Path.Combine(App.RootPath, "PluginConfig.json.bak");
|
||||
private static readonly string PluginConfigFile = Path.Combine(App.RootPath, "Configs", "PluginConfig.json");
|
||||
private static readonly string PluginConfigBackupFile = Path.Combine(App.RootPath, "Configs", "PluginConfig.json.bak");
|
||||
|
||||
private static PluginManager _instance;
|
||||
private static SemaphoreSlim _configLock = new SemaphoreSlim(1, 1);
|
||||
@@ -79,8 +79,6 @@ namespace Ink_Canvas.Helpers.Plugins
|
||||
Directory.CreateDirectory(PluginsDirectory);
|
||||
}
|
||||
|
||||
// 加载插件配置
|
||||
LoadConfig();
|
||||
|
||||
// 初始化自动保存计时器(3秒)
|
||||
_autoSaveTimer = new Timer(3000);
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Drawing;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 屏幕检测帮助类 - 用于检测窗口所在的屏幕和屏幕信息
|
||||
/// </summary>
|
||||
public static class ScreenDetectionHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取窗口所在的屏幕
|
||||
/// </summary>
|
||||
/// <param name="window">要检测的窗口</param>
|
||||
/// <returns>窗口所在的屏幕,如果无法检测则返回主屏幕</returns>
|
||||
public static Screen GetWindowScreen(Window window)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (window == null)
|
||||
return Screen.PrimaryScreen;
|
||||
|
||||
// 获取窗口的句柄
|
||||
var hwndSource = PresentationSource.FromVisual(window) as HwndSource;
|
||||
if (hwndSource == null)
|
||||
return Screen.PrimaryScreen;
|
||||
|
||||
// 获取窗口在屏幕上的位置
|
||||
var windowRect = GetWindowScreenBounds(window);
|
||||
|
||||
// 查找与窗口重叠最多的屏幕
|
||||
Screen targetScreen = null;
|
||||
double maxIntersection = 0;
|
||||
|
||||
foreach (var screen in Screen.AllScreens)
|
||||
{
|
||||
var intersection = Rectangle.Intersect(windowRect, screen.Bounds);
|
||||
if (intersection.Width * intersection.Height > maxIntersection)
|
||||
{
|
||||
maxIntersection = intersection.Width * intersection.Height;
|
||||
targetScreen = screen;
|
||||
}
|
||||
}
|
||||
|
||||
return targetScreen ?? Screen.PrimaryScreen;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检测窗口屏幕时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
return Screen.PrimaryScreen;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取窗口在屏幕坐标系中的边界
|
||||
/// </summary>
|
||||
/// <param name="window">要检测的窗口</param>
|
||||
/// <returns>窗口的屏幕边界</returns>
|
||||
private static Rectangle GetWindowScreenBounds(Window window)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取窗口左上角在屏幕上的位置
|
||||
var topLeft = window.PointToScreen(new Point(0, 0));
|
||||
|
||||
// 获取窗口右下角在屏幕上的位置
|
||||
var bottomRight = window.PointToScreen(new Point(window.ActualWidth, window.ActualHeight));
|
||||
|
||||
return new Rectangle(
|
||||
(int)topLeft.X,
|
||||
(int)topLeft.Y,
|
||||
(int)(bottomRight.X - topLeft.X),
|
||||
(int)(bottomRight.Y - topLeft.Y));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 如果无法获取精确位置,返回窗口的Left和Top
|
||||
return new Rectangle(
|
||||
(int)window.Left,
|
||||
(int)window.Top,
|
||||
(int)window.Width,
|
||||
(int)window.Height);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有多个屏幕
|
||||
/// </summary>
|
||||
/// <returns>如果有多个屏幕返回true,否则返回false</returns>
|
||||
public static bool HasMultipleScreens()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Screen.AllScreens.Length > 1;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取主屏幕
|
||||
/// </summary>
|
||||
/// <returns>主屏幕</returns>
|
||||
public static Screen GetPrimaryScreen()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Screen.PrimaryScreen;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有屏幕信息
|
||||
/// </summary>
|
||||
/// <returns>所有屏幕的数组</returns>
|
||||
public static Screen[] GetAllScreens()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Screen.AllScreens;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new Screen[] { Screen.PrimaryScreen };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查窗口是否在主屏幕上
|
||||
/// </summary>
|
||||
/// <param name="window">要检查的窗口</param>
|
||||
/// <returns>如果窗口在主屏幕上返回true,否则返回false</returns>
|
||||
public static bool IsWindowOnPrimaryScreen(Window window)
|
||||
{
|
||||
try
|
||||
{
|
||||
var windowScreen = GetWindowScreen(window);
|
||||
return windowScreen == Screen.PrimaryScreen;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return true; // 出错时假设在主屏幕
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ namespace Ink_Canvas.Helpers
|
||||
// 使用更直接的方法:先激活窗口,再置顶
|
||||
window.Activate();
|
||||
window.Focus();
|
||||
|
||||
|
||||
// 设置窗口为置顶
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER);
|
||||
|
||||
@@ -24,8 +24,12 @@
|
||||
<BootstrapperEnabled>false</BootstrapperEnabled>
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<UseWPF>true</UseWPF>
|
||||
<Configurations>Release;x86 Debug;Debug</Configurations>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
<Configurations>Debug;Release;x86 Debug</Configurations>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugType>embedded</DebugType>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<Prefer32Bit>True</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='x86 Debug|AnyCPU'">
|
||||
<DebugType>embedded</DebugType>
|
||||
@@ -154,6 +158,8 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="NHotkey.Wpf" Version="3.0.0" />
|
||||
<PackageReference Include="OSVersionExt" Version="3.0.0" />
|
||||
<PackageReference Include="AForge.Video" Version="2.2.5" />
|
||||
<PackageReference Include="AForge.Video.DirectShow" Version="2.2.5" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<COMReference Include="IWshRuntimeLibrary">
|
||||
@@ -186,11 +192,14 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="Resources\TimerDownNotice.wav" />
|
||||
<None Include="Resources\ProgressiveAudio.wav" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
@@ -202,6 +211,7 @@
|
||||
<Resource Include="Resources\DeveloperAvatars\RaspberryKan.jpg" />
|
||||
<Resource Include="Resources\DeveloperAvatars\wwei.png" />
|
||||
<Resource Include="Resources\DeveloperAvatars\yuwenhui2020.png" />
|
||||
<Resource Include="Resources\DeveloperAvatars\CJKmkp.jpg" />
|
||||
<Resource Include="Resources\icc.ico" />
|
||||
<Resource Include="Resources\Icons-png\AdmoxBooth.png" />
|
||||
<Resource Include="Resources\Icons-png\AdmoxWhiteboard.png" />
|
||||
@@ -216,6 +226,10 @@
|
||||
<Resource Include="Resources\Icons-png\icc-transparent-dark.png" />
|
||||
<Resource Include="Resources\Icons-png\icc-transparent.png" />
|
||||
<Resource Include="Resources\Icons-png\icc.png" />
|
||||
<Resource Include="Resources\Icons-png\icc-dark.png" />
|
||||
<Resource Include="Resources\Icons-png\icc-noshadow.png" />
|
||||
<Resource Include="Resources\Icons-png\icc-sharpdark.png" />
|
||||
<Resource Include="Resources\Icons-png\icc-transparent-light-small.png" />
|
||||
<Resource Include="Resources\Icons-png\InkCanvas.png" />
|
||||
<Resource Include="Resources\Icons-png\kuanciya.png" />
|
||||
<Resource Include="Resources\Icons-png\kuandogeyuanliangwo.png" />
|
||||
@@ -232,6 +246,10 @@
|
||||
<Resource Include="Resources\Icons-png\undo.png" />
|
||||
<Resource Include="Resources\Icons-png\minimize.png" />
|
||||
<Resource Include="Resources\Icons-png\penUpright.png" />
|
||||
<Resource Include="Resources\new-icons\multi-touch_white.png" />
|
||||
<Resource Include="Resources\new-icons\hand-move_white.png" />
|
||||
<Resource Include="Resources\new-icons\zoom_white.png" />
|
||||
<Resource Include="Resources\new-icons\rotate_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\twoFingelMove-Blue.png" />
|
||||
@@ -248,6 +266,7 @@
|
||||
<Resource Include="Resources\DeveloperAvatars\kengwang.png" />
|
||||
<Resource Include="Resources\DeveloperAvatars\STBBRD.png" />
|
||||
<Resource Include="Resources\DeveloperAvatars\WXRIW.png" />
|
||||
<Resource Include="Resources\DeveloperAvatars\PrefacedCorg.jpg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\down.png" />
|
||||
@@ -280,7 +299,9 @@
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_arrow_circle_left_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_arrow_circle_right_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_weather_moon_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_weather_moon_24_regular_white.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_weather_sunny_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_weather_sunny_24_regular_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_signature_24_regular.png" />
|
||||
@@ -315,6 +336,7 @@
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_arrow_rotate_clockwise_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_scale_fit_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_scale_fit_24_regular_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_scales_24_regular.png" />
|
||||
@@ -332,9 +354,6 @@
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_clock_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_book_question_mark_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_keyboard_24_regular.png" />
|
||||
</ItemGroup>
|
||||
@@ -449,6 +468,37 @@
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\cube.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\arrow_white.png" />
|
||||
<Resource Include="Resources\Icons-png\geo-icons\dashed-line_white.png" />
|
||||
<Resource Include="Resources\Icons-png\geo-icons\dotted-line_white.png" />
|
||||
<Resource Include="Resources\Icons-png\geo-icons\line_white.png" />
|
||||
<Resource Include="Resources\Icons-png\geo-icons\paralle-lines_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\centered-square_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\centered-circle_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\centered-circle-dashed_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\centered-oval_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\square_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\cylinder_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\cone_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\cube_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\EasiNote.png" />
|
||||
<Resource Include="Resources\Icons-png\EasiNote3C.png" />
|
||||
@@ -485,6 +535,19 @@
|
||||
<Resource Include="Resources\new-icons\unfold-chevron.png" />
|
||||
<Resource Include="Resources\new-icons\zoom.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_person_money_24_regular-light.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_people_money_24_regular-light.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_timer_24_regular-light.png" />
|
||||
<Resource Include="Resources\new-icons\blackboard-light.png" />
|
||||
<Resource Include="Resources\new-icons\end-slides-show-light.png" />
|
||||
<Resource Include="Resources\new-icons\eye-light.png" />
|
||||
<Resource Include="Resources\new-icons\chevron-left-light.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_keyboard_24_regular_white.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_control_button_24_regular_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
@@ -511,6 +574,10 @@
|
||||
<None Remove="Resources\Icons-png\icc-transparent-dark.png" />
|
||||
<None Remove="Resources\Icons-png\icc-transparent.png" />
|
||||
<None Remove="Resources\Icons-png\icc.png" />
|
||||
<None Remove="Resources\Icons-png\icc-dark.png" />
|
||||
<None Remove="Resources\Icons-png\icc-noshadow.png" />
|
||||
<None Remove="Resources\Icons-png\icc-sharpdark.png" />
|
||||
<None Remove="Resources\Icons-png\icc-transparent-light-small.png" />
|
||||
<None Remove="Resources\Icons-png\idt.png" />
|
||||
<None Remove="Resources\Icons-png\InkCanvas.png" />
|
||||
<None Remove="Resources\Icons-png\kuanciya.png" />
|
||||
@@ -547,14 +614,31 @@
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\idt.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Fonts\LXGWWenKaiTC-Regular.ttf" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\HiteAnnotation.png" />
|
||||
<Resource Include="Resources\Icons-png\AiClass.png" />
|
||||
<Resource Include="Resources\Icons-png\天喻教育云.png" />
|
||||
<Resource Include="Resources\Icons-png\畅言智慧课堂.png" />
|
||||
<Resource Include="Resources\PresentationExample\bottombar-dark.png" />
|
||||
<Resource Include="Resources\PresentationExample\bottombar-white.png" />
|
||||
<Resource Include="Resources\PresentationExample\page.jpg" />
|
||||
<Resource Include="Resources\PresentationExample\sidebar-dark.png" />
|
||||
<Resource Include="Resources\PresentationExample\sidebar-white.png" />
|
||||
<Resource Include="Resources\PresentationExample\toolbar.png" />
|
||||
<Resource Include="Resources\Startup-animation\ICC Spring.png" />
|
||||
<Resource Include="Resources\Startup-animation\ICC Summer.png" />
|
||||
<Resource Include="Resources\Startup-animation\ICC Autumn.png" />
|
||||
<Resource Include="Resources\Startup-animation\ICC Winter.png" />
|
||||
<Resource Include="Resources\Startup-animation\ICC Horse.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_copy_24_regular_white.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_copy_add_24_regular_white.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_flip_horizontal_24_regular_white.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_flip_vertical_24_regular_white.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_edit_24_regular_white.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_delete_24_regular_white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Update="Properties\Settings.Designer.cs">
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<_LastSelectedProfileId>D:\vs\ica\Ink Canvas\Properties\PublishProfiles\FolderProfile.pubxml</_LastSelectedProfileId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -12,6 +12,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Principal;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
@@ -31,14 +32,20 @@ using Cursors = System.Windows.Input.Cursors;
|
||||
using DpiChangedEventArgs = System.Windows.DpiChangedEventArgs;
|
||||
using File = System.IO.File;
|
||||
using GroupBox = System.Windows.Controls.GroupBox;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
// 新增:每一页一个Canvas对象
|
||||
[DllImport("UIAccessDLL_x86.dll", EntryPoint = "PrepareUIAccess", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern Int32 PrepareUIAccessX86();
|
||||
|
||||
[DllImport("UIAccessDLL_x64.dll", EntryPoint = "PrepareUIAccess", CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern Int32 PrepareUIAccessX64();
|
||||
|
||||
// 每一页一个Canvas对象
|
||||
private List<System.Windows.Controls.Canvas> whiteboardPages = new List<System.Windows.Controls.Canvas>();
|
||||
private int currentPageIndex;
|
||||
private System.Windows.Controls.Canvas currentCanvas;
|
||||
@@ -50,6 +57,16 @@ namespace Ink_Canvas
|
||||
// 墨迹渐隐管理器
|
||||
private InkFadeManager _inkFadeManager;
|
||||
|
||||
// 悬浮窗拦截管理器
|
||||
private FloatingWindowInterceptorManager _floatingWindowInterceptorManager;
|
||||
|
||||
// 快抽悬浮按钮
|
||||
private QuickDrawFloatingButton _quickDrawFloatingButton;
|
||||
|
||||
// 设置面板相关状态
|
||||
private bool userChangedNoFocusModeInSettings;
|
||||
private bool isTemporarilyDisablingNoFocusMode = false;
|
||||
|
||||
|
||||
|
||||
#region Window Initialization
|
||||
@@ -247,7 +264,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 为浮动栏按钮添加触摸事件支持
|
||||
AddTouchSupportToFloatingBarButtons();
|
||||
|
||||
|
||||
// 为滑块控件添加触摸事件支持
|
||||
AddTouchSupportToSliders();
|
||||
}
|
||||
@@ -301,7 +318,7 @@ namespace Ink_Canvas
|
||||
foreach (var gest in gestures)
|
||||
//Trace.WriteLine(string.Format("Gesture: {0}, Confidence: {1}", gest.ApplicationGesture, gest.RecognitionConfidence));
|
||||
// 只有在PPT放映模式下才响应翻页手势
|
||||
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
|
||||
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
|
||||
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
|
||||
PPTManager?.IsInSlideShow == true)
|
||||
{
|
||||
@@ -351,23 +368,21 @@ namespace Ink_Canvas
|
||||
|
||||
if (inkCanvas1.EditingMode == InkCanvasEditingMode.Ink) forcePointEraser = !forcePointEraser;
|
||||
|
||||
// 处理高级橡皮擦覆盖层的启用/禁用
|
||||
var eraserOverlay = FindName("AdvancedEraserOverlay") as Border;
|
||||
// 处理橡皮擦覆盖层的启用/禁用
|
||||
var eraserOverlay = FindName("EraserOverlayCanvas") as Canvas;
|
||||
if (eraserOverlay != null)
|
||||
{
|
||||
if (inkCanvas1.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
// 橡皮擦模式下启用覆盖层
|
||||
eraserOverlay.IsHitTestVisible = true;
|
||||
Trace.WriteLine("Advanced Eraser: Overlay enabled in eraser mode");
|
||||
EnableEraserOverlay();
|
||||
Trace.WriteLine("Eraser: Overlay enabled in eraser mode");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他模式下禁用覆盖层
|
||||
eraserOverlay.IsHitTestVisible = false;
|
||||
// 同时禁用高级橡皮擦系统
|
||||
DisableAdvancedEraserSystem();
|
||||
Trace.WriteLine("Advanced Eraser: Overlay disabled in non-eraser mode");
|
||||
DisableEraserOverlay();
|
||||
Trace.WriteLine("Eraser: Overlay disabled in non-eraser mode");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,7 +392,7 @@ namespace Ink_Canvas
|
||||
#region Definations and Loading
|
||||
|
||||
public static Settings Settings = new Settings();
|
||||
public static string settingsFileName = "Settings.json";
|
||||
public static string settingsFileName = Path.Combine("Configs", "Settings.json");
|
||||
private bool isLoaded;
|
||||
private bool forcePointEraser;
|
||||
|
||||
@@ -386,6 +401,8 @@ namespace Ink_Canvas
|
||||
loadPenCanvas();
|
||||
//加载设置
|
||||
LoadSettings(true);
|
||||
AutoBackupManager.Initialize(Settings);
|
||||
|
||||
// 检查保存路径是否可用,不可用则修正
|
||||
try
|
||||
{
|
||||
@@ -411,7 +428,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
if (needFix)
|
||||
{
|
||||
string newPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "saves");
|
||||
string newPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Saves");
|
||||
Settings.Automation.AutoSavedStrokesLocation = newPath;
|
||||
if (!Directory.Exists(newPath))
|
||||
Directory.CreateDirectory(newPath);
|
||||
@@ -451,14 +468,35 @@ namespace Ink_Canvas
|
||||
// HasNewUpdateWindow hasNewUpdateWindow = new HasNewUpdateWindow();
|
||||
if (Environment.Is64BitProcess) GroupBoxInkRecognition.Visibility = Visibility.Collapsed;
|
||||
|
||||
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
|
||||
SystemEvents_UserPreferenceChanged(null, null);
|
||||
// 根据设置应用主题
|
||||
switch (Settings.Appearance.Theme)
|
||||
{
|
||||
case 0: // 浅色主题
|
||||
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
|
||||
SetTheme("Light");
|
||||
break;
|
||||
case 1: // 深色主题
|
||||
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
|
||||
SetTheme("Dark");
|
||||
break;
|
||||
case 2: // 跟随系统
|
||||
if (IsSystemThemeLight())
|
||||
{
|
||||
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Light;
|
||||
SetTheme("Light");
|
||||
}
|
||||
else
|
||||
{
|
||||
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
|
||||
SetTheme("Dark");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
//TextBlockVersion.Text = Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
LogHelper.WriteLogToFile("Ink Canvas Loaded", LogHelper.LogType.Event);
|
||||
|
||||
isLoaded = true;
|
||||
|
||||
BlackBoardLeftSidePageListView.ItemsSource = blackBoardSidePageListViewObservableCollection;
|
||||
BlackBoardRightSidePageListView.ItemsSource = blackBoardSidePageListViewObservableCollection;
|
||||
|
||||
@@ -483,8 +521,8 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
SystemEvents.DisplaySettingsChanged += SystemEventsOnDisplaySettingsChanged;
|
||||
// 自动收纳到侧边栏
|
||||
if (Settings.Startup.IsFoldAtStartup)
|
||||
// 自动收纳到侧边栏(若通过 --board 进入白板模式或 --show 参数则跳过收纳)
|
||||
if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode && !App.StartWithShowMode)
|
||||
{
|
||||
FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
@@ -495,7 +533,8 @@ namespace Ink_Canvas
|
||||
else
|
||||
RadioCrashNoAction.IsChecked = true;
|
||||
|
||||
|
||||
// 显示快抽悬浮按钮
|
||||
ShowQuickDrawFloatingButton();
|
||||
|
||||
// 如果当前不是黑板模式,则切换到黑板模式
|
||||
if (currentMode == 0)
|
||||
@@ -525,12 +564,18 @@ namespace Ink_Canvas
|
||||
ToggleSwitchAlwaysOnTop.IsOn = Settings.Advanced.IsAlwaysOnTop;
|
||||
ApplyAlwaysOnTop();
|
||||
|
||||
// 初始化UIElement选择系统
|
||||
|
||||
// 初始化UIA置顶开关
|
||||
ToggleSwitchUIAccessTopMost.IsOn = Settings.Advanced.EnableUIAccessTopMost;
|
||||
UpdateUIAccessTopMostVisibility();
|
||||
|
||||
App.IsUIAccessTopMostEnabled = Settings.Advanced.EnableUIAccessTopMost;
|
||||
|
||||
// 初始化剪贴板监控
|
||||
InitializeClipboardMonitoring();
|
||||
|
||||
// 初始化悬浮窗拦截管理器
|
||||
InitializeFloatingWindowInterceptor();
|
||||
|
||||
// 初始化全局快捷键管理器
|
||||
InitializeGlobalHotkeyManager();
|
||||
|
||||
@@ -545,6 +590,32 @@ namespace Ink_Canvas
|
||||
|
||||
// 检查模式设置并应用
|
||||
CheckMainWindowVisibility();
|
||||
|
||||
// 检查是否通过--board参数启动,如果是则自动切换到白板模式
|
||||
if (App.StartWithBoardMode)
|
||||
{
|
||||
LogHelper.WriteLogToFile("检测到--board参数,自动切换到白板模式", LogHelper.LogType.Event);
|
||||
// 延迟执行,确保UI已完全加载
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
SwitchToBoardMode();
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
// 检查是否通过--show参数启动,如果是则确保退出收纳模式并恢复浮动栏
|
||||
if (App.StartWithShowMode)
|
||||
{
|
||||
LogHelper.WriteLogToFile("检测到--show参数,退出收纳模式并恢复浮动栏", LogHelper.LogType.Event);
|
||||
// 延迟执行,确保UI已完全加载
|
||||
Dispatcher.BeginInvoke(new Action(async () =>
|
||||
{
|
||||
// 如果当前处于收纳模式,则展开浮动栏
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
}
|
||||
|
||||
private void SystemEventsOnDisplaySettingsChanged(object sender, EventArgs e)
|
||||
@@ -603,6 +674,19 @@ namespace Ink_Canvas
|
||||
private void Window_Closing(object sender, CancelEventArgs e)
|
||||
{
|
||||
LogHelper.WriteLogToFile("Ink Canvas closing", LogHelper.LogType.Event);
|
||||
try
|
||||
{
|
||||
if (_quickDrawFloatingButton != null)
|
||||
{
|
||||
_quickDrawFloatingButton.Close();
|
||||
_quickDrawFloatingButton = null;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"关闭快抽悬浮按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
if (!CloseIsFromButton && Settings.Advanced.IsSecondConfirmWhenShutdownApp)
|
||||
{
|
||||
// 第一个确认对话框
|
||||
@@ -688,6 +772,13 @@ namespace Ink_Canvas
|
||||
_inkFadeManager = null;
|
||||
}
|
||||
|
||||
// 清理悬浮窗拦截管理器
|
||||
if (_floatingWindowInterceptorManager != null)
|
||||
{
|
||||
_floatingWindowInterceptorManager.Dispose();
|
||||
_floatingWindowInterceptorManager = null;
|
||||
}
|
||||
|
||||
// 停止置顶维护定时器
|
||||
StopTopmostMaintenance();
|
||||
|
||||
@@ -729,7 +820,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助方法:使用多线路组下载更新
|
||||
// 使用多线路组下载更新
|
||||
private async Task<bool> DownloadUpdateWithFallback(string version, AutoUpdateHelper.UpdateLineGroup primaryGroup, UpdateChannel channel)
|
||||
{
|
||||
try
|
||||
@@ -774,8 +865,14 @@ namespace Ink_Canvas
|
||||
// 声明下载状态变量,用于整个方法
|
||||
bool isDownloadSuccessful = false;
|
||||
|
||||
bool hasValidLineGroup = lineGroup != null;
|
||||
|
||||
if (AvailableLatestVersion != null)
|
||||
{
|
||||
// 检测到新版本,停止重试定时器
|
||||
timerCheckAutoUpdateRetry.Stop();
|
||||
updateCheckRetryCount = 0;
|
||||
|
||||
// 检测到新版本
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | New version available: {AvailableLatestVersion}");
|
||||
|
||||
@@ -939,8 +1036,26 @@ namespace Ink_Canvas
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (hasValidLineGroup)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | Current version is already the latest, no retry needed");
|
||||
|
||||
// 停止重试定时器
|
||||
timerCheckAutoUpdateRetry.Stop();
|
||||
updateCheckRetryCount = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 检查更新失败,启动重试定时器
|
||||
LogHelper.WriteLogToFile("AutoUpdate | Update check failed, starting retry timer");
|
||||
|
||||
// 重置重试计数
|
||||
updateCheckRetryCount = 0;
|
||||
|
||||
// 启动重试定时器,10分钟后重新检查
|
||||
timerCheckAutoUpdateRetry.Start();
|
||||
|
||||
// 清理更新文件夹
|
||||
AutoUpdateHelper.DeleteUpdatesFolder();
|
||||
}
|
||||
}
|
||||
@@ -1032,12 +1147,16 @@ namespace Ink_Canvas
|
||||
// 如果当前有选中的元素,取消选中状态
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
// 取消选中元素
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
|
||||
// 重置为选择模式,确保用户可以继续选择其他元素
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
// 更新模式缓存
|
||||
UpdateCurrentToolMode("select");
|
||||
// 刷新浮动栏高光显示
|
||||
SetFloatingBarHighlightPosition("select");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1118,14 +1237,14 @@ namespace Ink_Canvas
|
||||
RefreshDeviceInfo();
|
||||
}
|
||||
|
||||
// 新增:个性化设置
|
||||
// 个性化设置
|
||||
private void NavTheme_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 切换到个性化设置页面
|
||||
ShowSettingsSection("theme");
|
||||
}
|
||||
|
||||
// 新增:快捷键设置
|
||||
// 快捷键设置
|
||||
private void NavShortcuts_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OpenHotkeySettingsWindow();
|
||||
@@ -1222,7 +1341,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:折叠侧边栏
|
||||
// 折叠侧边栏
|
||||
private void CollapseNavSidebar_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 折叠/展开侧边栏
|
||||
@@ -1239,7 +1358,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 新增:显示侧边栏
|
||||
// 显示侧边栏
|
||||
private void ShowNavSidebar_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 确保侧边栏展开
|
||||
@@ -1247,7 +1366,7 @@ namespace Ink_Canvas
|
||||
columnDefinitions[0].Width = new GridLength(50);
|
||||
}
|
||||
|
||||
// 辅助方法:显示指定的设置部分
|
||||
// 显示指定的设置部分
|
||||
private async void ShowSettingsSection(string sectionTag)
|
||||
{
|
||||
// 显示设置面板
|
||||
@@ -1740,7 +1859,11 @@ namespace Ink_Canvas
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
if (Settings.Advanced.IsNoFocusMode)
|
||||
|
||||
bool shouldBeNoFocus = isTemporarilyDisablingNoFocusMode ?
|
||||
false : Settings.Advanced.IsNoFocusMode;
|
||||
|
||||
if (shouldBeNoFocus)
|
||||
{
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_NOACTIVATE);
|
||||
}
|
||||
@@ -1757,10 +1880,8 @@ namespace Ink_Canvas
|
||||
var hwnd = new WindowInteropHelper(this).Handle;
|
||||
if (Settings.Advanced.IsAlwaysOnTop)
|
||||
{
|
||||
// 先设置WPF的Topmost属性
|
||||
Topmost = true;
|
||||
|
||||
// 使用更强的Win32 API调用来确保置顶
|
||||
// 1. 设置窗口样式为置顶
|
||||
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST);
|
||||
@@ -1769,8 +1890,8 @@ namespace Ink_Canvas
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER);
|
||||
|
||||
// 3. 如果启用了无焦点模式,需要特殊处理
|
||||
if (Settings.Advanced.IsNoFocusMode)
|
||||
// 3. 如果启用了无焦点模式且未启用UIA置顶,需要特殊处理
|
||||
if (Settings.Advanced.IsNoFocusMode && !Settings.Advanced.EnableUIAccessTopMost)
|
||||
{
|
||||
// 启动置顶维护定时器
|
||||
StartTopmostMaintenance();
|
||||
@@ -1794,11 +1915,6 @@ namespace Ink_Canvas
|
||||
|
||||
// 3. 停止置顶维护定时器
|
||||
StopTopmostMaintenance();
|
||||
|
||||
// 注意:这里不直接设置Topmost,让其他代码根据模式决定
|
||||
|
||||
// 添加调试日志
|
||||
LogHelper.WriteLogToFile("应用窗口置顶: 取消置顶", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -1812,6 +1928,11 @@ namespace Ink_Canvas
|
||||
/// </summary>
|
||||
private void StartTopmostMaintenance()
|
||||
{
|
||||
if (Settings.Advanced.EnableUIAccessTopMost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTopmostMaintenanceEnabled) return;
|
||||
|
||||
if (topmostMaintenanceTimer == null)
|
||||
@@ -1846,6 +1967,12 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Settings.Advanced.EnableUIAccessTopMost)
|
||||
{
|
||||
StopTopmostMaintenance();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Settings.Advanced.IsAlwaysOnTop || !Settings.Advanced.IsNoFocusMode)
|
||||
{
|
||||
StopTopmostMaintenance();
|
||||
@@ -1922,6 +2049,12 @@ namespace Ink_Canvas
|
||||
var toggle = sender as ToggleSwitch;
|
||||
Settings.Advanced.IsNoFocusMode = toggle != null && toggle.IsOn;
|
||||
SaveSettingsToFile();
|
||||
|
||||
if (isTemporarilyDisablingNoFocusMode)
|
||||
{
|
||||
isTemporarilyDisablingNoFocusMode = false;
|
||||
}
|
||||
|
||||
ApplyNoFocusMode();
|
||||
|
||||
// 如果启用了窗口置顶,需要重新应用置顶设置以处理无焦点模式的变化
|
||||
@@ -1929,6 +2062,12 @@ namespace Ink_Canvas
|
||||
{
|
||||
ApplyAlwaysOnTop();
|
||||
}
|
||||
|
||||
// 如果当前在设置面板中,标记用户已修改无焦点模式设置
|
||||
if (BorderSettings.Visibility == Visibility.Visible)
|
||||
{
|
||||
userChangedNoFocusModeInSettings = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ToggleSwitchAlwaysOnTop_Toggled(object sender, RoutedEventArgs e)
|
||||
@@ -1938,6 +2077,21 @@ namespace Ink_Canvas
|
||||
Settings.Advanced.IsAlwaysOnTop = toggle != null && toggle.IsOn;
|
||||
SaveSettingsToFile();
|
||||
ApplyAlwaysOnTop();
|
||||
UpdateUIAccessTopMostVisibility();
|
||||
}
|
||||
|
||||
private void ToggleSwitchUIAccessTopMost_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
var toggle = sender as ToggleSwitch;
|
||||
bool newValue = toggle != null && toggle.IsOn;
|
||||
|
||||
Settings.Advanced.EnableUIAccessTopMost = newValue;
|
||||
SaveSettingsToFile();
|
||||
ApplyUIAccessTopMost();
|
||||
|
||||
App.IsUIAccessTopMostEnabled = newValue;
|
||||
|
||||
}
|
||||
|
||||
private void Window_Activated(object sender, EventArgs e)
|
||||
@@ -2029,14 +2183,14 @@ namespace Ink_Canvas
|
||||
MessageBox.Show("快捷键管理器尚未初始化,请稍后重试。", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// 暂时隐藏设置面板
|
||||
BorderSettings.Visibility = Visibility.Hidden;
|
||||
BorderSettingsMask.Visibility = Visibility.Hidden;
|
||||
|
||||
|
||||
// 创建快捷键设置窗口
|
||||
var hotkeySettingsWindow = new HotkeySettingsWindow(this, _globalHotkeyManager);
|
||||
|
||||
|
||||
// 设置窗口关闭事件,用于在快捷键设置窗口关闭后恢复设置面板
|
||||
hotkeySettingsWindow.Closed += (s, e) =>
|
||||
{
|
||||
@@ -2044,7 +2198,7 @@ namespace Ink_Canvas
|
||||
BorderSettings.Visibility = Visibility.Visible;
|
||||
BorderSettingsMask.Visibility = Visibility.Visible;
|
||||
};
|
||||
|
||||
|
||||
// 显示快捷键设置窗口
|
||||
hotkeySettingsWindow.ShowDialog();
|
||||
}
|
||||
@@ -2053,7 +2207,7 @@ namespace Ink_Canvas
|
||||
// 确保在发生错误时也恢复设置面板显示
|
||||
BorderSettings.Visibility = Visibility.Visible;
|
||||
BorderSettingsMask.Visibility = Visibility.Visible;
|
||||
|
||||
|
||||
LogHelper.WriteLogToFile($"打开快捷键设置窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"打开快捷键设置窗口时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
@@ -2154,13 +2308,13 @@ namespace Ink_Canvas
|
||||
var toggle = sender as ToggleSwitch;
|
||||
Settings.PowerPointSettings.ShowGestureButtonInSlideShow = toggle != null && toggle.IsOn;
|
||||
SaveSettingsToFile();
|
||||
|
||||
|
||||
// 如果当前在PPT放映模式,需要立即更新手势按钮的显示状态
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateGestureButtonVisibilityInPPTMode();
|
||||
}
|
||||
|
||||
|
||||
LogHelper.WriteLogToFile($"PPT放映模式显示手势按钮已{(Settings.PowerPointSettings.ShowGestureButtonInSlideShow ? "启用" : "禁用")}", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -2178,7 +2332,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (Settings.PowerPointSettings.ShowGestureButtonInSlideShow)
|
||||
{
|
||||
// 如果启用了PPT放映模式显示手势按钮,则显示手势按钮(在PPT模式下不依赖手势功能是否启用)
|
||||
// 如果启用了PPT放映模式显示手势按钮,则检查是否在批注模式下显示手势按钮
|
||||
CheckEnableTwoFingerGestureBtnVisibility(true);
|
||||
}
|
||||
else
|
||||
@@ -2272,11 +2426,11 @@ namespace Ink_Canvas
|
||||
{
|
||||
// 检查启动参数中是否有.icstk文件
|
||||
string icstkFile = FileAssociationManager.GetIcstkFileFromArgs(App.StartArgs);
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(icstkFile))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检测到命令行参数中的.icstk文件: {icstkFile}", LogHelper.LogType.Event);
|
||||
|
||||
|
||||
// 延迟执行,确保UI已完全加载
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
@@ -2322,10 +2476,15 @@ namespace Ink_Canvas
|
||||
_globalHotkeyManager.UpdateHotkeyStateForToolMode(isMouseMode);
|
||||
}
|
||||
|
||||
// 在PPT放映模式下,工具模式切换时需要更新手势按钮的显示状态
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateGestureButtonVisibilityInPPTMode();
|
||||
}
|
||||
|
||||
// 执行额外的操作(如果有)
|
||||
additionalActions?.Invoke();
|
||||
|
||||
LogHelper.WriteLogToFile($"工具模式已切换到: {newMode}, 鼠标模式: {isMouseMode}", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -2362,12 +2521,16 @@ namespace Ink_Canvas
|
||||
SideControlMinimumAutomationSlider,
|
||||
RandWindowOnceCloseLatencySlider,
|
||||
RandWindowOnceMaxStudentsSlider,
|
||||
TimerVolumeSlider,
|
||||
ProgressiveReminderVolumeSlider,
|
||||
BoardInkWidthSlider,
|
||||
BoardInkAlphaSlider,
|
||||
BoardHighlighterWidthSlider,
|
||||
InkWidthSlider,
|
||||
InkAlphaSlider,
|
||||
HighlighterWidthSlider
|
||||
HighlighterWidthSlider,
|
||||
MLAvoidanceHistorySlider,
|
||||
MLAvoidanceWeightSlider
|
||||
};
|
||||
|
||||
foreach (var slider in sliders)
|
||||
@@ -2416,17 +2579,17 @@ namespace Ink_Canvas
|
||||
if (slider == null) return;
|
||||
|
||||
// 捕获触摸设备
|
||||
if (e.RoutedEvent == UIElement.TouchDownEvent)
|
||||
if (e.RoutedEvent == TouchDownEvent)
|
||||
{
|
||||
slider.CaptureTouch(e.TouchDevice);
|
||||
}
|
||||
|
||||
// 计算触摸位置对应的滑块值
|
||||
var touchPoint = e.GetTouchPoint(slider);
|
||||
|
||||
|
||||
// 使用更精确的位置计算方法
|
||||
UpdateSliderValueFromPositionImproved(slider, touchPoint.Position);
|
||||
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
@@ -2439,7 +2602,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 释放触摸捕获
|
||||
slider.ReleaseTouchCapture(e.TouchDevice);
|
||||
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
@@ -2451,7 +2614,7 @@ namespace Ink_Canvas
|
||||
if (slider == null) return;
|
||||
|
||||
// 捕获手写笔设备
|
||||
if (e.RoutedEvent == UIElement.StylusDownEvent)
|
||||
if (e.RoutedEvent == StylusDownEvent)
|
||||
{
|
||||
slider.CaptureStylus();
|
||||
}
|
||||
@@ -2462,7 +2625,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
UpdateSliderValueFromPositionImproved(slider, stylusPoint[0].ToPoint());
|
||||
}
|
||||
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
@@ -2475,7 +2638,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 释放手写笔捕获
|
||||
slider.ReleaseStylusCapture();
|
||||
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
@@ -2501,9 +2664,9 @@ namespace Ink_Canvas
|
||||
|
||||
// 获取轨道的实际边界
|
||||
var trackBounds = track.TransformToAncestor(slider).TransformBounds(new Rect(0, 0, track.ActualWidth, track.ActualHeight));
|
||||
|
||||
|
||||
double relativePosition = 0;
|
||||
|
||||
|
||||
if (slider.Orientation == System.Windows.Controls.Orientation.Horizontal)
|
||||
{
|
||||
// 水平滑块
|
||||
@@ -2527,7 +2690,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 计算新的滑块值
|
||||
var newValue = slider.Minimum + relativePosition * (slider.Maximum - slider.Minimum);
|
||||
|
||||
|
||||
// 如果启用了吸附到刻度,则调整到最近的刻度
|
||||
if (slider.IsSnapToTickEnabled && slider.TickFrequency > 0)
|
||||
{
|
||||
@@ -2560,7 +2723,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
// 使用更简单直接的方法计算滑块值
|
||||
double relativePosition = 0;
|
||||
|
||||
|
||||
if (slider.Orientation == System.Windows.Controls.Orientation.Horizontal)
|
||||
{
|
||||
// 水平滑块 - 使用滑块的实际宽度
|
||||
@@ -2590,7 +2753,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 计算新的滑块值
|
||||
var newValue = slider.Minimum + relativePosition * (slider.Maximum - slider.Minimum);
|
||||
|
||||
|
||||
// 如果启用了吸附到刻度,则调整到最近的刻度
|
||||
if (slider.IsSnapToTickEnabled && slider.TickFrequency > 0)
|
||||
{
|
||||
@@ -2619,11 +2782,14 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
var toggle = sender as iNKORE.UI.WPF.Modern.Controls.ToggleSwitch;
|
||||
var toggle = sender as ToggleSwitch;
|
||||
if (toggle != null)
|
||||
{
|
||||
Settings.ModeSettings.IsPPTOnlyMode = toggle.IsOn;
|
||||
|
||||
|
||||
// 保存设置到文件
|
||||
SaveSettingsToFile();
|
||||
|
||||
// 如果切换到仅PPT模式,立即隐藏主窗口
|
||||
if (Settings.ModeSettings.IsPPTOnlyMode)
|
||||
{
|
||||
@@ -2681,6 +2847,272 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到白板模式(用于--board参数和IPC命令)
|
||||
/// 调用浮动栏上的白板功能
|
||||
/// </summary>
|
||||
public void SwitchToBoardMode()
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("开始切换到白板模式", LogHelper.LogType.Event);
|
||||
|
||||
// 调用浮动栏上的白板功能
|
||||
ImageBlackboard_MouseUp(null, null);
|
||||
|
||||
LogHelper.WriteLogToFile("已成功切换到白板模式", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换到白板模式时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Theme Toggle
|
||||
|
||||
/// <summary>
|
||||
/// 主题下拉框选择变化事件
|
||||
/// </summary>
|
||||
private void ComboBoxTheme_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
try
|
||||
{
|
||||
System.Windows.Controls.ComboBox comboBox = sender as System.Windows.Controls.ComboBox;
|
||||
if (comboBox != null)
|
||||
{
|
||||
Settings.Appearance.Theme = comboBox.SelectedIndex;
|
||||
|
||||
// 应用新主题
|
||||
ApplyTheme(comboBox.SelectedIndex);
|
||||
|
||||
// 保存设置
|
||||
SaveSettingsToFile();
|
||||
|
||||
// 显示通知
|
||||
string themeName;
|
||||
switch (comboBox.SelectedIndex)
|
||||
{
|
||||
case 0:
|
||||
themeName = "浅色主题";
|
||||
break;
|
||||
case 1:
|
||||
themeName = "深色主题";
|
||||
break;
|
||||
case 2:
|
||||
themeName = "跟随系统";
|
||||
break;
|
||||
default:
|
||||
themeName = "未知主题";
|
||||
break;
|
||||
}
|
||||
|
||||
ShowNotification($"已切换到{themeName}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换主题时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
ShowNotification("主题切换失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用指定主题
|
||||
/// </summary>
|
||||
/// <param name="themeIndex">主题索引:0-浅色,1-深色,2-跟随系统</param>
|
||||
private void ApplyTheme(int themeIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (themeIndex)
|
||||
{
|
||||
case 0: // 浅色主题
|
||||
SetTheme("Light", true);
|
||||
// 浅色主题下设置浮动栏为完全不透明
|
||||
ViewboxFloatingBar.Opacity = 1.0;
|
||||
break;
|
||||
case 1: // 深色主题
|
||||
SetTheme("Dark", true);
|
||||
// 深色主题下设置浮动栏为完全不透明
|
||||
ViewboxFloatingBar.Opacity = 1.0;
|
||||
break;
|
||||
case 2: // 跟随系统
|
||||
if (IsSystemThemeLight())
|
||||
{
|
||||
SetTheme("Light", true);
|
||||
ViewboxFloatingBar.Opacity = 1.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTheme("Dark", true);
|
||||
ViewboxFloatingBar.Opacity = 1.0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// 强制刷新通知框的颜色资源
|
||||
RefreshNotificationColors();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用主题时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新通知框的颜色资源
|
||||
/// </summary>
|
||||
private void RefreshNotificationColors()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 强制刷新通知框的背景和前景色
|
||||
var border = GridNotifications.Children.OfType<Border>().FirstOrDefault();
|
||||
if (border != null)
|
||||
{
|
||||
border.Background = (Brush)Application.Current.FindResource("SettingsPageBackground");
|
||||
border.BorderBrush = new SolidColorBrush(Color.FromRgb(185, 28, 28)); // 保持红色边框
|
||||
}
|
||||
|
||||
TextBlockNotice.Foreground = (Brush)Application.Current.FindResource("SettingsPageForeground");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"刷新通知框颜色时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region UIA置顶功能
|
||||
|
||||
/// <summary>
|
||||
/// 更新UIA置顶开关的可见性
|
||||
/// </summary>
|
||||
private void UpdateUIAccessTopMostVisibility()
|
||||
{
|
||||
try
|
||||
{
|
||||
var visibility = Settings.Advanced.IsAlwaysOnTop ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
if (UIAccessTopMostPanel != null)
|
||||
{
|
||||
UIAccessTopMostPanel.Visibility = visibility;
|
||||
}
|
||||
|
||||
if (UIAccessTopMostDescription != null)
|
||||
{
|
||||
UIAccessTopMostDescription.Visibility = visibility;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新UIA置顶开关可见性时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用UIA置顶功能
|
||||
/// </summary>
|
||||
private void ApplyUIAccessTopMost()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Settings.Advanced.EnableUIAccessTopMost && Settings.Advanced.IsAlwaysOnTop)
|
||||
{
|
||||
// 检查是否以管理员权限运行
|
||||
var identity = WindowsIdentity.GetCurrent();
|
||||
var principal = new WindowsPrincipal(identity);
|
||||
|
||||
if (principal.IsInRole(WindowsBuiltInRole.Administrator))
|
||||
{
|
||||
try
|
||||
{
|
||||
timerKillProcess.Stop();
|
||||
if (App.watchdogProcess != null && !App.watchdogProcess.HasExited)
|
||||
{
|
||||
App.watchdogProcess.Kill();
|
||||
App.watchdogProcess = null;
|
||||
}
|
||||
|
||||
|
||||
// 调用UIAccess DLL
|
||||
if (Environment.Is64BitProcess)
|
||||
{
|
||||
PrepareUIAccessX64();
|
||||
}
|
||||
else
|
||||
{
|
||||
PrepareUIAccessX86();
|
||||
}
|
||||
|
||||
App.StartWatchdogIfNeeded();
|
||||
timerKillProcess.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用UIA置顶功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("UIA置顶功能需要管理员权限", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("UIA置顶功能已禁用", LogHelper.LogType.Trace);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用UIA置顶功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示快抽悬浮按钮
|
||||
/// </summary>
|
||||
private void ShowQuickDrawFloatingButton()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查设置是否启用快抽功能
|
||||
if (Settings?.RandSettings?.EnableQuickDraw != true)
|
||||
{
|
||||
// 如果设置未启用,确保悬浮按钮被关闭
|
||||
if (_quickDrawFloatingButton != null)
|
||||
{
|
||||
_quickDrawFloatingButton.Close();
|
||||
_quickDrawFloatingButton = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已经存在悬浮按钮,先关闭它
|
||||
if (_quickDrawFloatingButton != null)
|
||||
{
|
||||
_quickDrawFloatingButton.Close();
|
||||
_quickDrawFloatingButton = null;
|
||||
}
|
||||
|
||||
// 创建并显示悬浮按钮
|
||||
_quickDrawFloatingButton = new QuickDrawFloatingButton();
|
||||
_quickDrawFloatingButton.Show();
|
||||
|
||||
LogHelper.WriteLogToFile("快抽悬浮按钮已显示", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示快抽悬浮按钮失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,6 @@ namespace Ink_Canvas
|
||||
HideSubPanels("cursor");
|
||||
SidePannelMarginAnimation(-10);
|
||||
});
|
||||
isFloatingBarChangingHideMode = false;
|
||||
}
|
||||
|
||||
private async void LeftUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
@@ -260,7 +259,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 只有在PPT放映模式下且页数有效时才显示翻页按钮
|
||||
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
|
||||
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
|
||||
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
|
||||
PPTManager?.IsInSlideShow == true &&
|
||||
PPTManager?.SlidesCount > 0)
|
||||
@@ -271,7 +270,6 @@ namespace Ink_Canvas
|
||||
if (dopsc[1] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(RightBottomPanelForPPTNavigation);
|
||||
if (dopsc[2] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(LeftSidePanelForPPTNavigation);
|
||||
if (dopsc[3] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(RightSidePanelForPPTNavigation);
|
||||
LogHelper.WriteLogToFile($"从收纳模式恢复时显示PPT翻页按钮 - 放映状态: {PPTManager?.IsInSlideShow}, 页数: {PPTManager?.SlidesCount}", LogHelper.LogType.Trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -280,25 +278,24 @@ namespace Ink_Canvas
|
||||
RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
LogHelper.WriteLogToFile($"从收纳模式恢复时隐藏PPT翻页按钮 - 放映状态: {PPTManager?.IsInSlideShow}, 页数: {PPTManager?.SlidesCount}", LogHelper.LogType.Trace);
|
||||
}
|
||||
|
||||
// 新增:只在屏幕模式下显示浮动栏
|
||||
// 新只在屏幕模式下显示浮动栏
|
||||
if (currentMode == 0)
|
||||
{
|
||||
// 强制更新布局以确保ActualWidth正确
|
||||
ViewboxFloatingBar.UpdateLayout();
|
||||
|
||||
|
||||
// 等待一小段时间让布局完全更新
|
||||
Task.Delay(50);
|
||||
|
||||
|
||||
// 再次强制更新布局
|
||||
ViewboxFloatingBar.UpdateLayout();
|
||||
|
||||
|
||||
// 强制重新测量和排列
|
||||
ViewboxFloatingBar.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
ViewboxFloatingBar.Arrange(new Rect(ViewboxFloatingBar.DesiredSize));
|
||||
|
||||
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
ViewboxFloatingBarMarginAnimation(60);
|
||||
else
|
||||
@@ -307,14 +304,13 @@ namespace Ink_Canvas
|
||||
SidePannelMarginAnimation(-50, !unfoldFloatingBarByUser);
|
||||
});
|
||||
|
||||
// 修复:在浮动栏展开后,重新设置按钮高亮状态
|
||||
await Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 等待UI完全更新
|
||||
await Task.Delay(100);
|
||||
|
||||
|
||||
// 获取当前选中的模式并重新设置高光位置
|
||||
string selectedToolMode = GetCurrentSelectedMode();
|
||||
if (!string.IsNullOrEmpty(selectedToolMode))
|
||||
@@ -328,7 +324,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
});
|
||||
|
||||
isFloatingBarChangingHideMode = false;
|
||||
}
|
||||
|
||||
private async void SidePannelMarginAnimation(int MarginFromEdge, bool isNoAnimation = false) // Possible value: -50, -10
|
||||
@@ -367,4 +362,4 @@ namespace Ink_Canvas
|
||||
isFloatingBarChangingHideMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,45 @@
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using Application = System.Windows.Application;
|
||||
using ui = iNKORE.UI.WPF.Modern.Controls;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private Color FloatBarForegroundColor = Color.FromRgb(102, 102, 102);
|
||||
private Color FloatBarForegroundColor;
|
||||
|
||||
private void SetTheme(string theme)
|
||||
private void SetTheme(string theme, bool autoSwitchIcon = false)
|
||||
{
|
||||
// 清理现有的主题资源
|
||||
var resourcesToRemove = new List<ResourceDictionary>();
|
||||
foreach (var dict in Application.Current.Resources.MergedDictionaries)
|
||||
{
|
||||
if (dict.Source != null &&
|
||||
(dict.Source.ToString().Contains("Light.xaml") ||
|
||||
dict.Source.ToString().Contains("Dark.xaml")))
|
||||
{
|
||||
resourcesToRemove.Add(dict);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var dict in resourcesToRemove)
|
||||
{
|
||||
Application.Current.Resources.MergedDictionaries.Remove(dict);
|
||||
}
|
||||
|
||||
if (theme == "Light")
|
||||
{
|
||||
var rd1 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/Styles/Light.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd1);
|
||||
|
||||
// 在主题资源之后添加其他资源
|
||||
var rd2 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/DrawShapeImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd2);
|
||||
@@ -33,13 +54,36 @@ namespace Ink_Canvas
|
||||
|
||||
ThemeManager.SetRequestedTheme(window, ElementTheme.Light);
|
||||
|
||||
FloatBarForegroundColor = (Color)Application.Current.FindResource("FloatBarForegroundColor");
|
||||
InitializeFloatBarForegroundColor();
|
||||
|
||||
// 刷新快速面板图标
|
||||
RefreshQuickPanelIcons();
|
||||
|
||||
// 刷新墨迹选中栏图标
|
||||
RefreshStrokeSelectionIcons();
|
||||
|
||||
// 刷新图片选中栏图标
|
||||
RefreshImageSelectionIcons();
|
||||
|
||||
RefreshFloatingBarHighlightColors();
|
||||
|
||||
if (autoSwitchIcon)
|
||||
{
|
||||
AutoSwitchFloatingBarIconForTheme("Light");
|
||||
}
|
||||
|
||||
// 强制刷新UI
|
||||
window.InvalidateVisual();
|
||||
|
||||
// 通知其他窗口刷新主题
|
||||
RefreshOtherWindowsTheme();
|
||||
}
|
||||
else if (theme == "Dark")
|
||||
{
|
||||
var rd1 = new ResourceDictionary { Source = new Uri("Resources/Styles/Dark.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd1);
|
||||
|
||||
// 在主题资源之后添加其他资源
|
||||
var rd2 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/DrawShapeImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd2);
|
||||
@@ -54,7 +98,195 @@ namespace Ink_Canvas
|
||||
|
||||
ThemeManager.SetRequestedTheme(window, ElementTheme.Dark);
|
||||
|
||||
InitializeFloatBarForegroundColor();
|
||||
|
||||
// 刷新快速面板图标
|
||||
RefreshQuickPanelIcons();
|
||||
|
||||
// 刷新墨迹选中栏图标
|
||||
RefreshStrokeSelectionIcons();
|
||||
|
||||
// 刷新图片选中栏图标
|
||||
RefreshImageSelectionIcons();
|
||||
|
||||
RefreshFloatingBarHighlightColors();
|
||||
|
||||
if (autoSwitchIcon)
|
||||
{
|
||||
AutoSwitchFloatingBarIconForTheme("Dark");
|
||||
}
|
||||
|
||||
// 强制刷新UI
|
||||
window.InvalidateVisual();
|
||||
|
||||
// 通知其他窗口刷新主题
|
||||
RefreshOtherWindowsTheme();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新浮动栏高光条颜色
|
||||
/// </summary>
|
||||
private void RefreshFloatingBarHighlightColors()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (FloatingbarSelectionBG != null && FloatingbarSelectionBG.Visibility == Visibility.Visible)
|
||||
{
|
||||
// 根据主题设置高光颜色
|
||||
Color highlightBackgroundColor;
|
||||
Color highlightBarColor;
|
||||
bool isDarkTheme = Settings.Appearance.Theme == 1 ||
|
||||
(Settings.Appearance.Theme == 2 && !IsSystemThemeLight());
|
||||
|
||||
if (isDarkTheme)
|
||||
{
|
||||
highlightBackgroundColor = Color.FromArgb(21, 102, 204, 255);
|
||||
highlightBarColor = Color.FromRgb(102, 204, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
highlightBackgroundColor = Color.FromArgb(21, 59, 130, 246);
|
||||
highlightBarColor = Color.FromRgb(37, 99, 235);
|
||||
}
|
||||
|
||||
// 设置高光背景颜色
|
||||
FloatingbarSelectionBG.Background = new SolidColorBrush(highlightBackgroundColor);
|
||||
if (FloatingbarSelectionBG.Child is System.Windows.Controls.Canvas canvas && canvas.Children.Count > 0)
|
||||
{
|
||||
var firstChild = canvas.Children[0];
|
||||
if (firstChild is Border innerBorder)
|
||||
{
|
||||
innerBorder.Background = new SolidColorBrush(highlightBarColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新浮动工具栏按钮颜色
|
||||
/// </summary>
|
||||
private void RefreshFloatingBarButtonColors()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 根据主题选择高光颜色
|
||||
Color selectedColor;
|
||||
bool isDarkTheme = Settings.Appearance.Theme == 1 ||
|
||||
(Settings.Appearance.Theme == 2 && !IsSystemThemeLight());
|
||||
|
||||
if (isDarkTheme)
|
||||
{
|
||||
selectedColor = Color.FromRgb(102, 204, 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
selectedColor = Color.FromRgb(30, 58, 138);
|
||||
}
|
||||
|
||||
// 根据当前模式设置按钮颜色
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,5 +323,202 @@ namespace Ink_Canvas
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
UpdateFloatingBarIcon();
|
||||
UpdateFloatingBarIconComboBox();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 忽略异常,确保主题切换不会因为图标刷新失败而中断
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归刷新图片选中栏内的图标
|
||||
/// </summary>
|
||||
private void RefreshImageSelectionIconsRecursive(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)
|
||||
{
|
||||
// 递归处理子面板
|
||||
RefreshImageSelectionIconsRecursive(childPanel);
|
||||
}
|
||||
else if (child is Border border && border.Child is System.Windows.Controls.Panel borderPanel)
|
||||
{
|
||||
// 处理Border内的面板
|
||||
RefreshImageSelectionIconsRecursive(borderPanel);
|
||||
}
|
||||
else if (child is Grid grid)
|
||||
{
|
||||
// 处理Grid内的子元素
|
||||
foreach (var gridChild in grid.Children)
|
||||
{
|
||||
if (gridChild is Image gridImage)
|
||||
{
|
||||
gridImage.InvalidateVisual();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// 忽略异常
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新其他窗口的主题
|
||||
/// </summary>
|
||||
private void RefreshOtherWindowsTheme()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 刷新所有打开的窗口
|
||||
foreach (Window window in Application.Current.Windows)
|
||||
{
|
||||
if (window is CountdownTimerWindow timerWindow)
|
||||
{
|
||||
timerWindow.RefreshTheme();
|
||||
}
|
||||
else if (window is RandWindow randWindow)
|
||||
{
|
||||
randWindow.RefreshTheme();
|
||||
}
|
||||
else if (window is OperatingGuideWindow operatingGuideWindow)
|
||||
{
|
||||
operatingGuideWindow.RefreshTheme();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -15,10 +16,10 @@ namespace Ink_Canvas
|
||||
private StrokeCollection[] strokeCollections = new StrokeCollection[101];
|
||||
private bool[] whiteboadLastModeIsRedo = new bool[101];
|
||||
private StrokeCollection lastTouchDownStrokeCollection = new StrokeCollection();
|
||||
|
||||
private int CurrentWhiteboardIndex = 1;
|
||||
private int WhiteboardTotalCount = 1;
|
||||
private TimeMachineHistory[][] TimeMachineHistories = new TimeMachineHistory[101][]; //最多99页,0用来存储非白板时的墨迹以便还原
|
||||
private TimeMachineHistory[][] TimeMachineHistories = new TimeMachineHistory[101][];
|
||||
private bool[] savedMultiTouchModeStates = new bool[101];
|
||||
|
||||
// 保存每页白板图片信息
|
||||
private void SaveStrokes(bool isBackupMain = false)
|
||||
@@ -97,6 +98,8 @@ namespace Ink_Canvas
|
||||
{
|
||||
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
|
||||
TimeMachineHistories[0] = timeMachineHistory;
|
||||
// 保存多指书写模式状态
|
||||
savedMultiTouchModeStates[0] = isInMultiTouchMode;
|
||||
timeMachine.ClearStrokeHistory();
|
||||
|
||||
|
||||
@@ -105,6 +108,8 @@ namespace Ink_Canvas
|
||||
{
|
||||
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
|
||||
TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory;
|
||||
// 保存多指书写模式状态
|
||||
savedMultiTouchModeStates[CurrentWhiteboardIndex] = isInMultiTouchMode;
|
||||
timeMachine.ClearStrokeHistory();
|
||||
|
||||
|
||||
@@ -116,15 +121,25 @@ namespace Ink_Canvas
|
||||
_currentCommitType = CommitReason.ClearingCanvas;
|
||||
if (isErasedByCode) _currentCommitType = CommitReason.CodeInput;
|
||||
|
||||
|
||||
|
||||
// 只清除笔画,不清除图片元素
|
||||
// 图片元素的清除由调用方决定
|
||||
inkCanvas.Strokes.Clear();
|
||||
|
||||
// 执行内存清理
|
||||
PerformLightweightMemoryCleanup();
|
||||
|
||||
_currentCommitType = CommitReason.UserInput;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行内存清理
|
||||
/// </summary>
|
||||
private void PerformLightweightMemoryCleanup()
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
GC.Collect();
|
||||
});
|
||||
}
|
||||
|
||||
// 恢复每页白板图片信息
|
||||
private void RestoreStrokes(bool isBackupMain = false)
|
||||
{
|
||||
@@ -161,12 +176,16 @@ namespace Ink_Canvas
|
||||
{
|
||||
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[0]);
|
||||
foreach (var item in TimeMachineHistories[0]) ApplyHistoryToCanvas(item);
|
||||
// 恢复多指书写模式状态
|
||||
RestoreMultiTouchModeState(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
timeMachine.ImportTimeMachineHistory(TimeMachineHistories[CurrentWhiteboardIndex]);
|
||||
// 通过时间机器历史恢复所有内容(墨迹和图片)
|
||||
foreach (var item in TimeMachineHistories[CurrentWhiteboardIndex]) ApplyHistoryToCanvas(item);
|
||||
// 恢复多指书写模式状态
|
||||
RestoreMultiTouchModeState(CurrentWhiteboardIndex);
|
||||
}
|
||||
|
||||
|
||||
@@ -177,6 +196,45 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 恢复多指书写模式状态
|
||||
/// </summary>
|
||||
private void RestoreMultiTouchModeState(int pageIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否保存了多指书写模式状态
|
||||
if (savedMultiTouchModeStates[pageIndex])
|
||||
{
|
||||
// 恢复多指书写模式
|
||||
EnterMultiTouchModeIfNeeded();
|
||||
|
||||
// 更新UI状态
|
||||
if (ToggleSwitchEnableMultiTouchMode != null)
|
||||
{
|
||||
ToggleSwitchEnableMultiTouchMode.IsOn = true;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"恢复多指书写模式状态 - 页面索引: {pageIndex}", LogHelper.LogType.Info);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 确保多指书写模式关闭
|
||||
ExitMultiTouchModeIfNeeded();
|
||||
|
||||
// 更新UI状态
|
||||
if (ToggleSwitchEnableMultiTouchMode != null)
|
||||
{
|
||||
ToggleSwitchEnableMultiTouchMode.IsOn = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"恢复多指书写模式状态失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private async void BtnWhiteBoardPageIndex_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (sender == BtnLeftPageListWB)
|
||||
@@ -191,9 +249,12 @@ namespace Ink_Canvas
|
||||
RefreshBlackBoardSidePageListView();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderLeftPageListView);
|
||||
await Task.Delay(1);
|
||||
ScrollViewToVerticalTop(
|
||||
(ListViewItem)BlackBoardLeftSidePageListView.ItemContainerGenerator.ContainerFromIndex(
|
||||
CurrentWhiteboardIndex - 1), BlackBoardLeftSidePageListScrollViewer);
|
||||
var leftContainer = BlackBoardLeftSidePageListView.ItemContainerGenerator.ContainerFromIndex(
|
||||
CurrentWhiteboardIndex - 1) as ListViewItem;
|
||||
if (leftContainer != null)
|
||||
{
|
||||
ScrollViewToVerticalTop(leftContainer, BlackBoardLeftSidePageListScrollViewer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sender == BtnRightPageListWB)
|
||||
@@ -208,9 +269,12 @@ namespace Ink_Canvas
|
||||
RefreshBlackBoardSidePageListView();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderRightPageListView);
|
||||
await Task.Delay(1);
|
||||
ScrollViewToVerticalTop(
|
||||
(ListViewItem)BlackBoardRightSidePageListView.ItemContainerGenerator.ContainerFromIndex(
|
||||
CurrentWhiteboardIndex - 1), BlackBoardRightSidePageListScrollViewer);
|
||||
var rightContainer = BlackBoardRightSidePageListView.ItemContainerGenerator.ContainerFromIndex(
|
||||
CurrentWhiteboardIndex - 1) as ListViewItem;
|
||||
if (rightContainer != null)
|
||||
{
|
||||
ScrollViewToVerticalTop(rightContainer, BlackBoardRightSidePageListScrollViewer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,13 +424,19 @@ namespace Ink_Canvas
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
||||
BtnRightWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
||||
|
||||
// 始终允许点击“下一页/新页面”按钮(除非已达最大页数)
|
||||
// 始终允许点击"下一页/新页面"按钮(除非已达最大页数)
|
||||
BtnWhiteBoardSwitchNext.IsEnabled = !isMaxPage;
|
||||
|
||||
// 保持按钮常亮(高亮)
|
||||
BtnLeftWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
// 获取主题颜色资源
|
||||
var iconForegroundBrush = Application.Current.FindResource("IconForeground") as SolidColorBrush;
|
||||
|
||||
// 设置下一页按钮颜色
|
||||
if (iconForegroundBrush != null)
|
||||
{
|
||||
BtnLeftWhiteBoardSwitchNextGeometry.Brush = iconForegroundBrush;
|
||||
BtnRightWhiteBoardSwitchNextGeometry.Brush = iconForegroundBrush;
|
||||
}
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Opacity = 1;
|
||||
BtnRightWhiteBoardSwitchNextGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
BtnRightWhiteBoardSwitchNextLabel.Opacity = 1;
|
||||
|
||||
BtnWhiteBoardSwitchPrevious.IsEnabled = true;
|
||||
@@ -374,16 +444,23 @@ namespace Ink_Canvas
|
||||
if (CurrentWhiteboardIndex == 1)
|
||||
{
|
||||
BtnWhiteBoardSwitchPrevious.IsEnabled = false;
|
||||
BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27));
|
||||
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;
|
||||
}
|
||||
BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 0.5;
|
||||
BtnRightWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(127, 24, 24, 27));
|
||||
BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
if (iconForegroundBrush != null)
|
||||
{
|
||||
BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = iconForegroundBrush;
|
||||
BtnRightWhiteBoardSwitchPreviousGeometry.Brush = iconForegroundBrush;
|
||||
}
|
||||
BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 1;
|
||||
BtnRightWhiteBoardSwitchPreviousGeometry.Brush = new SolidColorBrush(Color.FromArgb(255, 24, 24, 27));
|
||||
BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace Ink_Canvas
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 设置为白板默认背景色
|
||||
Color defaultWhiteboardColor = Color.FromRgb(234, 235, 237);
|
||||
Color defaultWhiteboardColor = Color.FromRgb(255, 255, 255);
|
||||
|
||||
if (currentMode == 1) // 白板模式
|
||||
{
|
||||
@@ -135,7 +135,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
Name = "BackgroundPalette",
|
||||
Visibility = Visibility.Collapsed,
|
||||
Background = new SolidColorBrush(Colors.White),
|
||||
Background = (SolidColorBrush)Application.Current.FindResource("SettingsPageBackground"),
|
||||
Opacity = 1,
|
||||
BorderBrush = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)),
|
||||
BorderThickness = new Thickness(1),
|
||||
@@ -166,7 +166,7 @@ namespace Ink_Canvas
|
||||
var titleText = new TextBlock
|
||||
{
|
||||
Text = "背景设置",
|
||||
Foreground = new SolidColorBrush(Colors.White),
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("FloatBarForeground"),
|
||||
Padding = new Thickness(0, 5, 0, 0),
|
||||
FontSize = 11,
|
||||
FontWeight = FontWeights.Bold,
|
||||
@@ -198,7 +198,7 @@ namespace Ink_Canvas
|
||||
var modeTitle = new TextBlock
|
||||
{
|
||||
Text = "白板模式",
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(0x17, 0x25, 0x54)),
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground"),
|
||||
FontSize = 10,
|
||||
FontWeight = FontWeights.Bold,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
@@ -233,7 +233,7 @@ namespace Ink_Canvas
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 设置为白板默认背景色
|
||||
Color defaultWhiteboardColor = Color.FromRgb(234, 235, 237);
|
||||
Color defaultWhiteboardColor = Color.FromRgb(255, 255, 255);
|
||||
|
||||
if (currentMode == 1) // 白板模式
|
||||
{
|
||||
@@ -319,7 +319,7 @@ namespace Ink_Canvas
|
||||
var separator = new Border
|
||||
{
|
||||
Height = 1,
|
||||
Background = new SolidColorBrush(Color.FromRgb(0xd4, 0xd4, 0xd8)),
|
||||
Background = (SolidColorBrush)Application.Current.FindResource("SettingsPageBorderBrush"),
|
||||
Margin = new Thickness(0, 12, 0, 12)
|
||||
};
|
||||
contentPanel.Children.Add(separator);
|
||||
@@ -328,7 +328,7 @@ namespace Ink_Canvas
|
||||
var colorTitle = new TextBlock
|
||||
{
|
||||
Text = "背景颜色",
|
||||
Foreground = new SolidColorBrush(Color.FromRgb(0x17, 0x25, 0x54)),
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground"),
|
||||
FontSize = 10,
|
||||
FontWeight = FontWeights.Bold,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
@@ -342,7 +342,7 @@ namespace Ink_Canvas
|
||||
Width = 100,
|
||||
Height = 40,
|
||||
BorderThickness = new Thickness(1),
|
||||
BorderBrush = new SolidColorBrush(Color.FromRgb(0xd4, 0xd4, 0xd8)),
|
||||
BorderBrush = (SolidColorBrush)Application.Current.FindResource("SettingsPageBorderBrush"),
|
||||
Background = new SolidColorBrush(Colors.White),
|
||||
CornerRadius = new CornerRadius(4),
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
@@ -378,7 +378,7 @@ namespace Ink_Canvas
|
||||
// 先创建所有滑块控件
|
||||
// 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 };
|
||||
var rLabel = new TextBlock { Text = "R:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
||||
var rSlider = new Slider
|
||||
{
|
||||
Minimum = 0,
|
||||
@@ -393,12 +393,13 @@ namespace Ink_Canvas
|
||||
Text = currentBackgroundColor.R.ToString(),
|
||||
Width = 30,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextAlignment = TextAlignment.Right
|
||||
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 };
|
||||
var gLabel = new TextBlock { Text = "G:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
||||
var gSlider = new Slider
|
||||
{
|
||||
Minimum = 0,
|
||||
@@ -413,12 +414,13 @@ namespace Ink_Canvas
|
||||
Text = currentBackgroundColor.G.ToString(),
|
||||
Width = 30,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextAlignment = TextAlignment.Right
|
||||
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 };
|
||||
var bLabel = new TextBlock { Text = "B:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
||||
var bSlider = new Slider
|
||||
{
|
||||
Minimum = 0,
|
||||
@@ -433,7 +435,8 @@ namespace Ink_Canvas
|
||||
Text = currentBackgroundColor.B.ToString(),
|
||||
Width = 30,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextAlignment = TextAlignment.Right
|
||||
TextAlignment = TextAlignment.Right,
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground")
|
||||
};
|
||||
|
||||
// 现在添加事件处理程序
|
||||
@@ -716,7 +719,7 @@ namespace Ink_Canvas
|
||||
//}
|
||||
//else {
|
||||
// 禁用高级橡皮擦系统
|
||||
DisableAdvancedEraserSystem();
|
||||
DisableEraserOverlay();
|
||||
|
||||
forceEraser = true;
|
||||
forcePointEraser = false;
|
||||
@@ -726,7 +729,6 @@ namespace Ink_Canvas
|
||||
SetCurrentToolMode(InkCanvasEditingMode.EraseByStroke);
|
||||
drawingShapeMode = 0;
|
||||
|
||||
// 修复:切换到线擦时,确保重置笔的状态
|
||||
penType = 0;
|
||||
drawingAttributes.IsHighlighter = false;
|
||||
drawingAttributes.StylusTip = StylusTip.Ellipse;
|
||||
|
||||
@@ -192,6 +192,8 @@ namespace Ink_Canvas
|
||||
elementForEvents.MouseWheel += Element_MouseWheel;
|
||||
|
||||
// 触摸事件
|
||||
elementForEvents.TouchDown += Element_TouchDown;
|
||||
elementForEvents.TouchUp += Element_TouchUp;
|
||||
elementForEvents.IsManipulationEnabled = true;
|
||||
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
||||
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
@@ -205,6 +207,11 @@ namespace Ink_Canvas
|
||||
// 提交到历史记录
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
|
||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
UpdateCurrentToolMode("select");
|
||||
HideSubPanels("select");
|
||||
|
||||
ShowNotification("图片已从剪贴板粘贴");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -18,7 +18,10 @@ namespace Ink_Canvas
|
||||
|
||||
private void ColorSwitchCheck()
|
||||
{
|
||||
HideSubPanels("color");
|
||||
if (penType != 1)
|
||||
{
|
||||
HideSubPanels("color");
|
||||
}
|
||||
if (GridTransparencyFakeBackground.Background == Brushes.Transparent)
|
||||
{
|
||||
if (currentMode == 1)
|
||||
|
||||
@@ -11,7 +11,9 @@ using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -68,6 +70,11 @@ namespace Ink_Canvas
|
||||
};
|
||||
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
|
||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
UpdateCurrentToolMode("select");
|
||||
HideSubPanels("select");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,6 +152,19 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸释放事件
|
||||
private void Element_TouchUp(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
{
|
||||
isDragging = false;
|
||||
element.ReleaseTouchCapture(e.TouchDevice);
|
||||
element.Cursor = Cursors.Hand;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 鼠标移动事件
|
||||
private void Element_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
@@ -161,6 +181,12 @@ namespace Ink_Canvas
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
}
|
||||
|
||||
// 如果是图片元素,更新选择点位置
|
||||
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||
}
|
||||
|
||||
dragStartPoint = currentPoint;
|
||||
e.Handled = true;
|
||||
}
|
||||
@@ -182,6 +208,40 @@ namespace Ink_Canvas
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
}
|
||||
|
||||
// 如果是图片元素,更新选择点位置
|
||||
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸按下事件
|
||||
private void Element_TouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
{
|
||||
// 取消之前选中的元素
|
||||
if (currentSelectedElement != null && currentSelectedElement != element)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
}
|
||||
|
||||
// 选中当前元素
|
||||
SelectElement(element);
|
||||
|
||||
// 开始拖动
|
||||
isDragging = true;
|
||||
dragStartPoint = e.GetTouchPoint(inkCanvas).Position;
|
||||
element.CaptureTouch(e.TouchDevice);
|
||||
element.Cursor = Cursors.SizeAll;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -191,7 +251,16 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (sender is FrameworkElement element)
|
||||
{
|
||||
// 使用触摸拖动的完整实现
|
||||
// 检查是否是双指手势
|
||||
if (e.Manipulators.Count() >= 2)
|
||||
{
|
||||
// 双指手势时,不处理单个元素的手势,让画布级别的手势处理
|
||||
// 这样可以实现图片与墨迹的同步移动
|
||||
e.Handled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 单指手势时,使用触摸拖动的完整实现
|
||||
ApplyTouchManipulationTransform(element, e);
|
||||
|
||||
// 如果是图片元素,更新工具栏位置
|
||||
@@ -200,6 +269,12 @@ namespace Ink_Canvas
|
||||
UpdateImageSelectionToolbarPosition(element);
|
||||
}
|
||||
|
||||
// 如果是图片元素,更新选择点位置
|
||||
if (element is Image && ImageResizeHandlesCanvas?.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(element));
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
@@ -276,25 +351,25 @@ namespace Ink_Canvas
|
||||
BorderImageSelectionControl.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// 隐藏笔画选择工具栏
|
||||
if (BorderStrokeSelectionControl != null)
|
||||
{
|
||||
BorderStrokeSelectionControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
// 显示图片缩放选择点
|
||||
ShowImageResizeHandles(element);
|
||||
|
||||
// 墨迹选择工具栏通过GridInkCanvasSelectionCover的可见性来控制
|
||||
// 不需要直接设置BorderStrokeSelectionControl.Visibility
|
||||
}
|
||||
else
|
||||
{
|
||||
// 显示笔画选择工具栏
|
||||
if (BorderStrokeSelectionControl != null)
|
||||
{
|
||||
BorderStrokeSelectionControl.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
// 隐藏图片选择工具栏
|
||||
if (BorderImageSelectionControl != null)
|
||||
{
|
||||
BorderImageSelectionControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
// 隐藏图片缩放选择点
|
||||
HideImageResizeHandles();
|
||||
|
||||
// 墨迹选择工具栏通过GridInkCanvasSelectionCover的可见性来控制
|
||||
// 不需要直接设置BorderStrokeSelectionControl.Visibility
|
||||
}
|
||||
|
||||
// 确保选择框不显示,避免蓝色边框
|
||||
@@ -308,8 +383,8 @@ namespace Ink_Canvas
|
||||
{
|
||||
// 清除当前选择
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
// 设置编辑模式为非选择模式
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
// 保持选择模式,这样用户可以直接点击墨迹来选择
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Select;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,16 +393,17 @@ namespace Ink_Canvas
|
||||
{
|
||||
// 去除选中效果
|
||||
|
||||
// 隐藏所有选择工具栏
|
||||
// 隐藏图片选择工具栏
|
||||
if (BorderImageSelectionControl != null)
|
||||
{
|
||||
BorderImageSelectionControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (BorderStrokeSelectionControl != null)
|
||||
{
|
||||
BorderStrokeSelectionControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
// 隐藏图片缩放选择点
|
||||
HideImageResizeHandles();
|
||||
|
||||
// 墨迹选择工具栏通过GridInkCanvasSelectionCover的可见性来控制
|
||||
// 不需要直接设置BorderStrokeSelectionControl.Visibility
|
||||
|
||||
// 确保选择框隐藏
|
||||
if (GridInkCanvasSelectionCover != null)
|
||||
@@ -335,7 +411,11 @@ namespace Ink_Canvas
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
|
||||
// 确保InkCanvas处于选择模式,这样用户可以直接点击墨迹来选择
|
||||
if (inkCanvas != null)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Select;
|
||||
}
|
||||
}
|
||||
|
||||
// 应用矩阵变换到元素
|
||||
@@ -1324,6 +1404,323 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 克隆墨迹集合
|
||||
/// </summary>
|
||||
/// <param name="strokes">要克隆的墨迹集合</param>
|
||||
/// <returns>克隆后的墨迹集合</returns>
|
||||
private StrokeCollection CloneStrokes(StrokeCollection strokes)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0) return new StrokeCollection();
|
||||
|
||||
try
|
||||
{
|
||||
// 创建墨迹集合的副本
|
||||
var clonedStrokes = strokes.Clone();
|
||||
|
||||
// 为每个墨迹添加位置偏移以避免重叠
|
||||
foreach (var stroke in clonedStrokes)
|
||||
{
|
||||
var offsetPoints = new StylusPointCollection();
|
||||
foreach (var point in stroke.StylusPoints)
|
||||
{
|
||||
offsetPoints.Add(new StylusPoint(point.X + 20, point.Y + 20, point.PressureFactor));
|
||||
}
|
||||
stroke.StylusPoints = offsetPoints;
|
||||
}
|
||||
|
||||
// 添加到画布
|
||||
inkCanvas.Strokes.Add(clonedStrokes);
|
||||
|
||||
// 提交到时间机器以支持撤销
|
||||
timeMachine.CommitStrokeUserInputHistory(clonedStrokes);
|
||||
|
||||
LogHelper.WriteLogToFile($"墨迹克隆完成: {clonedStrokes.Count} 个墨迹");
|
||||
return clonedStrokes;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录错误但不中断程序
|
||||
LogHelper.WriteLogToFile($"克隆墨迹时发生错误: {ex.Message}", LogHelper.LogType.Error);
|
||||
return new StrokeCollection();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 克隆墨迹集合到新页面
|
||||
/// </summary>
|
||||
/// <param name="strokes">要克隆的墨迹集合</param>
|
||||
private void CloneStrokesToNewBoard(StrokeCollection strokes)
|
||||
{
|
||||
if (strokes == null || strokes.Count == 0) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 创建墨迹集合的副本
|
||||
var clonedStrokes = strokes.Clone();
|
||||
|
||||
// 为每个墨迹添加位置偏移以避免重叠
|
||||
foreach (var stroke in clonedStrokes)
|
||||
{
|
||||
var offsetPoints = new StylusPointCollection();
|
||||
foreach (var point in stroke.StylusPoints)
|
||||
{
|
||||
offsetPoints.Add(new StylusPoint(point.X + 20, point.Y + 20, point.PressureFactor));
|
||||
}
|
||||
stroke.StylusPoints = offsetPoints;
|
||||
}
|
||||
|
||||
// 创建新页面
|
||||
BtnWhiteBoardAdd_Click(null, null);
|
||||
|
||||
// 添加到新页面的画布
|
||||
inkCanvas.Strokes.Add(clonedStrokes);
|
||||
|
||||
// 提交到时间机器以支持撤销
|
||||
timeMachine.CommitStrokeUserInputHistory(clonedStrokes);
|
||||
|
||||
LogHelper.WriteLogToFile($"墨迹克隆到新页面完成: {clonedStrokes.Count} 个墨迹");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 记录错误但不中断程序
|
||||
LogHelper.WriteLogToFile($"克隆墨迹到新页面时发生错误: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Image Resize Handles
|
||||
|
||||
// 图片缩放选择点相关变量
|
||||
private bool isResizingImage = false;
|
||||
private Point imageResizeStartPoint;
|
||||
private string activeResizeHandle = "";
|
||||
|
||||
// 显示图片缩放选择点
|
||||
private void ShowImageResizeHandles(FrameworkElement element)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ImageResizeHandlesCanvas == null || element == null) return;
|
||||
|
||||
// 获取元素的实际边界
|
||||
Rect elementBounds = GetElementActualBounds(element);
|
||||
|
||||
// 设置选择点位置
|
||||
UpdateImageResizeHandlesPosition(elementBounds);
|
||||
|
||||
// 显示选择点
|
||||
ImageResizeHandlesCanvas.Visibility = Visibility.Visible;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示图片缩放选择点失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏图片缩放选择点
|
||||
private void HideImageResizeHandles()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ImageResizeHandlesCanvas != null)
|
||||
{
|
||||
ImageResizeHandlesCanvas.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"隐藏图片缩放选择点失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新图片缩放选择点位置
|
||||
private void UpdateImageResizeHandlesPosition(Rect elementBounds)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ImageResizeHandlesCanvas == null) return;
|
||||
|
||||
ImageResizeHandlesCanvas.Margin = new Thickness(elementBounds.Left, elementBounds.Top, 0, 0);
|
||||
|
||||
// 四个角控制点
|
||||
System.Windows.Controls.Canvas.SetLeft(ImageTopLeftHandle, -4);
|
||||
System.Windows.Controls.Canvas.SetTop(ImageTopLeftHandle, -4);
|
||||
|
||||
System.Windows.Controls.Canvas.SetLeft(ImageTopRightHandle, elementBounds.Width - 4);
|
||||
System.Windows.Controls.Canvas.SetTop(ImageTopRightHandle, -4);
|
||||
|
||||
System.Windows.Controls.Canvas.SetLeft(ImageBottomLeftHandle, -4);
|
||||
System.Windows.Controls.Canvas.SetTop(ImageBottomLeftHandle, elementBounds.Height - 4);
|
||||
|
||||
System.Windows.Controls.Canvas.SetLeft(ImageBottomRightHandle, elementBounds.Width - 4);
|
||||
System.Windows.Controls.Canvas.SetTop(ImageBottomRightHandle, elementBounds.Height - 4);
|
||||
|
||||
// 四个边控制点
|
||||
System.Windows.Controls.Canvas.SetLeft(ImageTopHandle, elementBounds.Width / 2 - 4);
|
||||
System.Windows.Controls.Canvas.SetTop(ImageTopHandle, -4);
|
||||
|
||||
System.Windows.Controls.Canvas.SetLeft(ImageBottomHandle, elementBounds.Width / 2 - 4);
|
||||
System.Windows.Controls.Canvas.SetTop(ImageBottomHandle, elementBounds.Height - 4);
|
||||
|
||||
System.Windows.Controls.Canvas.SetLeft(ImageLeftHandle, -4);
|
||||
System.Windows.Controls.Canvas.SetTop(ImageLeftHandle, elementBounds.Height / 2 - 4);
|
||||
|
||||
System.Windows.Controls.Canvas.SetLeft(ImageRightHandle, elementBounds.Width - 4);
|
||||
System.Windows.Controls.Canvas.SetTop(ImageRightHandle, elementBounds.Height / 2 - 4);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新图片缩放选择点位置失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 图片缩放选择点鼠标按下事件
|
||||
private void ImageResizeHandle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (currentSelectedElement is Image image && sender is Ellipse ellipse)
|
||||
{
|
||||
isResizingImage = true;
|
||||
imageResizeStartPoint = e.GetPosition(inkCanvas);
|
||||
|
||||
// 确定是哪个控制点
|
||||
activeResizeHandle = ellipse.Name;
|
||||
|
||||
// 捕获鼠标
|
||||
ellipse.CaptureMouse();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"图片缩放选择点鼠标按下事件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 图片缩放选择点鼠标释放事件
|
||||
private void ImageResizeHandle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isResizingImage && sender is Ellipse ellipse)
|
||||
{
|
||||
isResizingImage = false;
|
||||
ellipse.ReleaseMouseCapture();
|
||||
activeResizeHandle = "";
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"图片缩放选择点鼠标释放事件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 图片缩放选择点鼠标移动事件
|
||||
private void ImageResizeHandle_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isResizingImage && currentSelectedElement is Image image && sender is Ellipse ellipse)
|
||||
{
|
||||
var currentPoint = e.GetPosition(inkCanvas);
|
||||
ResizeImageByHandle(image, imageResizeStartPoint, currentPoint, activeResizeHandle);
|
||||
imageResizeStartPoint = currentPoint;
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"图片缩放选择点鼠标移动事件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 根据控制点缩放图片
|
||||
private void ResizeImageByHandle(Image image, Point startPoint, Point currentPoint, string handleName)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (image.RenderTransform is TransformGroup transformGroup)
|
||||
{
|
||||
var scaleTransform = transformGroup.Children.OfType<ScaleTransform>().FirstOrDefault();
|
||||
var translateTransform = transformGroup.Children.OfType<TranslateTransform>().FirstOrDefault();
|
||||
|
||||
if (scaleTransform == null || translateTransform == null) return;
|
||||
|
||||
// 获取图片的当前边界
|
||||
Rect currentBounds = GetElementActualBounds(image);
|
||||
double deltaX = currentPoint.X - startPoint.X;
|
||||
double deltaY = currentPoint.Y - startPoint.Y;
|
||||
|
||||
// 计算缩放比例
|
||||
double scaleX = 1.0;
|
||||
double scaleY = 1.0;
|
||||
double translateX = 0;
|
||||
double translateY = 0;
|
||||
|
||||
switch (handleName)
|
||||
{
|
||||
case "ImageTopLeftHandle":
|
||||
scaleX = (currentBounds.Width - deltaX) / currentBounds.Width;
|
||||
scaleY = (currentBounds.Height - deltaY) / currentBounds.Height;
|
||||
translateX = deltaX;
|
||||
translateY = deltaY;
|
||||
break;
|
||||
case "ImageTopRightHandle":
|
||||
scaleX = (currentBounds.Width + deltaX) / currentBounds.Width;
|
||||
scaleY = (currentBounds.Height - deltaY) / currentBounds.Height;
|
||||
translateY = deltaY;
|
||||
break;
|
||||
case "ImageBottomLeftHandle":
|
||||
scaleX = (currentBounds.Width - deltaX) / currentBounds.Width;
|
||||
scaleY = (currentBounds.Height + deltaY) / currentBounds.Height;
|
||||
translateX = deltaX;
|
||||
break;
|
||||
case "ImageBottomRightHandle":
|
||||
scaleX = (currentBounds.Width + deltaX) / currentBounds.Width;
|
||||
scaleY = (currentBounds.Height + deltaY) / currentBounds.Height;
|
||||
break;
|
||||
case "ImageTopHandle":
|
||||
scaleY = (currentBounds.Height - deltaY) / currentBounds.Height;
|
||||
translateY = deltaY;
|
||||
break;
|
||||
case "ImageBottomHandle":
|
||||
scaleY = (currentBounds.Height + deltaY) / currentBounds.Height;
|
||||
break;
|
||||
case "ImageLeftHandle":
|
||||
scaleX = (currentBounds.Width - deltaX) / currentBounds.Width;
|
||||
translateX = deltaX;
|
||||
break;
|
||||
case "ImageRightHandle":
|
||||
scaleX = (currentBounds.Width + deltaX) / currentBounds.Width;
|
||||
break;
|
||||
}
|
||||
|
||||
// 限制缩放范围
|
||||
scaleX = Math.Max(0.1, Math.Min(scaleX, 5.0));
|
||||
scaleY = Math.Max(0.1, Math.Min(scaleY, 5.0));
|
||||
|
||||
// 应用缩放
|
||||
scaleTransform.ScaleX *= scaleX;
|
||||
scaleTransform.ScaleY *= scaleY;
|
||||
|
||||
// 应用平移
|
||||
translateTransform.X += translateX;
|
||||
translateTransform.Y += translateY;
|
||||
|
||||
// 更新选择点位置
|
||||
UpdateImageResizeHandlesPosition(GetElementActualBounds(image));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"根据控制点缩放图片失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,46 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<DrawingGroup x:Key="EraserDrawingGroup" ClipGeometry="M0,0 V56 H38 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#FFF2EEEB" Geometry="F1 M38,56z M0,0z M0,4C0,1.79086,1.79086,0,4,0L34,0C36.2091,0,38,1.79086,38,4L38,52C38,54.2091,36.2091,56,34,56L4,56C1.79086,56,0,54.2091,0,52L0,4z" />
|
||||
<GeometryDrawing Brush="#FFCDCDCD" Geometry="F0 M38,56z M0,0z M34,1L4,1C2.34315,1,1,2.34315,1,4L1,52C1,53.6569,2.34315,55,4,55L34,55C35.6569,55,37,53.6569,37,52L37,4C37,2.34315,35.6569,1,34,1z M4,0C1.79086,0,0,1.79086,0,4L0,52C0,54.2091,1.79086,56,4,56L34,56C36.2091,56,38,54.2091,38,52L38,4C38,1.79086,36.2091,0,34,0L4,0z" />
|
||||
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M38,56z M0,0z M12,19.5C12,18.1193,13.1193,17,14.5,17L14.5,17C15.8807,17,17,18.1193,17,19.5L17,36.5C17,37.8807,15.8807,39,14.5,39L14.5,39C13.1193,39,12,37.8807,12,36.5L12,19.5z" />
|
||||
<GeometryDrawing Geometry="F0 M38,56z M0,0z M11.5,19.5C11.5,17.8431 12.8431,16.5 14.5,16.5 16.1569,16.5 17.5,17.8431 17.5,19.5L17.5,36.5C17.5,38.1569 16.1569,39.5 14.5,39.5 12.8431,39.5 11.5,38.1569 11.5,36.5L11.5,19.5z M14.5,17.5C13.3954,17.5,12.5,18.3954,12.5,19.5L12.5,36.5C12.5,37.6046 13.3954,38.5 14.5,38.5 15.6046,38.5 16.5,37.6046 16.5,36.5L16.5,19.5C16.5,18.3954,15.6046,17.5,14.5,17.5z">
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M38,56z M0,0z M21,19.5C21,18.1193,22.1193,17,23.5,17L23.5,17C24.8807,17,26,18.1193,26,19.5L26,36.5C26,37.8807,24.8807,39,23.5,39L23.5,39C22.1193,39,21,37.8807,21,36.5L21,19.5z" />
|
||||
<GeometryDrawing Geometry="F0 M38,56z M0,0z M20.5,19.5C20.5,17.8431 21.8431,16.5 23.5,16.5 25.1569,16.5 26.5,17.8431 26.5,19.5L26.5,36.5C26.5,38.1569 25.1569,39.5 23.5,39.5 21.8431,39.5 20.5,38.1569 20.5,36.5L20.5,19.5z M23.5,17.5C22.3954,17.5,21.5,18.3954,21.5,19.5L21.5,36.5C21.5,37.6046 22.3954,38.5 23.5,38.5 24.6046,38.5 25.5,37.6046 25.5,36.5L25.5,19.5C25.5,18.3954,24.6046,17.5,23.5,17.5z">
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup>
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<!-- 矩形橡皮擦图像资源 -->
|
||||
<DrawingImage x:Key="RectangleEraserImageSource">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V56 H38 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#FFF2EEEB" Geometry="F1 M38,56z M0,0z M0,4C0,1.79086,1.79086,0,4,0L34,0C36.2091,0,38,1.79086,38,4L38,52C38,54.2091,36.2091,56,34,56L4,56C1.79086,56,0,54.2091,0,52L0,4z" />
|
||||
<GeometryDrawing Brush="#FFCDCDCD" Geometry="F0 M38,56z M0,0z M34,1L4,1C2.34315,1,1,2.34315,1,4L1,52C1,53.6569,2.34315,55,4,55L34,55C35.6569,55,37,53.6569,37,52L37,4C37,2.34315,35.6569,1,34,1z M4,0C1.79086,0,0,1.79086,0,4L0,52C0,54.2091,1.79086,56,4,56L34,56C36.2091,56,38,54.2091,38,52L38,4C38,1.79086,36.2091,0,34,0L4,0z" />
|
||||
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M38,56z M0,0z M12,19.5C12,18.1193,13.1193,17,14.5,17L14.5,17C15.8807,17,17,18.1193,17,19.5L17,36.5C17,37.8807,15.8807,39,14.5,39L14.5,39C13.1193,39,12,37.8807,12,36.5L12,19.5z" />
|
||||
<GeometryDrawing Geometry="F0 M38,56z M0,0z M11.5,19.5C11.5,17.8431 12.8431,16.5 14.5,16.5 16.1569,16.5 17.5,17.8431 17.5,19.5L17.5,36.5C17.5,38.1569 16.1569,39.5 14.5,39.5 12.8431,39.5 11.5,38.1569 11.5,36.5L11.5,19.5z M14.5,17.5C13.3954,17.5,12.5,18.3954,12.5,19.5L12.5,36.5C12.5,37.6046 13.3954,38.5 14.5,38.5 15.6046,38.5 16.5,37.6046 16.5,36.5L16.5,19.5C16.5,18.3954,15.6046,17.5,14.5,17.5z">
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M38,56z M0,0z M21,19.5C21,18.1193,22.1193,17,23.5,17L23.5,17C24.8807,17,26,18.1193,26,19.5L26,36.5C26,37.8807,24.8807,39,23.5,39L23.5,39C22.1193,39,21,37.8807,21,36.5L21,19.5z" />
|
||||
<GeometryDrawing Geometry="F0 M38,56z M0,0z M20.5,19.5C20.5,17.8431 21.8431,16.5 23.5,16.5 25.1569,16.5 26.5,17.8431 26.5,19.5L26.5,36.5C26.5,38.1569 25.1569,39.5 23.5,39.5 21.8431,39.5 20.5,38.1569 20.5,36.5L20.5,19.5z M23.5,17.5C22.3954,17.5,21.5,18.3954,21.5,19.5L21.5,36.5C21.5,37.6046 22.3954,38.5 23.5,38.5 24.6046,38.5 25.5,37.6046 25.5,36.5L25.5,19.5C25.5,18.3954,24.6046,17.5,23.5,17.5z">
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
<DrawingGroup x:Key="EraserCircleDrawingGroup" ClipGeometry="M0,0 V56 H56 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#FFF2EEEB" Geometry="F1 M56,56z M0,0z M0,28C0,12.536,12.536,0,28,0L28,0C43.464,0,56,12.536,56,28L56,28C56,43.464,43.464,56,28,56L28,56C12.536,56,0,43.464,0,28L0,28z" />
|
||||
<GeometryDrawing Brush="#FFCDCDCD" Geometry="F0 M56,56z M0,0z M1,28C1,42.9117 13.0883,55 28,55 42.9117,55 55,42.9117 55,28 55,13.0883 42.9117,1 28,1 13.0883,1 1,13.0883 1,28z M28,0C12.536,0 0,12.536 0,28 0,43.464 12.536,56 28,56 43.464,56 56,43.464 56,28 56,12.536 43.464,0 28,0z" />
|
||||
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M56,56z M0,0z M21,19.5C21,18.1193,22.1193,17,23.5,17L23.5,17C24.8807,17,26,18.1193,26,19.5L26,36.5C26,37.8807,24.8807,39,23.5,39L23.5,39C22.1193,39,21,37.8807,21,36.5L21,19.5z" />
|
||||
<GeometryDrawing Geometry="F0 M56,56z M0,0z M20.5,19.5C20.5,17.8431 21.8431,16.5 23.5,16.5 25.1569,16.5 26.5,17.8431 26.5,19.5L26.5,36.5C26.5,38.1569 25.1569,39.5 23.5,39.5 21.8431,39.5 20.5,38.1569 20.5,36.5L20.5,19.5z M23.5,17.5C22.3954,17.5,21.5,18.3954,21.5,19.5L21.5,36.5C21.5,37.6046 22.3954,38.5 23.5,38.5 24.6046,38.5 25.5,37.6046 25.5,36.5L25.5,19.5C25.5,18.3954,24.6046,17.5,23.5,17.5z">
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M56,56z M0,0z M30,19.5C30,18.1193,31.1193,17,32.5,17L32.5,17C33.8807,17,35,18.1193,35,19.5L35,36.5C35,37.8807,33.8807,39,32.5,39L32.5,39C31.1193,39,30,37.8807,30,36.5L30,19.5z" />
|
||||
<GeometryDrawing Geometry="F0 M56,56z M0,0z M29.5,19.5C29.5,17.8431 30.8431,16.5 32.5,16.5 34.1569,16.5 35.5,17.8431 35.5,19.5L35.5,36.5C35.5,38.1569 34.1569,39.5 32.5,39.5 30.8431,39.5 29.5,38.1569 29.5,36.5L29.5,19.5z M32.5,17.5C31.3954,17.5,30.5,18.3954,30.5,19.5L30.5,36.5C30.5,37.6046 31.3954,38.5 32.5,38.5 33.6046,38.5 34.5,37.6046 34.5,36.5L34.5,19.5C34.5,18.3954,33.6046,17.5,32.5,17.5z">
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup>
|
||||
<!-- 圆形橡皮擦图像资源 -->
|
||||
<DrawingImage x:Key="EllipseEraserImageSource">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V56 H56 V0 H0 Z">
|
||||
<GeometryDrawing Brush="#FFF2EEEB" Geometry="F1 M56,56z M0,0z M0,28C0,12.536,12.536,0,28,0L28,0C43.464,0,56,12.536,56,28L56,28C56,43.464,43.464,56,28,56L28,56C12.536,56,0,43.464,0,28L0,28z" />
|
||||
<GeometryDrawing Brush="#FFCDCDCD" Geometry="F0 M56,56z M0,0z M1,28C1,42.9117 13.0883,55 28,55 42.9117,55 55,42.9117 55,28 55,13.0883 42.9117,1 28,1 13.0883,1 1,13.0883 1,28z M28,0C12.536,0 0,12.536 0,28 0,43.464 12.536,56 28,56 43.464,56 56,43.464 56,28 56,12.536 43.464,0 28,0z" />
|
||||
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M56,56z M0,0z M21,19.5C21,18.1193,22.1193,17,23.5,17L23.5,17C24.8807,17,26,18.1193,26,19.5L26,36.5C26,37.8807,24.8807,39,23.5,39L23.5,39C22.1193,39,21,37.8807,21,36.5L21,19.5z" />
|
||||
<GeometryDrawing Geometry="F0 M56,56z M0,0z M20.5,19.5C20.5,17.8431 21.8431,16.5 23.5,16.5 25.1569,16.5 26.5,17.8431 26.5,19.5L26.5,36.5C26.5,38.1569 25.1569,39.5 23.5,39.5 21.8431,39.5 20.5,38.1569 20.5,36.5L20.5,19.5z M23.5,17.5C22.3954,17.5,21.5,18.3954,21.5,19.5L21.5,36.5C21.5,37.6046 22.3954,38.5 23.5,38.5 24.6046,38.5 25.5,37.6046 25.5,36.5L25.5,19.5C25.5,18.3954,24.6046,17.5,23.5,17.5z">
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
<GeometryDrawing Brush="#FFD1CFCD" Geometry="F1 M56,56z M0,0z M30,19.5C30,18.1193,31.1193,17,32.5,17L32.5,17C33.8807,17,35,18.1193,35,19.5L35,36.5C35,37.8807,33.8807,39,32.5,39L32.5,39C31.1193,39,30,37.8807,30,36.5L30,19.5z" />
|
||||
<GeometryDrawing Geometry="F0 M56,56z M0,0z M29.5,19.5C29.5,17.8431 30.8431,16.5 32.5,16.5 34.1569,16.5 35.5,17.8431 35.5,19.5L35.5,36.5C35.5,38.1569 34.1569,39.5 32.5,39.5 30.8431,39.5 29.5,38.1569 29.5,36.5L29.5,19.5z M32.5,17.5C31.3954,17.5,30.5,18.3954,30.5,19.5L30.5,36.5C30.5,37.6046 31.3954,38.5 32.5,38.5 33.6046,38.5 34.5,37.6046 34.5,36.5L34.5,19.5C34.5,18.3954,33.6046,17.5,32.5,17.5z">
|
||||
<GeometryDrawing.Brush>
|
||||
<SolidColorBrush Color="#FF6F6F6F" Opacity="0.25" />
|
||||
</GeometryDrawing.Brush>
|
||||
</GeometryDrawing>
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,358 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
#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)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化悬浮窗拦截管理器失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <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();
|
||||
}));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理窗口拦截事件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口被恢复事件处理
|
||||
/// </summary>
|
||||
private void OnFloatingWindowRestored(object sender, FloatingWindowInterceptor.WindowRestoredEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 在UI线程中更新状态
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
UpdateFloatingWindowInterceptorUI();
|
||||
}));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理窗口恢复事件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 悬浮窗拦截事件处理
|
||||
|
||||
/// <summary>
|
||||
/// 主开关切换事件
|
||||
/// </summary>
|
||||
private void ToggleSwitchFloatingWindowInterceptorEnabled_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
try
|
||||
{
|
||||
Settings.Automation.FloatingWindowInterceptor.IsEnabled = ToggleSwitchFloatingWindowInterceptorEnabled.IsOn;
|
||||
|
||||
if (_floatingWindowInterceptorManager != null)
|
||||
{
|
||||
if (Settings.Automation.FloatingWindowInterceptor.IsEnabled)
|
||||
{
|
||||
_floatingWindowInterceptorManager.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_floatingWindowInterceptorManager.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateFloatingWindowInterceptorUI();
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换悬浮窗拦截主开关失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃白板3拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoWhiteboard3Floating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard3Floating, ToggleSwitchSeewoWhiteboard3Floating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃白板5拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoWhiteboard5Floating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5Floating, ToggleSwitchSeewoWhiteboard5Floating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃白板5C拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoWhiteboard5CFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoWhiteboard5CFloating, ToggleSwitchSeewoWhiteboard5CFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃品课侧栏拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoPincoSideBarFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoSideBarFloating, ToggleSwitchSeewoPincoSideBarFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃品课画笔拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoPincoDrawingFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPincoDrawingFloating, ToggleSwitchSeewoPincoDrawingFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃PPT小工具拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoPPTFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoPPTFloating, ToggleSwitchSeewoPPTFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AiClass拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchAiClassFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.AiClassFloating, ToggleSwitchAiClassFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鸿合屏幕书写拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchHiteAnnotationFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.HiteAnnotationFloating, ToggleSwitchHiteAnnotationFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 畅言智慧课堂拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchChangYanFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanFloating, ToggleSwitchChangYanFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 畅言PPT拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchChangYanPptFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.ChangYanPptFloating, ToggleSwitchChangYanPptFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 天喻教育云拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchIntelligentClassFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.IntelligentClassFloating, ToggleSwitchIntelligentClassFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃桌面画笔拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoDesktopAnnotationFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopAnnotationFloating, ToggleSwitchSeewoDesktopAnnotationFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 希沃桌面侧栏拦截开关
|
||||
/// </summary>
|
||||
private void ToggleSwitchSeewoDesktopSideBarFloating_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
SetInterceptRule(FloatingWindowInterceptor.InterceptType.SeewoDesktopSideBarFloating, ToggleSwitchSeewoDesktopSideBarFloating.IsOn);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置拦截规则
|
||||
/// </summary>
|
||||
private void SetInterceptRule(FloatingWindowInterceptor.InterceptType type, bool enabled)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_floatingWindowInterceptorManager != null)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
var childRuleName = childType.ToString();
|
||||
if (Settings.Automation.FloatingWindowInterceptor.InterceptRules.ContainsKey(childRuleName))
|
||||
{
|
||||
Settings.Automation.FloatingWindowInterceptor.InterceptRules[childRuleName] = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 如果是子规则,更新父规则的设置
|
||||
else if (rule.ParentType.HasValue)
|
||||
{
|
||||
var parentRule = _floatingWindowInterceptorManager?.GetInterceptRule(rule.ParentType.Value);
|
||||
if (parentRule != null)
|
||||
{
|
||||
var parentRuleName = rule.ParentType.Value.ToString();
|
||||
if (Settings.Automation.FloatingWindowInterceptor.InterceptRules.ContainsKey(parentRuleName))
|
||||
{
|
||||
Settings.Automation.FloatingWindowInterceptor.InterceptRules[parentRuleName] = parentRule.IsEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新UI显示
|
||||
UpdateFloatingWindowInterceptorUI();
|
||||
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置拦截规则失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,8 @@ namespace Ink_Canvas
|
||||
private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
// 只有在PPT放映模式下才响应鼠标滚轮翻页
|
||||
if (StackPanelPPTControls.Visibility != Visibility.Visible ||
|
||||
currentMode != 0 ||
|
||||
if (StackPanelPPTControls.Visibility != Visibility.Visible ||
|
||||
currentMode != 0 ||
|
||||
BtnPPTSlideShowEnd.Visibility != Visibility.Visible ||
|
||||
PPTManager?.IsInSlideShow != true) return;
|
||||
|
||||
@@ -29,8 +29,8 @@ namespace Ink_Canvas
|
||||
private void Main_Grid_PreviewKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
// 只有在PPT放映模式下才响应键盘翻页快捷键
|
||||
if (StackPanelPPTControls.Visibility != Visibility.Visible ||
|
||||
currentMode != 0 ||
|
||||
if (StackPanelPPTControls.Visibility != Visibility.Visible ||
|
||||
currentMode != 0 ||
|
||||
BtnPPTSlideShowEnd.Visibility != Visibility.Visible ||
|
||||
PPTManager?.IsInSlideShow != true) return;
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
public static string LinedCursorIcon =
|
||||
"F1 M24,24z M0,0z M5.72106,15.9716L3.71327,3.00395C3.6389,2.6693 3.65747,2.41831 3.76902,2.25099 3.88057,2.08366 4.0479,2 4.271,2 4.4941,2 4.71711,2.07437 4.94021,2.2231 6.72502,3.39438 9.28149,5.10481 12.6094,7.3544 15.677,9.45526 18.1125,11.1285 19.9159,12.3742 20.1204,12.5229 20.2505,12.6995 20.3062,12.904 20.362,13.1085 20.3249,13.2944 20.1947,13.4618 20.0832,13.6105 19.8973,13.6849 19.637,13.6849L13.3902,13.6849 17.6291,19.7365C17.722,19.8666 17.75,20.0153 17.7128,20.1827 17.6942,20.3314 17.6198,20.4522 17.4897,20.5452L15.5654,21.8838C15.4353,21.9768 15.2865,22.0139 15.1192,21.9953 14.9704,21.9582 14.8496,21.8745 14.7566,21.7444L10.2389,15.2745 7.58956,19.9038C7.45942,20.1269 7.30144,20.2756 7.11552,20.35 6.92961,20.4058 6.75292,20.3872 6.5856,20.2942 6.43686,20.2013 6.34392,20.0339 6.30673,19.7922L6.00007,17.8959C5.88852,17.0779,5.79543,16.4364,5.72106,15.9716z";
|
||||
|
||||
public static string SolidCursorIcon =
|
||||
"F1 M24,24z M0,0z M5.72106,15.9716L3.71327,3.00395C3.6389,2.6693 3.65747,2.41831 3.76902,2.25099 3.88057,2.08366 4.0479,2 4.271,2 4.4941,2 4.71711,2.07437 4.94021,2.2231 6.72502,3.39438 9.28149,5.10481 12.6094,7.3544 15.677,9.45526 18.1125,11.1285 19.9159,12.3742 20.1204,12.5229 20.2505,12.6995 20.3062,12.904 20.362,13.1085 20.3249,13.2944 20.1947,13.4618 20.0832,13.6105 19.8973,13.6849 19.637,13.6849L13.3902,13.6849 17.6291,19.7365C17.722,19.8666 17.75,20.0153 17.7128,20.1827 17.6942,20.3314 17.6198,20.4522 17.4897,20.5452L15.5654,21.8838C15.4353,21.9768 15.2865,22.0139 15.1192,21.9953 14.9704,21.9582 14.8496,21.8745 14.7566,21.7444L10.2389,15.2745 7.58956,19.9038C7.45942,20.1269 7.30144,20.2756 7.11552,20.35 6.92961,20.4058 6.75292,20.3872 6.5856,20.2942 6.43686,20.2013 6.34392,20.0339 6.30673,19.7922L6.00007,17.8959C5.88852,17.0779,5.79543,16.4364,5.72106,15.9716z";
|
||||
|
||||
public static string LinedPenIcon =
|
||||
"F1 M24,24z M0,0z M16.996,2.34419L21.6823,7.00397C21.8941,7.23343 22,7.49819 22,7.79825 22,8.09831 21.8941,8.35425 21.6823,8.56606L10.8271,19.4212 4.57877,13.1994 15.4339,2.34419C15.6457,2.11473 15.9017,2 16.2018,2 16.5195,2 16.7842,2.11473 16.996,2.34419z M9.63571,20.5862L9.50333,20.6391 2.6725,21.9894C2.47834,22.0247 2.31066,21.9718 2.16946,21.8306 2.02825,21.707 1.97529,21.5481 2.01059,21.354L3.38736,14.5232C3.38736,14.4879,3.40502,14.4349,3.44032,14.3643L9.63571,20.5862z";
|
||||
|
||||
@@ -37,5 +40,36 @@
|
||||
|
||||
public static string EnabledGestureIconBadgeCheck =
|
||||
"M22.74,18.2234C22.74,20.8888 20.5793,23.0494 17.914,23.0494 15.2487,23.0494 13.088,20.8888 13.088,18.2234 13.088,15.5581 15.2487,13.3975 17.914,13.3975 20.5793,13.3975 22.74,15.5581 22.74,18.2234z M21.1673,15.8009C21.4651,16.0889,21.473,16.5637,21.1851,16.8614L17.5425,20.6282C17.4012,20.7743 17.2066,20.8568 17.0034,20.8568 16.8001,20.8568 16.6055,20.7743 16.4642,20.6282L14.6429,18.7448C14.355,18.447 14.3629,17.9722 14.6607,17.6843 14.9585,17.3963 15.4333,17.4043 15.7212,17.7021L17.0034,19.0279 20.1068,15.8187C20.3947,15.5209,20.8695,15.513,21.1673,15.8009z";
|
||||
|
||||
// 老版浮动栏按钮图标
|
||||
public static string LegacyLinedCursorIcon =
|
||||
"F0 M24,24z M0,0z M3.85151,2.7073C3.52422,2.57095 3.147,2.64558 2.89629,2.89629 2.64558,3.147 2.57095,3.52422 2.7073,3.85151L9.7773,20.8215C9.91729,21.1575 10.2507,21.3718 10.6145,21.3595 10.9783,21.3473 11.2965,21.1111 11.4135,20.7664L13.4711,14.7085 18.8963,20.1337C19.238,20.4754 19.792,20.4754 20.1337,20.1337 20.4754,19.792 20.4754,19.238 20.1337,18.8963L14.7085,13.4711 20.7664,11.4135C21.1111,11.2965 21.3473,10.9783 21.3595,10.6145 21.3718,10.2507 21.1575,9.91729 20.8215,9.7773L3.85151,2.7073z M10.5017,18.0097L5.13984,5.13984 18.0097,10.5017 12.8136,12.2665C12.5561,12.3539,12.3539,12.5561,12.2665,12.8136L10.5017,18.0097z";
|
||||
|
||||
public static string LegacySolidCursorIcon =
|
||||
"F0 M24,24z M0,0z M2.89629,2.89629C3.147,2.64558,3.52422,2.57095,3.85151,2.7073L20.8215,9.7773C21.1575,9.91729 21.3718,10.2507 21.3595,10.6145 21.3473,10.9783 21.1111,11.2965 20.7664,11.4135L14.7085,13.4711 20.1337,18.8963C20.4754,19.238 20.4754,19.792 20.1337,20.1337 19.792,20.4754 19.238,20.4754 18.8963,20.1337L13.4711,14.7085 11.4135,20.7664C11.2965,21.1111 10.9783,21.3473 10.6145,21.3595 10.2507,21.3718 9.91729,21.1575 9.7773,20.8215L2.7073,3.85151C2.57095,3.52422,2.64558,3.147,2.89629,2.89629z";
|
||||
|
||||
public static string LegacyLinedPenIcon =
|
||||
"F0 M24,24z M0,0z M18.7033,4.39761C18.4948,4.31644 18.2714,4.27922 18.0473,4.28846 17.8233,4.29771 17.6038,4.3532 17.403,4.4512 17.2022,4.54919 17.0246,4.68744 16.8813,4.8568 16.8665,4.87422 16.8511,4.89102 16.8349,4.90716L15.7108,6.03131 17.9591,8.27962 19.0832,7.15546C19.1021,7.13662 19.1218,7.11869 19.1424,7.10176 19.3143,6.96037 19.4543,6.7853 19.5537,6.58793 19.6531,6.39058 19.7099,6.1751 19.7207,5.95519 19.7314,5.73528 19.6959,5.51545 19.6163,5.30962 19.5367,5.10378 19.4147,4.91625 19.2576,4.75914 19.1004,4.60201 18.9117,4.47877 18.7033,4.39761z M16.7944,9.44428L14.5461,7.19597 5.47079,16.2713 4.62767,19.3627 7.7191,18.5196 16.7944,9.44428z M13.9636,5.44913L4.15148,15.2613C4.05014,15.3626,3.977,15.4886,3.93929,15.6269L2.65942,20.3198C2.58166,20.6049 2.66264,20.9098 2.87161,21.1188 3.08059,21.3277 3.38551,21.4087 3.67063,21.331L8.36347,20.0511C8.50174,20.0134,8.62777,19.9402,8.72911,19.8389L20.2217,8.34636C20.5551,8.06468 20.8283,7.71873 21.0247,7.3289 21.2275,6.92628 21.3437,6.48586 21.3658,6.03572 21.3878,5.58559 21.3151,5.13594 21.1525,4.71552 20.99,4.29512 20.7411,3.91338 20.4222,3.59447 20.1033,3.27558 19.7214,3.0265 19.3009,2.86277 18.8804,2.69905 18.4304,2.62417 17.9794,2.64278 17.5285,2.66139 17.0862,2.77308 16.6807,2.97095 16.2862,3.16344 15.9348,3.43348 15.6478,3.76494L13.9636,5.44913z";
|
||||
|
||||
public static string LegacySolidPenIcon =
|
||||
"F1 M24,24z M0,0z M19.3332,2.85933C18.9193,2.69814 18.4762,2.62442 18.0322,2.64274 17.5882,2.66106 17.1527,2.77103 16.7535,2.96583 16.3643,3.15575 16.0177,3.42232 15.7349,3.74956L14.5672,4.91725 19.0731,9.4231 20.2373,8.25888C20.5666,7.98121 20.8364,7.63993 21.0302,7.25528 21.2298,6.85899 21.3442,6.42551 21.3659,5.98249 21.3876,5.53947 21.3161,5.09692 21.1561,4.68313 20.996,4.26934 20.7511,3.89359 20.4372,3.57966 20.1232,3.26574 19.7472,3.02052 19.3332,2.85933z M18.0085,10.4877L13.5026,5.98183 4.14128,15.3432C4.04864,15.4358,3.98179,15.551,3.94732,15.6774L2.65684,20.4091C2.58577,20.6698 2.65979,20.9485 2.8508,21.1395 3.04182,21.3305 3.32054,21.4045 3.58117,21.3335L8.3129,20.043C8.43929,20.0085,8.5545,19.9417,8.64713,19.849L18.0085,10.4877z";
|
||||
|
||||
public static string LegacyLinedEraserStrokeIcon =
|
||||
"F0 M25,24z M0,0z M7.32029,21.36L13.0098,21.36 13.0122,21.36 21.5471,21.36C21.989,21.36 22.3473,21.0017 22.3473,20.5598 22.3473,20.1179 21.989,19.7596 21.5471,19.7596L14.9429,19.7596 21.4352,13.2673C22.7372,12.0786,22.6872,10.1353,21.449,8.89707L16.1515,3.59952C14.9628,2.29751,13.0195,2.34754,11.7813,3.58572L2.68992,12.6771C1.3879,13.8657,1.43793,15.8091,2.67611,17.0473L6.75447,21.1256C6.90453,21.2757,7.10807,21.36,7.32029,21.36z M14.9771,4.68685C14.4571,4.10907,13.5664,4.06392,12.9129,4.71737L6.55503,11.0753 13.9595,18.4797 20.3174,12.1218C20.3273,12.1119 20.3375,12.1022 20.3479,12.0929 20.9257,11.5729 20.9708,10.6822 20.3174,10.0287L15.006,4.71737C14.9961,4.70745,14.9864,4.69727,14.9771,4.68685z M12.8278,19.6114L5.42338,12.2069 3.80776,13.8225C3.79784,13.8324 3.78766,13.8421 3.77724,13.8515 3.19947,14.3715 3.15431,15.2622 3.80776,15.9156L7.65174,19.7596 12.6796,19.7596 12.8278,19.6114z";
|
||||
|
||||
public static string LegacySolidEraserStrokeIcon =
|
||||
"F1 M24,24z M0,0z M11.6199,3.61372C12.8916,2.34202,14.8995,2.2837,16.1307,3.62964L21.3433,8.84225C22.615,10.1139,22.6733,12.1218,21.3274,13.353L15.1877,19.4927 5.46434,9.76928 11.6199,3.61372z M7.33167,21.36C7.08919,21.36 6.86831,21.2676 6.70232,21.116 6.69184,21.1064 6.68155,21.0966 6.67147,21.0865L2.65671,17.0718C1.385,15.8001,1.32668,13.7922,2.67262,12.561L4.14394,11.0897 12.5469,19.4927 21.3367,19.4927C21.8523,19.4927 22.2703,19.9107 22.2703,20.4263 22.2703,20.942 21.8523,21.36 21.3367,21.36L7.33167,21.36z";
|
||||
|
||||
public static string LegacyLinedEraserCircleIcon =
|
||||
"F0 M25,24z M0,0z M2.47995,17.1206L6.56736,21.208C6.57733,21.218 6.58749,21.2277 6.59783,21.237 6.66429,21.2971 6.7405,21.3466 6.82381,21.3829L6.83712,21.3885C6.84698,21.3926 6.85693,21.3965 6.86698,21.4003 6.86818,21.4007 6.86937,21.4011 6.87057,21.4016 6.94576,21.4289 7.02412,21.4451 7.10303,21.45L7.12183,21.451 7.13076,21.4513 7.13345,21.4514 7.15549,21.4517 17.0847,21.4517C17.5973,22.3438 18.5597,22.9445 19.6624,22.9445 21.3031,22.9445 22.6332,21.6144 22.6332,19.9737 22.6332,18.3329 21.3031,17.0028 19.6624,17.0028 18.0839,17.0028 16.793,18.2338 16.6972,19.7882L14.8669,19.7882 21.3224,13.3327C22.6404,12.1289,22.5884,10.1619,21.3367,8.91021L16.0278,3.60138C14.8241,2.28336,12.8571,2.33535,11.6053,3.58706L2.49426,12.6981C1.17625,13.9019,1.22824,15.8689,2.47995,17.1206z M14.8072,4.7316C14.2984,4.16633,13.4255,4.11939,12.7816,4.76332L6.43063,11.1143 13.8094,18.4931 20.1604,12.1421C20.1707,12.1318 20.1813,12.1218 20.1921,12.112 20.7574,11.6033 20.8043,10.7304 20.1604,10.0865L14.8373,4.76332C14.8269,4.75301,14.8169,4.74243,14.8072,4.7316z M3.65621,13.8887C3.6459,13.899 3.63532,13.9091 3.62448,13.9188 3.05922,14.4276 3.01228,15.3004 3.65621,15.9444L7.50001,19.7882 12.752,19.7882 5.25437,12.2906 3.65621,13.8887z";
|
||||
|
||||
public static string LegacySolidEraserCircleIcon =
|
||||
"F1 M24,24z M0,0z M15.0919,19.6686L21.4282,13.3322C22.7462,12.1285,22.6942,10.1616,21.4426,8.90993L16.134,3.60133C14.9303,2.28338,12.9633,2.33537,11.7117,3.58702L5.36097,9.93771 15.0919,19.6686z M6.67201,21.2053C6.82267,21.3569,7.03137,21.4508,7.26201,21.4508L17.1907,21.4508C17.7033,22.3429 18.6657,22.9437 19.7683,22.9437 21.409,22.9437 22.7391,21.6136 22.7391,19.9729 22.7391,18.3322 21.409,17.0022 19.7683,17.0022 18.19,17.0022 16.8991,18.2331 16.8033,19.7874L12.8583,19.7874 4.18476,11.1139 2.60098,12.6977C1.28303,13.9014,1.33502,15.8683,2.58667,17.12L6.67201,21.2053z";
|
||||
|
||||
public static string LegacyLinedLassoSelectIcon =
|
||||
"F0 M24,24z M0,0z M14.4715,12.7092L14.4715,18.7882 15.8291,16.7546C15.9688,16.5453,16.2038,16.4196,16.4554,16.4196L19.0106,16.4196 14.4715,12.7092z M14.6618,10.9193C14.4981,10.784 14.2733,10.6788 14.0025,10.6788 13.9951,10.6788 13.9877,10.6789 13.9803,10.6791 13.7083,10.6872 13.4502,10.8008 13.2607,10.9961 13.0712,11.1913 12.9653,11.4526 12.9653,11.7246L12.9653,20.3314C12.9653,20.3403 12.9655,20.3491 12.9658,20.358 12.9734,20.5733 13.0468,20.7811 13.176,20.9534 13.3053,21.1258 13.4842,21.2544 13.6888,21.3219 13.765,21.3471 13.8447,21.36 13.925,21.36L14.0025,21.36C14.1661,21.36 14.3276,21.3218 14.474,21.2486 14.6204,21.1754 14.7477,21.0692 14.8459,20.9382 14.8542,20.9272 14.8622,20.9159 14.8698,20.9045L16.8582,17.9258 20.3145,17.9258C20.5287,17.9281 20.7384,17.8641 20.9149,17.7424 21.0941,17.6187 21.2299,17.4417 21.3029,17.2365 21.3759,17.0313 21.3825,16.8084 21.3217,16.5993 21.262,16.3936 21.14,16.2117 20.9729,16.0782L14.6618,10.9193z M8.14548,20.0044C7.70454,19.6737 7.34665,19.2448 7.10016,18.7519 6.94658,18.4447 6.83887,18.1179 6.7795,17.7818 7.131,17.6605 7.45404,17.4604 7.72196,17.1924 7.74959,17.1648 7.7765,17.1366 7.80267,17.1078 8.5567,17.4118 9.3392,17.6365 10.1444,17.7694 10.5548,17.8372 10.9424,17.5594 11.0101,17.149 11.0779,16.7387 10.8001,16.3511 10.3897,16.2833 9.72172,16.1731 9.06686,15.9883 8.42926,15.7362 8.44084,15.6393 8.44672,15.5413 8.44672,15.4427 8.44672,14.7865 8.18602,14.1571 7.72196,13.693 7.25791,13.229 6.62852,12.9682 5.97224,12.9682 5.65536,12.9682 5.34474,13.029 5.05598,13.1441 4.47073,12.3026 4.15196,11.303 4.14328,10.2756 4.14532,7.03688 7.49758,4.1462 11.9971,4.1462 16.4941,4.1462 19.8451,7.03371 19.8508,10.2703 19.8388,10.7807 19.7549,11.2869 19.6016,11.7739 19.4767,12.1706 19.697,12.5934 20.0938,12.7183 20.4905,12.8432 20.9134,12.6228 21.0383,12.2261 21.2351,11.6008 21.3424,10.9507 21.3568,10.2952L21.357,10.2786C21.357,5.91009 16.9982,2.64 11.9971,2.64 6.9959,2.64 2.63705,5.91009 2.63705,10.2786L2.6371,10.2845C2.6479,11.6579 3.08647,12.993 3.89074,14.1047 3.63615,14.5007 3.49777,14.9646 3.49777,15.4427 3.49777,16.099 3.75847,16.7284 4.22252,17.1924 4.51465,17.4846 4.87229,17.6961 5.26092,17.8128 5.33333,18.3726 5.49917,18.9179 5.75297,19.4255 6.10404,20.1276 6.61375,20.7383 7.24176,21.2093 7.5745,21.4589 8.04654,21.3915 8.2961,21.0587 8.54566,20.726 8.47822,20.2539 8.14548,20.0044z M5.97224,14.4745C5.71544,14.4745 5.46916,14.5765 5.28757,14.7581 5.10598,14.9396 5.00397,15.1859 5.00397,15.4427 5.00397,15.6995 5.10598,15.9458 5.28757,16.1274 5.46916,16.309 5.71544,16.411 5.97224,16.411 6.22904,16.411 6.47533,16.309 6.65692,16.1274 6.8385,15.9458 6.94052,15.6995 6.94052,15.4427 6.94052,15.1859 6.8385,14.9396 6.65692,14.7581 6.47533,14.5765 6.22904,14.4745 5.97224,14.4745z";
|
||||
|
||||
public static string LegacySolidLassoSelectIcon =
|
||||
"F1 M24,24z M0,0z M14.6618,10.9193C14.4981,10.784 14.2733,10.6788 14.0025,10.6788 13.9951,10.6788 13.9877,10.6789 13.9803,10.6791 13.7083,10.6872 13.4502,10.8008 13.2607,10.9961 13.0712,11.1913 12.9653,11.4526 12.9653,11.7246L12.9653,20.3314C12.9653,20.3403 12.9655,20.3491 12.9658,20.358 12.9734,20.5733 13.0468,20.7811 13.176,20.9534 13.3053,21.1258 13.4842,21.2544 13.6888,21.3219 13.765,21.3471 13.8447,21.36 13.925,21.36L14.0025,21.36C14.1661,21.36 14.3276,21.3218 14.474,21.2486 14.6204,21.1754 14.7477,21.0692 14.8459,20.9382 14.8542,20.9272 14.8622,20.9159 14.8698,20.9045L16.8582,17.9258 20.3145,17.9258C20.5287,17.9281 20.7384,17.8641 20.9149,17.7424 21.0941,17.6187 21.2299,17.4417 21.3029,17.2365 21.3759,17.0313 21.3825,16.8084 21.3217,16.5993 21.262,16.3936 21.14,16.2117 20.9729,16.0782L14.6618,10.9193z M8.14548,20.0044C7.70454,19.6737 7.34665,19.2448 7.10016,18.7519 6.94658,18.4447 6.83888,18.1179 6.7795,17.7818 7.131,17.6605 7.45404,17.4604 7.72196,17.1924 7.74959,17.1648 7.77649,17.1366 7.80267,17.1078 8.5567,17.4118 9.3392,17.6365 10.1444,17.7694 10.5548,17.8372 10.9424,17.5594 11.0101,17.149 11.0779,16.7387 10.8001,16.3511 10.3897,16.2833 9.72172,16.1731 9.06686,15.9883 8.42926,15.7362 8.44084,15.6393 8.44672,15.5413 8.44672,15.4427 8.44672,14.7865 8.18602,14.1571 7.72196,13.693 7.25791,13.229 6.62852,12.9682 5.97224,12.9682 5.65536,12.9682 5.34474,13.029 5.05598,13.1441 4.47073,12.3026 4.15196,11.303 4.14328,10.2756 4.14532,7.03688 7.49758,4.1462 11.9971,4.1462 16.4941,4.1462 19.8451,7.03371 19.8508,10.2703 19.8388,10.7807 19.7549,11.2869 19.6016,11.7739 19.4767,12.1706 19.697,12.5934 20.0938,12.7183 20.4905,12.8432 20.9134,12.6228 21.0383,12.2261 21.2351,11.6008 21.3424,10.9507 21.3568,10.2952L21.357,10.2786C21.357,5.91009 16.9982,2.64 11.9971,2.64 6.9959,2.64 2.63705,5.91009 2.63705,10.2786L2.6371,10.2845C2.6479,11.6579 3.08647,12.993 3.89074,14.1047 3.63615,14.5007 3.49777,14.9646 3.49777,15.4427 3.49777,16.099 3.75847,16.7284 4.22252,17.1924 4.51465,17.4846 4.87229,17.6961 5.26092,17.8128 5.33333,18.3726 5.49917,18.9178 5.75297,19.4255 6.10404,20.1276 6.61375,20.7383 7.24176,21.2093 7.5745,21.4589 8.04654,21.3915 8.2961,21.0587 8.54566,20.726 8.47822,20.2539 8.14548,20.0044z";
|
||||
}
|
||||
}
|
||||
@@ -27,11 +27,15 @@ namespace Ink_Canvas
|
||||
{
|
||||
public Rectangle Area;
|
||||
public List<Point> Path;
|
||||
public Bitmap CameraImage;
|
||||
public BitmapSource CameraBitmapSource;
|
||||
|
||||
public ScreenshotResult(Rectangle area, List<Point> path = null)
|
||||
public ScreenshotResult(Rectangle area, List<Point> path = null, Bitmap cameraImage = null, BitmapSource cameraBitmapSource = null)
|
||||
{
|
||||
Area = area;
|
||||
Path = path;
|
||||
CameraImage = cameraImage;
|
||||
CameraBitmapSource = cameraBitmapSource;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,34 +59,48 @@ namespace Ink_Canvas
|
||||
// 恢复窗口显示
|
||||
Visibility = originalVisibility;
|
||||
|
||||
if (screenshotResult.HasValue && screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0)
|
||||
if (screenshotResult.HasValue)
|
||||
{
|
||||
// 截取选定区域
|
||||
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
|
||||
// 检查是否是摄像头截图
|
||||
if (screenshotResult.Value.CameraBitmapSource != null)
|
||||
{
|
||||
if (originalBitmap != null)
|
||||
// 摄像头截图(使用BitmapSource)
|
||||
await InsertBitmapSourceToCanvas(screenshotResult.Value.CameraBitmapSource);
|
||||
}
|
||||
else if (screenshotResult.Value.CameraImage != null)
|
||||
{
|
||||
// 摄像头截图(使用Bitmap)
|
||||
await InsertScreenshotToCanvas(screenshotResult.Value.CameraImage);
|
||||
}
|
||||
else if (screenshotResult.Value.Area.Width > 0 && screenshotResult.Value.Area.Height > 0)
|
||||
{
|
||||
// 屏幕截图
|
||||
using (var originalBitmap = CaptureScreenArea(screenshotResult.Value.Area))
|
||||
{
|
||||
Bitmap finalBitmap = originalBitmap;
|
||||
bool needDisposeFinalBitmap = false;
|
||||
|
||||
try
|
||||
if (originalBitmap != null)
|
||||
{
|
||||
// 如果有路径信息,应用形状遮罩
|
||||
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
||||
Bitmap finalBitmap = originalBitmap;
|
||||
bool needDisposeFinalBitmap = false;
|
||||
|
||||
try
|
||||
{
|
||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
|
||||
// 如果有路径信息,应用形状遮罩
|
||||
if (screenshotResult.Value.Path != null && screenshotResult.Value.Path.Count > 0)
|
||||
{
|
||||
finalBitmap = ApplyShapeMask(originalBitmap, screenshotResult.Value.Path, screenshotResult.Value.Area);
|
||||
needDisposeFinalBitmap = true; // 标记需要释放新创建的位图
|
||||
}
|
||||
|
||||
// 将截图转换为WPF Image并插入到画布
|
||||
await InsertScreenshotToCanvas(finalBitmap);
|
||||
}
|
||||
|
||||
// 将截图转换为WPF Image并插入到画布
|
||||
await InsertScreenshotToCanvas(finalBitmap);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 如果创建了新的位图,需要释放它
|
||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||
finally
|
||||
{
|
||||
finalBitmap.Dispose();
|
||||
// 如果创建了新的位图,需要释放它
|
||||
if (needDisposeFinalBitmap && finalBitmap != originalBitmap)
|
||||
{
|
||||
finalBitmap.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -100,6 +118,46 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 直接全屏截图并插入到画布
|
||||
private async Task CaptureFullScreenAndInsert()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 隐藏主窗口以避免截图包含窗口本身
|
||||
var originalVisibility = Visibility;
|
||||
Visibility = Visibility.Hidden;
|
||||
|
||||
// 等待窗口隐藏
|
||||
await Task.Delay(200);
|
||||
|
||||
// 获取虚拟屏幕边界
|
||||
var virtualScreen = SystemInformation.VirtualScreen;
|
||||
var fullScreenArea = new Rectangle(virtualScreen.X, virtualScreen.Y, virtualScreen.Width, virtualScreen.Height);
|
||||
|
||||
// 截取全屏
|
||||
using (var fullScreenBitmap = CaptureScreenArea(fullScreenArea))
|
||||
{
|
||||
if (fullScreenBitmap != null)
|
||||
{
|
||||
// 将截图转换为WPF Image并插入到画布
|
||||
await InsertScreenshotToCanvas(fullScreenBitmap);
|
||||
}
|
||||
else
|
||||
{
|
||||
ShowNotification("全屏截图失败");
|
||||
}
|
||||
}
|
||||
|
||||
// 恢复窗口显示
|
||||
Visibility = originalVisibility;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowNotification($"全屏截图失败: {ex.Message}");
|
||||
Visibility = Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示截图区域选择器
|
||||
private async Task<ScreenshotResult?> ShowScreenshotSelector()
|
||||
{
|
||||
@@ -112,10 +170,31 @@ namespace Ink_Canvas
|
||||
var selectorWindow = new ScreenshotSelectorWindow();
|
||||
if (selectorWindow.ShowDialog() == true)
|
||||
{
|
||||
result = new ScreenshotResult(
|
||||
selectorWindow.SelectedArea.Value,
|
||||
selectorWindow.SelectedPath
|
||||
);
|
||||
// 检查是否是摄像头截图
|
||||
if (selectorWindow.CameraBitmapSource != null)
|
||||
{
|
||||
result = new ScreenshotResult(
|
||||
Rectangle.Empty, // 摄像头截图不需要区域
|
||||
null, // 摄像头截图不需要路径
|
||||
null, // 不再使用Bitmap
|
||||
selectorWindow.CameraBitmapSource // 摄像头BitmapSource
|
||||
);
|
||||
}
|
||||
else if (selectorWindow.CameraImage != null)
|
||||
{
|
||||
result = new ScreenshotResult(
|
||||
Rectangle.Empty, // 摄像头截图不需要区域
|
||||
null, // 摄像头截图不需要路径
|
||||
selectorWindow.CameraImage // 摄像头图像
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new ScreenshotResult(
|
||||
selectorWindow.SelectedArea.Value,
|
||||
selectorWindow.SelectedPath
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -158,7 +237,6 @@ namespace Ink_Canvas
|
||||
graphics.CopyFromScreen(x, y, 0, 0, new Size(width, height), CopyPixelOperation.SourceCopy);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"成功截取区域: X={x}, Y={y}, Width={width}, Height={height}");
|
||||
return bitmap;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -173,9 +251,22 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
// 验证位图有效性
|
||||
if (bitmap == null || bitmap.Width <= 0 || bitmap.Height <= 0)
|
||||
{
|
||||
ShowNotification("无效的截图");
|
||||
return;
|
||||
}
|
||||
|
||||
// 将Bitmap转换为WPF BitmapSource
|
||||
var bitmapSource = ConvertBitmapToBitmapSource(bitmap);
|
||||
|
||||
if (bitmapSource == null)
|
||||
{
|
||||
ShowNotification("转换截图失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建WPF Image控件
|
||||
var image = new Image
|
||||
{
|
||||
@@ -216,6 +307,11 @@ namespace Ink_Canvas
|
||||
// 提交历史记录
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
|
||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
UpdateCurrentToolMode("select");
|
||||
HideSubPanels("select");
|
||||
|
||||
ShowNotification("截图已插入到画布");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -223,6 +319,69 @@ namespace Ink_Canvas
|
||||
ShowNotification($"插入截图失败: {ex.Message}");
|
||||
LogHelper.WriteLogToFile($"插入截图失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
bitmap?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// 将BitmapSource插入到画布(用于摄像头截图)
|
||||
private async Task InsertBitmapSourceToCanvas(BitmapSource bitmapSource)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建WPF Image控件
|
||||
var image = new Image
|
||||
{
|
||||
Source = bitmapSource,
|
||||
Stretch = Stretch.Uniform
|
||||
};
|
||||
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
|
||||
|
||||
// 生成唯一名称
|
||||
string timestamp = "camera_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
image.Name = timestamp;
|
||||
|
||||
// 初始化TransformGroup
|
||||
InitializeScreenshotTransform(image);
|
||||
|
||||
// 设置截图属性,避免被InkCanvas选择系统处理
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
InitializeInkCanvasSelectionSettings();
|
||||
|
||||
// 等待图片加载完成后再进行居中处理
|
||||
image.Loaded += (sender, e) =>
|
||||
{
|
||||
// 确保在UI线程中执行
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
CenterAndScaleScreenshot(image);
|
||||
// 绑定事件处理器
|
||||
BindScreenshotEvents(image);
|
||||
}), DispatcherPriority.Loaded);
|
||||
};
|
||||
|
||||
// 添加到画布
|
||||
inkCanvas.Children.Add(image);
|
||||
|
||||
// 提交历史记录
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
|
||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
UpdateCurrentToolMode("select");
|
||||
HideSubPanels("select");
|
||||
|
||||
ShowNotification("摄像头截图已插入到画布");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowNotification($"插入摄像头截图失败: {ex.Message}");
|
||||
LogHelper.WriteLogToFile($"插入摄像头截图失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化截图的TransformGroup
|
||||
@@ -245,6 +404,8 @@ namespace Ink_Canvas
|
||||
image.MouseWheel += Element_MouseWheel;
|
||||
|
||||
// 触摸事件
|
||||
image.TouchDown += Element_TouchDown;
|
||||
image.TouchUp += Element_TouchUp;
|
||||
image.IsManipulationEnabled = true;
|
||||
image.ManipulationDelta += Element_ManipulationDelta;
|
||||
image.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
@@ -332,7 +493,6 @@ namespace Ink_Canvas
|
||||
InitializeScreenshotTransform(image);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"截图居中完成: 位置({centerX}, {centerY}), 尺寸({newWidth}x{newHeight})");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -433,24 +593,169 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var memory = new MemoryStream())
|
||||
// 验证位图有效性
|
||||
if (bitmap == null)
|
||||
return null;
|
||||
|
||||
// 验证位图尺寸
|
||||
if (bitmap.Width <= 0 || bitmap.Height <= 0)
|
||||
return null;
|
||||
|
||||
// 使用更安全的方法转换位图
|
||||
var bitmapData = bitmap.LockBits(
|
||||
new Rectangle(0, 0, bitmap.Width, bitmap.Height),
|
||||
ImageLockMode.ReadOnly,
|
||||
bitmap.PixelFormat);
|
||||
|
||||
try
|
||||
{
|
||||
bitmap.Save(memory, ImageFormat.Png);
|
||||
memory.Position = 0;
|
||||
// 根据像素格式选择合适的WPF像素格式
|
||||
System.Windows.Media.PixelFormat wpfPixelFormat;
|
||||
switch (bitmap.PixelFormat)
|
||||
{
|
||||
case PixelFormat.Format24bppRgb:
|
||||
wpfPixelFormat = PixelFormats.Bgr24;
|
||||
break;
|
||||
case PixelFormat.Format32bppArgb:
|
||||
wpfPixelFormat = PixelFormats.Bgra32;
|
||||
break;
|
||||
case PixelFormat.Format32bppRgb:
|
||||
wpfPixelFormat = PixelFormats.Bgr32;
|
||||
break;
|
||||
default:
|
||||
wpfPixelFormat = PixelFormats.Bgr24;
|
||||
break;
|
||||
}
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.StreamSource = memory;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
var bitmapSource = BitmapSource.Create(
|
||||
bitmapData.Width,
|
||||
bitmapData.Height,
|
||||
bitmap.HorizontalResolution,
|
||||
bitmap.VerticalResolution,
|
||||
wpfPixelFormat,
|
||||
null,
|
||||
bitmapData.Scan0,
|
||||
bitmapData.Stride * bitmapData.Height,
|
||||
bitmapData.Stride);
|
||||
|
||||
return bitmapImage;
|
||||
bitmapSource.Freeze();
|
||||
return bitmapSource;
|
||||
}
|
||||
finally
|
||||
{
|
||||
bitmap.UnlockBits(bitmapData);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"转换位图失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
|
||||
// 尝试使用备用方法:内存流转换
|
||||
try
|
||||
{
|
||||
return ConvertBitmapToBitmapSourceFallback(bitmap);
|
||||
}
|
||||
catch (Exception fallbackEx)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"备用转换方法也失败: {fallbackEx.Message}", LogHelper.LogType.Error);
|
||||
|
||||
// 最后尝试:使用最简单的转换方法
|
||||
try
|
||||
{
|
||||
return ConvertBitmapToBitmapSourceSimple(bitmap);
|
||||
}
|
||||
catch (Exception simpleEx)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"简单转换方法也失败: {simpleEx.Message}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 备用的位图转换方法(使用内存流)
|
||||
private BitmapSource ConvertBitmapToBitmapSourceFallback(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 验证位图有效性
|
||||
if (bitmap == null || bitmap.Width <= 0 || bitmap.Height <= 0)
|
||||
return null;
|
||||
|
||||
// 创建一个新的位图,确保格式正确
|
||||
using (var convertedBitmap = new Bitmap(bitmap.Width, bitmap.Height, PixelFormat.Format24bppRgb))
|
||||
{
|
||||
using (var graphics = Graphics.FromImage(convertedBitmap))
|
||||
{
|
||||
graphics.DrawImage(bitmap, 0, 0);
|
||||
}
|
||||
|
||||
using (var memory = new MemoryStream())
|
||||
{
|
||||
convertedBitmap.Save(memory, ImageFormat.Png);
|
||||
memory.Position = 0;
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.StreamSource = memory;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"备用转换方法失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// 最简单的位图转换方法
|
||||
private BitmapSource ConvertBitmapToBitmapSourceSimple(Bitmap bitmap)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (bitmap == null)
|
||||
return null;
|
||||
|
||||
// 使用最基础的方法:直接保存为PNG然后加载
|
||||
var tempFile = Path.GetTempFileName() + ".png";
|
||||
|
||||
try
|
||||
{
|
||||
bitmap.Save(tempFile, ImageFormat.Png);
|
||||
|
||||
var bitmapImage = new BitmapImage();
|
||||
bitmapImage.BeginInit();
|
||||
bitmapImage.UriSource = new Uri(tempFile);
|
||||
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
|
||||
bitmapImage.EndInit();
|
||||
bitmapImage.Freeze();
|
||||
|
||||
return bitmapImage;
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 清理临时文件
|
||||
try
|
||||
{
|
||||
if (File.Exists(tempFile))
|
||||
{
|
||||
File.Delete(tempFile);
|
||||
}
|
||||
}
|
||||
catch (Exception deleteEx)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"删除临时文件失败: {deleteEx.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"简单转换方法失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using Microsoft.Office.Core;
|
||||
using Microsoft.Office.Interop.PowerPoint;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security.Cryptography;
|
||||
@@ -11,11 +10,13 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using Application = System.Windows.Application;
|
||||
using File = System.IO.File;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
using MouseButtonEventArgs = System.Windows.Input.MouseButtonEventArgs;
|
||||
using MouseEventArgs = System.Windows.Input.MouseEventArgs;
|
||||
|
||||
@@ -77,7 +78,7 @@ namespace Ink_Canvas
|
||||
|
||||
#region PPT State Management
|
||||
private bool wasFloatingBarFoldedWhenEnterSlideShow;
|
||||
private bool isEnteredSlideShowEndEvent; //防止重复调用本函数导致墨迹保存失效
|
||||
private bool isEnteredSlideShowEndEvent;
|
||||
private bool isPresentationHaveBlackSpace;
|
||||
|
||||
// 长按翻页相关字段
|
||||
@@ -88,16 +89,27 @@ namespace Ink_Canvas
|
||||
|
||||
// PowerPoint应用程序守护相关字段
|
||||
private DispatcherTimer _powerPointProcessMonitorTimer;
|
||||
private const int ProcessMonitorInterval = 5000; // 应用程序监控间隔(毫秒)
|
||||
private const int ProcessMonitorInterval = 1000; // 应用程序监控间隔(毫秒)
|
||||
|
||||
// 上次播放位置相关字段
|
||||
private int _lastPlaybackPage = 0;
|
||||
private bool _shouldNavigateToLastPage = false;
|
||||
|
||||
// 页面切换防抖机制
|
||||
private DateTime _lastSlideSwitchTime = DateTime.MinValue;
|
||||
private int _pendingSlideIndex = -1;
|
||||
private System.Timers.Timer _slideSwitchDebounceTimer;
|
||||
private const int SlideSwitchDebounceMs = 150; // 防抖延迟150毫秒
|
||||
#endregion
|
||||
|
||||
#region PPT Managers
|
||||
private PPTManager _pptManager;
|
||||
private PPTInkManager _pptInkManager;
|
||||
private MultiPPTInkManager _multiPPTInkManager;
|
||||
private PPTInkManager _singlePPTInkManager;
|
||||
private PPTUIManager _pptUIManager;
|
||||
|
||||
/// <summary>
|
||||
/// 获取PPT管理器实例(供UI管理器使用)
|
||||
/// 获取PPT管理器实例
|
||||
/// </summary>
|
||||
public PPTManager PPTManager => _pptManager;
|
||||
#endregion
|
||||
@@ -123,10 +135,19 @@ namespace Ink_Canvas
|
||||
_pptManager.PresentationClose += OnPPTPresentationClose;
|
||||
_pptManager.SlideShowStateChanged += OnPPTSlideShowStateChanged;
|
||||
|
||||
// 初始化墨迹管理器
|
||||
_pptInkManager = new PPTInkManager();
|
||||
_pptInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint;
|
||||
_pptInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation;
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager = new PPTInkManager();
|
||||
_singlePPTInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint;
|
||||
_singlePPTInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager = new MultiPPTInkManager();
|
||||
_multiPPTInkManager.IsAutoSaveEnabled = Settings.PowerPointSettings.IsAutoSaveStrokesInPowerPoint;
|
||||
_multiPPTInkManager.AutoSaveLocation = Settings.Automation.AutoSavedStrokesLocation;
|
||||
_multiPPTInkManager.PPTManager = _pptManager;
|
||||
}
|
||||
|
||||
// 初始化UI管理器
|
||||
_pptUIManager = new PPTUIManager(this);
|
||||
@@ -230,12 +251,12 @@ namespace Ink_Canvas
|
||||
|
||||
// 创建新的PowerPoint应用程序实例
|
||||
pptApplication = new Microsoft.Office.Interop.PowerPoint.Application();
|
||||
|
||||
|
||||
// 设置为不可见,作为后台进程
|
||||
pptApplication.Visible = Microsoft.Office.Core.MsoTriState.msoFalse;
|
||||
|
||||
pptApplication.Visible = MsoTriState.msoFalse;
|
||||
|
||||
// 设置应用程序属性
|
||||
pptApplication.WindowState = Microsoft.Office.Interop.PowerPoint.PpWindowState.ppWindowMinimized;
|
||||
pptApplication.WindowState = PpWindowState.ppWindowMinimized;
|
||||
|
||||
// 直接设置PPTManager的PPTApplication属性,绕过COM注册问题
|
||||
Task.Delay(1000).ContinueWith(_ =>
|
||||
@@ -278,9 +299,9 @@ namespace Ink_Canvas
|
||||
|
||||
// 使用反射调用PPTManager的ConnectToPPT方法
|
||||
var pptManagerType = _pptManager.GetType();
|
||||
var connectMethod = pptManagerType.GetMethod("ConnectToPPT",
|
||||
var connectMethod = pptManagerType.GetMethod("ConnectToPPT",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
|
||||
|
||||
if (connectMethod != null)
|
||||
{
|
||||
connectMethod.Invoke(_pptManager, new object[] { app });
|
||||
@@ -289,9 +310,9 @@ namespace Ink_Canvas
|
||||
else
|
||||
{
|
||||
// 如果无法通过反射调用,尝试直接设置属性
|
||||
var pptApplicationProperty = pptManagerType.GetProperty("PPTApplication",
|
||||
var pptApplicationProperty = pptManagerType.GetProperty("PPTApplication",
|
||||
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
||||
|
||||
|
||||
if (pptApplicationProperty != null && pptApplicationProperty.CanWrite)
|
||||
{
|
||||
pptApplicationProperty.SetValue(_pptManager, app);
|
||||
@@ -318,7 +339,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (pptApplication == null) return false;
|
||||
if (!Marshal.IsComObject(pptApplication)) return false;
|
||||
|
||||
|
||||
// 尝试访问一个简单的属性来验证连接是否有效
|
||||
var _ = pptApplication.Name;
|
||||
return true;
|
||||
@@ -363,12 +384,12 @@ namespace Ink_Canvas
|
||||
|
||||
// 退出PowerPoint应用程序
|
||||
pptApplication.Quit();
|
||||
|
||||
|
||||
// 释放COM对象
|
||||
Marshal.ReleaseComObject(pptApplication);
|
||||
pptApplication = null;
|
||||
}
|
||||
|
||||
|
||||
LogHelper.WriteLogToFile("PowerPoint应用程序已关闭", LogHelper.LogType.Event);
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -409,11 +430,13 @@ namespace Ink_Canvas
|
||||
try
|
||||
{
|
||||
_pptManager?.Dispose();
|
||||
_pptInkManager?.Dispose();
|
||||
_multiPPTInkManager?.Dispose();
|
||||
_singlePPTInkManager?.Dispose();
|
||||
_longPressTimer?.Stop();
|
||||
_longPressTimer = null;
|
||||
_pptManager = null;
|
||||
_pptInkManager = null;
|
||||
_multiPPTInkManager = null;
|
||||
_singlePPTInkManager = null;
|
||||
_pptUIManager = null;
|
||||
|
||||
// 清理PowerPoint进程守护
|
||||
@@ -448,6 +471,8 @@ namespace Ink_Canvas
|
||||
if (!Settings.PowerPointSettings.EnablePPTButtonLongPressPageTurn) return;
|
||||
|
||||
_isLongPressNext = isNext;
|
||||
// 重置定时器间隔为初始延迟时间,确保每次长按检测都从正确的延迟开始
|
||||
_longPressTimer.Interval = TimeSpan.FromMilliseconds(LongPressDelay);
|
||||
_longPressTimer?.Start();
|
||||
}
|
||||
|
||||
@@ -496,8 +521,14 @@ namespace Ink_Canvas
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("PPT连接已断开", LogHelper.LogType.Event);
|
||||
// 清理墨迹管理器
|
||||
_pptInkManager?.ClearAllStrokes();
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.ClearAllStrokes();
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.ClearAllStrokes();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -522,8 +553,14 @@ namespace Ink_Canvas
|
||||
TimeMachineHistories[0] = null;
|
||||
}
|
||||
|
||||
// 初始化墨迹管理器
|
||||
_pptInkManager?.InitializePresentation(pres);
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.InitializePresentation(pres);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.InitializePresentation(pres);
|
||||
}
|
||||
|
||||
// 处理跳转到首页或上次播放页的逻辑
|
||||
HandlePresentationOpenNavigation(pres);
|
||||
@@ -557,18 +594,29 @@ namespace Ink_Canvas
|
||||
{
|
||||
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 保存所有墨迹
|
||||
_pptInkManager?.SaveAllStrokesToFile(pres);
|
||||
|
||||
// 清理墨迹管理器
|
||||
_pptInkManager?.ClearAllStrokes();
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
_multiPPTInkManager?.RemovePresentation(pres);
|
||||
}
|
||||
|
||||
_pptUIManager?.UpdateConnectionStatus(false);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
catch (COMException comEx)
|
||||
{
|
||||
// COM对象已失效,这是正常情况,完全静默处理
|
||||
var hr = (uint)comEx.HResult;
|
||||
if (hr == 0x8001010E || hr == 0x80004005 || hr == 0x800706BA || hr == 0x800706BE || hr == 0x80048010)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理演示文稿关闭事件失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,7 +631,6 @@ namespace Ink_Canvas
|
||||
|
||||
if (!isInSlideShow)
|
||||
{
|
||||
LogHelper.WriteLogToFile("PPT放映状态变化:退出放映模式", LogHelper.LogType.Trace);
|
||||
}
|
||||
|
||||
// 检查主窗口可见性(用于仅PPT模式)
|
||||
@@ -600,23 +647,48 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
// 记录进入放映时浮动栏收纳状态
|
||||
// 始终记录进入放映时浮动栏收纳状态,用于退出时恢复
|
||||
wasFloatingBarFoldedWhenEnterSlideShow = isFloatingBarFolded;
|
||||
|
||||
if (Settings.Automation.IsAutoFoldInPPTSlideShow && !isFloatingBarFolded)
|
||||
FoldFloatingBar_MouseUp(new object(), null);
|
||||
else if (isFloatingBarFolded)
|
||||
await UnFoldFloatingBar(new object());
|
||||
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
if (!isFloatingBarFolded)
|
||||
FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
|
||||
isStopInkReplay = true;
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 处理跳转到首页
|
||||
var activePresentation = _pptManager?.GetCurrentActivePresentation();
|
||||
if (activePresentation != null)
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SwitchToPresentation(activePresentation);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理跳转到首页或上次播放位置
|
||||
if (Settings.PowerPointSettings.IsAlwaysGoToFirstPageOnReenter)
|
||||
{
|
||||
_pptManager?.TryNavigateToSlide(1);
|
||||
}
|
||||
else if (_shouldNavigateToLastPage && _lastPlaybackPage > 0)
|
||||
{
|
||||
_pptManager?.TryNavigateToSlide(_lastPlaybackPage);
|
||||
_shouldNavigateToLastPage = false; // 重置标志位
|
||||
}
|
||||
|
||||
// 更新UI状态
|
||||
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
@@ -649,7 +721,7 @@ namespace Ink_Canvas
|
||||
// 在PPT模式下根据设置决定是否隐藏手势面板和手势按钮
|
||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||
|
||||
|
||||
// 根据设置决定是否在PPT放映模式下显示手势按钮
|
||||
if (Settings.PowerPointSettings.ShowGestureButtonInSlideShow)
|
||||
{
|
||||
@@ -667,7 +739,43 @@ namespace Ink_Canvas
|
||||
|
||||
if (Settings.PowerPointSettings.IsShowCanvasAtNewSlideShow &&
|
||||
!Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
// 先进入批注模式,这会显示调色盘
|
||||
PenIcon_Click(null, null);
|
||||
// 然后设置颜色
|
||||
BtnColorRed_Click(null, null);
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
{
|
||||
UpdateCurrentToolMode("pen");
|
||||
SetFloatingBarHighlightPosition("pen");
|
||||
if (Settings.Appearance.IsShowQuickColorPalette && QuickColorPalettePanel != null && QuickColorPaletteSingleRowPanel != null)
|
||||
{
|
||||
// 根据显示模式选择显示哪个面板
|
||||
if (Settings.Appearance.QuickColorPaletteDisplayMode == 0)
|
||||
{
|
||||
// 单行显示模式
|
||||
QuickColorPalettePanel.Visibility = Visibility.Collapsed;
|
||||
QuickColorPaletteSingleRowPanel.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 双行显示模式
|
||||
QuickColorPalettePanel.Visibility = Visibility.Visible;
|
||||
QuickColorPaletteSingleRowPanel.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"PPT进入批注模式后同步浮动栏高光状态失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
}
|
||||
|
||||
isEnteredSlideShowEndEvent = false;
|
||||
|
||||
@@ -699,16 +807,24 @@ namespace Ink_Canvas
|
||||
{
|
||||
Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var activePresentation = _pptManager?.GetCurrentActivePresentation();
|
||||
if (activePresentation != null)
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SwitchToPresentation(activePresentation);
|
||||
}
|
||||
}
|
||||
|
||||
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
var totalSlides = _pptManager?.SlidesCount ?? 0;
|
||||
|
||||
// 保存上一页墨迹并加载当前页墨迹
|
||||
SwitchSlideInk(currentSlide);
|
||||
// 使用防抖机制处理页面切换
|
||||
HandleSlideSwitchWithDebounce(currentSlide, totalSlides);
|
||||
|
||||
// 更新UI
|
||||
_pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides);
|
||||
|
||||
LogHelper.WriteLogToFile($"幻灯片切换到第{currentSlide}页", LogHelper.LogType.Trace);
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -721,21 +837,46 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
// 处理浮动栏状态
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow && wasFloatingBarFoldedWhenEnterSlideShow)
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
|
||||
{
|
||||
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null);
|
||||
if (wasFloatingBarFoldedWhenEnterSlideShow)
|
||||
{
|
||||
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded) await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded) await UnFoldFloatingBar(new object());
|
||||
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isEnteredSlideShowEndEvent) return;
|
||||
isEnteredSlideShowEndEvent = true;
|
||||
|
||||
// 保存所有墨迹
|
||||
_pptInkManager?.SaveAllStrokesToFile(pres);
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveAllStrokesToFile(pres);
|
||||
}
|
||||
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
@@ -767,6 +908,9 @@ namespace Ink_Canvas
|
||||
// 注意:这里只清空索引0的备份,不影响白板页面的墨迹(索引1及以上)
|
||||
TimeMachineHistories[0] = null;
|
||||
|
||||
// 重置墨迹管理器的锁定状态,防止下次放映时墨迹显示错误
|
||||
ResetInkManagerLockState();
|
||||
|
||||
// 退出PPT模式时恢复手势面板和手势按钮的显示状态
|
||||
if (Settings.Gesture.IsEnableTwoFingerGesture && ToggleSwitchEnableMultiTouchMode.IsOn)
|
||||
{
|
||||
@@ -791,6 +935,10 @@ namespace Ink_Canvas
|
||||
|
||||
if (GridTransparencyFakeBackground.Background != Brushes.Transparent)
|
||||
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
|
||||
SetCurrentToolMode(InkCanvasEditingMode.None);
|
||||
|
||||
UpdateCurrentToolMode("cursor");
|
||||
SetFloatingBarHighlightPosition("cursor");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -798,11 +946,9 @@ namespace Ink_Canvas
|
||||
}
|
||||
});
|
||||
|
||||
await Task.Delay(150);
|
||||
await Task.Delay(100);
|
||||
await Application.Current.Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 强制重新计算浮动栏位置,确保在退出PPT模式后正确复位
|
||||
// 先调用桌面模式的复位方法,然后调用通用的位置计算方法
|
||||
PureViewboxFloatingBarMarginAnimationInDesktopMode();
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
});
|
||||
@@ -850,9 +996,27 @@ namespace Ink_Canvas
|
||||
|
||||
if (int.TryParse(File.ReadAllText(positionFile), out var page) && page > 0)
|
||||
{
|
||||
_lastPlaybackPage = page;
|
||||
new YesOrNoNotificationWindow($"上次播放到了第 {page} 页, 是否立即跳转", () =>
|
||||
{
|
||||
_pptManager?.TryNavigateToSlide(page);
|
||||
try
|
||||
{
|
||||
if (_pptManager?.PPTApplication != null)
|
||||
{
|
||||
if (_pptManager.PPTApplication.SlideShowWindows.Count >= 1)
|
||||
{
|
||||
pres.SlideShowWindow.View.GotoSlide(page);
|
||||
}
|
||||
else
|
||||
{
|
||||
pres.Windows[1].View.GotoSlide(page);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"跳转到第{page}页失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}).ShowDialog();
|
||||
}
|
||||
}
|
||||
@@ -971,7 +1135,16 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
var strokes = _pptInkManager?.LoadSlideStrokes(slideIndex);
|
||||
StrokeCollection strokes = null;
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
strokes = _singlePPTInkManager?.LoadSlideStrokes(slideIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
strokes = _multiPPTInkManager?.LoadSlideStrokes(slideIndex);
|
||||
}
|
||||
|
||||
if (strokes != null)
|
||||
{
|
||||
inkCanvas.Strokes.Clear();
|
||||
@@ -984,19 +1157,180 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置墨迹管理器的锁定状态,防止墨迹显示错误
|
||||
/// </summary>
|
||||
private void ResetInkManagerLockState()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.ResetLockState();
|
||||
}
|
||||
else
|
||||
{
|
||||
var activePresentation = _pptManager?.GetCurrentActivePresentation();
|
||||
if (activePresentation != null)
|
||||
{
|
||||
_multiPPTInkManager?.SwitchToPresentation(activePresentation);
|
||||
_multiPPTInkManager?.ResetCurrentPresentationLockState();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"重置墨迹管理器锁定状态失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置PPT相关的状态变量,当PPT自动收纳设置变更时调用
|
||||
/// </summary>
|
||||
public void ResetPPTStateVariables()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 重置进入PPT时的浮动栏收纳状态记录
|
||||
wasFloatingBarFoldedWhenEnterSlideShow = false;
|
||||
|
||||
// 重置PPT放映结束事件标志
|
||||
isEnteredSlideShowEndEvent = false;
|
||||
|
||||
// 重置演示文稿黑边状态
|
||||
isPresentationHaveBlackSpace = false;
|
||||
|
||||
// 重置上次播放位置相关字段
|
||||
_lastPlaybackPage = 0;
|
||||
_shouldNavigateToLastPage = false;
|
||||
|
||||
// 重置页面切换防抖机制
|
||||
_lastSlideSwitchTime = DateTime.MinValue;
|
||||
_pendingSlideIndex = -1;
|
||||
|
||||
LogHelper.WriteLogToFile("PPT状态变量已重置", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"重置PPT状态变量失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用防抖机制处理页面切换
|
||||
/// </summary>
|
||||
private void HandleSlideSwitchWithDebounce(int currentSlide, int totalSlides)
|
||||
{
|
||||
try
|
||||
{
|
||||
var now = DateTime.Now;
|
||||
|
||||
// 如果距离上次切换时间太短,使用防抖机制
|
||||
if (now - _lastSlideSwitchTime < TimeSpan.FromMilliseconds(SlideSwitchDebounceMs))
|
||||
{
|
||||
_pendingSlideIndex = currentSlide;
|
||||
|
||||
// 停止之前的定时器
|
||||
_slideSwitchDebounceTimer?.Stop();
|
||||
|
||||
// 创建新的定时器
|
||||
_slideSwitchDebounceTimer = new System.Timers.Timer(SlideSwitchDebounceMs);
|
||||
_slideSwitchDebounceTimer.Elapsed += (sender, e) =>
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (_pendingSlideIndex > 0)
|
||||
{
|
||||
SwitchSlideInk(_pendingSlideIndex);
|
||||
_pptUIManager?.UpdateCurrentSlideNumber(_pendingSlideIndex, totalSlides);
|
||||
_pendingSlideIndex = -1;
|
||||
}
|
||||
});
|
||||
_slideSwitchDebounceTimer?.Stop();
|
||||
};
|
||||
_slideSwitchDebounceTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 直接处理页面切换
|
||||
SwitchSlideInk(currentSlide);
|
||||
_pptUIManager?.UpdateCurrentSlideNumber(currentSlide, totalSlides);
|
||||
}
|
||||
|
||||
_lastSlideSwitchTime = now;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理页面切换防抖失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchSlideInk(int newSlideIndex)
|
||||
{
|
||||
try
|
||||
{
|
||||
var newStrokes = _pptInkManager?.SwitchToSlide(newSlideIndex, inkCanvas.Strokes);
|
||||
// 检查PPT连接状态
|
||||
if (_pptManager?.IsConnected != true || _pptManager?.IsInSlideShow != true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前页面索引
|
||||
var currentSlideIndex = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
|
||||
|
||||
// 验证页面索引的有效性
|
||||
if (newSlideIndex <= 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"无效的新页面索引: {newSlideIndex},跳过页面切换", LogHelper.LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果有当前墨迹且不是第一次切换,先保存到当前页面
|
||||
if (inkCanvas.Strokes.Count > 0 && currentSlideIndex > 0 && currentSlideIndex != newSlideIndex)
|
||||
{
|
||||
bool canWrite = false;
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
canWrite = _singlePPTInkManager?.CanWriteInk(currentSlideIndex) == true;
|
||||
}
|
||||
else
|
||||
{
|
||||
canWrite = _multiPPTInkManager?.CanWriteInk(currentSlideIndex) == true;
|
||||
}
|
||||
|
||||
if (canWrite)
|
||||
{
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlideIndex, inkCanvas.Strokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlideIndex, inkCanvas.Strokes);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (inkCanvas.Strokes.Count > 0 && currentSlideIndex <= 0)
|
||||
{
|
||||
}
|
||||
|
||||
StrokeCollection newStrokes = null;
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
newStrokes = _singlePPTInkManager?.SwitchToSlide(newSlideIndex, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
newStrokes = _multiPPTInkManager?.SwitchToSlide(newSlideIndex, null);
|
||||
}
|
||||
if (newStrokes != null)
|
||||
{
|
||||
inkCanvas.Strokes.Clear();
|
||||
inkCanvas.Strokes.Add(newStrokes);
|
||||
}
|
||||
|
||||
// 设置墨迹锁定
|
||||
_pptInkManager?.LockInkForSlide(newSlideIndex);
|
||||
// 注意:LockInkForSlide已经在SwitchToSlide中调用,这里不需要重复调用
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1065,24 +1399,23 @@ namespace Ink_Canvas
|
||||
private void ToggleSwitchPowerPointEnhancement_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
|
||||
Settings.PowerPointSettings.EnablePowerPointEnhancement = ToggleSwitchPowerPointEnhancement.IsOn;
|
||||
|
||||
// 与WPS支持互斥
|
||||
|
||||
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
|
||||
{
|
||||
Settings.PowerPointSettings.IsSupportWPS = false;
|
||||
ToggleSwitchSupportWPS.IsOn = false;
|
||||
|
||||
|
||||
// 更新PPT管理器的WPS支持设置
|
||||
if (_pptManager != null)
|
||||
{
|
||||
_pptManager.IsSupportWPS = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SaveSettingsToFile();
|
||||
|
||||
|
||||
// 启动或停止PowerPoint进程守护
|
||||
if (Settings.PowerPointSettings.EnablePowerPointEnhancement)
|
||||
{
|
||||
@@ -1099,8 +1432,7 @@ namespace Ink_Canvas
|
||||
if (!isLoaded) return;
|
||||
|
||||
Settings.PowerPointSettings.IsSupportWPS = ToggleSwitchSupportWPS.IsOn;
|
||||
|
||||
// 与PowerPoint联动增强互斥
|
||||
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
Settings.PowerPointSettings.EnablePowerPointEnhancement = false;
|
||||
@@ -1122,24 +1454,6 @@ namespace Ink_Canvas
|
||||
public static bool IsShowingRestoreHiddenSlidesWindow;
|
||||
private static bool IsShowingAutoplaySlidesWindow;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private void BtnPPTSlidesUp_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
@@ -1150,7 +1464,14 @@ namespace Ink_Canvas
|
||||
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
if (currentSlide > 0)
|
||||
{
|
||||
_pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存截图(如果启用)
|
||||
@@ -1165,7 +1486,6 @@ namespace Ink_Canvas
|
||||
if (_pptManager?.TryNavigatePrevious() == true)
|
||||
{
|
||||
// 翻页成功,等待事件处理墨迹切换
|
||||
LogHelper.WriteLogToFile("成功切换到上一页", LogHelper.LogType.Trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1191,7 +1511,14 @@ namespace Ink_Canvas
|
||||
var currentSlide = _pptManager?.GetCurrentSlideNumber() ?? 0;
|
||||
if (currentSlide > 0)
|
||||
{
|
||||
_pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存截图(如果启用)
|
||||
@@ -1206,7 +1533,6 @@ namespace Ink_Canvas
|
||||
if (_pptManager?.TryNavigateNext() == true)
|
||||
{
|
||||
// 翻页成功,等待事件处理墨迹切换
|
||||
LogHelper.WriteLogToFile("成功切换到下一页", LogHelper.LogType.Trace);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1222,7 +1548,7 @@ namespace Ink_Canvas
|
||||
});
|
||||
}
|
||||
|
||||
private async void PPTNavigationBtn_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
private void PPTNavigationBtn_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
lastBorderMouseDownObject = sender;
|
||||
if (!Settings.PowerPointSettings.EnablePPTButtonPageClickable) return;
|
||||
@@ -1244,7 +1570,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private async void PPTNavigationBtn_MouseLeave(object sender, MouseEventArgs e)
|
||||
private void PPTNavigationBtn_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
lastBorderMouseDownObject = null;
|
||||
if (sender == PPTLSPageButton)
|
||||
@@ -1352,7 +1678,14 @@ namespace Ink_Canvas
|
||||
{
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
_pptInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
if (Settings.PowerPointSettings.IsSupportWPS)
|
||||
{
|
||||
_singlePPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
else
|
||||
{
|
||||
_multiPPTInkManager?.SaveCurrentSlideStrokes(currentSlide, inkCanvas.Strokes);
|
||||
}
|
||||
timeMachine.ClearStrokeHistory();
|
||||
});
|
||||
}
|
||||
@@ -1360,7 +1693,7 @@ namespace Ink_Canvas
|
||||
// 结束放映
|
||||
if (_pptManager?.TryEndSlideShow() == true)
|
||||
{
|
||||
LogHelper.WriteLogToFile("成功结束幻灯片放映", LogHelper.LogType.Event);
|
||||
// 如果成功结束放映,等待OnPPTSlideShowEnd事件处理收纳状态恢复
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1373,11 +1706,37 @@ namespace Ink_Canvas
|
||||
_pptUIManager?.UpdateSidebarExitButtons(false);
|
||||
LogHelper.WriteLogToFile("手动更新放映结束UI状态", LogHelper.LogType.Trace);
|
||||
});
|
||||
|
||||
// 手动处理收纳状态恢复,因为OnPPTSlideShowEnd事件可能未触发
|
||||
await HandleManualSlideShowEnd();
|
||||
}
|
||||
|
||||
HideSubPanels("cursor");
|
||||
SetCurrentToolMode(InkCanvasEditingMode.None);
|
||||
|
||||
await Task.Delay(150);
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
|
||||
{
|
||||
if (wasFloatingBarFoldedWhenEnterSlideShow)
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -1389,6 +1748,77 @@ namespace Ink_Canvas
|
||||
_pptUIManager?.UpdateSlideShowStatus(false);
|
||||
_pptUIManager?.UpdateSidebarExitButtons(false);
|
||||
});
|
||||
|
||||
// 异常情况下也手动处理收纳状态恢复
|
||||
await HandleManualSlideShowEnd();
|
||||
|
||||
// 异常情况下也要根据设置决定浮动栏边距
|
||||
await Task.Delay(150);
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
|
||||
{
|
||||
if (wasFloatingBarFoldedWhenEnterSlideShow)
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手动处理PPT放映结束时的收纳状态恢复
|
||||
/// </summary>
|
||||
private async Task HandleManualSlideShowEnd()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Settings.Automation.IsAutoFoldAfterPPTSlideShow)
|
||||
{
|
||||
if (wasFloatingBarFoldedWhenEnterSlideShow)
|
||||
{
|
||||
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(new object(), null);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded) await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果两个功能都关闭,确保浮动栏展开
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
await UnFoldFloatingBar(new object());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"手动处理PPT放映结束收纳状态恢复失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1547,28 +1977,5 @@ namespace Ink_Canvas
|
||||
{
|
||||
BtnPPTSlideShowEnd_Click(BtnPPTSlideShowEnd, null);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,9 +68,20 @@ namespace Ink_Canvas
|
||||
|
||||
public static void ScrollViewToVerticalTop(FrameworkElement element, ScrollViewer scrollViewer)
|
||||
{
|
||||
if (element == null || scrollViewer == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var scrollViewerOffset = scrollViewer.VerticalOffset;
|
||||
var point = new Point(0, scrollViewerOffset);
|
||||
var tarPos = element.TransformToVisual(scrollViewer).Transform(point);
|
||||
var transform = element.TransformToVisual(scrollViewer);
|
||||
if (transform == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var tarPos = transform.Transform(point);
|
||||
scrollViewer.ScrollToVerticalOffset(tarPos.Y);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Ink_Canvas
|
||||
|
||||
for (int i = 1; i <= totalSlides; i++)
|
||||
{
|
||||
var slideStrokes = _pptInkManager?.LoadSlideStrokes(i);
|
||||
var slideStrokes = _multiPPTInkManager?.LoadSlideStrokes(i);
|
||||
if (slideStrokes != null && slideStrokes.Count > 0)
|
||||
{
|
||||
allPageStrokes.Add(slideStrokes);
|
||||
@@ -528,7 +528,7 @@ namespace Ink_Canvas
|
||||
timeMachine.ClearStrokeHistory();
|
||||
|
||||
// 重置PPT墨迹存储
|
||||
_pptInkManager?.ClearAllStrokes();
|
||||
_multiPPTInkManager?.ClearAllStrokes();
|
||||
|
||||
// 读取所有页面的墨迹文件
|
||||
var files = Directory.GetFiles(tempDir, "page_*.icstk");
|
||||
@@ -542,7 +542,7 @@ namespace Ink_Canvas
|
||||
var strokes = new StrokeCollection(fs);
|
||||
if (strokes.Count > 0)
|
||||
{
|
||||
_pptInkManager?.SaveCurrentSlideStrokes(pageNumber, strokes);
|
||||
_multiPPTInkManager?.ForceSaveSlideStrokes(pageNumber, strokes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -552,7 +552,7 @@ namespace Ink_Canvas
|
||||
if (_pptManager?.IsInSlideShow == true)
|
||||
{
|
||||
int currentSlide = _pptManager.GetCurrentSlideNumber();
|
||||
var currentStrokes = _pptInkManager?.LoadSlideStrokes(currentSlide);
|
||||
var currentStrokes = _multiPPTInkManager?.LoadSlideStrokes(currentSlide);
|
||||
if (currentStrokes != null && currentStrokes.Count > 0)
|
||||
{
|
||||
inkCanvas.Strokes.Add(currentStrokes);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -7,6 +8,7 @@ using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Shapes;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Ink_Canvas
|
||||
@@ -32,23 +34,24 @@ namespace Ink_Canvas
|
||||
lastBorderMouseDownObject = sender;
|
||||
}
|
||||
|
||||
private bool isStrokeSelectionCloneOn;
|
||||
|
||||
private void BorderStrokeSelectionClone_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (lastBorderMouseDownObject != sender) return;
|
||||
|
||||
if (isStrokeSelectionCloneOn)
|
||||
try
|
||||
{
|
||||
BorderStrokeSelectionClone.Background = Brushes.Transparent;
|
||||
|
||||
isStrokeSelectionCloneOn = false;
|
||||
var strokes = inkCanvas.GetSelectedStrokes();
|
||||
if (strokes.Count > 0)
|
||||
{
|
||||
// 直接执行克隆操作,与图片克隆保持一致
|
||||
CloneStrokes(strokes);
|
||||
LogHelper.WriteLogToFile($"墨迹克隆完成: {strokes.Count} 个墨迹");
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
BorderStrokeSelectionClone.Background = new SolidColorBrush(StringToColor("#FF1ED760"));
|
||||
|
||||
isStrokeSelectionCloneOn = true;
|
||||
LogHelper.WriteLogToFile($"墨迹克隆失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +61,7 @@ namespace Ink_Canvas
|
||||
|
||||
var strokes = inkCanvas.GetSelectedStrokes();
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
strokes = strokes.Clone();
|
||||
BtnWhiteBoardAdd_Click(null, null);
|
||||
inkCanvas.Strokes.Add(strokes);
|
||||
CloneStrokesToNewBoard(strokes);
|
||||
}
|
||||
|
||||
private void BorderStrokeSelectionDelete_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
@@ -254,18 +255,101 @@ namespace Ink_Canvas
|
||||
#endregion
|
||||
|
||||
private bool isGridInkCanvasSelectionCoverMouseDown;
|
||||
private bool isStrokeDragging = false;
|
||||
private Point strokeDragStartPoint;
|
||||
private StrokeCollection StrokesSelectionClone = new StrokeCollection();
|
||||
|
||||
// 选择框和选择点相关变量
|
||||
private bool isResizing = false;
|
||||
private string currentResizeHandle = "";
|
||||
private Point resizeStartPoint;
|
||||
private Rect originalSelectionBounds;
|
||||
|
||||
private void GridInkCanvasSelectionCover_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
isGridInkCanvasSelectionCoverMouseDown = true;
|
||||
|
||||
// 检查是否有选中的墨迹
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
{
|
||||
// 获取鼠标点击位置
|
||||
var clickPoint = e.GetPosition(inkCanvas);
|
||||
var selectionBounds = inkCanvas.GetSelectionBounds();
|
||||
|
||||
// 检查点击位置是否在选择框边界内
|
||||
if (clickPoint.X >= selectionBounds.Left &&
|
||||
clickPoint.X <= selectionBounds.Right &&
|
||||
clickPoint.Y >= selectionBounds.Top &&
|
||||
clickPoint.Y <= selectionBounds.Bottom)
|
||||
{
|
||||
// 只有在选择框边界内才允许拖动
|
||||
isStrokeDragging = true;
|
||||
strokeDragStartPoint = clickPoint;
|
||||
GridInkCanvasSelectionCover.CaptureMouse();
|
||||
GridInkCanvasSelectionCover.Cursor = Cursors.SizeAll;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 点击在选择框外,取消选择
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GridInkCanvasSelectionCover_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!isGridInkCanvasSelectionCoverMouseDown) return;
|
||||
|
||||
// 如果正在拖动墨迹,执行拖动操作
|
||||
if (isStrokeDragging && GridInkCanvasSelectionCover.IsMouseCaptured)
|
||||
{
|
||||
var currentPoint = e.GetPosition(inkCanvas);
|
||||
var delta = currentPoint - strokeDragStartPoint;
|
||||
|
||||
// 创建变换矩阵
|
||||
var matrix = new Matrix();
|
||||
matrix.Translate(delta.X, delta.Y);
|
||||
|
||||
// 对选中的墨迹应用变换
|
||||
var selectedStrokes = inkCanvas.GetSelectedStrokes();
|
||||
foreach (var stroke in selectedStrokes)
|
||||
{
|
||||
stroke.Transform(matrix, false);
|
||||
}
|
||||
|
||||
// 更新选中栏位置
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
|
||||
// 更新起始点
|
||||
strokeDragStartPoint = currentPoint;
|
||||
}
|
||||
else if (inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
{
|
||||
// 当鼠标在选中区域移动时,更新墨迹选中栏位置
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
}
|
||||
}
|
||||
|
||||
private void GridInkCanvasSelectionCover_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!isGridInkCanvasSelectionCoverMouseDown) return;
|
||||
|
||||
// 结束墨迹拖动
|
||||
if (isStrokeDragging)
|
||||
{
|
||||
isStrokeDragging = false;
|
||||
GridInkCanvasSelectionCover.ReleaseMouseCapture();
|
||||
GridInkCanvasSelectionCover.Cursor = Cursors.Arrow;
|
||||
}
|
||||
|
||||
isGridInkCanvasSelectionCoverMouseDown = false;
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 只有在没有选中墨迹时才隐藏选中栏
|
||||
if (inkCanvas.GetSelectedStrokes().Count == 0)
|
||||
{
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnSelect_Click(object sender, RoutedEventArgs e)
|
||||
@@ -306,7 +390,29 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (isProgramChangeStrokeSelection) return;
|
||||
|
||||
// 检查是否有图片元素被选中
|
||||
// 优先检查墨迹选择状态
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
{
|
||||
// 有墨迹被选中,清除图片选择状态
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
currentSelectedElement = null;
|
||||
// 隐藏图片选择工具栏
|
||||
if (BorderImageSelectionControl != null)
|
||||
{
|
||||
BorderImageSelectionControl.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示墨迹选择栏和选择框
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
|
||||
BorderStrokeSelectionClone.Background = Brushes.Transparent;
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
UpdateSelectionDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有图片元素被选中(通过InkCanvas的选中元素)
|
||||
var selectedElements = inkCanvas.GetSelectedElements();
|
||||
bool hasImageElement = selectedElements.Any(element => element is Image);
|
||||
|
||||
@@ -314,20 +420,21 @@ namespace Ink_Canvas
|
||||
if (hasImageElement)
|
||||
{
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
HideSelectionDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
if (inkCanvas.GetSelectedStrokes().Count == 0)
|
||||
// 检查是否有图片元素被选中(通过currentSelectedElement)
|
||||
if (currentSelectedElement != null && currentSelectedElement is Image)
|
||||
{
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
HideSelectionDisplay();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
|
||||
BorderStrokeSelectionClone.Background = Brushes.Transparent;
|
||||
isStrokeSelectionCloneOn = false;
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
}
|
||||
|
||||
// 没有选中任何内容,隐藏选择框
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
HideSelectionDisplay();
|
||||
}
|
||||
|
||||
|
||||
@@ -336,7 +443,7 @@ namespace Ink_Canvas
|
||||
{
|
||||
var borderLeft = (inkCanvas.GetSelectionBounds().Left + inkCanvas.GetSelectionBounds().Right -
|
||||
BorderStrokeSelectionControlWidth) / 2;
|
||||
var borderTop = inkCanvas.GetSelectionBounds().Bottom + 1;
|
||||
var borderTop = inkCanvas.GetSelectionBounds().Bottom + 10; // 在墨迹下方10像素处显示
|
||||
if (borderLeft < 0) borderLeft = 0;
|
||||
if (borderTop < 0) borderTop = 0;
|
||||
if (Width - borderLeft < BorderStrokeSelectionControlWidth || double.IsNaN(borderLeft))
|
||||
@@ -344,7 +451,14 @@ namespace Ink_Canvas
|
||||
if (Height - borderTop < BorderStrokeSelectionControlHeight || double.IsNaN(borderTop))
|
||||
borderTop = Height - BorderStrokeSelectionControlHeight;
|
||||
|
||||
if (borderTop > 60) borderTop -= 60;
|
||||
// 确保墨迹选中栏始终显示在墨迹下方
|
||||
// 如果选中栏会超出屏幕底部,则显示在墨迹上方
|
||||
if (borderTop + BorderStrokeSelectionControlHeight > Height)
|
||||
{
|
||||
borderTop = inkCanvas.GetSelectionBounds().Top - BorderStrokeSelectionControlHeight - 10;
|
||||
if (borderTop < 0) borderTop = 10; // 如果上方也没有空间,则显示在顶部
|
||||
}
|
||||
|
||||
BorderStrokeSelectionControl.Margin = new Thickness(borderLeft, borderTop, 0, 0);
|
||||
}
|
||||
|
||||
@@ -408,25 +522,19 @@ namespace Ink_Canvas
|
||||
strokes = StrokesSelectionClone;
|
||||
else if (Settings.Gesture.IsEnableTwoFingerRotationOnSelection)
|
||||
m.RotateAt(rotate, center.X, center.Y); // 旋转
|
||||
|
||||
// 应用变换到选中的墨迹
|
||||
foreach (var stroke in strokes)
|
||||
{
|
||||
stroke.Transform(m, false);
|
||||
|
||||
try
|
||||
{
|
||||
stroke.DrawingAttributes.Width *= md.Scale.X;
|
||||
stroke.DrawingAttributes.Height *= md.Scale.Y;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"墨迹ManipulationDelta错误: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,6 +546,78 @@ namespace Ink_Canvas
|
||||
{
|
||||
}
|
||||
|
||||
private void GridInkCanvasSelectionCover_TouchMove(object sender, TouchEventArgs e)
|
||||
{
|
||||
// 处理触摸移动事件 - 用于拖动选中的墨迹
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0 && dec.Count == 1)
|
||||
{
|
||||
var currentTouchPoint = e.GetTouchPoint(inkCanvas).Position;
|
||||
|
||||
// 检查是否有有效的起始触摸点
|
||||
if (lastTouchPointOnGridInkCanvasCover != new Point(0, 0))
|
||||
{
|
||||
var delta = currentTouchPoint - lastTouchPointOnGridInkCanvasCover;
|
||||
|
||||
// 只有当移动距离足够大时才进行拖动(避免微小移动造成的抖动)
|
||||
if (Math.Abs(delta.X) > 1 || Math.Abs(delta.Y) > 1)
|
||||
{
|
||||
// 创建变换矩阵
|
||||
var matrix = new Matrix();
|
||||
matrix.Translate(delta.X, delta.Y);
|
||||
|
||||
// 对选中的墨迹应用变换
|
||||
var selectedStrokes = inkCanvas.GetSelectedStrokes();
|
||||
foreach (var stroke in selectedStrokes)
|
||||
{
|
||||
stroke.Transform(matrix, false);
|
||||
}
|
||||
|
||||
// 更新选中栏位置
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
|
||||
// 更新最后触摸点
|
||||
lastTouchPointOnGridInkCanvasCover = currentTouchPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void GridInkCanvasSelectionCover_PreviewTouchMove(object sender, TouchEventArgs e)
|
||||
{
|
||||
// 预览触摸移动事件 - 用于更精确的触摸处理
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0 && dec.Count == 1)
|
||||
{
|
||||
var currentTouchPoint = e.GetTouchPoint(inkCanvas).Position;
|
||||
|
||||
// 检查是否有有效的起始触摸点
|
||||
if (lastTouchPointOnGridInkCanvasCover != new Point(0, 0))
|
||||
{
|
||||
var delta = currentTouchPoint - lastTouchPointOnGridInkCanvasCover;
|
||||
|
||||
// 只有当移动距离足够大时才进行拖动(避免微小移动造成的抖动)
|
||||
if (Math.Abs(delta.X) > 1 || Math.Abs(delta.Y) > 1)
|
||||
{
|
||||
// 创建变换矩阵
|
||||
var matrix = new Matrix();
|
||||
matrix.Translate(delta.X, delta.Y);
|
||||
|
||||
// 对选中的墨迹应用变换
|
||||
var selectedStrokes = inkCanvas.GetSelectedStrokes();
|
||||
foreach (var stroke in selectedStrokes)
|
||||
{
|
||||
stroke.Transform(matrix, false);
|
||||
}
|
||||
|
||||
// 更新选中栏位置
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
|
||||
// 更新最后触摸点
|
||||
lastTouchPointOnGridInkCanvasCover = currentTouchPoint;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Point lastTouchPointOnGridInkCanvasCover = new Point(0, 0);
|
||||
|
||||
private void GridInkCanvasSelectionCover_PreviewTouchDown(object sender, TouchEventArgs e)
|
||||
@@ -448,25 +628,36 @@ namespace Ink_Canvas
|
||||
{
|
||||
var touchPoint = e.GetTouchPoint(null);
|
||||
centerPoint = touchPoint.Position;
|
||||
lastTouchPointOnGridInkCanvasCover = touchPoint.Position;
|
||||
lastTouchPointOnGridInkCanvasCover = e.GetTouchPoint(inkCanvas).Position;
|
||||
|
||||
if (isStrokeSelectionCloneOn)
|
||||
// 检查是否有选中的墨迹
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
{
|
||||
var strokes = inkCanvas.GetSelectedStrokes();
|
||||
isProgramChangeStrokeSelection = true;
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
StrokesSelectionClone = strokes.Clone();
|
||||
inkCanvas.Select(strokes);
|
||||
isProgramChangeStrokeSelection = false;
|
||||
inkCanvas.Strokes.Add(StrokesSelectionClone);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 新增:启动套索选择模式
|
||||
// 使用集中化的工具模式切换方法
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
// 获取触摸点位置
|
||||
var touchPosition = e.GetTouchPoint(inkCanvas).Position;
|
||||
var selectionBounds = inkCanvas.GetSelectionBounds();
|
||||
|
||||
// 检查触摸位置是否在选择框边界内
|
||||
if (touchPosition.X >= selectionBounds.Left &&
|
||||
touchPosition.X <= selectionBounds.Right &&
|
||||
touchPosition.Y >= selectionBounds.Top &&
|
||||
touchPosition.Y <= selectionBounds.Bottom)
|
||||
{
|
||||
// 只有在选择框边界内才允许拖动
|
||||
// 触摸拖动状态已通过TouchMove事件处理
|
||||
}
|
||||
else
|
||||
{
|
||||
// 触摸在选择框外,取消选择
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Collapsed;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,13 +665,32 @@ namespace Ink_Canvas
|
||||
{
|
||||
dec.Remove(e.TouchDevice.Id);
|
||||
if (dec.Count >= 1) return;
|
||||
|
||||
// 重置触摸状态
|
||||
lastTouchPointOnGridInkCanvasCover = new Point(0, 0);
|
||||
isProgramChangeStrokeSelection = false;
|
||||
if (lastTouchPointOnGridInkCanvasCover == e.GetTouchPoint(null).Position)
|
||||
|
||||
// 检查是否有点击(没有移动)
|
||||
var currentTouchPoint = e.GetTouchPoint(null).Position;
|
||||
if (Math.Abs(currentTouchPoint.X - centerPoint.X) < 5 && Math.Abs(currentTouchPoint.Y - centerPoint.Y) < 5)
|
||||
{
|
||||
if (!(lastTouchPointOnGridInkCanvasCover.X < inkCanvas.GetSelectionBounds().Left) &&
|
||||
!(lastTouchPointOnGridInkCanvasCover.Y < inkCanvas.GetSelectionBounds().Top) &&
|
||||
!(lastTouchPointOnGridInkCanvasCover.X > inkCanvas.GetSelectionBounds().Right) &&
|
||||
!(lastTouchPointOnGridInkCanvasCover.Y > inkCanvas.GetSelectionBounds().Bottom)) return;
|
||||
// 点击在选择框内,保持选择状态
|
||||
if (inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
{
|
||||
var selectionBounds = inkCanvas.GetSelectionBounds();
|
||||
if (currentTouchPoint.X >= selectionBounds.Left &&
|
||||
currentTouchPoint.X <= selectionBounds.Right &&
|
||||
currentTouchPoint.Y >= selectionBounds.Top &&
|
||||
currentTouchPoint.Y <= selectionBounds.Bottom)
|
||||
{
|
||||
// 点击在选择框内,保持选择
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
|
||||
StrokesSelectionClone = new StrokeCollection();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 点击在选择框外,取消选择
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
StrokesSelectionClone = new StrokeCollection();
|
||||
}
|
||||
@@ -494,6 +704,7 @@ namespace Ink_Canvas
|
||||
GridInkCanvasSelectionCover.Visibility = Visibility.Visible;
|
||||
StrokesSelectionClone = new StrokeCollection();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void LassoSelect_Click(object sender, RoutedEventArgs e)
|
||||
@@ -558,7 +769,177 @@ namespace Ink_Canvas
|
||||
|
||||
return new Rect(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Selection Display and Resize Handles
|
||||
|
||||
private void UpdateSelectionDisplay()
|
||||
{
|
||||
if (inkCanvas.GetSelectedStrokes().Count == 0)
|
||||
{
|
||||
HideSelectionDisplay();
|
||||
return;
|
||||
}
|
||||
|
||||
var selectionBounds = inkCanvas.GetSelectionBounds();
|
||||
|
||||
// 更新选择框
|
||||
SelectionRectangle.Visibility = Visibility.Visible;
|
||||
SelectionRectangle.Margin = new Thickness(selectionBounds.Left, selectionBounds.Top, 0, 0);
|
||||
SelectionRectangle.Width = selectionBounds.Width;
|
||||
SelectionRectangle.Height = selectionBounds.Height;
|
||||
|
||||
// 更新选择点位置
|
||||
UpdateSelectionHandles(selectionBounds);
|
||||
SelectionHandlesCanvas.Visibility = Visibility.Visible;
|
||||
}
|
||||
|
||||
private void HideSelectionDisplay()
|
||||
{
|
||||
SelectionRectangle.Visibility = Visibility.Collapsed;
|
||||
SelectionHandlesCanvas.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
private void UpdateSelectionHandles(Rect bounds)
|
||||
{
|
||||
// 四个角选择点
|
||||
TopLeftHandle.Margin = new Thickness(bounds.Left - 4, bounds.Top - 4, 0, 0);
|
||||
TopRightHandle.Margin = new Thickness(bounds.Right - 4, bounds.Top - 4, 0, 0);
|
||||
BottomLeftHandle.Margin = new Thickness(bounds.Left - 4, bounds.Bottom - 4, 0, 0);
|
||||
BottomRightHandle.Margin = new Thickness(bounds.Right - 4, bounds.Bottom - 4, 0, 0);
|
||||
|
||||
// 四个边选择点
|
||||
TopHandle.Margin = new Thickness(bounds.Left + bounds.Width / 2 - 4, bounds.Top - 4, 0, 0);
|
||||
BottomHandle.Margin = new Thickness(bounds.Left + bounds.Width / 2 - 4, bounds.Bottom - 4, 0, 0);
|
||||
LeftHandle.Margin = new Thickness(bounds.Left - 4, bounds.Top + bounds.Height / 2 - 4, 0, 0);
|
||||
RightHandle.Margin = new Thickness(bounds.Right - 4, bounds.Top + bounds.Height / 2 - 4, 0, 0);
|
||||
}
|
||||
|
||||
private void SelectionHandle_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is Rectangle handle)
|
||||
{
|
||||
isResizing = true;
|
||||
currentResizeHandle = handle.Name;
|
||||
resizeStartPoint = e.GetPosition(inkCanvas);
|
||||
originalSelectionBounds = inkCanvas.GetSelectionBounds();
|
||||
handle.CaptureMouse();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectionHandle_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!isResizing || !(sender is Rectangle handle)) return;
|
||||
|
||||
var currentPoint = e.GetPosition(inkCanvas);
|
||||
var delta = new Point(currentPoint.X - resizeStartPoint.X, currentPoint.Y - resizeStartPoint.Y);
|
||||
|
||||
var newBounds = CalculateNewBounds(originalSelectionBounds, delta, currentResizeHandle);
|
||||
|
||||
// 应用新的边界到选中的墨迹
|
||||
ApplyBoundsToStrokes(newBounds);
|
||||
|
||||
// 更新选择框显示
|
||||
UpdateSelectionDisplay();
|
||||
}
|
||||
|
||||
private void SelectionHandle_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (sender is Rectangle handle)
|
||||
{
|
||||
isResizing = false;
|
||||
currentResizeHandle = "";
|
||||
handle.ReleaseMouseCapture();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Rect CalculateNewBounds(Rect originalBounds, Point delta, string handleName)
|
||||
{
|
||||
var newBounds = originalBounds;
|
||||
double newWidth = originalBounds.Width;
|
||||
double newHeight = originalBounds.Height;
|
||||
double newX = originalBounds.X;
|
||||
double newY = originalBounds.Y;
|
||||
|
||||
switch (handleName)
|
||||
{
|
||||
case "TopLeftHandle":
|
||||
newX = originalBounds.X + delta.X;
|
||||
newY = originalBounds.Y + delta.Y;
|
||||
newWidth = originalBounds.Width - delta.X;
|
||||
newHeight = originalBounds.Height - delta.Y;
|
||||
break;
|
||||
case "TopRightHandle":
|
||||
newY = originalBounds.Y + delta.Y;
|
||||
newWidth = originalBounds.Width + delta.X;
|
||||
newHeight = originalBounds.Height - delta.Y;
|
||||
break;
|
||||
case "BottomLeftHandle":
|
||||
newX = originalBounds.X + delta.X;
|
||||
newWidth = originalBounds.Width - delta.X;
|
||||
newHeight = originalBounds.Height + delta.Y;
|
||||
break;
|
||||
case "BottomRightHandle":
|
||||
newWidth = originalBounds.Width + delta.X;
|
||||
newHeight = originalBounds.Height + delta.Y;
|
||||
break;
|
||||
case "TopHandle":
|
||||
newY = originalBounds.Y + delta.Y;
|
||||
newHeight = originalBounds.Height - delta.Y;
|
||||
break;
|
||||
case "BottomHandle":
|
||||
newHeight = originalBounds.Height + delta.Y;
|
||||
break;
|
||||
case "LeftHandle":
|
||||
newX = originalBounds.X + delta.X;
|
||||
newWidth = originalBounds.Width - delta.X;
|
||||
break;
|
||||
case "RightHandle":
|
||||
newWidth = originalBounds.Width + delta.X;
|
||||
break;
|
||||
}
|
||||
|
||||
// 确保最小尺寸和正值
|
||||
if (newWidth < 10) newWidth = 10;
|
||||
if (newHeight < 10) newHeight = 10;
|
||||
|
||||
// 创建新的Rect,确保所有值都是有效的
|
||||
newBounds = new Rect(newX, newY, newWidth, newHeight);
|
||||
|
||||
return newBounds;
|
||||
}
|
||||
|
||||
private void ApplyBoundsToStrokes(Rect newBounds)
|
||||
{
|
||||
var selectedStrokes = inkCanvas.GetSelectedStrokes();
|
||||
if (selectedStrokes.Count == 0) return;
|
||||
|
||||
var originalBounds = inkCanvas.GetSelectionBounds();
|
||||
|
||||
// 计算缩放比例
|
||||
var scaleX = newBounds.Width / originalBounds.Width;
|
||||
var scaleY = newBounds.Height / originalBounds.Height;
|
||||
|
||||
// 计算平移量
|
||||
var translateX = newBounds.X - originalBounds.X;
|
||||
var translateY = newBounds.Y - originalBounds.Y;
|
||||
|
||||
// 创建变换矩阵
|
||||
var matrix = new Matrix();
|
||||
matrix.Translate(translateX, translateY);
|
||||
matrix.ScaleAt(scaleX, scaleY, originalBounds.X + originalBounds.Width / 2, originalBounds.Y + originalBounds.Height / 2);
|
||||
|
||||
// 应用变换到选中的墨迹
|
||||
foreach (var stroke in selectedStrokes)
|
||||
{
|
||||
stroke.Transform(matrix, false);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -18,7 +18,7 @@ using Application = System.Windows.Application;
|
||||
using CheckBox = System.Windows.Controls.CheckBox;
|
||||
using ComboBox = System.Windows.Controls.ComboBox;
|
||||
using File = System.IO.File;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
using OpenFileDialog = Microsoft.Win32.OpenFileDialog;
|
||||
using OperatingSystem = OSVersionExtension.OperatingSystem;
|
||||
using RadioButton = System.Windows.Controls.RadioButton;
|
||||
@@ -186,6 +186,20 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableSplashScreen_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Appearance.EnableSplashScreen = ToggleSwitchEnableSplashScreen.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ComboBoxSplashScreenStyle_SelectionChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Appearance.SplashScreenStyle = ComboBoxSplashScreenStyle.SelectedIndex;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ViewboxFloatingBarScaleTransformValueSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -197,23 +211,23 @@ namespace Ink_Canvas
|
||||
val > 0.5 && val < 1.25 ? val : val <= 0.5 ? 0.5 : val >= 1.25 ? 1.25 : 1;
|
||||
ViewboxFloatingBarScaleTransform.ScaleY =
|
||||
val > 0.5 && val < 1.25 ? val : val <= 0.5 ? 0.5 : val >= 1.25 ? 1.25 : 1;
|
||||
|
||||
|
||||
// 等待UI更新后再重新计算浮动栏位置,确保居中计算准确
|
||||
Dispatcher.BeginInvoke(new Action(async () =>
|
||||
{
|
||||
// 强制更新布局以确保ActualWidth正确
|
||||
ViewboxFloatingBar.UpdateLayout();
|
||||
|
||||
|
||||
// 等待一小段时间让布局完全更新
|
||||
await Task.Delay(100);
|
||||
|
||||
|
||||
// 再次强制更新布局
|
||||
ViewboxFloatingBar.UpdateLayout();
|
||||
|
||||
|
||||
// 强制重新测量和排列
|
||||
ViewboxFloatingBar.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
|
||||
ViewboxFloatingBar.Arrange(new Rect(ViewboxFloatingBar.DesiredSize));
|
||||
|
||||
|
||||
// auto align - 新增:只在屏幕模式下重新计算浮动栏位置
|
||||
if (currentMode == 0)
|
||||
{
|
||||
@@ -333,52 +347,76 @@ namespace Ink_Canvas
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(0.5);
|
||||
}
|
||||
else if (index == 1)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/icc-noshadow.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(0.5);
|
||||
}
|
||||
else if (index == 2)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/icc-dark.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(0.5);
|
||||
}
|
||||
else if (index == 3)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/icc-sharpdark.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(0.5);
|
||||
}
|
||||
else if (index == 4)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/icc-transparent-light-small.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(0.5);
|
||||
}
|
||||
else if (index == 5)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(
|
||||
new Uri("pack://application:,,,/Resources/Icons-png/icc-transparent-dark-small.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(1.2);
|
||||
}
|
||||
else if (index == 2)
|
||||
else if (index == 6)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/kuandoujiyanhuaji.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(2, 2, 2, 1.5);
|
||||
}
|
||||
else if (index == 3)
|
||||
else if (index == 7)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/kuanshounvhuaji.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(2, 2, 2, 1.5);
|
||||
}
|
||||
else if (index == 4)
|
||||
else if (index == 8)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/kuanciya.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(2, 2, 2, 1.5);
|
||||
}
|
||||
else if (index == 5)
|
||||
else if (index == 9)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/kuanneikuhuaji.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(2, 2, 2, 1.5);
|
||||
}
|
||||
else if (index == 6)
|
||||
else if (index == 10)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/kuandogeyuanliangwo.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(2, 2, 2, 1.5);
|
||||
}
|
||||
else if (index == 7)
|
||||
else if (index == 11)
|
||||
{
|
||||
FloatingbarHeadIconImg.Source =
|
||||
new BitmapImage(new Uri("pack://application:,,,/Resources/Icons-png/tiebahuaji.png"));
|
||||
FloatingbarHeadIconImg.Margin = new Thickness(2, 2, 2, 1);
|
||||
}
|
||||
else if (index >= 8 && index - 8 < Settings.Appearance.CustomFloatingBarImgs.Count)
|
||||
else if (index >= 12 && index - 12 < Settings.Appearance.CustomFloatingBarImgs.Count)
|
||||
{
|
||||
// 使用自定义图标
|
||||
var customIcon = Settings.Appearance.CustomFloatingBarImgs[index - 8];
|
||||
var customIcon = Settings.Appearance.CustomFloatingBarImgs[index - 12];
|
||||
try
|
||||
{
|
||||
FloatingbarHeadIconImg.Source = new BitmapImage(new Uri(customIcon.FilePath));
|
||||
@@ -395,8 +433,8 @@ namespace Ink_Canvas
|
||||
|
||||
public void UpdateCustomIconsInComboBox()
|
||||
{
|
||||
// 保留前8个内置图标选项
|
||||
while (ComboBoxFloatingBarImg.Items.Count > 8)
|
||||
// 保留前12个内置图标选项
|
||||
while (ComboBoxFloatingBarImg.Items.Count > 12)
|
||||
{
|
||||
ComboBoxFloatingBarImg.Items.RemoveAt(ComboBoxFloatingBarImg.Items.Count - 1);
|
||||
}
|
||||
@@ -1000,11 +1038,73 @@ namespace Ink_Canvas
|
||||
PPTBtnPreviewRS.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
PPTBtnPreviewRSTransform.Y = -(Settings.PowerPointSettings.PPTRSButtonPosition * 0.5);
|
||||
PPTBtnPreviewLSTransform.Y = -(Settings.PowerPointSettings.PPTLSButtonPosition * 0.5);
|
||||
// 获取当前屏幕的实际尺寸(考虑DPI缩放)
|
||||
var actualScreenWidth = SystemParameters.PrimaryScreenWidth;
|
||||
var actualScreenHeight = SystemParameters.PrimaryScreenHeight;
|
||||
|
||||
PPTBtnPreviewLBTransform.X = -(Settings.PowerPointSettings.PPTLBButtonPosition * 0.5);
|
||||
PPTBtnPreviewRBTransform.X = -(Settings.PowerPointSettings.PPTRBButtonPosition * 0.5);
|
||||
// 预览区域固定尺寸
|
||||
const double previewWidth = 324.0;
|
||||
const double previewHeight = 182.0;
|
||||
|
||||
// 计算缩放比例(预览区域与实际屏幕的比例)
|
||||
double scaleX = previewWidth / actualScreenWidth;
|
||||
double scaleY = previewHeight / actualScreenHeight;
|
||||
|
||||
// 获取按钮位置设置
|
||||
double rsPosition = Settings.PowerPointSettings.PPTRSButtonPosition;
|
||||
double lsPosition = Settings.PowerPointSettings.PPTLSButtonPosition;
|
||||
double lbPosition = Settings.PowerPointSettings.PPTLBButtonPosition;
|
||||
double rbPosition = Settings.PowerPointSettings.PPTRBButtonPosition;
|
||||
|
||||
bool showSidePageButton = sopt.Length >= 1 && sopt[0] == '2';
|
||||
bool showBottomPageButton = bopt.Length >= 1 && bopt[0] == '2';
|
||||
|
||||
// 页码按钮的实际尺寸
|
||||
const double pageButtonWidth = 50.0;
|
||||
const double pageButtonHeight = 50.0;
|
||||
|
||||
// 计算侧边按钮位置(Y轴偏移)
|
||||
double sideOffsetY = showSidePageButton ? pageButtonHeight * scaleY : 0;
|
||||
PPTBtnPreviewRSTransform.Y = -(rsPosition * scaleY) - sideOffsetY;
|
||||
PPTBtnPreviewLSTransform.Y = -(lsPosition * scaleY) - sideOffsetY;
|
||||
|
||||
// 计算底部按钮位置(X轴偏移)
|
||||
const double bottomMarginOffset = 6.0;
|
||||
double scaledMarginOffset = bottomMarginOffset * scaleX;
|
||||
|
||||
double bottomOffsetX = showBottomPageButton ? pageButtonWidth * scaleX : 0;
|
||||
PPTBtnPreviewLBTransform.X = scaledMarginOffset + (lbPosition * scaleX) + bottomOffsetX;
|
||||
PPTBtnPreviewRBTransform.X = -(scaledMarginOffset + (rbPosition * scaleX) + bottomOffsetX);
|
||||
|
||||
// 计算工具栏尺寸
|
||||
var dpiScaleX = 1.0;
|
||||
var dpiScaleY = 1.0;
|
||||
try
|
||||
{
|
||||
var source = PresentationSource.FromVisual(this);
|
||||
if (source?.CompositionTarget != null)
|
||||
{
|
||||
var transform = source.CompositionTarget.TransformToDevice;
|
||||
dpiScaleX = transform.M11;
|
||||
dpiScaleY = transform.M22;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
dpiScaleX = 1.0;
|
||||
dpiScaleY = 1.0;
|
||||
}
|
||||
|
||||
// 计算工具栏的实际尺寸
|
||||
const double baseToolbarHeight = 24.0;
|
||||
|
||||
double actualToolbarHeight = baseToolbarHeight * dpiScaleY;
|
||||
double scaledToolbarHeight = actualToolbarHeight * scaleY;
|
||||
double scaledToolbarWidth = previewWidth;
|
||||
|
||||
// 设置工具栏尺寸
|
||||
PPTBtnPreviewToolbar.Height = scaledToolbarHeight;
|
||||
PPTBtnPreviewToolbar.Width = scaledToolbarWidth;
|
||||
}
|
||||
|
||||
private void ToggleSwitchShowCursor_Toggled(object sender, RoutedEventArgs e)
|
||||
@@ -1038,6 +1138,7 @@ namespace Ink_Canvas
|
||||
if (!isLoaded) return;
|
||||
|
||||
Settings.Canvas.DisablePressure = ToggleSwitchDisablePressure.IsOn;
|
||||
inkCanvas.DefaultDrawingAttributes.IgnorePressure = Settings.Canvas.DisablePressure;
|
||||
|
||||
// 如果启用了屏蔽压感,则自动关闭压感触屏模式
|
||||
if (Settings.Canvas.DisablePressure && Settings.Canvas.EnablePressureTouchMode)
|
||||
@@ -1406,7 +1507,18 @@ namespace Ink_Canvas
|
||||
private void ToggleSwitchAutoFoldInPPTSlideShow_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 记录设置变更前的状态
|
||||
bool previousState = Settings.Automation.IsAutoFoldInPPTSlideShow;
|
||||
Settings.Automation.IsAutoFoldInPPTSlideShow = ToggleSwitchAutoFoldInPPTSlideShow.IsOn;
|
||||
|
||||
// 如果设置状态发生变化,重置PPT相关状态变量
|
||||
if (previousState != Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
ResetPPTStateVariables();
|
||||
LogHelper.WriteLogToFile($"PPT自动收纳设置已变更: {Settings.Automation.IsAutoFoldInPPTSlideShow}, 已重置相关状态变量", LogHelper.LogType.Trace);
|
||||
}
|
||||
|
||||
if (Settings.Automation.IsAutoFoldInPPTSlideShow)
|
||||
{
|
||||
SettingsPPTInkingAndAutoFoldExplictBorder.Visibility = Visibility.Visible;
|
||||
@@ -1543,6 +1655,13 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchAutoFoldWhenExitWhiteboard_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Automation.IsAutoFoldWhenExitWhiteboard = ToggleSwitchAutoFoldWhenExitWhiteboard.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchSaveScreenshotsInDateFolders_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -1780,6 +1899,18 @@ namespace Ink_Canvas
|
||||
private void ToggleSwitchEnableTwoFingerZoom_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 如果多指书写模式启用,强制禁用双指手势
|
||||
if (ToggleSwitchEnableMultiTouchMode.IsOn)
|
||||
{
|
||||
ToggleSwitchEnableTwoFingerZoom.IsOn = false;
|
||||
BoardToggleSwitchEnableTwoFingerZoom.IsOn = false;
|
||||
Settings.Gesture.IsEnableTwoFingerZoom = false;
|
||||
CheckEnableTwoFingerGestureBtnColorPrompt();
|
||||
SaveSettingsToFile();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender == ToggleSwitchEnableTwoFingerZoom)
|
||||
BoardToggleSwitchEnableTwoFingerZoom.IsOn = ToggleSwitchEnableTwoFingerZoom.IsOn;
|
||||
else
|
||||
@@ -1814,7 +1945,11 @@ namespace Ink_Canvas
|
||||
|
||||
// 先设为None再设回原来的模式,避免可能的事件冲突
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
// 保存非笔画元素(如图片)
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
isInMultiTouchMode = true;
|
||||
|
||||
// 恢复到之前的编辑状态
|
||||
@@ -1855,6 +1990,32 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
Settings.Gesture.IsEnableMultiTouchMode = ToggleSwitchEnableMultiTouchMode.IsOn;
|
||||
|
||||
// 如果启用多指书写模式,强制禁用所有双指手势
|
||||
if (ToggleSwitchEnableMultiTouchMode.IsOn)
|
||||
{
|
||||
// 强制关闭所有双指手势设置
|
||||
Settings.Gesture.IsEnableTwoFingerTranslate = false;
|
||||
Settings.Gesture.IsEnableTwoFingerZoom = false;
|
||||
Settings.Gesture.IsEnableTwoFingerRotation = false;
|
||||
|
||||
// 更新UI开关状态
|
||||
if (ToggleSwitchEnableTwoFingerTranslate != null)
|
||||
ToggleSwitchEnableTwoFingerTranslate.IsOn = false;
|
||||
if (ToggleSwitchEnableTwoFingerZoom != null)
|
||||
ToggleSwitchEnableTwoFingerZoom.IsOn = false;
|
||||
if (ToggleSwitchEnableTwoFingerRotation != null)
|
||||
ToggleSwitchEnableTwoFingerRotation.IsOn = false;
|
||||
|
||||
// 更新设置窗口中的开关状态
|
||||
if (BoardToggleSwitchEnableTwoFingerTranslate != null)
|
||||
BoardToggleSwitchEnableTwoFingerTranslate.IsOn = false;
|
||||
if (BoardToggleSwitchEnableTwoFingerZoom != null)
|
||||
BoardToggleSwitchEnableTwoFingerZoom.IsOn = false;
|
||||
if (BoardToggleSwitchEnableTwoFingerRotation != null)
|
||||
BoardToggleSwitchEnableTwoFingerRotation.IsOn = false;
|
||||
}
|
||||
|
||||
CheckEnableTwoFingerGestureBtnColorPrompt();
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
@@ -1862,6 +2023,18 @@ namespace Ink_Canvas
|
||||
private void ToggleSwitchEnableTwoFingerTranslate_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 如果多指书写模式启用,强制禁用双指手势
|
||||
if (ToggleSwitchEnableMultiTouchMode.IsOn)
|
||||
{
|
||||
ToggleSwitchEnableTwoFingerTranslate.IsOn = false;
|
||||
BoardToggleSwitchEnableTwoFingerTranslate.IsOn = false;
|
||||
Settings.Gesture.IsEnableTwoFingerTranslate = false;
|
||||
CheckEnableTwoFingerGestureBtnColorPrompt();
|
||||
SaveSettingsToFile();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender == ToggleSwitchEnableTwoFingerTranslate)
|
||||
BoardToggleSwitchEnableTwoFingerTranslate.IsOn = ToggleSwitchEnableTwoFingerTranslate.IsOn;
|
||||
else
|
||||
@@ -1875,6 +2048,17 @@ namespace Ink_Canvas
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 如果多指书写模式启用,强制禁用双指手势
|
||||
if (ToggleSwitchEnableMultiTouchMode.IsOn)
|
||||
{
|
||||
ToggleSwitchEnableTwoFingerRotation.IsOn = false;
|
||||
BoardToggleSwitchEnableTwoFingerRotation.IsOn = false;
|
||||
Settings.Gesture.IsEnableTwoFingerRotation = false;
|
||||
CheckEnableTwoFingerGestureBtnColorPrompt();
|
||||
SaveSettingsToFile();
|
||||
return;
|
||||
}
|
||||
|
||||
if (sender == ToggleSwitchEnableTwoFingerRotation)
|
||||
BoardToggleSwitchEnableTwoFingerRotation.IsOn = ToggleSwitchEnableTwoFingerRotation.IsOn;
|
||||
else
|
||||
@@ -1893,6 +2077,7 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reset
|
||||
@@ -1907,6 +2092,10 @@ namespace Ink_Canvas
|
||||
Settings.Advanced.TouchMultiplier = 0.3;
|
||||
Settings.Advanced.NibModeBoundsWidth = 5;
|
||||
Settings.Advanced.FingerModeBoundsWidth = 20;
|
||||
Settings.Advanced.NibModeBoundsWidthThresholdValue = 2.5;
|
||||
Settings.Advanced.FingerModeBoundsWidthThresholdValue = 2.5;
|
||||
Settings.Advanced.NibModeBoundsWidthEraserSize = 0.8;
|
||||
Settings.Advanced.FingerModeBoundsWidthEraserSize = 0.8;
|
||||
Settings.Advanced.EraserBindTouchMultiplier = true;
|
||||
Settings.Advanced.IsLogEnabled = true;
|
||||
Settings.Advanced.IsSecondConfirmWhenShutdownApp = false;
|
||||
@@ -2271,6 +2460,26 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchIsAutoBackupEnabled_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Advanced.IsAutoBackupEnabled = ToggleSwitchIsAutoBackupEnabled.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ComboBoxAutoBackupInterval_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
if (ComboBoxAutoBackupInterval.SelectedItem is ComboBoxItem selectedItem && selectedItem.Tag != null)
|
||||
{
|
||||
if (int.TryParse(selectedItem.Tag.ToString(), out int interval))
|
||||
{
|
||||
Settings.Advanced.AutoBackupIntervalDays = interval;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnManualBackup_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -2396,6 +2605,154 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchUseLegacyTimerUI_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.UseLegacyTimerUI = ToggleSwitchUseLegacyTimerUI.IsOn;
|
||||
if (ToggleSwitchUseLegacyTimerUI.IsOn)
|
||||
{
|
||||
ToggleSwitchUseNewStyleUI.IsOn = false;
|
||||
Settings.RandSettings.UseNewStyleUI = false;
|
||||
}
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchUseNewStyleUI_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.UseNewStyleUI = ToggleSwitchUseNewStyleUI.IsOn;
|
||||
if (ToggleSwitchUseNewStyleUI.IsOn)
|
||||
{
|
||||
ToggleSwitchUseLegacyTimerUI.IsOn = false;
|
||||
Settings.RandSettings.UseLegacyTimerUI = false;
|
||||
}
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableOvertimeCountUp_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.EnableOvertimeCountUp = ToggleSwitchEnableOvertimeCountUp.IsOn;
|
||||
|
||||
if (!ToggleSwitchEnableOvertimeCountUp.IsOn)
|
||||
{
|
||||
ToggleSwitchEnableOvertimeRedText.IsOn = false;
|
||||
Settings.RandSettings.EnableOvertimeRedText = false;
|
||||
}
|
||||
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableOvertimeRedText_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
if (ToggleSwitchEnableOvertimeRedText.IsOn && !ToggleSwitchEnableOvertimeCountUp.IsOn)
|
||||
{
|
||||
ToggleSwitchEnableOvertimeRedText.IsOn = false;
|
||||
return;
|
||||
}
|
||||
|
||||
Settings.RandSettings.EnableOvertimeRedText = ToggleSwitchEnableOvertimeRedText.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void TimerVolumeSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.TimerVolume = TimerVolumeSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableProgressiveReminder_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.EnableProgressiveReminder = ToggleSwitchEnableProgressiveReminder.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 新点名UI设置事件处理
|
||||
private void ToggleSwitchUseNewRollCallUI_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.UseNewRollCallUI = ToggleSwitchUseNewRollCallUI.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchEnableMLAvoidance_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.EnableMLAvoidance = ToggleSwitchEnableMLAvoidance.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void MLAvoidanceHistorySlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.MLAvoidanceHistoryCount = (int)MLAvoidanceHistorySlider.Value;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void MLAvoidanceWeightSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.MLAvoidanceWeight = MLAvoidanceWeightSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ProgressiveReminderVolumeSlider_ValueChanged(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.RandSettings.ProgressiveReminderVolume = ProgressiveReminderVolumeSlider.Value;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ButtonSelectCustomProgressiveReminderSound_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Title = "选择渐进提醒音频文件",
|
||||
Filter = "音频文件 (*.wav)|*.wav|所有文件 (*.*)|*.*",
|
||||
DefaultExt = "wav"
|
||||
};
|
||||
|
||||
if (openFileDialog.ShowDialog() == true)
|
||||
{
|
||||
Settings.RandSettings.ProgressiveReminderSoundPath = openFileDialog.FileName;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonResetProgressiveReminderSound_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Settings.RandSettings.ProgressiveReminderSoundPath = "";
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ButtonSelectCustomTimerSound_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog
|
||||
{
|
||||
Title = "选择计时器提醒铃声",
|
||||
Filter = "音频文件 (*.wav)|*.wav|所有文件 (*.*)|*.*",
|
||||
DefaultExt = "wav"
|
||||
};
|
||||
|
||||
if (openFileDialog.ShowDialog() == true)
|
||||
{
|
||||
Settings.RandSettings.CustomTimerSoundPath = openFileDialog.FileName;
|
||||
SaveSettingsToFile();
|
||||
MessageBox.Show("自定义铃声设置成功!", "设置成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
|
||||
private void ButtonResetTimerSound_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Settings.RandSettings.CustomTimerSoundPath = "";
|
||||
SaveSettingsToFile();
|
||||
MessageBox.Show("已重置为默认铃声!", "重置成功", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
|
||||
private void ToggleSwitchShowRandomAndSingleDraw_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -2412,21 +2769,123 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchDirectCallCiRand_Toggled(object sender, RoutedEventArgs e)
|
||||
private void ToggleSwitchEnableQuickDraw_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 获取开关状态并保存到设置中
|
||||
Settings.RandSettings.DirectCallCiRand = ToggleSwitchDirectCallCiRand.IsOn;
|
||||
Settings.RandSettings.EnableQuickDraw = ToggleSwitchEnableQuickDraw.IsOn;
|
||||
|
||||
// 保存设置到文件
|
||||
SaveSettingsToFile();
|
||||
|
||||
// 根据设置状态显示或隐藏快抽悬浮按钮
|
||||
ShowQuickDrawFloatingButton();
|
||||
}
|
||||
|
||||
private void ToggleSwitchExternalCaller_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 获取开关状态并保存到设置中
|
||||
Settings.RandSettings.DirectCallCiRand = ToggleSwitchExternalCaller.IsOn;
|
||||
|
||||
// 保存设置到文件
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ComboBoxExternalCallerType_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 获取下拉框选择并保存到设置中
|
||||
Settings.RandSettings.ExternalCallerType = ComboBoxExternalCallerType.SelectedIndex;
|
||||
|
||||
// 保存设置到文件
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
public void UpdateFloatingBarIcons()
|
||||
{
|
||||
if (Settings.Appearance.UseLegacyFloatingBarUI)
|
||||
{
|
||||
// 使用老版图标
|
||||
CursorIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedCursorIcon);
|
||||
PenIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedPenIcon);
|
||||
StrokeEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedEraserStrokeIcon);
|
||||
CircleEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedEraserCircleIcon);
|
||||
LassoSelectIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LegacyLinedLassoSelectIcon);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用新版图标
|
||||
CursorIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedCursorIcon);
|
||||
PenIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedPenIcon);
|
||||
StrokeEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedEraserStrokeIcon);
|
||||
CircleEraserIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedEraserCircleIcon);
|
||||
LassoSelectIconGeometry.Geometry = Geometry.Parse(XamlGraphicsIconGeometries.LinedLassoSelectIcon);
|
||||
}
|
||||
}
|
||||
|
||||
public string GetCorrectIcon(string iconType, bool isSolid = false)
|
||||
{
|
||||
if (Settings.Appearance.UseLegacyFloatingBarUI)
|
||||
{
|
||||
// 使用老版图标
|
||||
switch (iconType)
|
||||
{
|
||||
case "cursor":
|
||||
return isSolid ? XamlGraphicsIconGeometries.LegacySolidCursorIcon : XamlGraphicsIconGeometries.LegacyLinedCursorIcon;
|
||||
case "pen":
|
||||
return isSolid ? XamlGraphicsIconGeometries.LegacySolidPenIcon : XamlGraphicsIconGeometries.LegacyLinedPenIcon;
|
||||
case "eraserStroke":
|
||||
return isSolid ? XamlGraphicsIconGeometries.LegacySolidEraserStrokeIcon : XamlGraphicsIconGeometries.LegacyLinedEraserStrokeIcon;
|
||||
case "eraserCircle":
|
||||
return isSolid ? XamlGraphicsIconGeometries.LegacySolidEraserCircleIcon : XamlGraphicsIconGeometries.LegacyLinedEraserCircleIcon;
|
||||
case "lassoSelect":
|
||||
return isSolid ? XamlGraphicsIconGeometries.LegacySolidLassoSelectIcon : XamlGraphicsIconGeometries.LegacyLinedLassoSelectIcon;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用新版图标
|
||||
switch (iconType)
|
||||
{
|
||||
case "cursor":
|
||||
return isSolid ? XamlGraphicsIconGeometries.SolidCursorIcon : XamlGraphicsIconGeometries.LinedCursorIcon;
|
||||
case "pen":
|
||||
return isSolid ? XamlGraphicsIconGeometries.SolidPenIcon : XamlGraphicsIconGeometries.LinedPenIcon;
|
||||
case "eraserStroke":
|
||||
return isSolid ? XamlGraphicsIconGeometries.SolidEraserStrokeIcon : XamlGraphicsIconGeometries.LinedEraserStrokeIcon;
|
||||
case "eraserCircle":
|
||||
return isSolid ? XamlGraphicsIconGeometries.SolidEraserCircleIcon : XamlGraphicsIconGeometries.LinedEraserCircleIcon;
|
||||
case "lassoSelect":
|
||||
return isSolid ? XamlGraphicsIconGeometries.SolidLassoSelectIcon : XamlGraphicsIconGeometries.LinedLassoSelectIcon;
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 浮动栏按钮显示控制
|
||||
|
||||
private void CheckBoxUseLegacyFloatingBarUI_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Appearance.UseLegacyFloatingBarUI = CheckBoxUseLegacyFloatingBarUI.IsChecked ?? false;
|
||||
UpdateFloatingBarIcons();
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void CheckBoxUseLegacyFloatingBarUI_Unchecked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Appearance.UseLegacyFloatingBarUI = CheckBoxUseLegacyFloatingBarUI.IsChecked ?? false;
|
||||
UpdateFloatingBarIcons();
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void CheckBoxShowShapeButton_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
@@ -2715,7 +3174,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
// 重新计算浮动栏位置,因为按钮可见性变化会影响浮动栏宽度
|
||||
// 修复:移除浮动栏收起状态检查,确保在收起状态下也能正确修正位置
|
||||
if (currentMode == 0) // 只在屏幕模式下重新计算浮动栏位置
|
||||
{
|
||||
if (BtnPPTSlideShowEnd.Visibility == Visibility.Visible)
|
||||
@@ -2747,6 +3205,12 @@ namespace Ink_Canvas
|
||||
var text = JsonConvert.SerializeObject(Settings, Formatting.Indented);
|
||||
try
|
||||
{
|
||||
string configsDir = Path.Combine(App.RootPath, "Configs");
|
||||
if (!Directory.Exists(configsDir))
|
||||
{
|
||||
Directory.CreateDirectory(configsDir);
|
||||
}
|
||||
|
||||
File.WriteAllText(App.RootPath + settingsFileName, text);
|
||||
}
|
||||
catch { }
|
||||
@@ -2798,6 +3262,7 @@ namespace Ink_Canvas
|
||||
if (Settings.Startup.IsAutoUpdate)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Channel changed to {newChannel}, performing immediate update check");
|
||||
ResetUpdateCheckRetry();
|
||||
|
||||
// 执行完整的更新检查
|
||||
await Task.Run(async () =>
|
||||
@@ -2823,6 +3288,149 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private async void ManualUpdateButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ManualUpdateButton.IsEnabled = false;
|
||||
ManualUpdateButton.Content = "正在检查更新...";
|
||||
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("ManualUpdate | Manual update button clicked");
|
||||
|
||||
// 使用当前选择的更新通道检查更新
|
||||
var (remoteVersion, lineGroup, apiReleaseNotes) = await AutoUpdateHelper.CheckForUpdates(Settings.Startup.UpdateChannel, true, false);
|
||||
|
||||
if (remoteVersion != null)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"ManualUpdate | Found new version: {remoteVersion}");
|
||||
|
||||
// 获取当前版本
|
||||
string currentVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
|
||||
|
||||
// 创建并显示更新窗口
|
||||
HasNewUpdateWindow updateWindow = new HasNewUpdateWindow(currentVersion, remoteVersion, "", apiReleaseNotes);
|
||||
updateWindow.Owner = Application.Current.MainWindow;
|
||||
bool? dialogResult = updateWindow.ShowDialog();
|
||||
|
||||
// 如果窗口被关闭但没有点击按钮,则不执行任何操作
|
||||
if (dialogResult != true)
|
||||
{
|
||||
LogHelper.WriteLogToFile("ManualUpdate | Update dialog closed without selection");
|
||||
return;
|
||||
}
|
||||
|
||||
// 根据用户选择处理更新
|
||||
switch (updateWindow.Result)
|
||||
{
|
||||
case HasNewUpdateWindow.UpdateResult.UpdateNow:
|
||||
// 立即更新:显示下载进度,下载完成后立即安装
|
||||
LogHelper.WriteLogToFile("ManualUpdate | User chose to update now");
|
||||
|
||||
// 显示下载进度提示
|
||||
MessageBox.Show("开始下载更新,请稍候...", "正在更新", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
// 下载更新文件,使用多线路组下载功能
|
||||
bool isDownloadSuccessful = await DownloadUpdateWithFallback(remoteVersion, lineGroup, Settings.Startup.UpdateChannel);
|
||||
|
||||
if (isDownloadSuccessful)
|
||||
{
|
||||
// 下载成功,提示用户准备安装
|
||||
MessageBoxResult result = MessageBox.Show("更新已下载完成,点击确定后将关闭软件并安装新版本!", "安装更新", MessageBoxButton.OKCancel, MessageBoxImage.Information);
|
||||
|
||||
// 只有当用户点击确定按钮后才关闭软件
|
||||
if (result == MessageBoxResult.OK)
|
||||
{
|
||||
// 设置为用户主动退出,避免被看门狗判定为崩溃
|
||||
App.IsAppExitByUser = true;
|
||||
|
||||
// 准备批处理脚本
|
||||
AutoUpdateHelper.InstallNewVersionApp(remoteVersion, true);
|
||||
|
||||
// 关闭软件,让安装程序接管
|
||||
Application.Current.Shutdown();
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("ManualUpdate | User cancelled update installation");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 下载失败
|
||||
MessageBox.Show("更新下载失败,请检查网络连接后重试。", "下载失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
break;
|
||||
|
||||
case HasNewUpdateWindow.UpdateResult.UpdateLater:
|
||||
// 稍后更新:静默下载,在软件关闭时自动安装
|
||||
LogHelper.WriteLogToFile("ManualUpdate | User chose to update later");
|
||||
|
||||
// 不管设置如何,都进行下载,使用多线路组下载功能
|
||||
isDownloadSuccessful = await DownloadUpdateWithFallback(remoteVersion, lineGroup, Settings.Startup.UpdateChannel);
|
||||
|
||||
if (isDownloadSuccessful)
|
||||
{
|
||||
LogHelper.WriteLogToFile("ManualUpdate | Update downloaded successfully, will install when application closes");
|
||||
|
||||
// 设置标志,在应用程序关闭时安装
|
||||
Settings.Startup.IsAutoUpdate = true;
|
||||
Settings.Startup.IsAutoUpdateWithSilence = true;
|
||||
|
||||
// 启动检查定时器
|
||||
timerCheckAutoUpdateWithSilence.Start();
|
||||
|
||||
// 通知用户
|
||||
MessageBox.Show("更新已下载完成,将在软件关闭时自动安装。", "更新已准备就绪", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("ManualUpdate | Update download failed", LogHelper.LogType.Error);
|
||||
MessageBox.Show("更新下载失败,请检查网络连接后重试。", "下载失败", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
break;
|
||||
|
||||
case HasNewUpdateWindow.UpdateResult.SkipVersion:
|
||||
// 跳过该版本:记录到设置中
|
||||
LogHelper.WriteLogToFile($"ManualUpdate | User chose to skip version {remoteVersion}");
|
||||
|
||||
// 记录要跳过的版本号
|
||||
Settings.Startup.SkippedVersion = remoteVersion;
|
||||
|
||||
// 保存设置到文件
|
||||
SaveSettingsToFile();
|
||||
|
||||
// 通知用户
|
||||
MessageBox.Show($"已设置跳过版本 {remoteVersion},在下次发布新版本之前不会再提示更新。",
|
||||
"已跳过此版本",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Information);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 没有更新
|
||||
LogHelper.WriteLogToFile("ManualUpdate | No updates available");
|
||||
MessageBox.Show("当前已是最新版本!", "无可用更新", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Error in ManualUpdateButton_Click: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show(
|
||||
$"手动更新过程中发生错误: {ex.Message}",
|
||||
"更新错误",
|
||||
MessageBoxButton.OK,
|
||||
MessageBoxImage.Error);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 恢复按钮状态
|
||||
ManualUpdateButton.IsEnabled = true;
|
||||
ManualUpdateButton.Content = "手动更新";
|
||||
}
|
||||
}
|
||||
|
||||
private async void FixVersionButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 显示确认对话框
|
||||
@@ -2961,6 +3569,13 @@ namespace Ink_Canvas
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchKeepFoldAfterSoftwareExit_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
Settings.Automation.KeepFoldAfterSoftwareExit = ToggleSwitchKeepFoldAfterSoftwareExit.IsOn;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
private void ToggleSwitchAlwaysGoToFirstPageOnReenter_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using Hardcodet.Wpf.TaskbarNotification;
|
||||
using Ink_Canvas.Helpers;
|
||||
using Newtonsoft.Json;
|
||||
using OSVersionExtension;
|
||||
@@ -10,8 +10,10 @@ using System.Windows.Ink;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
using File = System.IO.File;
|
||||
using OperatingSystem = OSVersionExtension.OperatingSystem;
|
||||
using WinForms = System.Windows.Forms;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
@@ -28,12 +30,92 @@ namespace Ink_Canvas
|
||||
{
|
||||
string text = File.ReadAllText(App.RootPath + settingsFileName);
|
||||
Settings = JsonConvert.DeserializeObject<Settings>(text);
|
||||
|
||||
// 验证设置是否成功加载
|
||||
if (Settings == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("配置文件解析失败,尝试从备份恢复", LogHelper.LogType.Warning);
|
||||
if (AutoBackupManager.TryRestoreFromBackup())
|
||||
{
|
||||
// 重新尝试加载
|
||||
text = File.ReadAllText(App.RootPath + settingsFileName);
|
||||
Settings = JsonConvert.DeserializeObject<Settings>(text);
|
||||
if (Settings != null)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然失败,使用默认设置
|
||||
if (Settings == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("从备份恢复失败,使用默认设置", LogHelper.LogType.Warning);
|
||||
BtnResetToSuggestion_Click(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"配置文件加载失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
|
||||
// 尝试从备份恢复
|
||||
LogHelper.WriteLogToFile("尝试从备份恢复配置文件", LogHelper.LogType.Warning);
|
||||
if (AutoBackupManager.TryRestoreFromBackup())
|
||||
{
|
||||
try
|
||||
{
|
||||
string text = File.ReadAllText(App.RootPath + settingsFileName);
|
||||
Settings = JsonConvert.DeserializeObject<Settings>(text);
|
||||
if (Settings != null)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception restoreEx)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从备份恢复后重新加载失败: {restoreEx.Message}", LogHelper.LogType.Error);
|
||||
BtnResetToSuggestion_Click(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然失败,使用默认设置
|
||||
if (Settings == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("从备份恢复失败,使用默认设置", LogHelper.LogType.Warning);
|
||||
BtnResetToSuggestion_Click(null, null);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
else
|
||||
{
|
||||
BtnResetToSuggestion_Click(null, null);
|
||||
LogHelper.WriteLogToFile("配置文件不存在,尝试从备份恢复", LogHelper.LogType.Warning);
|
||||
if (AutoBackupManager.TryRestoreFromBackup())
|
||||
{
|
||||
try
|
||||
{
|
||||
string text = File.ReadAllText(App.RootPath + settingsFileName);
|
||||
Settings = JsonConvert.DeserializeObject<Settings>(text);
|
||||
if (Settings != null)
|
||||
{
|
||||
}
|
||||
}
|
||||
catch (Exception restoreEx)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从备份恢复后加载失败: {restoreEx.Message}", LogHelper.LogType.Error);
|
||||
BtnResetToSuggestion_Click(null, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 备份恢复失败(备份目录不存在等),使用默认设置
|
||||
LogHelper.WriteLogToFile("备份恢复失败,使用默认设置", LogHelper.LogType.Warning);
|
||||
BtnResetToSuggestion_Click(null, null);
|
||||
}
|
||||
|
||||
// 如果仍然失败,使用默认设置
|
||||
if (Settings == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("从备份恢复失败,使用默认设置", LogHelper.LogType.Warning);
|
||||
BtnResetToSuggestion_Click(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -75,7 +157,7 @@ namespace Ink_Canvas
|
||||
Settings.Automation.AutoDelSavedFilesDaysThreshold);
|
||||
}
|
||||
|
||||
if (Settings.Startup.IsFoldAtStartup)
|
||||
if (Settings.Startup.IsFoldAtStartup && !App.StartWithBoardMode)
|
||||
{
|
||||
FoldFloatingBar_MouseUp(Fold_Icon, null);
|
||||
}
|
||||
@@ -245,10 +327,17 @@ namespace Ink_Canvas
|
||||
break;
|
||||
}
|
||||
|
||||
// 设置主题下拉框
|
||||
ComboBoxTheme.SelectedIndex = Settings.Appearance.Theme;
|
||||
|
||||
ComboBoxChickenSoupSource.SelectedIndex = Settings.Appearance.ChickenSoupSource;
|
||||
|
||||
ToggleSwitchEnableQuickPanel.IsOn = Settings.Appearance.IsShowQuickPanel;
|
||||
|
||||
ToggleSwitchEnableSplashScreen.IsOn = Settings.Appearance.EnableSplashScreen;
|
||||
|
||||
ComboBoxSplashScreenStyle.SelectedIndex = Settings.Appearance.SplashScreenStyle;
|
||||
|
||||
ToggleSwitchEnableTrayIcon.IsOn = Settings.Appearance.EnableTrayIcon;
|
||||
ICCTrayIconExampleImage.Visibility =
|
||||
Settings.Appearance.EnableTrayIcon ? Visibility.Visible : Visibility.Collapsed;
|
||||
@@ -256,7 +345,7 @@ namespace Ink_Canvas
|
||||
_taskbar.Visibility = Settings.Appearance.EnableTrayIcon ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
ViewboxFloatingBar.Opacity = Settings.Appearance.ViewboxFloatingBarOpacityValue;
|
||||
|
||||
|
||||
// 初始化浮动栏透明度滑块值
|
||||
ViewboxFloatingBarOpacityValueSlider.Value = Settings.Appearance.ViewboxFloatingBarOpacityValue;
|
||||
ViewboxFloatingBarOpacityInPPTValueSlider.Value = Settings.Appearance.ViewboxFloatingBarOpacityInPPTValue;
|
||||
@@ -320,6 +409,7 @@ namespace Ink_Canvas
|
||||
Settings.Appearance.EnableChickenSoupInWhiteboardMode;
|
||||
|
||||
// 浮动栏按钮显示控制开关初始化
|
||||
CheckBoxUseLegacyFloatingBarUI.IsChecked = Settings.Appearance.UseLegacyFloatingBarUI;
|
||||
CheckBoxShowShapeButton.IsChecked = Settings.Appearance.IsShowShapeButton;
|
||||
CheckBoxShowUndoButton.IsChecked = Settings.Appearance.IsShowUndoButton;
|
||||
CheckBoxShowRedoButton.IsChecked = Settings.Appearance.IsShowRedoButton;
|
||||
@@ -338,6 +428,9 @@ namespace Ink_Canvas
|
||||
// 应用浮动栏按钮可见性设置
|
||||
UpdateFloatingBarButtonsVisibility();
|
||||
|
||||
// 更新浮动栏图标
|
||||
UpdateFloatingBarIcons();
|
||||
|
||||
SystemEvents_UserPreferenceChanged(null, null);
|
||||
}
|
||||
else
|
||||
@@ -561,6 +654,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 初始化屏蔽压感开关状态
|
||||
ToggleSwitchDisablePressure.IsOn = Settings.Canvas.DisablePressure;
|
||||
inkCanvas.DefaultDrawingAttributes.IgnorePressure = Settings.Canvas.DisablePressure;
|
||||
|
||||
ComboBoxPenStyle.SelectedIndex = Settings.Canvas.InkStyle;
|
||||
BoardComboBoxPenStyle.SelectedIndex = Settings.Canvas.InkStyle;
|
||||
@@ -711,6 +805,17 @@ namespace Ink_Canvas
|
||||
ToggleSwitchIsEnableDPIChangeDetection.IsOn = Settings.Advanced.IsEnableDPIChangeDetection;
|
||||
ToggleSwitchIsEnableAvoidFullScreenHelper.IsOn = Settings.Advanced.IsEnableAvoidFullScreenHelper;
|
||||
ToggleSwitchIsAutoBackupBeforeUpdate.IsOn = Settings.Advanced.IsAutoBackupBeforeUpdate;
|
||||
ToggleSwitchIsAutoBackupEnabled.IsOn = Settings.Advanced.IsAutoBackupEnabled;
|
||||
|
||||
// 设置备份间隔下拉框
|
||||
foreach (ComboBoxItem item in ComboBoxAutoBackupInterval.Items)
|
||||
{
|
||||
if (item.Tag != null && int.TryParse(item.Tag.ToString(), out int interval) && interval == Settings.Advanced.AutoBackupIntervalDays)
|
||||
{
|
||||
ComboBoxAutoBackupInterval.SelectedItem = item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Settings.Advanced.IsEnableFullScreenHelper)
|
||||
{
|
||||
FullScreenHelper.MarkFullscreenWindowTaskbarList(new WindowInteropHelper(this).Handle, true);
|
||||
@@ -718,6 +823,14 @@ namespace Ink_Canvas
|
||||
if (Settings.Advanced.IsEnableAvoidFullScreenHelper)
|
||||
{
|
||||
AvoidFullScreenHelper.StartAvoidFullScreen(this);
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
if (isLoaded)
|
||||
{
|
||||
MoveWindow(new WindowInteropHelper(this).Handle, 0, 0,
|
||||
WinForms.Screen.PrimaryScreen.Bounds.Width, WinForms.Screen.PrimaryScreen.Bounds.Height, true);
|
||||
}
|
||||
}), DispatcherPriority.ApplicationIdle);
|
||||
}
|
||||
if (Settings.Advanced.IsEnableEdgeGestureUtil)
|
||||
{
|
||||
@@ -763,10 +876,36 @@ namespace Ink_Canvas
|
||||
RandWindowOnceCloseLatencySlider.Value = Settings.RandSettings.RandWindowOnceCloseLatency;
|
||||
RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents;
|
||||
ToggleSwitchShowRandomAndSingleDraw.IsOn = Settings.RandSettings.ShowRandomAndSingleDraw;
|
||||
ToggleSwitchDirectCallCiRand.IsOn = Settings.RandSettings.DirectCallCiRand;
|
||||
ToggleSwitchEnableQuickDraw.IsOn = Settings.RandSettings.EnableQuickDraw;
|
||||
ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand;
|
||||
ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType;
|
||||
RandomDrawPanel.Visibility = Settings.RandSettings.ShowRandomAndSingleDraw ? Visibility.Visible : Visibility.Collapsed;
|
||||
SingleDrawPanel.Visibility = Settings.RandSettings.ShowRandomAndSingleDraw ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
// 计时器设置
|
||||
ToggleSwitchUseLegacyTimerUI.IsOn = Settings.RandSettings.UseLegacyTimerUI;
|
||||
ToggleSwitchUseNewStyleUI.IsOn = Settings.RandSettings.UseNewStyleUI;
|
||||
ToggleSwitchEnableOvertimeCountUp.IsOn = Settings.RandSettings.EnableOvertimeCountUp;
|
||||
|
||||
// 新点名UI设置
|
||||
ToggleSwitchUseNewRollCallUI.IsOn = Settings.RandSettings.UseNewRollCallUI;
|
||||
ToggleSwitchEnableMLAvoidance.IsOn = Settings.RandSettings.EnableMLAvoidance;
|
||||
MLAvoidanceHistorySlider.Value = Settings.RandSettings.MLAvoidanceHistoryCount;
|
||||
MLAvoidanceWeightSlider.Value = Settings.RandSettings.MLAvoidanceWeight;
|
||||
|
||||
bool canEnableRedText = Settings.RandSettings.EnableOvertimeCountUp && Settings.RandSettings.EnableOvertimeRedText;
|
||||
ToggleSwitchEnableOvertimeRedText.IsOn = canEnableRedText;
|
||||
if (!canEnableRedText)
|
||||
{
|
||||
Settings.RandSettings.EnableOvertimeRedText = false;
|
||||
}
|
||||
|
||||
TimerVolumeSlider.Value = Settings.RandSettings.TimerVolume;
|
||||
|
||||
// 渐进提醒设置
|
||||
ToggleSwitchEnableProgressiveReminder.IsOn = Settings.RandSettings.EnableProgressiveReminder;
|
||||
ProgressiveReminderVolumeSlider.Value = Settings.RandSettings.ProgressiveReminderVolume;
|
||||
|
||||
// 加载自定义点名背景
|
||||
UpdatePickNameBackgroundsInComboBox();
|
||||
|
||||
@@ -783,13 +922,39 @@ namespace Ink_Canvas
|
||||
ToggleSwitchDisplayRandWindowNamesInputBtn.IsOn = Settings.RandSettings.DisplayRandWindowNamesInputBtn;
|
||||
RandWindowOnceCloseLatencySlider.Value = Settings.RandSettings.RandWindowOnceCloseLatency;
|
||||
RandWindowOnceMaxStudentsSlider.Value = Settings.RandSettings.RandWindowOnceMaxStudents;
|
||||
ToggleSwitchDirectCallCiRand.IsOn = Settings.RandSettings.DirectCallCiRand;
|
||||
ToggleSwitchEnableQuickDraw.IsOn = Settings.RandSettings.EnableQuickDraw;
|
||||
ToggleSwitchExternalCaller.IsOn = Settings.RandSettings.DirectCallCiRand;
|
||||
ComboBoxExternalCallerType.SelectedIndex = Settings.RandSettings.ExternalCallerType;
|
||||
ToggleSwitchUseLegacyTimerUI.IsOn = Settings.RandSettings.UseLegacyTimerUI;
|
||||
ToggleSwitchUseNewStyleUI.IsOn = Settings.RandSettings.UseNewStyleUI;
|
||||
ToggleSwitchEnableOvertimeCountUp.IsOn = Settings.RandSettings.EnableOvertimeCountUp;
|
||||
|
||||
bool canEnableRedText = Settings.RandSettings.EnableOvertimeCountUp && Settings.RandSettings.EnableOvertimeRedText;
|
||||
ToggleSwitchEnableOvertimeRedText.IsOn = canEnableRedText;
|
||||
if (!canEnableRedText)
|
||||
{
|
||||
Settings.RandSettings.EnableOvertimeRedText = false;
|
||||
}
|
||||
|
||||
TimerVolumeSlider.Value = Settings.RandSettings.TimerVolume;
|
||||
|
||||
// 渐进提醒设置
|
||||
ToggleSwitchEnableProgressiveReminder.IsOn = Settings.RandSettings.EnableProgressiveReminder;
|
||||
ProgressiveReminderVolumeSlider.Value = Settings.RandSettings.ProgressiveReminderVolume;
|
||||
}
|
||||
|
||||
// ModeSettings
|
||||
if (Settings.ModeSettings != null)
|
||||
{
|
||||
ToggleSwitchMode.IsOn = Settings.ModeSettings.IsPPTOnlyMode;
|
||||
|
||||
// 根据加载的配置状态执行相应的窗口显示/隐藏逻辑
|
||||
if (isStartup && Settings.ModeSettings.IsPPTOnlyMode)
|
||||
{
|
||||
// 启动时如果是仅PPT模式,隐藏主窗口
|
||||
Hide();
|
||||
LogHelper.WriteLogToFile("启动时检测到仅PPT模式,主窗口已隐藏", LogHelper.LogType.Event);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -848,6 +1013,8 @@ namespace Ink_Canvas
|
||||
|
||||
ToggleSwitchAutoFoldAfterPPTSlideShow.IsOn = Settings.Automation.IsAutoFoldAfterPPTSlideShow;
|
||||
|
||||
ToggleSwitchKeepFoldAfterSoftwareExit.IsOn = Settings.Automation.KeepFoldAfterSoftwareExit;
|
||||
|
||||
if (Settings.Automation.IsAutoKillEasiNote || Settings.Automation.IsAutoKillPptService ||
|
||||
Settings.Automation.IsAutoKillHiteAnnotation || Settings.Automation.IsAutoKillInkCanvas
|
||||
|| Settings.Automation.IsAutoKillICA || Settings.Automation.IsAutoKillIDT ||
|
||||
@@ -895,6 +1062,9 @@ namespace Ink_Canvas
|
||||
|
||||
// 加载退出收纳模式自动切换至批注模式设置
|
||||
ToggleSwitchAutoEnterAnnotationModeWhenExitFoldMode.IsOn = Settings.Automation.IsAutoEnterAnnotationModeWhenExitFoldMode;
|
||||
|
||||
// 加载退出白板时自动收纳设置
|
||||
ToggleSwitchAutoFoldWhenExitWhiteboard.IsOn = Settings.Automation.IsAutoFoldWhenExitWhiteboard;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -963,4 +1133,4 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using MessageBox = System.Windows.MessageBox;
|
||||
using System.Windows.Threading;
|
||||
using MessageBox = iNKORE.UI.WPF.Modern.Controls.MessageBox;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Ink_Canvas
|
||||
@@ -74,9 +75,9 @@ namespace Ink_Canvas
|
||||
ToggleSwitchDrawShapeBorderAutoHide.IsOn = !ToggleSwitchDrawShapeBorderAutoHide.IsOn;
|
||||
|
||||
if (ToggleSwitchDrawShapeBorderAutoHide.IsOn)
|
||||
((FontIcon)sender).Glyph = "";
|
||||
((SymbolIcon)sender).Symbol = Symbol.Pin;
|
||||
else
|
||||
((FontIcon)sender).Glyph = "";
|
||||
((SymbolIcon)sender).Symbol = Symbol.UnPin;
|
||||
}
|
||||
|
||||
private object lastMouseDownSender;
|
||||
@@ -107,10 +108,10 @@ namespace Ink_Canvas
|
||||
else if (sender == ImageDrawArrow || sender == BoardImageDrawArrow)
|
||||
drawingShapeMode = 2;
|
||||
else if (sender == ImageDrawParallelLine || sender == BoardImageDrawParallelLine) drawingShapeMode = 15;
|
||||
|
||||
|
||||
// 更新模式缓存
|
||||
UpdateCurrentToolMode("shape");
|
||||
|
||||
|
||||
isLongPressSelected = true;
|
||||
if (isSingleFingerDragMode) BtnFingerDragMode_Click(BtnFingerDragMode, null);
|
||||
}
|
||||
@@ -118,11 +119,15 @@ namespace Ink_Canvas
|
||||
|
||||
private void BtnPen_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 禁用高级橡皮擦系统
|
||||
DisableAdvancedEraserSystem();
|
||||
// 如果当前有选中的图片元素,先取消选中
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
UnselectElement(currentSelectedElement);
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
// 修复:从橡皮擦切换到批注模式时,退出多指书写模式
|
||||
// 这解决了从橡皮擦切换为批注时被锁定为多指书写的问题
|
||||
// 禁用高级橡皮擦系统
|
||||
DisableEraserOverlay();
|
||||
ExitMultiTouchModeIfNeeded();
|
||||
|
||||
// 如果当前已是批注模式,再次点击弹出批注子面板
|
||||
@@ -144,10 +149,12 @@ namespace Ink_Canvas
|
||||
}
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
|
||||
// 修复:确保从橡皮擦切换到笔时,多指手势功能能正确恢复
|
||||
|
||||
// 更新lastInkCanvasEditingMode以确保多指手势逻辑正确
|
||||
lastInkCanvasEditingMode = InkCanvasEditingMode.Ink;
|
||||
|
||||
ResetAllShapeButtonsOpacity();
|
||||
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
}
|
||||
|
||||
@@ -168,7 +175,6 @@ namespace Ink_Canvas
|
||||
lastIsInMultiTouchMode = true;
|
||||
}
|
||||
|
||||
// 修复:几何绘制模式下确保不切换到Ink模式,避免触摸轨迹被收集
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
@@ -337,7 +343,7 @@ namespace Ink_Canvas
|
||||
await CheckIsDrawingShapesInMultiTouchMode();
|
||||
EnterShapeDrawingMode(3);
|
||||
CancelSingleFingerDragMode();
|
||||
isLongPressSelected = true; // 设置为选中状态,避免抬笔后切换回笔模式
|
||||
isLongPressSelected = false;
|
||||
lastMouseDownSender = null;
|
||||
DrawShapePromptToPen();
|
||||
}
|
||||
@@ -483,27 +489,17 @@ namespace Ink_Canvas
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
}
|
||||
|
||||
// 修复:几何绘制模式下完全禁止触摸轨迹收集
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
|
||||
if (isWaitUntilNextTouchDown && dec.Count > 1) return;
|
||||
if (dec.Count > 1)
|
||||
{
|
||||
// 修复:双曲线绘制时,多指触摸不应该删除第一笔的辅助线
|
||||
if ((drawingShapeMode == 24 || drawingShapeMode == 25) && drawMultiStepShapeCurrentStep == 1)
|
||||
{
|
||||
// 第二笔绘制双曲线时,只删除第二笔的临时笔画,保留第一笔的辅助线
|
||||
try
|
||||
{
|
||||
inkCanvas.Strokes.Remove(lastTempStroke);
|
||||
}
|
||||
catch { }
|
||||
return;
|
||||
}
|
||||
if (!isTouchDown) return;
|
||||
|
||||
if (isWaitUntilNextTouchDown && dec.Count > 1) return;
|
||||
|
||||
// 对于多笔图形绘制,允许第二笔绘制,即使dec.Count > 1
|
||||
if (dec.Count > 1 && !((drawingShapeMode == 24 || drawingShapeMode == 25) && drawMultiStepShapeCurrentStep == 1))
|
||||
{
|
||||
// 其他情况正常删除临时笔画
|
||||
try
|
||||
{
|
||||
@@ -517,7 +513,17 @@ namespace Ink_Canvas
|
||||
return;
|
||||
}
|
||||
|
||||
// 修复:双曲线绘制时,第二笔应该基于第一笔的起点,而不是触摸实时位置
|
||||
// 第二笔绘制双曲线时,只删除第二笔的临时笔画,保留第一笔的辅助线
|
||||
if ((drawingShapeMode == 24 || drawingShapeMode == 25) && drawMultiStepShapeCurrentStep == 1)
|
||||
{
|
||||
try
|
||||
{
|
||||
inkCanvas.Strokes.Remove(lastTempStroke);
|
||||
}
|
||||
catch { }
|
||||
// 不直接返回,继续执行绘制逻辑
|
||||
}
|
||||
|
||||
Point touchPoint = e.GetTouchPoint(inkCanvas).Position;
|
||||
if ((drawingShapeMode == 24 || drawingShapeMode == 25) && drawMultiStepShapeCurrentStep == 1)
|
||||
{
|
||||
@@ -578,44 +584,20 @@ namespace Ink_Canvas
|
||||
{
|
||||
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
||||
};
|
||||
try
|
||||
{
|
||||
inkCanvas.Strokes.Remove(lastTempStroke);
|
||||
}
|
||||
catch { }
|
||||
|
||||
lastTempStroke = stroke;
|
||||
inkCanvas.Strokes.Add(stroke);
|
||||
UpdateTempStrokeSafely(stroke);
|
||||
break;
|
||||
case 8:
|
||||
_currentCommitType = CommitReason.ShapeDrawing;
|
||||
strokes.Add(GenerateDashedLineStrokeCollection(iniP, endP));
|
||||
try
|
||||
{
|
||||
inkCanvas.Strokes.Remove(lastTempStrokeCollection);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Trace.WriteLine("lastTempStrokeCollection failed.");
|
||||
}
|
||||
|
||||
lastTempStrokeCollection = strokes;
|
||||
inkCanvas.Strokes.Add(strokes);
|
||||
UpdateTempStrokeCollectionSafely(strokes);
|
||||
break;
|
||||
case 18:
|
||||
_currentCommitType = CommitReason.ShapeDrawing;
|
||||
strokes.Add(GenerateDotLineStrokeCollection(iniP, endP));
|
||||
try
|
||||
{
|
||||
inkCanvas.Strokes.Remove(lastTempStrokeCollection);
|
||||
}
|
||||
catch
|
||||
{
|
||||
Trace.WriteLine("lastTempStrokeCollection failed.");
|
||||
}
|
||||
|
||||
lastTempStrokeCollection = strokes;
|
||||
inkCanvas.Strokes.Add(strokes);
|
||||
UpdateTempStrokeCollectionSafely(strokes);
|
||||
break;
|
||||
case 2:
|
||||
_currentCommitType = CommitReason.ShapeDrawing;
|
||||
@@ -636,14 +618,9 @@ namespace Ink_Canvas
|
||||
{
|
||||
DrawingAttributes = inkCanvas.DefaultDrawingAttributes.Clone()
|
||||
};
|
||||
try
|
||||
{
|
||||
inkCanvas.Strokes.Remove(lastTempStroke);
|
||||
}
|
||||
catch { }
|
||||
|
||||
lastTempStroke = stroke;
|
||||
inkCanvas.Strokes.Add(stroke);
|
||||
// 优化:使用更安全的临时笔画更新方式,减少闪烁
|
||||
UpdateTempStrokeSafely(stroke);
|
||||
break;
|
||||
case 15:
|
||||
_currentCommitType = CommitReason.ShapeDrawing;
|
||||
@@ -1026,7 +1003,6 @@ namespace Ink_Canvas
|
||||
drawMultiStepShapeSpecialParameter3 = k;
|
||||
drawMultiStepShapeSpecialStrokeCollection = strokes;
|
||||
|
||||
// 修复:第一笔绘制的辅助线应该立即显示在画布上
|
||||
try
|
||||
{
|
||||
inkCanvas.Strokes.Remove(lastTempStrokeCollection);
|
||||
@@ -1119,7 +1095,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
// 修复:双曲线绘制完成后,需要将第一笔的辅助线和第二笔的双曲线合并
|
||||
try
|
||||
{
|
||||
// 删除第二笔的临时笔画
|
||||
@@ -1501,6 +1476,119 @@ namespace Ink_Canvas
|
||||
|
||||
private bool isWaitUntilNextTouchDown;
|
||||
|
||||
// 添加节流机制,减少更新频率
|
||||
private DateTime lastUpdateTime = DateTime.MinValue;
|
||||
private const int UpdateThrottleMs = 16; // 约60fps的更新频率
|
||||
|
||||
/// <summary>
|
||||
/// 安全地更新临时笔画,减少预览闪烁
|
||||
/// </summary>
|
||||
/// <param name="newStroke">新的临时笔画</param>
|
||||
private void UpdateTempStrokeSafely(Stroke newStroke)
|
||||
{
|
||||
// 节流机制:限制更新频率
|
||||
var now = DateTime.Now;
|
||||
if ((now - lastUpdateTime).TotalMilliseconds < UpdateThrottleMs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lastUpdateTime = now;
|
||||
|
||||
try
|
||||
{
|
||||
// 使用Dispatcher.BeginInvoke确保UI更新在UI线程上执行
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 先添加新笔画,再删除旧笔画,减少视觉闪烁
|
||||
inkCanvas.Strokes.Add(newStroke);
|
||||
|
||||
if (lastTempStroke != null && inkCanvas.Strokes.Contains(lastTempStroke))
|
||||
{
|
||||
inkCanvas.Strokes.Remove(lastTempStroke);
|
||||
}
|
||||
|
||||
lastTempStroke = newStroke;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"UpdateTempStrokeSafely 失败: {ex.Message}");
|
||||
// 如果更新失败,确保清理状态
|
||||
if (lastTempStroke != null && inkCanvas.Strokes.Contains(lastTempStroke))
|
||||
{
|
||||
try { inkCanvas.Strokes.Remove(lastTempStroke); } catch { }
|
||||
}
|
||||
lastTempStroke = newStroke;
|
||||
try { inkCanvas.Strokes.Add(newStroke); } catch { }
|
||||
}
|
||||
}), DispatcherPriority.Render);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"UpdateTempStrokeSafely Dispatcher 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 安全地更新临时笔画集合,减少预览闪烁
|
||||
/// </summary>
|
||||
/// <param name="newStrokeCollection">新的临时笔画集合</param>
|
||||
private void UpdateTempStrokeCollectionSafely(StrokeCollection newStrokeCollection)
|
||||
{
|
||||
// 节流机制:限制更新频率
|
||||
var now = DateTime.Now;
|
||||
if ((now - lastUpdateTime).TotalMilliseconds < UpdateThrottleMs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
lastUpdateTime = now;
|
||||
|
||||
try
|
||||
{
|
||||
// 使用Dispatcher.BeginInvoke确保UI更新在UI线程上执行
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 先添加新笔画集合,再删除旧笔画集合,减少视觉闪烁
|
||||
inkCanvas.Strokes.Add(newStrokeCollection);
|
||||
|
||||
if (lastTempStrokeCollection != null && lastTempStrokeCollection.Count > 0)
|
||||
{
|
||||
foreach (var stroke in lastTempStrokeCollection)
|
||||
{
|
||||
if (inkCanvas.Strokes.Contains(stroke))
|
||||
{
|
||||
inkCanvas.Strokes.Remove(stroke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastTempStrokeCollection = newStrokeCollection;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"UpdateTempStrokeCollectionSafely 失败: {ex.Message}");
|
||||
// 如果更新失败,确保清理状态
|
||||
if (lastTempStrokeCollection != null && lastTempStrokeCollection.Count > 0)
|
||||
{
|
||||
foreach (var stroke in lastTempStrokeCollection)
|
||||
{
|
||||
try { inkCanvas.Strokes.Remove(stroke); } catch { }
|
||||
}
|
||||
}
|
||||
lastTempStrokeCollection = newStrokeCollection;
|
||||
try { inkCanvas.Strokes.Add(newStrokeCollection); } catch { }
|
||||
}
|
||||
}), DispatcherPriority.Render);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"UpdateTempStrokeCollectionSafely Dispatcher 失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private List<Point> GenerateEllipseGeometry(Point st, Point ed, bool isDrawTop = true,
|
||||
bool isDrawBottom = true)
|
||||
{
|
||||
@@ -1668,6 +1756,7 @@ namespace Ink_Canvas
|
||||
}
|
||||
|
||||
private bool isMouseDown;
|
||||
private bool isTouchDown;
|
||||
|
||||
private void inkCanvas_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
@@ -1933,7 +2022,6 @@ namespace Ink_Canvas
|
||||
|
||||
private bool NeedUpdateIniP()
|
||||
{
|
||||
// 修复:双曲线绘制时,第二笔不应该更新起点,保持第一笔的起点
|
||||
if (drawingShapeMode == 24 || drawingShapeMode == 25)
|
||||
{
|
||||
if (drawMultiStepShapeCurrentStep == 1)
|
||||
@@ -1980,6 +2068,38 @@ namespace Ink_Canvas
|
||||
drawingShapeMode = mode;
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
ResetAllShapeButtonsOpacity();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重置所有几何绘制按钮的透明度状态
|
||||
/// </summary>
|
||||
private void ResetAllShapeButtonsOpacity()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 重置所有几何绘制按钮的透明度为1(完全不透明)
|
||||
var buttons = new UIElement[] {
|
||||
ImageDrawLine, BoardImageDrawLine,
|
||||
ImageDrawDashedLine, BoardImageDrawDashedLine,
|
||||
ImageDrawDotLine, BoardImageDrawDotLine,
|
||||
ImageDrawArrow, BoardImageDrawArrow,
|
||||
ImageDrawParallelLine, BoardImageDrawParallelLine,
|
||||
};
|
||||
|
||||
foreach (var button in buttons)
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
var dA = new DoubleAnimation(1, 1, new Duration(TimeSpan.FromMilliseconds(0)));
|
||||
button.BeginAnimation(OpacityProperty, dA);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"重置几何绘制按钮透明度失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Ink_Canvas
|
||||
@@ -63,10 +64,10 @@ namespace Ink_Canvas
|
||||
var startPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[0].ToPoint() : new Point();
|
||||
var endPoint = e.Stroke.StylusPoints.Count > 0 ? e.Stroke.StylusPoints[e.Stroke.StylusPoints.Count - 1].ToPoint() : new Point();
|
||||
|
||||
// 从InkCanvas中移除墨迹,因为我们要用渐隐管理器来管理它
|
||||
if (inkCanvas.Strokes.Contains(e.Stroke))
|
||||
// 确保InkCanvas保持Ink编辑模式,防止自动切换到鼠标模式
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink)
|
||||
{
|
||||
inkCanvas.Strokes.Remove(e.Stroke);
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
// 添加到墨迹渐隐管理器
|
||||
@@ -79,6 +80,30 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile("StrokeCollected: 墨迹渐隐管理器为空,无法添加墨迹", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 延迟移除墨迹,避免立即移除导致模式切换
|
||||
// 使用Dispatcher.BeginInvoke确保在UI线程上异步执行
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 再次确保InkCanvas保持Ink编辑模式
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.Ink)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
// 从InkCanvas中移除墨迹,因为我们要用渐隐管理器来管理它
|
||||
if (inkCanvas.Strokes.Contains(e.Stroke))
|
||||
{
|
||||
inkCanvas.Strokes.Remove(e.Stroke);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"延迟移除墨迹时出错: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}), DispatcherPriority.Background);
|
||||
|
||||
// 墨迹渐隐模式下不参与墨迹纠正和其他处理,直接返回
|
||||
return;
|
||||
}
|
||||
@@ -717,16 +742,22 @@ namespace Ink_Canvas
|
||||
catch { }
|
||||
|
||||
// 应用高级贝塞尔曲线平滑(仅在未进行直线拉直时)
|
||||
Debug.WriteLine($"墨迹平滑检查: UseAdvancedBezierSmoothing={Settings.Canvas.UseAdvancedBezierSmoothing}, wasStraightened={wasStraightened}");
|
||||
Debug.WriteLine($"异步平滑设置: UseAsyncInkSmoothing={Settings.Canvas.UseAsyncInkSmoothing}, _inkSmoothingManager={_inkSmoothingManager != null}");
|
||||
|
||||
if (Settings.Canvas.UseAdvancedBezierSmoothing && !wasStraightened)
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.WriteLine($"开始墨迹平滑处理: 原始点数={e.Stroke.StylusPoints.Count}, 直线拉直={wasStraightened}");
|
||||
|
||||
// 检查原始笔画是否仍然存在于画布中
|
||||
if (inkCanvas.Strokes.Contains(e.Stroke))
|
||||
{
|
||||
// 使用新的异步墨迹平滑管理器
|
||||
if (Settings.Canvas.UseAsyncInkSmoothing && _inkSmoothingManager != null)
|
||||
{
|
||||
Debug.WriteLine("使用异步墨迹平滑");
|
||||
// 异步处理
|
||||
_ = ProcessStrokeAsync(e.Stroke);
|
||||
}
|
||||
@@ -746,6 +777,10 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine("原始笔画不在画布中,跳过平滑处理");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -766,17 +801,27 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
Debug.WriteLine($"异步平滑开始: 原始点数={originalStroke.StylusPoints.Count}");
|
||||
await _inkSmoothingManager.SmoothStrokeAsync(originalStroke, (original, smoothed) =>
|
||||
{
|
||||
Debug.WriteLine($"异步平滑完成: 原始点数={original.StylusPoints.Count}, 平滑后点数={smoothed.StylusPoints.Count}");
|
||||
Debug.WriteLine($"墨迹比较: smoothed != original = {smoothed != original}");
|
||||
Debug.WriteLine($"画布包含原始墨迹: {inkCanvas.Strokes.Contains(original)}");
|
||||
|
||||
// 在UI线程上执行笔画替换
|
||||
if (inkCanvas.Strokes.Contains(original) && smoothed != original)
|
||||
{
|
||||
Debug.WriteLine("异步替换原始笔画为平滑后的笔画");
|
||||
SetNewBackupOfStroke();
|
||||
_currentCommitType = CommitReason.ShapeRecognition;
|
||||
inkCanvas.Strokes.Remove(original);
|
||||
inkCanvas.Strokes.Add(smoothed);
|
||||
_currentCommitType = CommitReason.UserInput;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.WriteLine($"异步平滑后的笔画与原始笔画相同,未进行替换 (contains={inkCanvas.Strokes.Contains(original)}, different={smoothed != original})");
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -815,20 +860,8 @@ namespace Ink_Canvas
|
||||
// 输出当前灵敏度值(调试用)
|
||||
Debug.WriteLine($"IsPotentialStraightLine - sensitivity: {sensitivity}, length: {lineLength}");
|
||||
|
||||
// 根据灵敏度调整快速检查阈值
|
||||
double quickThreshold;
|
||||
|
||||
// 如果灵敏度超过1.0,使用更宽松的快速检查标准
|
||||
if (sensitivity > 1.0)
|
||||
{
|
||||
// 高灵敏度模式 - 使用更宽松的阈值
|
||||
quickThreshold = Math.Min(0.2 + (sensitivity - 1.0) * 0.3, 0.5); // 映射到0.2-0.5范围
|
||||
}
|
||||
else
|
||||
{
|
||||
// 常规灵敏度模式
|
||||
quickThreshold = Math.Min(sensitivity * 1.5, 0.20);
|
||||
}
|
||||
// 将灵敏度转换为阈值:灵敏度0.05-2.0映射到阈值0.01-0.4
|
||||
double quickThreshold = Math.Max(0.01, sensitivity * 0.2); // 确保最小阈值为0.01
|
||||
|
||||
Debug.WriteLine($"使用快速检查阈值: {quickThreshold}");
|
||||
|
||||
@@ -854,26 +887,13 @@ namespace Ink_Canvas
|
||||
// 记录检测到的偏差(调试用)
|
||||
Debug.WriteLine($"Deviations: q={quarterDeviation}, m={midDeviation}, tq={threeQuarterDeviation}, threshold={quickRelativeThreshold}");
|
||||
|
||||
// 如果灵敏度超过1.5,则即使有一个点满足条件也认为可能是直线
|
||||
if (sensitivity > 1.5)
|
||||
// 修复后的逻辑:灵敏度越大,容许的偏差越大
|
||||
// 如果任一点偏离太大,直接排除(使用统一的判断标准)
|
||||
if (quarterDeviation > quickRelativeThreshold ||
|
||||
midDeviation > quickRelativeThreshold ||
|
||||
threeQuarterDeviation > quickRelativeThreshold)
|
||||
{
|
||||
// 超高灵敏度模式:只要有一个关键点偏差小,就认为可能是直线
|
||||
if (quarterDeviation <= quickRelativeThreshold ||
|
||||
midDeviation <= quickRelativeThreshold ||
|
||||
threeQuarterDeviation <= quickRelativeThreshold)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 常规判断:如果任一点偏离太大,直接排除
|
||||
if (quarterDeviation > quickRelativeThreshold ||
|
||||
midDeviation > quickRelativeThreshold ||
|
||||
threeQuarterDeviation > quickRelativeThreshold)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1308,23 +1328,7 @@ namespace Ink_Canvas
|
||||
|
||||
// 支持更广泛的灵敏度范围 (0.05-2.0)
|
||||
|
||||
// 如果灵敏度高于1.0,使用更宽松的判断标准
|
||||
if (sensitivity > 1.0)
|
||||
{
|
||||
// 高灵敏度模式 - 允许更大的偏差
|
||||
double adjustedSensitivity = 0.5 + (sensitivity - 1.0) * 1.5; // 映射到0.5-2.0范围
|
||||
|
||||
// 只判断平均偏差和相对偏差
|
||||
if (maxDeviation / lineLength < adjustedSensitivity && avgDeviation < lineLength * 0.1 * adjustedSensitivity)
|
||||
{
|
||||
Debug.WriteLine("接受拉直 (高灵敏度模式)");
|
||||
return true;
|
||||
}
|
||||
|
||||
Debug.WriteLine("拒绝拉直 (高灵敏度模式)");
|
||||
return false;
|
||||
}
|
||||
// 否则使用常规判断标准
|
||||
// 移除特殊的高灵敏度模式,使用统一的阈值计算逻辑
|
||||
|
||||
// 检查点分布的一致性 - 如果有些点偏离很大而其他点很接近直线,表明线条有明显弯曲
|
||||
double deviationVariance = 0;
|
||||
@@ -1417,19 +1421,22 @@ namespace Ink_Canvas
|
||||
// 输出更多调试信息
|
||||
Debug.WriteLine($"Deviation variance: {deviationVariance}, Threshold: {sensitivity * lineLength * 0.05}");
|
||||
|
||||
// 如果最大偏差超过线长的阈值比例,或者偏差方差较大(表示不均匀弯曲),则不拉直
|
||||
// 灵敏度越大,容许的偏差越大,更容易将线条识别为直线
|
||||
if ((maxDeviation / lineLength) > sensitivity)
|
||||
// 修复灵敏度逻辑:灵敏度越大,容许的偏差越大,更容易将线条识别为直线
|
||||
// 将灵敏度转换为阈值:灵敏度0.05-1.0映射到阈值0.01-0.2
|
||||
double threshold = Math.Max(0.01, sensitivity * 0.2); // 确保最小阈值为0.01
|
||||
|
||||
if ((maxDeviation / lineLength) > threshold)
|
||||
{
|
||||
Debug.WriteLine("拒绝拉直:最大偏差过大");
|
||||
Debug.WriteLine($"拒绝拉直:最大偏差过大 {maxDeviation / lineLength:F3} > {threshold:F3}");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果偏差方差大,说明线条弯曲不均匀
|
||||
// 灵敏度越大,容许的偏差方差越大
|
||||
if (deviationVariance > (sensitivity * lineLength * 0.05))
|
||||
double varianceThreshold = threshold * lineLength * 0.25; // 调整方差阈值比例
|
||||
if (deviationVariance > varianceThreshold)
|
||||
{
|
||||
Debug.WriteLine("拒绝拉直:偏差方差过大");
|
||||
Debug.WriteLine($"拒绝拉直:偏差方差过大 {deviationVariance:F3} > {varianceThreshold:F3}");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1441,13 +1448,14 @@ namespace Ink_Canvas
|
||||
double midDeviation = DistanceFromLineToPoint(start, end, midPoint);
|
||||
|
||||
// 输出中点偏差信息
|
||||
Debug.WriteLine($"Mid deviation: {midDeviation}, Threshold: {lineLength * sensitivity * 0.8}");
|
||||
double midThreshold = lineLength * threshold * 0.8;
|
||||
Debug.WriteLine($"Mid deviation: {midDeviation:F3}, Threshold: {midThreshold:F3}");
|
||||
|
||||
// 如果中点偏离过大,不拉直
|
||||
// 使用灵敏度作为判断基准,灵敏度越大,容许的中点偏离越大
|
||||
if (midDeviation > (lineLength * sensitivity * 0.8))
|
||||
// 使用调整后的阈值,灵敏度越大,容许的中点偏离越大
|
||||
if (midDeviation > midThreshold)
|
||||
{
|
||||
Debug.WriteLine("拒绝拉直:中点偏差过大");
|
||||
Debug.WriteLine($"拒绝拉直:中点偏差过大 {midDeviation:F3} > {midThreshold:F3}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
@@ -61,12 +61,22 @@ namespace Ink_Canvas
|
||||
private Timer timerCheckAutoFold = new Timer();
|
||||
private string AvailableLatestVersion;
|
||||
private Timer timerCheckAutoUpdateWithSilence = new Timer();
|
||||
private Timer timerCheckAutoUpdateRetry = new Timer();
|
||||
private bool isHidingSubPanelsWhenInking; // 避免书写时触发二次关闭二级菜单导致动画不连续
|
||||
|
||||
private int updateCheckRetryCount = 0;
|
||||
private const int MAX_UPDATE_CHECK_RETRIES = 6;
|
||||
private Timer timerDisplayTime = new Timer();
|
||||
private Timer timerDisplayDate = new Timer();
|
||||
private Timer timerNtpSync = new Timer();
|
||||
|
||||
private TimeViewModel nowTimeVM = new TimeViewModel();
|
||||
private DateTime cachedNetworkTime = DateTime.Now;
|
||||
private DateTime lastNtpSyncTime = DateTime.MinValue;
|
||||
private string lastDisplayedTime = "";
|
||||
private bool useNetworkTime = false;
|
||||
private TimeSpan networkTimeOffset = TimeSpan.Zero;
|
||||
private DateTime lastLocalTime = DateTime.Now; // 记录上次的本地时间,用于检测时间跳跃
|
||||
private bool isNtpSyncing = false; // 防止重复NTP同步的标志
|
||||
|
||||
private async Task<DateTime> GetNetworkTimeAsync()
|
||||
{
|
||||
@@ -79,7 +89,7 @@ namespace Ink_Canvas
|
||||
var ipEndPoint = new IPEndPoint(addresses[0], 123);
|
||||
using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp))
|
||||
{
|
||||
socket.ReceiveTimeout = 2000;
|
||||
socket.ReceiveTimeout = 5000;
|
||||
socket.Connect(ipEndPoint);
|
||||
await Task.Factory.FromAsync(socket.BeginSend(ntpData, 0, ntpData.Length, SocketFlags.None, null, socket), socket.EndSend);
|
||||
await Task.Factory.FromAsync(socket.BeginReceive(ntpData, 0, ntpData.Length, SocketFlags.None, null, socket), socket.EndReceive);
|
||||
@@ -91,7 +101,7 @@ namespace Ink_Canvas
|
||||
var networkDateTime = (new DateTime(1900, 1, 1)).AddMilliseconds((long)milliseconds);
|
||||
return networkDateTime.ToLocalTime();
|
||||
}
|
||||
catch
|
||||
catch (Exception)
|
||||
{
|
||||
return DateTime.Now;
|
||||
}
|
||||
@@ -109,48 +119,140 @@ namespace Ink_Canvas
|
||||
timerCheckAutoFold.Interval = 500;
|
||||
timerCheckAutoUpdateWithSilence.Elapsed += timerCheckAutoUpdateWithSilence_Elapsed;
|
||||
timerCheckAutoUpdateWithSilence.Interval = 1000 * 60 * 10;
|
||||
timerCheckAutoUpdateRetry.Elapsed += timerCheckAutoUpdateRetry_Elapsed;
|
||||
timerCheckAutoUpdateRetry.Interval = 1000 * 60 * 10;
|
||||
WaterMarkTime.DataContext = nowTimeVM;
|
||||
WaterMarkDate.DataContext = nowTimeVM;
|
||||
timerDisplayTime.Elapsed += async (s, e) => await TimerDisplayTime_ElapsedAsync();
|
||||
timerDisplayTime.Elapsed += TimerDisplayTime_Elapsed;
|
||||
timerDisplayTime.Interval = 1000;
|
||||
timerDisplayTime.Start();
|
||||
timerDisplayDate.Elapsed += TimerDisplayDate_Elapsed;
|
||||
timerDisplayDate.Interval = 1000 * 60 * 60 * 1;
|
||||
timerDisplayDate.Start();
|
||||
timerNtpSync.Elapsed += async (s, e) => await TimerNtpSync_ElapsedAsync();
|
||||
timerNtpSync.Interval = 1000 * 60 * 60 * 2; // 每2小时同步一次
|
||||
timerNtpSync.Start();
|
||||
timerKillProcess.Start();
|
||||
nowTimeVM.nowDate = DateTime.Now.ToString("yyyy'年'MM'月'dd'日' dddd");
|
||||
nowTimeVM.nowTime = DateTime.Now.ToString("tt hh'时'mm'分'ss'秒'");
|
||||
|
||||
// 程序启动时立即进行一次NTP同步
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await TimerNtpSync_ElapsedAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"程序启动时NTP同步失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 修改TimerDisplayTime_ElapsedAsync方法中的时间格式,实现校验制
|
||||
private async Task TimerDisplayTime_ElapsedAsync()
|
||||
// NTP同步定时器事件处理
|
||||
private async Task TimerNtpSync_ElapsedAsync()
|
||||
{
|
||||
// 防止重复同步
|
||||
if (isNtpSyncing) return;
|
||||
|
||||
isNtpSyncing = true;
|
||||
try
|
||||
{
|
||||
|
||||
// 添加超时机制,最多等待10秒
|
||||
var timeoutTask = Task.Delay(10000);
|
||||
var ntpTask = GetNetworkTimeAsync();
|
||||
|
||||
var completedTask = await Task.WhenAny(ntpTask, timeoutTask);
|
||||
|
||||
if (completedTask == timeoutTask)
|
||||
{
|
||||
cachedNetworkTime = DateTime.Now;
|
||||
lastNtpSyncTime = DateTime.Now;
|
||||
useNetworkTime = false;
|
||||
networkTimeOffset = TimeSpan.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
DateTime networkTime = await ntpTask;
|
||||
DateTime localTime = DateTime.Now;
|
||||
|
||||
cachedNetworkTime = networkTime;
|
||||
lastNtpSyncTime = localTime;
|
||||
|
||||
// 计算网络时间与本地时间的偏移量
|
||||
networkTimeOffset = networkTime - localTime;
|
||||
|
||||
// 如果时间差超过3分钟,则使用网络时间
|
||||
useNetworkTime = Math.Abs(networkTimeOffset.TotalMinutes) > 3.0;
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// NTP同步失败时,保持使用本地时间
|
||||
cachedNetworkTime = DateTime.Now;
|
||||
lastNtpSyncTime = DateTime.Now;
|
||||
useNetworkTime = false;
|
||||
networkTimeOffset = TimeSpan.Zero;
|
||||
|
||||
LogHelper.WriteLogToFile($"NTP同步失败: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
finally
|
||||
{
|
||||
isNtpSyncing = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 优化后的时间显示方法,仅在NTP同步时计算网络时间偏移
|
||||
private void TimerDisplayTime_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
DateTime localTime = DateTime.Now;
|
||||
DateTime displayTime = localTime; // 默认使用本地时间
|
||||
|
||||
try
|
||||
|
||||
// 检测系统时间是否发生重大跳跃(超过2分钟)
|
||||
TimeSpan timeJump = localTime - lastLocalTime;
|
||||
double timeJumpMinutes = Math.Abs(timeJump.TotalMinutes);
|
||||
|
||||
if (timeJumpMinutes > 3 && !isNtpSyncing)
|
||||
{
|
||||
DateTime networkTime = await GetNetworkTimeAsync();
|
||||
|
||||
// 计算时间差
|
||||
TimeSpan timeDifference = networkTime - localTime;
|
||||
double timeDifferenceMinutes = Math.Abs(timeDifference.TotalMinutes);
|
||||
|
||||
// 如果网络时间与本地时间相差不超过1分钟,则使用本地时间
|
||||
// 否则使用网络时间
|
||||
displayTime = timeDifferenceMinutes <= 1.0 ? localTime : networkTime;
|
||||
// 系统时间发生重大变化(超过3分钟),立即触发NTP同步
|
||||
// 使用异步方式触发NTP同步,避免阻塞主线程
|
||||
Task.Run(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
await TimerNtpSync_ElapsedAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"时间跳跃触发的NTP同步失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch
|
||||
lastLocalTime = localTime;
|
||||
|
||||
// 如果启用网络时间且偏移量已计算,则应用偏移量
|
||||
if (useNetworkTime && networkTimeOffset != TimeSpan.Zero)
|
||||
{
|
||||
// 网络时间获取失败时,使用本地时间
|
||||
displayTime = localTime;
|
||||
displayTime = localTime + networkTimeOffset;
|
||||
}
|
||||
|
||||
// 只更新时间,日期由原有逻辑定时更新即可
|
||||
Dispatcher.Invoke(() =>
|
||||
|
||||
// 格式化时间字符串
|
||||
string timeString = displayTime.ToString("tt hh'时'mm'分'ss'秒'");
|
||||
|
||||
|
||||
// 只有当时间字符串发生变化时才更新UI,避免不必要的UI刷新
|
||||
if (timeString != lastDisplayedTime)
|
||||
{
|
||||
nowTimeVM.nowTime = displayTime.ToString("tt hh'时'mm'分'ss'秒'");
|
||||
});
|
||||
lastDisplayedTime = timeString;
|
||||
|
||||
// 使用BeginInvoke异步更新UI,避免阻塞
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
nowTimeVM.nowTime = timeString;
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
// 修改TimerDisplayDate_Elapsed方法中的日期格式
|
||||
@@ -177,6 +279,8 @@ namespace Ink_Canvas
|
||||
{
|
||||
var processes = Process.GetProcessesByName("EasiNote");
|
||||
if (processes.Length > 0) arg += " /IM EasiNote.exe";
|
||||
var seewoStartProcesses = Process.GetProcessesByName("SeewoStart");
|
||||
if (seewoStartProcesses.Length > 0) arg += " /IM SeewoStart.exe";
|
||||
}
|
||||
|
||||
if (Settings.Automation.IsAutoKillHiteAnnotation)
|
||||
@@ -240,8 +344,18 @@ namespace Ink_Canvas
|
||||
ShowNotification("“鸿合屏幕书写”已自动关闭");
|
||||
if (Settings.Automation.IsAutoKillHiteAnnotation && Settings.Automation.IsAutoEnterAnnotationAfterKillHite)
|
||||
{
|
||||
// 自动进入批注状态
|
||||
PenIcon_Click(null, null);
|
||||
// 检查是否处于收纳状态,如果是则先展开浮动栏
|
||||
if (isFloatingBarFolded)
|
||||
{
|
||||
// 先展开浮动栏,然后进入批注状态
|
||||
// UnFoldFloatingBar 方法内部会根据设置自动进入批注模式
|
||||
UnFoldFloatingBar(null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果已经展开,直接进入批注状态
|
||||
PenIcon_Click(null, null);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -302,6 +416,44 @@ namespace Ink_Canvas
|
||||
{
|
||||
var windowTitle = ForegroundWindowInfo.WindowTitle();
|
||||
var windowRect = ForegroundWindowInfo.WindowRect();
|
||||
var windowProcessName = ForegroundWindowInfo.ProcessName();
|
||||
|
||||
// 检测希沃白板五的批注面板
|
||||
// 希沃白板五的批注面板通常具有以下特征:
|
||||
// 1. 窗口标题为空或包含特定关键词
|
||||
// 2. 窗口高度较小(批注工具栏)
|
||||
// 3. 窗口宽度适中(工具栏宽度)
|
||||
if (windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher")
|
||||
{
|
||||
// 检测希沃白板五的批注工具栏
|
||||
// 批注工具栏通常高度在50-200像素之间,宽度在200-800像素之间
|
||||
if (windowRect.Height >= 50 && windowRect.Height <= 200 &&
|
||||
windowRect.Width >= 200 && windowRect.Width <= 800)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检测希沃白板五的二级菜单面板
|
||||
// 二级菜单面板通常高度在100-400像素之间,宽度在150-400像素之间
|
||||
if (windowRect.Height >= 100 && windowRect.Height <= 400 &&
|
||||
windowRect.Width >= 150 && windowRect.Width <= 400)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 检测鸿合软件的批注面板
|
||||
if (windowProcessName == "HiteCamera" || windowProcessName == "HiteTouchPro" || windowProcessName == "HiteLightBoard")
|
||||
{
|
||||
// 鸿合软件的批注面板特征
|
||||
if (windowRect.Height >= 50 && windowRect.Height <= 300 &&
|
||||
windowRect.Width >= 200 && windowRect.Width <= 600)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 原有的检测逻辑(保持向后兼容)
|
||||
return windowTitle.Length == 0 && windowRect.Height < 500;
|
||||
}
|
||||
|
||||
@@ -325,10 +477,22 @@ namespace Ink_Canvas
|
||||
Trace.WriteLine(ForegroundWindowInfo.ProcessPath());
|
||||
Trace.WriteLine(version);
|
||||
Trace.WriteLine(prodName);
|
||||
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote && (!(windowTitle.Length == 0 && ForegroundWindowInfo.WindowRect().Height < 500) ||
|
||||
!Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno))
|
||||
if (version.StartsWith("5.") && Settings.Automation.IsAutoFoldInEasiNote)
|
||||
{ // EasiNote5
|
||||
if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
|
||||
// 检查是否是桌面批注窗口
|
||||
bool isAnnotationWindow = windowTitle.Length == 0 && ForegroundWindowInfo.WindowRect().Height < 500;
|
||||
|
||||
// 如果启用了忽略桌面批注窗口功能,且当前是批注窗口
|
||||
if (Settings.Automation.IsAutoFoldInEasiNoteIgnoreDesktopAnno && isAnnotationWindow)
|
||||
{
|
||||
// 强制保持收纳状态
|
||||
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
|
||||
}
|
||||
else if (!isAnnotationWindow)
|
||||
{
|
||||
// 非批注窗口时正常收纳
|
||||
if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
|
||||
}
|
||||
}
|
||||
else if (version.StartsWith("3.") && Settings.Automation.IsAutoFoldInEasiNote3)
|
||||
{ // EasiNote3
|
||||
@@ -369,7 +533,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
else if (Settings.Automation.IsAutoFoldInSeewoPincoTeacher && (windowProcessName == "BoardService" || windowProcessName == "seewoPincoTeacher"))
|
||||
{
|
||||
if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
|
||||
// 检测到希沃白板五的批注窗口时保持收纳状态
|
||||
if (IsAnnotationWindow())
|
||||
{
|
||||
// 批注窗口打开时,如果当前是展开状态则收纳
|
||||
if (!isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 非批注窗口时正常处理
|
||||
if (!unfoldFloatingBarByUser && !isFloatingBarFolded) FoldFloatingBar_MouseUp(null, null);
|
||||
}
|
||||
// HiteCamera
|
||||
}
|
||||
else if (Settings.Automation.IsAutoFoldInHiteCamera && windowProcessName == "HiteCamera" &&
|
||||
@@ -491,8 +665,18 @@ namespace Ink_Canvas
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isFloatingBarFolded && !foldFloatingBarByUser) UnFoldFloatingBar_MouseUp(new object(), null);
|
||||
unfoldFloatingBarByUser = false;
|
||||
// 检查是否启用了软件退出后保持收纳模式
|
||||
if (Settings.Automation.KeepFoldAfterSoftwareExit)
|
||||
{
|
||||
// 如果启用了保持收纳模式,则不自动展开浮动栏
|
||||
unfoldFloatingBarByUser = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 原有的逻辑:软件退出后自动展开浮动栏
|
||||
if (isFloatingBarFolded && !foldFloatingBarByUser) UnFoldFloatingBar_MouseUp(new object(), null);
|
||||
unfoldFloatingBarByUser = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
@@ -650,5 +834,92 @@ namespace Ink_Canvas
|
||||
timerCheckAutoUpdateWithSilence.Start();
|
||||
}
|
||||
}
|
||||
|
||||
// 检查更新失败重试定时器事件处理
|
||||
private async void timerCheckAutoUpdateRetry_Elapsed(object sender, ElapsedEventArgs e)
|
||||
{
|
||||
// 停止定时器,避免重复触发
|
||||
timerCheckAutoUpdateRetry.Stop();
|
||||
|
||||
try
|
||||
{
|
||||
// 检查是否启用了自动更新
|
||||
if (!Settings.Startup.IsAutoUpdate)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | Auto update is disabled, stopping retry timer");
|
||||
return;
|
||||
}
|
||||
|
||||
// 增加重试计数
|
||||
updateCheckRetryCount++;
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Retry check attempt {updateCheckRetryCount}/{MAX_UPDATE_CHECK_RETRIES}");
|
||||
|
||||
// 检查是否超过最大重试次数
|
||||
if (updateCheckRetryCount > MAX_UPDATE_CHECK_RETRIES)
|
||||
{
|
||||
LogHelper.WriteLogToFile("AutoUpdate | Maximum retry attempts reached, stopping retry timer", LogHelper.LogType.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行更新检查
|
||||
LogHelper.WriteLogToFile("AutoUpdate | Retrying update check after failure");
|
||||
|
||||
// 清除之前的更新状态
|
||||
AvailableLatestVersion = null;
|
||||
AvailableLatestLineGroup = null;
|
||||
|
||||
// 使用当前选择的更新通道检查更新
|
||||
var (remoteVersion, lineGroup, apiReleaseNotes) = await AutoUpdateHelper.CheckForUpdates(Settings.Startup.UpdateChannel);
|
||||
AvailableLatestVersion = remoteVersion;
|
||||
AvailableLatestLineGroup = lineGroup;
|
||||
|
||||
if (AvailableLatestVersion != null)
|
||||
{
|
||||
// 检查更新成功,重置重试计数
|
||||
updateCheckRetryCount = 0;
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Retry successful, found new version: {AvailableLatestVersion}");
|
||||
|
||||
// 停止重试定时器,因为已经找到了更新
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 检查更新仍然失败,继续重试
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Retry {updateCheckRetryCount} failed, will retry in 10 minutes");
|
||||
|
||||
// 重新启动定时器,10分钟后再次尝试
|
||||
timerCheckAutoUpdateRetry.Start();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Error in retry check: {ex.Message}", LogHelper.LogType.Error);
|
||||
|
||||
// 出错时也重新启动定时器,稍后再检查
|
||||
if (updateCheckRetryCount <= MAX_UPDATE_CHECK_RETRIES)
|
||||
{
|
||||
timerCheckAutoUpdateRetry.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置更新检查重试状态
|
||||
public void ResetUpdateCheckRetry()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 停止重试定时器
|
||||
timerCheckAutoUpdateRetry.Stop();
|
||||
|
||||
// 重置重试计数
|
||||
updateCheckRetryCount = 0;
|
||||
|
||||
LogHelper.WriteLogToFile("AutoUpdate | Update check retry state reset");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"AutoUpdate | Error resetting retry state: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows.Media.Imaging;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Ink_Canvas
|
||||
@@ -22,30 +22,138 @@ namespace Ink_Canvas
|
||||
private bool isSingleFingerDragMode;
|
||||
private Point centerPoint = new Point(0, 0);
|
||||
private InkCanvasEditingMode lastInkCanvasEditingMode = InkCanvasEditingMode.Ink;
|
||||
private DateTime lastTouchDownTime = DateTime.MinValue;
|
||||
private const double MULTI_TOUCH_DELAY_MS = 100;
|
||||
private bool isMultiTouchTimerActive = false;
|
||||
|
||||
/// <summary>
|
||||
/// </summary>
|
||||
/// 保存画布上的非笔画元素(如图片、媒体元素等)
|
||||
/// </summary>
|
||||
private List<UIElement> PreserveNonStrokeElements()
|
||||
{
|
||||
var preservedElements = new List<UIElement>();
|
||||
|
||||
// 遍历inkCanvas的所有子元素
|
||||
// 遍历inkCanvas的所有子元素,创建副本而不是直接引用
|
||||
for (int i = inkCanvas.Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var child = inkCanvas.Children[i];
|
||||
|
||||
// 保存图片、媒体元素等非笔画相关的UI元素
|
||||
if (child is Image || child is MediaElement ||
|
||||
(child is Border border && border.Name != "AdvancedEraserOverlay"))
|
||||
(child is Border border && border.Name != "EraserOverlayCanvas"))
|
||||
{
|
||||
preservedElements.Add(child);
|
||||
// 创建元素的深拷贝,避免直接引用导致的问题
|
||||
var clonedElement = CloneUIElement(child);
|
||||
if (clonedElement != null)
|
||||
{
|
||||
preservedElements.Add(clonedElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return preservedElements;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 克隆UI元素,创建深拷贝
|
||||
/// </summary>
|
||||
private UIElement CloneUIElement(UIElement originalElement)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (originalElement is Image originalImage)
|
||||
{
|
||||
var clonedImage = new Image();
|
||||
|
||||
// 复制图片源
|
||||
if (originalImage.Source is BitmapSource bitmapSource)
|
||||
{
|
||||
clonedImage.Source = bitmapSource;
|
||||
}
|
||||
|
||||
// 复制属性
|
||||
clonedImage.Width = originalImage.Width;
|
||||
clonedImage.Height = originalImage.Height;
|
||||
clonedImage.Stretch = originalImage.Stretch;
|
||||
clonedImage.StretchDirection = originalImage.StretchDirection;
|
||||
clonedImage.Name = originalImage.Name;
|
||||
clonedImage.IsHitTestVisible = originalImage.IsHitTestVisible;
|
||||
clonedImage.Focusable = originalImage.Focusable;
|
||||
clonedImage.Cursor = originalImage.Cursor;
|
||||
clonedImage.IsManipulationEnabled = originalImage.IsManipulationEnabled;
|
||||
|
||||
// 复制位置
|
||||
InkCanvas.SetLeft(clonedImage, InkCanvas.GetLeft(originalImage));
|
||||
InkCanvas.SetTop(clonedImage, InkCanvas.GetTop(originalImage));
|
||||
|
||||
// 复制变换
|
||||
if (originalImage.RenderTransform != null)
|
||||
{
|
||||
clonedImage.RenderTransform = originalImage.RenderTransform.Clone();
|
||||
}
|
||||
|
||||
return clonedImage;
|
||||
}
|
||||
else if (originalElement is MediaElement originalMedia)
|
||||
{
|
||||
var clonedMedia = new MediaElement();
|
||||
|
||||
// 复制媒体属性
|
||||
clonedMedia.Source = originalMedia.Source;
|
||||
clonedMedia.Width = originalMedia.Width;
|
||||
clonedMedia.Height = originalMedia.Height;
|
||||
clonedMedia.Name = originalMedia.Name;
|
||||
clonedMedia.IsHitTestVisible = originalMedia.IsHitTestVisible;
|
||||
clonedMedia.Focusable = originalMedia.Focusable;
|
||||
|
||||
// 复制位置
|
||||
InkCanvas.SetLeft(clonedMedia, InkCanvas.GetLeft(originalMedia));
|
||||
InkCanvas.SetTop(clonedMedia, InkCanvas.GetTop(originalMedia));
|
||||
|
||||
// 复制变换
|
||||
if (originalMedia.RenderTransform != null)
|
||||
{
|
||||
clonedMedia.RenderTransform = originalMedia.RenderTransform.Clone();
|
||||
}
|
||||
|
||||
return clonedMedia;
|
||||
}
|
||||
else if (originalElement is Border originalBorder)
|
||||
{
|
||||
var clonedBorder = new Border();
|
||||
|
||||
// 复制边框属性
|
||||
clonedBorder.Width = originalBorder.Width;
|
||||
clonedBorder.Height = originalBorder.Height;
|
||||
clonedBorder.Name = originalBorder.Name;
|
||||
clonedBorder.IsHitTestVisible = originalBorder.IsHitTestVisible;
|
||||
clonedBorder.Focusable = originalBorder.Focusable;
|
||||
clonedBorder.Background = originalBorder.Background;
|
||||
clonedBorder.BorderBrush = originalBorder.BorderBrush;
|
||||
clonedBorder.BorderThickness = originalBorder.BorderThickness;
|
||||
clonedBorder.CornerRadius = originalBorder.CornerRadius;
|
||||
|
||||
// 复制位置
|
||||
InkCanvas.SetLeft(clonedBorder, InkCanvas.GetLeft(originalBorder));
|
||||
InkCanvas.SetTop(clonedBorder, InkCanvas.GetTop(originalBorder));
|
||||
|
||||
// 复制变换
|
||||
if (originalBorder.RenderTransform != null)
|
||||
{
|
||||
clonedBorder.RenderTransform = originalBorder.RenderTransform.Clone();
|
||||
}
|
||||
|
||||
return clonedBorder;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"克隆UI元素失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 恢复之前保存的非笔画元素到画布
|
||||
/// </summary>
|
||||
@@ -55,11 +163,15 @@ namespace Ink_Canvas
|
||||
|
||||
foreach (var element in preservedElements)
|
||||
{
|
||||
// 确保元素没有父容器再添加到inkCanvas
|
||||
if (element is FrameworkElement fe && fe.Parent == null)
|
||||
try
|
||||
{
|
||||
// 由于现在使用的是克隆的元素,不需要检查Parent属性
|
||||
inkCanvas.Children.Add(element);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"恢复非笔画元素失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,17 +243,22 @@ namespace Ink_Canvas
|
||||
HideSubPanels(); // 书写时自动隐藏二级菜单
|
||||
}
|
||||
|
||||
// 修复:几何绘制模式下完全禁止触摸轨迹收集
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
|
||||
isTouchDown = true;
|
||||
ViewboxFloatingBar.IsHitTestVisible = false;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
||||
|
||||
// 设置起始点
|
||||
if (NeedUpdateIniP()) iniP = e.GetTouchPoint(inkCanvas).Position;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 只保留普通橡皮逻辑
|
||||
TouchDownPointsList[e.TouchDevice.Id] = InkCanvasEditingMode.None;
|
||||
inkCanvas.EraserShape = new EllipseStylusShape(50, 50);
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
|
||||
{
|
||||
@@ -163,29 +280,30 @@ namespace Ink_Canvas
|
||||
return;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"MainWindow_StylusDown 被调用,笔尾状态: {e.StylusDevice.Inverted}, 当前 drawingShapeMode: {drawingShapeMode}, 当前 EditingMode: {inkCanvas.EditingMode}");
|
||||
|
||||
// 新增:根据是否为笔尾自动切换橡皮擦/画笔模式
|
||||
// 根据是否为笔尾自动切换橡皮擦/画笔模式
|
||||
if (e.StylusDevice.Inverted)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
|
||||
LogHelper.WriteLogToFile("检测到笔尾,设置 EditingMode 为 EraseByPoint");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 修复:几何绘制模式下完全禁止触摸轨迹收集
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
LogHelper.WriteLogToFile("几何绘制模式,设置 EditingMode 为 None");
|
||||
|
||||
isTouchDown = true;
|
||||
ViewboxFloatingBar.IsHitTestVisible = false;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
||||
|
||||
// 设置起始点
|
||||
if (NeedUpdateIniP()) iniP = e.GetPosition(inkCanvas);
|
||||
|
||||
return;
|
||||
}
|
||||
// 修复:保持当前的线擦模式,不要强制切换到Ink模式
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
LogHelper.WriteLogToFile("设置 EditingMode 为 Ink");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -229,23 +347,56 @@ namespace Ink_Canvas
|
||||
|
||||
private async void MainWindow_StylusUp(object sender, StylusEventArgs e)
|
||||
{
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
// 重置触摸状态
|
||||
isTouchDown = false;
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
// 对于双曲线等需要多步绘制的图形,手写笔抬起时应该进入下一步
|
||||
if (drawingShapeMode == 24 || drawingShapeMode == 25)
|
||||
{
|
||||
if (drawMultiStepShapeCurrentStep == 0)
|
||||
{
|
||||
// 第一笔完成,进入第二笔
|
||||
drawMultiStepShapeCurrentStep = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 第二笔完成,完成绘制
|
||||
var mouseArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left)
|
||||
{
|
||||
RoutedEvent = MouseLeftButtonUpEvent,
|
||||
Source = inkCanvas
|
||||
};
|
||||
inkCanvas_MouseUp(inkCanvas, mouseArgs);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 其他单步绘制的图形,手写笔抬起时完成绘制
|
||||
var mouseArgs = new MouseButtonEventArgs(Mouse.PrimaryDevice, 0, MouseButton.Left)
|
||||
{
|
||||
RoutedEvent = MouseLeftButtonUpEvent,
|
||||
Source = inkCanvas
|
||||
};
|
||||
inkCanvas_MouseUp(inkCanvas, mouseArgs);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile($"MainWindow_StylusUp 被调用,EditingMode: {inkCanvas.EditingMode}, EnableInkFade: {Settings.Canvas.EnableInkFade}");
|
||||
|
||||
var stroke = GetStrokeVisual(e.StylusDevice.Id).Stroke;
|
||||
LogHelper.WriteLogToFile($"获取到墨迹,StylusPoints数量: {stroke.StylusPoints.Count}");
|
||||
|
||||
// 正常模式:添加到画布并参与墨迹纠正
|
||||
// 墨迹渐隐功能现在在 StrokeCollected 事件中统一处理所有输入方式
|
||||
LogHelper.WriteLogToFile("StylusUp: 添加墨迹到画布");
|
||||
|
||||
inkCanvas.Strokes.Add(stroke);
|
||||
await Task.Delay(5); // 避免渲染墨迹完成前预览墨迹被删除导致墨迹闪烁
|
||||
await Task.Delay(5);
|
||||
inkCanvas.Children.Remove(GetVisualCanvas(e.StylusDevice.Id));
|
||||
|
||||
inkCanvas_StrokeCollected(inkCanvas,
|
||||
new InkCanvasStrokeCollectedEventArgs(stroke));
|
||||
new InkCanvasStrokeCollectedEventArgs(stroke));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -284,6 +435,16 @@ namespace Ink_Canvas
|
||||
{
|
||||
try
|
||||
{
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
if (isTouchDown)
|
||||
{
|
||||
Point stylusPoint = e.GetPosition(inkCanvas);
|
||||
MouseTouchMove(stylusPoint);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetTouchDownPointsList(e.StylusDevice.Id) != InkCanvasEditingMode.None) return;
|
||||
try
|
||||
{
|
||||
@@ -369,14 +530,22 @@ namespace Ink_Canvas
|
||||
}
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Select)
|
||||
{
|
||||
// 套索选状态下只return,保证套索选可用
|
||||
// 套索选状态下不直接return,允许触摸事件继续处理
|
||||
dec.Add(e.TouchDevice.Id);
|
||||
return;
|
||||
}
|
||||
// 修复:几何绘制模式下完全禁止触摸轨迹收集
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹被收集
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
|
||||
// 设置触摸状态,类似鼠标事件处理
|
||||
isTouchDown = true;
|
||||
ViewboxFloatingBar.IsHitTestVisible = false;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
||||
|
||||
// 设置起始点
|
||||
if (NeedUpdateIniP()) iniP = e.GetTouchPoint(inkCanvas).Position;
|
||||
|
||||
return;
|
||||
}
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
@@ -399,11 +568,13 @@ namespace Ink_Canvas
|
||||
private InkCanvasEditingMode palmEraserLastEditingMode = InkCanvasEditingMode.Ink;
|
||||
private bool palmEraserLastIsHighlighter;
|
||||
private bool palmEraserWasEnabledBeforeMultiTouch;
|
||||
private bool palmEraserTouchDownHandled; // 新增:标记手掌擦触摸按下是否已处理
|
||||
private DateTime palmEraserActivationTime; // 新增:记录手掌擦激活时间
|
||||
private const int PALM_ERASER_TIMEOUT_MS = 3000; // 修改:减少手掌擦超时时间(3秒)
|
||||
private DispatcherTimer palmEraserRecoveryTimer; // 新增:手掌擦恢复定时器
|
||||
private HashSet<int> palmEraserTouchIds = new HashSet<int>(); // 新增:记录参与手掌擦的触摸点ID
|
||||
|
||||
public double GetTouchBoundWidth(TouchEventArgs e)
|
||||
{
|
||||
var args = e.GetTouchPoint(null).Bounds;
|
||||
if (!Settings.Advanced.IsQuadIR) return args.Width;
|
||||
else return Math.Sqrt(args.Width * args.Height); // 四边红外
|
||||
}
|
||||
|
||||
private void inkCanvas_PreviewTouchDown(object sender, TouchEventArgs e)
|
||||
{
|
||||
@@ -425,18 +596,16 @@ namespace Ink_Canvas
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 修复:几何绘制模式下完全禁止触摸轨迹收集
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
// 确保几何绘制模式下不切换到Ink模式,避免触摸轨迹收集
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
// 几何绘制模式下不记录触摸点,避免触摸轨迹被收集
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
inkCanvas.CaptureTouch(e.TouchDevice);
|
||||
ViewboxFloatingBar.IsHitTestVisible = false;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
||||
|
||||
// 修复:几何绘制模式下,只记录几何绘制的起点,不记录触摸轨迹
|
||||
isTouchDown = true;
|
||||
|
||||
if (dec.Count == 0)
|
||||
{
|
||||
var inkTouchPoint = e.GetTouchPoint(inkCanvas);
|
||||
@@ -466,80 +635,70 @@ namespace Ink_Canvas
|
||||
inkCanvas.CaptureTouch(e.TouchDevice);
|
||||
ViewboxFloatingBar.IsHitTestVisible = false;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = false;
|
||||
lastTouchDownTime = DateTime.Now;
|
||||
dec.Add(e.TouchDevice.Id);
|
||||
|
||||
// Palm Eraser 逻辑 - 优化:改进手掌判定条件,使用设备提供的触摸面积信息
|
||||
if (Settings.Canvas.EnablePalmEraser && dec.Count >= 2 && !isPalmEraserActive && !palmEraserTouchDownHandled)
|
||||
// Palm Eraser 逻辑
|
||||
if (Settings.Canvas.EnablePalmEraser && !isPalmEraserActive)
|
||||
{
|
||||
touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
var size = touchPoint.Size; // 使用设备提供的触摸面积信息
|
||||
var bounds = touchPoint.Bounds; // 保留bounds用于宽高比计算
|
||||
double boundWidth = GetTouchBoundWidth(e);
|
||||
|
||||
// 根据敏感度设置调整判定参数
|
||||
double palmAreaThreshold; // 改为面积阈值
|
||||
double aspectRatioThreshold;
|
||||
int minTouchPoints;
|
||||
|
||||
switch (Settings.Canvas.PalmEraserSensitivity)
|
||||
if ((Settings.Advanced.TouchMultiplier != 0 || !Settings.Advanced.IsSpecialScreen)
|
||||
&& (boundWidth > BoundsWidth))
|
||||
{
|
||||
case 0: // 低敏感度 - 更严格的判定
|
||||
palmAreaThreshold = 6400; // 80*80的面积
|
||||
aspectRatioThreshold = 0.4;
|
||||
minTouchPoints = 4;
|
||||
break;
|
||||
case 1: // 中敏感度 - 平衡的判定
|
||||
palmAreaThreshold = 3600; // 60*60的面积
|
||||
aspectRatioThreshold = 0.3;
|
||||
minTouchPoints = 3;
|
||||
break;
|
||||
case 2: // 高敏感度 - 较宽松的判定
|
||||
default:
|
||||
palmAreaThreshold = 2500; // 50*50的面积
|
||||
aspectRatioThreshold = 0.25;
|
||||
minTouchPoints = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
// 计算触摸面积(使用设备提供的Size)
|
||||
double touchArea = size.Width * size.Height;
|
||||
|
||||
// 计算宽高比(使用Bounds确保准确性)
|
||||
double aspectRatio = Math.Min(bounds.Width, bounds.Height) / Math.Max(bounds.Width, bounds.Height);
|
||||
|
||||
// 改进的手掌判定条件:使用面积而不是单独的宽高
|
||||
bool isLargeTouch = touchArea >= palmAreaThreshold;
|
||||
bool isPalmLikeShape = aspectRatio >= aspectRatioThreshold;
|
||||
bool hasMultipleTouchPoints = dec.Count >= minTouchPoints;
|
||||
|
||||
// 新增:额外的判定条件提高准确性
|
||||
bool isReasonableSize = size.Width >= 20 && size.Height >= 20 && size.Width <= 200 && size.Height <= 200; // 合理的触摸尺寸范围
|
||||
bool isNotTooElongated = aspectRatio >= 0.2; // 避免过于细长的触摸(可能是手指)
|
||||
bool hasEnoughArea = touchArea >= 400; // 最小面积要求,避免小面积误判
|
||||
|
||||
if (isLargeTouch && isPalmLikeShape && hasMultipleTouchPoints && isReasonableSize && isNotTooElongated && hasEnoughArea)
|
||||
{
|
||||
// 记录当前编辑模式和高光状态
|
||||
palmEraserLastEditingMode = inkCanvas.EditingMode;
|
||||
palmEraserLastIsHighlighter = drawingAttributes.IsHighlighter;
|
||||
|
||||
// 记录参与手掌擦的触摸点ID
|
||||
palmEraserTouchIds.Clear();
|
||||
foreach (int touchId in dec)
|
||||
// 根据敏感度调整阈值倍数
|
||||
double thresholdMultiplier;
|
||||
switch (Settings.Canvas.PalmEraserSensitivity)
|
||||
{
|
||||
palmEraserTouchIds.Add(touchId);
|
||||
case 0: // 低敏感度
|
||||
thresholdMultiplier = 3.0;
|
||||
break;
|
||||
case 1: // 中敏感度
|
||||
thresholdMultiplier = 2.5;
|
||||
break;
|
||||
case 2: // 高敏感度
|
||||
default:
|
||||
thresholdMultiplier = 2.0;
|
||||
break;
|
||||
}
|
||||
|
||||
// 切换为橡皮擦
|
||||
EraserIcon_Click(null, null);
|
||||
isPalmEraserActive = true;
|
||||
palmEraserActivationTime = DateTime.Now; // 记录激活时间
|
||||
palmEraserTouchDownHandled = true; // 标记已处理
|
||||
double EraserThresholdValue = Settings.Startup.IsEnableNibMode ?
|
||||
Settings.Advanced.NibModeBoundsWidthThresholdValue :
|
||||
Settings.Advanced.FingerModeBoundsWidthThresholdValue;
|
||||
|
||||
// 启动恢复定时器,防止卡死
|
||||
StartPalmEraserRecoveryTimer();
|
||||
if (boundWidth > BoundsWidth * EraserThresholdValue * thresholdMultiplier)
|
||||
{
|
||||
// 记录当前编辑模式和高光状态
|
||||
palmEraserLastEditingMode = inkCanvas.EditingMode;
|
||||
palmEraserLastIsHighlighter = drawingAttributes.IsHighlighter;
|
||||
|
||||
// 记录日志
|
||||
LogHelper.WriteLogToFile($"Palm eraser activated - Sensitivity: {Settings.Canvas.PalmEraserSensitivity}, Touch area: {touchArea:F0}, Size: {size.Width}x{size.Height}, Bounds: {bounds.Width}x{bounds.Height}, Aspect ratio: {aspectRatio:F2}, Touch points: {dec.Count}, Reasonable size: {isReasonableSize}, Not elongated: {isNotTooElongated}, Enough area: {hasEnoughArea}");
|
||||
// 动态调整橡皮大小
|
||||
boundWidth *= (Settings.Startup.IsEnableNibMode ?
|
||||
Settings.Advanced.NibModeBoundsWidthEraserSize :
|
||||
Settings.Advanced.FingerModeBoundsWidthEraserSize);
|
||||
|
||||
if (Settings.Advanced.IsSpecialScreen)
|
||||
boundWidth *= Settings.Advanced.TouchMultiplier;
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.EraseByPoint;
|
||||
isPalmEraserActive = true;
|
||||
|
||||
// 启用橡皮擦覆盖层显示手掌擦样式
|
||||
EnableEraserOverlay();
|
||||
// 更新橡皮擦大小以匹配手掌擦面积
|
||||
eraserWidth = boundWidth;
|
||||
UpdateEraserStyle();
|
||||
// 显示初始橡皮擦反馈位置
|
||||
touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
EraserOverlay_PointerDown(sender);
|
||||
EraserOverlay_PointerMove(sender, touchPoint.Position);
|
||||
if (Settings.Canvas.IsShowCursor)
|
||||
{
|
||||
inkCanvas.ForceCursor = false;
|
||||
inkCanvas.UseCustomCursor = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -549,7 +708,6 @@ namespace Ink_Canvas
|
||||
touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
centerPoint = touchPoint.Position;
|
||||
|
||||
// 修复:只允许在此处赋值iniP,防止TouchMove等其他地方覆盖,保证几何绘制起点一致
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
// 对于双曲线绘制,第一笔时记录起点,第二笔时不更新起点
|
||||
@@ -578,8 +736,29 @@ namespace Ink_Canvas
|
||||
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.None ||
|
||||
inkCanvas.EditingMode == InkCanvasEditingMode.Select) return;
|
||||
var timeSinceLastTouch = (DateTime.Now - lastTouchDownTime).TotalMilliseconds;
|
||||
if (timeSinceLastTouch < MULTI_TOUCH_DELAY_MS && inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
{
|
||||
if (!isMultiTouchTimerActive)
|
||||
{
|
||||
isMultiTouchTimerActive = true;
|
||||
var remainingTime = MULTI_TOUCH_DELAY_MS - timeSinceLastTouch;
|
||||
System.Threading.Tasks.Task.Delay((int)remainingTime).ContinueWith(_ =>
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
if (dec.Count > 1 && inkCanvas.EditingMode == InkCanvasEditingMode.Ink)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
isMultiTouchTimerActive = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
lastInkCanvasEditingMode = inkCanvas.EditingMode;
|
||||
// 修复:几何绘制模式下禁止切回Ink
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& drawingShapeMode == 0)
|
||||
@@ -589,6 +768,17 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void inkCanvas_PreviewTouchMove(object sender, TouchEventArgs e)
|
||||
{
|
||||
|
||||
// 如果手掌擦激活,更新橡皮擦反馈位置
|
||||
if (isPalmEraserActive)
|
||||
{
|
||||
var touchPoint = e.GetTouchPoint(inkCanvas);
|
||||
EraserOverlay_PointerMove(sender, touchPoint.Position);
|
||||
}
|
||||
}
|
||||
|
||||
private void inkCanvas_PreviewTouchUp(object sender, TouchEventArgs e)
|
||||
{
|
||||
// 橡皮状态下不做任何切换,直接return,保证橡皮可持续
|
||||
@@ -600,24 +790,25 @@ namespace Ink_Canvas
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
// Palm Eraser 逻辑:优化状态恢复机制
|
||||
// Palm Eraser 逻辑
|
||||
dec.Remove(e.TouchDevice.Id);
|
||||
|
||||
// 如果是手掌擦的触摸点,从记录中移除
|
||||
if (palmEraserTouchIds.Contains(e.TouchDevice.Id))
|
||||
// 重置多触控点定时器状态
|
||||
if (dec.Count <= 1)
|
||||
{
|
||||
palmEraserTouchIds.Remove(e.TouchDevice.Id);
|
||||
isMultiTouchTimerActive = false;
|
||||
}
|
||||
|
||||
// 当所有手掌擦触摸点都抬起时,恢复原编辑模式
|
||||
if (isPalmEraserActive && palmEraserTouchIds.Count == 0)
|
||||
|
||||
// 当手掌擦激活且所有触摸点都抬起时,恢复原编辑模式
|
||||
if (isPalmEraserActive && dec.Count == 0)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Palm eraser recovery triggered - Touch points remaining: {palmEraserTouchIds.Count}, dec.Count: {dec.Count}");
|
||||
|
||||
LogHelper.WriteLogToFile($"Palm eraser recovery triggered - Touch points remaining: {dec.Count}");
|
||||
|
||||
// 恢复高光状态
|
||||
drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter;
|
||||
|
||||
// 恢复编辑模式 - 优化:改进状态恢复逻辑
|
||||
// 恢复编辑模式
|
||||
try
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
@@ -648,74 +839,24 @@ namespace Ink_Canvas
|
||||
|
||||
// 重置手掌擦状态
|
||||
isPalmEraserActive = false;
|
||||
palmEraserTouchDownHandled = false;
|
||||
palmEraserTouchIds.Clear();
|
||||
|
||||
// 停止恢复定时器
|
||||
StopPalmEraserRecoveryTimer();
|
||||
|
||||
// 确保触摸事件能正常响应
|
||||
inkCanvas.IsHitTestVisible = true;
|
||||
inkCanvas.IsManipulationEnabled = true;
|
||||
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
// 禁用橡皮擦覆盖层
|
||||
DisableEraserOverlay();
|
||||
if (Settings.Canvas.IsShowCursor)
|
||||
{
|
||||
inkCanvas.ForceCursor = true;
|
||||
inkCanvas.UseCustomCursor = true;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("Palm eraser state reset completed");
|
||||
}
|
||||
|
||||
// 新增:超时检测 - 如果手掌擦激活时间过长,强制重置状态
|
||||
if (isPalmEraserActive)
|
||||
{
|
||||
var timeSinceActivation = DateTime.Now - palmEraserActivationTime;
|
||||
if (timeSinceActivation.TotalMilliseconds > PALM_ERASER_TIMEOUT_MS)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Palm eraser timeout detected ({timeSinceActivation.TotalMilliseconds}ms), forcing recovery", LogHelper.LogType.Warning);
|
||||
|
||||
// 强制恢复状态
|
||||
try
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
switch (palmEraserLastEditingMode)
|
||||
{
|
||||
case InkCanvasEditingMode.Ink:
|
||||
PenIcon_Click(null, null);
|
||||
break;
|
||||
case InkCanvasEditingMode.Select:
|
||||
SymbolIconSelect_MouseUp(null, null);
|
||||
break;
|
||||
default:
|
||||
inkCanvas.EditingMode = palmEraserLastEditingMode;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Palm eraser timeout recovery failed: {ex.Message}, forcing to Ink mode", LogHelper.LogType.Error);
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
// 重置所有手掌擦状态
|
||||
isPalmEraserActive = false;
|
||||
palmEraserTouchDownHandled = false;
|
||||
palmEraserTouchIds.Clear();
|
||||
inkCanvas.IsHitTestVisible = true;
|
||||
inkCanvas.IsManipulationEnabled = true;
|
||||
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
// 停止恢复定时器
|
||||
StopPalmEraserRecoveryTimer();
|
||||
|
||||
LogHelper.WriteLogToFile("Palm eraser timeout recovery completed");
|
||||
}
|
||||
}
|
||||
// 修复:几何绘制模式下,触摸抬手时应该正确处理,而不是简单模拟鼠标事件
|
||||
if (drawingShapeMode != 0)
|
||||
{
|
||||
isTouchDown = false;
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
// 对于双曲线等需要多步绘制的图形,触摸抬手时应该进入下一步
|
||||
if (drawingShapeMode == 24 || drawingShapeMode == 25)
|
||||
{
|
||||
@@ -773,14 +914,13 @@ namespace Ink_Canvas
|
||||
inkCanvas.EditingMode = lastInkCanvasEditingMode;
|
||||
}
|
||||
|
||||
// 修复:确保手掌擦除后触摸事件能正常响应
|
||||
if (isPalmEraserActive)
|
||||
{
|
||||
LogHelper.WriteLogToFile("Palm eraser force recovery - all touch points cleared");
|
||||
|
||||
|
||||
// 恢复高光状态
|
||||
drawingAttributes.IsHighlighter = palmEraserLastIsHighlighter;
|
||||
|
||||
|
||||
// 恢复编辑模式
|
||||
try
|
||||
{
|
||||
@@ -806,20 +946,22 @@ namespace Ink_Canvas
|
||||
LogHelper.WriteLogToFile($"Palm eraser force recovery failed: {ex.Message}, forcing to Ink mode", LogHelper.LogType.Error);
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
|
||||
// 如果手掌擦还在激活状态但触摸点已清空,强制重置状态
|
||||
isPalmEraserActive = false;
|
||||
palmEraserTouchDownHandled = false;
|
||||
palmEraserTouchIds.Clear(); // 确保清空触摸点ID
|
||||
inkCanvas.IsHitTestVisible = true;
|
||||
inkCanvas.IsManipulationEnabled = true;
|
||||
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
// 停止恢复定时器
|
||||
StopPalmEraserRecoveryTimer();
|
||||
|
||||
|
||||
DisableEraserOverlay();
|
||||
if (Settings.Canvas.IsShowCursor)
|
||||
{
|
||||
inkCanvas.ForceCursor = true;
|
||||
inkCanvas.UseCustomCursor = true;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("Palm eraser force recovery completed");
|
||||
}
|
||||
}
|
||||
@@ -846,13 +988,12 @@ namespace Ink_Canvas
|
||||
private void Main_Grid_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
|
||||
{
|
||||
if (e.Manipulators.Count() != 0) return;
|
||||
// 修复:几何绘制模式下不自动切换到Ink模式,避免触摸轨迹被收集
|
||||
if (drawingShapeMode == 0
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke)
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.Select)
|
||||
{
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
// 修复:确保多指手势完成后正确更新lastInkCanvasEditingMode
|
||||
lastInkCanvasEditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
}
|
||||
@@ -864,7 +1005,39 @@ namespace Ink_Canvas
|
||||
return;
|
||||
// 三指及以上禁止缩放
|
||||
bool disableScale = dec.Count >= 3;
|
||||
if (isInMultiTouchMode || !Settings.Gesture.IsEnableTwoFingerGesture) return;
|
||||
|
||||
if (isInMultiTouchMode) return;
|
||||
|
||||
if (dec.Count == 0 && (isSingleFingerDragMode || isInMultiTouchMode))
|
||||
{
|
||||
ResetTouchStates();
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果是单指拖动选中的墨迹,允许处理
|
||||
if (dec.Count == 1 && inkCanvas.GetSelectedStrokes().Count > 0)
|
||||
{
|
||||
var md = e.DeltaManipulation;
|
||||
var trans = md.Translation; // 获得位移矢量
|
||||
|
||||
if (trans.X != 0 || trans.Y != 0)
|
||||
{
|
||||
var m = new Matrix();
|
||||
m.Translate(trans.X, trans.Y); // 移动
|
||||
|
||||
var strokes = inkCanvas.GetSelectedStrokes();
|
||||
foreach (var stroke in strokes)
|
||||
{
|
||||
stroke.Transform(m, false);
|
||||
}
|
||||
|
||||
// 更新选择框位置
|
||||
updateBorderStrokeSelectionControlLocation();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Settings.Gesture.IsEnableTwoFingerGesture) return;
|
||||
if ((dec.Count >= 2 && (Settings.PowerPointSettings.IsEnableTwoFingerGestureInPresentationMode ||
|
||||
StackPanelPPTControls.Visibility != Visibility.Visible ||
|
||||
StackPanelPPTButtons.Visibility == Visibility.Collapsed)) ||
|
||||
@@ -914,13 +1087,6 @@ namespace Ink_Canvas
|
||||
break;
|
||||
}
|
||||
|
||||
if (!Settings.Gesture.IsEnableTwoFingerZoom) continue;
|
||||
try
|
||||
{
|
||||
stroke.DrawingAttributes.Width *= md.Scale.X;
|
||||
stroke.DrawingAttributes.Height *= md.Scale.Y;
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -938,12 +1104,15 @@ namespace Ink_Canvas
|
||||
catch { }
|
||||
}
|
||||
|
||||
;
|
||||
// 同时变换画布上的图片元素
|
||||
TransformCanvasImages(m);
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var stroke in inkCanvas.Strokes) stroke.Transform(m, false);
|
||||
;
|
||||
|
||||
// 同时变换画布上的图片元素
|
||||
TransformCanvasImages(m);
|
||||
}
|
||||
|
||||
foreach (var circle in circles)
|
||||
@@ -961,6 +1130,86 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 变换画布上的图片元素,使其与墨迹同步移动
|
||||
/// </summary>
|
||||
private void TransformCanvasImages(Matrix matrix)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 遍历inkCanvas的所有子元素,找到图片元素
|
||||
for (int i = inkCanvas.Children.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var child = inkCanvas.Children[i];
|
||||
|
||||
if (child is Image image)
|
||||
{
|
||||
// 应用矩阵变换到图片
|
||||
ApplyMatrixTransformToImage(image, matrix);
|
||||
}
|
||||
else if (child is MediaElement mediaElement)
|
||||
{
|
||||
// 对媒体元素也应用变换
|
||||
ApplyMatrixTransformToMediaElement(mediaElement, matrix);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"变换画布图片失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对图片应用矩阵变换
|
||||
/// </summary>
|
||||
private void ApplyMatrixTransformToImage(Image image, Matrix matrix)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取图片的RenderTransform,如果不存在则创建新的TransformGroup
|
||||
TransformGroup transformGroup = image.RenderTransform as TransformGroup;
|
||||
if (transformGroup == null)
|
||||
{
|
||||
transformGroup = new TransformGroup();
|
||||
image.RenderTransform = transformGroup;
|
||||
}
|
||||
|
||||
// 创建新的MatrixTransform并添加到变换组
|
||||
var matrixTransform = new MatrixTransform(matrix);
|
||||
transformGroup.Children.Add(matrixTransform);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用图片变换失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 对媒体元素应用矩阵变换
|
||||
/// </summary>
|
||||
private void ApplyMatrixTransformToMediaElement(MediaElement mediaElement, Matrix matrix)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取媒体元素的RenderTransform,如果不存在则创建新的TransformGroup
|
||||
TransformGroup transformGroup = mediaElement.RenderTransform as TransformGroup;
|
||||
if (transformGroup == null)
|
||||
{
|
||||
transformGroup = new TransformGroup();
|
||||
mediaElement.RenderTransform = transformGroup;
|
||||
}
|
||||
|
||||
// 创建新的MatrixTransform并添加到变换组
|
||||
var matrixTransform = new MatrixTransform(matrix);
|
||||
transformGroup.Children.Add(matrixTransform);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用媒体元素变换失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 退出多指书写模式,恢复InkCanvas的TouchDown事件绑定
|
||||
private void ExitMultiTouchModeIfNeeded()
|
||||
{
|
||||
@@ -971,7 +1220,6 @@ namespace Ink_Canvas
|
||||
inkCanvas.StylusUp -= MainWindow_StylusUp;
|
||||
inkCanvas.TouchDown -= MainWindow_TouchDown;
|
||||
inkCanvas.TouchDown += Main_Grid_TouchDown;
|
||||
// 修复:几何绘制模式下不自动切换到Ink模式,避免触摸轨迹被收集
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& drawingShapeMode == 0)
|
||||
@@ -1004,7 +1252,6 @@ namespace Ink_Canvas
|
||||
inkCanvas.StylusUp += MainWindow_StylusUp;
|
||||
inkCanvas.TouchDown += MainWindow_TouchDown;
|
||||
inkCanvas.TouchDown -= Main_Grid_TouchDown;
|
||||
// 修复:几何绘制模式下不自动切换到Ink模式,避免触摸轨迹被收集
|
||||
if (inkCanvas.EditingMode != InkCanvasEditingMode.EraseByPoint
|
||||
&& inkCanvas.EditingMode != InkCanvasEditingMode.EraseByStroke
|
||||
&& drawingShapeMode == 0)
|
||||
@@ -1025,87 +1272,6 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动手掌擦恢复定时器,防止卡死状态
|
||||
/// </summary>
|
||||
private void StartPalmEraserRecoveryTimer()
|
||||
{
|
||||
if (palmEraserRecoveryTimer == null)
|
||||
{
|
||||
palmEraserRecoveryTimer = new DispatcherTimer();
|
||||
palmEraserRecoveryTimer.Interval = TimeSpan.FromMilliseconds(1000); // 每秒检查一次
|
||||
palmEraserRecoveryTimer.Tick += PalmEraserRecoveryTimer_Tick;
|
||||
}
|
||||
|
||||
palmEraserRecoveryTimer.Start();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止手掌擦恢复定时器
|
||||
/// </summary>
|
||||
private void StopPalmEraserRecoveryTimer()
|
||||
{
|
||||
if (palmEraserRecoveryTimer != null)
|
||||
{
|
||||
palmEraserRecoveryTimer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手掌擦恢复定时器事件处理
|
||||
/// </summary>
|
||||
private void PalmEraserRecoveryTimer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
if (!isPalmEraserActive) return;
|
||||
|
||||
// 检查是否超时
|
||||
var timeSinceActivation = DateTime.Now - palmEraserActivationTime;
|
||||
if (timeSinceActivation.TotalMilliseconds > PALM_ERASER_TIMEOUT_MS)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Palm eraser recovery timer triggered, forcing recovery after {timeSinceActivation.TotalMilliseconds}ms", LogHelper.LogType.Warning);
|
||||
|
||||
// 强制恢复状态
|
||||
try
|
||||
{
|
||||
if (inkCanvas.EditingMode == InkCanvasEditingMode.EraseByPoint)
|
||||
{
|
||||
switch (palmEraserLastEditingMode)
|
||||
{
|
||||
case InkCanvasEditingMode.Ink:
|
||||
PenIcon_Click(null, null);
|
||||
break;
|
||||
case InkCanvasEditingMode.Select:
|
||||
SymbolIconSelect_MouseUp(null, null);
|
||||
break;
|
||||
default:
|
||||
inkCanvas.EditingMode = palmEraserLastEditingMode;
|
||||
break;
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"Palm eraser timer recovery to mode: {palmEraserLastEditingMode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"Palm eraser recovery timer failed: {ex.Message}, forcing to Ink mode", LogHelper.LogType.Error);
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
// 重置所有手掌擦状态
|
||||
isPalmEraserActive = false;
|
||||
palmEraserTouchDownHandled = false;
|
||||
palmEraserTouchIds.Clear();
|
||||
inkCanvas.IsHitTestVisible = true;
|
||||
inkCanvas.IsManipulationEnabled = true;
|
||||
|
||||
ViewboxFloatingBar.IsHitTestVisible = true;
|
||||
BlackboardUIGridForInkReplay.IsHitTestVisible = true;
|
||||
|
||||
// 停止定时器
|
||||
StopPalmEraserRecoveryTimer();
|
||||
|
||||
LogHelper.WriteLogToFile("Palm eraser timer recovery completed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,5 +201,60 @@ namespace Ink_Canvas
|
||||
}
|
||||
}
|
||||
|
||||
private void DisableAllHotkeysMenuItem_Clicked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var mainWin = (MainWindow)Current.MainWindow;
|
||||
if (mainWin.IsLoaded)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取全局快捷键管理器
|
||||
var hotkeyManagerField = typeof(MainWindow).GetField("_globalHotkeyManager",
|
||||
System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
|
||||
var hotkeyManager = hotkeyManagerField?.GetValue(mainWin) as GlobalHotkeyManager;
|
||||
|
||||
if (hotkeyManager != null)
|
||||
{
|
||||
// 禁用所有快捷键
|
||||
hotkeyManager.DisableHotkeyRegistration();
|
||||
|
||||
// 更新菜单项文本和状态
|
||||
var menuItem = sender as MenuItem;
|
||||
if (menuItem != null)
|
||||
{
|
||||
var headerPanel = menuItem.Header as SimpleStackPanel;
|
||||
if (headerPanel != null)
|
||||
{
|
||||
var textBlock = headerPanel.Children[0] as TextBlock;
|
||||
if (textBlock != null)
|
||||
{
|
||||
if (textBlock.Text == "禁用所有快捷键")
|
||||
{
|
||||
textBlock.Text = "启用所有快捷键";
|
||||
LogHelper.WriteLogToFile("已禁用所有快捷键", LogHelper.LogType.Event);
|
||||
}
|
||||
else
|
||||
{
|
||||
textBlock.Text = "禁用所有快捷键";
|
||||
// 重新启用快捷键
|
||||
hotkeyManager.EnableHotkeyRegistration();
|
||||
LogHelper.WriteLogToFile("已启用所有快捷键", LogHelper.LogType.Event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile("无法获取全局快捷键管理器", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用/启用快捷键时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ using System.Windows;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("CJK_mkp")]
|
||||
[assembly: AssemblyProduct("InkCanvasForClass")]
|
||||
[assembly: AssemblyCopyright("Copyright © HARKOTEK Studio 2024")]
|
||||
[assembly: AssemblyCopyright("Copyright © CJK_mkp 2025")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
@@ -49,5 +49,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.10.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.10.0")]
|
||||
[assembly: AssemblyVersion("1.7.16.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.16.0")]
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Any CPU</Platform>
|
||||
<PublishDir>dist\</PublishDir>
|
||||
<PublishProtocol>FileSystem</PublishProtocol>
|
||||
<_TargetId>Folder</_TargetId>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
-->
|
||||
<Project>
|
||||
</Project>
|
||||
@@ -74,5 +74,14 @@ namespace Ink_Canvas.Properties {
|
||||
return ResourceManager.GetStream("TimerDownNotice", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.IO.UnmanagedMemoryStream similar to System.IO.MemoryStream.
|
||||
/// </summary>
|
||||
internal static UnmanagedMemoryStream ProgressiveAudio {
|
||||
get {
|
||||
return ResourceManager.GetStream("ProgressiveAudio", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,4 +121,7 @@
|
||||
<data name="TimerDownNotice" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\TimerDownNotice.wav;System.IO.MemoryStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="ProgressiveAudio" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>..\Resources\ProgressiveAudio.wav;System.IO.MemoryStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
</root>
|
||||
|
After Width: | Height: | Size: 109 KiB |
|
After Width: | Height: | Size: 44 KiB |
@@ -1,100 +1,100 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="IconBrush" Color="White"></SolidColorBrush>
|
||||
<SolidColorBrush x:Key="IconBrush" Color="{DynamicResource IconForeground}"></SolidColorBrush>
|
||||
<DrawingImage x:Key="CursorIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M22.7989,10.1653L1.14304,1.14304 10.1653,22.7989 12.8305,14.9518 19.6892,21.8105 21.8105,19.6892 14.9518,12.8305 22.7989,10.1653z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M22.7989,10.1653L1.14304,1.14304 10.1653,22.7989 12.8305,14.9518 19.6892,21.8105 21.8105,19.6892 14.9518,12.8305 22.7989,10.1653z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="PenIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M20.4786,1.42438C19.9985,1.23743 19.4847,1.15194 18.9698,1.17319 18.4549,1.19444 17.9499,1.32197 17.4869,1.54789 17.0368,1.76752 16.6358,2.07554 16.3083,2.45361L3.85516,14.9067 9.08243,20.134 21.5311,7.68529C21.9113,7.36382 22.223,6.96912 22.447,6.52438 22.6786,6.06462 22.8113,5.56167 22.8365,5.04763 22.8616,4.5336 22.7787,4.02012 22.593,3.54002 22.4073,3.05994 22.1232,2.62403 21.759,2.25988 21.3949,1.89574 20.9587,1.61132 20.4786,1.42438z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M7.28056,21.1605L2.8286,16.7086 1.15912,22.83 7.28056,21.1605z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M20.4786,1.42438C19.9985,1.23743 19.4847,1.15194 18.9698,1.17319 18.4549,1.19444 17.9499,1.32197 17.4869,1.54789 17.0368,1.76752 16.6358,2.07554 16.3083,2.45361L3.85516,14.9067 9.08243,20.134 21.5311,7.68529C21.9113,7.36382 22.223,6.96912 22.447,6.52438 22.6786,6.06462 22.8113,5.56167 22.8365,5.04763 22.8616,4.5336 22.7787,4.02012 22.593,3.54002 22.4073,3.05994 22.1232,2.62403 21.759,2.25988 21.3949,1.89574 20.9587,1.61132 20.4786,1.42438z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M7.28056,21.1605L2.8286,16.7086 1.15912,22.83 7.28056,21.1605z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="EraserIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M15.6314,20.7262L22.7921,13.5655C24.3494,12.141,24.2819,9.81776,22.8105,8.34633L16.7793,2.31508C15.3547,0.757753,13.0315,0.825236,11.5601,2.29666L4.38099,9.47574 15.6314,20.7262z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M14.2172,22.1404L2.96677,10.89 1.20761,12.6491C-0.34971,14.0737,-0.281711,16.3974,1.18971,17.8688L6.15089,22.83 13.5276,22.83 14.2172,22.1404z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M15.6314,20.7262L22.7921,13.5655C24.3494,12.141,24.2819,9.81776,22.8105,8.34633L16.7793,2.31508C15.3547,0.757753,13.0315,0.825236,11.5601,2.29666L4.38099,9.47574 15.6314,20.7262z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M14.2172,22.1404L2.96677,10.89 1.20761,12.6491C-0.34971,14.0737,-0.281711,16.3974,1.18971,17.8688L6.15089,22.83 13.5276,22.83 14.2172,22.1404z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="TrashBinIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M2.15454,7.07729L2.15454,5.1082 7.07727,5.1082 7.07727,4.12365C7.07727,3.30648 7.47109,2.57791 7.98305,2.0758 8.49501,1.56383 9.22358,1.17001 10.0309,1.17001L13.9691,1.17001C14.7863,1.17001 15.5148,1.56383 16.0169,2.0758 16.5289,2.58776 16.9227,3.31632 16.9227,4.12365L16.9227,5.1082 21.8454,5.1082 21.8454,7.07729 2.15454,7.07729z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F0 M24,24z M0,0z M4.12363,19.7779C4.12363,20.6148 4.51745,21.3729 5.02941,21.8947 5.54138,22.4165 6.26994,22.83 7.07727,22.83L16.9227,22.83C17.7399,22.83 18.4685,22.4165 18.9706,21.8947 19.4825,21.3729 19.8764,20.6148 19.8764,19.7779L19.8764,8.06183 4.12363,8.06183 4.12363,19.7779z M12.9845,11.0155L14.9536,11.0155 14.9536,18.8918 12.9845,18.8918 12.9845,11.0155z M9.04636,11.0155L11.0154,11.0155 11.0154,18.8918 9.04636,18.8918 9.04636,11.0155z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M2.15454,7.07729L2.15454,5.1082 7.07727,5.1082 7.07727,4.12365C7.07727,3.30648 7.47109,2.57791 7.98305,2.0758 8.49501,1.56383 9.22358,1.17001 10.0309,1.17001L13.9691,1.17001C14.7863,1.17001 15.5148,1.56383 16.0169,2.0758 16.5289,2.58776 16.9227,3.31632 16.9227,4.12365L16.9227,5.1082 21.8454,5.1082 21.8454,7.07729 2.15454,7.07729z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F0 M24,24z M0,0z M4.12363,19.7779C4.12363,20.6148 4.51745,21.3729 5.02941,21.8947 5.54138,22.4165 6.26994,22.83 7.07727,22.83L16.9227,22.83C17.7399,22.83 18.4685,22.4165 18.9706,21.8947 19.4825,21.3729 19.8764,20.6148 19.8764,19.7779L19.8764,8.06183 4.12363,8.06183 4.12363,19.7779z M12.9845,11.0155L14.9536,11.0155 14.9536,18.8918 12.9845,18.8918 12.9845,11.0155z M9.04636,11.0155L11.0154,11.0155 11.0154,18.8918 9.04636,18.8918 9.04636,11.0155z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="LassoSelectIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M2.12162,2.12162C2.73093,1.51232,3.55732,1.17001,4.41901,1.17001L5.50201,1.17001 5.50201,3.33601 4.41901,3.33601C4.13178,3.33601 3.85632,3.45011 3.65321,3.65321 3.45011,3.85632 3.33601,4.13178 3.33601,4.41901L3.33601,5.50201 1.17001,5.50201 1.17001,4.41901C1.17001,3.55732,1.51232,2.73093,2.12162,2.12162z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M18.498,1.17001L19.581,1.17001C20.4427,1.17001 21.2691,1.51232 21.8784,2.12162 22.4877,2.73093 22.83,3.55732 22.83,4.41901L22.83,5.50201 20.664,5.50201 20.664,4.41901C20.664,4.13178 20.5499,3.85632 20.3468,3.65321 20.1437,3.45011 19.8682,3.33601 19.581,3.33601L18.498,3.33601 18.498,1.17001z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M3.33601,19.581L3.33601,18.498 1.17001,18.498 1.17001,19.581C1.17001,20.4427 1.51232,21.2691 2.12162,21.8784 2.73093,22.4877 3.55732,22.83 4.41901,22.83L5.50201,22.83 5.50201,20.664 4.41901,20.664C4.13178,20.664 3.85632,20.5499 3.65321,20.3468 3.45011,20.1437 3.33601,19.8682 3.33601,19.581z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M7.66801,1.17001L10.917,1.17001 10.917,3.33601 7.66801,3.33601 7.66801,1.17001z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M12,20.664L7.66801,20.664 7.66801,22.83 12,22.83 12,20.664z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M13.083,1.17001L16.332,1.17001 16.332,3.33601 13.083,3.33601 13.083,1.17001z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M3.33601,10.917L3.33601,7.66801 1.17001,7.66801 1.17001,10.917 3.33601,10.917z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M22.83,7.66801L22.83,12 20.664,12 20.664,7.66801 22.83,7.66801z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M3.33601,16.332L3.33601,13.083 1.17001,13.083 1.17001,16.332 3.33601,16.332z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M10.7469,10.747L22.83,15.781 18.4517,17.2681 22.2785,21.0949 21.0949,22.2785 17.2681,18.4517 15.781,22.83 10.7469,10.747z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M2.12162,2.12162C2.73093,1.51232,3.55732,1.17001,4.41901,1.17001L5.50201,1.17001 5.50201,3.33601 4.41901,3.33601C4.13178,3.33601 3.85632,3.45011 3.65321,3.65321 3.45011,3.85632 3.33601,4.13178 3.33601,4.41901L3.33601,5.50201 1.17001,5.50201 1.17001,4.41901C1.17001,3.55732,1.51232,2.73093,2.12162,2.12162z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M18.498,1.17001L19.581,1.17001C20.4427,1.17001 21.2691,1.51232 21.8784,2.12162 22.4877,2.73093 22.83,3.55732 22.83,4.41901L22.83,5.50201 20.664,5.50201 20.664,4.41901C20.664,4.13178 20.5499,3.85632 20.3468,3.65321 20.1437,3.45011 19.8682,3.33601 19.581,3.33601L18.498,3.33601 18.498,1.17001z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M3.33601,19.581L3.33601,18.498 1.17001,18.498 1.17001,19.581C1.17001,20.4427 1.51232,21.2691 2.12162,21.8784 2.73093,22.4877 3.55732,22.83 4.41901,22.83L5.50201,22.83 5.50201,20.664 4.41901,20.664C4.13178,20.664 3.85632,20.5499 3.65321,20.3468 3.45011,20.1437 3.33601,19.8682 3.33601,19.581z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M7.66801,1.17001L10.917,1.17001 10.917,3.33601 7.66801,3.33601 7.66801,1.17001z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M12,20.664L7.66801,20.664 7.66801,22.83 12,22.83 12,20.664z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M13.083,1.17001L16.332,1.17001 16.332,3.33601 13.083,3.33601 13.083,1.17001z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M3.33601,10.917L3.33601,7.66801 1.17001,7.66801 1.17001,10.917 3.33601,10.917z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M22.83,7.66801L22.83,12 20.664,12 20.664,7.66801 22.83,7.66801z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M3.33601,16.332L3.33601,13.083 1.17001,13.083 1.17001,16.332 3.33601,16.332z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M10.7469,10.747L22.83,15.781 18.4517,17.2681 22.2785,21.0949 21.0949,22.2785 17.2681,18.4517 15.781,22.83 10.7469,10.747z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="ShapesIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M17.8604,10.7597L12.0068,1.17001 6.13961,10.7597 17.8604,10.7597z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M10.9345,13.2403L1.34476,13.2403 1.34476,22.83 10.9345,22.83 10.9345,13.2403z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M17.8604,13.2403C15.2122,13.2403 13.0655,15.387 13.0655,18.0352 13.0655,20.6833 15.2122,22.83 17.8604,22.83 20.5085,22.83 22.6552,20.6833 22.6552,18.0352 22.6552,15.387 20.5085,13.2403 17.8604,13.2403z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M17.8604,10.7597L12.0068,1.17001 6.13961,10.7597 17.8604,10.7597z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M10.9345,13.2403L1.34476,13.2403 1.34476,22.83 10.9345,22.83 10.9345,13.2403z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M17.8604,13.2403C15.2122,13.2403 13.0655,15.387 13.0655,18.0352 13.0655,20.6833 15.2122,22.83 17.8604,22.83 20.5085,22.83 22.6552,20.6833 22.6552,18.0352 22.6552,15.387 20.5085,13.2403 17.8604,13.2403z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="UndoIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M8.71408,16.8493L0.874451,9.00964 8.71408,1.17001 8.71408,7.42358 15.7239,7.42358C16.7074,7.42358 17.6791,7.62744 18.583,8.02124 19.4866,8.41493 20.3023,8.98966 20.9857,9.70849 21.6689,10.4271 22.2069,11.276 22.5726,12.2047 22.9383,13.1333 23.1256,14.126 23.1256,15.1268 23.1256,16.1276 22.9383,17.1203 22.5726,18.0489 22.2069,18.9776 21.6689,19.8264 20.9857,20.5451 20.3023,21.2639 19.4866,21.8387 18.583,22.2324 17.6791,22.6262 16.7074,22.83 15.7239,22.83L10.437,22.83 10.437,19.6579 15.7239,19.6579C16.2679,19.6579 16.8086,19.5453 17.3159,19.3243 17.8235,19.1031 18.29,18.7767 18.6867,18.3594 19.0835,17.942 19.4023,17.4422 19.6211,16.8866 19.8399,16.3308 19.9534,15.7326 19.9534,15.1268 19.9534,14.5209 19.8399,13.9227 19.6211,13.367 19.4023,12.8114 19.0835,12.3115 18.6867,11.8941 18.29,11.4769 17.8235,11.1505 17.3159,10.9293 16.8086,10.7083 16.2679,10.5957 15.7239,10.5957L8.71408,10.5957 8.71408,16.8493z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M8.71408,16.8493L0.874451,9.00964 8.71408,1.17001 8.71408,7.42358 15.7239,7.42358C16.7074,7.42358 17.6791,7.62744 18.583,8.02124 19.4866,8.41493 20.3023,8.98966 20.9857,9.70849 21.6689,10.4271 22.2069,11.276 22.5726,12.2047 22.9383,13.1333 23.1256,14.126 23.1256,15.1268 23.1256,16.1276 22.9383,17.1203 22.5726,18.0489 22.2069,18.9776 21.6689,19.8264 20.9857,20.5451 20.3023,21.2639 19.4866,21.8387 18.583,22.2324 17.6791,22.6262 16.7074,22.83 15.7239,22.83L10.437,22.83 10.437,19.6579 15.7239,19.6579C16.2679,19.6579 16.8086,19.5453 17.3159,19.3243 17.8235,19.1031 18.29,18.7767 18.6867,18.3594 19.0835,17.942 19.4023,17.4422 19.6211,16.8866 19.8399,16.3308 19.9534,15.7326 19.9534,15.1268 19.9534,14.5209 19.8399,13.9227 19.6211,13.367 19.4023,12.8114 19.0835,12.3115 18.6867,11.8941 18.29,11.4769 17.8235,11.1505 17.3159,10.9293 16.8086,10.7083 16.2679,10.5957 15.7239,10.5957L8.71408,10.5957 8.71408,16.8493z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="RedoIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M15.2859,16.8493L23.1255,9.00964 15.2859,1.17001 15.2859,7.42358 8.27607,7.42358C7.29262,7.42358 6.32086,7.62744 5.41703,8.02124 4.51341,8.41493 3.69773,8.98966 3.01434,9.70849 2.33111,10.4271 1.79312,11.276 1.42741,12.2047 1.06174,13.1333 0.874422,14.126 0.874422,15.1268 0.874422,16.1276 1.06174,17.1203 1.42741,18.0489 1.79312,18.9776 2.33111,19.8264 3.01434,20.5451 3.69773,21.2639 4.51341,21.8387 5.41703,22.2324 6.32086,22.6262 7.29262,22.83 8.27607,22.83L13.563,22.83 13.563,19.6579 8.27607,19.6579C7.7321,19.6579 7.19139,19.5453 6.68406,19.3243 6.17652,19.1031 5.70999,18.7767 5.31333,18.3594 4.91651,17.942 4.59775,17.4422 4.37894,16.8866 4.1601,16.3308 4.04656,15.7326 4.04656,15.1268 4.04656,14.5209 4.1601,13.9227 4.37894,13.367 4.59775,12.8114 4.91651,12.3115 5.31333,11.8941 5.70999,11.4769 6.17652,11.1505 6.68406,10.9293 7.19139,10.7083 7.7321,10.5957 8.27607,10.5957L15.2859,10.5957 15.2859,16.8493z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M15.2859,16.8493L23.1255,9.00964 15.2859,1.17001 15.2859,7.42358 8.27607,7.42358C7.29262,7.42358 6.32086,7.62744 5.41703,8.02124 4.51341,8.41493 3.69773,8.98966 3.01434,9.70849 2.33111,10.4271 1.79312,11.276 1.42741,12.2047 1.06174,13.1333 0.874422,14.126 0.874422,15.1268 0.874422,16.1276 1.06174,17.1203 1.42741,18.0489 1.79312,18.9776 2.33111,19.8264 3.01434,20.5451 3.69773,21.2639 4.51341,21.8387 5.41703,22.2324 6.32086,22.6262 7.29262,22.83 8.27607,22.83L13.563,22.83 13.563,19.6579 8.27607,19.6579C7.7321,19.6579 7.19139,19.5453 6.68406,19.3243 6.17652,19.1031 5.70999,18.7767 5.31333,18.3594 4.91651,17.942 4.59775,17.4422 4.37894,16.8866 4.1601,16.3308 4.04656,15.7326 4.04656,15.1268 4.04656,14.5209 4.1601,13.9227 4.37894,13.367 4.59775,12.8114 4.91651,12.3115 5.31333,11.8941 5.70999,11.4769 6.17652,11.1505 6.68406,10.9293 7.19139,10.7083 7.7321,10.5957 8.27607,10.5957L15.2859,10.5957 15.2859,16.8493z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="MoreToolsIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M3.336,1.17001C2.13975,1.17001,1.17,2.13976,1.17,3.33601L1.17,8.75101C1.17,9.94726,2.13975,10.917,3.336,10.917L8.751,10.917C9.94725,10.917,10.917,9.94726,10.917,8.75101L10.917,3.33601C10.917,2.13976,9.94725,1.17001,8.751,1.17001L3.336,1.17001z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M15.249,1.17001C14.0527,1.17001,13.083,2.13976,13.083,3.33601L13.083,8.75101C13.083,9.94726,14.0527,10.917,15.249,10.917L20.664,10.917C21.8602,10.917,22.83,9.94726,22.83,8.75101L22.83,3.33601C22.83,2.13976,21.8602,1.17001,20.664,1.17001L15.249,1.17001z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M3.336,13.083C2.13975,13.083,1.17,14.0528,1.17,15.249L1.17,20.664C1.17,21.8603,2.13975,22.83,3.336,22.83L8.751,22.83C9.94725,22.83,10.917,21.8603,10.917,20.664L10.917,15.249C10.917,14.0528,9.94725,13.083,8.751,13.083L3.336,13.083z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M15.249,13.083C14.0527,13.083,13.083,14.0528,13.083,15.249L13.083,20.664C13.083,21.8603,14.0527,22.83,15.249,22.83L20.664,22.83C21.8602,22.83,22.83,21.8603,22.83,20.664L22.83,15.249C22.83,14.0528,21.8602,13.083,20.664,13.083L15.249,13.083z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M3.336,1.17001C2.13975,1.17001,1.17,2.13976,1.17,3.33601L1.17,8.75101C1.17,9.94726,2.13975,10.917,3.336,10.917L8.751,10.917C9.94725,10.917,10.917,9.94726,10.917,8.75101L10.917,3.33601C10.917,2.13976,9.94725,1.17001,8.751,1.17001L3.336,1.17001z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M15.249,1.17001C14.0527,1.17001,13.083,2.13976,13.083,3.33601L13.083,8.75101C13.083,9.94726,14.0527,10.917,15.249,10.917L20.664,10.917C21.8602,10.917,22.83,9.94726,22.83,8.75101L22.83,3.33601C22.83,2.13976,21.8602,1.17001,20.664,1.17001L15.249,1.17001z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M3.336,13.083C2.13975,13.083,1.17,14.0528,1.17,15.249L1.17,20.664C1.17,21.8603,2.13975,22.83,3.336,22.83L8.751,22.83C9.94725,22.83,10.917,21.8603,10.917,20.664L10.917,15.249C10.917,14.0528,9.94725,13.083,8.751,13.083L3.336,13.083z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M15.249,13.083C14.0527,13.083,13.083,14.0528,13.083,15.249L13.083,20.664C13.083,21.8603,14.0527,22.83,15.249,22.83L20.664,22.83C21.8602,22.83,22.83,21.8603,22.83,20.664L22.83,15.249C22.83,14.0528,21.8602,13.083,20.664,13.083L15.249,13.083z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="GestureIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F0 M24,24z M0,0z M7.82154,10.0753L7.82154,3.74613C7.82154,3.06604 8.08946,2.40655 8.57377,1.92224 9.05808,1.43793 9.70726,1.17001 10.3977,1.17001 11.0881,1.17001 11.7372,1.43793 12.2216,1.92224 12.7059,2.40655 12.9738,3.05573 12.9738,3.74613L12.9738,6.37308C13.1415,6.33947 13.3139,6.32225 13.489,6.32225 14.1794,6.32225 14.8286,6.59016 15.3129,7.07447 15.4484,7.21001 15.567,7.35845 15.6675,7.5171 15.9551,7.40916 16.2634,7.35269 16.5803,7.35269 17.2707,7.35269 17.9199,7.62061 18.4042,8.10492 18.5461,8.24683 18.6695,8.4029 18.7729,8.57001 19.6856,8.26338 20.7674,8.45871 21.4647,9.15599 21.949,9.6403 22.2169,10.2998 22.2169,10.9799L22.2169,15.6169C22.2169,17.5438 21.4647,19.3574 20.1045,20.7176 18.7443,22.0778 16.9307,22.83 15.0038,22.83L13.149,22.83 13.1799,22.8094 12.8398,22.8094C11.7682,22.7579 10.7068,22.4694 9.75878,21.9541 8.70773,21.3874 7.81124,20.563 7.15175,19.5738L6.94566,19.2647C6.60562,18.7494 5.49273,16.8019 3.52458,13.3087 3.19484,12.7213 3.1021,12.0412 3.27727,11.3818 3.45245,10.7326 3.86463,10.1761 4.44168,9.83608 5.00842,9.49604 5.66791,9.35177 6.31709,9.43421 6.86548,9.50385 7.39181,9.7279 7.82154,10.0753z M10.037,3.38547C10.1297,3.28243 10.2637,3.23091 10.3977,3.23091 10.5316,3.23091 10.6656,3.29273 10.7583,3.38547 10.8614,3.47821 10.9129,3.61217 10.9129,3.74613L10.9129,11.4745C10.9129,12.0412 11.3766,12.5049 11.9433,12.5049 12.5101,12.5049 12.9738,12.0412 12.9738,11.4745L12.9738,8.89836C12.9738,8.7644 13.0356,8.63045 13.1283,8.53771 13.2211,8.43466 13.355,8.38314 13.489,8.38314 13.623,8.38314 13.7569,8.44497 13.8497,8.53771 13.9527,8.63045 14.0042,8.7644 14.0042,8.89836L14.0042,11.4745C14.0042,12.0412 14.4679,12.5049 15.0347,12.5049 15.6014,12.5049 16.0651,12.0412 16.0651,11.4745L16.0651,9.92881C16.0651,9.79485 16.1269,9.66089 16.2197,9.56815 16.3124,9.46511 16.4464,9.41359 16.5803,9.41359 16.7143,9.41359 16.8483,9.47541 16.941,9.56815 17.044,9.66089 17.0956,9.79485 17.0956,9.92881L17.0956,10.5869C17.0752,10.7163 17.0646,10.8477 17.0646,10.9799 17.0646,11.0661 17.0754,11.1499 17.0956,11.2301L17.0956,11.4745C17.0956,12.0412 17.5593,12.5049 18.126,12.5049 18.6928,12.5049 19.1565,12.0412 19.1565,11.4745L19.1565,10.8128C19.1834,10.7399 19.2266,10.6727 19.2801,10.6192 19.4759,10.4234 19.8159,10.4234 20.0117,10.6192 20.1148,10.712 20.1663,10.8459 20.1663,10.9799L20.1663,15.6169C20.1663,16.9977 19.6408,18.296 18.6618,19.2647 17.6829,20.2333 16.3949,20.7691 15.0141,20.7691L13.1593,20.7691C12.3143,20.7691 11.4796,20.5527 10.7274,20.1509 9.98548,19.749 9.3363,19.1616 8.8726,18.4506L8.66651,18.1415C8.35738,17.6675 7.23419,15.7096 5.31756,12.2989 5.24543,12.1752 5.23512,12.0412 5.26604,11.9073 5.30725,11.7733 5.38969,11.6703 5.50304,11.5981 5.66791,11.4951 5.874,11.4539 6.06978,11.4745 6.26557,11.5054 6.45105,11.5878 6.59531,11.7321L8.11007,13.2469C8.49419,13.631 9.10425,13.648 9.50833,13.2978 9.73651,13.1084 9.88244,12.8229 9.88244,12.5049L9.88244,3.74613C9.88244,3.61217,9.94426,3.47821,10.037,3.38547z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M2.99905,6.31195L1.78313,4.65293 2.61779,4.04497C3.46275,3.4267,4.37985,2.89087,5.33817,2.46838L6.27587,2.0459 7.12084,3.93162 6.18313,4.3541C5.35878,4.72506,4.56533,5.17846,3.83372,5.71429L2.99905,6.32225 2.99905,6.31195z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M18.2806,5.20935L19.1565,5.75549 20.259,4.01404 19.3832,3.4679C18.1157,2.67446,16.7452,2.0768,15.3026,1.68523L14.303,1.41731 13.7672,3.40607 14.7667,3.67399C16.0033,4.00373,17.1883,4.51895,18.2806,5.20935z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F0 M24,24z M0,0z M7.82154,10.0753L7.82154,3.74613C7.82154,3.06604 8.08946,2.40655 8.57377,1.92224 9.05808,1.43793 9.70726,1.17001 10.3977,1.17001 11.0881,1.17001 11.7372,1.43793 12.2216,1.92224 12.7059,2.40655 12.9738,3.05573 12.9738,3.74613L12.9738,6.37308C13.1415,6.33947 13.3139,6.32225 13.489,6.32225 14.1794,6.32225 14.8286,6.59016 15.3129,7.07447 15.4484,7.21001 15.567,7.35845 15.6675,7.5171 15.9551,7.40916 16.2634,7.35269 16.5803,7.35269 17.2707,7.35269 17.9199,7.62061 18.4042,8.10492 18.5461,8.24683 18.6695,8.4029 18.7729,8.57001 19.6856,8.26338 20.7674,8.45871 21.4647,9.15599 21.949,9.6403 22.2169,10.2998 22.2169,10.9799L22.2169,15.6169C22.2169,17.5438 21.4647,19.3574 20.1045,20.7176 18.7443,22.0778 16.9307,22.83 15.0038,22.83L13.149,22.83 13.1799,22.8094 12.8398,22.8094C11.7682,22.7579 10.7068,22.4694 9.75878,21.9541 8.70773,21.3874 7.81124,20.563 7.15175,19.5738L6.94566,19.2647C6.60562,18.7494 5.49273,16.8019 3.52458,13.3087 3.19484,12.7213 3.1021,12.0412 3.27727,11.3818 3.45245,10.7326 3.86463,10.1761 4.44168,9.83608 5.00842,9.49604 5.66791,9.35177 6.31709,9.43421 6.86548,9.50385 7.39181,9.7279 7.82154,10.0753z M10.037,3.38547C10.1297,3.28243 10.2637,3.23091 10.3977,3.23091 10.5316,3.23091 10.6656,3.29273 10.7583,3.38547 10.8614,3.47821 10.9129,3.61217 10.9129,3.74613L10.9129,11.4745C10.9129,12.0412 11.3766,12.5049 11.9433,12.5049 12.5101,12.5049 12.9738,12.0412 12.9738,11.4745L12.9738,8.89836C12.9738,8.7644 13.0356,8.63045 13.1283,8.53771 13.2211,8.43466 13.355,8.38314 13.489,8.38314 13.623,8.38314 13.7569,8.44497 13.8497,8.53771 13.9527,8.63045 14.0042,8.7644 14.0042,8.89836L14.0042,11.4745C14.0042,12.0412 14.4679,12.5049 15.0347,12.5049 15.6014,12.5049 16.0651,12.0412 16.0651,11.4745L16.0651,9.92881C16.0651,9.79485 16.1269,9.66089 16.2197,9.56815 16.3124,9.46511 16.4464,9.41359 16.5803,9.41359 16.7143,9.41359 16.8483,9.47541 16.941,9.56815 17.044,9.66089 17.0956,9.79485 17.0956,9.92881L17.0956,10.5869C17.0752,10.7163 17.0646,10.8477 17.0646,10.9799 17.0646,11.0661 17.0754,11.1499 17.0956,11.2301L17.0956,11.4745C17.0956,12.0412 17.5593,12.5049 18.126,12.5049 18.6928,12.5049 19.1565,12.0412 19.1565,11.4745L19.1565,10.8128C19.1834,10.7399 19.2266,10.6727 19.2801,10.6192 19.4759,10.4234 19.8159,10.4234 20.0117,10.6192 20.1148,10.712 20.1663,10.8459 20.1663,10.9799L20.1663,15.6169C20.1663,16.9977 19.6408,18.296 18.6618,19.2647 17.6829,20.2333 16.3949,20.7691 15.0141,20.7691L13.1593,20.7691C12.3143,20.7691 11.4796,20.5527 10.7274,20.1509 9.98548,19.749 9.3363,19.1616 8.8726,18.4506L8.66651,18.1415C8.35738,17.6675 7.23419,15.7096 5.31756,12.2989 5.24543,12.1752 5.23512,12.0412 5.26604,11.9073 5.30725,11.7733 5.38969,11.6703 5.50304,11.5981 5.66791,11.4951 5.874,11.4539 6.06978,11.4745 6.26557,11.5054 6.45105,11.5878 6.59531,11.7321L8.11007,13.2469C8.49419,13.631 9.10425,13.648 9.50833,13.2978 9.73651,13.1084 9.88244,12.8229 9.88244,12.5049L9.88244,3.74613C9.88244,3.61217,9.94426,3.47821,10.037,3.38547z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M2.99905,6.31195L1.78313,4.65293 2.61779,4.04497C3.46275,3.4267,4.37985,2.89087,5.33817,2.46838L6.27587,2.0459 7.12084,3.93162 6.18313,4.3541C5.35878,4.72506,4.56533,5.17846,3.83372,5.71429L2.99905,6.32225 2.99905,6.31195z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M18.2806,5.20935L19.1565,5.75549 20.259,4.01404 19.3832,3.4679C18.1157,2.67446,16.7452,2.0768,15.3026,1.68523L14.303,1.41731 13.7672,3.40607 14.7667,3.67399C16.0033,4.00373,17.1883,4.51895,18.2806,5.20935z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
<DrawingImage x:Key="EyeOffIconV2">
|
||||
<DrawingImage.Drawing>
|
||||
<DrawingGroup ClipGeometry="M0,0 V24 H24 V0 H0 Z">
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F0 M24,24z M0,0z M22.8347,21.5006L2.50417,1.17001 1.16525,2.50893 5.45737,6.80104C3.85257,8.10197 2.53265,9.72576 1.64954,11.5964 1.53559,11.8433 1.52609,12.1282 1.63055,12.3751L1.63055,12.3941C1.63055,12.4131 1.64954,12.4321 1.65904,12.4606 1.68752,12.5175 1.72551,12.5935 1.77299,12.698 1.87744,12.8974 2.01988,13.1822 2.21929,13.5146 2.61812,14.1793 3.21635,15.0719 4.04249,15.9645 5.69477,17.7497 8.30612,19.5919 11.981,19.5919 13.7282,19.5919 15.4375,19.1551 16.9568,18.31L21.4768,22.83 22.8158,21.4911 22.8347,21.5006z M11.9905,15.8791C12.5033,15.8696 13.0066,15.7556 13.4719,15.5467 13.6428,15.4707 13.7852,15.3758 13.9372,15.2808L8.72394,10.0676C8.62898,10.2195 8.53402,10.3715 8.45805,10.5329 8.24915,10.9982 8.1257,11.5015 8.1257,12.0143 8.1162,12.527 8.21116,13.0303 8.40108,13.5051 8.591,13.9799 8.87587,14.4072 9.23671,14.768 9.59755,15.1289 10.0249,15.4138 10.4997,15.6037 10.9745,15.7936 11.4777,15.8791 11.9905,15.8791z" />
|
||||
<GeometryDrawing Brush="{StaticResource IconBrush}" Geometry="F1 M24,24z M0,0z M10.9063,6.37657C11.2749,6.33269 11.6452,6.30726 12,6.30726 14.974,6.30726 17.1134,7.78595 18.5447,9.32737 19.2601,10.0978 19.7852,10.8712 20.1309,11.4519 20.2587,11.6665 20.3611,11.8535 20.4389,12.0025 20.0809,12.6966 19.6562,13.3505 19.1703,13.9543L18.5749,14.694 20.0544,15.8848 20.6498,15.145C21.3248,14.3064 21.8966,13.387 22.3556,12.4078 22.4706,12.1625 22.475,11.8789 22.3683,11.6299L22.3669,11.6266 22.364,11.6201 22.3551,11.5998C22.3477,11.5833 22.3375,11.5605 22.3243,11.5319 22.2979,11.4748 22.2598,11.3946 22.2098,11.2945 22.1097,11.0944 21.9615,10.8142 21.7628,10.4805 21.3666,9.81482 20.7641,8.92644 19.9364,8.03508 18.2816,6.25295 15.6731,4.4081 12,4.4081 11.5572,4.4081 11.1108,4.43965 10.6818,4.49072L9.73885,4.60297 9.96336,6.48883 10.9063,6.37657z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F0 M24,24z M0,0z M22.8347,21.5006L2.50417,1.17001 1.16525,2.50893 5.45737,6.80104C3.85257,8.10197 2.53265,9.72576 1.64954,11.5964 1.53559,11.8433 1.52609,12.1282 1.63055,12.3751L1.63055,12.3941C1.63055,12.4131 1.64954,12.4321 1.65904,12.4606 1.68752,12.5175 1.72551,12.5935 1.77299,12.698 1.87744,12.8974 2.01988,13.1822 2.21929,13.5146 2.61812,14.1793 3.21635,15.0719 4.04249,15.9645 5.69477,17.7497 8.30612,19.5919 11.981,19.5919 13.7282,19.5919 15.4375,19.1551 16.9568,18.31L21.4768,22.83 22.8158,21.4911 22.8347,21.5006z M11.9905,15.8791C12.5033,15.8696 13.0066,15.7556 13.4719,15.5467 13.6428,15.4707 13.7852,15.3758 13.9372,15.2808L8.72394,10.0676C8.62898,10.2195 8.53402,10.3715 8.45805,10.5329 8.24915,10.9982 8.1257,11.5015 8.1257,12.0143 8.1162,12.527 8.21116,13.0303 8.40108,13.5051 8.591,13.9799 8.87587,14.4072 9.23671,14.768 9.59755,15.1289 10.0249,15.4138 10.4997,15.6037 10.9745,15.7936 11.4777,15.8791 11.9905,15.8791z" />
|
||||
<GeometryDrawing Brush="{DynamicResource IconBrush}" Geometry="F1 M24,24z M0,0z M10.9063,6.37657C11.2749,6.33269 11.6452,6.30726 12,6.30726 14.974,6.30726 17.1134,7.78595 18.5447,9.32737 19.2601,10.0978 19.7852,10.8712 20.1309,11.4519 20.2587,11.6665 20.3611,11.8535 20.4389,12.0025 20.0809,12.6966 19.6562,13.3505 19.1703,13.9543L18.5749,14.694 20.0544,15.8848 20.6498,15.145C21.3248,14.3064 21.8966,13.387 22.3556,12.4078 22.4706,12.1625 22.475,11.8789 22.3683,11.6299L22.3669,11.6266 22.364,11.6201 22.3551,11.5998C22.3477,11.5833 22.3375,11.5605 22.3243,11.5319 22.2979,11.4748 22.2598,11.3946 22.2098,11.2945 22.1097,11.0944 21.9615,10.8142 21.7628,10.4805 21.3666,9.81482 20.7641,8.92644 19.9364,8.03508 18.2816,6.25295 15.6731,4.4081 12,4.4081 11.5572,4.4081 11.1108,4.43965 10.6818,4.49072L9.73885,4.60297 9.96336,6.48883 10.9063,6.37657z" />
|
||||
</DrawingGroup>
|
||||
</DrawingImage.Drawing>
|
||||
</DrawingImage>
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Resources.ICCConfiguration {
|
||||
public enum InitialPositionTypes {
|
||||
namespace Ink_Canvas.Resources.ICCConfiguration
|
||||
{
|
||||
public enum InitialPositionTypes
|
||||
{
|
||||
TopLeft, TopRight, BottomLeft, BottomRight, TopCenter, BottomCenter, Custom
|
||||
}
|
||||
public enum ElementCornerRadiusTypes {
|
||||
public enum ElementCornerRadiusTypes
|
||||
{
|
||||
SuperEllipse, Circle, Custom, None
|
||||
}
|
||||
public class NearSnapAreaSize {
|
||||
public double[] TopLeft { get; set; } = {24,24};
|
||||
public double[] TopRight { get; set; } = {24,24};
|
||||
public double[] BottomLeft { get; set; } = {24,24};
|
||||
public double[] BottomRight { get; set; } = {24,24};
|
||||
public class NearSnapAreaSize
|
||||
{
|
||||
public double[] TopLeft { get; set; } = { 24, 24 };
|
||||
public double[] TopRight { get; set; } = { 24, 24 };
|
||||
public double[] BottomLeft { get; set; } = { 24, 24 };
|
||||
public double[] BottomRight { get; set; } = { 24, 24 };
|
||||
public double TopCenter { get; set; } = 24;
|
||||
public double BottomCenter { get; set; } = 24;
|
||||
}
|
||||
public class ICCFloatingBarConfiguration {
|
||||
public class ICCFloatingBarConfiguration
|
||||
{
|
||||
public bool SemiTransparent { get; set; } = false;
|
||||
public bool NearSnap { get; set; } = true;
|
||||
public InitialPositionTypes InitialPosition { get; set; } = InitialPositionTypes.BottomCenter;
|
||||
@@ -37,7 +37,8 @@ namespace Ink_Canvas.Resources.ICCConfiguration {
|
||||
public double MovingLimitationNoSnap { get; set; } = 12;
|
||||
public double MovingLimitationSnapped { get; set; } = 24;
|
||||
|
||||
public NearSnapAreaSize NearSnapAreaSize { get; set; } = new NearSnapAreaSize() {
|
||||
public NearSnapAreaSize NearSnapAreaSize { get; set; } = new NearSnapAreaSize()
|
||||
{
|
||||
TopLeft = new double[] { 24, 24 },
|
||||
TopRight = new double[] { 24, 24 },
|
||||
BottomLeft = new double[] { 24, 24 },
|
||||
@@ -55,7 +56,8 @@ namespace Ink_Canvas.Resources.ICCConfiguration {
|
||||
};
|
||||
}
|
||||
|
||||
public class ICCConfiguration {
|
||||
public class ICCConfiguration
|
||||
{
|
||||
public ICCFloatingBarConfiguration FloatingBar { get; set; } = new ICCFloatingBarConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 8.8 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 8.0 KiB |
|
After Width: | Height: | Size: 5.8 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 5.4 KiB |