在 WinRT/UWP 中创建自定义虚拟化控件

Zar*_*rat 6 c# winrt-xaml windows-store-apps uwp

在 WPF 中,FrameworkElement派生类可以通过AddVisualChild. 通过这种方式,可以实现您自己的虚拟化控件,这些控件仅生成可见的子项。您也可以在没有支持集合的情况下生成子项。

我想使用这种技术将几个控件从 WPF 移植到 Windows 10 UWP,但不清楚如何在 WinRT UI 中正确实现虚拟化。因为在我对问题的原始版本的评论中指出,询问实现技术对于 Stack Overflow 来说太笼统了,所以我创建了一个简约的示例来解释我试图涵盖的关键功能,它们是

  • 从数据模型动态生成子控件
  • 为生成的子控件执行自定义布局逻辑

我做了以下考虑:

  • 据我所知,自定义控件不可能像在 WPF 中那样管理自己的子控件
  • 我排除了Panel子类,因为当我的自定义控件(由其他人)使用时,很容易出错。面板子项应该由包含的 XAML 控制,而不是由面板控制。
  • 我排除了ItemsControl子类,因为不可能提供支持集合(数据虚拟化是必需的)

(请注意,排除它们可能是错误的,所以如果是,请指出。)

以下 WPF 代码创建了一个无限滚动的日期带,但只具体化当前可见的单元格。我有意将它保持得尽可能简约,因此没有多大意义,但它确实提供了我上面提到的两个关键功能,我需要了解如何在 WinRT 中实现。

所以我的问题是:是否可以在 WinRT 中创建这样一个控件,它动态地构建其子项以显示无限滚动带?请记住,它需要是自包含的,以便放置在任意页面上,而页面不必包含其他代码(否则它终将不是可重用的控件)。

如果您已经知道如何实现虚拟化并且可以给我一些提示,我会认为它足以概述如何在 WinRT 中完成它。

WPF 来源:

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;

namespace Sandbox
{
    public class DateBand : FrameworkElement
    {
        public static readonly DependencyProperty ScrollOffsetProperty = DependencyProperty.Register(
            nameof(ScrollOffset), typeof(double), typeof(DateBand), new FrameworkPropertyMetadata {
                AffectsMeasure = true,
            });

        public double ScrollOffset
        {
            get { return (double)GetValue(ScrollOffsetProperty); }
            set { SetValue(ScrollOffsetProperty, value); }
        }

        public static readonly DependencyProperty CellTemplateProperty = DependencyProperty.Register(
            nameof(CellTemplate), typeof(DataTemplate), typeof(DateBand), new FrameworkPropertyMetadata {
                AffectsMeasure = true,
            });

        public DataTemplate CellTemplate
        {
            get { return (DataTemplate)GetValue(CellTemplateProperty); }
            set { SetValue(CellTemplateProperty, value); }
        }

        private List<DateCell> _cells = new List<DateCell>();
        private DateTime _startDate = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1);
        private const double cSlotWidth = 5;
        private const double cSlotHeight = 20;

        protected override int VisualChildrenCount => _cells.Count;
        protected override Visual GetVisualChild(int index) => _cells[index];

        protected override Size MeasureOverride(Size availableSize)
        {
            int usedCells = 0;
            double desiredWidth = 0;
            double desiredHeight = 0;

            if (!double.IsPositiveInfinity(availableSize.Height))
            {
                var index = (int)Math.Floor(ScrollOffset);
                var offset = (index - ScrollOffset) * cSlotHeight;

                while (offset < availableSize.Height)
                {
                    DateCell cell;
                    if (usedCells < _cells.Count)
                    {
                        cell = _cells[usedCells];
                    }
                    else
                    {
                        cell = new DateCell();
                        AddVisualChild(cell);
                        _cells.Add(cell);
                    }
                    usedCells++;

                    var cellValue = _startDate.AddMonths(index);
                    cell._offset = offset;
                    cell._width = DateTime.DaysInMonth(cellValue.Year, cellValue.Month) * cSlotWidth;
                    cell.Content = cellValue;
                    cell.ContentTemplate = CellTemplate;
                    cell.Measure(new Size(cell._width, cSlotHeight));

                    offset += cSlotHeight;
                    index++;

                    desiredHeight = Math.Max(desiredHeight, offset);
                    desiredWidth = Math.Max(desiredWidth, cell._width);
                }
            }

            if (usedCells < _cells.Count)
            {
                for (int i = usedCells; i < _cells.Count; i++)
                    RemoveVisualChild(_cells[i]);

                _cells.RemoveRange(usedCells, _cells.Count - usedCells);
            }

            return new Size(desiredWidth, desiredHeight);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            foreach (var cell in _cells)
                cell.Arrange(new Rect(0, cell._offset, cell._width, cell.DesiredSize.Height));

            return finalSize;
        }
    }

    public class DateCell : ContentControl
    {
        internal double _offset;
        internal double _width;
    }

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Window_MouseWheel(object sender, MouseWheelEventArgs e)
        {
            Band.SetCurrentValue(DateBand.ScrollOffsetProperty, Band.ScrollOffset - e.Delta / Mouse.MouseWheelDeltaForOneLine);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

WPF XAML:

<Window x:Class="Sandbox.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:Sandbox"
        MouseWheel="Window_MouseWheel">
    <DockPanel>
        <ScrollBar x:Name="Scroll" Orientation="Vertical" Minimum="-24" Maximum="+24" ViewportSize="6"/>
        <local:DateBand x:Name="Band" ScrollOffset="{Binding ElementName=Scroll, Path=Value, Mode=OneWay}">
            <local:DateBand.CellTemplate>
                <DataTemplate>
                    <Border BorderBrush="Black" BorderThickness="1" Padding="5,2">
                        <TextBlock Text="{Binding StringFormat='yyyy - MMMM'}"/>
                    </Border>
                </DataTemplate>
            </local:DateBand.CellTemplate>
        </local:DateBand>
    </DockPanel>
</Window>
Run Code Online (Sandbox Code Playgroud)

小智 0

我会选择 TemplatedControl,您可以在 XAML 中定义您想要的控件的结构,这会更容易,

当我在 Codeplex 上构建可视化图表库时,我正是这样做的