如何刷新循环内设置的可视控件属性 (TextBlock.text)?
Posted
技术标签:
【中文标题】如何刷新循环内设置的可视控件属性 (TextBlock.text)?【英文标题】:How do I refresh visual control properties (TextBlock.text) set inside a loop? 【发布时间】:2011-07-27 03:07:32 【问题描述】:每次循环迭代时,我都想直观地更新文本块的文本。我的问题是 WPF 窗口或控件在循环完成之前不会在视觉上刷新。
for (int i = 0; i < 50; i++)
System.Threading.Thread.Sleep(100);
myTextBlock.Text = i.ToString();
在 VB6 中,我会调用 DoEvents()
或 control.Refresh
。目前我只想要一个类似于DoEvents()
的快速而肮脏的解决方案,但我也想知道替代方案或“正确”的方法来做到这一点。我可以添加一个简单的绑定语句吗?语法是什么?
提前致谢。
【问题讨论】:
【参考方案1】:您可以执行 Dispatcher.BeginInvoke 以执行线程上下文切换,以便渲染线程可以完成其工作。 但是,这不是处理此类事情的正确方法。你应该使用 Animation + Binding 来完成类似的事情,因为这是在 WPF 中做类似事情的免破解方式。
【讨论】:
只有当 UI 更新是纯粹的吸引眼球时,您才能使用动画。如果您确实希望 UI 反映某个进程的状态,则无法做到这一点。 我不同意。动画也可以做到这一点。您唯一需要做的就是在您希望动画发生的地方实现一个 DP。 @EladKatz:当然,您可以绑定到属性并使用适当的转换器来实现 UI 更改。动画从何而来? 您为 DP 制作动画。 TextBox 将绑定到该 DP。 @EladKatz:当你想让它反映进程的状态时,为什么要为它制作动画?【参考方案2】:这就是你通常的做法:
ThreadPool.QueueUserWorkItem(ignored =>
for (int i = 0; i < 50; i++)
System.Threading.Thread.Sleep(100);
myTextBlock.Dispatcher.BeginInvoke(
new Action(() => myTextBlock.Text = i.ToString()));
);
这会将操作委托给工作池线程,该线程允许您的 UI 线程处理消息(并防止您的 UI 冻结)。因为工作线程不能直接访问myTextBlock
,所以需要使用BeginInvoke
。
虽然这种方法不是“WPF 方式”做事,但它没有任何问题(事实上,我不相信这个小代码有任何替代方案)。但另一种做事方式是这样的:
-
将
TextBlock
的Text
绑定到实现INotifyPropertyChanged
的某个对象的属性
在循环中,将该属性设置为您想要的值;更改将自动传播到 UI
不需要线程、BeginInvoke
或其他任何东西
如果你已经有一个对象并且 UI 可以访问它,这就像写 Text="Binding MyObject.MyProperty"
一样简单。
更新:例如,假设你有这个:
class Foo : INotifyPropertyChanged // you need to implement the interface
private int number;
public int Number
get return this.number;
set
this.number = value;
// Raise PropertyChanged here
class MyWindow : Window
public Foo PropertyName get; set;
绑定将在 XAML 中像这样完成:
<TextBlock Text="Binding PropertyName.Number" />
【讨论】:
唯一实际做的是 Dispatcher.BeginInvoke。我之前已经说过... @EladKatz:将代码移动到另一个线程什么都不做?我想是的,如果将您的 UI 冻结 100 毫秒算作“无”。 +1 表示答案第二部分的 3 个步骤。这完美地工作并且很容易实现。这也是 MVVM 的重要组成部分。 虽然您的代码示例运行良好,但它太复杂了,我无法记住。我可以为 this.MyProperty 使用什么绑定语法?我快速而肮脏的形式只使用背后的代码 - 没有外部对象。我没有得到正确的绑定语法。如何用引用 WPF 窗口上的属性的绑定语句替换您的 MyObject.MyProperty?我不会直接设置 myTextBlock.text,而是更新 Window 属性,该属性又会更新绑定的 textblock.text。【参考方案3】:如果您真的想要快速而肮脏的实现,并且不关心将来维护产品或用户体验,您可以添加对 System.Windows.Forms 的引用并拨打System.Windows.Forms.Application.DoEvents()
:
for (int i = 0; i < 50; i++)
System.Threading.Thread.Sleep(100);
MyTextBlock.Text = i.ToString();
System.Windows.Forms.Application.DoEvents();
缺点是它真的很糟糕。您将在 Thread.Sleep() 期间锁定 UI,这会惹恼用户,并且您最终可能会根据程序的复杂性得出不可预测的结果(我见过一个应用程序,其中两个方法在UI线程,每一个都重复调用DoEvents()...)。
应该这样做:
-
任何时候您的应用程序必须等待某些事情发生(即磁盘读取、Web 服务调用或 Sleep()),它都应该在一个单独的线程上。
您不应手动设置 TextBlock.Text - 将其绑定到属性并实现 INotifyPropertyChanged。
这是一个示例,展示了您所要求的功能。编写只需要几秒钟的时间,而且使用起来更容易 - 而且它不会锁定 UI。
Xaml:
<StackPanel>
<TextBlock Name="MyTextBlock" Text="Binding Path=MyValue"></TextBlock>
<Button Click="Button_Click">OK</Button>
</StackPanel>
代码隐藏:
public partial class MainWindow : Window, INotifyPropertyChanged
public MainWindow()
InitializeComponent();
this.DataContext = this;
private void Button_Click(object sender, RoutedEventArgs e)
Task.Factory.StartNew(() =>
for (int i = 0; i < 50; i++)
System.Threading.Thread.Sleep(100);
MyValue = i.ToString();
);
private string myValue;
public string MyValue
get return myValue;
set
myValue = value;
RaisePropertyChanged("MyValue");
private void RaisePropertyChanged(string propName)
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
public event PropertyChangedEventHandler PropertyChanged;
代码可能看起来有点复杂,但它是 WPF 的基石,并且需要一些练习 - 非常值得学习。
【讨论】:
太棒了!其他答案很好,但我最了解这个答案,它包括我要求的快速而肮脏的解决方案。为了使 Greg 的解决方案发挥作用,我添加了: using System.ComponentModel; (和)使用 System.Threading.Tasks;。不要忘记参考 System.Windows.Forms 以使 DoEvents() 解决方案起作用。我学到了很多东西。谢谢! 这段代码在 .net 3.5 中是什么样的(特别是 Task.Factory.StartNew) Task.Factory.StartNew 是在 .net 4 中引入的。只需搜索.net 3.5 threading
即可找到遗留机制。【参考方案4】:
我尝试了此处公开的解决方案,但在我添加以下内容之前它对我不起作用:
创建一个扩展方法并确保从您的项目中引用它的包含程序集。
public static void Refresh(this UIElement uiElement)
uiElement.Dispatcher.Invoke(DispatcherPriority.Background, new Action( () => ));
然后在RaisePropertyChanged
之后直接调用:
RaisePropertyChanged("MyValue");
myTextBlock.Refresh();
这将强制 UI 线程控制一小段时间并调度 UI 元素上的任何未决更改。
【讨论】:
【参考方案5】:for (int i = 0; i < 50; i++)
System.Threading.Thread.Sleep(100);
myTextBlock.Text = i.ToString();
在后台工作组件中运行上述代码并使用绑定 updatesourcetrigeer
作为 propertychanged
将立即在 UI 控件中反映更改。
【讨论】:
以上是关于如何刷新循环内设置的可视控件属性 (TextBlock.text)?的主要内容,如果未能解决你的问题,请参考以下文章