WPF - Disable unloading of items when scrolled in VirtualizingStackPanel - wpf

I am looking for a way to disable the items unloading when ItemsControl with VirtualizingStackPanel is scrolled.
What I am trying to achieve here is - Load initial set of items based on ItemsControl height and then load next set of items as we scroll down.
Any suggestion achieving this will be of great help.

protected override void OnCleanUpVirtualizedItem(CleanUpVirtualizedItemEventArgs e)
{
if (e.UIElement != null)
{
//This is to suppress UI type editor disposal caused by virtualization.
e.Cancel = true;
}
}
In case anyone looking for such solution, override VirtualizedStackPanel's above method and cancel the cleanup process.

Related

'DeferRefresh' is not allowed during an AddNew or EditItem transaction

I have a tab control in the GUI and there is WPF 4.0 datagrid in one of the tabs. When I click on a cell in the grid and edit something and then switch tabs, I get a Defer Refresh error:
DeferRefresh' is not allowed during an AddNew or EditItem transaction.
So I call datagrid.CancelEdit(DataGridEditingUnit.Row) when tab is switched to cancel any pending edit and the Defer refresh issue is gone.
But what I really want to do is CommitEdit() so that the user doesn't have to reenter the data again.
And datagrid.CommitEdit(DataGridEditingUnit.Row, true) doesn't work for me.
I get the below error on CommitEnd():
Cannot perform this operation while dispatcher processing is
suspended.
PS: I have tried datagrid.CommitEdit() and datagrid.CommitEdit(DataGridEditingUnit.Column, true) and it didnt work.
I solved this by adding this handler for the DataGrid's Unloaded event:
void DataGrid_Unloaded(object sender, RoutedEventArgs e)
{
var grid = (DataGrid)sender;
grid.CommitEdit(DataGridEditingUnit.Row, true);
}
I've run in to this before. WPF only keeps the current tab's view in memory; when you switch tabs, WPF unloads the current view and loads the view of the selected tab. However, DataGrid throws this exception if is currently executing a AddNew or EditItem transaction and WPF tries to unload it.
The solution for me was to keep all the tab views in memory, but only set the current tab's view to visible. This link shows a method of doing this:
WPF TabControl - Preventing Unload on Tab Change?
This change will also make your tabs load more smoothly when you switch between them because the view doesn't have to be regenerated. In my case, the extra memory usage was a reasonable trade-off.
In Xaml :
Loaded="OnUserControlLoaded"
Unloaded="OnUserControlUnloaded"
In Code Behind Inside OnUserControlLoaded and OnUserControlUnloaded Methods:
dataGrid.CommitEdit()
dataGrid.CancelEdit()
I just solved a similar problem by "commiting" changes to the DataTable that is my source of datas.
So if you have a DataTable in the source you can try the following code :
DataTableSource.AcceptChanges();
I've fixed that issue by adding this piece of code:
private void tabControl_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (tabControl.SelectedIndex == 1)
{
WPFdataGrid.CancelEdit(DataGridEditingUnit.Row);
}
}
I think it's a problem of UI threads.

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 ;
}

Prevent WPF ListBox from selecting item under mouse when layout changes

If a WPF ListBox gets a MouseMove event while the mouse button is held down, it will change the listbox's selection. That is, if you click the mouse on item #1, and then drag over item #2, it will deselect item #1 and select item #2 instead. How can I prevent this?
That's the short version. The slightly longer version is this: When the user double-clicks an item in my ListBox, I make other changes to my layout, which includes showing other controls above the ListBox. This moves the ListBox downwards, which means the mouse is now positioned over a different ListBoxItem than it was when the user double-clicked.
Since I make these layout changes in response to the DoubleClick event (which is a mouse-down event), it's very likely that the mouse button will still be pressed when this layout change completes, which means WPF will send the ListBox a MouseMove event (since the mouse's position, relative to the ListBox, has changed). ListBox treats this as a drag, and selects the event that's now under the mouse.
I don't want the selection to change between the time I get the double-click event and the time the user releases the mouse (which might be well after the layout changes). I suspect that the simplest way to achieve this would be to disable the "change selection on drag" behavior, but I'm open to other suggestions.
How can I "lock in" the selection on double-click, and prevent it from changing until mouseup?
After some digging around in ILSpy, I found that there's no property that disables the "drag to select" behavior, nor is there an event I can mark as Handled to stop it.
But there is a good inflection point for changing this behavior: ListBoxItem.OnMouseEnter is virtual, and it calls back into the listbox to change the selection. It doesn't seem to do anything else substantive, so all I need to do is override it and do nothing.
EDIT: As it turns out, the above only keeps the selection from changing while you move the mouse around inside the listbox. It doesn't help if you move the mouse above or below the listbox -- then the auto-scroll kicks in and moves the selection. Most of the auto-scroll code is again in non-virtual methods; it looks like the best way to prevent auto-scroll is probably to disable mouse capture. Another override on ListBoxItem can take care of this.
It looks like the best way to use my own ListBoxItem descendant is to descend from ListBox. The final code looks something like this:
public class ListBoxEx : ListBox
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ListBoxExItem();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is ListBoxExItem;
}
}
public class ListBoxExItem : ListBoxItem
{
private Selector ParentSelector
{
get { return ItemsControl.ItemsControlFromItemContainer(this) as Selector; }
}
protected override void OnMouseEnter(MouseEventArgs e)
{
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
ParentSelector?.ReleaseMouseCapture();
}
}

Maintain scroll position on updating the ItemSource of a silverlight datagrid

I'm using a DataGrid in my silverlight application to display some data that's refreshed on a timer. My problem is that when this happens the vertical scrollbar in the grid resets to the top, whereas I want it to stay in the same position. Does anyone know how I can make this happen?
I've tried overriding the ItemsSource property on the grid to store the vertical scroll position and then reset it, but this only affects the scrollbar and doesn't force the correct rows to be displayed. Is there a way to force this behaviour?
Here is a similar question about Setting the scroll bar position on a ListBox
After rebinding Silverlight Listbox control how do you get it listbox to scroll to back to the top?
Since the DataGrid also supports a ScrollIntoView method, you should be able to use a similar technique such as
theDataGrid.ItemsSource = data;
theDataGrid.UpdateLayout();
theDataGrid.ScrollIntoView(theDataGrid.SelectedItem, theDataGrid.Columns[0]);
I couldn't find a decent answer last time I looked. I wanted to keep the current element selected in the grid but that wouldn't work on an ICollectionView refresh (I use MVVM and get automatic updates from the server).
ScrollIntoView() was not an option for me because the currently selected item may NOT be in view. Having the CurrentChanged event firing out of control was also quite a bother.
In the end, I used the Infragistics grid and it does just that out of the box. Problem solved for me.
You may have a look at the DevExpress free grid. I think it had the same nice behaviour (I tested it but I can't remember the outcome).
You could try setting the SelectedItem thro the UI thread, so that the UI can refresh itself,
like so
private void Button_Click(object sender, RoutedEventArgs e)
{
Person p = new Person() { Name="sss",Age=11}; //datagird's itemsSource is Collection<person>
people.Add(p);
dg.SelectedItem = p; //dg is my datagrid name
Dispatcher.BeginInvoke(() => { dg.SelectedItem = p; });
}
Im assuming that new rows are loaded thro the ViewModel, so thats why it makes sense to place the BeginInvoke there. Since the ViewModel operations run on a different thread, and just setting the SelectedItem on its own might not work, this has worked for someone else
I've also had issues with this. I solved it by remembering the item I want to scroll to, then re-binding the DataGrid. I handle the LayoutUpdated event in order to implement the desired functionality:
void MyDataGrid_LayoutUpdated(object sender, EventArgs e)
{
// Reference the data item in the list you want to scroll to.
object dataItem = yourDataItem;
// Make sure the item is not null and didn't already scroll to the item.
if (dataItem != null && this.dataItemScrolledTo != dataItem)
{
// Remember the item scrolled to.
this.dataItemScrolledTo = dataItem;
// Scroll datagrid to the desired item.
MyDataGrid.ScrollIntoView(dataItem, MyDataGrid.Columns[0]);
}
}
I've modified CodeMaster's solution so that you don't need a class level variable. Put this code in the method that updates the ItemsSource. It will dynamically create the eventhandler, attach it, then detach it.
EventHandler MyDataGrid_LayoutUpdated = null;
MyDataGrid_LayoutUpdated = (s, e) =>
{
MyDataGrid.ScrollIntoView(dataItem, MyDataGrid.Columns[0]);
MyDataGrid.LayoutUpdated -= MyDataGrid_LayoutUpdated;
};
MyDataGrid.LayoutUpdated += MyDataGrid_LayoutUpdated;

How to correctly resize scrollbar when underlying collection of a WPF ListView changes?

How to correctly resize scrollbar when underlying collection of a WPF ListView changes?
I have a WPF ListView bound to an observeable collection with several thousand items. When a large number of these are removed the view seems to only show the last item. When I move the position in the view with the thumbbar, the thumbbar resizes to reflect the new collection size. Is it possible to force the ListView and Scroll bar to synchronise when the collection changes?
I have found a work-around if anyone else has this problem.
The following code example shows the items source of the ListView bening changed on the first line. The following lines show the workaround which is just to scroll back to the first item.
this.ListViewResults.ItemsSource = this.itemsFiltered;
object firstItem = this.ListViewResults.Items.GetItemAt(0);
if(firstItem == null)
{
return;
}
this.ListViewResults.ScrollIntoView(firstItem);
Strange behaviour!!
I would try setting the binding context (Context) of the ListView to null, and then the same list again in order to refresh the bindings.
I have a different workaround which requires subclassing ListView. It's a bit more work but the result is better than just scrolling to the first item. But you'll need to adapt the ListView template such that the ScrollViewer in the template has a name (here PART_ScrollViewer) or you use another way to get the ScrollViewer object.
public class BetterListView : ListView
{
ScrollViewer sv;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//Get the scrollviewer in the template (I adapted the ListView template such that the ScrollViewer has a name property)
sv = (this.Template.FindName("PART_ScrollViewer", this)) as ScrollViewer;
}
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
{
base.OnItemsChanged(e);
//Prevent the bug where the ListView doesn't scroll correctly when a lot of items are removed
if (sv != null && e.Action == NotifyCollectionChangedAction.Remove)
{
sv.InvalidateScrollInfo();
}
}
}

Resources