简体   繁体   中英

Scroll ListView To Top On ItemsSource Changed Is Called Unexpectedly

I've got a wpf user control project set up similar (for the most part) to the WPF Apps With The Model-View-ViewModel Design Pattern article . Basically, I have a headered content control, that houses a tab control. The first tab holds search results and any additional tabs are opened by clicking on an item from the search results to see a more detailed view of the item.

My main problem is that I reuse the search results tab any time someone types in search criteria and runs the search command. The scrollviewer does not scroll to the top when the search results collection is repopulated (if the user had scrolled down the list).

Originally I was repopulating the search result item collection in a way that caused the CollectionChanged event to fire. Then I looked into using an attached property to allow scrolling to the top and it looked as if replacing the entire collection was the way to go (since ItemsControl does not implement the ICollectionNotifyChanged interface).

So I made the change and all seemed well. Running a new search replaced the search results item collection, which fired the attached property and scrolled the listview back to the top. However, I now have a problem that whenever I move to another tab to view a detailed view of an item, it fires the attached property and scrolls the search results on the first tab to the top . I'm not sure how to address this situation.

public static class ScrollToTopBehavior
{
    public static readonly DependencyProperty ScrollToTopProperty =
        DependencyProperty.RegisterAttached
            (
                "ScrollToTop",
                typeof (bool),
                typeof (ScrollToTopBehavior),
                new UIPropertyMetadata(false, OnScrollToTopPropertyChanged)
            );

    public static bool GetScrollToTop(DependencyObject obj)
    {
        return (bool) obj.GetValue(ScrollToTopProperty);
    }

    public static void SetScrollToTop(DependencyObject obj, bool value)
    {
        obj.SetValue(ScrollToTopProperty, value);
    }

    private static void OnScrollToTopPropertyChanged(DependencyObject dpo,
                                                     DependencyPropertyChangedEventArgs e)
    {
        var itemsControl = dpo as ItemsControl;
        if (itemsControl == null) return;

        DependencyPropertyDescriptor dependencyPropertyDescriptor =
            DependencyPropertyDescriptor.FromProperty(ItemsControl.ItemsSourceProperty, typeof (ItemsControl));
        if (dependencyPropertyDescriptor == null) return;
        if ((bool) e.NewValue)
        {
            dependencyPropertyDescriptor.AddValueChanged(itemsControl, ItemsSourceChanged);
        }
        else
        {
            dependencyPropertyDescriptor.RemoveValueChanged(itemsControl, ItemsSourceChanged);
        }
    }

    private static void ItemsSourceChanged(object sender, EventArgs e)
    {
        var itemsControl = sender as ItemsControl;
        EventHandler eventHandler = null;
        eventHandler = delegate
            {
                if (itemsControl != null &&
                    itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
                {
                    var scrollViewer = VisualTreeHelpers.FindChild<ScrollViewer>(itemsControl);
                    scrollViewer.ScrollToTop();
                    itemsControl.ItemContainerGenerator.StatusChanged -= eventHandler;
                }
            };
        if (itemsControl != null) itemsControl.ItemContainerGenerator.StatusChanged += eventHandler;
    }
}

I've attached this property to a search results user control like so:

<ListView ...some properties left out for brevity...
              DataContext="{StaticResource SearchResultsViewSource}"
              IsSynchronizedWithCurrentItem="True"
              ItemsSource="{Binding}"
              SelectionMode="Single"
              behaviours:ScrollToTopBehavior.ScrollToTop="True">
...etc...

The search results control is housed inside of another user control that is using a headered content control (I'll provide the details if needed).

This is a hack and doesn't resolve your real issue but it will resolve the symptoms.
You can detach the your behavior on tabControl.Selecting event, so when you start or go to the search results tab you attach the behavior/property and when you go to other tabs you detach/unregister the behavior.

should look like this:

yourTab.SelecnionChanged+= new TabControlCancelEventHandler(tabControl_Selecting);

void tabControl_SelecnionChange(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
    var current = (sender as TabControl).SelectedValue;

    //check if current is search tab then
    {
      ScrollToTopBehavior.SetScrollToTop(yourList,true);
    }
    else
    {
      ScrollToTopBehavior.SetScrollToTop(yourList,false);
    }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM