1

I am making a UWP app which is supposed to be on xbox for now and maybe in future ill release it on pc and other platforms. I know that on PC and for mobile we can enable this feature with following 2 properties on the GridView or ListView.

CanReorderItems=True
CanDrop=True

But according to Microsoft Docs, drag and drop feature is not available or supported on xbox.

So what are any other options to achieve this reorder feature on xbox GridView?

UPDATE 1

So here is my backend code for the gridview. selection mode is single but I am not using selectionchanged event because that just creates lot of confusion and for now just assume that we always need to swap the items I will set the boolean later once the swapping in working perfectly.

private void SamplePickerGridView_ChoosingItemContainer(Windows.UI.Xaml.Controls.ListViewBase sender, ChoosingItemContainerEventArgs args)
    {
        if (args.ItemContainer != null)
        {
            return;
        }
        GridViewItem container = (GridViewItem)args.ItemContainer ?? new GridViewItem();
        //should be xbox actually after pc testing
        if (DeviceTypeHelper.GetDeviceFormFactorType() == DeviceFormFactorType.Desktop)
        {
            container.GotFocus += Container_GotFocus;
            container.LostFocus += Container_LostFocus;
            //container.KeyDown += Container_KeyDown;
        }
        args.ItemContainer = container;
    }
    private TVShow GotItem, LostItem;
    private void Container_LostFocus(object sender, RoutedEventArgs e)
    {

        LostItem = OnNowAllGridView.ItemFromContainer(e.OriginalSource as GridViewItem) as TVShow;
        GotItem = null;

    }

    private void Container_GotFocus(object sender, RoutedEventArgs e)
    {

        GotItem = OnNowAllGridView.ItemFromContainer(e.OriginalSource as GridViewItem) as TVShow;
        if (GotItem != null && LostItem != null)
        {
            var focusedItem = GotItem;
            var lostitem = LostItem;
            var index1 = ViewModel.Source.IndexOf(focusedItem);
            var index2 = ViewModel.Source.IndexOf(lostitem);
            ViewModel.Source.Move(index1, index2);
        }
        LostItem = null;

    }

u can try the code with adaptivegridview or just normal gridview of uwp if it works with that it should work with adaptivegridview as well.

Current Bheaviour items are swaped but the focus remains at same index.

Expected the focus should also move along with the item.

Muhammad Touseef
  • 4,357
  • 4
  • 31
  • 75

1 Answers1

1

Your finding is true, drag and drop is not supported on Xbox out of the box (although when mouse support comes to Xbox in the future, I guess it will work).

So if you need this functionality, you will have to implement it manually from the start. One option would be to add a button, that will display on Xbox only and will read like Reorder Grid.

When this "reorder" mode were enabled, you have several solutions available.

The easiest solution for you would be to set the SelectionMode to Single and when a item is selected, you would bring it to fromt of the underlying collection.

collection.Remove( selectedItem );
collection.Insert( 0, selectedItem );

This bring to front solution was implemented on the Xbox One dashboard for reordering tiles.

Second option would be to set the SelectionMode to Multiple, where user would first select one item and then a second one. After that you could move the first selected item before the second selected:

collection.Remove( firstSelectedItem );
var targetIndex = collection.IndexOf( secondSelectedItem );
collection.Insert( targetIndex, firstSelectedItem );

The last solution is the most complex. With SelectionMode = Single you would select a single item and then observe the direction in which the user focus moves and move the tile "in real time". This is the most user friendly, but hardest to implement reliably.

Just as an outline of the third solution - you could capture the GotFocus event if you implement a custom template of the GridView:

<GridView.ItemsPanel>
    <ItemsPanelTemplate>
        <ItemsWrapGrid Orientation="Horizontal" 
                       GotFocus="GridViewItem_GotFocus"/>
    </ItemsPanelTemplate>
</GridView.ItemsPanel>

Now within this GotFocus handler you could retrieve the item that has currently focus from the EventArgs.OriginalSource. This way you could know which item got the focus and you could swap it with the item the user selected.

Update - hacky solution

I have come up with a hacky approach that solves the GotFocus/LostFocus mess.

The problem with GotFocus is that when we move the item in collection, the focus gets confused. But what if we didn't physically move the items at all?

Suppose your item type is TVShow. Let's create a wrapper around this type:

public class TVShowContainer : INotifyPropertyChanged
{
    private TVShow _tvShow;

    public TVShow TvShow
    {
        get => _tvShow;
        set
        {
            _tvShow = value; 
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Now change the collection item type to this new "wrapper" type. Of course, you also have to update your GridView DataTemplate to have the right references. Instead of "{Binding Property}" you will now need to use "{Binding TvShow.Property}", or you can set the DataContext="{Binding TvShow}" attribute to the root element inside the DataTemplate.

But you may now see where I am going with this. Currently you are using Move method to move the items in the collection. Let's replace this with a swap:

var item1 = focusedItem.TvShow;
focusedItem.TvShow = LostItem.TvShow;
LostItem.TvShow = item1;

This is a big difference, because we no longer change the collection itself, but just move the references to items that are wrapped in a "static" container. And thanks to bindings the items will properly display where they should.

This is still a hacky solution, because it requires you to wrap your items just for the sake of the reordering, but it at least works. I am however still interested in finding a better way to do this.

Martin Zikmund
  • 38,440
  • 7
  • 70
  • 91
  • thankyou for the answer, I was also thinking to do something like the third option, I know it can be complex but how can we do it? any ideas? – Muhammad Touseef Feb 06 '18 at 10:04
  • I have updated my answer with an idea how to implement this – Martin Zikmund Feb 06 '18 at 10:17
  • why should I use GotFocus? I mean wont the got focus and selected element be the same?bcz when selection changes an item is selected and also gets the focus right? – Muhammad Touseef Feb 06 '18 at 10:50
  • what if I use only the selectionchanged event on the grdivew and then swap the removeditem with selected item from the args? will that work? – Muhammad Touseef Feb 06 '18 at 10:53
  • or maybe I can use lost focus along with got focus, and then swap these 2 elements? what do you suggest? @Martin Zikmund – Muhammad Touseef Feb 06 '18 at 10:54
  • The problem is that you need to distinguish between the case when the user is navigating to the element he wants to move and the case when he is moving. So the selection of the item to be moved is handled my "Selection" and then the moving itself by GotFocus. Of course it is a bit tricky, because you have to make sure you don't handle the focus for the element that is selected and so on... – Martin Zikmund Feb 06 '18 at 11:13
  • I noticed you removed the resolved status, are you encountering problems? – Martin Zikmund Feb 09 '18 at 21:41
  • I have tried multiple ways to achieve this but every way is causing certain types of problems. I cannot use the 3rd way u provided bcz I am actually using AdaptiveGridView by uwp community toolkit which doesnt have a setter for "ItemsPanel" but I am using focus event for each gridviewItem through ChoosingItemContainer event – Muhammad Touseef Feb 09 '18 at 21:49
  • I was able to achieve it to some extent by using the Collection.Move(index,index) method, but the Focus remains at its original place, but I actually want the focus to move along with the item to its next destination, so that user can actually keep moving the object until they want to drop it in their desired location and then drop it there, I can easily toggle when to start it and when to end it with gamepadX using a bool variable in code behind – Muhammad Touseef Feb 09 '18 at 21:54
  • Could you use `SetFocus` method call to force the focus to move along? – Martin Zikmund Feb 09 '18 at 21:55
  • yeah I tried that as well, it creates some more problems, bcz I was using lost focus and got focus to find out the 2 elements to swap. so it creates some sort of nested looping of got focus and lost focus events – Muhammad Touseef Feb 09 '18 at 21:57
  • bcz apparently after swapping or moving the elements, they automatically call the gotfocus events again, bcz they are remade in the xaml I guess? so they are loaded again and get focus – Muhammad Touseef Feb 09 '18 at 21:58
  • You could create a boolean flag, that would indicate that the lost amd got focus events should be ignored until the GotFocus is called for the desired new focus "location" – Martin Zikmund Feb 09 '18 at 22:00
  • can you please make a small sample for this? Because I have actually tried everything which came to my mind and my project currently just have lot of messy code, I tried to create another boolean as well, but then only 1 time swaping was occuring bcz somehwre i need to change the boolean again – Muhammad Touseef Feb 09 '18 at 22:04
  • I am afraid I won't get to my Xbox anytime soon. Could update the question with the code you have now? I will try to apply my idea to it – Martin Zikmund Feb 09 '18 at 22:05
  • i dnt own a xbox either, I am testing the app on my laptop with a xbox controller attached to it, but the focus movement also works with normal keyboard arrows, so u can test that way as well, I will try to make a bit cleaner code and then update my question with it – Muhammad Touseef Feb 09 '18 at 22:09
  • Great idea! Let me know when the question is updated and I will try it out – Martin Zikmund Feb 09 '18 at 22:10
  • please check the updated question thanks @Martin Zikmund – Muhammad Touseef Feb 09 '18 at 22:22
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/164862/discussion-between-touseef-and-martin-zikmund). – Muhammad Touseef Feb 09 '18 at 22:39
  • I just had a hacky idea how to solve this. I will now update my answer with this idea. – Martin Zikmund Feb 10 '18 at 22:25
  • have you personally tested this method on ur pc as well? – Muhammad Touseef Feb 10 '18 at 23:01
  • btw I am already using an Observable Collection so do I need to add this Inotify property changed stuff again? – Muhammad Touseef Feb 11 '18 at 10:53
  • ObservableCollection notifies the binding about changes in the collection items themselves,but in this case they don't change at all, their *content* changes. That's why the container must notify about property changes too – Martin Zikmund Feb 11 '18 at 10:55
  • ok so when I add items to the collection I was adding new TVShow() and assigning all properties of the TVShow to it there in my viewmodel, now in this case, I should add a new TVShowContainer object to my collection and set its TVShow property to a new TVShow object right? – Muhammad Touseef Feb 11 '18 at 11:01
  • Yes, you essentially will have a collection of containers containing the TV shows themselves – Martin Zikmund Feb 11 '18 at 11:03
  • and what do u think about the nullifying of the objects? which I am doing? lostitem and gotitem I am nullifying both of them at end of the opposite events for both of them as u can see in my code. I am doing that becauseif the focus leaves the gridview, or just comes from something else onto the gridview on any item then it shouldnt try to do that swap, even if the boolean value is true, is there a better way to do this? or this way is fine? – Muhammad Touseef Feb 11 '18 at 11:11
  • I was using lostitem and gotitem as GridViewItemsand taking them as TVShow items, so now I should take them as TVShowContainer items and then swap their tvshow objects within them. but I was wondering if I shouldjust nullify whole gotitem or should I nullify the gotitem.TVShow object ? – Muhammad Touseef Feb 11 '18 at 11:18
  • No, nullification should not be necessary, but you should nullify the container reference I think – Martin Zikmund Feb 11 '18 at 14:06
  • yeah by container reference u mean the objects I am nullifying already in my code? which were tvshow before but now they are tvshowContainers, lostitem and gotitem – Muhammad Touseef Feb 11 '18 at 18:04