Is there a way to show a ContextMenu and block further execution until an item has been selected? In particular, I want to get behavior similar to ShowDialog() but for a ContextMenu.
The straight forward approach doesn't work:
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add("1", (s,e) => {value = 1;});
cm.Show(control, location);
since the Click callback isn't called directly from Show() but instead at some later point when the message loop processes the click event.
If you are unlucky, menu is garbage collected before the event is processed and in that case the event is just silently lost. (Meaning you can't really use local variables for ContextMenus in this way.)
This seems to work, but feels "unclean":
using (ContextMenu cm = new ContextMenu()) {
cm.MenuItems.Add("1", (s,e) => {value = 1;});
cm.Show(control, location);
Application.DoEvents();
}
Is there a better way?
Sorry for the first answer. Here is what I've tried. I made another Form where I put the context menu and a timer.Form2 is displayed as modal from Form1 then the timer shows the context menu on Form2.
Note that Form 2 has some properties set : to not be visible in task bar, not have boarders and the size should be equal with the size of the context menu.
Hope this helps.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
Form2 ctxForm = new Form2();
ctxForm.Location = this.PointToScreen(e.Location);
ctxForm.Size = new Size(0, 0);
ctxForm.ShowDialog();
}
}
}
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
private void timer1_Tick(object sender, EventArgs e)
{
//show menu once
contextMenuStrip1.Show(this, PointToClient(Location));
contextMenuStrip1.Focus();
timer1.Enabled = false;
}
private void contextMenuStrip1_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
this.Close();
}
}
You can easily prevent the garbage collection of the ContextMenu whilst it is still being shown.
The problem is that you are using a lambda as the event handler for the menu item. This is an
anonymous method and so not itself attached to any object instance that would cause the ContextMenu to be referenced and so kept alive. Add a method to the enclosing object and then create a standard EventHandler. That way the existence of the enclosing instance will keep the ContextMenu alive. Not as concise and very C# 1.0 but it will solve the problem.
Just wait for the menu to not be visiable.
ContextMenu cm = new ContextMenu();
cm.MenuItems.Add("1", (s,e) => {value = 1;});
cm.Show(control, location);
while (cm.Visible == true) Application.DoEvents();
Related
I have a form that minimizes when I open a secondary form (using a button click event). I want to be able to restore the original form when closing the secondary form. I have not been able to figure out how to restore the original. Here is the code I have (in example form)
The code from the main form:
private void BtnSecondaryForm_Click(object sender, EventArgs e)
{
// Open the secondary form.
FrmSecondaryForm fsf = new FrmSecondaryForm ();
fsf.Show();
this.WindowState = FormWindowState.Minimized;
}
Code from the secondary form:
private void FrmSecondaryForm_FormClosing(object sender, FormClosingEventArgs e)
{
// I need this code to be able to restore the main form.
}
Please don't just redirect me to someone else's similar question without explaining how I can get this to work in my application. I have looked at the other similar questions here on Stack Overflow already and don't understand how to get this to work.
You should find the form you want or restore, e.g.
using System.Linq;
...
private void FrmSecondaryForm_FormClosing(object sender, FormClosingEventArgs e)
var mainForm = Application
.OpenForms
.OfType<MainForm>()
.LastOrDefault();
if (mainForm != null) {
mainForm.WindowState = FormWindowState.Normal;
}
}
Move FormClosing event handler to primary form. This form is interested in the event anyway. I also changed event from FormClosing to FormClosed. Former can be called many times, but latter is called only once, when the form is actually closed.
private FrmSecondaryForm Fsf = null;
private void BtnSecondaryForm_Click(object sender, EventArgs e)
{
// Open the secondary form.
Fsf = new FrmSecondaryForm();
Fsf.Show();
Fsf.FormClosed += PrimaryForm_SecondaryFormClosed;
this.WindowState = FormWindowState.Minimized;
}
private void PrimaryForm_SecondaryFormClosed(object sender, FormClosedEventArgs e)
{
Fsf.FormClosed -= PrimaryForm_SecondaryFormClosed;
Fsf.Dispose();
Fsf = null;
this.WindowState = FormWindowState.Normal;
}
So you need Form2 to interact with Form1, which means that Form2 needs a reference to it.
Best is to do this during construction. But with forms, you should always keep a default no-parameter constructor, so we need to add a new one and make the original private.
//Add a new property (or field if you wish)
private Form formToMinimise { get; }
//Change this to be private
private SecondaryForm()
{
InitializeComponent();
}
//Add this new constructor as public
public SecondaryForm(Form form): this()
{
formToMinimise = form;
}
Now closing and restore original. We do a null check, just in case
private void FrmSecondaryForm_FormClosing(object sender, FormClosingEventArgs e)
{
formToMinimise?.WindowState = FormWindowState.Normal ;
}
Now you amend the creation and calling of your second form like this
private void BtnSecondaryForm_Click(object sender, EventArgs e)
{
// Create the secondary form with reference to this form
FrmSecondaryForm fsf = new FrmSecondaryForm(this);
fsf.Show();
this.WindowState = FormWindowState.Minimized;
}
My goal is to respond to the last child form of an mdi container closing(for example, close the parent itself or show something new). The problem I face is that the mdi container's MdiChildren collection still indicates that the container contains children.
The approach I have tried is
void childMdiForm_FormClosed(object sender, FormClosedEventArgs e)
{
if (this.MdiChildren.Any())
{
//Do stuff
}
}
MdiChildren.Count() is still 1 after the last child form is closed.
I have the same results by trying to handle the parentform.MdiChildActivate event.
The MdiChildren collection appears to not be updated yet when the child form has closed. The same occurs when there are multiple children: it will still contain all the crildren, it appears to update the collection at a later moment.
Is this the right approach? If not, how can I get an accurate count of the number of mdi children after closing a form?
Yes, the MdiChildren property doesn't get updated until after the FormClosed event is delivered. There's a universal solution for event order issues like this, you can elegantly get code to run after event handing is completed by using the Control.BeginInvoke() method. This code solves your problem:
protected override void OnMdiChildActivate(EventArgs e) {
base.OnMdiChildActivate(e);
this.BeginInvoke(new Action(() => {
if (this.MdiChildren.Length == 0) {
// Do your stuff
//...
MessageBox.Show("None left");
}
}));
}
WinForms can be a little quirky at times and this is one example of that. I am not entirely sure why closing the last MDI child does not make the MdiChildren property return an empty array immediately thereafter.
In most cases you're going to be keeping track of either the children forms or the data models yourself, so I would simply keep a local array for managing this:
List<Form> childForms = new List<Form>();
void AddChildWindow()
{
var window = new ChildForm();
window.MdiParent = this;
window.Tag = Guid.NewGuid();
window.FormClosed += (sender, e) => { OnMdiChildClosed(sender as Form, e.CloseReason); };
window.Shown += (sender, e) => { OnMdiChildShown(sender as Form); };
window.Show();
}
void OnMdiChildShown(Form window)
{
childForms.Add(window);
Trace.WriteLine(string.Format("Child form shown: {0}", window.Tag));
Trace.WriteLine(string.Format("Number of MDI children: {0}", childForms.Count));
}
void OnMdiChildClosed(Form window, CloseReason reason)
{
childForms.Remove(window);
Trace.WriteLine(string.Format("Child form closed: {0} (reason: {1})", window.Tag, reason));
Trace.WriteLine(string.Format("Number of MDI children: {0}", childForms.Count));
if (childForms.Count == 0)
{
// Do your logic here.
}
}
Here's a full test example:
namespace WindowsFormsApplication1
{
public partial class mdiMainForm : Form
{
private List<Form> _children = new List<Form>();
public mdiMainForm()
{
InitializeComponent();
}
private void mdiMainForm_Shown(Object sender, EventArgs e)
{
Form3 f3 = new Form3();
f3.MdiParent = this;
f3.FormClosed += mdiChildClosed;
_children.Add(f3);
f3.Show();
Form4 f4 = new Form4();
f4.MdiParent = this;
f4.FormClosed += mdiChildClosed;
_children.Add(f4);
f4.Show();
Form5 f5 = new Form5();
f5.MdiParent = this;
f5.FormClosed += mdiChildClosed;
_children.Add(f5);
f5.Show();
}
private void mdiChildClosed(Object sender, FormClosedEventArgs e)
{
if (_children.Contains((Form)sender))
_children.Remove((Form)sender);
if (_children.Count == 0)
MessageBox.Show("all closed");
}
}
}
You're code seems to imply you are checking the child Form's MdiChildren collection. But perhaps I'm misreading your code.
Use the MDI Form's 'MdiChildActivate' event:
private void Form1_MdiChildActivate(object sender, EventArgs e) {
if(this.MdiChildren.Length == 0) {
// replace with your "Do Stuff" code
MessageBox.Show("All Gone");
}
}
I know this answer is very late, but I have a much simpler solution to the problem than what has been provided so I'll share it here.
private void ChildForm_FormClosing(object sender, FormClosingEventArgs e)
{
// If WinForms will not remove the child from the parent, we will
((Form)sender).MdiParent = null;
}
Being a very first user in Windows Form Development I want to ask a simple question ...
I created a form(MainWindow.cs) within the solution which opens at the time of running that solution.
Latter I created a second form(SecondWindow.cs) and created a event so that it can be called from the first window by clicking a button.When the second window loded up the first window(MainWindow.cs) will be disabled.
Now I want to enable the MainWindow.cs when the second window is closed.
How to do That...
A simple solution I already have is to hide the MainWindow.cs and latter on closing the second window make a new object of first window and show it.But it is not a good way i think because there is already a object created by .net framework for first window automatically, so why we should create a new object of Mainwindow.cs .
Code Of First Window ( MainWindow.cs ) :
private void priceControllToolStripMenuItem_Click(object sender, EventArgs e)
{
SecondWindow price = new SecondWindow();
this.Enabled = false;
price.Show();
}
Code Of Second Window ( On closing SecondWindow.cs )
private void price_controll_FormClosed(object sender, FormClosedEventArgs e)
{
// what will goes here to make the MainWindow.cs to enable state
}
Use price.ShowDialog() to show second form as modal dialog. Main form will be disabled until you close second form:
private void priceControllToolStripMenuItem_Click(object sender, EventArgs e)
{
using(SecondWindow price = new SecondWindow())
price.ShowDialog();
}
You can pass the main form as an owner to the second form
private void priceControllToolStripMenuItem_Click(object sender, EventArgs e)
{
SecondWindow price = new SecondWindow() { Owner = this };
this.Enabled = false;
price.Show();
}
Then you can reference it from the second form.
private void price_controll_FormClosed(object sender, FormClosedEventArgs e)
{
Owner.Enabled = true;
}
This is my first time coding in C# and building applications in VS2010.
My task is to build a application, that has two windows. First with ListBox with several items. The second one opens on MouseDoubleClick on any item. At that point, second window opens and the title of it should be the same as ListBoxItem Name.
I have searched for a way to do this. But had no luck what so ever.
At this point I have this in code:
...
namespace WpfApplication20
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void MenuItem_Click(object sender, RoutedEventArgs e)
{
var newWindow = new Window1();
newWindow.Show();
}
private void seznamSporocil_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var newWindow = new Window1();
newWindow.Show();
}
}
}
At the end this should be an Email application, like Outlook or similar.
Thank you for all the help!
Whilst Freelancers answer is somewhat right, his solution assumes that the event was and will always be triggered by that one specific ListBox. You should really use the sender parameter to get a reference to the listbox that was triggering the event though, like so:
private void seznamSporocil_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
var listBox = sender as ListBox;
var item = listBox.SelectedItem as ListBoxItem;
var newWindow = new Window1();
newWindow.Title = item.Content.ToString();
newWindow.Show();
}
Some error-checking would not hurt either, i.e. verify wheather the sender object really is a ListBox-Type, etc.
Try with following:
var newWindow = new Window1();
newWindow.Title = listBox.SelectedItem.toString();
newWindow.Show();
Link:
http://msdn.microsoft.com/en-us/library/system.windows.controls.window.title.aspx
Hope its helpful.
if you run this snippet of code(put it in form1) in a fresh new winform app with 2 forms
private void Form1_Load(object sender, EventArgs e)
{
Form2 newForm = new Form2();
Button b = new Button();
newForm.Controls.Add(b);
b.Click += new EventHandler(click);
this.Show();
newForm.ShowDialog();
}
private void click(object sender, EventArgs e)
{
((Form)((Control)sender).Parent).ShowInTaskbar = false;
}
and you click on the button on the new form(should be form2), form2 will close.
How to keep it open?
It is not possible. I actually filed a bug report about it at Microsoft's feedback site but they flipped me the bird on it.
Admittedly, it is a tricky problem to solve, changing the property requires Windows Forms to recreate the window from scratch because it is controlled by a style flag. The kind you can only specify in a CreateWindowEx() call with the dwExStyle argument. Recreating a window makes it difficult to keep it modal, as required by the ShowDialog() method call.
Windows Forms works around a lot of User32 limitations. But not that one.
You keep it open by setting ShowInTaskbar to false before you ShowDialog();
private void Form1_Load(object sender, EventArgs e)
{
Form2 newForm = new Form2();
Button b = new Button();
newForm.Controls.Add(b);
b.Click += new EventHandler(click);
this.Show();
// add this line of code...
newForm.ShowInTaskbar = false;
newForm.ShowDialog();
}
private void click(object sender, EventArgs e)
{
((Form)((Control)sender).Parent).ShowInTaskbar = false;
}
Or just don't make the second form modal. This works also.
private void Form1_Load(object sender, EventArgs e)
{
Form2 newForm = new Form2();
Button b = new Button();
newForm.Controls.Add(b);
b.Click += new EventHandler(click);
this.Show();
newForm.Show();
}
I don't know the specific mechanism here, but clearly what is happening is that changing the flag (which actually changes one or more WS_EX_xxx window styles) is causing the modal pump of ShowDialog() to exit. This, in turn is causing you to (finally!) exit Form1_Load and then your newForm goes out of scope and is destroyed.
So your problem is a compbination of ShowDialog() and the fact that you aren't prepared for ShowDialog() to ever exit.
Now a modal for shouldn't show up with a taskbar icon in the first place, there really should be only 1 taskbar icon for an application and all of it's modal forms, since when a modal form is running, the main form is disabled anyway. When the main form is minimized, all of the modal forms that it owns will be hidden, etc.
So if you really want this second form to be modal, you shouldn't give the user the ability to give it a taskbar icon. If using ShowDialog() was just test code, then don't worry about it. The problem will go away when the form runs on the main application pump.
Your question isn't very clear to me. Anyway, the newForm form is displayed as a modal dialog, which means that it blocks the user from working with the parent form until it is closed. Modal dialogs usually have some buttons that automatically close them returning either OK or Cancel to the calling form (as a return value of ShowDialog). This is done using the DialogResult property, so if this property is set for your button, this may be a reason why the modal form closes when you click on it.
If you want to show more forms in a way that allows the user to work with both of them, you'll need to use modeless dialog. Here is a good overview article on MSDN.
how... my... this is an ugly hack
this work
private void Form1_Load(object sender, EventArgs e)
{
Form2 newForm = new Form2();
Button b = new Button();
newForm.Controls.Add(b);
b.Click += new EventHandler(click);
newForm.FormClosed += new FormClosedEventHandler(form2_closed);
newForm.FormClosing += new FormClosingEventHandler(form2_closing);
this.Show();
do
{
newForm.ShowDialog();
} while (newForm.IsDisposed == false );
}
private void click(object sender, EventArgs e)
{
((Form)((Control)sender).Parent).ShowInTaskbar = !((Form)((Control)sender).Parent).ShowInTaskbar;
}
private void form2_closed(object sender, FormClosedEventArgs e)
{
((Form)sender).Dispose();
}
private void form2_closing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.None)
e.Cancel = true;
}