Home > Enterprise >  Navigate to Another Form from System Tray Winforms
Navigate to Another Form from System Tray Winforms

Time:11-11

I am working on a WinForms application, I tried to minimize the form to system try while closing and when opening from the system tray I want to directly go to the main app without logging again. The main point is to reduce the login attempts to get into the main app and the app should run in the background until the account holder logs out of the app and sees the login screen.

This is what I am up to

Program.cs

static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Splashscreen());
        Application.Run(new SignIn());                                       
    }

This is how I navigate to the main app from sign-in form

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        waitForm.Close();
        this.Hide();
        //Nothing to do with this system tray problem, just checking for another valid condition
        if(condition == true)
        {                 
            MainApp mainapp = new MainApp();
            mainapp.ShowDialog();                
        }            
        this.Close();
    }

Main App to get into after login

private void MainApp_FormClosing(object sender, FormClosingEventArgs e)
    {
        if (e.CloseReason == CloseReason.UserClosing)
        {
            notifyIcon1.Visible = true;
            notifyIcon1.ShowBalloonTip(500);
            this.Hide();
            e.Cancel = true;                
        }
    }

    private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
    {
        this.Show();
        notifyIcon1.Visible = false;
        WindowState = FormWindowState.Normal;
    }

But, When I do this, the system tray icon disappears and cannot go to the specified page. Any thoughts or experience in making this happen?

Thanks in advance guys!

CodePudding user response:

I have some code that I dug out from a very long time ago (more than a decade ago). Windows 10 broke it a bit, so there are a few recent changes. One of the things you are going to want to do (if you are canceling closing) is to handle WmQueryEndSession and WmEndSession messages - otherwise your app will keep the system from shutting down.

private const int WmQueryEndSession=0x11;
private const int WmEndSession=0x16;

Here's some extracts from some working code. It's just copy/paste from an archive. I don't have a VS system I can test this code on right now.

Here's the end session code:

//needed to get this thing to close cleanly.  OnSessionEnding and OnSessionEnded didn't work
protected override void WndProc(ref Message m){
    switch(m.Msg){
        case WmQueryEndSession:
            m.Result=(IntPtr) 1;        //basically "e.Cancel = false;"
            goto case WmEndSession;
        case WmEndSession:
            forceExit=true;
            Close();
            return;
        default:
            base.WndProc(ref m);
            break;
    }
}

Note that that comment about OnSessionEnding and OnSessionEnded may date from the days of Framework v1.x; it may not be true today

The forceExit flag is initially false. It gets set when the user clicks the close button with the control key held down or choses Quit from a context menu.

private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
    if(forceExit)
        return; //close and quit
    //otherwise
    forceExit=false;
    if(!ModifierKeys.HasFlag(Keys.Control)) {
        WindowState=FormWindowState.Minimized;
        e.Cancel=true;
    }
 }

I have a Restore option in my context menu (that's how the the form is restored). This app has it's greatest use in the Notification Area (aka System Tray). When in its restored form, it's mostly used to change app settings, etc. From the comments, I needed to fiddle things here after Win10 came out:

private void RestoreCmd_Click(object sender, EventArgs e) {
    Show();
    //restoring from initially minimized on Windows 10 seems to completely mess up all the control positions and sizes
    //so store everything here and restore it below
    Dictionary<Control, (Point location, Size size)> controlPositions = null;
    if (textBox1.Size.Width != 0)
    {
        controlPositions = new Dictionary<Control, (Point pos, Size size)>();
        foreach (var ctrl in this.Controls.Cast<Control>())
        {
            controlPositions.Add(ctrl, (location: ctrl.Location, size:ctrl.Size));
        }
    }

    WindowState=FormWindowState.Normal;
    //restore all the control positions and sizes
    if (controlPositions != null)
    {
        foreach (var position in controlPositions)
        {
            position.Key.Size = position.Value.size;
            position.Key.Location = position.Value.location;
        }
    }
}

There are Hide and Quit entries in the context menu:

private void HideCmd_Click(object sender, EventArgs e) {
    WindowState=FormWindowState.Minimized;
}

private void QuitCmd_Click(object sender, EventArgs e) {
    forceExit=true;
    Close();
}

There's a ton of functionality in this app. I think I've extracted all the show/hide management and put it in this answer. If I'm missing something, let me know.

CodePudding user response:

The problem is, that hiding a form ends the ShowDialog() modal loop. So in backgroundWorker1_RunWorkerCompleted your SignIn form gets closed when the MainApp form is hidden and exits the Application.Run() loop in the main method. See my comments in your existing code:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    waitForm.Close();
    this.Hide();
    //Nothing to do with this system tray problem, just checking for another valid condition
    if(condition == true)
    {
        MainApp mainapp = new MainApp();
        // this method will exit when mainapp will be hidden:
        mainapp.ShowDialog();
    }
    // this will exit your Application.Run(new SignIn()) in the static Main methods:
    this.Close();
}

As you said you don't need the SignIn instance after a successful login, you could change your app as follows.
In SignIn form change the following method like this:

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    waitForm.Close();
    //Nothing to do with this system tray problem, just checking for another valid condition
    DialogResult = condition ? DialogResult.OK : DialogResult.Cancel;
}

This way you can use the DialogResult of SignIn in your Main method in Program.cs:

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Splashscreen());
    using (var signIn = new SignIn())
    {
        switch (signIn.ShowDialog())
        {
            case DialogResult.OK:
                break;
            default:
                return;
        }
    }
    Application.Run(new MainApp());
}

The rest of the code looks Ok at the first glance.

  • Related