C# 如何知道窗口/GUI是否已更新

C# 如何知道窗口/GUI是否已更新,c#,wpf,mvvm,C#,Wpf,Mvvm,背景: 我有一个应用程序,可以收集数据,进行计算,并在窗口中以图形的形式向用户显示。对于每一组数据,我都会拍摄一张窗口的照片,并将其以.png格式存储在硬盘上,以便用户稍后返回并检查结果 问题: 目前,我使用新数据更新viewmodel,然后有一个Task.Delay(…),以便给应用程序一些时间在视图上呈现新内容。但有时,如果延迟不够,我会得到上一个数据集的图片,我可以增加延迟时间,使其发生的频率降低,但这反过来会使程序速度减慢。我基本上是在寻找一种智能的方法来检查视图是否使用新的数据集进行了

背景: 我有一个应用程序,可以收集数据,进行计算,并在窗口中以图形的形式向用户显示。对于每一组数据,我都会拍摄一张窗口的照片,并将其以.png格式存储在硬盘上,以便用户稍后返回并检查结果

问题: 目前,我使用新数据更新viewmodel,然后有一个Task.Delay(…),以便给应用程序一些时间在视图上呈现新内容。但有时,如果延迟不够,我会得到上一个数据集的图片,我可以增加延迟时间,使其发生的频率降低,但这反过来会使程序速度减慢。我基本上是在寻找一种智能的方法来检查视图是否使用新的数据集进行了渲染,而不是有一个愚蠢的延迟


我已查看Window.ContentRendered事件。但这似乎只在第一次渲染窗口时触发,因此如果我想使用该窗口,我必须关闭并为每张图片重新创建一个新窗口,这对我来说是不必要的开销。我需要类似的东西,每次重新渲染时都会触发,或者以其他方式知道视图是否准备好拍摄图片?

简短回答:是,您可以通过在Dispatcher线程空闲时调用图片保存方法来实现这一点,方法是将优先级设置为
DispatcherPriority.applicationDLE

详细回答:下面是一个示例,说明了这一点。我这里有一个应用程序,当你点击一个按钮时,它会更新viewmodel的文本属性,但是它需要几秒钟来更新绑定到它的控件,因为文本太大了

当我知道要显示新数据时,我发出一个Dispatcher命令,等待UI空闲,然后再执行以下操作:

Dispatcher.Invoke((Action)(() => { // take your picture here }), DispatcherPriority.ApplicationIdle);
MainWindowViewModel.cs

public class MainWindowViewModel : INotifyPropertyChanged
{
    private string messages;
    private string controlText;
    public MainWindowViewModel Parent { get; private set; }
    public string Messages { get => this.messages; set { this.messages = value; OnPropertyChanged(); } }
    public string ControlText { get => this.controlText; set { this.controlText = value; OnPropertyChanged(); } }

    public void UpdateWithNewData()
    {
        var strBuilder = new StringBuilder();
        for (int i = 0; i < 100000; i++)
        {
            strBuilder.AppendLine($"{DateTime.Now:HH:mm:ss.ffffff}");
        }

        // This will update the TextBox that is bound to this property, 
        // but it will take awhile because the text is HUUUUGE.
        this.ControlText = strBuilder.ToString();
    }

    public MainWindowViewModel()
    {
        this.ControlText = "This area will take a while to render when you click the button below.";
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
public partial class MainWindow : Window
{
    private MainWindowViewModel viewModel;
    public MainWindow()
    {
        InitializeComponent();
        viewModel = new MainWindowViewModel();
        this.DataContext = viewModel;
        this.viewModel.PropertyChanged += ViewModel_PropertyChanged;
    }

        private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(this.viewModel.ControlText))
            {
                var sw = new Stopwatch();
                sw.Start();
                this.viewModel.Messages += $"Property Changed: {DateTime.Now:HH:mm:ss.ffffff}\n";

                // If you got here, you know that the DataContext has changed, but you don't know when it will be done rendering.
                // So use Dispatcher and wait for it to be idle before performing another action.
                // Put your picture-saving method inside of the 'Action' here.
                Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (Action)(() => 
                { 
                    this.viewModel.Messages += $"UI Became Idle At: {DateTime.Now:HH:mm:ss.ffffff}\nIt took {sw.ElapsedMilliseconds} ms to render, Take Picture Now!"; 
                }));
            }
        }
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.viewModel.UpdateWithNewData();
    }
}
另一种选择可能是你自己。这将是更多的工作,但你会有更多的控制。
public partial class MainWindow : Window
{
    private MainWindowViewModel viewModel;
    public MainWindow()
    {
        InitializeComponent();
        viewModel = new MainWindowViewModel();
        this.DataContext = viewModel;
        this.viewModel.PropertyChanged += ViewModel_PropertyChanged;
    }

        private void ViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if (e.PropertyName == nameof(this.viewModel.ControlText))
            {
                var sw = new Stopwatch();
                sw.Start();
                this.viewModel.Messages += $"Property Changed: {DateTime.Now:HH:mm:ss.ffffff}\n";

                // If you got here, you know that the DataContext has changed, but you don't know when it will be done rendering.
                // So use Dispatcher and wait for it to be idle before performing another action.
                // Put your picture-saving method inside of the 'Action' here.
                Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (Action)(() => 
                { 
                    this.viewModel.Messages += $"UI Became Idle At: {DateTime.Now:HH:mm:ss.ffffff}\nIt took {sw.ElapsedMilliseconds} ms to render, Take Picture Now!"; 
                }));
            }
        }
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        this.viewModel.UpdateWithNewData();
    }
}