This commit is contained in:
龙腾猫跃
2026-07-02 23:24:29 +08:00
parent 124e3be6bf
commit c6d0457946
193 changed files with 3112 additions and 3747 deletions
+30 -307
View File
@@ -1,21 +1,17 @@
Imports System.Globalization
Imports System.Runtime.CompilerServices
Imports System.Security.Cryptography
Imports System.Threading.Tasks
Imports System.Xaml
Imports Newtonsoft.Json
Public Module ModBase
#Region "声明"
'下列版本信息由更新器自动修改
Public Const VersionBaseName As String = "2.12.8.2" '显示用版本名
Public Const VersionBaseName As String = "2.13.0.0" '显示用版本名
Public Const CommitHash As String = "" 'Commit Hash,由 GitHub Workflow 自动替换
#If RELEASE Then
Public Const VersionCode As Integer = 404 '正式版
Public Const VersionCode As Integer = 406 '正式版
#Else
Public Const VersionCode As Integer = 403 '快照版
Public Const VersionCode As Integer = 405 '快照版
#End If
'版本信息
@@ -41,13 +37,9 @@ Public Module ModBase
''' </summary>
Public Handle As IntPtr
''' <summary>
''' 程序的启动路径,以 \ 结尾。
''' </summary>
Public PathExeFolder As String = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
''' <summary>
''' 包含程序名的完整路径。
''' </summary>
Public PathExe As String = PathExeFolder & AppDomain.CurrentDomain.SetupInformation.ApplicationName
Public PathExe As String = Paths.Base & AppDomain.CurrentDomain.SetupInformation.ApplicationName
''' <summary>
''' 程序内嵌图片文件夹路径,以 / 结尾。
''' </summary>
@@ -57,10 +49,6 @@ Public Module ModBase
''' </summary>
Public PathTemp As String = If(Settings.Get(Of String)("SystemSystemCache") = "", Path.GetTempPath() & "PCL\", Settings.Get(Of String)("SystemSystemCache")).ToString.Replace("/", "\").TrimEnd("\") & "\"
''' <summary>
''' AppData 中的 PCL 文件夹路径,以 \ 结尾。
''' </summary>
Public PathAppdata As String = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) & "\PCL\"
''' <summary>
''' 当前程序的语言。
''' </summary>
Public Lang As String = "zh_CN"
@@ -81,10 +69,6 @@ Public Module ModBase
''' </summary>
Public IsProgramEnding As Boolean = False
''' <summary>
''' 是否为 32 位系统。
''' </summary>
Public Is32BitSystem As Boolean = Not Environment.Is64BitOperatingSystem
''' <summary>
''' 是否使用 GBK 编码。
''' </summary>
Public IsGBKEncoding As Boolean = Encoding.Default.CodePage = 936
@@ -139,7 +123,7 @@ Public Module ModBase
''' <summary>
''' 图标按钮,刷新,0.85x
''' </summary>
Public Const IconButtonRefresh As String = "M875.52 148.48C783.36 56.32 655.36 0 512 0 291.84 0 107.52 138.24 30.72 332.8l122.88 46.08C204.8 230.4 348.16 128 512 128c107.52 0 199.68 40.96 271.36 112.64L640 384h384V0L875.52 148.48zM512 896c-107.52 0-199.68-40.96-271.36-112.64L384 640H0v384l148.48-148.48C240.64 967.68 368.64 1024 512 1024c220.16 0 404.48-138.24 481.28-332.8L870.4 645.12C819.2 793.6 675.84 896 512 896z"
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"
''' <summary>
''' 图标按钮,软盘,1x
''' </summary>
@@ -153,10 +137,18 @@ Public Module ModBase
''' </summary>
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"
''' <summary>
''' 图标按钮,文件夹,1.15x
''' 图标按钮,文件夹,1.1x
''' </summary>
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"
''' <summary>
''' 图标按钮,上箭头,0.95x
''' </summary>
Public Const IconButtonArrowUp As String = "M554 333V853h-85V333l-228 228-60-60L512 170l331 331-60 60L554 333z"
''' <summary>
''' 图标按钮,下箭头,0.95x
''' </summary>
Public Const IconButtonArrowDown As String = "M554 691V171h-85V691L241 463l-60 60L512 854l331-331-60-60L554 691z"
''' <summary>
''' 图标按钮,名片,1.1x
''' </summary>
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"
@@ -436,7 +428,7 @@ Public Module ModBase
Loading
Finished
Failed
Interrupted
Canceled
End Enum
''' <summary>
@@ -531,7 +523,7 @@ Public Module ModBase
''' </summary>
''' <param name="FileName">文件完整路径或简写文件名。简写将会使用“ApplicationName\文件名.ini”作为路径。</param>
Public Sub IniClearCache(FileName As String)
If Not FileName.Contains(":\") Then FileName = $"{PathExeFolder}PCL\{FileName}.ini"
If Not FileName.Contains(":\") Then FileName = $"{Paths.Base}PCL\{FileName}.ini"
IniCache.Remove(FileName)
End Sub
''' <summary>
@@ -542,7 +534,7 @@ Public Module ModBase
Private Function IniGetContent(FileName As String) As ConcurrentDictionary(Of String, String)
Try
'还原文件路径
If Not FileName.Contains(":\") Then FileName = $"{PathExeFolder}PCL\{FileName}.ini"
If Not FileName.Contains(":\") Then FileName = $"{Paths.Base}PCL\{FileName}.ini"
'检索缓存
Dim Cache As ConcurrentDictionary(Of String, String) = Nothing
If IniCache.TryGetValue(FileName, Cache) Then Return Cache
@@ -616,9 +608,9 @@ Public Module ModBase
FileContent.Append(Pair.Value)
FileContent.Append(vbCrLf)
Next
If Not FileName.Contains(":\") Then FileName = $"{PathExeFolder}PCL\{FileName}.ini"
If Not FileName.Contains(":\") Then FileName = $"{Paths.Base}PCL\{FileName}.ini"
'处理相对路径
FileName = If(FileName.Contains(":\"), FileName, PathExeFolder & FileName)
FileName = If(FileName.Contains(":\"), FileName, Paths.Base & FileName)
FileUtils.Write(FileName, FileContent.ToString)
Catch ex As Exception
Logger.Error(ex, $"写入文件失败({FileName} → {Key}:{Value})", LogBehavior.Toast)
@@ -657,142 +649,12 @@ Public Module ModBase
FileUtils.Delete(TestFilePath)
End Sub
''' <summary>
''' 文件的校验规则。
''' </summary>
Public Class FileChecker
''' <summary>
''' 文件的准确大小。
''' 不检查则为 -1。
''' </summary>
Public ActualSize As Long = -1
''' <summary>
''' 文件的最小大小。
''' 不检查则为 -1。
''' </summary>
Public MinSize As Long = -1
''' <summary>
''' 文件的 MD5、SHA1 或 SHA256。会根据输入字符串的长度自动判断种类。
''' 不检查则为 Nothing。
''' </summary>
Public Hash As String = Nothing
''' <summary>
''' 是否可以使用已经存在的文件。
''' </summary>
Public CanUseExistsFile As Boolean = True
''' <summary>
''' 是否要求为 JSON 文件。
''' 即,开头结尾必须为 {} 或 []。
''' </summary>
Public IsJson As Boolean = False
Public Sub New(Optional MinSize As Long = -1, Optional ActualSize As Long = -1, Optional Hash As String = Nothing, Optional CanUseExistsFile As Boolean = True, Optional IsJson As Boolean = False)
Me.ActualSize = ActualSize
Me.MinSize = MinSize
Me.Hash = Hash
Me.CanUseExistsFile = CanUseExistsFile
Me.IsJson = IsJson
End Sub
''' <summary>
''' 检查文件。若成功则返回 Nothing,失败则返回错误的描述文本,描述文本不以句号结尾。不会抛出错误。
''' </summary>
Public Function Check(LocalPath As String) As String
Try
Dim Info = FileUtils.GetInfo(LocalPath)
If Not Info.Exists Then Return "文件不存在:" & LocalPath
Dim FileSize As Long = Info.Length
If ActualSize >= 0 AndAlso ActualSize <> FileSize Then
Return $"文件大小应为 {ActualSize} B,实际为 {FileSize} B" &
If(FileSize < 2000, ",内容为:" & FileUtils.ReadAsString(LocalPath), "")
End If
If MinSize >= 0 AndAlso MinSize > FileSize Then
Return $"文件大小应大于 {MinSize} B,实际为 {FileSize} B" &
If(FileSize < 2000, ",内容为:" & FileUtils.ReadAsString(LocalPath), "")
End If
If Not String.IsNullOrEmpty(Hash) Then
If Hash.Length < 35 Then 'MD5
Dim md5 As String = CryptographyUtils.ComputeFileHash(LocalPath, CryptographyUtils.HashMethod.Md5)
If Hash.Lower <> md5 Then Return "文件 MD5 应为 " & Hash & ",实际为 " & md5
ElseIf Hash.Length = 64 Then 'SHA256
Dim sha256 As String = CryptographyUtils.ComputeFileHash(LocalPath, CryptographyUtils.HashMethod.Sha256)
If Hash.Lower <> sha256 Then Return "文件 SHA256 应为 " & Hash & ",实际为 " & sha256
Else 'SHA1 (40)
Dim sha1 As String = CryptographyUtils.ComputeFileHash(LocalPath, CryptographyUtils.HashMethod.Sha1)
If Hash.Lower <> sha1 Then Return "文件 SHA1 应为 " & Hash & ",实际为 " & sha1
End If
End If
If IsJson Then
Dim Content As String = FileUtils.ReadAsString(LocalPath)
If Content = "" Then Throw New Exception("读取到的文件为空")
Try
GetJson(Content)
Catch ex As Exception
Throw New Exception("不是有效的 json 文件", ex)
End Try
End If
Return Nothing
Catch ex As Exception
Logger.Warn(ex, "检查文件出错")
Return ex.GetDisplay(False)
End Try
End Function
End Class
#End Region
#Region "文本"
Public vbLQ As Char = Convert.ToChar(8220)
Public vbRQ As Char = Convert.ToChar(8221)
''' <summary>
''' 将对象转换为枚举类型,支持数字或原文两种格式,若为空字符串或 Nothing 则返回 0。
''' 若转换失败会抛出异常。
''' </summary>
<Extension> Public Function ToEnum(Of T As Structure)(Value As Object) As T
If IsNumeric(Value) Then
Return CType(CInt(Value), Object)
ElseIf String.IsNullOrEmpty(Value?.ToString) Then
Return CType(0, Object)
Else
Return [Enum].Parse(GetType(T), Value.ToString(), True)
End If
End Function
''' <summary>
''' 将文件大小转化为适合的文本形式,如“1.28 M”。
''' </summary>
''' <param name="FileSize">以字节为单位的大小表示。</param>
Public Function FormatFileSize(FileSize As Long) As String
Dim IsNegative = FileSize < 0
If IsNegative Then FileSize *= -1
If FileSize < 1000 Then
'B 级
Return If(IsNegative, "-", "") & FileSize & " B"
ElseIf FileSize < 1024 * 1000 Then
'K 级
Dim RoundResult As String = Math.Round(FileSize / 1024)
Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024, CInt((3 - RoundResult.Length).Clamp(0, 2))) & " K"
ElseIf FileSize < 1024 * 1024 * 1000 Then
'M 级
Dim RoundResult As String = Math.Round(FileSize / 1024 / 1024)
Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024 / 1024, CInt((3 - RoundResult.Length).Clamp(0, 2))) & " M"
Else
'G 级
Dim RoundResult As String = Math.Round(FileSize / 1024 / 1024 / 1024)
Return If(IsNegative, "-", "") & Math.Round(FileSize / 1024 / 1024 / 1024, CInt((3 - RoundResult.Length).Clamp(0, 2))) & " G"
End If
End Function
''' <summary>
''' 获取 JSON 对象。
''' </summary>
Public Function GetJson(Data As String) As JToken
Try
Return JsonConvert.DeserializeObject(Data, New JsonSerializerSettings With {.DateTimeZoneHandling = DateTimeZoneHandling.Local})
Catch ex As Exception
Dim Length As Integer = If(Data, "").Length
Throw New Exception("格式化 JSON 失败:" & If(Length > 2000, Data.Substring(0, 500) & $"...(全长 {Length} 个字符)..." & Right(Data, 500), Data), ex)
End Try
End Function
''' <summary>
''' 不会报错的 Val。
''' 如果输入有误,返回 0。
@@ -958,7 +820,7 @@ Public Module ModBase
TokenSource.Cancel()
Throw New TimeoutException($"任务超时({TimeoutMs} ms)")
End If
Return TargetTask.GetAwaiter().GetResult()
Return TargetTask.Run()
End Function
''' <summary>
''' 为 Task 设置超时,在超时时抛出 TimeoutException。
@@ -969,7 +831,7 @@ Public Module ModBase
TokenSource.Cancel()
Throw New TimeoutException($"任务超时({TimeoutMs} ms)")
End If
TargetTask.GetAwaiter().GetResult()
TargetTask.Run()
End Sub
''' <summary>
@@ -977,10 +839,10 @@ Public Module ModBase
''' </summary>
Public PathPure As String = GetPureASCIIDir()
Private Function GetPureASCIIDir() As String
If (PathExeFolder & "PCL").IsAsciiOnly() Then
Return PathExeFolder & "PCL\"
ElseIf PathAppdata.IsAsciiOnly() Then
Return PathAppdata
If Paths.Base.IsAsciiOnly() Then
Return Paths.Base & "PCL\"
ElseIf Paths.AppDataThenName.IsAsciiOnly() Then
Return Paths.AppDataThenName
ElseIf PathTemp.IsAsciiOnly() Then
Return PathTemp
Else
@@ -994,12 +856,6 @@ Public Module ModBase
Public Class RestartException
Inherits Exception
End Class
''' <summary>
''' 指示用户手动取消了操作,或用户已知晓操作被取消的原因。
''' </summary>
Public Class CancelledException
Inherits Exception
End Class
''' <summary>
''' 以管理员权限运行当前程序,并等待程序运行结束。
@@ -1011,19 +867,6 @@ Public Module ModBase
Return NewProcess.ExitCode
End Function
''' <summary>
''' 判断对象是否为某个泛型类型的实例。
''' </summary>
<Extension> Public Function IsInstanceOfGenericType(genericType As Type, obj As Object) As Boolean
If obj Is Nothing Then Return False
Dim t = obj.GetType()
While t IsNot Nothing
If t.IsGenericType AndAlso t.GetGenericTypeDefinition() Is genericType Then Return True
t = t.BaseType
End While
Return False
End Function
Private Uuid As Integer = 1
Private UuidLock As Object
''' <summary>
@@ -1045,79 +888,6 @@ Public Module ModBase
Return Stopwatch.GetTimestamp() \ (Stopwatch.Frequency \ 1000L)
End Function
''' <summary>
''' 将时间间隔转换为类似“5 分 10 秒前”的易于阅读的形式。
''' </summary>
Public Function GetTimeSpanString(Span As TimeSpan, IsShortForm As Boolean) As String
Dim EndFix = If(Span.TotalMilliseconds > 0, "后", "前")
If Span.TotalMilliseconds < 0 Then Span = -Span
Dim TotalMonthes = Math.Floor(Span.Days / 30)
Dim Result As String
If IsShortForm Then
If TotalMonthes >= 12 Then
'1+ 年,“3 年”
Result = Math.Floor(TotalMonthes / 12) & " 年"
ElseIf TotalMonthes >= 2 Then
'2~11 月,“5 个月”
Result = TotalMonthes & " 个月"
ElseIf Span.TotalDays >= 2 Then
'2 天 ~ 2 月,“23 天”
Result = Span.Days & " 天"
ElseIf Span.TotalHours >= 1 Then
'1 小时 ~ 2 天,“15 小时”
Result = Span.Hours & " 小时"
ElseIf Span.TotalMinutes >= 1 Then
'1 分钟 ~ 1 小时,“49 分钟”
Result = Span.Minutes & " 分钟"
ElseIf Span.TotalSeconds >= 1 Then
'1 秒 ~ 1 分钟,“23 秒”
Result = Span.Seconds & " 秒"
Else
'不到 1 秒
Result = "1 秒"
End If
Else
If TotalMonthes >= 61 Then
'5+ 年,“5 年”
Result = Math.Floor(TotalMonthes / 12) & " 年"
ElseIf TotalMonthes >= 12 Then
'12~60 月,“1 年 2 个月”
Result = Math.Floor(TotalMonthes / 12) & " 年" & If((TotalMonthes Mod 12) > 0, " " & (TotalMonthes Mod 12) & " 个月", "")
ElseIf TotalMonthes >= 4 Then
'4~11 月,“5 个月”
Result = TotalMonthes & " 个月"
ElseIf TotalMonthes >= 1 Then
'1~4 月,“2 个月 13 天”
Result = TotalMonthes & " 月" & If((Span.Days Mod 30) > 0, " " & (Span.Days Mod 30) & " 天", "")
ElseIf Span.TotalDays >= 4 Then
'4~30 天,“23 天”
Result = Span.Days & " 天"
ElseIf Span.TotalDays >= 1 Then
'1~3 天,“2 天 20 小时”
Result = Span.Days & " 天" & If(Span.Hours > 0, " " & Span.Hours & " 小时", "")
ElseIf Span.TotalHours >= 10 Then
'10 小时 ~ 1 天,“15 小时”
Result = Span.Hours & " 小时"
ElseIf Span.TotalHours >= 1 Then
'1~10 小时,“1 小时 20 分钟”
Result = Span.Hours & " 小时" & If(Span.Minutes > 0, " " & Span.Minutes & " 分钟", "")
ElseIf Span.TotalMinutes >= 10 Then
'10 分钟 ~ 1 小时,“49 分钟”
Result = Span.Minutes & " 分钟"
ElseIf Span.TotalMinutes >= 1 Then
'1~10 分钟,“9 分 23 秒”
Result = Span.Minutes & " 分" & If(Span.Seconds > 0, " " & Span.Seconds & " 秒", "")
ElseIf Span.TotalSeconds >= 1 Then
'1 秒 ~ 1 分钟,“23 秒”
Result = Span.Seconds & " 秒"
Else
'不到 1 秒
Result = "1 秒"
End If
End If
Result += EndFix
Return Result
End Function
''' <summary>
''' 获取十进制 Unix 时间戳。
''' </summary>
Public Function GetUnixTimestampUtc() As Long
@@ -1165,41 +935,6 @@ Public Module ModBase
Logger.Info($"启动进程:{StartInfo.FileName} {StartInfo.Arguments}")
Return Process.Start(StartInfo)
End Function
''' <summary>
''' 静默运行文件并返回输出流字符串。执行失败会抛出异常。
''' </summary>
''' <param name="FileName">文件名。可以为“notepad”等缩写。</param>
''' <param name="Arguments">运行参数。</param>
''' <param name="Timeout">等待该程序结束的最长时间(毫秒)。超时会抛出错误。</param>
Public Function StartProcessAndGetOutput(FileName As String, Optional Arguments As String = "", Optional Timeout As Integer = 1000000,
Optional WorkingDirectory As String = Nothing, Optional Encoding As Encoding = Nothing, Optional PrintLog As Boolean = True) As String
Dim Info = New ProcessStartInfo With {
.Arguments = Arguments,
.FileName = PathUtils.ToShortPath(FileName),
.UseShellExecute = False,
.CreateNoWindow = True,
.RedirectStandardError = True,
.RedirectStandardOutput = True,
.WorkingDirectory = PathUtils.ToShortPath(If(WorkingDirectory, PathExeFolder.TrimEnd("\"c))),
.StandardErrorEncoding = Encoding,
.StandardOutputEncoding = Encoding
}
If WorkingDirectory IsNot Nothing Then
If Info.EnvironmentVariables.ContainsKey("appdata") Then
Info.EnvironmentVariables("appdata") = WorkingDirectory
Else
Info.EnvironmentVariables.Add("appdata", WorkingDirectory)
End If
End If
If PrintLog Then Logger.Info($"执行命令并等待返回结果:{FileName} {Arguments}")
Using Program As New Process With {.StartInfo = Info}
Program.Start()
Dim Result As String = Program.StandardOutput.ReadToEnd & Program.StandardError.ReadToEnd
Program.WaitForExit(Timeout)
If Not Program.HasExited Then Program.Kill()
Return Result
End Using
End Function
''' <summary>
''' 在新的工作线程中执行代码。
@@ -1209,8 +944,6 @@ Public Module ModBase
Sub()
Try
Action()
Catch ex As ThreadInterruptedException
Logger.Info($"{Name}:线程已中止")
Catch ex As Exception
Logger.Error(ex, $"{Name}:线程执行失败")
End Try
@@ -1341,7 +1074,7 @@ Public Module ModBase
RunInThread(
Sub()
Try
ResilientUtils.Retry(
Retrier.Attempt(delay:=Function(Attempt) TimeSpan.FromMilliseconds(200), maxAttempts:=4, isRetryAllowed:=Function(ex) True, action:=
Sub()
RunInUi(
Sub()
@@ -1476,20 +1209,10 @@ Public Module ModBase
''' <summary>
''' 控件是否受到 TextTrimming 属性影响,导致内容被截取。
''' </summary>
<Extension> Public Function IsTextTrimmed(Control As TextBlock) As Boolean
Control.Measure(New Size(Double.MaxValue, Double.MaxValue))
Return Control.DesiredSize.Width > Control.ActualWidth
End Function
''' <summary>
''' 获取文本在被渲染后的宽度。
''' </summary>
Public Function MeasureStringWidth(text As String, Optional fontSize As Double = 14, Optional fontFamily As FontFamily = Nothing) As Double
If fontFamily Is Nothing Then fontFamily = New FontFamily("微软雅黑")
Dim formattedText = New FormattedText(
text, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, New Typeface(fontFamily, FontStyles.Normal, FontWeights.Normal, FontStretches.Normal),
fontSize, Brushes.Black, New NumberSubstitution(), TextFormattingMode.Display, New DpiScale(1, 1).PixelsPerDip)
Return formattedText.Width
<Extension> Public Function IsTextTrimmed(TextBlock As TextBlock) As Boolean
Dim typeface As New Typeface(TextBlock.FontFamily, TextBlock.FontStyle, TextBlock.FontWeight, TextBlock.FontStretch)
Dim formattedText As New FormattedText(TextBlock.Text, Thread.CurrentThread.CurrentCulture, TextBlock.FlowDirection, typeface, TextBlock.FontSize, TextBlock.Foreground, DPI)
Return formattedText.Width > TextBlock.ActualWidth
End Function
''' <summary>