granadaCoder
granadaCoder

Reputation: 27904

WPF StackPanel "Jumps"

I have a StackPanel that contains a TextBox and a Combobox.

When I set the focus inside a textbox (not the first one), the Contents of the StackPanel "jumps" and goes to the top.

Below is the code. I have researched this and post the one I found and tried (but did not worK).

I want to prevent the "jumping".

So run the code below. Scroll the vertical bar until you see:

Name Three       <<Text Box
(No Selection) ComboBox \/
Name Four      <<Text Box
(No Selection) ComboBox \/

Now put your cursor in the "Name Four" text box.......and watch it "jump" to the top. (You don't see Three and Four now, you see Four and Five.)

My stackpanel is much more complex than this in real life, and its driving my end-users nutty.

Thanks.

MainWindow.xaml

<Window x:Class="ListBoxControlSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="160" Width="250">
    <Grid Margin="10">
        <ListBox ItemsSource="{Binding Models}" SelectionMode="Single" RequestBringIntoView="FrameworkElement_OnRequestBringIntoView" SelectionChanged="Selector_OnSelectionChanged" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel>
                        <TextBox Text="{Binding Name}"/>
                        <ComboBox VerticalContentAlignment="Top" VerticalAlignment="Top" Grid.Column="1" ItemsSource="{Binding Options}" >
                        </ComboBox>
                        <TextBlock Text="{Binding Title}"></TextBlock>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Window>

MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace ListBoxControlSample
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
        }

        private IList<Model> _models;
        public IList<Model> Models
        {
            get
            {
                return _models ?? (_models = new List<Model>
                    {
                        new Model{ Name = "Name One", Title = "Title One"},
                        new Model{ Name = "Name Two", Title = "Title Two"},
                        new Model{ Name = "Name Three", Title = "Title Three"},
                        new Model{ Name = "Name Four", Title = "Title Four"},
                        new Model{ Name = "Name Five", Title = "Title Five"},
                        new Model{ Name = "Name Six", Title = "Title Six"},
                        new Model{ Name = "Name Seven", Title = "Title Seven"}
                    });
            }
        }

        private void FrameworkElement_OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
        {
            e.Handled = true;
        }

        private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            //throw new NotImplementedException();
        }

        private void Selector_OnSelected(object sender, RoutedEventArgs e)
        {
            //throw new NotImplementedException();
        }
    }

    public class Model
    {
        public string Name { get; set; }
        public string Title { get; set; }
        private IList<string> _options;
        public IList<string> Options
        {
            get
            {
                return _options ?? (_options = new List<string>
                    {
                        "left",
                        "right",
                        "both"
                    });
            }
        }
    }
}

What I've found and tried (to prevent the jumping)

        <DataTemplate>
                    <StackPanel ScrollViewer.CanContentScroll="False">

Upvotes: 4

Views: 3487

Answers (6)

mark.monteiro
mark.monteiro

Reputation: 2941

This issue is caused when a child item within the ScrollVewier receives focus and is subsequently scrolled into view (in this case the child element is the ListBoxItem).

If the element receiving focus does not need to be navigaable via the tab key, then a simple solution is to make the element unfocusable via Focusable=False. If the element does not receive focus then the scroll viewer will also not try to scroll it into view.

In this case this is likely an acceptable solution as I don't think the ListBoxItem is acting more as a container than as a control the user needs to be able to select.

Upvotes: 0

Soff
Soff

Reputation: 1

Use this:

<ListBox ScrollViewer.PanningMode="None">

Upvotes: 0

granadaCoder
granadaCoder

Reputation: 27904

Here is another answer. It works with a simple WPF.

However, my real situation had an Infragistics control and Rachel's answer worked.

However, I want to post this for completeness.

I'm pasting the code from this url:

http://social.msdn.microsoft.com/Forums/vstudio/en-US/a3532b1f-d76e-4955-b3da-84c98d6d435c/annoying-auto-scroll-of-partially-displayed-items-in-wpf-listbox?forum=wpf

Here is the code:

  <ListBox.ItemContainerStyle>
            <Style TargetType="ListBoxItem">
                <EventSetter Event="RequestBringIntoView" Handler="ListBoxItem_RequestBringIntoView"/>
            </Style>
        </ListBox.ItemContainerStyle>

and

  void ListBoxItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
        {
            e.Handled = true;
        }

Upvotes: 4

Rachel
Rachel

Reputation: 132648

I'm guessing it has something to do with the default scroll behavior of trying to show the full item whenever it gets selected.

Try disabling the scrolling, and wrap it in another ScrollViewer:

<ScrollViewer VerticalScrollBarVisibility="Auto" CanContentScroll="True" Height="250">
    <ListBox ScrollViewer.VerticalScrollBarVisibility="Disabled">
        ...
    </ListBox>
</ScrollViewer>

This way, instead of trying to scroll each individual StackPanel into view at a time, and ensuring the full item is visible, it will try to scroll the entire ListBox into view, which exceeds the height allotted to the ScrollViewer, so it will use the smoother content-based scrolling that you want.

It should be noted that this will render the entire ListBox at once, without any virutalization, so it isn't advisable to use this method if you have a lot of rows and are depending on virtualization for performance. But based on your question, it doesn't sound like that is your case.

I'm also not positive, but you may have to nest this in one more panel as well to allow the ListBox to grow to whatever height it wants. See this answer for more details if needed.

Upvotes: 4

jschroedl
jschroedl

Reputation: 4996

When a command like SetFocus runs on a child of the scrollviewer, it triggers a ReqestBringInto view on the scrrollviewer but the act of scrolling to the child controls is implemented under the covers by the scrollviewer's ScrollInfo object.

I think the key here is to create a class which implements IScrollInfo. I implemented one to override the amount the SV scrolls when using the mouse wheel. In my case, I cloned the default implementation from the .NET source for ScrollContentProvider) and changed some properties.

Internally, ScrollViewer will call the IScrollInfo::MakeVisible() on the desired child control. You could just ignore that request.

Create an instance of your own scroll provider to the scrollviewer using something like:

var myProvider = new MyScrollInfo();
myScrollViewer.ScrollInfo = myProvider;

Upvotes: 0

gomi42
gomi42

Reputation: 2529

The effect is reproducible if you click into the TextBox of the last visible row. If that row isn't fully scrolled into the view the listbox automatically scrolls that line into view to be fully visible. That in turn means all rows scroll up one row which makes you feel the jumping effect. To my knowledge you cannot change that behaviour.

Upvotes: 0

Related Questions