构建一个基本上是滚动树视图的自定义控件。稍后,它必须以某种方式监视其项目并与之交互。 (例如,它必须找到对应于某种名称路径的元素)。由于所有项目都具有相同的大小,因此这也是虚拟化的主要案例;)
这些元素应该支持基本的交互,例如响应(视觉上)让鼠标悬停在它们上面,这并不奇怪。
实际的树类有一个Items属性(它是一个IList)和两个滚动条作为它的可视子项和覆盖测量,排列和渲染(有两个 - 希望 - 无关的函数被遗漏):
public sealed partial class SyncTree : Control
{
ScrollBar verticalScrollbar = new ScrollBar();
ScrollBar horizontalScrollbar = new ScrollBar();
private VisualCollection visualchildren;
protected override int VisualChildrenCount
{
get
{
return visualchildren.Count + Items.Count;
}
}
protected override Visual GetVisualChild(int index)
{
if (index < visualchildren.Count)
return visualchildren[index];
else
return Items[index - visualchildren.Count];
}
private Size MeasuredSize = new Size(0, 0);
protected override Size MeasureOverride(Size availableSize)
{
UpdateMeasuredSize();
return availableSize;
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
ArrangeItems(ArrangeScrollbars(arrangeBounds));
return arrangeBounds;
}
private void ArrangeItems(Size arrangeBounds)
{
var y = Padding.Top - verticalScrollbar.Value;
foreach (var item in Items)
{
item.Arrange(new Rect(Padding.Left - horizontalScrollbar.Value, y, item.Width, item.Height));
y += item.Height;
y += RootNodeSpacing;
}
}
protected override void OnRender(DrawingContext context)
{
//kill the ugly background-colored patch between the scrollbars
if (verticalScrollbar.IsVisible && horizontalScrollbar.IsVisible) context.DrawRectangle(verticalScrollbar.Background, null, new Rect(RenderSize.Width - verticalScrollbar.Width, RenderSize.Height - horizontalScrollbar.Height, verticalScrollbar.Width, horizontalScrollbar.Height));
context.DrawRectangle(Background, null, new Rect(RenderSize));
}
}
这些项目有自己的类型:
public class SyncTreeItem : Control
{
protected override Size MeasureOverride(Size constraint)
{
var size = new Size(FormattedText.Width + FormattedText.Height + 5, FormattedText.Height);
if (IsExpanded)
{
foreach (var item in Items)
{
item.Measure(new Size(constraint.Width - Indent, constraint.Height - size.Height));
size.Height += item.DesiredSize.Height;
size.Width = Math.Max(size.Width, item.DesiredSize.Width + Indent);
}
}
Height = size.Height;
Width = size.Width;
return size;
}
protected override Size ArrangeOverride(Size arrangeBounds)
{
if (IsExpanded)
{
var y = FormattedText.Height;
foreach (var item in Items)
{
item.Arrange(new Rect(Indent, y, item.Width, item.Height));
y += item.Height;
}
return new Size(arrangeBounds.Width, y);
}
else return new Size(arrangeBounds.Width, FormattedText.Height);
}
protected override void OnRender(DrawingContext context)
{
context.DrawRectangle(Brushes.Transparent, null, new Rect(new Point(0, 0), RenderSize));
context.PushClip(new RectangleGeometry(new Rect(RenderSize)));
var ft = FormattedText;
context.DrawImage(Icon, new Rect(0, 0, ft.Height, ft.Height));
context.DrawText(ft, new Point(ft.Height + 5, 0));
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
Console.WriteLine("got mousedown!");
}
}
我的主要问题是,子元素似乎没有获得mousedown事件(或其他鼠标事件)。也许我没有获得整个Visual Tree和Routed Event的东西 - 但是当我点击该项目时我从未看到控制台输出。在树上,我可以同时获得previewmousdown和mousedown事件......
此外,内部控件不会剪切到内部显示区域(好吧,为什么要这样),我可以通过在安排滚动条和覆盖GetLayoutClip之前安排它们来解决这个问题。或者,我可以尝试让孩子们知道他们的水平偏移,并简单地将他们排列在一个不适合的矩形中。我确实想知道是否有更好的方法,但是:)
答案 0 :(得分:2)
确保为每个孩子的视觉效果调用AddVisualChild
。仅仅覆盖VisualChildrenCount和GetVisualChild是不够的;该框架还需要了解这种关系。 (也许你在“不相关的功能”中这样做。)
虚拟化实际上很难实现,而不是尝试从头开始构建它,您最好尝试使用VirtualizingPanel的功能。看起来您正在垂直堆叠元素并在每个深度缩进子项。您可以使用逻辑将分层数据展平为存储原始节点和嵌套级别的单个列表。然后,您可以将该列表绑定到具有VirtualizingStackPanel的ItemsControl,并根据嵌套级别设置每个容器的左边距。
如果您最终实现自己的虚拟化控件,则可能需要直接从VirtualizingPanel继承。以下是一系列博客文章,介绍了实施虚拟化面板所需执行的所有操作:http://blogs.msdn.com/b/dancre/archive/tags/virtualizingtilepanel/