从另一个类更新GUI的方法(C#)

3

嘿,我是C#的新手,请帮忙。

我正在编写一个程序,用于对文件中的数据进行排序,这是一个耗时的过程,因此我想在单独的线程中运行它,并且由于它有很多步骤,所以我为它创建了一个新类。问题是我想在主GUI中显示进度,我知道我必须使用Invoke函数,但问题是表单控件变量在这个类中不可访问。我该怎么办?

示例代码:

public class Sorter
{
    private string _path;
    public Sorter(string path)
    {
        _path = path;
    }

    public void StartSort()
    {
        try
        {
                 processFiles(_path, "h4x0r"); // Just kidding
        }
        catch (Exception e)
        {
            MessageBox.Show("Error: " + e.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }
    }

    private void processFiles(string Dir, string[] key)
    {
        /* sorting program */

    }

它被用作

    public partial class Form1 : Form
    {
        Sorter sort;
        public Form1()
        {
            InitializeComponent();
        }

        private void browseBtn_Click(object sender, EventArgs e)
        {
            if (folderBrowserDialog1.ShowDialog() == DialogResult.OK)
                textBox1.Text = folderBrowserDialog1.SelectedPath;
        }

        private void startBtn_Click(object sender, EventArgs e)
        {
            if (startBtn.Text == "Start Sorting")
            {
   Thread worker = new Thread(new ThreadStart(delegate() {
                sort = new Sorter(textBox1.Text);
                sort.StartSort(); })); 
                worker.start();
            }
            else
                MessageBox.Show("Cancel");//TODO: add cancelling code here
        }
    }

请帮忙..

使用另一个线程并不能神奇地使它更快。 - Polyfun
3
不,但它会阻止他锁定他的用户界面。所有运行时间较长的进程都应在UI线程以外的线程上运行。 - Ian
3
你曾考虑过使用BackgroundWorker吗?可以参考以下链接:http://msdn.microsoft.com/zh-cn/library/system.componentmodel.backgroundworker.aspx 和 http://www.dotnetperls.com/backgroundworker,它能帮助你实现后台操作。 - Adriaan Stander
1
这不会使其更快,但GUI在进程结束之前不会挂起。 - voldyman
@ShellShock:这不会使其更快,但它将防止GUI在处理过程中锁定。 - Nick
4个回答

4

在执行多线程工作的类中添加一个事件,当进度改变时触发该事件。让你的表单订阅此事件并更新进度条。

注意,ProgressEventArgs是一个继承自EventArgs并具有表示进度的整数的小类。

// delegate to update progress
public delegate void ProgressChangedEventHandler(Object sender, ProgressEventArgs e);

// Event added to your worker class.
public event ProgressChangedEventHandler ProgressUpdateEvent

// Method to raise the event
public void UpdateProgress(object sender, ProgressEventArgs e)
{
   ProgressChangedEventHandler handler;
   lock (progressUpdateEventLock)
   {
      handler = progressUpdateEvent;
   }

   if (handler != null)
      handler(sender, e);
}

该事件将由调用线程调用,因此处理程序的执行仍不在“Form”线程的上下文中,除非被作为这样调用。在这种情况下,操作员将需要查看“ISynchronizeInvoke”。 - Grant Thomas
嗯,是的,你仍然需要在某个时候调用,因为事件将在工作线程上触发。 - Nick
嗯,好的。我去掉了关于Invoke的注释,显然还是需要的。但仍然使用事件是最优雅的解决方案。 - Ian
谢谢你的帮助,它完美地运行了。只是好奇,有没有办法从排序器类更新进度条? - voldyman

2
我建议您了解BackgroundWorker类。它正是用于您正在尝试解决的问题,比手动线程编程更容易。
简单示例
    public Form1()
    {
        InitializeComponent();

        backgroundWorker.WorkerReportsProgress = true;
        backgroundWorker.WorkerSupportsCancellation = true;
        backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker_ProgressChanged);
    }

    void backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar1.Value = e.ProgressPercentage;
    }

    private void btnStart_Click(object sender, EventArgs e)
    {
        if (!backgroundWorker.IsBusy)
            backgroundWorker.RunWorkerAsync();
    }

    private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        for (int i = 1; i < 101; ++i)
        {
            if (backgroundWorker.CancellationPending)
            {
                e.Cancel = true;
                break;
            }
            else
            {
                //Sort Logic is in here.
                Thread.Sleep(250);
                backgroundWorker.ReportProgress(i);
            }
        }
    }

    private void btnCancel_Click(object sender, EventArgs e)
    {
        if (backgroundWorker.IsBusy && backgroundWorker.WorkerSupportsCancellation)
            backgroundWorker.CancelAsync();
    }

1
你可以像这样做:
public delegate void StatusReporter(double progressPercentage);

public class MainClass
{

    public void MainMethod()
    {
        Worker worker = new Worker(ReportProgress);

        ThreadStart start = worker.DoWork;
        Thread workThread = new Thread(start);

        workThread.Start();

    }

    private void ReportProgress(double progressPercentage)
    {
        //Report here!!!
    }
}


public class Worker
{
    private readonly StatusReporter _reportProgress;

    public Worker(StatusReporter reportProgress)
    {
        _reportProgress = reportProgress;
    }

    public void DoWork()
    {
        for (int i = 0; i < 100; i++ )
        {
            // WORK, WORK, WORK
            _reportProgress(i);
        }
    }
}

1

有几种可用的选项来解决这种问题。无论如何,您都必须使用Invoke进行调整以更新UI。

你可以...

  • ...添加一个事件,在新类上触发该事件,您的UI可以侦听该事件,并在适当时调用Invoke - 您仍然需要通过构造函数、属性、方法调用等方式将数据传递给您的工作类
  • ...将该方法保留为表单上的方法,并从中传递以启动新线程(毕竟,新线程不必在不同的类中启动)
  • ...更改控件的访问修饰符,例如internal,以使同一程序集中的任何类都可以Invoke更改控件或从中读取。
  • ...使您的工作类成为需要访问的表单的子级 - 只要它被传递了实例的引用,它就可以看到其父级的private

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接