从BackroundWorker线程向StackPanel添加控件

时间:2013-09-10 08:26:05

标签: c# wpf backgroundworker

我创建了一个演示版,希望能够证明我过去几天遇到的问题。

我创建了一个名为 数据供应商 的简单类,其中包含一个名为 GenerateRandomInt() 的公共静态方法这是为了模拟一个可能需要一些时间的过程。上述课程的代码可以在下面找到:

class DataSupplier
    {
        public static int GenerateRandomInt()
        {
            Random rnd = new Random();
            Thread.Sleep(1000);
            return rnd.Next();
        }
    }

我的MainWindow simple包含一个ScrollViewer,其中嵌入了StackPanel,名为 stackPanel1 ,还有一个名为 button1 的按钮这个XAML如下所示:

<Window x:Class="ThreadingDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="40" />
        </Grid.RowDefinitions>
        <ScrollViewer>
            <StackPanel Name="stackPanel1" />
        </ScrollViewer>
        <Button Grid.Row="1"
                Name="button1"
                Content="Generate 10 Labels"
                Click="button1_Click" />
    </Grid>
</Window>

我想要实现的是,当我点击 button1 生成10个标签时,会使用静态 DataSupplier.GetRandomInt显示一个随机数() 方法。但是我希望它们一个接一个地显示,即只要它们被单独创建。我的 MainWindow 的代码隐藏如下所示:

public partial class MainWindow : Window
    {
        private BackgroundWorker worker;

        public MainWindow()
        {
            InitializeComponent();

            worker = new BackgroundWorker();
            worker.DoWork += new DoWorkEventHandler(worker_DoWork);
            worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
        }

        void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            button1.IsEnabled = true;
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            this.Dispatcher.Invoke((Action)(() =>
            {
                for (int i = 1; i <= 10; i++)
                {
                    //create a new label, and set it's content to a randomly generated number
                    Label lbl = new Label();
                    lbl.Content = DataSupplier.GenerateRandomInt();

                    //add this label to stackPanel1
                    stackPanel1.Children.Add(lbl);
                }
            }));
        }

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            button1.IsEnabled = false;
            worker.RunWorkerAsync();
        }
    }

然而,正在发生的事情是,在我的所有随机生成的标签已添加到stackPanel1之前,没有任何视觉效果。

无论如何我可以对此进行编程,以便在每个标签创建时显示每个标签吗?同时还要确保我的UI保持响应?

4 个答案:

答案 0 :(得分:1)

你的问题是你已经创建了一个后台工作程序来卸载UI线程的工作,但需要花费很长时间GenerateRandomInt()的操作才会被调度回UI线程,从而导致你的应用程序开始冻结。

有类似的东西:

private void worker_DoWork(object sender, DoWorkEventArgs e) {
  for (int i = 1; i <= 10; i++) {
    int x = DataSupplier.GenerateRandomInt();
    Dispatcher.BeginInvoke(
      DispatcherPriority.Normal,
      (Action)(() => {
        Label lbl = new Label { Content = x };
        stackPanel1.Children.Add(lbl);
      }));
  }
}

“长时间运行的功能”在后台线程中运行,只需将UI内容调度到UI线程,从而为您提供所期望的内容。最初我们创建了一个线程,最终发送了我们想要它做的工作,然后回到调用者那里使它变得毫无意义。

现在,如果您可以使用 .net 4.5 ,这可以更加简单,例如:

长时间运行的静态功能:

internal class DataSupplier {
  public static async Task<int> GenerateRandomInt() {
    Random rnd = new Random();
    await Task.Delay(1000);
    return rnd.Next();
  }
}

并在您的MainWindow的代码隐藏中:

private async void button1_Click(object sender, RoutedEventArgs e) {
  button1.IsEnabled = false;
  await AddLabels();
  button1.IsEnabled = true;
}

private async Task AddLabels() {
  for (int i = 0; i < 10; ++i) {
    Label lb = new Label { Content = await DataSupplier.GenerateRandomInt() };
    stackPanel1.Children.Add(lb);
  }
}

这会给你相同的结果,更简单(只是我的意见)

答案 1 :(得分:0)

尝试使用BeginInvoke。

for (int i = 1; i <= 10; i++)
  {
    string content = DataSupplier.GenerateRandomInt();
    this.Dispatcher.BeginInvoke((Action)(() =>
   {
     lbl.Content = content;
//create a new label, and set it's content to a randomly generated number
     Label lbl = new Label();  
    //add this label to stackPanel1
     stackPanel1.Children.Add(lbl);
                        }));
    Thread.Sleep(100);
  }

Invoke将同步执行,因此当执行调用时,您的线程将返回,而BeginInvoke将异步执行。 我添加了一个thread.sleep来暂停后台线程一段时间。 在这种情况下,后台线程完成的所有工作都是更新UI,因此UI总是很忙。

答案 2 :(得分:0)

尝试在您的工作人员DispatcherPriority中将Render设置为_DoWork。它应该修复渲染

            this.Dispatcher.Invoke((Action)(() =>
            {
                for (int i = 1; i <= 10; i++)
                {
                    //create a new label, and set it's content to a randomly generated number
                    Label lbl = new Label();
                    lbl.Content = DataSupplier.GenerateRandomInt();

                    //add this label to stackPanel1
                    stackPanel1.Children.Add(lbl);
                }
            }), DispatcherPriority.Render);

答案 3 :(得分:0)

报告您的进度,因此您的代码将如下所示:

public partial class MainWindow : Window
{
    private BackgroundWorker worker;

    public MainWindow()
    {
        InitializeComponent();

        worker = new BackgroundWorker();
        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        worker.ProgressChanged += worker_ProgressChanged;
        worker.WorkerReportsProgress = true;
        worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
    }

    void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        this.Dispatcher.BeginInvoke((Action)(() =>
        {
            Label lbl = new Label();
            lbl.Content = e.ProgressPercentage;
            stackPanel1.Children.Add(lbl);
        }), DispatcherPriority.Background);
    }

    void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        button1.IsEnabled = true;
    }

    void worker_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i = 1; i <= 10; i++)
        {
            worker.ReportProgress(DataSupplier.GenerateRandomInt());
        }
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        button1.IsEnabled = false;
        worker.RunWorkerAsync();
    }

    private int GenerateRandomInt(int i)
    {
        Thread.Sleep(1000);
        return i;
    }
}

class DataSupplier
{
    public static int GenerateRandomInt()
    {
        Random rnd = new Random();
        Thread.Sleep(1000);
        return rnd.Next();
    }
}