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()