One thing that I use a lot is sample data. Every article I write needs some data to explain the concepts, I need some data to see how it fits in my designs or even sample data for testing. This is a real trouble, as I must find some reliable data for my programs. Sometimes, I go to databases (Northwind and AdventureWorks are my good friends), sometimes, I use Json or XML data and other times I create the sample data by myself.

None of them are perfect, and it’s not consistent. Every time I get a new way of accessing data (yes, it can be good for learning purposes, but it’s a nightmare for maintenance). Then, looking around, I found Bogus (https://github.com/bchavez/Bogus), It’s a simple data generator for C#. All you have to do is create rules for your data and generate it. Simple as that! Then, you’ve got the data to use in your programs. It can be fixed (every time you run your program, you have the same data) or variable (every time you get a different set of data), and once you got it, you can serialize it to whichever data format you want: json files, databases, xml or plain text files.

Generating sample data

The first step to generate sample data is to create your classes. Create a new console app and add these two classes:

public class Customer
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
    public string ZipCode { get; set; }
    public string Phone { get; set; }
    public string Email { get; set; }
    public string ContactName { get; set; }
    public IEnumerable<Order> Orders { get; set; }
}
public class Order
{
    public Guid Id { get; set; }
    public DateTime Date { get; set; }
    public Decimal OrderValue { get; set; }
    public bool Shipped { get; set; }
}

Once you’ve got the classes, you can add the repositories to get the sample data. To use the sample data generator, you must add the Bogus NuGet package to your project, with the command Install-Package Bogus, in the package manager console. Then we can add the repository class to retrieve the data. Add a new class to the project and name it SampleCustomerRepository. Then add this code in the class:

public IEnumerable<Customer> GetCustomers()
{
    Randomizer.Seed = new Random(123456);
    var ordergenerator = new Faker<Order>()
        .RuleFor(o => o.Id, Guid.NewGuid)
        .RuleFor(o => o.Date, f => f.Date.Past(3))
        .RuleFor(o => o.OrderValue, f => f.Finance.Amount(0, 10000))
        .RuleFor(o => o.Shipped, f => f.Random.Bool(0.9f));
    var customerGenerator = new Faker<Customer>()
        .RuleFor(c => c.Id, Guid.NewGuid())
        .RuleFor(c => c.Name, f => f.Company.CompanyName())
        .RuleFor(c => c.Address, f => f.Address.FullAddress())
        .RuleFor(c => c.City, f => f.Address.City())
        .RuleFor(c => c.Country, f => f.Address.Country())
        .RuleFor(c => c.ZipCode, f => f.Address.ZipCode())
        .RuleFor(c => c.Phone, f => f.Phone.PhoneNumber())
        .RuleFor(c => c.Email, f => f.Internet.Email())
        .RuleFor(c => c.ContactName, (f, c) => f.Name.FullName())
        .RuleFor(c => c.Orders, f => ordergenerator.Generate(f.Random.Number(10)).ToList());
    return customerGenerator.Generate(100);
}

In line 3, we have set the Randomizer.Seed to a fixed seed, so the data is always the same for all runs. If we don’t want it, we just don’t need to set it up. Then we set the rules for the order and customer generation. Then we call the Generate method to generate the sample data. Easy as that.

As you can see, the generator has a lot of classes to generate data. For example, the Company  class generates data for the company, like CompanyName. You can use this data as sample data for your programs. I can see some uses for it:

  • Test data for unit testing
  • Sample data for design purposes
  • Sample data for prototypes

but I’m sure you can find some more.

To use the data, you can add this code in the main program:

static void Main(string[] args)
{
    var repository = new SampleCustomerRepository();
    var customers = repository.GetCustomers();
    Console.WriteLine(JsonConvert.SerializeObject(customers, 
        Formatting.Indented));
}

We are serializing the data to Json, so you must add the Newtonsoft.Json Nuget package to your project. When you run this project, you will see something like this:

As you can see, it generated a whole set of customers with their orders, so you can use in your programs.

You may say that this is just dummy data, it will be cluttering your project, and it this data will remain unused when the program goes to production, and you’re right. But that’s not the way I would recommend to use this kind of data. It is disposable and will clutter the project. So, a better way would be create another project, a class library with the repository. That solves one problem, the maintenance one. When entering in production, you replace the sample dll with the real repository and you’re done. Or no? Not too fast. If you see the code in the main program, you will see that we are instantiating an instance of the SampleCustomerRepository, that won’t exist anymore. That way, you must also change the code for the main program. Not a good solution.

You can use conditional compilation to instantiate the repository you want, like this:

        static void Main(string[] args)
        {
#if SAMPLEREPO
            var repository = new SampleCustomerRepository();
#else
            var repository = new CustomerRepository();
#endif
            var customers = repository.GetCustomers();
            Console.WriteLine(JsonConvert.SerializeObject(customers, 
                Formatting.Indented));
        }

That’s better, but still not optimal: you need to compile the program twice: one time to use the sample data and the other one for going to production. With automated builds that may be less than a problem, but if you need to deploy by yourself, it can be a nightmare.

The solution? Dependency injection. Just create an interface for the repository, use a dependency injection framework (I like to use Unity or Ninject, but there are many others out there, just check this list). That way, the code will be completely detached from the data and you won’t need to recompile the project to use this or that data. Just add the correct dll and you are using the data you want. This is a nice approach, but the topic gives space for another post, just wait for it!

The code for this article is at https://github.com/bsonnino/SampleData

 

Once upon a time, in the old days, when WPF was a little new baby and people needed a tool to fiddle around with the new design language, Microsoft released a tool named XAMLPad, and things were all good: you didn’t need Visual Studio to try your designs, you could learn XAML without having to start a new project and you could try as much as you wanted, the UI changed as you changed the XAML code.

Then, time has passed. WPF is old news, many new XAML platforms appeared and XAMLPad faded out and all that remained from it was an old page.

Although it was not there, the need remained: how to do quick tests on your XAML? How to do them for the new platforms, like UWP? Then, a Microsoft Engineer decided to revive this and XAML Studio was born.

XAML Studio is a UWP app that allows you to fiddle with the XAML code in UWP and even debug the bindings! You can get the app from the Windows Store and install it in your machine for free.

Once you install and open it, you will get a window like this:

The app has a tabbed interface and you can create a new page and start typing your code. When you create a new page, a new tab will open, like this one:

You can start changing things immediately and the changes will reflect on the top pane. For example, if you change the color of the first run of text to Navy, the top pane will reflect that. If  you make a typing mistake, a pink box will be shown in the top pane, indicating the compilation error:

The last icon in the navigation bar is the toolbox. When you select it, you will have all available components to insert in your code. You can filter the items you want by typing in the search box at the top:

In this post, I’ve shown how to use the NavigationView in UWP. With XAML Studio, we don’t need to use Visual Studio to test the code, we can just type it in the code window and the UI will be shown in the top pane. If you type something like:

<Page
  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"
  mc:Ignorable="d" xmlns:winui="using:Microsoft.UI.Xaml.Controls">
  <winui:NavigationView >
    <winui:NavigationView.MenuItems>
      <winui:NavigationViewItem Content="Main" Icon="Home" />
    </winui:NavigationView.MenuItems>
    <Grid Padding="40">
      <TextBlock>
        <Run FontSize="24" Foreground="Navy">Get Started with XAML Studio</Run><LineBreak/>
        <Run> Modify this text below to see a live preview.</Run>
      </TextBlock>
    </Grid>
  </winui:NavigationView>
</Page>

You will get something like this:

You can even use controls from Telerik or from the Microsoft Community Toolkit. For example, if you want to add a Radial Gauge to your UI, you can use something like this:

<Page
  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"
  mc:Ignorable="d" xmlns:winui="using:Microsoft.UI.Xaml.Controls" 
  xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls">
  <winui:NavigationView >
    <winui:NavigationView.MenuItems>
      <winui:NavigationViewItem Content="Main" Icon="Home" />
    </winui:NavigationView.MenuItems>
    <Grid Padding="40">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
      </Grid.RowDefinitions>
      <TextBlock>
        <Run FontSize="24" Foreground="Navy">Get Started with XAML Studio</Run><LineBreak/>
        <Run> Modify this text below to see a live preview.</Run>
      </TextBlock>
      <controls:RadialGauge Value="25" Unit="mph" NeedleBrush="Red"
                                  TrailBrush="Green"
                                  Grid.Row="1" Width="200" 
                                  Height="200" Foreground="Green"/>
    </Grid>
  </winui:NavigationView>
</Page>

One thing very interesting that can be done is to use data bindings in your code. You can create a Json Data Source or use a remote data source to bind to your data. For example, if you go to the second icon (Data Source) and add something like this:

{
  'Title' : 'Using XAML Studio',
  'Subtitle' : 'By Bruno Sonnino',
  'GaugeValue' : 35,
  'GaugeUnit' : 'km/h',
  'GaugeNeedle' : 'DarkGreen'
}

You can bind to your code with something like this:

<Page
  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"
  mc:Ignorable="d" xmlns:winui="using:Microsoft.UI.Xaml.Controls" 
  xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls">
  <winui:NavigationView >
    <winui:NavigationView.MenuItems>
      <winui:NavigationViewItem Content="Main" Icon="Home" />
    </winui:NavigationView.MenuItems>
    <Grid Padding="40">
      <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
      </Grid.RowDefinitions>
      <TextBlock>
        <Run FontSize="24" Foreground="Navy" Text="{Binding Title}"/><LineBreak/>
        <Run Text="{Binding Subtitle}"/>
      </TextBlock>
      <controls:RadialGauge Value="{Binding GaugeValue}" Unit="{Binding GaugeUnit}" 
                                  NeedleBrush="{Binding GaugeNeedle}"
                                  TrailBrush="Green"
                                  Grid.Row="1" Width="200" 
                                  Height="200" Foreground="Green"/>
    </Grid>
  </winui:NavigationView>
</Page>

You can also bind to a remote data source, using an URL to a JSON service. For example, we can bind to the European Currency Exchange service to get the quotes of the currencies against the Euro. Just go to the Data Source option and select the Remote Data Source and enter this URL:

https://api.exchangeratesapi.io/latest

Then you can show the values for the currencies in a DataGrid with this code:

<Page
  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"
  mc:Ignorable="d" xmlns:controls="using:Microsoft.Toolkit.Uwp.UI.Controls">
  <Grid Padding="40">
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto" />
      <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal" Margin="0,10">
      <TextBlock Text="Base" Margin="5,0"/>
      <TextBlock Text="{Binding base}" Margin="5,0"/>
      <TextBlock Text="Date" Margin="5,0"/>
      <TextBlock Text="{Binding date}" Margin="5,0"/>
    </StackPanel>
    <controls:DataGrid ItemsSource="{Binding rates}" Grid.Row="1"/>
  </Grid>
</Page>

One nice feature is that you can debug your bindings. In the code window, you can notice a green background in the bindings. If you hover the mouse in the binding, it will show you the current value:

When you go to the third menu item (Debug Bindings), you can even get more information, like the history for the binding or the values for all bindings at once:

Conclusions

As you can see XAML Studio has a lot of features for who wants to try some XAML code or even learn XAML. You don’t need to fire Visual Studio to test your XAML code and see what happens if you change something. It’s still a work in progress and has a lot of room for improvement, but with no doubt it’s a very nice tool to have in your toolbox.

Many times I need to enumerate the files in my disk or in a folder and subfolders, but that always has been slow. All the file enumeration techniques go through the disk structures querying the file names and going to the next one. With the Windows file indexing, this has gone to another level of speed: you can query and filter your data almost instantaneously, with one pitfall: it only works in the indexed parts of the disk (usually the libraries and Windows folders), being unusable for your data folders, unless you add them to the indexer:

Wouldn’t it be nice to have a database that stores all files in the system and is updated as the files change? Some apps, like Copernic Desktop Search or X1 Search do exactly that: they have a database that indexes your system and can do fast queries for you. The pitfall is that you don’t have an API to integrate to your programs, so the only way you have is to query the files is to use their apps.

At some time, Microsoft thought of doing something like a database of files, creating what was called WinFS – Windows Future Storage, but the project was cancelled. So, we have to stick with our current APIs to query files. Or no? As a matter of fact there is something in the Windows system that allows us to query the files in a very fast way, and it’s called the NTFS MFT (NT file system master file table).

The NTFS MFT is a file structure use internally by Windows that allows querying files in a very fast way. It was designed to be fast and safe (there are two copies of the MFT, in case one of them gets corrupt), and we can access it to get our files enumerated. But some things should be noted when accessing the MFT:

  • The MFT is only available for NTFS volumes. So, you cannot access FAT drives with this API
  • To access the MFT structures, you must have elevated privileges – a normal user won’t be able to access it
  • With great power comes great responsibility (this is a SpiderMan comic book quote), so you should know that accessing the internal NTFS structures may harm you system irreversively – use the code with care, and don’t blame me if something goes wrong (but here’s a suggestion to Windows API designers: why not create some APIs that query the NTFS structures safely for normal users? That could be even be added to UWP programming).

Acessing the MFT structure

There is no formal API to access the MFT structure. You will have to decipher the structure (there is a lot of material here) and access the data using raw disk data read (that’s why you need elevated privileges). This is a lot of work and hours of trial and error.

Fortunately, there are some libraries that do that in C#, and I’ve used this one, which is licensed as LGPL. You can use the library in your compiled work as a library, with no restriction. If you include the library source code in your code, it will be “derived work” and you must distribute all code as LGPL.

We will create a WPF program that will show the disk usage. It will enumerate all files and show them in the list, so you can see what’s taking space in your disk. You will be able to select any of the NTFS disks in your machine.

Open Visual Studio with administrative rights (this is very important, or you won’t be able to debug your program). Then create a new WPF project and add a new item. Choose Application Manifest File, you will have an app.manifest file added to your project. Then, you must change the requestedExecutionLevel tag of the file to:

<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />

The next step is to detect the NTFS disks in your system. This is done with this code:

var ntfsDrives = DriveInfo.GetDrives()
    .Where(d => d.DriveFormat == "NTFS").ToList();

Then, add the NtfsReader project to the solution and add a reference to it in the WPF project. Then, add the following UI in MainWindow.xaml.cs:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal" Margin="5">
        <TextBlock Text="Drive" VerticalAlignment="Center"/>
        <ComboBox x:Name="DrvCombo" Margin="5,0" Width="100" 
                  VerticalContentAlignment="Center"/>
    </StackPanel>
    <ListBox x:Name="FilesList" Grid.Row="1" 
             VirtualizingPanel.IsVirtualizing="True"
             VirtualizingPanel.IsVirtualizingWhenGrouping="True"
             >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding FullName}" 
                               Margin="5,0" Width="450"/>
                    <TextBlock Text="{Binding Size,StringFormat=N0}" 
                               Margin="5,0" Width="150" TextAlignment="Right"/>
                    <TextBlock Text="{Binding LastChangeTime, StringFormat=g}" 
                               Margin="5,0" Width="200"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <TextBlock x:Name="StatusTxt" Grid.Row="2" HorizontalAlignment="Center" Margin="5"/>
</Grid>

We will have a combobox with all drives in the first row and a listbox with the files. The listbox has an ItemTemplate that will show the name, size and date of last change of each file. To fill this data, you will have to add this code in MainWindow.xaml.cs:

public MainWindow()
{
    InitializeComponent();
    var ntfsDrives = DriveInfo.GetDrives()
        .Where(d => d.DriveFormat == "NTFS").ToList();
    DrvCombo.ItemsSource = ntfsDrives;
    DrvCombo.SelectionChanged += DrvCombo_SelectionChanged;
}

private void DrvCombo_SelectionChanged(object sender, 
    System.Windows.Controls.SelectionChangedEventArgs e)
{
    if (DrvCombo.SelectedItem != null)
    {
        var driveToAnalyze = (DriveInfo) DrvCombo.SelectedItem;
        var ntfsReader =
            new NtfsReader(driveToAnalyze, RetrieveMode.All);
        var nodes =
            ntfsReader.GetNodes(driveToAnalyze.Name)
                .Where(n => (n.Attributes & 
                             (Attributes.Hidden | Attributes.System | 
                              Attributes.Temporary | Attributes.Device | 
                              Attributes.Directory | Attributes.Offline | 
                              Attributes.ReparsePoint | Attributes.SparseFile)) == 0)
                .OrderByDescending(n => n.Size);
        FilesList.ItemsSource = nodes;
    }
}

It gets all NTFS drives in your system and fills the combobox. In the SelectionChanged event handler, the reader gets all nodes in the drive. These nodes are filtered to remove all that are not normal files and then ordered descending by size and added to the listbox.

If you run the program you will see some things:

  • If you look at the output window in Visual Studio, you will see these debug messages:
1333.951 MB of volume metadata has been read in 26.814 s at 49.748 MB/s
1324082 nodes have been retrieved in 2593.669 ms

This means that it took 2.6s to read and analyze all files in the disk (pretty fast for 1.3 million files, no?).

  • When you change the drive in the combobox, the program will freeze for some time and the list will be filled with the files. The freezing is due to the fact that you are blocking the main thread while you are analyzing the disk. To avoid this, you should run the code in a secondary thread, like this code:
private async void DrvCombo_SelectionChanged(object sender, 
    System.Windows.Controls.SelectionChangedEventArgs e)
{
    if (DrvCombo.SelectedItem != null)
    {
        var driveToAnalyze = (DriveInfo) DrvCombo.SelectedItem;
        DrvCombo.IsEnabled = false;
        StatusTxt.Text = "Analyzing drive";
        List<INode> nodes = null;
        await Task.Factory.StartNew(() =>
        {
            var ntfsReader =
                new NtfsReader(driveToAnalyze, RetrieveMode.All);
            nodes =
                ntfsReader.GetNodes(driveToAnalyze.Name)
                    .Where(n => (n.Attributes &
                                 (Attributes.Hidden | Attributes.System |
                                  Attributes.Temporary | Attributes.Device |
                                  Attributes.Directory | Attributes.Offline |
                                  Attributes.ReparsePoint | Attributes.SparseFile)) == 0)
                    .OrderByDescending(n => n.Size).ToList();
        });
        FilesList.ItemsSource = nodes;
        DrvCombo.IsEnabled = true;
        StatusTxt.Text = $"{nodes.Count} files listed. " +
                         $"Total size: {nodes.Sum(n => (double)n.Size):N0}";
    }
}

This code creates a task and runs the analyzing code in it, and doesn’t freeze the UI. I just took care of disabling the combobox and putting a warning for the user. After the code is run, the nodes list is assigned to the listbox and the UI is re-enabled.

This code can show you the list of the largest files in your disk, but you may want to analyze it by other ways, like grouping by extension or by folder. WPF has an easy way to group and show data: the CollectionViewSource. With it, you can do grouping and sorting in the ListBox. We will change our UI to add a new ComboBox to show the new groupings:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="40"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>
    <StackPanel Orientation="Horizontal" Margin="5">
        <TextBlock Text="Drive" VerticalAlignment="Center"/>
        <ComboBox x:Name="DrvCombo" Margin="5,0" Width="100" 
                  VerticalContentAlignment="Center"/>
    </StackPanel>
    <StackPanel Grid.Row="0" HorizontalAlignment="Right" Orientation="Horizontal" Margin="5">
        <TextBlock Text="Sort" VerticalAlignment="Center"/>
        <ComboBox x:Name="SortCombo" Margin="5,0" Width="100" 
                  VerticalContentAlignment="Center" SelectedIndex="0" 
                  SelectionChanged="SortCombo_OnSelectionChanged">
            <ComboBoxItem>Size</ComboBoxItem>
            <ComboBoxItem>Extension</ComboBoxItem>
            <ComboBoxItem>Folder</ComboBoxItem>
        </ComboBox>
    </StackPanel>
    <ListBox x:Name="FilesList" Grid.Row="1" 
             VirtualizingPanel.IsVirtualizing="True"
             VirtualizingPanel.IsVirtualizingWhenGrouping="True" >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <TextBlock Text="{Binding FullName}" 
                               Margin="5,0" Width="450"/>
                    <TextBlock Text="{Binding Size,StringFormat=N0}" 
                               Margin="5,0" Width="150" TextAlignment="Right"/>
                    <TextBlock Text="{Binding LastChangeTime, StringFormat=g}" 
                               Margin="5,0" Width="200"/>
                </StackPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
        <ListBox.GroupStyle>
            <GroupStyle>
                <GroupStyle.HeaderTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Name}" FontSize="15" FontWeight="Bold" 
                                       Margin="5,0"/>
                            <TextBlock Text="(" VerticalAlignment="Center" Margin="5,0,0,0" />
                            <TextBlock Text="{Binding Items.Count}" VerticalAlignment="Center"/>
                            <TextBlock Text=" files - " VerticalAlignment="Center"/>
                            <TextBlock Text="{Binding Items, 
                                Converter={StaticResource ItemsSizeConverter}, StringFormat=N0}"
                                         VerticalAlignment="Center"/>
                            <TextBlock Text=" bytes)" VerticalAlignment="Center"/>
                        </StackPanel>
                    </DataTemplate>
                </GroupStyle.HeaderTemplate>
            </GroupStyle>
        </ListBox.GroupStyle>
    </ListBox>
    <TextBlock x:Name="StatusTxt" Grid.Row="2" HorizontalAlignment="Center" Margin="5"/>
</Grid>

The combobox has three options, Size, Extension and Folder. The first one is the same thing we’ve had until now; the second will group the files by extension and the third will group the files by top folder. We’ve also added a GroupStyle to the listbox. If we don’t do that, the data will be grouped, but the groups won’t be shown. If you notice the GroupStyle, you will see that we’re adding the name, then the count of the items (number of files in the group), then we have a third TextBox where we pass the Items and a converter. That’s because we want to show the total size in bytes of the group. For that, I’ve created a converter that converts the Items in the group to the sum of the bytes of the file:

public class ItemsSizeConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
        CultureInfo culture)
    {
        var items = value as ReadOnlyObservableCollection<object>;
        return items?.Sum(n => (double) ((INode)n).Size);
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
        CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

The code for the SelectionChanged for the sort combobox is:

private void SortCombo_OnSelectionChanged(object sender, 
    SelectionChangedEventArgs e)
{
    if (_view == null)
        return;
    _view.GroupDescriptions.Clear();
    _view.SortDescriptions.Clear();
    switch (SortCombo.SelectedIndex)
    {
        case 1:
            _view.GroupDescriptions.Add(new PropertyGroupDescription("FullName", 
                new FileExtConverter()));
            break;
        case 2:
            _view.SortDescriptions.Add(new SortDescription("FullName",
                ListSortDirection.Ascending));
            _view.GroupDescriptions.Add(new PropertyGroupDescription("FullName", 
                new FilePathConverter()));
            break;
    }
}

We add GroupDescriptions for each kind of group. As we don’t have the extension and top path properties in the nodes shown in the listbox, I’ve created two converters to get these from the full name. The converter that gets the extension from the name is:

class FileExtConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
        CultureInfo culture)
    {
        var fileName = value as string;
        return string.IsNullOrWhiteSpace(fileName) ? 
            null : 
            Path.GetExtension(fileName).ToLowerInvariant();
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
        CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

The converter that gets the top path of the file is:

class FilePathConverter :IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
        CultureInfo culture)
    {
        var fileName = value as string;
        return string.IsNullOrWhiteSpace(fileName) ?
            null :
            GetTopPath(fileName);
    }

    private string GetTopPath(string fileName)
    {
        var paths = fileName.Split(Path.DirectorySeparatorChar).Take(2);
        return string.Join(Path.DirectorySeparatorChar.ToString(), paths);
    }

    public object ConvertBack(object value, Type targetType, object parameter, 
        CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

One last thing is to create the _view field, when we are filling the listbox:

 private async void DrvCombo_SelectionChanged(object sender, 
     System.Windows.Controls.SelectionChangedEventArgs e)
 {
     if (DrvCombo.SelectedItem != null)
     {
         var driveToAnalyze = (DriveInfo) DrvCombo.SelectedItem;
         DrvCombo.IsEnabled = false;
         StatusTxt.Text = "Analyzing drive";
         List<INode> nodes = null;
         await Task.Factory.StartNew(() =>
         {
             var ntfsReader =
                 new NtfsReader(driveToAnalyze, RetrieveMode.All);
             nodes =
                 ntfsReader.GetNodes(driveToAnalyze.Name)
                     .Where(n => (n.Attributes &
                                  (Attributes.Hidden | Attributes.System |
                                   Attributes.Temporary | Attributes.Device |
                                   Attributes.Directory | Attributes.Offline |
                                   Attributes.ReparsePoint | Attributes.SparseFile)) == 0)
                     .OrderByDescending(n => n.Size).ToList();
         });
         FilesList.ItemsSource = nodes;
         _view = (CollectionView)CollectionViewSource.GetDefaultView(FilesList.ItemsSource);

         DrvCombo.IsEnabled = true;
         StatusTxt.Text = $"{nodes.Count} files listed. " +
                          $"Total size: {nodes.Sum(n => (double)n.Size):N0}";
     }
     else
     {
         _view = null;
     }
 }

With all these in place, you can run the app and get a result like this:

Conclusions

As you can see, there is a way to get fast file enumeration for your NTFS disks, but you must have admin privileges to use it. We’ve created a WPF program that uses this kind of enumeration and allows you to group the data in different ways, using the WPF resources. If you need to enumerate  your files very fast, you can consider this way to do it.

The full source code for this article is at https://github.com/bsonnino/NtfsFileEnum

One thing that has been recently announced by Microsoft is the availability of .NET Core 3. With it, you will be able to create WPF and Winforms apps with .NET Core. And one extra bonus is that both WPF and Winforms are being open sourced. You can check these in https://github.com/dotnet/wpf and https://github.com/dotnet/winforms.

The first step to create a .NET Core WPF program is to download the .NET Core 3.0 preview from https://dotnet.microsoft.com/download/dotnet-core/3.0. Once you have it installed, you can check that it was installed correctly by open a Command Line window and typing dotnet –info and seeing the installed version:

:

With that in place, you can change the current folder to a new folder and type

dotnet new wpf
dotnet run

This will create a new .NET Core 3.0 WPF project and will compile and run it. You should get something like this:

If you click on the Exit button, the application exits. If you take a look at the folder, you will see that it generated the WPF project file, App.xaml and App.xaml.cs, MainWindow.xaml and MainWindow.xaml.cs. The easiest way to edit these files is to use Visual Studio Code. Just open Visual Studio Code and go to menu File/Open Folder and open the folder for the project. There you will see the project files and will be able to run and debug your code:

A big difference can be noted in the csproj file. If you open it, you will see something like this:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>

</Project>

That’s very simple and there’s nothing else in the project file. There are some differences between this project and other types of .NET Core, like the console one:

  • The output type is WinExe, and not Exe, in the console app
  • The UseWPF clause is there and it’s set to true

Now, you can modify and run the project inside VS Code. Modify MainWindow.xaml and put this code in it:

<Window x:Class="DotNetCoreWPF.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:DotNetCoreWPF" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
                <RowDefinition Height="40"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="2*"/>
            </Grid.ColumnDefinitions>

            <TextBlock Text="Id"      Grid.Column="0" Grid.Row="0" Margin="5" VerticalAlignment="Center"/>
            <TextBlock Text="Name"    Grid.Column="0" Grid.Row="1" Margin="5" VerticalAlignment="Center"/>
            <TextBlock Text="Address" Grid.Column="0" Grid.Row="2" Margin="5" VerticalAlignment="Center"/>
            <TextBlock Text="City"    Grid.Column="0" Grid.Row="3" Margin="5" VerticalAlignment="Center"/>
            <TextBlock Text="Email"   Grid.Column="0" Grid.Row="4" Margin="5" VerticalAlignment="Center"/>
            <TextBlock Text="Phone"   Grid.Column="0" Grid.Row="5" Margin="5" VerticalAlignment="Center"/>
            <TextBox Grid.Column="1" Grid.Row="0" Margin="5"/>
            <TextBox Grid.Column="1" Grid.Row="1" Margin="5"/>
            <TextBox Grid.Column="1" Grid.Row="2" Margin="5"/>
            <TextBox Grid.Column="1" Grid.Row="3" Margin="5"/>
            <TextBox Grid.Column="1" Grid.Row="4" Margin="5"/>
            <TextBox Grid.Column="1" Grid.Row="5" Margin="5"/>
        </Grid>
        <Button Content="Submit" Width="65" Height="35" Grid.Row="1" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0"/>
    </Grid>
</Window>

Now, you can compile and run the app in VS Code with F5, and you will get something like this:

If you don’t want to use Visual Studio Code, you can edit your project in Visual Studio 2019. The first preview still doesn’t have a visual editor for the XAML file, but you can edit the XAML file in the editor, it will work fine.

Porting a WPF project to .NET Core

To port a WPF project to .NET Core, you should run the Portability Analyzer tool first, to see what problems you will find before porting it to .NET Core. This tool can be found here. You can download it and run on your current application, and check what APIs that are not portable.

I will be porting my DiskAnalisys project. This is a simple project, that uses the File.IO functions to enumerate the files in a folder and uses two NuGet packages to add a Folder Browser and Charts to WPF. The first step is to run the portability analysis on it. Run the PortabilityAnalizer app and point it to the folder where the executable is located:

When you click on the Analyze button, it will analyze the executable and generate an Excel spreadsheet with the results:

As you can see, all the code is compatible with .NET Core 3.0. So, let’s port it to .NET Core 3.0. I will show you three ways to do it: creating a new project, updating the .csproj file and using a tool.

Upgrading by Creating a new project

This way needs the most work, but it’s the simpler to fix. Just create a new folder and name it DiskAnalysisCorePrj. Then open a command line window and change the directory to the folder you’ve created. Then, type these commands:

dotnet new wpf
dotnet add package wpffolderbrowser
dotnet add package dotnetprojects.wpf.toolkit
dotnet run

These commands will create the WPF project, add the two required NuGet packages and run the default app. You may see a warning like this:

D:\Documentos\Artigos\Artigos\CSharp\WPFCore\DiskAnalysisCorePrj\DiskAnalysisCorePrj.csproj : warning NU1701: Package 'DotNetProjects.Wpf.Toolkit 5.0.43' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.
D:\Documentos\Artigos\Artigos\CSharp\WPFCore\DiskAnalysisCorePrj\DiskAnalysisCorePrj.csproj : warning NU1701: Package 'WPFFolderBrowser 1.0.2' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.
D:\Documentos\Artigos\Artigos\CSharp\WPFCore\DiskAnalysisCorePrj\DiskAnalysisCorePrj.csproj : warning NU1701: Package 'DotNetProjects.Wpf.Toolkit 5.0.43' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.
D:\Documentos\Artigos\Artigos\CSharp\WPFCore\DiskAnalysisCorePrj\DiskAnalysisCorePrj.csproj : warning NU1701: Package 'WPFFolderBrowser 1.0.2' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.

This means that the NuGet packages weren’t converted to .NET Core 3.0, but they are still usable (remember, the compatibility report showed 100% compatibility). Then, copy MainWindow.xaml and MainWindow.xaml.cs from the original folder to the new one. We don’t need to copy any other files, as no other files were changed. Then, type

dotnet run

and the program is executed:

Converting by Changing the .csproj file

This way is very simple, just changing the project file, but can be challenging, especially for very large projects. Just create a new folder and name it DiskAnalysisCoreCsp. Copy all files from the main folder of the original project (there’s no need of copying the Properties folder) and edit the .csproj file, changing it to:

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>netcoreapp3.0</TargetFramework>
    <UseWPF>true</UseWPF>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="dotnetprojects.wpf.toolkit" Version="5.0.43" />
    <PackageReference Include="wpffolderbrowser" Version="1.0.2" />
  </ItemGroup>
</Project>

Then, type

dotnet run

and the program is executed.

Converting using a tool

The third way is to use a tool to convert the project. You must install the conversion extension created by Brian Lagunas, from here. Then, open your WPF project in Visual Studio, right-click in the project and select “Convert Project to .NET Core 3”.

That’s all. You now have a NET Core 3 app. If you did that in Visual Studio 2017, you won’t be able to open the project, you will need to compile it with dotnet run, or open it in Visual Studio code.

Conclusions

As you can see, although this is the first preview of WPF .NET Core, it has a lot of work done, and you will be able to port most of your WPF projects to .NET Core.

In a previous post, I’ve shown how to publish your Delphi app to the Windows Store. If it is a paid app, you can start getting money with it as soon as it starts to sell. But sometimes, you don’t want to make it a paid app, but you still want to earn money with it.

One way to do it is to do the same that many games do: to use In-app purchases. You can offer a free basic version and put some paid add-ins in the app, so the user must pay to get the premium features. Even if it’s a paid app, you can also give the user a trial period, so, the user can use the premium version for some time and then, if he doesn’t want to pay for the app, he can still use the basic features.

Delphi 10.3 introduced a new component, TWindowsStore, that allows you to control the store features (add-ins, trial version) and enable or disable features depending on what the user pays. In this article, I will show how to use the TWindowsStore component to control the features and set the In-app purchases.

We will use the same Financial Calculator we’ve used in the previous article, but we’ll do some changes:

  • The basic calculator will be able to calculate only the Present Value of the investment. The other calculators won’t be available.
  • The user will have a trial period where all the calculators are available. Once the trial period is expired, the app will revert to the basic version
  • The user will be able to buy calculators as add-in purchases. He won’t need to buy all, he will be able to buy just the ones he needs as add-in purchases.

Developing the trial and in-app purchases

The first step in the development of the trial version is to add a TWindowsStore component in the main window of the calculator. Then, we will change the app to show the basic features for the app. To do this, change the PageIndex of the calculators:

  • Present Value – 0
  • Future Value – 1
  • Payments – 2
  • Return Rate – 3

Put a panel with a button over each calculator, except the first one, with this text: “To open this calculator, click on the button”. Change the ParentBackground property to false. Add another TabSheet, with a label with a caption with text “Trial version – % days remaining”. If you run the app, you should have something like this:

Now, let’s program the trial version. When the user is in trial period, he will be able to use all calculators. To do that, we’ll do something like this:

procedure TForm1.CheckIfTrial;
begin
  if WindowsStore1.AppLicense.IsActive then begin
    if WindowsStore1.AppLicense.IsTrial then begin
      var RemainingDays := WindowsStore1.AppLicense.TrialTimeRemaining.Days;
      Label21.Caption := Format(Label21.Caption, [RemainingDays]);
      EnableFullVersion;
    end
  end
  else begin
    CheckBoughtCalculators;
  end;
end;

The CheckIfTrial pocedure will be called in the OnCreate handler of the form, thus setting the UI accordingly at start. The EnableFullVersion procedure will hide all trial panels:

procedure TForm1.EnableFullVersion;
begin
  Panel1.Visible := False;
  Panel2.Visible := False;
  Panel3.Visible := False;
end;

The CheckBoughtCalculators will only hide the panels for the add-ons that had been bought:

procedure TForm1.CheckBoughtCalculators;
begin
  Panel1.Visible := not WindowsStore1.UserHasBought('FutureCalc');
  Panel2.Visible := not WindowsStore1.UserHasBought('PaymentCalc');
  Panel3.Visible := not WindowsStore1.UserHasBought('RateCalc');
end;

The code to buy the add-ons is:

function TForm1.PurchaseItem(Item: string) : string;
begin
  LogMessage('Will purchase item: ' +Item);
  for var i := 0 to WindowsStore1.AppProducts.Count - 1 do
    if TWindowsString.HStringToString(WindowsStore1.AppProducts[i].InAppOfferToken) = Item then begin
      BuyProduct(WindowsStore1.AppProducts[i]);
      exit;
    end;
  LogMessage('Item not found: ' +Item);
end;

procedure TForm1.BuyProduct(Product: IStoreProduct);
begin
  try
    var status := WindowsStore1.PurchaseProduct(Product);
    LogMessage('Got status: '+Integer(status).ToString);
    if status = StorePurchaseStatus.Succeeded then begin
      LogMessage('Item ' +TWindowsString.HStringToString(Product.Title)+' bought');
      CheckBoughtCalculators();
    end
    else begin
      ShowMessage('Item could not be purchased. Error: '+Integer(status).ToString);
    end;
  except
    On e : Exception do
      LogMessage('Exception while buying item.'+Chr(13)+
        E.ClassName+', with message : '+E.Message);
  end;
end;

The code for the buttons is:

procedure TForm1.Button1Click(Sender: TObject);
begin
  PurchaseItem('FutureCalc');
end;

While developing the app, I found that this code generates an exception, there is something in the code that doesn’t like the Delphi Window handle and crashes the app when the code is run. Not a good experience for a paid app. So, I searched the web for an alternative and found this article that shows how to create a dll in C# that can be used in Delphi, to allow buying add-in purchases in the Windows Store. If you follow the instructions there and compile the dll, you will have the IAPWrapper.dll that can be used in our Delphi program.

This dll exposes a function, Purchase, that makes the purchase of an add-in. It’s different than the TWindowsStore’s PurchaseProduct, because it receives the item id as a parameter and not the item itself. That way, we must add the dll to the project, declare the Purchase function and change the BuyProduct method:

function Purchase(StoreId : PAnsiChar) : PAnsiChar; stdcall; external 'IAPWrapper.dll';

procedure TForm1.BuyProduct(Product: IStoreProduct);
begin
  try
    var status := Purchase(PAnsiChar(AnsiString(TWindowsString.HStringToString(Product.StoreId))));
    LogMessage('Got status: '+status);
    if status = 'Succeeded' then begin
      LogMessage('Item ' +TWindowsString.HStringToString(Product.Title)+' bought');
      CheckBoughtCalculators();
    end
    else begin
      ShowMessage('Item could not be purchased. Error: '+Integer(status).ToString);
    end;
  except
    On e : Exception do
      LogMessage('Exception while buying item.'+Chr(13)+
        E.ClassName+', with message : '+E.Message);
  end;
end;

With this code, you can put your app in the store and monetize with it. When the user wants a calculator, he can buy the item and it will be available forever. You could also make it consumable, so it can be bought again at anytime, but for our purposes, that’s fine.

Creating the add-ins in the store

Once you have the program developed, you must create your add-ins in the store. Go to the Windows Development Center, select the app you have developed for the store and click on the “Create a new add-on” button.

There you can create many types of add-ons:

  • Developer-managed consumable – this kind of add-on is managed by your app, like some kind of strength. If this consumable is used on your app, the user can buy it again.
  • Store-managed consumable – this kind of add-on is managed by the store, like some kind of gun. You can query the store to know how many guns the user has bought
  • Durable – this kind of add-on is bought once and it is available until its lifetime
  • Subscription – this kind of add-on needs periodic payment to keep using it

Our add-ons will all be durable, once the user buys a calculator, he won’t need to buy it again. Create a new durable add-on and name it FutureCalc. Then start the submission for it:

Set the properties for the add-on, with a product lifetime Forever and Content type as Software as service:

Then, set the price and availability of the add-on:

We won’t change anything here – we will leave the add-in for free, so you just need to click the Save button.

The next step is to add the store listings. Add a new language and select English (United States) and edit it:

Set the title and description and click on the Save button. When you have everything set, just click on the Submit to the store to submit it. Do the same thing with the PaymentCalc and RateCalc add-ons.

The next step is to create a new flight for the package. Just change the version to 1.1 and compile the app for the store (don’t forget to change the Provisioning data to Store). Then, create a new flight for the submission and send the package there. It will be available to download as soon as it passes certification. It will show that it has in-app purchases:

Once you’ve installed it, it will show you the new version, and you can unlock the calculator you want:

 

Conclusions

As you can see you can send your Delphi apps to the Windows store and get some money with them The TWindowsStore component allows you to interact with the add-ins you’ve set in the store, so you can earn money by creating a paid version or even by adding purchases in the app. While developing the app, I’ve shown that you can also create a dll in C# that interacts with your Delphi program in the same way that a Win32 dll does. You can use this dll in the store to buy add-ins and monetize your app.

The full source code for this article is in https://github.com/bsonnino/FinCalcAddIn

 

 

In the a previous post, I’ve told that you should use controls that are responsive to ease your work of creating an UI that adapts to the screen size. One of the controls that can help you a lot in this task is the Navigation View. In fact, if you do nothing and create something like:

<Grid>
    <NavigationView/>
</Grid>

You will have a full featured UI with a navigation pane, a content pane and a settings button:

And this UI adapts itself to the screen size. If you resize your screen to decrease its height, the navigation pane will collapse and, if you decrease a little more, it will collapse, remaining only the Hamburger and Back buttons:

This is a great bonus for you: you don’t need to worry about adapting to the device – the control does that automatically for you. In this article, I will show you how to use the Navigation View to show your content and navigate between pages.

Adding Navigation Items

To add navigation items, you should use its MenuItems property. There you can add simple items (NavigationItem), separators (NavigationItemSeparator) and headers (MenuItemHeader). For example, when you use this code:

<Grid>
    <NavigationView>
        <NavigationView.MenuItems>
            <NavigationViewItemHeader Content="Main"/>
            <NavigationViewItem Content="Customers"/>
            <NavigationViewItem Content="Orders"/>
            <NavigationViewItemSeparator/>
            <NavigationViewItemHeader Content="Reports"/>
            <NavigationViewItem Content="Customers"/>
            <NavigationViewItem Content="Orders"/>
            <NavigationViewItem Content="Sales"/>
            <NavigationViewItemSeparator/>
            <NavigationViewItemHeader Content="Charts"/>
            <NavigationViewItem Content="Sales by Customer"/>
            <NavigationViewItem Content="Sales by Product"/>
            <NavigationViewItem Content="Sales by Date"/>
        </NavigationView.MenuItems>
    </NavigationView>
</Grid>

You will get this UI:

Although it seems good when the navigation pane is open, when it’s closed, it doesn’t work so well:

You need to add an icon to the items. This is done with the Icon property. You can set it directly using an enumeration for the symbol (see all enumerations here) or you can set the icon from an image. If you want to set a symbol form the Segoe MDL2 Assets font, you can use a FontIcon. This code shows how this is done:

<NavigationView>
    <NavigationView.MenuItems>
        <NavigationViewItemHeader Content="Main" />
        <NavigationViewItem Content="Customers" Icon="People"/>
        <NavigationViewItem Content="Orders" Icon="Shop"/>
        <NavigationViewItemSeparator/>
        <NavigationViewItemHeader Content="Reports"/>
        <NavigationViewItem Content="Customers" >
            <NavigationViewItem.Icon>
                <FontIcon Glyph="" FontFamily="Segoe UI Symbol"/>
            </NavigationViewItem.Icon>
        </NavigationViewItem>
        <NavigationViewItem Content="Orders">
            <NavigationViewItem.Icon>
                <FontIcon Glyph="" FontFamily="Segoe UI Symbol"/>
            </NavigationViewItem.Icon>
        </NavigationViewItem>
        <NavigationViewItem Content="Sales">
            <NavigationViewItem.Icon>
                <PathIcon HorizontalAlignment="Center" VerticalAlignment="Center" 
                          Data="M 0,0 L20,0 40,70 140,70 140,74 36,74 20,4z 
                          M25,12 L150,12 140,50 37,55z 
                          M70,82 A10,10 360 1 1 70,81.99z 
                          M120,82 A10,10 360 1 1 120,81.99z" />
            </NavigationViewItem.Icon>
        </NavigationViewItem>
        <NavigationViewItemSeparator/>
        <NavigationViewItemHeader Content="Charts"/>
        <NavigationViewItem Content="Sales by Customer">
            <NavigationViewItem.Icon>
                <FontIcon Glyph=""/>
            </NavigationViewItem.Icon>
        </NavigationViewItem>
        <NavigationViewItem Content="Sales by Product">
            <NavigationViewItem.Icon>
                <PathIcon Data="M0,0 L1.25,0 1.25,18 18,18 18,19.25 0,19.25z
                          M0,18 L18,0 19.25,0 19.25,1.25z" />
            </NavigationViewItem.Icon>
        </NavigationViewItem>
        <NavigationViewItem Content="Sales by Date" >
            <NavigationViewItem.Icon>
                <FontIcon Glyph=""/>
            </NavigationViewItem.Icon>
        </NavigationViewItem>
    </NavigationView.MenuItems>
</NavigationView>

In the code above, there are some different ways to set the icon to the item:

  • The Customers and Orders items use the named icons from the MDL2 Assets
  • The Customers and Orders reports use font glyphs from the Segoe UI Symbol font. The Glyph property is the unicode number of the glyph
  • The Sales report doesn’t have a FontIcon, but a PathIcon, that has a Data property that creates the icon as a geometry
  • The Sales by Customers chart uses a glyph from the MDL2 Assets, but uses the unicode number of the gliph. In this case, you don’t need the FontFamily property

This code shows something like this:

Navigating between pages

When you are using this control, two events are key for the navigation: ItemInvoked  and SelectionChanged. ItemInvoked is called when an item is selected by a user interaction (tap, pointer, mouse), and it can be called even if the item is already selected. SelectionChanged is only called if the current item has effectively changed and can be triggered programatically (in this case, ItemInvoked is not called). In the handler, you have to find out which is the item selected and take the according action, usually changing the content of the control. This code shows how to handle this:

private NavigationViewItem _lastItem;

private void NavigationView_OnItemInvoked(
    Windows.UI.Xaml.Controls.NavigationView sender, 
    NavigationViewItemInvokedEventArgs args)
{
    var item = args.InvokedItemContainer as NavigationViewItem;
    if (item == null)
    {
        NavView.Content = null;
        return;
    }
    if (item == _lastItem)
        return;
    var textSelected = item.Content?.ToString() ?? "";
    NavView.Header = textSelected;
    var grid = new Grid();
    var text = new TextBlock
    {
        Text = textSelected,
        FontFamily = new FontFamily("Arial"),
        FontSize = 24,
        HorizontalAlignment = HorizontalAlignment.Center,
        VerticalAlignment = VerticalAlignment.Center
    };
    grid.Children.Add(text);
    NavView.Content = grid;
    _lastItem = item;
}

We are checking the selected item, if it is a different one, we create a grid with a textblock that shows the text of the selected option and set it as the content for the NavigationView. This works fine, but there is more that can be done here: we can also handle the back button and act if it’s clicked (until now it’s been disabled. If you don’t want to show it, just set the IsBackButtonVisible property to False).

Managing content and navigation

We could manage the content and navigation by ourselves. In this case, we would need a navigation mechanism, that handles the back navigation and replaces the content in the NavigationView. But we can do it by adding a single control: the Frame. It can control the back navigation and even replace the content with a transition. This can be done with something like this:

<NavigationView x:Name="NavView" 
                ItemInvoked="NavigationView_OnItemInvoked"
                BackRequested="NavView_OnBackRequested"
                IsBackEnabled="{Binding ElementName=ContentFrame, Path=CanGoBack}">
....
    <Frame x:Name="ContentFrame" 
           NavigationFailed="ContentFrame_OnNavigationFailed"/>
</NavigationView>

The IsBackEnabled property of the NavigationView is bound to the CanGoBack property of the frame. In the previous version of the code, I’ve used the title of the navigation item, but I think this option is less than optimal, because if we change the title, the code breaks. In this case, I think it’s better to use the Tag property, a property that can store any object. In our case, we will store an unique string that will point to the view, so we can navigate to it. Our code will be something like this:

<NavigationView.MenuItems>
    <NavigationViewItemHeader Content="Main" />
    <NavigationViewItem Content="Customers" Icon="People" Tag="CustView"/>
    <NavigationViewItem Content="Orders" Icon="Shop" Tag="OrderView"/>
    <NavigationViewItemSeparator/>
    <NavigationViewItemHeader Content="Reports"/>
    <NavigationViewItem Content="Customers" Tag="CustRepoView">
        <NavigationViewItem.Icon>
            <FontIcon Glyph="" FontFamily="Segoe UI Symbol"/>
        </NavigationViewItem.Icon>
    </NavigationViewItem>
    <NavigationViewItem Content="Orders" Tag="OrderRepoView">
        <NavigationViewItem.Icon>
            <FontIcon Glyph="" FontFamily="Segoe UI Symbol"/>
        </NavigationViewItem.Icon>
    </NavigationViewItem>
    <NavigationViewItem Content="Sales" Tag="SalesRepoView">
        <NavigationViewItem.Icon>
            <PathIcon HorizontalAlignment="Center" VerticalAlignment="Center" 
                      Data="M 0,0 L20,0 40,70 140,70 140,74 36,74 20,4z 
                      M25,12 L150,12 140,50 37,55z 
                      M70,82 A10,10 360 1 1 70,81.99z 
                      M120,82 A10,10 360 1 1 120,81.99z" />
        </NavigationViewItem.Icon>
    </NavigationViewItem>
    <NavigationViewItemSeparator/>
    <NavigationViewItemHeader Content="Charts"/>
    <NavigationViewItem Content="Sales by Customer" Tag="SalesCustChartView">
        <NavigationViewItem.Icon>
            <FontIcon Glyph=""/>
        </NavigationViewItem.Icon>
    </NavigationViewItem>
    <NavigationViewItem Content="Sales by Product" Tag="SalesProdChartView">
        <NavigationViewItem.Icon>
            <PathIcon Data="M0,0 L1.25,0 1.25,18 18,18 18,19.25 0,19.25z
                      M0,18 L18,0 19.25,0 19.25,1.25z" />
        </NavigationViewItem.Icon>
    </NavigationViewItem>
    <NavigationViewItem Content="Sales by Date" Tag="SalesDateChartView">
        <NavigationViewItem.Icon>
            <FontIcon Glyph=""/>
        </NavigationViewItem.Icon>
    </NavigationViewItem>
</NavigationView.MenuItems>

Now, we must create the views for each item. Create a new folder named Views and, in this folder, create new pages, one for each item: CustView, OrderView, CustRepoView,OrderRepoView, SalesRepoView, SalesCustChartView, SalesProdChartView and SalesDateChartView.

With the views in place, we can create the ItemInvoked handler:

private NavigationViewItem _lastItem;
private void NavigationView_OnItemInvoked(
    Windows.UI.Xaml.Controls.NavigationView sender, 
    NavigationViewItemInvokedEventArgs args)
{
    var item = args.InvokedItemContainer as NavigationViewItem;
    if (item == null || item == _lastItem)
        return;
    var clickedView = item.Tag?.ToString();
    if (!NavigateToView(clickedView)) return;
    _lastItem = item;
}

The NavigateToView method is:

private bool NavigateToView(string clickedView)
{
    var view = Assembly.GetExecutingAssembly()
        .GetType($"NavigationView.Views.{clickedView}");

    if (string.IsNullOrWhiteSpace(clickedView) || view == null)
    {
        return false;
    }

    ContentFrame.Navigate(view, null, new EntranceNavigationTransitionInfo());
    return true;
}

This method uses reflection to get the type corresponding to the view name that’s in the Tag property and navigates to it. If you run the program, you will see something like this when you click an item:

This code has one problem: if you click the Settings item, you will get an error. That’s because the Settings item doesn’t have a valid Tag. So, we must create the SettingsView page in the Views folder and change the ItemInvoked handler:

private void NavigationView_OnItemInvoked(
    Windows.UI.Xaml.Controls.NavigationView sender, 
    NavigationViewItemInvokedEventArgs args)
{
    var item = args.InvokedItemContainer as NavigationViewItem;
    if (item == null || item == _lastItem)
        return;
    var clickedView = item.Tag?.ToString() ?? "SettingsView";
    if (!NavigateToView(clickedView)) return;
    _lastItem = item;
}

If the tag is null, we will navigate to the SettingsView. With this change, the code works fine and you can click on the Settings item. There is only one thing that can be made, here: manage when the back button is clicked. We do this in the BackRequested handler:

private void NavView_OnBackRequested(
    Windows.UI.Xaml.Controls.NavigationView sender, 
    NavigationViewBackRequestedEventArgs args)
{
    if (ContentFrame.CanGoBack)
        ContentFrame.GoBack();
}

With this code, the back button is also handled with very little code.

Conclusions

As you can see, the NavigationView allows you to create easily a responsive UI, with lots of features and many different ways to handle navigation. There are some features that I didn’t mention in the article, like the fact that you can stick to a view and make it not respond to size changes, you can change the stops where the view changes, you can add a header or footer to the page or you can even put the items at the top, just by changing a property – If you set the PaneDisplayMode to Top, you will get this UI:

This is a very powerful control and a nice improvement for your UI.

The full source code for this project is at https://github.com/bsonnino/NavigationView

In a previous post, I’ve shown how to package a Delphi application for the Windows Store, and in this post I’ve shown how to package a WPF application. In both cases, the apps were packaged alone or with their references, so there was no trouble to package them to the store.

In the last post, I’ve refactored the Financial Calculator into a main app and a dll, so the code for the calculators could be reusable and testable. Now we have to package the apps for the store again, but this time there will be an extra task: to package the dll together with the app. In this article, I’ll show how to do that for both applications, the Delphi and the WPF one.

Packaging a Delphi app

To package the Delphi app, we just need to go to the project, select the Application Store configuration, then go to Project/Options/Provisioning and select the Ad Hoc deployment and add the certificate we created earlier. Then, we run the packaging and install the appx file. When you install and run the app, you will see a message like this:

That’s because the dll wasn’t packaged with the executable. If you rename the appx file to zip and open it with a zip manager (yes, an appx file is a zip file with another extension), you will see something like this:

As you can see, the appx file is a zip file with all the content needed to run the app. If you take a look at it, you will see that the dll isn’t there, that’s why you get the message. There is nothing that says that the dll should be added to the package, there are no references and the load of the dll is only done at runtime, so the packager doesn’t knows the dll is needed. We must do some things, so the dll is packaged with the app.

Go to the Projects window and right click on the FinCalc.exe node and select the Add option. Then add the FinCalcDll.dll to the project:

Now, when you rebuild the app, you will see that FinCalcDll.dll was added to the appx file and it will be used when you install it from the store:

There is something to note, here: this setting won’t work for the normal app. If you want to run the app as a normal app, you must continue to xcopy the dll to the output directory, this setting won’t copy it to the output directory. So, to be safe, do both things:

  • Add the dll to the project
  • Xcopy the dll to the output dir

Packaging a WPF app

Now that we’ve packaged the Delphi app with the dll, we must package the WPF app. To create a package to a WPF app, we must create a packaging project in Visual Studio:

Once the new project is created, in the Solution Explorer, you must right-click in the Applications node and select Add Reference. Then you must add the WPF project to the package. Then, right click on the project node and select Store/Create App Packages. There you can select if you want to create packages for the Store or for Sideloading:

If you want to create an app for the store, you must have a developer account and associate a name for the app in the store. We will choose to sideload the app:

Here we must note some things: as our dll is a 32 bit one, we must choose the x86 package only. We don’t need and app bundle, where all the platforms are packaged in a single file – we will use the “Generate app bundle” to Never, then click Create. That will create the appx package and show the folder where it was created. Opening the folder, you will find some files to install the app, including the appx file.

If you double click on it and click on Install, you will see something like this (just for the first time):

That’s because the certificate you’ve used is not installed in the machine. To install it, you can open the Package.appxmanifest file in Visual Studio and go to the Packaging tab:

Then, click on the Choose Certificate button and then on View Full Certificate. That will open the install certificate window:

You must click on the Install Certificate button and select Local Machine as the store location. Then click on Next and then in Place all certificates in the following store, clicking on Browse and selecting Trusted People. The certificate will be installed and you can click again in the Appx file to install it.

This app will run without problems, because the dll will be added to the package. Adding the dll in the original project as content will make it to be packaged for the store, there’s nothing else to do.

Conclusions

As you can see, packaging an app with a dll for the Windows Store is not too difficult, but you must be aware of these things, so the app doesn’t fail when running, for the lack of the dll.

All the source code for these projects are at https://github.com/bsonnino/FinCalcDll

 

While writing my last article, something occurred to me: what if the app uses an external dll for some functions, how can I package them to send them to the store. Once you are sending an app to the Windows Store, everything it needs to start and run must be packaged, or it won’t be certified.

When using an installer to package an app, all you must do is to include the main executable and all the needed files in the install script and the installer will take care of packaging everything. But when your are packaging an app for the Windows Store, there is no such thing as an install script. If you are packaging a Delphi app, just compile it to the Store and voilà!, an appx file is created. With Visual Studio, you can create a packaging project, add all the projects in the solution that you want and it will create an appx file for you.

But sometimes, you need to use an external dll, which you may not have the source code, and it must be packaged with the main executable. In this article, I will show how to package an external dll with the main executable with Delphi and with Visual Studio.

For the article, we will take on the Financial Calculator that we created for the last article and refactor it: the financial functions will be in an external Win32 dll and the UI will be in the main executable. That way, we will do two things:

  • Separate the UI and the business rules
  • Create an external component that may be used in many situations: we will use it for our two apps – the Delphi and the WPF one. That is a great way to refactor your code when you have stable business rules that you don’t want to touch and you need to evolve the UI of your app

Refactoring the Delphi UI

As you can see from this code, the business rules are mixed with the UI, thus making it difficult to understand it and change the code, if it’s needed.

procedure TForm1.CalculatePV;
begin
  try
    var FutureValue := StrToFloat(FvPresentValue.Text);
    var InterestRate := StrToFloat(IrPresentValue.Text) / 100.0;
    var NumPeriods := StrToInt(NpPresentValue.Text);
    var PresentValue := FutureValue / Power((1 + InterestRate), NumPeriods);
    PvPresentValue.Text := FormatFloat('0.00', PresentValue);
  except
    On EConvertError do
      PvPresentValue.Text := '';
  end;
end;

For example, all the values are dependent on the text box values and the result is also posted in the result text box. This is bad design and not testable. A better thing would be something like this:

function TForm1.CalculatePV(FutureValue, InterestRate : Double; NumPeriods : Integer); double;
begin
  try
    Result := FutureValue / Power((1 + InterestRate), NumPeriods);
  except
    Result := NAN;
  end;
end;

This is cleaner, does not depend on the UI and easier to understand. But it is not testable, yet, because the method is in the code behind for the UI, so to test it you should need to instantiate a new Form1, which is not feasible under automated tests (unless you are doing UI tests, which is not the case). You could move this code to another unit, to allow it to be testable, but it won’t be reusable. If you want to use the same code in another program, you should have to copy the unit, with all the problems you may have with that:

  • Difficulty to change the code: if you find an error or want to refactor the code, you should fix the same thing in many places
  • Impossible to use in programs written in other languages, unless you rewrite the code

The best way in this case is to move the code to an external dll. That way, the code will be both testable and reusable: you can even use the same dll in programs written in other languages with no change.

The first step is to create a new project in the project group, a dynamic library. Save it and call it FinCalcDll. Then add a new unit to it and save it as PVCalculator. You should be asking why am I saving the unit with this name and not as FinancialCalculators. I am doing this because I want to treat each unit as a single class and respect the Single Responsibility Principle. Following that principle, the class should have only one reason to change. If I put all calculators in a single unit (class), there will be more than one reason to change it: any change in any of the calculators will be a reason to change. Then, we can add the first function:

unit PVCalculator;

interface

Uses Math;

function CalculatePV(FutureValue, InterestRate: Double; NumPeriods : Integer): double; stdcall;

implementation

function CalculatePV(FutureValue, InterestRate: Double; NumPeriods : Integer):
  double;
begin
  try
    Result := FutureValue / Power((1 + InterestRate), NumPeriods);
  except
    Result := NAN;
  end;
end;

end.

We must use the Math unit, to have the Power function available and declare the function in the Interface section, so it can be visible externally. It must be declared as stdcall to be called by other languages. Create new units and save them as FVCalculator, IRRCalculator and PmtCalculator and add these functions:

unit PVCalculator;

interface

Uses Math;

function CalculatePV(FutureValue, InterestRate: Double; NumPeriods : Integer):
  double; stdcall;

implementation

function CalculatePV(FutureValue, InterestRate: Double; NumPeriods : Integer):
  double;
begin
  try
    Result := FutureValue / Power((1 + InterestRate), NumPeriods);
  except
    Result := NAN;
  end;
end;

end.
unit FVCalculator;

interface

Uses Math;

function CalculateFV(PresentValue, InterestRate: Double;NumPeriods : Integer):
  Double; stdcall;

implementation

function CalculateFV(PresentValue, InterestRate: Double;NumPeriods : Integer):
  Double;
begin
  try
    Result := PresentValue * Power((1 + InterestRate), NumPeriods);
  except
    Result := NAN;
  end;
end;

end.
unit IRRCalculator;

interface

Uses Math;

function CalculateIRR(PresentValue, Payment: Double;NumPeriods : Integer):
  Double; stdcall;

implementation

function CalculateIRR(PresentValue, Payment: Double;NumPeriods : Integer):
  Double;
begin
  Result := Nan;
  try
    var FoundRate := False;
    var MinRate := 0.0;
    var MaxRate := 1.0;
    if Payment * NumPeriods < PresentValue then begin
      Result := -1;
      exit;
    end;
    if Payment * NumPeriods = PresentValue then begin
      Result := 0;
      exit;
    end;
    while not FoundRate do begin
      var Rate := (MaxRate + MinRate) / 2.0;
      var SumPayments := 0.0;
      for var I := 1 to NumPeriods do
        SumPayments := SumPayments + Payment / Power((1 + Rate), I);
      if Abs(SumPayments - PresentValue) > 0.01 then begin
        if PresentValue < SumPayments then begin
          MinRate := Rate;
        end
        else begin
          MaxRate := Rate;
        end;
      end
      else begin
        FoundRate := True;
        Result := Rate;
      end;
    end;
  except
  end;
end;

end.<span id="mce_marker" data-mce-type="bookmark" data-mce-fragment="1">​</span>
unit PmtCalculator;

interface

Uses Math;

function CalculatePmt(PresentValue, InterestRate: Double;NumPeriods : Integer):
  Double; stdcall;

implementation

function CalculatePmt(PresentValue, InterestRate: Double;NumPeriods : Integer):
  Double;
begin
  try
    Result := (PresentValue * InterestRate) * Power((1 + InterestRate),
      NumPeriods) / (Power((1 + InterestRate), NumPeriods) - 1);
  except
    Result := Nan;
  end;
end;

end.

In the dpr file, you must export the functions. In the Projects window, select the dll and right-click on it, selecting the View Source option. In the source for the dpr file, add the Exports clause:

{$R *.res}
Exports
  CalculatePV, CalculateFV, CalculateIRR, CalculatePmt;

Then, in the Unit1 for the executable, make the changes needed to use the new dll functions:

implementation

{$R *.dfm}
function CalculatePV(FutureValue, InterestRate : Double;NumPeriods : Integer) :
  Double; stdcall; external 'FinCalcDll.dll';

function CalculateFV(PresentValue, InterestRate : Double;NumPeriods : Integer) :
  Double; stdcall; external 'FinCalcDll.dll';

function CalculateIRR(PresentValue, Payment: Double;NumPeriods : Integer) :
  Double; stdcall; external 'FinCalcDll.dll';

function CalculatePmt(PresentValue, InterestRate : Double;NumPeriods : Integer) :
  Double; stdcall; external 'FinCalcDll.dll';

procedure TForm1.PaymentChange(Sender: TObject);
begin
  try
    var PresentValue := StrToFloat(PvPayment.Text);
    var InterestRate := StrToFloat(IRPayment.Text) / 100.0;
    var NumPayments := StrToInt(NpPayment.Text);
    var Payment := CalculatePmt(PresentValue,InterestRate, NumPayments);
    PmtPayment.Text := FormatFloat('0.00', Payment);
  except
    On EConvertError do
      PmtPayment.Text := '';
  end;
end;

procedure TForm1.PresentValueChange(Sender: TObject);
begin
  try
    var FutureValue := StrToFloat(FvPresentValue.Text);
    var InterestRate := StrToFloat(IrPresentValue.Text) / 100.0;
    var NumPeriods := StrToInt(NpPresentValue.Text);
    var PresentValue := CalculatePV(FutureValue, InterestRate, NumPeriods);
    if IsNan(PresentValue) then
      PvPresentValue.Text := ''
    else
      PvPresentValue.Text := FormatFloat('0.00', PresentValue);
  except
    On EConvertError do
      PvPresentValue.Text := '';
  end;
end;

procedure TForm1.IRRChange(Sender: TObject);
begin
  try
    var NumPayments := StrToInt(NpIRR.Text);
    var PresentValue := StrToFloat(PvIRR.Text);
    var Payment := StrToFloat(PmtIRR.Text);
    var Rate := CalculateIRR(PresentValue, Payment, NumPayments);
    if Rate < 0 then begin
      IRIRR.Text := 'Rate Less than 0';
      exit;
    end;
    if IsNan(Rate) then begin
      IRIRR.Text := 'Error calculating rate';
      exit;
    end;
    IRIRR.Text := FormatFloat('0.00', Rate * 100.0);
  except
    On EConvertError do
      IRIRR.Text := '';
  end;
end;

procedure TForm1.FutureValueChange(Sender: TObject);
begin
  try
    var PresentValue := StrToFloat(PvFutureValue.Text);
    var InterestRate := StrToFloat(IrFutureValue.Text) / 100.0;
    var NumPeriods := StrToInt(NpFutureValue.Text);
    var FutureValue := CalculateFV(PresentValue,InterestRate, NumPeriods);
    if IsNan(FutureValue) then
      FvFutureValue.Text := ''
    else
      FvFutureValue.Text := FormatFloat('0.00', FutureValue);
  except
    On EConvertError do
      FvFutureValue.Text := '';
  end;
end;

We declare the functions and use them in the OnChange handlers of the textboxes. When you build and run the program, you will see something like this:

That’s because the dll is not where it should be, in the same folder of the executable. For that, you must take some steps:

  • Build the dll before the executable. If you don’t do that, the executable will be built and will use an outdated dll
  • Copy the dll after building the executable

For the first step, you need to go to the Projects window, select the dll, right click and select the “Build Sooner”  option. That will move the dll up in the project list and will make it to be built before the executable.

For the second step, you need to add a post-build step for the executable and copy the dll to the output dir. For that, you need to select the Project Options and go to Build Events:

There, in the Post-build events, you should add a command to copy the dll to the executable output dir:

One thing must be noted here: you must build the dll and the executable for the same platform. If you build the dll for x64, it won’t run on a x86 executable. Once you’ve done the two steps, you can build all projects and run the executable, it will run the same way as before. Now, we’ve refactored all the business rules into a dll and we can reuse it in other languages. To show that, we will create a WPF project in C# that will use this dll.

Creating a WPF project that uses the DLL

Go to Visual Studio and create a new WPF project, and name it FinCalcWPF. Then go to solution explorer and add the dll file to the project. When adding the dll, select Add as Link, to avoid to make a physical copy of the dll in the source directory. This way, you are just adding a link to the dll and when it’s rebuilt, the new version will be used. In the properties window, select Build Action to None and Copy to Output Directory to Copy if newer.

One thing must be noted here: the dll is for Win32, so the executable should also be for Win32. When you build the WPF app with the default setting (Any CPU), you can’t be sure it if will run as a Win32 process:

  • For a Win32 operating system, it will run as a Win32 process, so that’s ok
  • For a Win64 operating system, it may run as a Win32 or Win64 process, depending on your settings. If you go to Project/Options/Build and check the “Prefer 32-bit”, it will run as a Win32 process, else it will run as a Win64 process

So, if you don’t want any surprises, just change the build from Any CPU to x86 and you will be sure that the program will run with the dll.

Then, in MainWindow.xaml, add this code:

<Window x:Class="FinCalcWpf.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"
        mc:Ignorable="d"
        Title="Financial Calulator WPF" Height="293.774" Width="419.623">
    <Grid>
        <TabControl>
            <TabItem Header="Present Value">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="2*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" 
                             Margin="5" Text="Future Value"/>
                    <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                            Text="Interest Rate"/>
                    <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                            Text="Num.Periods"/>
                    <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                            Text="Present Value"/>
                    <TextBox Grid.Row="0" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                            x:Name="PvFvBox" TextChanged="PvOnTextChanged"/>
                    <TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PvIrBox" TextChanged="PvOnTextChanged"/>
                    <TextBox Grid.Row="2" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PvNpBox" TextChanged="PvOnTextChanged"/>
                    <TextBox Grid.Row="3" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PvPvBox" IsReadOnly="True"/>
                </Grid>
            </TabItem>
            <TabItem Header="Future Value">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="2*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" 
                               Margin="5" Text="Present Value"/>
                    <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Interest Rate"/>
                    <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Num.Periods"/>
                    <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Future Value"/>
                    <TextBox Grid.Row="0" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="FvPvBox" TextChanged="FvOnTextChanged"/>
                    <TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="FvIrBox" TextChanged="FvOnTextChanged"/>
                    <TextBox Grid.Row="2" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="FvNpBox" TextChanged="FvOnTextChanged"/>
                    <TextBox Grid.Row="3" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="FvFvBox" IsReadOnly="True"/>
                </Grid>
            </TabItem>
            <TabItem Header="Payment">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="2*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" 
                               Margin="5" Text="Present Value"/>
                    <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Interest Rate"/>
                    <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Num.Periods"/>
                    <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Payment"/>
                    <TextBox Grid.Row="0" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PmtPvBox" TextChanged="PmtOnTextChanged"/>
                    <TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PmtIrBox" TextChanged="PmtOnTextChanged"/>
                    <TextBox Grid.Row="2" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PmtNpBox" TextChanged="PmtOnTextChanged"/>
                    <TextBox Grid.Row="3" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="PmtPmtBox" IsReadOnly="True"/>
                </Grid>
            </TabItem>
            <TabItem Header="Return Rate">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                        <RowDefinition Height="40"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="2*"/>
                    </Grid.ColumnDefinitions>
                    <TextBlock Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" 
                               Margin="5" Text="Present Value"/>
                    <TextBlock Grid.Row="1" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Payment"/>
                    <TextBlock Grid.Row="2" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Num.Periods"/>
                    <TextBlock Grid.Row="3" Grid.Column="0" VerticalAlignment="Center" Margin="5" 
                               Text="Return Rate"/>
                    <TextBox Grid.Row="0" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="RrPvBox" TextChanged="RrOnTextChanged"/>
                    <TextBox Grid.Row="1" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="RrPmtBox" TextChanged="RrOnTextChanged"/>
                    <TextBox Grid.Row="2" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="RrNpBox" TextChanged="RrOnTextChanged"/>
                    <TextBox Grid.Row="3" Grid.Column="1" Margin="5" VerticalContentAlignment="Center"
                             x:Name="RrRrBox" IsReadOnly="True"/>
                </Grid>
            </TabItem>
        </TabControl>
    </Grid>
</Window>

We are adding the four tabs with the boxes, the same way we’ve added in the Delphi app. The code behind for the window is:

using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;

namespace FinCalcWpf
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        [DllImport("FinCalcDll.dll")]
        private static extern double CalculatePV(double futureValue, double interestRate, int numPeriods);

        [DllImport("FinCalcDll.dll")]
        private static extern double CalculateFV(double presentValue, double interestRate, int numPeriods);

        [DllImport("FinCalcDll.dll")]
        private static extern double CalculatePmt(double presentValue, double interestRate, int numPeriods);

        [DllImport("FinCalcDll.dll")]
        private static extern double CalculateIRR(double presentValue, double payment, int numPeriods);

        public MainWindow()
        {
            InitializeComponent();
        }

        private void PvOnTextChanged(object sender, TextChangedEventArgs e)
        {
            if (double.TryParse(PvFvBox.Text, out double futureValue) &&
                double.TryParse(PvIrBox.Text, out double interestRate) &&
                int.TryParse(PvNpBox.Text, out int numPeriods))
              PvPvBox.Text = CalculatePV(futureValue, interestRate / 100.0, numPeriods).ToString("N2");
        }

        private void FvOnTextChanged(object sender, TextChangedEventArgs e)
        {
            if (double.TryParse(FvPvBox.Text, out double presentValue) &&
                double.TryParse(FvIrBox.Text, out double interestRate) &&
                int.TryParse(FvNpBox.Text, out int numPeriods))
                FvFvBox.Text = CalculateFV(presentValue, interestRate / 100.0, numPeriods).ToString("N2");
        }

        private void PmtOnTextChanged(object sender, TextChangedEventArgs e)
        {
            if (double.TryParse(PmtPvBox.Text, out double presentValue) &&
                double.TryParse(PmtIrBox.Text, out double interestRate) &&
                int.TryParse(PmtNpBox.Text, out int numPeriods))
                PmtPmtBox.Text = CalculatePmt(presentValue, interestRate / 100.0, numPeriods).ToString("N2");
        }

        private void RrOnTextChanged(object sender, TextChangedEventArgs e)
        {
            if (double.TryParse(RrPvBox.Text, out double presentValue) &&
                double.TryParse(RrPmtBox.Text, out double payment) &&
                int.TryParse(RrNpBox.Text, out int numPeriods))
                RrRrBox.Text = (CalculateIRR(presentValue, payment, numPeriods)*100.0).ToString("N2");
        }
    }
}

We’ve declared the functions in the dll and the we used them in the TextChanged event handlers. That will fill the result boxes in the tabs when you fill the input boxes. When you run the program, you will have the same result in both apps:

As you can see, refactoring the code into a dll brings many advantages: the code is not dependent on the UI, it is reusable and, best of all, it is testable. Creating unit tests for the code is a great way to be sure that everything works fine and, if you are making a change, you haven’t introduced a bug. Now, we’ll add the tests for the dll functions.

Adding tests to the dll

To add tests to the dll we must create a new test project to the group. Right click on the Project group and select “Add new project”. Then, select the DUnitX project, and set its settings:

When you click the OK button, Delphi will create a new test project with an unit with sample tests. You need to add the four calculator units to your new project and then, we can create the first test:

unit PVCalculatorTests;

interface
uses
  DUnitX.TestFramework, PVCalculator, Math;

type

  [TestFixture]
  TPvCalculatorTests = class(TObject)
  public
    [Test]
    [TestCase('FutureValue','-1,0,0')]
    [TestCase('Rate','0,-1,0')]
    [TestCase('Periods','0,0,-1')]
    procedure NegativeInputParametersReturnNan(const FutureValue : Double;
      const Rate : Double; const Periods : Integer);
  end;

implementation

procedure TPvCalculatorTests.NegativeInputParametersReturnNan(const FutureValue,
  Rate: Double; const Periods: Integer);
begin
  var result := CalculatePv(FutureValue,Rate,Periods);
  Assert.IsTrue(IsNan(result));
end;

initialization
  TDUnitX.RegisterTestFixture(TPvCalculatorTests);
end.

We named the unit PvCalculatorTests. Then we add the PVCalculator and Math units to the Uses clause. Then, we set the [TextFixture] attribute to the test class, to tell the test framework that this is a class that will have tests. Then we create a method and decorate it with the [Test] attribute. As this will be a parametrized test, we add the cases with the TestCase attribute.

The test is simple. We will run the test with the parameters (there will always be a negative parameter) and the result must always be NaN, thus pointing an invalid entry. If you run the project you will see something like this:

As you can see, the generated test project is a console app that runs the tests and shows the results. If you want a GUI app for the tests, you should install the TestInsight IDE plugin. As you can see from the image, all tests failed, because we have not checked the input parameters. We can change that in PVCalculator:

function CalculatePV(FutureValue, InterestRate: Double; NumPeriods : Integer):
  double;
begin
  if (FutureValue < 0) or (InterestRate < 0) or (NumPeriods < 0) then begin
    Result := NaN;
    exit;
  end;
  try
    Result := FutureValue / Power((1 + InterestRate), NumPeriods);
  except
    Result := NaN;
  end;
end;

Now, when you run the tests, all pass:

Now, we can create more tests for this calculator:

unit PVCalculatorTests;

interface
uses
  DUnitX.TestFramework, PVCalculator, Math;

type

  [TestFixture]
  TPvCalculatorTests = class(TObject)
  public
    [Test]
    [TestCase('FutureValue','-1,0,0')]
    [TestCase('Rate','0,-1,0')]
    [TestCase('Periods','0,0,-1')]
    procedure NegativeInputParametersReturnNan(const FutureValue : Double;
      const Rate : Double; const Periods : Integer);

    [Test]
    [TestCase('OnePeriod','100,1')]
    [TestCase('TenPeriods','100,10')]
    [TestCase('OneHundredPeriods','100,100')]
    procedure ZeroRatePresentValueEqualsFutureValue(const FutureValue : Double;
      const Periods : Integer);

    [Test]
    [TestCase('OnePeriodOnePercent','0.01,1')]
    [TestCase('OnePeriodTenPercent','0.10,1')]
    [TestCase('OnePeriodHundredPercent','1.00,1')]
    [TestCase('TenPeriodOnePercent','0.01,10')]
    [TestCase('TenPeriodTenPercent','0.10,10')]
    [TestCase('TenPeriodHundredPercent','1.00,10')]
    [TestCase('HundredPeriodOnePercent','0.01,100')]
    [TestCase('HundredPeriodTenPercent','0.10,100')]
    [TestCase('HundredPeriodHundredPercent','1.00,100')]
    procedure ZeroFutureValueEqualsZeroPresentValue(const Rate : Double;
      const Periods : Integer);
      
    [Test]
    [TestCase('OnePeriodOnePercent','100,0.01,1,99.01')]
    [TestCase('OnePeriodTenPercent','100,0.10,1,90.91')]
    [TestCase('OnePeriodHundredPercent','100,1.00,1,50')]
    [TestCase('TenPeriodOnePercent','100,0.01,10,90.53')]
    [TestCase('TenPeriodTenPercent','100,0.10,10,38.55')]
    [TestCase('TenPeriodHundredPercent','100,1.00,10,0.10')]
    [TestCase('HundredPeriodOnePercent','100,0.01,100,36.97')]
    [TestCase('HundredPeriodTenPercent','100,0.10,100,0.01')]
    [TestCase('HundredPeriodHundredPercent','100,1.00,100,0.00')]
    procedure VariablePeriodTests(const FutureValue : Double;
      const Rate : Double; const Periods : Integer; const Expected : Double);

  end;

implementation

procedure TPvCalculatorTests.NegativeInputParametersReturnNan(const FutureValue,
  Rate: Double; const Periods: Integer);
begin
  var result := CalculatePv(FutureValue,Rate,Periods);
  Assert.IsTrue(IsNan(result));
end;

procedure TPvCalculatorTests.VariablePeriodTests(const FutureValue, Rate: Double;
  const Periods: Integer; const Expected: Double);
begin
  var result := CalculatePv(FutureValue,Rate,Periods);
  Assert.AreEqual(Expected,Double(result));
end;

procedure TPvCalculatorTests.ZeroFutureValueEqualsZeroPresentValue(
  const Rate: Double; const Periods: Integer);
begin
  var result := CalculatePv(0,Rate,Periods);
  Assert.AreEqual(Double(0.0),Double(result));
end;

procedure TPvCalculatorTests.ZeroRatePresentValueEqualsFutureValue(
  const FutureValue Double; const Periods: Integer);
begin
  var result := CalculatePv(FutureValue,0,Periods);
  Assert.AreEqual(FutureValue,Double(result));
end;

initialization
  TDUnitX.RegisterTestFixture(TPvCalculatorTests);
end.

You should note one thing. When you run the tests, you will see that some of them fail:

This is not a failure in our code, but a failure in the test. As we are comparing double values, there are many decimals to compare and that’s not what you want. You can change your test to compare the difference to a maximum value. If the difference is greater than the maximum, the test fails:

procedure TPvCalculatorTests.VariablePeriodTests(const FutureValue, Rate: Double;
  const Periods: Integer; const Expected: Double);
begin
  var result := CalculatePv(FutureValue,Rate,Periods);
  Assert.AreEqual(Expected,Double(result), 0.01);
end;

Now, all tests pass. You can create tests for the other calculators the same way we did for this one. If you are using Delphi Rio and run the tests with debugging, you will see that some tests give a floating point error:

procedure TPmtCalculatorTests.ZeroPeriodsValueEqualsPresentValue(
  const PresentValue, Rate: Double);
begin
  var result := CalculatePmt(PresentValue,Rate, 0);
  Assert.AreEqual(Double(PresentValue),Double(result),0.01);
end;

procedure TPmtCalculatorTests.ZeroRatePmtEqualsPresentValueDivPeriods(
  const PresentValue: Double; const Periods: Integer);
begin
  var result := CalculatePmt(PresentValue,0,Periods);
  Assert.AreEqual(Double(PresentValue/Periods),Double(result),0.01);
end;

But the tests still pass. That’s because there is a bug in Delphi Rio (QC#RSP-19882), where comparisons with NaN return true, while they should return false. This can be solved by changing the tests to:

procedure TPmtCalculatorTests.ZeroPeriodsValueEqualsPresentValue(
  const PresentValue, Rate: Double);
begin
  var result := CalculatePmt(PresentValue,Rate, 0);
  Assert.IsFalse(IsNan(Result));
  Assert.AreEqual(Double(PresentValue),Double(result),0.01);
end;

procedure TPmtCalculatorTests.ZeroRatePmtEqualsPresentValueDivPeriods(
  const PresentValue: Double; const Periods: Integer);
begin
  var result := CalculatePmt(PresentValue,0,Periods);
  Assert.IsFalse(IsNan(Result));
  Assert.AreEqual(Double(PresentValue/Periods),Double(result),0.01);
end;

When you run the tests again, you will see that they will fail. We must change the calculator to solve this:

function CalculatePmt(PresentValue, InterestRate: Double;NumPeriods : Integer):
  Double;
begin
  if (PresentValue < 0) or (InterestRate < 0) or (NumPeriods < 0) then begin
    Result := NaN;
    exit;
  end;
  try
    if InterestRate = 0 then
      Result := PresentValue/NumPeriods
    else if NumPeriods = 0 then
      Result := PresentValue
    else
      Result := (PresentValue * InterestRate) * Power((1 + InterestRate),
        NumPeriods) / (Power((1 + InterestRate), NumPeriods) - 1);
  except
    Result := Nan;
  end;
end;

After this, our dll and its tests are ready to use and can be used in any language that supports Win32 dlls.

Conclusions

We’ve come a long way from the calculator code mixed with the UI to a new dll with unit tests. This architecture is more robust, reusable and easier to maintain. If we need to make changes to the dll, we are covered by unit tests, that can assure we are not introducing new bugs. And if some bug is found in the functions, it’s just a matter of writing a new test that fails, thus making sure of the bug, fix the code and rerun the test, making sure it’s passed. Using the Red-Refactor-Green procedure, we have a safety net for changing our code.

The full code for this article is at https://github.com/bsonnino/FinCalcDll

Until some time ago, creating an app to the Windows Store was only possible by creating an UWP (Universal Windows Platform) app and submitting it to the Windows Store. This was somewhat limiting because you had to know the technology and create a special new app using this platform.

With the introduction of the Desktop Bridge, Microsoft gave the developers the opportunity to submit their current Windows apps to the store, no matter which language they were programmed. And, besides that, you can also improve your app to use the new features and APIs introduced in Windows 10. That way, you can modernize your applications without having to learn a completely new technology.

Why add your app to the store?

There are a lot of advantages of adding your app to the store:

  • Discoverabilty – your app will be listed in a worldwide directory and can be discovered by people you never would have reached
  • Easy install and uninstall – installation and uninstalling Windows apps has always been a nightmare: you need admin rights, every installation program is different from the other, you need to create install scripts and, the worst, uninstalling the app always leave traces behind that will clutter your machine. With the store, install and uninstall are always a breeze
  • Security – apps downloaded from the store are guaranteed to be safe: no viruses or harm to your machine. The apps run in a sandbox and will not do anything harmful
  • Monetization – you don’t have to worry to sell your app. You can use several ways to monetize it: put a price to download it, in-app purchases, ads or even use a subscription-based app. You don’t have to setup a special platform to sell your app.

To add your app to the store you need to do special preparations and submit it there and wait for approval. It’s not as straightforward as posting an executable in the web, but the extra work is worth the benefits. The latest versions of Rad Studio make this procedure easier and Delphi Rio 10.3 bring a new component, TWindowsStore, that will ease the task of managing trial versions and in-app purchases.

This article will show how to package an app and send it to the Windows Store, so it can be installed and downloaded from there.

Packaging the app to the store

For this article, we will be using a financial calculator app, that has four tabs, to calculate payments for an installment payment, the rate used for an installment payment, the future value of an investment and the present value of an investment:

The user will fill the first three boxes of the page and the fourth will be calculated automatically. The first step is to add it to the store. We will not do anything right now, just add it there, so it can be installed and uninstalled easily. Even if you don’t want to add your app to the store, it’s interesting to do these steps, so you have an easy and safe way to distribute and install your app.

For this app, I’ve chosen to add a new feature, introduced in Delphi 10.3: inline variables and type inference. If you look at the calculation for the Present Value, you will see this:

procedure TForm1.CalculatePV;
begin
  try
    var FutureValue := StrToFloat(FvPresentValue.Text);
    var InterestRate := StrToFloat(IRPresentValue.Text)/100.0;
    var NumPeriods := StrToInt(NpPresentValue.Text);
    var PresentValue := FutureValue/Power((1+InterestRate),NumPeriods);
    PvPresentValue.Text := FormatFloat('0.00',PresentValue);
  except
    On EConvertError do
      PvPresentValue.Text := '';
  end;
end;

You will notice two things in this code:

  • There is no Variable definition section (var) in the method. All variables are defined inline, as they are needed
  • The variables have not an explicit type. Their type is inferred from the expression that follows it. That doesn’t mean that these variables have no type, like in Javascript or VB. It just means that the types are inferred by the compiler. FutureValue is an Extended variable, and if you try to assign it to something else (a string, for example), you will get a compiler error.

You may say that these two features can obfuscate your code and make it unreadable, but my experience with C#, that have had these features for a long time, is that it simplifies your code and make it easier to maintain. Anyway, it’s your call: if you want to keep using the old notation, it’s up to you. I really liked these ones.

But let’s go back to the subject of the article, creating a Windows Store app from this app. This is not new in Delphi 10.3, you can create a Windows Store app since Delphi Berlin. The first step is to install a Windows SDK, that can be downloaded and installed from https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk.

Once you have it installed, you must go to the SDK Manager, in Tools/Options/Deployment/SDK Manager and check if the SDK is setup and all the paths are ok:

 

The next step is to create a certificate. Your apps must be signed with this certificate to go to the store. You don’t need to have a certificate issued by a Certified Issuer, you can use a self-signed certificate. To create one, go to Provisioning in the options dialog and select the certificate you want to use:

The distribution type indicates how you will distribute your app: Ad Hoc will make an appx file that can distributed to your users and is used to install the app without going to the store. Store will create a package for the store. If you want to distribute the app Ad Hoc, you need to create a certificate, by clicking the “Create self-signed certificate” button.

Just select a file name and a password and it will be created. Then, you can use it for creating your apps for the store.

Once you have created the certificate, you can compile the app. For distribution without the store, you can select the Ad Hoc distribution, with the certificate you’ve just created. Build your app and Delphi will show a message box like this one:

You can go to the location where the appx file is located and double click on it. A dialog like this will be shown:

If you click in the Install button, the app will be installed and launched.

This is a Windows 10 app and it has the same features as any other Windows 10 app: you can uninstall it easily, pin it to the Start Menu, and so on. But, for this app, the only way to install it is to distribute the appx file and double click on it. We want more than that. We want to distribute it to the store.

Distributing the app in the store

To distribute the app in the store, you must create a submission for it. The first thing to do is to create a developer account in the Windows Dev Center. This account costs a one-time fee of $19.00 and the instructions to create it are at https://docs.microsoft.com/en-us/windows/uwp/publish/opening-a-developer-account.

When you have the account created, you can create your app submission. In the dashboard, click on the “Create New App” button. That will take you to the screen where you must reserve the name for your app. This name must be unique, and the availability of the name will be checked.

Once you click on the “Reserve product name” button, you will be directed to the screen to create the submission:

The next step is to create the submission, by clicking the “Start your submission” button:

You must walk through the options, setting the way you want to distribute the app:

  • Pricing and availability sets the markets, price and visibility for your app (you may make it public, or private). You can also set a free trial and sale price
  • Properties will set the category of your app and what kind of hardware is required/recommended
  • Age ratings will set what kind of app you are sending to the store and the age rating for you
  • Packages is where you send the package for the app. You must check the Windows Desktop box, as this is a Windows Desktop app and send the correct appx file.

You must build your app with the Distribution type to Store, set the Package Display Name to the one you reserved in the store and set the publisher display name to the one you created the account in the dev center:

Then you can drag the generated appx file to the package place in the store. The package will be validated and, if something is wrong, it will be pointed to you:

You must fix these things and rebuild the package:

Once everything is ok, you can save the package submission, and then set all the other options: the descriptions, screenshots and data for submission. Then you can check if everything is ok and submit to the store:

As the app needs full trust, the certification process will take some days. When it passes the certification, your app is available in the store and can be downloaded from there.

At this point, anyone with a Windows 10 machine will be able to download and install the app from the store.

In another article, I will show how to use the TWindowsStore component to manage trial versions and In-App purchases, so you can sell and get money from your app.

The source code for this article is at https://github.com/bsonnino/FinCalc

When I talk about UWP development and how it works for a large number of devices, from the tiny Raspberry Pi to the huge Surface Hub, and including the Hololens and Xbox, one question that often arises is: “is there any kind of responsive design in UWP” and my answer is “Yes, you can make a design that adapts to your device – both in the screen size and in the device unique features”. In this article, I will show how to create responsive design that adapts to your screen size.

This is not a new feature, Microsoft has some documents about the responsive design in UWP and this document shows you some techniques to use to adapt to the screen size. But first, let’s see how UWP will help you to create these designs.

Resizeable controls

Some controls are resized when the window is resized. You don’t need to do anything in this case, just design using these controls in your favor. For example, you can have some code like this:

<Page
    x:Class="_1___ResponsiveControls.MainPage"
    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"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

    <Grid>
        <Pivot>
            <PivotItem Header="Grid">
                <Grid Margin="5">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="2*"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Rectangle Fill="Red" Grid.Row="0" Grid.Column="0"/>
                    <Rectangle Fill="Green" Grid.Row="0" Grid.Column="1"/>
                    <Rectangle Fill="Yellow" Grid.Row="1" Grid.Column="0"/>
                    <Rectangle Fill="Navy" Grid.Row="1" Grid.Column="1"/>
                </Grid>
            </PivotItem>
            <PivotItem Header="VariableSizedWrapGrid">
                <VariableSizedWrapGrid Orientation="Horizontal" ItemHeight="150" ItemWidth="150" Margin="5">
                    <Rectangle Fill="Red"/>
                    <Rectangle Fill="Navy" VariableSizedWrapGrid.ColumnSpan="2"/>
                    <Rectangle Fill="Green" VariableSizedWrapGrid.RowSpan="2"/>
                    <Rectangle Fill="Yellow" VariableSizedWrapGrid.ColumnSpan="2"/>
                </VariableSizedWrapGrid>
            </PivotItem>
            <PivotItem Header="StackPanel H">
                <StackPanel Orientation="Horizontal" Margin="5">
                    <Rectangle Fill="Red" Width="100"/>
                    <Rectangle Fill="Navy" Width="100"/>
                    <Rectangle Fill="Green" Width="100"/>
                    <Rectangle Fill="Yellow" Width="100"/>
                </StackPanel>
            </PivotItem>
            <PivotItem Header="StackPanel V">
                <StackPanel Margin="5">
                    <Rectangle Fill="Red" Height="100"/>
                    <Rectangle Fill="Navy" Height="100"/>
                    <Rectangle Fill="Green" Height="100"/>
                    <Rectangle Fill="Yellow" Height="100"/>
                </StackPanel>
            </PivotItem>
            <PivotItem Header="RelativePanel">
                <RelativePanel Margin="5">
                    <Rectangle x:Name="RedRect" Fill="Red" Height="100" Width="100"/>
                    <Rectangle x:Name="BlueRect" Fill="Navy" Height="100" 
                               RelativePanel.RightOf="RedRect" 
                               RelativePanel.AlignRightWithPanel="True"/>
                    <Rectangle x:Name="GreenRect" Fill="Green" 
                               RelativePanel.Below="RedRect" 
                               RelativePanel.AlignLeftWithPanel="True" 
                               RelativePanel.AlignBottomWithPanel="True" 
                               RelativePanel.AlignRightWith="RedRect"/>
                    <Rectangle Fill="Yellow"
                               RelativePanel.Below="BlueRect" 
                               RelativePanel.AlignLeftWith="BlueRect" 
                               RelativePanel.AlignRightWithPanel="True"
                               RelativePanel.AlignBottomWithPanel="True"/>
                </RelativePanel>
            </PivotItem>
            <PivotItem Header="Canvas">
                <Canvas Margin="5">
                    <Rectangle Fill="Red" Height="100" Width="100"/>
                    <Rectangle Fill="Navy" Height="100" Width="100" Canvas.Left="50" Canvas.Top="50"/>
                    <Rectangle Fill="Green" Height="100" Width="100" Canvas.Left="100" Canvas.Top="100"/>
                    <Rectangle Fill="Yellow" Height="100" Width="100" Canvas.Left="150" Canvas.Top="150"/>
                </Canvas>
            </PivotItem>
        </Pivot>
    </Grid>
</Page>

If you run the program with this code, you will see different behaviors for each layout panel:

  • The Grid will resize the rectangles, according to its rows and columns (the first column will always be half the width of the second and the rows will have the same height
  • The VariableSizedWrapGrid will not resize its controls, but will reposition them when the window is resized
  • The StackPanel Horizontal will align its controls horizontally and will resize in the vertical
  • The StackPanel Vertical will align its controls vertically and will resize in the horizontally
  • The RelativePanel will leave the controls aligned relatively one to the other
  • The Canvas won’t resize anything

With this knowledge, you can start tweaking your design and making sure that it doesn’t interfere with your screen size. There is nothing worse than this design for your app:

<Canvas>
    <TextBlock Text="Id" Canvas.Left="10" Canvas.Top="10"/>
    <TextBlock Text="Name" Canvas.Left="10" Canvas.Top="50"/>
    <TextBlock Text="Address" Canvas.Left="10" Canvas.Top="90"/>
    <TextBlock Text="City" Canvas.Left="10" Canvas.Top="130"/>
    <TextBlock Text="Email" Canvas.Left="10" Canvas.Top="170"/>
    <TextBlock Text="Phone" Canvas.Left="10" Canvas.Top="210"/>
    <TextBox Width="300" Canvas.Left="100" Canvas.Top="5"/>
    <TextBox Width="300" Canvas.Left="100" Canvas.Top="45"/>
    <TextBox Width="300" Canvas.Left="100" Canvas.Top="85"/>
    <TextBox Width="300" Canvas.Left="100" Canvas.Top="125"/>
    <TextBox Width="300" Canvas.Left="100" Canvas.Top="165"/>
    <TextBox Width="300" Canvas.Left="100" Canvas.Top="205"/>
    <Button Content="Submit" Width="65" Height="35" 
            Canvas.Top="350" Canvas.Left="350"/>
</Canvas>

As you can see, I’ve used a Canvas to create the layout. This may be the easiest way to put your controls on the screen, but when you resize the window, the controls are not repositioned nor resized, so you can have an almost empty screen or a clipped screen. That won’t get high rates for your app.

So, the first lesson here is: don’t interfere with the layout, leave it flow with the screen size. Use the right control for the layout you want. With this simple lesson you will solve most of your design issues, you won’t have clipped or hidden controls or lots of white space in your screen.

Responsive design

Sometimes, just using the right control isn’t enough. You need more than that to handle the way the device is used: for example, a smartphone is used with one hand while you are on the go, the tablet is used on your lap or with two hands, the desktop has a large display, mouse and keyboard and the Surface Hub can be used collaboratively with more than one person. And you must make use of the screen size – there’s no sense to show the same amount of information in a 80″ screen than in a 5″ screen – you must reposition and hide/show some information, depending on the screen size.

For that, UWP has the AdaptiveTrigger that will allow you to create different designs for the different screens. You have to use this in the VisualStateManager, setting the properties of the components for every screen size you want. The first step is define the “snap points”, where the window will change. From this page, we will choose three breakpoints:

  • Small – less than 640 pixels
  • Medium – between 641 and 1007 pixels
  • Large – More than 1008 pixels

We will be designing a screen with three panes of information, in the Large size: a list with all the available items, a text pane with textual information and a photo of the item. When the screen is in the medium size, the photo will be on top of the textual data and in the small size, only the list will be shown. The following images show that:

The first step is to create the full UI, with some code like this one:

<Page
    x:Class="_2___ResponsiveLayout.MainPage"
    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"
    mc:Ignorable="d"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Page.Resources>
        <Style TargetType="ListViewItem">
            <Setter Property="FontFamily" Value="Comic Sans MS"/>
            <Setter Property="FontSize" Value="14"/>
        </Style>
    </Page.Resources>
    
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="640"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <ListView Grid.Column="0" Margin="5">
            <ListViewItem>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</ListViewItem>
            <ListViewItem>Vivamus faucibus erat at urna fermentum, vitae rutrum metus condimentum.</ListViewItem>
            <ListViewItem>Etiam scelerisque enim eu quam maximus malesuada.</ListViewItem>
            <ListViewItem>Donec et eros sit amet risus scelerisque gravida.</ListViewItem>
            <ListViewItem>Duis lacinia orci sit amet justo sagittis, luctus lacinia quam consectetur.</ListViewItem>
            <ListViewItem>Ut suscipit sem at ultrices venenatis.</ListViewItem>
            <ListViewItem>Maecenas a massa ac sem pulvinar hendrerit.</ListViewItem>
            <ListViewItem>Duis at ligula cursus, euismod massa quis, pretium velit.</ListViewItem>
            <ListViewItem>Vivamus ut nulla sed magna suscipit scelerisque.</ListViewItem>
            <ListViewItem>Proin varius enim quis quam placerat, id tincidunt mauris dignissim.</ListViewItem>
            <ListViewItem>Cras pellentesque dui quis justo suscipit mollis.</ListViewItem>
            <ListViewItem>Cras eget lorem ut velit accumsan efficitur.</ListViewItem>
            <ListViewItem>Sed eget erat bibendum nisl pulvinar facilisis.</ListViewItem>
            <ListViewItem>Mauris feugiat nisi eget enim volutpat, vel bibendum ipsum maximus.</ListViewItem>
            <ListViewItem>Vestibulum sed nisi accumsan, malesuada sapien et, volutpat justo.</ListViewItem>
            <ListViewItem>Mauris volutpat lectus nec velit fermentum egestas.</ListViewItem>
            <ListViewItem>Maecenas accumsan justo sed ultrices lobortis.</ListViewItem>
            <ListViewItem>Cras lacinia mi quis nibh efficitur, ut sodales felis elementum.</ListViewItem>
            <ListViewItem>Donec sit amet turpis in ligula dignissim pulvinar.</ListViewItem>
            <ListViewItem>Nullam vel quam et tortor pellentesque fringilla vel eget justo.</ListViewItem>
            <ListViewItem>Donec mattis leo sit amet diam iaculis convallis ac a nisl.</ListViewItem>
        </ListView>
        <RelativePanel Grid.Column="1" x:Name="DetailsPane" Margin="5">
            <Image Source="Picasso.jpg" x:Name="Img" Width="250" Height="250" Margin="30" 
                   RelativePanel.AlignVerticalCenterWithPanel="True"
                   RelativePanel.RightOf="Details"/>
            <Grid Margin="5,5,5,15" x:Name="Details" 
                  RelativePanel.AlignLeftWithPanel="True" MaxWidth="450">
                <Grid.RowDefinitions>
                    <RowDefinition Height="60"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <TextBlock Text="Details" FontSize="20" HorizontalAlignment="Center" 
                           Grid.Row="0" VerticalAlignment="Center" Foreground="Red"/>
                <ScrollViewer Grid.Row="1">
                    <TextBlock  FontFamily="Comic Sans MS" TextWrapping="Wrap" xml:space="preserve">
                Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus nisl tellus, vulputate mollis risus nec, placerat fringilla erat. Maecenas ullamcorper malesuada mauris sit amet placerat. Sed elit lorem, sagittis nec scelerisque sed, pharetra ac diam. Mauris et dui quis risus euismod tincidunt. Etiam vel molestie risus. Vivamus sodales pellentesque nibh, id efficitur purus tristique et. Nullam commodo, tellus at ullamcorper tempor, purus felis volutpat enim, vitae placerat lacus nulla a nulla. Praesent eget eros elit. Integer pulvinar sed magna et cursus. Nam tincidunt nibh lacus, in feugiat nisi rutrum sit amet. Morbi eget magna a nibh viverra condimentum eget nec arcu. 
                Praesent luctus ante in sem malesuada egestas. Mauris posuere nunc sit amet augue posuere, eget pulvinar orci sodales. In facilisis metus lectus. Fusce ac eros ac tellus imperdiet pellentesque. Sed aliquet ornare sodales. Nullam euismod lacus sem, sit amet viverra odio porta nec. Nulla facilisi. Proin vestibulum, ante eget lacinia dignissim, nibh turpis consequat dolor, eget hendrerit orci ipsum eget arcu. Pellentesque sagittis ut dolor quis suscipit. Phasellus purus tellus, efficitur ut mattis maximus, mollis vel lorem. Vivamus ultrices luctus velit, vel dignissim eros iaculis vel. Maecenas non sodales quam. Ut eget dolor in nulla sagittis tempor ut sit amet purus. Aliquam id nulla a eros elementum pulvinar et sit amet justo. 
                Nam nec auctor nisl. Suspendisse dignissim sodales risus, sit amet gravida lorem posuere ut. Sed a finibus lectus. Sed tristique erat et dictum tincidunt. Nunc nisi diam, dapibus sed risus sit amet, volutpat commodo mi. Maecenas mauris nisl, commodo at elementum a, euismod id metus. Suspendisse imperdiet, orci nec sollicitudin ultrices, neque dolor facilisis diam, aliquet efficitur nulla ex ut velit. In fringilla malesuada enim eu placerat. Integer lacinia dolor eu dui venenatis, at mollis urna vestibulum. Morbi ornare erat cursus orci hendrerit consequat. Fusce eu tellus sollicitudin, vehicula justo id, feugiat velit. Etiam nec felis ac libero mattis tincidunt eget eu urna. Duis dapibus auctor risus. Suspendisse id nunc lorem. Vestibulum quis libero ligula. 
                Nam a enim vitae odio pellentesque posuere. Suspendisse sed fermentum mi, ornare posuere est. Etiam dignissim lacinia neque, vitae luctus tellus accumsan id. Suspendisse aliquet gravida erat, eget dapibus purus rhoncus quis. Nullam id lectus a ante bibendum molestie. Vestibulum a nunc pellentesque, tincidunt risus sit amet, tincidunt velit. Fusce sit amet ultricies mi. Proin ornare vehicula lobortis. Pellentesque velit ligula, tempus id mollis blandit, rutrum vel sapien. Suspendisse ut bibendum felis. Aliquam vel magna enim. Cras diam sem, facilisis auctor vulputate at, laoreet non tortor. 
                In fringilla vulputate lacus. Praesent posuere leo nibh, non feugiat dui rutrum vel. Aliquam id ligula viverra, blandit sem sit amet, efficitur nulla. Suspendisse hendrerit pretium massa, semper egestas massa mattis in. Suspendisse potenti. Vivamus ut odio vestibulum, venenatis ex vel, cursus lorem. Vestibulum sapien lorem, vestibulum at accumsan et, ornare et est. Pellentesque condimentum vulputate quam et imperdiet.
                Aenean nec magna nec nisl accumsan fermentum. Quisque vel sodales sapien. Nunc laoreet erat lorem, eu facilisis ipsum euismod eget. Nullam quam eros, ultrices ut sapien vel, laoreet lacinia sem. Duis sodales lacinia odio, ut commodo odio vulputate sodales. Nunc mollis feugiat ipsum, in auctor dolor ultricies et. Sed vitae fermentum augue, eu ullamcorper quam.
                    </TextBlock>
                </ScrollViewer>
            </Grid>
        </RelativePanel>
    </Grid>
</Page>

In this code, I’ve just created a mockup to show the concepts. Once you’ve got them, you can add your real data in the screen. If you run the code, you will see something like the first image. Now, we should create our layouts for the other screen sizes. To do that, you must add the VisualStateGroup that will change the layout for the screen sizes. We could use the recommended steps for snap points, but in this case, I’ve opted to do differently: we have three columns, the list (640 pixels with margin=5 – 650 pixels), the text (450 pixels with margin=5 – 460 pixels) and the image (250 pixels with margin=30 – 310 pixels). Then, we can do this: when the screen is large enough to show all data, we will show the default layout. When the screen starts to cover the image (1300 pixels), we will put the image above the text. When the screen size is to small to show the data (900 pixels), we will hide the details and only show the list.

To get this, we must add a VisualStateGroup like this:

        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState>
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="1300"/>
                    </VisualState.StateTriggers>
                </VisualState>

                <VisualState>
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="900"/>
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="DetailsPane.Visibility" Value="Visible"/>
                        <Setter Target="Details.(RelativePanel.Below)" Value="Img"/>
                        <Setter Target="Img.(RelativePanel.RightOf)" Value=""/>
                        <Setter Target="Img.Margin" Value="100,30,30,0"/>
                        <Setter Target="Img.(RelativePanel.AlignVerticalCenterWithPanel)" Value="False"/>
                    </VisualState.Setters>
                </VisualState>

                <VisualState>
                    <VisualState.StateTriggers >
                        <AdaptiveTrigger MinWindowWidth="0"/>
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="DetailsPane.Visibility" Value="Collapsed"/>
                    </VisualState.Setters>
                </VisualState>

            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

As you can see, we are setting three visual states:

  • The first will be active when the window has more than 1300 pixels and will show the default layout
  • When the window has less than 1300 pixels and more than 900 pixels, the details will be below the image, and the image right margin will be set to 100 pixels
  • When the window has less than 900 pixels, the details pane will be hidden, and just the list will be shown

With this visual state, we have different layouts for the many screen sizes. If you run the program and resize the window, you can see the position of the controls changing accordingly. As you can see it’s very easy to get a responsive design with an UWP application. But this isn’t the only way to adapt to a device: you can even create new pages and load them depending on the device where the app is being used. In App.xaml.cs, we have this code in the OnLaunched override:

if (rootFrame.Content == null)
{
    // When the navigation stack isn't restored navigate to the first page,
    // configuring the new page by passing required information as a navigation
    // parameter
    rootFrame.Navigate(typeof(MainPage), e.Arguments);
}

We can use the AnalyticsInfo.VersionInfo.DeviceForm property to detect the device that is being used and open a different page for each device:

switch (AnalyticsInfo.VersionInfo.DeviceFamily)
{
    case "Windows.Mobile":
        rootFrame.Navigate(typeof(MainPageMobile), e.Arguments);
        break;
    case "Windows.Xbox":
        rootFrame.Navigate(typeof(MainPageXBox), e.Arguments);
        break;
    case "Windows.Holographic":
        rootFrame.Navigate(typeof(MainPageHololens), e.Arguments);
        break;
    case "Windows.Team":
        rootFrame.Navigate(typeof(MainPageSurfaceHub), e.Arguments);
        break;

    case "Windows.IoT":
        rootFrame.Navigate(typeof(MainPageRaspberry), e.Arguments);
        break;
    case "Windows.Desktop":
        rootFrame.Navigate(typeof(MainPageDesktop), e.Arguments);
        break;
}

As you can see, there are many ways to make the layout of your program to adapt to the used device. Although Windows 10 allows you to use the same program in a wide range of devices, that doesn’t mean that you need  to keep a single layout, you can adapt to the device and even make use of the unique characteristics of each one.

The source code for this article is at https://github.com/bsonnino/ResponsiveLayout