Коллеги,
Столкнулся со следующей проблемой. Имеется оконное приложение. Иногда оно выполняет длительные операции. В таких случаях в отдельном потоке запускается другое окно методом Application.Run(), в котором имеется ProgressBar. Как только длительный процесс завершается, главное окно сообщает об этом дочернему и оно закрывается. Все работало нормально до тех пор, пока не потрбовалось показывать диалоговое окно после завершения длительной операции. Теперь после того, как я жму на кнопку Ок диалогового окна, главное окно приложения по какой-то причине оказывается...позади всех активных окон Windows.
Привожу код:
1. Класс формы с ProgressBar
public partial class ProgressForm : Form
{
private int numberOfOperations;
public ProgressForm(int numberOfOpertaions, string initialMessage)
{
// стандартная инициализация + задаю размер шага progressBar
...
this.ShowInTaskbar = false;
this.ControlBox = false;
}
// Этот метод вызывается из другого потока, что бы сообщить о следующем шагеpublic void ReportProgress(string message)
{
if (this.InvokeRequired)
{
BeginInvoke(new MethodInvoker(delegate() { ReportProgress(message); }));
}
else
{
pb.PerformStep();
lblStatus.Text = message;
this.Refresh();
}
}
// Когда длительная операция завершена, другой поток инициирует закрытие этого окнаpublic void CloseForm()
{
if (this.InvokeRequired)
{
BeginInvoke(new MethodInvoker(delegate() { this.Dispose(false); }));
}
else
{
this.Dispose(false);
}
}
}
public partial class MainForm
{
...
public void HeavyFunction(...)
{
Program.ShowProgressForm(...);
// Выполняем различные операции, иногда сообщаем о прогрессе
...
Program.ReportProgres(...);
...
// Закрываем
Program.CloseProgressForm();
// А вот теперь вызываем диалоговое окно, что бы сообщить пользователю об окончании операции
MessageBox.Show(..., MessageBoxButtons.Ok, ...);
}
}
Собственно проблема возникает, когда я взываю MessageBox.Show() и жму на Ok — главная форма оказывается позади всех активных окон. Если просто выполнять функцию без вызова ProgressForm в другом потоке — все Ок. Если вызывать ProgressForm, но не вызывать MessageBox.Show() — тоже все в порядке. Подскажите пожалуйста в чем может быть проблема. На мой взгляд либо все решается одной строкой, либо я что-т опринципиально сделал не правильно и придется пересмотреть архитектуру.
Спасибо.
28.07.10 23:56: Перенесено модератором из '.NET' — TK
Re: MessageBox.Show отправляет главную форму на задний план
Здравствуйте, sVenom, Вы писали:
V>Собственно проблема возникает, когда я взываю MessageBox.Show() и жму на Ok — главная форма оказывается позади всех активных окон. Если просто выполнять функцию без вызова ProgressForm в другом потоке — все Ок. Если вызывать ProgressForm, но не вызывать MessageBox.Show() — тоже все в порядке. Подскажите пожалуйста в чем может быть проблема. На мой взгляд либо все решается одной строкой, либо я что-т опринципиально сделал не правильно и придется пересмотреть архитектуру.
Зачем вам нужен "многопоточный" UI? Покажите MessageBox.Show из основного потока.
Если у Вас нет паранойи, то это еще не значит, что они за Вами не следят.
Re[2]: MessageBox.Show отправляет главную форму на задний пл
Я и так показываю MessageBox из основного потока. Проясню некоторые детали. Представте себе главную форму с несколькими кнопками, выполняющие раличные операции, причем эти операции взаимоисключающие, т.е. когда выполняется одна, никакие другие выполняться не должны. Эти "тяжелые" операции выполняются в главном потоке, соответственно пока это происходит главное окно кажется пользователю "подвисшим". Что бы сделать интерфейс более дружественным было решено вызывать в новом потоке форму, на которой будет ProgressBar, а главный поток будет периодически репортить о прогрессе операции. Приведенный мною способ вызова дополнительной формы — единственно мне известный. Поясню:
1) Если вызывать дополнительную форму методом new Form(...).Show(), то оно будет не живой, так как отсутствует цикл обработки сообщений.
2) Если я попытаюсь вызвать дочернюю форму из основного потока методом Application.Run(), то поток подиснет, ибо цикл обработки сообщений бесконечный.
Соответственно единственный выход сделать так, что бы новая форма была "живой", а основной поток не останавливался — новая форма в новом потоке черех Application.Run(), как у меня и сделано.
В принципе это работало без проблем до тех пор, пока мне не потребовалось выдавать диалоговое окно по завершении длительной операции. Что было раньше:
ОП — основной поток, ДП — дополнительный поток
1. ОП выполняется и периодически извещает ДП о прогрессе
2. После заврешения операции ОП вызывает CloseForm() на объекте ProgressForm ДП
3. Происходит уничтожение ProgressForm, цикл обработки оконных сообщений завершается, ДП умирает
4. ОП вызывает метод MessageBox.Show() что бы уведомить пользователя о завершении.
Так вот, если выкинуть последний шаг, то все Ок. Если же он присутствует, то после того, как я кликну на Ок месседж-бокса, то есть когда я выйду из 4-го шага, форма теряет выделение и отправляется на самый задний план Windows.
Как с этим бороться?
Re[3]: MessageBox.Show отправляет главную форму на задний пл
Здравствуйте, sVenom, Вы писали:
V>Я и так показываю MessageBox из основного потока. Проясню некоторые детали. Представте себе главную форму с несколькими кнопками, выполняющие раличные операции, причем эти операции взаимоисключающие, т.е. когда выполняется одна, никакие другие выполняться не должны. Эти "тяжелые" операции выполняются в главном потоке, соответственно пока это происходит главное окно кажется пользователю "подвисшим". Что бы сделать интерфейс более дружественным было решено вызывать в новом потоке форму, на которой будет ProgressBar, а главный поток будет периодически репортить о прогрессе операции. Приведенный мною способ вызова дополнительной формы — единственно мне известный. Поясню: V>1) Если вызывать дополнительную форму методом new Form(...).Show(), то оно будет не живой, так как отсутствует цикл обработки сообщений. V>2) Если я попытаюсь вызвать дочернюю форму из основного потока методом Application.Run(), то поток подиснет, ибо цикл обработки сообщений бесконечный. V>Соответственно единственный выход сделать так, что бы новая форма была "живой", а основной поток не останавливался — новая форма в новом потоке черех Application.Run(), как у меня и сделано. V>В принципе это работало без проблем до тех пор, пока мне не потребовалось выдавать диалоговое окно по завершении длительной операции. Что было раньше: V>ОП — основной поток, ДП — дополнительный поток
V>1. ОП выполняется и периодически извещает ДП о прогрессе V>2. После заврешения операции ОП вызывает CloseForm() на объекте ProgressForm ДП V>3. Происходит уничтожение ProgressForm, цикл обработки оконных сообщений завершается, ДП умирает V>4. ОП вызывает метод MessageBox.Show() что бы уведомить пользователя о завершении.
V>Так вот, если выкинуть последний шаг, то все Ок. Если же он присутствует, то после того, как я кликну на Ок месседж-бокса, то есть когда я выйду из 4-го шага, форма теряет выделение и отправляется на самый задний план Windows. V>Как с этим бороться?
Такие "тяжелые" операции обычно выполняются на отдельном потоке, а в основном отображается форма с прогрессом
Re: MessageBox.Show отправляет главную форму на задний план
Use an overload of the Show method, which enables you to specify an owner window. Otherwise, the message box is owned by the window that is currently active.
Принимаю платежи в любой валюте
Re[4]: MessageBox.Show отправляет главную форму на задний пл
Согласен, сделано принципиально неправильно. Гораздо логичнее, если я буду вызывать Application.Run() для прогресс бара из основного потока, а тяжелая операция выпоняется в другом gjnjrt и репортит в основной. По окончании дополнительный поток убивает цикл Application.Run() и выполнение основного потока продолжается.
Жалко, что раньше об этом не подумал, типо работает и работает.
Но с другой стороны вопрос остается открытым — в связи с чем основная форма отправляется на задний план? Ведь MessageBox вызывается из освного потока, то есть у него родителем является главная форма.
Re[5]: MessageBox.Show отправляет главную форму на задний пл
V>Но с другой стороны вопрос остается открытым — в связи с чем основная форма отправляется на задний план? Ведь MessageBox вызывается из освного потока, то есть у него родителем является главная форма.
Вы в параметрах метода Show объект главного окна указываете (то, что я в предыдущем посте написал)? Сам по себе тот факт, что вы это из основного потока вызываете, нисколько не влияет на то — будет главная форма родителем или нет.
Принимаю платежи в любой валюте
Re[6]: MessageBox.Show отправляет главную форму на задний пл
Метод Show явно не вызывается вообще. Вместо этого вызывается Application.Run() в дополнительном потокею Завтра переделаю это по-человечески и посмотрим, что получится.
Re[7]: MessageBox.Show отправляет главную форму на задний пл
Здравствуйте, sVenom, Вы писали:
V>Так вот, если выкинуть последний шаг, то все Ок. Если же он присутствует, то после того, как я кликну на Ок месседж-бокса, то есть когда я выйду из 4-го шага, форма теряет выделение и отправляется на самый задний план Windows. V>Как с этим бороться?
mainForm.Activate();
Но подсказывают правильно. Тяжелые операции в фоновом потоке, основной пусть в Idle. Для того чтобы запретить любой ввод в главной форме можно ее выключить (Enabled = false). Чтобы оставить форму отзывчивой но выключить весь ввод, можно пробежать по детям формы и выключить их. После завершения тяжелой операции включить ранее выключенное.
Re[4]: MessageBox.Show отправляет главную форму на задний пл
Коллеги,
Все, разобрался. Привожу мое решение, может кому-нибудь пригодиться:
1. ProgressForm
public partial class ProgressForm : Form
{
private static ProgressForm pf = null;
...
private ProgressForm(int numberOfOperations, string initialMessage)
{
// Инициализация
...
this.TopMost = true;
}
private void ReportProgressInternal(string message)
{
if (this.InvokeRequired)
{
BeginInvoke(new MethodInvoker(delegate() {
ReportProgressInternal(message);
}));
}
else
{
pb.PerformStep();
lblStatus.Text = message;
}
}
private void CloseFormInternal()
{
if (this.InvokeRequired)
{
BeginInvoke(new MethodInvoker(delegate() { CloseFormInternal(); }));
}
else
{
this.Dispose();
}
}
// Думал здесь ограничиться вызовом Show(), но форма тогда не обновляется, так что
// оставил вызов в новом потокеpublic static void ShowProgressForm(int numberOfOperations, string initialMessage)
{
ThreadStart starter = delegate
{
pf = new ProgressForm(numberOfOperations, initialMessage);
Application.Run(pf);
};
new Thread(starter).Start();
}
public static void ReportProgress(string message)
{
pf.ReportProgressInternal(message);
}
public static void CloseProgressForm()
{
pf.CloseFormInternal();
}
}
2. Маленький класс-хелпер для вызова "тяжелых функций"
public class ThreadHelper
{
public delegate void SeparateThreadFunction();
public static void ExecuteFunction(SeparateThreadFunction func)
{
ThreadStart ts = delegate { func.Invoke(); };
Thread t = new Thread(ts);
t.Start();
t.Join(); // Блокируем основной поток
}
}
3. Пример "тяжелой функции"
public class SomeClass
{
public static void SomeFunction()
{
...bla-bla-bla
ProgressForm.ShowProgressForm(...);
...bla-bla-bla
ProgressForm.ReportProgress(...);
...bla-bla-bla
ProgressForm.CloseProgressForm();
}
}
4. Класс-обертка, реализующий IWin32Window
public class WindowWrapper : IWin32Window
{
private IntPtr hwnd;
public WindowWrapper(IntPtr hwnd)
{
this.hwnd = hwnd;
}
public IntPtr Handle
{
get { return hwnd; }
}
}
5. Вызов всего этого хозяйства из главной формы
public class MainForm
{
...
SomewhereInTheForm(...)
{
// Так как следующая строка заблокирует основной поток, обновим главнео окноthis.Refresh();
ThreadHelper.ExceuteFucntion(SomeClass.SomeFunction);
// А вот и решение моей главной проблемы, когда окно пряталось на задний план
MessageBox.Show(new WindowWrapper(this.Handle),...);
}
}
По сути, как я и думал, все решалось элементарно — добавить параметр IWin32Window в вызов MessageBox.Show(...), зато вы помогли пересмотреть некорректно реализованный принцип.
Всем спасибо за советы.
Re[5]: MessageBox.Show отправляет главную форму на задний пл