Caliburn.Micro Notify Change for Static Property

Normally in Caliburn.Micro and MVVM in general, you would have non-static properties that would be updated and notified like this:

private string _name;

public string Name
{
    get { return _name; }
    set
    {
        _name = value;
        NotifyOfPropertyChange(() => Name);
    }
}

However, if I had a static property, for example, if I wanted a property that could be accessed by another namespace, I would need to implement my own event handler as the default NotifyOfPropertyChange doesn’t operate on static properties.

This is the new EventHandler that I implemented:

public static event EventHandler<PropertyChangedEventArgs> StaticPropertyChanged;
private static void NotifyStaticPropertyChanged(string propertyName)
{
    StaticPropertyChanged?.Invoke(null, new PropertyChangedEventArgs(propertyName));
}

Finally, in order to use it. I would use this instead of the first example for non-static properties:

private string _name;

public string Name
{
    get { return _name; }
    set
    {
        _name = value;
        NotifyStaticPropertyChanged(() => Name);
    }
}

I hope this helps someone. Enjoy!

Caliburn.Micro MVVM Boolean To Visibility Converter

Say I wanted to toggle the visibility of a WPF object in an MVVM way, what would I need to do?

Here is what I currently have:

  • ViewModels
    • ShellViewModel
    • LoadingViewModel
  • Views
    • ShellView
    • LoadingView

When the application is loaded, the ShellViewModel is used to display the LoadingView in a ContentControl object. I have a button on there that I want to become visible after the LoadingView has been activated for 5 seconds.

What I need to do is created a custom class that has two methods: a way of converting a boolean to a visibility; and a way of converting a visibility to a visibility.

So I created a new folder in my tree called “Converters“. In here, I create a new class called “BooleanToVisiblityConverter“.  Here is what my tree now looks like:

  • ViewModels
    • ShellViewModel
    • LoadingViewModel
  • Views
    • ShellView
    • LoadingView
  • Converters
    • BooleanToVisibilityConverter

In my BooleanToVisibilityConverter class, I inherited from IValueConverter and added the necessary two methods. You can see the entire class below:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Data;

namespace Project.Converters
{
    /// <summary>
    /// Contains the converter and convertback methods for the boolean to visibility conversions
    /// </summary>
    public sealed class BooleanToVisibilityConverter : IValueConverter
    {
        /// <summary>
        /// Used to convert a boolean to a visibility
        /// </summary>
        /// <param name="value">This is the boolean input</param>
        /// <param name="targetType"></param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns>Returns a visibility</returns>
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(value is bool))
            {
                //If there is an issue with the input, return collapsed
                return Visibility.Collapsed;
            }
            return (bool)value ? Visibility.Visible : Visibility.Collapsed;
        }

        /// <summary>
        /// Used to take a visibility and returns a visibility
        /// </summary>
        /// <param name="value">This is the boolean input</param>
        /// <param name="targetType"></param>
        /// <param name="parameter"></param>
        /// <param name="culture"></param>
        /// <returns>Returns a visibility</returns>
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(value is Visibility))
            {
                //If there is an issue wtih the input, return collapsed
                return Visibility.Collapsed;
            }
            return (Visibility)value == Visibility.Visible;
        }
    }
}

Next, in my LoadingView, I added “ xmlns:sp=”clr-namespace:Project.Converters” so that the view could use the converter namespace. This is what my view dependancies look like:

<UserControl x:Class="Project.Views.MainView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:cal="http://www.caliburnproject.org"
             xmlns:sp="clr-namespace:Project.Converters"
             xmlns:local="clr-namespace:Project.Views"
             mc:Ignorable="d" FontSize="14" FontFamily="/Project;component/Assets/Fonts/#Roboto"
             d:DesignHeight="550" d:DesignWidth="400">

I then added the binding to the visibility property of my button. You can see this below:

<Button Name="LoadingButton" Content="Press Me" Visibility="{Binding ButtonIsVisible, Converter={StaticResource BooleanConverter}, FallBackValue=Collapsed}"

This means that it will get its visibility value from the LoadingViewModel and if this fails, it will fall back to being collapsed.

We’re almost done. In the LoadingViewModel create a full property which will hold and change the value for the visibility. This needs to be named the same as the binding given in the LoadingView (i.e ButtonVisibility). You can see this below:

private bool _buttonIsVisible;

public bool ButtonIsVisible
{
    get {return _buttonIsVisible;
    set
    {
        if (value != _buttonIsVisible)
        {
            _buttonIsVisible = value;
            NotifyOfPropertyChange(() => ButtonIsVisible);
        }
    }
}

So now if you want the button to be visible, you can just update the ButtonIsVisible property in the LoadingViewModel. Here is an example below (Don’t actually do this):

public LoadingViewModel(){
    ButtonIsVisible = false;
    Task.Delay(5000);
    ButtonIsVisible = true;
}

I hope this helped you. Enjoy!

Setting Up Caliburn.Micro MVVM

This is a fairly lengthy post that shows how to set up an initial MVVM WPF form using Caliburn.Micro.

Let’s get started, we’ll open up Visual Studio and chose to create a new WPF:

Visual Studio -> New Project -> WPF App (.Net Framework)

I usually set the project name to something like WPFUI and my solution name to be something like MVVMProject or the actual product name. For example, Microsoft might use Microsoft Outlook. Probably not, but you get what I’m saying…

 

1)

First I will add Caliburn.Micro to my project. To do this go to Solution Explorer -> References -> Add NuGet Packages and search for Caliburn.Micro:

 

2)

Now we can delete the MainWindow.xaml

 

3)

Create a Views and ViewModels folder. This is where your viewmodels and views will call home.

 

4)

Create a new class in the ViewModels folder and call it ShellViewModel and make it public. You’ll also want to inherit from screen and add the using statement for Caliburn.Micro:

5)

Create a new window in the Views folder and call it ShellView

6)

Create a new class in the root directory called Bootstrapper and inherit from BootstrapperBase and add the using statement for Caliburn.Micro

7)

In App.xaml remove the StartupUri element and add the bootstrapper class as a resource by adding:

<ResourceDictionary>
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary>
            <local:Bootstrapper x:Key="Bootstrapper" />
        </ResourceDictionary>
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

Your app.xaml should look like this:

 

8)

Now go back into your Bootstrapper class and add the following:

public Bootstrapper()
{
    Initialize();
}
protected override void OnStartup(object sender, StartupEventArgs e)
{
    DisplayRootViewFor<ShellViewModel>();
}

You’ll also need to add a using statement for System.Windows and ProjectName.ViewModels.

The program should now launch and show an empty white screen with “ShellView” as the window’s title. This is the first entry into a hopefully long series into the world of Caliburn.Micro.

Enjoy! ?

Don’t use $input in PowerShell Functions

Just a short post and a gentle reminder to check conventions before pulling my hair out over a simple issue.

This all started when I wanted to test something simple in PowerShell. I had started using C# more often and I think this is what caused my brain just to use $input as I would normally use something like this in C#.

In C# my test functions (or methods) might look something like this:

public string TestFunction(string input){
    Console.WriteLine(input);
}

In C#, the above works…

However, $input is a reserved variable. You can see a list of them all here. But much to my annoyance, the PowerShell ISE doesn’t flag this as an issue (technically it isn’t) and just runs like nothing is wrong (once again, technically nothing is wrong).

In PowerShell, this doesn’t work…

function Test-Function([string]$input){
    Write-Host $input
}

After messing for about thirty minutes, I finally found this knowledge out and renamed my parameter. I think you’ll like the name I continued to use… ?

function Test-Function([string]$stupidFuckingInput){
    Write-Host $stupidFuckingInput
}

Enjoy!

WPF Spinning Image – Loading Wheel

This is just a quick post for something that caused me quite a bit of hassle. How to spin an image in WPF. This is useful for creating loading icons such as a spinning icon.

This is what my implementation looks like:

To create this effect, I first added a resource to my user control. This is what that looks like:

    <UserControl.Resources>
        <Storyboard x:Key="imageRotationStoryboard" Storyboard.TargetName="loadingImage" Storyboard.TargetProperty="RenderTransform.(RotateTransform.Angle)">
            <DoubleAnimation From="0" To="360" BeginTime="0:0:0" Duration="0:0:1.5" RepeatBehavior="Forever"/>
        </Storyboard>
    </UserControl.Resources>

What this does is create a story animation that rotates the image from 0 degrees to 360 degrees (a full circle) in 5 seconds. It also does this forever, hence the spinning effect ?

I then created an image object and set my source. I also changed the RenderTransformOrigin to be 0.5,0.5, this means that the rotation happens in the centre of the image instead of the default top left corner. I then created an event on the image at loaded to start the storyboard animation and set the rotate to 0 on load. Heres what that looks like:

<Image Name="loadingImage" Margin="200"  RenderTransformOrigin="0.5,0.5" Source="/SlickRHI;component/Assets/Images/Loading.png">
	<Image.Triggers>
		<EventTrigger RoutedEvent="FrameworkElement.Loaded">
			<EventTrigger.Actions>
				<BeginStoryboard Storyboard="{StaticResource imageRotationStoryboard}" />
			</EventTrigger.Actions>
		</EventTrigger>
	</Image.Triggers>
	<Image.RenderTransform>
		<RotateTransform Angle="0"/>
	</Image.RenderTransform>
</Image>

Hope this helps someone, I know I will need this at least once again in the future. Enjoy ?

Office Click-To-Run and XML Files

So, it used to be that we would install Office using a batch script that would invoke a setup.exe, assign a specific /configure flag and manually assign a specific XML file that contained the product that we wanted to install. This was bulky. It got too bulky when we needed to install 32-bit and 64-bit versions.

TIME FOR A CHANGE!

This is when I started thinking: “wow I really hate batch. I’m really glad I’m not the one that had to write this old script. Lets PowerShell this shit!”

First I needed a template XML file to modify, So this is what that looks like:

<Configuration>
  <Add OfficeClientEdition="64" Channel="Current">
    <Product ID="O365BusinessRetail">
      <Language ID="MatchOS" />
    </Product>
  </Add>
</Configuration>

This is the file that we will edit to say which product we want installing also if we want 64-bit or 32-bit.

Next, I needed to create a PowerShell script that would take a user’s input, edit the XML file accordingly and start the setup.exe with this flag. I also needed the bit-version that they wanted.

I started by defining the variables I would need for the script:

#Variables used for the installation
$bitVersion = ""
$officeProduct = ""
$pathToOffice = "\\path\to\office\folder"
$xmlFile = "OfficeXML.xml"
$pathToXMLFile = Join-Path -Path $pathToOffice -ChildPath $xmlFile

Then I created a function I would use to update the XML file. I needed two parameters, the product that they wanted installing and the bit version they wanted:

#Updates the XML file based on the input
function Update-XMLFile([string]$product, [string]$bit){

    try{
        #Loading the XML document
        $xmlDoc = Get-Content -Path $pathToXMLFile

        #Edit the document
        $xmlDoc.Configuration.Add.OfficeClientEdition = $bit
        $xmlDoc.Configuration.Add.Product.ID = $product

        #Save the document
        $xmlDoc.Save($pathToXMLFile)
    }catch{
        $errorMessage = $_.Exception.Message
        Write-Host $errorMessage -ForegroundColor Red
        Read-Host "The script encountered the above error - will now exit"
    }
}

I then created another function to start the installation. This also required two parameters, the bit version and the XML file name

#Function to start the installation
function Start-Installation([string]$bit, [string]$xmlName){
    try{
        .\setup.exe /configure $bit\$xmlName
    }catch{
        $errorMessage = $_.Exception.Message
        Write-Host $errorMessage
        Read-Host "The script encountered the above error - will now exit"
    }
}

My final function was a verification test. Since we want to only use 64-bit for future installations, I had to make sure that whoever was using the script knew this and would be competent enough to do a little bit of math:

#Function to check the user wants 32 bit
function Get-Verification(){
    $output = $false

    Write-Host "Are you sure you want to install 32-bit?" -ForegroundColor Red
    Write-Host "All new installs should use 64-bit instead"
    Write-Host "If you want to install 32-bit, complete the test below, otherwise enter the wrong answer"

    $firstNumber = Get-Random -Minimum 1 -Maximum 11
    $secondNumber = Get-Random -Minimum 1 -Maximum 11

    $sumToCheck = $firstNumber + $secondNumber

    $verificationInput = Read-Host "$($firstNumber) + $($secondNumber) = ?"

    if ($verificationInput -eq $sumToCheck){
        Write-Host "Fine! 32-bit will be installed..."
        $output = $true
    }else{
        Write-Host "Finally! 64-bit will be installed"
        $output = $false
    }
    return $output
}

Now that all my functions were defined, I could start with the actual meat of the script. This included cleaning the screen, asking the user some questions, launching the 32-bit verification is needed, updating the XML file using a switch statement and finally kicking off the installation. Heres what that looked like:

#Clear the screen
Clear-Host

#region Checking if the user wants 64 bit or 32 bit

do{

    Write-Host "Do you want" -NoNewline
    Write-Host " 64-bit " -NoNewline -ForegroundColor Yellow
    Write-Host "or" -NoNewline
    Write-Host " 32-bit " -NoNewline -ForegroundColor Green
    Write-Host "? (64 or 32): " -NoNewline
    $bitVersionInput = (Read-Host).ToUpper()
}while((64 ,32) -notcontains $bitVersionInput)

#endregion

#Check the user definitely wants 32 bit
if ($bitVersionInput -eq "32"){
    if (Get-Verification){
        $bitVersion = $bitVersionInput
    }else{
        $bitVersionInput = "64"
    }
}

#Update the bitVersion variable
$bitVersion = $bitVersionInput

#region Asking what product to install

#Ask the user what product they want to install
Write-Host @"

Please select one product from the below list

"@

Write-Host @"
1) Business Retail
2) ProPlus Retail

"@ -ForegroundColor Cyan

Write-Host @"
3) Visio Std Volume
4) Visio Pro Volume
5) Visio Pro Retail

"@ -ForegroundColor Green

Write-Host @"
6) Project Std Volume
7) Project Pro Volume
8) Project Pro Retail

"@ -ForegroundColor Gray

Write-Host @"
C) Cancel

"@ -ForegroundColor Red

do{
    $officeProductInput = (Read-Host "Enter a number").ToUpper()
}while((1,2,3,4,5,6,7,8, "C") -notcontains $officeProductInput)

#endregion

#Update the product variable
$officeProduct = $officeProductInput

#region Switch the input to see what it is and perform the required operation

switch($officeProduct){
    
    #Business Retail
    1 { Update-XMLFile -product "O365BusinessRetail" -bit $bitVersion}
    #ProPlus
    2 { Update-XMLFile -product "O365ProPlusRetail" -bit $bitVersion}
    #Visio Std Volume
    3 { Update-XMLFile -product "VisioStd2019Volume" -bit $bitVersion}
    #Visio Pro Volume
    4 { Update-XMLFile -product "VisioPro2019Volume" -bit $bitVersion}
    #Visio Pro Retail
    5 { Update-XMLFile -product "VisioPro2019Retail" -bit $bitVersion}
    #Project Std Volume
    6 { Update-XMLFile -product "ProjectStd2019Volume" -bit $bitVersion}
    #Project Pro Volume
    7 { Update-XMLFile -product "ProjectPro2019Volume" -bit $bitVersion}
    #Project Pro Retail
    8 { Update-XMLFile -product "ProjectPro2019Retail" -bit $bitVersion}
    #Cancel
    "C" {Exit}
    default {Exit}
}

#endregion

#Start the installation
Write-Host "Installing..." -ForegroundColor Green
Start-Installation -bit $bitVersion -xmlName $xmlFile
Write-Host "This window can be closed"
Read-Host

Done!

If you’re wondering what the script looks like as a whole, wonder no longer:

#Variables used for the installation
$bitVersion = ""
$officeProduct = ""
$pathToOffice = "\\sandpdc\software\Office"
$xmlFile = "OfficeXML.xml"
$pathToXMLFile = Join-Path -Path $pathToOffice -ChildPath $xmlFile

#Updates the XML file based on the input
function Update-XMLFile([string]$product, [string]$bit){

    try{
        #Loading the XML document
        $xmlDoc = Get-Content -Path $pathToXMLFile

        #Edit the document
        $xmlDoc.Configuration.Add.OfficeClientEdition = $bit
        $xmlDoc.Configuration.Add.Product.ID = $product

        #Save the document
        $xmlDoc.Save($pathToXMLFile)
    }catch{
        $errorMessage = $_.Exception.Message
        Write-Host $errorMessage -ForegroundColor Red
        Read-Host "The script encountered the above error - will now exit"
    }
}

#Function to start the installation
function Start-Installation([string]$bit, [string]$xmlName){
    try{
        .\setup.exe /configure $bit\$xmlName
    }catch{
        $errorMessage = $_.Exception.Message
        Write-Host $errorMessage
        Read-Host "The script encountered the above error - will now exit"
    }
}

#Function to check the user wants 32 bit
function Get-Verification(){
    $output = $false

    Write-Host "Are you sure you want to install 32-bit?" -ForegroundColor Red
    Write-Host "All new installs should use 64-bit instead"
    Write-Host "If you want to install 32-bit, complete the test below, otherwise enter the wrong answer"

    $firstNumber = Get-Random -Minimum 1 -Maximum 11
    $secondNumber = Get-Random -Minimum 1 -Maximum 11

    $sumToCheck = $firstNumber + $secondNumber

    $verificationInput = Read-Host "$($firstNumber) + $($secondNumber) = ?"

    if ($verificationInput -eq $sumToCheck){
        Write-Host "Fine! 32-bit will be installed..."
        $output = $true
    }else{
        Write-Host "Finally! 64-bit will be installed"
        $output = $false
    }
    return $output
}

#Clear the screen
Clear-Host

#region Checking if the user wants 64 bit or 32 bit

do{

    Write-Host "Do you want" -NoNewline
    Write-Host " 64-bit " -NoNewline -ForegroundColor Yellow
    Write-Host "or" -NoNewline
    Write-Host " 32-bit " -NoNewline -ForegroundColor Green
    Write-Host "? (64 or 32): " -NoNewline
    $bitVersionInput = (Read-Host).ToUpper()
}while((64 ,32) -notcontains $bitVersionInput)

#endregion

#Check the user definitely wants 32 bit
if ($bitVersionInput -eq "32"){
    if (Get-Verification){
        $bitVersion = $bitVersionInput
    }else{
        $bitVersionInput = "64"
    }
}

#Update the bitVersion variable
$bitVersion = $bitVersionInput

#region Asking what product to install

#Ask the user what product they want to install
Write-Host @"

Please select one product from the below list

"@

Write-Host @"
1) Business Retail
2) ProPlus Retail

"@ -ForegroundColor Cyan

Write-Host @"
3) Visio Std Volume
4) Visio Pro Volume
5) Visio Pro Retail

"@ -ForegroundColor Green

Write-Host @"
6) Project Std Volume
7) Project Pro Volume
8) Project Pro Retail

"@ -ForegroundColor Gray

Write-Host @"
C) Cancel

"@ -ForegroundColor Red

do{
    $officeProductInput = (Read-Host "Enter a number").ToUpper()
}while((1,2,3,4,5,6,7,8, "C") -notcontains $officeProductInput)

#endregion

#Update the product variable
$officeProduct = $officeProductInput

#region Switch the input to see what it is and perform the required operation

switch($officeProduct){
    
    #Business Retail
    1 { Update-XMLFile -product "O365BusinessRetail" -bit $bitVersion}
    #ProPlus
    2 { Update-XMLFile -product "O365ProPlusRetail" -bit $bitVersion}
    #Visio Std Volume
    3 { Update-XMLFile -product "VisioStd2019Volume" -bit $bitVersion}
    #Visio Pro Volume
    4 { Update-XMLFile -product "VisioPro2019Volume" -bit $bitVersion}
    #Visio Pro Retail
    5 { Update-XMLFile -product "VisioPro2019Retail" -bit $bitVersion}
    #Project Std Volume
    6 { Update-XMLFile -product "ProjectStd2019Volume" -bit $bitVersion}
    #Project Pro Volume
    7 { Update-XMLFile -product "ProjectPro2019Volume" -bit $bitVersion}
    #Project Pro Retail
    8 { Update-XMLFile -product "ProjectPro2019Retail" -bit $bitVersion}
    #Cancel
    "C" {Exit}
    default {Exit}
}

#endregion

#Start the installation
Write-Host "Installing..." -ForegroundColor Green
Start-Installation -bit $bitVersion -xmlName $xmlFile
Write-Host "This window can be closed"
Read-Host