开发者

WPF ListBox scrolls to top when I change status message or show wait screen

开发者 https://www.devze.com 2023-03-29 09:56 出处:网络
I\'m developing an opensource applicati开发者_Go百科on named Media Assistant. I used a ListBox to show the library. ItemsSource is bound to a list of LibraryItem. Here is the XALM.

I'm developing an opensource applicati开发者_Go百科on named Media Assistant. I used a ListBox to show the library. ItemsSource is bound to a list of LibraryItem. Here is the XALM.

<ListBox Name="Tree" DockPanel.Dock="Top" 
    ItemsSource="{Binding DataSource.OrderedLibraryItems}" 
    Background="{StaticResource LibraryBackground}"
    Width="220" HorizontalAlignment="Left"
    BorderThickness="0"
    VirtualizingStackPanel.IsVirtualizing="True"
    VirtualizingStackPanel.VirtualizationMode="Standard"
    ScrollViewer.IsDeferredScrollingEnabled="True"
    ItemTemplate="{StaticResource ListLibraryItemTemplate}"
    SelectionMode="Single"
    MouseDoubleClick="HandleMouseDoubleClick"
/>

The problem is when I show any status message at the bottom of my window from a thread by using Dispatcher.

Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,new ParameterizedThreadStart(action), state);

The ListBox scrolls to at the top. If I don't show any status message then it works just fine. The datacontext or list items or focus has not been changed. I could not found any reason why it's doing that. It happens when I display any wait screen which is a non modal window. I could not recreate it in a different project. Here is the source code of Media Assistant. You can easily re-create it by un-commenting the return statement of method SetStatusMessage at BackgroundScanner class.


I found the reason behind this, so the solution. I used a DockPanel to layout my UI. I put my status bar at the bottom, the ListBox on the Left and other items are on middle and top. There is a TextBlock in my StatusBar which has width and Height set to Auto. So, when I changed text of my StatusBar TextBlock it's width and height gets recalculated and It's parent's recalculates it's layout. Hence the ListBox gets invoked to Measures and Arrange. Even though it's size does not gets changed it resets it's scroll position to top. It happens only if I use ScrollViewer.CanContentScroll="True" at the ListBox. By default it is True. So, even though I did not set this value It was resetting the scroll position. If I disable it by using ScrollViewer.CanContentScroll="False" then it works fine.

<ListBox Name="Tree" DockPanel.Dock="Top" 
    ItemsSource="{Binding DataSource.OrderedLibraryItems}" 
    Background="{StaticResource LibraryBackground}"
    Width="220" HorizontalAlignment="Left"
    BorderThickness="0"
    VirtualizingStackPanel.IsVirtualizing="True"
    VirtualizingStackPanel.VirtualizationMode="Standard"
    ScrollViewer.IsDeferredScrollingEnabled="True"
    ScrollViewer.CanContentScroll="False"
    ItemTemplate="{StaticResource ListLibraryItemTemplate}"
    SelectionMode="Single"
    MouseDoubleClick="HandleMouseDoubleClick"
/>

But setting ScrollViewer.CanContentScroll="False" disables virtualization and I want to use virtualization to my ListBox so I set fixed Height and Width to the TextBlock. So, the DockPanel does not re-arrange it's children if I change the status message.

May be it's a bug at ScrollViewer. It should not change the scroll position if the size has not changed.


In reply to @user904627's answer, here is an enhanced version of his workaround. The issue with just fixing Width and Height is the ListBox keeps the same position even if the user resizes the Window. This is not acceptable.

This is why I created this tiny behavior which fixes Width and Height but listens to the parent element's SizeChanged event to let the ListBox resize itself when the container's size changes.

The code is here: VirtualizedListBoxFixBehavior

When the parent element is resized, I restore Width and Height to double.NaN (so the control can resize itself) and I queue the bit of code which fixes the size properties to actual values in the Dispatcher for later execution.

But this still is an ugly working workaround...


Try this:

listBox1.ScrollIntoView(listBox1.Items.GetItemAt(listBox1.Items.Count - 1));


Since the layout is being reset, it is expected that the list box to select the first item (0).

Can you try to set the selected item to the number of existing items in the list box:

Tree.SelectedIndex = Tree.Items.Count;

I did not test this solution on your code but I have used it in another project of mine where I had a similar problem.

Hope it helps.

0

精彩评论

暂无评论...
验证码 换一张
取 消