【问题标题】:wpf rendering synchronizationwpf渲染同步
【发布时间】:2014-06-17 08:44:01
【问题描述】:

我有两个自定义 WPF 控件(带有自定义呈现逻辑)放在彼此之上(透明背景和东西)。在某些时候,我想重新绘制这些控件。我的第一个想法是打电话:

foreach(var control in Controls)
{
    control.InvalidateVisual();
}

但这并不完全奏效。我的意思是它确实强制渲染我的控件,但视觉更新不会同时发生。控件一个一个地更新,这看起来不好看并且可能导致混乱(如果一个控件显示更新的视觉效果,而另一个控件仍然显示旧的视觉效果)。 InvalidateVisual 方法的文档真的很差,但我想这是一个异步方法,这就是我遇到这个问题的原因。

那么问题来了:有没有办法同步多个控件的渲染?除了创建一个包含所有渲染逻辑的大型控件(我想避免这种情况)。

【问题讨论】:

    标签: c# wpf rendering


    【解决方案1】:

    您通常不应该使用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();
        }
    
    }    
    

    【讨论】:

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

    您需要暂停刷新控制,直到您完成所有这些无效的一种方式是这样的:

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

    其他方式见这里:How do I suspend painting for a control and its children?

    【讨论】:

    • 谢谢,这可能是我需要的。
    【解决方案3】:

    如果控件有一些绑定到视图模型的Data 属性,则另一种方法是使用数据绑定来触发呈现。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多