【问题标题】:Data binds to the UI after the click has been completely executed in WPF在 WPF 中完全执行单击后,数据将绑定到 UI
【发布时间】:2015-09-09 17:26:45
【问题描述】:

我有一个包含员工属性的类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Collections.ObjectModel;

namespace SimpleDatabinding03
{
   public class Employee:INotifyPropertyChanged
    {
     int _employeenumber;
      string _firstname;
      string _lastname;
      string _dept;
      string _title;


       //constructor

       public Employee()
       {

       }


       //properties

       public int EmployeeNum
       {
           get { return _employeenumber; }
           set { _employeenumber = value; NotifyPropertyChanged("EmployeeNum"); }
       }

       public string FirstName
       {
           get { return _firstname; }
           set { _firstname = value; NotifyPropertyChanged("FirstName"); }
       }

       public string LastName
       {
           get { return _lastname; }
           set { _lastname = value; NotifyPropertyChanged("LastName"); }

       }

       public string Dept
       {
           get { return _dept; }
           set { _dept = value; NotifyPropertyChanged("Dept"); }
       }

       public string Title
       {
           get { return _title; }
           set { _title = value; NotifyPropertyChanged("Title"); }
       }


       public event PropertyChangedEventHandler PropertyChanged;

       private void NotifyPropertyChanged(string propertyname)
       {

           if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyname));
       }


       //internal object GetBindingExpression(System.Windows.DependencyProperty dependencyProperty)
       //{
       //    throw new NotImplementedException();
       //}
    }
}

在 XAML 中,我将它们绑定到一个文本框:

<Grid>
        <Grid.DataContext>
            <m:Employee x:Name="employee"/>
        </Grid.DataContext>


        <Label Grid.Row="0">Employee Number</Label>
        <TextBox Name="EmpNum" Grid.Row="0" Grid.Column="1" Text="{Binding EmployeeNum}" ></TextBox>

        <Label Grid.Row="2" >first name</Label>
        <TextBox Name="Fname" Grid.Row="2" Grid.Column="1" Text="{Binding FirstName}"></TextBox>

        <Label Grid.Row="3" >Last name</Label>
        <TextBox Name="Lname" Grid.Row="3" Grid.Column="1" Text="{Binding LastName}"></TextBox>

        <Label Grid.Row="4" >Dept</Label>
        <TextBox Name="Dept" Grid.Row="4" Grid.Column="1" Text="{Binding Dept}"></TextBox>
    </Grid>

后面的代码是:

private void button1_Click(object sender, RoutedEventArgs e)
    {

        employee.EmployeeNum = 123;
        System.Threading.Thread.Sleep(3000);
        employee.FirstName = "John";
        System.Threading.Thread.Sleep(3000);
       employee.LastName = "kepler"; });

    }

要求:当属性更改时,绑定到员工实例的 UI 文本框不会更新。它等待按钮单击事件完成,然后更新 UI。我正在寻找一种即时更新 UI 的解决方案。

【问题讨论】:

  • 那你在问什么?另外,你有没有尝试过让它工作?
  • 好吧,您正在将当前线程置于您的代码设置属性的位置。这就是为什么您的 UI 没有更新的原因,它在同一个线程中。尝试创建另一个线程或异步方法。
  • 因此,当属性发生变化时,UI 需要立即更新,而不应等待按钮单击完成。注意:尝试使用 UpdateSourceTrigger=PropertyChanged 仍然没有帮助。
  • 您使用的是哪个版本的 .Net?
  • @OmegaMan 3.5 版

标签: c# wpf xaml .net-3.5


【解决方案1】:

.net 中的 Windows UI 通常是单线程的。您在方法内部阻塞了 UI 线程。您可以使用以下内容更干净地处理此问题:

private async void button1_Click(object sender, RoutedEventArgs e)
{
    employee.EmployeeNum = 123;
    await Task.Delay(3000);
    employee.FirstName = "John";
    await Task.Delay(3000);
    employee.LastName = "kepler"; });
}

注意 async、await 和 Task.Delay 的使用。有关这些的更多信息,请参阅 Asynchronous Programming with Async and Await (C# and Visual Basic)

编辑 - .net 3.5 答案:

由于您使用的是 .net 3.5,这会使事情稍微复杂化。您通常不希望从除 UI 线程之外的任何线程更新绑定到 UI 的对象,但是,您可以从另一个线程完成您的工作,然后传递到 UI 线程。

var backgroundThread = new Thread(o => 
    {
        Application.Current.Dispatcher.Invoke(new Action(() => employee.EmployeeNum = 123));
        Thread.Sleep(3000);
        Application.Current.Dispatcher.Invoke(new Action(() => employee.FirstName = "John"));
        Thread.Sleep(3000);
        Application.Current.Dispatcher.Invoke(new Action(() => employee.LastName = "kepler"));
    });
backgroundThread.Start();

这里要注意的关键是 Dispatcher 将确保调用的操作将发生在 UI 线程上,而“工作”将发生在后台线程上。

【讨论】:

  • 不要说这里作为链接,请使用页面的实际标题。
  • 我目前使用的是 VS 2010,能否分享一下如何在 VS2010 中实现这一点?
  • 有了 VS2010,事情会变得相当复杂。您可以使用后台工作程序或处理线程。你能给我一些关于为什么 thread.Sleeps 在那里的上下文吗?模拟工作?
  • @willaien,是的,我正在尝试使用 thread.sleep 来模拟我的工作。
  • 这适用于 4.5+,但是 OP 在评论中声明他们使用的是 3.5,因此他们无权访问 await/async 关键字。他们可能不得不使用调度程序或后台线程。
【解决方案2】:

绑定文本属性默认在控件失去焦点时更新。

当属性改变时……员工实例没有更新。

更改每个绑定以报告即时更新,例如

="{Binding EmployeeNum, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay }"

更多信息请参见Binding.UpdateSourceTrigger Property


更新 .Net 4+

您的代码在单击事件中阻止了 GUI 线程。在后台线程/任务中执行更改。

private void button1_Click(object sender, RoutedEventArgs e)
{

   Task.Factory.StartNew( () => { 

        employee.EmployeeNum = 123;
        System.Threading.Thread.Sleep(3000);
        employee.FirstName = "John";
        System.Threading.Thread.Sleep(3000);
        employee.LastName = "kepler"; }
   );

}

.Net 3.5 更新

 private void button1_Click(object sender, RoutedEventArgs e)
  {
       ThreadPool.QueueUserWorkItem(
            delegate // Anonymous Delegate         {
                employee.EmployeeNum = 123;
                System.Threading.Thread.Sleep(3000);
                employee.FirstName = "John";
                System.Threading.Thread.Sleep(3000);
                employee.LastName = "kepler";
            }
        );
    }

要查看更多信息,请参阅我的博客文章C# MultiThreading Using ThreadPool, Anonymous Delegates and Locks

【讨论】:

  • @Prashanth 我忘了Mode=TwoWay,试试看。查看更新。
  • @Prashanth 问题不是控件,而是单击事件阻碍了视觉更新。请参阅上面的更新,通过将代码放在任务中以在单独的线程上运行来解决问题。
  • @Prashanth 我使用 ThreadPool QueueUserWorkItem 将操作放置在后台线程中。请参阅更新 2。
  • 感谢 Threadpool 上的帖子,它确实达到了我的预期。感谢您对此的支持。
  • @Prashanth 如果你完成了,请将主题标记为任何看到这篇文章的人的答案。谢谢。
【解决方案3】:

您需要使用后台线程。

默认情况下,方法块中的所有代码都会在 UI 更新之前完成,因此您必须等到所有代码行完成后,才能重新绘制 UI。

如果您想在后台运行代码,BackgroundWorker(实际上使用 ThreadPool 作为 OmegaMan's answer 提及)可用于 3.5。

根据您的代码的运行方式,您可能想要这样的东西:

// run on current thread
employee.EmployeeNum = 123;

// create a separate background worker thread to run this set of code
// once it's completed, also update the FirstName
var bg1 = new BackgroundWorker();
bg1.DoWork += delegate() 
{
    System.Threading.Thread.Sleep(3000);
};
bg1.RunWorkerCompleted += delegate() 
{ 
    employee.FirstName = "John";
};
bg1.RunWorkerAsync();

// create a 2nd background worker to run this set of code.
// Can either start from your click method if it can run at the same time
// as other background worker, or you can start it from inside bg1's
// RunWorkerCompleted delegate if it should run after bg1 runs
var bg2 = new BackgroundWorker();
bg2.DoWork += delegate() 
{
    System.Threading.Thread.Sleep(3000);
};
bg2.RunWorkerCompleted += delegate() 
{ 
    employee.LastName = "kepler";
};
bg2.RunWorkerAsync();

(应该注意,我这里的语法可能有问题。我没有在编译器中测试它,我很确定我的委托语法不正确)

您要在 RunWorkerCompleted 中更新 UI 对象的原因是因为使用 WPF,在主 UI 线程上创建的对象无法被其他线程(例如后台工作线程)更新。

如果您的 DoWork 方法的结果应该为您的 RunWorkerCompleted 方法创建结果,那么您可以使用.Result 属性将最终值从DoWork 方法传递给RunWorkerCompleted 方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-16
    • 1970-01-01
    相关资源
    最近更新 更多