UWP Reorder Gridviewitems on Xbox - xaml

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.

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.

Related

How can I toggle the checkbox of an item in a CheckedListbox if CheckOnClick is True, but SelectionMode is None..?

How can I toggle the checkbox of an item in a CheckedListbox if CheckOnClick is True, but SelectionMode is None..?
If I set SelectionMode to One it works as expected, but I would like to prevent items in the list from being selected. The only purpose of the CheckedListbox is to use the checkboxes; having items selected is not desired.
I tried a bit of code with the various Click and Mouse events, but none of them seem to report the item in the list that was clicked. If I could determine that, it would be a simple matter to toggle the checkbox of the clicked item.
The MouseClick event will tell you when the control was clicked and where. Determine whether the click was on an item and which one, then toggle it:
Private Sub CheckedListBox1_MouseClick(sender As Object, e As MouseEventArgs) Handles CheckedListBox1.MouseClick
Dim clickedIndex = CheckedListBox1.IndexFromPoint(e.X, e.Y)
If clickedIndex <> ListBox.NoMatches Then
CheckedListBox1.SetItemChecked(clickedIndex, Not CheckedListBox1.GetItemChecked(clickedIndex))
End If
End Sub
After a little testing, I would say that you need to consider “how” you want to achieve the non-selection mode you describe. As you have noted, if you set the CheckedListBoxes SelectionMode to None… then basically the check boxes become unusable.
The user cannot check or un-check any check box, as you already know… and this is why you want “your” code to change the check boxes checked state. So, you have now inherited the job of changing the check boxes check state because you set its “SelectionMode” to “None" … ? …
In addition, when the selection mode is set to “None” … then many “typical” properties of the CheckedListBox will lose functionality and become useless. Example, the checked list boxes SelectedItem property will always be null and its SelectedIndex property will most likely always be -1. Meaning, most “selection” type events will need to find what item was clicked by using the mouse location as shown in another answer.
The main point here is that when you decided to set the checked list boxes selection mode to “None”, then you basically open the door for more coding/responsibility on your part as far as “managing” the checked list box. I am just saying that the out-of-the-box .Net CheckedListBox is not feature rich and is a basic control. I am guessing there “may” be a third-party CheckedListBox Control that may have this “non-selected” functionality built-in.
So… I suggest another approach… however… it also has some drawbacks… basically you have to create a new Class MyCheckedListBox type “Control” that inherits from the CheckedListBox and then override its draw method to paint the cell the way we want.
I tend to avoid creating new controls. However, this will allow us to “keep” the CheckedListBoxes “selection functionality” by keeping its SelectionMode set to One. In addition to removing the job of “your” code having to manage each check box’s check state… we can also use all the checked list boxes “selection” events and use them as we typically would using the list boxes “selection” properties.
Below is a crude example of how to override the CheckedListBox’s Draw method to keep the “selected” items back color to the same color of the non-selected items.
class CheckedListBox_NoSelect : CheckedListBox {
protected override void OnDrawItem(DrawItemEventArgs e) {
DrawItemEventArgs new_e_Args = new DrawItemEventArgs
(e.Graphics,
e.Font,
new Rectangle(e.Bounds.Location, e.Bounds.Size),
e.Index,
(e.State & DrawItemState.Focus) == DrawItemState.Focus ? DrawItemState.Focus : DrawItemState.None,
this.ForeColor,
this.BackColor);
base.OnDrawItem(new_e_Args);
}
}
The code above is a simplified version of this SO question… How change the color of SelectedItem in CheckedListBox in WindowsForms? …
As I started, you will have to decide which approach you need. I may tend to go the route of #user18387401‘s answer … simply to avoid creating a new User Control. However, if you want this functionality for all the CheckedListBoxes, then creating the control may be a better approach.
Below is a full example of what is described above.
The CheckedListBox on the left is a regular CheckedListBox and uses the approach from user18387401‘s answer. The CheckedListBox on the right is our new control class CheckedListBox_NoSelect above.
For each control, the SelectedIndexChanged event is wired up to demonstrate that the checked list box on the left with its SelectionMode set to None will always have its SelectedItem set to null and its SelectedIndex will always be set to -1. However, it is not difficult to figure out “which” item was selected using user18387401‘s approach. This index is also displayed in its SelectedIndexChanged event.
private void Form1_Load(object sender, EventArgs e) {
checkedListBox1.SelectionMode = SelectionMode.None;
checkedListBox1.Items.Add("Item 1");
checkedListBox1.Items.Add("Item 2");
checkedListBox1.Items.Add("Item 3");
checkedListBox1.Items.Add("Item 4");
checkedListBox1.Items.Add("Item 5");
checkedListBox1.CheckOnClick = true;
// Leave default selection mode to "One"
checkedListBox_NoSelect1.Items.Add("Item 1");
checkedListBox_NoSelect1.Items.Add("Item 2");
checkedListBox_NoSelect1.Items.Add("Item 3");
checkedListBox_NoSelect1.Items.Add("Item 4");
checkedListBox_NoSelect1.Items.Add("Item 5");
checkedListBox_NoSelect1.CheckOnClick = true;
}
private void checkedListBox1_MouseClick(object sender, MouseEventArgs e) {
int clickedIndex = checkedListBox1.IndexFromPoint(e.X, e.Y);
if (clickedIndex != -1) {
checkedListBox1.SetItemChecked(clickedIndex, !checkedListBox1.GetItemChecked(clickedIndex));
Debug.WriteLine("LEFT: MouseClick Selected Index: " + clickedIndex);
}
}
private void checkedListBox1_SelectedIndexChanged(object sender, EventArgs e) {
Debug.WriteLine(" LEFT -> Item: " + (checkedListBox1.SelectedItem == null ? "Null" : checkedListBox1.SelectedItem));
Debug.WriteLine(" LEFT -> Index: " + checkedListBox1.SelectedIndex);
}
private void checkedListBox_NoSelect1_SelectedIndexChanged(object sender, EventArgs e) {
Debug.WriteLine("RIGHT -> Item: " + (checkedListBox_NoSelect1.SelectedItem == null ? "Null" : checkedListBox_NoSelect1.SelectedItem));
Debug.WriteLine("RIGHT -> Index: " + checkedListBox_NoSelect1.SelectedIndex);
}
I hope this makes sense and helps. Sorry that I did this in C#. If you can not convert the code to a VB version, then let me know and I will add a VB version.

How to override the default response to a touch for a control in UWP?

Is it possible to override the response to a touch interaction for a control in UWP? For example, for a DataGrid XAML control, the default behavior when a row is tapped is to select this row and deselect all other selected rows. Can this be changed to have the tapped row added to the selection as if the control key was pressed?
EDIT: My solution is for Surface Pro in tablet mode so the user would only interact with the app via touch. So I wanted him to be able to select multiple items using touch only. In the image I added, the default behavior if the user clicks on "Chicken Sandwich" is to deselect "Burger" and select "Chicken Sandwich" unless the CTRL key is held down. However using the CTRL key on Surface device without mouse and keyboard would mean that we will need to have the on-screen keyboard on display which would be a bit cumbersome. I would like instead to change the default behavior where if the user clicks on an unselected item it's added to the selection, and if he clicks on a selected item the item it gets removed from selection (in the example below, "Chicken Sandwich" will be added to the selection on first touch and removed from selection on second tap), so basically same functionality as holding the CTRL key down but without using it.
Based on the document, the DataGrid class provides the behavior that selecting multiple items while holding down the SHIFT or CTRL keys during selection. What you need to do is just to set the SelectionMode property as Extended.
Update:
Please check the following code as a sample:
List<object> selectedItems = new List<object>();
bool flag = false;
private void dataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(flag==false)
{
var item = dataGrid.SelectedItem;
if (selectedItems.Contains(item))
{
selectedItems.Remove(item);
}
else
{
selectedItems.Add(item);
}
flag = true;
dataGrid.SelectedItems.Clear();
foreach(var cur in selectedItems)
{
flag = true;
dataGrid.SelectedItems.Add(cur);
}
}
else
{
flag = false;
}
}

Mutually exclusive selection of two listviews

I've implemented a UWP SplitView similar to the one made by Diederik Krols. I prefer the approach of using a ListView over using RadioButtons as shown by Jerry Nixon's implementation of the SplitView.
However, I have a problem when I add secondary commands at the bottom of the SplitView, which Diederik doesn't do. These secondary commands are implemented by another ListView bound to a collection of Commands. So I have TWO ListViews that should only allow ONE item to be selected among them at a time.
I've tried two things:
I've bound the SelectedItem property of both ListViews to the same object. The idea was that maybe ListView doesn't display a selection if SelectedItem is not in the list bound to ItemsSource. Sadly, it simply goes on displaying the last selected item.
I've bound each ListView's SelectedItem to its own property. When one of the ListViews' item is selected, the SelectedItem of the other property is set to 'null' by the ViewModel. This produces the same result as in 1.
Any ideas on how to solve this problem?
I had the same problem.
I have a fix, but I'm not that proud of it ;) it's a dirty hack and I'm hoping other solutions will present itself so I can change it too.
But here it is:
First the listviews hook up to the SelectionChanged event even though we also bind the selected item to the viewmodel ( full code shown here https://github.com/AppCreativity/Kliva/blob/master/src/Kliva/Controls/SidePaneControl.xaml )
<ListView x:Name="TopMenu"
SelectionChanged="OnTopMenuSelectionChanged"
Background="Transparent"
ItemsSource="{x:Bind ViewModel.TopMenuItems}"
ItemTemplateSelector="{StaticResource MenuItemTemplateSelector}"
ItemContainerStyle="{StaticResource MenuItemContainerStyle}"
SelectedItem="{x:Bind ViewModel.SelectedTopMenuItem, Mode=TwoWay, Converter={StaticResource XBindItemCastingConverter}}"
Grid.Row="0" />
In that SelectionChanged, we'll deselect the 'other' listviews selection! ( full code shown here https://github.com/AppCreativity/Kliva/blob/master/src/Kliva/Controls/SidePaneControl.xaml.cs )
Note that we need to keep track that we are already in a deselecting process otherwise we'll end up with an endless loop. This is done with the _listViewChanging field.
private void OnBottomMenuSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!_listViewChanging)
{
_listViewChanging = true;
TopMenu.SelectedIndex = -1;
_listViewChanging = false;
}
}
Last thing to do is making sure we handle the selection and clear it again in the viewmodel for next itteration ( full code shown here https://github.com/AppCreativity/Kliva/blob/master/src/Kliva/ViewModels/SidePaneViewModel.cs )
public MenuItem SelectedBottomMenuItem
{
get { return _selectedBottomMenuItem; }
set
{
if (Set(() => SelectedBottomMenuItem, ref _selectedBottomMenuItem, value))
{
if (value != null)
{
if (string.IsNullOrEmpty(SelectedBottomMenuItem.Title))
HamburgerCommand.Execute(null);
if (SelectedBottomMenuItem.Title.Equals("settings", StringComparison.OrdinalIgnoreCase))
SettingsCommand.Execute(null);
SelectedBottomMenuItem = null;
}
}
}
}

FlipView SelectionChanged event occurs only when touch manipulations are complete

From the docs:
Note When a user flips through FlipView content using touch
interaction, a SelectionChanged event occurs only when touch
manipulations are complete. This means that when a user flips through
content quickly, individual SelectionChanged events are not always
generated for every item because the manipulation is still occurring.
Is there a way to configure the FlipView control to fire SelectionChanged for each flip? This behavior makes implementing paging interesting as the user, if flipping fast enough, can flip to the end of the list before more items can be added.
One solution to the problem is to extend the FlipView and monitor its ScrollViewer. Here is a quick sample of what I'm suggesting. Seems to work on horizontal flip view (haven't handled any other cases, and haven't tested too much).
public class FixedFlipView : FlipView {
public ScrollViewer ScrollViewer {
get;
private set;
}
protected override void OnApplyTemplate() {
base.OnApplyTemplate();
this.ScrollViewer = (ScrollViewer)this.GetTemplateChild("ScrollingHost");
this.ScrollViewer.ViewChanged += ScrollViewer_ViewChanged;
}
void ScrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e) {
var index = (int)this.ScrollViewer.HorizontalOffset - 2;
if (this.SelectedIndex != index) {
this.SelectedIndex = index;
}
}
}
Some things to note:
You may want to get the ScrollViewer in a different way that does not depend on its name. Like using the method in my answer here. Although, I'd guess this is fine, too.
It may be a better idea to use a separate event for this. In the code above I set the SelectedIndex property, which raises the SelectionChanged event, but it is also very likely to be doing other stuff as well, so it may be a problem in some cases.

How to disable right-button selection on List View Item (ListViewItem)?

I want to prevent the ability to deselect a list view item if it is already selected. Therefore how do I prevent right mouse click ability to deselect an item?
I have prevented the ability to deselect via swiping by using IsSwipeEnabled="False" on the List View. I didn't require swipe ability on the list view.
I'm happy to completely prevent right mouse click on the list view items if needed.
If I am reading your question correctly, it sounds like you want the ability for the user to select items, but to not be able to de-select. If that is the case, it seems like a strange requirement - it goes against normal UI convention and does something that the user is not expecting.
Having said that, you can do so by handling the SelectionChanged event in the ListView.
When the event is triggered, it gives you a list of removed (de-selected) items. You then just need to add those items back into the ListView's selected items list:
private void itemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.RemovedItems)
{
itemListView.SelectedItems.Add(item);
}
}
Note that if you use the above code, you do not have to handle any swipe or mouse events.
Edit - Per OP's comment, the requirement is slightly different than what I thought:
I want the selected item to deselect if a different item is selected. however what I dont want is an already selected item to be (manually) deselected
Assuming that you have a single select ListView, you can still use the SelectionChanged event and the SelectionChangedEventArgs to do what you are asking for:
private void itemListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.RemovedItems.Count > 0 && e.AddedItems.Count == 0)
{
var removed = e.RemovedItems[0];
itemListView.SelectedItem = removed;
}
}
I have found a simple solution on the following forum.
You simply add a RightTapped event to the ListView DataTemplate content.
<ListView>
<ListView.ItemTemplate>
<DataTemplate>
<ContentPresenter RightTapped="daves_RightTapped" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
And then in the code behind:
private void daves_RightTapped(object sender, Windows.UI.Xaml.Input.RightTappedRoutedEventArgs e)
{
e.Handled = true;
}
This works fine on an outlook style ListView.