проблема с ContainerVisual.Transform

в моем пользовательском элементе управления у меня есть объект ContainerVisual и DrawingVisual под ним.

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

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

это не работает, и рисунок смещается. если вместо этого я устанавливаю преобразование объекта DrawingVisual, он работает, и прямоугольник отображается так, как он должен быть.

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

спасибо за любую помощь

РЕДАКТИРОВАТЬ: обновлен исходный код, чтобы показать полный код.


class MyControl : Control
{
    private readonly ContainerVisual container = new ContainerVisual();
    private readonly DrawingVisual drawing = new DrawingVisual();
    private Rect rect;

    private void RenderDrawing()
    {
        using (var c = drawing.RenderOpen())
        {
            var p = new Pen(new SolidColorBrush(Colors.Black), 1);

            c.DrawRectangle(null, p, new Rect(0, 0, rect.Width, rect.Height));
        }
    }

    protected override Size ArrangeOverride(Size s)
    {
        var h = Math.Max(0, s.Height - Padding.Top - Padding.Bottom);
        var w = Math.Max(0, s.Width - Padding.Left - Padding.Right);

        var r = new Rect(Padding.Left, Padding.Top, w, h);

        if (rect != r)
        {
            rect = r;

            container.Clip = new RectangleGeometry(rect);
            container.Transform = new TranslateTransform(rect.Left, rect.Top);

            // replace the line above with the following line to make it work
            // drawing.Transform = new TranslateTransform(rect.Left, rect.Top);

            RenderDrawing();
        }
        return s;
    }

    protected override Visual GetVisualChild(int index)
    {
        return container;
    }

    protected override Size MeasureOverride(Size s)
    {
        return new Size();
    }

    protected override int VisualChildrenCount
    {
        get { return 1; }
    }

    public MyControl()
    {
        container.Children.Add(drawing);
        AddVisualChild(container);
    }
}

<Window x:Class="MyApp.MyWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:c="clr-namespace:MyApp"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <Grid>
    <c:MyControl Padding="20" />
  </Grid>
</Window>

person akonsu    schedule 09.11.2010    source источник


Ответы (2)


Объяснение странного поведения обрезки

Теперь, когда вы разместили свой полный исходный код, я, наконец, смог увидеть то, что вы видели. Ваша проблема вовсе не в трансформации: она в клипе!

  • Если вы закомментируете оператор присваивания container.Clip, вы получите идентичные результаты независимо от того, поместите ли вы преобразование в container или drawing.
  • Если вы раскомментировали оператор присваивания container.Clip, область отсечения точно центрируется при преобразовании рисунка, но при преобразовании контейнера область отсечения смещается, так что видны только нижняя и правая линии прямоугольника (а не все те)

Это происходит потому, что геометрия, указанная для container.Clip, является частью контейнера, поэтому на нее влияет container.Transform, но не drawing.Transform:

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

Когда вы устанавливаете преобразование на чертеже:

  • Контейнер находится в (0,0) относительно окна (нулевое преобразование)
  • Область отсечения находится в (20,20) относительно окна (нулевое преобразование + RectangleGeometry)
  • Рисунок находится в (20,20) относительно окна (нулевое преобразование + TranslateTransform)
  • Прямоугольник находится в точке (20,20) относительно окна (нулевое преобразование + TranslateTransform + 0,0)

Когда вы устанавливаете преобразование в контейнере:

  • Контейнер находится в (20,20) относительно окна (TranslateTransform)
  • Область отсечения находится в (40,40) относительно окна (TranslateTransform + RectangleGeometry)
  • Рисунок находится в (20,20) относительно окна (TranslateTransform + нулевое преобразование)
  • Прямоугольник находится в (20,20) относительно окна (TranslateTransform + нулевое преобразование + 0,0)

Таким образом, ваша проблема не в том, что преобразование не происходит: преобразование также перемещает область отсечения, поэтому область отсечения больше не совпадает с прямоугольником, и вы можете видеть только две стороны прямоугольника.

Ответ дан для исходного кода (сохранен, поскольку содержит полезное объяснение)

Фактически, код, который вы разместили, никогда не использует «контейнер», поэтому все, что вы увидите, это пустой экран.

В вашем фактическом коде вы неправильно используете «контейнер», предотвращая возникновение событий в правильной последовательности, чтобы вызвать его преобразование и передать его на уровень MIL.

Помните, что когда визуал имеет набор преобразований, это не сам визуал, а визуальный родитель этого визуала, который на самом деле обрабатывает это преобразование. Например, если вы визуализируете страницу в XPS с помощью ReachFramework или выполняете тестирование нажатия, преобразование на самом внешнем визуальном элементе игнорируется.

Вы правильно поняли: если ваше визуальное дерево построено по всем правилам, не имеет значения, находится ли ваше преобразование на вашем «контейнере» или на вашем «рисунке».

Поскольку вы все равно используете Control, мне любопытно, почему вы просто не позволяете обычной системе компоновки на основе UIElement обрабатывать ваши потребности в компоновке.

Первое обновление (сохранено по той же причине)

Спасибо за исправление кода. Как я и подозревал: вы неправильно строите визуальное дерево. Если вы используете AddVisualChild, вы также должны переопределить GetVisualChild и VisuaChildrenCount. Это связано с тем, что Visual не хранит список дочерних элементов: это должен сделать подкласс (ваш класс). Что происходит:

  • Когда вы вызываете AddVisualChild, преобразование контейнера равно null, поэтому оно передается в MILCore.
  • Позже, когда вы изменяете преобразование контейнера, он использует свой родительский указатель (который был установлен в AddVisualChild), чтобы сигнализировать о том, что его данные преобразования должны быть обновлены. Это обновление требует, чтобы часть визуального дерева сканировалась с помощью GetVisualChild и VisualChildrenCount.
  • Поскольку вы не реализовали эти методы, эта часть обновления не работает.

Вы говорите, что вы «новичок в WPF». Знаете ли вы, что вы играете с некоторыми из самых низкоуровневых и эзотерических функций WPF, которые никогда не будут использоваться в большинстве обычных приложений WPF? Это эквивалентно началу изучения программирования с использованием машинного языка. Обычно для этой цели вы используете шаблоны с Path, Rectangle и т. д. Иногда вы можете перейти на более низкий уровень и использовать DrawingBrush с DrawingGroup, содержащей GeometryDrawings и т. д. Но вы почти никогда не дойдете до DrawingVisual и RenderOpen! Это можно сделать только в том случае, если у вас есть огромные чертежи, состоящие из миллионов отдельных элементов, и вы хотите обойти все накладные расходы на компоновку и структуру более высоких уровней для абсолютной максимальной производительности.

Самостоятельное управление визуальным деревом (AddVisualChild и т. д.) также является расширенной функцией. Я всегда рекомендую новичкам в WPF придерживаться UIElement и выше в течение первых нескольких месяцев, используя Control с шаблонами. Я рекомендую им использовать Path и другие подклассы фигур для своих рисунков, а также использовать VisualBrushes, когда необходимы расширенные эффекты рисования.

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

person Ray Burns    schedule 09.11.2010
comment
Рэй, спасибо за ответ. Я отредактировал свой код, я забыл добавить строку, которая добавляет контейнер в элемент управления. я новичок в WPF, поэтому я не знаю, как правильно использовать контейнер. не могли бы вы уточнить, пожалуйста? каковы правила, которым я должен следовать, чтобы правильно построить визуальное дерево? причина, по которой я делаю свой собственный макет, заключается в том, что, поскольку у меня есть визуальные элементы внутри моего элемента управления, реализация ArrangeOverride по умолчанию выдает исключение, поскольку она ожидает UIElement (если я правильно помню) вместо Visual. это то, что вы спрашиваете? - person akonsu; 10.11.2010
comment
да, это помогает. большое спасибо. я отредактировал свой вопрос и включил туда полный код. у меня были GetVisualChild и т. д., я их пропустил. проблема все еще возникает. если я устанавливаю преобразование для контейнера, он рисует только нижние правые края прямоугольника. я пытаюсь понять почему. причина, по которой я использую визуальные эффекты, заключается в том, что я пишу элемент управления диаграммой, который будет иметь несколько слоев с разными графиками в сетке, и пользователю нужно будет прокручивать диаграмму, поэтому производительность важна. я решил не использовать пути и т. д., потому что у них есть функциональность, которая мне не нужна, привязка данных и т. д. - person akonsu; 10.11.2010
comment
Хорошо, теперь можно посмотреть, что происходит. Я добавил обновление, объясняющее реальную проблему. - person Ray Burns; 11.11.2010

проблема с контейнером.Клип. так должно быть


container.Clip = new RectangleGeometry(new Rect(0, 0, w, h));
person akonsu    schedule 10.11.2010