I wanted to include notifications in my existing WinUI 3 application which uses Windows App SDK 1.1.4 and .NET 6 (The application does not and shall not use packaging / MSIX).
In order to achieve this, I tried to extract some code of an example application that I created with the "Template studio for WinUI" project template (assistant), see https://github.com/microsoft/TemplateStudio/ (The sample application also works with the 'unpackaged' deployment model).
The code which I extracted from the example application looks like this (the relevant parts should be the methods 'Initialize' and 'Show'):
public class AppNotificationService : IAppNotificationService
{
public AppNotificationService()
{
}
~AppNotificationService()
{
Unregister();
}
public void Initialize()
{
AppNotificationManager.Default.NotificationInvoked += OnNotificationInvoked;
AppNotificationManager.Default.Register();
}
public void OnNotificationInvoked(AppNotificationManager sender, AppNotificationActivatedEventArgs args)
{
// TODO: Handle notification invocations when your app is already running.
//// // Navigate to a specific page based on the notification arguments.
//// if (ParseArguments(args.Argument)["action"] == "Settings")
//// {
//// App.MainWindow.DispatcherQueue.TryEnqueue(() =>
//// {
//// _navigationService.NavigateTo(typeof(SettingsViewModel).FullName!);
//// });
//// }
App.MainWindow.DispatcherQueue.TryEnqueue(() =>
{
App.MainWindow.ShowMessageDialogAsync("TODO: Handle notification invocations when your app is already running.", "Notification Invoked");
App.MainWindow.BringToFront();
});
}
// EXCEPTION IN THIS METHOD
public bool Show(string payload)
{
var appNotification = new AppNotification(payload); // COM EXCEPTION HERE
AppNotificationManager.Default.Show(appNotification);
return appNotification.Id != 0;
}
public NameValueCollection ParseArguments(string arguments)
{
return HttpUtility.ParseQueryString(arguments);
}
public void Unregister()
{
AppNotificationManager.Default.Unregister();
}
}
As you can see, the code contains a method "Show" that has a string for the payload that represents the notification message. In addition there is a "Initialize" method that the example code calls upon application startup.
In order to call the "Show" method of the code above, I created some small event handler in my application that gets called when I click a button:
private void CreateNotification_Click(object sender, RoutedEventArgs e)
{
AppNotificationService notificationService = new AppNotificationService();
notificationService.Initialize();
string notificationContent = "test";
notificationService.Show(notificationContent);
}
However, the call to "notificationService.Show(notificationContent);" always causes a ComException "0xC00CE556" that is raised when the code tries to instanciate the AppNotification instance see here:
I do not know what I am missing here. It seems that the template studio application does something additional to get the notification working, that I am currently not doing in my code. But I have no idea what that is. Any suggestions?
I couldn't reproduce your COM Exception but these steps worked.
Create a simple WinUI 3 app project.
Bring AppNotificationService.cs and IAppNotificationService.cs from a TemplateStudio project with app notifications.
Open Package.appxmanifest using a text editor (VSCode).
Add these namespaces:
<Package
xmlns:com="http://schemas.microsoft.com/appx/manifest/com/windows10"
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
</Package>
Declare these Extensions inside Applications:
<Applications>
<Application Id="App" Executable="$targetnametoken$.exe" EntryPoint="$targetentrypoint$">
<uap:VisualElements DisplayName="WinUI3BlankAppProjectTemplate" Description="WinUI3BlankAppProjectTemplate" BackgroundColor="transparent" Square150x150Logo="Assets\Square150x150Logo.png" Square44x44Logo="Assets\Square44x44Logo.png">
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" />
<uap:SplashScreen Image="Assets\SplashScreen.png" />
</uap:VisualElements>
<Extensions>
<!--Specify which CLSID to activate when notification is clicked-->
<desktop:Extension Category="windows.toastNotificationActivation">
<desktop:ToastNotificationActivation ToastActivatorCLSID="12345678-9ABC-DEFG-HIJK-LMNOPQRSTUVW" />
</desktop:Extension>
<!--Register COM CLSID-->
<com:Extension Category="windows.comServer">
<com:ComServer>
<com:ExeServer Executable="AppNotifications.exe" Arguments="----AppNotificationActivated:" DisplayName="Toast activator">
<com:Class Id="12345678-9ABC-DEFG-HIJK-LMNOPQRSTUVW" DisplayName="Toast activator" />
</com:ExeServer>
</com:ComServer>
</com:Extension>
</Extensions>
</Application>
</Applications>
Replace Executable="AppNotifications.exe" with your app name.
Create a GUID from [Tools]-[Create GUID] in VisualStudio menu.
Replace the two GUIDs in the Extensions with the GUID you created.
Save the Package.appxmanifest file and reopen and rebuild the solution.
Call the Show method passing a valid payload. For example:
var xmlPayload =
#"
<toast launch=""action=ToastClick"">
<visual>
<binding template=""ToastGeneric"">
<text>App Notification</text>
<text></text>
</binding>
</visual>
<actions>
<action content=""Settings"" arguments=""action=Settings""/>
</actions>
</toast>
";
appNotificationService.Show(xmlPayload);
UPDATE
For un-packaged(non-packaged) apps you get a COM Exception if you don't call the Initialize() method. So, the step 10. should be something like this:
Call Initialize then Show method passing a valid payload. For example:
AppNotificationService appNotificationService = new();
appNotificationService.Initialize();
var xmlPayload =
#"
<toast launch=""action=ToastClick"">
<visual>
<binding template=""ToastGeneric"">
<text>App Notification</text>
<text></text>
</binding>
</visual>
<actions>
<action content=""Settings"" arguments=""action=Settings""/>
</actions>
</toast>
";
appNotificationService.Show(xmlPayload);
I found out what the problem was in my case.
I turned out, that the problem was related to the string I used as notification content. This must not be an arbitrary string (like "test" in the example I used in the question), but an xml string that has a specific format which is needed to represent a "toast" message.
This xml string for the toast message can contain specific elements for text, images and buttons that may appear in the message.
Simple example:
<?xml version="1.0" encoding="utf-8"?>
<toast>
<visual>
<binding template="ToastGeneric">
<text>Some text</text>
</binding>
</visual>
</toast>
This article shows an example of the possible syntax of this xml string:
Quickstart: App notifications in the Windows App SDK - 4 Display an app notification
There are also classes and helpers for the construction of the xml string that you can use by installing the "CommunityToolkit.WinUI.Notifications"
Nuget package:
ToastContent class
ToastContentBuilder class
Example code
Basically, all you need to do to create a toast notification is this:
private void CreateNotification_Click(object sender, RoutedEventArgs e)
{
// Version 1: Directly define the xmlPayload string:
// string xmlPayload = #"<?xml version=""1.0"" encoding=""utf-8""?><toast><visual><binding template=""ToastGeneric""><text>Some text</text></binding></visual></toast>";
// Version 2: Create the the xmlPayload string using the ToastContentBuilder
// ToastContentBuilder comes with the "CommunityToolkit.WinUI.Notifications" Nuget package
ToastContent toastContent = new ToastContentBuilder()
.AddText("Some text")
.GetToastContent();
string xmlPayload = toastContent.GetContent();
var toast = new AppNotification(xmlPayload);
AppNotificationManager.Default.Show(toast);
}
In the example code, I have two versions on how to create the xml string that represents the toast message. You can create the xml yourself or use the ToastContentBuilder class from the "CommunityToolkit.WinUI.Notifications" Nuget package.
Personal opinion
I know that StackOverflow does no focus on personal opinions. However, I would like to express that I am quite disappointed to see that a code like
new AppNotification("test");
raises an ComException lacking any useful information instead of an meaningful exception that contains some hint for the developer complaining about the incorrect format of the provided xml string.
Related
I am using Flowable 6.4.1 in spring-boot to create processes and run from my java code, but requirement is to not use any xml, so due to this I have hit a blockade.
I have a user task, taking input from user, depending on that input, call to corresponding service task is made.
Below is a short example of what I am going to do:
basic-process.bpmn20.xml:
<process id="basicprocess" name="Basic Process" isExecutable="true">
<startEvent id="startEvent"/>
<sequenceFlow sourceRef="startEvent" targetRef="getInput"/>
<userTask id="getInput" name="Get input from user" />
<sequenceFlow sourceRef="getInput" targetRef="decision"/>
<exclusiveGateway id="decision"/>
<sequenceFlow sourceRef="decision" targetRef="firstServiceTask">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${number>100}
]]>
</conditionExpression>
</sequenceFlow>
<sequenceFlow sourceRef="decision" targetRef="secondServiceTask">
<conditionExpression xsi:type="tFormalExpression">
<![CDATA[
${number<=100}
]]>
</conditionExpression>
</sequenceFlow>
<serviceTask id="firstServiceTask" name="Number is greater than predefined target"
flowable:class="demo.service.tasks.FirstServiceTask"/>
<sequenceFlow sourceRef="firstServiceTask" targetRef="greaterEnd"/>
<serviceTask id="secondServiceTask" name="Number is less than predefined target"
flowable:class="demo.service.tasks.SecondServiceTask"/>
<sequenceFlow sourceRef="secondServiceTask" targetRef="lesserEnd"/>
<endEvent id="greaterEnd"/>
<endEvent id="lesserEnd"/>
</process>
Above, XML shows the process and I'm starting the process using REST API
Below is the controller:
DefinitionsController.java:
#RestController
#SuppressWarnings("rawtypes")
public class DefinitionsController {
#Autowired
private RepositoryService mRepositoryService;
#Autowired
private RuntimeService mRuntimeService;
#Autowired
private TaskService mTaskService;
#PostMapping("/start-service")
public String startService(#RequestBody String input) {
Integer request = Integer.parseInt(input);
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("number", request);
ProcessInstance instance = mRuntimeService.startProcessInstanceByKey("basicprocess", variables);
Task userTask = mTaskService.createTaskQuery().processInstanceId(instance.getId()).taskDefinitionKey("getInput").singleResult();
mTaskService.complete(userTask.getId());
return "ProcessInstance id is "+instance.getProcessInstanceId();
}
}
FirstServiceTask.java:
public class FirstServiceTask implements JavaDelegate{
#Override
public void execute(DelegateExecution execution) {
System.err.println("Came in first service task");
}
}
Same for SecondServiceTask.java except the sysout statement.
REST RESPONSE: I get the processInstance Id and sysout statement of respective service task gets printed in console..
Pretty easy to wire the Service Task classes from xml, however if I were to not use XML, I would need to create the same process using flowable-modeler api of FLOWABLE.
So, basically I want to have control over those service tasks from my java code and in order to do that how do I wire the Service Tasks that are created using flowable-modeler with my java code ?
I have gone through docs, but found the xml way only.
Configuring Service Tasks (created using flowable-modeler) with Java code can be done by the 4 ways shown here.
The delegate expression which is going to be configured should be either present on the classpath or should have a spring-bean created.
I created bean using a method in main class, and put the name of method in delegate expressionattribute in flowable-modeler/process api and that's what was needed to do that.
Attached image should clarify things, which shows the way to wire Service Tasks (created using flowable-modeler API) with Java classes in workspace.
firstServiceTask in highlighted field is the method which returns bean of FirstServiceTask
EDIT: Apart from above solution, we can also specify class name in class field alone and all configuration is done. Forex: I have a class called TestClass.java in package org.flowable.learning, so I'll just specify org.flowable.learning.TestClass in class field, which is just above highlighted Delegate expression field in attached screenshot
I'm learning about how to create an IntelliJ plugin. I was reading through some of the documentation on JetBrains. With that documentation, I've created a sample project and now I understand little bits and pieces of SDK. What I'm struggling with right now is how can I create a form that takes some input from the user, submit the form and show the response that it got from the server.
This can be under the tools window. Any sample GitHub project that does something like this?
in plugin.xml you should add
<extensions defaultExtensionNs="com.intellij">
...
<toolWindow factoryClass="SomeClass" id="someUniqueID" />
</extensions>
and then create the factory class like this
public class SomeClass implements ToolWindowFactory {
#Override
public void createToolWindowContent(#NotNull Project p, #NotNull ToolWindow w) {
ContentFactory contentFactory = ContentFactory.SERVICE.getInstance();
JComponent form = new MyMagicForm(...);
Content content = contentFactory.createContent(form, "My form", false);
content.setCloseable(false);
w.getComponent().putClientProperty(ToolWindowContentUi.HIDE_ID_LABEL, "true");
w.getContentManager().addContent(content);
}
}
I have a Burn Bundle with the following variable
<Variable Name="INSTALLFOLDER" Type="string "Value="[ProgramFilesFolder]" />
With the following property in my bootstrapper UI project's main view model
public string InstallDirectory
{
get
{
if (_Engine.StringVariables.Contains("INSTALLFOLDER"))
return _Engine.StringVariables["INSTALLFOLDER"];
return string.Empty;
}
set
{
if (_Engine.StringVariables.Contains("INSTALLFOLDER"))
{
_Engine.StringVariables["INSTALLFOLDER"] = value;
OnPropertyChanged("InstallDirectory");
}
}
}
In my WPF view which has a textbox bound to the InstallDirectory property I only see "[ProgramFilesfolder]" but I was hoping to see something like "C:\Program Files"
I would like to end up with something like the following which will populate my install directory textbox with the default install folder and give the user the option to change it there.
<Variable Name='INSTALLFOLDER' Type='string' Value='[ProgramFilesFolder]$(var.AppName)' />
I could use the Net Framework to get the program files folder for my WPF UI but seems like I should be able to get it from the Wix Bundle. Also the Wix log shows that I am setting the INSTALLFOLDER property from my UI.
My bootstrapper Run looks like this:
protected override void Run()
{
this.Engine.Log(LogLevel.Verbose, "Run has been called on the UI application.");
CurrentDispatcher = Dispatcher.CurrentDispatcher;
_MainWindow = new MainWindow(new MainWindowViewModel(this));
Engine.Detect();
_MainWindow.Show();
Dispatcher.Run();
Engine.Quit(0);
}
I have thought I might need to listen to some event on the BootstrapperApplication after which I could fire on property changed for the InstallDirectory property but haven't found anything interesting yet.
I have been through the Developer Guide book for 3.6 and it doesn't seem to address this exact issue although the final two chapters do deal with burn projects and WPF.
In your get method you should be able to use this to get the actual value of the property:
get
{
if (_Engine.StringVariables.Contains("INSTALLFOLDER"))
return _Engine.FormatString("[INSTALLFOLDER]");
return string.Empty;
}
I'm attaching a event receiver to a single list (Web scope). But the ER runs for all lists in the Web. This question says that the feature, the ER is deployed in, have to be Web scope. This is the case.
The Feature is activated programmatically bound to an ER of a list in the TLS.
newProjectWeb.Features.Add(new Guid("57e21870-6285-4e0a-b9a0-067f774492ae"));
Please see my code below. Am I missing an Update or anything?
Thanks for your help in advance.
public void AddEventReceiverToMemberList()
{
try
{
_clsLists.AddEventReceiverToList(Web, ProjectMemberList.LIST_INTERNAL_NAME, typeof(SCMUProjectMemberList), SPEventReceiverType.ItemAdded);
_clsLists.AddEventReceiverToList(Web, ProjectMemberList.LIST_INTERNAL_NAME, typeof(SCMUProjectMemberList), SPEventReceiverType.ItemDeleting);
_clsLists.AddEventReceiverToList(Web, ProjectMemberList.LIST_INTERNAL_NAME, typeof(SCMUProjectMemberList), SPEventReceiverType.ItemUpdated);
Web.Update();
}
catch (Exception)
{
throw;
}
}
public void AddEventReceiverToList(SPWeb web, string listName, Type eventReceiverClass, SPEventReceiverType eventType)
{
SPList list = this.GetListByName(web, listName);
string className = eventReceiverClass.FullName;
string assemblyName = Assembly.GetAssembly(eventReceiverClass).FullName;
list.EventReceivers.Add(eventType, assemblyName, className);
}
If you want to run the event receiver for a single list..
Refer Here
Check the end of the post, Changing the attribute to "ListTemplateId" to "ListURL" in Elements.xml
In the Elements.xml file replace:
<Receivers ListTemplateId="100">
by
<Receivers ListUrl="Lists/Your List Name">
i'm writing a plugin for Eclipse and i would like to attach one of my actions to Eclipse F5/Refresh event.
Can anyone help me?
Thanks!
You can attach a IExecutionListener to the ICommandService. You will get notification of all the commands executed. You can look for the command id that you want (in this case org.eclipse.ui.file.refresh) and do your operation
I'm assuming you're writing this for Eclipse Helios (3.6).
In Eclipse help, in the Platform Plug-in Developer Guide -> Programmer's Guide -> Advanced resource concepts -> Refresh providers, there's an extension point.
org.eclipse.core.resources.refreshProviders
Your class has to extend RefreshProvider to use this extension.
According to Prakash G. R., I show the sample code.
Because the initialization code in Activator does not work if we need to use Workbench, therefore I use the startup extension point. The plugin.xml is
<extension
point="org.eclipse.ui.startup">
<startup
class="sampleplugin.MyStartUp">
</startup>
</extension>
Therefore in MyStartUp class we add the ExecutionListener to ICommandService.
The important thing is that the ExecutionEvent in the preExecute method is not able to
extract the selection. This is different from usual ExecutionEvent in Command.
Therefore , the MyStartUp.java is
public class MyStartUp implements IStartup {
#Override
public void earlyStartup() {
ICommandService service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService .class);
service.addExecutionListener(
new IExecutionListener() {
...
#Override
public void postExecuteSuccess(String commandId,
Object returnValue) {
// do something post
}
#Override
public void preExecute(String commandId,
final ExecutionEvent event) {
if (org.eclipse.ui.IWorkbenchCommandConstants.FILE_REFRESH.equals(commandId) ) {
IWorkbench wb = PlatformUI.getWorkbench();
IWorkbenchWindow win = wb.getActiveWorkbenchWindow();
IWorkbenchPage page = win.getActivePage();
ISelection selection = page.getSelection();
// do something using selection
}
}
});
}
}
I use the
IWorkbench wb = PlatformUI.getWorkbench();
IWorkbenchWindow win = wb.getActiveWorkbenchWindow();
IWorkbenchPage page = win.getActivePage();
ISelection selection = page.getSelection();
instead of
IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event);
because the above reason. However, this is due to the Eclipse inner mechanism.
The refresh event uses an old action mechanism and The ExternalActionManager call
preExecute method directly in which the event has no data for selection.
I want the second formula
IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event);
may be available in preExecute method in the future.