WPF TabControl Switch behaviour - wpf

I have a tabcontrol which binds to an observable collection of tabs.
The tabcontrol always has the first tab which hosts a listview bound to another observable collection.
On selecting an item in that list view a new tab is created an focus given to it.
The problem I am having is:
When I switch back to the first tab there is a pause while it redraws / creates the listview items (contains images so slow)
The item selected before moving to the new tab is nolonger selected. Instead the listview is at the top with no item selected.
Can someone please explain to me how the tabcontrol operates is it really distroying the tab item content each time? and how I can instead have a behaviour where the item remains selected when I return to that tab?
Update:
I have confirmed by adding debug print messages to events that no events fire on this switch-back and forth but the first tab is being unloaded - more specifically the usercontrol hosted in that tab is??.

It sounds like the ObservableCollection is the culprit. If you are changing the collection items to control the display, then every time the collection changes won't it redraw the entire tab collection?
Instead, why not maintain the TabItem collection directly? You could then manage the Visibility property of the TabItems to display them or not.

First I needed to ensure my listview bound to my collection correctly i.e. the item stayed selected by adding the property:
IsSynchronizedWithCurrentItem="True"
I then added a loaded event handler to the listview so the item is scrolled into view on switching back:
private void ListView_Loaded(object sender, RoutedEventArgs e)
{
ICollectionView collectionView = CollectionViewSource.GetDefaultView(DataContext);
if (collectionView != null)
{
ItemControl.ScrollIntoView(collectionView.CurrentItem);
}
}

Related

WPF ListBoxItem Events

One of those 'Why is this so hard?" questions.
I have a ListBox (containing details of share portfolios). The listbox item uses a grid to display attributes of the portfolio. Source is a list of portfolios in the View Model.
ListBox is multiselect - when selection changes, a list of the constituents of the selected portfolios is re-populated.
What I want to do is put a button (or menu or whatever) on the listboxitem to display a list of possible actions (Trade, Unitise, Delete etc).
When an action is selected I need to execute the action against the appropriate portfolio. Ideally I want the actions to be available for both selected and unselected items.
I can handle the event, but how do I detect which item (portfolio) the user selected? I've looked at GotFocus() but it doesn't seem to fire.
In other words if a control in a Listboxitem, fires an event, how does the event 'know' which ListBoxItem raised it?
For me, the solution here, seen as you mentioned MVVM, would be to have the ListBox populated by a collection of ViewModels, e.g., something like ObservableCollection<PortfolioViewModel>.
It would then just be a case of binding the Command property of the Button to an ICommand on the ViewModel that executes whatever work you need doing.
I can handle the event, but how do I detect which item (portfolio) the user selected? I've looked at GotFocus() but it doesn't seem to fire.
You could cast the DataContext of the clicked Button to the corresponding object in te ListBox, e.g.:
private void DeleteButton_Clicked(object sender, RoutedEventArgs e)
{
Button deleteButton = sender as Button;
var portfolio = deleteButton.DataContext as Portfolio; //or whatever your type is called
//access any members of the portfolio...
}

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.

TabControl - databinding TabItem order

I've got a datababound TabControl and would like to bind the index of each TabItem to a corresponding property in my view model. The ItemsSource is an ObservableCollection, and I'm using Bea Stollnitz's Drag/Drop functionality to provide tab control re-ordering.
My gut feeling is that it should be able to be handled in the data template for the tab item header, but I haven't been able to get it working.
Your TabControl.ItemsSource should be bound to your collection, so to re-arrange the order of tab items, simply re-arrange the collection.
I've worked with Bea's drag/drop code before to create a TabControl that allowed users to drag/drop the tab items, and I think most of what was needed is in the code she provides. On drop, it removes the dragged object from it's parent collection, and inserts it to its new location in the drop target collection, which in your case is the same collection.
Edit
Based on your comment below about updating your ViewModel with the Tab Index, try using the CollectionChanged event.
void MyCollection_CollectionChanged(object sender, CollectionChangedEventArgs e)
{
foreach (var item in MyCollection)
item.TabIndex = MyCollection.IndexOf(item);
}

TabControl losing selected tab on Window.Show

We have an MVVM (Cinch) solution that has a Window with a TabControl in it. The ItemsSource is bound to a CollectionView (DefaultView generated from an ObservableCollection), with IsSynchronizedWithCurrentItem=true. Everything works great the first time the Window loads... tabs are displaying correctly and the user can switch between them.
The problem occurs when the Window is hidden and shown again. The first tab is always selected again, regardless of what the CurrentItem in the CollectionView is. The line before the .Show has the CurrentItem as the tab we want, but the tab switches during the .Show operation.
Has anyone else run into this issue?
This is definitely a hack. But you could override the Activated event and store the tab index before it gets reset and set it again when it reactivates.
protected override void OnActivated(EventArgs e)
{
int tabControlIndex = myTabController.SelectedIndex;
base.OnActivated(e);
myTabController.SelectedIndex = tabControlIndex ;
}

What's the best way to auto-scroll a list view to the last added item?

I use a ListView to show a list of errors as they occur in my application. It behaves and looks exactly like the Error List in Visual Studio. I want to add auto-scrolling when the last error item is selected (like how Visual Studio's Log Window auto-scrolls when you place the caret at the end).
The list of errors is in an ObservableCollection, which is passed to the ListView.ItemsSource like this:
public ObservableCollection<ErrorListItem> Items;
...
MyListView.ItemsSource = _Items;
I tried performing the auto-scroll in the _Items_CollectionChanged event handler, but because this is the event on the ItemsSource and not on the actual ListViewItems, it's a pain to figure out if the last item is selected, select the new row, etc. It's especially hard since it seems the ListViewItems are not created instantly. I managed to make it auto-scroll by delaying the call to set the last item selected like this:
void _Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// determine the last item to select from 'e'
...
_ItemPendingToBeScrolled = newItemToSelect;
ListView.SelectedItem = newItemToSelect;
Dispatcher.BeginInvoke(DispatcherPriority.Background,
(ThreadStart)delegate
{
if (_ItemPendingToBeScrolled != null)
{
ListView.ScrollIntoView(_ItemPendingToBeScrolled);
ItemPendingToBeScrolled = null;
}
})
}
But that's obviously not the right way to do it. Also, I want things to keep working if the list is filtered (not checking the last item in my source, but the last ListViewItem in the ListView).
Is there a way to listen to events when a ListViewItem gets added to the ListView following an addition to the bound collection? That would be the ideal event to capture in order to properly do my auto-scrolling. Or is there another technique I could use?
I have a lot of issues with listboxes/listviews and their scrolling, however, you mentioned hooking to the listview's changed event, is it because you can't listen to the observable collection's CollectionChanged event? ObservableCollection is way more stable than List controls, and you'll get the same notifications.
You can also bubble these events up if it's not working in the UI and you don't have access, this way you treat your scrolling in the UI without having access to the actual collection, just keep a reference to the Selected Item in your custom EventArgs class

Resources