【问题标题】:How to know if window/GUI has been updated [duplicate]如何知道窗口/GUI是否已更新[重复]
【发布时间】:2021-02-04 08:09:31
【问题描述】:

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

问题: 目前,我使用新数据更新视图模型,然后使用 Task.Delay(...) 给应用程序一些时间在视图上呈现新内容。但有时如果延迟不够,我会得到以前数据集的图片,我可以增加延迟时间以减少它发生的频率,但这反过来会不必要地减慢程序的速度。我基本上是在寻找一种聪明的方法来检查视图是否已使用新数据集呈现,而不是延迟延迟。

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

【问题讨论】:

标签: c# wpf mvvm


【解决方案1】:

简短回答:是的,您可以通过在 Dispatcher 线程空闲时调用您的图片保存方法来执行此操作,方法是将其优先级设置为 DispatcherPriority.ApplicationIdle

详细回答:这是一个示例,展示了这一点。我这里有一个应用程序,当您单击按钮时会更新视图模型的文本属性,但它需要几秒钟来更新绑定到它的控件,因为文本很大。

当我知道正在尝试显示新数据时,我发出一个 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));
    }
}

MainWindow.xaml

<Window x:Class="_65951670.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition/>
        </Grid.RowDefinitions>
        <Grid Background="LightSalmon">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <TextBox IsReadOnly="True" Text="{Binding ControlText,UpdateSourceTrigger=PropertyChanged}"  TextWrapping="Wrap" Margin="5" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Visible"/>
            <Button Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Padding="15,5" Content="Update Above With Lots Of Text" Click="Button_Click"/>
        </Grid>
        <Grid Grid.Row="1">
            <TextBox Text="{Binding Messages}" TextWrapping="Wrap" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Disabled" Margin="5" IsReadOnly="True"/>
        </Grid>
    </Grid>
</Window>

MainWindow.xaml.cs

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-02
    • 2011-07-11
    • 1970-01-01
    • 1970-01-01
    • 2013-12-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多