Introduction: Display Tiled Content with GridView

Windows 8 Start screen introduced a new approach to organize a list of items or application navigation area. Microsoft’s GridView control is a great way to display such tiled content. The GridView can display variable-sized tiles and group them for you, or it can display non-grouped items of the same size with support for drag and drop. Unfortunately, you can’t have everything enabled by default. For instance, you don’t get drag and drop for all items panels. Certain items' panels are necessary if you want a mixture of different-sized items (i.e., VariableSizedWrapGrid). Also, drag and drop is not supported when grouping is enabled. This is a second look at implementation of the GridViewEx for UWP control, which removes these limitations. The first attempt was for WinRT platform and provided support for drag-and-drop functionality with enabled grouping and variable-sized items. You can find previous article here: Extending GridView with Drag and Drop for Grouping and Variable Sized Items. This article describes upgrading GridViewEx control and sample application from WinRT to Universal Windows Platform (UWP). The most of drag-and-drop functionality remained the same, so I only describe platform-specific changes and new additions. If you're interested in more details about drag-and-drop, please read the previous article and look at attached code. extended GridView with grouping, variable sized items and drag and drop support

Upgrading Existent WinRT application to Universal Windows Platform (UWP)

Creating UWP Application

There are a number of blog posts and msdn articles about migrating existent projects. (For example, this one.) In many cases it would be much easier to start from the new empty UWP project. After that you can decide which parts of the old application you want to use. Our old sample uses LayoutAwarePage and SuspensionManager classes, which were included into Windows Store project templates. If you have several projects built on these templates, you'll probably want to make minimal changes and re-use the rest of code. So, let’s add some old common classes to the new UWP application:

  • Common/VisibilityConverter.cs
  • Common/LayoutAwarePage.cs
  • Common/SuspensionManager.cs

Also, let’s re-use the same data and samples, so add all files from DataModel and Samples folders.

Changes in Layout and Navigation

VisibilityConverter and SuspensionsManager classes don’t need any changes to be used in UWP. Let’s look at what is changed in layout and navigation logic. With new form factors and wide range of supported devices Microsoft deprecated ApplicationViewState handling. UWP platform supplies other means such as AdaptiveTriggers to build adaptive layout. So we can safely remove all the code about handling ApplicationViewStates. Without it LayoutAwarePage name is not perfect for remaining functionality, but we can leave it as is for the sake of compatibility. Both in WinRT and UWP Microsoft recommends using the back button in apps that use a hierarchical navigation pattern. In desktop WinRT application you have to add this button to your own xaml. In UWP you can enable title bar back button in desktop mode and use it in the same way as hardware Back button on mobile or tablet devices. You can find UWP back button guidelines here. UWP approach looks very generic and doesn’t require any custom xaml, so related code is a good candidate to include it into base class for different xaml pages. Let’s do it in LayoutAwarePage class:


protected override void OnNavigatedTo(NavigationEventArgs e)  
{  
    // subscribe on Back button event  
    if (IsWindowsPhoneDevice())  
    {  
        // use hardware button  
        Windows.Phone.UI.Input.HardwareButtons.BackPressed += HardwareButtons_BackPressed;  
    }  
    else  
    {  
        // enable/disable window back button depending on navigation state  
        var currentView = SystemNavigationManager.GetForCurrentView();  
        currentView.AppViewBackButtonVisibility = this.Frame != null && this.Frame.CanGoBack ?  
        AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;  
        currentView.BackRequested += backButton_Tapped;  
    }  
    ...  
    protected override void OnNavigatedFrom(NavigationEventArgs e)  
    {  
        // unsubscribe from Back button event  
        if (IsWindowsPhoneDevice())  
     {  
      Windows.Phone.UI.Input.HardwareButtons.BackPressed -= HardwareButtons_BackPressed;  
     }  
     else  
     {  
            // unsubscribe from window back button  
            var currentView = SystemNavigationManager.GetForCurrentView();  
            currentView.BackRequested -= backButton_Tapped;  
     }  
        ...  
        // handle Back button events  
        private void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e)  
        {  
         if (this.Frame != null && this.Frame.CanGoBack)  
        {  
            e.Handled = true;  
            this.Frame.GoBack();  
        }  
  }  
    private void backButton_Tapped(object sender, BackRequestedEventArgs e)  
    {  
        this.GoBack(this, new RoutedEventArgs());  
    }  

Handling device Back button is only included into Windows Mobile Extensions for the UWP, so you need to add this reference to the project. Now all navigation is handled by LayoutAwarePage and derived pages should not worry about it. One last small addition to this class is a static method to detect device type at runtime:


public static bool IsWindowsPhoneDevice()  
{  
    if (Windows.Foundation.Metadata.ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))  
    {  
     return true;  
    }  
    return false;  
}  

1. If you want your application look to conform to other Windows 10 apps, use Windows 10 ThemeResources. 2. Microsoft made a number of changes in GridView control comparing with Windows 8 version https://msdn.microsoft.com/en-us/library/windows/apps/mt188204.aspx#gridview. The main of them is changing default orientation from horizontal to vertical. It requires changes in the control template and default property settings. 3. I haven’t found any notes about this, but apparently the behavior of VariableSizedWrapGrid panel also changed. In all samples, you should set its Orientation to Horizontal, or it will always show all items as a single column. 4. The other critical change I’ve found in VariableSizedWrapGrid (or maybe in Microsoft’s GridView implementation) is that it doesn’t set column and row spans according to the item size automatically, as it did in Windows 8. Now it is application responsibility. Here is xaml which worked in Windows 8 and doesn’t work in Windows 10:













The best solution I’ve found is to set VariableSizedWrapGrid attached properties on items and propagate them to ListViewItemPresenter elements in a custom control derived from the GridView:


///   
    /// This class sets VariableSizedWrapGrid.ColumnSpanProperty for GridViewItem controls,  
    /// so that every item can have different size in the VariableSizedWrapGrid.  
    /// Also it sets VerticalContentAlignment and HorizontalContentAlignment to Stretch.  
///   
public class GridViewTiled : GridView  
{  
// set ColumnSpan according to the business logic (maybe some GridViewSamples.Samples.Item or group properties)  
protected override void PrepareContainerForItemOverride(Windows.UI.Xaml.DependencyObject element, object item)  
    {  
     element.SetValue(ContentControl.HorizontalContentAlignmentProperty, HorizontalAlignment.Stretch);  
      element.SetValue(ContentControl.VerticalContentAlignmentProperty, VerticalAlignment.Stretch);  
        UIElement el = item as UIElement;  
        if (el != null)  
        {  
            int colSpan = Windows.UI.Xaml.Controls.VariableSizedWrapGrid.GetColumnSpan(el);  
            int rowSpan = Windows.UI.Xaml.Controls.VariableSizedWrapGrid.GetRowSpan(el);  
            if (rowSpan > 1)  
            {  
                // only set it if it has non-defaul value  
                element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.RowSpanProperty, rowSpan);  
            }  
            if (colSpan > 1)  
            {  
             // only set it if it has non-defaul value  
             element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, colSpan);  
         }  
     }  
    base.PrepareContainerForItemOverride(element, item);  
    }  
}  

Here is xaml working in UWP (Unbound page from the attached GridView demo uses it):













The NewGroupPlaceholder Control

WinRT version of the GridViewEx control uses simple border as a placeholder for new groups. It doesn’t allow altering its appearance during drag and drop operations. To make it more user-friendly and highlight drop area when drag over, let’s add the NewGroupPlaceholder control with very simple logic of switching visual states during dragging over: GridViewEx - NewGroupPlaceholder The code is very simple; you can find it in attached sample. Default control template uses system accent color to highlight drop area:



























Changes in the GridViewEx Control

Now let’s look what we should change in the GridViewEx control to make it working under UWP. The most of GridViewEx control code works in UWP exactly in the same way as in WinRT. The only critical thing is a necessity to set DragEventArgs.AcceptedOperation property in the OnDragOver override. Apparently, default GridView implementation in UWP resets this property to None for non-supported items panels. So, if you don’t set it again in the OnDragOver override, Drop event will never happen. Fortunately, it is only one line of code:


protected override void OnDragOver(DragEventArgs e)  
  {  
    int newIndex = GetDragOverIndex(e);  
    if (newIndex >= 0)  
  {  
e.AcceptedOperation = Windows.ApplicationModel.DataTransfer.DataPackageOperation.Move;  

If you build the application now, you'l see a number of compiler warnings about obsolete ItemContainerGenerator methods. It's easy to fix—just use the corresponding methods of the ItemsControl class instead. To work around VariableSizedWrapGrid limitations, add the same PrepareContainerForItemOverride method as described above. And the last thing to do is to update default GridViewEx control style with new vertical orientation and NewGroupPlaceholder controls. You can find it in generic.xaml file in attached samples.

Make it More Handy

Working on samples for this article I found one more extension point in standard GridView control. If you need to customize UI elements for individual items, in many cases you should inherit from the GridView and override PrepareContainerForItemOverride method. To avoid it, I added new PreparingContainerForItem event to the GridViewEx control. Event arguments for this event contain both data object and UI container for it, so you can adjust UI element properties as you need, for example:


///   
/// Set column spans depending on group id.  
///   
///   
///   
private void gve_PreparingContainerForItem(object sender, GridViewEx.PreparingContainerForItemEventArgs e)  
{  
    try  
    {  
        Item it = e.Item as Item;  
        if (it != null)  
        {  
            e.Element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, it.GroupId % 2 + 1);  
        }  
    }  
    catch  
    {  
        e.Element.SetValue(Windows.UI.Xaml.Controls.VariableSizedWrapGrid.ColumnSpanProperty, 1);  
    }  
}  

And the final point is to pack GridViewEx control and related reusable classes (NewGroupPlaceholder and VisibilityConverter) as a custom control assembly.

Device-specific UI

There are some different ways to make adaptive layout. In this sample, the main thing I wanted to do is changing item size for mobile version. Unfortunately, I haven’t found a way to use adaptive triggers to change VariableSizedWrapGrid ItemHeight and ItemWidth properties used in the GridView’s ItemsPanelTemplate. It's possible to include two GridView instances with different settings and alter their visibility depending on screen size; you can try to do that. Personally, I think that using device-specific xaml pages is more correct as it allows application to download device-specific xaml only and so to save some resources. And in this case you are free to make more changes to layout than you could with adaptive triggers. So, let’s create Samples\DeviceFamily-Mobile folder in the project and add xaml for Bound, Unbound and Grouped samples with slightly different appearance. Grouped sample shows single GridView control, so mobile page can alter tile size and some paddings and that’s it. Bound and Unbound samples show two GridView controls side-by-side. On a small screen, it looks too crowded and doesn’t show all details well enough. Let’s use Pivot control to only show one GridView control at a time and allow navigation between 2 controls. You can find source xaml in attached sample. Note that code-behind file is the same for desktop and mobile pages, and you don’t need to do anything special to run one or another xaml; the environment will do this for you. For Customized sample, let’s try a different approach with adding dependency property for tile size:


public double TileSize  
  {  
    get { return (double)GetValue(TileSizeProperty); }  
    set { SetValue(TileSizeProperty, value); }  
  }  
public static readonly DependencyProperty TileSizeProperty =  
  DependencyProperty.Register(nameof(TileSize), typeof(double), typeof(Customized), new PropertyMetadata(100));  
public Customized()  
  {  
    if (IsWindowsPhoneDevice())  
      {  
        TileSize = 72;  
      }  
    this.InitializeComponent();  
  }  

The GridView or GridViewEx control parts can bind to this property from xaml:




        ItemWidth="{Binding TileSize, ElementName=pageRoot}"  
    Orientation="Horizontal" MaximumRowsOrColumns="10"/>  



Known Issues

1. Mobile Emulators installed with Windows SDK 10.0.10240.0 have a problem with touch interaction. Attached sample works well if you use mouse (regardless of Emulator input settings), but it’s extremely hard to drag something with real touch interaction. The problem is the same with default GridView settings, so apparently it's something in the Microsoft’s GridView implementation or emulator issue. It may be the same issue as described here: https://github.com/Microsoft/Windows-universal-samples/issues/131. 2. If you set GridViewEx to collection of value type objects, you will get System.ExecutionEngineException at extracting data from DragEventArgs. If you really need value types, wrap them into some reference type. For example, the WinRT Bound sample uses collection of integers:


ObservableCollection coll = new ObservableCollection();  
for (int i = 1; i <= 31; i++)  
{  
coll.Add(i);  
}  

Use items in DataTemplate:










      item  




In UWP version we can replace it by anonymous type:


ObservableCollection coll = new ObservableCollection();  
for (int i = 1; i <= 31; i++)  
{  
coll.Add(new { Id = i }); // add anonymous type object  
}  
Updated xaml:
        item  

Summary
-------

The custom GridViewEx control enables us to combine several useful features of the GridView control. Taking advantage of more features allows us to deliver user experiences that are becoming the new norm when it comes to Universal Windows Platform app development. Attached GridView demo described above was initially made for research purposes and looks not real. The second sample with live tiles shows images in rotating tiles of different size. To run it you will need to download our UWP Edition. Thanks a lot to [Flickr](https://www.flickr.com) for not asking for API keys and making it possible: ![live tiles](//gccontent.blob.core.windows.net/gccontent/blogs/legacy/c1/2015/10/liveTiles.png "extended GridView with grouping, variable sized items and drag and drop support")

*   [Download GridViewEx control assembly - 10.6 KB](//gccontent.blob.core.windows.net/gccontent/blogs/legacy/c1/2015/10/GridViewEx.zip)
*   [Download GridView demo - 62 KB](//gccontent.blob.core.windows.net/gccontent/blogs/legacy/c1/2015/10/GridViewDemo.zip)
*   [Download sample with live tiles - 40.5 KB](//gccontent.blob.core.windows.net/gccontent/blogs/legacy/c1/2015/10/GridViewLiveTiles.zip)

### [Learn more about C1Studio UWP Edition](http://www.componentone.com/Studio/Platform/UWP)