wpfstylessnackbarmaterial-design-in-xaml

WPF MaterialDesignInXamlToolkit Snackbar different style for each message


How to change the style (eg background/foreground colors) for different messages? I want to show Sucess, Info and Error messages with different styles.

I tried using bindings with a converter like this:

XAML:

<materialDesign:Snackbar
    x:Name="MainSnackbar"
    Grid.Row="0"
    HorizontalAlignment="Center"
    ActionButtonPlacement="Inline"
    Background="{Binding MessageQueueType, Converter={StaticResource StatusToMessageBackgroundColorConverter}, UpdateSourceTrigger=PropertyChanged}"
    Foreground="{Binding MessageQueueType, Converter={StaticResource StatusToMessageForegroundColorConverter}, UpdateSourceTrigger=PropertyChanged}"
    MessageQueue="{Binding MessageQueue}" />

Converter C#:

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        switch ((MessageQueueStatus)value)
        {
            case MessageQueueStatus.Error:
                return new SolidColorBrush(Colors.DarkRed);
            case MessageQueueStatus.Info:
                return new SolidColorBrush(Colors.DimGray);
            case MessageQueueStatus.Sucess:
                return new SolidColorBrush(Colors.ForestGreen);
        }
        return null;
    }

VM C#:

private void ShowSnackMessage(string message, MessageQueueStatus messageQueueType, bool ClearOtherMessages = false)
    {
        if(ClearOtherMessages)
            MessageQueue.Clear();
        MessageQueueType = messageQueueType;
        MessageQueue.Enqueue(message);
    }

But the if I queue more than 1 type of message at a time, only the last selected 'style' is applied. If I queue first an 'Info' message than a 'Success' message, both will show as 'Sucess style'.

I achieved what I want creating 3 different Snackbars directly in XAML with different styles set and 3 different MessageQueues in VM. But this way I need to create a different object and queue for each style I need. Is this the only way to do it?


Solution

  • We made a trick to have this behaviour without having the need of multiple snack bars.

    The idea is to keep the list of enqueued messages in the snackbar in a separate list and when a message is removed from the snack bar (just before putting the next one) we change the background color of the snackbar with a trigger on a property containing the info to choose the color.

    Our snackbar in xaml

     <materialDesign:Snackbar Name="MainSnackbar" ActionButtonStyle="{StaticResource SnackActionButton}" Grid.Row="2" Grid.ColumnSpan="2" MessageQueue="{Binding MessageQueue}" Message="{Binding Message, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Center">
        <utilities:TreeHelpers.Modifiers>
            <utilities:ModifierCollection >
                <utilities:Modifier TemplatePartName="ContentBorder" Property="{x:Static Border.CornerRadiusProperty}" Value="20 20 0 0">
                </utilities:Modifier>
            </utilities:ModifierCollection>
        </utilities:TreeHelpers.Modifiers>
        <materialDesign:Snackbar.Style>
            <Style TargetType="materialDesign:Snackbar" BasedOn="{StaticResource {x:Type materialDesign:Snackbar}}">
                <Setter Property="Background" Value="{StaticResource MaterialDesignSnackbarBackground}"></Setter>
                <Setter Property="MaxWidth" Value="{Binding ActualWidth, ElementName=AppGrid}" />
                <Style.Triggers>
                    <DataTrigger  Binding="{Binding CurrentMessageLevel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="1">
                        <Setter Property="Background" Value="{StaticResource FwBrushError}"></Setter>
                    </DataTrigger>
                    <DataTrigger  Binding="{Binding CurrentMessageLevel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="2">
                        <Setter Property="Background" Value="{StaticResource FwBrushWarning}"></Setter>
                    </DataTrigger>
                    <DataTrigger  Binding="{Binding CurrentMessageLevel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="3">
                        <Setter Property="Background" Value="{StaticResource FwBrushInfo}"></Setter>
                    </DataTrigger>
                    <DataTrigger  Binding="{Binding CurrentMessageLevel, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="4">
                        <Setter Property="Background" Value="{StaticResource FwBrushSuccess}"></Setter>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </materialDesign:Snackbar.Style>
    </materialDesign:Snackbar>
    

    The part where we enqueue messages

    
    public class MessageToSnack
    {
        public string Content { get; set; } = "";
        public MessageToSnackLevel Level { get; set; } = 0;
        public TimeSpan? Duration { get; set; } = null; // if null it will use the default duration of material design -> 3s
        public bool WithCloseButton { get; set; } = true;
    }
    
    public enum MessageToSnackLevel
    {
        NoLevel = 0,
        Error = 1,
        Warning = 2,
        Info = 3,
        Success = 4,
    }
    
    private void EventSnackMessageReceived(MessageToSnack snakMsg)
    {
        _snackedMessages.Add(snakMsg);
    
        if (snakMsg.WithCloseButton)
        {
            MessageQueue.Enqueue(snakMsg.Content, new PackIcon() { Kind = PackIconKind.Close}, (queue) => CloseCommand(queue), MessageQueue, false, false, snakMsg.Duration);
        }
        else
        {
            MessageQueue.Enqueue(snakMsg.Content, null, null, null, false, false, snakMsg.Duration);
        }
    }
    

    The part to manage the CurrenMessageLevel property

    List<MessageToSnack> _snackedMessages = new List<MessageToSnack>(); // used when intercepting a snackbar message poping , to find its reference and be able to change the color with a trigger on the CurrentLevel
    
    private SnackbarMessage _message;
    public SnackbarMessage Message
    {
        get { return _message; }
        set 
        { 
            SetProperty(ref _message, value);
    
            if (_message != null)
            {
                var localMessage = _snackedMessages.FirstOrDefault(m => m.Content.Equals(_message.Content.ToString()));
                if (localMessage != null)
                {
                    CurrentMessageLevel = localMessage.Level;
                    _snackedMessages.Remove(localMessage);
                }
                else
                {
                    CurrentMessageLevel = 0;
                }
            }
            else
            {
                CurrentMessageLevel = 0;
            }
        }
    }
    
    private MessageToSnackLevel _currentMessageLevel;
    public MessageToSnackLevel CurrentMessageLevel
    {
        get { return _currentMessageLevel; }
        set { SetProperty(ref _currentMessageLevel, value); }
    }