Notify applications where the form disappears from the taskbar on minimize


Jim asks:



“I’ve been following your Windows Forms articles, and was hoping that you could post about the correct way to minimize a Windows Form to the tray. The ShowInTask property of the Form is mentioned in newsgroups, but the Form still shows in the Alt+Tab list.”


The best way to handle this situation is to actually close the form.  Then the form cannot possibly be in the ALT+Tab list.  This article discusses how to build notify applications which close the form on minimize. 



private void CalendarForm_SizeChanged(object sender, EventArgs e) {
      if (this.WindowState == FormWindowState.Minimized) {
          this.Close();
      }
}


Note that this would usually end your application – you’ll either need to use Application.Run() instead or a custom ApplicationContext.  This is discussed in the above article.



“Also, I’ve noticed that applications such as Messenger animate the window correctly towards the tray, which is different from the WindowState/ShowInTaskBar behavior.”


Here my recommendation would be to cancel the closing event on the form and use a System.Windows.Forms.Timer to shrink the form to the bottom right of the working area.  The rough idea would be



     System.Windows.Forms.Timer timer = new Timer(components);


   private bool animationComplete = false;


   private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
      // in 2.0 could also check if e.CloseReason == CloseReason.UserClosing
      // in 1.1 can use the Form.Closing event instead.
            if (!animationComplete) {  
                e.Cancel = true;
                timer.Tick += new EventHandler(timer_Tick);
                timer.Interval = 10;     
                timer.Enabled = true;
            }
   }
           


   // you may have to play with this code to get the desired effect you want
 
    void timer_Tick(object sender, EventArgs e) {
            Rectangle workingArea = Screen.GetWorkingArea(this.Bounds);


            this.Bounds = new Rectangle(
                Math.Max(workingArea.Width – this.Width, this.Left + 10),
                Math.Max(workingArea.Height – this.Height, this.Top + 10),
                Math.Max(0, this.Width – 10),
                Math.Max(0, this.Height-10));


            if (this.Bounds.Bottom >= workingArea.Bottom) {
                timer.Tick -= new EventHandler(timer_Tick);
                timer.Enabled = false;
                
animationComplete = true;
                this.Close();
            }
      
        }   
   }


Hope this helps! 


Related Article: http://www.windowsforms.net/articles/notifyiconapplications.aspx

Comments (14)

  1. Ben Hollis says:

    Of course, this animation only works if your taskbar is on the bottom of the screen.

  2. Regarding the Alt-Tab problem… Is it really necessary to bind a NotifyIcon to a Form the way it’s done in the Designer?

    When I was facing the same problem, I ended up hooking a NativeWindow to my Form and calling Shell_NotifyIcon() internally, only to find out much later (credits go to Ian Griffiths) that this also works:

    static void Main()

    {

    Form1 myForm = new Form1();

    NotifyIcon ni = new NotifyIcon();

    // Set up icon, text, event handlers etc.

    ni.Visible = true;

    Application.Run(); // No form passed here, no Alt-Tab problem

    }

    Apparently:

    – There is no need to construct NotifyIcon passing a Form’s components member (which is what the Designer is doing);

    – NotifyIcon does not require an external window/form handle to work. (This is a bit surprising, because the API equivalent NOTIFYICONDATA does need a window handle.)

    Any comments on this?

  3. jfoscoding says:

    Ooooo Ben you caught me putting together a 5 minute quick sample. Good catch

  4. jfoscoding says:

    Vladimir –

    You’re right, the designer generated code model for Notify Icon only works if you have a notify icon up as long as your main form is open.

    If you want to go the other way, have the notify icon drive the lifetime of the application, you’ll have to change around your code like you have above. (At some point you’ll have to call Application.Exit())

    The related article shows the best practice how to modify your main to not show the form automatically.

    What I assumed Jim was asking was that he has a form that is up for a little while, then goes away. (Your code sample never shows the form). If you ever wanted to show the form, it would then show up in the ALT+Tab list, if you did something like set it to ShowInTaskbar = false when the Form.WindowState = Minimized.

    Finally, the point of the notify icon constructor taking the components collection is when designing the the application the way the designer generated code does (the notify icon is alive as long as the form is visible) the components constructor adds the notify icon onto the list of things to dispose when the form closes.

    More info: http://blogs.msdn.com/jfoscoding/articles/450835.aspx

  5. Jim says:

    Basically, what I’m trying to do is to have my application behave the same way that Messenger does (startup in tray, mimimize to tray). From the looks of it, I would say that Messenger uses the Shell to animate the mimimize to tray action, and not a custom animation of the window bounds.

    As a side note, I can’t close the form since I need a message queue to listen for DDE messages from an external application. Also, I found that I need to use Form.Show() in order to get notified in the OnClose event when the user is logging out, or windows is shutting down. So when my app starts minimized to tray, I show it offscreen before using ShowInTaskBar=false and WindowState=Minimized to place it in the tray.

  6. jfoscoding says:

    Regarding the message window, you can have a hidden ContainerControl with SetTopLevel(true), and Visible = false that is tied to the lifetime of your application context. It does not have to be your form.

    Regarding a more automagic API, I’m not aware of one that is built into the Win32 API, perhaps someone else is…

    The AnimateWindow API may get you pretty close – I’m not sure if this gives you 100% of the automaticness you want.

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowfunctions/animatewindow.asp

  7. Ben Hollis says:

    *sigh* No need to get snippy. What I perhaps should have said was: "How do I find out the location of the taskbar/status tray so I can corrently animate towards it wherever it may be?" A tangent, I know, but as one of the few who run their taskbars docked to different edges of the screen, it has become apparent that very many developers hardcode things to assume the taskbar is at the bottom.

  8. Mark O says:

    Thanks for the articles, very useful. I have created a custom context for my application and every thing is working well, one small side effect I noticed is when using tooltips on the notify icons context menu, the tooltips are displayed behind the menu, incorrect z-order. I don’t think tooltips are necessary as the menu items are self explanatory, have you see this behaviour?

  9. jfoscoding says:

    Ah Ben! Sorry about that, I didnt mean to come off as "snippy". You totally called me out for a bad sample and I encourage everyone to do that when I mess up.

    You’ll have to dig into the shell APIs to figure out the exact location of the start bar.

    The ABM_GETTASKBARPOS gets you pretty close

    NativeMethods.APPBARDATA appBarData = NativeMethods.APPBARDATA.Create();

    NativeMethods.SHAppBarMessage(NativeMethods.ABM_GETTASKBARPOS, ref appBarData);

    NativeMethods.RECT taskBarLocation = appBarData.rc;

    internal class NativeMethods {

    // All definitions taken from http://pinvoke.net

    [DllImport("shell32.dll")]

    public static extern IntPtr SHAppBarMessage(uint dwMessage, ref APPBARDATA pData);

    [DllImport("user32.dll")]

    public static extern IntPtr FindWindow(

    string lpClassName,

    string lpWindowName

    );

    public const string TaskbarClass = "Shell_TrayWnd";

    [StructLayout(LayoutKind.Sequential)]

    public struct APPBARDATA {

    public static APPBARDATA Create() {

    APPBARDATA appBarData = new APPBARDATA();

    appBarData.cbSize =Marshal.SizeOf(typeof(NativeMethods.APPBARDATA));

    return appBarData;

    }

    public int cbSize;

    public IntPtr hWnd;

    public uint uCallbackMessage;

    public uint uEdge;

    public RECT rc;

    public int lParam;

    }

    public const int ABM_QUERYPOS = 0x00000002,

    ABM_GETTASKBARPOS=5;

    public const int ABE_LEFT = 0;

    public const int ABE_TOP = 1;

    public const int ABE_RIGHT = 2;

    public const int ABE_BOTTOM = 3;

    [Serializable, StructLayout(LayoutKind.Sequential)]

    public struct RECT {

    public int Left;

    public int Top;

    public int Right;

    public int Bottom;

    public RECT(int left_, int top_, int right_, int bottom_) {

    Left = left_;

    Top = top_;

    Right = right_;

    Bottom = bottom_;

    }

    public int Height { get { return Bottom – Top + 1; } }

    public int Width { get { return Right – Left + 1; } }

    public Size Size { get { return new Size(Width, Height); } }

    public Point Location { get { return new Point(Left, Top); } }

    // Handy method for converting to a System.Drawing.Rectangle

    public Rectangle ToRectangle() {

    return Rectangle.FromLTRB(Left, Top, Right, Bottom); }

    public static RECT FromRectangle(Rectangle rectangle) {

    return new RECT(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom);

    }

    public override int GetHashCode() {

    return Left ^ ((Top << 13) | (Top >> 0x13))

    ^ ((Width << 0x1a) | (Width >> 6))

    ^ ((Height << 7) | (Height >> 0x19));

    }

    #region Operator overloads

    public static implicit operator Rectangle( RECT rect ) {

    return Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom);

    }

    public static implicit operator RECT( Rectangle rect ) {

    return new RECT(rect.Left, rect.Top, rect.Right, rect.Bottom);

    }

    #endregion

    }

    }

    There are a couple of good articles on code project which talk about how to do this in unmanaged code.

    http://www.codeproject.com/shell/trayposition.asp

    http://www.codeproject.com/shell/minimizetotray.asp

    Hope this helps!

  10. Ben Hollis says:

    Thanks for the tip. I’m currently working on one of those msn-messenger-style popup notifications, and none of the ones I’ve found supported different taskbar locations – this will be very helpful!

  11. Ben Hollis says:

    Here’s an interesting take on the Alt+Tab issue (I’m not sure how useful it is)

    From http://www.syncfusion.com/FAQ/WindowsForms/FAQ_c84c.aspx

    18.34 I add my application to the Window’s Tray, set ShowInTaskBar to false, but the program still appears in the Alt+Tab list.

    An easy way to keep the program from appearing in the Alt+Tab list is to set the Form’s FormBorderStyle property to be a ToolWindow (fixed or sizable).

    One caveat to this is that if you display another Form (that is not a ToolWindow) through a call in your application, the main Form will again appear in the Alt+Tab listing. To get around this, you can make all of the Forms in your Tray application have a FormBorderStyle of ToolWindow.

  12. jfoscoding says:

    Mark –

    If you’re using tooltips on the MenuItems I’m assuming you’re using ContextMenuStrip? There was a problem with Beta2 Windows Forms 2.0 where the ToolTips were not set to HWND_TOPMOST, therefore would fall behind the ContextMenuStrip.

    If you’re using something else or a later version, please let me know.

  13. jfoscoding says:

    Ben –

    Thanks for bringing this up. If you’re building a "toaster window", this is probably the right thing to do.

    The official support article for this is:

    http://support.microsoft.com/default.aspx?scid=kb;en-us;205158

    Note if you’re using Windows Forms 2.0, just use a ToolStripDropDown instead. Like Form, the Close reason can be inspected/cancelled so you can change it from having ContextMenu like closing behavior.

    It will save you quite a bit of headache. If you want TableLayout – here’s a sample of how to use it in the designer with the same properties as TableLayoutPanel.

    http://blogs.msdn.com/jfoscoding/articles/481306.aspx