Compare commits
702 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 61fe7197f5 | |||
| 1881bfd69e | |||
| 4287b7def5 | |||
| 27d683a7cc | |||
| 38902c8f88 | |||
| d8e8142ff4 | |||
| fdaf9cb3ef | |||
| b2fe091c88 | |||
| 8548244cef | |||
| 91a5881600 | |||
| 93fd043b14 | |||
| 47948ef530 | |||
| 61fa618673 | |||
| 545aecc6ae | |||
| ee41f53286 | |||
| cc8036f736 | |||
| 67a982e6e4 | |||
| b16000f8e5 | |||
| a142ce0542 | |||
| c87b8b65fc | |||
| 7332df1d56 | |||
| de90c17ab1 | |||
| 3d54edf25b | |||
| 111819dbf3 | |||
| 3665753ba2 | |||
| 75e38aa8f3 | |||
| 29dc6938c3 | |||
| 304933b02b | |||
| 49f268bb62 | |||
| d0793c546d | |||
| 375aec6f6c | |||
| 12ec527dcd | |||
| 52d95173d7 | |||
| 67e3d51106 | |||
| 9081aea926 | |||
| 02c6caf465 | |||
| 1a9ddf1969 | |||
| 41bcae9d05 | |||
| 28109a0c97 | |||
| d8825f0a73 | |||
| 82c045a243 | |||
| 4d11f69282 | |||
| 664e4ea048 | |||
| 6d21c5e24e | |||
| ba21b6884d | |||
| 60065aab3e | |||
| 4d978936ac | |||
| 4ea585633b | |||
| dee0bd7f4d | |||
| edb206ffa5 | |||
| 21932056f3 | |||
| 0c3809b789 | |||
| d5ae596ad6 | |||
| 42db7e60f2 | |||
| 85827820a4 | |||
| fad2571edb | |||
| c53b6de68c | |||
| c86eb12168 | |||
| fb31f8df11 | |||
| 147b4fc5ed | |||
| cdcb2d2af0 | |||
| ff086e497c | |||
| a2b711da05 | |||
| 40ecfbefa3 | |||
| 2097a1250b | |||
| 22025f9b00 | |||
| 80c1badba5 | |||
| be308ba280 | |||
| fa7f3d44e4 | |||
| 9bb00489fe | |||
| 33948c604c | |||
| b8fe5bbd66 | |||
| 3fcc01c253 | |||
| 658d48c17b | |||
| 8d76c014c8 | |||
| 26cd125534 | |||
| 1bc23af61a | |||
| a2aa6b48a8 | |||
| 5784c974f7 | |||
| 80503dc42e | |||
| d76195f7ae | |||
| 2fe482b802 | |||
| 16283f4643 | |||
| cd7a801400 | |||
| f6d8558d07 | |||
| de20b506f0 | |||
| ea1d52292e | |||
| 463e506ca3 | |||
| 3bae64a2c7 | |||
| 5d13c8b543 | |||
| c6c0789794 | |||
| d7df39290f | |||
| b64cefad46 | |||
| 4690ab3c30 | |||
| b61c7490b3 | |||
| 9e511d29a6 | |||
| a9cdc36967 | |||
| 96c51cd7ff | |||
| 636769c0ef | |||
| 7beb2a3cc5 | |||
| 52d318054c | |||
| 5ee2ad6f3d | |||
| 097d414d0d | |||
| e0ce119374 | |||
| da9a5242cb | |||
| 0211b38be9 | |||
| 512e9b21cd | |||
| 9181ad55ae | |||
| a1cb9e1b28 | |||
| ea09a3e798 | |||
| 21fa146f6f | |||
| 1a3130041f | |||
| 7095a5890c | |||
| 4a86d1aa05 | |||
| 19fe7223fb | |||
| 8867fde3f2 | |||
| 588d1822b1 | |||
| d2b3b38d9e | |||
| 206ae3d9ed | |||
| a67b7a2fd0 | |||
| f5a08b225c | |||
| c807f97c29 | |||
| 8382413137 | |||
| c6acafd3a6 | |||
| 9d43056361 | |||
| 52f8e24954 | |||
| dac05fec84 | |||
| cb4ed77572 | |||
| 3427cbdc2e | |||
| ec0fb0c3cf | |||
| 357983179c | |||
| 78b66c141e | |||
| 3ac88bb400 | |||
| 27493b857c | |||
| e5a976abcf | |||
| 9e89bf5303 | |||
| 54eb330711 | |||
| e9014c6f5d | |||
| 620bcf6fab | |||
| c03bbd9e13 | |||
| b428b4ec5b | |||
| 876cc116ea | |||
| 0ba00c08ef | |||
| d41ecd9d55 | |||
| 35d351ce6b | |||
| 5d3af58361 | |||
| 8554b92f42 | |||
| 76363b4263 | |||
| b4250b9161 | |||
| 441f40886f | |||
| e8e8ad5d63 | |||
| 454078ec82 | |||
| 63f29c2686 | |||
| 280445f613 | |||
| d7f6433b53 | |||
| 22da4cc408 | |||
| 4e185ef584 | |||
| fd9b4b4ba6 | |||
| 2f79bbcb0f | |||
| 2c70d243df | |||
| 7710b77255 | |||
| d1eed23399 | |||
| ff9ce4df44 | |||
| b7c52842f2 | |||
| 365459f649 | |||
| e6354f724f | |||
| 14eedca939 | |||
| 83529cfe09 | |||
| bd4e1c1810 | |||
| 5665fcc823 | |||
| 710a9014dd | |||
| 108c6b2b17 | |||
| 70735943c3 | |||
| d01a24f879 | |||
| ec2d5043ff | |||
| 15082c2c52 | |||
| 44278d68b4 | |||
| 40eeb9db66 | |||
| a5eb1dfca7 | |||
| 8719677f11 | |||
| 8f01b6c5fe | |||
| 9e63a3f49b | |||
| 84edb7bbe6 | |||
| c86ce00a17 | |||
| aba6c18a25 | |||
| 62c81e6d44 | |||
| 68ea323855 | |||
| f94d81ad20 | |||
| 063f8b1751 | |||
| 35cfd26ece | |||
| f331cb1b4d | |||
| 502e205071 | |||
| 975b563b8d | |||
| cc88630859 | |||
| cce0b930ec | |||
| 723c825df2 | |||
| 5b4c966354 | |||
| 69023f98d9 | |||
| dce5722e96 | |||
| cbdbc93274 | |||
| 042b153684 | |||
| 4054423721 | |||
| 2e63a6eaca | |||
| 9eca7eb2ee | |||
| 1489fb645e | |||
| 5c0ca841d7 | |||
| a4d3d3ff9c | |||
| c3b67f4a4b | |||
| cde5daf19a | |||
| 904b2c0988 | |||
| f9907e2ec6 | |||
| 7329c0097c | |||
| 0478949305 | |||
| 34172a54fe | |||
| 6dd629eda5 | |||
| 636dd2b8d5 | |||
| b20dbc5202 | |||
| ee45104eb9 | |||
| 152be89860 | |||
| 6b5a375542 | |||
| 03d049846d | |||
| 6ed084bb94 | |||
| f5332b63a9 | |||
| 87356215c3 | |||
| 07c7acc37a | |||
| 4a6d9dee67 | |||
| 52eb93e59c | |||
| 2245a018e6 | |||
| 8b327fd715 | |||
| 7a7289a4c8 | |||
| ceb259819f | |||
| fdfbaedbd7 | |||
| 9591fbf146 | |||
| b2a09dbf6d | |||
| 11a5a7fdbe | |||
| 745b798d89 | |||
| 5f7a6f2e07 | |||
| 865415a6c0 | |||
| 207560bcc7 | |||
| 5d17a586d5 | |||
| 6058cb9cff | |||
| f5f989d140 | |||
| 3541522fc6 | |||
| cceadd2a3d | |||
| 5fc715b7d0 | |||
| 32c1ddab46 | |||
| 1085f6c57a | |||
| e9ec424625 | |||
| a8de65ab0e | |||
| c6a95c99ef | |||
| e1f3a6ada4 | |||
| b95da2d8d5 | |||
| 8586735ca8 |
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"projectName": "community",
|
||||
"projectOwner": "InkCanvasForClass",
|
||||
"files": [
|
||||
"README.md"
|
||||
],
|
||||
"commitType": "docs",
|
||||
"commitConvention": "angular",
|
||||
"contributorsPerLine": 7,
|
||||
"contributors": [
|
||||
{
|
||||
"login": "CJKmkp",
|
||||
"name": "CJK_mkp",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/113243675?v=4",
|
||||
"profile": "https://github.com/CJKmkp",
|
||||
"contributions": [
|
||||
"maintenance",
|
||||
"doc",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "CreeperAWA",
|
||||
"name": "CreeperAWA",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/134939494?v=4",
|
||||
"profile": "https://github.com/CreeperAWA",
|
||||
"contributions": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "2-2-3-trimethylpentane",
|
||||
"name": "2,2,3-三甲基戊烷",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/141403762?v=4",
|
||||
"profile": "https://github.com/2-2-3-trimethylpentane",
|
||||
"contributions": [
|
||||
"blog",
|
||||
"doc",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Alan-CRL",
|
||||
"name": "Alan-CRL",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/92425617?v=4",
|
||||
"profile": "https://github.com/Alan-CRL",
|
||||
"contributions": [
|
||||
"code",
|
||||
"infra",
|
||||
"doc",
|
||||
"financial"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "MKStoler1024",
|
||||
"name": "MKStoler1024",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/158786854?v=4",
|
||||
"profile": "https://github.com/MKStoler1024",
|
||||
"contributions": [
|
||||
"doc",
|
||||
"code",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "awesome-iwb",
|
||||
"name": "Awesome Iwb",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/184760810?v=4",
|
||||
"profile": "https://github.com/awesome-iwb",
|
||||
"contributions": [
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "PrefacedCorg",
|
||||
"name": "PrefacedCorg",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/129855423?v=4",
|
||||
"profile": "https://github.com/PrefacedCorg",
|
||||
"contributions": [
|
||||
"code",
|
||||
"design"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "Jursin",
|
||||
"name": "Jursin",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/127487914?v=4",
|
||||
"profile": "http://blog.jursin.top",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
|
||||
## Reproduction
|
||||
|
||||
|
||||
## Expected behavior
|
||||
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
||||
## Additional context
|
||||
@@ -0,0 +1,56 @@
|
||||
name: Bug 报告 | Bug Report
|
||||
description: 反馈软件缺陷或异常 | Report a bug to help us improve
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢你的反馈!请详细填写以下内容,便于我们定位问题。
|
||||
Thank you for your feedback! Please fill out the following information to help us locate the issue.
|
||||
- type: input
|
||||
id: version
|
||||
attributes:
|
||||
label: 软件版本 | App Version
|
||||
description: 可在设置中的“关于”界面查看 | You can find it on the "About" interface in the settings
|
||||
placeholder: 例如 v1.2.3 | e.g. v1.2.3
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: os
|
||||
attributes:
|
||||
label: 操作系统及版本 | OS & Version
|
||||
placeholder: 例如 Windows 10 22H2 64位 | e.g. Windows 10 22H2 64bit
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 问题描述 | Description
|
||||
description: 简要描述遇到的问题 | Briefly describe the problem
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: 复现步骤 | Steps to Reproduce
|
||||
description: 如何复现该问题?如有必要可附截图/录屏 | How to reproduce this bug? Screenshots/recordings if needed
|
||||
placeholder: |
|
||||
1.
|
||||
2.
|
||||
3.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expected
|
||||
attributes:
|
||||
label: 期望结果 | Expected Behavior
|
||||
description: 你期望的正确行为或结果 | What did you expect to happen?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: extra
|
||||
attributes:
|
||||
label: 其他补充信息 | Additional Info
|
||||
description: 其他相关信息(如日志、配置、特殊环境等)| Any other context, logs, configs, special environment, etc.
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Description
|
||||
@@ -0,0 +1,37 @@
|
||||
name: 功能请求 | Feature Request
|
||||
description: 提出你对本项目的功能建议 | Suggest an idea for this project
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
感谢你的建议!请详细描述你的需求。
|
||||
Thank you for your suggestion! Please describe your needs in detail.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: 功能描述 | Description
|
||||
description: 请描述你希望添加的功能 | Describe the feature you want
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: motivation
|
||||
attributes:
|
||||
label: 需求动机 | Motivation
|
||||
description: 为什么需要这个功能?| Why do you need this feature?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: design
|
||||
attributes:
|
||||
label: 期望设计 | Expected Design
|
||||
description: (可选)描述或画出你期望的界面或交互 | (Optional) Describe or sketch the expected UI/UX
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: extra
|
||||
attributes:
|
||||
label: 其他补充信息 | Additional Info
|
||||
description: 其他补充说明或建议 | Any other context or suggestions
|
||||
validations:
|
||||
required: false
|
||||
@@ -0,0 +1,35 @@
|
||||
name: .NET Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main,beta ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4.2.2
|
||||
|
||||
- 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: Build the Solution
|
||||
run: |
|
||||
msbuild -t:restore /p:GitFlow="Github Action"
|
||||
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/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"
|
||||
+428
-16
@@ -1,16 +1,428 @@
|
||||
InkCanvasForClass/obj
|
||||
InkCanvasForClass/bin
|
||||
InkCanvasForClassX/obj
|
||||
InkCanvasForClassX/bin
|
||||
InkCanvasForClass.IACoreHelper/obj
|
||||
InkCanvasForClass.IACoreHelper/bin
|
||||
InkCanvasForClass.PowerPoint.InteropHelper/obj
|
||||
InkCanvasForClass.PowerPoint.InteropHelper/bin
|
||||
InkCanvasForClass.PowerPoint.VstoPlugin/obj
|
||||
InkCanvasForClass.PowerPoint.VstoPlugin/bin
|
||||
InkCanvasForClass.IccInkCanvas/obj
|
||||
InkCanvasForClass.IccInkCanvas/bin
|
||||
InkCanvasForClass.IccInkCanvas.Demo/obj
|
||||
InkCanvasForClass.IccInkCanvas.Demo/bin
|
||||
.vs
|
||||
.idea
|
||||
## 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/
|
||||
# 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
|
||||
Generated
+4
-4
@@ -1,12 +1,12 @@
|
||||
# 默认忽略的文件
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Rider 忽略的文件
|
||||
/contentModel.xml
|
||||
# Rider ignored files
|
||||
/projectSettingsUpdater.xml
|
||||
/modules.xml
|
||||
/contentModel.xml
|
||||
/.idea.Ink Canvas.iml
|
||||
# 基于编辑器的 HTTP 客户端请求
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
|
||||
+3
-1
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="UserContentModel">
|
||||
<attachedFolders />
|
||||
<attachedFolders>
|
||||
<Path>../../ICC CE main</Path>
|
||||
</attachedFolders>
|
||||
<explicitIncludes />
|
||||
<explicitExcludes />
|
||||
</component>
|
||||
|
||||
Generated
+2
-1
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/../alpha" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1 @@
|
||||
1.7.15.0
|
||||
@@ -1,36 +0,0 @@
|
||||
rev-1.0.0.0:
|
||||
1. 重写大部分代码中...
|
||||
|
||||
5.0.5.0:
|
||||
1. 支持自动收纳希沃轻白板5C,希沃白板3,鸿合轻量白板。
|
||||
2. 支持自动查杀鸿合屏幕书写。
|
||||
3. 带来FitToCurve,给平滑墨迹加上了开关。
|
||||
4. 修复了FitToCurve导致的部分形状绘制出错的问题也保留了其他墨迹的FitToCurve。
|
||||
5. 白板,浮动工具栏,子面板,设置UI几乎全部优化。
|
||||
6. 添加修改橡皮大小,橡皮形状的菜单。
|
||||
7. PPT现在会自动提示是否关闭自动播放和排练计时。
|
||||
8. 条件性的墨迹识别,可以关闭部分不需要的形状识别。可以修改是否为墨迹识别的三角形或矩形应用模拟压感值。
|
||||
9. 带来了荧光笔功能并为荧光笔适配了形状识别。
|
||||
10. 允许清空墨迹时可删除历史记录。
|
||||
11. 带来了Quick Panel。
|
||||
12. 浮动工具栏缩放调节,取消收纳按钮图标修改。
|
||||
13. 修复了多指书写StylusDown事件可能触发在工具栏而不是inkCanvas上导致的Bug。
|
||||
14. 修复了鼠标和触摸屏(无RealTimeStylus和非多指书写模式)的绘制时Pointer捕获Bug。
|
||||
15. Merge了Ink Canvas的新撤回行为
|
||||
16. 修复了白板模式下直接新增页面导致的TimeMachine不记录历史记录的Bug
|
||||
17. 添加了一个简陋的白板页面列表。
|
||||
18. 优化了墨迹重播,支持暂停,重新开始和倍速,拥有更人性化的UI。
|
||||
19. 优化自动收纳行为和FoldFloatingBar函数的相关Bug修复。
|
||||
20. 添加了EdgeGestureUtil实验性选项。
|
||||
21. 添加了ForceFullScreen实验性选项
|
||||
22. 添加了FullScreenHelper实验性选项。
|
||||
23. 添加了ResolutionChangeDetection和DPIChangeDetection实验性选项。
|
||||
24. 修复了同时打开ICC和ICA的冲突。
|
||||
25. 修复浮动工具栏手势按钮开启多指书写时InkCanvas的InkCanvasEditingMode被修改为None的Bug。
|
||||
26. 修复Merge ICA仓库代码后导致浮动工具栏批注按钮UI MouseLeave反馈的问题。
|
||||
27. 为大部分UI的按钮添加了lastBorderMouseDown的检测代码和MouseDown的视觉反馈。
|
||||
28. 支持自动收纳安道系列,艺云系列和 MAXHUB 白板书写。
|
||||
|
||||
4.5.8.0:
|
||||
1. 将ICA的代码换了个名字变成了InkCanvasForClass,万物起源。
|
||||
2. 优化了ICA的部分UI界面设计。
|
||||
@@ -0,0 +1,128 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported to the community leaders responsible for enforcement at
|
||||
QQ:2564608840.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 550 KiB |
+21
-228
@@ -3,19 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.33530.505
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InkCanvasForClassX", "InkCanvasForClassX\InkCanvasForClassX.csproj", "{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InkCanvasForClass", "InkCanvasForClass\InkCanvasForClass.csproj", "{2474F5B0-6FA7-4D70-8A00-167BBB03264D}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.IACoreHelper", "InkCanvasForClass.IACoreHelper\InkCanvasForClass.IACoreHelper.csproj", "{693973B6-69C1-4A37-B329-F366A07BF60A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.PowerPoint.InteropHelper", "InkCanvasForClass.PowerPoint.InteropHelper\InkCanvasForClass.PowerPoint.InteropHelper.csproj", "{2D8A9217-465A-4F57-BD58-CE02450390C4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.PowerPoint.VstoPlugin", "InkCanvasForClass.PowerPoint.VstoPlugin\InkCanvasForClass.PowerPoint.VstoPlugin.csproj", "{8C593467-E54D-4FA7-881C-78F3CC48A867}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.IccInkCanvas", "InkCanvasForClass.IccInkCanvas\InkCanvasForClass.IccInkCanvas.csproj", "{43929D8F-5630-4786-B75D-E203EA3E992F}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InkCanvasForClass.IccInkCanvas.Demo", "InkCanvasForClass.IccInkCanvas.Demo\InkCanvasForClass.IccInkCanvas.Demo.csproj", "{94E97F70-CACD-453B-8114-08DFFDDA5A46}"
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InkCanvasForClass", "Ink Canvas\InkCanvasForClass.csproj", "{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -29,223 +17,28 @@ Global
|
||||
Release|ARM64 = Release|ARM64
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
x86 Debug|Any CPU = x86 Debug|Any CPU
|
||||
x86 Debug|ARM = x86 Debug|ARM
|
||||
x86 Debug|ARM64 = x86 Debug|ARM64
|
||||
x86 Debug|x64 = x86 Debug|x64
|
||||
x86 Debug|x86 = x86 Debug|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|x64.Build.0 = Release|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.Release|x86.Build.0 = Release|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|x64.Build.0 = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{98DF6AA1-DD4D-4C70-A0A2-4B2974D97D51}.x86 Debug|x86.Build.0 = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|x64.Build.0 = Release|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.Release|x86.Build.0 = Release|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|Any CPU.ActiveCfg = x86 Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|Any CPU.Build.0 = x86 Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|ARM.ActiveCfg = x86 Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|ARM.Build.0 = x86 Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|ARM64.ActiveCfg = x86 Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|ARM64.Build.0 = x86 Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|x64.ActiveCfg = x86 Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|x64.Build.0 = x86 Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|x86.ActiveCfg = x86 Debug|Any CPU
|
||||
{2474F5B0-6FA7-4D70-8A00-167BBB03264D}.x86 Debug|x86.Build.0 = x86 Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Release|x64.Build.0 = Release|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.Release|x86.Build.0 = Release|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|x64.Build.0 = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{693973B6-69C1-4A37-B329-F366A07BF60A}.x86 Debug|x86.Build.0 = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|x64.Build.0 = Release|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.Release|x86.Build.0 = Release|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|x64.Build.0 = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{2D8A9217-465A-4F57-BD58-CE02450390C4}.x86 Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|x64.Build.0 = Release|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.Release|x86.Build.0 = Release|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|x64.Build.0 = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8C593467-E54D-4FA7-881C-78F3CC48A867}.x86 Debug|x86.Build.0 = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Release|x64.Build.0 = Release|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.Release|x86.Build.0 = Release|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|x64.Build.0 = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{43929D8F-5630-4786-B75D-E203EA3E992F}.x86 Debug|x86.Build.0 = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|ARM64.ActiveCfg = Release|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|ARM64.Build.0 = Release|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|x64.Build.0 = Release|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.Release|x86.Build.0 = Release|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|ARM64.ActiveCfg = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|ARM64.Build.0 = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|x64.Build.0 = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{94E97F70-CACD-453B-8114-08DFFDDA5A46}.x86 Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM.ActiveCfg = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.ActiveCfg = Debug|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|ARM64.Build.0 = Debug|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x64.Build.0 = Debug|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM.ActiveCfg = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM.Build.0 = Release|Any CPU
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.ActiveCfg = Release|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|ARM64.Build.0 = Release|ARM64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.ActiveCfg = Release|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x64.Build.0 = Release|x64
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.ActiveCfg = Release|x86
|
||||
{8D0EDFC7-F974-4571-BC49-6F3A6653FE81}.Release|x86.Build.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -1,5 +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/AssemblyExplorer/XmlDocument/@EntryValue"><AssemblyExplorer>
|
||||
<Assembly Path="D:\vs\ica\InkCanvasForClass\IAWinFX.dll" />
|
||||
</AssemblyExplorer></s:String></wpf:ResourceDictionary>
|
||||
@@ -3,13 +3,14 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Ink_Canvas"
|
||||
xmlns:tb="http://www.hardcodet.net/taskbar"
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern">
|
||||
xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern"
|
||||
>
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<Style TargetType="ui:ScrollViewerEx">
|
||||
<EventSetter Event="PreviewMouseWheel" Handler="ScrollViewer_PreviewMouseWheel"/>
|
||||
</Style>
|
||||
<ContextMenu Opened="SysTrayMenu_Opened" x:Shared="false" x:Key="SysTrayMenu" Padding="6" ui:ThemeManager.RequestedTheme="Light">
|
||||
<ContextMenu Opened="SysTrayMenu_Opened" Closed="SysTrayMenu_Closed" x:Shared="false" x:Key="SysTrayMenu" Padding="6" ui:ThemeManager.RequestedTheme="Light">
|
||||
<MenuItem IsCheckable="True" IsChecked="False" Checked="HideICCMainWindowTrayIconMenuItem_Checked" Unchecked="HideICCMainWindowTrayIconMenuItem_UnChecked" Name="HideICCMainWindowTrayIconMenuItem">
|
||||
<MenuItem.Header>
|
||||
<ui:SimpleStackPanel Orientation="Horizontal" Margin="-4,0,0,0">
|
||||
@@ -31,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="禁用所有快捷键" />
|
||||
@@ -231,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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
|
||||
@@ -8,9 +8,9 @@ using System.Windows;
|
||||
[assembly: AssemblyTitle("InkCanvasForClass")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("Dubi906w")]
|
||||
[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("5.0.2.0")]
|
||||
[assembly: AssemblyFileVersion("5.0.2.0")]
|
||||
[assembly: AssemblyVersion("1.7.15.0")]
|
||||
[assembly: AssemblyFileVersion("1.7.15.0")]
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<Costura ExcludeAssemblies="IACore|IALoader|IAWinFX" />
|
||||
</Weavers>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,209 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 防止窗口进入全屏状态的辅助类
|
||||
/// </summary>
|
||||
public static class AvoidFullScreenHelper
|
||||
{
|
||||
private static readonly DependencyProperty IsAvoidFullScreenEnabledProperty =
|
||||
DependencyProperty.RegisterAttached(
|
||||
"IsAvoidFullScreenEnabled",
|
||||
typeof(bool),
|
||||
typeof(AvoidFullScreenHelper));
|
||||
|
||||
private static bool _isBoardMode;
|
||||
public static void SetBoardMode(bool isBoardMode)
|
||||
{
|
||||
_isBoardMode = isBoardMode;
|
||||
}
|
||||
|
||||
public static void StartAvoidFullScreen(Window window)
|
||||
{
|
||||
if (window == null)
|
||||
throw new ArgumentNullException(nameof(window));
|
||||
|
||||
if (!(bool)window.GetValue(IsAvoidFullScreenEnabledProperty))
|
||||
{
|
||||
var hwndSource = PresentationSource.FromVisual(window) as HwndSource;
|
||||
if (hwndSource != null)
|
||||
{
|
||||
hwndSource.AddHook(KeepInWorkingAreaHook);
|
||||
window.SetValue(IsAvoidFullScreenEnabledProperty, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void StopAvoidFullScreen(Window window)
|
||||
{
|
||||
if (window == null)
|
||||
throw new ArgumentNullException(nameof(window));
|
||||
|
||||
if ((bool)window.GetValue(IsAvoidFullScreenEnabledProperty))
|
||||
{
|
||||
var hwndSource = PresentationSource.FromVisual(window) as HwndSource;
|
||||
if (hwndSource != null)
|
||||
{
|
||||
hwndSource.RemoveHook(KeepInWorkingAreaHook);
|
||||
window.ClearValue(IsAvoidFullScreenEnabledProperty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool GetIsAvoidFullScreenEnabled(DependencyObject obj) => (bool)obj.GetValue(IsAvoidFullScreenEnabledProperty);
|
||||
public static void SetIsAvoidFullScreenEnabled(DependencyObject obj, bool value) => obj.SetValue(IsAvoidFullScreenEnabledProperty, value);
|
||||
|
||||
private static IntPtr KeepInWorkingAreaHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
||||
{
|
||||
// 只拦截主画布窗口的全屏(最大化)操作
|
||||
var window = HwndSource.FromHwnd(hwnd)?.RootVisual as Window;
|
||||
if (window == null) return IntPtr.Zero;
|
||||
// 这里假设主画布窗口类名为MainWindow(如有不同请调整)
|
||||
if (window.GetType().Name != "MainWindow") return IntPtr.Zero;
|
||||
|
||||
if (_isBoardMode)
|
||||
{
|
||||
// 画板模式下允许全屏/最大化,不拦截
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
const int WM_WINDOWPOSCHANGING = 0x0046;
|
||||
const int WM_SYSCOMMAND = 0x0112;
|
||||
const int SC_MAXIMIZE = 0xF030;
|
||||
if (msg == WM_SYSCOMMAND && wParam.ToInt32() == SC_MAXIMIZE)
|
||||
{
|
||||
// 拦截最大化命令,强制还原窗口并调整到工作区
|
||||
window.WindowState = WindowState.Normal;
|
||||
var workingArea = GetWorkingArea(new Rect(window.Left, window.Top, window.Width, window.Height));
|
||||
window.Left = workingArea.Left;
|
||||
window.Top = workingArea.Top;
|
||||
window.Width = workingArea.Width;
|
||||
window.Height = workingArea.Height;
|
||||
handled = true;
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
if (msg != WM_WINDOWPOSCHANGING)
|
||||
return IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
var pos = (WindowPosition)Marshal.PtrToStructure(lParam, typeof(WindowPosition));
|
||||
if ((pos.Flags & (WindowPositionFlags.SWP_NOMOVE | WindowPositionFlags.SWP_NOSIZE)) != 0)
|
||||
return IntPtr.Zero;
|
||||
// 只处理主画布窗口
|
||||
// 计算目标矩形
|
||||
var targetRect = new Rect(
|
||||
(pos.Flags & WindowPositionFlags.SWP_NOMOVE) == 0 ? pos.X : window.Left,
|
||||
(pos.Flags & WindowPositionFlags.SWP_NOMOVE) == 0 ? pos.Y : window.Top,
|
||||
(pos.Flags & WindowPositionFlags.SWP_NOSIZE) == 0 ? pos.Width : window.Width,
|
||||
(pos.Flags & WindowPositionFlags.SWP_NOSIZE) == 0 ? pos.Height : window.Height);
|
||||
var workingArea = GetWorkingArea(targetRect);
|
||||
var adjustedRect = AdjustRectToWorkingArea(targetRect, workingArea);
|
||||
pos.X = (int)adjustedRect.Left;
|
||||
pos.Y = (int)adjustedRect.Top;
|
||||
pos.Width = (int)adjustedRect.Width;
|
||||
pos.Height = (int)adjustedRect.Height;
|
||||
pos.Flags &= ~(WindowPositionFlags.SWP_NOSIZE | WindowPositionFlags.SWP_NOMOVE | WindowPositionFlags.SWP_NOREDRAW);
|
||||
pos.Flags |= WindowPositionFlags.SWP_NOCOPYBITS;
|
||||
Marshal.StructureToPtr(pos, lParam, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"窗口位置调整失败: {ex.Message}");
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
private static Rect GetWorkingArea(Rect windowRect)
|
||||
{
|
||||
// 获取所有显示器
|
||||
var screens = Screen.AllScreens;
|
||||
|
||||
// 确定窗口主要位于哪个显示器上
|
||||
Screen targetScreen = null;
|
||||
double maxIntersection = 0;
|
||||
|
||||
foreach (var screen in screens)
|
||||
{
|
||||
var screenRect = new Rect(
|
||||
screen.WorkingArea.X,
|
||||
screen.WorkingArea.Y,
|
||||
screen.WorkingArea.Width,
|
||||
screen.WorkingArea.Height);
|
||||
|
||||
var intersection = Rect.Intersect(windowRect, screenRect);
|
||||
if (intersection.Width * intersection.Height > maxIntersection)
|
||||
{
|
||||
maxIntersection = intersection.Width * intersection.Height;
|
||||
targetScreen = screen;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没找到,使用主显示器
|
||||
if (targetScreen == null)
|
||||
targetScreen = Screen.PrimaryScreen;
|
||||
|
||||
return new Rect(
|
||||
targetScreen.WorkingArea.X,
|
||||
targetScreen.WorkingArea.Y,
|
||||
targetScreen.WorkingArea.Width,
|
||||
targetScreen.WorkingArea.Height);
|
||||
}
|
||||
|
||||
private static Rect AdjustRectToWorkingArea(Rect windowRect, Rect workingArea)
|
||||
{
|
||||
// 调整尺寸以适应工作区域
|
||||
if (windowRect.Width > workingArea.Width)
|
||||
windowRect.Width = workingArea.Width;
|
||||
|
||||
if (windowRect.Height > workingArea.Height)
|
||||
windowRect.Height = workingArea.Height;
|
||||
|
||||
// 调整位置以确保窗口完全在工作区域内
|
||||
if (windowRect.Left < workingArea.Left)
|
||||
windowRect.X = workingArea.Left;
|
||||
else if (windowRect.Right > workingArea.Right)
|
||||
windowRect.X = workingArea.Right - windowRect.Width;
|
||||
|
||||
if (windowRect.Top < workingArea.Top)
|
||||
windowRect.Y = workingArea.Top;
|
||||
else if (windowRect.Bottom > workingArea.Bottom)
|
||||
windowRect.Y = workingArea.Bottom - windowRect.Height;
|
||||
|
||||
return windowRect;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用WPF原生类型替代Win32结构
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
internal struct WindowPosition
|
||||
{
|
||||
public IntPtr Hwnd;
|
||||
public IntPtr HwndInsertAfter;
|
||||
public int X;
|
||||
public int Y;
|
||||
public int Width;
|
||||
public int Height;
|
||||
public WindowPositionFlags Flags;
|
||||
}
|
||||
|
||||
[Flags]
|
||||
internal enum WindowPositionFlags : uint
|
||||
{
|
||||
SWP_NOSIZE = 0x0001,
|
||||
SWP_NOMOVE = 0x0002,
|
||||
SWP_NOZORDER = 0x0004,
|
||||
SWP_NOREDRAW = 0x0008,
|
||||
SWP_NOACTIVATE = 0x0010,
|
||||
SWP_FRAMECHANGED = 0x0020,
|
||||
SWP_SHOWWINDOW = 0x0040,
|
||||
SWP_HIDEWINDOW = 0x0080,
|
||||
SWP_NOCOPYBITS = 0x0100,
|
||||
SWP_NOOWNERZORDER = 0x0200,
|
||||
SWP_NOSENDCHANGING = 0x0400,
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Ink_Canvas.Converter
|
||||
{
|
||||
public class BooleanToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((bool)value)
|
||||
{
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
public class VisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
Visibility visibility = (Visibility)value;
|
||||
if (visibility == Visibility.Visible)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
Visibility visibility = (Visibility)value;
|
||||
if (visibility == Visibility.Visible)
|
||||
{
|
||||
return Visibility.Collapsed;
|
||||
}
|
||||
|
||||
return Visibility.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
public class IntNumberToString : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "无限制";
|
||||
}
|
||||
|
||||
return ((double)value) + "人";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "无限制";
|
||||
}
|
||||
|
||||
return ((double)value) + "人";
|
||||
}
|
||||
}
|
||||
|
||||
public class IntNumberToString2 : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "自动截图";
|
||||
}
|
||||
|
||||
return ((double)value) + "条";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if ((double)value == 0)
|
||||
{
|
||||
return "自动截图";
|
||||
}
|
||||
|
||||
return ((double)value) + "条";
|
||||
}
|
||||
}
|
||||
|
||||
public class IsEnabledToOpacityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
bool isChecked = (bool)value;
|
||||
if (isChecked)
|
||||
{
|
||||
return 1d;
|
||||
}
|
||||
|
||||
return 0.35;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+34
-18
@@ -1,48 +1,64 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers {
|
||||
internal class DelAutoSavedFiles {
|
||||
public static void DeleteFilesOlder(string directoryPath, int daysThreshold) {
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class DelAutoSavedFiles
|
||||
{
|
||||
public static void DeleteFilesOlder(string directoryPath, int daysThreshold)
|
||||
{
|
||||
string[] extensionsToDel = { ".icstk", ".png" };
|
||||
if (Directory.Exists(directoryPath)) {
|
||||
if (Directory.Exists(directoryPath))
|
||||
{
|
||||
// 获取目录中的所有子目录
|
||||
string[] subDirectories = Directory.GetDirectories(directoryPath, "*", SearchOption.AllDirectories);
|
||||
foreach (string subDirectory in subDirectories) {
|
||||
try {
|
||||
foreach (string subDirectory in subDirectories)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取子目录下的所有文件
|
||||
string[] files = Directory.GetFiles(subDirectory);
|
||||
foreach (string filePath in files) {
|
||||
foreach (string filePath in files)
|
||||
{
|
||||
// 获取文件的创建日期
|
||||
DateTime creationDate = File.GetCreationTime(filePath);
|
||||
// 获取文件的扩展名
|
||||
string fileExtension = Path.GetExtension(filePath);
|
||||
// 如果文件的创建日期早于指定天数且是要删除的扩展名,则删除文件
|
||||
if (creationDate < DateTime.Now.AddDays(-daysThreshold)) {
|
||||
if (creationDate < DateTime.Now.AddDays(-daysThreshold))
|
||||
{
|
||||
if (Array.Exists(extensionsToDel, ext => ext.Equals(fileExtension, StringComparison.OrdinalIgnoreCase))
|
||||
|| Path.GetFileName(filePath).Equals("Position", StringComparison.OrdinalIgnoreCase)) {
|
||||
|| Path.GetFileName(filePath).Equals("Position", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex.ToString(), LogHelper.LogType.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex, LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
try { // 递归删除空文件夹
|
||||
try
|
||||
{ // 递归删除空文件夹
|
||||
DeleteEmptyFolders(directoryPath);
|
||||
} catch (Exception ex) {
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex.ToString(), LogHelper.LogType.Error);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile("DelAutoSavedFiles | 处理文件时出错: " + ex, LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteEmptyFolders(string directoryPath) {
|
||||
foreach (string dir in Directory.GetDirectories(directoryPath)) {
|
||||
private static void DeleteEmptyFolders(string directoryPath)
|
||||
{
|
||||
foreach (string dir in Directory.GetDirectories(directoryPath))
|
||||
{
|
||||
DeleteEmptyFolders(dir);
|
||||
if (Directory.GetFiles(dir).Length == 0 && Directory.GetDirectories(dir).Length == 0) {
|
||||
if (Directory.GetFiles(dir).Length == 0 && Directory.GetDirectories(dir).Length == 0)
|
||||
{
|
||||
Directory.Delete(dir, false);
|
||||
}
|
||||
}
|
||||
+6
-7
@@ -1,9 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Timers;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
@@ -18,10 +14,13 @@ namespace Ink_Canvas.Helpers
|
||||
/// <param name="inv">同步的對象,一般傳入控件,不需要可null</param>
|
||||
public void DebounceAction(int timeMs, ISynchronizeInvoke inv, Action action)
|
||||
{
|
||||
lock (this) {
|
||||
if (_timerDebounce == null) {
|
||||
lock (this)
|
||||
{
|
||||
if (_timerDebounce == null)
|
||||
{
|
||||
_timerDebounce = new Timer(timeMs) { AutoReset = false };
|
||||
_timerDebounce.Elapsed += (o, e) => {
|
||||
_timerDebounce.Elapsed += (o, e) =>
|
||||
{
|
||||
_timerDebounce.Stop(); _timerDebounce.Close(); _timerDebounce = null;
|
||||
InvokeAction(action, inv);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
+9
-21
@@ -1,19 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualBasic;
|
||||
using System.Collections;
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
@@ -83,7 +71,7 @@ namespace Ink_Canvas.Helpers
|
||||
[FieldOffset(8)]
|
||||
private DateTime date;
|
||||
[FieldOffset(8)]
|
||||
private System.Runtime.InteropServices.ComTypes.FILETIME filetime;
|
||||
private FILETIME filetime;
|
||||
|
||||
[FieldOffset(8)]
|
||||
private Blob blobVal;
|
||||
@@ -128,7 +116,7 @@ namespace Ink_Canvas.Helpers
|
||||
case VarEnum.VT_BLOB:
|
||||
return GetBlob();
|
||||
}
|
||||
throw new NotImplementedException("PropVariant " + ve.ToString());
|
||||
throw new NotImplementedException("PropVariant " + ve);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,17 +145,17 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
#region "Interfaces"
|
||||
|
||||
[ComImport(), Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
[ComImport, Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
|
||||
public interface IPropertyStore
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetCount([Out(), In()] ref uint cProps);
|
||||
void GetCount([Out, In] ref uint cProps);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetAt([In()] uint iProp, ref PropertyKey pkey);
|
||||
void GetAt([In] uint iProp, ref PropertyKey pkey);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void GetValue([In()] ref PropertyKey key, ref PropVariant pv);
|
||||
void GetValue([In] ref PropertyKey key, ref PropVariant pv);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void SetValue([In()] ref PropertyKey key, [In()] ref PropVariant pv);
|
||||
void SetValue([In] ref PropertyKey key, [In] ref PropVariant pv);
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
void Commit();
|
||||
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
|
||||
@@ -0,0 +1,589 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件关联管理器,用于注册和处理.icstk文件的关联
|
||||
/// </summary>
|
||||
public static class FileAssociationManager
|
||||
{
|
||||
private const string FileExtension = ".icstk";
|
||||
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>
|
||||
/// 注册.icstk文件关联
|
||||
/// </summary>
|
||||
public static bool RegisterFileAssociation()
|
||||
{
|
||||
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"))
|
||||
using (RegistryKey commandKey = openKey.CreateSubKey("command"))
|
||||
{
|
||||
commandKey.SetValue("", $"\"{exePath}\" \"%1\"");
|
||||
}
|
||||
}
|
||||
|
||||
// 注册文件扩展名
|
||||
using (RegistryKey extensionKey = Registry.ClassesRoot.CreateSubKey(FileExtension))
|
||||
{
|
||||
extensionKey.SetValue("", FileTypeName);
|
||||
}
|
||||
|
||||
// 刷新系统文件关联缓存
|
||||
RefreshSystemFileAssociations();
|
||||
|
||||
LogHelper.WriteLogToFile($"成功注册{FileExtension}文件关联", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (SecurityException ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册文件关联时权限不足: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册文件关联时访问被拒绝: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注册文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销.icstk文件关联
|
||||
/// </summary>
|
||||
public static bool UnregisterFileAssociation()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 删除文件扩展名关联
|
||||
Registry.ClassesRoot.DeleteSubKeyTree(FileExtension, false);
|
||||
|
||||
// 删除文件类型定义
|
||||
Registry.ClassesRoot.DeleteSubKeyTree(FileTypeName, false);
|
||||
|
||||
// 刷新系统文件关联缓存
|
||||
RefreshSystemFileAssociations();
|
||||
|
||||
LogHelper.WriteLogToFile($"成功注销{FileExtension}文件关联", LogHelper.LogType.Event);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"注销文件关联时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查文件关联是否已注册
|
||||
/// </summary>
|
||||
public static bool IsFileAssociationRegistered()
|
||||
{
|
||||
try
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"检查文件关联状态时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示文件关联状态
|
||||
/// </summary>
|
||||
public static void ShowFileAssociationStatus()
|
||||
{
|
||||
bool isRegistered = IsFileAssociationRegistered();
|
||||
LogHelper.WriteLogToFile($"{FileExtension}文件关联状态: {(isRegistered ? "已注册" : "未注册")}", LogHelper.LogType.Event);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 刷新系统文件关联缓存
|
||||
/// </summary>
|
||||
private static void RefreshSystemFileAssociations()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 通知系统文件关联已更改
|
||||
SHChangeNotify(0x08000000, 0, IntPtr.Zero, IntPtr.Zero);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"刷新文件关联缓存时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理命令行参数中的文件路径
|
||||
/// </summary>
|
||||
/// <param name="args">命令行参数</param>
|
||||
/// <returns>找到的.icstk文件路径,如果没有找到则返回null</returns>
|
||||
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))
|
||||
{
|
||||
// 检查文件是否存在
|
||||
if (File.Exists(arg))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从命令行参数中找到.icstk文件: {arg}", LogHelper.LogType.Event);
|
||||
return arg;
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"命令行参数中的.icstk文件不存在: {arg}", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试通过IPC将文件路径发送给已运行的实例
|
||||
/// </summary>
|
||||
/// <param name="filePath">要打开的文件路径</param>
|
||||
/// <returns>是否成功发送</returns>
|
||||
public static bool TrySendFileToExistingInstance(string filePath)
|
||||
{
|
||||
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
|
||||
{
|
||||
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 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>
|
||||
public static void StartIpcListener()
|
||||
{
|
||||
try
|
||||
{
|
||||
Thread ipcThread = new Thread(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
LogHelper.WriteLogToFile("启动IPC监听器", LogHelper.LogType.Event);
|
||||
|
||||
using (EventWaitHandle ipcEvent = new EventWaitHandle(false, EventResetMode.ManualReset, IpcEventName))
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
// 等待IPC事件
|
||||
if (ipcEvent.WaitOne(IpcTimeout))
|
||||
{
|
||||
// 处理IPC文件
|
||||
ProcessIpcFiles();
|
||||
|
||||
// 重置事件
|
||||
ipcEvent.Reset();
|
||||
}
|
||||
|
||||
// 检查应用是否还在运行
|
||||
if (Application.Current == null || Application.Current.Dispatcher == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"IPC监听器出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
ipcThread.IsBackground = true;
|
||||
ipcThread.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动IPC监听器失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理IPC文件
|
||||
/// </summary>
|
||||
private static void ProcessIpcFiles()
|
||||
{
|
||||
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(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 获取主窗口并打开文件
|
||||
if (Application.Current.MainWindow is MainWindow mainWindow)
|
||||
{
|
||||
mainWindow.OpenSingleStrokeFile(filePath);
|
||||
mainWindow.ShowNotification($"已加载墨迹文件: {Path.GetFileName(filePath)}");
|
||||
}
|
||||
}
|
||||
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[] 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
|
||||
{
|
||||
if (File.Exists(ipcFile))
|
||||
{
|
||||
File.Delete(ipcFile);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理IPC文件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("shell32.dll")]
|
||||
private static extern void SHChangeNotify(uint wEventId, uint uFlags, IntPtr dwItem1, IntPtr dwItem2);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
}
|
||||
+28
-8
@@ -2,6 +2,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
@@ -24,7 +25,8 @@ namespace Ink_Canvas.Helpers
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct RECT {
|
||||
public struct RECT
|
||||
{
|
||||
public int Left;
|
||||
public int Top;
|
||||
public int Right;
|
||||
@@ -34,7 +36,8 @@ namespace Ink_Canvas.Helpers
|
||||
public int Height => Bottom - Top;
|
||||
}
|
||||
|
||||
public static string WindowTitle() {
|
||||
public static string WindowTitle()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
|
||||
const int nChars = 256;
|
||||
@@ -44,7 +47,8 @@ namespace Ink_Canvas.Helpers
|
||||
return windowTitle.ToString();
|
||||
}
|
||||
|
||||
public static string WindowClassName() {
|
||||
public static string WindowClassName()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
|
||||
const int nChars = 256;
|
||||
@@ -54,7 +58,8 @@ namespace Ink_Canvas.Helpers
|
||||
return className.ToString();
|
||||
}
|
||||
|
||||
public static RECT WindowRect() {
|
||||
public static RECT WindowRect()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
|
||||
RECT windowRect;
|
||||
@@ -63,15 +68,19 @@ namespace Ink_Canvas.Helpers
|
||||
return windowRect;
|
||||
}
|
||||
|
||||
public static string ProcessName() {
|
||||
public static string ProcessName()
|
||||
{
|
||||
IntPtr foregroundWindowHandle = GetForegroundWindow();
|
||||
uint processId;
|
||||
GetWindowThreadProcessId(foregroundWindowHandle, out processId);
|
||||
|
||||
try {
|
||||
try
|
||||
{
|
||||
Process process = Process.GetProcessById((int)processId);
|
||||
return process.ProcessName;
|
||||
} catch (ArgumentException) {
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
// Process with the given ID not found
|
||||
return "Unknown";
|
||||
}
|
||||
@@ -88,10 +97,21 @@ namespace Ink_Canvas.Helpers
|
||||
Process process = Process.GetProcessById((int)processId);
|
||||
return process.MainModule.FileName;
|
||||
}
|
||||
catch {
|
||||
catch
|
||||
{
|
||||
// Process with the given ID not found
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public static double GetTaskbarHeight(Screen screen, double dpiScaleY)
|
||||
{
|
||||
// 获取工作区和屏幕高度的差值
|
||||
var workingArea = screen.WorkingArea;
|
||||
var bounds = screen.Bounds;
|
||||
int taskbarHeight = bounds.Height - workingArea.Height;
|
||||
// 考虑 DPI 缩放
|
||||
return taskbarHeight / dpiScaleY;
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-12
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
|
||||
// 由衷感謝 lindexi 提供的 《WPF 稳定的全屏化窗口方法》
|
||||
// 文章鏈接:https://blog.lindexi.com/post/WPF-%E7%A8%B3%E5%AE%9A%E7%9A%84%E5%85%A8%E5%B1%8F%E5%8C%96%E7%AA%97%E5%8F%A3%E6%96%B9%E6%B3%95.html
|
||||
@@ -83,7 +81,7 @@ namespace Ink_Canvas.Helpers
|
||||
public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
|
||||
|
||||
public static IntPtr GetWindowLongPtr(IntPtr hWnd, GetWindowLongFields nIndex) =>
|
||||
GetWindowLongPtr(hWnd, (int) nIndex);
|
||||
GetWindowLongPtr(hWnd, (int)nIndex);
|
||||
|
||||
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
|
||||
{
|
||||
@@ -101,7 +99,7 @@ namespace Ink_Canvas.Helpers
|
||||
public static extern IntPtr GetWindowLongPtr_x64(IntPtr hWnd, int nIndex);
|
||||
|
||||
public static IntPtr SetWindowLongPtr(IntPtr hWnd, GetWindowLongFields nIndex, IntPtr dwNewLong) =>
|
||||
SetWindowLongPtr(hWnd, (int) nIndex, dwNewLong);
|
||||
SetWindowLongPtr(hWnd, (int)nIndex, dwNewLong);
|
||||
|
||||
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
|
||||
{
|
||||
@@ -266,8 +264,8 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public int Width
|
||||
{
|
||||
get { return unchecked((int) (Right - Left)); }
|
||||
set { Right = unchecked((int) (Left + value)); }
|
||||
get { return unchecked(Right - Left); }
|
||||
set { Right = unchecked(Left + value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -275,8 +273,8 @@ namespace Ink_Canvas.Helpers
|
||||
/// </summary>
|
||||
public int Height
|
||||
{
|
||||
get { return unchecked((int) (Bottom - Top)); }
|
||||
set { Bottom = unchecked((int) (Top + value)); }
|
||||
get { return unchecked(Bottom - Top); }
|
||||
set { Bottom = unchecked(Top + value); }
|
||||
}
|
||||
|
||||
public bool Equals(Rectangle other)
|
||||
@@ -298,10 +296,10 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = (int) Left;
|
||||
hashCode = (hashCode * 397) ^ (int) Top;
|
||||
hashCode = (hashCode * 397) ^ (int) Right;
|
||||
hashCode = (hashCode * 397) ^ (int) Bottom;
|
||||
var hashCode = Left;
|
||||
hashCode = (hashCode * 397) ^ Top;
|
||||
hashCode = (hashCode * 397) ^ Right;
|
||||
hashCode = (hashCode * 397) ^ Bottom;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
+9
-12
@@ -67,12 +67,12 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
//获取当前窗口的位置大小状态并保存
|
||||
var placement = new WINDOWPLACEMENT();
|
||||
placement.Size = (uint) Marshal.SizeOf(placement);
|
||||
placement.Size = (uint)Marshal.SizeOf(placement);
|
||||
Win32.User32.GetWindowPlacement(hwnd, ref placement);
|
||||
window.SetValue(BeforeFullScreenWindowPlacementProperty, placement);
|
||||
|
||||
//修改窗口样式
|
||||
var style = (WindowStyles) Win32.User32.GetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE);
|
||||
var style = (WindowStyles)Win32.User32.GetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE);
|
||||
window.SetValue(BeforeFullScreenWindowStyleProperty, style);
|
||||
//将窗口恢复到还原模式,在有标题栏的情况下最大化模式下无法全屏,
|
||||
//这里采用还原,不修改标题栏的方式
|
||||
@@ -81,7 +81,7 @@ namespace Ink_Canvas.Helpers
|
||||
//去掉WS_MAXIMIZEBOX,禁用最大化,如果最大化会退出全屏
|
||||
//去掉WS_MAXIMIZE,使窗口变成还原状态,不使用ShowWindow(hwnd, ShowWindowCommands.SW_RESTORE),避免看到窗口变成还原状态这一过程(也避免影响窗口的Visible状态)
|
||||
style &= (~(WindowStyles.WS_THICKFRAME | WindowStyles.WS_MAXIMIZEBOX | WindowStyles.WS_MAXIMIZE));
|
||||
Win32.User32.SetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE, (IntPtr) style);
|
||||
Win32.User32.SetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE, (IntPtr)style);
|
||||
|
||||
//禁用 DWM 过渡动画 忽略返回值,若DWM关闭不做处理
|
||||
Win32.Dwmapi.DwmSetWindowAttribute(hwnd, DWMWINDOWATTRIBUTE.DWMWA_TRANSITIONS_FORCEDISABLED, 1,
|
||||
@@ -95,8 +95,8 @@ namespace Ink_Canvas.Helpers
|
||||
//不能用 placement 的坐标,placement是工作区坐标,不是屏幕坐标。
|
||||
|
||||
//使用窗口当前的矩形调用下设置窗口位置和尺寸的方法,让Hook来进行调整窗口位置和尺寸到全屏模式
|
||||
Win32.User32.SetWindowPos(hwnd, (IntPtr) HwndZOrder.HWND_TOPMOST, rect.Left, rect.Top, rect.Width,
|
||||
rect.Height, (int) WindowPositionFlags.SWP_NOZORDER);
|
||||
Win32.User32.SetWindowPos(hwnd, (IntPtr)HwndZOrder.HWND_TOPMOST, rect.Left, rect.Top, rect.Width,
|
||||
rect.Height, (int)WindowPositionFlags.SWP_NOZORDER);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -139,7 +139,7 @@ namespace Ink_Canvas.Helpers
|
||||
//不要改变Style里的WS_MAXIMIZE,否则会使窗口变成最大化状态,但是尺寸不对
|
||||
//也不要设置回Style里的WS_MINIMIZE,否则会导致窗口最小化按钮显示成还原按钮
|
||||
Win32.User32.SetWindowLongPtr(hwnd, GetWindowLongFields.GWL_STYLE,
|
||||
(IntPtr) (style & (~(WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE))));
|
||||
(IntPtr)(style & (~(WindowStyles.WS_MAXIMIZE | WindowStyles.WS_MINIMIZE))));
|
||||
|
||||
if ((style & WindowStyles.WS_MINIMIZE) != 0)
|
||||
{
|
||||
@@ -201,7 +201,7 @@ namespace Ink_Canvas.Helpers
|
||||
try
|
||||
{
|
||||
//得到WINDOWPOS结构体
|
||||
var pos = (WindowPosition) Marshal.PtrToStructure(lParam, typeof(WindowPosition));
|
||||
var pos = (WindowPosition)Marshal.PtrToStructure(lParam, typeof(WindowPosition));
|
||||
|
||||
if ((pos.Flags & WindowPositionFlags.SWP_NOMOVE) != 0 &&
|
||||
(pos.Flags & WindowPositionFlags.SWP_NOSIZE) != 0)
|
||||
@@ -245,7 +245,7 @@ namespace Ink_Canvas.Helpers
|
||||
//使用目标矩形获取显示器信息
|
||||
var monitor = Win32.User32.MonitorFromRect(targetRect, MonitorFlag.MONITOR_DEFAULTTOPRIMARY);
|
||||
var info = new MonitorInfo();
|
||||
info.Size = (uint) Marshal.SizeOf(info);
|
||||
info.Size = (uint)Marshal.SizeOf(info);
|
||||
if (Win32.User32.GetMonitorInfo(monitor, ref info))
|
||||
{
|
||||
//基于显示器信息设置窗口尺寸位置
|
||||
@@ -278,10 +278,7 @@ namespace Ink_Canvas.Helpers
|
||||
window.Width = logicalSize.X;
|
||||
window.Height = logicalSize.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
//这个hwnd是前面从Window来的,如果现在他不是Window...... 你信么
|
||||
}
|
||||
//这个hwnd是前面从Window来的,如果现在他不是Window...... 你信么
|
||||
}
|
||||
|
||||
//将修改后的结构体拷贝回去
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,199 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 硬件加速的墨迹处理器,利用WPF的GPU渲染能力
|
||||
/// </summary>
|
||||
public class HardwareAcceleratedInkProcessor
|
||||
{
|
||||
private readonly RenderTargetBitmap _renderTarget;
|
||||
private readonly DrawingVisual _drawingVisual;
|
||||
private bool _isInitialized;
|
||||
|
||||
public HardwareAcceleratedInkProcessor(int width = 1920, int height = 1080)
|
||||
{
|
||||
// 创建硬件加速的渲染目标
|
||||
_renderTarget = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
|
||||
_drawingVisual = new DrawingVisual();
|
||||
|
||||
// 启用硬件加速
|
||||
RenderOptions.SetBitmapScalingMode(_drawingVisual, BitmapScalingMode.HighQuality);
|
||||
RenderOptions.SetEdgeMode(_drawingVisual, EdgeMode.Aliased);
|
||||
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用GPU加速的贝塞尔曲线平滑
|
||||
/// </summary>
|
||||
public async Task<Stroke> SmoothStrokeWithGPU(Stroke originalStroke)
|
||||
{
|
||||
if (!_isInitialized || originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
return await Task.Run(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用PathGeometry进行硬件加速的曲线拟合
|
||||
var pathGeometry = CreateSmoothPathGeometry(originalStroke.StylusPoints);
|
||||
|
||||
// 将PathGeometry转换回StylusPoint集合
|
||||
var smoothedPoints = ConvertPathGeometryToStylusPoints(pathGeometry, originalStroke.StylusPoints);
|
||||
|
||||
return new Stroke(new StylusPointCollection(smoothedPoints))
|
||||
{
|
||||
DrawingAttributes = originalStroke.DrawingAttributes.Clone()
|
||||
};
|
||||
}
|
||||
catch
|
||||
{
|
||||
return originalStroke;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建平滑的路径几何体
|
||||
/// </summary>
|
||||
private PathGeometry CreateSmoothPathGeometry(StylusPointCollection points)
|
||||
{
|
||||
var pathGeometry = new PathGeometry();
|
||||
var pathFigure = new PathFigure();
|
||||
|
||||
if (points.Count < 2) return pathGeometry;
|
||||
|
||||
pathFigure.StartPoint = new Point(points[0].X, points[0].Y);
|
||||
|
||||
// 使用贝塞尔曲线段创建平滑路径,增加插点密度
|
||||
for (int i = 0; i < points.Count - 1; i += 2) // 从i+=3改为i+=2,增加插点密度
|
||||
{
|
||||
var p1 = i + 1 < points.Count ? new Point(points[i + 1].X, points[i + 1].Y) : pathFigure.StartPoint;
|
||||
var p2 = i + 2 < points.Count ? new Point(points[i + 2].X, points[i + 2].Y) : p1;
|
||||
var p3 = i + 3 < points.Count ? new Point(points[i + 3].X, points[i + 3].Y) : p2;
|
||||
|
||||
var bezierSegment = new BezierSegment(p1, p2, p3, true);
|
||||
pathFigure.Segments.Add(bezierSegment);
|
||||
}
|
||||
|
||||
pathGeometry.Figures.Add(pathFigure);
|
||||
return pathGeometry;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将PathGeometry转换为StylusPoint集合
|
||||
/// </summary>
|
||||
private List<StylusPoint> ConvertPathGeometryToStylusPoints(PathGeometry pathGeometry, StylusPointCollection originalPoints)
|
||||
{
|
||||
var result = new List<StylusPoint>();
|
||||
var flattened = pathGeometry.GetFlattenedPathGeometry();
|
||||
|
||||
foreach (var figure in flattened.Figures)
|
||||
{
|
||||
result.Add(new StylusPoint(figure.StartPoint.X, figure.StartPoint.Y, 0.5f));
|
||||
|
||||
foreach (var segment in figure.Segments)
|
||||
{
|
||||
if (segment is LineSegment lineSegment)
|
||||
{
|
||||
result.Add(new StylusPoint(lineSegment.Point.X, lineSegment.Point.Y, 0.5f));
|
||||
}
|
||||
else if (segment is PolyLineSegment polyLineSegment)
|
||||
{
|
||||
foreach (var point in polyLineSegment.Points)
|
||||
{
|
||||
result.Add(new StylusPoint(point.X, point.Y, 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保持原始压感信息
|
||||
InterpolatePressure(result, originalPoints);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插值压感信息
|
||||
/// </summary>
|
||||
private void InterpolatePressure(List<StylusPoint> smoothedPoints, StylusPointCollection originalPoints)
|
||||
{
|
||||
if (originalPoints.Count == 0 || smoothedPoints.Count == 0) return;
|
||||
|
||||
for (int i = 0; i < smoothedPoints.Count; i++)
|
||||
{
|
||||
double ratio = (double)i / (smoothedPoints.Count - 1);
|
||||
int originalIndex = (int)(ratio * (originalPoints.Count - 1));
|
||||
originalIndex = Math.Max(0, Math.Min(originalIndex, originalPoints.Count - 1));
|
||||
|
||||
var point = smoothedPoints[i];
|
||||
float pressure = originalPoints[originalIndex].PressureFactor;
|
||||
smoothedPoints[i] = new StylusPoint(point.X, point.Y, Math.Max(pressure, 0.1f));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用GPU加速的并行贝塞尔计算
|
||||
/// </summary>
|
||||
public static StylusPoint[] ParallelBezierInterpolation(StylusPoint[] controlPoints, int segments = 32)
|
||||
{
|
||||
if (controlPoints.Length < 4) return controlPoints;
|
||||
|
||||
var result = new StylusPoint[segments * (controlPoints.Length / 4)];
|
||||
|
||||
Parallel.For(0, controlPoints.Length / 4, segmentIndex =>
|
||||
{
|
||||
var p0 = controlPoints[segmentIndex * 4];
|
||||
var p1 = controlPoints[segmentIndex * 4 + 1];
|
||||
var p2 = controlPoints[segmentIndex * 4 + 2];
|
||||
var p3 = controlPoints[segmentIndex * 4 + 3];
|
||||
|
||||
for (int i = 0; i < segments; i++)
|
||||
{
|
||||
double t = (double)i / (segments - 1);
|
||||
result[segmentIndex * segments + i] = CubicBezierFast(p0, p1, p2, p3, t);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 优化的三次贝塞尔曲线计算
|
||||
/// </summary>
|
||||
private static StylusPoint CubicBezierFast(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3, double t)
|
||||
{
|
||||
double u = 1 - t;
|
||||
double tt = t * t;
|
||||
double uu = u * u;
|
||||
double uuu = uu * u;
|
||||
double ttt = tt * t;
|
||||
|
||||
double x = uuu * p0.X + 3 * uu * t * p1.X + 3 * u * tt * p2.X + ttt * p3.X;
|
||||
double y = uuu * p0.Y + 3 * uu * t * p1.Y + 3 * u * tt * p2.Y + ttt * p3.Y;
|
||||
float pressure = (float)(p1.PressureFactor * u + p2.PressureFactor * t);
|
||||
|
||||
return new StylusPoint(x, y, Math.Max(pressure, 0.1f));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 释放GPU资源
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
_renderTarget?.Clear();
|
||||
_isInitialized = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// IACore DLL自动释放器
|
||||
/// 在应用启动时自动释放IACore相关的DLL文件到应用程序目录
|
||||
/// </summary>
|
||||
public static class IACoreDllExtractor
|
||||
{
|
||||
private static readonly string[] RequiredDlls = {
|
||||
"IACore.dll",
|
||||
"IALoader.dll",
|
||||
"IAWinFX.dll"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 在应用启动时释放IACore相关DLL
|
||||
/// </summary>
|
||||
public static void ExtractIACoreDlls()
|
||||
{
|
||||
try
|
||||
{
|
||||
string appDirectory = AppDomain.CurrentDomain.BaseDirectory;
|
||||
LogHelper.WriteLogToFile("开始检查并释放IACore相关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},可能影响形状识别功能", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile("IACore DLL释放检查完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"释放IACore 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.Resources.IACore.{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($"清理IACore DLL时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,325 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 改进的三次贝塞尔曲线平滑算法
|
||||
/// </summary>
|
||||
public class ImprovedBezierSmoothing
|
||||
{
|
||||
private readonly InkSmoothingConfig _config;
|
||||
|
||||
public ImprovedBezierSmoothing(InkSmoothingConfig config = null)
|
||||
{
|
||||
_config = config ?? new InkSmoothingConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用改进的贝塞尔曲线算法平滑笔画
|
||||
/// </summary>
|
||||
public Stroke SmoothStroke(Stroke originalStroke)
|
||||
{
|
||||
if (originalStroke == null || originalStroke.StylusPoints.Count < 3)
|
||||
return originalStroke;
|
||||
|
||||
var originalPoints = originalStroke.StylusPoints.ToArray();
|
||||
|
||||
// 预处理:去除噪声点
|
||||
var cleanedPoints = RemoveNoisePoints(originalPoints);
|
||||
|
||||
// 使用改进的贝塞尔曲线拟合
|
||||
var smoothedPoints = ApplyCubicBezierSmoothing(cleanedPoints);
|
||||
|
||||
// 后处理:重采样和优化
|
||||
var finalPoints = PostProcessPoints(smoothedPoints);
|
||||
|
||||
return new Stroke(new StylusPointCollection(finalPoints))
|
||||
{
|
||||
DrawingAttributes = originalStroke.DrawingAttributes.Clone()
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 去除噪声点
|
||||
/// </summary>
|
||||
private StylusPoint[] RemoveNoisePoints(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 3) return points;
|
||||
|
||||
var result = new List<StylusPoint> { points[0] };
|
||||
double minDistance = _config.ResampleInterval * 0.5;
|
||||
|
||||
for (int i = 1; i < points.Length - 1; i++)
|
||||
{
|
||||
var prev = result[result.Count - 1];
|
||||
var curr = points[i];
|
||||
var next = points[i + 1];
|
||||
|
||||
// 计算到前一个点的距离
|
||||
double distToPrev = Math.Sqrt((curr.X - prev.X) * (curr.X - prev.X) +
|
||||
(curr.Y - prev.Y) * (curr.Y - prev.Y));
|
||||
|
||||
// 如果距离太近,跳过这个点
|
||||
if (distToPrev < minDistance)
|
||||
continue;
|
||||
|
||||
// 检查是否为异常点(与前后点形成锐角)
|
||||
if (IsOutlierPoint(prev, curr, next))
|
||||
continue;
|
||||
|
||||
result.Add(curr);
|
||||
}
|
||||
|
||||
result.Add(points[points.Length - 1]);
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否为异常点
|
||||
/// </summary>
|
||||
private bool IsOutlierPoint(StylusPoint prev, StylusPoint curr, StylusPoint next)
|
||||
{
|
||||
var v1 = new Vector(curr.X - prev.X, curr.Y - prev.Y);
|
||||
var v2 = new Vector(next.X - curr.X, next.Y - curr.Y);
|
||||
|
||||
if (v1.Length == 0 || v2.Length == 0) return false;
|
||||
|
||||
v1.Normalize();
|
||||
v2.Normalize();
|
||||
|
||||
double dotProduct = Vector.Multiply(v1, v2);
|
||||
double angle = Math.Acos(Math.Max(-1, Math.Min(1, dotProduct)));
|
||||
|
||||
// 如果角度小于30度,认为是异常点
|
||||
return angle < Math.PI / 6;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用三次贝塞尔曲线平滑
|
||||
/// </summary>
|
||||
private StylusPoint[] ApplyCubicBezierSmoothing(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length < 4) return points;
|
||||
|
||||
var result = new List<StylusPoint>();
|
||||
result.Add(points[0]);
|
||||
|
||||
// 使用滑动窗口进行贝塞尔曲线拟合
|
||||
for (int i = 0; i <= points.Length - 4; i++)
|
||||
{
|
||||
var p0 = points[i];
|
||||
var p1 = points[i + 1];
|
||||
var p2 = points[i + 2];
|
||||
var p3 = points[i + 3];
|
||||
|
||||
// 计算控制点
|
||||
var controlPoints = CalculateOptimalControlPoints(p0, p1, p2, p3);
|
||||
|
||||
// 计算插值步数
|
||||
int steps = CalculateInterpolationSteps(p0, p1, p2, p3);
|
||||
|
||||
// 生成贝塞尔曲线点
|
||||
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 result.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算最优控制点
|
||||
/// </summary>
|
||||
private (Point cp1, Point cp2) CalculateOptimalControlPoints(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
// 计算切线方向
|
||||
var tangent1 = CalculateTangent(p0, p1, p2);
|
||||
var tangent2 = CalculateTangent(p1, p2, p3);
|
||||
|
||||
// 计算控制点距离
|
||||
double dist1 = CalculateDistance(p0, p1);
|
||||
double dist2 = CalculateDistance(p2, p3);
|
||||
|
||||
double controlDist1 = dist1 * _config.CurveTension;
|
||||
double controlDist2 = dist2 * _config.CurveTension;
|
||||
|
||||
// 计算控制点
|
||||
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 Vector CalculateTangent(StylusPoint p0, StylusPoint p1, StylusPoint p2)
|
||||
{
|
||||
var v1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||
var v2 = new Vector(p2.X - p1.X, p2.Y - p1.Y);
|
||||
|
||||
// 如果向量长度为零,返回零向量
|
||||
if (v1.Length == 0 || v2.Length == 0)
|
||||
return new Vector(0, 0);
|
||||
|
||||
v1.Normalize();
|
||||
v2.Normalize();
|
||||
|
||||
// 返回平均方向
|
||||
var tangent = (v1 + v2) / 2;
|
||||
if (tangent.Length > 0)
|
||||
tangent.Normalize();
|
||||
|
||||
return tangent;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算两点间距离
|
||||
/// </summary>
|
||||
private double CalculateDistance(StylusPoint p1, StylusPoint p2)
|
||||
{
|
||||
double dx = p2.X - p1.X;
|
||||
double dy = p2.Y - p1.Y;
|
||||
return Math.Sqrt(dx * dx + dy * dy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算插值步数
|
||||
/// </summary>
|
||||
private int CalculateInterpolationSteps(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
if (!_config.UseAdaptiveInterpolation)
|
||||
return _config.InterpolationSteps;
|
||||
|
||||
// 计算曲线长度
|
||||
double totalLength = CalculateDistance(p0, p1) + CalculateDistance(p1, p2) + CalculateDistance(p2, p3);
|
||||
|
||||
// 计算曲率
|
||||
double curvature = CalculateCurvature(p0, p1, p2, p3);
|
||||
|
||||
// 基于长度和曲率计算步数
|
||||
int baseSteps = Math.Max(8, Math.Min(20, (int)(totalLength / 10)));
|
||||
int curvatureSteps = (int)(curvature * 15);
|
||||
|
||||
return Math.Max(_config.InterpolationSteps, Math.Min(30, baseSteps + curvatureSteps));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算曲率
|
||||
/// </summary>
|
||||
private double CalculateCurvature(StylusPoint p0, StylusPoint p1, StylusPoint p2, StylusPoint p3)
|
||||
{
|
||||
var v1 = new Vector(p1.X - p0.X, p1.Y - p0.Y);
|
||||
var v2 = new Vector(p2.X - p1.X, p2.Y - p1.Y);
|
||||
var v3 = new Vector(p3.X - p2.X, p3.Y - p2.Y);
|
||||
|
||||
if (v1.Length == 0 || v2.Length == 0 || v3.Length == 0) return 0;
|
||||
|
||||
v1.Normalize();
|
||||
v2.Normalize();
|
||||
v3.Normalize();
|
||||
|
||||
// 计算角度变化
|
||||
double angle1 = Math.Acos(Math.Max(-1, Math.Min(1, Vector.Multiply(v1, v2))));
|
||||
double angle2 = Math.Acos(Math.Max(-1, Math.Min(1, Vector.Multiply(v2, v3))));
|
||||
|
||||
return (angle1 + angle2) / Math.PI; // 归一化到0-1
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算贝塞尔曲线上的点
|
||||
/// </summary>
|
||||
private StylusPoint CalculateBezierPoint(StylusPoint p0, Point cp1, Point cp2, StylusPoint p3, double t)
|
||||
{
|
||||
double u = 1 - t;
|
||||
double tt = t * t;
|
||||
double uu = u * u;
|
||||
double uuu = uu * u;
|
||||
double ttt = tt * t;
|
||||
|
||||
// 预计算系数
|
||||
double c0 = uuu;
|
||||
double c1 = 3 * uu * t;
|
||||
double c2 = 3 * u * tt;
|
||||
double c3 = ttt;
|
||||
|
||||
double x = c0 * p0.X + c1 * cp1.X + c2 * cp2.X + c3 * p3.X;
|
||||
double y = c0 * p0.Y + c1 * cp1.Y + c2 * cp2.Y + c3 * p3.Y;
|
||||
|
||||
// 插值压力值
|
||||
float pressure = (float)(p0.PressureFactor * u + p3.PressureFactor * t);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
return new StylusPoint(x, y, pressure);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 后处理点集
|
||||
/// </summary>
|
||||
private StylusPoint[] PostProcessPoints(StylusPoint[] points)
|
||||
{
|
||||
if (points.Length == 0) return points;
|
||||
|
||||
// 如果点数过多,进行重采样
|
||||
if (points.Length > _config.MaxPointsPerStroke)
|
||||
{
|
||||
return ResamplePoints(points, _config.ResampleInterval);
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重采样点集
|
||||
/// </summary>
|
||||
private StylusPoint[] ResamplePoints(StylusPoint[] points, double interval)
|
||||
{
|
||||
var result = new List<StylusPoint> { points[0] };
|
||||
double accumulated = 0;
|
||||
|
||||
for (int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var prev = result[result.Count - 1];
|
||||
var curr = points[i];
|
||||
double dx = curr.X - prev.X;
|
||||
double dy = curr.Y - prev.Y;
|
||||
double dist = Math.Sqrt(dx * dx + dy * dy);
|
||||
|
||||
if (dist + accumulated >= interval)
|
||||
{
|
||||
double t = (interval - accumulated) / dist;
|
||||
double x = prev.X + t * dx;
|
||||
double y = prev.Y + t * dy;
|
||||
float pressure = (float)(prev.PressureFactor * (1 - t) + curr.PressureFactor * t);
|
||||
pressure = Math.Max(pressure, 0.1f);
|
||||
|
||||
result.Add(new StylusPoint(x, y, pressure));
|
||||
accumulated = 0;
|
||||
i--; // 重新处理当前点
|
||||
}
|
||||
else
|
||||
{
|
||||
accumulated += dist;
|
||||
}
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,897 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Effects;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 墨迹渐隐管理器 - 管理墨迹的渐隐动画和状态
|
||||
/// </summary>
|
||||
public class InkFadeManager
|
||||
{
|
||||
#region Properties
|
||||
/// <summary>
|
||||
/// 是否启用墨迹渐隐功能
|
||||
/// </summary>
|
||||
public bool IsEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹渐隐时间(毫秒)
|
||||
/// </summary>
|
||||
public int FadeTime { get; set; } = 3000;
|
||||
|
||||
/// <summary>
|
||||
/// 渐隐动画持续时间(毫秒)
|
||||
/// </summary>
|
||||
public int AnimationDuration { get; set; } = 1000;
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private readonly MainWindow _mainWindow;
|
||||
private readonly Dispatcher _dispatcher;
|
||||
private readonly Dictionary<Stroke, DispatcherTimer> _fadeTimers;
|
||||
private readonly Dictionary<Stroke, UIElement> _strokeVisuals;
|
||||
private readonly Dictionary<Stroke, Point> _strokeStartPoints;
|
||||
private readonly Dictionary<Stroke, Point> _strokeEndPoints;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public InkFadeManager(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
|
||||
_dispatcher = _mainWindow.Dispatcher;
|
||||
_fadeTimers = new Dictionary<Stroke, DispatcherTimer>();
|
||||
_strokeVisuals = new Dictionary<Stroke, UIElement>();
|
||||
_strokeStartPoints = new Dictionary<Stroke, Point>();
|
||||
_strokeEndPoints = new Dictionary<Stroke, Point>();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 添加需要渐隐的墨迹
|
||||
/// </summary>
|
||||
/// <param name="stroke">墨迹对象</param>
|
||||
/// <param name="startPoint">落笔点</param>
|
||||
/// <param name="endPoint">抬笔点</param>
|
||||
public void AddFadingStroke(Stroke stroke, Point startPoint, Point endPoint)
|
||||
{
|
||||
if (!IsEnabled || stroke == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 确保主窗口的InkCanvas保持Ink编辑模式,防止墨迹渐隐时切换到鼠标模式
|
||||
if (_mainWindow.inkCanvas.EditingMode != InkCanvasEditingMode.Ink)
|
||||
{
|
||||
_mainWindow.inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
}
|
||||
|
||||
// 记录墨迹的起点和终点
|
||||
_strokeStartPoints[stroke] = startPoint;
|
||||
_strokeEndPoints[stroke] = endPoint;
|
||||
|
||||
// 创建墨迹的视觉元素(湿墨迹状态)
|
||||
var strokeVisual = CreateStrokeVisual(stroke);
|
||||
if (strokeVisual == null) return;
|
||||
|
||||
_strokeVisuals[stroke] = strokeVisual;
|
||||
|
||||
// 创建定时器,在指定时间后开始渐隐动画
|
||||
var timer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(FadeTime)
|
||||
};
|
||||
|
||||
timer.Tick += (sender, e) =>
|
||||
{
|
||||
StartFadeAnimation(stroke);
|
||||
timer.Stop();
|
||||
_fadeTimers.Remove(stroke);
|
||||
};
|
||||
|
||||
_fadeTimers[stroke] = timer;
|
||||
timer.Start();
|
||||
|
||||
// 将视觉元素添加到画布上
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_mainWindow.inkCanvas != null)
|
||||
{
|
||||
// 将墨迹添加到 inkCanvas 的父容器中,而不是 inkCanvas.Children
|
||||
// 这样可以避免坐标系统问题
|
||||
var parent = _mainWindow.inkCanvas.Parent as Panel;
|
||||
if (parent != null)
|
||||
{
|
||||
parent.Children.Add(strokeVisual);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果无法获取父容器,则添加到 inkCanvas.Children
|
||||
_mainWindow.inkCanvas.Children.Add(strokeVisual);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加墨迹视觉元素到画布失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除墨迹
|
||||
/// </summary>
|
||||
/// <param name="stroke">要移除的墨迹</param>
|
||||
public void RemoveStroke(Stroke stroke)
|
||||
{
|
||||
if (stroke == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
if (_fadeTimers.TryGetValue(stroke, out var timer))
|
||||
{
|
||||
timer.Stop();
|
||||
_fadeTimers.Remove(stroke);
|
||||
}
|
||||
|
||||
if (_strokeVisuals.TryGetValue(stroke, out var visual))
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 从父容器中移除墨迹
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
if (parent != null && parent.Children.Contains(visual))
|
||||
{
|
||||
parent.Children.Remove(visual);
|
||||
}
|
||||
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(visual))
|
||||
{
|
||||
_mainWindow.inkCanvas.Children.Remove(visual);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从画布移除墨迹视觉元素失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
_strokeVisuals.Remove(stroke);
|
||||
}
|
||||
|
||||
_strokeStartPoints.Remove(stroke);
|
||||
_strokeEndPoints.Remove(stroke);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"移除渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有渐隐墨迹
|
||||
/// </summary>
|
||||
public void ClearAllFadingStrokes()
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var timer in _fadeTimers.Values)
|
||||
{
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
_fadeTimers.Clear();
|
||||
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_mainWindow.inkCanvas != null)
|
||||
{
|
||||
var parent = _mainWindow.inkCanvas.Parent as Panel;
|
||||
foreach (var visual in _strokeVisuals.Values)
|
||||
{
|
||||
if (parent != null && parent.Children.Contains(visual))
|
||||
{
|
||||
parent.Children.Remove(visual);
|
||||
}
|
||||
else if (_mainWindow.inkCanvas.Children.Contains(visual))
|
||||
{
|
||||
_mainWindow.inkCanvas.Children.Remove(visual);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除所有墨迹视觉元素失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
_strokeVisuals.Clear();
|
||||
_strokeStartPoints.Clear();
|
||||
_strokeEndPoints.Clear();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除所有渐隐墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新渐隐时间设置
|
||||
/// </summary>
|
||||
/// <param name="fadeTime">新的渐隐时间(毫秒)</param>
|
||||
public void UpdateFadeTime(int fadeTime)
|
||||
{
|
||||
FadeTime = fadeTime;
|
||||
|
||||
foreach (var kvp in _fadeTimers)
|
||||
{
|
||||
var stroke = kvp.Key;
|
||||
var timer = kvp.Value;
|
||||
|
||||
timer.Stop();
|
||||
timer.Interval = TimeSpan.FromMilliseconds(FadeTime);
|
||||
timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 启用墨迹渐隐功能
|
||||
/// </summary>
|
||||
public void Enable()
|
||||
{
|
||||
IsEnabled = true;
|
||||
LogHelper.WriteLogToFile("墨迹渐隐功能已启用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用墨迹渐隐功能
|
||||
/// </summary>
|
||||
public void Disable()
|
||||
{
|
||||
IsEnabled = false;
|
||||
LogHelper.WriteLogToFile("墨迹渐隐功能已禁用");
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
/// <summary>
|
||||
/// 创建墨迹的视觉元素
|
||||
/// </summary>
|
||||
/// <param name="stroke">墨迹对象</param>
|
||||
/// <returns>视觉元素</returns>
|
||||
private UIElement CreateStrokeVisual(Stroke stroke)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建路径几何,使用墨迹的实际位置
|
||||
var geometry = stroke.GetGeometry();
|
||||
if (geometry == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取绘画属性
|
||||
var drawingAttribs = stroke.DrawingAttributes;
|
||||
|
||||
// 创建路径元素,确保使用正确的绘画属性
|
||||
var path = new Path
|
||||
{
|
||||
Data = geometry,
|
||||
Stroke = new SolidColorBrush(drawingAttribs.Color),
|
||||
StrokeThickness = drawingAttribs.Width, // 使用原始墨迹的粗细
|
||||
StrokeStartLineCap = PenLineCap.Round,
|
||||
StrokeEndLineCap = PenLineCap.Round,
|
||||
StrokeLineJoin = PenLineJoin.Round,
|
||||
Fill = drawingAttribs.IsHighlighter ? new SolidColorBrush(drawingAttribs.Color) : null, // 高亮笔需要填充
|
||||
Opacity = 0.95, // 初始透明度更高,显得更自然
|
||||
|
||||
// 优化渲染质量
|
||||
UseLayoutRounding = false,
|
||||
SnapsToDevicePixels = false
|
||||
};
|
||||
|
||||
// 如果是高亮笔,调整透明度和混合模式
|
||||
if (drawingAttribs.IsHighlighter)
|
||||
{
|
||||
path.Opacity = 0.4; // 高亮笔初始透明度更低,更符合荧光笔特性
|
||||
|
||||
// 为高亮笔添加特殊的混合效果
|
||||
// 使用更柔和的笔触样式
|
||||
path.StrokeStartLineCap = PenLineCap.Flat;
|
||||
path.StrokeEndLineCap = PenLineCap.Flat;
|
||||
path.StrokeLineJoin = PenLineJoin.Miter;
|
||||
|
||||
// 高亮笔通常需要更宽的笔触来覆盖下面的内容
|
||||
if (drawingAttribs.Width < 20)
|
||||
{
|
||||
path.StrokeThickness = Math.Max(drawingAttribs.Width * 1.5, 20);
|
||||
}
|
||||
|
||||
// 为高亮笔添加轻微的模糊效果,使渐隐更加自然
|
||||
path.Effect = new BlurEffect
|
||||
{
|
||||
Radius = 0.5, // 轻微的模糊效果
|
||||
KernelType = KernelType.Gaussian
|
||||
};
|
||||
}
|
||||
|
||||
// 不设置任何变换,保持墨迹原有粗细
|
||||
var bounds = geometry.Bounds;
|
||||
|
||||
// 设置墨迹的初始位置
|
||||
System.Windows.Controls.Canvas.SetLeft(path, bounds.Left);
|
||||
System.Windows.Controls.Canvas.SetTop(path, bounds.Top);
|
||||
|
||||
return path;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始渐隐动画
|
||||
/// </summary>
|
||||
/// <param name="stroke">要渐隐的墨迹</param>
|
||||
private void StartFadeAnimation(Stroke stroke)
|
||||
{
|
||||
if (!_strokeVisuals.TryGetValue(stroke, out var visual)) return;
|
||||
|
||||
try
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 获取当前透明度和判断是否为高亮笔
|
||||
var currentOpacity = visual.Opacity;
|
||||
var isHighlighter = stroke.DrawingAttributes.IsHighlighter;
|
||||
|
||||
// 根据墨迹类型选择不同的动画效果
|
||||
if (isHighlighter)
|
||||
{
|
||||
StartHighlighterFadeAnimation(visual, stroke, currentOpacity);
|
||||
}
|
||||
else
|
||||
{
|
||||
StartNormalStrokeFadeAnimation(visual, stroke, currentOpacity);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"开始渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始普通墨迹的渐隐动画
|
||||
/// </summary>
|
||||
private void StartNormalStrokeFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity)
|
||||
{
|
||||
try
|
||||
{
|
||||
StartProgressiveFadeAnimation(visual, stroke, currentOpacity, AnimationDuration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"开始普通墨迹渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 统一渐隐动画 - 整个墨迹作为一个整体进行渐隐,与擦除效果一致
|
||||
/// </summary>
|
||||
private void StartUnifiedFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建透明度动画,模拟擦除时的效果
|
||||
var fadeAnimation = new DoubleAnimation
|
||||
{
|
||||
From = currentOpacity,
|
||||
To = 0.0,
|
||||
Duration = TimeSpan.FromMilliseconds(duration),
|
||||
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }
|
||||
};
|
||||
|
||||
// 如果是高亮笔,添加轻微的缩放效果,使渐隐更加自然
|
||||
if (stroke.DrawingAttributes.IsHighlighter)
|
||||
{
|
||||
// 创建轻微的缩放动画,模拟墨迹"蒸发"的效果
|
||||
var scaleAnimation = new DoubleAnimation
|
||||
{
|
||||
From = 1.0,
|
||||
To = 0.95, // 轻微缩小,增加自然感
|
||||
Duration = TimeSpan.FromMilliseconds(duration),
|
||||
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn }
|
||||
};
|
||||
|
||||
// 创建缩放变换
|
||||
var scaleTransform = new ScaleTransform();
|
||||
visual.RenderTransform = scaleTransform;
|
||||
visual.RenderTransformOrigin = new Point(0.5, 0.5);
|
||||
|
||||
// 应用缩放动画
|
||||
scaleTransform.BeginAnimation(ScaleTransform.ScaleXProperty, scaleAnimation);
|
||||
scaleTransform.BeginAnimation(ScaleTransform.ScaleYProperty, scaleAnimation);
|
||||
}
|
||||
|
||||
// 添加动画完成事件
|
||||
fadeAnimation.Completed += (sender, e) => OnAnimationCompleted(visual, stroke);
|
||||
|
||||
// 应用透明度动画
|
||||
visual.BeginAnimation(UIElement.OpacityProperty, fadeAnimation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"统一渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
OnAnimationCompleted(visual, stroke);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始高亮笔的渐隐动画
|
||||
/// </summary>
|
||||
private void StartHighlighterFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 高亮笔使用统一的渐隐动画,与擦除效果一致
|
||||
StartUnifiedFadeAnimation(visual, stroke, currentOpacity, (int)(AnimationDuration * 1.2));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"开始高亮笔渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 渐进式渐隐动画 - 从起点到终点逐渐消失
|
||||
/// </summary>
|
||||
private void StartProgressiveFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保所有墨迹都能显示动画,包括短墨迹
|
||||
if (stroke.StylusPoints.Count < 2)
|
||||
{
|
||||
// 只有1个点的墨迹也使用分段动画,确保视觉效果
|
||||
CreateSegmentedStroke(visual, stroke, currentOpacity, duration);
|
||||
return;
|
||||
}
|
||||
|
||||
// 将墨迹分段并创建多个 Path
|
||||
CreateSegmentedStroke(visual, stroke, currentOpacity, duration);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"渐进式渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
// 失败时回退到简单动画
|
||||
StartSimpleFadeAnimation(visual, stroke, currentOpacity, duration);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建分段墨迹并开始渐进消失
|
||||
/// </summary>
|
||||
private void CreateSegmentedStroke(UIElement originalVisual, Stroke stroke, double opacity, int duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stylusPoints = stroke.StylusPoints;
|
||||
var totalPoints = stylusPoints.Count;
|
||||
|
||||
// 分段算法 - 确保所有墨迹都有足够的动画效果
|
||||
var strokeLength = CalculateStrokeLength(stylusPoints);
|
||||
var segmentCount = CalculateOptimalSegmentCount(totalPoints, strokeLength);
|
||||
|
||||
// 强制最小分段数量,确保短墨迹也有动画效果
|
||||
segmentCount = Math.Max(segmentCount, 4);
|
||||
|
||||
var pointsPerSegment = Math.Max(1, totalPoints / segmentCount);
|
||||
|
||||
// 隐藏原始视觉元素
|
||||
originalVisual.Visibility = Visibility.Hidden;
|
||||
|
||||
var segments = new List<UIElement>();
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
if (parent == null)
|
||||
{
|
||||
// 如果父容器不是Panel,直接使用InkCanvas
|
||||
parent = null; // 稍后会检查并使用InkCanvas.Children
|
||||
}
|
||||
|
||||
// 创建各个分段 - 确保短墨迹也能正确分段
|
||||
for (int i = 0; i < segmentCount; i++)
|
||||
{
|
||||
var startIndex = i * pointsPerSegment;
|
||||
var endIndex = (i == segmentCount - 1) ? totalPoints - 1 : (i + 1) * pointsPerSegment;
|
||||
|
||||
// 确保有足够的点来创建分段,对于短墨迹特殊处理
|
||||
if (endIndex <= startIndex && totalPoints > 1)
|
||||
{
|
||||
// 短墨迹:每个点作为一个分段
|
||||
startIndex = i;
|
||||
endIndex = Math.Min(i + 1, totalPoints - 1);
|
||||
}
|
||||
|
||||
// 为每个分段添加重叠,确保连接处平滑
|
||||
var overlap = Math.Max(1, pointsPerSegment / 6); // 15%的重叠,平衡平滑与速度
|
||||
var actualStartIndex = Math.Max(0, startIndex - overlap);
|
||||
var actualEndIndex = Math.Min(totalPoints - 1, endIndex + overlap);
|
||||
|
||||
var segment = CreateStrokeSegment(stroke, actualStartIndex, actualEndIndex, opacity);
|
||||
if (segment != null)
|
||||
{
|
||||
segments.Add(segment);
|
||||
if (parent != null)
|
||||
{
|
||||
parent.Children.Add(segment);
|
||||
}
|
||||
else if (_mainWindow.inkCanvas != null)
|
||||
{
|
||||
_mainWindow.inkCanvas.Children.Add(segment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 开始分段渐隐动画
|
||||
StartSegmentedFadeAnimation(segments, stroke, originalVisual, duration);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
StartSimpleFadeAnimation(originalVisual, stroke, opacity, duration);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建墨迹分段
|
||||
/// </summary>
|
||||
private UIElement CreateStrokeSegment(Stroke originalStroke, int startIndex, int endIndex, double opacity)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建分段的 StylusPoint 集合
|
||||
var segmentPoints = new StylusPointCollection();
|
||||
for (int i = startIndex; i <= endIndex && i < originalStroke.StylusPoints.Count; i++)
|
||||
{
|
||||
segmentPoints.Add(originalStroke.StylusPoints[i]);
|
||||
}
|
||||
|
||||
if (segmentPoints.Count < 2) return null;
|
||||
|
||||
// 创建分段墨迹
|
||||
var segmentStroke = new Stroke(segmentPoints)
|
||||
{
|
||||
DrawingAttributes = originalStroke.DrawingAttributes.Clone()
|
||||
};
|
||||
|
||||
// 创建分段的视觉元素
|
||||
var geometry = segmentStroke.GetGeometry();
|
||||
if (geometry == null) return null;
|
||||
|
||||
var drawingAttribs = segmentStroke.DrawingAttributes;
|
||||
var path = new Path
|
||||
{
|
||||
Data = geometry,
|
||||
Stroke = new SolidColorBrush(drawingAttribs.Color),
|
||||
StrokeThickness = drawingAttribs.Width,
|
||||
StrokeStartLineCap = drawingAttribs.IsHighlighter ? PenLineCap.Flat : PenLineCap.Round,
|
||||
StrokeEndLineCap = drawingAttribs.IsHighlighter ? PenLineCap.Flat : PenLineCap.Round,
|
||||
StrokeLineJoin = drawingAttribs.IsHighlighter ? PenLineJoin.Miter : PenLineJoin.Round,
|
||||
Fill = drawingAttribs.IsHighlighter ? new SolidColorBrush(drawingAttribs.Color) : null,
|
||||
Opacity = opacity,
|
||||
UseLayoutRounding = false,
|
||||
SnapsToDevicePixels = false
|
||||
};
|
||||
|
||||
// 设置位置
|
||||
var bounds = geometry.Bounds;
|
||||
System.Windows.Controls.Canvas.SetLeft(path, bounds.Left);
|
||||
System.Windows.Controls.Canvas.SetTop(path, bounds.Top);
|
||||
|
||||
return path;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 开始分段渐隐动画
|
||||
/// </summary>
|
||||
private void StartSegmentedFadeAnimation(List<UIElement> segments, Stroke originalStroke, UIElement originalVisual, int totalDuration)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 动画时序算法
|
||||
var segmentDuration = CalculateOptimalSegmentDuration(totalDuration, segments.Count);
|
||||
var animationCurve = CreateAppleStyleAnimationCurve(segments.Count, totalDuration);
|
||||
|
||||
// 跟踪动画完成状态
|
||||
var completedSegments = new HashSet<UIElement>();
|
||||
var totalSegments = segments.Count;
|
||||
|
||||
// 渐隐效果 - 使用自然的动画曲线
|
||||
for (int i = 0; i < segments.Count; i++)
|
||||
{
|
||||
var segment = segments[i];
|
||||
|
||||
// 使用预计算的动画曲线获取延迟时间
|
||||
var delay = animationCurve[i];
|
||||
|
||||
// 使用定时器延迟启动每个分段的动画
|
||||
var timer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(delay)
|
||||
};
|
||||
|
||||
int segmentIndex = i; // 捕获当前索引
|
||||
timer.Tick += (sender, e) =>
|
||||
{
|
||||
StartSingleSegmentFadeAnimation(segment, segmentDuration, () =>
|
||||
{
|
||||
// 动画完成回调
|
||||
lock (completedSegments)
|
||||
{
|
||||
completedSegments.Add(segment);
|
||||
|
||||
// 检查是否所有分段都完成了
|
||||
if (completedSegments.Count >= totalSegments)
|
||||
{
|
||||
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
|
||||
}
|
||||
}
|
||||
});
|
||||
timer.Stop();
|
||||
};
|
||||
|
||||
timer.Start();
|
||||
}
|
||||
|
||||
// 设置一个安全超时定时器,防止无限等待
|
||||
var safetyTimeout = totalDuration + (segments.Count * segmentDuration) + 1200; // 额外1.2秒缓冲,确保动画完整
|
||||
var safetyTimer = new DispatcherTimer
|
||||
{
|
||||
Interval = TimeSpan.FromMilliseconds(safetyTimeout)
|
||||
};
|
||||
|
||||
safetyTimer.Tick += (sender, e) =>
|
||||
{
|
||||
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
|
||||
safetyTimer.Stop();
|
||||
};
|
||||
|
||||
safetyTimer.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"分段渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
CleanupSegmentedAnimation(segments, originalStroke, originalVisual);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 单个分段的渐隐动画
|
||||
/// </summary>
|
||||
private void StartSingleSegmentFadeAnimation(UIElement segment, int duration, Action onCompleted = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只使用透明度动画,保持墨迹原有粗细
|
||||
var fadeAnimation = new DoubleAnimation
|
||||
{
|
||||
From = segment.Opacity,
|
||||
To = 0.0,
|
||||
Duration = TimeSpan.FromMilliseconds(duration),
|
||||
EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut } // 更平滑的缓动
|
||||
};
|
||||
|
||||
// 添加动画完成事件
|
||||
if (onCompleted != null)
|
||||
{
|
||||
fadeAnimation.Completed += (sender, e) =>
|
||||
{
|
||||
onCompleted?.Invoke();
|
||||
};
|
||||
}
|
||||
|
||||
// 只应用透明度动画,不改变墨迹大小
|
||||
segment.BeginAnimation(UIElement.OpacityProperty, fadeAnimation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"单个分段渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
// 即使失败也要调用完成回调
|
||||
onCompleted?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理分段动画
|
||||
/// </summary>
|
||||
private void CleanupSegmentedAnimation(List<UIElement> segments, Stroke originalStroke, UIElement originalVisual)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 移除所有分段
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
|
||||
foreach (var segment in segments)
|
||||
{
|
||||
if (parent != null && parent.Children.Contains(segment))
|
||||
{
|
||||
parent.Children.Remove(segment);
|
||||
}
|
||||
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(segment))
|
||||
{
|
||||
_mainWindow.inkCanvas.Children.Remove(segment);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理原始墨迹
|
||||
OnAnimationCompleted(originalVisual, originalStroke);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理分段动画失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简单渐隐动画(备用方案)
|
||||
/// </summary>
|
||||
private void StartSimpleFadeAnimation(UIElement visual, Stroke stroke, double currentOpacity, int duration)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fadeAnimation = new DoubleAnimation
|
||||
{
|
||||
From = currentOpacity,
|
||||
To = 0.0,
|
||||
Duration = TimeSpan.FromMilliseconds(duration),
|
||||
EasingFunction = new QuadraticEase { EasingMode = EasingMode.EaseIn }
|
||||
};
|
||||
|
||||
fadeAnimation.Completed += (sender, e) => OnAnimationCompleted(visual, stroke);
|
||||
visual.BeginAnimation(UIElement.OpacityProperty, fadeAnimation);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"简单渐隐动画失败: {ex}", LogHelper.LogType.Error);
|
||||
OnAnimationCompleted(visual, stroke);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算墨迹的实际长度
|
||||
/// </summary>
|
||||
private double CalculateStrokeLength(StylusPointCollection points)
|
||||
{
|
||||
if (points.Count < 2) return 0;
|
||||
|
||||
double totalLength = 0;
|
||||
for (int i = 1; i < points.Count; i++)
|
||||
{
|
||||
var p1 = points[i - 1].ToPoint();
|
||||
var p2 = points[i].ToPoint();
|
||||
totalLength += Math.Sqrt(Math.Pow(p2.X - p1.X, 2) + Math.Pow(p2.Y - p1.Y, 2));
|
||||
}
|
||||
return totalLength;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据墨迹特性计算最优分段数量 - 平衡速度与完整性
|
||||
/// </summary>
|
||||
private int CalculateOptimalSegmentCount(int pointCount, double strokeLength)
|
||||
{
|
||||
// 平衡速度与完整性,确保动画效果的同时提高速度
|
||||
const double PIXELS_PER_SEGMENT = 12.0; // 每段适中长度,平衡效果与速度
|
||||
const int MIN_SEGMENTS = 5; // 适当的最小分段数,确保动画效果
|
||||
const int MAX_SEGMENTS = 100; // 适中的最大分段数,平衡性能与效果
|
||||
|
||||
// 根据长度计算基础分段数
|
||||
var lengthBasedSegments = Math.Max(MIN_SEGMENTS, (int)(strokeLength / PIXELS_PER_SEGMENT));
|
||||
|
||||
// 根据点密度调整,平衡效果与速度
|
||||
var density = pointCount > 0 ? strokeLength / pointCount : 1;
|
||||
var densityFactor = Math.Max(0.4, Math.Min(2.5, density / 1.8));
|
||||
|
||||
var finalSegments = (int)(lengthBasedSegments * densityFactor);
|
||||
|
||||
// 对于短墨迹,确保至少有4个分段
|
||||
if (pointCount <= 5)
|
||||
{
|
||||
finalSegments = Math.Max(finalSegments, 4);
|
||||
}
|
||||
|
||||
// 限制在合理范围内
|
||||
return Math.Min(MAX_SEGMENTS, Math.Max(MIN_SEGMENTS, finalSegments));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算最优的单段动画持续时间 - 平衡速度与完整性
|
||||
/// </summary>
|
||||
private int CalculateOptimalSegmentDuration(int totalDuration, int segmentCount)
|
||||
{
|
||||
// 平衡速度与动画完整性
|
||||
var baseDuration = totalDuration / Math.Max(segmentCount, 1);
|
||||
var minDuration = 150; // 每段最少150ms,确保动画完整显示
|
||||
var maxDuration = 500; // 每段最多500ms,平衡速度与完整性
|
||||
|
||||
return Math.Max(minDuration, Math.Min(maxDuration, baseDuration));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建优化的动画时间曲线 - 平衡速度与完整性
|
||||
/// </summary>
|
||||
private int[] CreateAppleStyleAnimationCurve(int segmentCount, int totalDuration)
|
||||
{
|
||||
var curve = new int[segmentCount];
|
||||
|
||||
// 平衡速度与完整性,确保动画有足够时间播放
|
||||
var availableTime = totalDuration * 0.6; // 使用60%的总时间,给动画留足够缓冲
|
||||
var delayBetweenSegments = Math.Max(60, availableTime / Math.Max(segmentCount, 1));
|
||||
|
||||
for (int i = 0; i < segmentCount; i++)
|
||||
{
|
||||
// 线性延迟,确保每个分段都有足够时间
|
||||
curve[i] = (int)(i * delayBetweenSegments);
|
||||
}
|
||||
|
||||
return curve;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画完成后的统一处理
|
||||
/// </summary>
|
||||
private void OnAnimationCompleted(UIElement visual, Stroke stroke)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 从父容器中移除墨迹
|
||||
var parent = _mainWindow.inkCanvas?.Parent as Panel;
|
||||
if (parent != null && parent.Children.Contains(visual))
|
||||
{
|
||||
parent.Children.Remove(visual);
|
||||
}
|
||||
else if (_mainWindow.inkCanvas != null && _mainWindow.inkCanvas.Children.Contains(visual))
|
||||
{
|
||||
_mainWindow.inkCanvas.Children.Remove(visual);
|
||||
}
|
||||
|
||||
RemoveStroke(stroke);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"渐隐动画完成后清理墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+8
-1
@@ -15,7 +15,7 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
var analyzer = new InkAnalyzer();
|
||||
analyzer.AddStrokes(strokes);
|
||||
analyzer.SetStrokesType(strokes, System.Windows.Ink.StrokeType.Drawing);
|
||||
analyzer.SetStrokesType(strokes, StrokeType.Drawing);
|
||||
|
||||
AnalysisAlternate analysisAlternate = null;
|
||||
int strokesCount = strokes.Count;
|
||||
@@ -67,6 +67,13 @@ namespace Ink_Canvas.Helpers
|
||||
|
||||
//Recognizer 的实现
|
||||
|
||||
public enum RecognizeLanguage
|
||||
{
|
||||
SimplifiedChinese = 0x0804,
|
||||
TraditionalChinese = 0x7c03,
|
||||
English = 0x0809
|
||||
}
|
||||
|
||||
public class ShapeRecognizeResult
|
||||
{
|
||||
public ShapeRecognizeResult(Point centroid, PointCollection hotPoints, AnalysisAlternate analysisAlternate, InkDrawingNode node)
|
||||
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 墨迹平滑配置类
|
||||
/// </summary>
|
||||
public class InkSmoothingConfig
|
||||
{
|
||||
// 基本平滑参数
|
||||
public double SmoothingStrength { get; set; } = 0.4;
|
||||
public double ResampleInterval { get; set; } = 2.5;
|
||||
public int InterpolationSteps { get; set; } = 12;
|
||||
|
||||
// 贝塞尔曲线参数
|
||||
public bool UseAdaptiveInterpolation { get; set; } = true;
|
||||
public double CurveTension { get; set; } = 0.3;
|
||||
public double MinCurvatureThreshold { get; set; } = 0.1;
|
||||
public double MaxCurvatureThreshold { get; set; } = 0.8;
|
||||
|
||||
// 性能参数
|
||||
public bool UseHardwareAcceleration { get; set; } = true;
|
||||
public bool UseAsyncProcessing { get; set; } = true;
|
||||
public int MaxConcurrentTasks { get; set; } = Environment.ProcessorCount;
|
||||
public int MaxPointsPerStroke { get; set; } = 10000;
|
||||
|
||||
// 质量设置
|
||||
public SmoothingQuality Quality { get; set; } = SmoothingQuality.Balanced;
|
||||
|
||||
public enum SmoothingQuality
|
||||
{
|
||||
Performance, // 性能优先
|
||||
Balanced, // 平衡
|
||||
Quality // 质量优先
|
||||
}
|
||||
|
||||
// 兼容性枚举
|
||||
public enum InkSmoothingQuality
|
||||
{
|
||||
HighPerformance = 0, // 高性能低质量
|
||||
Balanced = 1, // 平衡
|
||||
HighQuality = 2 // 高质量低性能
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从设置中加载配置
|
||||
/// </summary>
|
||||
public static InkSmoothingConfig FromSettings()
|
||||
{
|
||||
var config = new InkSmoothingConfig();
|
||||
|
||||
try
|
||||
{
|
||||
// 尝试从MainWindow.Settings加载配置(兼容性)
|
||||
if (MainWindow.Settings?.Canvas != null)
|
||||
{
|
||||
config.Quality = (SmoothingQuality)MainWindow.Settings.Canvas.InkSmoothingQuality;
|
||||
config.UseHardwareAcceleration = MainWindow.Settings.Canvas.UseHardwareAcceleration;
|
||||
config.UseAsyncProcessing = MainWindow.Settings.Canvas.UseAsyncInkSmoothing;
|
||||
config.MaxConcurrentTasks = MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks > 0 ?
|
||||
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks : Environment.ProcessorCount;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"加载平滑配置失败: {ex.Message}");
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用质量设置
|
||||
/// </summary>
|
||||
public void ApplyQualitySettings()
|
||||
{
|
||||
// 保存用户设置的异步处理偏好
|
||||
bool userAsyncPreference = UseAsyncProcessing;
|
||||
|
||||
switch (Quality)
|
||||
{
|
||||
case SmoothingQuality.Performance:
|
||||
SmoothingStrength = 0.15;
|
||||
ResampleInterval = 5.0;
|
||||
InterpolationSteps = 4;
|
||||
UseAdaptiveInterpolation = false;
|
||||
CurveTension = 0.15;
|
||||
MaxConcurrentTasks = Math.Max(1, Environment.ProcessorCount / 2);
|
||||
UseHardwareAcceleration = true;
|
||||
UseAsyncProcessing = userAsyncPreference;
|
||||
break;
|
||||
|
||||
case SmoothingQuality.Balanced:
|
||||
SmoothingStrength = 0.3;
|
||||
ResampleInterval = 3.0;
|
||||
InterpolationSteps = 8;
|
||||
UseAdaptiveInterpolation = true;
|
||||
CurveTension = 0.25;
|
||||
MaxConcurrentTasks = Environment.ProcessorCount;
|
||||
UseHardwareAcceleration = true;
|
||||
UseAsyncProcessing = userAsyncPreference;
|
||||
break;
|
||||
|
||||
case SmoothingQuality.Quality:
|
||||
SmoothingStrength = 0.5;
|
||||
ResampleInterval = 2.0;
|
||||
InterpolationSteps = 15;
|
||||
UseAdaptiveInterpolation = true;
|
||||
CurveTension = 0.35;
|
||||
MaxConcurrentTasks = Environment.ProcessorCount;
|
||||
UseHardwareAcceleration = true;
|
||||
UseAsyncProcessing = userAsyncPreference;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存配置到设置
|
||||
/// </summary>
|
||||
public void SaveToSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 尝试保存到MainWindow.Settings(兼容性)
|
||||
if (MainWindow.Settings?.Canvas != null)
|
||||
{
|
||||
MainWindow.Settings.Canvas.InkSmoothingQuality = (int)Quality;
|
||||
MainWindow.Settings.Canvas.UseHardwareAcceleration = UseHardwareAcceleration;
|
||||
MainWindow.Settings.Canvas.UseAsyncInkSmoothing = UseAsyncProcessing;
|
||||
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks = MaxConcurrentTasks;
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"保存平滑配置失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 验证配置参数
|
||||
/// </summary>
|
||||
public bool Validate()
|
||||
{
|
||||
return SmoothingStrength >= 0.0 && SmoothingStrength <= 1.0 &&
|
||||
ResampleInterval > 0.0 &&
|
||||
InterpolationSteps > 0 && InterpolationSteps <= 50 &&
|
||||
CurveTension >= 0.0 && CurveTension <= 1.0 &&
|
||||
MaxConcurrentTasks > 0 &&
|
||||
MaxPointsPerStroke > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取配置摘要
|
||||
/// </summary>
|
||||
public string GetSummary()
|
||||
{
|
||||
return $"质量: {Quality}, 强度: {SmoothingStrength:F2}, 间隔: {ResampleInterval:F1}, " +
|
||||
$"步数: {InterpolationSteps}, 自适应: {UseAdaptiveInterpolation}, " +
|
||||
$"张力: {CurveTension:F2}, 硬件加速: {UseHardwareAcceleration}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 统一的墨迹平滑管理器,整合异步处理和硬件加速
|
||||
/// </summary>
|
||||
public class InkSmoothingManager : IDisposable
|
||||
{
|
||||
private readonly AsyncAdvancedBezierSmoothing _asyncSmoothing;
|
||||
private readonly HardwareAcceleratedInkProcessor _hardwareProcessor;
|
||||
private readonly InkSmoothingPerformanceMonitor _performanceMonitor;
|
||||
private readonly InkSmoothingConfig _config;
|
||||
private readonly Dispatcher _uiDispatcher;
|
||||
private bool _disposed;
|
||||
|
||||
public InkSmoothingManager(Dispatcher uiDispatcher)
|
||||
{
|
||||
_uiDispatcher = uiDispatcher;
|
||||
_config = InkSmoothingConfig.FromSettings();
|
||||
_config.ApplyQualitySettings();
|
||||
|
||||
_asyncSmoothing = new AsyncAdvancedBezierSmoothing(uiDispatcher)
|
||||
{
|
||||
SmoothingStrength = _config.SmoothingStrength,
|
||||
ResampleInterval = _config.ResampleInterval,
|
||||
InterpolationSteps = _config.InterpolationSteps,
|
||||
UseHardwareAcceleration = _config.UseHardwareAcceleration,
|
||||
MaxConcurrentTasks = _config.MaxConcurrentTasks
|
||||
};
|
||||
|
||||
_hardwareProcessor = new HardwareAcceleratedInkProcessor();
|
||||
_performanceMonitor = new InkSmoothingPerformanceMonitor();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 平滑笔画(自动选择最佳方法)
|
||||
/// </summary>
|
||||
public async Task<Stroke> SmoothStrokeAsync(Stroke originalStroke,
|
||||
Action<Stroke, Stroke> onCompleted = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
Stroke result = originalStroke;
|
||||
|
||||
try
|
||||
{
|
||||
if (_config.UseAsyncProcessing)
|
||||
{
|
||||
// 使用异步处理
|
||||
result = await _asyncSmoothing.SmoothStrokeAsync(originalStroke, onCompleted, cancellationToken);
|
||||
}
|
||||
else if (_config.UseHardwareAcceleration)
|
||||
{
|
||||
// 使用硬件加速但同步处理
|
||||
result = await _hardwareProcessor.SmoothStrokeWithGPU(originalStroke);
|
||||
onCompleted?.Invoke(originalStroke, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 回退到传统同步处理
|
||||
result = await Task.Run(() =>
|
||||
{
|
||||
var traditionalSmoothing = new AdvancedBezierSmoothing();
|
||||
return traditionalSmoothing.SmoothStroke(originalStroke);
|
||||
}, cancellationToken);
|
||||
onCompleted?.Invoke(originalStroke, result);
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
result = originalStroke;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"墨迹平滑失败: {ex.Message}");
|
||||
result = originalStroke;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
_performanceMonitor.RecordProcessingTime(stopwatch.Elapsed);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 同步平滑笔画(用于向后兼容)
|
||||
/// </summary>
|
||||
public Stroke SmoothStroke(Stroke originalStroke)
|
||||
{
|
||||
if (originalStroke == null || originalStroke.StylusPoints.Count < 2)
|
||||
return originalStroke;
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
Stroke result;
|
||||
|
||||
try
|
||||
{
|
||||
if (_config.UseHardwareAcceleration)
|
||||
{
|
||||
// 使用硬件加速的同步版本
|
||||
var task = _hardwareProcessor.SmoothStrokeWithGPU(originalStroke);
|
||||
task.Wait(5000); // 5秒超时
|
||||
result = task.Status == TaskStatus.RanToCompletion ? task.Result : originalStroke;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 传统同步处理
|
||||
var traditionalSmoothing = new AdvancedBezierSmoothing();
|
||||
result = traditionalSmoothing.SmoothStroke(originalStroke);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.WriteLine($"同步墨迹平滑失败: {ex.Message}");
|
||||
result = originalStroke;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
_performanceMonitor.RecordProcessingTime(stopwatch.Elapsed);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新配置
|
||||
/// </summary>
|
||||
public void UpdateConfig()
|
||||
{
|
||||
var newConfig = InkSmoothingConfig.FromSettings();
|
||||
newConfig.ApplyQualitySettings();
|
||||
|
||||
_asyncSmoothing.SmoothingStrength = newConfig.SmoothingStrength;
|
||||
_asyncSmoothing.ResampleInterval = newConfig.ResampleInterval;
|
||||
_asyncSmoothing.InterpolationSteps = newConfig.InterpolationSteps;
|
||||
_asyncSmoothing.UseHardwareAcceleration = newConfig.UseHardwareAcceleration;
|
||||
_asyncSmoothing.MaxConcurrentTasks = newConfig.MaxConcurrentTasks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取性能统计信息
|
||||
/// </summary>
|
||||
public string GetPerformanceStats()
|
||||
{
|
||||
return $"平均处理时间: {_performanceMonitor.GetAverageProcessingTimeMs():F2}ms, " +
|
||||
$"最大处理时间: {_performanceMonitor.GetMaxProcessingTimeMs():F2}ms, " +
|
||||
$"样本数: {_performanceMonitor.GetSampleCount()}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 取消所有正在进行的任务
|
||||
/// </summary>
|
||||
public void CancelAllTasks()
|
||||
{
|
||||
_asyncSmoothing?.CancelAllTasks();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查系统是否支持硬件加速
|
||||
/// </summary>
|
||||
public static bool IsHardwareAccelerationSupported()
|
||||
{
|
||||
try
|
||||
{
|
||||
return RenderCapability.Tier >= 0x00020000;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取推荐的配置
|
||||
/// </summary>
|
||||
public static InkSmoothingConfig GetRecommendedConfig()
|
||||
{
|
||||
var config = new InkSmoothingConfig();
|
||||
|
||||
// 根据系统性能调整配置
|
||||
var processorCount = Environment.ProcessorCount;
|
||||
var isHardwareAccelerated = IsHardwareAccelerationSupported();
|
||||
|
||||
if (processorCount >= 4 && isHardwareAccelerated)
|
||||
{
|
||||
// 降低高质量模式的门槛,4核以上且支持硬件加速就使用高质量
|
||||
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.HighQuality;
|
||||
config.UseHardwareAcceleration = true;
|
||||
config.UseAsyncProcessing = true;
|
||||
config.MaxConcurrentTasks = Math.Min(processorCount, 8);
|
||||
}
|
||||
else if (processorCount >= 2)
|
||||
{
|
||||
// 2核以上使用平衡模式
|
||||
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.Balanced;
|
||||
config.UseHardwareAcceleration = isHardwareAccelerated;
|
||||
config.UseAsyncProcessing = true;
|
||||
config.MaxConcurrentTasks = Math.Min(processorCount, 4);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 单核或性能较低的设备使用高性能模式
|
||||
config.Quality = (InkSmoothingConfig.SmoothingQuality)InkSmoothingConfig.InkSmoothingQuality.HighPerformance;
|
||||
config.UseHardwareAcceleration = false;
|
||||
config.UseAsyncProcessing = false;
|
||||
config.MaxConcurrentTasks = 1;
|
||||
}
|
||||
|
||||
config.ApplyQualitySettings();
|
||||
return config;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用推荐配置到设置
|
||||
/// </summary>
|
||||
public static void ApplyRecommendedSettings()
|
||||
{
|
||||
var config = GetRecommendedConfig();
|
||||
|
||||
MainWindow.Settings.Canvas.InkSmoothingQuality = (int)config.Quality;
|
||||
MainWindow.Settings.Canvas.UseHardwareAcceleration = config.UseHardwareAcceleration;
|
||||
MainWindow.Settings.Canvas.UseAsyncInkSmoothing = config.UseAsyncProcessing;
|
||||
MainWindow.Settings.Canvas.MaxConcurrentSmoothingTasks = config.MaxConcurrentTasks;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
CancelAllTasks();
|
||||
_asyncSmoothing?.Dispose();
|
||||
_hardwareProcessor?.Dispose();
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 墨迹平滑事件参数
|
||||
/// </summary>
|
||||
public class InkSmoothingEventArgs : EventArgs
|
||||
{
|
||||
public Stroke OriginalStroke { get; set; }
|
||||
public Stroke SmoothedStroke { get; set; }
|
||||
public TimeSpan ProcessingTime { get; set; }
|
||||
public bool WasAsync { get; set; }
|
||||
public bool UsedHardwareAcceleration { get; set; }
|
||||
}
|
||||
}
|
||||
+16
-12
@@ -1,32 +1,36 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Interop;
|
||||
using Point = System.Windows.Point;
|
||||
|
||||
namespace Ink_Canvas.Helpers {
|
||||
internal class IsOutsideOfScreenHelper {
|
||||
public static bool IsOutsideOfScreen(FrameworkElement target) {
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class IsOutsideOfScreenHelper
|
||||
{
|
||||
public static bool IsOutsideOfScreen(FrameworkElement target)
|
||||
{
|
||||
var hwndSource = (HwndSource)PresentationSource.FromVisual(target);
|
||||
if (hwndSource is null) {
|
||||
if (hwndSource is null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var hWnd = hwndSource.Handle;
|
||||
var targetBounds = GetPixelBoundsToScreen(target);
|
||||
|
||||
var screens = System.Windows.Forms.Screen.AllScreens;
|
||||
var screens = Screen.AllScreens;
|
||||
return !screens.Any(x => x.Bounds.IntersectsWith(targetBounds));
|
||||
|
||||
System.Drawing.Rectangle GetPixelBoundsToScreen(FrameworkElement visual) {
|
||||
Rectangle GetPixelBoundsToScreen(FrameworkElement visual)
|
||||
{
|
||||
var pixelBoundsToScreen = Rect.Empty;
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(0, 0)));
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(visual.ActualWidth, 0)));
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(0, visual.ActualHeight)));
|
||||
pixelBoundsToScreen.Union(visual.PointToScreen(new Point(visual.ActualWidth, visual.ActualHeight)));
|
||||
return new System.Drawing.Rectangle(
|
||||
return new Rectangle(
|
||||
(int)pixelBoundsToScreen.X, (int)pixelBoundsToScreen.Y,
|
||||
(int)pixelBoundsToScreen.Width, (int)pixelBoundsToScreen.Height);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
class LogHelper
|
||||
{
|
||||
public static string LogFile = "Log.txt";
|
||||
private static string LogsFolder = "Logs";
|
||||
private static string AppStartTime = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
|
||||
private static readonly long MaxLogsFolderSizeBytes = 5 * 1024 * 1024; // 5MB
|
||||
|
||||
public static void NewLog(string str)
|
||||
{
|
||||
WriteLogToFile(str);
|
||||
}
|
||||
|
||||
public static void NewLog(Exception ex)
|
||||
{
|
||||
if (ex == null) return;
|
||||
var stackTrace = ex.StackTrace ?? "<no stack trace>";
|
||||
var msg = $"[Exception] Type: {ex.GetType().FullName}\nMessage: {ex.Message}\nStackTrace: {stackTrace}";
|
||||
if (ex.InnerException != null)
|
||||
{
|
||||
msg += $"\nInnerException: {ex.InnerException.GetType().FullName} - {ex.InnerException.Message}\n{ex.InnerException.StackTrace}";
|
||||
}
|
||||
WriteLogToFile(msg, LogType.Error);
|
||||
}
|
||||
|
||||
public static void WriteLogToFile(string str, LogType logType = LogType.Info)
|
||||
{
|
||||
// 检查日志是否启用
|
||||
if (MainWindow.Settings != null && MainWindow.Settings.Advanced != null && !MainWindow.Settings.Advanced.IsLogEnabled) return;
|
||||
|
||||
string strLogType = logType.ToString();
|
||||
try
|
||||
{
|
||||
string file;
|
||||
|
||||
// 检查是否启用了日期保存功能
|
||||
if (MainWindow.Settings != null && MainWindow.Settings.Advanced != null && MainWindow.Settings.Advanced.IsSaveLogByDate)
|
||||
{
|
||||
// 确保Logs文件夹存在
|
||||
string logsPath = Path.Combine(App.RootPath, LogsFolder);
|
||||
if (!Directory.Exists(logsPath))
|
||||
{
|
||||
Directory.CreateDirectory(logsPath);
|
||||
}
|
||||
|
||||
// 检查Logs文件夹大小,如果超过5MB则清空
|
||||
CheckAndCleanLogsFolder(logsPath);
|
||||
|
||||
// 使用软件启动时间作为日志文件名
|
||||
file = Path.Combine(logsPath, $"Log_{AppStartTime}.txt");
|
||||
}
|
||||
else
|
||||
{
|
||||
file = App.RootPath + LogFile;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(App.RootPath))
|
||||
{
|
||||
Directory.CreateDirectory(App.RootPath);
|
||||
}
|
||||
|
||||
var threadId = Thread.CurrentThread.ManagedThreadId;
|
||||
var callingMethod = new StackTrace(2, true).GetFrame(0);
|
||||
string callerInfo = "<unknown>";
|
||||
if (callingMethod != null)
|
||||
{
|
||||
var method = callingMethod.GetMethod();
|
||||
if (method != null)
|
||||
{
|
||||
var className = method.DeclaringType != null ? method.DeclaringType.FullName : "<no class>";
|
||||
callerInfo = $"{className}.{method.Name}";
|
||||
}
|
||||
}
|
||||
string logLine = string.Format("{0} [T{1}] [{2}] [{3}] {4}", DateTime.Now.ToString("O"), threadId, strLogType, callerInfo, str);
|
||||
using (StreamWriter sw = new StreamWriter(file, true))
|
||||
{
|
||||
sw.WriteLine(logLine);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private static void CheckAndCleanLogsFolder(string logsPath)
|
||||
{
|
||||
try
|
||||
{
|
||||
long totalSize = 0;
|
||||
DirectoryInfo dirInfo = new DirectoryInfo(logsPath);
|
||||
|
||||
// 如果目录不存在,直接返回
|
||||
if (!dirInfo.Exists) return;
|
||||
|
||||
// 计算文件夹大小
|
||||
foreach (FileInfo file in dirInfo.GetFiles())
|
||||
{
|
||||
totalSize += file.Length;
|
||||
}
|
||||
|
||||
// 如果超过5MB,清空文件夹
|
||||
if (totalSize > MaxLogsFolderSizeBytes)
|
||||
{
|
||||
foreach (FileInfo file in dirInfo.GetFiles())
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
// 记录清理操作
|
||||
string cleanupMessage = $"Logs folder exceeded size limit ({totalSize / 1024.0 / 1024.0:F2} MB > {MaxLogsFolderSizeBytes / 1024.0 / 1024.0:F2} MB). Folder cleaned.";
|
||||
using (StreamWriter sw = new StreamWriter(Path.Combine(logsPath, $"Log_{AppStartTime}.txt"), true))
|
||||
{
|
||||
sw.WriteLine($"{DateTime.Now:O} [Cleanup] {cleanupMessage}");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
internal static void WriteLogToFile(string v, object warning)
|
||||
{
|
||||
WriteLogToFile($"[Warning] {v}", LogType.Warning);
|
||||
}
|
||||
|
||||
public enum LogType
|
||||
{
|
||||
Info,
|
||||
Trace,
|
||||
Error,
|
||||
Event,
|
||||
Warning
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
+36
-5
@@ -25,14 +25,18 @@ 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>
|
||||
public StrokeVisual() : this(new DrawingAttributes()
|
||||
public StrokeVisual() : this(new DrawingAttributes
|
||||
{
|
||||
Color = Colors.Red,
|
||||
//FitToCurve = true,
|
||||
@@ -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)
|
||||
@@ -0,0 +1,659 @@
|
||||
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;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// PPT墨迹管理器 - 负责PPT中墨迹的保存、加载和同步
|
||||
/// </summary>
|
||||
public class PPTInkManager : IDisposable
|
||||
{
|
||||
#region Properties
|
||||
public bool IsAutoSaveEnabled { get; set; } = true;
|
||||
public string AutoSaveLocation { get; set; } = "";
|
||||
public StrokeCollection CurrentStrokes { get; private set; } = new StrokeCollection();
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private MemoryStream[] _memoryStreams;
|
||||
private int _maxSlides = 100;
|
||||
private string _currentPresentationId = "";
|
||||
private readonly object _lockObject = new object();
|
||||
private bool _disposed;
|
||||
|
||||
// 墨迹锁定机制,防止翻页时的墨迹冲突
|
||||
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
|
||||
public PPTInkManager()
|
||||
{
|
||||
InitializeMemoryStreams();
|
||||
}
|
||||
|
||||
private void InitializeMemoryStreams()
|
||||
{
|
||||
_memoryStreams = new MemoryStream[_maxSlides + 2];
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 初始化新的演示文稿
|
||||
/// </summary>
|
||||
public void InitializePresentation(Presentation presentation)
|
||||
{
|
||||
if (presentation == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 完全清理之前的墨迹状态
|
||||
ClearAllStrokes();
|
||||
|
||||
// 重置墨迹锁定状态
|
||||
_inkLockUntil = DateTime.MinValue;
|
||||
_lockedSlideIndex = -1;
|
||||
|
||||
// 生成演示文稿唯一标识符
|
||||
_currentPresentationId = GeneratePresentationId(presentation);
|
||||
|
||||
// 重新初始化内存流数组
|
||||
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];
|
||||
|
||||
// 如果启用自动保存,尝试加载已保存的墨迹
|
||||
if (IsAutoSaveEnabled && !string.IsNullOrEmpty(AutoSaveLocation))
|
||||
{
|
||||
LoadSavedStrokes();
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化演示文稿墨迹管理失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存当前页面的墨迹
|
||||
/// </summary>
|
||||
public void SaveCurrentSlideStrokes(int slideIndex, StrokeCollection strokes)
|
||||
{
|
||||
if (slideIndex <= 0 || strokes == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查墨迹锁定
|
||||
if (!CanWriteInk(slideIndex))
|
||||
{
|
||||
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] = ms;
|
||||
|
||||
if (ms.Length > 0)
|
||||
{
|
||||
}
|
||||
|
||||
// 检查内存使用情况
|
||||
CheckAndPerformMemoryCleanup();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public StrokeCollection LoadSlideStrokes(int slideIndex)
|
||||
{
|
||||
if (slideIndex <= 0) return new StrokeCollection();
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (slideIndex < _memoryStreams.Length && _memoryStreams[slideIndex] != null && _memoryStreams[slideIndex].Length > 0)
|
||||
{
|
||||
_memoryStreams[slideIndex].Position = 0;
|
||||
var strokes = new StrokeCollection(_memoryStreams[slideIndex]);
|
||||
return strokes;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载第{slideIndex}页墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
return new StrokeCollection();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定页面并加载墨迹
|
||||
/// </summary>
|
||||
public StrokeCollection SwitchToSlide(int slideIndex, StrokeCollection currentStrokes = null)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查快速切换保护
|
||||
var now = DateTime.Now;
|
||||
if (now - _lastSwitchTime < TimeSpan.FromMilliseconds(MinSwitchIntervalMs) &&
|
||||
_lastSwitchSlideIndex == slideIndex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"快速切换保护:忽略重复的页面切换请求 {slideIndex}", LogHelper.LogType.Warning);
|
||||
return LoadSlideStrokes(slideIndex);
|
||||
}
|
||||
|
||||
|
||||
// 设置墨迹锁定
|
||||
LockInkForSlide(slideIndex);
|
||||
|
||||
// 加载新页面的墨迹
|
||||
var newStrokes = LoadSlideStrokes(slideIndex);
|
||||
|
||||
// 更新切换记录
|
||||
_lastSwitchTime = now;
|
||||
_lastSwitchSlideIndex = slideIndex;
|
||||
|
||||
if (newStrokes.Count > 0)
|
||||
{
|
||||
}
|
||||
|
||||
return newStrokes;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"切换到第{slideIndex}页失败: {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 folderPath = GetPresentationFolderPath();
|
||||
if (!Directory.Exists(folderPath))
|
||||
{
|
||||
Directory.CreateDirectory(folderPath);
|
||||
}
|
||||
|
||||
// 保存位置信息
|
||||
try
|
||||
{
|
||||
File.WriteAllText(Path.Combine(folderPath, "Position"), _lockedSlideIndex.ToString());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存位置信息失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 保存所有页面的墨迹
|
||||
int savedCount = 0;
|
||||
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)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_memoryStreams[i].Length > 8)
|
||||
{
|
||||
var srcBuf = new byte[_memoryStreams[i].Length];
|
||||
_memoryStreams[i].Position = 0;
|
||||
var byteLength = _memoryStreams[i].Read(srcBuf, 0, srcBuf.Length);
|
||||
|
||||
var filePath = Path.Combine(folderPath, i.ToString("0000") + ".icstk");
|
||||
File.WriteAllBytes(filePath, srcBuf);
|
||||
savedCount++;
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// 删除空的墨迹文件
|
||||
var filePath = Path.Combine(folderPath, i.ToString("0000") + ".icstk");
|
||||
if (File.Exists(filePath))
|
||||
{
|
||||
File.Delete(filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存第{i}页墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存墨迹到文件失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从文件加载已保存的墨迹
|
||||
/// </summary>
|
||||
public void LoadSavedStrokes()
|
||||
{
|
||||
if (!IsAutoSaveEnabled || string.IsNullOrEmpty(AutoSaveLocation)) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
var folderPath = GetPresentationFolderPath();
|
||||
if (!Directory.Exists(folderPath)) return;
|
||||
|
||||
var files = new DirectoryInfo(folderPath).GetFiles("*.icstk");
|
||||
int loadedCount = 0;
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (int.TryParse(Path.GetFileNameWithoutExtension(file.Name), out int slideIndex))
|
||||
{
|
||||
if (slideIndex > 0 && slideIndex < _memoryStreams.Length)
|
||||
{
|
||||
var fileBytes = File.ReadAllBytes(file.FullName);
|
||||
_memoryStreams[slideIndex] = new MemoryStream(fileBytes);
|
||||
_memoryStreams[slideIndex].Position = 0;
|
||||
loadedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载墨迹文件{file.Name}失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"从文件加载墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有墨迹
|
||||
/// </summary>
|
||||
public void ClearAllStrokes()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 安全释放所有内存流
|
||||
if (_memoryStreams != 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();
|
||||
LogHelper.WriteLogToFile("已清除所有墨迹", LogHelper.LogType.Trace);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除墨迹失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 翻页后锁定墨迹写入
|
||||
/// </summary>
|
||||
public void LockInkForSlide(int slideIndex)
|
||||
{
|
||||
_inkLockUntil = DateTime.Now.AddMilliseconds(InkLockMilliseconds);
|
||||
_lockedSlideIndex = slideIndex;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以写入墨迹
|
||||
/// </summary>
|
||||
public bool CanWriteInk(int currentSlideIndex)
|
||||
{
|
||||
// 如果锁定时间已过,允许写入
|
||||
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
|
||||
|
||||
#region Private Methods
|
||||
private string GeneratePresentationId(Presentation presentation)
|
||||
{
|
||||
try
|
||||
{
|
||||
var presentationPath = presentation.FullName;
|
||||
var fileHash = GetFileHash(presentationPath);
|
||||
return $"{presentation.Name}_{presentation.Slides.Count}_{fileHash}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"生成演示文稿ID失败: {ex}", LogHelper.LogType.Error);
|
||||
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 ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"计算文件哈希值失败: {ex}", LogHelper.LogType.Error);
|
||||
return "error";
|
||||
}
|
||||
}
|
||||
|
||||
private string GetPresentationFolderPath()
|
||||
{
|
||||
return Path.Combine(AutoSaveLocation, "Auto Saved - Presentations", _currentPresentationId);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Dispose
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_disposed)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
ClearAllStrokes();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,516 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// PPT UI管理器 - 统一管理PPT相关的UI更新和样式设置
|
||||
/// </summary>
|
||||
public class PPTUIManager
|
||||
{
|
||||
#region Properties
|
||||
public bool ShowPPTButton { get; set; } = true;
|
||||
public int PPTButtonsDisplayOption { get; set; } = 2222;
|
||||
public int PPTSButtonsOption { get; set; } = 221;
|
||||
public int PPTBButtonsOption { get; set; } = 121;
|
||||
public int PPTLSButtonPosition { get; set; } = 0;
|
||||
public int PPTRSButtonPosition { get; set; } = 0;
|
||||
public int PPTLBButtonPosition { get; set; } = 0;
|
||||
public int PPTRBButtonPosition { get; set; } = 0;
|
||||
public bool EnablePPTButtonPageClickable { get; set; } = true;
|
||||
public bool EnablePPTButtonLongPressPageTurn { get; set; } = true;
|
||||
#endregion
|
||||
|
||||
#region Private Fields
|
||||
private readonly MainWindow _mainWindow;
|
||||
private readonly Dispatcher _dispatcher;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
public PPTUIManager(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow ?? throw new ArgumentNullException(nameof(mainWindow));
|
||||
_dispatcher = _mainWindow.Dispatcher;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Public Methods
|
||||
/// <summary>
|
||||
/// 更新PPT连接状态UI
|
||||
/// </summary>
|
||||
public void UpdateConnectionStatus(bool isConnected)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isConnected)
|
||||
{
|
||||
_mainWindow.StackPanelPPTControls.Visibility = Visibility.Visible;
|
||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
_mainWindow.StackPanelPPTControls.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Collapsed;
|
||||
HideAllNavigationPanels();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新PPT连接状态UI失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新幻灯片放映状态UI
|
||||
/// </summary>
|
||||
public void UpdateSlideShowStatus(bool isInSlideShow, int currentSlide = 0, int totalSlides = 0)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isInSlideShow)
|
||||
{
|
||||
_mainWindow.BtnPPTSlideShow.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.BtnPPTSlideShowEnd.Visibility = Visibility.Visible;
|
||||
|
||||
// 只有在页数有效时才更新页码显示
|
||||
if (currentSlide > 0 && totalSlides > 0)
|
||||
{
|
||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 页数无效时清空页码显示
|
||||
_mainWindow.PPTBtnPageNow.Text = "?";
|
||||
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新幻灯片放映状态UI失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新当前页码显示
|
||||
/// </summary>
|
||||
public void UpdateCurrentSlideNumber(int currentSlide, int totalSlides)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有在页数有效时才更新页码显示
|
||||
if (currentSlide > 0 && totalSlides > 0)
|
||||
{
|
||||
_mainWindow.PPTBtnPageNow.Text = currentSlide.ToString();
|
||||
_mainWindow.PPTBtnPageTotal.Text = $"/ {totalSlides}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// 页数无效时清空页码显示
|
||||
_mainWindow.PPTBtnPageNow.Text = "?";
|
||||
_mainWindow.PPTBtnPageTotal.Text = "/ ?";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新页码显示失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 处理PPT放映状态变化
|
||||
/// </summary>
|
||||
public void OnSlideShowStateChanged(bool isInSlideShow)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!isInSlideShow)
|
||||
{
|
||||
// 如果不在放映模式,隐藏所有导航面板
|
||||
HideAllNavigationPanels();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理PPT放映状态变化失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新导航面板显示状态
|
||||
/// </summary>
|
||||
public void UpdateNavigationPanelsVisibility()
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查是否应该显示PPT按钮
|
||||
// 不仅要检查按钮设置,还要确保确实在PPT放映模式下且页数有效
|
||||
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 &&
|
||||
!MainWindow.Settings.Automation.IsAutoFoldInPPTSlideShow;
|
||||
|
||||
if (!shouldShowButtons)
|
||||
{
|
||||
HideAllNavigationPanels();
|
||||
return;
|
||||
}
|
||||
|
||||
// 设置侧边按钮位置
|
||||
_mainWindow.LeftSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTLSButtonPosition * 2);
|
||||
_mainWindow.RightSidePanelForPPTNavigation.Margin = new Thickness(0, 0, 0, PPTRSButtonPosition * 2);
|
||||
|
||||
// 设置底部按钮水平位置
|
||||
_mainWindow.LeftBottomPanelForPPTNavigation.Margin = new Thickness(6 + PPTLBButtonPosition, 0, 0, 6);
|
||||
_mainWindow.RightBottomPanelForPPTNavigation.Margin = new Thickness(0, 0, 6 + PPTRBButtonPosition, 6);
|
||||
|
||||
// 根据显示选项设置面板可见性
|
||||
var displayOption = PPTButtonsDisplayOption.ToString();
|
||||
if (displayOption.Length >= 4)
|
||||
{
|
||||
var options = displayOption.ToCharArray();
|
||||
|
||||
// 左下角面板
|
||||
if (options[0] == '2')
|
||||
AnimationsHelper.ShowWithFadeIn(_mainWindow.LeftBottomPanelForPPTNavigation);
|
||||
else
|
||||
_mainWindow.LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 右下角面板
|
||||
if (options[1] == '2')
|
||||
AnimationsHelper.ShowWithFadeIn(_mainWindow.RightBottomPanelForPPTNavigation);
|
||||
else
|
||||
_mainWindow.RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 左侧面板
|
||||
if (options[2] == '2')
|
||||
AnimationsHelper.ShowWithFadeIn(_mainWindow.LeftSidePanelForPPTNavigation);
|
||||
else
|
||||
_mainWindow.LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 右侧面板
|
||||
if (options[3] == '2')
|
||||
AnimationsHelper.ShowWithFadeIn(_mainWindow.RightSidePanelForPPTNavigation);
|
||||
else
|
||||
_mainWindow.RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新导航面板显示状态失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新导航按钮样式
|
||||
/// </summary>
|
||||
public void UpdateNavigationButtonStyles()
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateSideButtonStyles();
|
||||
UpdateBottomButtonStyles();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新导航按钮样式失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏所有导航面板
|
||||
/// </summary>
|
||||
public void HideAllNavigationPanels()
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_mainWindow.LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
_mainWindow.RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"隐藏导航面板失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示/隐藏侧边栏退出按钮
|
||||
/// </summary>
|
||||
public void UpdateSidebarExitButtons(bool show)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var visibility = show ? Visibility.Visible : Visibility.Collapsed;
|
||||
|
||||
if (_mainWindow.BtnExitPptFromSidebarLeft != null)
|
||||
_mainWindow.BtnExitPptFromSidebarLeft.Visibility = visibility;
|
||||
|
||||
if (_mainWindow.BtnExitPptFromSidebarRight != null)
|
||||
_mainWindow.BtnExitPptFromSidebarRight.Visibility = visibility;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新侧边栏退出按钮失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置浮动栏透明度
|
||||
/// </summary>
|
||||
public void SetFloatingBarOpacity(double opacity)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_mainWindow.ViewboxFloatingBar.Opacity = opacity;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置浮动栏透明度失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置主面板边距
|
||||
/// </summary>
|
||||
public void SetMainPanelMargin(Thickness margin)
|
||||
{
|
||||
_dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
_mainWindow.ViewBoxStackPanelMain.Margin = margin;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置主面板边距失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
private void UpdateSideButtonStyles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var sideOption = PPTSButtonsOption.ToString();
|
||||
if (sideOption.Length < 3) return;
|
||||
|
||||
var options = sideOption.ToCharArray();
|
||||
|
||||
// 页码按钮显示
|
||||
var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
|
||||
_mainWindow.PPTLSPageButton.Visibility = pageButtonVisibility;
|
||||
_mainWindow.PPTRSPageButton.Visibility = pageButtonVisibility;
|
||||
|
||||
// 透明度设置
|
||||
var opacity = options[1] == '2' ? 0.5 : 1.0;
|
||||
_mainWindow.PPTBtnLSBorder.Opacity = opacity;
|
||||
_mainWindow.PPTBtnRSBorder.Opacity = opacity;
|
||||
|
||||
// 颜色主题
|
||||
bool isDarkTheme = options[2] == '2';
|
||||
ApplyButtonTheme(_mainWindow.PPTBtnLSBorder, _mainWindow.PPTBtnRSBorder, isDarkTheme, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新侧边按钮样式失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateBottomButtonStyles()
|
||||
{
|
||||
try
|
||||
{
|
||||
var bottomOption = PPTBButtonsOption.ToString();
|
||||
if (bottomOption.Length < 3) return;
|
||||
|
||||
var options = bottomOption.ToCharArray();
|
||||
|
||||
// 页码按钮显示
|
||||
var pageButtonVisibility = options[0] == '2' ? Visibility.Visible : Visibility.Collapsed;
|
||||
_mainWindow.PPTLBPageButton.Visibility = pageButtonVisibility;
|
||||
_mainWindow.PPTRBPageButton.Visibility = pageButtonVisibility;
|
||||
|
||||
// 透明度设置
|
||||
var opacity = options[1] == '2' ? 0.5 : 1.0;
|
||||
_mainWindow.PPTBtnLBBorder.Opacity = opacity;
|
||||
_mainWindow.PPTBtnRBBorder.Opacity = opacity;
|
||||
|
||||
// 颜色主题
|
||||
bool isDarkTheme = options[2] == '2';
|
||||
ApplyButtonTheme(_mainWindow.PPTBtnLBBorder, _mainWindow.PPTBtnRBBorder, isDarkTheme, false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新底部按钮样式失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyButtonTheme(Border leftBorder, Border rightBorder, bool isDarkTheme, bool isSideButton)
|
||||
{
|
||||
try
|
||||
{
|
||||
Color backgroundColor, borderColor, foregroundColor, feedbackColor;
|
||||
|
||||
if (isDarkTheme)
|
||||
{
|
||||
backgroundColor = Color.FromRgb(39, 39, 42);
|
||||
borderColor = Color.FromRgb(82, 82, 91);
|
||||
foregroundColor = Colors.White;
|
||||
feedbackColor = Colors.White;
|
||||
}
|
||||
else
|
||||
{
|
||||
backgroundColor = Color.FromRgb(244, 244, 245);
|
||||
borderColor = Color.FromRgb(161, 161, 170);
|
||||
foregroundColor = Color.FromRgb(39, 39, 42);
|
||||
feedbackColor = Color.FromRgb(24, 24, 27);
|
||||
}
|
||||
|
||||
// 应用背景和边框颜色
|
||||
var backgroundBrush = new SolidColorBrush(backgroundColor);
|
||||
var borderBrush = new SolidColorBrush(borderColor);
|
||||
|
||||
leftBorder.Background = backgroundBrush;
|
||||
leftBorder.BorderBrush = borderBrush;
|
||||
rightBorder.Background = backgroundBrush;
|
||||
rightBorder.BorderBrush = borderBrush;
|
||||
|
||||
// 应用图标和文字颜色
|
||||
var foregroundBrush = new SolidColorBrush(foregroundColor);
|
||||
var feedbackBrush = new SolidColorBrush(feedbackColor);
|
||||
|
||||
if (isSideButton)
|
||||
{
|
||||
ApplySideButtonColors(foregroundBrush, feedbackBrush);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyBottomButtonColors(foregroundBrush, feedbackBrush);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用按钮主题失败: {ex}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplySideButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush)
|
||||
{
|
||||
// 图标颜色
|
||||
_mainWindow.PPTLSPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRSPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTLSNextButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRSNextButtonGeometry.Brush = foregroundBrush;
|
||||
|
||||
// 反馈背景颜色
|
||||
_mainWindow.PPTLSPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRSPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLSPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRSPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLSNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRSNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
|
||||
// 文字颜色
|
||||
TextBlock.SetForeground(_mainWindow.PPTLSPageButton, foregroundBrush);
|
||||
TextBlock.SetForeground(_mainWindow.PPTRSPageButton, foregroundBrush);
|
||||
}
|
||||
|
||||
private void ApplyBottomButtonColors(SolidColorBrush foregroundBrush, SolidColorBrush feedbackBrush)
|
||||
{
|
||||
// 图标颜色
|
||||
_mainWindow.PPTLBPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRBPreviousButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTLBNextButtonGeometry.Brush = foregroundBrush;
|
||||
_mainWindow.PPTRBNextButtonGeometry.Brush = foregroundBrush;
|
||||
|
||||
// 反馈背景颜色
|
||||
_mainWindow.PPTLBPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRBPreviousButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLBPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRBPageButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTLBNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
_mainWindow.PPTRBNextButtonFeedbackBorder.Background = feedbackBrush;
|
||||
|
||||
// 文字颜色
|
||||
TextBlock.SetForeground(_mainWindow.PPTLBPageButton, foregroundBrush);
|
||||
TextBlock.SetForeground(_mainWindow.PPTRBPageButton, foregroundBrush);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
using iNKORE.UI.WPF.Modern.Controls;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮控件
|
||||
/// </summary>
|
||||
public class LauncherButton
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 实际按钮控件
|
||||
/// </summary>
|
||||
private readonly SimpleStackPanel _panel;
|
||||
|
||||
/// <summary>
|
||||
/// 获取按钮UI元素
|
||||
/// </summary>
|
||||
public UIElement Element => _panel;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="plugin">父插件</param>
|
||||
public LauncherButton(SuperLauncherPlugin plugin)
|
||||
{
|
||||
try
|
||||
{
|
||||
_plugin = plugin;
|
||||
LogHelper.WriteLogToFile("开始创建启动台按钮");
|
||||
|
||||
// 创建SimpleStackPanel
|
||||
_panel = new SimpleStackPanel
|
||||
{
|
||||
Name = "Launcher_Icon",
|
||||
Orientation = Orientation.Vertical,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Width = 28,
|
||||
Margin = new Thickness(0, -2, 0, 0),
|
||||
Background = Brushes.Transparent
|
||||
};
|
||||
|
||||
LogHelper.WriteLogToFile("创建SimpleStackPanel完成");
|
||||
|
||||
// 添加图标
|
||||
var image = CreateIconImage();
|
||||
_panel.Children.Add(image);
|
||||
|
||||
// 添加文本
|
||||
TextBlock textBlock = new TextBlock
|
||||
{
|
||||
Text = "启动台",
|
||||
Foreground = Brushes.Black,
|
||||
FontSize = 8,
|
||||
Margin = new Thickness(0, 1, 0, 0),
|
||||
TextAlignment = TextAlignment.Center
|
||||
};
|
||||
_panel.Children.Add(textBlock);
|
||||
|
||||
// 设置鼠标事件
|
||||
_panel.MouseDown += Panel_MouseDown;
|
||||
_panel.MouseUp += Panel_MouseUp;
|
||||
_panel.MouseLeave += Panel_MouseLeave;
|
||||
|
||||
// 右键菜单支持
|
||||
_panel.ContextMenu = CreateContextMenu();
|
||||
|
||||
// 设置工具提示
|
||||
_panel.ToolTip = "启动台";
|
||||
|
||||
LogHelper.WriteLogToFile("启动台按钮创建完成");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建右键菜单
|
||||
/// </summary>
|
||||
private ContextMenu CreateContextMenu()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建菜单
|
||||
ContextMenu menu = new ContextMenu();
|
||||
|
||||
// 创建位置切换菜单项
|
||||
MenuItem positionMenuItem = new MenuItem();
|
||||
positionMenuItem.Header = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ?
|
||||
"移至右侧" : "移至左侧";
|
||||
positionMenuItem.Click += (s, e) =>
|
||||
{
|
||||
// 切换位置
|
||||
_plugin.Config.ButtonPosition = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left ?
|
||||
LauncherButtonPosition.Right : LauncherButtonPosition.Left;
|
||||
|
||||
// 更新按钮位置
|
||||
_plugin.UpdateButtonPosition();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile($"通过右键菜单切换启动台按钮位置为: {_plugin.Config.ButtonPosition}");
|
||||
};
|
||||
menu.Items.Add(positionMenuItem);
|
||||
|
||||
// 添加设置菜单项
|
||||
MenuItem settingsMenuItem = new MenuItem();
|
||||
settingsMenuItem.Header = "打开设置";
|
||||
settingsMenuItem.Click += (s, e) =>
|
||||
{
|
||||
// 打开插件设置窗口
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 使用反射调用主窗口的ShowPluginSettings方法
|
||||
var method = mainWindow.GetType().GetMethod("ShowPluginSettings");
|
||||
if (method != null)
|
||||
{
|
||||
method.Invoke(mainWindow, null);
|
||||
LogHelper.WriteLogToFile("已打开插件设置窗口");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"打开插件设置窗口失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
};
|
||||
menu.Items.Add(settingsMenuItem);
|
||||
|
||||
return menu;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建右键菜单时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实际的UI元素
|
||||
/// </summary>
|
||||
[Obsolete("使用Element属性代替")]
|
||||
public UIElement GetUIElement()
|
||||
{
|
||||
return _panel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建图标图像
|
||||
/// </summary>
|
||||
private Image CreateIconImage()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建图像
|
||||
Image image = new Image
|
||||
{
|
||||
Height = 17,
|
||||
Margin = new Thickness(0, 3, 0, 0)
|
||||
};
|
||||
|
||||
// 设置位图缩放模式
|
||||
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
|
||||
|
||||
// 创建绘图图像
|
||||
DrawingImage drawingImage = new DrawingImage();
|
||||
DrawingGroup drawingGroup = new DrawingGroup();
|
||||
drawingGroup.ClipGeometry = Geometry.Parse("M0,0 V24 H24 V0 H0 Z");
|
||||
|
||||
// 使用提供的应用网格图标
|
||||
GeometryDrawing geometryDrawing = new GeometryDrawing
|
||||
{
|
||||
Brush = new SolidColorBrush(Color.FromRgb(0x1B, 0x1B, 0x1B)),
|
||||
Geometry = Geometry.Parse("F0 M24,24z M0,0z M4.41721,4.29873C4.35178,4.29873,4.29873,4.35178,4.29873,4.41721L4.29873,9.15646C4.29873,9.22189,4.35178,9.27494,4.41721,9.27494L9.15646,9.27494C9.22189,9.27494,9.27494,9.22189,9.27494,9.15646L9.27494,4.41721C9.27494,4.35178,9.22189,4.29873,9.15646,4.29873L4.41721,4.29873z M2.64,4.41721C2.64,3.43569,3.43569,2.64,4.41721,2.64L9.15646,2.64C10.138,2.64,10.9337,3.43569,10.9337,4.41721L10.9337,9.15646C10.9337,10.138,10.138,10.9337,9.15646,10.9337L4.41721,10.9337C3.43569,10.9337,2.64,10.138,2.64,9.15646L2.64,4.41721z M14.8435,4.29873C14.7781,4.29873,14.7251,4.35178,14.7251,4.41721L14.7251,9.15646C14.7251,9.22189,14.7781,9.27494,14.8435,9.27494L19.5828,9.27494C19.6482,9.27494,19.7013,9.22189,19.7013,9.15646L19.7013,4.41721C19.7013,4.35178,19.6482,4.29873,19.5828,4.29873L14.8435,4.29873z M13.0663,4.41721C13.0663,3.43569,13.862,2.64,14.8435,2.64L19.5828,2.64C20.5643,2.64,21.36,3.43569,21.36,4.41721L21.36,9.15646C21.36,10.138,20.5643,10.9337,19.5828,10.9337L14.8435,10.9337C13.862,10.9337,13.0663,10.138,13.0663,9.15646L13.0663,4.41721z M14.8435,14.7251C14.7781,14.7251,14.7251,14.7781,14.7251,14.8435L14.7251,19.5828C14.7251,19.6482,14.7781,19.7013,14.8435,19.7013L19.5828,19.7013C19.6482,19.7013,19.7013,19.6482,19.7013,19.5828L19.7013,14.8435C19.7013,14.7781,19.6482,14.7251,19.5828,14.7251L14.8435,14.7251z M13.0663,14.8435C13.0663,13.862,13.862,13.0663,14.8435,13.0663L19.5828,13.0663C20.5643,13.0663,21.36,13.862,21.36,14.8435L21.36,19.5828C21.36,20.5643,20.5643,21.36,19.5828,21.36L14.8435,21.36C13.862,21.36,13.0663,20.5643,13.0663,19.5828L13.0663,14.8435z M4.41721,14.7251C4.35178,14.7251,4.29873,14.7781,4.29873,14.8435L4.29873,19.5828C4.29873,19.6482,4.35178,19.7013,4.41721,19.7013L9.15646,19.7013C9.22189,19.7013,9.27494,19.6482,9.27494,19.5828L9.27494,14.8435C9.27494,14.7781,9.22189,14.7251,9.15646,14.7251L4.41721,14.7251z M2.64,14.8435C2.64,13.862,3.43569,13.0663,4.41721,13.0663L9.15646,13.0663C10.138,13.0663,10.9337,13.862,10.9337,14.8435L10.9337,19.5828C10.9337,20.5643,10.138,21.36,9.15646,21.36L4.41721,21.36C3.43569,21.36,2.64,20.5643,2.64,19.5828L2.64,14.8435z")
|
||||
};
|
||||
|
||||
drawingGroup.Children.Add(geometryDrawing);
|
||||
|
||||
// 设置图像源
|
||||
drawingImage.Drawing = drawingGroup;
|
||||
image.Source = drawingImage;
|
||||
|
||||
return image;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"创建图标图像时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
|
||||
// 返回一个空图像
|
||||
return new Image();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标按下事件
|
||||
/// </summary>
|
||||
private void Panel_MouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 提供反馈
|
||||
_panel.Background = new SolidColorBrush(Color.FromArgb(40, 0, 0, 0));
|
||||
LogHelper.WriteLogToFile("启动台按钮鼠标按下");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标按下事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标抬起事件
|
||||
/// </summary>
|
||||
private void Panel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有左键点击才显示启动台窗口
|
||||
if (e.ChangedButton != MouseButton.Left)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 恢复背景
|
||||
_panel.Background = Brushes.Transparent;
|
||||
LogHelper.WriteLogToFile("启动台按钮鼠标抬起,准备显示启动台窗口");
|
||||
|
||||
// 获取按钮在屏幕上的位置
|
||||
Point buttonPosition = _panel.PointToScreen(new Point(_panel.ActualWidth / 2, 0));
|
||||
|
||||
// 显示启动台窗口
|
||||
_plugin.ShowLauncherWindow(buttonPosition);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标抬起事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 鼠标离开事件
|
||||
/// </summary>
|
||||
private void Panel_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 恢复背景
|
||||
_panel.Background = Brushes.Transparent;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动台按钮鼠标离开事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
using Microsoft.Win32;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮位置
|
||||
/// </summary>
|
||||
public enum LauncherButtonPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// 左侧
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// 右侧
|
||||
/// </summary>
|
||||
Right
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动台配置
|
||||
/// </summary>
|
||||
public class LauncherConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动台按钮位置
|
||||
/// </summary>
|
||||
public LauncherButtonPosition ButtonPosition { get; set; } = LauncherButtonPosition.Right;
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用程序列表
|
||||
/// </summary>
|
||||
public List<LauncherItem> Items { get; set; } = new List<LauncherItem>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用项
|
||||
/// </summary>
|
||||
public class LauncherItem
|
||||
{
|
||||
/// <summary>
|
||||
/// 应用程序名称
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 应用程序路径
|
||||
/// </summary>
|
||||
public string Path { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可见
|
||||
/// </summary>
|
||||
public bool IsVisible { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 在启动台中的位置(0-39)
|
||||
/// </summary>
|
||||
public int Position { get; set; } = -1;
|
||||
|
||||
/// <summary>
|
||||
/// 是否已固定位置
|
||||
/// </summary>
|
||||
public bool IsPositionFixed { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 图标缓存
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
private ImageSource _iconCache;
|
||||
|
||||
/// <summary>
|
||||
/// 获取应用程序图标
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public ImageSource Icon
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_iconCache != null)
|
||||
{
|
||||
return _iconCache;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (File.Exists(Path))
|
||||
{
|
||||
// 从文件中获取图标
|
||||
Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(Path);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 从注册表中获取文件类型关联图标
|
||||
string extension = System.IO.Path.GetExtension(Path);
|
||||
if (!string.IsNullOrEmpty(extension))
|
||||
{
|
||||
string fileType = Registry.ClassesRoot.OpenSubKey(extension)?.GetValue(string.Empty) as string;
|
||||
if (!string.IsNullOrEmpty(fileType))
|
||||
{
|
||||
string iconPath = Registry.ClassesRoot.OpenSubKey(fileType + "\\DefaultIcon")?.GetValue(string.Empty) as string;
|
||||
if (!string.IsNullOrEmpty(iconPath))
|
||||
{
|
||||
string[] parts = iconPath.Split(',');
|
||||
string iconFile = parts[0].Trim('"');
|
||||
int iconIndex = parts.Length > 1 ? Convert.ToInt32(parts[1]) : 0;
|
||||
|
||||
if (File.Exists(iconFile))
|
||||
{
|
||||
Icon icon = IconExtractor.Extract(iconFile, iconIndex, true);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取应用图标时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
// 返回默认图标
|
||||
return GetDefaultIcon();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取默认图标
|
||||
/// </summary>
|
||||
private ImageSource GetDefaultIcon()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 对于资源管理器,使用特定图标
|
||||
if (Path.EndsWith("explorer.exe", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 直接从C:\Windows\explorer.exe获取图标
|
||||
string explorerPath = @"C:\Windows\explorer.exe";
|
||||
if (File.Exists(explorerPath))
|
||||
{
|
||||
Icon icon = System.Drawing.Icon.ExtractAssociatedIcon(explorerPath);
|
||||
if (icon != null)
|
||||
{
|
||||
_iconCache = Imaging.CreateBitmapSourceFromHIcon(
|
||||
icon.Handle,
|
||||
Int32Rect.Empty,
|
||||
BitmapSizeOptions.FromEmptyOptions());
|
||||
|
||||
icon.Dispose();
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取资源管理器图标时出错: {ex.Message}", LogHelper.LogType.Warning);
|
||||
// 如果获取Windows图标失败,回退到默认图标
|
||||
}
|
||||
|
||||
// 回退到备用图标
|
||||
string explorerIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_folder_24_regular.png");
|
||||
if (File.Exists(explorerIconPath))
|
||||
{
|
||||
Uri uri = new Uri(explorerIconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
|
||||
// 返回一个简单的默认图标
|
||||
string iconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-png", "icc.png");
|
||||
if (File.Exists(iconPath))
|
||||
{
|
||||
Uri uri = new Uri(iconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
|
||||
// 如果还是没有找到,尝试使用应用程序图标
|
||||
string appIconPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Resources", "Icons-Fluent", "ic_fluent_apps_24_regular.png");
|
||||
if (File.Exists(appIconPath))
|
||||
{
|
||||
Uri uri = new Uri(appIconPath);
|
||||
BitmapImage image = new BitmapImage(uri);
|
||||
_iconCache = image;
|
||||
return _iconCache;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取默认图标时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动应用程序
|
||||
/// </summary>
|
||||
public void Launch()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(Path))
|
||||
{
|
||||
LogHelper.WriteLogToFile("无法启动应用程序:路径为空", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if (!File.Exists(Path) && !Path.Contains(":\\"))
|
||||
{
|
||||
// 可能是系统命令,如explorer.exe
|
||||
ProcessStartInfo psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 使用Process.Start启动应用程序
|
||||
ProcessStartInfo psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = Path,
|
||||
UseShellExecute = true
|
||||
};
|
||||
Process.Start(psi);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已启动应用程序: {Path}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动应用程序时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"启动应用程序时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 图标提取工具类
|
||||
/// </summary>
|
||||
public static class IconExtractor
|
||||
{
|
||||
/// <summary>
|
||||
/// 从文件中提取图标
|
||||
/// </summary>
|
||||
/// <param name="file">文件路径</param>
|
||||
/// <param name="index">图标索引</param>
|
||||
/// <param name="largeIcon">是否提取大图标</param>
|
||||
/// <returns>提取的图标</returns>
|
||||
public static Icon Extract(string file, int index, bool largeIcon)
|
||||
{
|
||||
try
|
||||
{
|
||||
IntPtr large;
|
||||
IntPtr small;
|
||||
ExtractIconEx(file, index, out large, out small, 1);
|
||||
|
||||
try
|
||||
{
|
||||
return Icon.FromHandle(largeIcon ? large : small);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (large != IntPtr.Zero)
|
||||
DestroyIcon(large);
|
||||
|
||||
if (small != IntPtr.Zero)
|
||||
DestroyIcon(small);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("Shell32.dll", EntryPoint = "ExtractIconEx")]
|
||||
private static extern int ExtractIconEx(
|
||||
[MarshalAs(UnmanagedType.LPStr)] string lpszFile,
|
||||
int nIconIndex,
|
||||
out IntPtr phiconLarge,
|
||||
out IntPtr phiconSmall,
|
||||
int nIcons);
|
||||
|
||||
[DllImport("User32.dll")]
|
||||
private static extern int DestroyIcon(IntPtr hIcon);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<UserControl x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherSettingsControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="500" d:DesignWidth="600">
|
||||
|
||||
<UserControl.Resources>
|
||||
<!-- 自定义按钮样式 -->
|
||||
<Style x:Key="DefaultButtonStyle" TargetType="Button">
|
||||
<Setter Property="FontWeight" Value="SemiBold"/>
|
||||
<Setter Property="FontSize" Value="12"/>
|
||||
<Setter Property="Cursor" Value="Hand"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="4"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
TextElement.Foreground="{TemplateBinding Foreground}"/>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.8"/>
|
||||
<Setter Property="Effect">
|
||||
<Setter.Value>
|
||||
<DropShadowEffect Color="Black" Direction="270" ShadowDepth="2" Opacity="0.3" BlurRadius="4"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.6"/>
|
||||
<Setter Property="RenderTransform">
|
||||
<Setter.Value>
|
||||
<ScaleTransform ScaleX="0.95" ScaleY="0.95"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="RenderTransformOrigin" Value="0.5,0.5"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Setter TargetName="border" Property="Opacity" Value="0.4"/>
|
||||
<Setter Property="Cursor" Value="Arrow"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题 -->
|
||||
<TextBlock Grid.Row="0" Text="超级启动台设置" FontSize="16" FontWeight="Bold" Margin="0,0,0,15" Foreground="Black"/>
|
||||
|
||||
<!-- 基本设置 -->
|
||||
<StackPanel Grid.Row="1" Margin="0,0,0,15">
|
||||
<TextBlock Text="基本设置" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
|
||||
<Grid Margin="10,0,0,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="120"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 按钮位置 -->
|
||||
<TextBlock Grid.Row="0" Grid.Column="0" Text="按钮位置:" VerticalAlignment="Center" Foreground="Black"/>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" Orientation="Horizontal" Margin="0,5">
|
||||
<RadioButton x:Name="RbtnLeft" Content="浮动栏左侧" Margin="0,0,20,0" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
||||
<RadioButton x:Name="RbtnRight" Content="浮动栏右侧" IsChecked="True" Checked="RbtnPosition_Checked" Foreground="Black"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
<!-- 应用管理 -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Grid.Row="0" Text="应用管理" FontSize="14" FontWeight="SemiBold" Margin="0,0,0,10" Foreground="Black"/>
|
||||
|
||||
<Border Grid.Row="1" BorderThickness="1" BorderBrush="#CCCCCC" CornerRadius="5">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 应用列表 -->
|
||||
<DataGrid Grid.Row="0" x:Name="DgApps" AutoGenerateColumns="False" Margin="5"
|
||||
CanUserAddRows="False" CanUserDeleteRows="False"
|
||||
HeadersVisibility="Column" SelectionMode="Single"
|
||||
SelectionChanged="DgApps_SelectionChanged">
|
||||
<DataGrid.Columns>
|
||||
<DataGridCheckBoxColumn Header="显示" Binding="{Binding IsVisible}" Width="50"/>
|
||||
<DataGridTextColumn Header="名称" Binding="{Binding Name}" Width="150"/>
|
||||
<DataGridTextColumn Header="路径" Binding="{Binding Path}" Width="*"/>
|
||||
<DataGridTextColumn Header="位置" Binding="{Binding Position}" Width="50"/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<StackPanel Grid.Row="1" Orientation="Horizontal" Margin="5">
|
||||
<Button x:Name="BtnAdd" Content="添加" Padding="10,5" Margin="0,5,5,5" Click="BtnAdd_Click"
|
||||
Background="#FF007ACC" Foreground="White" BorderBrush="#FF005A9B" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
<Button x:Name="BtnEdit" Content="编辑" Padding="10,5" Margin="5" Click="BtnEdit_Click"
|
||||
Background="#FF6C757D" Foreground="White" BorderBrush="#FF5A6268" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
<Button x:Name="BtnDelete" Content="删除" Padding="10,5" Margin="5" Click="BtnDelete_Click"
|
||||
Background="#FFDC3545" Foreground="White" BorderBrush="#FFBD2130" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,15,0,0">
|
||||
<Button x:Name="BtnSave" Content="保存设置" Padding="15,5" Click="BtnSave_Click"
|
||||
Background="#FF28A745" Foreground="White" BorderBrush="#FF1E7E34" BorderThickness="1"
|
||||
Style="{StaticResource DefaultButtonStyle}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,396 @@
|
||||
using Ink_Canvas.Windows;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// LauncherSettingsControl.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LauncherSettingsControl : UserControl
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="plugin">父插件</param>
|
||||
public LauncherSettingsControl(SuperLauncherPlugin plugin)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_plugin = plugin;
|
||||
|
||||
// 设置按钮位置
|
||||
RbtnLeft.IsChecked = _plugin.Config.ButtonPosition == LauncherButtonPosition.Left;
|
||||
RbtnRight.IsChecked = _plugin.Config.ButtonPosition == LauncherButtonPosition.Right;
|
||||
|
||||
// 绑定应用列表
|
||||
DgApps.ItemsSource = _plugin.LauncherItems;
|
||||
|
||||
// 初始化按钮状态
|
||||
UpdateButtonStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新按钮状态
|
||||
/// </summary>
|
||||
private void UpdateButtonStates()
|
||||
{
|
||||
bool hasSelection = DgApps.SelectedItem != null;
|
||||
BtnEdit.IsEnabled = hasSelection;
|
||||
BtnDelete.IsEnabled = hasSelection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 位置单选按钮选择事件
|
||||
/// </summary>
|
||||
private void RbtnPosition_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
|
||||
LauncherButtonPosition oldPosition = _plugin.Config.ButtonPosition;
|
||||
|
||||
if (sender == RbtnLeft)
|
||||
{
|
||||
_plugin.Config.ButtonPosition = LauncherButtonPosition.Left;
|
||||
}
|
||||
else if (sender == RbtnRight)
|
||||
{
|
||||
_plugin.Config.ButtonPosition = LauncherButtonPosition.Right;
|
||||
}
|
||||
|
||||
// 如果位置发生变化,更新按钮位置
|
||||
if (oldPosition != _plugin.Config.ButtonPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 更新按钮位置
|
||||
_plugin.UpdateButtonPosition();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile($"启动台按钮位置已更改为: {_plugin.Config.ButtonPosition}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"更新启动台按钮位置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnAdd_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 创建新的启动项
|
||||
LauncherItem item = new LauncherItem
|
||||
{
|
||||
Name = "",
|
||||
Path = "",
|
||||
IsVisible = true,
|
||||
Position = -1 // 让插件管理器分配位置
|
||||
};
|
||||
|
||||
// 直接显示编辑对话框
|
||||
EditLauncherItem(item, true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加启动项时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"添加启动项时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑应用按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnEdit_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DgApps.SelectedItem is LauncherItem item)
|
||||
{
|
||||
EditLauncherItem(item, false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除应用按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnDelete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (DgApps.SelectedItem is LauncherItem item)
|
||||
{
|
||||
// 确认删除
|
||||
MessageBoxResult result = MessageBox.Show(
|
||||
$"确定要删除 {item.Name} 吗?",
|
||||
"删除确认",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
// 从集合中移除
|
||||
_plugin.LauncherItems.Remove(item);
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnSave_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
// 如果插件已启用,重新加载启动台按钮
|
||||
if (_plugin.IsEnabled)
|
||||
{
|
||||
_plugin.Disable();
|
||||
_plugin.Enable();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果插件未启用,则启用它
|
||||
_plugin.Enable();
|
||||
|
||||
// 通知PluginSettingsWindow刷新插件列表
|
||||
var window = Window.GetWindow(this);
|
||||
if (window is PluginSettingsWindow pluginSettingsWindow)
|
||||
{
|
||||
// 触发刷新
|
||||
pluginSettingsWindow.RefreshPluginList();
|
||||
}
|
||||
}
|
||||
|
||||
MessageBox.Show("设置已保存并应用!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"保存设置时出错: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用项选择变更事件
|
||||
/// </summary>
|
||||
private void DgApps_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
UpdateButtonStates();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 编辑启动项
|
||||
/// </summary>
|
||||
/// <param name="item">启动项</param>
|
||||
/// <param name="isNew">是否为新建</param>
|
||||
private void EditLauncherItem(LauncherItem item, bool isNew)
|
||||
{
|
||||
// 创建简单的编辑窗口
|
||||
Window editWindow = new Window
|
||||
{
|
||||
Title = isNew ? "添加" : "编辑应用",
|
||||
Width = 400,
|
||||
Height = 200,
|
||||
WindowStartupLocation = WindowStartupLocation.CenterScreen,
|
||||
ResizeMode = ResizeMode.NoResize
|
||||
};
|
||||
|
||||
// 创建编辑表单
|
||||
Grid grid = new Grid
|
||||
{
|
||||
Margin = new Thickness(20)
|
||||
};
|
||||
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(80) });
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
|
||||
// 名称输入框
|
||||
TextBlock nameLabel = new TextBlock
|
||||
{
|
||||
Text = "名称:",
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
TextBox nameTextBox = new TextBox
|
||||
{
|
||||
Text = item.Name,
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
};
|
||||
|
||||
Grid.SetRow(nameLabel, 0);
|
||||
Grid.SetColumn(nameLabel, 0);
|
||||
Grid.SetRow(nameTextBox, 0);
|
||||
Grid.SetColumn(nameTextBox, 1);
|
||||
|
||||
grid.Children.Add(nameLabel);
|
||||
grid.Children.Add(nameTextBox);
|
||||
|
||||
// 路径输入框
|
||||
TextBlock pathLabel = new TextBlock
|
||||
{
|
||||
Text = "路径:",
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
Grid pathGrid = new Grid();
|
||||
pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
pathGrid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength() });
|
||||
|
||||
TextBox pathTextBox = new TextBox
|
||||
{
|
||||
Text = item.Path,
|
||||
Margin = new Thickness(0, 5, 5, 5)
|
||||
};
|
||||
Button browseButton = new Button
|
||||
{
|
||||
Content = "浏览",
|
||||
Padding = new Thickness(5, 0, 5, 0),
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
};
|
||||
|
||||
browseButton.Click += (s, e) =>
|
||||
{
|
||||
OpenFileDialog dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "选择应用程序",
|
||||
Filter = "应用程序 (*.exe)|*.exe|所有文件 (*.*)|*.*",
|
||||
Multiselect = false,
|
||||
FileName = pathTextBox.Text
|
||||
};
|
||||
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
pathTextBox.Text = dialog.FileName;
|
||||
|
||||
// 如果选择的是.exe文件,自动获取文件名填入名称字段
|
||||
if (Path.GetExtension(dialog.FileName).ToLower() == ".exe")
|
||||
{
|
||||
string fileName = Path.GetFileNameWithoutExtension(dialog.FileName);
|
||||
// 只有在名称字段为空或者是新建项目时才自动填入
|
||||
if (string.IsNullOrWhiteSpace(nameTextBox.Text) || isNew)
|
||||
{
|
||||
nameTextBox.Text = fileName;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Grid.SetColumn(pathTextBox, 0);
|
||||
Grid.SetColumn(browseButton, 1);
|
||||
pathGrid.Children.Add(pathTextBox);
|
||||
pathGrid.Children.Add(browseButton);
|
||||
|
||||
Grid.SetRow(pathLabel, 1);
|
||||
Grid.SetColumn(pathLabel, 0);
|
||||
Grid.SetRow(pathGrid, 1);
|
||||
Grid.SetColumn(pathGrid, 1);
|
||||
|
||||
grid.Children.Add(pathLabel);
|
||||
grid.Children.Add(pathGrid);
|
||||
|
||||
// 确认和取消按钮
|
||||
StackPanel buttonPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right,
|
||||
Margin = new Thickness(0, 10, 0, 0)
|
||||
};
|
||||
|
||||
Button okButton = new Button
|
||||
{
|
||||
Content = "确定",
|
||||
Padding = new Thickness(15, 5, 15, 5),
|
||||
Margin = new Thickness(0, 0, 10, 0),
|
||||
IsDefault = true
|
||||
};
|
||||
|
||||
Button cancelButton = new Button
|
||||
{
|
||||
Content = "取消",
|
||||
Padding = new Thickness(15, 5, 15, 5),
|
||||
IsCancel = true
|
||||
};
|
||||
|
||||
okButton.Click += (s, e) =>
|
||||
{
|
||||
// 验证输入
|
||||
if (string.IsNullOrWhiteSpace(nameTextBox.Text))
|
||||
{
|
||||
MessageBox.Show("请输入应用名称!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(pathTextBox.Text))
|
||||
{
|
||||
MessageBox.Show("请输入应用路径!", "提示", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新项目
|
||||
item.Name = nameTextBox.Text;
|
||||
item.Path = pathTextBox.Text;
|
||||
|
||||
// 如果是新建,添加到集合
|
||||
if (isNew)
|
||||
{
|
||||
_plugin.AddLauncherItem(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 触发属性变更通知,刷新DataGrid
|
||||
if (DgApps.ItemsSource is ICollectionView view)
|
||||
{
|
||||
view.Refresh();
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
}
|
||||
|
||||
editWindow.DialogResult = true;
|
||||
editWindow.Close();
|
||||
};
|
||||
|
||||
cancelButton.Click += (s, e) =>
|
||||
{
|
||||
editWindow.DialogResult = false;
|
||||
editWindow.Close();
|
||||
};
|
||||
|
||||
buttonPanel.Children.Add(okButton);
|
||||
buttonPanel.Children.Add(cancelButton);
|
||||
|
||||
Grid.SetRow(buttonPanel, 2);
|
||||
Grid.SetColumnSpan(buttonPanel, 2);
|
||||
|
||||
grid.Children.Add(buttonPanel);
|
||||
|
||||
// 设置窗口内容
|
||||
editWindow.Content = grid;
|
||||
|
||||
// 显示窗口
|
||||
editWindow.ShowDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<Window x:Class="Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher.LauncherWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher"
|
||||
mc:Ignorable="d"
|
||||
Title="启动台"
|
||||
Width="400"
|
||||
Height="300"
|
||||
WindowStyle="None"
|
||||
AllowsTransparency="True"
|
||||
Background="#80000000"
|
||||
ResizeMode="NoResize"
|
||||
Topmost="True"
|
||||
Deactivated="Window_Deactivated"
|
||||
ShowInTaskbar="False">
|
||||
|
||||
<Window.Resources>
|
||||
<!-- 应用项样式 -->
|
||||
<Style x:Key="LauncherItemStyle" TargetType="Button">
|
||||
<Setter Property="Width" Value="80"/>
|
||||
<Setter Property="Height" Value="80"/>
|
||||
<Setter Property="Margin" Value="5"/>
|
||||
<Setter Property="Background" Value="#40FFFFFF"/>
|
||||
<Setter Property="Foreground" Value="White"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border x:Name="border" Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Image Grid.Row="0" Source="{Binding Icon}" Width="32" Height="32" Margin="0,10,0,5"/>
|
||||
<TextBlock Grid.Row="1" Text="{Binding Name}" TextWrapping="Wrap" TextAlignment="Center"
|
||||
Margin="2,0,2,8" FontSize="11" Foreground="White"/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="#80FFFFFF"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsPressed" Value="True">
|
||||
<Setter Property="Background" Value="#C0FFFFFF"/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Border CornerRadius="15" Background="#80000000" BorderThickness="1" BorderBrush="#40FFFFFF">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- 标题栏 -->
|
||||
<Grid Grid.Row="0" Height="40">
|
||||
<TextBlock Text="启动台" Foreground="White" FontSize="18" FontWeight="Bold"
|
||||
VerticalAlignment="Center" Margin="15,0,0,0"/>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,0,10,0">
|
||||
<Button x:Name="BtnFixMode" Click="BtnFixMode_Click" Width="30" Height="30"
|
||||
Margin="5,0" Background="Transparent" BorderThickness="0"
|
||||
ToolTip="切换固定模式">
|
||||
<Path x:Name="FixModeIcon" Data="M7,2V13H10V22L17,10H13L17,2H7Z" Fill="White" Stretch="Uniform" Width="16" Height="16"/>
|
||||
</Button>
|
||||
<Button x:Name="BtnClose" Click="BtnClose_Click" Width="30" Height="30"
|
||||
Background="Transparent" BorderThickness="0"
|
||||
ToolTip="关闭">
|
||||
<Path Data="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z"
|
||||
Fill="White" Stretch="Uniform" Width="16" Height="16"/>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<!-- 应用网格 -->
|
||||
<ScrollViewer Grid.Row="1" Margin="10" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
|
||||
<WrapPanel x:Name="AppPanel" Orientation="Horizontal" HorizontalAlignment="Center"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
@@ -0,0 +1,466 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher
|
||||
{
|
||||
/// <summary>
|
||||
/// LauncherWindow.xaml 的交互逻辑
|
||||
/// </summary>
|
||||
public partial class LauncherWindow : Window
|
||||
{
|
||||
/// <summary>
|
||||
/// 父插件
|
||||
/// </summary>
|
||||
private readonly SuperLauncherPlugin _plugin;
|
||||
|
||||
/// <summary>
|
||||
/// 是否处于固定模式
|
||||
/// </summary>
|
||||
private bool _isFixMode;
|
||||
|
||||
/// <summary>
|
||||
/// 应用项按钮列表
|
||||
/// </summary>
|
||||
private readonly Dictionary<Button, LauncherItem> _appButtons = new Dictionary<Button, LauncherItem>();
|
||||
|
||||
/// <summary>
|
||||
/// 拖拽中的按钮
|
||||
/// </summary>
|
||||
private Button _draggingButton;
|
||||
|
||||
/// <summary>
|
||||
/// 拖拽开始位置
|
||||
/// </summary>
|
||||
private Point _dragStartPoint;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public LauncherWindow(SuperLauncherPlugin plugin)
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_plugin = plugin;
|
||||
|
||||
// 加载应用项
|
||||
LoadLauncherItems();
|
||||
|
||||
// 添加鼠标按下事件(用于拖动窗口)
|
||||
MouseDown += (s, e) =>
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left && e.ButtonState == MouseButtonState.Pressed)
|
||||
{
|
||||
DragMove();
|
||||
}
|
||||
};
|
||||
|
||||
// 根据应用数量调整窗口大小
|
||||
AdjustWindowSize();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载启动台应用项
|
||||
/// </summary>
|
||||
private void LoadLauncherItems()
|
||||
{
|
||||
// 清空现有应用项
|
||||
AppPanel.Children.Clear();
|
||||
_appButtons.Clear();
|
||||
|
||||
// 获取显示的应用项
|
||||
var visibleItems = _plugin.LauncherItems
|
||||
.Where(item => item.IsVisible)
|
||||
.OrderBy(item => item.Position)
|
||||
.ToList();
|
||||
|
||||
foreach (var item in visibleItems)
|
||||
{
|
||||
// 创建应用按钮
|
||||
Button appButton = new Button
|
||||
{
|
||||
Style = (Style)FindResource("LauncherItemStyle"),
|
||||
DataContext = item,
|
||||
Tag = item.Position
|
||||
};
|
||||
|
||||
// 添加点击事件
|
||||
appButton.Click += AppButton_Click;
|
||||
|
||||
// 在固定模式下,添加拖拽事件
|
||||
appButton.PreviewMouseDown += AppButton_PreviewMouseDown;
|
||||
appButton.PreviewMouseMove += AppButton_PreviewMouseMove;
|
||||
appButton.PreviewMouseUp += AppButton_PreviewMouseUp;
|
||||
|
||||
// 记录按钮和项目的对应关系
|
||||
_appButtons.Add(appButton, item);
|
||||
|
||||
// 添加到面板
|
||||
AppPanel.Children.Add(appButton);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据应用数量调整窗口大小
|
||||
/// </summary>
|
||||
private void AdjustWindowSize()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 每行最多显示4个应用
|
||||
const int appsPerRow = 4;
|
||||
|
||||
// 计算行数
|
||||
int visibleCount = _appButtons.Count;
|
||||
int rowCount = (int)Math.Ceiling(visibleCount / (double)appsPerRow);
|
||||
|
||||
// 设置窗口宽度(每个应用90像素宽 = 80 + 5*2)
|
||||
Width = Math.Min(appsPerRow * 90 + 40, 400); // 最大宽度400
|
||||
|
||||
// 设置窗口高度(每个应用90像素高 = 80 + 5*2)
|
||||
Height = Math.Min(rowCount * 90 + 60, 600); // 最大高度600,标题栏40 + 边距20
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"调整启动台窗口大小时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮点击事件
|
||||
/// </summary>
|
||||
private void AppButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_isFixMode) return; // 在固定模式下,不响应点击事件
|
||||
|
||||
if (sender is Button button && _appButtons.TryGetValue(button, out LauncherItem item))
|
||||
{
|
||||
// 获取应用路径和名称,用于后续启动
|
||||
string appPath = item.Path;
|
||||
string appName = item.Name;
|
||||
|
||||
LogHelper.WriteLogToFile($"点击启动应用: {appName}, 路径: {appPath}");
|
||||
|
||||
// 首先标记窗口正在关闭
|
||||
IsClosing = true;
|
||||
|
||||
// 创建一个应用启动任务
|
||||
var launchTask = new Task(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 等待一段时间,确保窗口关闭流程已经开始
|
||||
Thread.Sleep(200);
|
||||
|
||||
// 使用UI线程启动应用
|
||||
Application.Current.Dispatcher.Invoke(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 检查应用路径是否存在
|
||||
if (File.Exists(appPath) || !appPath.Contains(":\\"))
|
||||
{
|
||||
// 创建进程启动信息
|
||||
var psi = new ProcessStartInfo
|
||||
{
|
||||
FileName = appPath,
|
||||
UseShellExecute = true,
|
||||
};
|
||||
|
||||
// 启动应用程序
|
||||
var process = Process.Start(psi);
|
||||
LogHelper.WriteLogToFile($"应用程序 {appName} 已启动");
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用路径不存在: {appPath}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"找不到应用程序: {appPath}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启动应用程序失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
MessageBox.Show($"启动应用程序失败: {ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用启动任务出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
// 关闭窗口
|
||||
try
|
||||
{
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try { Close(); } catch { }
|
||||
|
||||
// 启动应用程序任务
|
||||
launchTask.Start();
|
||||
}), DispatcherPriority.Background);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"关闭窗口或启动任务时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
// 如果无法通过UI关闭窗口,直接启动任务
|
||||
launchTask.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"应用按钮点击事件出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
try { IsClosing = true; Close(); } catch { }
|
||||
}
|
||||
}
|
||||
|
||||
#region 固定模式拖拽事件
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标按下事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isFixMode) return;
|
||||
|
||||
if (e.ChangedButton == MouseButton.Left && sender is Button button)
|
||||
{
|
||||
_draggingButton = button;
|
||||
_dragStartPoint = e.GetPosition(AppPanel);
|
||||
button.CaptureMouse();
|
||||
button.Opacity = 0.7;
|
||||
|
||||
// 阻止事件冒泡,以避免触发按钮点击
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标移动事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!_isFixMode || _draggingButton == null) return;
|
||||
|
||||
if (e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Point currentPosition = e.GetPosition(AppPanel);
|
||||
|
||||
// 移动按钮
|
||||
System.Windows.Controls.Canvas.SetLeft(_draggingButton, currentPosition.X - _draggingButton.ActualWidth / 2);
|
||||
System.Windows.Controls.Canvas.SetTop(_draggingButton, currentPosition.Y - _draggingButton.ActualHeight / 2);
|
||||
|
||||
// 将按钮移到最上层
|
||||
Panel.SetZIndex(_draggingButton, 100);
|
||||
|
||||
// 阻止事件冒泡
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用按钮鼠标释放事件
|
||||
/// </summary>
|
||||
private void AppButton_PreviewMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (!_isFixMode || _draggingButton == null) return;
|
||||
|
||||
// 释放鼠标捕获
|
||||
_draggingButton.ReleaseMouseCapture();
|
||||
|
||||
// 计算新位置
|
||||
Point releasePoint = e.GetPosition(AppPanel);
|
||||
int newPosition = CalculateGridPosition(releasePoint);
|
||||
|
||||
// 获取当前项目
|
||||
LauncherItem currentItem = _appButtons[_draggingButton];
|
||||
|
||||
// 重新排序
|
||||
ReorderItems(currentItem, newPosition);
|
||||
|
||||
// 重新加载应用项
|
||||
LoadLauncherItems();
|
||||
|
||||
// 保存配置
|
||||
_plugin.SaveConfig();
|
||||
|
||||
// 清除拖拽状态
|
||||
_draggingButton.Opacity = 1;
|
||||
Panel.SetZIndex(_draggingButton, 0);
|
||||
_draggingButton = null;
|
||||
|
||||
// 阻止事件冒泡
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算网格位置
|
||||
/// </summary>
|
||||
private int CalculateGridPosition(Point point)
|
||||
{
|
||||
// 计算行和列
|
||||
int columnCount = 4; // 每行最多4个应用
|
||||
int columnWidth = 90; // 应用宽度(包括边距)
|
||||
int rowHeight = 90; // 应用高度(包括边距)
|
||||
|
||||
int column = (int)(point.X / columnWidth);
|
||||
int row = (int)(point.Y / rowHeight);
|
||||
|
||||
// 确保在有效范围内
|
||||
column = Math.Max(0, Math.Min(column, columnCount - 1));
|
||||
row = Math.Max(0, row);
|
||||
|
||||
// 计算位置索引
|
||||
return row * columnCount + column;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重新排序应用项
|
||||
/// </summary>
|
||||
private void ReorderItems(LauncherItem item, int newPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 设置项目为固定位置
|
||||
item.IsPositionFixed = true;
|
||||
|
||||
// 如果位置相同,无需调整
|
||||
if (item.Position == newPosition)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取所有可见项目
|
||||
var visibleItems = _plugin.LauncherItems
|
||||
.Where(i => i.IsVisible)
|
||||
.OrderBy(i => i.Position)
|
||||
.ToList();
|
||||
|
||||
// 移除当前项目
|
||||
visibleItems.Remove(item);
|
||||
|
||||
// 查找插入位置
|
||||
int insertIndex = 0;
|
||||
for (int i = 0; i < visibleItems.Count; i++)
|
||||
{
|
||||
if (visibleItems[i].Position >= newPosition)
|
||||
{
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
insertIndex = i + 1;
|
||||
}
|
||||
|
||||
// 插入项目
|
||||
visibleItems.Insert(insertIndex, item);
|
||||
|
||||
// 重新分配位置
|
||||
for (int i = 0; i < visibleItems.Count; i++)
|
||||
{
|
||||
visibleItems[i].Position = i;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"重新排序应用项时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口事件处理
|
||||
|
||||
/// <summary>
|
||||
/// 窗口失去焦点事件
|
||||
/// </summary>
|
||||
private void Window_Deactivated(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只有在非固定模式、窗口已加载、未处于关闭状态且IsLoaded=true时关闭窗口
|
||||
if (!_isFixMode && IsLoaded && !IsClosing)
|
||||
{
|
||||
// 标记为正在关闭
|
||||
IsClosing = true;
|
||||
|
||||
// 使用Dispatcher.BeginInvoke而不是直接调用Close,避免冲突
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 再次检查窗口状态
|
||||
if (IsLoaded && !IsClosing)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"延迟关闭窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}), DispatcherPriority.Background);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"窗口失去焦点关闭时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 窗口是否正在关闭
|
||||
/// </summary>
|
||||
private bool IsClosing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 重写OnClosing方法,标记窗口正在关闭
|
||||
/// </summary>
|
||||
protected override void OnClosing(CancelEventArgs e)
|
||||
{
|
||||
IsClosing = true;
|
||||
base.OnClosing(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 关闭按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnClose_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 固定模式按钮点击事件
|
||||
/// </summary>
|
||||
private void BtnFixMode_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 切换固定模式
|
||||
_isFixMode = !_isFixMode;
|
||||
|
||||
// 更新固定模式按钮图标颜色
|
||||
FixModeIcon.Fill = _isFixMode ? Brushes.Yellow : Brushes.White;
|
||||
|
||||
// 显示提示
|
||||
if (_isFixMode)
|
||||
{
|
||||
MessageBox.Show("已进入固定模式,您可以拖动应用图标调整位置。", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,589 @@
|
||||
using Ink_Canvas.Helpers.Plugins.BuiltIn.SuperLauncher;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins.BuiltIn
|
||||
{
|
||||
/// <summary>
|
||||
/// 超级启动台插件
|
||||
/// </summary>
|
||||
public class SuperLauncherPlugin : PluginBase
|
||||
{
|
||||
#region 插件基本信息
|
||||
|
||||
public override string Name => "超级启动台";
|
||||
|
||||
public override string Description => "在浮动栏添加一个启动台按钮,可快速启动常用应用程序。";
|
||||
|
||||
public override Version Version => new Version(1, 0, 1);
|
||||
|
||||
public override string Author => "ICC CE 团队";
|
||||
|
||||
public override bool IsBuiltIn => true;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件属性和字段
|
||||
|
||||
/// <summary>
|
||||
/// 启动台配置
|
||||
/// </summary>
|
||||
public LauncherConfig Config { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动台应用程序列表
|
||||
/// </summary>
|
||||
public ObservableCollection<LauncherItem> LauncherItems { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 启动台按钮
|
||||
/// </summary>
|
||||
private LauncherButton _launcherButton;
|
||||
|
||||
/// <summary>
|
||||
/// 启动台窗口
|
||||
/// </summary>
|
||||
private LauncherWindow _launcherWindow;
|
||||
|
||||
/// <summary>
|
||||
/// 配置文件路径
|
||||
/// </summary>
|
||||
private readonly string _configPath = Path.Combine(App.RootPath, "PluginConfigs", "SuperLauncher.json");
|
||||
|
||||
/// <summary>
|
||||
/// 标记是否已添加到浮动栏
|
||||
/// </summary>
|
||||
private bool _isAddedToFloatingBar;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件生命周期
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
try
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
// 创建配置目录
|
||||
string configDir = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
if (!Directory.Exists(configDir))
|
||||
{
|
||||
Directory.CreateDirectory(configDir);
|
||||
}
|
||||
|
||||
// 加载配置
|
||||
LoadConfig();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已初始化");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Enable()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsEnabled) return; // 防止重复启用
|
||||
|
||||
// 创建启动台按钮
|
||||
if (_launcherButton == null)
|
||||
{
|
||||
_launcherButton = new LauncherButton(this);
|
||||
LogHelper.WriteLogToFile("超级启动台按钮已创建");
|
||||
}
|
||||
|
||||
// 添加启动台按钮到浮动栏
|
||||
AddLauncherButtonToFloatingBar();
|
||||
|
||||
// 设置启用状态
|
||||
base.Enable();
|
||||
|
||||
// 保存插件配置
|
||||
SavePluginSettings();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已启用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Disable()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!IsEnabled) return; // 防止重复禁用
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
|
||||
// 如果启动台窗口打开,则关闭
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
}
|
||||
|
||||
// 设置禁用状态
|
||||
base.Disable();
|
||||
|
||||
// 保存插件配置
|
||||
SavePluginSettings();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件已禁用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用超级启动台插件时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public override UserControl GetSettingsView()
|
||||
{
|
||||
return new LauncherSettingsControl(this);
|
||||
}
|
||||
|
||||
public override void Cleanup()
|
||||
{
|
||||
// 保存配置
|
||||
SaveConfig();
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
|
||||
// 如果启动台窗口打开,则关闭
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
}
|
||||
|
||||
base.Cleanup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件设置
|
||||
/// </summary>
|
||||
public override void SavePluginSettings()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 确保配置已加载
|
||||
if (Config == null)
|
||||
{
|
||||
LoadConfig();
|
||||
}
|
||||
|
||||
// 更新其他设置,但不更改插件启用状态
|
||||
|
||||
// 保存配置
|
||||
SaveConfig();
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台插件设置已保存");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存超级启动台插件设置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 配置管理
|
||||
|
||||
/// <summary>
|
||||
/// 加载配置
|
||||
/// </summary>
|
||||
private void LoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(_configPath))
|
||||
{
|
||||
string json = File.ReadAllText(_configPath);
|
||||
Config = JsonConvert.DeserializeObject<LauncherConfig>(json) ?? CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items ?? new List<LauncherItem>());
|
||||
|
||||
// 注意:不再根据配置更改插件启用状态
|
||||
// 插件状态由PluginManager统一管理
|
||||
}
|
||||
else
|
||||
{
|
||||
Config = CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items);
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
Config = CreateDefaultConfig();
|
||||
LauncherItems = new ObservableCollection<LauncherItem>(Config.Items);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存配置
|
||||
/// </summary>
|
||||
public void SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 同步LauncherItems到Config
|
||||
Config.Items = new List<LauncherItem>(LauncherItems);
|
||||
|
||||
// 序列化并保存配置
|
||||
string json = JsonConvert.SerializeObject(Config, Formatting.Indented);
|
||||
File.WriteAllText(_configPath, json);
|
||||
|
||||
LogHelper.WriteLogToFile("超级启动台配置已保存");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存超级启动台配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建默认配置
|
||||
/// </summary>
|
||||
private LauncherConfig CreateDefaultConfig()
|
||||
{
|
||||
var config = new LauncherConfig
|
||||
{
|
||||
ButtonPosition = LauncherButtonPosition.Right,
|
||||
// 不再使用IsEnabled,插件状态由PluginManager管理
|
||||
Items = new List<LauncherItem>
|
||||
{
|
||||
new LauncherItem
|
||||
{
|
||||
Name = "资源管理器",
|
||||
Path = @"C:\Windows\explorer.exe",
|
||||
IsVisible = true,
|
||||
Position = 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 启动台按钮管理
|
||||
|
||||
/// <summary>
|
||||
/// 将启动台按钮添加到浮动栏
|
||||
/// </summary>
|
||||
private void AddLauncherButtonToFloatingBar()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果已经添加,先移除
|
||||
if (_isAddedToFloatingBar)
|
||||
{
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
_isAddedToFloatingBar = false;
|
||||
}
|
||||
|
||||
// 获取主窗口实例
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到主窗口实例,无法添加启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建启动台按钮
|
||||
_launcherButton = new LauncherButton(this);
|
||||
var buttonElement = _launcherButton.Element;
|
||||
|
||||
// 查找浮动栏
|
||||
var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel;
|
||||
if (floatingBar == null)
|
||||
{
|
||||
// 如果直接查找失败,则尝试遍历可视树查找
|
||||
Panel floatingBarPanelFromTree = null;
|
||||
FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree);
|
||||
floatingBar = floatingBarPanelFromTree;
|
||||
}
|
||||
|
||||
if (floatingBar == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到浮动栏,无法添加启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 添加启动台按钮到浮动栏
|
||||
if (Config.ButtonPosition == LauncherButtonPosition.Left)
|
||||
{
|
||||
floatingBar.Children.Insert(0, buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏左侧");
|
||||
}
|
||||
else
|
||||
{
|
||||
floatingBar.Children.Add(buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已添加到浮动栏右侧");
|
||||
}
|
||||
|
||||
_isAddedToFloatingBar = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"添加启动台按钮到浮动栏时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归查找StackPanelFloatingBar
|
||||
/// </summary>
|
||||
private void FindStackPanelFloatingBar(DependencyObject parent, ref Panel result)
|
||||
{
|
||||
if (parent == null || result != null) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 检查当前对象是否为我们要找的面板
|
||||
if (parent is Panel panel && panel.Name == "StackPanelFloatingBar")
|
||||
{
|
||||
result = panel;
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取子元素数量
|
||||
int childCount = VisualTreeHelper.GetChildrenCount(parent);
|
||||
|
||||
// 遍历所有子元素
|
||||
for (int i = 0; i < childCount; i++)
|
||||
{
|
||||
DependencyObject child = VisualTreeHelper.GetChild(parent, i);
|
||||
FindStackPanelFloatingBar(child, ref result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"查找StackPanelFloatingBar时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从浮动栏移除启动台按钮
|
||||
/// </summary>
|
||||
private void RemoveLauncherButtonFromFloatingBar()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_isAddedToFloatingBar || _launcherButton == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取主窗口实例
|
||||
var mainWindow = Application.Current.MainWindow;
|
||||
if (mainWindow == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到主窗口实例,无法移除启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取按钮元素
|
||||
var buttonElement = _launcherButton.Element;
|
||||
|
||||
// 查找浮动栏
|
||||
var floatingBar = mainWindow.FindName("StackPanelFloatingBar") as Panel;
|
||||
if (floatingBar == null)
|
||||
{
|
||||
// 如果直接查找失败,则尝试遍历可视树查找
|
||||
Panel floatingBarPanelFromTree = null;
|
||||
FindStackPanelFloatingBar(mainWindow, ref floatingBarPanelFromTree);
|
||||
floatingBar = floatingBarPanelFromTree;
|
||||
}
|
||||
|
||||
if (floatingBar == null)
|
||||
{
|
||||
LogHelper.WriteLogToFile("未找到浮动栏,无法移除启动台按钮", LogHelper.LogType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
// 从浮动栏移除启动台按钮
|
||||
if (floatingBar.Children.Contains(buttonElement))
|
||||
{
|
||||
floatingBar.Children.Remove(buttonElement);
|
||||
LogHelper.WriteLogToFile("启动台按钮已从浮动栏移除");
|
||||
}
|
||||
|
||||
_isAddedToFloatingBar = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"移除启动台按钮时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新启动台按钮位置
|
||||
/// </summary>
|
||||
public void UpdateButtonPosition()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果按钮已添加到浮动栏,重新添加以更新位置
|
||||
if (_isAddedToFloatingBar)
|
||||
{
|
||||
RemoveLauncherButtonFromFloatingBar();
|
||||
AddLauncherButtonToFloatingBar();
|
||||
LogHelper.WriteLogToFile($"启动台按钮位置已更新为: {Config.ButtonPosition}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"更新启动台按钮位置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
LogHelper.NewLog(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 启动台功能
|
||||
|
||||
/// <summary>
|
||||
/// 显示启动台窗口
|
||||
/// </summary>
|
||||
/// <param name="buttonPosition">按钮在屏幕上的位置</param>
|
||||
public void ShowLauncherWindow(Point buttonPosition)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 如果窗口已存在,关闭它
|
||||
if (_launcherWindow != null && _launcherWindow.IsVisible)
|
||||
{
|
||||
_launcherWindow.Close();
|
||||
_launcherWindow = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建新的启动台窗口
|
||||
_launcherWindow = new LauncherWindow(this);
|
||||
|
||||
// 计算窗口位置,使其位于按钮上方
|
||||
PositionLauncherWindow(_launcherWindow, buttonPosition);
|
||||
|
||||
// 显示窗口
|
||||
_launcherWindow.Show();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示启动台窗口时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置启动台窗口位置
|
||||
/// </summary>
|
||||
/// <param name="window">启动台窗口</param>
|
||||
/// <param name="buttonPosition">按钮在屏幕上的位置</param>
|
||||
private void PositionLauncherWindow(LauncherWindow window, Point buttonPosition)
|
||||
{
|
||||
// 确保窗口已加载
|
||||
if (window.ActualWidth == 0 || window.ActualHeight == 0)
|
||||
{
|
||||
window.WindowStartupLocation = WindowStartupLocation.CenterScreen;
|
||||
|
||||
// 设置窗口加载完成后的位置
|
||||
window.Loaded += (s, e) =>
|
||||
{
|
||||
// 窗口位于按钮上方居中
|
||||
double left = buttonPosition.X - (window.ActualWidth / 2);
|
||||
double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距
|
||||
|
||||
// 确保窗口在屏幕内
|
||||
left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth));
|
||||
top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight));
|
||||
|
||||
window.Left = left;
|
||||
window.Top = top;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
// 窗口位于按钮上方居中
|
||||
double left = buttonPosition.X - (window.ActualWidth / 2);
|
||||
double top = buttonPosition.Y - window.ActualHeight - 10; // 在按钮上方留出一些间距
|
||||
|
||||
// 确保窗口在屏幕内
|
||||
left = Math.Max(0, Math.Min(left, SystemParameters.WorkArea.Width - window.ActualWidth));
|
||||
top = Math.Max(0, Math.Min(top, SystemParameters.WorkArea.Height - window.ActualHeight));
|
||||
|
||||
window.Left = left;
|
||||
window.Top = top;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加应用到启动台
|
||||
/// </summary>
|
||||
/// <param name="item">启动台项</param>
|
||||
public void AddLauncherItem(LauncherItem item)
|
||||
{
|
||||
// 如果项目数量已达上限,则不添加
|
||||
if (LauncherItems.Count >= 40)
|
||||
{
|
||||
MessageBox.Show("启动台项目数量已达上限(40个)!", "提示", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
// 寻找合适的位置
|
||||
if (item.Position < 0)
|
||||
{
|
||||
item.Position = FindNextAvailablePosition();
|
||||
}
|
||||
|
||||
// 添加项目并保存配置
|
||||
LauncherItems.Add(item);
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 查找下一个可用位置
|
||||
/// </summary>
|
||||
private int FindNextAvailablePosition()
|
||||
{
|
||||
// 获取已使用的位置列表
|
||||
var usedPositions = new HashSet<int>();
|
||||
foreach (var item in LauncherItems)
|
||||
{
|
||||
usedPositions.Add(item.Position);
|
||||
}
|
||||
|
||||
// 查找第一个可用位置
|
||||
for (int i = 0; i < 40; i++)
|
||||
{
|
||||
if (!usedPositions.Contains(i))
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有位置都已使用,则返回0
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件基类,提供对插件服务的访问和基本实现
|
||||
/// </summary>
|
||||
public abstract class EnhancedPluginBase : PluginBase, IEnhancedPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务实例
|
||||
/// </summary>
|
||||
public IPluginService PluginService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
protected EnhancedPluginBase()
|
||||
{
|
||||
PluginService = PluginServiceManager.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
public virtual void OnStartup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
public virtual void OnShutdown()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
public virtual MenuItem[] GetMenuItems()
|
||||
{
|
||||
return new MenuItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
public virtual Button[] GetToolbarButtons()
|
||||
{
|
||||
return new Button[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
public virtual string GetStatusBarInfo()
|
||||
{
|
||||
return $"{Name} v{Version} - {(IsEnabled ? "已启用" : "已禁用")}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
public virtual void OnConfigurationChanged()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写初始化方法,调用OnStartup
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
OnStartup();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重写清理方法,调用OnShutdown
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
OnShutdown();
|
||||
base.Cleanup();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,241 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件基类 V2,提供对三个专门服务接口的访问
|
||||
/// 插件开发者可以根据需要选择性地使用这些服务
|
||||
/// </summary>
|
||||
public abstract class EnhancedPluginBaseV2 : PluginBase, IEnhancedPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取服务实例
|
||||
/// </summary>
|
||||
public IGetService GetService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 窗口服务实例
|
||||
/// </summary>
|
||||
public IWindowService WindowService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 操作服务实例
|
||||
/// </summary>
|
||||
public IActionService ActionService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件服务实例(兼容性)
|
||||
/// </summary>
|
||||
public IPluginService PluginService { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
protected EnhancedPluginBaseV2()
|
||||
{
|
||||
// 初始化所有服务实例
|
||||
PluginService = PluginServiceManager.Instance;
|
||||
GetService = PluginServiceManager.Instance;
|
||||
WindowService = PluginServiceManager.Instance;
|
||||
ActionService = PluginServiceManager.Instance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
public virtual void OnStartup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启动");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
public virtual void OnShutdown()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 正在关闭");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
public virtual MenuItem[] GetMenuItems()
|
||||
{
|
||||
return new MenuItem[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
public virtual Button[] GetToolbarButtons()
|
||||
{
|
||||
return new Button[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
public virtual string GetStatusBarInfo()
|
||||
{
|
||||
return $"{Name} v{Version} - {(IsEnabled ? "已启用" : "已禁用")}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
public virtual void OnConfigurationChanged()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 配置已变更");
|
||||
}
|
||||
|
||||
#region 便捷方法
|
||||
|
||||
/// <summary>
|
||||
/// 显示通知消息
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
protected void ShowNotification(string message, NotificationType type = NotificationType.Info)
|
||||
{
|
||||
WindowService.ShowNotification(message, type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示确认对话框
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <returns>用户选择结果</returns>
|
||||
protected bool ShowConfirmDialog(string message, string title = "确认")
|
||||
{
|
||||
return WindowService.ShowConfirmDialog(message, title);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 显示输入对话框
|
||||
/// </summary>
|
||||
/// <param name="message">提示消息</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>用户输入内容</returns>
|
||||
protected string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
|
||||
{
|
||||
return WindowService.ShowInputDialog(message, title, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>设置值</returns>
|
||||
protected T GetSetting<T>(string key, T defaultValue = default(T))
|
||||
{
|
||||
return GetService.GetSetting(key, defaultValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="value">设置值</param>
|
||||
protected void SetSetting<T>(string key, T value)
|
||||
{
|
||||
ActionService.SetSetting(key, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置
|
||||
/// </summary>
|
||||
protected void SaveSettings()
|
||||
{
|
||||
ActionService.SaveSettings();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除当前画布
|
||||
/// </summary>
|
||||
protected void ClearCanvas()
|
||||
{
|
||||
ActionService.ClearCanvas();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 撤销操作
|
||||
/// </summary>
|
||||
protected void Undo()
|
||||
{
|
||||
ActionService.Undo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重做操作
|
||||
/// </summary>
|
||||
protected void Redo()
|
||||
{
|
||||
ActionService.Redo();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以撤销
|
||||
/// </summary>
|
||||
protected bool CanUndo => GetService.CanUndo;
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否可以重做
|
||||
/// </summary>
|
||||
protected bool CanRedo => GetService.CanRedo;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前绘制模式
|
||||
/// </summary>
|
||||
protected int CurrentDrawingMode => GetService.CurrentDrawingMode;
|
||||
|
||||
/// <summary>
|
||||
/// 设置绘制模式
|
||||
/// </summary>
|
||||
/// <param name="mode">绘制模式</param>
|
||||
protected void SetDrawingMode(int mode)
|
||||
{
|
||||
ActionService.SetDrawingMode(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
protected void RegisterEventHandler(string eventName, System.EventHandler handler)
|
||||
{
|
||||
ActionService.RegisterEventHandler(eventName, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注销事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
protected void UnregisterEventHandler(string eventName, System.EventHandler handler)
|
||||
{
|
||||
ActionService.UnregisterEventHandler(eventName, handler);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发事件
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="args">事件参数</param>
|
||||
protected void TriggerEvent(string eventName, object sender, System.EventArgs args)
|
||||
{
|
||||
ActionService.TriggerEvent(eventName, sender, args);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
using System;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 操作服务接口,统一所有执行操作相关的方法
|
||||
/// </summary>
|
||||
public interface IActionService
|
||||
{
|
||||
#region 画布操作
|
||||
|
||||
/// <summary>
|
||||
/// 清除当前画布
|
||||
/// </summary>
|
||||
void ClearCanvas();
|
||||
|
||||
/// <summary>
|
||||
/// 清除所有画布
|
||||
/// </summary>
|
||||
void ClearAllCanvases();
|
||||
|
||||
/// <summary>
|
||||
/// 添加新页面
|
||||
/// </summary>
|
||||
void AddNewPage();
|
||||
|
||||
/// <summary>
|
||||
/// 删除当前页面
|
||||
/// </summary>
|
||||
void DeleteCurrentPage();
|
||||
|
||||
/// <summary>
|
||||
/// 切换到指定页面
|
||||
/// </summary>
|
||||
/// <param name="pageIndex">页面索引</param>
|
||||
void SwitchToPage(int pageIndex);
|
||||
|
||||
/// <summary>
|
||||
/// 切换到下一页
|
||||
/// </summary>
|
||||
void NextPage();
|
||||
|
||||
/// <summary>
|
||||
/// 切换到上一页
|
||||
/// </summary>
|
||||
void PreviousPage();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制操作
|
||||
|
||||
/// <summary>
|
||||
/// 设置绘制模式
|
||||
/// </summary>
|
||||
/// <param name="mode">绘制模式</param>
|
||||
void SetDrawingMode(int mode);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触宽度
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
void SetInkWidth(double width);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触颜色
|
||||
/// </summary>
|
||||
/// <param name="color">颜色</param>
|
||||
void SetInkColor(Color color);
|
||||
|
||||
/// <summary>
|
||||
/// 设置高亮笔宽度
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
void SetHighlighterWidth(double width);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦大小
|
||||
/// </summary>
|
||||
/// <param name="size">大小</param>
|
||||
void SetEraserSize(int size);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦类型
|
||||
/// </summary>
|
||||
/// <param name="type">类型</param>
|
||||
void SetEraserType(int type);
|
||||
|
||||
/// <summary>
|
||||
/// 设置橡皮擦形状
|
||||
/// </summary>
|
||||
/// <param name="shape">形状</param>
|
||||
void SetEraserShape(int shape);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触透明度
|
||||
/// </summary>
|
||||
/// <param name="alpha">透明度</param>
|
||||
void SetInkAlpha(double alpha);
|
||||
|
||||
/// <summary>
|
||||
/// 设置笔触样式
|
||||
/// </summary>
|
||||
/// <param name="style">样式</param>
|
||||
void SetInkStyle(int style);
|
||||
|
||||
/// <summary>
|
||||
/// 设置背景颜色
|
||||
/// </summary>
|
||||
/// <param name="color">颜色</param>
|
||||
void SetBackgroundColor(string color);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 文件操作
|
||||
|
||||
/// <summary>
|
||||
/// 保存画布内容
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void SaveCanvas(string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// 加载画布内容
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void LoadCanvas(string filePath);
|
||||
|
||||
/// <summary>
|
||||
/// 导出为图片
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
/// <param name="format">图片格式</param>
|
||||
void ExportAsImage(string filePath, string format);
|
||||
|
||||
/// <summary>
|
||||
/// 导出为PDF
|
||||
/// </summary>
|
||||
/// <param name="filePath">文件路径</param>
|
||||
void ExportAsPDF(string filePath);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 撤销重做操作
|
||||
|
||||
/// <summary>
|
||||
/// 撤销操作
|
||||
/// </summary>
|
||||
void Undo();
|
||||
|
||||
/// <summary>
|
||||
/// 重做操作
|
||||
/// </summary>
|
||||
void Redo();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 选择操作
|
||||
|
||||
/// <summary>
|
||||
/// 全选
|
||||
/// </summary>
|
||||
void SelectAll();
|
||||
|
||||
/// <summary>
|
||||
/// 取消选择
|
||||
/// </summary>
|
||||
void DeselectAll();
|
||||
|
||||
/// <summary>
|
||||
/// 删除选中内容
|
||||
/// </summary>
|
||||
void DeleteSelected();
|
||||
|
||||
/// <summary>
|
||||
/// 复制选中内容
|
||||
/// </summary>
|
||||
void CopySelected();
|
||||
|
||||
/// <summary>
|
||||
/// 剪切选中内容
|
||||
/// </summary>
|
||||
void CutSelected();
|
||||
|
||||
/// <summary>
|
||||
/// 粘贴内容
|
||||
/// </summary>
|
||||
void Paste();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统设置操作
|
||||
|
||||
/// <summary>
|
||||
/// 设置系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="value">设置值</param>
|
||||
void SetSetting<T>(string key, T value);
|
||||
|
||||
/// <summary>
|
||||
/// 保存设置到文件
|
||||
/// </summary>
|
||||
void SaveSettings();
|
||||
|
||||
/// <summary>
|
||||
/// 从文件加载设置
|
||||
/// </summary>
|
||||
void LoadSettings();
|
||||
|
||||
/// <summary>
|
||||
/// 重置设置为默认值
|
||||
/// </summary>
|
||||
void ResetSettings();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件管理操作
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void EnablePlugin(string pluginName);
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void DisablePlugin(string pluginName);
|
||||
|
||||
/// <summary>
|
||||
/// 卸载插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
void UnloadPlugin(string pluginName);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 事件系统操作
|
||||
|
||||
/// <summary>
|
||||
/// 注册事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
void RegisterEventHandler(string eventName, EventHandler handler);
|
||||
|
||||
/// <summary>
|
||||
/// 注销事件处理器
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="handler">事件处理器</param>
|
||||
void UnregisterEventHandler(string eventName, EventHandler handler);
|
||||
|
||||
/// <summary>
|
||||
/// 触发事件
|
||||
/// </summary>
|
||||
/// <param name="eventName">事件名称</param>
|
||||
/// <param name="sender">事件发送者</param>
|
||||
/// <param name="args">事件参数</param>
|
||||
void TriggerEvent(string eventName, object sender, EventArgs args);
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用程序操作
|
||||
|
||||
/// <summary>
|
||||
/// 重启应用程序
|
||||
/// </summary>
|
||||
void RestartApplication();
|
||||
|
||||
/// <summary>
|
||||
/// 退出应用程序
|
||||
/// </summary>
|
||||
void ExitApplication();
|
||||
|
||||
/// <summary>
|
||||
/// 检查更新
|
||||
/// </summary>
|
||||
void CheckForUpdates();
|
||||
|
||||
/// <summary>
|
||||
/// 打开帮助文档
|
||||
/// </summary>
|
||||
void OpenHelpDocument();
|
||||
|
||||
/// <summary>
|
||||
/// 打开关于页面
|
||||
/// </summary>
|
||||
void OpenAboutPage();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// ICCPP 插件适配器,用于加载和管理 .iccpp 格式的插件
|
||||
/// </summary>
|
||||
public class ICCPPPluginAdapter : PluginBase
|
||||
{
|
||||
private readonly byte[] _pluginData;
|
||||
private readonly string _pluginPath;
|
||||
private readonly string _pluginName;
|
||||
private readonly Version _pluginVersion;
|
||||
private bool _isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 创建 ICCPP 插件适配器
|
||||
/// </summary>
|
||||
/// <param name="pluginPath">插件文件路径</param>
|
||||
/// <param name="pluginData">插件文件数据</param>
|
||||
public ICCPPPluginAdapter(string pluginPath, byte[] pluginData)
|
||||
{
|
||||
_pluginPath = pluginPath;
|
||||
_pluginData = pluginData;
|
||||
PluginPath = pluginPath;
|
||||
|
||||
// 从文件名获取插件名称
|
||||
_pluginName = Path.GetFileNameWithoutExtension(pluginPath);
|
||||
_pluginVersion = new Version(1, 0, 0); // 默认版本
|
||||
|
||||
// 尝试从插件数据中读取更多信息
|
||||
TryReadPluginMetadata();
|
||||
}
|
||||
|
||||
public ICCPPPluginAdapter()
|
||||
{
|
||||
_pluginPath = string.Empty;
|
||||
_pluginData = new byte[0];
|
||||
PluginPath = string.Empty;
|
||||
_pluginName = "ICCPPPlugin";
|
||||
_pluginVersion = new Version(1, 0, 0);
|
||||
// 可选:初始化其他字段
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试从插件数据中读取元数据
|
||||
/// </summary>
|
||||
private void TryReadPluginMetadata()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以根据 .iccpp 文件的实际格式解析元数据
|
||||
// 例如,如果文件有特定的头部结构,可以在这里解析
|
||||
|
||||
// 示例:如果前100字节包含元数据
|
||||
if (_pluginData.Length > 100)
|
||||
{
|
||||
// 解析元数据的代码...
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"解析插件 {_pluginName} 元数据时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#region IPlugin 接口实现
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public override string Name => _pluginName;
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public override string Description => $"{_pluginName} (ICCPP 格式插件)";
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public override Version Version => _pluginVersion;
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public override string Author => "未知";
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
public override bool IsBuiltIn => false;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
if (_isInitialized) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的初始化逻辑
|
||||
// 例如,根据文件格式加载特定资源
|
||||
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已初始化");
|
||||
_isInitialized = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
public override void Enable()
|
||||
{
|
||||
if (IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的启用逻辑
|
||||
// 例如,加载动态库、注册事件等
|
||||
|
||||
base.Enable(); // 设置启用状态并触发事件
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已启用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"启用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
public override void Disable()
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的禁用逻辑
|
||||
// 例如,卸载动态库、注销事件等
|
||||
|
||||
base.Disable(); // 设置禁用状态并触发事件
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已禁用");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"禁用 ICCPP 插件 {Name} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理插件资源
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 这里可以添加 .iccpp 插件的清理逻辑
|
||||
// 例如,释放资源等
|
||||
|
||||
LogHelper.WriteLogToFile($"ICCPP 插件 {Name} 已清理资源");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理 ICCPP 插件 {Name} 资源时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 增强的插件接口,提供对插件服务的访问
|
||||
/// </summary>
|
||||
public interface IEnhancedPlugin : IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取插件服务实例
|
||||
/// </summary>
|
||||
IPluginService PluginService { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件启动时调用,在Initialize之后
|
||||
/// </summary>
|
||||
void OnStartup();
|
||||
|
||||
/// <summary>
|
||||
/// 插件关闭时调用,在Cleanup之前
|
||||
/// </summary>
|
||||
void OnShutdown();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的菜单项
|
||||
/// </summary>
|
||||
/// <returns>菜单项集合</returns>
|
||||
MenuItem[] GetMenuItems();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的工具栏按钮
|
||||
/// </summary>
|
||||
/// <returns>工具栏按钮集合</returns>
|
||||
Button[] GetToolbarButtons();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的状态栏信息
|
||||
/// </summary>
|
||||
/// <returns>状态栏信息</returns>
|
||||
string GetStatusBarInfo();
|
||||
|
||||
/// <summary>
|
||||
/// 插件配置变更时调用
|
||||
/// </summary>
|
||||
void OnConfigurationChanged();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取服务接口,统一所有获取类的方法
|
||||
/// </summary>
|
||||
public interface IGetService
|
||||
{
|
||||
#region 窗口和UI获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取主窗口引用
|
||||
/// </summary>
|
||||
Window MainWindow { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前画布
|
||||
/// </summary>
|
||||
InkCanvas CurrentCanvas { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有画布页面
|
||||
/// </summary>
|
||||
List<Canvas> AllCanvasPages { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前页面索引
|
||||
/// </summary>
|
||||
int CurrentPageIndex { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前页面数量
|
||||
/// </summary>
|
||||
int TotalPageCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取浮动工具栏
|
||||
/// </summary>
|
||||
FrameworkElement FloatingToolBar { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取左侧面板
|
||||
/// </summary>
|
||||
FrameworkElement LeftPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取右侧面板
|
||||
/// </summary>
|
||||
FrameworkElement RightPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取顶部面板
|
||||
/// </summary>
|
||||
FrameworkElement TopPanel { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取底部面板
|
||||
/// </summary>
|
||||
FrameworkElement BottomPanel { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制工具状态获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前绘制模式
|
||||
/// </summary>
|
||||
int CurrentDrawingMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触宽度
|
||||
/// </summary>
|
||||
double CurrentInkWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触颜色
|
||||
/// </summary>
|
||||
Color CurrentInkColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前高亮笔宽度
|
||||
/// </summary>
|
||||
double CurrentHighlighterWidth { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦大小
|
||||
/// </summary>
|
||||
int CurrentEraserSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦类型
|
||||
/// </summary>
|
||||
int CurrentEraserType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前橡皮擦形状
|
||||
/// </summary>
|
||||
int CurrentEraserShape { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触透明度
|
||||
/// </summary>
|
||||
double CurrentInkAlpha { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前笔触样式
|
||||
/// </summary>
|
||||
int CurrentInkStyle { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前背景颜色
|
||||
/// </summary>
|
||||
string CurrentBackgroundColor { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用状态获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前主题模式
|
||||
/// </summary>
|
||||
bool IsDarkTheme { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为白板模式
|
||||
/// </summary>
|
||||
bool IsWhiteboardMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为PPT模式
|
||||
/// </summary>
|
||||
bool IsPPTMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为全屏模式
|
||||
/// </summary>
|
||||
bool IsFullScreenMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为画板模式
|
||||
/// </summary>
|
||||
bool IsCanvasMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为选择模式
|
||||
/// </summary>
|
||||
bool IsSelectionMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为擦除模式
|
||||
/// </summary>
|
||||
bool IsEraserMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为形状绘制模式
|
||||
/// </summary>
|
||||
bool IsShapeDrawingMode { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前是否为高亮模式
|
||||
/// </summary>
|
||||
bool IsHighlighterMode { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 撤销重做状态获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否可以撤销
|
||||
/// </summary>
|
||||
bool CanUndo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取是否可以重做
|
||||
/// </summary>
|
||||
bool CanRedo { get; }
|
||||
|
||||
#endregion
|
||||
|
||||
#region 系统设置获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取系统设置
|
||||
/// </summary>
|
||||
/// <typeparam name="T">设置类型</typeparam>
|
||||
/// <param name="key">设置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>设置值</returns>
|
||||
T GetSetting<T>(string key, T defaultValue = default(T));
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件信息获取
|
||||
|
||||
/// <summary>
|
||||
/// 获取所有已加载的插件
|
||||
/// </summary>
|
||||
/// <returns>插件列表</returns>
|
||||
List<IPlugin> GetAllPlugins();
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定插件
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <returns>插件实例</returns>
|
||||
IPlugin GetPlugin(string pluginName);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 定义插件的基本接口
|
||||
/// </summary>
|
||||
public interface IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
string Author { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
bool IsBuiltIn { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// 此方法在插件加载时被调用,用于执行一些初始化工作
|
||||
/// </summary>
|
||||
void Initialize();
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// 此方法在插件被用户或系统启用时调用,激活插件功能
|
||||
/// </summary>
|
||||
void Enable();
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// 此方法在插件被用户或系统禁用时调用,停用插件功能
|
||||
/// </summary>
|
||||
void Disable();
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// 此方法返回插件的设置界面控件,用于展示在设置窗口
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
UserControl GetSettingsView();
|
||||
|
||||
/// <summary>
|
||||
/// 插件卸载时的清理工作
|
||||
/// 此方法在插件被卸载前调用,用于释放资源和执行清理
|
||||
/// </summary>
|
||||
void Cleanup();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务接口,提供对软件内部功能的访问
|
||||
/// 继承自三个专门的服务接口:获取服务、窗口服务、操作服务
|
||||
/// </summary>
|
||||
public interface IPluginService : IGetService, IWindowService, IActionService
|
||||
{
|
||||
// 这个接口现在继承自三个专门的服务接口
|
||||
// 所有方法都在子接口中定义,这里不需要重复定义
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通知类型枚举
|
||||
/// </summary>
|
||||
public enum NotificationType
|
||||
{
|
||||
/// <summary>
|
||||
/// 信息
|
||||
/// </summary>
|
||||
Info,
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
Success,
|
||||
|
||||
/// <summary>
|
||||
/// 警告
|
||||
/// </summary>
|
||||
Warning,
|
||||
|
||||
/// <summary>
|
||||
/// 错误
|
||||
/// </summary>
|
||||
Error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗口服务接口,统一所有窗口操作相关的方法
|
||||
/// </summary>
|
||||
public interface IWindowService
|
||||
{
|
||||
#region 窗口显示和隐藏
|
||||
|
||||
/// <summary>
|
||||
/// 显示设置窗口
|
||||
/// </summary>
|
||||
void ShowSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏设置窗口
|
||||
/// </summary>
|
||||
void HideSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示插件设置窗口
|
||||
/// </summary>
|
||||
void ShowPluginSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏插件设置窗口
|
||||
/// </summary>
|
||||
void HidePluginSettingsWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示帮助窗口
|
||||
/// </summary>
|
||||
void ShowHelpWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏帮助窗口
|
||||
/// </summary>
|
||||
void HideHelpWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 显示关于窗口
|
||||
/// </summary>
|
||||
void ShowAboutWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏关于窗口
|
||||
/// </summary>
|
||||
void HideAboutWindow();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 对话框和通知
|
||||
|
||||
/// <summary>
|
||||
/// 显示通知消息
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
void ShowNotification(string message, NotificationType type = NotificationType.Info);
|
||||
|
||||
/// <summary>
|
||||
/// 显示确认对话框
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <returns>用户选择结果</returns>
|
||||
bool ShowConfirmDialog(string message, string title = "确认");
|
||||
|
||||
/// <summary>
|
||||
/// 显示输入对话框
|
||||
/// </summary>
|
||||
/// <param name="message">提示消息</param>
|
||||
/// <param name="title">标题</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>用户输入内容</returns>
|
||||
string ShowInputDialog(string message, string title = "输入", string defaultValue = "");
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口状态控制
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口全屏状态
|
||||
/// </summary>
|
||||
/// <param name="isFullScreen">是否全屏</param>
|
||||
void SetFullScreen(bool isFullScreen);
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口置顶状态
|
||||
/// </summary>
|
||||
/// <param name="isTopMost">是否置顶</param>
|
||||
void SetTopMost(bool isTopMost);
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口可见性
|
||||
/// </summary>
|
||||
/// <param name="isVisible">是否可见</param>
|
||||
void SetWindowVisibility(bool isVisible);
|
||||
|
||||
/// <summary>
|
||||
/// 最小化窗口
|
||||
/// </summary>
|
||||
void MinimizeWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 最大化窗口
|
||||
/// </summary>
|
||||
void MaximizeWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 恢复窗口
|
||||
/// </summary>
|
||||
void RestoreWindow();
|
||||
|
||||
/// <summary>
|
||||
/// 关闭窗口
|
||||
/// </summary>
|
||||
void CloseWindow();
|
||||
|
||||
#endregion
|
||||
|
||||
#region 窗口位置和大小
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口位置
|
||||
/// </summary>
|
||||
/// <param name="x">X坐标</param>
|
||||
/// <param name="y">Y坐标</param>
|
||||
void SetWindowPosition(double x, double y);
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口大小
|
||||
/// </summary>
|
||||
/// <param name="width">宽度</param>
|
||||
/// <param name="height">高度</param>
|
||||
void SetWindowSize(double width, double height);
|
||||
|
||||
/// <summary>
|
||||
/// 获取窗口位置
|
||||
/// </summary>
|
||||
/// <returns>窗口位置</returns>
|
||||
(double x, double y) GetWindowPosition();
|
||||
|
||||
/// <summary>
|
||||
/// 获取窗口大小
|
||||
/// </summary>
|
||||
/// <returns>窗口大小</returns>
|
||||
(double width, double height) GetWindowSize();
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件基类,提供基本实现
|
||||
/// </summary>
|
||||
public abstract class PluginBase : IPlugin
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件状态(私有字段)
|
||||
/// </summary>
|
||||
private bool _isEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// 插件状态(公共属性)
|
||||
/// </summary>
|
||||
public bool IsEnabled
|
||||
{
|
||||
get => _isEnabled;
|
||||
protected set
|
||||
{
|
||||
if (_isEnabled != value)
|
||||
{
|
||||
_isEnabled = value;
|
||||
OnEnabledStateChanged(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件ID
|
||||
/// </summary>
|
||||
public string Id { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件路径
|
||||
/// </summary>
|
||||
public string PluginPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public abstract Version Version { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public abstract string Author { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件
|
||||
/// </summary>
|
||||
public virtual bool IsBuiltIn => false;
|
||||
|
||||
/// <summary>
|
||||
/// 状态变更事件
|
||||
/// </summary>
|
||||
public event EventHandler<bool> EnabledStateChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 初始化插件
|
||||
/// </summary>
|
||||
public virtual void Initialize()
|
||||
{
|
||||
Id = GetType().FullName;
|
||||
|
||||
// 添加日志,记录插件名称
|
||||
try
|
||||
{
|
||||
string name = Name;
|
||||
LogHelper.WriteLogToFile($"初始化插件: ID={Id}, 名称={name ?? "未命名"}");
|
||||
|
||||
if (string.IsNullOrEmpty(name))
|
||||
{
|
||||
LogHelper.WriteLogToFile($"警告: 插件 {Id} 的名称为空", LogHelper.LogType.Warning);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取插件名称时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已初始化");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// </summary>
|
||||
public virtual void Enable()
|
||||
{
|
||||
if (!IsEnabled)
|
||||
{
|
||||
IsEnabled = true;
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// </summary>
|
||||
public virtual void Disable()
|
||||
{
|
||||
if (IsEnabled)
|
||||
{
|
||||
IsEnabled = false;
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
public virtual UserControl GetSettingsView()
|
||||
{
|
||||
// 默认返回空设置页面
|
||||
return new UserControl();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件卸载时的清理工作
|
||||
/// </summary>
|
||||
public virtual void Cleanup()
|
||||
{
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已卸载");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件自身的设置
|
||||
/// 注意:此方法仅用于保存插件的特定设置,不应影响插件启用/禁用状态
|
||||
/// 插件启用状态由PluginManager统一管理
|
||||
/// </summary>
|
||||
public virtual void SavePluginSettings()
|
||||
{
|
||||
// 默认实现不做任何事情
|
||||
// 子类可以重写此方法,将自身设置保存到配置文件中
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 设置已保存", LogHelper.LogType.Event);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 触发状态变更事件
|
||||
/// </summary>
|
||||
/// <param name="isEnabled">是否启用</param>
|
||||
protected virtual void OnEnabledStateChanged(bool isEnabled)
|
||||
{
|
||||
EnabledStateChanged?.Invoke(this, isEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件配置管理器,允许插件管理自己的配置
|
||||
/// </summary>
|
||||
public class PluginConfigurationManager
|
||||
{
|
||||
private static readonly string PluginConfigDirectory = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
private static readonly Dictionary<string, Dictionary<string, object>> _pluginConfigs = new Dictionary<string, Dictionary<string, object>>();
|
||||
private static readonly object _lockObject = new object();
|
||||
|
||||
static PluginConfigurationManager()
|
||||
{
|
||||
// 确保配置目录存在
|
||||
if (!Directory.Exists(PluginConfigDirectory))
|
||||
{
|
||||
Directory.CreateDirectory(PluginConfigDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">配置值类型</typeparam>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>配置值</returns>
|
||||
public static T GetConfiguration<T>(string pluginName, string key, T defaultValue = default(T))
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
if (pluginConfig.TryGetValue(key, out var value))
|
||||
{
|
||||
if (value is T typedValue)
|
||||
{
|
||||
return typedValue;
|
||||
}
|
||||
|
||||
// 尝试类型转换
|
||||
try
|
||||
{
|
||||
return (T)Convert.ChangeType(value, typeof(T));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"获取插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置插件配置值
|
||||
/// </summary>
|
||||
/// <typeparam name="T">配置值类型</typeparam>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
/// <param name="value">配置值</param>
|
||||
public static void SetConfiguration<T>(string pluginName, string key, T value)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!_pluginConfigs.ContainsKey(pluginName))
|
||||
{
|
||||
_pluginConfigs[pluginName] = new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
_pluginConfigs[pluginName][key] = value;
|
||||
|
||||
// 异步保存配置
|
||||
Task.Run(() => SavePluginConfiguration(pluginName));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"设置插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <param name="key">配置键</param>
|
||||
public static void RemoveConfiguration(string pluginName, string key)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
if (pluginConfig.Remove(key))
|
||||
{
|
||||
// 异步保存配置
|
||||
Task.Run(() => SavePluginConfiguration(pluginName));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"删除插件 {pluginName} 配置 {key} 时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件的所有配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
/// <returns>配置字典</returns>
|
||||
public static Dictionary<string, object> GetAllConfigurations(string pluginName)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (_pluginConfigs.TryGetValue(pluginName, out var pluginConfig))
|
||||
{
|
||||
return new Dictionary<string, object>(pluginConfig);
|
||||
}
|
||||
return new Dictionary<string, object>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除插件的所有配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
public static void ClearAllConfigurations(string pluginName)
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_pluginConfigs.Remove(pluginName))
|
||||
{
|
||||
// 删除配置文件
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
File.Delete(configFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清除插件 {pluginName} 所有配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
public static void LoadPluginConfiguration(string pluginName)
|
||||
{
|
||||
try
|
||||
{
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
if (File.Exists(configFile))
|
||||
{
|
||||
string json = File.ReadAllText(configFile);
|
||||
var config = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
_pluginConfigs[pluginName] = config ?? new Dictionary<string, object>();
|
||||
}
|
||||
|
||||
LogHelper.WriteLogToFile($"已加载插件 {pluginName} 的配置");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件配置
|
||||
/// </summary>
|
||||
/// <param name="pluginName">插件名称</param>
|
||||
private static void SavePluginConfiguration(string pluginName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dictionary<string, object> pluginConfig;
|
||||
lock (_lockObject)
|
||||
{
|
||||
if (!_pluginConfigs.TryGetValue(pluginName, out pluginConfig))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
string configFile = Path.Combine(PluginConfigDirectory, $"{pluginName}.json");
|
||||
string json = JsonConvert.SerializeObject(pluginConfig, Formatting.Indented);
|
||||
File.WriteAllText(configFile, json);
|
||||
|
||||
LogHelper.WriteLogToFile($"已保存插件 {pluginName} 的配置");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存插件 {pluginName} 配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 加载所有插件的配置
|
||||
/// </summary>
|
||||
public static void LoadAllPluginConfigurations()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Directory.Exists(PluginConfigDirectory))
|
||||
{
|
||||
string[] configFiles = Directory.GetFiles(PluginConfigDirectory, "*.json");
|
||||
foreach (string configFile in configFiles)
|
||||
{
|
||||
string pluginName = Path.GetFileNameWithoutExtension(configFile);
|
||||
LoadPluginConfiguration(pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存所有插件的配置
|
||||
/// </summary>
|
||||
public static void SaveAllPluginConfigurations()
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
foreach (string pluginName in _pluginConfigs.Keys)
|
||||
{
|
||||
SavePluginConfiguration(pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存所有插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,509 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件服务管理器,实现IPluginService接口,提供对软件内部功能的访问
|
||||
/// </summary>
|
||||
public class PluginServiceManager : IPluginService
|
||||
{
|
||||
private static PluginServiceManager _instance;
|
||||
private MainWindow _mainWindow;
|
||||
private Dictionary<string, EventHandler> _eventHandlers;
|
||||
|
||||
/// <summary>
|
||||
/// 单例实例
|
||||
/// </summary>
|
||||
public static PluginServiceManager Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_instance == null)
|
||||
{
|
||||
_instance = new PluginServiceManager();
|
||||
}
|
||||
return _instance;
|
||||
}
|
||||
}
|
||||
|
||||
private PluginServiceManager()
|
||||
{
|
||||
_eventHandlers = new Dictionary<string, EventHandler>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置主窗口引用
|
||||
/// </summary>
|
||||
/// <param name="mainWindow">主窗口实例</param>
|
||||
public void SetMainWindow(MainWindow mainWindow)
|
||||
{
|
||||
_mainWindow = mainWindow;
|
||||
}
|
||||
|
||||
#region 窗口和UI访问
|
||||
|
||||
public Window MainWindow => _mainWindow;
|
||||
|
||||
public InkCanvas CurrentCanvas => null; // 暂时返回null,避免访问权限问题
|
||||
|
||||
public List<Canvas> AllCanvasPages => new List<Canvas>(); // 暂时返回空列表
|
||||
|
||||
public int CurrentPageIndex => 0; // 暂时返回0
|
||||
|
||||
public int TotalPageCount => 0; // 暂时返回0
|
||||
|
||||
public FrameworkElement FloatingToolBar => _mainWindow?.ViewboxFloatingBar;
|
||||
|
||||
public FrameworkElement LeftPanel => _mainWindow?.BlackboardLeftSide;
|
||||
|
||||
public FrameworkElement RightPanel => _mainWindow?.BlackboardRightSide;
|
||||
|
||||
public FrameworkElement TopPanel => _mainWindow?.BorderTools;
|
||||
|
||||
public FrameworkElement BottomPanel => _mainWindow?.BorderSettings;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 绘制工具状态
|
||||
|
||||
public int CurrentDrawingMode => 0; // 暂时返回0
|
||||
|
||||
public double CurrentInkWidth => 2.5; // 暂时返回默认值
|
||||
|
||||
public Color CurrentInkColor => Colors.Black; // 暂时返回默认值
|
||||
|
||||
public double CurrentHighlighterWidth => 20.0; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserSize => 2; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserType => 0; // 暂时返回默认值
|
||||
|
||||
public int CurrentEraserShape => 0; // 暂时返回默认值
|
||||
|
||||
public double CurrentInkAlpha => 255.0; // 暂时返回默认值
|
||||
|
||||
public int CurrentInkStyle => 0; // 暂时返回默认值
|
||||
|
||||
public string CurrentBackgroundColor => "#162924"; // 暂时返回默认值
|
||||
|
||||
#endregion
|
||||
|
||||
#region 应用状态
|
||||
|
||||
public bool IsDarkTheme => false; // 暂时返回默认值
|
||||
|
||||
public bool IsWhiteboardMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsPPTMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsFullScreenMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsCanvasMode => true; // 暂时返回默认值
|
||||
|
||||
public bool IsSelectionMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsEraserMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsShapeDrawingMode => false; // 暂时返回默认值
|
||||
|
||||
public bool IsHighlighterMode => false; // 暂时返回默认值
|
||||
|
||||
#endregion
|
||||
|
||||
#region IGetService 实现
|
||||
|
||||
public bool CanUndo => false; // 暂时返回默认值
|
||||
|
||||
public bool CanRedo => false; // 暂时返回默认值
|
||||
|
||||
public T GetSetting<T>(string key, T defaultValue = default(T))
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public List<IPlugin> GetAllPlugins()
|
||||
{
|
||||
return new List<IPlugin>(PluginManager.Instance.Plugins);
|
||||
}
|
||||
|
||||
public IPlugin GetPlugin(string pluginName)
|
||||
{
|
||||
return PluginManager.Instance.Plugins.FirstOrDefault(p => p.Name == pluginName);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IWindowService 实现
|
||||
|
||||
public void ShowSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowPluginSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HidePluginSettingsWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowHelpWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideHelpWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowAboutWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void HideAboutWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ShowNotification(string message, NotificationType type = NotificationType.Info)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public bool ShowConfirmDialog(string message, string title = "确认")
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return false;
|
||||
}
|
||||
|
||||
public string ShowInputDialog(string message, string title = "输入", string defaultValue = "")
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
public void SetFullScreen(bool isFullScreen)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetTopMost(bool isTopMost)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetWindowVisibility(bool isVisible)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void MinimizeWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void MaximizeWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void RestoreWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CloseWindow()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetWindowPosition(double x, double y)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetWindowSize(double width, double height)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public (double x, double y) GetWindowPosition()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
public (double width, double height) GetWindowSize()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
return (800, 600);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IActionService 实现
|
||||
|
||||
public void ClearCanvas()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ClearAllCanvases()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void AddNewPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeleteCurrentPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SwitchToPage(int pageIndex)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void NextPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void PreviousPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetDrawingMode(int mode)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkWidth(double width)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkColor(Color color)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetHighlighterWidth(double width)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserSize(int size)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserType(int type)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetEraserShape(int shape)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkAlpha(double alpha)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetInkStyle(int style)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetBackgroundColor(string color)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SaveCanvas(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void LoadCanvas(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExportAsImage(string filePath, string format)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExportAsPDF(string filePath)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Undo()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Redo()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SelectAll()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeselectAll()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void DeleteSelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CopySelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CutSelected()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void Paste()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SetSetting<T>(string key, T value)
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void SaveSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void LoadSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ResetSettings()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void EnablePlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.TogglePlugin(plugin, true);
|
||||
}
|
||||
}
|
||||
|
||||
public void DisablePlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.TogglePlugin(plugin, false);
|
||||
}
|
||||
}
|
||||
|
||||
public void UnloadPlugin(string pluginName)
|
||||
{
|
||||
var plugin = GetPlugin(pluginName);
|
||||
if (plugin != null)
|
||||
{
|
||||
PluginManager.Instance.UnloadPlugin(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterEventHandler(string eventName, EventHandler handler)
|
||||
{
|
||||
if (!_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName] = handler;
|
||||
}
|
||||
else
|
||||
{
|
||||
_eventHandlers[eventName] += handler;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterEventHandler(string eventName, EventHandler handler)
|
||||
{
|
||||
if (_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName] -= handler;
|
||||
}
|
||||
}
|
||||
|
||||
public void TriggerEvent(string eventName, object sender, EventArgs args)
|
||||
{
|
||||
if (_eventHandlers.ContainsKey(eventName))
|
||||
{
|
||||
_eventHandlers[eventName]?.Invoke(sender, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void RestartApplication()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void ExitApplication()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void CheckForUpdates()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void OpenHelpDocument()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
public void OpenAboutPage()
|
||||
{
|
||||
// 暂时不实现,避免访问权限问题
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Ink_Canvas.Helpers.Plugins
|
||||
{
|
||||
/// <summary>
|
||||
/// 插件模板,用于开发者参考
|
||||
/// 注意:实际开发时,请将此类移到单独的程序集中
|
||||
/// </summary>
|
||||
public class PluginTemplate : PluginBase
|
||||
{
|
||||
#region 插件基本信息
|
||||
|
||||
/// <summary>
|
||||
/// 插件名称
|
||||
/// </summary>
|
||||
public override string Name => "插件模板";
|
||||
|
||||
/// <summary>
|
||||
/// 插件描述
|
||||
/// </summary>
|
||||
public override string Description => "这是一个插件开发模板,用于开发者参考。";
|
||||
|
||||
/// <summary>
|
||||
/// 插件版本
|
||||
/// </summary>
|
||||
public override Version Version => new Version(1, 0, 0);
|
||||
|
||||
/// <summary>
|
||||
/// 插件作者
|
||||
/// </summary>
|
||||
public override string Author => "Your Name";
|
||||
|
||||
/// <summary>
|
||||
/// 是否为内置插件(外部插件请返回false)
|
||||
/// </summary>
|
||||
public override bool IsBuiltIn => false;
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件生命周期
|
||||
|
||||
/// <summary>
|
||||
/// 插件初始化
|
||||
/// 在这里进行插件的初始化工作,如加载配置、注册事件等
|
||||
/// </summary>
|
||||
public override void Initialize()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件ID和记录日志
|
||||
base.Initialize();
|
||||
|
||||
// TODO: 在这里进行插件初始化工作
|
||||
|
||||
// 示例:记录初始化信息
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 开始初始化");
|
||||
|
||||
// 示例:加载配置
|
||||
LoadConfig();
|
||||
|
||||
// 示例:注册自定义事件
|
||||
// MainWindow.Instance.SomeEvent += OnSomeEvent;
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 初始化完成");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启用插件
|
||||
/// 在这里激活插件功能
|
||||
/// </summary>
|
||||
public override void Enable()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件状态和记录日志
|
||||
base.Enable();
|
||||
|
||||
// TODO: 在这里启用插件功能
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已启用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 禁用插件
|
||||
/// 在这里停用插件功能
|
||||
/// </summary>
|
||||
public override void Disable()
|
||||
{
|
||||
// 先调用基类方法,这样会设置插件状态和记录日志
|
||||
base.Disable();
|
||||
|
||||
// TODO: 在这里禁用插件功能
|
||||
|
||||
LogHelper.WriteLogToFile($"插件 {Name} 已禁用");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理资源
|
||||
/// 在插件卸载时调用,清理资源
|
||||
/// </summary>
|
||||
public override void Cleanup()
|
||||
{
|
||||
// TODO: 在这里清理插件资源
|
||||
|
||||
// 示例:取消注册事件
|
||||
// MainWindow.Instance.SomeEvent -= OnSomeEvent;
|
||||
|
||||
// 示例:保存配置
|
||||
SaveConfig();
|
||||
|
||||
// 最后调用基类方法
|
||||
base.Cleanup();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件配置
|
||||
|
||||
/// <summary>
|
||||
/// 加载插件配置
|
||||
/// </summary>
|
||||
private void LoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 从文件或其他位置加载配置
|
||||
// 示例:
|
||||
// string configPath = Path.Combine(App.RootPath, "PluginConfigs", "YourPluginName.json");
|
||||
// if (File.Exists(configPath))
|
||||
// {
|
||||
// string json = File.ReadAllText(configPath);
|
||||
// YourConfig = Newtonsoft.Json.JsonConvert.DeserializeObject<YourConfigClass>(json);
|
||||
// }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"加载插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 保存插件配置
|
||||
/// </summary>
|
||||
private void SaveConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
// TODO: 保存配置到文件或其他位置
|
||||
// 示例:
|
||||
// string configDir = Path.Combine(App.RootPath, "PluginConfigs");
|
||||
// if (!Directory.Exists(configDir))
|
||||
// {
|
||||
// Directory.CreateDirectory(configDir);
|
||||
// }
|
||||
// string configPath = Path.Combine(configDir, "YourPluginName.json");
|
||||
// string json = Newtonsoft.Json.JsonConvert.SerializeObject(YourConfig, Newtonsoft.Json.Formatting.Indented);
|
||||
// File.WriteAllText(configPath, json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"保存插件配置时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件设置界面
|
||||
|
||||
/// <summary>
|
||||
/// 获取插件设置界面
|
||||
/// </summary>
|
||||
/// <returns>插件设置界面</returns>
|
||||
public override UserControl GetSettingsView()
|
||||
{
|
||||
// 创建插件设置界面
|
||||
return new PluginTemplateSettingsControl();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 插件功能方法
|
||||
|
||||
// TODO: 在这里添加插件的具体功能方法
|
||||
|
||||
/// <summary>
|
||||
/// 示例方法:执行一些功能
|
||||
/// </summary>
|
||||
public void DoSomething()
|
||||
{
|
||||
if (!IsEnabled) return;
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: 实现你的功能
|
||||
MessageBox.Show("插件功能执行示例", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"执行插件功能时出错: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 插件设置控件
|
||||
/// </summary>
|
||||
public class PluginTemplateSettingsControl : UserControl
|
||||
{
|
||||
public PluginTemplateSettingsControl()
|
||||
{
|
||||
// 创建设置界面布局
|
||||
var panel = new StackPanel
|
||||
{
|
||||
Margin = new Thickness(10)
|
||||
};
|
||||
|
||||
// 添加标题
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "插件模板设置",
|
||||
FontSize = 16,
|
||||
FontWeight = FontWeights.Bold,
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
});
|
||||
|
||||
// 添加说明文字
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "这是一个示例设置界面,你可以在这里添加自己的设置控件。",
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Margin = new Thickness(0, 0, 0, 15)
|
||||
});
|
||||
|
||||
// 添加示例设置选项
|
||||
var checkBox = new CheckBox
|
||||
{
|
||||
Content = "启用某项功能",
|
||||
Margin = new Thickness(0, 0, 0, 10)
|
||||
};
|
||||
panel.Children.Add(checkBox);
|
||||
|
||||
// 添加文本输入框
|
||||
panel.Children.Add(new TextBlock
|
||||
{
|
||||
Text = "设置项:",
|
||||
Margin = new Thickness(0, 5, 0, 5)
|
||||
});
|
||||
|
||||
panel.Children.Add(new TextBox
|
||||
{
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
Width = 200,
|
||||
HorizontalAlignment = HorizontalAlignment.Left
|
||||
});
|
||||
|
||||
// 添加按钮
|
||||
var button = new Button
|
||||
{
|
||||
Content = "保存设置",
|
||||
Padding = new Thickness(10, 5, 10, 5),
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
HorizontalAlignment = HorizontalAlignment.Left
|
||||
};
|
||||
|
||||
button.Click += (sender, e) =>
|
||||
{
|
||||
MessageBox.Show("设置已保存!", "插件模板", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
};
|
||||
|
||||
panel.Children.Add(button);
|
||||
|
||||
// 设置控件内容
|
||||
Content = panel;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; // 出错时假设在主屏幕
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-6
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
@@ -27,10 +28,7 @@ namespace Ink_Canvas.Helpers
|
||||
//MessageBox.Show("启动失败: " + ex.Message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//Console.WriteLine(softwareName + " 未找到可执行文件路径。");
|
||||
}
|
||||
//Console.WriteLine(softwareName + " 未找到可执行文件路径。");
|
||||
}
|
||||
|
||||
private static string FindEasiCameraExecutablePath(string softwareName)
|
||||
@@ -51,7 +49,7 @@ namespace Ink_Canvas.Helpers
|
||||
{
|
||||
if (!string.IsNullOrEmpty(installLocation))
|
||||
{
|
||||
executablePath = System.IO.Path.Combine(installLocation, "sweclauncher.exe");
|
||||
executablePath = Path.Combine(installLocation, "sweclauncher.exe");
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(uninstallString))
|
||||
{
|
||||
@@ -59,7 +57,7 @@ namespace Ink_Canvas.Helpers
|
||||
if (lastSlashIndex >= 0)
|
||||
{
|
||||
string folderPath = uninstallString.Substring(0, lastSlashIndex);
|
||||
executablePath = System.IO.Path.Combine(folderPath, "sweclauncher", "sweclauncher.exe");
|
||||
executablePath = Path.Combine(folderPath, "sweclauncher", "sweclauncher.exe");
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.IO;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public static class StartupCount
|
||||
{
|
||||
private static readonly string CountFilePath = Path.Combine(App.RootPath, "startup-count");
|
||||
private static readonly object fileLock = new object();
|
||||
|
||||
public static int GetCount()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(CountFilePath))
|
||||
{
|
||||
var text = File.ReadAllText(CountFilePath).Trim();
|
||||
if (int.TryParse(text, out int count))
|
||||
return count;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static void Increment()
|
||||
{
|
||||
lock (fileLock)
|
||||
{
|
||||
int count = GetCount() + 1;
|
||||
try
|
||||
{
|
||||
File.WriteAllText(CountFilePath, count.ToString());
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
public static void Reset()
|
||||
{
|
||||
lock (fileLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (File.Exists(CountFilePath))
|
||||
File.Delete(CountFilePath);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
// Added for UIElement
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
public class TimeMachine
|
||||
public partial class TimeMachine
|
||||
{
|
||||
private readonly List<TimeMachineHistory> _currentStrokeHistory = new List<TimeMachineHistory>();
|
||||
|
||||
@@ -133,12 +135,13 @@ namespace Ink_Canvas.Helpers
|
||||
public class TimeMachineHistory
|
||||
{
|
||||
public TimeMachineHistoryType CommitType;
|
||||
public bool StrokeHasBeenCleared = false;
|
||||
public bool StrokeHasBeenCleared;
|
||||
public StrokeCollection CurrentStroke;
|
||||
public StrokeCollection ReplacedStroke;
|
||||
//这里说一下 Tuple的 Value1 是初始值 ; Value 2 是改变值
|
||||
public Dictionary<Stroke, Tuple<StylusPointCollection, StylusPointCollection>> StylusPointDictionary;
|
||||
public Dictionary<Stroke, Tuple<DrawingAttributes, DrawingAttributes>> DrawingAttributes;
|
||||
public UIElement InsertedElement; // 新增
|
||||
public TimeMachineHistory(StrokeCollection currentStroke, TimeMachineHistoryType commitType, bool strokeHasBeenCleared)
|
||||
{
|
||||
CommitType = commitType;
|
||||
@@ -163,6 +166,11 @@ namespace Ink_Canvas.Helpers
|
||||
StrokeHasBeenCleared = strokeHasBeenCleared;
|
||||
ReplacedStroke = replacedStroke;
|
||||
}
|
||||
public TimeMachineHistory(UIElement element, TimeMachineHistoryType commitType) // 新增
|
||||
{
|
||||
CommitType = commitType;
|
||||
InsertedElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
public enum TimeMachineHistoryType
|
||||
@@ -171,6 +179,34 @@ namespace Ink_Canvas.Helpers
|
||||
ShapeRecognition,
|
||||
Clear,
|
||||
Manipulation,
|
||||
DrawingAttributes
|
||||
DrawingAttributes,
|
||||
ElementInsert // 新增
|
||||
}
|
||||
|
||||
public partial class TimeMachine // 新增partial,便于扩展
|
||||
{
|
||||
public void CommitElementInsertHistory(UIElement element)
|
||||
{
|
||||
if (_currentIndex + 1 < _currentStrokeHistory.Count)
|
||||
{
|
||||
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
|
||||
}
|
||||
_currentStrokeHistory.Add(new TimeMachineHistory(element, TimeMachineHistoryType.ElementInsert));
|
||||
_currentIndex = _currentStrokeHistory.Count - 1;
|
||||
NotifyUndoRedoState();
|
||||
}
|
||||
|
||||
public void CommitElementRemoveHistory(UIElement element)
|
||||
{
|
||||
if (_currentIndex + 1 < _currentStrokeHistory.Count)
|
||||
{
|
||||
_currentStrokeHistory.RemoveRange(_currentIndex + 1, (_currentStrokeHistory.Count - 1) - _currentIndex);
|
||||
}
|
||||
var history = new TimeMachineHistory(element, TimeMachineHistoryType.ElementInsert);
|
||||
history.StrokeHasBeenCleared = true; // 标记为已清除
|
||||
_currentStrokeHistory.Add(history);
|
||||
_currentIndex = _currentStrokeHistory.Count - 1;
|
||||
NotifyUndoRedoState();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
-10
@@ -1,7 +1,9 @@
|
||||
using System.Windows.Automation;
|
||||
|
||||
namespace Ink_Canvas.Helpers {
|
||||
internal class WinTabWindowsChecker {
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
internal class WinTabWindowsChecker
|
||||
{
|
||||
/*
|
||||
public static bool IsWindowMinimized(string windowName, bool matchFullName = true) {
|
||||
// 获取Win+Tab预览中的窗口
|
||||
@@ -40,28 +42,37 @@ namespace Ink_Canvas.Helpers {
|
||||
}
|
||||
*/
|
||||
|
||||
public static bool IsWindowExisted(string windowName, bool matchFullName = true) {
|
||||
public static bool IsWindowExisted(string windowName, bool matchFullName = true)
|
||||
{
|
||||
// 获取Win+Tab预览中的窗口
|
||||
AutomationElementCollection windows = AutomationElement.RootElement.FindAll(
|
||||
TreeScope.Children, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Window));
|
||||
|
||||
foreach (AutomationElement window in windows) {
|
||||
foreach (AutomationElement window in windows)
|
||||
{
|
||||
//LogHelper.WriteLogToFile("" + window.Current.Name);
|
||||
|
||||
string windowTitle = window.Current.Name;
|
||||
|
||||
// 如果窗口标题包含 windowName,则进行检查
|
||||
if (!string.IsNullOrEmpty(windowTitle) && windowTitle.Contains(windowName)) {
|
||||
if (matchFullName) {
|
||||
if (windowTitle.Length == windowName.Length) {
|
||||
if (!string.IsNullOrEmpty(windowTitle) && windowTitle.Contains(windowName))
|
||||
{
|
||||
if (matchFullName)
|
||||
{
|
||||
if (windowTitle.Length == windowName.Length)
|
||||
{
|
||||
WindowPattern windowPattern = window.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
|
||||
if (windowPattern != null) {
|
||||
if (windowPattern != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else
|
||||
{
|
||||
WindowPattern windowPattern = window.GetCurrentPattern(WindowPattern.Pattern) as WindowPattern;
|
||||
if (windowPattern != null) {
|
||||
if (windowPattern != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,263 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Windows;
|
||||
using System.Windows.Interop;
|
||||
|
||||
namespace Ink_Canvas.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗口Z-Order管理器,用于管理窗口的层级顺序
|
||||
/// 在无焦点模式下,确保后打开的窗口能够置顶于先打开的窗口
|
||||
/// </summary>
|
||||
public static class WindowZOrderManager
|
||||
{
|
||||
#region Win32 API 声明
|
||||
private const int GWL_EXSTYLE = -20;
|
||||
private const int WS_EX_TOPMOST = 0x00000008;
|
||||
private const int WS_EX_NOACTIVATE = 0x08000000;
|
||||
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
|
||||
private static readonly IntPtr HWND_NOTOPMOST = new IntPtr(-2);
|
||||
private const uint SWP_NOMOVE = 0x0002;
|
||||
private const uint SWP_NOSIZE = 0x0001;
|
||||
private const uint SWP_NOACTIVATE = 0x0010;
|
||||
private const uint SWP_SHOWWINDOW = 0x0040;
|
||||
private const uint SWP_NOOWNERZORDER = 0x0200;
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindow(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsWindowVisible(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern bool IsIconic(IntPtr hWnd);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern IntPtr GetForegroundWindow();
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);
|
||||
|
||||
[DllImport("kernel32.dll")]
|
||||
private static extern uint GetCurrentProcessId();
|
||||
#endregion
|
||||
|
||||
// 窗口层级管理
|
||||
private static readonly List<WindowInfo> _windowStack = new List<WindowInfo>();
|
||||
private static readonly object _lockObject = new object();
|
||||
|
||||
/// <summary>
|
||||
/// 窗口信息类
|
||||
/// </summary>
|
||||
private class WindowInfo
|
||||
{
|
||||
public IntPtr Handle { get; set; }
|
||||
public Window Window { get; set; }
|
||||
public DateTime CreatedTime { get; set; }
|
||||
public bool IsTopmost { get; set; }
|
||||
public bool IsNoFocusMode { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册窗口到Z-Order管理器
|
||||
/// </summary>
|
||||
/// <param name="window">要注册的窗口</param>
|
||||
/// <param name="isTopmost">是否置顶</param>
|
||||
/// <param name="isNoFocusMode">是否无焦点模式</param>
|
||||
public static void RegisterWindow(Window window, bool isTopmost = false, bool isNoFocusMode = false)
|
||||
{
|
||||
if (window == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(window).Handle;
|
||||
if (hwnd == IntPtr.Zero) return;
|
||||
|
||||
// 移除已存在的记录
|
||||
_windowStack.RemoveAll(w => w.Handle == hwnd);
|
||||
|
||||
// 添加新记录
|
||||
var windowInfo = new WindowInfo
|
||||
{
|
||||
Handle = hwnd,
|
||||
Window = window,
|
||||
CreatedTime = DateTime.Now,
|
||||
IsTopmost = isTopmost,
|
||||
IsNoFocusMode = isNoFocusMode
|
||||
};
|
||||
|
||||
_windowStack.Add(windowInfo);
|
||||
|
||||
// 应用Z-Order
|
||||
ApplyZOrder();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从Z-Order管理器中移除窗口
|
||||
/// </summary>
|
||||
/// <param name="window">要移除的窗口</param>
|
||||
public static void UnregisterWindow(Window window)
|
||||
{
|
||||
if (window == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(window).Handle;
|
||||
_windowStack.RemoveAll(w => w.Handle == hwnd);
|
||||
ApplyZOrder();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置窗口为置顶状态
|
||||
/// </summary>
|
||||
/// <param name="window">要置顶的窗口</param>
|
||||
/// <param name="isTopmost">是否置顶</param>
|
||||
public static void SetWindowTopmost(Window window, bool isTopmost)
|
||||
{
|
||||
if (window == null) return;
|
||||
|
||||
lock (_lockObject)
|
||||
{
|
||||
var windowInfo = _windowStack.FirstOrDefault(w => w.Window == window);
|
||||
if (windowInfo != null)
|
||||
{
|
||||
windowInfo.IsTopmost = isTopmost;
|
||||
ApplyZOrder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将窗口移到最顶层
|
||||
/// </summary>
|
||||
/// <param name="window">要移到最顶层的窗口</param>
|
||||
public static void BringToTop(Window window)
|
||||
{
|
||||
if (window == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
var hwnd = new WindowInteropHelper(window).Handle;
|
||||
if (hwnd == IntPtr.Zero) return;
|
||||
|
||||
// 使用更直接的方法:先激活窗口,再置顶
|
||||
window.Activate();
|
||||
window.Focus();
|
||||
|
||||
// 设置窗口为置顶
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER);
|
||||
|
||||
// 确保窗口样式正确
|
||||
int exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST);
|
||||
|
||||
// 再次确保置顶
|
||||
SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"BringToTop失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用Z-Order排序
|
||||
/// </summary>
|
||||
private static void ApplyZOrder()
|
||||
{
|
||||
// 简化逻辑:直接设置所有窗口为置顶,让Windows系统自然处理层级
|
||||
foreach (var windowInfo in _windowStack.ToList())
|
||||
{
|
||||
if (windowInfo.IsTopmost && IsWindow(windowInfo.Handle) && IsWindowVisible(windowInfo.Handle) && !IsIconic(windowInfo.Handle))
|
||||
{
|
||||
// 设置窗口为置顶
|
||||
SetWindowPos(windowInfo.Handle, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER);
|
||||
|
||||
// 确保窗口样式正确
|
||||
int exStyle = GetWindowLong(windowInfo.Handle, GWL_EXSTYLE);
|
||||
if ((exStyle & WS_EX_TOPMOST) == 0)
|
||||
{
|
||||
SetWindowLong(windowInfo.Handle, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否有子窗口在前景
|
||||
/// </summary>
|
||||
/// <returns>如果有子窗口在前景返回true</returns>
|
||||
public static bool HasChildWindowInForeground()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
var foregroundWindow = GetForegroundWindow();
|
||||
if (foregroundWindow == IntPtr.Zero) return false;
|
||||
|
||||
return _windowStack.Any(w => w.Handle == foregroundWindow);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理无效的窗口记录
|
||||
/// </summary>
|
||||
public static void CleanupInvalidWindows()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
_windowStack.RemoveAll(w => !IsWindow(w.Handle) || !IsWindowVisible(w.Handle));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前注册的窗口数量
|
||||
/// </summary>
|
||||
/// <returns>窗口数量</returns>
|
||||
public static int GetWindowCount()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
return _windowStack.Count;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制刷新所有窗口的置顶状态
|
||||
/// </summary>
|
||||
public static void ForceRefreshAllWindows()
|
||||
{
|
||||
lock (_lockObject)
|
||||
{
|
||||
foreach (var windowInfo in _windowStack.ToList())
|
||||
{
|
||||
if (windowInfo.IsTopmost && IsWindow(windowInfo.Handle))
|
||||
{
|
||||
// 强制设置窗口为置顶
|
||||
SetWindowPos(windowInfo.Handle, HWND_TOPMOST, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOOWNERZORDER);
|
||||
|
||||
// 确保窗口样式正确
|
||||
int exStyle = GetWindowLong(windowInfo.Handle, GWL_EXSTYLE);
|
||||
SetWindowLong(windowInfo.Handle, GWL_EXSTYLE, exStyle | WS_EX_TOPMOST);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"Version": "1.0",
|
||||
"LastModified": "2025-01-28T15:30:00",
|
||||
"Hotkeys": [
|
||||
{
|
||||
"Name": "Undo",
|
||||
"Key": "Z",
|
||||
"Modifiers": "Control"
|
||||
},
|
||||
{
|
||||
"Name": "Redo",
|
||||
"Key": "Y",
|
||||
"Modifiers": "Control"
|
||||
},
|
||||
{
|
||||
"Name": "Clear",
|
||||
"Key": "E",
|
||||
"Modifiers": "Control"
|
||||
},
|
||||
{
|
||||
"Name": "Paste",
|
||||
"Key": "V",
|
||||
"Modifiers": "Control"
|
||||
},
|
||||
{
|
||||
"Name": "SelectTool",
|
||||
"Key": "S",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "DrawTool",
|
||||
"Key": "D",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "EraserTool",
|
||||
"Key": "E",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "BlackboardTool",
|
||||
"Key": "B",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "QuitDrawTool",
|
||||
"Key": "Q",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Pen1",
|
||||
"Key": "D1",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Pen2",
|
||||
"Key": "D2",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Pen3",
|
||||
"Key": "D3",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Pen4",
|
||||
"Key": "D4",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Pen5",
|
||||
"Key": "D5",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "DrawLine",
|
||||
"Key": "L",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Screenshot",
|
||||
"Key": "C",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Hide",
|
||||
"Key": "V",
|
||||
"Modifiers": "Alt"
|
||||
},
|
||||
{
|
||||
"Name": "Exit",
|
||||
"Key": "Escape",
|
||||
"Modifiers": "None"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,656 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||
<PropertyGroup>
|
||||
<RuntimeIdentifiers>win;win-x86;win-x64;win-arm64</RuntimeIdentifiers>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>Ink_Canvas</RootNamespace>
|
||||
<AssemblyName>InkCanvasForClass</AssemblyName>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<IsWebBootstrapper>false</IsWebBootstrapper>
|
||||
<PublishUrl>publish\</PublishUrl>
|
||||
<Install>true</Install>
|
||||
<InstallFrom>Disk</InstallFrom>
|
||||
<UpdateEnabled>false</UpdateEnabled>
|
||||
<UpdateMode>Foreground</UpdateMode>
|
||||
<UpdateInterval>7</UpdateInterval>
|
||||
<UpdateIntervalUnits>Days</UpdateIntervalUnits>
|
||||
<UpdatePeriodically>false</UpdatePeriodically>
|
||||
<UpdateRequired>false</UpdateRequired>
|
||||
<MapFileExtensions>true</MapFileExtensions>
|
||||
<ApplicationRevision>2</ApplicationRevision>
|
||||
<ApplicationVersion>2.0.2.%2a</ApplicationVersion>
|
||||
<UseApplicationTrust>false</UseApplicationTrust>
|
||||
<BootstrapperEnabled>false</BootstrapperEnabled>
|
||||
<GenerateAssemblyInfo>False</GenerateAssemblyInfo>
|
||||
<UseWPF>true</UseWPF>
|
||||
<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>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<Prefer32Bit>True</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>embedded</DebugType>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<Prefer32Bit>True</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationIcon>Resources\icc.ico</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='x86 Debug|x86'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||
<Title>InkCanvasForClass</Title>
|
||||
<Version>5.0.4</Version>
|
||||
<Authors>Dubi906w</Authors>
|
||||
<Product>InkCanvasForClass</Product>
|
||||
<Copyright>© Copyright HARKOTEK Studio 2024-now</Copyright>
|
||||
<PackageProjectUrl>https://icc.bliemhax.com</PackageProjectUrl>
|
||||
<FileVersion>bundled</FileVersion>
|
||||
<GeneratePackageOnBuild>False</GeneratePackageOnBuild>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|ARM64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='x86 Debug|ARM64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|ARM64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='x86 Debug|x64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>full</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
|
||||
<OutputPath>bin\$(Platform)\$(Configuration)\</OutputPath>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<LangVersion>7.3</LangVersion>
|
||||
<Prefer32Bit>true</Prefer32Bit>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="IACore">
|
||||
<HintPath>.\IACore.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="IALoader">
|
||||
<HintPath>.\IALoader.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="IAWinFX">
|
||||
<HintPath>.\IAWinFX.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.VisualBasic" />
|
||||
<Reference Include="netstandard" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Xaml" />
|
||||
<Reference Include="UIAutomationClient" />
|
||||
<Reference Include="UIAutomationTypes" />
|
||||
<Reference Include="WindowsBase" />
|
||||
<Reference Include="PresentationCore" />
|
||||
<Reference Include="PresentationFramework" />
|
||||
<Reference Include="System.IO.Compression" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="WindowsFormsIntegration" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.manifest" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Costura.Fody" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Hardcodet.NotifyIcon.Wpf" Version="1.1.0" />
|
||||
<PackageReference Include="iNKORE.UI.WPF.Modern" Version="0.9.27" />
|
||||
<PackageReference Include="MdXaml" Version="1.27.0" />
|
||||
<PackageReference Include="Microsoft.Office.Interop.PowerPoint" Version="15.0.4420.1018" />
|
||||
<PackageReference Include="MicrosoftOfficeCore" Version="15.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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">
|
||||
<Guid>{F935DC20-1CF0-11D0-ADB9-00C04FD58A0B}</Guid>
|
||||
<VersionMajor>1</VersionMajor>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>tlbimp</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="stdole">
|
||||
<Guid>{00020430-0000-0000-C000-000000000046}</Guid>
|
||||
<VersionMajor>2</VersionMajor>
|
||||
<VersionMinor>0</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>primary</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
<COMReference Include="VBIDE">
|
||||
<Guid>{0002E157-0000-0000-C000-000000000046}</Guid>
|
||||
<VersionMajor>5</VersionMajor>
|
||||
<VersionMinor>3</VersionMinor>
|
||||
<Lcid>0</Lcid>
|
||||
<WrapperTool>primary</WrapperTool>
|
||||
<Isolated>False</Isolated>
|
||||
<EmbedInteropTypes>True</EmbedInteropTypes>
|
||||
</COMReference>
|
||||
</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" />
|
||||
<Resource Include="Resources\Cursors\Pen.cur" />
|
||||
<Resource Include="Resources\DeveloperAvatars\aaaaaaccd.jpg" />
|
||||
<Resource Include="Resources\DeveloperAvatars\Alan-CRL.png" />
|
||||
<Resource Include="Resources\DeveloperAvatars\NetheriteBowl.png" />
|
||||
<Resource Include="Resources\DeveloperAvatars\NotYoojun.png" />
|
||||
<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" />
|
||||
<Resource Include="Resources\Icons-png\check-box-background.png" />
|
||||
<Resource Include="Resources\Icons-png\Donview.png" />
|
||||
<Resource Include="Resources\Icons-png\EasiNote3.png" />
|
||||
<Resource Include="Resources\Icons-png\eraser-line.png" />
|
||||
<Resource Include="Resources\Icons-png\eraser-outline.png" />
|
||||
<Resource Include="Resources\Icons-png\HiteLightBoard.png" />
|
||||
<Resource Include="Resources\Icons-png\ica.png" />
|
||||
<Resource Include="Resources\Icons-png\icc-transparent-dark-small.png" />
|
||||
<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" />
|
||||
<Resource Include="Resources\Icons-png\kuandoujiyanhuaji.png" />
|
||||
<Resource Include="Resources\Icons-png\kuanneikuhuaji.png" />
|
||||
<Resource Include="Resources\Icons-png\kuanshounvhuaji.png" />
|
||||
<Resource Include="Resources\Icons-png\MaxHubWhiteboard.png" />
|
||||
<Resource Include="Resources\Icons-png\playCircle.png" />
|
||||
<Resource Include="Resources\Icons-png\redo.png" />
|
||||
<Resource Include="Resources\Icons-png\Seewo2Annotation.png" />
|
||||
<Resource Include="Resources\Icons-png\setting.png" />
|
||||
<Resource Include="Resources\Icons-png\tiebahuaji.png" />
|
||||
<Resource Include="Resources\Icons-png\transparent-grid.png" />
|
||||
<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" />
|
||||
<Resource Include="Resources\Icons-png\twoFingelMove.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\close-circle.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\DeveloperAvatars\ChangSakura.png" />
|
||||
<Resource Include="Resources\DeveloperAvatars\clover-yan.png" />
|
||||
<Resource Include="Resources\DeveloperAvatars\CN-Ironegg.jpg" />
|
||||
<Resource Include="Resources\DeveloperAvatars\jiajiaxd.jpg" />
|
||||
<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" />
|
||||
<Resource Include="Resources\Icons-png\up.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\EasiCamera.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\Desmos.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_cursor_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_lasso_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_settings_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_whiteboard_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_dark_theme_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_dual_screen_span_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_timer_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_delete_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_cursorWITHdelete_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_save_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_camera_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_copy_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_copy_add_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_add_circle_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_flip_horizontal_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_flip_vertical_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_edit_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_folder_open_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_people_money_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_person_money_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_arrow_clockwise_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_calendar_sync_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_dismiss_circle_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_drag_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_shapes_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_power_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_clock_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_keyboard_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_control_button_24_regular.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\ic_fluent_people_24_regular.png" />
|
||||
<Resource Include="Resources\Icons-png\VComYouJiao.png" />
|
||||
<Resource Include="Resources\Icons-png\WenXiang.png" />
|
||||
<Resource Include="Resources\Icons-png\YiYunVisualPresenter.png" />
|
||||
<Resource Include="Resources\Icons-png\YiYunWhiteboard.png" />
|
||||
<Resource Include="Resources\new-icons\chevron-left.png" />
|
||||
<Resource Include="Resources\new-icons\end-slides-show.png">
|
||||
<CopyToOutputDirectory></CopyToOutputDirectory>
|
||||
</Resource>
|
||||
<Resource Include="Resources\new-icons\eye.png" />
|
||||
<Resource Include="Resources\new-icons\hand-move.png" />
|
||||
<Resource Include="Resources\new-icons\highlighter-white.png" />
|
||||
<Resource Include="Resources\new-icons\multi-touch.png" />
|
||||
<Resource Include="Resources\new-icons\osu-lazer-triangles.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\pen-lined.png" />
|
||||
<Resource Include="Resources\new-icons\pen-solid.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\cursor-lined.png" />
|
||||
<Resource Include="Resources\new-icons\cursor-solid.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\eraser-lined.png" />
|
||||
<Resource Include="Resources\new-icons\eraser-solid.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\circle-eraser-lined.png" />
|
||||
<Resource Include="Resources\new-icons\circle-eraser-solid.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\lasso-select-lined.png" />
|
||||
<Resource Include="Resources\new-icons\lasso-select-solid.png" />
|
||||
<Resource Include="Resources\new-icons\pen-white.png" />
|
||||
<Resource Include="Resources\new-icons\rotate.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\trash.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\shapes.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\eye-off.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\grid.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\blackboard.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\cursor-clear.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\DeveloperAvatars\dubi906w.jpg" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\gesture.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\gesture-enabled.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\close-white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\checked-white.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\checked-black.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\pressdown-background.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\arrow.png" />
|
||||
<Resource Include="Resources\Icons-png\geo-icons\dashed-line.png" />
|
||||
<Resource Include="Resources\Icons-png\geo-icons\dotted-line.png" />
|
||||
<Resource Include="Resources\Icons-png\geo-icons\line.png" />
|
||||
<Resource Include="Resources\Icons-png\geo-icons\paralle-lines.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\centered-square.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\centered-circle.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\centered-circle-dashed.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\centered-oval.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\square.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\cylinder.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\geo-icons\cone.png" />
|
||||
</ItemGroup>
|
||||
<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" />
|
||||
<Resource Include="Resources\Icons-png\EasiNote5C.png" />
|
||||
<Resource Include="Resources\Icons-png\SeewoPinco.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\HiteBoard.png" />
|
||||
<Resource Include="Resources\Icons-png\HiteCamera.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\Whiteboard.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\Powerpoint.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\PPTTools.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-png\WPS.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\hatsune-miku1.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\Icons-Fluent\party.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Resource Include="Resources\new-icons\redo.png" />
|
||||
<Resource Include="Resources\new-icons\undo.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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>
|
||||
<ItemGroup>
|
||||
<None Remove="MainWindow.xaml~RF6c3144.TMP" />
|
||||
<None Remove="Resources\Cursors\Cursor.cur" />
|
||||
<None Remove="Resources\Cursors\Pen.cur" />
|
||||
<None Remove="Resources\DeveloperAvatars\aaaaaaccd.jpg" />
|
||||
<None Remove="Resources\DeveloperAvatars\Alan-CRL.png" />
|
||||
<None Remove="Resources\DeveloperAvatars\NetheriteBowl.png" />
|
||||
<None Remove="Resources\DeveloperAvatars\NotYoojun.png" />
|
||||
<None Remove="Resources\DeveloperAvatars\RaspberryKan.jpg" />
|
||||
<None Remove="Resources\DeveloperAvatars\wwei.png" />
|
||||
<None Remove="Resources\DeveloperAvatars\yuwenhui2020.png" />
|
||||
<None Remove="Resources\icc.ico" />
|
||||
<None Remove="Resources\Icons-png\AdmoxBooth.png" />
|
||||
<None Remove="Resources\Icons-png\AdmoxWhiteboard.png" />
|
||||
<None Remove="Resources\Icons-png\Donview.png" />
|
||||
<None Remove="Resources\Icons-png\EasiNote3.png" />
|
||||
<None Remove="Resources\Icons-png\HiteAnnotation.png" />
|
||||
<None Remove="Resources\Icons-png\HiteLightBoard.png" />
|
||||
<None Remove="Resources\Icons-png\ica.png" />
|
||||
<None Remove="Resources\Icons-png\icc-transparent-dark-small.png" />
|
||||
<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" />
|
||||
<None Remove="Resources\Icons-png\kuandogeyuanliangwo.png" />
|
||||
<None Remove="Resources\Icons-png\kuandoujiyanhuaji.png" />
|
||||
<None Remove="Resources\Icons-png\kuanneikuhuaji.png" />
|
||||
<None Remove="Resources\Icons-png\kuanshounvhuaji.png" />
|
||||
<None Remove="Resources\Icons-png\MaxHubWhiteboard.png" />
|
||||
<None Remove="Resources\Icons-png\Seewo2Annotation.png" />
|
||||
<None Remove="Resources\Icons-png\tiebahuaji.png" />
|
||||
<None Remove="Resources\Icons-png\transparent-grid.png" />
|
||||
<None Remove="Resources\Icons-png\VComYouJiao.png" />
|
||||
<None Remove="Resources\Icons-png\WenXiang.png" />
|
||||
<None Remove="Resources\Icons-png\YiYunVisualPresenter.png" />
|
||||
<None Remove="Resources\Icons-png\YiYunWhiteboard.png" />
|
||||
<None Remove="Resources\new-icons\chevron-left.png" />
|
||||
<None Remove="Resources\new-icons\end-slides-show.png" />
|
||||
<None Remove="Resources\new-icons\eye.png" />
|
||||
<None Remove="Resources\new-icons\hand-move.png" />
|
||||
<None Remove="Resources\new-icons\highlighter-white.png" />
|
||||
<None Remove="Resources\new-icons\multi-touch.png" />
|
||||
<None Remove="Resources\new-icons\osu-lazer-triangles.png" />
|
||||
<None Remove="Resources\new-icons\pen-white.png" />
|
||||
<None Remove="Resources\new-icons\rotate.png" />
|
||||
<None Remove="Resources\new-icons\unfold-chevron.png" />
|
||||
<None Remove="Resources\new-icons\zoom.png" />
|
||||
<None Remove="Resources\PresentationExample\bottombar-dark.png" />
|
||||
<None Remove="Resources\PresentationExample\bottombar-white.png" />
|
||||
<None Remove="Resources\PresentationExample\page.jpg" />
|
||||
<None Remove="Resources\PresentationExample\sidebar-dark.png" />
|
||||
<None Remove="Resources\PresentationExample\sidebar-white.png" />
|
||||
<None Remove="Resources\PresentationExample\toolbar.png" />
|
||||
</ItemGroup>
|
||||
<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">
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
internal class ConfigHelper
|
||||
{
|
||||
}
|
||||
}
|
||||
+162
-71
@@ -1,23 +1,23 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using iNKORE.UI.WPF.Modern;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas {
|
||||
public partial class MainWindow : PerformanceTransparentWin {
|
||||
public bool isFloatingBarFolded = false;
|
||||
private bool isFloatingBarChangingHideMode = false;
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public bool isFloatingBarFolded;
|
||||
private bool isFloatingBarChangingHideMode;
|
||||
|
||||
private void CloseWhiteboardImmediately() {
|
||||
private void CloseWhiteboardImmediately()
|
||||
{
|
||||
if (isDisplayingOrHidingBlackboard) return;
|
||||
isDisplayingOrHidingBlackboard = true;
|
||||
HideSubPanelsImmediately();
|
||||
@@ -26,23 +26,29 @@ namespace Ink_Canvas {
|
||||
WaterMarkTime.Visibility = Visibility.Collapsed;
|
||||
WaterMarkDate.Visibility = Visibility.Collapsed;
|
||||
BlackBoardWaterMark.Visibility = Visibility.Collapsed;
|
||||
BtnSwitch_Click(null, null);
|
||||
ICCWaterMarkDark.Visibility = Visibility.Collapsed;
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
||||
BtnSwitch_Click(BtnSwitch, null);
|
||||
BtnExit.Foreground = Brushes.White;
|
||||
ThemeManager.Current.ApplicationTheme = ApplicationTheme.Dark;
|
||||
new Thread(new ThreadStart(() => {
|
||||
new Thread(() =>
|
||||
{
|
||||
Thread.Sleep(200);
|
||||
Application.Current.Dispatcher.Invoke(() => { isDisplayingOrHidingBlackboard = false; });
|
||||
})).Start();
|
||||
}).Start();
|
||||
}
|
||||
|
||||
public async void FoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
public async void FoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
await FoldFloatingBar(sender);
|
||||
}
|
||||
|
||||
public async Task FoldFloatingBar(object sender)
|
||||
public async Task FoldFloatingBar(object sender, bool isAutoFoldCommand = false)
|
||||
{
|
||||
var isShouldRejectAction = false;
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (lastBorderMouseDownObject != null && lastBorderMouseDownObject is Panel)
|
||||
((Panel)lastBorderMouseDownObject).Background = new SolidColorBrush(Colors.Transparent);
|
||||
if (sender == Fold_Icon && lastBorderMouseDownObject != Fold_Icon) isShouldRejectAction = true;
|
||||
@@ -59,7 +65,8 @@ namespace Ink_Canvas {
|
||||
|
||||
if (isFloatingBarChangingHideMode) return;
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
InkCanvasForInkReplay.Visibility = Visibility.Collapsed;
|
||||
InkCanvasGridForInkReplay.Visibility = Visibility.Visible;
|
||||
InkCanvasGridForInkReplay.IsHitTestVisible = true;
|
||||
@@ -71,41 +78,43 @@ namespace Ink_Canvas {
|
||||
isStopInkReplay = true;
|
||||
});
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
isFloatingBarChangingHideMode = true;
|
||||
isFloatingBarFolded = true;
|
||||
if (currentMode != 0) CloseWhiteboardImmediately();
|
||||
if (StackPanelCanvasControls.Visibility == Visibility.Visible)
|
||||
if (foldFloatingBarByUser && inkCanvas.Strokes.Count > 2)
|
||||
ShowNewToast("正在清空墨迹并收纳至屏幕两边,可进入批注模式后通过 “撤销” 功能来恢复原先墨迹。",MW_Toast.ToastType.Informative, 3000);
|
||||
CursorWithDelIcon_Click(null, null);
|
||||
RectangleSelectionHitTestBorder.Visibility = Visibility.Collapsed;
|
||||
ShowNotification("正在清空墨迹并收纳至侧边栏,可进入批注模式后通过【撤销】功能来恢复原先墨迹。");
|
||||
lastBorderMouseDownObject = sender;
|
||||
CursorWithDelIcon_Click(sender, null);
|
||||
});
|
||||
|
||||
await Task.Delay(5);
|
||||
await Task.Delay(300);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
GridForFloatingBarDraging.Visibility = Visibility.Collapsed;
|
||||
ViewboxFloatingBarMarginAnimation(-60);
|
||||
HideSubPanels("cursor");
|
||||
// update tool selection
|
||||
SelectedMode = ICCToolsEnum.CursorMode;
|
||||
ForceUpdateToolSelection(null);
|
||||
SidePannelMarginAnimation(-10);
|
||||
|
||||
});
|
||||
isFloatingBarChangingHideMode = false;
|
||||
}
|
||||
|
||||
private async void LeftUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
if (Settings.Appearance.IsShowQuickPanel == true) {
|
||||
private async void LeftUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (Settings.Appearance.IsShowQuickPanel)
|
||||
{
|
||||
HideRightQuickPanel();
|
||||
LeftUnFoldButtonQuickPanel.Visibility = Visibility.Visible;
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
var marginAnimation = new ThicknessAnimation {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = new Thickness(-50, 0, 0, -150),
|
||||
To = new Thickness(-1, 0, 0, -150)
|
||||
@@ -115,21 +124,27 @@ namespace Ink_Canvas {
|
||||
});
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
LeftUnFoldButtonQuickPanel.Margin = new Thickness(-1, 0, 0, -150);
|
||||
});
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
UnFoldFloatingBar_MouseUp(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void RightUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
if (Settings.Appearance.IsShowQuickPanel == true) {
|
||||
private async void RightUnFoldButtonDisplayQuickPanel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (Settings.Appearance.IsShowQuickPanel)
|
||||
{
|
||||
HideLeftQuickPanel();
|
||||
RightUnFoldButtonQuickPanel.Visibility = Visibility.Visible;
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
var marginAnimation = new ThicknessAnimation {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = new Thickness(0, 0, -50, -150),
|
||||
To = new Thickness(0, 0, -1, -150)
|
||||
@@ -139,19 +154,25 @@ namespace Ink_Canvas {
|
||||
});
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
RightUnFoldButtonQuickPanel.Margin = new Thickness(0, 0, -1, -150);
|
||||
});
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
UnFoldFloatingBar_MouseUp(sender, e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void HideLeftQuickPanel() {
|
||||
if (LeftUnFoldButtonQuickPanel.Visibility == Visibility.Visible) {
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
var marginAnimation = new ThicknessAnimation {
|
||||
private async void HideLeftQuickPanel()
|
||||
{
|
||||
if (LeftUnFoldButtonQuickPanel.Visibility == Visibility.Visible)
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = new Thickness(-1, 0, 0, -150),
|
||||
To = new Thickness(-50, 0, 0, -150)
|
||||
@@ -161,17 +182,22 @@ namespace Ink_Canvas {
|
||||
});
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
LeftUnFoldButtonQuickPanel.Margin = new Thickness(0, 0, -50, -150);
|
||||
LeftUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async void HideRightQuickPanel() {
|
||||
if (RightUnFoldButtonQuickPanel.Visibility == Visibility.Visible) {
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
var marginAnimation = new ThicknessAnimation {
|
||||
private async void HideRightQuickPanel()
|
||||
{
|
||||
if (RightUnFoldButtonQuickPanel.Visibility == Visibility.Visible)
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
From = new Thickness(0, 0, -1, -150),
|
||||
To = new Thickness(0, 0, -50, -150)
|
||||
@@ -181,29 +207,33 @@ namespace Ink_Canvas {
|
||||
});
|
||||
await Task.Delay(100);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
RightUnFoldButtonQuickPanel.Margin = new Thickness(0, 0, -50, -150);
|
||||
RightUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void HideQuickPanel_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
private void HideQuickPanel_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
HideLeftQuickPanel();
|
||||
HideRightQuickPanel();
|
||||
}
|
||||
|
||||
public async void UnFoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e) {
|
||||
public async void UnFoldFloatingBar_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
await UnFoldFloatingBar(sender);
|
||||
}
|
||||
|
||||
public async Task UnFoldFloatingBar(object sender)
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
LeftUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed;
|
||||
RightUnFoldButtonQuickPanel.Visibility = Visibility.Collapsed;
|
||||
});
|
||||
if (sender == null || BorderFloatingBarExitPPTBtn.Visibility == Visibility.Visible)
|
||||
if (sender == null || StackPanelPPTControls.Visibility == Visibility.Visible)
|
||||
unfoldFloatingBarByUser = false;
|
||||
else
|
||||
unfoldFloatingBarByUser = true;
|
||||
@@ -211,47 +241,107 @@ namespace Ink_Canvas {
|
||||
|
||||
if (isFloatingBarChangingHideMode) return;
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
isFloatingBarChangingHideMode = true;
|
||||
isFloatingBarFolded = false;
|
||||
});
|
||||
|
||||
await Task.Delay(0);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
if (BorderFloatingBarExitPPTBtn.Visibility == Visibility.Visible)
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
// 根据设置决定是否自动切换至批注模式
|
||||
if (Settings.Automation.IsAutoEnterAnnotationModeWhenExitFoldMode && currentMode == 0)
|
||||
{
|
||||
// 切换至批注模式
|
||||
PenIcon_Click(null, null);
|
||||
}
|
||||
|
||||
// 只有在PPT放映模式下且页数有效时才显示翻页按钮
|
||||
if (StackPanelPPTControls.Visibility == Visibility.Visible &&
|
||||
BtnPPTSlideShowEnd.Visibility == Visibility.Visible &&
|
||||
PPTManager?.IsInSlideShow == true &&
|
||||
PPTManager?.SlidesCount > 0)
|
||||
{
|
||||
var dops = Settings.PowerPointSettings.PPTButtonsDisplayOption.ToString();
|
||||
var dopsc = dops.ToCharArray();
|
||||
if (dopsc[0] == '2' && isDisplayingOrHidingBlackboard == false) AnimationsHelper.ShowWithFadeIn(LeftBottomPanelForPPTNavigation);
|
||||
if (dopsc[1] == '2' && isDisplayingOrHidingBlackboard == false) AnimationsHelper.ShowWithFadeIn(RightBottomPanelForPPTNavigation);
|
||||
if (dopsc[2] == '2' && isDisplayingOrHidingBlackboard == false) AnimationsHelper.ShowWithFadeIn(LeftSidePanelForPPTNavigation);
|
||||
if (dopsc[3] == '2' && isDisplayingOrHidingBlackboard == false) AnimationsHelper.ShowWithFadeIn(RightSidePanelForPPTNavigation);
|
||||
if (dopsc[0] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(LeftBottomPanelForPPTNavigation);
|
||||
if (dopsc[1] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(RightBottomPanelForPPTNavigation);
|
||||
if (dopsc[2] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(LeftSidePanelForPPTNavigation);
|
||||
if (dopsc[3] == '2' && !isDisplayingOrHidingBlackboard) AnimationsHelper.ShowWithFadeIn(RightSidePanelForPPTNavigation);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果条件不满足,确保隐藏翻页按钮
|
||||
LeftBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
RightBottomPanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
LeftSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
RightSidePanelForPPTNavigation.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (BorderFloatingBarExitPPTBtn.Visibility == Visibility.Visible)
|
||||
ViewboxFloatingBarMarginAnimation(60);
|
||||
else
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
// 新只在屏幕模式下显示浮动栏
|
||||
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
|
||||
ViewboxFloatingBarMarginAnimation(100, true);
|
||||
}
|
||||
SidePannelMarginAnimation(-50, !unfoldFloatingBarByUser);
|
||||
});
|
||||
|
||||
isFloatingBarChangingHideMode = false;
|
||||
await Dispatcher.InvokeAsync(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
// 等待UI完全更新
|
||||
await Task.Delay(100);
|
||||
|
||||
// 获取当前选中的模式并重新设置高光位置
|
||||
string selectedToolMode = GetCurrentSelectedMode();
|
||||
if (!string.IsNullOrEmpty(selectedToolMode))
|
||||
{
|
||||
SetFloatingBarHighlightPosition(selectedToolMode);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"浮动栏展开后重新设置按钮高亮状态失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private async void SidePannelMarginAnimation(int MarginFromEdge, bool isNoAnimation = false) // Possible value: -50, -10
|
||||
{
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
if (MarginFromEdge == -10) LeftSidePanel.Visibility = Visibility.Visible;
|
||||
|
||||
var LeftSidePanelmarginAnimation = new ThicknessAnimation {
|
||||
Duration = isNoAnimation == true ? TimeSpan.FromSeconds(0) : TimeSpan.FromSeconds(0.175),
|
||||
var LeftSidePanelmarginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = isNoAnimation ? TimeSpan.FromSeconds(0) : TimeSpan.FromSeconds(0.175),
|
||||
From = LeftSidePanel.Margin,
|
||||
To = new Thickness(MarginFromEdge, 0, 0, -150)
|
||||
};
|
||||
LeftSidePanelmarginAnimation.EasingFunction = new CubicEase();
|
||||
var RightSidePanelmarginAnimation = new ThicknessAnimation {
|
||||
Duration = isNoAnimation == true ? TimeSpan.FromSeconds(0) : TimeSpan.FromSeconds(0.175),
|
||||
var RightSidePanelmarginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = isNoAnimation ? TimeSpan.FromSeconds(0) : TimeSpan.FromSeconds(0.175),
|
||||
From = RightSidePanel.Margin,
|
||||
To = new Thickness(0, 0, MarginFromEdge, -150)
|
||||
};
|
||||
@@ -262,7 +352,8 @@ namespace Ink_Canvas {
|
||||
|
||||
await Task.Delay(600);
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
LeftSidePanel.Margin = new Thickness(MarginFromEdge, 0, 0, -150);
|
||||
RightSidePanel.Margin = new Thickness(0, 0, MarginFromEdge, -150);
|
||||
|
||||
@@ -271,4 +362,4 @@ namespace Ink_Canvas {
|
||||
isFloatingBarChangingHideMode = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-11
@@ -1,17 +1,22 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using IWshRuntimeLibrary;
|
||||
using IWshRuntimeLibrary;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using Application = System.Windows.Forms.Application;
|
||||
using File = System.IO.File;
|
||||
|
||||
namespace Ink_Canvas {
|
||||
public partial class MainWindow : PerformanceTransparentWin {
|
||||
public static bool StartAutomaticallyCreate(string exeName) {
|
||||
try {
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public static bool StartAutomaticallyCreate(string exeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
var shell = new WshShell();
|
||||
var shortcut = (IWshShortcut)shell.CreateShortcut(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName + ".lnk");
|
||||
//设置快捷方式的目标所在的位置(源程序完整路径)
|
||||
shortcut.TargetPath = System.Windows.Forms.Application.ExecutablePath;
|
||||
shortcut.TargetPath = Application.ExecutablePath;
|
||||
//应用程序的工作目录
|
||||
//当用户没有指定一个具体的目录时,快捷方式的目标应用程序将使用该属性所指定的目录来装载或保存文件。
|
||||
shortcut.WorkingDirectory = Environment.CurrentDirectory;
|
||||
@@ -29,10 +34,12 @@ namespace Ink_Canvas {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool StartAutomaticallyDel(string exeName) {
|
||||
try {
|
||||
System.IO.File.Delete(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName +
|
||||
".lnk");
|
||||
public static bool StartAutomaticallyDel(string exeName)
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(Environment.GetFolderPath(Environment.SpecialFolder.Startup) + "\\" + exeName +
|
||||
".lnk");
|
||||
return true;
|
||||
}
|
||||
catch (Exception) { }
|
||||
@@ -0,0 +1,524 @@
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
var rd3 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/SeewoImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd3);
|
||||
|
||||
var rd4 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/IconImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd4);
|
||||
|
||||
ThemeManager.SetRequestedTheme(window, ElementTheme.Light);
|
||||
|
||||
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);
|
||||
|
||||
var rd3 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/SeewoImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd3);
|
||||
|
||||
var rd4 = new ResourceDictionary
|
||||
{ Source = new Uri("Resources/IconImageDictionary.xaml", UriKind.Relative) };
|
||||
Application.Current.Resources.MergedDictionaries.Add(rd4);
|
||||
|
||||
ThemeManager.SetRequestedTheme(window, ElementTheme.Dark);
|
||||
|
||||
InitializeFloatBarForegroundColor();
|
||||
|
||||
// 刷新快速面板图标
|
||||
RefreshQuickPanelIcons();
|
||||
|
||||
// 刷新墨迹选中栏图标
|
||||
RefreshStrokeSelectionIcons();
|
||||
|
||||
// 刷新图片选中栏图标
|
||||
RefreshImageSelectionIcons();
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private void SystemEvents_UserPreferenceChanged(object sender, UserPreferenceChangedEventArgs e)
|
||||
{
|
||||
switch (Settings.Appearance.Theme)
|
||||
{
|
||||
case 0:
|
||||
SetTheme("Light");
|
||||
break;
|
||||
case 1:
|
||||
SetTheme("Dark");
|
||||
break;
|
||||
case 2:
|
||||
if (IsSystemThemeLight()) SetTheme("Light");
|
||||
else SetTheme("Dark");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsSystemThemeLight()
|
||||
{
|
||||
var light = false;
|
||||
try
|
||||
{
|
||||
var registryKey = Registry.CurrentUser;
|
||||
var themeKey =
|
||||
registryKey.OpenSubKey("software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize");
|
||||
var keyValue = 0;
|
||||
if (themeKey != null) keyValue = (int)themeKey.GetValue("SystemUsesLightTheme");
|
||||
if (keyValue == 1) light = true;
|
||||
}
|
||||
catch { }
|
||||
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,470 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
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][];
|
||||
private bool[] savedMultiTouchModeStates = new bool[101];
|
||||
|
||||
// 保存每页白板图片信息
|
||||
private void SaveStrokes(bool isBackupMain = false)
|
||||
{
|
||||
// 确保画布上的所有UI元素都被保存到时间机器历史记录中
|
||||
var currentHistory = timeMachine.ExportTimeMachineHistory();
|
||||
var elementsInHistory = new HashSet<UIElement>();
|
||||
|
||||
// 收集已经在历史记录中的元素
|
||||
if (currentHistory != null)
|
||||
{
|
||||
foreach (var h in currentHistory)
|
||||
{
|
||||
if (h.CommitType == TimeMachineHistoryType.ElementInsert &&
|
||||
h.InsertedElement != null &&
|
||||
!h.StrokeHasBeenCleared)
|
||||
{
|
||||
elementsInHistory.Add(h.InsertedElement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查画布上的所有UI元素,确保它们都在历史记录中
|
||||
var missingElements = 0;
|
||||
foreach (UIElement child in inkCanvas.Children)
|
||||
{
|
||||
if (child is Image || child is MediaElement)
|
||||
{
|
||||
if (!elementsInHistory.Contains(child))
|
||||
{
|
||||
timeMachine.CommitElementInsertHistory(child);
|
||||
missingElements++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 确保画布上的所有墨迹都被保存
|
||||
if (inkCanvas.Strokes.Count > 0)
|
||||
{
|
||||
// 检查是否有墨迹没有在时间机器历史记录中
|
||||
var strokesInHistory = new HashSet<Stroke>();
|
||||
if (currentHistory != null)
|
||||
{
|
||||
foreach (var h in currentHistory)
|
||||
{
|
||||
if (h.CommitType == TimeMachineHistoryType.UserInput &&
|
||||
h.CurrentStroke != null &&
|
||||
!h.StrokeHasBeenCleared)
|
||||
{
|
||||
foreach (Stroke stroke in h.CurrentStroke)
|
||||
{
|
||||
strokesInHistory.Add(stroke);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 收集没有在历史记录中的墨迹
|
||||
var missingStrokes = new StrokeCollection();
|
||||
foreach (Stroke stroke in inkCanvas.Strokes)
|
||||
{
|
||||
if (!strokesInHistory.Contains(stroke))
|
||||
{
|
||||
missingStrokes.Add(stroke);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingStrokes.Count > 0)
|
||||
{
|
||||
timeMachine.CommitStrokeUserInputHistory(missingStrokes);
|
||||
}
|
||||
}
|
||||
|
||||
if (isBackupMain)
|
||||
{
|
||||
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
|
||||
TimeMachineHistories[0] = timeMachineHistory;
|
||||
// 保存多指书写模式状态
|
||||
savedMultiTouchModeStates[0] = isInMultiTouchMode;
|
||||
timeMachine.ClearStrokeHistory();
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
var timeMachineHistory = timeMachine.ExportTimeMachineHistory();
|
||||
TimeMachineHistories[CurrentWhiteboardIndex] = timeMachineHistory;
|
||||
// 保存多指书写模式状态
|
||||
savedMultiTouchModeStates[CurrentWhiteboardIndex] = isInMultiTouchMode;
|
||||
timeMachine.ClearStrokeHistory();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearStrokes(bool isErasedByCode)
|
||||
{
|
||||
_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)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 隐藏图片选择工具栏
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
var targetIndex = isBackupMain ? 0 : CurrentWhiteboardIndex;
|
||||
|
||||
// 先清空当前画布的墨迹
|
||||
inkCanvas.Strokes.Clear();
|
||||
|
||||
// 清空当前画布的所有内容(墨迹和图片)
|
||||
// 这里必须清除图片,因为页面切换时需要完全重置画布状态
|
||||
inkCanvas.Children.Clear();
|
||||
|
||||
// 如果历史记录为空,直接返回(新页面或空页面)
|
||||
if (TimeMachineHistories[targetIndex] == null)
|
||||
{
|
||||
timeMachine.ClearStrokeHistory();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBackupMain)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
if (BoardBorderLeftPageListView.Visibility == Visibility.Visible)
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderLeftPageListView);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderRightPageListView);
|
||||
RefreshBlackBoardSidePageListView();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderLeftPageListView);
|
||||
await Task.Delay(1);
|
||||
var leftContainer = BlackBoardLeftSidePageListView.ItemContainerGenerator.ContainerFromIndex(
|
||||
CurrentWhiteboardIndex - 1) as ListViewItem;
|
||||
if (leftContainer != null)
|
||||
{
|
||||
ScrollViewToVerticalTop(leftContainer, BlackBoardLeftSidePageListScrollViewer);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (sender == BtnRightPageListWB)
|
||||
{
|
||||
if (BoardBorderRightPageListView.Visibility == Visibility.Visible)
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderRightPageListView);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderLeftPageListView);
|
||||
RefreshBlackBoardSidePageListView();
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardBorderRightPageListView);
|
||||
await Task.Delay(1);
|
||||
var rightContainer = BlackBoardRightSidePageListView.ItemContainerGenerator.ContainerFromIndex(
|
||||
CurrentWhiteboardIndex - 1) as ListViewItem;
|
||||
if (rightContainer != null)
|
||||
{
|
||||
ScrollViewToVerticalTop(rightContainer, BlackBoardRightSidePageListScrollViewer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void BtnWhiteBoardSwitchPrevious_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (CurrentWhiteboardIndex <= 1) return;
|
||||
|
||||
// 隐藏图片选择工具栏
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
SaveStrokes();
|
||||
|
||||
ClearStrokes(true);
|
||||
CurrentWhiteboardIndex--;
|
||||
|
||||
RestoreStrokes();
|
||||
|
||||
UpdateIndexInfoDisplay();
|
||||
}
|
||||
|
||||
private void BtnWhiteBoardSwitchNext_Click(object sender, EventArgs e)
|
||||
{
|
||||
Trace.WriteLine("113223234");
|
||||
|
||||
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
|
||||
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) SaveScreenShot(true);
|
||||
if (CurrentWhiteboardIndex >= WhiteboardTotalCount)
|
||||
{
|
||||
// 在最后一页时,点击"新页面"按钮直接新增一页
|
||||
BtnWhiteBoardAdd_Click(sender, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 隐藏图片选择工具栏
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
SaveStrokes();
|
||||
|
||||
ClearStrokes(true);
|
||||
CurrentWhiteboardIndex++;
|
||||
|
||||
RestoreStrokes();
|
||||
|
||||
UpdateIndexInfoDisplay();
|
||||
}
|
||||
|
||||
private void BtnWhiteBoardAdd_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (WhiteboardTotalCount >= 99) return;
|
||||
if (Settings.Automation.IsAutoSaveStrokesAtClear &&
|
||||
inkCanvas.Strokes.Count > Settings.Automation.MinimumAutomationStrokeNumber) SaveScreenShot(true);
|
||||
|
||||
// 隐藏图片选择工具栏
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
SaveStrokes();
|
||||
ClearStrokes(true);
|
||||
|
||||
WhiteboardTotalCount++;
|
||||
CurrentWhiteboardIndex++;
|
||||
|
||||
if (CurrentWhiteboardIndex != WhiteboardTotalCount)
|
||||
for (var i = WhiteboardTotalCount; i > CurrentWhiteboardIndex; i--)
|
||||
TimeMachineHistories[i] = TimeMachineHistories[i - 1];
|
||||
|
||||
// 确保新页面的历史记录为空
|
||||
TimeMachineHistories[CurrentWhiteboardIndex] = null;
|
||||
|
||||
// 恢复新页面(这会清空画布,因为历史记录为null)
|
||||
RestoreStrokes();
|
||||
|
||||
UpdateIndexInfoDisplay();
|
||||
|
||||
if (WhiteboardTotalCount >= 99) BtnWhiteBoardAdd.IsEnabled = false;
|
||||
|
||||
if (BlackBoardLeftSidePageListView.Visibility == Visibility.Visible)
|
||||
{
|
||||
RefreshBlackBoardSidePageListView();
|
||||
}
|
||||
}
|
||||
|
||||
private void BtnWhiteBoardDelete_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// 隐藏图片选择工具栏
|
||||
if (currentSelectedElement != null)
|
||||
{
|
||||
// 保存当前编辑模式
|
||||
var previousEditingMode = inkCanvas.EditingMode;
|
||||
UnselectElement(currentSelectedElement);
|
||||
// 恢复编辑模式
|
||||
inkCanvas.EditingMode = previousEditingMode;
|
||||
currentSelectedElement = null;
|
||||
}
|
||||
|
||||
ClearStrokes(true);
|
||||
|
||||
if (CurrentWhiteboardIndex != WhiteboardTotalCount)
|
||||
for (var i = CurrentWhiteboardIndex; i <= WhiteboardTotalCount; i++)
|
||||
TimeMachineHistories[i] = TimeMachineHistories[i + 1];
|
||||
else
|
||||
CurrentWhiteboardIndex--;
|
||||
|
||||
WhiteboardTotalCount--;
|
||||
|
||||
RestoreStrokes();
|
||||
|
||||
UpdateIndexInfoDisplay();
|
||||
|
||||
if (WhiteboardTotalCount < 99) BtnWhiteBoardAdd.IsEnabled = true;
|
||||
}
|
||||
|
||||
private void UpdateIndexInfoDisplay()
|
||||
{
|
||||
TextBlockWhiteBoardIndexInfo.Text =
|
||||
$"{CurrentWhiteboardIndex}/{WhiteboardTotalCount}";
|
||||
|
||||
bool isLastPage = CurrentWhiteboardIndex == WhiteboardTotalCount;
|
||||
bool isMaxPage = WhiteboardTotalCount >= 99;
|
||||
|
||||
// 设置按钮文本
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
||||
BtnRightWhiteBoardSwitchNextLabel.Text = isLastPage ? "新页面" : "下一页";
|
||||
|
||||
// 始终允许点击"下一页/新页面"按钮(除非已达最大页数)
|
||||
BtnWhiteBoardSwitchNext.IsEnabled = !isMaxPage;
|
||||
|
||||
// 获取主题颜色资源
|
||||
var iconForegroundBrush = Application.Current.FindResource("IconForeground") as SolidColorBrush;
|
||||
|
||||
// 设置下一页按钮颜色
|
||||
if (iconForegroundBrush != null)
|
||||
{
|
||||
BtnLeftWhiteBoardSwitchNextGeometry.Brush = iconForegroundBrush;
|
||||
BtnRightWhiteBoardSwitchNextGeometry.Brush = iconForegroundBrush;
|
||||
}
|
||||
BtnLeftWhiteBoardSwitchNextLabel.Opacity = 1;
|
||||
BtnRightWhiteBoardSwitchNextLabel.Opacity = 1;
|
||||
|
||||
BtnWhiteBoardSwitchPrevious.IsEnabled = true;
|
||||
|
||||
if (CurrentWhiteboardIndex == 1)
|
||||
{
|
||||
BtnWhiteBoardSwitchPrevious.IsEnabled = false;
|
||||
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;
|
||||
BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 0.5;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (iconForegroundBrush != null)
|
||||
{
|
||||
BtnLeftWhiteBoardSwitchPreviousGeometry.Brush = iconForegroundBrush;
|
||||
BtnRightWhiteBoardSwitchPreviousGeometry.Brush = iconForegroundBrush;
|
||||
}
|
||||
BtnLeftWhiteBoardSwitchPreviousLabel.Opacity = 1;
|
||||
BtnRightWhiteBoardSwitchPreviousLabel.Opacity = 1;
|
||||
}
|
||||
|
||||
BtnWhiteBoardDelete.IsEnabled = WhiteboardTotalCount != 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,860 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private void BoardChangeBackgroundColorBtn_MouseUp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (!isLoaded) return;
|
||||
|
||||
// 创建背景选项面板(如果不存在)
|
||||
if (BackgroundPalette == null)
|
||||
{
|
||||
CreateBackgroundPalette();
|
||||
}
|
||||
|
||||
// 显示或隐藏背景选项面板
|
||||
if (BackgroundPalette != null)
|
||||
{
|
||||
if (BackgroundPalette.Visibility == Visibility.Visible)
|
||||
{
|
||||
// 如果面板已经显示,则隐藏它
|
||||
AnimationsHelper.HideWithSlideAndFade(BackgroundPalette);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 隐藏其他可能显示的面板
|
||||
AnimationsHelper.HideWithSlideAndFade(EraserSizePanel);
|
||||
AnimationsHelper.HideWithSlideAndFade(BorderTools);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderTools);
|
||||
AnimationsHelper.HideWithSlideAndFade(PenPalette);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardPenPalette);
|
||||
AnimationsHelper.HideWithSlideAndFade(BorderDrawShape);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardBorderDrawShape);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardEraserSizePanel);
|
||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardImageOptionsPanel);
|
||||
|
||||
// 显示背景选项面板
|
||||
AnimationsHelper.ShowWithSlideFromBottomAndFade(BackgroundPalette);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 原有的背景切换代码
|
||||
Settings.Canvas.UsingWhiteboard = !Settings.Canvas.UsingWhiteboard;
|
||||
SaveSettingsToFile();
|
||||
if (Settings.Canvas.UsingWhiteboard)
|
||||
{
|
||||
if (inkColor == 5) lastBoardInkColor = 0;
|
||||
ICCWaterMarkDark.Visibility = Visibility.Visible;
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 设置为白板默认背景色
|
||||
Color defaultWhiteboardColor = Color.FromRgb(255, 255, 255);
|
||||
|
||||
if (currentMode == 1) // 白板模式
|
||||
{
|
||||
// 设置背景为默认白板背景色
|
||||
GridBackgroundCover.Background = new SolidColorBrush(defaultWhiteboardColor);
|
||||
|
||||
// 更新RGB滑块的值为默认白板背景色
|
||||
if (BackgroundPalette != null && BackgroundPalette.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateRGBSliders(defaultWhiteboardColor);
|
||||
}
|
||||
|
||||
// 更新自定义背景色为默认白板背景色
|
||||
CustomBackgroundColor = defaultWhiteboardColor;
|
||||
|
||||
// 保存到设置
|
||||
string colorHex = $"#{defaultWhiteboardColor.R:X2}{defaultWhiteboardColor.G:X2}{defaultWhiteboardColor.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 设置墨迹颜色为黑色
|
||||
CheckLastColor(0);
|
||||
forceEraser = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (inkColor == 0) lastBoardInkColor = 5;
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Visible;
|
||||
ICCWaterMarkDark.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 设置为黑板默认背景色
|
||||
Color defaultBlackboardColor = Color.FromRgb(22, 41, 36);
|
||||
|
||||
if (currentMode == 1) // 黑板模式
|
||||
{
|
||||
// 设置背景为默认黑板背景色
|
||||
GridBackgroundCover.Background = new SolidColorBrush(defaultBlackboardColor);
|
||||
|
||||
// 更新RGB滑块的值为默认黑板背景色
|
||||
if (BackgroundPalette != null && BackgroundPalette.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateRGBSliders(defaultBlackboardColor);
|
||||
}
|
||||
|
||||
// 更新自定义背景色为默认黑板背景色
|
||||
CustomBackgroundColor = defaultBlackboardColor;
|
||||
|
||||
// 保存到设置
|
||||
string colorHex = $"#{defaultBlackboardColor.R:X2}{defaultBlackboardColor.G:X2}{defaultBlackboardColor.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 设置墨迹颜色为白色
|
||||
CheckLastColor(5);
|
||||
forceEraser = false;
|
||||
}
|
||||
|
||||
CheckColorTheme(true);
|
||||
}
|
||||
|
||||
// 创建背景选项面板
|
||||
private void CreateBackgroundPalette()
|
||||
{
|
||||
// 确保加载自定义背景色
|
||||
LoadCustomBackgroundColor();
|
||||
|
||||
// 创建一个类似于PenPalette的面板
|
||||
BackgroundPalette = new Border
|
||||
{
|
||||
Name = "BackgroundPalette",
|
||||
Visibility = Visibility.Collapsed,
|
||||
Background = (SolidColorBrush)Application.Current.FindResource("SettingsPageBackground"),
|
||||
Opacity = 1,
|
||||
BorderBrush = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)),
|
||||
BorderThickness = new Thickness(1),
|
||||
CornerRadius = new CornerRadius(8),
|
||||
Width = 300,
|
||||
MaxHeight = 400
|
||||
};
|
||||
|
||||
// 确保面板显示在顶层
|
||||
Panel.SetZIndex(BackgroundPalette, 1000);
|
||||
|
||||
// 创建面板内容
|
||||
var stackPanel = new StackPanel();
|
||||
|
||||
// 创建标题栏
|
||||
var titleBorder = new Border
|
||||
{
|
||||
BorderBrush = new SolidColorBrush(Color.FromRgb(0x1e, 0x3a, 0x8a)),
|
||||
Height = 32,
|
||||
BorderThickness = new Thickness(0, 0, 0, 1),
|
||||
CornerRadius = new CornerRadius(8, 8, 0, 0),
|
||||
Background = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)),
|
||||
Margin = new Thickness(-1, -1, -1, 0),
|
||||
Padding = new Thickness(1, 1, 1, 0)
|
||||
};
|
||||
|
||||
var titleCanvas = new System.Windows.Controls.Canvas { Height = 24, ClipToBounds = true };
|
||||
var titleText = new TextBlock
|
||||
{
|
||||
Text = "背景设置",
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("FloatBarForeground"),
|
||||
Padding = new Thickness(0, 5, 0, 0),
|
||||
FontSize = 11,
|
||||
FontWeight = FontWeights.Bold,
|
||||
TextAlignment = TextAlignment.Center
|
||||
};
|
||||
System.Windows.Controls.Canvas.SetLeft(titleText, 8);
|
||||
titleCanvas.Children.Add(titleText);
|
||||
|
||||
// 关闭按钮
|
||||
var closeImage = new Image
|
||||
{
|
||||
Source = new BitmapImage(new Uri("/Resources/new-icons/close-white.png", UriKind.Relative)),
|
||||
Height = 16,
|
||||
Width = 16
|
||||
};
|
||||
RenderOptions.SetBitmapScalingMode(closeImage, BitmapScalingMode.HighQuality);
|
||||
closeImage.MouseUp += CloseBordertools_MouseUp;
|
||||
System.Windows.Controls.Canvas.SetRight(closeImage, 8);
|
||||
System.Windows.Controls.Canvas.SetTop(closeImage, 4);
|
||||
titleCanvas.Children.Add(closeImage);
|
||||
|
||||
titleBorder.Child = titleCanvas;
|
||||
stackPanel.Children.Add(titleBorder);
|
||||
|
||||
// 创建背景选项内容区域
|
||||
var contentPanel = new StackPanel { Margin = new Thickness(8) };
|
||||
|
||||
// 黑板/白板选择
|
||||
var modeTitle = new TextBlock
|
||||
{
|
||||
Text = "白板模式",
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground"),
|
||||
FontSize = 10,
|
||||
FontWeight = FontWeights.Bold,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Margin = new Thickness(0, 4, 0, 8)
|
||||
};
|
||||
contentPanel.Children.Add(modeTitle);
|
||||
|
||||
var modePanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Center };
|
||||
|
||||
// 白板按钮
|
||||
var whiteboardButton = new Border
|
||||
{
|
||||
Width = 60,
|
||||
Height = 30,
|
||||
Background = Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) : new SolidColorBrush(Colors.LightGray),
|
||||
CornerRadius = new CornerRadius(4),
|
||||
Margin = new Thickness(0, 0, 8, 0)
|
||||
};
|
||||
var whiteboardText = new TextBlock
|
||||
{
|
||||
Text = "白板",
|
||||
Foreground = Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.Black),
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
whiteboardButton.Child = whiteboardText;
|
||||
whiteboardButton.MouseUp += (s, args) =>
|
||||
{
|
||||
Settings.Canvas.UsingWhiteboard = true;
|
||||
SaveSettingsToFile();
|
||||
ICCWaterMarkDark.Visibility = Visibility.Visible;
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 设置为白板默认背景色
|
||||
Color defaultWhiteboardColor = Color.FromRgb(255, 255, 255);
|
||||
|
||||
if (currentMode == 1) // 白板模式
|
||||
{
|
||||
// 设置背景为默认白板背景色
|
||||
GridBackgroundCover.Background = new SolidColorBrush(defaultWhiteboardColor);
|
||||
|
||||
// 更新RGB滑块的值为默认白板背景色
|
||||
UpdateRGBSliders(defaultWhiteboardColor);
|
||||
|
||||
// 更新自定义背景色为默认白板背景色
|
||||
CustomBackgroundColor = defaultWhiteboardColor;
|
||||
|
||||
// 保存到设置
|
||||
string colorHex = $"#{defaultWhiteboardColor.R:X2}{defaultWhiteboardColor.G:X2}{defaultWhiteboardColor.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 设置墨迹颜色为黑色
|
||||
CheckLastColor(0);
|
||||
forceEraser = false;
|
||||
|
||||
CheckColorTheme(true);
|
||||
UpdateBackgroundButtonsState();
|
||||
};
|
||||
modePanel.Children.Add(whiteboardButton);
|
||||
|
||||
// 黑板按钮
|
||||
var blackboardButton = new Border
|
||||
{
|
||||
Width = 60,
|
||||
Height = 30,
|
||||
Background = !Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) : new SolidColorBrush(Colors.LightGray),
|
||||
CornerRadius = new CornerRadius(4)
|
||||
};
|
||||
var blackboardText = new TextBlock
|
||||
{
|
||||
Text = "黑板",
|
||||
Foreground = !Settings.Canvas.UsingWhiteboard ? new SolidColorBrush(Colors.White) : new SolidColorBrush(Colors.Black),
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
blackboardButton.Child = blackboardText;
|
||||
blackboardButton.MouseUp += (s, args) =>
|
||||
{
|
||||
Settings.Canvas.UsingWhiteboard = false;
|
||||
SaveSettingsToFile();
|
||||
ICCWaterMarkWhite.Visibility = Visibility.Visible;
|
||||
ICCWaterMarkDark.Visibility = Visibility.Collapsed;
|
||||
|
||||
// 设置为黑板默认背景色
|
||||
Color defaultBlackboardColor = Color.FromRgb(22, 41, 36);
|
||||
|
||||
if (currentMode == 1) // 黑板模式
|
||||
{
|
||||
// 设置背景为默认黑板背景色
|
||||
GridBackgroundCover.Background = new SolidColorBrush(defaultBlackboardColor);
|
||||
|
||||
// 更新RGB滑块的值为默认黑板背景色
|
||||
UpdateRGBSliders(defaultBlackboardColor);
|
||||
|
||||
// 更新自定义背景色为默认黑板背景色
|
||||
CustomBackgroundColor = defaultBlackboardColor;
|
||||
|
||||
// 保存到设置
|
||||
string colorHex = $"#{defaultBlackboardColor.R:X2}{defaultBlackboardColor.G:X2}{defaultBlackboardColor.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
SaveSettingsToFile();
|
||||
}
|
||||
|
||||
// 设置墨迹颜色为白色
|
||||
CheckLastColor(5);
|
||||
forceEraser = false;
|
||||
|
||||
CheckColorTheme(true);
|
||||
UpdateBackgroundButtonsState();
|
||||
};
|
||||
modePanel.Children.Add(blackboardButton);
|
||||
|
||||
contentPanel.Children.Add(modePanel);
|
||||
|
||||
// 添加一条分隔线
|
||||
var separator = new Border
|
||||
{
|
||||
Height = 1,
|
||||
Background = (SolidColorBrush)Application.Current.FindResource("SettingsPageBorderBrush"),
|
||||
Margin = new Thickness(0, 12, 0, 12)
|
||||
};
|
||||
contentPanel.Children.Add(separator);
|
||||
|
||||
// 添加RGB颜色选择器部分
|
||||
var colorTitle = new TextBlock
|
||||
{
|
||||
Text = "背景颜色",
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground"),
|
||||
FontSize = 10,
|
||||
FontWeight = FontWeights.Bold,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Margin = new Thickness(0, 4, 0, 8)
|
||||
};
|
||||
contentPanel.Children.Add(colorTitle);
|
||||
|
||||
// 创建颜色预览
|
||||
Border colorPreview = new Border
|
||||
{
|
||||
Width = 100,
|
||||
Height = 40,
|
||||
BorderThickness = new Thickness(1),
|
||||
BorderBrush = (SolidColorBrush)Application.Current.FindResource("SettingsPageBorderBrush"),
|
||||
Background = new SolidColorBrush(Colors.White),
|
||||
CornerRadius = new CornerRadius(4),
|
||||
Margin = new Thickness(0, 0, 0, 10),
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
contentPanel.Children.Add(colorPreview);
|
||||
|
||||
// 获取当前背景颜色
|
||||
Color currentBackgroundColor;
|
||||
if (currentMode == 1) // 白板或黑板模式
|
||||
{
|
||||
if (GridBackgroundCover.Background is SolidColorBrush brush)
|
||||
{
|
||||
currentBackgroundColor = brush.Color;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 默认颜色
|
||||
currentBackgroundColor = Settings.Canvas.UsingWhiteboard ?
|
||||
Color.FromRgb(234, 235, 237) : // 白板默认颜色
|
||||
Color.FromRgb(22, 41, 36); // 黑板默认颜色
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 默认白色
|
||||
currentBackgroundColor = Colors.White;
|
||||
}
|
||||
|
||||
// 更新颜色预览
|
||||
colorPreview.Background = new SolidColorBrush(currentBackgroundColor);
|
||||
|
||||
// 先创建所有滑块控件
|
||||
// R滑块和文本框
|
||||
var rPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) };
|
||||
var rLabel = new TextBlock { Text = "R:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
||||
var rSlider = new Slider
|
||||
{
|
||||
Minimum = 0,
|
||||
Maximum = 255,
|
||||
Value = currentBackgroundColor.R,
|
||||
Width = 150,
|
||||
Margin = new Thickness(5, 0, 5, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
var rValueText = new TextBlock
|
||||
{
|
||||
Text = currentBackgroundColor.R.ToString(),
|
||||
Width = 30,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextAlignment = TextAlignment.Right,
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground")
|
||||
};
|
||||
|
||||
// G滑块和文本框
|
||||
var gPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) };
|
||||
var gLabel = new TextBlock { Text = "G:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
||||
var gSlider = new Slider
|
||||
{
|
||||
Minimum = 0,
|
||||
Maximum = 255,
|
||||
Value = currentBackgroundColor.G,
|
||||
Width = 150,
|
||||
Margin = new Thickness(5, 0, 5, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
var gValueText = new TextBlock
|
||||
{
|
||||
Text = currentBackgroundColor.G.ToString(),
|
||||
Width = 30,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextAlignment = TextAlignment.Right,
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground")
|
||||
};
|
||||
|
||||
// B滑块和文本框
|
||||
var bPanel = new StackPanel { Orientation = Orientation.Horizontal, Margin = new Thickness(10, 0, 10, 5) };
|
||||
var bLabel = new TextBlock { Text = "B:", Width = 20, VerticalAlignment = VerticalAlignment.Center, Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground") };
|
||||
var bSlider = new Slider
|
||||
{
|
||||
Minimum = 0,
|
||||
Maximum = 255,
|
||||
Value = currentBackgroundColor.B,
|
||||
Width = 150,
|
||||
Margin = new Thickness(5, 0, 5, 0),
|
||||
VerticalAlignment = VerticalAlignment.Center
|
||||
};
|
||||
var bValueText = new TextBlock
|
||||
{
|
||||
Text = currentBackgroundColor.B.ToString(),
|
||||
Width = 30,
|
||||
VerticalAlignment = VerticalAlignment.Center,
|
||||
TextAlignment = TextAlignment.Right,
|
||||
Foreground = (SolidColorBrush)Application.Current.FindResource("TextForeground")
|
||||
};
|
||||
|
||||
// 现在添加事件处理程序
|
||||
rSlider.ValueChanged += (s, e) =>
|
||||
{
|
||||
int value = (int)e.NewValue;
|
||||
rValueText.Text = value.ToString();
|
||||
UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider);
|
||||
};
|
||||
|
||||
gSlider.ValueChanged += (s, e) =>
|
||||
{
|
||||
int value = (int)e.NewValue;
|
||||
gValueText.Text = value.ToString();
|
||||
UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider);
|
||||
};
|
||||
|
||||
bSlider.ValueChanged += (s, e) =>
|
||||
{
|
||||
int value = (int)e.NewValue;
|
||||
bValueText.Text = value.ToString();
|
||||
UpdateColorPreview(colorPreview, rSlider, gSlider, bSlider);
|
||||
};
|
||||
|
||||
// 添加控件到面板
|
||||
rPanel.Children.Add(rLabel);
|
||||
rPanel.Children.Add(rSlider);
|
||||
rPanel.Children.Add(rValueText);
|
||||
contentPanel.Children.Add(rPanel);
|
||||
|
||||
gPanel.Children.Add(gLabel);
|
||||
gPanel.Children.Add(gSlider);
|
||||
gPanel.Children.Add(gValueText);
|
||||
contentPanel.Children.Add(gPanel);
|
||||
|
||||
bPanel.Children.Add(bLabel);
|
||||
bPanel.Children.Add(bSlider);
|
||||
bPanel.Children.Add(bValueText);
|
||||
contentPanel.Children.Add(bPanel);
|
||||
|
||||
// 应用按钮
|
||||
var applyButton = new Button
|
||||
{
|
||||
Content = "应用颜色",
|
||||
Margin = new Thickness(0, 10, 0, 0),
|
||||
Padding = new Thickness(10, 5, 10, 5),
|
||||
Background = new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)),
|
||||
Foreground = new SolidColorBrush(Colors.White),
|
||||
BorderThickness = new Thickness(0),
|
||||
HorizontalAlignment = HorizontalAlignment.Center
|
||||
};
|
||||
|
||||
applyButton.Click += (s, e) =>
|
||||
{
|
||||
Color selectedColor = Color.FromRgb(
|
||||
(byte)rSlider.Value,
|
||||
(byte)gSlider.Value,
|
||||
(byte)bSlider.Value
|
||||
);
|
||||
ApplyCustomBackgroundColor(selectedColor);
|
||||
};
|
||||
|
||||
contentPanel.Children.Add(applyButton);
|
||||
|
||||
stackPanel.Children.Add(contentPanel);
|
||||
|
||||
// 将面板添加到父容器
|
||||
BackgroundPalette.Child = stackPanel;
|
||||
|
||||
// 获取主窗口中的根网格,确保面板添加到顶层
|
||||
Grid mainGrid = FindName("Main_Grid") as Grid;
|
||||
if (mainGrid != null)
|
||||
{
|
||||
// 删除可能已存在的BackgroundPalette
|
||||
foreach (UIElement element in mainGrid.Children)
|
||||
{
|
||||
if (element is Border border && border.Name == "BackgroundPalette")
|
||||
{
|
||||
mainGrid.Children.Remove(border);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 重新定位面板
|
||||
BackgroundPalette.HorizontalAlignment = HorizontalAlignment.Center;
|
||||
BackgroundPalette.VerticalAlignment = VerticalAlignment.Center;
|
||||
BackgroundPalette.Margin = new Thickness(0, 0, 0, 0);
|
||||
|
||||
// 添加到主网格
|
||||
mainGrid.Children.Add(BackgroundPalette);
|
||||
|
||||
// 设置面板位置
|
||||
var clickElement = FindName("BoardChangeBackgroundColorBtn") as FrameworkElement;
|
||||
if (clickElement != null)
|
||||
{
|
||||
Point position = clickElement.TranslatePoint(new Point(0, 0), mainGrid);
|
||||
BackgroundPalette.Margin = new Thickness(
|
||||
position.X - 150,
|
||||
position.Y + clickElement.ActualHeight + 5,
|
||||
0, 0);
|
||||
BackgroundPalette.HorizontalAlignment = HorizontalAlignment.Left;
|
||||
BackgroundPalette.VerticalAlignment = VerticalAlignment.Top;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新背景按钮状态
|
||||
private void UpdateBackgroundButtonsState()
|
||||
{
|
||||
if (BackgroundPalette != null && BackgroundPalette.Child is StackPanel stackPanel)
|
||||
{
|
||||
if (stackPanel.Children.Count > 1 && stackPanel.Children[1] is StackPanel contentPanel)
|
||||
{
|
||||
if (contentPanel.Children.Count > 1 && contentPanel.Children[1] is StackPanel modePanel)
|
||||
{
|
||||
if (modePanel.Children.Count > 1)
|
||||
{
|
||||
var whiteboardButton = modePanel.Children[0] as Border;
|
||||
var blackboardButton = modePanel.Children[1] as Border;
|
||||
|
||||
if (whiteboardButton != null && whiteboardButton.Child is TextBlock whiteboardText)
|
||||
{
|
||||
whiteboardButton.Background = Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) :
|
||||
new SolidColorBrush(Colors.LightGray);
|
||||
whiteboardText.Foreground = Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Colors.White) :
|
||||
new SolidColorBrush(Colors.Black);
|
||||
}
|
||||
|
||||
if (blackboardButton != null && blackboardButton.Child is TextBlock blackboardText)
|
||||
{
|
||||
blackboardButton.Background = !Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Color.FromRgb(0x25, 0x63, 0xeb)) :
|
||||
new SolidColorBrush(Colors.LightGray);
|
||||
blackboardText.Foreground = !Settings.Canvas.UsingWhiteboard ?
|
||||
new SolidColorBrush(Colors.White) :
|
||||
new SolidColorBrush(Colors.Black);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加成员变量保存背景面板引用
|
||||
private Border BackgroundPalette { get; set; }
|
||||
|
||||
// 添加成员变量保存当前自定义背景色
|
||||
private Color? CustomBackgroundColor { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新颜色预览框的颜色
|
||||
/// </summary>
|
||||
private void UpdateColorPreview(Border colorPreview, Slider rSlider, Slider gSlider, Slider bSlider)
|
||||
{
|
||||
Color previewColor = Color.FromRgb(
|
||||
(byte)rSlider.Value,
|
||||
(byte)gSlider.Value,
|
||||
(byte)bSlider.Value
|
||||
);
|
||||
colorPreview.Background = new SolidColorBrush(previewColor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用自定义背景颜色
|
||||
/// </summary>
|
||||
private void ApplyCustomBackgroundColor(Color color)
|
||||
{
|
||||
// 保存当前选择的颜色
|
||||
CustomBackgroundColor = color;
|
||||
|
||||
// 将颜色转换为十六进制字符串并保存到设置中
|
||||
string colorHex = $"#{color.R:X2}{color.G:X2}{color.B:X2}";
|
||||
Settings.Canvas.CustomBackgroundColor = colorHex;
|
||||
|
||||
// 只在白板或黑板模式下应用自定义背景色
|
||||
if (currentMode == 1) // 白板或黑板模式
|
||||
{
|
||||
// 设置白板/黑板模式下的背景
|
||||
GridBackgroundCover.Background = new SolidColorBrush(color);
|
||||
}
|
||||
|
||||
// 保存设置
|
||||
SaveSettingsToFile();
|
||||
|
||||
// 立即更新界面
|
||||
if (BackgroundPalette != null)
|
||||
{
|
||||
UpdateBackgroundButtonsState();
|
||||
UpdateRGBSliders(color); // 更新RGB滑块的值
|
||||
}
|
||||
|
||||
// 显示提示信息
|
||||
ShowNotification($"已应用自定义背景色: {colorHex}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从设置中加载自定义背景色
|
||||
/// </summary>
|
||||
private void LoadCustomBackgroundColor()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Settings.Canvas.CustomBackgroundColor))
|
||||
{
|
||||
try
|
||||
{
|
||||
// 解析颜色字符串
|
||||
string colorHex = Settings.Canvas.CustomBackgroundColor;
|
||||
if (colorHex.StartsWith("#") && colorHex.Length == 7) // #RRGGBB 格式
|
||||
{
|
||||
byte r = Convert.ToByte(colorHex.Substring(1, 2), 16);
|
||||
byte g = Convert.ToByte(colorHex.Substring(3, 2), 16);
|
||||
byte b = Convert.ToByte(colorHex.Substring(5, 2), 16);
|
||||
|
||||
// 保存到内存中
|
||||
CustomBackgroundColor = Color.FromRgb(r, g, b);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// 解析失败,根据当前模式设置默认颜色
|
||||
if (!Settings.Canvas.UsingWhiteboard)
|
||||
{
|
||||
// 黑板模式默认颜色
|
||||
CustomBackgroundColor = Color.FromRgb(22, 41, 36);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 白板模式默认颜色
|
||||
CustomBackgroundColor = Color.FromRgb(234, 235, 237);
|
||||
}
|
||||
|
||||
// 可以在这里记录日志
|
||||
Console.WriteLine($"解析自定义背景色失败: {ex.Message}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果没有设置自定义背景色,根据当前模式设置默认颜色
|
||||
if (!Settings.Canvas.UsingWhiteboard)
|
||||
{
|
||||
// 黑板模式默认颜色
|
||||
CustomBackgroundColor = Color.FromRgb(22, 41, 36);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 白板模式默认颜色
|
||||
CustomBackgroundColor = Color.FromRgb(234, 235, 237);
|
||||
}
|
||||
}
|
||||
|
||||
// 只在白板或黑板模式下应用自定义背景色
|
||||
if (currentMode == 1 && CustomBackgroundColor.HasValue) // 白板或黑板模式
|
||||
{
|
||||
// 设置白板/黑板模式下的背景
|
||||
GridBackgroundCover.Background = new SolidColorBrush(CustomBackgroundColor.Value);
|
||||
|
||||
// 更新RGB滑块的值(如果调色板已经创建)
|
||||
if (BackgroundPalette != null && BackgroundPalette.Visibility == Visibility.Visible)
|
||||
{
|
||||
UpdateRGBSliders(CustomBackgroundColor.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void BoardLassoIcon_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
forceEraser = false;
|
||||
forcePointEraser = false;
|
||||
drawingShapeMode = 0;
|
||||
// 使用集中化的工具模式切换方法
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
SetCursorBasedOnEditingMode(inkCanvas);
|
||||
}
|
||||
|
||||
private void BoardEraserIconByStrokes_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
//if (BoardEraserByStrokes.Background.ToString() == "#FF679CF4") {
|
||||
// AnimationsHelper.ShowWithSlideFromBottomAndFade(BoardDeleteIcon);
|
||||
//}
|
||||
//else {
|
||||
// 禁用高级橡皮擦系统
|
||||
DisableEraserOverlay();
|
||||
|
||||
forceEraser = true;
|
||||
forcePointEraser = false;
|
||||
|
||||
inkCanvas.EraserShape = new EllipseStylusShape(5, 5);
|
||||
// 使用集中化的工具模式切换方法
|
||||
SetCurrentToolMode(InkCanvasEditingMode.EraseByStroke);
|
||||
drawingShapeMode = 0;
|
||||
|
||||
penType = 0;
|
||||
drawingAttributes.IsHighlighter = false;
|
||||
drawingAttributes.StylusTip = StylusTip.Ellipse;
|
||||
|
||||
inkCanvas_EditingModeChanged(inkCanvas, null);
|
||||
CancelSingleFingerDragMode();
|
||||
|
||||
HideSubPanels("eraserByStrokes");
|
||||
//}
|
||||
}
|
||||
|
||||
private void BoardSymbolIconDelete_MouseUp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PenIcon_Click(null, null);
|
||||
SymbolIconDelete_MouseUp(null, null);
|
||||
|
||||
// 根据设置决定是否清空图片
|
||||
if (Settings.Canvas.ClearCanvasAlsoClearImages)
|
||||
{
|
||||
// 如果设置为清空图片,则直接清空所有子元素
|
||||
Debug.WriteLine("BoardSymbolIconDelete: Clearing all children including images");
|
||||
inkCanvas.Children.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 保存非笔画元素(如图片)
|
||||
Debug.WriteLine("BoardSymbolIconDelete: Preserving non-stroke elements (images)");
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
Debug.WriteLine($"BoardSymbolIconDelete: Preserved elements count: {preservedElements.Count}");
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
Debug.WriteLine($"BoardSymbolIconDelete: inkCanvas.Children.Count after restore: {inkCanvas.Children.Count}");
|
||||
}
|
||||
}
|
||||
private void BoardSymbolIconDeleteInkAndHistories_MouseUp(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PenIcon_Click(null, null);
|
||||
SymbolIconDelete_MouseUp(null, null);
|
||||
if (!Settings.Canvas.ClearCanvasAndClearTimeMachine) timeMachine.ClearStrokeHistory();
|
||||
|
||||
// 根据设置决定是否清空图片
|
||||
if (Settings.Canvas.ClearCanvasAlsoClearImages)
|
||||
{
|
||||
// 如果设置为清空图片,则直接清空所有子元素
|
||||
Debug.WriteLine("BoardSymbolIconDeleteInkAndHistories: Clearing all children including images");
|
||||
inkCanvas.Children.Clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
// 保存非笔画元素(如图片)
|
||||
Debug.WriteLine("BoardSymbolIconDeleteInkAndHistories: Preserving non-stroke elements (images)");
|
||||
var preservedElements = PreserveNonStrokeElements();
|
||||
Debug.WriteLine($"BoardSymbolIconDeleteInkAndHistories: Preserved elements count: {preservedElements.Count}");
|
||||
inkCanvas.Children.Clear();
|
||||
// 恢复非笔画元素
|
||||
RestoreNonStrokeElements(preservedElements);
|
||||
Debug.WriteLine($"BoardSymbolIconDeleteInkAndHistories: inkCanvas.Children.Count after restore: {inkCanvas.Children.Count}");
|
||||
}
|
||||
}
|
||||
|
||||
private void BoardLaunchEasiCamera_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
ImageBlackboard_MouseUp(null, null);
|
||||
SoftwareLauncher.LaunchEasiCamera("希沃视频展台");
|
||||
}
|
||||
|
||||
private void BoardLaunchDesmos_MouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
HideSubPanelsImmediately();
|
||||
ImageBlackboard_MouseUp(null, null);
|
||||
Process.Start("https://www.desmos.com/calculator?lang=zh-CN");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据当前背景颜色更新RGB滑块的值
|
||||
/// </summary>
|
||||
private void UpdateRGBSliders(Color color)
|
||||
{
|
||||
if (BackgroundPalette != null && BackgroundPalette.Child is StackPanel stackPanel)
|
||||
{
|
||||
if (stackPanel.Children.Count > 1 && stackPanel.Children[1] is StackPanel contentPanel)
|
||||
{
|
||||
// 查找RGB滑块
|
||||
Slider rSlider = null;
|
||||
Slider gSlider = null;
|
||||
Slider bSlider = null;
|
||||
|
||||
// 遍历面板查找RGB滑块
|
||||
foreach (var child in contentPanel.Children)
|
||||
{
|
||||
if (child is StackPanel panel && panel.Orientation == Orientation.Horizontal)
|
||||
{
|
||||
foreach (var panelChild in panel.Children)
|
||||
{
|
||||
if (panelChild is Slider slider)
|
||||
{
|
||||
if (panel.Children.Count > 0 && panel.Children[0] is TextBlock label)
|
||||
{
|
||||
if (label.Text == "R:")
|
||||
{
|
||||
rSlider = slider;
|
||||
}
|
||||
else if (label.Text == "G:")
|
||||
{
|
||||
gSlider = slider;
|
||||
}
|
||||
else if (label.Text == "B:")
|
||||
{
|
||||
bSlider = slider;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新滑块值
|
||||
if (rSlider != null && gSlider != null && bSlider != null)
|
||||
{
|
||||
rSlider.Value = color.R;
|
||||
gSlider.Value = color.G;
|
||||
bSlider.Value = color.B;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Forms;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Threading;
|
||||
using Clipboard = System.Windows.Clipboard;
|
||||
using ContextMenu = System.Windows.Controls.ContextMenu;
|
||||
using Cursors = System.Windows.Input.Cursors;
|
||||
using MenuItem = System.Windows.Controls.MenuItem;
|
||||
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private bool isClipboardMonitoringEnabled;
|
||||
private BitmapSource lastClipboardImage;
|
||||
|
||||
// 初始化剪贴板监控
|
||||
private void InitializeClipboardMonitoring()
|
||||
{
|
||||
try
|
||||
{
|
||||
// 监听剪贴板变化
|
||||
ClipboardNotification.ClipboardUpdate += OnClipboardUpdate;
|
||||
isClipboardMonitoringEnabled = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"初始化剪贴板监控失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 剪贴板内容变化事件处理
|
||||
private void OnClipboardUpdate()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Clipboard.ContainsImage())
|
||||
{
|
||||
var clipboardImage = Clipboard.GetImage();
|
||||
if (clipboardImage != null && clipboardImage != lastClipboardImage)
|
||||
{
|
||||
lastClipboardImage = clipboardImage;
|
||||
// 在白板模式下显示粘贴提示
|
||||
if (currentMode == 1) // 白板模式
|
||||
{
|
||||
ShowPasteNotification();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理剪贴板更新失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 显示粘贴提示
|
||||
private void ShowPasteNotification()
|
||||
{
|
||||
try
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
ShowNotification("检测到剪贴板中有图片,右键点击白板可粘贴");
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示粘贴提示失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理右键菜单显示
|
||||
private void ShowPasteContextMenu(Point position)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Clipboard.ContainsImage()) return;
|
||||
|
||||
// 创建右键菜单
|
||||
var contextMenu = new ContextMenu();
|
||||
|
||||
var pasteMenuItem = new MenuItem
|
||||
{
|
||||
Header = "粘贴图片"
|
||||
};
|
||||
|
||||
pasteMenuItem.Click += async (s, e) => await PasteImageFromClipboard(position);
|
||||
contextMenu.Items.Add(pasteMenuItem);
|
||||
|
||||
// 显示菜单
|
||||
contextMenu.IsOpen = true;
|
||||
contextMenu.PlacementTarget = inkCanvas;
|
||||
contextMenu.Placement = PlacementMode.MousePoint;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"显示粘贴菜单失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 从剪贴板粘贴图片
|
||||
private async Task PasteImageFromClipboard(Point? position = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!Clipboard.ContainsImage())
|
||||
{
|
||||
ShowNotification("剪贴板中没有图片");
|
||||
return;
|
||||
}
|
||||
|
||||
var clipboardImage = Clipboard.GetImage();
|
||||
if (clipboardImage == null)
|
||||
{
|
||||
ShowNotification("无法获取剪贴板图片");
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建Image控件
|
||||
var image = new Image
|
||||
{
|
||||
Source = clipboardImage,
|
||||
Width = clipboardImage.PixelWidth,
|
||||
Height = clipboardImage.PixelHeight,
|
||||
Stretch = Stretch.Fill
|
||||
};
|
||||
|
||||
// 生成唯一名称
|
||||
string timestamp = "img_clipboard_" + DateTime.Now.ToString("yyyyMMdd_HH_mm_ss_fff");
|
||||
image.Name = timestamp;
|
||||
|
||||
// 初始化TransformGroup
|
||||
if (image is FrameworkElement element)
|
||||
{
|
||||
var transformGroup = new TransformGroup();
|
||||
transformGroup.Children.Add(new ScaleTransform(1, 1));
|
||||
transformGroup.Children.Add(new TranslateTransform(0, 0));
|
||||
transformGroup.Children.Add(new RotateTransform(0));
|
||||
element.RenderTransform = transformGroup;
|
||||
}
|
||||
|
||||
// 设置图片属性,避免被InkCanvas选择系统处理
|
||||
image.IsHitTestVisible = true;
|
||||
image.Focusable = false;
|
||||
|
||||
// 初始化InkCanvas选择设置
|
||||
if (inkCanvas != null)
|
||||
{
|
||||
// 清除当前选择,避免显示控制点
|
||||
inkCanvas.Select(new StrokeCollection());
|
||||
// 设置编辑模式为非选择模式,这样可以保持图片的交互功能
|
||||
// 同时通过图片的IsHitTestVisible和Focusable属性来避免InkCanvas选择系统的干扰
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.None;
|
||||
}
|
||||
|
||||
// 添加到画布
|
||||
inkCanvas.Children.Add(image);
|
||||
|
||||
// 等待图片加载完成后再进行居中处理
|
||||
image.Loaded += (sender, e) =>
|
||||
{
|
||||
// 确保在UI线程中执行
|
||||
Dispatcher.BeginInvoke(new Action(() =>
|
||||
{
|
||||
// 先进行缩放居中处理
|
||||
CenterAndScaleElement(image);
|
||||
|
||||
// 如果有指定位置,调整到指定位置
|
||||
if (position.HasValue)
|
||||
{
|
||||
// 在指定位置居中显示
|
||||
InkCanvas.SetLeft(image, position.Value.X - image.Width / 2);
|
||||
InkCanvas.SetTop(image, position.Value.Y - image.Height / 2);
|
||||
}
|
||||
|
||||
// 绑定事件处理器
|
||||
if (image is FrameworkElement elementForEvents)
|
||||
{
|
||||
// 鼠标事件
|
||||
elementForEvents.MouseLeftButtonDown += Element_MouseLeftButtonDown;
|
||||
elementForEvents.MouseLeftButtonUp += Element_MouseLeftButtonUp;
|
||||
elementForEvents.MouseMove += Element_MouseMove;
|
||||
elementForEvents.MouseWheel += Element_MouseWheel;
|
||||
|
||||
// 触摸事件
|
||||
elementForEvents.TouchDown += Element_TouchDown;
|
||||
elementForEvents.TouchUp += Element_TouchUp;
|
||||
elementForEvents.IsManipulationEnabled = true;
|
||||
elementForEvents.ManipulationDelta += Element_ManipulationDelta;
|
||||
elementForEvents.ManipulationCompleted += Element_ManipulationCompleted;
|
||||
|
||||
// 设置光标
|
||||
elementForEvents.Cursor = Cursors.Hand;
|
||||
}
|
||||
}), DispatcherPriority.Loaded);
|
||||
};
|
||||
|
||||
// 提交到历史记录
|
||||
timeMachine.CommitElementInsertHistory(image);
|
||||
|
||||
// 插入图片后切换到选择模式并刷新浮动栏高光显示
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Select);
|
||||
UpdateCurrentToolMode("select");
|
||||
HideSubPanels("select");
|
||||
|
||||
ShowNotification("图片已从剪贴板粘贴");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ShowNotification($"粘贴图片失败: {ex.Message}");
|
||||
LogHelper.WriteLogToFile($"粘贴图片失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 处理白板右键事件
|
||||
private void InkCanvas_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只在白板模式下处理
|
||||
if (currentMode != 1) return;
|
||||
|
||||
// 检查是否有图片在剪贴板中
|
||||
if (Clipboard.ContainsImage())
|
||||
{
|
||||
var position = e.GetPosition(inkCanvas);
|
||||
ShowPasteContextMenu(position);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理右键事件失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 处理全局粘贴快捷键
|
||||
internal async void HandleGlobalPaste(object sender, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 只在白板模式下处理
|
||||
if (currentMode != 1) return;
|
||||
|
||||
if (Clipboard.ContainsImage())
|
||||
{
|
||||
await PasteImageFromClipboard();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"处理全局粘贴失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
// 清理剪贴板监控
|
||||
private void CleanupClipboardMonitoring()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (isClipboardMonitoringEnabled)
|
||||
{
|
||||
ClipboardNotification.ClipboardUpdate -= OnClipboardUpdate;
|
||||
isClipboardMonitoringEnabled = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.WriteLogToFile($"清理剪贴板监控失败: {ex.Message}", LogHelper.LogType.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 剪贴板通知类
|
||||
public static class ClipboardNotification
|
||||
{
|
||||
public static event Action ClipboardUpdate;
|
||||
|
||||
private static Timer clipboardTimer;
|
||||
private static string lastClipboardText = "";
|
||||
private static bool lastHadImage;
|
||||
|
||||
static ClipboardNotification()
|
||||
{
|
||||
clipboardTimer = new Timer();
|
||||
clipboardTimer.Interval = 500; // 每500ms检查一次
|
||||
clipboardTimer.Tick += CheckClipboard;
|
||||
clipboardTimer.Start();
|
||||
}
|
||||
|
||||
private static void CheckClipboard(object sender, EventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
bool currentHasImage = Clipboard.ContainsImage();
|
||||
string currentText = Clipboard.ContainsText() ? Clipboard.GetText() : "";
|
||||
|
||||
if (currentHasImage != lastHadImage || currentText != lastClipboardText)
|
||||
{
|
||||
lastHadImage = currentHasImage;
|
||||
lastClipboardText = currentText;
|
||||
ClipboardUpdate?.Invoke();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// 忽略剪贴板访问错误
|
||||
}
|
||||
}
|
||||
|
||||
public static void Stop()
|
||||
{
|
||||
clipboardTimer?.Stop();
|
||||
clipboardTimer?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
+168
-137
@@ -1,44 +1,56 @@
|
||||
using Ink_Canvas.Helpers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Ink;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Animation;
|
||||
using Ink_Canvas.Popups;
|
||||
using System.Windows.Media.Imaging;
|
||||
|
||||
namespace Ink_Canvas {
|
||||
public partial class MainWindow : PerformanceTransparentWin {
|
||||
namespace Ink_Canvas
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
private int inkColor = 1;
|
||||
|
||||
private void ColorSwitchCheck() {
|
||||
//HideSubPanels("color");
|
||||
if (GridTransparencyFakeBackground.Background == Brushes.Transparent) {
|
||||
if (currentMode == 1) {
|
||||
private void ColorSwitchCheck()
|
||||
{
|
||||
if (penType != 1)
|
||||
{
|
||||
HideSubPanels("color");
|
||||
}
|
||||
if (GridTransparencyFakeBackground.Background == Brushes.Transparent)
|
||||
{
|
||||
if (currentMode == 1)
|
||||
{
|
||||
currentMode = 0;
|
||||
GridBackgroundCover.Visibility = Visibility.Collapsed;
|
||||
AnimationsHelper.HideWithSlideAndFade(BlackboardLeftSide);
|
||||
AnimationsHelper.HideWithSlideAndFade(BlackboardCenterSide);
|
||||
AnimationsHelper.HideWithSlideAndFade(BlackboardRightSide);
|
||||
|
||||
// 在PPT模式下隐藏手势面板和手势按钮
|
||||
AnimationsHelper.HideWithSlideAndFade(TwoFingerGestureBorder);
|
||||
AnimationsHelper.HideWithSlideAndFade(BoardTwoFingerGestureBorder);
|
||||
EnableTwoFingerGestureBorder.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
BtnHideInkCanvas_Click(null, null);
|
||||
BtnHideInkCanvas_Click(BtnHideInkCanvas, null);
|
||||
}
|
||||
|
||||
var strokes = inkCanvas.GetSelectedStrokes();
|
||||
if (strokes.Count != 0) {
|
||||
if (strokes.Count != 0)
|
||||
{
|
||||
foreach (var stroke in strokes)
|
||||
try {
|
||||
try
|
||||
{
|
||||
stroke.DrawingAttributes.Color = inkCanvas.DefaultDrawingAttributes.Color;
|
||||
}
|
||||
catch {
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
@@ -51,64 +63,89 @@ namespace Ink_Canvas {
|
||||
item.Value.Clear();
|
||||
}
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
inkCanvas.IsManipulationEnabled = true;
|
||||
drawingShapeMode = 0;
|
||||
inkCanvas.EditingMode = InkCanvasEditingMode.Ink;
|
||||
// 使用集中化的工具模式切换方法
|
||||
SetCurrentToolMode(InkCanvasEditingMode.Ink);
|
||||
CancelSingleFingerDragMode();
|
||||
forceEraser = false;
|
||||
CheckColorTheme();
|
||||
}
|
||||
|
||||
isLongPressSelected = false;
|
||||
}
|
||||
|
||||
private bool isUselightThemeColor = false, isDesktopUselightThemeColor = false;
|
||||
private int penType = 0; // 0是签字笔,1是荧光笔
|
||||
private bool isUselightThemeColor, isDesktopUselightThemeColor;
|
||||
private int penType; // 0是签字笔,1是荧光笔
|
||||
private int lastDesktopInkColor = 1, lastBoardInkColor = 5;
|
||||
private int highlighterColor = 102;
|
||||
|
||||
private void CheckColorTheme(bool changeColorTheme = false) {
|
||||
private void CheckColorTheme(bool changeColorTheme = false)
|
||||
{
|
||||
if (changeColorTheme)
|
||||
if (currentMode != 0) {
|
||||
var bgC = BoardPagesSettingsList[CurrentWhiteboardIndex - 1].BackgroundColor;
|
||||
GridBackgroundCover.Background = new SolidColorBrush(BoardBackgroundColors[(int)bgC]);
|
||||
if (bgC == BlackboardBackgroundColorEnum.BlackBoardGreen
|
||||
|| bgC == BlackboardBackgroundColorEnum.BlueBlack
|
||||
|| bgC == BlackboardBackgroundColorEnum.GrayBlack
|
||||
|| bgC == BlackboardBackgroundColorEnum.RealBlack) {
|
||||
if (currentMode != 0)
|
||||
{
|
||||
if (Settings.Canvas.UsingWhiteboard)
|
||||
{
|
||||
// 检查是否有自定义背景色,如果有则使用自定义背景色
|
||||
if (CustomBackgroundColor.HasValue)
|
||||
{
|
||||
GridBackgroundCover.Background = new SolidColorBrush(CustomBackgroundColor.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
GridBackgroundCover.Background = new SolidColorBrush(Color.FromRgb(234, 235, 237));
|
||||
}
|
||||
WaterMarkTime.Foreground = new SolidColorBrush(Color.FromRgb(22, 41, 36));
|
||||
WaterMarkDate.Foreground = new SolidColorBrush(Color.FromRgb(22, 41, 36));
|
||||
BlackBoardWaterMark.Foreground = new SolidColorBrush(Color.FromRgb(22, 41, 36));
|
||||
isUselightThemeColor = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 黑板模式下,检查是否有自定义背景色
|
||||
if (CustomBackgroundColor.HasValue)
|
||||
{
|
||||
GridBackgroundCover.Background = new SolidColorBrush(CustomBackgroundColor.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
GridBackgroundCover.Background = new SolidColorBrush(Color.FromRgb(22, 41, 36));
|
||||
}
|
||||
WaterMarkTime.Foreground = new SolidColorBrush(Color.FromRgb(234, 235, 237));
|
||||
WaterMarkDate.Foreground = new SolidColorBrush(Color.FromRgb(234, 235, 237));
|
||||
BlackBoardWaterMark.Foreground = new SolidColorBrush(Color.FromRgb(234, 235, 237));
|
||||
isUselightThemeColor = true;
|
||||
} else {
|
||||
WaterMarkTime.Foreground = new SolidColorBrush(Color.FromRgb(22, 22,22));
|
||||
WaterMarkDate.Foreground = new SolidColorBrush(Color.FromRgb(22, 22, 22));
|
||||
BlackBoardWaterMark.Foreground = new SolidColorBrush(Color.FromRgb(22, 22, 22));
|
||||
isUselightThemeColor = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentMode == 0) {
|
||||
if (currentMode == 0)
|
||||
{
|
||||
isUselightThemeColor = isDesktopUselightThemeColor;
|
||||
inkColor = lastDesktopInkColor;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
inkColor = lastBoardInkColor;
|
||||
}
|
||||
|
||||
double alpha = inkCanvas.DefaultDrawingAttributes.Color.A;
|
||||
|
||||
if (penType == 0) {
|
||||
if (inkColor == 0) {
|
||||
if (penType == 0)
|
||||
{
|
||||
if (inkColor == 0)
|
||||
{
|
||||
// Black
|
||||
inkCanvas.DefaultDrawingAttributes.Color = Color.FromArgb((byte)alpha, 0, 0, 0);
|
||||
}
|
||||
else if (inkColor == 5) {
|
||||
else if (inkColor == 5)
|
||||
{
|
||||
// White
|
||||
inkCanvas.DefaultDrawingAttributes.Color = Color.FromArgb((byte)alpha, 255, 255, 255);
|
||||
}
|
||||
else if (isUselightThemeColor) {
|
||||
else if (isUselightThemeColor)
|
||||
{
|
||||
if (inkColor == 1)
|
||||
// Red
|
||||
inkCanvas.DefaultDrawingAttributes.Color = Color.FromArgb((byte)alpha, 239, 68, 68);
|
||||
@@ -131,7 +168,8 @@ namespace Ink_Canvas {
|
||||
// Orange (亮色)
|
||||
inkCanvas.DefaultDrawingAttributes.Color = Color.FromArgb((byte)alpha, 249, 115, 22);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (inkColor == 1)
|
||||
// Red
|
||||
inkCanvas.DefaultDrawingAttributes.Color = Color.FromArgb((byte)alpha, 220, 38, 38);
|
||||
@@ -155,7 +193,8 @@ namespace Ink_Canvas {
|
||||
inkCanvas.DefaultDrawingAttributes.Color = Color.FromArgb((byte)alpha, 234, 88, 12);
|
||||
}
|
||||
}
|
||||
else if (penType == 1) {
|
||||
else if (penType == 1)
|
||||
{
|
||||
if (highlighterColor == 100)
|
||||
// Black
|
||||
inkCanvas.DefaultDrawingAttributes.Color = Color.FromRgb(0, 0, 0);
|
||||
@@ -188,7 +227,8 @@ namespace Ink_Canvas {
|
||||
inkCanvas.DefaultDrawingAttributes.Color = Color.FromRgb(249, 115, 22);
|
||||
}
|
||||
|
||||
if (isUselightThemeColor) {
|
||||
if (isUselightThemeColor)
|
||||
{
|
||||
// 亮系
|
||||
// 亮色的红色
|
||||
BorderPenColorRed.Background = new SolidColorBrush(Color.FromRgb(239, 68, 68));
|
||||
@@ -223,7 +263,8 @@ namespace Ink_Canvas {
|
||||
ColorThemeSwitchTextBlock.Text = "暗系";
|
||||
BoardColorThemeSwitchTextBlock.Text = "暗系";
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
// 暗系
|
||||
// 暗色的红色
|
||||
BorderPenColorRed.Background = new SolidColorBrush(Color.FromRgb(220, 38, 38));
|
||||
@@ -302,7 +343,8 @@ namespace Ink_Canvas {
|
||||
BoardHighlighterPenViewboxBtnColorYellowContent.Visibility = Visibility.Collapsed;
|
||||
BoardHighlighterPenViewboxBtnColorZincContent.Visibility = Visibility.Collapsed;
|
||||
|
||||
switch (inkColor) {
|
||||
switch (inkColor)
|
||||
{
|
||||
case 0:
|
||||
ViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
||||
BoardViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
||||
@@ -339,7 +381,8 @@ namespace Ink_Canvas {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (highlighterColor) {
|
||||
switch (highlighterColor)
|
||||
{
|
||||
case 100:
|
||||
HighlighterPenViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
||||
BoardHighlighterPenViewboxBtnColorBlackContent.Visibility = Visibility.Visible;
|
||||
@@ -381,20 +424,31 @@ namespace Ink_Canvas {
|
||||
BoardHighlighterPenViewboxBtnColorOrangeContent.Visibility = Visibility.Visible;
|
||||
break;
|
||||
}
|
||||
|
||||
// 更新快捷调色盘选择指示器
|
||||
if (penType == 0)
|
||||
{
|
||||
UpdateQuickColorPaletteIndicator(inkCanvas.DefaultDrawingAttributes.Color);
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckLastColor(int inkColor, bool isHighlighter = false) {
|
||||
if (isHighlighter == true) {
|
||||
private void CheckLastColor(int inkColor, bool isHighlighter = false)
|
||||
{
|
||||
if (isHighlighter)
|
||||
{
|
||||
highlighterColor = inkColor;
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
if (currentMode == 0) lastDesktopInkColor = inkColor;
|
||||
else lastBoardInkColor = inkColor;
|
||||
}
|
||||
}
|
||||
|
||||
private async void CheckPenTypeUIState() {
|
||||
if (penType == 0) {
|
||||
private async void CheckPenTypeUIState()
|
||||
{
|
||||
if (penType == 0)
|
||||
{
|
||||
DefaultPenPropsPanel.Visibility = Visibility.Visible;
|
||||
DefaultPenColorsPanel.Visibility = Visibility.Visible;
|
||||
HighlighterPenColorsPanel.Visibility = Visibility.Collapsed;
|
||||
@@ -430,7 +484,8 @@ namespace Ink_Canvas {
|
||||
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Collapsed;
|
||||
|
||||
// PenPalette.Margin = new Thickness(-160, -200, -33, 32);
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
@@ -441,7 +496,8 @@ namespace Ink_Canvas {
|
||||
PenPalette.BeginAnimation(MarginProperty, marginAnimation);
|
||||
});
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
@@ -459,7 +515,8 @@ namespace Ink_Canvas {
|
||||
|
||||
await Dispatcher.InvokeAsync(() => { BoardPenPaletteGrid.Margin = new Thickness(-160, -200, -33, 50); });
|
||||
}
|
||||
else if (penType == 1) {
|
||||
else if (penType == 1)
|
||||
{
|
||||
DefaultPenPropsPanel.Visibility = Visibility.Collapsed;
|
||||
DefaultPenColorsPanel.Visibility = Visibility.Collapsed;
|
||||
HighlighterPenColorsPanel.Visibility = Visibility.Visible;
|
||||
@@ -495,7 +552,8 @@ namespace Ink_Canvas {
|
||||
BoardHighlightPenTabButtonIndicator.Visibility = Visibility.Visible;
|
||||
|
||||
// PenPalette.Margin = new Thickness(-160, -157, -33, 32);
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
@@ -506,7 +564,8 @@ namespace Ink_Canvas {
|
||||
PenPalette.BeginAnimation(MarginProperty, marginAnimation);
|
||||
});
|
||||
|
||||
await Dispatcher.InvokeAsync(() => {
|
||||
await Dispatcher.InvokeAsync(() =>
|
||||
{
|
||||
var marginAnimation = new ThicknessAnimation
|
||||
{
|
||||
Duration = TimeSpan.FromSeconds(0.1),
|
||||
@@ -525,7 +584,8 @@ namespace Ink_Canvas {
|
||||
}
|
||||
}
|
||||
|
||||
private void SwitchToDefaultPen(object sender, MouseButtonEventArgs e) {
|
||||
private void SwitchToDefaultPen(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
penType = 0;
|
||||
CheckPenTypeUIState();
|
||||
CheckColorTheme();
|
||||
@@ -535,7 +595,8 @@ namespace Ink_Canvas {
|
||||
drawingAttributes.IsHighlighter = false;
|
||||
}
|
||||
|
||||
private void SwitchToHighlighterPen(object sender, MouseButtonEventArgs e) {
|
||||
private void SwitchToHighlighterPen(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
penType = 1;
|
||||
CheckPenTypeUIState();
|
||||
CheckColorTheme();
|
||||
@@ -543,145 +604,150 @@ namespace Ink_Canvas {
|
||||
drawingAttributes.Height = Settings.Canvas.HighlighterWidth;
|
||||
drawingAttributes.StylusTip = StylusTip.Rectangle;
|
||||
drawingAttributes.IsHighlighter = true;
|
||||
|
||||
// 确保荧光笔模式切换后正确更新颜色和快捷调色板指示器
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnColorBlack_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnColorBlack_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(0);
|
||||
forceEraser = false;
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnColorRed_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnColorRed_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(1);
|
||||
forceEraser = false;
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnColorGreen_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnColorGreen_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(2);
|
||||
forceEraser = false;
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnColorBlue_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnColorBlue_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(3);
|
||||
forceEraser = false;
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnColorYellow_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnColorYellow_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(4);
|
||||
forceEraser = false;
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnColorWhite_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnColorWhite_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(5);
|
||||
forceEraser = false;
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnColorPink_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnColorPink_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(6);
|
||||
forceEraser = false;
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnColorOrange_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnColorOrange_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(8);
|
||||
forceEraser = false;
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnColorTeal_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnColorTeal_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(7);
|
||||
forceEraser = false;
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnHighlighterColorBlack_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnHighlighterColorBlack_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(100, true);
|
||||
penType = 1;
|
||||
forceEraser = false;
|
||||
CheckPenTypeUIState();
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnHighlighterColorWhite_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnHighlighterColorWhite_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(101, true);
|
||||
penType = 1;
|
||||
forceEraser = false;
|
||||
CheckPenTypeUIState();
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnHighlighterColorRed_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnHighlighterColorRed_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(102, true);
|
||||
penType = 1;
|
||||
forceEraser = false;
|
||||
CheckPenTypeUIState();
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnHighlighterColorYellow_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnHighlighterColorYellow_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(103, true);
|
||||
penType = 1;
|
||||
forceEraser = false;
|
||||
CheckPenTypeUIState();
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnHighlighterColorGreen_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnHighlighterColorGreen_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(104, true);
|
||||
penType = 1;
|
||||
forceEraser = false;
|
||||
CheckPenTypeUIState();
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnHighlighterColorZinc_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnHighlighterColorZinc_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(105, true);
|
||||
penType = 1;
|
||||
forceEraser = false;
|
||||
CheckPenTypeUIState();
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnHighlighterColorBlue_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnHighlighterColorBlue_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(106, true);
|
||||
penType = 1;
|
||||
forceEraser = false;
|
||||
CheckPenTypeUIState();
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnHighlighterColorPurple_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnHighlighterColorPurple_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(107, true);
|
||||
penType = 1;
|
||||
forceEraser = false;
|
||||
CheckPenTypeUIState();
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnHighlighterColorTeal_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnHighlighterColorTeal_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(108, true);
|
||||
penType = 1;
|
||||
forceEraser = false;
|
||||
CheckPenTypeUIState();
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private void BtnHighlighterColorOrange_Click(object sender, RoutedEventArgs e) {
|
||||
private void BtnHighlighterColorOrange_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
CheckLastColor(109, true);
|
||||
penType = 1;
|
||||
forceEraser = false;
|
||||
CheckPenTypeUIState();
|
||||
ColorSwitchCheck();
|
||||
}
|
||||
|
||||
private Color StringToColor(string colorStr) {
|
||||
|
||||
private Color StringToColor(string colorStr)
|
||||
{
|
||||
var argb = new byte[4];
|
||||
for (var i = 0; i < 4; i++) {
|
||||
for (var i = 0; i < 4; i++)
|
||||
{
|
||||
var charArray = colorStr.Substring(i * 2 + 1, 2).ToCharArray();
|
||||
var b1 = toByte(charArray[0]);
|
||||
var b2 = toByte(charArray[1]);
|
||||
@@ -691,45 +757,10 @@ namespace Ink_Canvas {
|
||||
return Color.FromArgb(argb[0], argb[1], argb[2], argb[3]); //#FFFFFFFF
|
||||
}
|
||||
|
||||
private static byte toByte(char c) {
|
||||
private static byte toByte(char c)
|
||||
{
|
||||
var b = (byte)"0123456789ABCDEF".IndexOf(c);
|
||||
return b;
|
||||
}
|
||||
|
||||
#region PenPaletteV2
|
||||
|
||||
private void PenPaletteV2Init() {
|
||||
FloatingToolBarV2.PenPaletteV2_ColorSelectionChanged += PenpaletteV2_ColorSelectionChanged;
|
||||
FloatingToolBarV2.PenPaletteV2_ColorModeChanged += PenpaletteV2_ColorModeChanged;
|
||||
FloatingToolBarV2.PenPaletteV2_CustomColorChanged += PenpaletteV2_CustomColorChanged;
|
||||
FloatingToolBarV2.PenPaletteV2_PenModeChanged += PenpaletteV2_PenModeChanged;
|
||||
FloatingToolBarV2.PenPaletteV2.SelectedColor = ColorPalette.ColorPaletteColor.ColorRed;
|
||||
}
|
||||
|
||||
private void PenpaletteV2_ColorSelectionChanged(object sender, ColorPalette.ColorSelectionChangedEventArgs e) {
|
||||
if (e.TriggerMode == ColorPalette.TriggerMode.TriggeredByCode) return;
|
||||
drawingAttributes.Color = FloatingToolBarV2.PenPaletteV2.GetColor(e.NowColor, false, null);
|
||||
}
|
||||
|
||||
private void PenpaletteV2_ColorModeChanged(object sender, ColorPalette.ColorModeChangedEventArgs e) {
|
||||
if (e.TriggerMode == ColorPalette.TriggerMode.TriggeredByCode) return;
|
||||
drawingAttributes.Color = FloatingToolBarV2.PenPaletteV2.GetColor(FloatingToolBarV2.PenPaletteV2.SelectedColor, false, null);
|
||||
}
|
||||
|
||||
private void PenpaletteV2_CustomColorChanged(object sender, ColorPalette.CustomColorChangedEventArgs e) {
|
||||
if (e.TriggerMode == ColorPalette.TriggerMode.TriggeredByCode) return;
|
||||
if (FloatingToolBarV2.PenPaletteV2.SelectedColor == ColorPalette.ColorPaletteColor.ColorCustom)
|
||||
drawingAttributes.Color = e.NowColor??new Color();
|
||||
}
|
||||
|
||||
private void PenpaletteV2_PenModeChanged(object sender, ColorPalette.PenModeChangedEventArgs e) {
|
||||
penType = e.NowMode == ColorPalette.PenMode.HighlighterMode ? 1 : 0;
|
||||
drawingAttributes.Width = e.NowMode == ColorPalette.PenMode.HighlighterMode ? Settings.Canvas.HighlighterWidth / 2 : Settings.Canvas.InkWidth;
|
||||
drawingAttributes.Height = e.NowMode == ColorPalette.PenMode.HighlighterMode ? Settings.Canvas.HighlighterWidth : Settings.Canvas.InkWidth;
|
||||
drawingAttributes.StylusTip = e.NowMode == ColorPalette.PenMode.HighlighterMode ? StylusTip.Rectangle : StylusTip.Ellipse;
|
||||
drawingAttributes.IsHighlighter = e.NowMode == ColorPalette.PenMode.HighlighterMode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user