Подтвердить что ты не робот

Как я могу вручную передать обработанный владельцем элемент управления WPF для обновления/перерисовки без выполнения меры или организации проходов?

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

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

Опять же, мы просто ищем простой способ сообщить контроллеру, что ему нужно перезапустить OnRender. Я видел один "взлом", в котором вы создаете новый DependencyProperty и регистрируете его с помощью "AffectsRender", который вы только что установили для некоторого значения, когда хотите обновить элемент управления, но меня больше интересует, что происходит внутри по умолчанию для этих свойств: то, что они называют, влияет на это поведение.


Обновление:

Ну, похоже, что такого вызова нет, так как даже флаг AffectsRender по-прежнему вызывает уступку Arrange внутри (в соответствии с нижеприведенным ответом CodeNaked), но я опубликовал второй ответ, который показывает встроенное поведение а также обход, чтобы подавить код прохода макета от запуска с простым размером NULL как флаг. См. Ниже.

4b9b3361

Ответ 1

Хорошо, я отвечаю на это, чтобы показать людям, почему CodeNaked ответ правильный, но со звездочкой, если хотите, а также обеспечить обход. Но в хорошем СО-гражданстве я все еще отмечаю его, как ответил, так как его ответ привел меня сюда.

Обновление: с тех пор я перевел принятый ответ по двум причинам. Во-первых, я хочу, чтобы люди знали, что есть решение для этого (большинство людей только читают принятый ответ и ходят) и два, учитывая, что у него репутация 25 тыс., Я не думаю, что он возражал бы, если бы я взял его обратно!:)

Вот что я сделал. Чтобы проверить это, я создал этот подкласс...

public class TestPanel : DockPanel
{
    protected override Size MeasureOverride(Size constraint)
    {
        System.Console.WriteLine("MeasureOverride called for " + this.Name + ".");
        return base.MeasureOverride(constraint);
    }

    protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
    {
        System.Console.WriteLine("ArrangeOverride called for " + this.Name + ".");
        return base.ArrangeOverride(arrangeSize);
    }

    protected override void OnRender(System.Windows.Media.DrawingContext dc)
    {
        System.Console.WriteLine("OnRender called for " + this.Name + ".");
        base.OnRender(dc);
    }

}

... который я написал так (обратите внимание, что они вложены):

<l:TestPanel x:Name="MainTestPanel" Background="Yellow">

    <Button Content="Test" Click="Button_Click" DockPanel.Dock="Top" HorizontalAlignment="Left" />

    <l:TestPanel x:Name="InnerPanel" Background="Red" Margin="16" />

</l:TestPanel>

Когда я изменил размер окна, я получил это...

MeasureOverride called for MainTestPanel.
MeasureOverride called for InnerPanel.
ArrangeOverride called for MainTestPanel.
ArrangeOverride called for InnerPanel.
OnRender called for InnerPanel.
OnRender called for MainTestPanel.

но когда я вызывал InvalidateVisual в 'MainTestPanel' (в событии 'Click'), я получил это вместо...

ArrangeOverride called for MainTestPanel.
OnRender called for MainTestPanel.

Обратите внимание, что ни один из переопределений измерений не был вызван, и был вызван только ArrangeOverride для внешнего управления.

Это не идеально, как если бы у вас очень тяжелый расчет внутри ArrangeOverride в вашем подклассе (который, к сожалению, мы делаем), который все еще выполняется (повторно), но, по крайней мере, дети не попадают в ту же участь.

Однако, если вы знаете, что ни один из дочерних элементов управления не имеет свойства с установленным битом AffectsParentArrange (опять же, что мы делаем), вы можете пойти лучше и использовать Nullable Size в качестве флага для подавления логики ArrangeOverride из повторного ввода, за исключением случаев, когда это необходимо...

public class TestPanel : DockPanel
{
    Size? arrangeResult;

    protected override Size MeasureOverride(Size constraint)
    {
        arrangeResult = null;
        System.Console.WriteLine("MeasureOverride called for " + this.Name + ".");
        return base.MeasureOverride(constraint);
    }

    protected override System.Windows.Size ArrangeOverride(System.Windows.Size arrangeSize)
    {
        if(!arrangeResult.HasValue)
        {
            System.Console.WriteLine("ArrangeOverride called for " + this.Name + ".");
            // Do your arrange work here
            arrangeResult = base.ArrangeOverride(arrangeSize);
        }

        return arrangeResult.Value;
    }

    protected override void OnRender(System.Windows.Media.DrawingContext dc)
    {
        System.Console.WriteLine("OnRender called for " + this.Name + ".");
        base.OnRender(dc);
    }

}

Теперь, если что-то не требуется для повторного выполнения логики организации (в качестве вызова метода MeasureOverride), вы получаете только OnRender, и если вы хотите явно принудительно установить логику Arrange, просто уберите размер, вызовите InvalidateVisual и Bob your дядя!:)

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

Ответ 2

К сожалению, вы должны вызвать InvalidateVisual, который вызывает InvalidateArrange внутри страны. Метод OnRender вызывается как часть фазы аранжировки, поэтому вам нужно сообщить WPF о перестановке элемента управления (что делает InvalidateArrange) и что ему нужно перерисовать (что делает InvalidateVisual).

Опция FrameworkPropertyMetadata.AffectsRender просто сообщает WPF вызвать InvalidateVisual при изменении связанного свойства.

Если у вас есть элемент управления (позвоните в этот MainControl), который переопределяет OnRender и содержит несколько элементов управления потомками, то вызов InvalidateVisual может потребовать, чтобы элементы управления потомками были перегруппированы или даже переопределены. Но я считаю, что WPF имеет оптимизацию на месте, чтобы предотвратить перегруппировку элементов управления потомками, если их свободное пространство не изменилось.

Вы можете обойти это, переместив свою визуализацию на отдельный элемент управления (скажем, NestedControl), который будет визуальным дочерним элементом MainControl. MainControl может добавить это как визуальный ребенок автоматически или как часть его ControlTemplate, но он должен быть самым младшим в z-порядке. Затем вы можете открыть метод типа InvalidateNestedControl в MainControl, который вызовет InvalidateVisual в NestedControl.

Ответ 4

Вы не должны вызывать InvalidateVisual(), если размер вашего элемента управления не изменится, и даже тогда есть другие способы вызвать повторную компоновку.

Эффективно обновлять визуальное представление элемента управления без изменения его размера. Используйте DrawingGroup. Вы создаете DrawingGroup и помещаете его в DrawingContext во время OnRender(), а затем в любое время после этого вы можете Open() DrawingGroup изменить его команды визуального рисования, и WPF автоматически и эффективно повторно отобразит эту часть UI. (вы также можете использовать эту технику с помощью RenderTargetBitmap, если вы предпочитаете иметь растровое изображение, которое вы можете внести в инкрементные изменения, а не перерисовывать каждый раз)

Это выглядит так:

DrawingGroup backingStore = new DrawingGroup();

protected override void OnRender(DrawingContext drawingContext) {      
    base.OnRender(drawingContext);            

    Render(); // put content into our backingStore
    drawingContext.DrawDrawing(backingStore);
}

// I can call this anytime, and it'll update my visual drawing
// without ever triggering layout or OnRender()
private void Render() {            
    var drawingContext = backingStore.Open();
    Render(drawingContext);
    drawingContext.Close();            
}

private void Render(DrawingContext drawingContext) {
    // put your render code here
}