Assign a value to an attribute of a Microsoft.TeamFoundation.Build.Common.BuildParameter object using XAML - vb.net

my BuildParameter is defined in my xaml build as follows:
<Activity this:Process.AdvancedBuildSettings=
"[New Microsoft.TeamFoundation.Build.Common.BuildParameter(
" { ""Attribute1"": """",
""Attribute2"": ""Value2"",
""Attribute3"": ""Value3"" } "
)]">
Now I want to update the value of Attribute1 of my BuildParameter but I can't figure out how to do it.
It doesn't look like I can use an Assign block because these attributes names are not known by the compiler, so I want to use BuildParameter's SetValue method but I'm not sure how to call this VB code in my xaml.
<Assign DisplayName="Update That Attribute">
<Assign.To>
<OutArgument x:TypeArguments="x:String">[AdvancedBuildSettings.Attribute1]</OutArgument><!-- this throws a compiler error because it doesn't know what Attribute1 is -->
</Assign.To>
<Assign.Value>
<InArgument x:TypeArguments="x:String">""NewValue""</InArgument>
</Assign.Value>
</Assign>

Not familiar with XAML, but here is a code snippet that using TFS API in C# to update the parameter name. You can use WorkflowHelpers.DeserializeProcessParameters Method and WorkflowHelpers.SerializeProcessParameters Method to get parameter name, remove it, and add the new parameter name, maybe it can help you something:
string argumentName = "Attribute1";
var process = Microsoft.TeamFoundation.Build.Workflow.WorkflowHelpers.DeserializeProcessParameters(BuildDefinition.ProcessParameters);
if (process.ContainsKey(argumentName))
{
process.Remove(argumentName);
process.Add(argumentName, attributeValue);
BuildDefinition.ProcessParameters = WorkflowHelpers.SerializeProcessParameters(process);
BuildDefinition.Save();
}

I was right, the "Assign" workflow tool was not the tool I wanted. I needed to use the "InvokeMethod" workflow tool in order to invoke the SetValue() method of my BuildParameter object in my XAML build.
MSDN InvokeMethod documentation
More details about the properties of InvokeMethod
So my solution looks like this:
<InvokeMethod DisplayName="Invoke That Method" MethodName="SetValue">
<InvokeMethod.GenericTypeArguments>
<x:Type Type="x:String" />
</InvokeMethod.GenericTypeArguments>
<InvokeMethod.TargetObject>
<InArgument x:TypeArguments="mtbc:BuildParameter">[AdvancedBuildSettings]</InArgument>
</InvokeMethod.TargetObject>
<InArgument x:TypeArguments="x:String">Attribute1</InArgument>
<InArgument x:TypeArguments="x:String">[NewValue]</InArgument>
</InvokeMethod>

Related

WinUI notifications: COM exception is raised when instanciating instance of "AppNotification"

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.

How to get [ProgramFilesFolder] to populate to "C:\Program Files" burn UI project

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;
}

How can I set up expectations for event registration on a multimock

I am using RhinoMocks 3.6 and would like to use the multimock feature to implement both a class and a interface.
var mocks = new MockRepository();
var project = mocks.StrictMultiMock(
typeof(Project),
typeof(INotifyCollectionChanged));
using (mocks.Record())
{
((INotifyCollectionChanged)project).CollectionChanged += null;
LastCall.Constraints(Is.NotNull()).Repeat.Any();
}
The LastCall is working though. I get this message :
System.InvalidOperationException : Invalid call, the last call has been used or no call has been made (make sure that you are calling a virtual (C#) / Overridable (VB) method).
What am I doing wrong here??
Have you actually checked that the Project class has methods you can override as the error message indicates? I'll assume you have. :-)
I'd suggest you switch to using the AAA syntax instead of record/replay as shown here:
I assume you're wanting to know if the class under test reacts the right way when the CollectionChanged event is fired? If that's the case, you can do it something like this:
var project = MockRepository.GenerateMock<Project, INotifyPropertyChanged>();
project.Expect(p => p.SomeMethod())
.Repeat.Any()
.Raise(p => ((INotifyCollectionChanged)p).CollectionChanged += null,p,new NotifyCollectionChangedEventArgs());

Changing/Adding Resource Dictionary in code behind, Please help

I'm am currently developing a silverlight application, I am still a beginner with this.
I am wondering if it is possible to change the resource dictionary's source in code behind(C#)
within the App.xaml?
I have tried the code below, but get an exception, i am getting the style folder name from a WCF Service, the variable is called Style(this contains the name of the folder)
ResourceDictionary rDictionary = this.Resources.MergedDictionaries[0];
rDictionary.Source = new Uri(string.Format("Resources/Styles/{0}/Styles.xaml", style), UriKind.Relative);
this.Resources.MergedDictionaries.Add(rDictionary);
I'm getting an error at
rDictionary.Source = new Uri(string.Format("Resources/{0}/Styles.xaml", "Default"), UriKind.RelativeOrAbsolute);
Which reads
System.Exception: Error HRESULT E_FAIL has been returned from a call to a COM component.
at MS.Internal.XcpImports.CheckHResult(UInt32 hr)
at MS.Internal.XcpImports.SetValue(IManagedPeerBase obj, DependencyProperty property, String s)
at MS.Internal.XcpImports.SetValue(IManagedPeerBase doh, DependencyProperty property, Object obj)
at System.Windows.DependencyObject.SetObjectValueToCore(DependencyProperty dp, Object value)
at System.Windows.DependencyObject.SetEffectiveValue(DependencyProperty property, EffectiveValueEntry& newEntry, Object newValue)
at System.Windows.DependencyObject.UpdateEffectiveValue(DependencyProperty property, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, ValueOperation operation)
at System.Windows.DependencyObject.SetValueInternal(DependencyProperty dp, Object value, Boolean allowReadOnlySet)
at System.Windows.ResourceDictionary.set_Source(Uri value)
at FCStarFish.App..ctor()
Does this work
<Application.Resources>
<ResourceDictionary x:Key="resourcestyles">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary /> <!-- Dummy, this is the one we will replace -->
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Then we place a ResourceDictionary in [0] (where our dummy ResourceDictionary is).
Load or replace the style dictionary (load it with the default style in Application_Startup in app.xaml.cs)
var rDictionary = new ResourceDictionary();
rDictionary.Source = new Uri(string.Format("/MyApp;component/Resources/Styles/{0}/Styles.xaml", style), UriKind.Relative);
this.Resources.MergedDictionaries[0] = rDictionary;
Replace MyApp with your applications name.
First follow the steps mentioned by NateTheGreat below and prepend your UriStirng with "/". It should look like:
ResourceDictionary rDictionary = this.Resources.MergedDictionaries[0];
rDictionary.Source = new Uri(string.Format("/Resources/Styles/{0}/Styles.xaml", style), UriKind.Relative);
this.Resources.MergedDictionaries.Add(rDictionary);
I just encountered this problem. In my case, the solution was to change the resource dictionary XAML file's build action to "Content", Copy to Output Directory to "Copy if newer", and Custom Tool to an empty string. The defaults were set to Page/Do not copy/MSBuild:Compile, respectively.

Why are the InitParams I pass into Silverlight always empty?

My Silverlight application needs one parameter, an integer. In my Html, I have written:
<param name="InitParameters" value="UserId=1" />
In code I am reading the parameters in:
foreach (KeyValuePair<string, string> pair in e.InitParams)
{
Resources.Add(pair.Key, pair.Value);
}
e.InitParams is always empty. Any ideas?
Because I'm passing in "InitParameters" instead of "InitParams". That's what I get for staring at my code for too long.