Windows.Forms, showing a disabled form in non-modal state - c#

I have some trouble showing a disabled Form in non-modal state. Here is the example code:
public partial class Form1 : Form
{
// ....
private void button1_Click(object sender, EventArgs e)
{
try
{
Form2 form = new Form2();
form.Enabled = false;
form.Show(); // works, but form has no owner
// form.Show(this); // gives an System.InvalidOperationException
// ...
// ... my program here shows a message box, ask user for something
// ... while 'form' is shown in the background
form.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
Any idea why Show() (without parameter) works, but Show(this) gives an exception? In my scenario, form must know its owner to be shown correctly, so I can do the following:
form.Enabled = false;
form.Owner=this;
form.Show();
but is this really a good solution?
EDIT: Thanks for the quick answers. Seems that we really found a bug in the framework here. In spite of your suggestions, I think I will keep with my solution, since disabling the form after the 'Show' gives an ugly visible effect to the user.

It's a classic cut-and-paste bug. Looks like they copied the code from ShowDialog(), it is indeed invalid to show a disabled form as a dialog. The user would be stuck and can't do anything anymore. But they forgot to remove the test in the Show() method. Just disable it after the Show() call.

From Microsoft's reference source:
public void Show(IWin32Window owner)
{
if (owner == this)
{
throw new InvalidOperationException(SR.GetString("OwnsSelfOrOwner", new object[] { "Show" }));
}
if (base.Visible)
{
throw new InvalidOperationException(SR.GetString("ShowDialogOnVisible", new object[] { "Show" }));
}
// Here!!!
if (!base.Enabled)
{
throw new InvalidOperationException(SR.GetString("ShowDialogOnDisabled", new object[] { "Show" }));
}
if (!this.TopLevel)
{
throw new InvalidOperationException(SR.GetString("ShowDialogOnNonTopLevel", new object[] { "Show" }));
}
if (!SystemInformation.UserInteractive)
{
throw new InvalidOperationException(SR.GetString("CantShowModalOnNonInteractive"));
}
if (((owner != null) && ((((int) UnsafeNativeMethods.GetWindowLong(new HandleRef(owner, Control.GetSafeHandle(owner)), -20)) & 8) == 0)) && (owner is Control))
{
owner = ((Control) owner).TopLevelControlInternal;
}
By the way, there is a MS Connect bug declared.

That or call Show(this) and then disable it are the only two ways that I can think of.

Related

ManagementObjectSearcher causes re-entrancy issues for onclick handler

I am having an odd problem with protecting a section of code. My application is a tray app. I create a NotifyIcon inside my class (ApplicationContext). I have assigned a balloon click handler and a double click handler to the NotifyIcon object. there is also a context menu but I am not showing all code. Only important pieces.
public class SysTrayApplicationContext: ApplicationContext
{
private NotifyIcon notifyIcon;
private MainForm afDashBoardForm;
public SysTrayApplicationContext()
{
this.notifyIcon = new NotifyIcon();
this.notifyIcon.BalloonTipClicked += notifyIcon_BalloonTipClicked;
this.notifyIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick;
// ... more code
}
Both handlers launch or create/show my form:
private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
openDashboard();
}
}
private void notifyIcon_BalloonTipClicked(object sender, EventArgs e)
{
openDashboard();
}
private void openDashboard()
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
There is a problem with the above code. Maybe more than 1. Issue: it is possible to display 2 dashboard forms which is not what I want. If user double clicks on tray icon while balloon message is displaying causes a race condition in openDashboard. I can reproduce this easily. So I added a lock around the code in openDashboard code and, to my surprise, that did NOT prevent 2 dashboard forms from displaying. I should not be able to create 2 MainForms. Where am I going wrong here?
here is the updated code with lock statement:
private void openDashboard()
{
lock (dashBoardFormlocker)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
}
Note: lock object was added to the class and initialized in constructor.
private object dashBoardFormlocker;
UPDATE: Showing more code. this is how code gets started :
static void Main()
{
if (SingleInstance.Start())
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
XmlConfigurator.Configure();
// For a system tray application we don't want to create
// a form, we instead create a new ApplicationContext. The Run method takes
Application.Run(new SysTrayApplicationContext());
SingleInstance.Stop();
SingleInstance.Dispose();
}
}
}
UPDATE 2: Provide more code for clarity
public partial class MainForm : Form
{
public MainForm()
{
log.Trace("MainForm constructor...");
InitializeComponent();
// ... code not shown
this.label_OSVersion.Text = getOSFriendlyName();
// .. more code
}
private string getOSFriendlyName()
{
try
{
string result = string.Empty;
var mgmtObj = (from x in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().OfType<ManagementObject>()
select x.GetPropertyValue("Caption")).FirstOrDefault();
result = mgmtObj != null ? mgmtObj.ToString() : string.Empty;
OperatingSystem os = Environment.OSVersion;
String sp = os.ServicePack ?? string.Empty;
return !string.IsNullOrWhiteSpace(result) ? result + sp : "Unknown";
}
catch (System.Exception ex)
{
log.Error("Error trying to get the OS version", ex);
return "Unknown";
}
}
}
The main UI thread must always pump a message loop to support communication from COM components.
So when you do a blocking operation from the UI thread like locking or joining a thread, (EDIT: edited based on Peter Duniho's fix) the UI thread will enter an 'alertable' state, allowing COM to dispatch certain type of messages, which in turn can cause re-entrancy issues like in your scenario.
Look at the answer to this question (Why did entering a lock on a UI thread trigger an OnPaint event?) for a much more accurate explanation.
Looking at the source code of ManagementObjectSearcher.Get there is a lock (inside Initialize), and since you call it from the constructor of your form, it may lead to the second event triggering while the form's constructor has not finished. The assignment to the dashBoardFormlocker variable only happens after the constructor finishes, so that would explain why it was null on the second entry.
The moral of the story is never do blocking operations on the UI thread.
Without a good, minimal, complete code example that reliably reproduces the problem, it's impossible to know for sure what the problem is. But the guess by answerer tzachs seems reasonable. If so, you can fix your problem by changing your method to look like this:
private bool _dashboardOpen;
private void openDashboard()
{
if (_dashboardOpen)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
}
else
{
log.Debug("Dashboard form does not exist, create it");
_dashboardOpen = true;
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
In that way, any re-entrant attempt to open the window will be detected. Note that you still need the check for null before actually activating; you can't activate a window that hasn't actually finished being created yet. The subsequent call to Show() will take care of activation anyway, so ignoring the activation in the re-entrant case shouldn't matter.

Accessing background worker from another form's button click event

I'm getting my way around c# slowly but surely lol in this code:
// create an instance of the main form
public formMain _formMain;
public void btnDynaDotCheck_Click(object sender, EventArgs e)
{
if (_formMain.bgWorker.IsBusy != true)
{
this.btnDynaDotCheck.Enabled = false;
_formMain.bgWorker.RunWorkerAsync("dr_begin_dd_check");
}
else
{
_formMain.returnMessage("Please wait untill the current task is finished...");
return;
}
}
I'm trying to access the background worker in formMain.cs from anotherForm.cs there is no errors in VS, but when run i get
"An unhandled exception of type 'System.NullReferenceException'
occurred in " and "Additional information: Object reference not set to
an instance of an object."
On this line:
if (_formMain.bgWorker.IsBusy != true)
So i'm not really getting access in this case eh?
Use dependency injection to inject a reference to your mainform into the otherone : somewhere in your mainform code do the following :
anotherForm _anotherForm = new anotherForm(this);
_anotherForm.Show();
assuming you are creating anotherform from code within the mainform, this is actually referring to the mainform.
In the constructor of anotherFrom do this :
public anotherForm(MainForm formMain){
_formMain = formMain;
}
This is by far the most elegant way to solve this issue. Because it makes clear that there is a dependency from one form to the other and makes the design intention clear.
Using a parent is also fine, but only if the mainform is really a parent of the other form.
Going via Application object will work, but the application object is a global and you hide your dependency that way.
_formMain = Application.OpenForms["formMain"];
Add this code in button click and try it.
When accessing _formMain from anotherForm:
I assume anotherForm is instantiated and called from _formMain like this:
anotherForm _anotherForm = new anotherForm();
_anotherForm.Show();
there's now several ways to access _formMain from _anotherForm but the easiest I think is to set _formMain as the parent of _anotherForm:
_anotherForm.Parent = this; // insert before _anotherForm.Show()
this way you can get hold of it in _anotherForm like this
public void btnDynaDotCheck_Click(object sender, EventArgs e)
{
...
formMain _formMain = this.Parent as formMain;
if(_formMain != null)
{
... // do whatever ever you have to do
}
}
but be careful... getting your BackgroundWorker in _formMain requires public methods you can call and return your BackgroundWorker.
Hope this helps!
Thanks for the help guys :)
I now have:
// create an instance of the formMain
formMain _formMain = new formMain();
public void btnDynaDotCheck_Click(object sender, EventArgs e)
{
if (_formMain.bgWorker.IsBusy != true)
{
this.btnDynaDotCheck.Enabled = false;
_formMain.bgWorker.RunWorkerAsync("dr_begin_dd_check");
}
else
{
_formMain.returnMessage("Please wait untill the current task is finished...");
return;
}
}
Which works :) it gets through to the main form:
public void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
// action to string format
string action = e.Argument as string;
if (action == "dr_begin_dd_check")
{
BeginInvoke((Action)(() =>
{
statusLabel.Text = "Access the bgw...";
}
));
} // dr_begin_dd_check
I'm now getting the error in the formMain:
Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
I'm not sure where the error lies in my above code or actually in the formMain section, or should i open a new question? :)
cheers guys
Graham

How to handle several windows form

I'm doing a small project to store movies. I'm using three windows form, a intro form, a main form and a new movie form. I also have a class that I call MovieManager that is the heart of the application. My problem is that I'm not sure how I should handle this windows.
Let's say I want the application to start with the intro form and when the user click on the OK-button the main form should appear. What is the best way to do this? Should I in the Program.cs create an object of the MovieManager class that show and hide the different windows form or should I in the Program.cs just start by showing the intro form?
You can simply do all staff in Program.cs when starting application. Show your IntroForm as dialog. If user clicks OK, then start main application form, otherwise close application.
static void Main()
{
IntroForm introForm = new IntroForm();
if (introForm.ShowDialog() != DialogResult.OK)
{
Application.Exit();
return;
}
Application.Run(new MainForm());
}
If you need single MovieManager instance for all these forms, then you can create it in Main method and pass same instance to IntroForm and MainForm:
MovieManager movieManager = new MovieManager();
IntroForm introForm = new IntroForm(movieManager);
if (introForm.ShowDialog() != DialogResult.OK)
{
Application.Exit();
return;
}
Application.Run(new MainForm(movieManager));
Also you can implement MovieManager as Singleton, which will be accessible everywhere via static property MovieManager.Instance.
What you call intro form, I should call a splash screen. In the program.cs I should just pop-up the splash screen (with a logo, title and info about the application, version number, etc.). The splash screen is shown for a certain amount of time (use a timer for this, or Thread.Sleep is alos possible altough a bit heavy).
When the splash screen closes show the MainForm is shown a from there you can instantiate a MovieManager or use a static MovieManager (it depends on its use). From the mainform you can then just instantiate and show new movie form(s).
We use a piece of code something like this:
static void Main(string[] args)
{
try
{
SplashScreen.ShowSplashScreen();
Application.DoEvents();
SplashScreen.WaitForVisibility(.5);
bool starting = true;
while (starting)
{
try
{
SplashScreen.SetStatus("Initialize mainform...");
starting = false;
Application.Run(new MainForm());
}
catch (Exception ex)
{
if (starting)
starting = XtraMessageBox.Show(ex.Message, "Fout bij initialiseren", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1) == DialogResult.Retry;
else
throw (ex);
}
}
}
catch (Exception ex)
{
if (ex is object)
XtraMessageBox.Show(ex.Message, "Algemene fout", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
}
}
And code in the splashscreen (excerpt) looks like this:
if (_splashForm == null)
{
ThreadPool.QueueUserWorkItem(RunSplashForm, null);
while (_splashForm == null || _splashForm.IsHandleCreated == false)
{
System.Threading.Thread.Sleep(50);
}
}
Maybe these links will also provide you some usefull information:
http://www.reflectionit.nl/Articles/Splash.aspx
And we used this as the basis for our own code:
http://www.codeproject.com/Articles/5454/A-Pretty-Good-Splash-Screen-in-C
There are different ways to show a new form. you can use MdiWinForm first you must change IsMdiContainer property to true then use these codes in the MainForm :
Form2 f2;
private void button1_Click(object sender, EventArgs e)
{
if (f2 == null) {
f2 = new Form2();
f2.MdiParent = this;
f2.FormClosed += delegate { f2 = null; };
f2.Show();
}
else {
f2.Activate();
}
}

In C# what is the best way to close a newly opened form if there is a failure in CTOR/Load event?

What is the best method [pattern] to close a newly opened Windows form in C#/.NET if there is a trappable error in the CTOR/_Load event ?
i.e.,
bool loadError;
MyForm_Load(...) {
try {
} catch (SomeException ex) {
// Alert the user
MessageBox.Show("There was a critical error. The form will close. Please try again.");
// There was some sort of error, and I just want the form to close now.
loadError = true;
return;
}
}
Now I want to act on loadError.
I've tired using the Activate event, but that yielded no results. Any suggestions on what the best way to go about doing this is ?
Update: Perhaps I should have been a little more explicit. Doing a "this.Close();" during the forms Load event will cause an exception to be thrown as a form can't be closed and the exception will be "Cannot call Close() while doing CreateHandle()".
As I mentioned in the comments, I tried with a sample example and called this.Closed() inside of the catch block. It worked just fine. The application showed the message box and didn't show me the form. I am using .NET3.5 SP1, though.
Suppose this error happens in earlier version of .NET Framework, can you try to see whether any of the followings works for you or not? They, again, seem to work fine on my machine, yet, cannot guarantee whether they will work on yours, though.
Put this.Close() in the finally block to see it works
finally {
if (this.loadError)
this.Close();
}
Defer the Form.Close() after Form.OnLoad event handler completes.
See the sample below: this does not contain any anonymous delegate or lambda expression for older versions of .NET FW. Please forgive me for partial class :)
using System;
using System.Windows.Forms;
namespace ClosingFormWithException
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public delegate void InvokeDelegate();
private void Form1_Load(object sender, EventArgs e)
{
try
{
MyTroublesomeClass myClass = new MyTroublesomeClass();
}
catch (ApplicationException ex)
{
MessageBox.Show("There was a critical error. The form will close. Please try again.");
this.BeginInvoke(new InvokeDelegate(CloseTheForm));
}
}
private void CloseTheForm()
{
this.Close();
}
}
class MyTroublesomeClass
{
public MyTroublesomeClass()
{
throw new ApplicationException();
}
}
}
Use the combination of 1 and 2 if 2 does not work:
finally {
if (this.loadError)
this.BeginInvoke(new InvokeDelegate(CloseTheForm));
}
Have you tried calling this.Close() in the FormShown event? That event is fired once when your form is first shown. You could check your error condition there and then call close.
I think you should call form.Dispose() and then set form = null. This should cover for the main usage scenario.

Issue with NotifyIcon not disappearing on Winforms App

I've got a .Net 3.5 C# Winforms app. It's got no GUI as such, just a NotifyIcon with a ContextMenu.
I've tried to set the NotifyIcon to visible=false and dispose of it in the Application_Exit event, as follows:
if (notifyIcon != null)
{
notifyIcon.Visible = false;
notifyIcon.Dispose();
}
The app gets to the code inside the brackets, but throws a null ref exception when it tries to set Visible = false.
I've read in a few places to put it in the form closing event, but that code never gets hit (maybe as I don't have a form showing as such?).
Where can I put this code so it actually works? If I don't put it in, I get the annoying lingering icon in the tray until you move the mouse over it.
Cheers.
EDIT
Just something extra I've noticed...........
I'm using ClickOnce in the app.........if I just exit the app via the ContextMenu on the NotifyIcon, no exception is logged.
Just when the Application_Exit event is fired after the applicaiton has checked for an upgrade here..
private void CheckForUpdate()
{
EventLogger.Instance.LogEvent("Checking for Update");
if (ApplicationDeployment.IsNetworkDeployed && ApplicationDeployment.CurrentDeployment.CheckForUpdate())
{
EventLogger.Instance.LogEvent("Update available - updating");
ApplicationDeployment.CurrentDeployment.Update();
Application.Restart();
}
}
Does this help?
On Windows 7, I had to also set the Icon property to null. Otherwise, the icon remained in the tray's "hidden icons" popup after the application had closed. HTH somebody.
// put this inside the window's class constructor
Application.ApplicationExit += new EventHandler(this.OnApplicationExit);
private void OnApplicationExit(object sender, EventArgs e)
{
try
{
if (trayIcon != null)
{
trayIcon.Visible = false;
trayIcon.Icon = null; // required to make icon disappear
trayIcon.Dispose();
trayIcon = null;
}
}
catch (Exception ex)
{
// handle the error
}
}
This code works for me, but I don't know how you are keeping your application alive, so... without further ado:
using System;
using System.Drawing;
using System.Windows.Forms;
static class Program
{
static System.Threading.Timer test =
new System.Threading.Timer(Ticked, null, 5000, 0);
[STAThread]
static void Main(string[] args)
{
NotifyIcon ni = new NotifyIcon();
ni.Icon = Icon.ExtractAssociatedIcon(Application.ExecutablePath);
ni.Visible = true;
Application.Run();
ni.Visible = false;
}
static void Ticked(object o) {
Application.Exit();
}
}
This is what I'm doing in WPF.
I am using this in conjunction to David Anson's Minimize to tray sample app, which lets you hook up a tray icon to a window (you may have multiple windows open).
Just added this code to the constructor for MinimizeToTrayInstance.
_window.Closed += (s, e) =>
{
if (_notifyIcon != null)
{
_notifyIcon.Visible = false;
_notifyIcon.Dispose();
_notifyIcon = null;
}
};
Sometimes Application_Exit event can be raised several times
Just put notifyIcon = null; in the end
if (notifyIcon != null)
{
notifyIcon.Visible = false;
notifyIcon.Dispose();
notifyIcon = null;
}
This code worked for me
this.Closed += (a, b) =>
{
if (notifyIcon1 != null)
{
notifyIcon1.Dispose();
notifyIcon1.Icon = null;
notifyIcon1.Visible = false;
}
};
Have you overridden the dispose method of the object where you've initialised the notifyIcon to also dispose the notifyIcon?
protected override void Dispose(bool disposing)
{
if (disposing)
{
notifyIcon.Dispose();
notifyIcon = null;
}
base.Dispose(disposing);
}
before im sorry for my bad english.
if u use "end" for exit program. then dont close notify icon.
before u will close notifyicon later close form.
u need to use me.close() for run form closing
example
its work...
notifyIcon1.Icon = Nothing
notifyIcon1.Visible = False
notifyIcon1.Dispose()
Me.Close()
but its not work
End
or only
Me.Close()

Categories

Resources