7
7
using System . Windows . Controls ;
8
8
using System . Windows . Input ;
9
9
using System . Windows . Media ;
10
- using System . Windows . Media . Animation ;
11
10
using System . Windows . Threading ;
12
11
13
12
namespace MaterialDesignThemes . Wpf
@@ -17,17 +16,13 @@ namespace MaterialDesignThemes.Wpf
17
16
/// </summary>
18
17
public class Snackbar : ContentControl
19
18
{
20
- private const string PartActionButtonName = "PART_actionButton" ;
21
- private const string PartContentGridName = "PART_contentGrid" ;
22
- private const string PartContentPanelName = "PART_contentPanel" ;
19
+ public const string PartActionButtonName = "PART_actionButton" ;
23
20
24
21
/// <summary>
25
- /// The duration of the open and close animations in milliseconds.
22
+ /// The duration of the animation in milliseconds.
26
23
/// </summary>
27
24
public const int AnimationDuration = 300 ;
28
25
29
- private const int OpacityAnimationHintOffset = 50 ;
30
-
31
26
/// <summary>
32
27
/// The minimum timeout for a visible <see cref="Snackbar" /> in milliseconds.
33
28
/// </summary>
@@ -155,45 +150,22 @@ public bool IsOpen
155
150
}
156
151
}
157
152
158
- private static void IsOpenChangedHandler ( DependencyObject sender , DependencyPropertyChangedEventArgs args )
153
+ private async static void IsOpenChangedHandler ( DependencyObject sender , DependencyPropertyChangedEventArgs args )
159
154
{
160
- // trigger the animations
155
+ // the animations are triggered by the value of the IsOpen property
156
+
161
157
Snackbar snackbar = ( Snackbar ) sender ;
162
158
159
+ // stop the timer because it may mess up the behaviour of the Snackbar
160
+ snackbar . _timer ? . Stop ( ) ;
161
+
163
162
if ( ( bool ) args . NewValue )
164
163
{
165
- if ( snackbar . _openStoryboard != null )
166
- {
167
- // set the duration of the dummy visibility timeout animation as it may has changed since the last call
168
- if ( snackbar . _dummyVisibilityAnimation != null )
169
- {
170
- int timeout = snackbar . VisibilityTimeout ;
171
-
172
- if ( timeout < MinimumVisibilityTimeout )
173
- {
174
- timeout = MinimumVisibilityTimeout ;
175
- }
176
-
177
- snackbar . _dummyVisibilityAnimation . Duration = TimeSpan . FromMilliseconds ( timeout ) ;
178
- }
179
-
180
- // start the open animation
181
- snackbar . _openStoryboard . Begin ( snackbar , true ) ;
182
- }
164
+ await snackbar . ShowAsync ( ) ;
183
165
}
184
166
else
185
167
{
186
- // stop the open animation if the Snackbar should close before the visibility timeout is reached
187
- if ( snackbar . _openStoryboard != null )
188
- {
189
- snackbar . _openStoryboard . Stop ( snackbar ) ;
190
- }
191
-
192
- // start the close animation
193
- if ( snackbar . _closeStoryboard != null )
194
- {
195
- snackbar . _closeStoryboard . Begin ( snackbar , true ) ;
196
- }
168
+ await snackbar . HideAsync ( ) ;
197
169
}
198
170
}
199
171
@@ -237,9 +209,8 @@ public int VisibilityTimeout
237
209
}
238
210
}
239
211
240
- private Storyboard _openStoryboard ;
241
- private Storyboard _closeStoryboard ;
242
- private DoubleAnimation _dummyVisibilityAnimation ;
212
+ // used to implement the behaviour of the HalfAutomatic mode defined in the Material Design specs
213
+ private DispatcherTimer _timer ;
243
214
244
215
private Button _actionButton ;
245
216
@@ -248,14 +219,13 @@ static Snackbar()
248
219
DefaultStyleKeyProperty . OverrideMetadata ( typeof ( Snackbar ) , new FrameworkPropertyMetadata ( typeof ( Snackbar ) ) ) ;
249
220
250
221
ContentProperty . OverrideMetadata ( typeof ( Snackbar ) , new FrameworkPropertyMetadata ( ContentChangedHandler ) ) ;
251
- TagProperty . OverrideMetadata ( typeof ( Snackbar ) , new FrameworkPropertyMetadata ( 0.0 ) ) ;
222
+ TagProperty . OverrideMetadata ( typeof ( Snackbar ) , new FrameworkPropertyMetadata ( "0" ) ) ;
252
223
}
253
224
254
225
public Snackbar ( ) : base ( ) { }
255
226
256
227
public override void OnApplyTemplate ( )
257
228
{
258
- // set the event handler for the action button from the template
259
229
if ( _actionButton != null )
260
230
{
261
231
_actionButton . Click -= ActionButtonClickHandler ;
@@ -268,80 +238,28 @@ public override void OnApplyTemplate()
268
238
_actionButton . Click += ActionButtonClickHandler ;
269
239
}
270
240
271
- // Storyboard for the open animation
272
- _openStoryboard = new Storyboard ( ) ;
273
-
274
- // height
275
- DoubleAnimation contentPanelTagAnimation = new DoubleAnimation ( 0.0 , 1.0 , TimeSpan . FromMilliseconds ( AnimationDuration ) ) ;
276
- contentPanelTagAnimation . BeginTime = TimeSpan . FromMilliseconds ( 0.0 ) ;
277
- contentPanelTagAnimation . EasingFunction = new QuarticEase ( ) { EasingMode = EasingMode . EaseOut } ;
278
- Storyboard . SetTarget ( contentPanelTagAnimation , GetTemplateChild ( PartContentPanelName ) ) ;
279
- Storyboard . SetTargetProperty ( contentPanelTagAnimation , new PropertyPath ( StackPanel . TagProperty ) ) ;
280
- _openStoryboard . Children . Add ( contentPanelTagAnimation ) ;
281
-
282
- // opacity of the content
283
- DoubleAnimation contentGridOpacityAnimation = new DoubleAnimation ( 0.0 , TimeSpan . FromMilliseconds ( 0.0 ) ) ;
284
- contentGridOpacityAnimation . BeginTime = TimeSpan . FromMilliseconds ( 0.0 ) ;
285
- Storyboard . SetTarget ( contentGridOpacityAnimation , GetTemplateChild ( PartContentPanelName ) ) ;
286
- Storyboard . SetTargetProperty ( contentGridOpacityAnimation , new PropertyPath ( Grid . OpacityProperty ) ) ;
287
- _openStoryboard . Children . Add ( contentGridOpacityAnimation ) ;
288
-
289
- contentGridOpacityAnimation = new DoubleAnimation ( 0.0 , 1.0 , TimeSpan . FromMilliseconds ( AnimationDuration - OpacityAnimationHintOffset ) ) ;
290
- contentGridOpacityAnimation . BeginTime = TimeSpan . FromMilliseconds ( OpacityAnimationHintOffset ) ;
291
- contentGridOpacityAnimation . EasingFunction = new QuarticEase ( ) { EasingMode = EasingMode . EaseOut } ;
292
- Storyboard . SetTarget ( contentGridOpacityAnimation , GetTemplateChild ( PartContentPanelName ) ) ;
293
- Storyboard . SetTargetProperty ( contentGridOpacityAnimation , new PropertyPath ( Grid . OpacityProperty ) ) ;
294
- _openStoryboard . Children . Add ( contentGridOpacityAnimation ) ;
295
-
296
- // dummy animation to keep the HalfAutomatic mode Snackbar open during the visibility timeout
297
- _dummyVisibilityAnimation = new DoubleAnimation ( 1.0 , 1.0 , TimeSpan . FromMilliseconds ( VisibilityTimeout ) ) ;
298
- _dummyVisibilityAnimation . BeginTime = TimeSpan . FromMilliseconds ( AnimationDuration ) ;
299
- Storyboard . SetTarget ( _dummyVisibilityAnimation , GetTemplateChild ( PartContentPanelName ) ) ;
300
- Storyboard . SetTargetProperty ( _dummyVisibilityAnimation , new PropertyPath ( StackPanel . TagProperty ) ) ;
301
- _openStoryboard . Children . Add ( _dummyVisibilityAnimation ) ;
302
-
303
- _openStoryboard . Completed += ( object sender , EventArgs args ) =>
304
- {
305
- // close the Snackbar after the animation in the HalfAutomatic mode
306
- if ( Mode == SnackbarMode . HalfAutomatic )
307
- {
308
- IsOpen = false ;
309
- }
310
- } ;
311
-
312
- // Storyboard for the close animation
313
- _closeStoryboard = new Storyboard ( ) ;
314
-
315
- // height
316
- contentPanelTagAnimation = new DoubleAnimation ( 1.0 , 0.0 , TimeSpan . FromMilliseconds ( AnimationDuration ) ) ;
317
- contentPanelTagAnimation . BeginTime = TimeSpan . FromMilliseconds ( 0.0 ) ;
318
- contentPanelTagAnimation . EasingFunction = new QuarticEase ( ) { EasingMode = EasingMode . EaseOut } ;
319
- Storyboard . SetTarget ( contentPanelTagAnimation , GetTemplateChild ( PartContentPanelName ) ) ;
320
- Storyboard . SetTargetProperty ( contentPanelTagAnimation , new PropertyPath ( StackPanel . TagProperty ) ) ;
321
- _closeStoryboard . Children . Add ( contentPanelTagAnimation ) ;
322
-
323
241
base . OnApplyTemplate ( ) ;
324
242
}
325
243
326
244
private async Task ShowContentAsync ( object content )
327
245
{
328
246
if ( Mode == SnackbarMode . HalfAutomatic )
329
247
{
330
- // first close the Snackbar if it is already visible
248
+ // first hide the Snackbar if its already visible
331
249
if ( IsOpen )
332
250
{
333
251
IsOpen = false ;
334
252
335
- // wait for the animation, otherwise the new content will already be shown in the close animation
253
+ // wait for the animation, otherwise the new content will already be shown in the hide animation
336
254
await Task . Delay ( AnimationDuration ) ;
337
255
}
338
256
339
- // now set the new content and open the Snackbar
257
+ // now set the new content and show the Snackbar
340
258
InternalContent = content ;
341
259
342
260
if ( content != null )
343
261
{
344
- // only open it with content
262
+ // only show it with content
345
263
IsOpen = true ;
346
264
347
265
await Task . Delay ( AnimationDuration ) ;
@@ -354,6 +272,44 @@ private async Task ShowContentAsync(object content)
354
272
}
355
273
}
356
274
275
+ private async Task ShowAsync ( )
276
+ {
277
+ // wait for the animation
278
+ await Task . Delay ( AnimationDuration ) ;
279
+
280
+ // start the timeout in HalfAutomatic mode
281
+ if ( Mode == SnackbarMode . HalfAutomatic )
282
+ {
283
+ // start the timer which will hide the Snackbar
284
+ int timeout = VisibilityTimeout ;
285
+
286
+ if ( timeout < MinimumVisibilityTimeout )
287
+ {
288
+ timeout = MinimumVisibilityTimeout ;
289
+ }
290
+
291
+ if ( _timer == null )
292
+ {
293
+ _timer = new DispatcherTimer ( ) ;
294
+ }
295
+
296
+ _timer . Tick += async ( object sender , EventArgs args ) =>
297
+ {
298
+ IsOpen = false ;
299
+
300
+ await Task . Delay ( AnimationDuration ) ;
301
+ } ;
302
+ _timer . Interval = new TimeSpan ( 0 , 0 , 0 , 0 , timeout ) ;
303
+ _timer . Start ( ) ;
304
+ }
305
+ }
306
+
307
+ private async Task HideAsync ( )
308
+ {
309
+ // wait for the animation
310
+ await Task . Delay ( AnimationDuration ) ;
311
+ }
312
+
357
313
private async void ActionButtonClickHandler ( object sender , RoutedEventArgs args )
358
314
{
359
315
// do not you raise the event if the Snackbar is not fully visible
@@ -364,7 +320,7 @@ private async void ActionButtonClickHandler(object sender, RoutedEventArgs args)
364
320
365
321
Task task = null ;
366
322
367
- // close the Snackbar in HalfAutomatic mode
323
+ // hide the Snackbar in HalfAutomatic mode
368
324
if ( Mode == SnackbarMode . HalfAutomatic )
369
325
{
370
326
IsOpen = false ;
@@ -408,4 +364,4 @@ public enum SnackbarMode : byte
408
364
Manual
409
365
}
410
366
}
411
- }
367
+ }
0 commit comments