我在滚动查看器中有一个画布。 为了能够使用scrollviewer,我重写了Canvas的MeasureOverride方法以返回所有子节点的大小。
这很好用,除了画布只能包含正空间中的项目以使scrollviewer正常工作。
我希望用户能够拖动元素而不必限制它们必须位于画布原点的正面。
基本上我希望能够将元素定位在任何位置,例如(-200,10)或(500,-20),并且能够从最左边的元素(-200)滚动到最右边(500),从上到下。
使问题复杂化可以使用LayoutTransform缩放画布,并且我想在滚动条中包含视图区域,因此不仅滚动条会考虑子画面的最小/最大边界,而且也是当前视图区域的最小值/最大值。
有人知道如何使这项工作吗?
答案 0 :(得分:1)
今天我只处理这个问题:) 很高兴分享有效的代码:
public void RepositionAllObjects(Canvas canvas)
{
adjustNodesHorizontally(canvas);
adjustNodesVertically(canvas);
}
private void adjustNodesVertically(Canvas canvas)
{
double minLeft = Canvas.GetLeft(canvas.Children[0]);
foreach (UIElement child in canvas.Children)
{
double left = Canvas.GetLeft(child);
if (left < minLeft)
minLeft = left;
}
if (minLeft < 0)
{
minLeft = -minLeft;
foreach (UIElement child in canvas.Children)
Canvas.SetLeft(child, Canvas.GetLeft(child) + minLeft);
}
}
private void adjustNodesHorizontally(Canvas canvas)
{
double minTop = Canvas.GetTop(canvas.Children[0]);
foreach (UIElement child in canvas.Children)
{
double top = Canvas.GetTop(child);
if (top < minTop)
minTop = top;
}
if (minTop < 0)
{
minTop = -minTop;
foreach (UIElement child in canvas.Children)
Canvas.SetTop(child, Canvas.GetTop(child) + minTop);
}
}
现在调用RepositionAllObjects
方法在需要时重新对齐对象。
当然,您需要拥有自己的Canvas
画布。这个方法是必需的(如果你愿意,你可以调整它):
public static Rect GetDimension(UIElement element)
{
Rect box = new Rect();
box.X = Canvas.GetLeft(element);
box.Y = Canvas.GetTop(element);
box.Width = element.DesiredSize.Width;
box.Height = element.DesiredSize.Height;
return box;
}
protected override Size MeasureOverride(Size constraint)
{
Size availableSize = new Size(double.PositiveInfinity, double.PositiveInfinity);
double minX = 900000; //some dummy high number
double minY = 900000; //some dummy high number
double maxX = 0;
double maxY = 0;
foreach (UIElement element in this.Children)
{
element.Measure(availableSize);
Rect box = GetDimension(element);
if (minX > box.X) minX = box.X;
if (minY > box.Y) minY = box.Y;
if (maxX < box.X + box.Width) maxX = box.X + box.Width;
if (maxY < box.Y + box.Height) maxY = box.Y + box.Height;
}
if (minX == 900000) minX = 0;
if (minY == 900000) minY = 0;
return new Size { Width = maxX - minX, Height = maxY - minY };
}
现在,您需要做的就是将此画布包装在scrollviewer中。
希望它有所帮助!
答案 1 :(得分:0)
我认为ScrollViewer假定原点位于(0,0) - 毕竟,没有办法告诉它; Canvas是唯一知道指定和非零起源的面板。
一个简单的选择可能是忘记ScrollViewer,并实现自己的平移功能 - 可能是拖动,或者可能在视图左侧有一个大的“向左滚动”按钮,“向右滚动”按钮在右边等等 - 并根据对内容的转换来实现它。
但是如果你需要坚持使用经典的滚动条比喻,我认为你可以创建自己的面板(或者覆盖ArrangeOverride,这相当于同样的事情)并偏移所有位置 - 使它们从零开始。因此,如果在(20,-20)处有一个元素而在(0,5)处有另一个元素,则需要将所有内容向下偏移20,以使其适合从零开始的空间;当你布置你的孩子时,第一个将在(20,0)和第二个在(0,25)。
但现在滚动很奇怪。如果用户一直滚动到底部,并从底部边缘拖出一些东西,则视图保持不变;与右边相同。但如果它们一直滚动到顶部,并从顶部边缘拖出一些东西,突然一切都向下跳,因为ScrollViewer的VerticalOffset之前为零并且仍然为零,但零意味着因布局偏移而有所不同。 / p>
您可以通过绑定HorizontalOffset和VerticalOffset来绕过滚动怪异。如果ViewModel在控件移动“否定”时自动修复了这些属性(并触发了PropertyChanged),那么即使您现在正在告诉WPF的布局,您也可以将视图滚动到相同的逻辑内容引擎,一切都在别的地方。即,您的ViewModel将跟踪逻辑滚动位置(零,在拖动元素负数之前),并将基于零的滚动位置报告给ScrollViewer(因此在拖动之后,您将告诉ScrollViewer其VerticalOffset现在为20 )。