I create and post a Toast as follows:
public async Task Handle(Alert a) {
var tst = String.Format(#"<toast><visual><binding template='ToastGeneric'><text>Alert</text><text>{0}</text></binding></visual></toast>", a.Msg);
var xml = new XmlDocument();
xml.LoadXml(tst);
var tn = new ToastNotification(xml);
var t = new TaskCompletionSource<bool>();
tn.Dismissed += (s, e) => {
t.TrySetResult(true);
PersistLog.i(TAG, "Handle:Toast Dismissed:" + e.Reason);
};
tn.Failed += (s, e) => {
t.TrySetResult(false);
PersistLog.i(TAG, "Handle:Toast Failed");
};
*/
ToastNotificationManager.CreateToastNotifier().Show(tn);
var how = await t.Task;
}
The Toast Never displays. If Does call the Dismissed CB ... and the Reason is UserCancelled. Why ??? It does show up in the Windows 10 (Anniversary) Action Center.
I have tried calling this from a UI thread as well from the App's OnBackgroundActivated event. The latter is where I really want to call this from ... I want to display a Toast when my App is activated as a Background task from an WNS event.
Related
I've created a toast-message in WPF, with a button.
public void ShowToast(string message)
{
var toastButton = new ToastButton();
toastButton.ActivationType = ToastActivationType.Foreground;
toastButton.SetContent("Reconnect");
toastButton.AddArgument("action", "like");
var toast = new ToastContentBuilder()
.AddText("VPN Notifications")
.AddText(message)
.AddButton(toastButton).SetBackgroundActivation();
toast.Show();
}
How can I get the button callback in my C# dotnet 6 app?
I'm currently working on a winforms app which after certain action sends a notification to user, when activated(clicked) it opens a link. So I can send the notification, I can open the link with toast.Activated but when banner disappear and gets in to the action center when I click on the notification it doesn't activate. So, I have searched a lot but couldn't find a way to activate the notification on action center.
Here is the code I'm currently using to send notification.
{
public void Toasty()
{
// Get a toast XML template
Windows.Data.Xml.Dom.XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(ToastTemplateType.ToastText04);
// Fill in the text elements
Windows.Data.Xml.Dom.XmlNodeList stringElements = toastXml.GetElementsByTagName("text");
stringElements[0].AppendChild(toastXml.CreateTextNode("Header"));
stringElements[1].AppendChild(toastXml.CreateTextNode("Message"));
stringElements[2].AppendChild(toastXml.CreateTextNode("From"));
ToastNotification toast = new ToastNotification(toastXml);
toast.Activated += toast_Activated;
//toast.SuppressPopup = true;
ToastNotificationManager.CreateToastNotifier("App").Show(toast);
}
async void toast_Activated(ToastNotification sender, object args)
{
await Launcher.LaunchUriAsync(new Uri("http://www.google.com"));
}
}
You don't need raw xml for this, get this nuget package :
Microsoft.Toolkit.Uwp.Notifications, then use the ToastContentBuilder to build a toast notification, it's easier and cleaner:
// Construct the visuals of the toast (using Notifications library)
ToastContent toastContent = new ToastContentBuilder()
.AddToastActivationInfo("action=viewConversation&conversationId=5", ToastActivationType.Foreground)
.AddText("Hello world!")
.GetToastContent();
// And create the toast notification
var toast = new ToastNotification(toastContent.GetXml());
// And then show it
DesktopNotificationManagerCompat.CreateToastNotifier().Show(toast);
Now for the activation part:
//Another way of creating a notification
public void CreateAndShowPrompt(string message)
{
ToastContent toastContent = new ToastContent()
{
Launch = "bodyTapped",
Visual = new ToastVisual()
{
BindingGeneric = new ToastBindingGeneric()
{
Children =
{
new AdaptiveText()
{
Text = message
},
}
}
},
Actions = new ToastActionsCustom()
{
Buttons = { new ToastButton("Yes", "Yes"), new ToastButton("No", "No") }
},
Header = new ToastHeader("header", "App", "header")
};
var doc = new XmlDocument();
doc.LoadXml(toastContent.GetContent());
var promptNotification = new ToastNotification(doc);
promptNotification.Activated += PromptNotificationOnActivated;
DesktopNotificationManagerCompat.CreateToastNotifier().Show(promptNotification);
}
Event handler :
private void PromptNotificationOnActivated(ToastNotification sender, object args)
{
ToastActivatedEventArgs strArgs = args as ToastActivatedEventArgs;
switch (strArgs.Arguments)
{
case "Yes":
//stuff
break;
case "No":
//stuff
break;
case "bodyTapped":
//stuff
break;
}
}
This works in all cases, even when the toast is pushed back to the action center.
You can see it in action in an app that i made: NetStalker
I have a background task that is supposed to open a toast message, à la: ToastNotificationManager.CreateToastNotifier().Show(toast);. My code executes fine, no errors are thrown, nothing hangs -- but also, no toast message appears.
I checked the Event Viewer logs, and they say this:
An instance of the background task with entry point
BG.ToastBackgroundTask running for user [me] in session [sesh]
returned with error code 0x0.
I have looked all over to see what this error code might mean but found nothing.
Here's my code:
public sealed class ToastBackgroundTask : IBackgroundTask
{
private BackgroundTaskDeferral _deferral;
public async void Run(IBackgroundTaskInstance taskInstance)
{
var cancelToken = new System.Threading.CancellationTokenSource();
taskInstance.Canceled += (s, e) =>
{
cancelToken.Cancel();
cancelToken.Dispose();
};
taskInstance.Task.Completed += Task_Completed;
_deferral = taskInstance.GetDeferral();
try
{
await SendNotificationAsync();
}
finally { _deferral.Complete(); }
}
public static async void Register()
{
var isRegistered = BackgroundTaskRegistration.AllTasks.Values.Any(x => x.Name == nameof(ToastBackgroundTask));
if (isRegistered) return;
var accessStatus = await BackgroundExecutionManager.RequestAccessAsync();
if (accessStatus == BackgroundAccessStatus.DeniedByUser || accessStatus == BackgroundAccessStatus.DeniedBySystemPolicy) return;
var builder = new BackgroundTaskBuilder
{
Name = nameof(ToastBackgroundTask),
TaskEntryPoint = $"{nameof(MyNameSpace)}.{nameof(BG)}.{nameof(ToastBackgroundTask)}"
};
builder.SetTrigger(new TimeTrigger(120, false));
var task = builder.Register();
}
private static void Task_Completed(BackgroundTaskRegistration sender, BackgroundTaskCompletedEventArgs args)
{
try
{
args.CheckResult();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
private Task SendNotificationAsync()
{
var service = new ToastService();
service.CreateToast(new ToastViewModel { Title = "Title", Text = "Text", ImagePath = "", Id = 3 });
return Task.CompletedTask;
}
}
If I run CheckResult() on the completed task, no errors are thrown. Argh! Does anyone know (a) what this Event Viewer log error means, or (b) why my toast isn't showing up?
Here's my Toast code too in case it helps:
public class ToastService
{
public void CreateToast(ToastViewModel model)
{
var visual = new ToastVisual()
{
BindingGeneric = new ToastBindingGeneric()
{
Children =
{
new AdaptiveText() { Text = model.Title },
new AdaptiveText() { Text = model.Text }
},
Attribution = new ToastGenericAttributionText() { Text = "Via Me" }
}
};
var tContent = new ToastContent()
{
Visual = visual,
ActivationType = ToastActivationType.Background,
Scenario = ToastScenario.Reminder
};
var toast = new ToastNotification(tContent.GetXml())
{
ExpirationTime = DateTime.Now.AddDays(model.Expiration)
};
toast.Failed += (o, args) => {
var message = args.ErrorCode;
};
ToastNotificationManager.CreateToastNotifier().Show(toast);
}
}
Does anyone know (a) what this Event Viewer log error means, or (b) why my toast isn't showing up?
The above event log showed on my side is information level, not error level. The reason for the toast isn't showing up should be that you setting the ExpirationTime property for the ToastNotification. This property is for:
Gets or sets the time after which a toast notification should not be displayed.
So that if the model.Expiration equals to 0, the toast will not show from now. Ensure the expiration time later than now should work.
var toast = new ToastNotification(tContentxml)
{
ExpirationTime = DateTime.Now.AddDays(1)
};
Otherwise your code snippet can work will on my side. If you still have issues, you can provide a minimal reproduced project to let us have a testing.
I am new to WP 8.1 App development and I have been trying to build an app which sends out toast notifications when the user enters a geofence. This works well when only one geofence has been set . But when I set more than 1 geofence, the toasts are sometimes shown only when all geofences have been activated , sometimes all toasts are shown just after reaching the first destination. For example, if I set two geofences , one at Googleplex and the other at Disneyland , the toasts are shown only when I have gone to both Googleplex and to Disneyland. (or both toasts are shown when I go to the first destination). I am at my wits end about what to do with this. Is there a way to settle this issue?
(The code below sets up a geofence on the tap of an image)('App.nameofplace' captures the name of the place selected. 'selectedpoint' captures coordinates of the place.)
private async void SetAlarm_Tapped(object sender, TappedRoutedEventArgs e)
{
//This line of code sets up a custom DataModel that need to display details about the geofence that has been set.
await App.DataModel.AddCurrentAlarmAsync(App.nameofplace,DateTime.Now,selectedPoint.Position.Latitude,
selectedPoint.Position.Longitude,
currentPoint.Position.Latitude,currentPoint.Position.Longitude,vicinity);
// Set fence ID
string fenceId = App.nameofplace;
double radius = 300; // in meters
// Set circular region for geofence
Geocircle geocircle = new Geocircle(selectedPoint.Position, radius);
// Remove geofence after the first trigger
bool singleUse = true;
// Set monitored states
MonitoredGeofenceStates monitoredStates =
MonitoredGeofenceStates.Entered;
// Set how long you need to be in geofence for enter event to fire
TimeSpan dwellTime = TimeSpan.FromSeconds(1.00);
// Set how long the geofence should be active
TimeSpan duration = TimeSpan.FromDays(5);
// Set up the start time of the geofence
DateTimeOffset startTime = DateTime.Now;
// Create geofence
Geofence geofence = new Geofence(fenceId, geocircle, monitoredStates, singleUse, dwellTime, startTime, duration);
GeofenceMonitor.Current.Geofences.Add(geofence);
//Register Background task
await Register(App.nameofplace);
Frame.Navigate(typeof(MainPage));
}
Method to register Background Task
public async Task Register(string taskName)
{
if (!IsTaskRegistered(taskName))
{
var result = await BackgroundExecutionManager.RequestAccessAsync();
var builder = new BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = "WindowsRuntimeComponent1.Bgt";
builder.SetTrigger(new LocationTrigger(LocationTriggerType.Geofence));
builder.Register();
}
return;
}
Checks if the task is registered
private static bool IsTaskRegistered(string taskName)
{
// throw new NotImplementedException();
var taskRegistered = false;
var entry = BackgroundTaskRegistration.AllTasks.FirstOrDefault(task => task.Value.Name == taskName);
if (entry.Value != null)
{
taskRegistered = true;
}
return taskRegistered;
}
Background Task(WinRT component)
public sealed class Bgt:IBackgroundTask
{
BackgroundTaskDeferral _deferral = null;
public void Run(IBackgroundTaskInstance taskInstance)
{
ToastTemplateType toastTemplate = ToastTemplateType.ToastImageAndText01;
XmlDocument toastXml = ToastNotificationManager.GetTemplateContent(toastTemplate);
XmlNodeList toastTextElements = toastXml.GetElementsByTagName("text");
toastTextElements[0].AppendChild(toastXml.CreateTextNode("You have reached " + taskInstance.Task.Name + "."));
XmlNodeList toastImageElements = toastXml.GetElementsByTagName("image");
((XmlElement)toastImageElements[0]).SetAttribute("src", "ms:appx///Assets/Square71x71Logo.scale-240.png");
((XmlElement)toastImageElements[0]).SetAttribute("alt", "Default Graphic");
IXmlNode toastNode = toastXml.SelectSingleNode("/toast");
((XmlElement)toastNode).SetAttribute("launch", "{\"type\":\"toast\",\"param1\":\"12345\",\"param2\":\"67890\"}");
ToastNotification toast = new ToastNotification(toastXml);
ToastNotificationManager.CreateToastNotifier().Show(toast);
taskInstance.Task.Unregister(true);
_deferral.Complete();
}
}
}
I have a WPF application running a background task which uses async/await. The task is updating the app's status UI as it progresses. During the process, if a certain condition has been met, I am required to show a modal window to make the user aware of such event, and then continue processing, now also updating the status UI of that modal window.
This is a sketch version of what I am trying to achieve:
async Task AsyncWork(int n, CancellationToken token)
{
// prepare the modal UI window
var modalUI = new Window();
modalUI.Width = 300; modalUI.Height = 200;
modalUI.Content = new TextBox();
using (var client = new HttpClient())
{
// main loop
for (var i = 0; i < n; i++)
{
token.ThrowIfCancellationRequested();
// do the next step of async process
var data = await client.GetStringAsync("http://www.bing.com/search?q=item" + i);
// update the main window status
var info = "#" + i + ", size: " + data.Length + Environment.NewLine;
((TextBox)this.Content).AppendText(info);
// show the modal UI if the data size is more than 42000 bytes (for example)
if (data.Length < 42000)
{
if (!modalUI.IsVisible)
{
// show the modal UI window
modalUI.ShowDialog();
// I want to continue while the modal UI is still visible
}
}
// update modal window status, if visible
if (modalUI.IsVisible)
((TextBox)modalUI.Content).AppendText(info);
}
}
}
The problem with modalUI.ShowDialog() is that it is a blocking call, so the processing stops until the dialog is closed. It would not be a problem if the window was modeless, but it has to be modal, as dictated by the project requirements.
Is there a way to get around this with async/await?
This can be achieved by executing modalUI.ShowDialog() asynchronously (upon a future iteration of the UI thread's message loop). The following implementation of ShowDialogAsync does that by using TaskCompletionSource (EAP task pattern) and SynchronizationContext.Post.
Such execution workflow might be a bit tricky to understand, because your asynchronous task is now spread across two separate WPF message loops: the main thread's one and the new nested one (started by ShowDialog). IMO, that's perfectly fine, we're just taking advantage of the async/await state machine provided by C# compiler.
Although, when your task comes to the end while the modal window is still open, you probably want to wait for user to close it. That's what CloseDialogAsync does below. Also, you probably should account for the case when user closes the dialog in the middle of the task (AFAIK, a WPF window can't be reused for multiple ShowDialog calls).
The following code works for me:
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
namespace WpfAsyncApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Content = new TextBox();
this.Loaded += MainWindow_Loaded;
}
// AsyncWork
async Task AsyncWork(int n, CancellationToken token)
{
// prepare the modal UI window
var modalUI = new Window();
modalUI.Width = 300; modalUI.Height = 200;
modalUI.Content = new TextBox();
try
{
using (var client = new HttpClient())
{
// main loop
for (var i = 0; i < n; i++)
{
token.ThrowIfCancellationRequested();
// do the next step of async process
var data = await client.GetStringAsync("http://www.bing.com/search?q=item" + i);
// update the main window status
var info = "#" + i + ", size: " + data.Length + Environment.NewLine;
((TextBox)this.Content).AppendText(info);
// show the modal UI if the data size is more than 42000 bytes (for example)
if (data.Length < 42000)
{
if (!modalUI.IsVisible)
{
// show the modal UI window asynchronously
await ShowDialogAsync(modalUI, token);
// continue while the modal UI is still visible
}
}
// update modal window status, if visible
if (modalUI.IsVisible)
((TextBox)modalUI.Content).AppendText(info);
}
}
// wait for the user to close the dialog (if open)
if (modalUI.IsVisible)
await CloseDialogAsync(modalUI, token);
}
finally
{
// always close the window
modalUI.Close();
}
}
// show a modal dialog asynchronously
static async Task ShowDialogAsync(Window window, CancellationToken token)
{
var tcs = new TaskCompletionSource<bool>();
using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
{
RoutedEventHandler loadedHandler = (s, e) =>
tcs.TrySetResult(true);
window.Loaded += loadedHandler;
try
{
// show the dialog asynchronously
// (presumably on the next iteration of the message loop)
SynchronizationContext.Current.Post((_) =>
window.ShowDialog(), null);
await tcs.Task;
Debug.Print("after await tcs.Task");
}
finally
{
window.Loaded -= loadedHandler;
}
}
}
// async wait for a dialog to get closed
static async Task CloseDialogAsync(Window window, CancellationToken token)
{
var tcs = new TaskCompletionSource<bool>();
using (token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: true))
{
EventHandler closedHandler = (s, e) =>
tcs.TrySetResult(true);
window.Closed += closedHandler;
try
{
await tcs.Task;
}
finally
{
window.Closed -= closedHandler;
}
}
}
// main window load event handler
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var cts = new CancellationTokenSource(30000);
try
{
// test AsyncWork
await AsyncWork(10, cts.Token);
MessageBox.Show("Success!");
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
}
[EDITED] Below is a slightly different approach which uses Task.Factory.StartNew to invoke modalUI.ShowDialog() asynchronously. The returned Task can be awaited later to make sure the user has closed the modal dialog.
async Task AsyncWork(int n, CancellationToken token)
{
// prepare the modal UI window
var modalUI = new Window();
modalUI.Width = 300; modalUI.Height = 200;
modalUI.Content = new TextBox();
Task modalUITask = null;
try
{
using (var client = new HttpClient())
{
// main loop
for (var i = 0; i < n; i++)
{
token.ThrowIfCancellationRequested();
// do the next step of async process
var data = await client.GetStringAsync("http://www.bing.com/search?q=item" + i);
// update the main window status
var info = "#" + i + ", size: " + data.Length + Environment.NewLine;
((TextBox)this.Content).AppendText(info);
// show the modal UI if the data size is more than 42000 bytes (for example)
if (data.Length < 42000)
{
if (modalUITask == null)
{
// invoke modalUI.ShowDialog() asynchronously
modalUITask = Task.Factory.StartNew(
() => modalUI.ShowDialog(),
token,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
// continue after modalUI.Loaded event
var modalUIReadyTcs = new TaskCompletionSource<bool>();
using (token.Register(() =>
modalUIReadyTcs.TrySetCanceled(), useSynchronizationContext: true))
{
modalUI.Loaded += (s, e) =>
modalUIReadyTcs.TrySetResult(true);
await modalUIReadyTcs.Task;
}
}
}
// update modal window status, if visible
if (modalUI.IsVisible)
((TextBox)modalUI.Content).AppendText(info);
}
}
// wait for the user to close the dialog (if open)
if (modalUITask != null)
await modalUITask;
}
finally
{
// always close the window
modalUI.Close();
}
}