ScrollIntoView - item不应该从视图中消失

本文关键字:视图 消失 不应该 item ScrollIntoView | 更新日期: 2023-09-27 18:02:44

从一开始我就想说,我将奖励200美元的赏金给那些能帮我解决问题的人。

这是我的简单代码(c#与WPF):
namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    /// 
    public partial class Window1 : Window
    {
        string fixedItem;
        public Window1()
        {
            InitializeComponent();
            listBox1.ItemContainerGenerator.ItemsChanged += new System.Windows.Controls.Primitives.ItemsChangedEventHandler(list_changes);
        }

        private void list_changes(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
        {
            listBox1.ScrollIntoView(fixedItem);
        }
        private void listBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {      
            fixedItem = (string)listBox1.SelectedItem;        
        }
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            listBox1.Items.Add("item0");
            listBox1.Items.Add("item1");
            listBox1.Items.Add("item2");
            listBox1.Items.Add("item3");
            listBox1.Items.Add("item4");
            listBox1.Items.Add("item5");
            listBox1.Items.Add("item6");
        }
        private void button2_Click(object sender, RoutedEventArgs e)
        {
            listBox1.Items.Insert(0, "item7");
            listBox1.Items.Insert(0, "item8");
            listBox1.Items.Insert(0, "item9");
            listBox1.Items.Insert(0, "item10");
            listBox1.Items.Insert(0, "item11");
            listBox1.Items.Insert(0, "item12");
            listBox1.Items.Insert(0, "item13");
            listBox1.Items.Insert(0, "item14");
            listBox1.Items.Insert(0, "item15");
        }
        private void button3_Click(object sender, RoutedEventArgs e)
        {
            listBox1.Items.Insert(0, "item16");
            listBox1.Items.Insert(0, "item17");
            listBox1.Items.Insert(0, "item18");
            listBox1.Items.Insert(0, "item19");
            listBox1.Items.Insert(0, "item20");
            listBox1.Items.Insert(0, "item21");
            listBox1.Items.Insert(0, "item22");
            listBox1.Items.Insert(0, "item23");
            listBox1.Items.Insert(0, "item24");
        }
    }
}

首先,我创建了3个按钮,在列表框中插入一些文本。比如我点击button1和button2,就会得到如下列表:

item15
item14
item13
item12
......
item7
item0
item1
.....
item6

之后,我想点击"item12",然后当我点击button3时,我想让我的"item12"在生成文本时保持在同一位置(列表中的第4位)。
简而言之,每次我点击项目,我希望它保持在完全相同的位置,而生成文本。

有谁知道怎么做吗?我是否需要使用ScrollViewer对象与VerticallOffset和viewportheighth一起工作?这个简单的代码,我张贴,当我点击项目,然后生成文本,它会移动项目在底部(可视位置),并保持在那里之后。但是我根本不想移动它。

编辑:

好的,我从这里尝试了这个代码:

FrameworkElement container = listRadioItems.ItemContainerGenerator.ContainerFromItem(fixedItem) as FrameworkElement;
            if (null != container)
            {
                if (ScrollViewer.GetCanContentScroll(listBox))
                {
                    IScrollInfo scrollInfo = VisualTreeHelper.GetParent(container) as IScrollInfo;
                    if (null != scrollInfo)
                    {
                        StackPanel stackPanel = scrollInfo as StackPanel;
                        VirtualizingStackPanel virtualizingStackpanel = scrollInfo as VirtualizingStackPanel;
                        int index = listBox.ItemContainerGenerator.IndexFromContainer(container);
                        if (((null != stackPanel) && (Orientation.Horizontal == stackPanel.Orientation)) || ((null != virtualizingStackpanel) && (Orientation.Horizontal == virtualizingStackpanel.Orientation)))
                        {
                            scrollInfo.SetHorizontalOffset(index - Math.Floor(scrollInfo.ViewportWidth / 2));
                        }
                        else
                        {
                            scrollInfo.SetVerticalOffset(index - Math.Floor(scrollInfo.ViewportHeight / 2));
                        }
                    }
                }
                else
                {
                    Rect rect = new Rect(new Point(), container.RenderSize);
                    FrameworkElement constrainingParent = container;
                    do
                    {
                        constrainingParent = VisualTreeHelper.GetParent(constrainingParent) as FrameworkElement;
                    } while ((null != constrainingParent) && (listBox != constrainingParent) && !(constrainingParent is ScrollContentPresenter));
                    if (null != constrainingParent)
                    {
                        rect.Inflate(Math.Max((constrainingParent.ActualWidth - rect.Width) / 2, 0), Math.Max((constrainingParent.ActualHeight - rect.Height) / 2, 0));
                    }
                    container.BringIntoView(rect);
                }
            }

它对我的作用是使选中的项目居中,但滚动条向下滚动,只是有时它也居中。我的问题是,只是有时选中的项目会从视图中消失。

如果我能把项目和滚动条都居中,那就太棒了。但首先要考虑的是,项目不应该从视图中消失。

ScrollIntoView - item不应该从视图中消失

下面的代码获取ListBox中的第一个可见项,将这些项添加到您指定的索引中,然后使用Dispatcher在所有新项呈现后将列表滚动回第一个可见项。

ScrollIntoViewTop()扩展方法与您在原始问题中发布的链接相同,但我将其更改为将项目留在列表的顶部。

WPFHelpers.IsObjectVisibleInContainer()方法是我过去用来测试对象在另一个容器中是否完全或部分可见的方法。为了获得第一个可见的项目,我简单地遍历ListBox项目,获得与每个项目相关联的ListBoxItem容器,然后检查该容器是否可见。第一个返回true的是ListBox中的第一个可见项。

完整代码:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
    private void button1_Click(object sender, RoutedEventArgs e)
    {
        var firstVisibleItem = GetFirstVisibleItem(listBox1);
        listBox1.Items.Insert(0, "item0");
        listBox1.Items.Insert(0, "item1");
        listBox1.Items.Insert(0, "item2");
        listBox1.Items.Insert(0, "item3");
        listBox1.Items.Insert(0, "item4");
        listBox1.Items.Insert(0, "item5");
        listBox1.Items.Insert(0, "item6");
        if (firstVisibleItem != null)
        {
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Loaded,
                new Action(delegate()
                {
                    listBox1.ScrollIntoViewTop(firstVisibleItem);
                }));
        }
    }
    private void button2_Click(object sender, RoutedEventArgs e)
    {
        var firstVisibleItem = GetFirstVisibleItem(listBox1);
        listBox1.Items.Insert(0, "item7");
        listBox1.Items.Insert(0, "item8");
        listBox1.Items.Insert(0, "item9");
        listBox1.Items.Insert(0, "item10");
        listBox1.Items.Insert(0, "item11");
        listBox1.Items.Insert(0, "item12");
        listBox1.Items.Insert(0, "item13");
        listBox1.Items.Insert(0, "item14");
        listBox1.Items.Insert(0, "item15");
        if (firstVisibleItem != null)
        {
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Loaded,
                new Action(delegate()
                {
                    listBox1.ScrollIntoViewTop(firstVisibleItem);
                }));
        }
    }
    private void button3_Click(object sender, RoutedEventArgs e)
    {
        var firstVisibleItem = GetFirstVisibleItem(listBox1);
        listBox1.Items.Insert(0, "item16");
        listBox1.Items.Insert(0, "item17");
        listBox1.Items.Insert(0, "item18");
        listBox1.Items.Insert(0, "item19");
        listBox1.Items.Insert(0, "item20");
        listBox1.Items.Insert(0, "item21");
        listBox1.Items.Insert(0, "item22");
        listBox1.Items.Insert(0, "item23");
        listBox1.Items.Insert(0, "item24");
        if (firstVisibleItem != null)
        {
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Loaded,
                new Action(delegate()
                {
                    listBox1.ScrollIntoViewTop(firstVisibleItem);
                }));
        }
    }
    private object GetFirstVisibleItem(ListBox listBox)
    {
        foreach (var item in listBox.Items)
        {
            var itemContainer = (ListBoxItem)listBox.ItemContainerGenerator.ContainerFromItem(item);
            if (WPFHelpers.IsObjectVisibleInContainer(itemContainer, listBox) == ControlVisibility.Full)
            {
                return item;
            }
        }
        return null;
    }
}
public enum ControlVisibility
{
    Hidden,
    Partial,
    Full,
    FullHeightPartialWidth,
    FullWidthPartialHeight
}
public class WPFHelpers
{
    /// <summary>
    /// Checks to see if an object is rendered visible within a parent container
    /// </summary>
    /// <param name="child">UI element of child object</param>
    /// <param name="parent">UI Element of parent object</param>
    /// <returns>ControlVisibility Enum: Hidden, Partial or Visible</returns>
    public static ControlVisibility IsObjectVisibleInContainer(FrameworkElement child, UIElement parent)
    {
        GeneralTransform childTransform = child.TransformToAncestor(parent);
        //Rect childSize = childTransform.TransformBounds(new Rect(new Point(0, 0), child.RenderSize));
        Rect childSize = childTransform.TransformBounds(new Rect(new Point(0, 0), new Point(child.ActualWidth, child.ActualHeight)));
        Rect result = Rect.Intersect(new Rect(new Point(0, 0), parent.RenderSize), childSize);
        if (result == Rect.Empty)
        {
            return ControlVisibility.Hidden;
        }
        if (result.Height == childSize.Height && result.Width == childSize.Width)
        {
            return ControlVisibility.Full;
        }
        if (result.Height == childSize.Height)
        {
            return ControlVisibility.FullHeightPartialWidth;
        }
        if (result.Width == childSize.Width)
        {
            return ControlVisibility.FullWidthPartialHeight;
        }
        return ControlVisibility.Partial;
    }
}
/// <summary>
/// Class implementing helpful extensions to ListBox.
/// </summary>
public static class ListBoxExtensions
{
    /// <summary>
    /// Causes the object to scroll into view centered.
    /// </summary>
    /// <param name="listBox">ListBox instance.</param>
    /// <param name="item">Object to scroll.</param>
    //[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters",
    //    Justification = "Deliberately targeting ListBox.")]
    public static void ScrollIntoViewTop(this ListBox listBox, object item)
    {
        Debug.Assert(!VirtualizingStackPanel.GetIsVirtualizing(listBox),
            "VirtualizingStackPanel.IsVirtualizing must be disabled for ScrollIntoViewCentered to work.");
        // Get the container for the specified item
        var container = listBox.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
        if (null != container)
        {
            if (ScrollViewer.GetCanContentScroll(listBox))
            {
                // Get the parent IScrollInfo
                var scrollInfo = VisualTreeHelper.GetParent(container) as IScrollInfo;
                if (null != scrollInfo)
                {
                    // Need to know orientation, so parent must be a known type
                    var stackPanel = scrollInfo as StackPanel;
                    var virtualizingStackPanel = scrollInfo as VirtualizingStackPanel;
                    Debug.Assert((null != stackPanel) || (null != virtualizingStackPanel),
                        "ItemsPanel must be a StackPanel or VirtualizingStackPanel for ScrollIntoViewCentered to work.");
                    // Get the container's index
                    var index = listBox.ItemContainerGenerator.IndexFromContainer(container);
                    // Center the item by splitting the extra space
                    if (((null != stackPanel) && (Orientation.Horizontal == stackPanel.Orientation)) ||
                        ((null != virtualizingStackPanel) && (Orientation.Horizontal == virtualizingStackPanel.Orientation)))
                    {
                        //scrollInfo.SetHorizontalOffset(index - Math.Floor(scrollInfo.ViewportWidth / 2));
                        scrollInfo.SetHorizontalOffset(index);
                    }
                    else
                    {
                        //scrollInfo.SetVerticalOffset(index - Math.Floor(scrollInfo.ViewportHeight / 2));
                        scrollInfo.SetVerticalOffset(index);
                    }
                }
            }
            else
            {
                // Get the bounds of the item container
                var rect = new Rect(new Point(), container.RenderSize);
                // Find constraining parent (either the nearest ScrollContentPresenter or the ListBox itself)
                FrameworkElement constrainingParent = container;
                do
                {
                    constrainingParent = VisualTreeHelper.GetParent(constrainingParent) as FrameworkElement;
                } while ((null != constrainingParent) &&
                         (listBox != constrainingParent) &&
                         !(constrainingParent is ScrollContentPresenter));
                if (null != constrainingParent)
                {
                    // Inflate rect to fill the constraining parent
                    rect.Inflate(
                        Math.Max((constrainingParent.ActualWidth - rect.Width) / 2, 0),
                        Math.Max((constrainingParent.ActualHeight - rect.Height) / 2, 0));
                }
                // Bring the (inflated) bounds into view
                container.BringIntoView(rect);
            }
        }
    }
}

我对你的代码做了一些修改,请看看。

在添加按钮2和按钮3上的项目之前,点击…我正在获得固定项目和固定项目索引,并在添加项目....后重新安排固定项目

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        string fixedItem;
        public Window1()
        {
            InitializeComponent();
            listBox1.ItemContainerGenerator.ItemsChanged += new System.Windows.Controls.Primitives.ItemsChangedEventHandler(list_changes);
        }

        private void list_changes(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
        {
            listBox1.ScrollIntoView(fixedItem);
        }
        private void listBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            //fixedItem = (string)listBox1.SelectedItem;
        }
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            listBox1.Items.Add("item0");
            listBox1.Items.Add("item1");
            listBox1.Items.Add("item2");
            listBox1.Items.Add("item3");
            listBox1.Items.Add("item4");
            listBox1.Items.Add("item5");
            listBox1.Items.Add("item6");
        }
        private void button2_Click(object sender, RoutedEventArgs e)
        {
            fixedItem = (string)listBox1.SelectedItem;
            int selectedIndex = listBox1.SelectedIndex;
            listBox1.Items.Insert(0, "item7");
            listBox1.Items.Insert(0, "item8");
            listBox1.Items.Insert(0, "item9");
            listBox1.Items.Insert(0, "item10");
            listBox1.Items.Insert(0, "item11");
            listBox1.Items.Insert(0, "item12");
            listBox1.Items.Insert(0, "item13");
            listBox1.Items.Insert(0, "item14");
            listBox1.Items.Insert(0, "item15");
            listBox1.Items.Remove(fixedItem);
            listBox1.Items.Insert(selectedIndex, fixedItem);
            listBox1.SelectedItem = fixedItem;
        }
        private void button3_Click(object sender, RoutedEventArgs e)
        {
            fixedItem = (string)listBox1.SelectedItem;
            int selectedIndex = listBox1.SelectedIndex;
            listBox1.Items.Insert(0, "item16");
            listBox1.Items.Insert(0, "item17");
            listBox1.Items.Insert(0, "item18");
            listBox1.Items.Insert(0, "item19");
            listBox1.Items.Insert(0, "item20");
            listBox1.Items.Insert(0, "item21");
            listBox1.Items.Insert(0, "item22");
            listBox1.Items.Insert(0, "item23");
            listBox1.Items.Insert(0, "item24");
            listBox1.Items.Remove(fixedItem);
            listBox1.Items.Insert(selectedIndex, fixedItem);
            listBox1.SelectedItem = fixedItem;
        }
    }
}

更新引用类型:我创建了一个名为item的类。不是添加字符串,而是添加item…

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        Item fixedItem;
        int selectedIndex;
        public Window1()
        {
            InitializeComponent();
            listBox1.ItemContainerGenerator.ItemsChanged += new System.Windows.Controls.Primitives.ItemsChangedEventHandler(list_changes);
        }

        private void list_changes(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
        {
        }
        private void listBox1_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
        }
        private void button1_Click(object sender, RoutedEventArgs e)
        {
            listBox1.Items.Add(new Item { ItemName = "item0" });
            listBox1.Items.Add(new Item { ItemName = "item1" });
            listBox1.Items.Add(new Item { ItemName = "item2" });
            listBox1.Items.Add(new Item { ItemName = "item3" });
            listBox1.Items.Add(new Item { ItemName = "item4" });
            listBox1.Items.Add(new Item { ItemName = "item5" });
            listBox1.Items.Add(new Item { ItemName = "item6" });
        }
        private void button2_Click(object sender, RoutedEventArgs e)
        {
            if (listBox1.SelectedItem != null)
            {
                fixedItem = (Item)listBox1.SelectedItem;
                selectedIndex = listBox1.SelectedIndex;
            }
            listBox1.Items.Insert(0, new Item { ItemName = "item7" });
            listBox1.Items.Insert(0, new Item { ItemName = "item8" });
            listBox1.Items.Insert(0, new Item { ItemName = "item9" });
            listBox1.Items.Insert(0, new Item { ItemName = "item10" });
            listBox1.Items.Insert(0, new Item { ItemName = "item11" });
            listBox1.Items.Insert(0, new Item { ItemName = "item12" });
            listBox1.Items.Insert(0, new Item { ItemName = "item13" });
            listBox1.Items.Insert(0, new Item { ItemName = "item14" });
            listBox1.Items.Insert(0, new Item { ItemName = "item15" });
            listBox1.Items.Remove(fixedItem);
            listBox1.Items.Insert(selectedIndex, fixedItem);
            listBox1.SelectedItem = fixedItem;
            listBox1.ScrollIntoView(fixedItem);
        }
        private void button3_Click(object sender, RoutedEventArgs e)
        {
            if (listBox1.SelectedItem != null)
            {
                fixedItem = (Item)listBox1.SelectedItem;
                selectedIndex = listBox1.SelectedIndex;
            }
            listBox1.Items.Insert(0, new Item { ItemName = "item16" });
            listBox1.Items.Insert(0, new Item { ItemName = "item17" });
            listBox1.Items.Insert(0, new Item { ItemName = "item18" });
            listBox1.Items.Insert(0, new Item { ItemName = "item19" });
            listBox1.Items.Insert(0, new Item { ItemName = "item20" });
            listBox1.Items.Insert(0, new Item { ItemName = "item21" });
            listBox1.Items.Insert(0, new Item { ItemName = "item22" });
            listBox1.Items.Insert(0, new Item { ItemName = "item23" });
            listBox1.Items.Insert(0, new Item { ItemName = "item24" });
            listBox1.Items.Remove(fixedItem);
            listBox1.Items.Insert(selectedIndex, fixedItem);
            listBox1.SelectedItem = fixedItem;
            listBox1.ScrollIntoView(fixedItem);
        }
    }
    class Item
    {
        public string ItemName { get; set; }
    }
}

XAML改变……

<ListBox x:Name="listBox1" Height="300" SelectionChanged="listBox1_SelectionChanged">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding ItemName}"/>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

你想选择"item12" -然后添加更多的项目-然后"item12"应该仍然被选中并在视图中?

如果是,为什么不使用ICollectionView。添加新项目后的MoveTo方法?你只需要在添加新项目之前记住最后选择的项目。

我在我的项目中使用MoveTo和ScrollIntoView,它工作得很好。

编辑:

不直接向列表框中添加项目。我使用一个集合,并将这个集合添加到列表框的itemssource中。我使用ScrollItemsIntoView的SelectionChanged事件。

 var _myview = (ICollectionView)CollectionViewSource.GetDefaultView(this._mycollection);
 _myview.MoveCurrentTo(this.rememberredItem);

如果你使用Ado.net集合,那么你必须使用BindingListCollectionView而不是ICollectionView

EDIT2:代替CollectionViewSource.GetDefaultView(listBox1.ItemsSource),您可以尝试以下方法。创建一个observablecollection,并将其设置为列表框的itemssource。

ICollectionView myview;
OberservableCollection<string> mysource = new ObservableCollection<string>();
myview = (ICollectionView)CollectionViewSource.GetDefaultView(this.mysource);
listbox1.ItemsSource = mysource; //you should better use binding in xaml here

如果您现在想添加项目,只需将其添加到集合中。

this.mysource.Add("Item 13");
this.mysource.Add("Item 14");

EDIT3: copy from here

David Anson在他的博客上发表了一些可能对你有所帮助的文章:第1部分和第2部分。他给出了一个扩展方法,可以将列表框中的项居中。你可以在

的基础上

下面的代码正在工作只是保持位置,直到您手动更改所选项目。

XAML代码
 <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="189*" VirtualizingStackPanel.IsVirtualizing="False"  VirtualizingStackPanel.VirtualizationMode="Recycling"/>
            <RowDefinition Height="72*" />
        </Grid.RowDefinitions>
        <ListBox x:Name="listBox1" Grid.Row="0" />
        <Button Click="Button_Click"  Grid.Row="1" />
    </Grid>

c#代码
  private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (listBox1.Items.Count <= 0)
            {
                for (int i = 0; i < 25; i++)
                {
                    ListBoxItem item = new ListBoxItem();
                    item.Content = "Content " + i;
                    listBox1.Items.Insert(i, item);
                  //  listBox1.SelectedItem = item;
                }
                listBox1.SelectedItem = listBox1.Items[12];
            }
            else
            {
                ListBoxItem item = new ListBoxItem();
                item.Content = "Content " + listBox1.Items.Count;
                listBox1.Items.Insert(listBox1.Items.Count, item);
              //  listBox1.SelectedItem = item;
            }
            Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Loaded,
                new Action(delegate()
                {
                    listBox1.ScrollIntoView(listBox1.SelectedItem);
                }));
        }

你可以试试:

namespace WpfApplication1 {   
/// <summary>     
/// Interaction logic for Window1.xaml  
/// </summary>  
///
public partial class Window1 : Window  
{
     string fixedItem;
      public Window1()
     {
         InitializeComponent();
         listBox1.ItemContainerGenerator.ItemsChanged += new System.Windows.Controls.Primitives.ItemsChangedEventHandler(list_changes);
      }
        private void list_changes(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
     {
         listBox1.UpdateLayout();
         listBox1.ScrollIntoView(fixedItem); 
    }
     // REST OF YOUR CODE...

如果你的意思是你的滚动已经是正确的,只是将项目移动到最后一个可见的位置,我会从三个(或更多)索引中获取项目,以便将项目集中在列表框上。

我已经在Silverlight中测试过了,但是差异是存在的

你的代码不工作

private void list_changes(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
{
      listBox1.ScrollIntoView(fixedItem);
}

但是这个代码可以工作!

private void list_changes(object sender, System.Windows.Controls.Primitives.ItemsChangedEventArgs e)
{
      Dispatcher.BeginInvoke(() => listBox1.ScrollIntoView(fixedItem));
}

问题来自于列表框滚动,然后添加新项目,然后重新滚动。如果在调度程序上调用滚动,列表框将完成添加项目的工作,然后滚动到您的项目