@@ -24,9 +24,11 @@ public partial class ImageExBase
24
24
/// </summary>
25
25
public static readonly DependencyProperty SourceProperty = DependencyProperty . Register ( nameof ( Source ) , typeof ( object ) , typeof ( ImageExBase ) , new PropertyMetadata ( null , SourceChanged ) ) ;
26
26
27
- private Uri _uri ;
28
- private bool _isHttpSource ;
29
- private CancellationTokenSource _tokenSource = null ;
27
+ /// <summary>
28
+ /// Gets value tracking the currently requested source Uri. This can be helpful to use when implementing <see cref="ProvideCachedResourceAsync(Uri)"/> where loading an image from a cache takes longer and the current container has been recycled and is no longer valid since a new image has been set.
29
+ /// </summary>
30
+ protected Uri CurrentSourceUri { get ; private set ; }
31
+
30
32
private object _lazyLoadingSource ;
31
33
32
34
/// <summary>
@@ -66,7 +68,11 @@ private static bool IsHttpUri(Uri uri)
66
68
return uri . IsAbsoluteUri && ( uri . Scheme == "http" || uri . Scheme == "https" ) ;
67
69
}
68
70
69
- private void AttachSource ( ImageSource source )
71
+ /// <summary>
72
+ /// Method to call to assign an <see cref="ImageSource"/> value to the underlying <see cref="Image"/> powering <see cref="ImageExBase"/>.
73
+ /// </summary>
74
+ /// <param name="source"><see cref="ImageSource"/> to assign to the image.</param>
75
+ protected void AttachSource ( ImageSource source )
70
76
{
71
77
var image = Image as Image ;
72
78
var brush = Image as ImageBrush ;
@@ -88,9 +94,7 @@ private async void SetSource(object source)
88
94
return ;
89
95
}
90
96
91
- this . _tokenSource ? . Cancel ( ) ;
92
-
93
- this . _tokenSource = new CancellationTokenSource ( ) ;
97
+ OnNewSourceRequested ( source ) ;
94
98
95
99
AttachSource ( null ) ;
96
100
@@ -112,37 +116,38 @@ private async void SetSource(object source)
112
116
return ;
113
117
}
114
118
115
- _uri = source as Uri ;
116
- if ( _uri == null )
119
+ CurrentSourceUri = source as Uri ;
120
+ if ( CurrentSourceUri == null )
117
121
{
118
122
var url = source as string ?? source . ToString ( ) ;
119
- if ( ! Uri . TryCreate ( url , UriKind . RelativeOrAbsolute , out _uri ) )
123
+ if ( ! Uri . TryCreate ( url , UriKind . RelativeOrAbsolute , out Uri uri ) )
120
124
{
121
125
VisualStateManager . GoToState ( this , FailedState , true ) ;
122
126
return ;
123
127
}
128
+
129
+ CurrentSourceUri = uri ;
124
130
}
125
131
126
- _isHttpSource = IsHttpUri ( _uri ) ;
127
- if ( ! _isHttpSource && ! _uri . IsAbsoluteUri )
132
+ if ( ! IsHttpUri ( CurrentSourceUri ) && ! CurrentSourceUri . IsAbsoluteUri )
128
133
{
129
- _uri = new Uri ( "ms-appx:///" + _uri . OriginalString . TrimStart ( '/' ) ) ;
134
+ CurrentSourceUri = new Uri ( "ms-appx:///" + CurrentSourceUri . OriginalString . TrimStart ( '/' ) ) ;
130
135
}
131
136
132
- await LoadImageAsync ( _uri ) ;
137
+ await LoadImageAsync ( CurrentSourceUri ) ;
133
138
}
134
139
135
140
private async Task LoadImageAsync ( Uri imageUri )
136
141
{
137
- if ( _uri != null )
142
+ if ( imageUri != null )
138
143
{
139
144
if ( IsCacheEnabled )
140
145
{
141
- AttachSource ( new BitmapImage ( imageUri ) ) ;
146
+ await ProvideCachedResourceAsync ( imageUri ) ;
142
147
}
143
- else if ( string . Equals ( _uri . Scheme , "data" , StringComparison . OrdinalIgnoreCase ) )
148
+ else if ( string . Equals ( imageUri . Scheme , "data" , StringComparison . OrdinalIgnoreCase ) )
144
149
{
145
- var source = _uri . OriginalString ;
150
+ var source = imageUri . OriginalString ;
146
151
const string base64Head = "base64," ;
147
152
var index = source . IndexOf ( base64Head ) ;
148
153
if ( index >= 0 )
@@ -155,12 +160,90 @@ private async Task LoadImageAsync(Uri imageUri)
155
160
}
156
161
else
157
162
{
158
- AttachSource ( new BitmapImage ( _uri )
163
+ AttachSource ( new BitmapImage ( imageUri )
159
164
{
160
165
CreateOptions = BitmapCreateOptions . IgnoreImageCache
161
166
} ) ;
162
167
}
163
168
}
164
169
}
170
+
171
+ /// <summary>
172
+ /// This method is provided in case a developer would like their own custom caching strategy for <see cref="ImageExBase"/>.
173
+ /// By default it uses the built-in UWP cache provided by <see cref="BitmapImage"/> and
174
+ /// the <see cref="Image"/> control itself. Call <see cref="AttachSource(ImageSource)"/> to set
175
+ /// the retrieved cache value to the image.
176
+ /// </summary>
177
+ /// <example>
178
+ /// <code>
179
+ /// try
180
+ /// {
181
+ /// var propValues = new List<KeyValuePair<string, object>>();
182
+ ///
183
+ /// if (DecodePixelHeight > 0)
184
+ /// {
185
+ /// propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelHeight), D ecodePixelHeight));
186
+ /// }
187
+ /// if (DecodePixelWidth > 0)
188
+ /// {
189
+ /// propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelWidth), D ecodePixelWidth));
190
+ /// }
191
+ /// if (propValues.Count > 0)
192
+ /// {
193
+ /// propValues.Add(new KeyValuePair<string, object>(nameof(DecodePixelType), DecodePixelType));
194
+ /// }
195
+ ///
196
+ /// // A token could be provided here as well to cancel the request to the cache,
197
+ /// // if a new image is requested. That token can be canceled in the OnNewSourceRequested method.
198
+ /// var img = await ImageCache.Instance.GetFromCacheAsync(imageUri, true, initializerKeyValues: propValues);
199
+ ///
200
+ /// lock (LockObj)
201
+ /// {
202
+ /// // If you have many imageEx in a virtualized ListView for instance
203
+ /// // controls will be recycled and the uri will change while waiting for the previous one to load
204
+ /// if (_currentSourceUri == imageUri)
205
+ /// {
206
+ /// AttachSource(img);
207
+ /// ImageExOpened?.Invoke(this, new ImageExOpenedEventArgs());
208
+ /// VisualStateManager.GoToState(this, LoadedState, true);
209
+ /// }
210
+ /// }
211
+ /// }
212
+ /// catch (OperationCanceledException)
213
+ /// {
214
+ /// // nothing to do as cancellation has been requested.
215
+ /// }
216
+ /// catch (Exception e)
217
+ /// {
218
+ /// lock (LockObj)
219
+ /// {
220
+ /// if (_currentSourceUri == imageUri)
221
+ /// {
222
+ /// ImageExFailed?.Invoke(this, new ImageExFailedEventArgs(e));
223
+ /// VisualStateManager.GoToState(this, FailedState, true);
224
+ /// }
225
+ /// }
226
+ /// }
227
+ /// </code>
228
+ /// </example>
229
+ /// <param name="imageUri"><see cref="Uri"/> of the image to load from the cache.</param>
230
+ /// <returns><see cref="Task"/></returns>
231
+ protected virtual Task ProvideCachedResourceAsync ( Uri imageUri )
232
+ {
233
+ AttachSource ( new BitmapImage ( imageUri ) ) ;
234
+
235
+ return Task . CompletedTask ;
236
+ }
237
+
238
+ /// <summary>
239
+ /// This method is called when a new source is requested by the control. This can be useful when
240
+ /// implementing a custom caching strategy to cancel any open request on the cache if a new
241
+ /// request comes in due to container recycling before the previous one has completed.
242
+ /// Be default, this method does nothing.
243
+ /// </summary>
244
+ /// <param name="source">Incoming requested source.</param>
245
+ protected virtual void OnNewSourceRequested ( object source )
246
+ {
247
+ }
165
248
}
166
249
}
0 commit comments