You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在WPF中等待渲染线程完成?.NET6+Caliburn.Micro场景

WPF中等待UI渲染完成后截图的解决方案

问题背景

在.NET 6 + Caliburn.Micro的项目中,点击添加按钮新增UI块后立即截图,会出现新块未渲染完成的情况。尝试过Dispatcher.Invoke/BeginInvoke无效,用ContextIdle优先级虽能解决但存在风险,需要更可靠的渲染完成回调方案。

原代码参考

UI标记(XAML)

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:cal="http://www.caliburnproject.org"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800"
        >
    <Window.DataContext>
        <local:MainWindowContext/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <ItemsControl ItemsSource="{Binding Blocks}" Grid.Column="0">
            <ItemsControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Vertical">
                        <Grid Height="30">
                            <TextBlock Text="{Binding Text}" 
                               HorizontalAlignment="Left"  
                               VerticalAlignment="Center" 
                               Padding="5" 
                               Margin="0,0,0,10"/>
                            <Rectangle Fill="Green" Opacity="0.5"/>
                        </Grid>
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
            <ItemsControl.ItemsPanel >
                <ItemsPanelTemplate>
                    <StackPanel IsItemsHost="True" Orientation="Vertical"/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
        <Rectangle Fill="Red" Grid.Column="1" Width="1"/>
        <StackPanel Grid.Column="2" Orientation="Vertical">
            <Button Width="100" Margin="20" Content="AddBlock" cal:Message.Attach="Add"/>
            <Button Width="100" Margin="20" Content="RemoveBlock" cal:Message.Attach="Remove"/>
        </StackPanel>
    </Grid>
</Window>

ViewModel代码

using Caliburn.Micro;
using ShadowBot.UIAutomation.Utilities;
using System;
using System.Windows;

namespace WpfApp1
{
    public class MainWindowContext : Screen
    {
        public BindableCollection<Block> Blocks { get; set; } = new BindableCollection<Block>();

        public MainWindowContext()
        {
            Blocks.Add(new Block() { Text = DateTime.Now.ToString() });
        }

        public void Add()
        {
            Blocks.Add(new Block() { Text = DateTime.Now.ToString() });
            NotifyOfPropertyChange(nameof(Blocks));

            Application.Current.Dispatcher.Invoke(() =>
            {
                using (var bitmap = SnippingTool.Screenshots())
                {
                    bitmap.Save("D:\\Desktop\\temp\\test.png", System.Drawing.Imaging.ImageFormat.Png);
                }
            });
        }

        public void Remove()
        {
            if (Blocks.Count > 0)
            {
                Blocks.RemoveAt(Blocks.Count - 1);
                NotifyOfPropertyChange(nameof(Blocks));
            }
        }
    }

    public class Block : PropertyChangedBase
    {
        private string _text;

        public string Text
        {
            get => _text;
            set
            {
                _text = value;
                NotifyOfPropertyChange(nameof(Text));
            }
        }
    }
}

解决方案

方案1:使用Dispatcher.InvokeAsync指定Render优先级

将截图逻辑放在DispatcherPriority.Render优先级的异步调用中,确保UI渲染完成后再执行截图。此方法无需修改View,仅需调整ViewModel的Add方法:

public async void Add()
{
    var newBlock = new Block() { Text = DateTime.Now.ToString() };
    Blocks.Add(newBlock);
    NotifyOfPropertyChange(nameof(Blocks));

    // 等待UI完成渲染
    await Application.Current.Dispatcher.InvokeAsync(() => {}, DispatcherPriority.Render);

    using (var bitmap = SnippingTool.Screenshots())
    {
        bitmap.Save("D:\\Desktop\\temp\\test.png", System.Drawing.Imaging.ImageFormat.Png);
    }
}

方案2:监听Item的Loaded事件(MVVM友好)

通过Caliburn.Micro的消息机制,在新增Item的UI元素加载完成后通知ViewModel执行截图。

  1. 修改XAML的ItemTemplate,给根元素添加Loaded事件绑定:
<DataTemplate>
    <StackPanel Orientation="Vertical" cal:Message.Attach="[Event Loaded] = [Action OnBlockLoaded($dataContext)]">
        <Grid Height="30">
            <TextBlock Text="{Binding Text}" 
                       HorizontalAlignment="Left"  
                       VerticalAlignment="Center" 
                       Padding="5" 
                       Margin="0,0,0,10"/>
            <Rectangle Fill="Green" Opacity="0.5"/>
        </Grid>
    </StackPanel>
</DataTemplate>
  1. 在ViewModel中添加OnBlockLoaded方法,并记录最新添加的Block,确保只对刚新增的块触发截图:
private Block _lastAddedBlock;

public void Add()
{
    _lastAddedBlock = new Block() { Text = DateTime.Now.ToString() };
    Blocks.Add(_lastAddedBlock);
    NotifyOfPropertyChange(nameof(Blocks));
}

public void OnBlockLoaded(Block block)
{
    // 仅当加载的是刚新增的块时执行截图
    if (block == _lastAddedBlock)
    {
        using (var bitmap = SnippingTool.Screenshots())
        {
            bitmap.Save("D:\\Desktop\\temp\\test.png", System.Drawing.Imaging.ImageFormat.Png);
        }
        _lastAddedBlock = null;
    }
}

方案3:强制触发布局更新

在添加Block后,强制让ItemsControl完成布局和渲染,再执行截图:

public void Add()
{
    Blocks.Add(new Block() { Text = DateTime.Now.ToString() });
    NotifyOfPropertyChange(nameof(Blocks));

    // 获取ItemsControl实例(需通过View绑定或Caliburn的ViewAware获取)
    var mainWindow = (MainWindow)GetView();
    var itemsControl = mainWindow.FindName("BlocksItemsControl") as ItemsControl;

    itemsControl.UpdateLayout();
    itemsControl.ApplyTemplate();

    using (var bitmap = SnippingTool.Screenshots())
    {
        bitmap.Save("D:\\Desktop\\temp\\test.png", System.Drawing.Imaging.ImageFormat.Png);
    }
}

注:需给XAML中的ItemsControl添加Name属性:Name="BlocksItemsControl"

方案对比

  • 方案1:简单易实现,无需修改View,适合大多数场景。
  • 方案2:MVVM更友好,精准监听新增元素的加载状态,避免不必要的截图触发。
  • 方案3:直接操作UI元素,适合需要精确控制布局的场景,但需获取UI实例,耦合度略高。

内容的提问来源于stack exchange,提问作者huojian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 03:15:00