WPF: Reset ListView selected index after items refreshed - wpf

I have an ListView defined in XAML, and it's ItemsSource is set code-behind. ItemsSource is not a property, so I dont want bind it to observable collection.
To update GUI I call ListView.Items.Refresh() method after selected index was changed (I do some work on selection changed and list view items are display the result).
After that two situations may occurs:
if I change selected item of ListView by mouse, selected index is changed right and stay in its place after Refresh() method was called;
if I change selected item by arrows up and down on keyboard, selected index is always jumps to first item.
My question is what may I do to make selected item index of ListView right after selected item was changed by keyboard and items were refreshed in code?

Instead of SelectionChanged event why dont you try MouseLeftButtonDown event and KeyDown event.
This will solve your issue.
Snippet as follow,
private void lst_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
item = lst.SelectedItem;
fnTask();
}
private void lst_KeyDown(object sender, KeyEventArgs e)
{
item = lst.SelectedItem;
fnTask();
}
private void fnTask()
{
lst.Items.Refresh();
lst.SelectedItem = item;
}

Related

WPF SelectedItem binding to source doesn't affect keyboard navigation

OK, so I'm using a typical Binding to my ViewModel. It works beautifully, to source or to target, or so it seems. The vm collection is an ObservableCollection which is initialized and never modified (no setter).
public ObservableCollection<Statement> StatementsList { get; } = new();
#region SelectedStatement
private Statement _selectedStatement;
public Statement SelectedStatement
{
get => _selectedStatement;
set => Set(ref _selectedStatement, value, nameof(SelectedStatement));
}
#endregion SelectedStatement
I can set SelectedStatement from the ViewModel, and the UI updates fine. I can watch the SelectionChanged event of the DataGrid and confirm the added items and removed items are exactly as expected.
Then, I select a different row USING THE MOUSE, and use my search function to select another row using SelectedItem = some statement, which visually selects the row perfectly (again), confirmed by the SelectionChanged event again. SelectedStatement in my view model has the correct value!
Then, the weirdness starts. I press the down arrow the keyboard.
You'd expect the next line after the selected statement to be selected, but instead the next line after the previously selected item (using the mouse) is selected. It's like the keyboard responding code in the DataGrid is not recognizing the prior new row selection via the VM.
Has anyone seen this behavior? I've done WPF development for many years, and I've seen many weird WPF bugs, but this one I've never noticed!
Note that IsSynchronizedWithCurrentItem="True" on the DataGrid. I tried setting it to false just as a stab in the dark, but no change in behavior. I also tried changing my SelectedItem property to wrap a call to GetDefaultCollectionView() and getting/changing the selected item via the collection view instead of using a binding to SelectedItem. The behavior is identical.
Selecting an item is essentially setting IsSelected = true.
And setting this property does not affect the Focus transition to the selected element in any way.
And when controlling from the keyboard, the transition occurs from the element with Focus.
You can add the SelectionChanged processing to the Selector (ListBox, DataGrid,...) and in it perform the Focus transition to the selected item (by the index in the SelectedIndex).
An example of such a handler:
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (sender is Selector selector)
{
int index = selector.SelectedIndex;
if (index >=0)
{
var element = selector.ItemContainerGenerator.ContainerFromIndex(index);
if (element is UIElement uiElement)
uiElement.Focus();
}
}
}

Remove selected rows from a DataGrid when the user clicks on any other control

I want to remove the selected items from a DataGrid when the user clicks on any other control in the UserControl. The grid has selection mode as "Extended".
I thought of oneapproach
LostFocus event on the DataGrid or the same event on DataGridCell:- But this event is called whenever I select any row in the grid. So I can't remove selected items here.
Use FrameworkElement.PreviewGotKeyboardFocusEvent and handle it at the root which is UserControl in your case like this :
<UserControl ... FrameworkElement.PreviewGotKeyboardFocus="FrameworkPreviewGotKeyboardFocus" />
// Handler :
Important is to check for DataGridCells etc, so we use IsDescendantOf() method to check if some element which lies in our DataGrid is having focus.
Take note of e.Handled and set it's value accordingly.
private void FrameworkPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
{
if (sender is FrameworkElement)
{
Debug.WriteLine(((FrameworkElement)e.OriginalSource).ToString());
if (!((FrameworkElement)e.OriginalSource).IsDescendantOf(MyDataGrid))
{
Debug.WriteLine("Datagrid lost focus completely !");
//e.Handled = true;
// Do something now
}
}
}

Get event trigger

I am using WPF ListBox, binding the ItemsSource to an ObservableCollection.
I subscribed to the SelectionChanged event, which will notify me when the user select/deselect any ListItem.
Now, can I get whether the selection in the ListBox was changed due to user click or Collection change (i.e. items were removed from the Collection which were selected in the ListBox) ??
As you know when you remove some items from the collection, you could set a bool flag as you remove something and then ignore calls to the handler when the flag is true... of course, don't forget to set the flag to false again afterwards:
isProgramAction = true;
Items.Remove(item);
isProgramAction = false;
...
private void SelectionChanged(object sender, RoutedEventArgs e)
{
if (!isProgramAction)
{
// User Action
}
}

Dynamic collection in FlipView

Is it possible to load next\previous item in FlipView on demand? Lets say I display an Item and will download next one only when user press left\right button?
FlipView has Items and ItemsSource properties, but in such case I have to specify full collection of items at once instead of downloading them one by one.
Use an ObservableCollection for the ItemsSource property on your FlipView.
You can subscribe to the SelectionChanged event on your FlipView:
private void FlipView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
//
}
In this handler, you can get the current index and decide if you have to load the next item or not (i.e. if the index is Count - 1 of your collection).
Beware that when adding an item to the collection, the event will fire and your handler will be called.

How do you ensure that the user has selected at least one item in a ListView?

For a ListView how can you make it so you CAN'T deselect the selected index by holding the control button while you select any item?
Thank you very much
Subscribe to the PreviewMouseButtonDown event on the ListView. In that event handler you can catch when the user ctrl-clicks and mark the event has handled. Then it won't be passed on.
As andrea pointed out they can do unselect through shortcut keys as well. Instead I think you should subscribe to the SelectionChangedEvent. You can then loop through the removed items and remark them as selected:
void list_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
foreach (var item in e.RemovedItems)
{
myList.SelectedItems.Add(item);
}
}

Resources