QuickCrypto For .NET MAUI – Part 3

QuickCrypto For .NET MAUI – Part 3

Last time I started walking through the project, populating the various folders and files with C# and XAML code. I’ll continue that effort now. Let’s start with the “Events” folder.

Here is the code for the ErrorRaisedArgs.cs file:

namespace CG.Tools.QuickCrypto.Events;

/// <summary>
/// This class contains arguments for the <see cref="ViewModels.ViewModelBase{T}.ErrorRaised"/> 
/// event.
/// </summary>
public class ErrorRaisedArgs : EventArgs
{
    /// <summary>
    /// This property contains the suggested message for the error prompt.
    /// </summary>
    public string Message { get; set; } = null!;

    /// <summary>
    /// This property contains the optional exception for the error prompt.
    /// </summary>
    public Exception? Exception { get; set; }
}


/// <summary>
/// This delegates handles the <see cref="ViewModels.ViewModelBase{T}.ErrorRaised"/> 
/// event.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="args">The arguments for the event.</param>
public delegate void ErrorRaisedEventHandler(object sender, ErrorRaisedArgs args);

This class contains arguments for the ErrorRaised event, which is part of the ViewModelBase class. We’ll look at that shortly. For now, just know that this class allows us to send error messages from our view-model(s) back to their corresponding view(s). That way, we handle business logic in the view-models, and UI logic in the views, as it should be.

The code for the WarningRaisedArgs.cs file looks very similar:

namespace CG.Tools.QuickCrypto.Events;

/// <summary>
/// This class contains arguments for the <see cref="ViewModels.ViewModelBase{T}.WarningRaised"/> 
/// event.
/// </summary>
public class WarningRaisedArgs : EventArgs
{
    /// <summary>
    /// This property contains the suggested message for the Warning prompt.
    /// </summary>
    public string Message { get; set; } = null!;

    /// <summary>
    /// This property contains the optional exception for the Warning prompt.
    /// </summary>
    public Exception? Exception { get; set; }
}


/// <summary>
/// This delegates handles the <see cref="ViewModels.ViewModelBase{T}.WarningRaised"/> 
/// event.
/// </summary>
/// <param name="sender">The sender of the event.</param>
/// <param name="args">The arguments for the event.</param>
public delegate void WarningRaisedEventHandler(object sender, WarningRaisedArgs args);

Almost the same thing going on, but this time, the WarningRaisedArgs contains arguments for the WarningRaised event, which allows us to send warnings from our view-model(s) to their corresponding view(s).

We’ll skip the “Platforms” and “Resources” folders, since I didn’t change anything in them. Well, I did add a few icons to the Resources\Images folder, but that’s about it. You can find those icons in my project source code HERE, or you can provide your own. Whichever you prefer.

Let’s go through the “Views” folder next. Let me say, before we start, that I’ll be moving fast over the XAML, since there’s really very little to talk about, in XAML. I’ll leave the detailed explanations for the C# code in the view-models, coming up soon.

Here is the markup for the AboutPage.xaml file:

<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage x:Class="CG.Tools.QuickCrypto.Views.AboutPage"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:CG.Tools.QuickCrypto.Views">
    <ContentPage.Content>
        <StackLayout>
            <Label Text="{Binding Title}" FontSize="Medium" FontAttributes="Bold" />
            <Label Text="{Binding Version}" FontSize="Micro" FontAttributes="Bold" />
            <Label Text="{Binding Description}" FontSize="Micro" Margin="0, 30, 0, 0" />
            <Label Text="{Binding Copyright}" FontSize="Caption" Margin="0, 30, 0, 0" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Here is the c# code for the corresponding AboutPage.xaml.cs file:

namespace CG.Tools.QuickCrypto.Views;

/// <summary>
/// This class is the code-behind for the <see cref="AboutPage"/> view.
/// </summary>
public partial class AboutPage : ContentPage
{
    /// <summary>
    /// This constructor creates a new instance of the <see cref="AboutPage"/>
    /// class.
    /// </summary>
    /// <param name="viewModel">The view-model to use with the page.</param>
    public AboutPage(
        AboutPageViewModel viewModel
        )
    {
        // Save the binding context.
        BindingContext = viewModel;

        // Wire-up a handler for errors.
        viewModel.ErrorRaised += async (s, e) =>
        {
            // How should we format the error?
            if (null == e.Exception)
            {
                await DisplayAlert(
                    "QuickCrypto - Error",
                    e.Message,
                    "OK"
                    );
            }
            else
            {
                await DisplayAlert(
                    "QuickCrypto - Error",
                    $"{e.Message}{Environment.NewLine}{Environment.NewLine}" +
                    $"{e.Exception.GetBaseException().Message}",
                    "OK"
                    );
            }
        };

        // Wire-up a handler for warnings.
        viewModel.WarningRaised += async (s, e) =>
        {
            await DisplayAlert(
                "QuickCrypto - Warning",
                e.Message,
                "OK"
                );
        };

        // Make the designer happy.
        InitializeComponent();
    }
}

Most of what’s going on, with this page’s code-behind, is repeated for all the other pages. In fact, it’s so similar that I had intended to create a base view class and it just slipped my mind. In any event, I’ll explain it here, for this class, then I’ll gloss over it for the other pages.

The constructor passes in an AboutPageViewModel instance, as a parameter. The DI container handles creating that instance for us. We set the BindingContext of the view to that instance, so our data-binding, in the XAML, will just work. If you’re all at familiar with WPF data binding, this is exactly the same thing. After that, we have to wire up handlers for two events that we want to listen to, in our view: ErrorRaised, and WarningRaised. Inside those handlers, we call DisplayAlert, to popup a MAUI message box, on the UI. The final call, in the constructor, is the InitializeComponent method, which is typically where the framework performs any actions it needs, to initialize the view.

All the other pages, in this application are very similar to the AboutPage we just covered, so I’ll provide the XAML and code-behind here, but I won’t walk through each page.

Here is the XAML for the AesPage.xaml file:

<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage x:Class="CG.Tools.QuickCrypto.Views.AesPage"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:CG.Tools.QuickCrypto.Views"
             Title="QuickCrypto - AES"
             BackgroundColor="{DynamicResource TertiaryWindowColor}">
    <ContentPage.Content>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="40" />
                <RowDefinition Height="30" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <StackLayout>
                <Editor x:Name="clearText" 
                        Placeholder="Put plain text here" 
                        HorizontalTextAlignment="Start"
                        VerticalTextAlignment="Start"
                        HorizontalOptions="FillAndExpand"
                        VerticalOptions="FillAndExpand"
                        Text="{Binding DecryptedText}" />
            </StackLayout>

            <StackLayout Grid.Row="1"
                         Orientation="Horizontal">
                <Button x:Name="encryptBtn" 
                        Text="↓ Encrypt"
                        BackgroundColor="DodgerBlue"
                        HorizontalOptions="FillAndExpand"
                        Command="{Binding Encrypt}"/>
                <Button x:Name="decryptBtn"
                        Text="↑ Decrypt" 
                        BackgroundColor="LightGreen"
                        HorizontalOptions="FillAndExpand"
                        Command="{Binding Decrypt}"/>
            </StackLayout>

            <StackLayout Grid.Row="2" Orientation="Horizontal">
                <Button x:Name="encryptedCopy"
                        FontSize="Caption"
                        TextColor="Blue"
                        HorizontalOptions="FillAndExpand"
                        Text="Copy To Clipboard"
                        Command="{Binding EncryptCopy}"/>
                <Button x:Name="decryptedClear"
                        FontSize="Caption"
                        TextColor="Blue"
                        Text="Clear Decrypted Text"
                        HorizontalOptions="FillAndExpand"
                        Command="{Binding DecryptClear}"/>
            </StackLayout>

            <StackLayout Grid.Row="3">
                <Editor x:Name="encryptedText" 
                        Grid.Row="2" 
                        AutoSize="TextChanges"
                        HorizontalTextAlignment="Start"
                        VerticalTextAlignment="Start"
                        Placeholder="Put encrypted text here" 
                        HorizontalOptions="FillAndExpand"
                        VerticalOptions="FillAndExpand"
                        Text="{Binding EncryptedText, Mode=TwoWay}" />
            </StackLayout>
        </Grid>
    </ContentPage.Content>
</ContentPage>

Here is the C# code for the corresponding AesPage.xaml.cs file:

namespace CG.Tools.QuickCrypto.Views;

/// <summary>
/// This class is the code-behind for the <see cref="AesPage"/> view.
/// </summary>
public partial class AesPage : ContentPage
{
    /// <summary>
    /// This constructor creates a new instance of the <see cref="AesPage"/>
    /// class.
    /// </summary>
    /// <param name="viewModel">The view-model to use with the page.</param>
    public AesPage(
        AesPageViewModel viewModel
        )
    {
        // Save the binding context.
        BindingContext = viewModel;

        // Wire-up a handler for errors.
        viewModel.ErrorRaised += async (s, e) =>
        {
            // How should we format the error?
            if (null == e.Exception)
            {
                await DisplayAlert(
                    "QuickCrypto - Error",
                    e.Message,
                    "OK"
                    );
            }
            else
            {
                await DisplayAlert(
                    "QuickCrypto - Error",
                    $"{e.Message}{Environment.NewLine}{Environment.NewLine}" +
                    $"{e.Exception.GetBaseException().Message}",
                    "OK"
                    );
            }
        };

        // Wire-up a handler for warnings.
        viewModel.WarningRaised += async (s, e) =>
        {
            await DisplayAlert(
                "QuickCrypto - Warning",
                e.Message,
                "OK"
                );
        };

        // Make the designer happy.
        InitializeComponent();
    }
}

Here is the XAML for the DataProtectionPage.xaml file:

<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage x:Class="CG.Tools.QuickCrypto.Views.DataProtectionPage"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:CG.Tools.QuickCrypto.Views"
             Title="QuickCrypto - Data Protection"
             BackgroundColor="{DynamicResource SecondaryWindowColor}">
    <ContentPage.Content>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="*" />
                <RowDefinition Height="40" />
                <RowDefinition Height="30" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <StackLayout>
                <Editor x:Name="clearText" 
                        Placeholder="Put plain text here" 
                        HorizontalTextAlignment="Start"
                        VerticalTextAlignment="Start"
                        HorizontalOptions="FillAndExpand"
                        VerticalOptions="FillAndExpand"
                    Text="{Binding DecryptedText}" />
            </StackLayout>

            <StackLayout Grid.Row="1"
                         Orientation="Horizontal">
                <Button x:Name="encryptBtn" 
                        Text="↓ Encrypt"
                        BackgroundColor="DodgerBlue"
                        HorizontalOptions="FillAndExpand"
                        Command="{Binding Encrypt}"/>
                <Button x:Name="decryptBtn"
                        Text="↑ Decrypt" 
                        BackgroundColor="LightGreen"
                        HorizontalOptions="FillAndExpand"
                        Command="{Binding Decrypt}"/>
            </StackLayout>

            <StackLayout Grid.Row="2" Orientation="Horizontal">
                <Button x:Name="encryptedCopy"
                        FontSize="Caption"
                        TextColor="Blue"
                        HorizontalOptions="FillAndExpand"
                        Text="Copy To Clipboard"
                        Command="{Binding EncryptCopy}"/>
                <Button x:Name="decryptedClear"
                        FontSize="Caption"
                        TextColor="Blue"
                        Text="Clear Decrypted Text"
                        HorizontalOptions="FillAndExpand"
                        Command="{Binding DecryptClear}"/>
            </StackLayout>

            <StackLayout Grid.Row="3">
                <Editor x:Name="encryptedText" 
                        Grid.Row="2" 
                        AutoSize="TextChanges"
                        HorizontalTextAlignment="Start"
                        VerticalTextAlignment="Start"
                        Placeholder="Put encrypted text here" 
                        HorizontalOptions="FillAndExpand"
                        VerticalOptions="FillAndExpand"
                        Text="{Binding EncryptedText, Mode=TwoWay}" />
            </StackLayout>
        </Grid>
    </ContentPage.Content>
</ContentPage>

Here is the C# code for the corresponding DataProtectionPage.xaml.cs file:

namespace CG.Tools.QuickCrypto.Views;

/// <summary>
/// This class is the code-behind for the <see cref="DataProtectionPage"/> view.
/// </summary>
public partial class DataProtectionPage : ContentPage
{
    /// <summary>
    /// This constructor creates a new instance of the <see cref="DataProtectionPage"/>
    /// class.
    /// </summary>
    /// <param name="viewModel">The view-model to use with the page.</param>
    public DataProtectionPage(
        DataProtectionPageViewModel viewModel
        )
    {
        // Save the binding context.
        BindingContext = viewModel;

        // Wire-up a handler for errors.
        viewModel.ErrorRaised += async (s, e) =>
        {
            // How should we format the error?
            if (null == e.Exception)
            {
                await DisplayAlert(
                    "QuickCrypto - Error",
                    e.Message,
                    "OK"
                    );
            }
            else
            {
                await DisplayAlert(
                    "QuickCrypto - Error",
                    $"{e.Message}{Environment.NewLine}{Environment.NewLine}" +
                    $"{e.Exception.GetBaseException().Message}",
                    "OK"
                    );
            }
        };

        // Wire-up a handler for warnings.
        viewModel.WarningRaised += async (s, e) =>
        {
            await DisplayAlert(
                "QuickCrypto - Warning",
                e.Message,
                "OK"
                );
        };

        // Make the designer happy.
        InitializeComponent();
    }
}

Here is the XAML for the SettingsPage.xaml file:

<?xml version="1.0" encoding="UTF-8" ?>
<ContentPage x:Class="CG.Tools.QuickCrypto.Views.SettingsPage"
             xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:CG.Tools.QuickCrypto"
             Title="QuickCrypto - Settings"
             BackgroundColor="{DynamicResource PrimaryWindowColor}">
    <ContentPage.Content>
        <ScrollView>
            <StackLayout>
                <Label Text="AES" FontSize="Medium" />
                <StackLayout BackgroundColor="{DynamicResource TertiaryWindowColor}">
                    <Label Text="SALT (must be >= 8 bytes)" />
                    <Entry x:Name="salt" 
                       Placeholder="Put 8 bytes (or longer) SALT here"
                       Text="{Binding Salt}" />

                    <Label Text="Password" />
                    <Entry x:Name="password" 
                       Placeholder="Put password here."
                       Text="{Binding Password}" />

                    <Label Text="{Binding IterationsLabel}" />
                    <Slider x:Name="iterations" 
                        Maximum="50000" 
                        Value="{Binding Iterations}" />
                </StackLayout>

                <Label Text="Data Protection" FontSize="Medium" />
                <StackLayout BackgroundColor="{DynamicResource SecondaryWindowColor}">
                    <Label Text="X509 PEM (optional)" />
                    <Editor x:Name="x509Pem"
                        HeightRequest="270"
                        Placeholder="Put the contents of your PEM file here"
                        VerticalTextAlignment="Start"
                        HorizontalTextAlignment="Start"                        
                        Text="{Binding X509Pem}" />
                </StackLayout>
            </StackLayout>
        </ScrollView>        
    </ContentPage.Content>
</ContentPage>

Here is the C# code for the corresponding SettingsPage.xaml.cs file:

namespace CG.Tools.QuickCrypto.Views;

/// <summary>
/// This class is the code-behind for the <see cref="SettingsPage"/> view.
/// </summary>
public partial class SettingsPage : ContentPage
{
    /// <summary>
    /// This constructor creates a new instance of the <see cref="SettingsPage"/>
    /// class.
    /// </summary>
    /// <param name="viewModel">The view-model to use with the page.</param>
    public SettingsPage(
        SettingsPageViewModel viewModel
        )
    {
        // Save the binding context.
        BindingContext = viewModel;

        // Wire-up a handler for errors.
        viewModel.ErrorRaised += async (s, e) =>
        {
            // How should we format the error?
            if (null == e.Exception)
            {
                await DisplayAlert(
                    "QuickCrypto - Error",
                    e.Message,
                    "OK"
                    );
            }
            else
            {
                await DisplayAlert(
                    "QuickCrypto - Error",
                    $"{e.Message}{Environment.NewLine}{Environment.NewLine}" +
                    $"{e.Exception.GetBaseException().Message}",
                    "OK"
                    );
            }
        };

        // Wire-up a handler for warnings.
        viewModel.WarningRaised += async (s, e) =>
        {
            await DisplayAlert(
                "QuickCrypto - Warning",
                e.Message,
                "OK"
                );
        };

        // Make the designer happy.
        InitializeComponent();
    }
}

At this point we really only have the “ViewModels” folder, and a few classes in the root of the project to cover. I’ll do that next time.

Photo by olieman.eth on Unsplash