ScrollViewer с Canvas, автоматическое изменение размера холста до содержимого, включая отрицательное пространство - PullRequest
1 голос
/ 22 ноября 2010

У меня есть холст в просмотре прокрутки. Чтобы иметь возможность использовать scrollviewer, я переопределил метод MeasureOverride в Canvas, чтобы вернуть размер всех дочерних элементов.

Это прекрасно работает, за исключением того, что холст может содержать только элементы в положительном пространстве для правильной работы прокрутки.

Я бы хотел, чтобы пользователь мог перетаскивать элементы, не ограничивая их нахождением на положительной стороне источника холста.

Итак, в основном я хочу иметь возможность располагать элементы в любом месте, например, в (-200, 10) или (500, -20), и иметь возможность прокрутки от самого левого элемента (-200) до самого правого (500) и сверху вниз.

Чтобы усложнить ситуацию, холст можно масштабировать с помощью LayoutTransform, и я хотел бы включить область просмотра в полосы прокрутки, чтобы полосы прокрутки учитывали не только минимальные / максимальные границы дочерних элементов, но и также мин / макс текущей области просмотра.

Кто-нибудь знает, как заставить это работать?

Ответы [ 2 ]

1 голос
/ 18 октября 2011

Сегодня я работал только над этой проблемой :) Рад поделиться с вами кодом, который работает:

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 };
    }

Теперь все, что вам нужно сделать, - это обернуть этот холст внутри средства просмотра прокрутки.

Надеюсь, это поможет!

0 голосов
/ 22 ноября 2010

Я думаю, что ScrollViewer предполагает, что источник будет в (0, 0) - в конце концов, нет никакого способа сказать это иначе; Canvas - единственная панель, которая знает об указанном и ненулевом происхождении.

Простым вариантом может быть забыть о ScrollViewer и реализовать свои собственные функции панорамирования - возможно, с помощью перетаскивания, или, возможно, с помощью большой кнопки «прокрутка влево» с левой стороны вида, кнопки «прокрутка вправо» справа и т. д. - и реализовать его с точки зрения преобразования содержимого.

Но если вам нужно придерживаться классической метафоры полосы прокрутки, я думаю, что вы можете создать свою собственную панель (или переопределить ArrangeOverride тоже, что равноценно) и сместить все позиции - сделать их на основе нуля. Поэтому, если у вас есть элемент в (20, -20), а другой в (0, 5), вам нужно будет сместить все вниз на 20, чтобы он поместился в пространстве с нулями; и когда вы раскладываете своих детей, первый будет в (20, 0), а второй в (0, 25).

Но сейчас прокрутка странная. Если пользователь прокручивается до самого дна и перетаскивает что-то за нижний край, вид остается на месте; то же самое с правом. Но если они прокручиваются до самого верха и перетаскивают что-то за верхний край, внезапно все прыгает вниз, потому что VerticalOffset ScrollViewer был нулевым и все еще нулевым, но ноль означает что-то другое из-за смещения вашего макета.

Возможно, вы сможете обойти странность прокрутки, связав HorizontalOffset и VerticalOffset. Если ваша ViewModel автоматически исправила эти свойства (и запустила PropertyChanged ) всякий раз, когда элемент управления перемещается «отрицательно», то вы могли бы сохранить прокрутку представления до того же логического содержимого, даже если вы сейчас говорите Движок макета WPF, что все где-то еще. То есть, ваша ViewModel будет отслеживать позицию логической прокрутки (ноль, прежде чем перетаскивать отрицательный элемент), и будет сообщать позицию прокрутки на основе нуля в ScrollViewer (поэтому после перетаскивания вы должны сказать ScrollViewer, что его VerticalOffset теперь 20 ).

...