From 23e687a8d1d0ac7dc2d7ee7be476941406c3e480 Mon Sep 17 00:00:00 2001 From: Open-KFC <1811593346@qq.com> Date: Sat, 31 May 2025 02:26:31 +0800 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=E5=BC=95=E5=85=A5=E5=8F=AF?= =?UTF-8?q?=E4=BE=9B=E8=87=AA=E5=AE=9A=E4=B9=89=E4=B8=BB=E9=A1=B5=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E7=9A=84=E5=BC=82=E6=AD=A5=E7=BD=91=E7=BB=9C=20ImageS?= =?UTF-8?q?ource?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modules/Base/ModBase.vb | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index e444e9f97..7a276258b 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -1,3 +1,4 @@ +Imports System.ComponentModel Imports System.Globalization Imports System.IO.Compression Imports System.Reflection @@ -5,6 +6,7 @@ Imports System.Runtime.CompilerServices Imports System.Security.Cryptography Imports System.Security.Principal Imports System.Text.RegularExpressions +Imports System.Threading.Tasks Imports System.Xaml Imports Newtonsoft.Json @@ -3208,4 +3210,229 @@ Public Class InverseBooleanConverter End Function End Class +''' +''' 异步加载的网络图片源,需传入 Url,最终内容使用 MyBitmap 解析。
+''' Source - 源 Url,必须指定。
+''' FallbackSource - 备用 Url;在设计理念上,返回的内容应当与主 Url 相同。
+''' LoadingSource - 加载时显示的图片,合法值为 空 / 可被 MyBitmap 解析的字符串 / ImageSource。
+''' EnableCache - 是否启用缓存(默认启用),不启用的话每次都会联网获取图片。
+''' FileCacheExpiredTime - 缓存到期时间,默认为七天,遵循 TimeSpan 的格式解析。
+''' Result - 不在 xaml 中使用,用于存储输出的 ImageSource。

+''' 在 xaml 中引用的语法为:Source="{local:AsyncImageSource https://example.com/example.png}",
+''' 最终效果相当于将 Source 属性绑定到了一个动态改变的值上。 +'''
+Public Class AsyncImageSource + Inherits Markup.MarkupExtension + Implements INotifyPropertyChanged + + ''' + ''' 工具类,接受同样的标识符时始终返回同一个对象,除非该对象已被回收。 + ''' + Private Class InstanceProvider(Of T As Class) + Private ReadOnly _InstanceSupplier As Func(Of T) + Private ReadOnly _ExistingInstances As New Concurrent.ConcurrentDictionary(Of Object, WeakReference(Of T)) + Private ReadOnly _CleanupTimer As New Timer(AddressOf CleanupGoneInstances, Nothing, TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(1)) + + Public Sub New(InstanceSupplier As Func(Of T)) + If InstanceSupplier Is Nothing Then Throw New ArgumentNullException("InstanceSupplier") + _InstanceSupplier = InstanceSupplier + End Sub + + Public Function GetFrom(Key As Object) As T + If Key Is Nothing Then Throw New ArgumentNullException("Key") + GetFrom = Nothing + While True + Dim Wr As WeakReference(Of T) = Nothing + If _ExistingInstances.TryGetValue(Key, Wr) Then + If Wr.TryGetTarget(GetFrom) Then + Exit While + Else + GetFrom = _InstanceSupplier.Invoke() + If _ExistingInstances.TryUpdate(Key, New WeakReference(Of T)(GetFrom), Wr) Then + Exit While + End If + End If + Else + GetFrom = _InstanceSupplier.Invoke() + If _ExistingInstances.TryAdd(Key, New WeakReference(Of T)(GetFrom)) Then + Exit While + End If + End If + End While + If GetFrom Is Nothing Then Throw New Exception("获取实例意外失败。") + End Function + + Private Sub CleanupGoneInstances() + Try + _ExistingInstances _ + .Where(Function(e) Not e.Value.TryGetTarget(Nothing)) _ + .Select(Function(e) e.Key) _ + .ToList() _ + .ForEach(AddressOf AttemptRemoveGoneInstance) + Catch ex As Exception + Log(ex, $"清理失效 {GetType(T).Name} 实例意外失败") + End Try + End Sub + + Private Function AttemptRemoveGoneInstance(Key As Object) As Boolean + Dim Wr As WeakReference(Of T) = Nothing + If Not _ExistingInstances.TryGetValue(Key, Wr) Then Return False + If Wr.TryGetTarget(Nothing) Then Return False + Return CType(_ExistingInstances, ICollection(Of KeyValuePair(Of Object, WeakReference(Of T)))) _ + .Remove(New KeyValuePair(Of Object, WeakReference(Of T))(Key, Wr)) + End Function + End Class + + Private Shared ReadOnly _FileCacheDirectory As String = $"{PathTemp}MyImage\" + Private Shared ReadOnly _SemaphoreProvider As New InstanceProvider(Of SemaphoreSlim)(Function() New SemaphoreSlim(1, 1)) + Private Shared ReadOnly _LoadingSourceRealDefault As ImageSource = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png") + + Private _Source As String + Private _TempDownloadingPath As String + Private _FileCacheExpiredTimeReal As TimeSpan = TimeSpan.FromDays(7) + Private _LoadingSourceReal As ImageSource = _LoadingSourceRealDefault + Private _Result As ImageSource + + Public Property Source As String + Get + Return _Source + End Get + Set(value As String) + If value Is Nothing Then Throw New InvalidOperationException("AsyncImageSource.Source 不可设置为 null。") + If _Source IsNot Nothing Then Throw New InvalidOperationException("AsyncImageSource.Source 不可重复设置。") + _TempDownloadingPath = $"{_FileCacheDirectory}_{GetHash(value)}.png" + _Source = value + End Set + End Property + + Public Property FallbackSource As String + + Public WriteOnly Property LoadingSource As Object + Set(value As Object) + If value Is Nothing Then + _LoadingSourceReal = Nothing + ElseIf TypeOf value Is String Then + If CType(value, String).Length = 0 Then + _LoadingSourceReal = Nothing + Else + _LoadingSourceReal = New MyBitmap(CType(value, String)) + End If + Else + _LoadingSourceReal = CType(value, ImageSource) + End If + End Set + End Property + + Public Property EnableCache As Boolean = True + + Public WriteOnly Property FileCacheExpiredTime As Object + Set(value As Object) + Static Converter As New TimeSpanConverter + _FileCacheExpiredTimeReal = Converter.ConvertFrom(value) + End Set + End Property + + Public Property Result As ImageSource + Get + Return _Result + End Get + Private Set(value As ImageSource) + _Result = value + RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Result")) + End Set + End Property + + Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged + + Public Sub New() + End Sub + + Public Sub New(Source As String) + Me.Source = Source + End Sub + + Public Overrides Function ProvideValue(serviceProvider As IServiceProvider) As Object + If Source Is Nothing Then Throw New InvalidOperationException("AsyncImageSource.Source 未被设置。") + StartLoad() + Return New Binding("Result") With {.Source = Me}.ProvideValue(serviceProvider) + End Function + + Public Sub StartLoad() + Windows.Application.Current.Dispatcher.InvokeAsync(AddressOf LoadAsync) + End Sub + + Private Async Function LoadAsync() As Task + '需运行在 UI 线程上 + Try + Result = _LoadingSourceReal '加载中占位符 + Dim LoadSemaphore = Await Task.Run(Function() _SemaphoreProvider.GetFrom(_TempDownloadingPath)) + Await LoadSemaphore.WaitAsync() '保证使用同样文件缓存路径的实例串行加载 + Try + '尝试使用缓存 + Dim ResultFromCache As ImageSource + ResultFromCache = Await Task.Run(AddressOf TryLoadCache) + If ResultFromCache IsNot Nothing Then + Result = ResultFromCache + Exit Function + End If + '缓存无效 + Await Task.Run(AddressOf DownloadImage) '从网络下载图片 + Result = Await Task.Run(Function() New MyBitmap(_TempDownloadingPath)) '加载图片 + Finally + LoadSemaphore.Release() + End Try + Catch ex As Exception + Log(ex, $"异步网络图片加载失败(图片源:{Source},备用源:{If(FallbackSource, "无")})", LogLevel.Hint) + Result = Nothing + End Try + End Function + + ''' + ''' 从缓存获取 ImageSource,缓存未启用/不存在/过期/损坏或运行失败返回 Nothing,不会抛出异常。 + ''' + Private Function TryLoadCache() As MyBitmap + Try + If Not EnableCache Then Return Nothing '未启用缓存 + '判断缓存是否有效 + Dim CacheAvailable As Boolean + With New FileInfo(_TempDownloadingPath) + CacheAvailable = .Exists AndAlso (Date.Now - .LastWriteTime < _FileCacheExpiredTimeReal) + End With + If CacheAvailable Then + '缓存有效 + Try + Return New MyBitmap(_TempDownloadingPath) + Catch + 'MyBitmap 从文件解析失败 + File.Delete(_TempDownloadingPath) + End Try + End If + Catch ex As Exception + Log(ex, $"读取网络图片缓存(缓存位置 {_TempDownloadingPath},源 {Source})时预期之外的异常") + End Try + Return Nothing + End Function + + ''' + ''' 下载图片至本地缓存文件,失败后若指定了 FallbackSource 会再尝试,再失败后抛出异常。 + ''' + Private Sub DownloadImage() + Dim TargetUrl As String = Source, Retried As Boolean = False + Try +DownloadRetry: + Using Client As New WebClient() + Client.DownloadFile(TargetUrl, _TempDownloadingPath) + End Using + Catch ex As Exception When (Not Retried) AndAlso (FallbackSource IsNot Nothing) + Log(ex, $"下载图片可重试地失败({Source})", LogLevel.Developer) + TargetUrl = FallbackSource + Retried = True + GoTo DownloadRetry + Catch ex As Exception + Throw New Exception("下载图片失败。", ex) + End Try + End Sub + +End Class + #End Region From 2c81cb164af7edb52b0afb9536367a0422f651af Mon Sep 17 00:00:00 2001 From: Open-KFC <1811593346@qq.com> Date: Sun, 1 Jun 2025 02:15:39 +0800 Subject: [PATCH 2/6] =?UTF-8?q?fix(AsyncImageSource):=20=E6=9C=AA=E6=A3=80?= =?UTF-8?q?=E6=9F=A5=E6=96=87=E4=BB=B6=E5=A4=B9=E6=98=AF=E5=90=A6=E5=AD=98?= =?UTF-8?q?=E5=9C=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Plain Craft Launcher 2/Modules/Base/ModBase.vb | 1 + 1 file changed, 1 insertion(+) diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index 7a276258b..70e0afeb0 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -3420,6 +3420,7 @@ Public Class AsyncImageSource Dim TargetUrl As String = Source, Retried As Boolean = False Try DownloadRetry: + Directory.CreateDirectory(IO.Path.GetDirectoryName(_TempDownloadingPath)) Using Client As New WebClient() Client.DownloadFile(TargetUrl, _TempDownloadingPath) End Using From 3cbf8da39b7ab315cefb5602272a48135e2eec3e Mon Sep 17 00:00:00 2001 From: Open-KFC <1811593346@qq.com> Date: Wed, 4 Jun 2025 03:37:07 +0800 Subject: [PATCH 3/6] =?UTF-8?q?perf(AsyncImageSource):=20=E6=8A=8A=20MyBit?= =?UTF-8?q?map=20=E8=BD=AC=20ImageSource=20=E7=9A=84=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E5=85=A8=E9=83=BD=E6=94=BE=E5=88=B0=E5=B7=A5=E4=BD=9C=E7=BA=BF?= =?UTF-8?q?=E7=A8=8B=E4=B8=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modules/Base/ModBase.vb | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index 70e0afeb0..f97fe491f 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -3312,11 +3312,7 @@ Public Class AsyncImageSource If value Is Nothing Then _LoadingSourceReal = Nothing ElseIf TypeOf value Is String Then - If CType(value, String).Length = 0 Then - _LoadingSourceReal = Nothing - Else - _LoadingSourceReal = New MyBitmap(CType(value, String)) - End If + _LoadingSourceReal = LoadFileViaMyBitmap(value) Else _LoadingSourceReal = CType(value, ImageSource) End If @@ -3369,15 +3365,14 @@ Public Class AsyncImageSource Await LoadSemaphore.WaitAsync() '保证使用同样文件缓存路径的实例串行加载 Try '尝试使用缓存 - Dim ResultFromCache As ImageSource - ResultFromCache = Await Task.Run(AddressOf TryLoadCache) + Dim ResultFromCache = Await Task.Run(AddressOf TryLoadCache) If ResultFromCache IsNot Nothing Then Result = ResultFromCache Exit Function End If '缓存无效 Await Task.Run(AddressOf DownloadImage) '从网络下载图片 - Result = Await Task.Run(Function() New MyBitmap(_TempDownloadingPath)) '加载图片 + Result = Await Task.Run(Function() LoadFileViaMyBitmap(_TempDownloadingPath)) '加载图片 Finally LoadSemaphore.Release() End Try @@ -3390,7 +3385,7 @@ Public Class AsyncImageSource ''' ''' 从缓存获取 ImageSource,缓存未启用/不存在/过期/损坏或运行失败返回 Nothing,不会抛出异常。 ''' - Private Function TryLoadCache() As MyBitmap + Private Function TryLoadCache() As ImageSource Try If Not EnableCache Then Return Nothing '未启用缓存 '判断缓存是否有效 @@ -3401,7 +3396,7 @@ Public Class AsyncImageSource If CacheAvailable Then '缓存有效 Try - Return New MyBitmap(_TempDownloadingPath) + Return LoadFileViaMyBitmap(_TempDownloadingPath) Catch 'MyBitmap 从文件解析失败 File.Delete(_TempDownloadingPath) @@ -3434,6 +3429,14 @@ DownloadRetry: End Try End Sub + ''' + ''' 使用 MyBitmap 从文件路径创建 ImageSource 并 Freeze 住。 + ''' + Private Shared Function LoadFileViaMyBitmap(FilePath As String) As ImageSource + LoadFileViaMyBitmap = New MyBitmap(FilePath) + LoadFileViaMyBitmap.Freeze() + End Function + End Class #End Region From fe355379a7be354a4edbcb6bd414b12f93f3bd7a Mon Sep 17 00:00:00 2001 From: Open-KFC <1811593346@qq.com> Date: Wed, 4 Jun 2025 04:14:12 +0800 Subject: [PATCH 4/6] =?UTF-8?q?refactor(AsyncImageSource):=20=E5=88=86?= =?UTF-8?q?=E7=A6=BB=20MarkupExtension=20=E4=B8=8E=E5=85=B7=E4=BD=93?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modules/Base/ModBase.vb | 148 ++++++++++-------- 1 file changed, 82 insertions(+), 66 deletions(-) diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index f97fe491f..26c976d54 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -3221,10 +3221,69 @@ End Class ''' 在 xaml 中引用的语法为:Source="{local:AsyncImageSource https://example.com/example.png}",
''' 最终效果相当于将 Source 属性绑定到了一个动态改变的值上。 ''' -Public Class AsyncImageSource +Public Class AsyncImageSourceExtension Inherits Markup.MarkupExtension - Implements INotifyPropertyChanged + Private Shared ReadOnly _TimeSpanConverter As New TimeSpanConverter + Private Shared ReadOnly _LoadingSourceDefault As ImageSource + + Private _LoadingSource As ImageSource = _LoadingSourceDefault + Private _FileCacheExpiredTime As TimeSpan = TimeSpan.FromDays(7) + + Public Property Source As String + + Public Property FallbackSource As String + + Public WriteOnly Property LoadingSource As Object + Set(value As Object) + If value Is Nothing Then + _LoadingSource = Nothing + ElseIf TypeOf value Is String Then + _LoadingSource = New MyBitmap(DirectCast(value, String)) + Else + _LoadingSource = CType(value, ImageSource) + End If + End Set + End Property + + Public Property EnableCache As Boolean = True + + Public WriteOnly Property FileCacheExpiredTime As Object + Set(value As Object) + _FileCacheExpiredTime = _TimeSpanConverter.ConvertFrom(value) + End Set + End Property + + Shared Sub New() + _LoadingSourceDefault = Windows.Application.Current.Dispatcher.Invoke( + Function() + Dim Result As ImageSource = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png") + Result.Freeze() + Return Result + End Function) + End Sub + + Public Sub New() + End Sub + Public Sub New(Source As String) + Me.Source = Source + End Sub + + Public Overrides Function ProvideValue(serviceProvider As IServiceProvider) As Object + If Source Is Nothing Then Throw New InvalidOperationException("AsyncImageSource.Source 未被设置。") + Dim Result As New AsyncImageSourceImpl(Source) With { + .FallbackSource = FallbackSource, + .LoadingSource = _LoadingSource, + .EnableCache = EnableCache, + .FileCacheExpiredTime = _FileCacheExpiredTime + } + Result.StartLoad() + Return New Binding("Result") With {.Source = Result}.ProvideValue(serviceProvider) + End Function +End Class + +Public Class AsyncImageSourceImpl + Implements INotifyPropertyChanged ''' ''' 工具类,接受同样的标识符时始终返回同一个对象,除非该对象已被回收。 ''' @@ -3283,56 +3342,22 @@ Public Class AsyncImageSource End Function End Class - Private Shared ReadOnly _FileCacheDirectory As String = $"{PathTemp}MyImage\" - Private Shared ReadOnly _SemaphoreProvider As New InstanceProvider(Of SemaphoreSlim)(Function() New SemaphoreSlim(1, 1)) - Private Shared ReadOnly _LoadingSourceRealDefault As ImageSource = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png") - - Private _Source As String - Private _TempDownloadingPath As String - Private _FileCacheExpiredTimeReal As TimeSpan = TimeSpan.FromDays(7) - Private _LoadingSourceReal As ImageSource = _LoadingSourceRealDefault - Private _Result As ImageSource - - Public Property Source As String - Get - Return _Source - End Get - Set(value As String) - If value Is Nothing Then Throw New InvalidOperationException("AsyncImageSource.Source 不可设置为 null。") - If _Source IsNot Nothing Then Throw New InvalidOperationException("AsyncImageSource.Source 不可重复设置。") - _TempDownloadingPath = $"{_FileCacheDirectory}_{GetHash(value)}.png" - _Source = value - End Set - End Property + Private Shared ReadOnly FileCacheDirectory As String = $"{PathTemp}MyImage\" + Private Shared ReadOnly SemaphoreProvider As New InstanceProvider(Of SemaphoreSlim)(Function() New SemaphoreSlim(1, 1)) - Public Property FallbackSource As String - - Public WriteOnly Property LoadingSource As Object - Set(value As Object) - If value Is Nothing Then - _LoadingSourceReal = Nothing - ElseIf TypeOf value Is String Then - _LoadingSourceReal = LoadFileViaMyBitmap(value) - Else - _LoadingSourceReal = CType(value, ImageSource) - End If - End Set - End Property - - Public Property EnableCache As Boolean = True - - Public WriteOnly Property FileCacheExpiredTime As Object - Set(value As Object) - Static Converter As New TimeSpanConverter - _FileCacheExpiredTimeReal = Converter.ConvertFrom(value) - End Set - End Property + Public ReadOnly Source As String + Public ReadOnly TempDownloadingPath As String + Public FallbackSource As String + Public LoadingSource As ImageSource + Public EnableCache As Boolean + Public FileCacheExpiredTime As TimeSpan + Private _Result As ImageSource Public Property Result As ImageSource Get Return _Result End Get - Private Set(value As ImageSource) + Set(value As ImageSource) _Result = value RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Result")) End Set @@ -3340,28 +3365,19 @@ Public Class AsyncImageSource Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged - Public Sub New() - End Sub - Public Sub New(Source As String) Me.Source = Source + TempDownloadingPath = $"{FileCacheDirectory}_{GetHash(Source)}.png" End Sub - Public Overrides Function ProvideValue(serviceProvider As IServiceProvider) As Object - If Source Is Nothing Then Throw New InvalidOperationException("AsyncImageSource.Source 未被设置。") - StartLoad() - Return New Binding("Result") With {.Source = Me}.ProvideValue(serviceProvider) - End Function - Public Sub StartLoad() Windows.Application.Current.Dispatcher.InvokeAsync(AddressOf LoadAsync) End Sub Private Async Function LoadAsync() As Task - '需运行在 UI 线程上 Try - Result = _LoadingSourceReal '加载中占位符 - Dim LoadSemaphore = Await Task.Run(Function() _SemaphoreProvider.GetFrom(_TempDownloadingPath)) + Result = LoadingSource '加载中占位符 + Dim LoadSemaphore = Await Task.Run(Function() SemaphoreProvider.GetFrom(TempDownloadingPath)) Await LoadSemaphore.WaitAsync() '保证使用同样文件缓存路径的实例串行加载 Try '尝试使用缓存 @@ -3372,7 +3388,7 @@ Public Class AsyncImageSource End If '缓存无效 Await Task.Run(AddressOf DownloadImage) '从网络下载图片 - Result = Await Task.Run(Function() LoadFileViaMyBitmap(_TempDownloadingPath)) '加载图片 + Result = Await Task.Run(Function() LoadFileViaMyBitmap(TempDownloadingPath)) '加载图片 Finally LoadSemaphore.Release() End Try @@ -3390,20 +3406,20 @@ Public Class AsyncImageSource If Not EnableCache Then Return Nothing '未启用缓存 '判断缓存是否有效 Dim CacheAvailable As Boolean - With New FileInfo(_TempDownloadingPath) - CacheAvailable = .Exists AndAlso (Date.Now - .LastWriteTime < _FileCacheExpiredTimeReal) + With New FileInfo(TempDownloadingPath) + CacheAvailable = .Exists AndAlso (Date.Now - .LastWriteTime < FileCacheExpiredTime) End With If CacheAvailable Then '缓存有效 Try - Return LoadFileViaMyBitmap(_TempDownloadingPath) + Return LoadFileViaMyBitmap(TempDownloadingPath) Catch 'MyBitmap 从文件解析失败 - File.Delete(_TempDownloadingPath) + File.Delete(TempDownloadingPath) End Try End If Catch ex As Exception - Log(ex, $"读取网络图片缓存(缓存位置 {_TempDownloadingPath},源 {Source})时预期之外的异常") + Log(ex, $"读取网络图片缓存(缓存位置 {TempDownloadingPath},源 {Source})时预期之外的异常") End Try Return Nothing End Function @@ -3415,9 +3431,9 @@ Public Class AsyncImageSource Dim TargetUrl As String = Source, Retried As Boolean = False Try DownloadRetry: - Directory.CreateDirectory(IO.Path.GetDirectoryName(_TempDownloadingPath)) + Directory.CreateDirectory(IO.Path.GetDirectoryName(TempDownloadingPath)) Using Client As New WebClient() - Client.DownloadFile(TargetUrl, _TempDownloadingPath) + Client.DownloadFile(TargetUrl, TempDownloadingPath) End Using Catch ex As Exception When (Not Retried) AndAlso (FallbackSource IsNot Nothing) Log(ex, $"下载图片可重试地失败({Source})", LogLevel.Developer) From cc0ed065108e2240ba38e557515b2ea7e32341d9 Mon Sep 17 00:00:00 2001 From: Open-KFC <1811593346@qq.com> Date: Wed, 4 Jun 2025 04:49:12 +0800 Subject: [PATCH 5/6] =?UTF-8?q?feat(AsyncImageSource):=20=E5=BC=95?= =?UTF-8?q?=E5=85=A5=20XamlReferenceType=EF=BC=8C=E5=85=81=E8=AE=B8?= =?UTF-8?q?=E5=9C=A8=20xaml=20=E4=B8=AD=E4=B8=8D=E4=BB=A5=E7=9B=B4?= =?UTF-8?q?=E6=8E=A5=E7=BB=91=E5=AE=9A=E7=9A=84=E6=96=B9=E5=BC=8F=E5=BC=95?= =?UTF-8?q?=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Modules/Base/ModBase.vb | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index 26c976d54..5e2fce2d1 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -3217,9 +3217,10 @@ End Class ''' LoadingSource - 加载时显示的图片,合法值为 空 / 可被 MyBitmap 解析的字符串 / ImageSource。
''' EnableCache - 是否启用缓存(默认启用),不启用的话每次都会联网获取图片。
''' FileCacheExpiredTime - 缓存到期时间,默认为七天,遵循 TimeSpan 的格式解析。
-''' Result - 不在 xaml 中使用,用于存储输出的 ImageSource。

+''' Result - 可绑定,用于存储输出的 ImageSource。
+''' XamlReferenceType - 设置为 Instance 即可拿到 AsyncImageSourceImpl 而非 Binding 对象。

''' 在 xaml 中引用的语法为:Source="{local:AsyncImageSource https://example.com/example.png}",
-''' 最终效果相当于将 Source 属性绑定到了一个动态改变的值上。 +''' 此时效果相当于将 Source 属性绑定到了一个动态改变的值上。 ''' Public Class AsyncImageSourceExtension Inherits Markup.MarkupExtension @@ -3253,6 +3254,12 @@ Public Class AsyncImageSourceExtension End Set End Property + Public Property XamlReferenceType As XamlReferenceTypeEnum = XamlReferenceTypeEnum.Binding + Public Enum XamlReferenceTypeEnum + Binding + Instance + End Enum + Shared Sub New() _LoadingSourceDefault = Windows.Application.Current.Dispatcher.Invoke( Function() @@ -3277,8 +3284,15 @@ Public Class AsyncImageSourceExtension .EnableCache = EnableCache, .FileCacheExpiredTime = _FileCacheExpiredTime } + Select Case XamlReferenceType + Case XamlReferenceTypeEnum.Binding + ProvideValue = New Binding("Result") With {.Source = Result}.ProvideValue(serviceProvider) + Case XamlReferenceTypeEnum.Instance + ProvideValue = Result + Case Else + Throw New InvalidOperationException("不具意义的 AsyncImageSource.XamlReferenceType。") + End Select Result.StartLoad() - Return New Binding("Result") With {.Source = Result}.ProvideValue(serviceProvider) End Function End Class From e2f13c1ed95b7407dc7c2edbf38f16e945a0a826 Mon Sep 17 00:00:00 2001 From: Open-KFC <1811593346@qq.com> Date: Wed, 4 Jun 2025 07:53:53 +0800 Subject: [PATCH 6/6] =?UTF-8?q?imp(AsyncImageSource):=20=E6=94=B9=E4=B8=80?= =?UTF-8?q?=E4=B8=8B=20LoadingSourceDefault=20=E7=9A=84=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Plain Craft Launcher 2/Modules/Base/ModBase.vb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/Plain Craft Launcher 2/Modules/Base/ModBase.vb b/Plain Craft Launcher 2/Modules/Base/ModBase.vb index 5e2fce2d1..959b3cd56 100644 --- a/Plain Craft Launcher 2/Modules/Base/ModBase.vb +++ b/Plain Craft Launcher 2/Modules/Base/ModBase.vb @@ -3225,7 +3225,7 @@ End Class Public Class AsyncImageSourceExtension Inherits Markup.MarkupExtension Private Shared ReadOnly _TimeSpanConverter As New TimeSpanConverter - Private Shared ReadOnly _LoadingSourceDefault As ImageSource + Private Shared ReadOnly _LoadingSourceDefault As ImageSource = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png") Private _LoadingSource As ImageSource = _LoadingSourceDefault Private _FileCacheExpiredTime As TimeSpan = TimeSpan.FromDays(7) @@ -3261,12 +3261,7 @@ Public Class AsyncImageSourceExtension End Enum Shared Sub New() - _LoadingSourceDefault = Windows.Application.Current.Dispatcher.Invoke( - Function() - Dim Result As ImageSource = New MyBitmap("pack://application:,,,/images/Icons/NoIcon.png") - Result.Freeze() - Return Result - End Function) + _LoadingSourceDefault.Freeze() End Sub Public Sub New()