Closing secondary windows when main window is closed in UWP

In UWP it is not possible to show multiple windows for a single app. Depending on the usage, the user might expect secondary windows to close once the main window closes.

There are two problems to solve to make this happen:

  1. Figure out when the main window has been closed
  2. Close all secondary windows once the main window is closed

Both turned out to be harder than expected.

For the first issue, one might expect Window.Current.Closed or  Window.Current.CoreWindow.Closed to be signalled when the window is closed. This is not the case when a secondary window is open. After trying several other events, the ApplicationView.Consolidated event was the only one that I had success with. It would appear that when multiple windows are opened, the main window is not actually closed, but just hidden.

For the second issue, I likewise tried calling several Close methods to get the second window to close. I initially stayed away from Application.Current.Exit because the documentation says it should not be called, but the MSDN article for multiple views in UWP actually recommends invoking it to close down the main window and thereby all secondary windows as well.

So the final solution ended up being:

ApplicationView.GetForCurrentView().Consolidated += (ss, ee) =>
     (ss, ee) => {Application.Current.Exit();};

 

 

Using GridView/ListView’s Built-in Animations

The GridView and ListView controls in Windows 8 both come with built-in animation for various operations. When the controls are bound to an ObservableCollection the will do a fancy animation whenever an item is

  • Added to the collection (even if it’s added in the middle of the collection, in which case the other elements will slide away to give room for the new element)
  • Removed from the collection

I mention the two animations/operations above specifically because I rarely see developers taking advantage of this built-in functionality. Most of the time, when applications fetch new data, they simply clear the previous data, and insert the new data, even though there might be elements that were both present in the new and the old data set. This is commonly seen in news/feed application that retrieve new posts — an obvious use for the built animations.

To see an example of these animations in action see my Open Nearby app (You might have to add a location in Denmark, if you are not from here). There is an option to filter the shops in the app bar. When this filter is applied the add/remove operations are used to filter away shops that no longer fit in the result set, and likewise it is filled up with new shops that does.

It can be a hassle to synchronize the old dataset for the GridView/ListView with freshly fetched data. Below is a few extension methods that will make it easier to get going:

public static void SyncCollection<T>(this ObservableCollection<T> observableCollection, IEnumerable<T> dataToSync, Func<T, T, bool> insertBefore, Func<T, T, bool> equality)
{
    var recentlyToRemove = observableCollection.Where(s => dataToSync.All(ss => !equality(ss,s))).ToList();
    var recentlyToAdd = dataToSync.Where(s => observableCollection.All(ss => !equality(ss, s)));
    foreach (var item in recentlyToRemove)
    {
        observableCollection.Remove(item);
    }
 
    foreach (var item in recentlyToAdd)
    {
        InsertInOrder(item, observableCollection, insertBefore);
    }
}
private static void InsertInOrder<T>(T item, ObservableCollection<T> observableCollection, Func<T, T, bool> insertBefore)
{
    for (int i = 0; i < observableCollection.Count; i++)
    {
        if (insertBefore(item, observableCollection[i]))
        {
            observableCollection.Insert(i, item);
            return;
        }
    }
    // if empty or last
    observableCollection.Add(item);
}

 

Here’s an example of usage from Open Nearby:

var newShops = ...// newly fetched shops from data service;

ObservableCollection<Shop> previousShops = ..// the old data current being shown in the view;

Qua.WS.ObservableCollectionExtensions.SyncCollection(previousShops, newShops,
(newShop, oldShop) => newShop.Distance.DistanceInKilometers < oldShop.Distance.DistanceInKilometers,
(shop1, shop2) => shop1.Id == shop2.Id);

Remember ScrollViewer Position

Often in Windows 8 apps the user will be presented with long horizontal groups of content. After having navigated to these items, and then returning to the original page there is no built in functionality to remember where in the list that the user scrolled to.

Creating this feature is relatively quick:

  1. Whenever the user leaves the page, remember the current scroll position.
  2. Whenever the user navigates back to the page, retrieve the previous scroll position
  3. When all your content is loaded, scroll to the previous position
// Step 1
private double? horizontalOffsetState;

protected override void SaveState(Dictionary<string, object> pageState)
{
base.SaveState(pageState);

var myScrollViewer = … // your scroll viewer
var offset = myScrollViewer.HorizontalOffset;

pageState["horizontalOffset"] = offset;
}

// Step 2
protected override void LoadState(object navigationParameter,Dictionary<string, object> pageState)
{
base.LoadState(navigationParameter, pageState);

if (pageState != null)
{
horizontalOffsetState = (double) pageState["horizontalOffset"];
}
}

// step 3 - put in constructor or loadstate/navigatedTo
this.Loaded += (sender, args) =>
{
var myScrollViewer = … // your scroll viewer
if (horizontalOffsetState.HasValue)
{
myScrollViewer.ScrollToHorizontalOffset(horizontalOffsetState.Value);
}

};