每次循环迭代,我想直观地更新文本块的文本。我的问题是WPF窗口或控件在循环完成之前不会直观刷新。
for (int i = 0; i < 50; i++)
{
System.Threading.Thread.Sleep(100);
myTextBlock.Text = i.ToString();
}
在VB6中,我会拨打DoEvents()
或control.Refresh
。目前我只是喜欢类似于DoEvents()
的快速而肮脏的解决方案,但我也想知道替代方案或“正确”方法。我可以添加一个简单的绑定语句吗?语法是什么?
提前谢谢。
答案 0 :(得分:26)
如果确实想要快速而肮脏的实现并且不关心将来维护产品或关于用户体验,那么您只需添加对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()......)。
以下示例显示了您要求的功能。它只需要几秒钟的时间来编写,并且它更容易使用 - 而且它不会锁定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的基石,它与一些练习相结合 - 非常值得学习。
答案 1 :(得分:5)
我尝试了这里公开的解决方案,但在我添加以下内容之前它对我不起作用:
创建一个扩展方法,并确保从项目中引用其包含的程序集。
public static void Refresh(this UIElement uiElement){
uiElement.Dispatcher.Invoke(DispatcherPriority.Background, new Action( () => { }));
}
然后在RaisePropertyChanged
之后立即调用它:
RaisePropertyChanged("MyValue");
myTextBlock.Refresh();
这将迫使UI线程暂时控制并在UI元素上调度任何挂起的更改。
答案 2 :(得分:4)
这就是你通常会这样做的方式:
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方式”来做事,但它没有任何问题(事实上,我不相信这个小代码有任何替代方法)。但另一种做事的方式是这样的:
Text
的{{1}}绑定到某个实现TextBlock
的对象的属性INotifyPropertyChanged
或其他任何内容如果您已有一个对象且UI可以访问它,那么就像编写BeginInvoke
一样简单。
更新:例如,我们假设你有这个:
Text="{Binding MyObject.MyProperty}"
绑定将在XAML中完成:
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; }
}
答案 3 :(得分:1)
您可以执行Dispatcher.BeginInvoke以执行线程上下文切换,以便呈现线程可以完成其工作。 但是,这不是正确的方法。你应该使用Animation + Binding这样的东西,因为这是在WPF中做这样的事情的无黑客方法。
答案 4 :(得分:0)
for (int i = 0; i < 50; i++)
{
System.Threading.Thread.Sleep(100);
myTextBlock.Text = i.ToString();
}
在后台工作程序组件中运行上述代码并使用绑定updatesourcetrigeer
作为propertychanged
将立即反映UI控件中的更改。