Enterwell / Wpf.Notifications

WPF notifications UI controls (as seen in VS Code)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Cannot launch notification from ViewModel

amkuchta opened this issue · comments

Synopsis

This may be related to #1 , but I am having issues with getting a notification to launch using an MVVM pattern. Relevant code is below; if you'd like to take a look at the application yourself, please feel free to clone from here - if you run into any issues getting the application to start, let me know, you may need to work some magic to get the application to run in Visual Studio.

View (/View/UI/DevWindow.xaml)

...
<!--Namespace/DataContext declaration-->
xmlns:Notifications="clr-namespace:Enterwell.Clients.Wpf.Notifications.Controls;assembly=Enterwell.Clients.Wpf.Notifications"
DataContext="{Binding Main, Source={StaticResource Locator}}"
...
<!--NotificationMessageContainer is placed within a Grid-->
<Notifications:NotificationMessageContainer 
    Background="{DynamicResource WindowBackgroundBrush}"
    Foreground="{DynamicResource LabelTextBrush}"
    Manager="{Binding NotificationMessageManager}" />
...

ViewModel (/ViewModel/MainViewModel.cs)

// Instantiate NotificationMessageManager, which the View is bound to above
public INotificationMessageManager NotificationMessageManager { get; } = new NotificationMessageManager();
...
// Create the message in the constructor
public MainViewModel()
{
    try
    {
        NotificationMessageManager.CreateMessage()
            .Accent("#1751C3")
            .Animates(true)
            .AnimationInDuration(0.75)
            .AnimationOutDuration(2)
            .HasBadge("Info")
            .HasMessage("Please ingest the latest STIG Compilation Library on the settings page.")
            .Dismiss().WithButton("Dismiss", button => { })
            .Queue();
    }
    catch (Exception exception)
    {
        log.Error(string.Format("Unable to instantiate MainViewModel."));
        log.Debug("Exception details:", exception);
    }
}
...

Debugging

Running through a quick debug, NotificationMessageManager is properly instantiated and the message is created and associated to it. Similarly, looking at the visual tree, the NotificationMessageContainer exists, but is empty. Finally, no error messages are generated and logged, and my output window isn't throwing anything concerning this control, so I have nothing to suggest that anything is failing.

Thanks in advance! I love the way this looks, so I am really hoping to be able to utilize the control. However, I am following a strict "no code behind" policy, which I would rather not violate.

Hey @amkuchta! In my work project, I am indeed using MVVM. Well, for the most part, anyway (grumble grumble need to refactor). I'm using data binding for the notification manager, so my code still applies.

I'm indeed having problems getting your solution to compile, mostly because MahApps doesn't want to build due to not finding that Nuget reference. Don't I have to add some reference to the Appveyor build or something? Could you please point me in the right direction?

I'm not sure without poking around more myself, but does your notification manager GUI item have any width? Have you looked at it in WPF Snoop? I mean, I see that you have it set up in a Grid, but since the project won't build, I'm having a hard time visualizing what it's doing.

Here's the applicable code from my work project if it's helpful:

MainWindow.xaml

<Grid Grid.Row="0" Grid.RowSpan="3" ClipToBounds="True" VerticalAlignment="Stretch">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="3*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Rectangle Fill="#AACCCCCC" Grid.Column="0" Grid.ColumnSpan="3" 
                       Visibility="{Binding PreventWindowInteraction, Converter={StaticResource BooleanToVisibilityConverter}}"/>
    <Border Grid.Column="1">
        <notification:NotificationMessageContainer Manager="{Binding NotificationManager}" />
    </Border>
    <Label Content="doesn't matter for this sample code" 
                   FontSize="36" Grid.ColumnSpan="3" HorizontalAlignment="Center" VerticalAlignment="Center"
                   FontStyle="Italic"
                   Visibility="{Binding AThingForWork, Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>

ViewModel

private INotificationMessageManager _notificationManager;
...
public MainWindowViewModel()
{
    ...
    NotificationManager = new NotificationMessageManager();
}
...
public INotificationMessageManager NotificationManager
{
    get { return _notificationManager; }
    set { _notificationManager = value; RaisePropertyChangedEvent(nameof(NotificationManager)); }
}
...

public NotificationMessageBuilder GetBuilderBase(string message, int autoDismissSeconds = -1)
{
    var builder =
        NotificationManager...
             return builder;
}

MainWindow.xaml.cs (I know, I know...long story. Maybe someday ™️ I can refactor to avoid this)

_dataContext.GetBuilderBase(
     string.Format("blah blah blah"))
    .WithButton("Btn1", button => { Void(); })
    .WithButton("Btn2", button => {
        Void();
     })
    .Dismiss().WithButton("Close", button => { })
    .Queue();

So technically I'm queuing it from the code behind, but the actual NotificationManager is in the view model.

I was also working on a branch for including animations with this lib, but didn't get to finish it. If it's helpful, check out the top two commits on this branch.

@Deadpikle thanks for the response! Yes, I am pulling my MA.M build from Appveyor - the source is https://ci.appveyor.com/nuget/mahapps.metro, and can be added in the Options window (Options -> NuGet Package Manager -> Package Sources). You may also get an error for SQLite - let me know if you do and I can get you through that, too (I had to have the SQLite DLLs set up to do a "portable" install, as users cannot actually install the application on their machines).

As far as the Grid width goes, I do not have one specifically set, but shouldn't it just follow the width of the window? I also saw that you have the NotificationMessageContainer wrapped in a Border - any reason for this? (I tried it, and it still didn't work... grrrr). Also, as a side note, I do not currently have INotifyPropertyChanged implemented for NotificationMessageManager, though I did try this as well and it didn't have any effect.

I'll try running it against WPF Snoop - thanks for the hint on that! Let me know if you can get my solution built!

@amkuchta Yeah, it's a bug. Since you're doing CreateMessage call from VM constructor, the message container in the view isn't yet bound to the manager.

NotificationMessageContainer only listens for OnMessageQueued and that's not triggered when you give it a manager with message already in queue.

Here after attaching event handlers, ManagerOnOnMessageDismissed should be called for all existing messages and ManagerOnOnMessageQueued for each message from the new manager.

Could this be it? Can you test this by not calling CreateMessage from constructor but for example on view Loaded?

Eureka! That was it! My updated code:

View

...
<!--Added an event trigger on the window (which requires an additional namespace for interactivity)-->
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
<i:Interaction.Triggers>
    <i:EventTrigger EventName="Loaded">
        <i:InvokeCommandAction Command="{Binding LaunchStigNotificationCommand}" />
    </i:EventTrigger>
</i:Interaction.Triggers>
...
<!--Everything else stays the same with regards to the NotificationMessageContainer-->
...

ViewModel

...
// Moved the logic to create the NotificationMessage into its own function, which is launched via the command bound above
public RelayCommand LaunchStigNotificationCommand
{ get { return new RelayCommand(LaunchStigNotification); } }

private void LaunchStigNotification()
{ 
    try
    {
        NotificationMessageManager.CreateMessage()
            .Accent("#1751C3")
            .Animates(true)
            .AnimationInDuration(0.75)
            .AnimationOutDuration(0.75)
            .HasBadge("Info")
            .HasMessage("Please ingest the latest STIG Compilation Library on the settings page.")
            .Dismiss().WithButton("Dismiss", button => { })
            .Queue();
    }
    catch (Exception exception)
    {
        log.Error(string.Format("Unable to launch STIG library ingestion notification."));
        log.Debug("Exception details:", exception);
    }
}
...

Hopefully this approach helps you when you decide to refactor your application 😉 Thank you for the help!!!

@amkuchta So is this resolved? Can I close this issue?

@AleksandarDev it sure is! I'll go ahead and close it out, thank you!

Unrelated - how open are you to accepting PRs for enhancements, both to the code (I'd like to tinker with enhancing the control a bit) and to the documentation (perhaps using the wiki)? I love these controls, and I'd like to contribute to them in some way. Do you require issues be opened to tie all PRs back to for tracking?

@amkuchta An issue should be opened before commiting to the feature so that we have a place to discuss issues/problems that may occur. Other than that, we accept all PR's for non-breaking changes w/o question. But if there is a real need to break existing behaviour, we can work out the release plan for that too, but that should be discussed on per-issue basis.

For the wiki, can we have the docs in the source? I'm not really sure where GitHub's wiki stores the docs and how can we control the access.

@AleksandarDev GitHub treats the wiki just like any other source - you can clone it and submit updates / PRs via git, which would then have to be reviewed / approved before they are accepted.