ScrollIntoView - 项目不应从视图中消失

时间:2011-07-18 07:29:52

标签: c# wpf

从一开始我就想说我会奖励一个可以帮助我解决问题的人的200赏金。

这是我的简单代码(带WPF的C#):

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个按钮在listBox中插入一些文本。让我说我点击button1和button2,我将有这个列表:

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

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

所以任何人都有任何想法如何做到这一点?我是否需要使用ScrollViewer对象来处理VerticallOffset和ViewportHeigth?我点击项目然后生成文本时发布的这个简单代码将在底部(可查看位置)移动项目并保持在那之后。但我根本不想动它。

编辑:

好的我从here尝试了这段代码:

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

它对我的作用是它中心选择的项目,但滚动下降,有时它也是中心。而我的问题是,有时只有选定的项目会从视图中消失。

如果我可以将项目和滚动设为中心,那将是多么美妙的事情。但首先要关注的是该项目不应该从视角中消失。

6 个答案:

答案 0 :(得分:2)

这里有一些代码可以获取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);
            }
        }
    }
}

答案 1 :(得分:1)

是否要选择“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:从here

复制
  

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

答案 2 :(得分:1)

我对您的代码进行了一些更改,请查看。

在按钮2和按钮3上添加项目之前,单击...我将获得fixeditem和固定项目索引,并在添加项目后重新排列固定项目....

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的类..而不是添加字符串我正在添加项目...

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>

答案 3 :(得分:1)

下面的代码正常工作,只需按住该位置,直到您手动更改所选项目。

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

        }

答案 4 :(得分:0)

你可以试试这个:

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...

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

答案 5 :(得分:0)

我在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));
}

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