C# Popup UI Base Class

C# Popup Base Class

In a previous post i created a splash screen for WPF, i later needed to create a Popup Window, Splash Screen and Toast Window. Each of these has essentially the same base requirements, show a window for sometime.

So I created a base class, that I can then use to create each of these types.

Base Class

The base class allows, the popup to be positioned in accordance to a specific screen location, so in the case of a toast window we need it to display in the bottom right-hand corner of the screen.

using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Input;

namespace UI_Samples.PopUps.Base
{
    public interface IPopupBase
    {
        double ScreenWidth { get; }
        double ScreenHeight { get; }
        Point MousePosition { get; }
        Point MainWindowPosition { get; }
        Point InAppMousePosition { get; }
        Window? parentWindow { get; set; }
        Window? splashWindow { get; }
        string Title { get; set; }
        string Description { get; set; }
        string ImagePath { get; set; }
        int ImageWidth { get; set; }
        int ImageHeight { get; set; }
        int Delay { get; set; }
        int Sleep { get; set; }
        void Show();
        void Hide();
    }

    public class PopupBase : INotifyPropertyChanged, IPopupBase
    {
        private BackgroundWorker _worker = new BackgroundWorker();

        public double ScreenHeight
        {
            get
            {
                return SystemParameters.PrimaryScreenHeight;
            }
        }
        public double ScreenWidth
        {
            get
            {
                return SystemParameters.PrimaryScreenWidth;
            }
        }       
        public Point MousePosition
        {
            get
            {
                return Mouse.GetPosition(Application.Current.MainWindow);
            }
        }
        public Point MainWindowPosition 
        {
            get 
            { 
                return new Point(Application.Current.MainWindow.Left, Application.Current.MainWindow.Top);
            }
        }
        public Point InAppMousePosition
        {
            get
            {
                return new Point(
                    this.MousePosition.X + this.MainWindowPosition.X,
                    this.MousePosition.Y + this.MainWindowPosition.Y);
            }
        }

        public Window? parentWindow { get; set; } = null;
        public Window? splashWindow { get; } = null;
        public string Title { get; set; } = "PopupTitle";
        public string Description { get; set; } = "PopupDescription";
        public string ImagePath { get; set; } = "";
        public int ImageWidth { get; set; } = 100;
        public int ImageHeight { get; set; } = 50;
        public int Delay { get; set; } = 20;
        public int Sleep { get; set; } = 80;

        public event PropertyChangedEventHandler? PropertyChanged;

        public PopupBase() { }
        public PopupBase(Window window)
        {
            splashWindow = window;
        }
        
        public void Show()
        {
            try
            {
                _worker.WorkerReportsProgress = true;
                _worker.WorkerSupportsCancellation = true;
                _worker.DoWork += worker_DoWork;
                _worker.ProgressChanged += worker_ProgressChanged;
                _worker.RunWorkerAsync();
                parentWindow?.Show();
            }
            catch 
            {
                _worker.CancelAsync();
            }
        }
        public void Hide()
        {
            _worker.CancelAsync();
            parentWindow?.Close();
        }
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        private void worker_DoWork(object? sender, DoWorkEventArgs e)
        {
            for (int i = 0; i <= this.Delay; i++)
            {
                if (_worker.CancellationPending == true)
                {
                    e.Cancel = true;
                    return;
                }
                _worker.ReportProgress(i);
                Thread.Sleep(this.Sleep);
            }
        }
        public virtual void worker_ProgressChanged(object? sender, ProgressChangedEventArgs e)
        {
            if (e.ProgressPercentage == this.Delay)
            {
                parentWindow?.Close();
                if (splashWindow != null)
                {
                    splashWindow.ShowDialog();
                }
            }
        }
    }
}

Splash Screen

Below is shown an example of the splash screen window that was created in WPF.

Splash Screen XAML

Below is the XAML code used to create the splash screen

<Window x:Class="UI_Samples.Windows.SplashScreen"
    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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
    mc:Ignorable="d"
    Title="SplashScreen"
    Topmost="True"
    Height="300" 
    Width="450"
    WindowStyle="None"
    WindowStartupLocation="CenterScreen"
    AllowsTransparency="True"
    Background="{x:Null}" 
    ContentRendered="Window_ContentRendered">
    
    <materialDesign:Card 
        UniformCornerRadius="50"
        Background="Black"
        materialDesign:ElevationAssist.Elevation="Dp4"
        Margin="25">
        <Grid>
            <Image
                Source="../Assets/SplashScreen/SplashScreen.png"
                HorizontalAlignment="Center"
                Height="200"/>
            <ProgressBar 
                    Name="progressBar"
                    Value="0"
                    Height="1"
                    Width="200"
                    IsIndeterminate="True"
                    VerticalAlignment="Bottom"
                    Margin="0,0,0,47"/>
        </Grid>
    </materialDesign:Card>
</Window>

Splash Screen Window XAML.CS

Since the splash screen needs to be launch before everything else.

using System;
using System.Windows;
using UI_Samples.PopUps;

namespace UI_Samples.Windows
{
    public partial class SplashScreen : Window
    {
        private readonly ISplashPopup _splashScreen;
        public SplashScreen()
        {
            InitializeComponent();
            _splashScreen = new SplashPopup(new MainWindow());
            DataContext = _splashScreen;
            _splashScreen.parentWindow = this;
        }

        private void Window_ContentRendered(object sender, EventArgs e)
        {
            _splashScreen.Show();
        }
    }
}

ISplashPopUp

Below is the class into which we will pass the XAML window into. This class implements IPopupBase and PopupBase previously created.

using System.Windows;
using UI_Samples.PopUps.Base;

namespace UI_Samples.PopUps
{
    public interface ISplashPopup : IPopupBase
    {
    }
    public class SplashPopup : PopupBase, ISplashPopup
    {
        private Window _window;
        public SplashPopup(Window window) : base(window) 
        { 
            _window = window;
            this.Delay = 100;
        }
    }
}

In this section we will look at a simple implementation of the toast window utilizing the base class IPopupBase

Toast Window

Below is shown an example of the toast window that was created in WPF.

Toast Window XAML

Below is the XAML code that was used to create the user interface UI.

<Window x:Class="UI_Samples.Windows.ToastWindow"
        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:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
        mc:Ignorable="d"
        Title="ToastWindow"
        WindowStyle="None"
        Topmost="True"
        AllowsTransparency="True"
        Background="{x:Null}"
        Width="150"
        Height="220">

    <Window.Resources>
        <Storyboard x:Key="FadeInAnimation">
            <DoubleAnimation From="0" To="1" Duration="0:0:1" Storyboard.TargetProperty="Opacity"/>
        </Storyboard>
        <Storyboard x:Key="FadeOutAnimation">
            <DoubleAnimation From="1" To="0" Duration="0:0:1" Storyboard.TargetProperty="Opacity"/>
        </Storyboard>
    </Window.Resources>
    <Window.Triggers>
        <EventTrigger RoutedEvent="Window.Loaded">
            <BeginStoryboard Storyboard="{StaticResource FadeInAnimation}"/>
        </EventTrigger>
        <EventTrigger RoutedEvent="Window.Unloaded">
            <BeginStoryboard Storyboard="{StaticResource FadeOutAnimation}"/>
        </EventTrigger>
    </Window.Triggers>

    <materialDesign:Card
        UniformCornerRadius="10"
        Background="{Binding Color,FallbackValue=White}"       
        materialDesign:ElevationAssist.Elevation="Dp4">

        <StackPanel 
            Orientation="Vertical">
            <TextBlock 
                    Text="{Binding Title,FallbackValue=Sample}"
                    HorizontalAlignment="Center"
                    FontFamily="Times Roman"
                    FontWeight="Black"
                    Margin="0,5,0,0"/>
            <Image 
                    Source="../Assets/SampleImages/Sample1.png"
                    Width="150"
                    Height="150"
                    Margin="0,5,0,0"/>
            <TextBlock
                    Text="Hello World this is a description."
                    HorizontalAlignment="Center"
                    FontFamily="Times Roman"
                    FontWeight="Regular"
                    TextWrapping="Wrap"
                    Margin="0,5,0,0"/>
        </StackPanel>
    </materialDesign:Card>
</Window>

Toast Window XAML.CS

When the UI is shown we need to pass in the interface IToastPopup and set the DataContext and in return set the interfaces parent window property.

using System.Windows;
using UI_Samples.PopUps;

namespace UI_Samples.Windows
{
    public partial class ToastWindow : Window
    {
        public ToastWindow(IToastPopup toastWindow)
        {
            InitializeComponent();
            DataContext = toastWindow;
            toastWindow.parentWindow = this;
        }
    }
}

IToastPopup

In the ToastPopup class we will add a new interface IToastPopup and inherit into this the IPopupBase interface, and then add two additional property’s that must be implemented; ToastWindowLeft and ToastWindowTop.These additional attributes are for the calculated position of the Toast Window, relative to the screen size. This will help when creating the WPF view model.

using UI_Samples.PopUps.Base;

namespace UI_Samples.PopUps
{
    public interface IToastPopup : IPopupBase
    {
        double ToastWindowLeft { get; }
        double ToastWindowTop { get; }
    }
    public class ToastPopup : PopupBase, IToastPopup
    {
        public double ToastWindowLeft
        {
            get
            {
                if (this.parentWindow != null)
                {
                    return this.ScreenWidth - this.parentWindow.Width;
                }
                return 0;
            }
        }
        public double ToastWindowTop
        {
            get
            {
                if (this.parentWindow != null)
                {
                    return this.ScreenHeight - this.parentWindow.Height;
                }
                return 0;
            }
        }  
    
        public ToastPopup() 
        { 
            this.Delay = 100; 
        }   
    }
}

Toast Popup in Code Implementation

Within the View Model, we can now create a new instance of the Toast Window and pass in an instance of the IToastPopup.

ToastWindow toastWindow = new ToastWindow(ToastPopup);
if (ToastPopup.parentWindow != null)
{
    toastWindow.Left = ToastPopup.ToastWindowLeft;
    toastWindow.Top = ToastPopup.ToastWindowTop;
    ToastPopup.Show();
}

Popup Window

For this example we will use the same Xaml and Xaml.cs

IHelpPopup

In this implementation we will again add a new interface IHelpPopup and inherit into this the IPopupBase interface, and add an additional property Color, this will be used to drive the color of the UI background. In addition within the constructor we will set the delay of how long the window must remain shown.

using System.Windows.Media;
using System;
using System.Linq;

using UI_Samples.PopUps.Base;

namespace UI_Samples.PopUps
{
    public interface IHelpPopup: IPopupBase
    {
        Brush Color { get; set; }
    }
    public class HelpPopup : PopupBase, IHelpPopup
    {
        Brush _color = getRandomBrush();
        public Brush Color
        {
            get{return _color;}
            set{
                _color = value;
                OnPropertyChanged(nameof(Color));}
        }

        public HelpPopup()
        {
            this.Delay = 100;
        }
        private static Brush getRandomBrush()
        {
            string[] brushArray = typeof(Brushes).GetProperties().
                                        Select(c => c.Name).ToArray();

            Random randomGen = new Random();
            string randomColorName = brushArray[randomGen.Next(brushArray.Length)];
            SolidColorBrush color = (SolidColorBrush)new BrushConverter().ConvertFromString(randomColorName);

            return color;
        }
    }
}