using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Threading;
namespace MaterialDesignThemes.Wpf
{
///
/// Implements a inspired by the Material Design specs (https://material.google.com/components/snackbars-toasts.html).
///
[ContentProperty(nameof(Message))]
public class Snackbar : Control
{
private const string ActivateStoryboardName = "ActivateStoryboard";
private const string DeactivateStoryboardName = "DeactivateStoryboard";
private Action _messageQueueRegistrationCleanUp = null;
static Snackbar()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(Snackbar), new FrameworkPropertyMetadata(typeof(Snackbar)));
}
public static readonly DependencyProperty MessageProperty = DependencyProperty.Register(
nameof(Message), typeof(SnackbarMessage), typeof(Snackbar), new PropertyMetadata(default(SnackbarMessage)));
public SnackbarMessage Message
{
get { return (SnackbarMessage) GetValue(MessageProperty); }
set { SetValue(MessageProperty, value); }
}
public static readonly DependencyProperty MessageQueueProperty = DependencyProperty.Register(
nameof(MessageQueue), typeof(SnackbarMessageQueue), typeof(Snackbar), new PropertyMetadata(default(SnackbarMessageQueue), MessageQueuePropertyChangedCallback));
private static void MessageQueuePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
var snackbar = (Snackbar) dependencyObject;
(snackbar._messageQueueRegistrationCleanUp ?? (() => { }))();
var messageQueue = dependencyPropertyChangedEventArgs.NewValue as SnackbarMessageQueue;
snackbar._messageQueueRegistrationCleanUp = messageQueue?.Pair(snackbar);
}
public SnackbarMessageQueue MessageQueue
{
get { return (SnackbarMessageQueue) GetValue(MessageQueueProperty); }
set { SetValue(MessageQueueProperty, value); }
}
public static readonly DependencyProperty IsActiveProperty = DependencyProperty.Register(
nameof(IsActive), typeof(bool), typeof(Snackbar), new PropertyMetadata(default(bool), IsActivePropertyChangedCallback));
public bool IsActive
{
get { return (bool) GetValue(IsActiveProperty); }
set { SetValue(IsActiveProperty, value); }
}
public event RoutedPropertyChangedEventHandler IsActiveChanged
{
add { AddHandler(IsActiveChangedEvent, value); }
remove { RemoveHandler(IsActiveChangedEvent, value); }
}
public static readonly RoutedEvent IsActiveChangedEvent =
EventManager.RegisterRoutedEvent(
nameof(IsActiveChanged),
RoutingStrategy.Bubble,
typeof(RoutedPropertyChangedEventHandler),
typeof(Snackbar));
private static void OnIsActiveChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var instance = d as Snackbar;
var args = new RoutedPropertyChangedEventArgs(
(bool) e.OldValue,
(bool) e.NewValue) {RoutedEvent = IsActiveChangedEvent };
instance?.RaiseEvent(args);
}
public static readonly RoutedEvent DeactivateStoryboardCompletedEvent =
EventManager.RegisterRoutedEvent(
nameof(DeactivateStoryboardCompleted),
RoutingStrategy.Bubble,
typeof(SnackbarMessageEventArgs),
typeof(Snackbar));
public event RoutedPropertyChangedEventHandler DeactivateStoryboardCompleted
{
add { AddHandler(DeactivateStoryboardCompletedEvent, value); }
remove { RemoveHandler(DeactivateStoryboardCompletedEvent, value); }
}
private static void OnDeactivateStoryboardCompleted(
IInputElement snackbar, SnackbarMessage message)
{
var args = new SnackbarMessageEventArgs(DeactivateStoryboardCompletedEvent, message);
snackbar.RaiseEvent(args);
}
public TimeSpan ActivateStoryboardDuration { get; private set; }
public TimeSpan DeactivateStoryboardDuration { get; private set; }
public static readonly DependencyProperty ActionButtonStyleProperty = DependencyProperty.Register(
nameof(ActionButtonStyle), typeof(Style), typeof(Snackbar), new PropertyMetadata(default(Style)));
public Style ActionButtonStyle
{
get { return (Style) GetValue(ActionButtonStyleProperty); }
set { SetValue(ActionButtonStyleProperty, value); }
}
public override void OnApplyTemplate()
{
//we regards to notification of deactivate storyboard finishing,
//we either build a storyboard in code and subscribe to completed event,
//or take the not 100% proof of the storyboard duration from the storyboard itself
//...HOWEVER...we can both methods result can work under the same public API so
//we can flip the implementation if this version doesnt pan out
//(currently we have no even on the activate animation; don't
// need it just now, but it would mirror the deactivate)
ActivateStoryboardDuration = GetStoryboardResourceDuration(ActivateStoryboardName);
DeactivateStoryboardDuration = GetStoryboardResourceDuration(DeactivateStoryboardName);
base.OnApplyTemplate();
}
private TimeSpan GetStoryboardResourceDuration(string resourceName)
{
var storyboard = Template.Resources.Contains(resourceName)
? (Storyboard)Template.Resources[resourceName]
: null;
return storyboard != null && storyboard.Duration.HasTimeSpan
? storyboard.Duration.TimeSpan
: new Func(() =>
{
System.Diagnostics.Debug.WriteLine(
$"Warning, no Duration was specified at root of storyboard '{resourceName}'.");
return TimeSpan.Zero;
})();
}
private static void IsActivePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
{
OnIsActiveChanged(dependencyObject, dependencyPropertyChangedEventArgs);
if ((bool)dependencyPropertyChangedEventArgs.NewValue) return;
var snackbar = (Snackbar)dependencyObject;
if (snackbar.Message == null) return;
var dispatcherTimer = new DispatcherTimer
{
Tag = new Tuple(snackbar, snackbar.Message),
Interval = snackbar.DeactivateStoryboardDuration
};
dispatcherTimer.Tick += DeactivateStoryboardDispatcherTimerOnTick;
dispatcherTimer.Start();
}
private static void DeactivateStoryboardDispatcherTimerOnTick(object sender, EventArgs eventArgs)
{
var dispatcherTimer = (DispatcherTimer)sender;
dispatcherTimer.Stop();
dispatcherTimer.Tick -= DeactivateStoryboardDispatcherTimerOnTick;
var source = (Tuple)dispatcherTimer.Tag;
OnDeactivateStoryboardCompleted(source.Item1, source.Item2);
}
}
}