WPF Series -11- Custom MetroDialog

After a long break it’s finally time to continue with the WPF series. Today I’ll show you how we implemented a custom metro dialog. Our dialog contains 2 comboboxes and gets displayed during data export to determine the accuracy of the data to be exported.

Let’s have a look at the code.

AccuracySelectionDialog.xaml

First we implemented the view part of the dialog that inherits from BaseMetroDialog. The code behind is in class AccuracySelectionDialog (more on this later). As you can see the dialog refers to the view model AccuracySelectionDialogViewModel and defines the before mentioned comboboxes, the corresponding labels and an ok button.

<dialogs:BaseMetroDialog x:Class="biz.dfch.CS.ArbitraryWpfApplication.UI.Controls.AccuracySelectionDialog"
        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:ext="clr-namespace:biz.dfch.CS.ArbitraryWpfApplication.UI.Extensions"
        xmlns:sew="clr-namespace:biz.dfch.CS.ArbitraryWpfApplication.UI.Domain.Sewer"
        xmlns:vm="clr-namespace:biz.dfch.CS.ArbitraryWpfApplication.UI.ViewModels"
        xmlns:dialogs="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
        xmlns:p="clr-namespace:biz.dfch.CS.ArbitraryWpfApplication.UI.Properties"
        mc:Ignorable="d"
        Title="{x:Static p:Resources.Dialog_AccuracySelection_Title}">

    <dialogs:BaseMetroDialog.DataContext>
        <vm:AccuracySelectionDialogViewModel></vm:AccuracySelectionDialogViewModel>
    </dialogs:BaseMetroDialog.DataContext>

    <Grid HorizontalAlignment="Stretch" Margin="0,20,0,0">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Label Grid.Row="0" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Content="{x:Static p:Resources.AccuracySelectionDialog_Label__LocationAccuracy}"/>
        <ComboBox Grid.Row="0" Grid.Column="1" MinWidth="200" HorizontalAlignment="Left" VerticalAlignment="Center" ItemsSource="{Binding Source={ext:EnumBindingSource {x:Type sew:LocationAccuracy}}}" SelectedItem="{Binding LocationAccuracy, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" />
        <Label Grid.Row="1" Grid.Column="0" HorizontalAlignment="Left" VerticalAlignment="Center" Content="{x:Static p:Resources.AccuracySelectionDialog_Label__HeightAccuracy}"/>
        <ComboBox Grid.Row="1" Grid.Column="1" MinWidth="200" HorizontalAlignment="Left" VerticalAlignment="Center" ItemsSource="{Binding Source={ext:EnumBindingSource {x:Type sew:HeightAccuracy}}}" SelectedItem="{Binding HeightAccuracy, Mode=TwoWay, NotifyOnValidationError=True, ValidatesOnDataErrors=True}" />

        <!-- ReSharper disable once Xaml.StaticResourceNotResolved -->
        <Button Grid.Row="2" Grid.Column="1" Margin="0,30,20,0" Width="75" HorizontalAlignment="Right" Style="{StaticResource AccentedSquareButtonStyle}" Click="ButtonOk_OnClick" Content="{x:Static p:Resources.AccuracySelectionDialog_Button__Text}" />
    </Grid>
</dialogs:BaseMetroDialog>

AccuracySelectionDialog.xaml.cs

Class AccuracySelectionDialog in code behind file also inherits from BaseMetroDialog and defines a default constructor and an overloaded constructor where the component gets initialized. Furthermore there is a method that gets called when the ok button gets clicked to hide the metro dialog.

/**
 * Copyright 2017 d-fens GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System.Diagnostics.Contracts;
using System.Windows;
using MahApps.Metro.Controls;
using MahApps.Metro.Controls.Dialogs;

namespace biz.dfch.CS.ArbitraryWpfApplication.UI.Controls
{
    /// <summary>
    /// Interaction logic for AccuracySelectionDialog.xaml
    /// </summary>
    // ReSharper disable once RedundantExtendsListEntry
    public partial class AccuracySelectionDialog : BaseMetroDialog
    {
        private readonly MetroWindow window;
        // ReSharper disable once NotAccessedField.Local
        private readonly MetroDialogSettings dialogSettings;

        public AccuracySelectionDialog()
        {
            InitializeComponent();
        }

        public AccuracySelectionDialog(MetroWindow window, MetroDialogSettings dialogSettings)
        {
            Contract.Requires(null != window);

            this.window = window;
            this.dialogSettings = dialogSettings;

            InitializeComponent();
        }

        private void ButtonOk_OnClick(object sender, RoutedEventArgs e)
        {
            window.HideMetroDialogAsync(this);
        }
    }
}

AccuracySelectionDialogViewModel.cs

Now we come to the view model of the dialog. It just defines two properties with backing fields to store the combobox selections. Both properties are marked as required and are of type enum.

/**
 * Copyright 2017 d-fens GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System.ComponentModel.DataAnnotations;
using biz.dfch.CS.ArbitraryWpfApplication.UI.Domain.Sewer;

namespace biz.dfch.CS.ArbitraryWpfApplication.UI.ViewModels
{
    public class AccuracySelectionDialogViewModel : ViewModelBase
    {
        // ReSharper disable once InconsistentNaming
        private LocationAccuracy _locationAccuracy;
        [Required]
        public LocationAccuracy LocationAccuracy
        {
            get { return _locationAccuracy; }
            set
            {
                _locationAccuracy = value;
                RaisePropertyChangedEvent(nameof(LocationAccuracy));
            }
        }

        // ReSharper disable once InconsistentNaming
        private HeightAccuracy _heightAccuracy;
        [Required]
        public HeightAccuracy HeightAccuracy
        {
            get { return _heightAccuracy; }
            set
            {
                _heightAccuracy = value;
                RaisePropertyChangedEvent(nameof(HeightAccuracy));
            }
        }
    }
}

ProtocolsViewModel.cs

Last but not least the code that opens and handles the AccuracySelectionDialog. When the button to export protocols to Geonis gets clicked the method ExportProtocolsToGeonis will be executed. First it opens a file dialog to select the Microsoft Access Database (.mdb) the data should be exported to. Afterwards a task gets created that shows the AccuracySelectionDialog and processes the result of the dialog.

        public SimpleCommand ExportToGeonisCommand => new ExportToGeonisCommand(ExportProtocolsToGeonis);

        private void ExportProtocolsToGeonis()
        {
            // show open file dialog
            var openFileDialog = new OpenFileDialog
            {
                Title = Resources.Dialog_OpenMdb__Title,
                DefaultExt = Export.OPEN_FILE_DIALOG_DEFAULT_EXT,
                Filter = Export.OPEN_FILE_DIALOG_FILTER,
                Multiselect = false,
                CheckFileExists = true,
                CheckPathExists = true
            };

            var result = openFileDialog.ShowDialog(CurrentApp.MainWindow);
            if (!result.Value)
            {
                return;
            }

            // show accuracy selection dialog
            var window = CurrentApp.MainWindow as MetroWindow;
            Contract.Assert(null != window);

            var accuracySelectionDialogTask = window.ShowMetroDialogAsync<AccuracySelectionDialog>();

            Task.Run(() =>
            {
                var accuracySelectionDialog = accuracySelectionDialogTask.Result;
                accuracySelectionDialog.WaitUntilUnloadedAsync();

                accuracySelectionDialog.Unloaded += (sender, args) =>
                {
                    var accuracyViewModel = accuracySelectionDialog.DataContext as AccuracySelectionDialogViewModel;
                    Contract.Assert(null != accuracyViewModel);

                    ExportSettings = new Dictionary<string, object>
                    {
                        { Export.LOCATION_ACCURACY, (int)accuracyViewModel.LocationAccuracy },
                        { Export.HEIGHT_ACCURACY, (int)accuracyViewModel.HeightAccuracy }
                    };

                    // show progress dialog
                    var pathToDbFile = ResolvePath(openFileDialog.FileName);
                    ShowProgressDialogForGeonisExport(pathToDbFile);
                };
            });            
        }

        private string ResolvePath(string path)
        {
            Contract.Requires(!string.IsNullOrWhiteSpace(path));

            if (path.StartsWith("\\"))
            {
                return path;
            }

            using (var managementObject = new ManagementObject())
            {
                managementObject.Path = new ManagementPath(string.Format(FileSystem.WIN_32_LOGICAL_DISK_TEMPLATE, path.Substring(0, 2)));
                var driveType = (DriveType)(uint)managementObject[FileSystem.DRIVE_TYPE];
                var networkPath = Convert.ToString(managementObject[FileSystem.PROVIDER_NAME]);

                if (driveType == DriveType.Network)
                {
                    return path.Replace(path.Substring(0, 2), networkPath);
                }
            }

            return path;
        }

Result

The result looks as follows.

« WPF Series -10- InkCanvas with initial StrokeCollection WPF Series -12- Global Exception Logging »

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: