Wpf 当托管在ItemsControl中时,自定义IScrollInfo面板将失去滚动功能

Wpf 当托管在ItemsControl中时,自定义IScrollInfo面板将失去滚动功能,wpf,panel,scrollviewer,itemscontrol,Wpf,Panel,Scrollviewer,Itemscontrol,我正在尝试使用该界面创建自定义面板,但收效甚微。如果我在XAML的自定义面板中手动声明项,我可以让它工作,但是当我将它放入ItemsControl时,滚动功能停止。如果有人能告诉我哪里出了问题,我将不胜感激。下面是面板的代码(相当长,但很简单): using IScrollInfoExample.Extentions; using System; using System.Windows; using System.Windows.Controls; using System.Windows.C

我正在尝试使用该界面创建自定义面板,但收效甚微。如果我在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;
        }
    }
}
以及背后的代码:

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); }
        }
    }
}
使用System.Collections.Generic;
使用System.Collections.ObjectModel;
使用System.Linq;
使用System.Windows;
名称空间IScrollInfoExample
{
公共部分类主窗口:窗口
{
公共主窗口()
{
初始化组件();
按钮=新的ObservableCollection();
IEnumerable characterCodes=可枚举范围(65,26);
foreach(字符代码中的int characterCode)按钮。添加(((char)characterCode.ToString().ToUpper());
}
public static readonly dependencProperty buttonproperty=dependencProperty.Register(按钮名称)、类型(ObservableCollection)、类型(主窗口)、空值);
公共可观察收集按钮
{
获取{return(observateCollection)GetValue(buttonProperty);}
set{SetValue(按钮属性,值);}
}
}
}
首先查看XAML,您可以看到手动添加的
按钮
对象。运行应用程序,您应该会在面板中看到一些垂直滚动的按钮。。。到目前为止,一切顺利。如果在
mouseweelUp
mouseweelDown
方法中放置断点并滚动,您会注意到断点会立即被击中

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

请注意,
ScrollOwner
属性是使用自定义的
GetParentOfType
方法填充的,该方法成功地找到了相应的
ScrollViewer
,而不是此问题的原因。因此,我没有为此方法包含基于
VisualTreeHelper
的代码


此外,我注意到,一旦面板位于
ItemsControl
内,滚动条就不再出现,但我进行了检查,并且面板的
范围
视口
值似乎仍在成功更新。任何帮助都将不胜感激。

项目控件
没有自己的
ScrollViewer
,它无法访问您为此目的提供的外部控件。您需要使用
ItemsControl.Template
属性在
ItemsControl
可以访问的地方添加
ScrollViewer
,如下所示:

<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
的设置为
True
,以通知它您已在面板中实现了
IScrollInfo
界面,并希望接管滚动功能。

现在工作正常。非常感谢,这个问题我已经困扰了很久了!
<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>