ModBase.vb 68 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259
  1. Imports System.Runtime.CompilerServices
  2. Imports System.Xaml
  3. Public Module ModBase
  4. #Region "声明"
  5. '下列版本信息由更新器自动修改
  6. Public Const VersionBaseName As String = "2.13.0.0" '显示用版本名
  7. Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
  8. #If RELEASE Then
  9. Public Const VersionCode As Integer = 406 '正式版
  10. #Else
  11. Public Const VersionCode As Integer = 405 '快照版
  12. #End If
  13. '版本信息
  14. Public Const VersionDisplay As String = BuildTypeDisplay & " " & VersionBaseName
  15. #If DEBUG Then
  16. Public Const BuildTypeDisplay As String = "开发版"
  17. Public Const BuildType As BuildTypes = BuildTypes.Debug
  18. #ElseIf RELEASE Then
  19. Public Const BuildTypeDisplay As String = "正式版"
  20. Public Const BuildType As BuildTypes = BuildTypes.Release
  21. #Else
  22. Public Const BuildTypeDisplay As String = "快照版"
  23. Public Const BuildType As BuildTypes = BuildTypes.Snapshot
  24. #End If
  25. Public Enum BuildTypes
  26. Debug = 100
  27. Release = 50
  28. Snapshot = 0
  29. End Enum
  30. ''' <summary>
  31. ''' 主窗口句柄。
  32. ''' </summary>
  33. Public Handle As IntPtr
  34. ''' <summary>
  35. ''' 包含程序名的完整路径。
  36. ''' </summary>
  37. Public PathExe As String = Paths.Base & AppDomain.CurrentDomain.SetupInformation.ApplicationName
  38. ''' <summary>
  39. ''' 程序内嵌图片文件夹路径,以 / 结尾。
  40. ''' </summary>
  41. Public PathImage As String = "pack://application:,,,/Plain Craft Launcher 2;component/Images/"
  42. ''' <summary>
  43. ''' 程序的缓存文件夹路径,以 \ 结尾。
  44. ''' </summary>
  45. Public PathTemp As String = If(Settings.Get(Of String)("SystemSystemCache") = "", Path.GetTempPath() & "PCL\", Settings.Get(Of String)("SystemSystemCache")).ToString.Replace("/", "\").TrimEnd("\") & "\"
  46. ''' <summary>
  47. ''' 当前程序的语言。
  48. ''' </summary>
  49. Public Lang As String = "zh_CN"
  50. ''' <summary>
  51. ''' 程序的打开计时。
  52. ''' </summary>
  53. Public ApplicationStartTick As Long = GetTimeMs()
  54. ''' <summary>
  55. ''' 程序打开时的时间。
  56. ''' </summary>
  57. Public ApplicationOpenTime As Date = Date.Now
  58. ''' <summary>
  59. ''' 识别码。
  60. ''' </summary>
  61. Public Identify As String = GetIdentify()
  62. ''' <summary>
  63. ''' 程序是否正在结束。
  64. ''' </summary>
  65. Public IsProgramEnding As Boolean = False
  66. ''' <summary>
  67. ''' 是否使用 GBK 编码。
  68. ''' </summary>
  69. Public IsGBKEncoding As Boolean = Encoding.Default.CodePage = 936
  70. ''' <summary>
  71. ''' 系统盘盘符,以 \ 结尾。例如 “C:\”。
  72. ''' </summary>
  73. Public OsDrive As String = Environment.GetLogicalDrives().Where(Function(p) Directory.Exists(p)).First.Upper.First & ":\" '#3799
  74. Public ReadOnly Property ModeDebug As Boolean
  75. Get
  76. Return Settings.Get(Of Boolean)("SystemDebugMode")
  77. End Get
  78. End Property
  79. #End Region
  80. #Region "矢量图标"
  81. Public Class Logo
  82. ''' <summary>
  83. ''' 图标按钮,心(空心),1.1x
  84. ''' </summary>
  85. Public Const IconButtonLikeLine As String = "M512 896a42.666667 42.666667 0 0 1-30.293333-12.373333l-331.52-331.946667a224.426667 224.426667 0 0 1 0-315.733333 223.573333 223.573333 0 0 1 315.733333 0L512 282.026667l46.08-46.08a223.573333 223.573333 0 0 1 315.733333 0 224.426667 224.426667 0 0 1 0 315.733333l-331.52 331.946667A42.666667 42.666667 0 0 1 512 896zM308.053333 256a136.533333 136.533333 0 0 0-97.28 40.106667 138.24 138.24 0 0 0 0 194.986666L512 792.746667l301.226667-301.653334a138.24 138.24 0 0 0 0-194.986666 141.653333 141.653333 0 0 0-194.56 0l-76.373334 76.8a42.666667 42.666667 0 0 1-60.586666 0L405.333333 296.106667A136.533333 136.533333 0 0 0 308.053333 256z"
  86. ''' <summary>
  87. ''' 图标按钮,心(实心),1.1x
  88. ''' </summary>
  89. Public Const IconButtonLikeFill As String = "M700.856 155.543c-74.769 0-144.295 72.696-190.046 127.26-45.737-54.576-115.247-127.26-190.056-127.26-134.79 0-244.443 105.78-244.443 235.799 0 77.57 39.278 131.988 70.845 175.713C238.908 694.053 469.62 852.094 479.39 858.757c9.41 6.414 20.424 9.629 31.401 9.629 11.006 0 21.998-3.215 31.398-9.63 9.782-6.662 240.514-164.703 332.238-291.701 31.587-43.724 70.874-98.143 70.874-175.713-0.001-130.02-109.656-235.8-244.445-235.8z m0 0"
  90. ''' <summary>
  91. ''' 图标按钮,垃圾桶,1.1x
  92. ''' </summary>
  93. Public Const IconButtonDelete As String = "M520.192 0C408.43 0 317.44 82.87 313.563 186.734H52.736c-29.038 0-52.663 21.943-52.663 49.079s23.625 49.152 52.663 49.152h58.075v550.473c0 103.35 75.118 187.757 167.717 187.757h472.43c92.599 0 167.716-83.894 167.716-187.757V285.477h52.59c29.038 0 52.59-21.943 52.663-49.08-0.073-27.135-23.625-49.151-52.663-49.151H726.235C723.237 83.017 631.955 0 520.192 0zM404.846 177.957c3.803-50.03 50.176-89.015 107.447-89.015 57.197 0 103.57 38.985 106.788 89.015H404.92zM284.379 933.669c-33.353 0-69.997-39.351-69.997-95.525v-549.01H833.39v549.522c0 56.247-36.645 95.525-69.998 95.525H284.379v-0.512z M357.23 800.695a48.274 48.274 0 0 0 47.616-49.006V471.7a48.274 48.274 0 0 0-47.543-49.08 48.274 48.274 0 0 0-47.69 49.006V751.69c0 27.282 20.846 49.006 47.617 49.006z m166.62 0a48.274 48.274 0 0 0 47.688-49.006V471.7a48.274 48.274 0 0 0-47.689-49.08 48.274 48.274 0 0 0-47.543 49.006V751.69c0 27.282 21.431 49.006 47.543 49.006z m142.92 0a48.274 48.274 0 0 0 47.543-49.006V471.7a48.274 48.274 0 0 0-47.543-49.08 48.274 48.274 0 0 0-47.616 49.006V751.69c0 27.282 20.773 49.006 47.543 49.006z"
  94. ''' <summary>
  95. ''' 图标按钮,禁止,1x
  96. ''' </summary>
  97. Public Const IconButtonStop As String = "M508 990.4c-261.6 0-474.4-212-474.4-474.4S246.4 41.6 508 41.6s474.4 212 474.4 474.4S769.6 990.4 508 990.4zM508 136.8c-209.6 0-379.2 169.6-379.2 379.2 0 209.6 169.6 379.2 379.2 379.2s379.2-169.6 379.2-379.2C887.2 306.4 717.6 136.8 508 136.8zM697.6 563.2 318.4 563.2c-26.4 0-47.2-21.6-47.2-47.2 0-26.4 21.6-47.2 47.2-47.2l379.2 0c26.4 0 47.2 21.6 47.2 47.2C744.8 542.4 724 563.2 697.6 563.2z"
  98. ''' <summary>
  99. ''' 图标按钮,勾选,1x
  100. ''' </summary>
  101. Public Const IconButtonCheck As String = "M512 0a512 512 0 1 0 512 512A512 512 0 0 0 512 0z m0 921.6a409.6 409.6 0 1 1 409.6-409.6 409.6 409.6 0 0 1-409.6 409.6z M716.8 339.968l-256 253.44L328.192 460.8A51.2 51.2 0 0 0 256 532.992l168.448 168.96a51.2 51.2 0 0 0 72.704 0l289.28-289.792A51.2 51.2 0 0 0 716.8 339.968z"
  102. ''' <summary>
  103. ''' 图标按钮,笔,1x
  104. ''' </summary>
  105. Public Const IconButtonEdit As String = "M732.64 64.32C688.576 21.216 613.696 21.216 569.6 64.32L120.128 499.52c-17.6 12.896-26.432 30.144-30.848 51.68L32 870.048c0 25.856 8.8 56 26.432 73.248 17.632 17.216 17.632 48.704 88.64 48.704h13.248l326.08-56c22.016-4.32 39.68-12.928 52.864-30.176l449.472-435.2c22.048-21.536 35.264-47.36 35.264-77.536 0-30.176-13.216-56-35.264-77.568l-256.096-251.2zM139.712 903.776l56-326.912 311.04-295.136 267.104 269.44-310.976 295.168-323.168 57.44zM844.576 467.84l-273.984-260.672 61.856-59.84c8.832-8.512 26.528-8.512 39.776 0l234.24 226.496c4.384 4.288 8.832 12.8 8.832 17.088s-4.416 8.544-8.864 12.8l-61.856 64.128z"
  106. ''' <summary>
  107. ''' 图标按钮,齿轮,1.1x
  108. ''' </summary>
  109. Public Const IconButtonSetup As String = "M651.946667 1001.813333c-22.186667 0-42.666667-10.24-61.44-27.306666-23.893333-23.893333-49.493333-35.84-75.093334-35.84-29.013333 0-56.32 11.946667-73.386666 30.72v3.413333c-17.066667 17.066667-42.666667 27.306667-66.56 27.306667h-6.826667c-6.826667 0-11.946667-1.706667-15.36-1.706667l-6.826667-1.706667c-64.853333-20.48-121.173333-54.613333-168.96-98.986666-29.013333-23.893333-37.546667-63.146667-25.6-95.573334 8.533333-23.893333 5.12-51.2-10.24-75.093333-15.36-27.306667-34.133333-40.96-59.733333-47.786667h-1.706667l-5.12-1.706666c-35.84-8.533333-61.44-34.133333-66.56-69.973334C1.706667 575.146667 0 537.6 0 512c0-32.426667 3.413333-63.146667 8.533333-93.866667v-6.826666l3.413334-8.533334c10.24-23.893333 23.893333-40.96 44.373333-51.2 5.12-3.413333 11.946667-6.826667 20.48-8.533333 27.306667-8.533333 51.2-25.6 63.146667-44.373333 13.653333-23.893333 17.066667-52.906667 10.24-81.92-11.946667-34.133333 0-71.68 30.72-93.866667 44.373333-37.546667 97.28-68.266667 158.72-93.866667l3.413333-1.706666c44.373333-13.653333 75.093333 3.413333 92.16 20.48 23.893333 23.893333 49.493333 35.84 75.093333 35.84 30.72 0 56.32-10.24 71.68-30.72l3.413334-3.413334c27.306667-27.306667 63.146667-35.84 93.866666-22.186666 63.146667 22.186667 117.76 54.613333 165.546667 97.28 29.013333 23.893333 37.546667 63.146667 25.6 95.573333-8.533333 23.893333-5.12 51.2 10.24 75.093333 15.36 27.306667 34.133333 40.96 59.733333 47.786667h1.706667l5.12 1.706667c35.84 8.533333 61.44 34.133333 66.56 71.68 6.826667 30.72 10.24 63.146667 11.946667 93.866666v3.413334c0 32.426667-3.413333 63.146667-8.533334 93.866666v6.826667l-3.413333 8.533333c-10.24 23.893333-23.893333 40.96-44.373333 51.2-5.12 3.413333-11.946667 6.826667-20.48 8.533334-27.306667 8.533333-51.2 25.6-63.146667 46.08-13.653333 23.893333-17.066667 52.906667-10.24 81.92 11.946667 35.84-1.706667 75.093333-30.72 95.573333-44.373333 35.84-95.573333 66.56-157.013333 92.16-15.36 3.413333-27.306667 3.413333-35.84 3.413333z m3.413333-83.626666z m1.706667 0zM517.12 853.333333c47.786667 0 93.866667 20.48 134.826667 59.733334 1.706667 1.706667 3.413333 1.706667 3.413333 3.413333 52.906667-22.186667 97.28-49.493333 136.533333-80.213333l1.706667-1.706667v-3.413333c-13.653333-52.906667-8.533333-104.106667 17.066667-148.48 23.893333-39.253333 64.853333-69.973333 114.346666-85.333334 1.706667 0 3.413333-1.706667 6.826667-6.826666 5.12-25.6 8.533333-51.2 8.533333-78.506667-1.706667-29.013333-3.413333-56.32-10.24-81.92v-5.12h-1.706666c-51.2-11.946667-90.453333-39.253333-119.466667-87.04-27.306667-44.373333-34.133333-100.693333-17.066667-148.48l-1.706666-1.706667h-3.413334c-39.253333-35.84-85.333333-63.146667-136.533333-80.213333H648.533333s-1.706667 1.706667-3.413333 1.706667c-32.426667 39.253333-80.213333 59.733333-136.533333 59.733333-47.786667 0-93.866667-20.48-134.826667-59.733333l-1.706667-1.706667h-1.706666c-54.613333 22.186667-98.986667 49.493333-136.533334 80.213333l-1.706666 1.706667v3.413333c13.653333 52.906667 8.533333 104.106667-17.066667 148.48-23.893333 39.253333-64.853333 69.973333-114.346667 85.333334-1.706667 0-3.413333 1.706667-6.826666 6.826666-6.826667 25.6-8.533333 51.2-8.533334 78.506667 0 30.72 3.413333 58.026667 6.826667 76.8l1.706667 5.12h1.706666c51.2 11.946667 90.453333 39.253333 119.466667 87.04 27.306667 44.373333 34.133333 100.693333 17.066667 148.48l1.706666 1.706667 1.706667 1.706666c37.546667 35.84 83.626667 63.146667 134.826667 80.213334 1.706667 0 3.413333 0 3.413333 1.706666h1.706667s1.706667 0 5.12-1.706666c34.133333-37.546667 81.92-59.733333 136.533333-59.733334z m-6.826667-146.773333c-110.933333 0-199.68-85.333333-199.68-196.266667 0-109.226667 87.04-196.266667 199.68-196.266666s199.68 85.333333 199.68 196.266666c-1.706667 109.226667-88.746667 196.266667-199.68 196.266667z m0-307.2c-63.146667 0-114.346667 49.493333-114.346666 110.933333 0 63.146667 49.493333 110.933333 114.346666 110.933334 30.72 0 59.733333-11.946667 80.213334-32.426667 20.48-20.48 32.426667-49.493333 32.426666-78.506667 0-63.146667-49.493333-110.933333-112.64-110.933333z"
  110. ''' <summary>
  111. ''' 图标按钮,重置,0.9x
  112. ''' </summary>
  113. Public Const IconButtonReset As String = "M667.6817627 313.65283203l-45.28564454 55.76660156L858.06933594 391.27124023 787.61950684 165.93066406l-56.01379395 69.01611328A354.47387695 354.47387695 0 0 0 520.89892578 165.93066406C324.87536621 165.93066406 165.93066406 324.43041992 165.93066406 519.91015625c0 195.52917481 158.94470215 353.97949219 354.96826172 353.97949219a355.06713867 355.06713867 0 0 0 331.73217774-227.66418458 50.52612305 50.52612305 0 0 0-29.21813966-65.25878905 50.77331543 50.77331543 0 0 0-65.50598144 29.16870117A253.61938477 253.61938477 0 0 1 520.94836426 772.78796387c-140.05920411 0-253.61938477-113.21411133-253.61938477-252.87780762 0-139.61425781 113.56018067-252.82836914 253.61938477-252.82836914 53.59130859 0 104.46350098 16.61132813 146.73339843 46.57104492"
  114. ''' <summary>
  115. ''' 图标按钮,刷新,0.85x
  116. ''' </summary>
  117. Public Const IconButtonRefresh As String = "M512.0 838.3c-80.2 0-153.4-29.3-210.2-77.4l75.5-75.5c11.5-11.5 25.8-22.0 25.8-37.0a27.2 27.2 0 0 0-27.1-27.1H104.0c-27.1 0-27.1 23.9-27.1 27.1v271.9a27.1 27.1 0 0 0 27.1 27.1c15.0 0 27.8-16.6 42.5-31.2l77.9-77.9c76.6 67.7 177.1 108.9 287.4 108.9 221.7 0 404.5-166.0 431.2-380.6h-109.8c-25.9 154.2-159.7 271.9-321.3 271.9zM919.9 76.6c-15.0 0-27.8 16.6-42.5 31.3L799.5 185.8c-76.5-67.7-177.1-108.9-287.4-108.9-221.8 0-404.5 166.1-431.3 380.6H190.6c25.9-154.2 159.7-271.9 321.4-271.9 80.2 0 153.4 29.3 210.1 77.4l-75.5 75.5c-11.6 11.5-25.8 22.0-25.8 37.1a27.2 27.2 0 0 0 27.1 27.1h271.9c27.1 0 27.1-23.9 27.1-27.1V103.8a27.1 27.1 0 0 0-27.1-27.1z"
  118. ''' <summary>
  119. ''' 图标按钮,软盘,1x
  120. ''' </summary>
  121. Public Const IconButtonSave As String = "M819.392 0L1024 202.752v652.16a168.96 168.96 0 0 1-168.832 168.768h-104.192a47.296 47.296 0 0 1-10.752 0H283.776a47.232 47.232 0 0 1-10.752 0H168.832A168.96 168.96 0 0 1 0 854.912V168.768A168.96 168.96 0 0 1 168.832 0h650.56z m110.208 854.912V242.112l-149.12-147.776H168.896c-41.088 0-74.432 33.408-74.432 74.432v686.144c0 41.024 33.344 74.432 74.432 74.432h62.4v-190.528c0-33.408 27.136-60.544 60.544-60.544h440.448c33.408 0 60.544 27.136 60.544 60.544v190.528h62.4c41.088 0 74.432-33.408 74.432-74.432z m-604.032 74.432h372.864v-156.736H325.568v156.736z m403.52-596.48a47.168 47.168 0 1 1 0 94.336H287.872a47.168 47.168 0 1 1 0-94.336h441.216z m0-153.728a47.168 47.168 0 1 1 0 94.4H287.872a47.168 47.168 0 1 1 0-94.4h441.216z"
  122. ''' <summary>
  123. ''' 图标按钮,信息,1.05x
  124. ''' </summary>
  125. Public Const IconButtonInfo As String = "M512 917.333333c223.861333 0 405.333333-181.472 405.333333-405.333333S735.861333 106.666667 512 106.666667 106.666667 288.138667 106.666667 512s181.472 405.333333 405.333333 405.333333z m0 106.666667C229.226667 1024 0 794.773333 0 512S229.226667 0 512 0s512 229.226667 512 512-229.226667 512-512 512z m-32-597.333333h64a21.333333 21.333333 0 0 1 21.333333 21.333333v320a21.333333 21.333333 0 0 1-21.333333 21.333333h-64a21.333333 21.333333 0 0 1-21.333333-21.333333V448a21.333333 21.333333 0 0 1 21.333333-21.333333z m0-192h64a21.333333 21.333333 0 0 1 21.333333 21.333333v64a21.333333 21.333333 0 0 1-21.333333 21.333333h-64a21.333333 21.333333 0 0 1-21.333333-21.333333v-64a21.333333 21.333333 0 0 1 21.333333-21.333333z"
  126. ''' <summary>
  127. ''' 图标按钮,列表,1x
  128. ''' </summary>
  129. Public Const IconButtonList As String = "M384 128h640v128H384zM160 192m-96 0a96 96 0 1 0 192 0 96 96 0 1 0-192 0ZM384 448h640v128H384zM160 512m-96 0a96 96 0 1 0 192 0 96 96 0 1 0-192 0ZM384 768h640v128H384zM160 832m-96 0a96 96 0 1 0 192 0 96 96 0 1 0-192 0Z"
  130. ''' <summary>
  131. ''' 图标按钮,文件夹,1.1x
  132. ''' </summary>
  133. Public Const IconButtonOpen As String = "M889.018182 418.909091H884.363636V316.509091a93.090909 93.090909 0 0 0-99.607272-89.832727h-302.545455l-93.090909-76.334546A46.545455 46.545455 0 0 0 358.865455 139.636364H146.152727A93.090909 93.090909 0 0 0 46.545455 229.469091V837.818182a46.545455 46.545455 0 0 0 46.545454 46.545454 46.545455 46.545455 0 0 0 16.756364-3.258181 109.381818 109.381818 0 0 0 25.134545 3.258181h586.472727a85.178182 85.178182 0 0 0 87.04-63.301818l163.374546-302.545454a46.545455 46.545455 0 0 0 5.585454-21.876364A82.385455 82.385455 0 0 0 889.018182 418.909091z m-744.727273-186.181818h198.283636l93.09091 76.334545a46.545455 46.545455 0 0 0 29.323636 10.705455h319.301818a12.101818 12.101818 0 0 1 6.516364 0V418.909091H302.545455a85.178182 85.178182 0 0 0-87.04 63.301818L139.636364 622.778182V232.727273a19.549091 19.549091 0 0 1 6.516363 0z m578.094546 552.029091a27.461818 27.461818 0 0 0-2.792728 6.516363H154.530909l147.083636-272.290909a27.461818 27.461818 0 0 0 2.792728-6.981818h565.061818z"
  134. ''' <summary>
  135. ''' 图标按钮,上箭头,0.95x
  136. ''' </summary>
  137. Public Const IconButtonArrowUp As String = "M554 333V853h-85V333l-228 228-60-60L512 170l331 331-60 60L554 333z"
  138. ''' <summary>
  139. ''' 图标按钮,下箭头,0.95x
  140. ''' </summary>
  141. Public Const IconButtonArrowDown As String = "M554 691V171h-85V691L241 463l-60 60L512 854l331-331-60-60L554 691z"
  142. ''' <summary>
  143. ''' 图标按钮,名片,1.1x
  144. ''' </summary>
  145. Public Const IconButtonCard As String = "M834.5 684.1c-31.2-70.4-98.9-120.9-179.1-127.3 63.5-8.5 112.6-63 112.6-128.8 0-71.8-58.2-130-130-130s-130 58.2-130 130c0 65.9 49 120.3 112.6 128.8-80.2 6.4-148 57-179.1 127.3-8.7 19.7 6 42 27.6 42 12.1 0 22.7-7.5 27.7-18.5 24.3-53.9 78.5-91.5 141.3-91.5s117 37.6 141.3 91.5c5 11.1 15.6 18.5 27.7 18.5 21.4 0 36.1-22.3 27.4-42zM567.9 427.9c0-38.6 31.4-70 70-70s70 31.4 70 70-31.4 70-70 70-70-31.4-70-70zM460.3 347.9H216.9c-16.6 0-30 13.4-30 30s13.4 30 30 30h243.3c16.6 0 30-13.4 30-30 0.1-16.5-13.4-30-29.9-30zM367.4 459.6H216.9c-16.6 0-30 13.4-30 30s13.4 30 30 30h150.4c16.6 0 30-13.4 30-30 0.1-16.6-13.4-30-29.9-30zM297.4 571.2H217c-16.6 0-30 13.4-30 30s13.4 30 30 30h80.4c16.6 0 30-13.4 30-30 0-16.5-13.5-30-30-30zM900 236v552H124V236h776m0-60H124c-33.1 0-60 26.9-60 60v552c0 33.1 26.9 60 60 60h776c33.1 0 60-26.9 60-60V236c0-33.1-26.9-60-60-60z"
  146. ''' <summary>
  147. ''' 图标按钮,×,0.85x
  148. ''' </summary>
  149. Public Const IconButtonCross As String = "F1 M 26.9166,22.1667L 37.9999,33.25L 49.0832,22.1668L 53.8332,26.9168L 42.7499,38L 53.8332,49.0834L 49.0833,53.8334L 37.9999,42.75L 26.9166,53.8334L 22.1666,49.0833L 33.25,38L 22.1667,26.9167L 26.9166,22.1667 Z"
  150. ''' <summary>
  151. ''' 图标按钮,Mojang,1.1x
  152. ''' </summary>
  153. Public Const IconButtonMojang As String = "M9.183,18.967c-0.109-2.239,1.336-5.119,3.92-4.96c3.657-0.027,7.319-0.044,10.977,0.005 c3.712,0.214,6.596,2.759,9.652,4.533c0.181-1.697-0.197-3.717,1.237-4.982c1.899,2.091,3.143,4.894,5.677,6.334 c1.577,0.805,2.973-0.668,4.221-1.44c1.55,2.305,2.108,5.075,2.622,7.752c0.657,3.438,0.947,6.925,1.111,10.413 c-1.734-0.733-3.355-1.708-4.971-2.671c-1.396-4.933-5.349-8.656-9.723-11.059c-3.46-1.817-7.752-2.185-11.338-0.52 c-3.761,1.593-6.285,5.666-5.891,9.75c0.121,4.577,3.17,8.765,7.391,10.456c7.746,3.285,16.407,2.234,24.521,1.243 c0.454,2.315-1.511,4.527-3.695,4.889c-10.577,0.038-21.148-0.011-31.725,0.022c-2.217,0.279-4.079-1.879-3.964-4.008 C9.167,36.141,9.216,27.556,9.183,18.967z M40.114,8.09c0.294-0.801,0.872-1.417,1.542-1.924c1.379,2.589,2.742,5.482,2.311,8.491 c-0.332,2.54-4.42,2.507-5.052,0.163C38.349,12.527,39.209,10.178,40.114,8.09z M0,53 l0.1,0 z"
  154. ''' <summary>
  155. ''' 图标按钮,用户,0.95x
  156. ''' </summary>
  157. Public Const IconButtonUser As String = "M660.338 528.065c63.61-46.825 105.131-121.964 105.131-206.83 0-141.7-115.29-256.987-256.997-256.987-141.706 0-256.998 115.288-256.998 256.987 0 85.901 42.52 161.887 107.456 208.562-152.1 59.92-260.185 207.961-260.185 381.077 0 21.276 17.253 38.53 38.53 38.53 21.278 0 38.53-17.254 38.53-38.53 0-183.426 149.232-332.671 332.667-332.671 1.589 0 3.113-0.207 4.694-0.244 0.8 0.056 1.553 0.244 2.362 0.244 183.434 0 332.664 149.245 332.664 332.671 0 21.276 17.255 38.53 38.533 38.53 21.277 0 38.53-17.254 38.53-38.53 0-174.885-110.354-324.13-264.917-382.809z m-331.803-206.83c0-99.22 80.72-179.927 179.935-179.927s179.937 80.708 179.937 179.927c0 99.203-80.721 179.91-179.937 179.91s-179.935-80.708-179.935-179.91z"
  158. ''' <summary>
  159. ''' 图标按钮,盾牌,1x
  160. ''' </summary>
  161. Public Const IconButtonShield As String = "M511.488256 95.184408c35.310345 22.516742 95.184408 55.78011 167.34033 84.437781 75.738131 29.681159 148.405797 40.93953 191.392304 45.033483v353.615193c0 73.691154-50.662669 164.781609-136.123938 244.101949C649.65917 901.181409 558.568716 942.12094 512 942.12094c-46.568716 0-137.65917-40.93953-222.096952-119.748126C204.441779 742.54073 153.77911 651.450275 153.77911 577.247376v-353.103448c42.474763-4.093953 116.165917-15.352324 191.904048-45.545227 75.226387-30.192904 133.565217-63.456272 165.805098-83.414293M512 0c-4.093953 0-8.187906 1.535232-11.258371 3.582209l-14.84058 10.234882c-1.023488 0.511744-67.550225 47.592204-170.410794 88.531735-100.813593 39.916042-198.556722 41.963018-199.58021 41.963018l-25.075462 0.511744c-10.746627 0.511744-18.934533 8.187906-18.934533 18.422789v414.000999c0 216.97951 286.064968 446.24088 440.09995 446.24088s440.09995-229.261369 440.09995-445.729136V163.758121c0-10.234883-8.69965-18.422789-18.934533-18.422789l-24.563718-0.511744c-1.023488 0-98.766617-2.046977-199.58021-41.963018-103.372314-40.93953-170.410795-88.01999-170.922538-88.531734L523.258371 3.582209c-3.070465-2.558721-7.164418-3.582209-11.258371-3.582209z M743.308346 410.930535l-260.477761 260.477761c-15.864068 15.864068-41.963018 15.864068-57.827087 0l-144.823588-144.823588c-15.864068-15.864068-15.864068-41.963018 0-57.827087 8.187906-8.187906 18.422789-11.770115 29.169415-11.770115 10.234883 0 20.981509 4.093953 29.169416 11.770115l115.654173 115.654173L685.993003 352.591704c15.864068-15.864068 41.963018-15.864068 57.827087 0 15.352324 16.375812 15.352324 42.474763-0.511744 58.338831z"
  162. ''' <summary>
  163. ''' 图标按钮,离线,0.85x
  164. ''' </summary>
  165. Public Const IconButtonOffline As String = "M533.293176 788.841412a60.235294 60.235294 0 1 1 85.202824 85.202823l-42.616471 42.586353c-129.355294 129.385412-339.124706 129.385412-468.510117 0-129.385412-129.385412-129.385412-339.124706 0-468.510117l42.586353-42.616471a60.235294 60.235294 0 1 1 85.202823 85.202824l-42.61647 42.586352a210.823529 210.823529 0 1 0 298.164706 298.164706l42.586352-42.61647z m255.548236-255.548236l42.61647-42.586352a210.823529 210.823529 0 1 0-298.164706-298.164706l-42.586352 42.61647a60.235294 60.235294 0 1 1-85.202824-85.202823l42.616471-42.586353c129.355294-129.385412 339.124706-129.385412 468.510117 0 129.385412 129.385412 129.385412 339.124706 0 468.510117l-42.586353 42.616471a60.235294 60.235294 0 1 1-85.202823-85.202824zM192.542118 192.542118a60.235294 60.235294 0 0 1 85.202823 0l553.712941 553.712941a60.235294 60.235294 0 0 1-85.202823 85.202823L192.542118 277.744941a60.235294 60.235294 0 0 1 0-85.202823z"
  166. ''' <summary>
  167. ''' 图标,服务端,1x
  168. ''' </summary>
  169. Public Const IconButtonServer As String = "M224 160a64 64 0 0 0-64 64v576a64 64 0 0 0 64 64h576a64 64 0 0 0 64-64V224a64 64 0 0 0-64-64H224z m0 384h576v256H224v-256z m192 96v64h320v-64H416z m-128 0v64h64v-64H288zM224 224h576v256H224V224z m192 96v64h320v-64H416z m-128 0v64h64v-64H288z"
  170. ''' <summary>
  171. ''' 图标,音符,1x
  172. ''' </summary>
  173. Public Const IconMusic As String = "M348.293565 716.53287V254.797913c0-41.672348 28.004174-78.358261 68.919652-90.37913L815.994435 40.826435c62.775652-18.610087 125.907478 26.579478 125.907478 89.933913v539.158261c8.013913 42.25113-8.94887 89.177043-47.014956 127.109565a232.848696 232.848696 0 0 1-170.785392 65.758609c-61.885217-2.938435-111.081739-33.435826-129.113043-80.050087-18.031304-46.614261-2.137043-102.177391 41.672348-145.853218a232.848696 232.848696 0 0 1 170.785391-65.80313c21.014261 1.024 40.514783 5.164522 57.878261 12.065391V233.338435c0-12.109913-10.551652-20.034783-20.569044-20.034783a24.620522 24.620522 0 0 0-5.787826 0.934957L439.785739 338.18713a19.545043 19.545043 0 0 0-14.825739 19.144348v438.984348H423.846957c11.53113 43.987478-5.164522 94.208-45.412174 134.322087a232.848696 232.848696 0 0 1-170.785392 65.758609c-61.885217-2.938435-111.081739-33.435826-129.113043-80.050087-18.031304-46.614261-2.137043-102.177391 41.672348-145.853218a232.848696 232.848696 0 0 1 170.785391-65.80313c20.791652 1.024 40.069565 5.075478 57.299478 11.842783z"
  174. ''' <summary>
  175. ''' 图标,播放,0.8x
  176. ''' </summary>
  177. Public Const IconPlay As String = "M803.904 463.936a55.168 55.168 0 0 1 0 96.128l-463.616 264.448C302.848 845.888 256 819.136 256 776.448V247.616c0-42.752 46.848-69.44 84.288-48.064l463.616 264.384z"
  178. End Class
  179. #End Region
  180. #Region "自定义类"
  181. ''' <summary>
  182. ''' 支持小数与常见类型隐式转换的颜色。
  183. ''' </summary>
  184. Public Class MyColor
  185. Public A As Double = 255
  186. Public R As Double = 0
  187. Public G As Double = 0
  188. Public B As Double = 0
  189. '类型转换
  190. Public Shared Widening Operator CType(str As String) As MyColor
  191. Return New MyColor(str)
  192. End Operator
  193. Public Shared Widening Operator CType(col As Color) As MyColor
  194. Return New MyColor(col)
  195. End Operator
  196. Public Shared Widening Operator CType(conv As MyColor) As Color
  197. Return Color.FromArgb(ClampToByte(conv.A), ClampToByte(conv.R), ClampToByte(conv.G), ClampToByte(conv.B))
  198. End Operator
  199. Public Shared Widening Operator CType(conv As MyColor) As System.Drawing.Color
  200. Return System.Drawing.Color.FromArgb(ClampToByte(conv.A), ClampToByte(conv.R), ClampToByte(conv.G), ClampToByte(conv.B))
  201. End Operator
  202. Public Shared Widening Operator CType(bru As SolidColorBrush) As MyColor
  203. Return New MyColor(bru.Color)
  204. End Operator
  205. Public Shared Widening Operator CType(conv As MyColor) As SolidColorBrush
  206. Return New SolidColorBrush(Color.FromArgb(ClampToByte(conv.A), ClampToByte(conv.R), ClampToByte(conv.G), ClampToByte(conv.B)))
  207. End Operator
  208. Public Shared Widening Operator CType(bru As Brush) As MyColor
  209. Return New MyColor(bru)
  210. End Operator
  211. Public Shared Widening Operator CType(conv As MyColor) As Brush
  212. Return New SolidColorBrush(Color.FromArgb(ClampToByte(conv.A), ClampToByte(conv.R), ClampToByte(conv.G), ClampToByte(conv.B)))
  213. End Operator
  214. '颜色运算
  215. Public Shared Operator +(a As MyColor, b As MyColor) As MyColor
  216. Return New MyColor With {.A = a.A + b.A, .B = a.B + b.B, .G = a.G + b.G, .R = a.R + b.R}
  217. End Operator
  218. Public Shared Operator -(a As MyColor, b As MyColor) As MyColor
  219. Return New MyColor With {.A = a.A - b.A, .B = a.B - b.B, .G = a.G - b.G, .R = a.R - b.R}
  220. End Operator
  221. Public Shared Operator *(a As MyColor, b As Double) As MyColor
  222. Return New MyColor With {.A = a.A * b, .B = a.B * b, .G = a.G * b, .R = a.R * b}
  223. End Operator
  224. Public Shared Operator /(a As MyColor, b As Double) As MyColor
  225. Return New MyColor With {.A = a.A / b, .B = a.B / b, .G = a.G / b, .R = a.R / b}
  226. End Operator
  227. Public Shared Operator =(a As MyColor, b As MyColor) As Boolean
  228. If IsNothing(a) AndAlso IsNothing(b) Then Return True
  229. If IsNothing(a) OrElse IsNothing(b) Then Return False
  230. Return a.A = b.A AndAlso a.R = b.R AndAlso a.G = b.G AndAlso a.B = b.B
  231. End Operator
  232. Public Shared Operator <>(a As MyColor, b As MyColor) As Boolean
  233. If IsNothing(a) AndAlso IsNothing(b) Then Return False
  234. If IsNothing(a) OrElse IsNothing(b) Then Return True
  235. Return Not (a.A = b.A AndAlso a.R = b.R AndAlso a.G = b.G AndAlso a.B = b.B)
  236. End Operator
  237. ''' <summary>
  238. ''' 获取两颜色间的百分比,基于 RGB 计算。
  239. ''' </summary>
  240. Public Shared Function Lerp(ValueA As MyColor, ValueB As MyColor, Percent As Double) As MyColor
  241. Return Round(ValueA * (1 - Percent) + ValueB * Percent, 6)
  242. End Function
  243. Public Shared Function Round(col As MyColor, Optional w As Integer = 0) As MyColor
  244. Return New MyColor With {.A = Math.Round(col.A, w), .R = Math.Round(col.R, w), .G = Math.Round(col.G, w), .B = Math.Round(col.B, w)}
  245. End Function
  246. ''' <summary>
  247. ''' 将一个数字限制为 0~255 的 Byte 值。
  248. ''' </summary>
  249. Public Shared Function ClampToByte(d As Double) As Byte
  250. If d < 0 Then d = 0
  251. If d > 255 Then d = 255
  252. Return Math.Round(d)
  253. End Function
  254. '构造函数
  255. Public Sub New()
  256. End Sub
  257. Public Sub New(col As Color)
  258. Me.A = col.A
  259. Me.R = col.R
  260. Me.G = col.G
  261. Me.B = col.B
  262. End Sub
  263. Public Sub New(HexString As String)
  264. Dim StringColor As Media.Color = ColorConverter.ConvertFromString(HexString)
  265. A = StringColor.A
  266. R = StringColor.R
  267. G = StringColor.G
  268. B = StringColor.B
  269. End Sub
  270. Public Sub New(newA As Double, col As MyColor)
  271. Me.A = newA
  272. Me.R = col.R
  273. Me.G = col.G
  274. Me.B = col.B
  275. End Sub
  276. Public Sub New(newR As Double, newG As Double, newB As Double)
  277. Me.A = 255
  278. Me.R = newR
  279. Me.G = newG
  280. Me.B = newB
  281. End Sub
  282. Public Sub New(newA As Double, newR As Double, newG As Double, newB As Double)
  283. Me.A = newA
  284. Me.R = newR
  285. Me.G = newG
  286. Me.B = newB
  287. End Sub
  288. Public Sub New(brush As Brush)
  289. Dim Color As Color = CType(brush, SolidColorBrush).Color
  290. A = Color.A
  291. R = Color.R
  292. G = Color.G
  293. B = Color.B
  294. End Sub
  295. Public Sub New(brush As SolidColorBrush)
  296. Dim Color As Color = brush.Color
  297. A = Color.A
  298. R = Color.R
  299. G = Color.G
  300. B = Color.B
  301. End Sub
  302. Public Sub New(obj As Object)
  303. If obj Is Nothing Then
  304. A = 255 : R = 255 : G = 255 : B = 255
  305. Else
  306. If TypeOf obj Is SolidColorBrush Then
  307. '避免反复获取 Color 对象造成性能下降
  308. Dim Color As Color = CType(obj, SolidColorBrush).Color
  309. A = Color.A
  310. R = Color.R
  311. G = Color.G
  312. B = Color.B
  313. Else
  314. A = obj.A
  315. R = obj.R
  316. G = obj.G
  317. B = obj.B
  318. End If
  319. End If
  320. End Sub
  321. 'HSL
  322. Public Function Hue(v1 As Double, v2 As Double, vH As Double) As Double
  323. If vH < 0 Then vH += 1
  324. If vH > 1 Then vH -= 1
  325. If vH < 0.16667 Then Return v1 + (v2 - v1) * 6 * vH
  326. If vH < 0.5 Then Return v2
  327. If vH < 0.66667 Then Return v1 + (v2 - v1) * (4 - vH * 6)
  328. Return v1
  329. End Function
  330. Public Function FromHSL(sH As Double, sS As Double, sL As Double) As MyColor
  331. A = 255
  332. If sS = 0 Then
  333. R = sL * 2.55 : G = R : B = R
  334. Return Me
  335. End If
  336. Dim H = sH / 360
  337. Dim S = sS / 100
  338. Dim L = sL / 100
  339. S = If(L < 0.5, S * L + L, S * (1.0 - L) + L)
  340. L = 2 * L - S
  341. R = 255 * Hue(L, S, H + 1 / 3)
  342. G = 255 * Hue(L, S, H)
  343. B = 255 * Hue(L, S, H - 1 / 3)
  344. Return Me
  345. End Function
  346. Public Function FromHSL2(sH As Double, sS As Double, sL As Double) As MyColor
  347. A = 255
  348. If sS = 0 Then
  349. R = sL * 2.55 : G = R : B = R
  350. Return Me
  351. End If
  352. '初始化
  353. sH = (sH + 3600000) Mod 360
  354. Dim cent As Double() = {
  355. +0.1, -0.06, -0.3, '0, 30, 60
  356. -0.19, -0.15, -0.24, '90, 120, 150
  357. -0.32, -0.09, +0.18, '180, 210, 240
  358. +0.05, -0.12, -0.02, '270, 300, 330
  359. +0.1, -0.06} '最后两位与前两位一致,加是变亮,减是变暗
  360. '计算色调对应的亮度片区
  361. Dim center As Double = sH / 30.0
  362. Dim intCenter As Integer = Math.Floor(center) '亮度片区编号
  363. center = 50 - (
  364. (1 - center + intCenter) * cent(intCenter) + (center - intCenter) * cent(intCenter + 1)
  365. ) * sS
  366. sL = If(sL < center, sL / center, 1 + (sL - center) / (100 - center)) * 50
  367. FromHSL(sH, sS, sL)
  368. Return Me
  369. End Function
  370. Public Overrides Function ToString() As String
  371. Return "(" & A & "," & R & "," & G & "," & B & ")"
  372. End Function
  373. Public Overrides Function Equals(obj As Object) As Boolean
  374. Return Me = obj
  375. End Function
  376. End Class
  377. ''' <summary>
  378. ''' 支持负数与浮点数的矩形。
  379. ''' </summary>
  380. Public Class MyRect
  381. '属性
  382. Public Property Width As Double = 0
  383. Public Property Height As Double = 0
  384. Public Property Left As Double = 0
  385. Public Property Top As Double = 0
  386. '构造函数
  387. Public Sub New()
  388. End Sub
  389. Public Sub New(left As Double, top As Double, width As Double, height As Double)
  390. Me.Left = left
  391. Me.Top = top
  392. Me.Width = width
  393. Me.Height = height
  394. End Sub
  395. End Class
  396. ''' <summary>
  397. ''' 模块加载状态枚举。
  398. ''' </summary>
  399. Public Enum LoadState
  400. Waiting
  401. Loading
  402. Finished
  403. Failed
  404. Canceled
  405. End Enum
  406. ''' <summary>
  407. ''' 执行返回值。
  408. ''' </summary>
  409. Public Enum ProcessReturnValues
  410. ''' <summary>
  411. ''' 执行成功。
  412. ''' </summary>
  413. Success = 0
  414. ''' <summary>
  415. ''' 执行失败。
  416. ''' </summary>
  417. Fail = 1
  418. ''' <summary>
  419. ''' 执行时出现未经处理的异常。
  420. ''' </summary>
  421. Exception = 2
  422. ''' <summary>
  423. ''' 执行超时。
  424. ''' </summary>
  425. Timeout = 3
  426. ''' <summary>
  427. ''' 取消执行。可能是由于不满足执行的前置条件。
  428. ''' </summary>
  429. Cancel = 4
  430. ''' <summary>
  431. ''' 任务成功完成。
  432. ''' </summary>
  433. TaskDone = 5
  434. End Enum
  435. #End Region
  436. #Region "文件"
  437. '=============================
  438. ' 注册表
  439. '=============================
  440. ''' <summary>
  441. ''' 读取注册表键。如果失败则返回默认值。
  442. ''' </summary>
  443. Public Function ReadReg(Key As String, Optional DefaultValue As String = "") As String
  444. Try
  445. Return If(My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, False)?.GetValue(Key), DefaultValue)
  446. Catch ex As Exception
  447. Logger.Error(ex, $"读取注册表出错:{Key}", LogBehavior.Toast)
  448. Return DefaultValue
  449. End Try
  450. End Function
  451. ''' <summary>
  452. ''' 写入注册表键。
  453. ''' </summary>
  454. Public Sub WriteReg(Key As String, Value As String, Optional ThrowException As Boolean = False)
  455. Try
  456. Dim SubKey As Microsoft.Win32.RegistryKey = My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, True)
  457. If SubKey Is Nothing Then SubKey = My.Computer.Registry.CurrentUser.CreateSubKey("Software\" & RegFolder) '如果不存在就创建
  458. SubKey.SetValue(Key, Value)
  459. Catch ex As Exception
  460. Logger.Warn(ex, $"写入注册表出错:{Key}", If(ThrowException, LogBehavior.Toast, LogBehavior.ToastIfDebug))
  461. If ThrowException Then Throw
  462. End Try
  463. End Sub
  464. ''' <summary>
  465. ''' 是否存在某个注册表键。
  466. ''' </summary>
  467. Public Function HasReg(Key As String) As Boolean
  468. Return ReadReg(Key, Nothing) IsNot Nothing
  469. End Function
  470. ''' <summary>
  471. ''' 删除注册表键。
  472. ''' </summary>
  473. Public Sub DeleteReg(Key As String, Optional ThrowException As Boolean = False)
  474. Try
  475. Dim SubKey As Microsoft.Win32.RegistryKey = My.Computer.Registry.CurrentUser.OpenSubKey("Software\" & RegFolder, True)
  476. If SubKey?.GetValue(Key) Is Nothing Then Return
  477. SubKey.DeleteValue(Key)
  478. Catch ex As Exception
  479. Logger.Warn(ex, $"删除注册表出错:{Key}", If(ThrowException, LogBehavior.Toast, LogBehavior.ToastIfDebug))
  480. If ThrowException Then Throw
  481. End Try
  482. End Sub
  483. '=============================
  484. ' ini
  485. '=============================
  486. Private ReadOnly IniCache As New ConcurrentDictionary(Of String, ConcurrentDictionary(Of String, String))
  487. ''' <summary>
  488. ''' 清除某 ini 文件的运行时缓存。
  489. ''' </summary>
  490. ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
  491. Public Sub IniClearCache(FileName As String)
  492. If Not FileName.Contains(":\") Then FileName = $"{Paths.Base}PCL\{FileName}.ini"
  493. IniCache.Remove(FileName)
  494. End Sub
  495. ''' <summary>
  496. ''' 获取 ini 文件缓存。如果没有,则新读取 ini 文件内容。
  497. ''' 在文件不存在或读取失败时返回 Nothing。
  498. ''' </summary>
  499. ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
  500. Private Function IniGetContent(FileName As String) As ConcurrentDictionary(Of String, String)
  501. Try
  502. '还原文件路径
  503. If Not FileName.Contains(":\") Then FileName = $"{Paths.Base}PCL\{FileName}.ini"
  504. '检索缓存
  505. Dim Cache As ConcurrentDictionary(Of String, String) = Nothing
  506. If IniCache.TryGetValue(FileName, Cache) Then Return Cache
  507. '读取文件
  508. If Not FileUtils.Exists(FileName) Then Return Nothing
  509. Dim Ini As New ConcurrentDictionary(Of String, String)
  510. For Each Line In FileUtils.ReadAsLines(FileName, True)
  511. Dim Index As Integer = Line.IndexOfF(":")
  512. If Index > 0 Then Ini(Line.Substring(0, Index)) = Line.Substring(Index + 1) '可能会有重复键,见 #3616
  513. Next
  514. IniCache(FileName) = Ini
  515. Return Ini
  516. Catch ex As Exception
  517. Logger.Error(ex, $"生成 ini 文件缓存失败({FileName})", LogBehavior.Toast)
  518. Return Nothing
  519. End Try
  520. End Function
  521. ''' <summary>
  522. ''' 读取 ini 文件。这可能会使用到缓存。
  523. ''' </summary>
  524. ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
  525. ''' <param name="Key">键。</param>
  526. ''' <param name="DefaultValue">没有找到键时返回的默认值。</param>
  527. Public Function ReadIni(FileName As String, Key As String, Optional DefaultValue As String = "") As String
  528. Dim Content = IniGetContent(FileName)
  529. If Content Is Nothing Then Return DefaultValue
  530. Return Content.GetOrDefault(Key, DefaultValue)
  531. End Function
  532. ''' <summary>
  533. ''' 判断 ini 文件中是否包含某个键。这可能会使用到缓存。
  534. ''' </summary>
  535. Public Function HasIniKey(FileName As String, Key As String) As Boolean
  536. Dim Content = IniGetContent(FileName)
  537. Return Content IsNot Nothing AndAlso Content.ContainsKey(Key)
  538. End Function
  539. ''' <summary>
  540. ''' 从 ini 文件中移除某个键。这会更新缓存。
  541. ''' </summary>
  542. Public Sub DeleteIniKey(FileName As String, Key As String)
  543. WriteIni(FileName, Key, Nothing)
  544. End Sub
  545. ''' <summary>
  546. ''' 写入 ini 文件,这会更新缓存。
  547. ''' 若 Value 为 Nothing,则删除该键。
  548. ''' </summary>
  549. ''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
  550. ''' <param name="Key">键。</param>
  551. ''' <param name="Value">值。</param>
  552. Public Sub WriteIni(FileName As String, Key As String, Value As String)
  553. Try
  554. '预处理
  555. If Key.Contains(":") Then Throw New Exception($"尝试写入 ini 文件 {FileName} 的键名中包含了冒号:{Key}")
  556. Key = Key.ReplaceLineEndings("")
  557. Value = Value?.ReplaceLineEndings("")
  558. '获取目前文件
  559. Dim Content = IniGetContent(FileName)
  560. If Content Is Nothing Then Content = New ConcurrentDictionary(Of String, String)
  561. '更新值
  562. If Value Is Nothing Then
  563. If Not Content.Remove(Key) Then Return '无需处理
  564. Else
  565. Dim ExistingValue As String = Nothing
  566. If Content.TryGetValue(Key, ExistingValue) AndAlso ExistingValue = Value Then Return '无需处理
  567. Content(Key) = Value
  568. End If
  569. '写入文件
  570. Dim FileContent As New StringBuilder
  571. For Each Pair In Content
  572. FileContent.Append(Pair.Key)
  573. FileContent.Append(":")
  574. FileContent.Append(Pair.Value)
  575. FileContent.Append(vbCrLf)
  576. Next
  577. If Not FileName.Contains(":\") Then FileName = $"{Paths.Base}PCL\{FileName}.ini"
  578. '处理相对路径
  579. FileName = If(FileName.Contains(":\"), FileName, Paths.Base & FileName)
  580. FileUtils.Write(FileName, FileContent.ToString)
  581. Catch ex As Exception
  582. Logger.Error(ex, $"写入文件失败({FileName} → {Key}:{Value})", LogBehavior.Toast)
  583. End Try
  584. End Sub
  585. '文件校验
  586. ''' <summary>
  587. ''' 检查是否拥有某一文件夹的 I/O 权限。如果文件夹不存在,会返回 False。
  588. ''' </summary>
  589. Public Function CheckPermission(Folder As String) As Boolean
  590. Try
  591. If String.IsNullOrEmpty(Folder) Then Return False
  592. Folder = PathUtils.AddSlashSuffix(Folder)
  593. If Folder.EndsWithF(":\System Volume Information\") OrElse Folder.EndsWithF(":\$RECYCLE.BIN\") Then Return False
  594. If Not DirectoryUtils.Exists(Folder) Then Return False
  595. Dim TestFilePath As String = $"{Folder}CheckPermission{GetUuid()}.txt"
  596. FileUtils.Write(TestFilePath, "临时文件,用于检测该文件夹的权限是否正常。")
  597. FileUtils.Delete(TestFilePath)
  598. Return True
  599. Catch ex As Exception
  600. Logger.Warn(ex, $"没有对文件夹 {Folder} 的权限,请尝试以管理员权限运行 PCL")
  601. Return False
  602. End Try
  603. End Function
  604. ''' <summary>
  605. ''' 检查是否拥有某一文件夹的 I/O 权限。如果出错,则抛出异常。
  606. ''' </summary>
  607. Public Sub CheckPermissionWithException(Folder As String)
  608. If String.IsNullOrWhiteSpace(Folder) Then Throw New ArgumentNullException("文件夹名不能为空!")
  609. Folder = PathUtils.AddSlashSuffix(Folder)
  610. If Folder.EndsWithF(":\System Volume Information\") OrElse Folder.EndsWithF(":\$RECYCLE.BIN\") Then Throw New UnauthorizedAccessException("没有对系统文件夹的权限!")
  611. If Not DirectoryUtils.Exists(Folder) Then Throw New DirectoryNotFoundException("文件夹不存在!")
  612. Dim TestFilePath As String = $"{Folder}CheckPermission{GetUuid()}.txt"
  613. FileUtils.Write(TestFilePath, "临时文件,用于检测该文件夹的权限是否正常。")
  614. FileUtils.Delete(TestFilePath)
  615. End Sub
  616. #End Region
  617. #Region "文本"
  618. Public vbLQ As Char = Convert.ToChar(8220)
  619. Public vbRQ As Char = Convert.ToChar(8221)
  620. ''' <summary>
  621. ''' 不会报错的 Val。
  622. ''' 如果输入有误,返回 0。
  623. ''' </summary>
  624. Public Function Val(Str As Object) As Double
  625. If Str Is Nothing Then Return 0
  626. Try
  627. Return If(TypeOf Str Is String AndAlso Str = "&", 0, Conversion.Val(Str))
  628. Catch
  629. Return 0
  630. End Try
  631. End Function
  632. #End Region
  633. #Region "搜索"
  634. ''' <summary>
  635. ''' 获取搜索文本的相似度。
  636. ''' </summary>
  637. ''' <param name="Source">被搜索的长内容。</param>
  638. ''' <param name="Query">用户输入的搜索文本。</param>
  639. Private Function SearchSimilarity(Source As String, Query As String) As Double
  640. If String.IsNullOrEmpty(Source) OrElse String.IsNullOrEmpty(Query) Then Return 0
  641. Dim qp As Integer = 0, lenSum As Double = 0
  642. Dim str As New StringBuilder(Source.Length)
  643. str.Append(Source.Lower().Replace(" ", ""))
  644. Query = Query.Lower().Replace(" ", "")
  645. Dim sourceLength As Integer = str.Length, queryLength As Integer = Query.Length '用于计算最后因数的长度缓存
  646. If queryLength = 0 Then Return 0
  647. Do While qp < queryLength
  648. '对 qp 作为开始位置计算
  649. Dim sp As Integer = 0, lenMax As Integer = 0, spMax As Integer = 0
  650. Dim currentSourceLength As Integer = str.Length
  651. Do While sp < currentSourceLength
  652. Dim len As Integer = 0
  653. While (qp + len) < queryLength AndAlso (sp + len) < currentSourceLength AndAlso str(sp + len) = Query(qp + len)
  654. len += 1
  655. End While
  656. '存储 len
  657. If len > lenMax Then
  658. lenMax = len
  659. spMax = sp
  660. End If
  661. '根据结果增加 sp
  662. sp += If(len > 0, len, 1)
  663. Loop
  664. If lenMax > 0 Then
  665. str.Remove(spMax, lenMax) '将源中的对应字段移除
  666. '存储 lenSum
  667. Dim IncWeight = (Math.Pow(1.4, 3 + lenMax) - 3.6) '根据长度加成
  668. IncWeight *= 1 + 0.3 * Math.Max(0, 3 - Math.Abs(qp - spMax)) '根据位置加成
  669. lenSum += IncWeight
  670. End If
  671. qp += If(lenMax > 0, lenMax, 1)
  672. Loop
  673. '计算结果:重复字段量 × 源长度影响比例
  674. Return (lenSum / queryLength) * (3 / Math.Sqrt(sourceLength + 15)) * If(queryLength <= 2, 3 - queryLength, 1)
  675. End Function
  676. ''' <summary>
  677. ''' 获取多段文本加权后的相似度。
  678. ''' </summary>
  679. Private Function SearchSimilarityWeighted(Source As List(Of SearchSource), Query As String) As Double
  680. Dim TotalWeight As Double = 0
  681. Dim Sum As Double = 0
  682. For Each Pair In Source
  683. If Pair.Aliases.Any Then
  684. Sum += Pair.Aliases.Max(Function(a) SearchSimilarity(a, Query)) * Pair.Weight
  685. End If
  686. TotalWeight += Pair.Weight
  687. Next
  688. Return Sum / TotalWeight
  689. End Function
  690. ''' <summary>
  691. ''' 用于搜索的项目。
  692. ''' </summary>
  693. Public Class SearchEntry(Of T)
  694. ''' <summary>
  695. ''' 该项目对应的源数据。
  696. ''' </summary>
  697. Public Item As T
  698. ''' <summary>
  699. ''' 该项目用于搜索的文本源。
  700. ''' 在搜索时,会对每个文本源单独加权,但单个文本源内的多个别名只取最高的一个的相似度。
  701. ''' </summary>
  702. Public SearchSource As List(Of SearchSource)
  703. ''' <summary>
  704. ''' 相似度。
  705. ''' </summary>
  706. Public Similarity As Double
  707. ''' <summary>
  708. ''' 是否完全匹配。
  709. ''' </summary>
  710. Public AbsoluteRight As Boolean
  711. Public Overrides Function ToString() As String
  712. Return Math.Round(Similarity, 3) & " - " & Item.ToString()
  713. End Function
  714. End Class
  715. ''' <summary>
  716. ''' 单个用于搜索的文本源。
  717. ''' </summary>
  718. Public Class SearchSource
  719. Public Aliases As String()
  720. Public Weight As Double
  721. Public Sub New(Aliases As String(), Optional Weight As Double = 1)
  722. Me.Aliases = Aliases
  723. Me.Weight = Weight
  724. End Sub
  725. Public Sub New(Text As String, Optional Weight As Double = 1)
  726. Me.Aliases = {Text}
  727. Me.Weight = Weight
  728. End Sub
  729. End Class
  730. ''' <summary>
  731. ''' 进行多段文本加权搜索,获取相似度较高的数项结果。
  732. ''' 在搜索时,会对每个文本源单独加权,但单个文本源内的多个别名只取最高的一个的相似度。
  733. ''' 这会修改 Entries 中每项的 Similarity 与 AbsoluteRight 字段。
  734. ''' </summary>
  735. ''' <param name="MaxBlurCount">返回的最大模糊结果数。</param>
  736. ''' <param name="MinBlurSimilarity">返回结果要求的最低相似度。</param>
  737. Public Function Search(Of T)(Entries As List(Of SearchEntry(Of T)), Query As String, Optional MaxBlurCount As Integer = 5, Optional MinBlurSimilarity As Double = 0.1) As List(Of SearchEntry(Of T))
  738. '初始化
  739. Dim ResultList As New List(Of SearchEntry(Of T))
  740. If Not Entries.Any() Then Return ResultList
  741. '进行搜索,获取相似信息
  742. Dim QueryParts = Query.Split(" ", True)
  743. Dim Candidates As New List(Of SearchEntry(Of T))
  744. For Each Entry In Entries
  745. Entry.Similarity = SearchSimilarityWeighted(Entry.SearchSource, Query)
  746. Entry.AbsoluteRight =
  747. QueryParts.All( '对于按空格分割的每一段
  748. Function(QueryPart) Entry.SearchSource.Any( '若与任意一个搜索源完全匹配,则标记为完全匹配项
  749. Function(Source) Source.Aliases.Any(
  750. Function([Alias]) [Alias].Replace(" ", "").ContainsIgnoreCase(QueryPart))))
  751. If Entry.AbsoluteRight OrElse Entry.Similarity >= MinBlurSimilarity Then Candidates.Add(Entry)
  752. Next
  753. '按照相似度进行排序
  754. Candidates = Candidates.SortByComparison(Function(Left, Right) If(Left.AbsoluteRight <> Right.AbsoluteRight, Left.AbsoluteRight, Left.Similarity > Right.Similarity))
  755. '返回结果
  756. Dim BlurCount As Integer = 0
  757. For Each Entry In Candidates
  758. If Entry.AbsoluteRight Then
  759. ResultList.Add(Entry) '完全匹配,直接加入
  760. Else
  761. If BlurCount = MaxBlurCount Then Exit For
  762. ResultList.Add(Entry)
  763. BlurCount += 1 '模糊结果计数
  764. End If
  765. Next
  766. Return ResultList
  767. End Function
  768. #End Region
  769. #Region "系统"
  770. ''' <summary>
  771. ''' 为 Task 设置超时,在超时时抛出 TimeoutException。
  772. ''' </summary>
  773. <Extension> Public Function GetResultWithTimeout(Of T)(TargetTask As Task(Of T), TokenSource As CancellationTokenSource, TimeoutMs As Integer) As T
  774. Dim DelayTask = Task.Delay(TimeoutMs)
  775. If Task.WhenAny(TargetTask, DelayTask).ConfigureAwait(False).GetAwaiter().GetResult() Is DelayTask Then
  776. TokenSource.Cancel()
  777. Throw New TimeoutException($"任务超时({TimeoutMs} ms)")
  778. End If
  779. Return TargetTask.Run()
  780. End Function
  781. ''' <summary>
  782. ''' 为 Task 设置超时,在超时时抛出 TimeoutException。
  783. ''' </summary>
  784. <Extension> Public Sub GetResultWithTimeout(TargetTask As Task, TokenSource As CancellationTokenSource, TimeoutMs As Integer)
  785. Dim DelayTask = Task.Delay(TimeoutMs)
  786. If Task.WhenAny(TargetTask, DelayTask).ConfigureAwait(False).GetAwaiter().GetResult() Is DelayTask Then
  787. TokenSource.Cancel()
  788. Throw New TimeoutException($"任务超时({TimeoutMs} ms)")
  789. End If
  790. TargetTask.Run()
  791. End Sub
  792. ''' <summary>
  793. ''' 可用于临时存放文件的,不含任何特殊字符的文件夹路径,以“\”结尾。
  794. ''' </summary>
  795. Public PathPure As String = GetPureASCIIDir()
  796. Private Function GetPureASCIIDir() As String
  797. If Paths.Base.IsAsciiOnly() Then
  798. Return Paths.Base & "PCL\"
  799. ElseIf Paths.AppDataThenName.IsAsciiOnly() Then
  800. Return Paths.AppDataThenName
  801. ElseIf PathTemp.IsAsciiOnly() Then
  802. Return PathTemp
  803. Else
  804. Return OsDrive & "ProgramData\PCL\"
  805. End If
  806. End Function
  807. ''' <summary>
  808. ''' 指示接取到这个异常的函数进行重试。
  809. ''' </summary>
  810. Public Class RestartException
  811. Inherits Exception
  812. End Class
  813. ''' <summary>
  814. ''' 以管理员权限运行当前程序,并等待程序运行结束。
  815. ''' 返回程序的返回代码,如果运行失败将抛出异常。
  816. ''' </summary>
  817. Public Function RunAsAdmin(Argument As String) As Integer
  818. Dim NewProcess = StartProcess(New ProcessStartInfo(PathExe) With {.Verb = "runas", .Arguments = Argument})
  819. NewProcess.WaitForExit()
  820. Return NewProcess.ExitCode
  821. End Function
  822. Private Uuid As Integer = 1
  823. Private UuidLock As Object
  824. ''' <summary>
  825. ''' 获取一个全程序内不会重复的数字(伪 Uuid)。
  826. ''' </summary>
  827. Public Function GetUuid() As Integer
  828. If UuidLock Is Nothing Then UuidLock = New Object
  829. SyncLock UuidLock
  830. Uuid += 1
  831. Return Uuid
  832. End SyncLock
  833. End Function
  834. '时间相关
  835. ''' <summary>
  836. ''' 获取一个单调递增时间值(毫秒)。
  837. ''' </summary>
  838. Public Function GetTimeMs() As Long
  839. Return Stopwatch.GetTimestamp() \ (Stopwatch.Frequency \ 1000L)
  840. End Function
  841. ''' <summary>
  842. ''' 获取十进制 Unix 时间戳。
  843. ''' </summary>
  844. Public Function GetUnixTimestampUtc() As Long
  845. Return (Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000
  846. End Function
  847. ''' <summary>
  848. ''' 时间戳转化为日期。
  849. ''' </summary>
  850. Public Function GetDate(timeStamp As Integer) As Date
  851. Return DateTimeOffset.FromUnixTimeSeconds(timeStamp).LocalDateTime
  852. End Function
  853. ''' <summary>
  854. ''' 将 UTC 时间转化为当前时区的时间。
  855. ''' </summary>
  856. Public Function GetLocalTime(UtcDate As Date) As Date
  857. Return DateTime.SpecifyKind(UtcDate, DateTimeKind.Utc).ToLocalTime
  858. End Function
  859. ''' <summary>
  860. ''' 用于储存 RaiseByMouse 的 EventArgs。
  861. ''' </summary>
  862. Public NotInheritable Class RouteEventArgs
  863. Inherits EventArgs
  864. Public RaiseByMouse As Boolean
  865. Public Handled As Boolean = False
  866. Public Sub New(Optional RaiseByMouse As Boolean = False)
  867. Me.RaiseByMouse = RaiseByMouse
  868. End Sub
  869. End Class
  870. ''' <summary>
  871. ''' 启动进程并返回进程实例,若失败则抛出异常。
  872. ''' 会自动进行长路径处理。
  873. ''' </summary>
  874. Public Function StartProcess(FileName As String, Optional Arguments As String = "") As Process
  875. Return StartProcess(New ProcessStartInfo With {.FileName = FileName, .Arguments = Arguments})
  876. End Function
  877. ''' <summary>
  878. ''' 启动进程并返回进程实例,若失败则抛出异常。
  879. ''' 会自动进行长路径处理。
  880. ''' </summary>
  881. Public Function StartProcess(StartInfo As ProcessStartInfo) As Process
  882. StartInfo.FileName = PathUtils.ToShortPath(StartInfo.FileName)
  883. If Not String.IsNullOrEmpty(StartInfo.WorkingDirectory) Then StartInfo.WorkingDirectory = PathUtils.ToShortPath(StartInfo.WorkingDirectory)
  884. Logger.Info($"启动进程:{StartInfo.FileName} {StartInfo.Arguments}")
  885. Return Process.Start(StartInfo)
  886. End Function
  887. ''' <summary>
  888. ''' 在新的工作线程中执行代码。
  889. ''' </summary>
  890. Public Function RunInNewThread(Action As Action, Optional Name As String = Nothing, Optional Priority As ThreadPriority = ThreadPriority.Normal) As Thread
  891. Dim th As New Thread(
  892. Sub()
  893. Try
  894. Action()
  895. Catch ex As Exception
  896. Logger.Error(ex, $"{Name}:线程执行失败")
  897. End Try
  898. End Sub) With {.Name = If(Name, "Runtime New Invoke " & GetUuid() & "#"), .Priority = Priority}
  899. th.Start()
  900. Return th
  901. End Function
  902. ''' <summary>
  903. ''' 确保在 UI 线程中执行代码。
  904. ''' 如果当前并非 UI 线程,则会阻断当前线程,直至 UI 线程执行完毕。
  905. ''' 为防止线程互锁,请仅在开始加载动画、从 UI 获取输入时使用!
  906. ''' </summary>
  907. Public Function RunInUiWait(Of Output)(Action As Func(Of Output)) As Output
  908. If RunInUi() Then
  909. Return Action()
  910. Else
  911. Return Application.Current.Dispatcher.Invoke(Action)
  912. End If
  913. End Function
  914. ''' <summary>
  915. ''' 确保在 UI 线程中执行代码。
  916. ''' 如果当前并非 UI 线程,则会阻断当前线程,直至 UI 线程执行完毕。
  917. ''' 为防止线程互锁,请仅在开始加载动画、从 UI 获取输入时使用!
  918. ''' </summary>
  919. Public Sub RunInUiWait(Action As Action)
  920. If RunInUi() Then
  921. Action()
  922. Else
  923. Application.Current.Dispatcher.Invoke(Action)
  924. End If
  925. End Sub
  926. ''' <summary>
  927. ''' 确保在 UI 线程中执行代码,代码按触发顺序执行。
  928. ''' 如果当前并非 UI 线程,也不阻断当前线程的执行。
  929. ''' </summary>
  930. Public Sub RunInUi(Action As Action, Optional ForceWaitUntilLoaded As Boolean = False)
  931. If ForceWaitUntilLoaded Then
  932. Application.Current.Dispatcher.InvokeAsync(Action, Threading.DispatcherPriority.Loaded)
  933. ElseIf RunInUi() Then
  934. Action()
  935. Else
  936. Application.Current.Dispatcher.InvokeAsync(Action)
  937. End If
  938. End Sub
  939. ''' <summary>
  940. ''' 确保在工作线程中执行代码。
  941. ''' </summary>
  942. Public Sub RunInThread(Action As Action)
  943. If RunInUi() Then
  944. RunInNewThread(Action, "Invoke " & GetUuid())
  945. Else
  946. Action()
  947. End If
  948. End Sub
  949. ''' <summary>
  950. ''' 按照既定的函数进行选择排序。
  951. ''' 返回第一个对象是否应该排在前面(a > b)。
  952. ''' </summary>
  953. <Extension> Public Function SortByComparison(Of T)(List As IList(Of T), SortRule As Func(Of T, T, Boolean)) As List(Of T)
  954. Dim NewList As New List(Of T)
  955. While List.Any
  956. Dim Highest = List(0)
  957. For i = 1 To List.Count - 1
  958. If SortRule(List(i), Highest) Then Highest = List(i)
  959. Next
  960. List.Remove(Highest)
  961. NewList.Add(Highest)
  962. End While
  963. Return NewList
  964. End Function
  965. ''' <summary>
  966. ''' 获取程序启动参数。
  967. ''' </summary>
  968. ''' <param name="Name">参数名。</param>
  969. ''' <param name="DefaultValue">默认值。</param>
  970. Public Function GetProgramArgument(Name As String, Optional DefaultValue As Object = "")
  971. Dim AllArguments() As String = Command.Split(" ")
  972. For i = 0 To AllArguments.Length - 1
  973. If AllArguments(i) = "-" & Name Then
  974. If AllArguments.Length = i + 1 OrElse AllArguments(i + 1).StartsWithF("-") Then Return True
  975. Return AllArguments(i + 1)
  976. End If
  977. Next
  978. Return DefaultValue
  979. End Function
  980. ''' <summary>
  981. ''' 打开网页。
  982. ''' </summary>
  983. Public Sub OpenWebsite(Url As String)
  984. Try
  985. If Not Url.StartsWithF("http", True) AndAlso Not Url.StartsWithF("minecraft://", True) AndAlso Not Url.StartsWithF("minecraft-preview://", True) Then
  986. Throw New Exception(Url & " 不是一个有效的网址,它必须以 http 开头!")
  987. End If
  988. Logger.Info($"正在打开网页:{Url}")
  989. StartProcess(Url)
  990. Catch ex As Exception
  991. Logger.Warn(ex, $"无法打开网页({Url})")
  992. ClipboardSet(Url, False)
  993. MyMsgBox("可能由于浏览器未正确配置,PCL 无法为你打开网页。" & vbCrLf & "网址已经复制到剪贴板,若有需要可以手动粘贴访问。" & vbCrLf &
  994. $"网址:{Url}", "无法打开网页")
  995. End Try
  996. End Sub
  997. ''' <summary>
  998. ''' 打开 explorer。
  999. ''' 若不以 \ 结尾,则将视作文件路径,打开并选中此文件。
  1000. ''' </summary>
  1001. Public Sub OpenExplorer(Location As String)
  1002. Try
  1003. Location = Location.Replace("/", "\").Trim(" "c, """"c)
  1004. Logger.Info($"正在打开资源管理器:{Location}")
  1005. If Location.EndsWithF("\") Then
  1006. StartProcess(Location)
  1007. Else
  1008. StartProcess("explorer", $"/select,""{Location}""")
  1009. End If
  1010. Catch ex As Exception
  1011. Logger.Error(ex, "打开资源管理器失败,请尝试关闭安全软件(如 360 安全卫士)", LogBehavior.Alert)
  1012. End Try
  1013. End Sub
  1014. ''' <summary>
  1015. ''' 设置剪贴板。将在另一线程运行,且不会抛出异常。
  1016. ''' </summary>
  1017. Public Sub ClipboardSet(Text As String, Optional ShowSuccessHint As Boolean = True)
  1018. RunInThread(
  1019. Sub()
  1020. Try
  1021. Retrier.Attempt(delay:=Function(Attempt) TimeSpan.FromMilliseconds(200), maxAttempts:=4, isRetryAllowed:=Function(ex) True, action:=
  1022. Sub()
  1023. RunInUi(
  1024. Sub()
  1025. My.Computer.Clipboard.Clear()
  1026. If Not String.IsNullOrEmpty(Text) Then My.Computer.Clipboard.SetText(Text)
  1027. End Sub)
  1028. End Sub)
  1029. If ShowSuccessHint Then Hint("已成功复制!", HintType.Green)
  1030. Catch ex As Exception
  1031. Logger.Error(ex, "可能由于剪贴板被其他程序占用,文本复制失败", LogBehavior.Toast)
  1032. End Try
  1033. End Sub)
  1034. End Sub
  1035. ''' <summary>
  1036. ''' 获取剪贴板文本。将在 UI 线程运行,且不会抛出异常。
  1037. ''' </summary>
  1038. Public Function ClipboardGetText() As String
  1039. Dim Result As String = Nothing
  1040. RunInUiWait(
  1041. Sub()
  1042. Try
  1043. If My.Computer.Clipboard.ContainsText() Then Result = My.Computer.Clipboard.GetText()
  1044. Catch ex As Exception
  1045. Logger.Warn(ex, "获取剪贴板文本失败")
  1046. End Try
  1047. End Sub)
  1048. Return Result
  1049. End Function
  1050. ''' <summary>
  1051. ''' 将程序中的资源释放到文件。
  1052. ''' </summary>
  1053. Public Sub ExtractResources(FilePath As String, ResourceName As String)
  1054. Dim Resource = My.Resources.ResourceManager.GetObject(ResourceName)
  1055. If TypeOf Resource Is Byte() Then
  1056. Dim Bytes = DirectCast(Resource, Byte())
  1057. If FileUtils.Exists(FilePath) AndAlso CryptographyUtils.ComputeFileHash(FilePath) = CryptographyUtils.ComputeHash(Bytes) Then Return
  1058. Logger.Info($"将资源写入到文件:{ResourceName} → {FilePath}")
  1059. FileUtils.Write(FilePath, Bytes)
  1060. ElseIf TypeOf Resource Is String Then
  1061. Dim Content = DirectCast(Resource, String)
  1062. If FileUtils.Exists(FilePath) AndAlso FileUtils.ReadAsString(FilePath) = Content Then Return
  1063. Logger.Info($"将资源写入到文件:{ResourceName} → {FilePath}")
  1064. FileUtils.Write(FilePath, Content)
  1065. Else
  1066. Throw New Exception($"资源 {ResourceName} 的类型不支持:{Resource.GetType.Name}")
  1067. End If
  1068. End Sub
  1069. #End Region
  1070. #Region "UI"
  1071. 'DPI 转换
  1072. Public ReadOnly DPI As Integer = System.Drawing.Graphics.FromHwnd(IntPtr.Zero).DpiX
  1073. ''' <summary>
  1074. ''' 将经过 DPI 缩放的 WPF 尺寸转化为实际的像素尺寸。
  1075. ''' </summary>
  1076. Public Function GetPixelSize(WPFSize As Double) As Double
  1077. Return WPFSize / 96 * DPI
  1078. End Function
  1079. ''' <summary>
  1080. ''' 将实际的像素尺寸转化为经过 DPI 缩放的 WPF 尺寸。
  1081. ''' </summary>
  1082. Public Function GetWPFSize(PixelSize As Double) As Double
  1083. Return PixelSize * 96 / DPI
  1084. End Function
  1085. ''' <summary>
  1086. ''' 将 XML 转换为对应 UI 对象。
  1087. ''' 注意:性能较差,不应大量使用。
  1088. ''' </summary>
  1089. Public Function GetObjectFromXML(Str As XElement)
  1090. Return GetObjectFromXML(Str.ToString)
  1091. End Function
  1092. ''' <summary>
  1093. ''' 将 XML 转换为对应 UI 对象。
  1094. ''' 注意:性能较差,不应大量使用。
  1095. ''' </summary>
  1096. Public Function GetObjectFromXML(Str As String) As Object
  1097. Str = Str. '兼容旧版自定义事件写法
  1098. Replace("EventType=""", "local:CustomEventService.EventType=""").
  1099. Replace("EventData=""", "local:CustomEventService.EventData=""").
  1100. Replace("EventType='", "local:CustomEventService.EventType='").
  1101. Replace("EventData='", "local:CustomEventService.EventData='").
  1102. Replace("Property=""EventType""", "Property=""local:CustomEventService.EventType""").
  1103. Replace("Property=""EventData""", "Property=""local:CustomEventService.EventData""").
  1104. Replace("Property='EventType'", "Property='local:CustomEventService.EventType'").
  1105. Replace("Property='EventData'", "Property='local:CustomEventService.EventData'")
  1106. Using Stream As New MemoryStream(Encoding.UTF8.GetBytes(Str))
  1107. '类型检查
  1108. Using Reader As New XamlXmlReader(Stream)
  1109. While Reader.Read()
  1110. For Each BlackListType In {GetType(WebBrowser), GetType(Frame), GetType(MediaElement), GetType(ObjectDataProvider), GetType(XamlReader), GetType(Window), GetType(XmlDataProvider), GetType(SettingService)}
  1111. If Reader.Type IsNot Nothing AndAlso BlackListType.IsAssignableFrom(Reader.Type.UnderlyingType) Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 类型。")
  1112. If Reader.Value IsNot Nothing AndAlso Reader.Value = BlackListType.Name Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListType.Name} 值。")
  1113. Next
  1114. For Each BlackListMember In {"Code", "FactoryMethod", "Static"}
  1115. If Reader.Member IsNot Nothing AndAlso Reader.Member.Name = BlackListMember Then Throw New UnauthorizedAccessException($"基于安全考虑,不允许使用 {BlackListMember} 成员。")
  1116. Next
  1117. End While
  1118. End Using
  1119. '实际的加载
  1120. Stream.Position = 0
  1121. Using Writer As New StreamWriter(Stream)
  1122. Writer.Write(Str)
  1123. Writer.Flush()
  1124. Stream.Position = 0
  1125. Return Markup.XamlReader.Load(Stream)
  1126. End Using
  1127. End Using
  1128. End Function
  1129. Private ReadOnly UiThreadId As Integer = Thread.CurrentThread.ManagedThreadId
  1130. ''' <summary>
  1131. ''' 当前线程是否为主线程。
  1132. ''' </summary>
  1133. Public Function RunInUi() As Boolean
  1134. Return Thread.CurrentThread.ManagedThreadId = UiThreadId
  1135. End Function
  1136. ''' <summary>
  1137. ''' 检查某个控件是否位于主窗口可视区域内,且控件本身可见。
  1138. ''' </summary>
  1139. <Extension> Public Function IsVisibleInForm(element As FrameworkElement) As Boolean
  1140. If Not element.IsVisible Then Return False
  1141. Dim bounds As Rect = element.TransformToAncestor(FrmMain).TransformBounds(New Rect(0, 0, element.ActualWidth, element.ActualHeight))
  1142. Dim rect As New Rect(0, 0, FrmMain.ActualWidth, FrmMain.ActualHeight)
  1143. Return rect.Contains(bounds.TopLeft) OrElse rect.Contains(bounds.BottomRight)
  1144. End Function
  1145. ''' <summary>
  1146. ''' 控件是否受到 TextTrimming 属性影响,导致内容被截取。
  1147. ''' </summary>
  1148. <Extension> Public Function IsTextTrimmed(TextBlock As TextBlock) As Boolean
  1149. Dim typeface As New Typeface(TextBlock.FontFamily, TextBlock.FontStyle, TextBlock.FontWeight, TextBlock.FontStretch)
  1150. Dim formattedText As New FormattedText(TextBlock.Text, Thread.CurrentThread.CurrentCulture, TextBlock.FlowDirection, typeface, TextBlock.FontSize, TextBlock.Foreground, DPI)
  1151. Return formattedText.Width > TextBlock.ActualWidth
  1152. End Function
  1153. ''' <summary>
  1154. ''' 将布尔值转换为 Visibility。True 转换为 Visible,False 转换为 Collapsed。
  1155. ''' </summary>
  1156. <Extension> Public Function ToVisibility(IsVisible As Boolean) As Visibility
  1157. Return If(IsVisible, Visibility.Visible, Visibility.Collapsed)
  1158. End Function
  1159. #End Region
  1160. #Region "随机"
  1161. Private ReadOnly Random As New Random
  1162. ''' <summary>
  1163. ''' 随机选择其一。
  1164. ''' </summary>
  1165. Public Function RandomOne(Of T)(objects As ICollection(Of T)) As T
  1166. Return objects(RandomInteger(0, objects.Count - 1))
  1167. End Function
  1168. ''' <summary>
  1169. ''' 取随机整数(包含)。
  1170. ''' </summary>
  1171. Public Function RandomInteger(min As Integer, max As Integer) As Integer
  1172. Return Math.Floor((max - min + 1) * Random.NextDouble) + min
  1173. End Function
  1174. ''' <summary>
  1175. ''' 将数组随机打乱。
  1176. ''' </summary>
  1177. <Extension> Public Iterator Function Shuffle(Of T)(Raw As IEnumerable(Of T)) As IEnumerable(Of T)
  1178. Dim RawCopy As New List(Of T)(Raw)
  1179. Do While RawCopy.Any
  1180. Dim i As Integer = RandomInteger(0, RawCopy.Count - 1)
  1181. Yield RawCopy(i)
  1182. RawCopy.RemoveAt(i)
  1183. Loop
  1184. End Function
  1185. #End Region
  1186. End Module