MyImage.vb 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. Public Class MyImage
  2. Inherits Image
  3. #Region "公开属性"
  4. ''' <summary>
  5. ''' 网络图片的缓存有效期。
  6. ''' 在这个时间后,才会重新尝试下载图片。
  7. ''' </summary>
  8. Public FileCacheExpiredTime As New TimeSpan(7, 0, 0, 0) '7 天
  9. ''' <summary>
  10. ''' 是否允许将网络图片存储到本地用作缓存。
  11. ''' </summary>
  12. Public Property EnableCache As Boolean
  13. Get
  14. Return _EnableCache
  15. End Get
  16. Set(value As Boolean)
  17. _EnableCache = value
  18. End Set
  19. End Property
  20. Private _EnableCache As Boolean = True
  21. ''' <summary>
  22. ''' 与 Image 的 Source 类似。
  23. ''' 若输入以 http 开头的字符串,则会尝试下载图片然后显示,图片会保存为本地缓存。
  24. ''' 支持 WebP 格式的图片。
  25. ''' </summary>
  26. Public Shadows Property Source As String '覆写 Image 的 Source 属性
  27. Get
  28. Return _Source
  29. End Get
  30. Set(value As String)
  31. If value = "" Then value = Nothing
  32. If _Source = value Then Exit Property
  33. _Source = value
  34. If Not IsInitialized Then Exit Property '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
  35. Load()
  36. End Set
  37. End Property
  38. Private _Source As String = ""
  39. Public Shared Shadows ReadOnly SourceProperty As DependencyProperty = DependencyProperty.Register(
  40. "Source", GetType(String), GetType(MyImage), New PropertyMetadata(New PropertyChangedCallback(
  41. Sub(sender, e) If sender IsNot Nothing Then CType(sender, MyImage).Source = e.NewValue.ToString())))
  42. ''' <summary>
  43. ''' 当 Source 首次下载失败时,会从该备用地址加载图片。
  44. ''' </summary>
  45. Public Property FallbackSource As String
  46. Get
  47. Return _FallbackSource
  48. End Get
  49. Set(value As String)
  50. _FallbackSource = value
  51. End Set
  52. End Property
  53. Private _FallbackSource As String = Nothing
  54. ''' <summary>
  55. ''' 正在下载网络图片时显示的本地图片。
  56. ''' </summary>
  57. Public Property LoadingSource As String
  58. Get
  59. Return _LoadingSource
  60. End Get
  61. Set(value As String)
  62. _LoadingSource = value
  63. End Set
  64. End Property
  65. Private _LoadingSource As String = "pack://application:,,,/images/Icons/NoIcon.png"
  66. #End Region
  67. ''' <summary>
  68. ''' 实际被呈现的图片地址。
  69. ''' </summary>
  70. Public Property ActualSource As String
  71. Get
  72. Return _ActualSource
  73. End Get
  74. Set(value As String)
  75. If value = "" Then value = Nothing
  76. If _ActualSource = value Then Exit Property
  77. _ActualSource = value
  78. Try
  79. Dim Bitmap As MyBitmap = If(value Is Nothing, Nothing, New MyBitmap(value)) '在这里先触发可能的文件读取,尽量避免在 UI 线程中读取文件
  80. RunInUiWait(Sub() MyBase.Source = Bitmap)
  81. Catch ex As Exception
  82. Log(ex, $"加载图片失败({value})")
  83. Try
  84. If value.StartsWithF(PathTemp) AndAlso File.Exists(value) Then File.Delete(value)
  85. Catch
  86. End Try
  87. End Try
  88. End Set
  89. End Property
  90. Private _ActualSource As String = Nothing
  91. Private Sub Load() _
  92. Handles Me.Initialized '属性读取顺序修正:在完成 XAML 属性读取后再触发图片加载(#4868)
  93. '空
  94. If Source Is Nothing Then
  95. ActualSource = Nothing
  96. Exit Sub
  97. End If
  98. '本地图片
  99. If Not Source.StartsWithF("http") Then
  100. ActualSource = Source
  101. Exit Sub
  102. End If
  103. '从缓存加载网络图片
  104. Dim Url As String = Source
  105. Dim Retried As Boolean = False
  106. Dim TempPath As String = GetTempPath(Url)
  107. Dim TempFile As New FileInfo(TempPath)
  108. If EnableCache AndAlso TempFile.Exists Then
  109. ActualSource = TempPath
  110. If (Date.Now - TempFile.CreationTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存
  111. End If
  112. RunInNewThread(
  113. Sub()
  114. Dim TempDownloadingPath As String = Nothing
  115. Try
  116. RetryStart:
  117. '下载
  118. ActualSource = LoadingSource '显示加载中图片
  119. TempDownloadingPath = TempPath & RandomInteger(0, 10000000)
  120. NetDownload(Url, TempDownloadingPath, True)
  121. If Url <> Source AndAlso Url <> FallbackSource Then
  122. '已经更换了地址
  123. File.Delete(TempDownloadingPath)
  124. ElseIf EnableCache Then
  125. '保存缓存并显示
  126. If File.Exists(TempPath) Then File.Delete(TempPath)
  127. Rename(TempDownloadingPath, TempPath)
  128. RunInUi(Sub() ActualSource = TempPath)
  129. Else
  130. '直接显示
  131. RunInUiWait(Sub() ActualSource = TempDownloadingPath)
  132. File.Delete(TempDownloadingPath)
  133. End If
  134. Catch ex As Exception
  135. Try
  136. If TempPath IsNot Nothing Then File.Delete(TempPath)
  137. If TempDownloadingPath IsNot Nothing Then File.Delete(TempDownloadingPath)
  138. Catch
  139. End Try
  140. If Not Retried Then
  141. '更换备用地址
  142. Log(ex, $"下载图片可重试地失败({Url})", LogLevel.Developer)
  143. Retried = True
  144. Url = If(FallbackSource, Source)
  145. '空
  146. If Url Is Nothing Then
  147. ActualSource = Nothing
  148. Exit Sub
  149. End If
  150. '本地图片
  151. If Not Url.StartsWithF("http") Then
  152. ActualSource = Url
  153. Exit Sub
  154. End If
  155. '从缓存加载网络图片
  156. TempPath = GetTempPath(Url)
  157. TempFile = New FileInfo(TempPath)
  158. If EnableCache AndAlso TempFile.Exists() Then
  159. ActualSource = TempPath
  160. If (Date.Now - TempFile.CreationTime) < FileCacheExpiredTime Then Exit Sub '无需刷新缓存
  161. End If
  162. '下载
  163. If Source = Url Then Thread.Sleep(1000) '延迟 1s 重试
  164. GoTo RetryStart
  165. Else
  166. Log(ex, $"下载图片失败({Url})", LogLevel.Hint)
  167. End If
  168. End Try
  169. End Sub, "MyImage PicLoader " & GetUuid() & "#", ThreadPriority.BelowNormal)
  170. End Sub
  171. Public Shared Function GetTempPath(Url As String) As String
  172. Return $"{PathTemp}MyImage\{GetHash(Url)}.png"
  173. End Function
  174. End Class