Friday, January 9, 2015

Expandable/Collapsable ListView items in WinRT Xaml

This post contains a lot of references to specific behaviors from SnooStream but the general principle involved is the same now matter where you're doing it. You can see the all of the source here https://github.com/hippiehunter/Baconography/tree/appx or you can see the specific xaml in question for the email conversation like view here https://github.com/hippiehunter/Baconography/blob/appx/SnooStream/SnooStream.Shared/View/Controls/SelfActivityView.xaml.

Expander controls can be used to show a preview of a control that when clicked on expands out to show a potentially large number of more specific items. A particular location where this is used is the built in email client. A conversation with multiple items will show as a single item in the list until you click on the preview item, expanding out to show all of the items in the conversation. Unlike Windows Phone 8.0 there aren't any readily available controls that can be directly used as an Expander. Even if there was one available, my experience in the past with the ExpanderControl from the WindowsPhone toolkit is that it doesn't do well when placed inside a ListView. The performance is quite bad because changing the size of a visible ListItem causes a pretty complete reevaluation of the layout for all of the visible ListItems. Quite frequently even if you can live with the performance issues, on collapse of the Expander, you will just see a big blank space leftover. Now thatwe know what ListView doesnt like us doing, what can we do differently to achieve the same effect? Well, virtualized controls like ListView love to add and remove items, and they even have a user controllable add/delete animation. So the solution we're using in SnooStream is to remove items from the list rather than collapse them, and add new items into the list rather than expand from one ListItem. So far it has delivered all of the behaviors we're looking for without any performance issues. You can see an example of this in action in two places, the SelfActivityView and the CommentsView. SelfActivityView uses it in very much the same style as the built in email client as that is the user interaction we're trying to mimic. CommentsView is considerably more complicated because it starts out fully expanded and has the potential for very deep nesting. It also has to deal with items being added on the fly when users “LoadMore” or load context from a comment set that was only partially loaded.

Really the most important idea to take away for use in other source bases, is your ViewModel needs to understand how to add and remove the children from the ObservableCollection that is bound to your ListView, separately to its own internal representation of the items that it wants to present. So When a user expands on of the group header items, the ViewModel can add all of the groups child items into the actual bound ObservableCollection, and remove them again when the user no longer wants that group header to be expanded. The meat of this flattening can be seen here https://github.com/hippiehunter/Baconography/blob/appx/SnooStreamCore/ViewModel/SelfStreamViewModel.cs.

Thursday, January 1, 2015

Working with WinRT DateTime in C++

DateTime in managed languages has a very comprehensive set of methods that make working with it very easy, C++/CX not so much. Some answers on stackoverflow suggest using ATL and all kinds of other very complicated solutions. Here is my entry into this fight, if I can convert DateTime -> std::chrono::seconds and std::chrono::seconds -> DateTime then I can use the entirely reasonable set of functions that surrounds std::chrono.

DateTime toFileTime(std::chrono::seconds sec)
{
    long long unixTime = sec.count();
    DateTime result;
    result.UniversalTime = unixTime * 10000000ULL + 116444736000000000LL;
    return result;
}

std::chrono::seconds toDuration(DateTime dt)
{
    return std::chrono::seconds((dt.UniversalTime - 116444736000000000LL) / 10000000ULL);
}

Now this is assuming that seconds is a duration from Unix epoch, you can easily get the current distance from epoch using time(nullptr) or std::chrono::system_clock::now().time_since_epoch().count()