wpf 如何刷新循环内设置的视觉控件属性 (TextBlock.text)?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/5504244/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I refresh visual control properties (TextBlock.text) set inside a loop?
提问by DeveloperDan
With each loop iteration, I want to visually update the text of a textblock. My problem is that the WPF window or control does not visually refresh until the loop is complete.
在每次循环迭代中,我想直观地更新文本块的文本。我的问题是 WPF 窗口或控件在循环完成之前不会在视觉上刷新。
for (int i = 0; i < 50; i++)
{
System.Threading.Thread.Sleep(100);
myTextBlock.Text = i.ToString();
}
In VB6, I would call DoEvents()
or control.Refresh
. At the moment I'd just like a quick and dirty solution similar to DoEvents()
, but I'd also like to know about alternatives or the "right" way to do this. Is there a simple binding statement I could add? What is the syntax?
Thanks in advance.
在 VB6 中,我会调用DoEvents()
或control.Refresh
。目前,我只是想要一个类似于 的快速而肮脏的解决方案DoEvents()
,但我也想知道替代方案或“正确”的方法来做到这一点。我可以添加一个简单的绑定语句吗?语法是什么?提前致谢。
回答by Greg Sansom
If you reallywant the quick and dirty implementation and don't care about maintaining the product in the future or about the user experience, you can just add a reference to System.Windows.Forms and call System.Windows.Forms.Application.DoEvents()
:
如果你真的想要快速和肮脏的实现并且不关心将来维护产品或用户体验,你可以只添加对 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();
}
The downside is that it's really really bad. You're going to lock up the UI during the Thread.Sleep(), which annoys the user, and you could end up with unpredictable results depending on the complexity of the program (I have seen one application where two methods were running on the UI thread, each one calling DoEvents() repeatedly...).
缺点是它真的很糟糕。您将在 Thread.Sleep() 期间锁定 UI,这会惹恼用户,并且根据程序的复杂性,您最终可能会得到不可预知的结果(我曾见过一个应用程序,其中在UI 线程,每个线程都重复调用 DoEvents()...)。
This is how it should be done:
应该这样做:
- Any time your application has to wait for something to happen (ie a disk read, a web service call, or a Sleep()), it should be on a separate thread.
- You should not set TextBlock.Text manually - bind it to a property and implement INotifyPropertyChanged.
- 任何时候您的应用程序必须等待某些事情发生(即磁盘读取、Web 服务调用或 Sleep()),它应该在一个单独的线程上。
- 您不应手动设置 TextBlock.Text - 将其绑定到属性并实现 INotifyPropertyChanged。
Here is an example showing the functionality you've asked for. It only takes a few seconds longer to write and it's so much easier to work with - and it doesn't lock up the UI.
这是一个示例,显示了您要求的功能。编写只需要几秒钟的时间,并且使用起来更加容易 - 而且它不会锁定 UI。
Xaml:
Xml:
<StackPanel>
<TextBlock Name="MyTextBlock" Text="{Binding Path=MyValue}"></TextBlock>
<Button Click="Button_Click">OK</Button>
</StackPanel>
CodeBehind:
代码隐藏:
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;
}
The code might seem a bit complicated, but it's a cornerstone of WPF, and it comes together with a bit of practice - it's well worth learning.
代码可能看起来有点复杂,但它是 WPF 的基石,它结合了一些练习 - 非常值得学习。
回答by Jazz
I tried the solution exposed here and it didn't work for me until I added the following:
我尝试了此处公开的解决方案,但在添加以下内容之前它对我不起作用:
Create an extension method and make sure you reference its containing assembly from your project.
创建扩展方法并确保从项目中引用其包含的程序集。
public static void Refresh(this UIElement uiElement){
uiElement.Dispatcher.Invoke(DispatcherPriority.Background, new Action( () => { }));
}
Then call it right after RaisePropertyChanged
:
然后在之后立即调用它RaisePropertyChanged
:
RaisePropertyChanged("MyValue");
myTextBlock.Refresh();
That will force the UI thread to take control for a small while and dispatch any pending changes on the UI element.
这将强制 UI 线程控制一小段时间并在 UI 元素上调度任何挂起的更改。
回答by Jon
This is how you would do it normally:
这是您通常的做法:
ThreadPool.QueueUserWorkItem(ignored =>
{
for (int i = 0; i < 50; i++) {
System.Threading.Thread.Sleep(100);
myTextBlock.Dispatcher.BeginInvoke(
new Action(() => myTextBlock.Text = i.ToString()));
}
});
This delegates the operation to a worker pool thread, which allows your UI thread to process messages (and keeps your UI from freezing). Because the worker thread cannot access myTextBlock
directly, it needs to use BeginInvoke
.
这将操作委托给一个工作池线程,它允许您的 UI 线程处理消息(并防止您的 UI 冻结)。因为工作线程不能myTextBlock
直接访问,所以需要使用BeginInvoke
.
Although this approach is not "the WPF way" to do things, there's nothing wrong with it (and indeed, I don't believe there's any alternative with this little code). But another way to do things would go like this:
虽然这种方法不是做事的“WPF 方式”,但它并没有错(事实上,我认为这个小代码没有任何替代方案)。但另一种做事的方式是这样的:
- Bind the
Text
of theTextBlock
to a property of some object that implementsINotifyPropertyChanged
- From within the loop, set that property to the value you want; the changes will be propagated to the UI automatically
- No need for threads,
BeginInvoke
, or anything else
- 绑定
Text
的TextBlock
一些对象实现的属性INotifyPropertyChanged
- 在循环中,将该属性设置为您想要的值;更改将自动传播到 UI
- 不需要线程
BeginInvoke
,或其他任何东西
If you already have an object and the UI has access to it, this is as simple as writing Text="{Binding MyObject.MyProperty}"
.
如果您已经有一个对象并且 UI 可以访问它,这就像编写Text="{Binding MyObject.MyProperty}"
.
Update:For example, let's assume you have this:
更新:例如,假设你有这个:
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; }
}
The binding would be done like this in XAML:
绑定将在 XAML 中像这样完成:
<TextBlock Text="{Binding PropertyName.Number}" />
回答by Elad Katz
You can do Dispatcher.BeginInvoke in order to do a thread context-switch so that the rendering thread could do its job. However, this is not the right way for such things. You should use Animation + Binding for things like that, as this is the hack-free way of doing things like that in WPF.
您可以执行 Dispatcher.BeginInvoke 以进行线程上下文切换,以便渲染线程可以完成其工作。但是,这不是处理此类事情的正确方法。你应该使用动画 + 绑定来做这样的事情,因为这是在 WPF 中做这样的事情的无黑客方式。
回答by Padmanaban
for (int i = 0; i < 50; i++)
{
System.Threading.Thread.Sleep(100);
myTextBlock.Text = i.ToString();
}
Running the above code inside a background worker component and using a binding updatesourcetrigeer
as propertychanged
will reflect the changes immediately in the UI control.
在后台工作组件中运行上述代码并使用绑定updatesourcetrigeer
aspropertychanged
将立即反映 UI 控件中的更改。