In the below code, i am trying to reference an external .dll, which creates a custom taskbar in the desktop. After which i am creating tabs on the taskbar as per my requirement.
Everything works fine, but after i terminate my application for creating the taskbar, the space which is occupied by the custom taskbar is blocked. which means that, the resources are not released after exiting the application.
I am trying the force the application to dispose the unmanaged resources. But it dosent help.
How to do it?? Please refer to the code below which i am trying with...
namespace Daemon
{
public partial class MDIParent : ShellLib.ApplicationDesktopToolbar , IDisposable
{
private static MDIParent MDIParentInstance = null;
public MDIParent()
{
InitializeComponent();
timer1.Interval = 50;
timer1.Enabled = true;
}
int childCount = 1;
int iHitcount = 0;
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
private void MDIParent_FormClosed(object sender, FormClosedEventArgs e)
{
Taskbar.Show();
}
public void TabIt(string strProcessName)
{
//Get the collection of opened tab names and check against the new tabs.
//If exists, dont allow to open the same tab again.
bool found = false;
if (Global.ExistingTabProcessNames.Count > 0)
{
foreach (string currentTab in Global.ExistingTabProcessNames)
{
if (currentTab.Equals(strProcessName))
{
found = true;
}
}
}
if (found == false)
{
this.Show();
//Creating MDI child form and initialize its fields
MDIChild childForm = new MDIChild();
childForm.Text = strProcessName;
childForm.MdiParent = this;
//child Form will now hold a reference value to the tab control
childForm.TabCtrl = tabControl1;
//Add a Tabpage and enables it
TabPage tp = new TabPage();
tp.Parent = tabControl1;
tp.Text = childForm.Text;
tp.Show();
//child Form will now hold a reference value to a tabpage
childForm.TabPag = tp;
//Activate the MDI child form
childForm.Show();
childCount++;
//Activate the newly created Tabpage
tabControl1.SelectedTab = tp;
tp.Height = tp.Parent.Height;
tp.Width = tp.Parent.Width;
}
}
private void MDIParent_Load(object sender, EventArgs e)
{
Edge = AppBarEdges.Top;
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern int ShowWindow(IntPtr hWnd, int nCmdShow);
private void tabControl1_SelectedIndexChanged(object sender, System.EventArgs e)
{
iHitcount = iHitcount + 1;
if (iHitcount != 1)
BringFront(tabControl1.SelectedTab.Text.ToString());
}
[System.Runtime.InteropServices.DllImport("user32.dll", CharSet = System.Runtime.InteropServices.CharSet.Auto)]
private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);
private const int SW_SHOWNORMAL = 1;
private const int SW_SHOWMINIMIZED = 2;
private const int SW_SHOWMAXIMIZED = 3;
private void BringFront(string ExecutablePath)
{
Process[] Processes = Process.GetProcesses();
foreach (Process clsProcess in Process.GetProcesses())
{
if (clsProcess.ProcessName.Contains(ExecutablePath))
{
ShowWindowAsync(clsProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
}
else
{
ShowWindowAsync(clsProcess.MainWindowHandle, SW_SHOWMINIMIZED);
}
}
}
public static MDIParent MDIParentRef
{
get
{
if (MDIParentInstance == null)
MDIParentInstance = new MDIParent();
return MDIParentInstance;
}
}
public void showScreen()
{
if (this.InvokeRequired == true)
{
this.Invoke(new MethodInvoker(this.showScreen));
}
else
{
this.Show();
}
}
private void timer1_Tick(object sender, EventArgs e)
{
if (Global.TabProcessNames.Count > 0)
{
foreach (string strProcessName in Global.TabProcessNames)
{
TabIt(strProcessName);
}
}
}
}
}
The appbar is removed by the OnClosing event of ApplicationDesktopToolbar.
Are you using Application.Exit to exit your application? From MSDN:
The Application.Exit method does not raise the Form.Closed and Form.Closing events [...]
Related
This is rather a difficult situation to explain. I have a Windows Forms application that uses a notification icon with a context menu. The app can be configured to start with no form shown or the form can be closed by the user leaving just the notify icon present. Selecting the "Show" option from the context menu strip of the notification icon will create (if closed)/restore(if minimized) the main form. Now this all works fine as the events generated from the menu are running on the STA thread. The problem is the single instance implementation. A notify icon type application really should only ever be running one instance, otherwise you'd have multiple notify icons in the tray and it'd be a huge mess. This is accomplished using a named EventWaitHandle and also includes detection of an attempt to run a second instance, when this happens it signals the main running instance which then restores/creates the main form thus:
public static bool InitSingleInstance(this Control control, string handleName, Action? NewInstanceAttempt = null)
{
EventWaitHandle ewh = new(false, EventResetMode.ManualReset, handleName, out bool isNew);
if (isNew)
{
Task.Run(() =>
{
while (!control.IsDisposed)
{
ewh.WaitOne();
ewh.Reset();
NewInstanceAttempt?.Invoke();
}
});
}
else
{
Task.Run(() =>
{
EventWaitHandle.SignalAndWait(ewh, ewh, 100, true);
}).Wait();
}
return isNew;
}
The issue I have is that the loop waiting for a signal from a second instance runs in another Thread and thus needs to be delegated to the STA thread to restore/create the form. I use a UserControl to contain the NotifyIcon and Menu (So I can edit them in the designer) and this is created in Program.cs in place of Application.Run(new Form()) but this control is never actually shown itself so it never gets a Handle assigned to it that I can Invoke on so I get an exception when the second instance is run.
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if(!this.InitSingleInstance("FOOBFOOB387846", NewInstanceAttempt))
return;
InitializeComponent();
Application.Run();
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Visible = true;
mainForm.Resize += MainForm_Resize;
}
mainForm.WindowState = mainFormState;
}
private void Quit()
{
Dispose();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Invoke(ShowMainForm); // << exception
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) ShowMainForm();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
Perhaps there is a better way to do this? I did think of running a loop in the constructor waiting on a locked object and pulsing it from the other thread. Like this
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("NotIconDemo387846", NewInstanceAttempt))
return;
InitializeComponent();
lock (this)
{
while (!IsDisposed)
{
Monitor.Wait(this);
ShowMainForm();
}
}
}
private void NewInstanceAttempt()
{
lock(this)
{
Monitor.Pulse(this);
}
}
But this seems messy.
EDIT: And indeed wouldn't work as it would lock up the STA thread.
I managed to solve it like this:
public partial class NotifyIconAndMenu : UserControl
{
private Form? mainForm = null;
private FormWindowState mainFormState = FormWindowState.Normal;
private readonly Func<Form> NewForm;
public NotifyIconAndMenu(Func<Form> newForm)
{
NewForm = newForm;
if (!this.InitSingleInstance("GROWPLENTYOFCHEESE4444", NewInstanceAttempt))
return;
InitializeComponent();
FormShowLoop();
Application.Run();
}
private async void FormShowLoop()
{
while (!IsDisposed)
{
await Task.Run(() =>
{
lock (this)
{
Monitor.Wait(this);
}
});
if(!IsDisposed)
ShowMainForm();
}
}
private void ShowMainForm()
{
if (mainForm == null || mainForm.IsDisposed)
{
mainForm = NewForm();
mainForm.Resize += MainForm_Resize;
mainForm.Visible = true;
}
mainForm.WindowState = mainFormState;
}
private void Pulse()
{
lock (this)
{
Monitor.Pulse(this);
}
}
private void Quit()
{
Dispose();
Pulse();
Application.Exit();
}
private void MainForm_Resize(object? sender, EventArgs e)
{
if (mainForm != null && mainForm.WindowState != FormWindowState.Minimized)
mainFormState = mainForm.WindowState;
}
private void NewInstanceAttempt() => Pulse();
private void IconMenu_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
if (e.ClickedItem == MI_Show) Pulse();
else
if (e.ClickedItem == MI_Quit) Quit();
}
}
I was looking for an answer and found an almost complete one by #LarsTech but without the exception :
https://stackoverflow.com/a/21314496/7026554
//You can still use MessageFilter and just filter for the ActiveForm:
private class MouseDownFilter : IMessageFilter {
public event EventHandler FormClicked;
private int WM_LBUTTONDOWN = 0x201;
private Form form = null;
[DllImport("user32.dll")]
public static extern bool IsChild(IntPtr hWndParent, IntPtr hWnd);
public MouseDownFilter(Form f) {
form = f;
}
public bool PreFilterMessage(ref Message m) {
if (m.Msg == WM_LBUTTONDOWN) {
if (Form.ActiveForm != null && Form.ActiveForm.Equals(form)) {
OnFormClicked();
}
}
return false;
}
protected void OnFormClicked() {
if (FormClicked != null) {
FormClicked(form, EventArgs.Empty);
}
}
}
//Then in your form, attach it:
public Form1() {
InitializeComponent();
MouseDownFilter mouseFilter = new MouseDownFilter(this);
mouseFilter.FormClicked += mouseFilter_FormClicked;
Application.AddMessageFilter(mouseFilter);
}
void mouseFilter_FormClicked(object sender, EventArgs e) {
// do something...
}
What I want is to hide the notifications panel but not when clicking on its content or the profile picture that shows it.
I have a procedure called NotificationVisible(bool IsVisible)
Any help is greatly appreciated.
So your mouseFilter_FormClicked fires whenever user clickes on form. Now the only thing you need to do is to detect the control that is placed behind the mouse cursor and with that you can deside whether you need to hide your panel or not.
You can do this with WindowFromPoint method. Check this thread.
[System.Runtime.InteropServices.DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(Point pnt);
void mouseFilter_FormClicked(object sender, EventArgs e) {
IntPtr hWnd = WindowFromPoint(Control.MousePosition);
if (hWnd != IntPtr.Zero) {
Control ctl = Control.FromHandle(hWnd);
if (ctl != YourPanelControl) {
HideThePanel();
}
}
}
I usually call this piece of code to show a form whenever a button is clicked.
private frmSelection _frmSelection;`
private void _frmSelection_FormClosing(object sender, FormClosingEventArgs e)
{
_frmSelection = null;
}
private void changeFeedOrderToolStripMenuItem_Click(object sender, EventArgs e)
{
if (_frmSelection == null)
{
_frmSelection = new frmSelection();
_frmSelection.FormClosing += _frmSelection_FormClosing;
_frmSelection.WindowState = FormWindowState.Minimized;
_frmSelection.Show();
_frmSelection.WindowState = FormWindowState.Normal;
}
else
{
_frmSelection.WindowState = FormWindowState.Minimized;
_frmSelection.WindowState = FormWindowState.Normal;
}
}
If the form is already open it will show the already opened instance instead of creating new instance. It is working fine.
But my problem is i need to copy paste and change the form name whenever i am adding a new form.
How it can be generalized and added to
Helper
class?
Something like this:
public sealed class ReusableFormContainer<T> : IDisposable
where T : Form, new()
{
private bool isDisposed;
private void HandleFormClosing(object sender, FormClosingEventArgs e)
{
Form = null;
}
public T Form { get; private set; }
public void Show()
{
if (isDisposed)
{
throw new ObjectDisposedException(null);
}
if (Form == null)
{
Form = new T { WindowState = FormWindowState.Minimized };
Form.FormClosing += HandleFormClosing;
Form.Show();
}
else
{
Form.WindowState = FormWindowState.Minimized;
}
Form.WindowState = FormWindowState.Normal;
}
public void Dispose()
{
// IDisposable.Dispose is implemented to handle cases, when you want to close
// wrapped form using code
if (!isDisposed)
{
Form?.Dispose();
isDisposed = true;
}
}
}
Usage:
// must be initialized somewhere in constructors
private readonly ReusableFormContainer<FormA> container_A;
private readonly ReusableFormContainer<FormB> container_B;
private void button1_Click(object sender, EventArgs e)
{
container_A.Show();
}
private void button2_Click(object sender, EventArgs e)
{
container_B.Show();
}
The following code might do what you want. Just thought about to use the Application functions because those can cover all forms that are on thread.
public partial class Form1 : Form
{
public int i;
private Form1 _frmSelection;
public Form1()
{
InitializeComponent();
i = Application.OpenForms.Count;
}
private void _frmSelection_FormClosing(object sender, FormClosingEventArgs e)
{
_frmSelection = null;
}
private void button1_Click(object sender, EventArgs e)
{
if (_frmSelection == null)
{
_frmSelection = new Form1();
_frmSelection.FormClosing += _frmSelection_FormClosing;
_frmSelection.WindowState = FormWindowState.Minimized;
_frmSelection.WindowState = FormWindowState.Normal;
_frmSelection.Show();
if (Application.OpenForms.Count > 1)
{
_frmSelection.Text = Application.OpenForms[i].Text + " and going";
}
}
else
{
_frmSelection.WindowState = FormWindowState.Minimized;
_frmSelection.WindowState = FormWindowState.Normal;
}
}
}
How to force fully focus child form when we click on client area of it's parent form? [Like MessageBox or Error message have such focus so we must click on Ok button of messagebox.]
This is my code:
form = new Form2();
form.MdiParent = this;
form.ShowDialog(ParentForm);
But it gives me the error:
Form that is not a top-level form cannot be displayed as a modal
dialog box. Remove the form from any parent form before calling
showDialog.
Original answer - for non-MDIChild
Make the child form modal using the ShowDialog method.
ChildForm.ShowDialog (ParentForm);
To keep an MDI Child form at the top:
Handle the MDIChildActivate event on the parent form, and in that, set the child form that you want to stay visible to be active:. In this example, Form2 is the modal form, and Form3 is another form for testing.
private Form2 frm2;
private Form3 frm3;
private void Form1_Load(object sender, EventArgs e)
{
frm2=new Form2();
frm2.MdiParent=this ;
frm2.Show();
frm3= new Form3() ;
frm3.MdiParent=this;
frm3.Show();
}
private void Form1_MdiChildActivate(object sender, EventArgs e)
{
frm2.Activate();
}
I think you want to have some kind of MessageBox in the MDI Parent Form with TopLevel = false. To make the effect look like the real effect a window shown by ShowDialog() has, I think this is the only solution which requires you to create a custom Form for your MDI child form:
public class ChildForm : Form
{
[DllImport("user32")]
private static extern int FlashWindowEx(FLASHWINFO flashInfo);
struct FLASHWINFO
{
public int cbSize;
public IntPtr hwnd;
public uint flags;
public int count;
public int timeOut;
}
protected override void WndProc(ref Message m)
{
if (ParentForm != null)
{
ChildForm dialog = ((Form1)ParentForm).Dialog;
if (dialog != this && dialog!=null)
{
if (m.Msg == 0x84) //WM_NCHITTEST
{
if (MouseButtons == MouseButtons.Left)
{
Flash(dialog.Handle);
}
return;
}
}
}
base.WndProc(ref m);
}
private void Flash(IntPtr handle)
{
FLASHWINFO flashInfo = new FLASHWINFO()
{
hwnd = handle,
count = 9,
cbSize = Marshal.SizeOf(typeof(FLASHWINFO)),
flags = 3,
timeOut = 50
};
FlashWindowEx(flashInfo);
}
public void Flash()
{
Flash(Handle);
}
//This is to disable Tab when the Dialog is shown
protected override bool ProcessTabKey(bool forward)
{
if(((Form1)ParentForm).Dialog == this) return true;
return base.ProcessTabKey(forward);
}
}
//Here is your MDI parent form
public class Form1 : Form {
public Form1(){
InitializeComponent();
IsMdiContainer = true;
f1 = new ChildForm();
f2 = new ChildForm();
f1.MdiParent = this;
f2.MdiParent = this;
//Get MDI Client
foreach(Control c in Controls){
if(c is MdiClient){
client = c;
break;
}
}
client.Click += ClientClicked;
}
ChildForm f1, f2;
MdiClient client;
public ChildForm Dialog {get;set;}
private void ClientClicked(object sender, EventArgs e){
if(Dialog != null) Dialog.Flash();
else {//Show dialog, you can show dialog in another event handler, this is for demonstrative purpose
Dialog = new ChildForm(){MdiParent = this, Visible = true};
ActivateMdiChild(Dialog);
//Suppose clicking on the Dialog client area will close the dialog
Dialog.Click += (s,ev) => {
Dialog.Close();
};
Dialog.FormClosed += (s,ev) => {
Dialog = null;
};
}
}
}
I am porting an MFC application to .NET WinForms. In the MFC application, you can right click on a menu or on a context menu item and we show another context menu with diagnostic and configuration items. I am trying to port this functionality to .NET, but I am having trouble.
I have been able to capture the right click, disable the click of the underlying menu and pop up a context menu at the right location, but the original menu disappears as soon as it loses focus.
In MFC, we show the new context menu by calling TrackPopupMenuEx with the TPM_RECURSE flag.
ContextMenu and the newer ContextMenuStrip classes in .NET only have a Show method. Does anyone know how to do this in .NET?
EDIT
I have tried using TrackPopupMenuEx through a p/invoke, but that limits you to using a ContextMenu instead of a ContextMenuStrip which looks out of place in our application. It also still does not work correctly. It doesn't work with the new MenuStrip and ContextMenuStrip.
I have also tried subclassing ToolStripMenuItem to see if I can add a context menu to it. That is working for MenuStrip, but ContextMenuStrip still allows the right click events to pass through as clicks.
Edit, due to a comment:
In:
protected override void OnClick(EventArgs e)
{
if (SecondaryContextMenu == null || MouseButtons != MouseButtons.Right)
{
base.OnClick(e);
}
}
this part
MouseButtons != MouseButtons.Right
should and does compile as it is a call to Control.MouseButtons. Since the Form inherits Control class, it is sufficient to call MouseButtons property directly.
Hope this helps:
public partial class Form1 : Form
{
class CustomToolStripMenuItem : ToolStripMenuItem
{
private ContextMenuStrip secondaryContextMenu;
public ContextMenuStrip SecondaryContextMenu
{
get
{
return secondaryContextMenu;
}
set
{
secondaryContextMenu = value;
}
}
public CustomToolStripMenuItem(string text)
: base(text)
{ }
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (secondaryContextMenu != null)
{
secondaryContextMenu.Dispose();
secondaryContextMenu = null;
}
}
base.Dispose(disposing);
}
protected override void OnClick(EventArgs e)
{
if (SecondaryContextMenu == null || MouseButtons != MouseButtons.Right)
{
base.OnClick(e);
}
}
}
class CustomContextMenuStrip : ContextMenuStrip
{
private bool secondaryContextMenuActive = false;
private ContextMenuStrip lastShownSecondaryContextMenu = null;
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (lastShownSecondaryContextMenu != null)
{
lastShownSecondaryContextMenu.Close();
lastShownSecondaryContextMenu = null;
}
}
base.Dispose(disposing);
}
protected override void OnControlAdded(ControlEventArgs e)
{
e.Control.MouseClick += new MouseEventHandler(Control_MouseClick);
base.OnControlAdded(e);
}
protected override void OnControlRemoved(ControlEventArgs e)
{
e.Control.MouseClick -= new MouseEventHandler(Control_MouseClick);
base.OnControlRemoved(e);
}
private void Control_MouseClick(object sender, MouseEventArgs e)
{
ShowSecondaryContextMenu(e);
}
protected override void OnMouseClick(MouseEventArgs e)
{
ShowSecondaryContextMenu(e);
base.OnMouseClick(e);
}
private bool ShowSecondaryContextMenu(MouseEventArgs e)
{
CustomToolStripMenuItem ctsm = this.GetItemAt(e.Location) as CustomToolStripMenuItem;
if (ctsm == null || ctsm.SecondaryContextMenu == null || e.Button != MouseButtons.Right)
{
return false;
}
lastShownSecondaryContextMenu = ctsm.SecondaryContextMenu;
secondaryContextMenuActive = true;
ctsm.SecondaryContextMenu.Closed += new ToolStripDropDownClosedEventHandler(SecondaryContextMenu_Closed);
ctsm.SecondaryContextMenu.Show(Cursor.Position);
return true;
}
void SecondaryContextMenu_Closed(object sender, ToolStripDropDownClosedEventArgs e)
{
((ContextMenuStrip)sender).Closed -= new ToolStripDropDownClosedEventHandler(SecondaryContextMenu_Closed);
lastShownSecondaryContextMenu = null;
secondaryContextMenuActive = false;
Focus();
}
protected override void OnClosing(ToolStripDropDownClosingEventArgs e)
{
if (secondaryContextMenuActive)
{
e.Cancel = true;
}
base.OnClosing(e);
}
}
public Form1()
{
InitializeComponent();
CustomToolStripMenuItem itemPrimary1 = new CustomToolStripMenuItem("item primary 1");
itemPrimary1.SecondaryContextMenu = new ContextMenuStrip();
itemPrimary1.SecondaryContextMenu.Items.AddRange(new ToolStripMenuItem[] {
new ToolStripMenuItem("item primary 1.1"),
new ToolStripMenuItem("item primary 1.2"),
});
CustomToolStripMenuItem itemPrimary2 = new CustomToolStripMenuItem("item primary 2");
itemPrimary2.DropDownItems.Add("item primary 2, sub 1");
itemPrimary2.DropDownItems.Add("item primary 2, sub 2");
itemPrimary2.SecondaryContextMenu = new ContextMenuStrip();
itemPrimary2.SecondaryContextMenu.Items.AddRange(new ToolStripMenuItem[] {
new ToolStripMenuItem("item primary 2.1"),
new ToolStripMenuItem("item primary 2.2"),
});
CustomContextMenuStrip primaryContextMenu = new CustomContextMenuStrip();
primaryContextMenu.Items.AddRange(new ToolStripItem[]{
itemPrimary1,
itemPrimary2
});
this.ContextMenuStrip = primaryContextMenu;
}
}
You'll probably have to p/invoke the method.
[DllImport("user32.dll")]
static extern bool TrackPopupMenuEx(IntPtr hmenu, uint fuFlags, int x, int y,
IntPtr hwnd, IntPtr lptpm);
const int TPM_RECURSE = 0x0001;
This shows how to use multiple ContextMenus as well as different ones with any combination of mouse clicks.
More here: http://code.msdn.microsoft.com/TheNotifyIconExample