【发布时间】:2016-03-01 21:04:48
【问题描述】:
在 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);
}
}
}
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>
【问题讨论】:
-
“据我所知,因为控件无法控制他们的孩子”——这也是我的理解,虽然我远非 Winrt 专家,所以可能有一些东西我忽略了。当我有与您在这里相同的目标(将带有自定义控件的 WPF 移植到 Winrt)时,我发现的唯一解决方案是将自定义控件实现为现有容器的子类(在我的情况下为
Grid)然后管理子元素作为完整的 UI 元素(与 WPF 实现中的Visual相反)。可以想象,这远非理想。 :( -
我会警告你,你所说的问题对于 Stack Overflow 来说是“太宽泛”的边界。它非常笼统。如果你能提供一个很好的minimal reproducible example 来准确显示你在做什么,以及一个特定的问题陈述,给出一个相对于你目前所做工作的狭隘目标,那将会有所帮助。我觉得这个问题很有用,但你可能需要改进它才能得到好的答案(假设你得到了答案)。
-
@PeterDuniho 我不知道为什么它“太宽泛”,我在问如何将 WPF 中的常见虚拟化技术转换为 WinRT 中可用的任何虚拟化技术。我已经编写了一个最小代码示例来表示 WPF 中的技术。除此之外,我不确定如何改进这个问题,如果你能更直接地指出我不清楚的地方,我可能会更详细地解释它。
-
“我不知道为什么它“太宽泛””——因为可能的答案太多了。一个全面的回复会太长,并且包含太多不同的场景,不适合 Stack Overflow 模型。至于您提供的代码示例,这很有帮助,但您确实应该提供一个很好的minimal reproducible example,展示您在 Winrt 版本中尝试过的内容,并准确解释您正在尝试的 特定 问题在那个版本中解决。
-
@PeterDuniho 我重新提出了这个问题,并基于示例代码。我无法为 WinRT 展示任何东西,因为问题的重点是首先弄清楚如何在 WinRT 中进行虚拟化。
标签: c# windows-store-apps winrt-xaml uwp