How might one go about creating a Modeless MessageBox? Do I have to just create my own Windows Form class and use that? If so, is there an easy way of adding a warning icon (rather than inserting my own image of one) and resizing based on text volume?
If you need a message box that just displays itself while your code continues running in the background (the box is still modal and will prevent the user from using other windows until OK is clicked), you can always start the message box in its own thread and continue doing what ever you do in the original thread:
// Do stuff before.
// Start the message box -thread:
new Thread(new ThreadStart(delegate
{
MessageBox.Show
(
"Hey user, stuff runs in the background!",
"Message",
MessageBoxButtons.OK,
MessageBoxIcon.Warning
);
})).Start();
// Continue doing stuff while the message box is visible to the user.
// The message box thread will end itself when the user clicks OK.
You'll have to create a Form and use Show() to display it Modeless. MessageBox.Show(...) behaved Modal as seen in the example by ghiboz; "Description of the message" is displayed until the user presses a button.
With MessageBox.Show(...) you get the result as soon as the messagebox is closed; with a modeless message box, your code will have to have a mechanism such as an event to react when the user eventually selects something on your message box.
Short of writing the code, you could create a small form that in the constructor does the following
Takes a parameter string as the message to display
Fills up a label on the form with this string
Loads an icon with one of the following (pass in an Enum to the constructor)
SystemIcons.Application
SystemIcons.Asterix
SystemIcons.Error
SystemIcons.Exclamation
SystemIcons.Hand
SystemIcons.Information
SystemIcons.Question
SystemIcons.Shield
SystemIcons.Warning
SystemIcons.WinLogo
Calls Show() which will cause it to be a modal dialog
If you really wanted, you could listen to an event that is fired when the OK button is pushed.
You can use the standard system warning icon using SystemIcons
You have to either use form and call showDialog()
And for icons use
MessageBoxIcon.Warning
Note: this will create a Modal dialog box, which is not what the question is asking
here is a sample code
if (MessageBox.Show("Description of the message", "Caption text", MessageBoxButtons.YesNo, MessageBoxIcon.Error) == DialogResult.Yes)
{
// Do some stuff if yes pressed
}
else
{
// no pressed
}
//no commnet
object sync = new object();
ManualResetEvent Wait = new ManualResetEvent();
//you should create a place holder named MessageData for Message Data.
List<MessageData> Messages = new List<MessageData>();
internal void ShowMessage(string Test, string Title, ....)
{
MessageData MSG = new MessageData(Test, Title);
Wait.Set();
lock(sync) Messages.Add(MSG);
}
// another thread should run here.
void Private_Show()
{
while(true)
{
while(Messsages.Count != 0)
{
MessageData md;
lock(sync)
{
md = List[0];
List.RemoveAt(0);
}
MessageBox.Show(md.Text, md.Title, md....);
}
Wait.WaitOne();
}
}
needs more threads and more code(I don't have enough time to write) for concurrent messageboxes.
Related
I'm having an issue closing a program when there is a large amount of dialogs open. I was able to reproduce the bug using the following functions. The parent form on which this runs on is created using Application.Run(mainform).
When button1 is clicked, it creates a very basic form that opens another dialog of itself when "ok" is pressed. It then initiates the OnShutdown() function. This waits 15 seconds, then attempts to close all windows and exit the program. When more than 6 dummy windows are open, the function closes all the dummy windows but not the main window.
The steps for me reproducing the problem is:
Click button1
Close created heavy dialog
Click on on the msgbox
Click on button1 again
On the new heavy dialog, I press Ok to open another heavy dialog
Continue to open more heavy dialog, until 6-10 are open concurrently
Once the 15 seconds are up, all the dialogs close, but the parent form remains open, when it should be closed.
private void button1_Click(object sender,EventArgs e) {
BasicTemplate heavy = new BasicTemplate();
heavy.ShowDialog();
OnShutdown();
}
private void OnShutdown() {
timerSignals.Enabled=false;//quit receiving signals.
string msg = "";
msg+=Process.GetCurrentProcess().ProcessName+" ";
msg+=Lan.g(this,"will shut down in 15 seconds. Quickly click OK on any open windows with unsaved data.");
MsgBoxCopyPaste msgbox = new MsgBoxCopyPaste(msg);
msgbox.Size=new Size(300,300);
msgbox.TopMost=true;
msgbox.Show();
ODThread killThread = new ODThread((o) => {
Thread.Sleep(15000);//15 seconds
//Also happens with BeginInvoke()
Invoke((Action)(() => { CloseOpenForms(true); }));
Thread.Sleep(1000);//1 second
Invoke((Action)Application.Exit);
});
killThread.Start(true);
return;
}
public void Start(bool isAutoCleanup) {
_isAutoCleanup=isAutoCleanup;
if(_thread.IsAlive) {
return;//The thread is already running.
}
if(_hasQuit) {
return;//The thread has finished.
}
_dateTimeStart=DateTime.Now;
_thread.Start();
}
//Code for "OK" button on the heavy dialog created above
private void butOK_Click(object sender,EventArgs e) {
BasicTemplate formp = new BasicTemplate();
formp.ShowDialog();
}
private bool CloseOpenForms(bool isForceClose) {
for(int f=Application.OpenForms.Count-1;f>=0;f--) { //Count backwards to avoid out of bounds
if(Application.OpenForms[f]==this) {
continue;
}
Form openForm=Application.OpenForms[f];//Copy so we have a reference to it after we close it.
openForm.Hide();
if(isForceClose) {
openForm.Dispose();
}
else {
openForm.Close();//Attempt to close the form
if(openForm.IsDisposed==false) {
openForm.Show();//Show that form again
return false;
}
}
}
return true;
}
When line 3's "heavy.ShowDialog()" is changed to "heavy.Show()", the holdup no longer takes place. When multiple heavy dialogs are opened using a different button, the issue no longer takes place (up to ~20 new dialogs).
The shutdown sequence is run as a thread to allow the user to save any changes they have made before the program's database updates. I'm not sure if what I've implemented correctly shows the bug, but it at least produces similar events.
In my application, I am overriding the OnClose event the way shown below. Since the application can take some time to perform SynchronizeLotsaStuff, I want to notify the user that application will soon close.
I tried with a MessageBox, but that blocks the continuation of the program, and also displays an "OK" button that is not desired.
I guess I would prefer something more characteristic, such as fading/graying the window, or even a "splash screen" for closing, but a regular messagebox would be fine too.
// MainWindow.xaml.cs:
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
MessageBox.Show("Wait while application is closed...");
if (this.DataContext != null) {
var vm = this.DataContext as ShellViewModel;
// possibly long-running method
vm.SynchronizeLotsaStuff();
}
base.OnClosing(e);
}
UPDATE: Following Stijn Bernards advice, I put the MessageBox stuff inside a thread, but I haven't found (yes I googled) a proper way to terminate it. Even if Abort, the MessageBox keeps displaying after MainWindow has closed, until I click "OK" button.
// MainWindow.xaml.cs:
protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
{
var messagethread = new Thread(new ThreadStart(() => {
MessageBox.Show("Aguarde enquanto o aplicativo é encerrado...");
}));
messagethread.Start();
if (this.DataContext != null) {
var vm = this.DataContext as ShellViewModel;
// possibly long-running method
vm.SynchronizeLotsaStuff();
}
// UGLY UGLY UGLY (and doesn't work either)
messagethread.Abort();
base.OnClosing(e);
}
I have tried using the abort thread and to my suprises it isn't working.
And well I can say it doesn't look easy...
So I advice to look into this: click me
It's a good tutorial about how to create your own message box, which would then also help you with your problem of removing the ok button.
Cheers.
I apologize if this question has been answered tons of times, but I can't seem to find an answer that works for me. I would like to create a modal window that shows various progress messages while my application performs long running tasks. These tasks are run on a separate thread and I am able to update the text on the progress window at different stages of the process. The cross-thread communication is all working nicely. The problem is that I can't get the window to be on top of only other application windows (not every application on the computer), stay on top, prevent interaction with the parent window, and still allow the work to continue.
Here's what I've tried so far:
First, my splash window is a custom class that extends the Window class and has methods to update the message box. I create a new instance of the splash class early on and Show/Hide it as needed.
In the simplest of cases, I instantiate the window and call .Show() on it:
//from inside my secondary thread
this._splash.Dispatcher.Invoke(new Action(() => this._splash.Show());
//Do things
//update splash text
//Do more things
//close the splash when done
this._splash.Dispatcher.Invoke(new Action(() => this._splash.Hide());
This correctly displays the window and continues running my code to handle the initialization tasks, but it allows me to click on the parent window and bring that to the front.
Next I tried disabling the main window and re-enabling later:
Application.Current.Dispatcher.Invoke(new Action(() => this.MainWindow.IsEnabled = false));
//show splash, do things, etc
Application.Current.Dispatcher.Invoke(new Action(() => this.MainWindow.IsEnabled = true));
This disables all the elements in the window, but I can still click the main window and bring it in front of the splash screen, which is not what I want.
Next I tried using the topmost property on the splash window. This keeps it in front of everything, and in conjunction with setting the main window IsEnabled property I could prevent interaction, but this makes the splash screen appear in front of EVERYTHING, including other applications. I don't want that either. I just want it to be the topmost window within THIS application.
Then I found posts about using .ShowDialog() instead of .Show(). I tried this, and it correctly showed the dialog and did not allow me to click on the parent window, but calling .ShowDialog() makes the program hang waiting for you to close the dialog before it will continue running code. This is obviously, not what I want either. I suppose I could call ShowDialog() on a different thread so that that thread would hang but the thread doing the work would not...is that the recommended method?
I have also considered the possibility of not using a window at all and instead putting a full-sized window element in front of everything else on the page. This would work except that I have other windows I open and I'd like to be able to use the splash screen when those are open too. If I used a window element I would have to re-create it on every window and I wouldn't be able to use my handy UpdateSplashText method in my custom splash class.
So this brings me to the question. What is the right way to handle this?
Thanks for your time and sorry for the long question but details are important :)
You are correct that ShowDialog gives you most of the UI behavior that you want.
It does have the problem that as soon as you call it you block execution though. How could you possibly run some code after you show the form, but define what it should be before it's shown? That's your problem.
You could just do all of the work within the splash class, but that's rather poor practice due to tight coupling.
What you can do is leverage the Loaded event of Window to define code that should run after the window is shown, but where it is defined before you show it.
public static void DoWorkWithModal(Action<IProgress<string>> work)
{
SplashWindow splash = new SplashWindow();
splash.Loaded += (_, args) =>
{
BackgroundWorker worker = new BackgroundWorker();
Progress<string> progress = new Progress<string>(
data => splash.Text = data);
worker.DoWork += (s, workerArgs) => work(progress);
worker.RunWorkerCompleted +=
(s, workerArgs) => splash.Close();
worker.RunWorkerAsync();
};
splash.ShowDialog();
}
Note that this method is designed to encapsulate the boilerplate code here, so that you can pass in any worker method that accepts the progress indicator and it will do that work in a background thread while showing a generic splash screen that has progress indicated from the worker.
This could then be called something like this:
public void Foo()
{
DoWorkWithModal(progress =>
{
Thread.Sleep(5000);//placeholder for real work;
progress.Report("Finished First Task");
Thread.Sleep(5000);//placeholder for real work;
progress.Report("Finished Second Task");
Thread.Sleep(5000);//placeholder for real work;
progress.Report("Finished Third Task");
});
}
The accepted answer from #Servy helped me a lot! And I wanted to share my Version with the async and MVVM approach. It also contains a small delay to avoid "window flickering" for too fast operations.
Dialog Method:
public static async void ShowModal(Func<IProgress<string>, Task> workAsync, string title = null, TimeSpan? waitTimeDialogShow = null)
{
if (!waitTimeDialogShow.HasValue)
{
waitTimeDialogShow = TimeSpan.FromMilliseconds(300);
}
var progressWindow = new ProgressWindow();
progressWindow.Owner = Application.Current.MainWindow;
var viewModel = progressWindow.DataContext as ProgressWindowViewModel;
Progress<string> progress = new Progress<string>(text => viewModel.Text = text);
if(!string.IsNullOrEmpty(title))
{
viewModel.Title = title;
}
var workingTask = workAsync(progress);
progressWindow.Loaded += async (s, e) =>
{
await workingTask;
progressWindow.Close();
};
await Task.Delay((int)waitTimeDialogShow.Value.TotalMilliseconds);
if (!workingTask.IsCompleted && !workingTask.IsFaulted)
{
progressWindow.ShowDialog();
}
}
Usage:
ShowModal(async progress =>
{
await Task.Delay(5000); // Task 1
progress.Report("Finished first task");
await Task.Delay(5000); // Task 2
progress.Report("Finished second task");
});
Thanks again #Servy, saved me a lot of time.
You can use the Visibility property on Window to hide the whole window while the splash screen runs.
XAML
<Window ... Name="window" />
Code
window.Visibility = System.Windows.Visibility.Hidden;
//show splash
//do work
//end splash
window.Visibility = System.Windows.Visibility.Visible;
You can have your progress window's constructor take a Task and then ensure the window calls task.Start on the OnLoaded event. Then you use ShowDialog from the parent form, which will cause the progress window to start the task.
Note you could also call task.Start in the constructor, or in the parent form anywhere before calling ShowDialog. Whichever makes most sense to you.
Another option would be just to use a progress bar in the status strip of the main window, and get rid of the popup. This option seems to be more and more common these days.
I found a way to make this work by calling ShowDialog() on a separate thread. I created my own ShowMe() and HideMe() methods in my dialog class that handle the work. I also capture the Closing event to prevent closing the dialog so I can re-use it.
Here's my code for my splash screen class:
public partial class StartupSplash : Window
{
private Thread _showHideThread;
public StartupSplash()
{
InitializeComponent();
this.Closing += OnCloseDialog;
}
public string Message
{
get
{
return this.lb_progress.Content.ToString();
}
set
{
if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread)
this.lb_progress.Content = value;
else
this.lb_progress.Dispatcher.Invoke(new Action(() => this.lb_progress.Content = value));
}
}
public void ShowMe()
{
_showHideThread = new Thread(new ParameterizedThreadStart(doShowHideDialog));
_showHideThread.Start(true);
}
public void HideMe()
{
//_showHideThread.Start(false);
this.doShowHideDialog(false);
}
private void doShowHideDialog(object param)
{
bool show = (bool)param;
if (show)
{
if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread)
this.ShowDialog();
else
Application.Current.Dispatcher.Invoke(new Action(() => this.ShowDialog()));
}
else
{
if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread)
this.Close();
else
Application.Current.Dispatcher.Invoke(new Action(() => this.Close()));
}
}
private void OnCloseDialog(object sender, CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
}
On click of a button I have this code with is should show a dialog on top of the current form and display text, wait for one second, change the text and then finally close it:
Form p = new Form();
p.ShowDialog();
p.Text = "Start.";
Thread.Sleep(1000);
p.Text = "Counting.";
Thread.Sleep(1000);
p.Text = "End.";
Thread.Sleep(1000);
p.Close();
However once it executes p.ShowDialog(); it stops the code until the form p is closed and it doesn't work as I intended it to. Can I get some guidance on this? Not necessarily the solution, but at least maybe some keywords I could google on?
UPDATE: due to the difficulties I am facing trying to access business logic, which is irrelevant to the problem, I am delaying providing the working example. Stay tuned and sorry :)
SOLUTION: what I did is in fact used Show() instead of ShowDialog(). Since i was impossible to access form from business logic, BackgroundWorker came in handy and was being used between them. I cannot share any code or the layout of the project structure, but in conclusion, the accepted answer's main statement was the key to the solution :)
That is the point of ShowDialog(). It creates a modal form and does not return control to the calling function until you are done. If it doesn't need to be modal, then use .Show(). If it does need to be modal, then put code in the Form Load method to update the text as needed.
http://msdn.microsoft.com/en-us/library/c7ykbedk.aspx
taken from the link above:
When this method is called, the code following it is not executed until after the dialog box is closed.
if you want to form to display whatever it is you want to display you should write the code inside the the form itself, do that in an eventhandler of the form show event.
As you have found, ShowDialog is a blocking method that does not return until the dialog is closed. Your code to change the text and handle the delay needs to be within the dialogue itself.
However, it's worth noting the next problem that you'll find: if you call Thread.Sleep(1000) from the UI thread, your application will become unresponsive for 1 second at a time. This is probably not what you're aiming for! I'd suggest you look into the Timer or BackgroundWorker classes to handle this more smoothly.
Check this out:
public partial class Form2 : Form
{
delegate void SetTextCallback(string text);
delegate void CloseFormCallback();
public Form2()
{
InitializeComponent();
new Thread(DoMagic).Start();
}
public void DoMagic()
{
this.SetText("Start.");
Thread.Sleep(1000);
this.SetText("Counting.");
Thread.Sleep(1000);
this.SetText("End");
Thread.Sleep(1000);
this.CloseForm();
}
private void CloseForm()
{
if (this.InvokeRequired)
{
CloseFormCallback c = new CloseFormCallback(CloseForm);
this.Invoke(c);
}
else
{
this.Close();
}
}
private void SetText(string text)
{
if (this.label1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.label1.Text = text;
}
}
}
I'm having a problem with threads. My code:
Task.Factory.StartNew(() =>
{
cts = new CancellationTokenSource();
var lines = File.ReadLines(Path.Combine(Environment.CurrentDirectory, "urls.txt"));
try
{
var q = from line in lines.AsParallel().WithDegreeOfParallelism(30).WithCancellation(cts.Token)
let result = Parse(line, cts.Token)
select new
{
res = result
};
foreach (var x in q)
{
if (x != null)
{
Console.WriteLine("{0}", x.res);
}
}
}
catch (OperationCanceledException ex)
{
Console.WriteLine(ex.Message);
}
});
Now in Parse I have:
public String Parse(String url,CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
/* many lines of code */
InputForm iForm = new InputForm();
iForm.setPageData(pageData);
if (iForm.ShowDialog() == DialogResult.OK)
{
string userInput = iForm.textBox.Text;
/* code block */
return result;
} else {
return Parse(newUrl,ct);
}
}
I'm using ShowDialog because I need to get user input from iForm (this form has a timer and is auto closed after 60 seconds). Now, when I opened about 30 forms and click Cancel (on main form) this dialog forms need to be closed manualy. Is it posible to close this form after clicking Cancel?
I do this a lot.
What you're going to need to do is
create a way to communicate with your Main Thread Of Execution (MTOE)
call your main thread and wait for the response
in your main thread, display your dialog box
set the return value for your thread
signal your thread that you are done
A custom event handler works great for getting a message from your thread back to the MTOE.
A ManualResetEvent is good for your thread to know when the MTOE is complete.
A class instance can be passed in an event handler that the MTOE uses to fill a few data items and pass back to the thread whenever it is done.
Typically, when I create my special class, it contains the event handler and the ManualResetEvent object.
From your MTOE, if you close your form, you can signal all of your waiting dialog boxes to Cancel.
This would require a little redesign, but I think it would give you what you are after.
You may want to look at http://msdn.microsoft.com/en-us/library/system.windows.forms.application.openforms.aspx
You could iterate over the open open forms and call close on those that are of type InputForm
EDIT:
The below comment is correct this would throw an exception. You would actually need something like FormToClose.BeginInvoke(delegate ()=> FormToClose.Close());