Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I make a notification in a C# Windows app with a custom image and onclick function?

My ultimate goal is to have a Windows 10 app in C#.NET that displays a notification to the user. The notification should have a title, description, at least one image, and when clicked, it should open a webpage. It should also be stored in notification manager (or whatever that place in Windows that lists notifications is called) if the user doesn't click on it. So that's the goal.

I've tried a bunch of different ways of doing this, and cannot get any of them to work properly.

My current code uses the Microsoft.Toolkit.Uwp.Notifications NuGet package and is primarily taken from this sample: https://learn.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/adaptive-interactive-toasts?tabs=builder-syntax

The following code works great, as far as it goes:

using System;
using System.Windows;
using Microsoft.Toolkit.Uwp.Notifications;

namespace PushClient
{
    public partial class App : System.Windows.Application
    {
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            new ToastContentBuilder()
            .AddText("My Title")
            .AddText("My Description")
            .Show();
        }
    }
}

Here is the notification it produces...

Just title and description


FIRST PROBLEM

The first problem is that I cannot add a custom image, even though there are supposedly three different methods through which an image can be added. I first tried this:

new ToastContentBuilder()
.AddText("My Title")
.AddText("My Description")
.AddAppLogoOverride((new Uri("https://picsum.photos/48?image=883")), ToastGenericAppLogoCrop.Circle)
.Show();

That successfully removes the default icon at the left of the notification and adds the name of the app at the bottom (since the idea is that the logo has been gotten rid of, so the app has to be identified through some other method). However, there is no new image in place of the old. Just blank space.

An empty icon area

I also attempted this:

new ToastContentBuilder()
.AddText("My Title")
.AddText("My Description")
.AddHeroImage(new Uri("https://picsum.photos/364/180?image=1043"))
.Show();

But that changed literally nothing from the first version as far as I could tell.

Same as the plain text version

Finally I tried this:

new ToastContentBuilder()
.AddText("My Title")
.AddText("My Description")
.AddInlineImage(new Uri("https://picsum.photos/360/202?image=1043"))
.Show();

That seems to have added some blank space beneath the description, but no image.

Blank space at foot


SECOND PROBLEM

Another problem is that I cannot figure out how to add a full onclick action through this process. I would be perfectly happy with either a button that needs to be clicked, or a click action on the notification itself. But however it works, it ultimately needs to open a specified URL in the user's default browser.


OTHER ATTEMPTS

I have also played with other processes of sending notifications, like the ShowBalloonTip process. This seems to have no option at all for a custom image, which is what I want. However, I can select an image from a specified list of images, including a "warning" icon, which I've chosen in this code, and the onclick action is simple to add:

using System;
using System.Threading;
using System.Windows;
using System.Windows.Forms;

namespace PushClient
{
    public partial class App : System.Windows.Application
    {
        private NotifyIcon _notifyIcon = new NotifyIcon();

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            try
            {
                _notifyIcon = new NotifyIcon();
                _notifyIcon.Icon = PushClient.Properties.Resources.accountable2you;
                _notifyIcon.Visible = true;
                _notifyIcon.ContextMenuStrip = new System.Windows.Forms.ContextMenuStrip();

                Thread t = new Thread(() => LaunchBalloonNotification(_notifyIcon, "My Title", "My Description"));
                t.Start();
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
        private static void notifyIcon_BalloonTipClicked(object sender, EventArgs e)
        {
            Console.WriteLine("Action clicked");
        }
        private static void LaunchBalloonNotification(NotifyIcon ico, String title, String msg)
        {
            ico.ShowBalloonTip(
                10000,
                title,
                msg,
                ToolTipIcon.Warning
            );
            ico.BalloonTipClicked += new EventHandler(notifyIcon_BalloonTipClicked);
        }
    }
}

No custom image

I have also tried working with ToastNotificationManager, but the results I get with that are identical to the results I get with Microsoft.Toolkit.Uwp.Notifications... except that ToastNotificationManager requires an AppID to work, and I've had difficulty figuring out how I'm properly supposed to create such a thing for my little Visual Studio test app.

Anyway, if you can point me in the right direction to help me achieve my goal here (preferably with a minimal, reproducible example!), it would be much appreciated!

like image 308
gcdev Avatar asked Mar 18 '21 15:03

gcdev


People also ask

How many types of notifications are there in Android?

Following are the three types of android notifications, Toast Notification – Shows message that fades away after a few seconds. (Background type also) Status Notification – Shows notification message and displayed till user action.


3 Answers

With help from a link @Fildor provided in comments, and with some creative thinking about how to handle local images, I finally have a working solution. Here is the link: https://learn.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/send-local-toast?tabs=desktop

I wound up converting the two images that I need, both of which were going to be deployed with the app, into data URIs like a person might use in HTML. Then I saved those locally. Then I use the C# Uri object in the AddAppLogoOverride method. There's probably an easier way, but this was the best I could come up with that actually worked.

My revised & working (that is -- "working" if you use real image data from a real image uploaded to the opinionatedgeek encoder) sample code is below.

using System;
using System.Windows;
using Microsoft.Toolkit.Uwp.Notifications;
using System.IO;

namespace PushClient
{
    public partial class App : System.Windows.Application
    {
        private String imageFilePath = String.Empty;

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            try
            {
                SaveImageFilesToCommonFolder();
                LaunchToastNotification("Hello World!", "This is such a nice world!", "https://presuppositions.org");
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        private void LaunchToastNotification(string title, string description, string url)
        {
            // https://learn.microsoft.com/en-us/windows/uwp/design/shell/tiles-and-notifications/send-local-toast?tabs=desktop
            Uri img = new Uri(imageFilePath);

            // Listen to notification activation
            ToastNotificationManagerCompat.OnActivated += toastArgs =>
            {
                // Obtain the arguments from the notification
                ToastArguments args = ToastArguments.Parse(toastArgs.Argument);
                // https://stackoverflow.com/questions/4580263/how-to-open-in-default-browser-in-c-sharp
                System.Diagnostics.Process.Start(args["url"]);
            };

            new ToastContentBuilder()
                .AddText(title)
                .AddText(description)
                .AddAppLogoOverride(img)
                .AddButton(new ToastButton()
                    .SetContent("View Report")
                    .AddArgument("action", "viewReport")
                    .AddArgument("url", url))
                .Show();
        }

        private string SaveDataUrlToFile(string dataUrl, string savePath)
        {
            // https://stackoverflow.com/questions/27710576/convert-from-a-dataurl-to-an-image-in-c-sharp-and-write-a-file-with-the-bytes
            var binData = Convert.FromBase64String(dataUrl);
            System.IO.File.WriteAllBytes(savePath, binData);
            return savePath;
        }

        private void SaveImageFilesToCommonFolder()
        {
            // https://stackoverflow.com/questions/27710576/convert-from-a-dataurl-to-an-image-in-c-sharp-and-write-a-file-with-the-bytes
            // Write images to common app data folder
            var directory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
            // Uploaded PNG to this location to get the Data URI,
            // which is really much longer than displayed here:
            // https://www.opinionatedgeek.com/codecs/base64encoder
            String imageFileData = "iVBORw0KGgoAAAANUgAAAMAAAADRU5ErkJggg==";
            imageFilePath = Path.Combine(directory, "myimage.png");
            SaveDataUrlToFile(imageFileData, imageFilePath);
        }
    }
}
like image 157
gcdev Avatar answered Oct 31 '22 17:10

gcdev


I reached this post because I had the exact same problem. It's funny because based on your application name, I think we are building the same app :) Anyway, I've found out an easier way of achieving this.

First thing, create a directory on your project and put your images there. In my case, I created an "Images" directory:

enter image description here

Make sure the "Build Action" for your image file is set to "Content" and your "Copy to Output Directory" is set to "Copy if newer" (or "Always").

Then your code could be simply something like this:

var imageUri = Path.GetFullPath(@"Images\logo.png");

new ToastContentBuilder()
    .AddArgument("action", "viewConversation")
    .AddArgument("conversationId", 9813)
    .AddText(title)
    .AddText(message + " URL:" + url)
    .AddAppLogoOverride(new Uri(imageUri))
    .AddHeroImage(new Uri(imageUri))
    .AddButton(new ToastButton()
        .SetContent("Open URL")
        .SetProtocolActivation(new Uri(url))
     )
    .Show();

The result:

enter image description here

like image 23
Tiago Avatar answered Oct 31 '22 15:10

Tiago


If you don't need to fetch images from the web and can just reference files within your project, the docs don't make this clear enough but when you're working with a regular desktop/console application you're not bound by the same restrictions as a UWP app would have.

You can make a file:/// URL directly to images located in your project and it will work for both app icons and "hero images":

var iconUri = "file:///" + Path.GetFullPath("images/icon.png");
var imageUri = "file:///" + Path.GetFullPath("images/image.jpg");

new ToastContentBuilder()
    .AddText("Notification text")
    .AddAppLogoOverride(new Uri(iconUri), ToastGenericAppLogoCrop.Circle)
    .AddHeroImage(new Uri(imageUri))
    .Show();

like image 31
Julian Melville Avatar answered Oct 31 '22 17:10

Julian Melville