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