C # - Обновление GUI с использованием неосновного потока - PullRequest
3 голосов
/ 15 октября 2010

У меня есть программа, которая имеет классы

  • GUI
  • Загрузить
  • и буфер между двумя классами - т.е. используется для связи между двумя классами.

Класс Upload использует Process для запуска приложения FTP из командной строки. Я хочу вернуть вывод, выданный приложением FTP, в textbox в графическом интерфейсе.
Я попытался использовать следующий код, который был усечен.

Класс загрузки (beginProcess () - это метод, используемый для запуска потока (здесь не показан)):

public delegate void WputOutputHandler(object sender, DataReceivedEventArgs e);
class Upload
{
    private WputOutputHandler wputOutput;

    beginProcess()
    {
        Process pr = new Process();                                                 
        pr.StartInfo.FileName = @"wput.exe";                                        
        pr.StartInfo.RedirectStandardOutput = true;
        pr.StartInfo.UseShellExecute = false;
        pr.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
        pr.OutputDataReceived += new DataReceivedEventHandler(OnDataReceived);
        pr.ErrorDataReceived += new DataReceivedEventHandler(OnDataReceived);
        pr.Start();                                                                 
        pr.BeginOutputReadLine();
        pr.WaitForExit();
    }


    public void OnDataReceived(object sender, DataReceivedEventArgs e)
    {
        if(wputOutput != null)
        {
            wputOutput(sender, e);
        }
    }


    public event WputOutputHandler WputOutput
    {
        add
        {
            wputOutput += value;
        }
        remove
        {
            wputOutput -= value;
        }
    }
}

Класс буфера:

public void EventSubscriber()
{
    uploadSession.WputOutput += Main.writeToTextBoxEvent;
}

Основной класс:

public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e)
{
    if(this.textBox1.InvokeRequired)
    {
        MethodInvoker what now?
    }
    else
    {
        textBox1.Text = e.Data;
    }
}

Как вы можете видеть, когда дело доходит до метода writeToTextBoxEvent метода Main, у меня заканчиваются идеи. Я не уверен, что обновление пользовательского интерфейса с помощью пользовательского события - даже лучший способ сделать это. Если бы кто-то мог указать мне правильное направление, я был бы очень признателен.

Ответы [ 5 ]

3 голосов
/ 15 октября 2010

Как насчет этого:

public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e)
{
    if(this.textBox1.InvokeRequired)
    {
        // In .Net 2.0
        this.textBox1.BeginInvoke(new MethodInvoker(() => writeToTextBoxEvent(sender, e)));

        // In .Net 3.5 (above is also possible, but looks nicer)
        this.textBox1.BeginInvoke(new Action(() => writeToTextBoxEvent(sender, e)));
    }
    else
    {
        textBox1.Text = e.Data;
    }
}

Преимущество этого метода перед решением Ричарда состоит в том, что вам не нужно писать исполняемый код дважды (в пределах BeginInvoke() и снова в пути else).).

Обновление

Если вы используете .Net 3.5, сделайте это в качестве метода расширения:

public static class ControlExtensions
{
    public static void SafeInvoke(this Control control, Action action)
    {
        if (control.InvokeRequired)
        {
            control.Invoke(action);
        }
        else
        {
            action();
        }
    }

    public static void SafeBeginInvoke(this Control control, Action action)
    {
        if (control.InvokeRequired)
        {
            control.BeginInvoke(action);
        }
        else
        {
            action();
        }
    }
}

И используйте его так:

public void writeToTextBoxEvent(object sender, System.Diagnostics.DataReceivedEventArgs e)
{
    // Write it as a single line
    this.textBox1.SafeBeginInvoke(new Action(() => textBox1.Text = e.Data));

    this.textBox1.SafeBeginInvoke(new Action(() =>
        {
            //Write it with multiple lines
            textBox1.Text = e.Data;
        }));
}
2 голосов
/ 15 октября 2010

Вот популярный механизм расширения:

public static void InvokeIfRequired(this System.Windows.Forms.Control c,
                                    Action action) {
    if (c.InvokeRequired) {
        c.Invoke((Action)(() => action()));
    }
    else {
        action();
    }
}

Использование:

public void writeToTextBoxEvent(object sender, DataReceivedEventArgs e) {
    this.textBox1.InvokeIfRequired(() => { textBox1.Text = e.Data; }
}
2 голосов
/ 15 октября 2010
if(this.textBox1.InvokeRequired)
{
   Action a = () => { textBox1.Text = e.Data; };
  textBox1.Invoke(a);
}

т.е. используйте делегат (который может быть закрытием для захвата локальных переменных) с методом Invoke. Это отправит делегат, который будет выполнен в потоке текстового поля. Invoke вернется, когда делегат завершит выполнение.

Существует также асинхронная версия, когда вам не нужно ждать (BeginInvoke).

РЕДАКТИРОВАТЬ: Забыли, потому что Invoke принимает вывод типа Delegate не удается, поэтому используйте локальный. NB Вы, конечно, можете создать делегата на более раннем этапе и использовать его в обеих ветвях, чтобы избежать дублирования кода.

0 голосов
/ 15 октября 2010

Я создал для этого небольшого помощника:

class LayoutsEngine 
{
    internal static void ThreadSafeDoer(Form Form, Delegate Whattodo)
    {
        if (!Form.IsDisposed)
        {
            if (Form.InvokeRequired)
            {
                Form.Invoke(Whattodo);
            }
            else
            {
                Whattodo.DynamicInvoke();
            }
        }
    }
}

и использую его так:

    LayoutsEngine.ThreadSafeDoer(this, new MethodInvoker(delegate()
    {
         txtWhatever.Text=whatever();
         //  and any other GUI meddling
    }));

Кроме того, если у кого-то есть способ уменьшить MethodInvoker() здесь,пожалуйста, прокомментируйте.

0 голосов
/ 15 октября 2010

Если вы хотите адаптивный пользовательский интерфейс, вам нужен рабочий поток и основной поток для обновления пользовательского интерфейса.

Я думаю, что вам нужен рабочий поток для поиска полученных данных, а затем из вашего рабочего потока используйте делегата для записи данных в ваш TextBox.

Что-то вроде:

public delegate void textBoxWriteDelegate(string msg);

private void textBoxWrite(string sMess) {
  textBox.AppendText(sMess);
}

А из твоего рабочего потока:

Invoke(new textBoxWriteDelegate(textBoxWrite), new object [] { "Launching ftp cmd line .... \n" });

Извините, это net 1.1;) Есть лучший способ написать делегатов в версии 2.0 и выше ...

...