#.net #vb.net #winforms
#.net #vb.net #winforms
Вопрос:
Я использую visual Studio 2008. В программе я пытаюсь создать кнопку, при нажатии на которую запускается довольно длительный цикл. Для завершения требуется пара минут. Пока это происходит, я пытаюсь отправить обновление хода выполнения циклов в настроенное мной поле списка. К сожалению, эти обновления не попадают в listbox до завершения цикла. Я пробовал это с несколькими различными типами (индикатор выполнения / поле расширенного текста и т.д.). Кажется, я не могу внести какие-либо изменения в форму во время выполнения цикла.
Есть ли какая-то опция или событие, которые мне нужно указать, чтобы иметь возможность вносить изменения в форму во время цикла?
Комментарии:
1. Извините, я преобразовал свой комментарий в ответ. В любом случае, я подозреваю , что вы путаете потоки и методы. Вам придется проделать довольно много дополнительной работы, чтобы создать второй поток. Без публикации вашего кода трудно сказать, сделали вы это или нет.
2. ну, сейчас я не на своем домашнем компьютере с кодом, но код для потока выглядит примерно так…. объявляем worker как new threading.thread(адрес ‘functionname’) (я думаю, что-то в этом роде), затем, когда вы нажимаете на кнопку, запускается worker.start() и выполняется код в функции, определенной в потоке? звучит вроде как правильно, извините, я хотел бы, чтобы код был здесь передо мной.
Ответ №1:
Если вам не нужно, чтобы пользователь взаимодействовал (останавливал или приостанавливал) с процессом, вы можете просто использовать:
lblMyStatus.Text = "Finished " i.ToString() "%";
lblMyStatus.Refresh(); //this forces the label to redraw itself
Это обновит графический интерфейс, но он по-прежнему не будет реагировать на пользовательский ввод. Если вам нужно отвечать на вводимые пользователем данные во время цикла, то фоновый рабочий является одним из вариантов…
Комментарии:
1. это должно работать идеально, я единственный, кто когда-либо будет использовать программу, и этот цикл является частью настройки программы, поэтому нет, я не хочу, чтобы пользователь мог что-либо делать, пока это происходит, поэтому это решение должно работать идеально.
2. но будьте осторожны, тай. Если ваш цикл запущен, и они щелкнут несколько раз, окно станет серым и заявит, что не отвечает
Ответ №2:
Урок здесь заключается в том, что вы никогда не должны выполнять какую-либо трудоемкую работу в потоке GUI. Это потому, что графический интерфейс не может быть обновлен, пока процессор занят выполнением ваших вычислений. Без нескольких потоков ваше приложение может выполнять только одно действие одновременно, поэтому что-то должно быть упреждающим. Имея это в виду, ответ заключается в том, чтобы перенести ваши длительные вычисления в отдельный поток. Использование встроенного BackgroundWorker
компонента является простым решением.
В документации, связанной выше, даже приведен отличный пример:
Чтобы попробовать этот код, создайте приложение Windows Forms. Добавьте элемент управления Label с именем resultLabel и добавьте два элемента управления Button с именами startAsyncButton и cancelAsyncButton. Создайте обработчики событий нажатия для обеих кнопок. На вкладке Компоненты панели инструментов добавьте компонент BackgroundWorker с именем backgroundWorker1. Создайте обработчики событий DoWork, ProgressChanged и RunWorkerCompleted для BackgroundWorker. В коде формы замените существующий код следующим кодом:
VB.NET:
Imports System.ComponentModel
Imports System.Windows.Forms
Namespace BackgroundWorkerSimple
Public Partial Class Form1
Inherits Form
Public Sub New()
InitializeComponent()
backgroundWorker1.WorkerReportsProgress = True
backgroundWorker1.WorkerSupportsCancellation = True
End Sub
Private Sub startAsyncButton_Click(sender As Object, e As EventArgs)
If Not backgroundWorker1.IsBusy Then
' Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync()
End If
End Sub
Private Sub cancelAsyncButton_Click(sender As Object, e As EventArgs)
If backgroundWorker1.WorkerSupportsCancellation Then
' Cancel the asynchronous operation.
backgroundWorker1.CancelAsync()
End If
End Sub
' This event handler is where the time-consuming work is done.
Private Sub backgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs)
Dim worker As BackgroundWorker = TryCast(sender, BackgroundWorker)
For i As Integer = 1 To 10
If worker.CancellationPending Then
e.Cancel = True
Exit For
Else
' Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500)
worker.ReportProgress(i * 10)
End If
Next
End Sub
' This event handler updates the progress.
Private Sub backgroundWorker1_ProgressChanged(sender As Object, e As ProgressChangedEventArgs)
resultLabel.Text = (e.ProgressPercentage.ToString() amp; "%")
End Sub
' This event handler deals with the results of the background operation.
Private Sub backgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs)
If e.Cancelled Then
resultLabel.Text = "Canceled!"
ElseIf e.[Error] IsNot Nothing Then
resultLabel.Text = "Error: " amp; e.[Error].Message
Else
resultLabel.Text = "Done!"
End If
End Sub
End Class
End Namespace
C#:
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace BackgroundWorkerSimple
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
}
private void startAsyncButton_Click(object sender, EventArgs e)
{
if (!backgroundWorker1.IsBusy)
{
// Start the asynchronous operation.
backgroundWorker1.RunWorkerAsync();
}
}
private void cancelAsyncButton_Click(object sender, EventArgs e)
{
if (backgroundWorker1.WorkerSupportsCancellation)
{
// Cancel the asynchronous operation.
backgroundWorker1.CancelAsync();
}
}
// This event handler is where the time-consuming work is done.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i )
{
if (worker.CancellationPending)
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
}
// This event handler updates the progress.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
resultLabel.Text = (e.ProgressPercentage.ToString() "%");
}
// This event handler deals with the results of the background operation.
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
resultLabel.Text = "Canceled!";
}
else if (e.Error != null)
{
resultLabel.Text = "Error: " e.Error.Message;
}
else
{
resultLabel.Text = "Done!";
}
}
}
}
Комментарии:
1. большое вам спасибо за этот пример кода, решение veljkoz должно решить мою непосредственную проблему, но я уверен, что захочу изучить ваше решение в будущем. еще раз спасибо.
Ответ №3:
Приложение.Doevents является ключом к этой проблеме.
Это обновит поле списка и заставит программу реагировать на другие действия. Например, кнопка щелкает во время выполнения цикла. Таким образом, у вас может быть кнопка для прерывания длительно выполняемой задачи.
private abort as boolean =false
sub doit()
abort=false
do until abort
listbox1.additem "weee"
my.application.doevents
loop
end sub
sub abortbutton_click(...) handles abortbutton.click()
abort=true
end sub
Из MSDN:
http://msdn.microsoft.com/en-us/library/system.windows.forms.application.doevents.aspx
Если вы вызываете DoEvents в своем коде, ваше приложение может обрабатывать другие события. Например, если у вас есть форма, которая добавляет данные в ListBox и добавляет DoEvents в ваш код, ваша форма перерисовывается при перетаскивании на нее другого окна. Если вы удалите DoEvents из своего кода, ваша форма не будет перерисовываться до тех пор, пока обработчик события click кнопки не завершит выполнение.
Комментарии:
1. -1 Единственное,
Application.DoEvents
что является ключевым, — это ошибки повторного входа, которые невозможно отладить, и множество других сложных проблем. Удивительно, как часто я вижу, что это публикуется как ответ на подобные вопросы. Это всегда неправильная форма.2. Я не согласен. Это решает проблему и чрезвычайно легко отлаживается, если вы проверяете стек вызовов. Нет необходимости запускать новые потоки и использовать backgroundworkers для решения такого рода мелких проблем.
3. Я не думаю, что DoEvents — это плохо. Важно понимать, что во время этого могут выполняться какие-либо действия. НАПРИМЕР. Если у вас есть программа, которая подсчитывает все строки, когда вы нажимаете countlines. и кнопка, которая изменяет файл, из которого вы читаете. тогда вам нужно будет убедиться, что либо заблокировать нажатие кнопки inputfile во время подсчета, либо заблокировать подсчет количества нажатий. Отличные события, когда вы отключаете все свои кнопки и вам нужна кнопка «Стоп».
Ответ №4:
Я бы предложил использовать фонового рабочего для запуска вашего цикла и реализации изменений пользовательского интерфейса с использованием события ProgressChanged.
Это может помочь вам начать http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx
Комментарии:
1. Краткий вопрос по этому поводу, в настоящее время у меня есть операция кнопки, запускающая поток, который обрабатывает цикл. Таким образом, фактический цикл находится в другом потоке, который, как я надеялся, исправит проблему, но этого не происходит. Изменит ли изменение этого потока на фоновый рабочий процесс значение?
2. Если ваш цикл фактически находится в другом потоке, преимущество использования фонового рабочего процесса заключается в том, что у него уже есть событие ProgressChanged, на которое вы можете подписаться. Вам не нужно создавать делегатов и вызывать Invoke для внесения изменений в пользовательский интерфейс, пока эти изменения выполняются в обработчике событий ProgressChanged.
Ответ №5:
Вы выполняетесь в потоке пользовательского интерфейса.
Пока поток пользовательского интерфейса находится в вашем цикле, он не может обновить вашу форму или ответить на системные сообщения (например, «Перерисуйте свою программу намотки!»).
Это одна из первых реальных проблем, с которыми сталкиваются программисты, разрабатывающие новые формы. У вас есть два варианта.
Во-первых, если ваша работа недолговечна, признайте, что вы не можете обновить свою форму, пока ваша логика работает. Сдавайтесь. Перестаньте хотеть перемещать этот индикатор выполнения. Остановите это. Просто делайте свою работу и готово.
Другой вариант — запустить многопоточность. Два других (на данный момент) ответа предполагают это. Работа с потоками сложна для начинающих программистов. Черт возьми, это сложно для большинства программистов. Я оставляю вам решать, стоит ли пытаться это сделать. Но, если вы это сделаете, поймите, что вы не можете обновить пользовательский интерфейс из фонового потока; вы будете Invoke()
редактировать повсюду, поверьте мне.