【问题标题】:Custom IScrollInfo panel loses scrolling ability when hosted in an ItemsControl托管在 ItemsControl 中时,自定义 IScrollInfo 面板失去滚动能力
【发布时间】:2018-04-17 09:07:07
【问题描述】:

我正在尝试使用IScrollInfo 界面创建自定义面板,但收效甚微。如果我在 XAML 的自定义面板中手动声明项目,我可以让它工作,但是当我将它放入 ItemsControl 时,滚动功能停止。如果有人能告诉我哪里出错了,我将不胜感激。这是面板的(相当长,但很简单)代码:

using IScrollInfoExample.Extentions;
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace IScrollInfoExample
{
    public class ExampleScrollPanel : Panel, IScrollInfo
    {
        private TranslateTransform _trans = new TranslateTransform();
        private Size _extent = new Size(0, 0);
        private Size _viewport = new Size(0, 0);
        private Point _offset;
        private const double _scrollAmount = 3;

        public ExampleScrollPanel()
        {
            Loaded += ExampleScrollPanel_Loaded;
            RenderTransform = (_trans = new TranslateTransform());
        }

        public bool CanHorizontallyScroll { get; set; } = false;

        public bool CanVerticallyScroll { get; set; } = true;

        public double HorizontalOffset
        {
            get { return _offset.X; }
        }

        public double VerticalOffset
        {
            get { return _offset.Y; }
        }

        public double ExtentHeight
        {
            get { return _extent.Height; }
        }

        public double ExtentWidth
        {
            get { return _extent.Width; }
        }

        public double ViewportHeight
        {
            get { return _viewport.Height; }
        }

        public double ViewportWidth
        {
            get { return _viewport.Width; }
        }

        public ScrollViewer ScrollOwner { get; set; }

        public void LineUp()
        {
            SetVerticalOffset(VerticalOffset - _scrollAmount);
        }

        public void PageUp()
        {
            SetVerticalOffset(VerticalOffset - _viewport.Height);
        }

        public void MouseWheelUp()
        {
            SetVerticalOffset(VerticalOffset - _scrollAmount);
        }

        public void LineDown()
        {
            SetVerticalOffset(VerticalOffset + _scrollAmount);
        }

        public void PageDown()
        {
            SetVerticalOffset(VerticalOffset + _viewport.Height);
        }

        public void MouseWheelDown()
        {
            SetVerticalOffset(VerticalOffset + _scrollAmount);
        }

        public void LineLeft()
        {
            SetHorizontalOffset(HorizontalOffset - _scrollAmount);
        }

        public void PageLeft()
        {
            SetHorizontalOffset(HorizontalOffset - _viewport.Width);
        }

        public void MouseWheelLeft()
        {
            LineLeft();
        }

        public void LineRight()
        {
            SetHorizontalOffset(HorizontalOffset + _scrollAmount);
        }

        public void PageRight()
        {
            SetHorizontalOffset(HorizontalOffset + _viewport.Width);
        }

        public void MouseWheelRight()
        {
            LineRight();
        }

        public Rect MakeVisible(Visual visual, Rect rectangle)
        {
            return new Rect();
        }

        public void SetHorizontalOffset(double offset)
        {
            if (offset < 0 || _viewport.Width >= _extent.Width)
            {
                offset = 0;
            }
            else if (offset + _viewport.Width >= _extent.Width)
            {
                offset = _extent.Width - _viewport.Width;
            }
            _offset.X = offset;
            if (ScrollOwner != null) ScrollOwner.InvalidateScrollInfo();
            _trans.X = -offset;
        }

        public void SetVerticalOffset(double offset)
        {
            offset = CoerceVerticalOffset(offset);
            _offset.Y = offset;
            _trans.Y = -offset;
            if (ScrollOwner != null) ScrollOwner.InvalidateScrollInfo();
        }

        private double CoerceVerticalOffset(double offset)
        {
            if (offset < 0 || _viewport.Height >= _extent.Height)
            {
                offset = 0;
            }
            else if (offset + _viewport.Height >= _extent.Height)
            {
                offset = _extent.Height - _viewport.Height;
            }
            return offset;
        }

        private void ExampleScrollPanel_Loaded(object sender, RoutedEventArgs e)
        {
            ScrollViewer scrollOwner = this.GetParentOfType<ScrollViewer>();
            if (scrollOwner != null) ScrollOwner = scrollOwner;
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            UpdateScrollInfo(availableSize);
            Size totalSize = new Size();
            foreach (UIElement child in InternalChildren)
            {
                child.Measure(availableSize);
                totalSize.Height += child.DesiredSize.Height;
                totalSize.Width = Math.Max(totalSize.Width, child.DesiredSize.Width);
            }
            return base.MeasureOverride(totalSize);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            UpdateScrollInfo(finalSize);
            double verticalPosition = 0;

            int childCount = InternalChildren.Count;
            for (int i = 0; i < childCount; i++)
            {
                UIElement child = InternalChildren[i];
                child.Arrange(new Rect(0, verticalPosition, finalSize.Width, finalSize.Height));
                verticalPosition += child.DesiredSize.Height;
            }
            return base.ArrangeOverride(finalSize);
        }

        private void UpdateScrollInfo(Size availableSize)
        {
            bool viewportChanged = false, extentChanged = false;
            Size extent = CalculateExtent(availableSize);
            if (extent != _extent)
            {
                _extent = extent;
                extentChanged = true;
            }
            if (availableSize != _viewport)
            {
                _viewport = availableSize;
                viewportChanged = true;
            }
            if (extentChanged || viewportChanged) ScrollOwner?.InvalidateScrollInfo();
        }

        private Size CalculateExtent(Size availableSize)
        {
            Size totalExtentSize = new Size();
            foreach (UIElement child in InternalChildren)
            {
                child.Measure(availableSize);
                totalExtentSize.Height += child.DesiredSize.Height;
                totalExtentSize.Width = Math.Max(totalExtentSize.Width, child.DesiredSize.Width);
            }
            return totalExtentSize;
        }
    }
}

现在MainWindow.xaml.cs:

<Window x:Class="IScrollInfoExample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:Local="clr-namespace:IScrollInfoExample"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
    <ScrollViewer CanContentScroll="True">
        <ItemsControl ItemsSource="{Binding Buttons, RelativeSource={RelativeSource AncestorType={x:Type Local:MainWindow}}}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <Local:ExampleScrollPanel IsItemsHost="True" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <Button Height="100" Width="250" HorizontalAlignment="Center" Content="{Binding}" />
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>
        <!--<Local:ExampleScrollPanel>
            <Button Height="100" Width="250" HorizontalAlignment="Center" Content="A" />
            <Button Height="100" Width="250" HorizontalAlignment="Center" Content="B" />
            <Button Height="100" Width="250" HorizontalAlignment="Center" Content="C" />
            <Button Height="100" Width="250" HorizontalAlignment="Center" Content="D" />
            <Button Height="100" Width="250" HorizontalAlignment="Center" Content="E" />
            <Button Height="100" Width="250" HorizontalAlignment="Center" Content="F" />
            <Button Height="100" Width="250" HorizontalAlignment="Center" Content="G" />
            <Button Height="100" Width="250" HorizontalAlignment="Center" Content="H" />
            <Button Height="100" Width="250" HorizontalAlignment="Center" Content="I" />
        </Local:ExampleScrollPanel>-->
    </ScrollViewer>
</Window>

以及背后的代码:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;

namespace IScrollInfoExample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Buttons = new ObservableCollection<string>();
            IEnumerable<int> characterCodes = Enumerable.Range(65, 26);
            foreach (int characterCode in characterCodes) Buttons.Add(((char)characterCode).ToString().ToUpper());
        }

        public static readonly DependencyProperty ButtonsProperty = DependencyProperty.Register(nameof(Buttons), typeof(ObservableCollection<string>), typeof(MainWindow), null);

        public ObservableCollection<string> Buttons
        {
            get { return (ObservableCollection<string>)GetValue(ButtonsProperty); }
            set { SetValue(ButtonsProperty, value); }
        }
    }
}

先看XAML,可以看到手动添加的Button对象。运行应用程序,您应该会在面板中看到一些可垂直滚动的按钮……到目前为止,一切都很好。如果您在 MouseWheelUp 或 MouseWheelDown 方法中放置断点并滚动,您会注意到断点会立即被命中。

现在,如果您使用手动创建的按钮注释掉下方的ExampleScrollPanel 并取消注释上面的ItemsControl,您会看到滚动项目的功能已经消失。我的问题是“当自定义面板托管在 ItemsControl 元素中时,如何使滚动工作?”

请注意ScrollOwner 属性是使用自定义GetParentOfType&lt;T&gt; 方法填充的,该方法会成功找到适当的ScrollViewer,而不是导致此问题的原因。因此,我没有包含此方法的基于 VisualTreeHelper 的代码。

此外,我注意到一旦面板位于 ItemsControl 内,滚动条就不再出现,但我检查了面板的 extent 和 viewport 值似乎仍然成功更新。任何帮助将不胜感激。

【问题讨论】:

    标签: wpf panel scrollviewer itemscontrol


    【解决方案1】:

    ItemsControl 没有自己的ScrollViewer,它无法访问您为此目的提供的外部ScrollViewer。您需要使用ItemsControl.Template 属性将ScrollViewer 添加到ItemsControl 可以访问它的位置,如下所示:

    <ItemsControl ItemsSource="{Binding Buttons, RelativeSource={RelativeSource 
        AncestorType={x:Type Local:MainWindow}}}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Local:ExampleScrollPanel />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.Template>
    
            <ControlTemplate TargetType="{x:Type ItemsControl}">
    
                <!--Add the ScrollViewer here, inside the ControlTemplate-->
                <ScrollViewer CanContentScroll="True">
    
                    <!--Your items will be added here-->
                    <ItemsPresenter/> 
    
                </ScrollViewer>
    
            </ControlTemplate>
    
        </ItemsControl.Template>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Button Height="100" Width="250" HorizontalAlignment="Center" 
                    Content="{Binding}" />
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    注意,你必须将ScrollViewer的CanContentScroll property设置为True,以告知它你已经在面板中实现了IScrollInfo接口,并希望接管滚动功能。

    【讨论】:

    • 现在完美运行。非常感谢,我被这个问题困扰了很久!
    猜你喜欢
    • 2011-07-04
    • 2016-10-30
    • 1970-01-01
    • 2014-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-08
    • 1970-01-01
    相关资源
    最近更新 更多