C# 标签值发生变化,但 UI 上的文本没有 [关闭]

Posted

技术标签:

【中文标题】C# 标签值发生变化,但 UI 上的文本没有 [关闭]【英文标题】:C# Label value changes but text on UI doesn't [closed] 【发布时间】:2021-05-07 13:56:49 【问题描述】:

我有一个 Winforms 程序,我在其中异步地从数据库中检索一些数据,并将其显示在 UI 上。这是我用来在标签上显示User & Scan iinformation 的函数:

private async Task PopulateLastScanAsync()
        
            var scan = await Access.GetLastScanAsync(); // retrieve User/Scan data from database
            if (scan.Item4) // Ignore this stuff
            
                var result = await Access.GetUserAsync(scan.Item1.UserCode);
                if (result.Item2)
                
                    // Display logic is here
                    User usr = result.Item1; // Display on UI
                    LastScannedName.Text = $"usr.FirstName usr.LastName";
                    LastScannedUserId.Text = $"usr.UserCode";
                    LastScanTime.Text = scan.Item1.ScanTime.ToString("g");
                    LastScanImage.Image = Manager.ConvertBytesToImage(usr.Picture);
                    // Read following comments after going thru the problem
                    // If here, I debug, like Console.WriteLine(LastScannedName.Text), it displays correct results on console, but incorrect text on UI. [During Timer_Tick event]
                
            
        

我在Form_Load事件中调用了这个方法,这个事件只是在UI上显示最后扫描的信息,效果很好:

private async void UC_Dashboard_Load(object sender, EventArgs e)
        
            await PopulateLastScanAsync();
            timerRefresher.Start();
        

我有一个计时器timerRefresher,每隔几秒就会计时一次,它的目的是刷新 UI 上显示的数据。它的tick事件中的代码是:

private async void timerRefresher_Tick(object sender, EventArgs e)
        
            await PopulateLastScanAsync();
        

问题是:表单加载时数据在 UI 上正确显示,但 ticks 事件没有刷新标签上的更新值。

我知道这个问题来自async 的东西,但不知道如何摆脱它。 我试过的:

- Label.Update();
- Label.Refresh();
- Label.Invoke();
- Label.Invalidate();
// And some other stuff answered in other similar questions

以及Label控件自带的其他方法。

谢谢。

【问题讨论】:

如果您删除await Task.Delay(100); 并在处理程序中只保留LastScannedName.Text += "*";,那么标签是否会更新?请在不附加调试器的情况下尝试。使用 Ctrl+F5 从 Visual Studio 启动程序。 我对值是相同还是不同不感兴趣。如果标签在每个Tick 上更新,或者永远保持其初始值,我很感兴趣。关于连接你的电脑,我做不到,抱歉。 标签在初始值上保持冻结状态。我已经公开了timerRefresher 访问权限,并以不同的形式启动了timerRefresher。我只是尝试从相同的表格开始,它现在可以工作了。感谢您的宝贵时间。 您在哪里展示这些控件 (LastScannedName & Co.)? -- 添加签入PopulateLastScanAsync():bool hasHandle = LastScannedName.IsHandleCreated;。如果hasHandlefalse,那么var handle = LastScannedName.Handle 等等。看看这是否使控件更新。删除所有那些.Refresh().Update() 等。另外,打印到控制台Thread.CurrentThread.ManagedThreadId,看看那是什么。除非你在说什么,否则 async/await 在 U 线程中恢复。 "并且正在从不同的表单启动 timerRefresher。"您的问题很可能是参考问题,您正在创建第一个表单的实例并认为这将允许您更改屏幕上已经存在的现有表单... 【参考方案1】:

当您从 Form_Load 事件中的方法调用方法时,await PopulateLastScanAsync() 方法将在 MainThread 中执行,因此它会更新 UI。

当你通过另一个线程(不是 UI/Dispatcher 线程)调用相同的方法时,它会抛出异常。

如果你把 Try catch 块放在PopulateLastScanAsync() 方法中,你会得到异常。

您可以调用 Dispatcher 线程来执行 UI 相关的更改。

更新代码:

         private async Task PopulateLastScanAsync()
    
        var scan = await Access.GetLastScanAsync(); // retrieve User/Scan data from database
        if (scan.Item4) // Ignore this stuff
        
            var result = await Access.GetUserAsync(scan.Item1.UserCode);
            if (result.Item2)
                this.Invoke((MethodInvoker)delegate
                

                    // Display logic is here
                    User usr = result.Item1; // Display on UI
                    LastScannedName.Text = $"usr.FirstName usr.LastName";
                    LastScannedUserId.Text = $"usr.UserCode";
                    LastScanTime.Text = scan.Item1.ScanTime.ToString("g");
                    LastScanImage.Image = Manager.ConvertBytesToImage(usr.Picture);
                    // Read following comments after going thru the problem
                    // If here, I debug, like Console.WriteLine(LastScannedName.Text), it displays correct results on console, but incorrect text on UI. [During Timer_Tick event]

                );
        
    

在更新任何控件之前检查InvokeRequired 总是一个好主意。

【讨论】:

感谢您的回答,但我已经尝试过提到的事情。 1. 我已将方法包含在 try-catch 块中,没有出现任何异常。 2. 我尝试使用this.Invoke 并得到一个异常control can't be invoked/begin unless its handle is created 3. 我将该代码包含在InvokeRequired 检查中,即使标签需要更新UI,它也总是返回false。 你检查过 LastScannedName.Text = $"usr.FirstName usr.LastName";通过 Debug 更新? 我在PopulateLastScanAsync() 中添加了一行代码:Console.WriteLine(LastScannedName.Text)。从Timer_Tick 调用时,控制台上的值与 UI 上的值不同。 您的问题真的解决了吗?您的最后一条评论看起来好像您没有...【参考方案2】:

您应该将数据收集与 UI 管理分开。

private async Task PopulateLastScanAsync()

    var usr = await GetLastScanDataAsync(); // retrieve User/Scan data
    if (usr != null) // Ignore this stuff
    
        LastScannedName.Text = $"usr.FirstName usr.LastName";
        LastScannedUserId.Text = $"usr.UserCode";
        LastScanTime.Text = scan.Item1.ScanTime.ToString("g");
        LastScanImage.Image = Manager.ConvertBytesToImage(usr.Picture);
    


private async Task<LastScan> GetLastScanDataAsync()

    var scan = await Access.GetLastScanAsync().ConfigureAwait(false); // retrieve User/Scan data from database
    if (scan.Item4) // Ignore this stuff
    
        var result = await Access.GetUserAsync(scan.Item1.UserCode).ConfigureAwait(false);
        if (result.Item2)
        
            return result.Item1;
        
    
    return null;

有关ConfigureAwait(false)的更多信息,请阅读ConfigureAwait FAQ。

您没有显示您正在使用的计时器时间,但如果您使用的是Windows Forms Timer,我预计不会有任何问题,因为:

注意

Windows 窗体计时器组件是单线程的,精度限制为 55 毫秒。如果您需要更准确的多线程计时器,请使用 System.Timers 命名空间中的 Timer 类。

【讨论】:

以上是关于C# 标签值发生变化,但 UI 上的文本没有 [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中从外部应用程序获取 UI 文本

外部模型对象的 SwiftUI 数据流和 UI 更新,其值发生变化但 id 相同

由于字体大小的变化,Swift标签没有更新框架

Swift UI 测试静态文本值

为啥绑定更新没有实现 INotifyPropertyChanged?

文本框和标签数组如何在 C# 中的提交方法中获取值