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 😊

Quering and Adding Info To Access Database Using C#

In this post, I will show you how I created a program to extract and add data to an Access database. Before we get started, you can see my current specifications below:

Getting values from a table:

Using System.Data.OleDB;

//Create a new list to hold all the values
List<String> values = new List<String>();

//Build the connection string and SQL string
string connectionString = @$"Provider=Microsoft.ACE.OLEDB;Data Source = C:\Path\To\Access.accdb";
string sqlString = "SELECT * FROM Table_Name";

//Create a new connection to the Access file
using (OleDbConnection connection = new OleDbConnection(connectionString)){

    //Creating a new command
    OleDbCommand command = new OleDbCommand(sqlString, connection);
    
    //Try/catch to catch errors, DON'T DO THIS IN SERIOUS PROJECTS!
    try{
        
        //Opening the connection and reading the data
        connection.Open();
        using(OleDbDataReader reader = command.ExecuteReader()){
            while(reader.Read()){
                
                //Adding the value to the values list
                values.Add(reader["Field_Name"].ToString());
            }
        }
    }catch{ }
    
    //Closing the connection
    connection.Close();
}

//Sorting the list in ascending order
values.Sort();

 

Adding a new row to the table:

Using System.Data.OleDb;

//Building the connection string and SQL string
string connectionString = @$"Provider=Microsoft.ACE.OLEDB.12.0;Data Source = C:\Path\To\Access.accdb";
string sqlString = $"INSERT INTO Table_Name(Field_Name1, Field_Name2) VALUES ('{Field_Value1}','{Field_Value2}')";

//Creating a new connection to the Access file
using (OleDbConnection connection = new OleDbConnection(connectionString))
{
    //Build a new command
    using(OleDbCommand command = new OleDbCommand(sqlString, connection))
    {
        //Open the database connection and execute the write
        connection.Open();
        command.ExecuteReader();
    }
    //Close the database connection
    connection.Close();
}

Enjoy!

Launch Chrome and Other Applications in C#

Hi Everyone,

Short post, I just wanted to put down into writing how I open Chrome windows and other applications in C# code. This is used by one of my startup programs which allowed me to open all my most used applications with ease 🙂

First I create a Process variables using the System.Diagnostics resource:

Process process = new Process();

I then point the process variables start info filename to the location of my Chrome executable:

process.StartInfo.FileName = @"C:\Path\To\Chrome.exe";

I then add my URL to the start info arguments:

process.StartInfo.Arguments = @"https://bbc.co.uk";

Then, if I want the link to open as a new window instead of onto an existing Chrome window I use:

process.StartInfo.Arguments += " --new-windows";

Finally, I start the process:

process.Start();

Here are some different scenarios:

Opening a URL in an existing Chrome window:

Process process = new Process();
process.StartInfo.FileName = @"C:\Path\To\Chrome.exe";
process.StartInfo.Arguments = @"https://bbc.co.uk" ;
process.Start()

Opening a URL in a new Chrome window:

Process process = new Process();
process.StartInfo.FileName = @"C:\Path\To\chrome.exe";
process.StartInfo.Arguments = @"https://bbc.co.uk";
process.StartInfo.Arguments += " --new-window";
process.Start();

Opening multiple URLs in a new Chrome window:

List<String> URLs = new List<String>(){
    "https://bbc.co.uk",
    "https://mharwood.uk",
    "https://youtube.com"
}

Process process = new Process();
process.StartInfo.FileName = @"C:\Path\To\chrome.exe";
foreach (string i in URLs)
{
    process.StartInfo.Arguments += i;
}
process.StartInfo.Arguments += " --new-window";
process.Start();

Opening different programs:

List<String> Programs = new List<String>(){
    @"C:\Path\To\First\Program.exe",
    @"C:\Path\To\Second\Program.exe",
    @"C:\Path\To\Third\Program.exe"
}

foreach(string i in Programs){
    Process.Start(i);
}

I hope this is useful information for someone. Enjoy!

Performing Asynchronous Remote WMI Calls C#

This was a problem that I was stuck on for a long time. Originally, I made a program in PowerShell that myself and a couple of other people in the company used to query remote computers to retrieve certain information (SN etc). I wanted to make the move to using WPF with C# simply because of its scale, simplicity and flexibility. The things that I saw being done in WPF with C# was beyond anything I could ever dream with PowerShell.

After making the initial leap, I realised that I would how much code I would have to re-write/re-imagine into C# code. Go figure… One thing that I thought would be simple is performing asynchronous operations but this proved to be something I should use with caution. See, I could simply use Task.Run() and perform the actions, but this is like using a sledgehammer to drive in a nail. After a lot of research, I moved from using this approve to using an observer class to watch the operation and using the QueryInstancesAsync method instead of QueryInstances()

This is the code that I ended up with:

//Used to define what is returned in the async results
public static CimAsyncMultipleResults<CimInstance> GetValues(CimSession _session)
{
    return _session.QueryInstancesAsync(@"root\cimv2", "WQL", "SELECT Username FROM Win32_ComputerSystem");
}

//This watches the async progress
class CimInstanceWatcher : IObserver<CimInstance>
{
    public void OnCompleted()
    {
        Console.WriteLine("Done");
    }

    public void OnError(Exception e)
    {
        Console.WriteLine("Error: " + e.Message);
    }

    public void OnNext (CimInstance value)
    {
        Console.WriteLine("Value: " + value);
    }
}

private static void Main()
{
    //Leaving cimsession creation as sync because is happens "instantly"
    CimSession Session = CimSession.Create("PC-NAME");
    //Creating a new watcher object
    var instanceObject = new CimInstanceWatcher();
    //Subscribing the watcher object to the async call
    GetValues(Session).Subscribe(instanceObject);
    Console.ReadLine();
}

This information was provided from Microsofts forums

Enjoy!

Button Masher Game in WPF and C#

I’ve been trying to learn WPF with C# as the backend instead of PowerShell. Since there’s a lot more information on using C# over PowerShell, I decided I would take the plunge and learn as much as I could to create some apps.

This is the first app that I completed. In all honesty, it only took me around 2 hours to get the functionality completed. I did this whilst feeling ill and waiting for the wife to get back from work. I was really surprised by how easy it was for me to understand and write in C# and I was quite proud of the final outcome.

The app will probably look really different on your screen but it looks really nice on my computer. This is what the app looks like once it’s loaded:

The goal of the game is for one person to mash the A button as fast as possible and for another person to mash the B button as fast as possible. The first person to mash their respective buttons 100 times is the winner. It then displays a winning screen and allows the users to play again:

Here is a little GIF of what the game looks and feels like:

And here is the download for the game’s exe file:

ButtonMasher

Now for the nitty gritty stuff. I have included the XML and C# code used for the project below and will also include them as downloads:

XML

<Window x:Class="ButtonMasher.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:ButtonMasher"
        mc:Ignorable="d"
        Title="Button Masher" Width="450" MinHeight="145" MinWidth="300" KeyDown="WindowKeyDown" SizeToContent="Height" Icon="Icon.ico">
    <Window.Background>
        <LinearGradientBrush StartPoint="-1,-1" EndPoint="1.5,1.5" >
            <GradientStop Color="#FF018A8F" Offset="0.41" />
            <GradientStop Color="#FF00FFD6" Offset="1" />
        </LinearGradientBrush>
    </Window.Background>
    <Grid Name="Grid">
        <Grid.Background>
            <ImageBrush ImageSource="Winner_Background.jpg" Stretch="UniformToFill" Opacity="0" />
        </Grid.Background>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*"/>
            <ColumnDefinition Width="1*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Label Visibility="Visible" Name="PlayerOneLabel" Content="0" FontSize="46" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White" FontFamily="Arial"  />
        <Label Visibility="Visible" Name="PlayerTwoLabel" Content="0" FontSize="46" HorizontalAlignment="Center" VerticalAlignment="Center" Grid.Column="1" Foreground="White" FontFamily="Arial"/>
        <ProgressBar Visibility="Visible" Name="ProgressBar1" Margin="10,5" Background="Transparent" BorderBrush="White" BorderThickness="2" Minimum="0" Maximum="100" Value="0" HorizontalAlignment="Stretch" Height="20"  Grid.Row="1" VerticalAlignment="Center" >
            <ProgressBar.Foreground>
                <LinearGradientBrush EndPoint="0,0.5" StartPoint="1,0.5">
                    <GradientStop Color="#83EAF1" Offset="0"/>
                    <GradientStop Color="#63A4FF" Offset="1"/>
                </LinearGradientBrush>
            </ProgressBar.Foreground>
        </ProgressBar>
        <ProgressBar Visibility="Visible" Name="ProgressBar2" Margin="10,5" Background="Transparent" BorderBrush="White" BorderThickness="2" Minimum="0" Maximum="100" Value="0" HorizontalAlignment="Stretch" Height="20" Grid.Row="1" Grid.Column="1" VerticalAlignment="Center" RenderTransformOrigin="0.5,0.5"  >
            <ProgressBar.Foreground>
                <LinearGradientBrush EndPoint="0,0.5" StartPoint="1,0.5">
                    <GradientStop Color="#83EAF1" Offset="0"/>
                    <GradientStop Color="#63A4FF" Offset="1"/>
                </LinearGradientBrush>
            </ProgressBar.Foreground>
            <ProgressBar.RenderTransform>
                <TransformGroup>
                    <ScaleTransform/>
                    <SkewTransform/>
                    <RotateTransform Angle="180"/>
                    <TranslateTransform/>
                </TransformGroup>
            </ProgressBar.RenderTransform>
        </ProgressBar>
        <TextBlock Name="RulesTextBlock" MouseUp="ToggleRules" Visibility="Visible" Grid.Row="2" VerticalAlignment="Bottom" HorizontalAlignment="Left" FontFamily="Arial" Foreground="White" FontSize="10" Cursor="Hand" >
            See Rules
        </TextBlock>
        <TextBlock Name="WonLabel" Text="Winner" Padding="0,40,0,0" Visibility="Collapsed" Grid.ColumnSpan="2" FontSize="46" HorizontalAlignment="Center" VerticalAlignment="Bottom" FontWeight="Bold" TextDecorations="{x:Null}" Foreground="#FFFECE26" FontFamily="Arial">
            <TextBlock.Effect>
                <DropShadowEffect BlurRadius="1" Direction="320" ShadowDepth="2"/>
            </TextBlock.Effect>
        </TextBlock>
        <Border Name="PlayAgainBorder" Visibility="Collapsed" Grid.Row="1" Cursor="Hand" Grid.ColumnSpan="2" CornerRadius="10" Margin="0,0,0,10" HorizontalAlignment="Center" VerticalAlignment="Center" MouseUp="PlayAgain">
            <Border.Style>
                <Style>
                    <Setter Property="Border.Background" Value="#FF494949" />
                    <Setter Property="Border.BorderThickness" Value="3"/>
                    <Style.Triggers>
                        <Trigger Property="Border.IsMouseOver" Value="True" >
                            <Setter Property="Border.Background" Value="#2ecc71"/>
                        </Trigger>
                    </Style.Triggers>
                </Style>
            </Border.Style>
            <Label Content="Play Again" VerticalAlignment="Center" Foreground="White" HorizontalAlignment="Center" VerticalContentAlignment="Center" FontSize="16" FontFamily="Arial"/>
        </Border>
        <TextBlock Name="RulesSection" Foreground="White" Grid.Row="3" Grid.ColumnSpan="2" TextWrapping="Wrap" TextAlignment="Center" Padding="20" Visibility="Collapsed" FontFamily="Arial" FontSize="14">
            The rules are simple, player one (left side) has to mash the 'A' button as fast as possible while player two (right side) has to mash the 'B' button as fast as possible. The first one to reach 100 button "mashes" wins the game!
        </TextBlock>
    </Grid>
</Window>

C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ButtonMasher
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        //player one score
        int player1Score = 0;
        int player2Score = 0;

        //LOAD FORM
        public MainWindow()
        {
            InitializeComponent();
        }

        //UPDATE PLAYER ONE SCORE
        void UpdatePlayerOneScore()
        {
            player1Score++;
            PlayerOneLabel.Content = player1Score;
        }

        //UPDATE PLAYER TWO SCORE
        void UpdatePlayerTwoScore()
        {
            player2Score++;
            PlayerTwoLabel.Content = player2Score;
        }

        //SET WON SCREEN VISIBILITY
        void WonScreenVisibility(bool visible)
        {
            if (visible)
            {
                //show won screen
                PlayerOneLabel.Visibility = Visibility.Collapsed;
                PlayerTwoLabel.Visibility = Visibility.Collapsed;
                ProgressBar1.Visibility = Visibility.Collapsed;
                ProgressBar2.Visibility = Visibility.Collapsed;
                RulesTextBlock.Visibility = Visibility.Collapsed;
                RulesSection.Visibility = Visibility.Collapsed;
                WonLabel.Visibility = Visibility.Visible;
                PlayAgainBorder.Visibility = Visibility.Visible;
                Grid.Background.Opacity = 100;
                MinWidth = 350;
                MinHeight = 170;
            }
            else
            {
                //hide won screen
                PlayerOneLabel.Visibility = Visibility.Visible;
                PlayerTwoLabel.Visibility = Visibility.Visible;
                ProgressBar1.Visibility = Visibility.Visible;
                ProgressBar2.Visibility = Visibility.Visible;
                RulesTextBlock.Visibility = Visibility.Visible;
                RulesSection.Visibility = Visibility.Collapsed;
                WonLabel.Visibility = Visibility.Collapsed;
                PlayAgainBorder.Visibility = Visibility.Collapsed;
                Grid.Background.Opacity = 0;
                MinWidth = 300;
                MinHeight = 145;
            }
            //switch "show" or "hide" in rules textblock
            if (RulesSection.Visibility == Visibility.Visible)
            {
                RulesTextBlock.Text = "Hide Rules";
            }
            else
            {
                RulesTextBlock.Text = "Show Rules";
            }
        }

        //WON SCREEN
        void WonScreen(string player)
        {
            if(player == "1")
            {
                WonScreenVisibility(true);
                WonLabel.Text = "Player 1 won!";
            }else if(player == "2")
            {
                WonScreenVisibility(true);
                WonLabel.Text = "Player 2 won!";
            }
        }

        //RESET SCORES AND BARS
        void Reset()
        {
            player1Score = 0;
            player2Score = 0;
            PlayerOneLabel.Content = player1Score;
            PlayerTwoLabel.Content = player2Score;
            ProgressBar1.Value = player1Score;
            ProgressBar2.Value = player2Score;
        }

        //USER PRESSES BUTTON FORM
        private void WindowKeyDown(object sender, KeyEventArgs e)
        {
            //checking which key was pressed
            if (e.Key == Key.A)
            {
                UpdatePlayerOneScore(); 
            }else if(e.Key == Key.L)
            {
                UpdatePlayerTwoScore();
            }

            //calculating progress bar value
            ProgressBar1.Value = player1Score;
            ProgressBar2.Value = player2Score;

            //checking if anyone won
            if (ProgressBar1.Value > 99)
            {
                //player one has won
                WonScreen("1");

            }
            else if (ProgressBar2.Value > 99)
            {
                //player two has won
                WonScreen("2");
            }            
        }        

        //PLAY AGAIN
        private void PlayAgain(object sender, RoutedEventArgs e)
        {
            WonScreenVisibility(false);
            Reset();
        }

        //TOGGLE RULES
        void ToggleRules(object sender, RoutedEventArgs e)
        {
            if (RulesSection.Visibility == Visibility.Collapsed)
            {
                //show rules
                RulesSection.Visibility = Visibility.Visible;
                RulesTextBlock.Text = "Hide Rules";
                SizeToContent = SizeToContent.Height;
                MinHeight = 265;
            }
            else
            {
                //hide rules
                RulesSection.Visibility = Visibility.Collapsed;
                RulesTextBlock.Text = "Show Rules";
                SizeToContent = SizeToContent.Height;
                MinHeight = 145;
            }
        }
    }
}

I really hope you enjoy this little game that I created. Please feel free to use it, change it and distribute your own version at will 🙂

Enjoy!