C# wpf渲染同步

C# wpf渲染同步,c#,wpf,rendering,C#,Wpf,Rendering,我有两个自定义WPF控件(带有自定义渲染逻辑),它们相互重叠(透明背景和其他东西)。在某个时刻,我想重新绘制这些控件。我的第一个想法是: foreach(var control in Controls) { control.InvalidateVisual(); } 但这并不完全奏效。我的意思是它确实会强制呈现我的控件,但视觉更新不会同时发生。控件一个接一个地更新,这看起来不好看,可能会导致混淆(在一个控件显示更新的视觉效果,而另一个控件仍显示旧的视觉效果的情况下)。关于Invalid

我有两个自定义WPF控件(带有自定义渲染逻辑),它们相互重叠(透明背景和其他东西)。在某个时刻,我想重新绘制这些控件。我的第一个想法是:

foreach(var control in Controls)
{
    control.InvalidateVisual();
}
但这并不完全奏效。我的意思是它确实会强制呈现我的控件,但视觉更新不会同时发生。控件一个接一个地更新,这看起来不好看,可能会导致混淆(在一个控件显示更新的视觉效果,而另一个控件仍显示旧的视觉效果的情况下)。关于
InvalidateVisual
方法的文档非常糟糕,但我想这是一个异步方法,这就是我遇到这个问题的原因


所以问题是:有没有一种方法可以同步多个控件的渲染?除了创建一个包含所有呈现逻辑的巨型控件(我希望避免这种情况)。

您需要暂停刷新控件,直到您完成所有这些控件的无效操作为止,一种方法如下:

class DrawingControl
{
    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, bool wParam, Int32 lParam);

    private const int WM_SETREDRAW = 11; 

    public static void SuspendDrawing( Control parent )
    {
        SendMessage(parent.Handle, WM_SETREDRAW, false, 0);
    }

    public static void ResumeDrawing( Control parent )
    {
        SendMessage(parent.Handle, WM_SETREDRAW, true, 0);
        parent.Refresh();
    }
}

请参见此处了解其他方法:

通常不应使用
InvalidateVisual()

要更新保持相同大小的控件,您可以创建一个
DrawingGroup
,并在
OnRender()
期间将其放入可视化树中,然后您可以随时使用
DrawingGroup.Open()
更新图形组,WPF将更新UI

对于您的情况,这将类似于:

class Control1 : UIElement {
    DrawingGroup backingStore = new DrawingGroup();

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

        Render(); // put content into our backingStore
        drawingContext.DrawDrawing(backingStore);
    }
    private void Render() {            
        var drawingContext = backingStore.Open();
        Render(drawingContext);
        drawingContext.Close();            
    }
    private void Render(DrawingContext) {
        // put your existing drawing commands here.
    }

}

class Control2 : UIElement {
    DrawingGroup backingStore = new DrawingGroup();

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

        Render(); // put content into our backingStore
        drawingContext.DrawDrawing(backingStore);
    }
    private void Render() {            
        var drawingContext = backingStore.Open();
        Render(drawingContext);
        drawingContext.Close();            
    }
    private void Render(DrawingContext) {
        // put your existing drawing commands here.
    }

}


class SomeOtherClass {
    public void SomeOtherMethod() {
         control1.Render();
         control2.Render();
    }

}    

另一种方法是,如果控件具有一些绑定到viewmodel的
数据
属性,则使用数据绑定触发渲染

public static readonly DependencyProperty DataProperty 
    = DependencyProperty.Register("Data", typeof(DataType), typeof(MyView), 
       new FrameworkPropertyMetadata(default(DataType), 
           FrameworkPropertyMetadataOptions.AffectsRender));
public DataType Data
{
    get { return (DataType)GetValue(DataProperty); }
    set { SetValue(DataProperty, value); }
}
注意
AffectsRender
标志。然后,可以通过同时更新绑定属性来同时重新绘制所有控件:

foreach(var viewModel in ViewModels)
{
    viewModel.Data = ...;
}

谢谢,这可能是我需要的。谢谢,这是一个有趣的方法。您是否知道在非UI线程上调用
backingStore.Open()
是否安全?我对此表示怀疑,但您可以在另一个线程中创建图形,并将该图形添加到主线程中的DrawingGroup中。谢谢。我正在寻找方法来卸载一些沉重的渲染到背景线程。目前,我只使用后台线程来构造和冻结几何体对象,然后在UI上呈现这些对象,这仍然需要相当长的时间。在非UI线程上创建图形的可能性看起来很有希望,我会检查一下。