myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Knowledge Base » Artikel » [Artikel] Tray Applikationen (NotifyIcon)
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

[Artikel] Tray Applikationen (NotifyIcon)

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
egrath egrath ist männlich
myCSharp.de-Mitglied

avatar-2119.jpg


Dabei seit: 24.07.2005
Beiträge: 871
Entwicklungsumgebung: MonoDevelop, NetBeans, Vi
Herkunft: Österreich / Steyr


egrath ist offline

[Artikel] Tray Applikationen (NotifyIcon)

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Windows Tray Applikationen

Grundlegendes

Unter einer Windows Tray Applikation (im folgenden nur mehr kurz TrayApp genannt) versteht man eine Applikation welche im Gegensatz zu "normalen" Applikationen in erster Linie kein permanent geöffnetes User Interface besitzt, sondern nur in der Windows Tray sichtbar ist und im Hintergrund seine Aufgaben erledigt. Im Gegensatz zu Windows Services welche auch Aufgaben im Hintergrund erledigen, wird eine TrayApp allerdings im Kontext des gerade Lokal angemeldeten Benutzers ausgeführt und bietet in der Regel die möglichkeit, dass diese über eine grafische Oberfläche direkt konfiguriert wird.

Beispiele für Applikationen die dazu prädisteniert sind als TrayApp zu laufen:
  • Tools für die schnelle Rekonfiguration von Systemparametern (bsp. Lautstärkenregler, Ändern der Bildschirmauflösung, etc.)
  • Tools für die Überwachung von Systemparametern (bsp. Netzwerkverbindungen, Systemtemperatur, Systemlast, etc.)
  • Konfigurationsinterfaces für Systemdienste (bsp. Virenscanner, Personal Firewall, etc.)
  • Benachrichtigungsanzeigen für Applikationen (bsp. E-Mail, Instant Messenger, etc.)
Im folgenden Tutorial werden wir eine TrayApp entwickeln, welche zwar keinerlei relevante Funktionalität enthält, allerdings die grundlegenden Konzepte und möglichkeiten Aufzeigt welche für TrayApp's typisch sind:
  • Icon in der Tray
  • Kontextmenü für das Icon in der Tray
  • Hauptfenster durch Auswahl im Kontextmenü oder Doppelklick auf das Icon
  • Benachrichtungsfenster beim Minimieren des Hauptfensters (ala Windows Messenger beim anmelden neuer Benutzer in der Kontaktliste)
1. Das Grundgerüst der TrayApp

Das erstellen einer TrayApp unterscheidet sich nur in sehr wenigen Punkten von denen, die man für das erstellen einer anderen Anwendung benötigt. Das erstellen des Icons in der Tray und das reagieren auf Ereignisse, welche von diesem ausgelöst werden werden über die Klasse "NotifyIcon" gesteuert. Um ein Icon in der Tray zu erstellen reicht es also, wenn wir uns ein Objekt vom Typ "NotifyIcon" instantiieren und die entsprechenden Eigenschaften setzen (Sourcecode ab Zeile 73)

2. Das Hauptfenster

Da die Interaktion mit der Applikation über ein Windows Forms Fenster erfolgen soll müssen wir auch dieses erstellen und so mit dem Tray Icon verbinden, dass es in den folgenden Situationen geöffnet wird:
  • Der Benutzer wählt den entsprechenden Eintrag im Kontextmenü der TrayApp
  • Der Benuzter tätigt einen Doppelklick auf das Icon der TrayApp - in diesem Fall muss je nach zustand des Hauptfensters (angezeigt/nicht angezeigt) der inverse Zustand hergestellt werden.
Für unsere Demo TrayApp habe ich mich dazu entschieden die Hauptklasse welche alle Aktionen kontrolliert direkt von System.Windows.Forms.Form abzuleiten (Sourcecode ab Zeile 25). Im Konstruktor dieser Klasse werden alle Initialisierungsarbeiten geleistet die für die TrayApp notwendig sind (Erstellen des Layouts des Hauptfensters, Erstellen des Tray-Icons, Erstellen des Kontextmenüs für das Tray-Icon) .

Den Ereignissen für Doppelklick auf das Tray-Icon und auswahl im Kontextmenü werden im gleichen Zug entsprechende Event-Handler zugewiesen damit wir die gewünschte Aktion durchführen können. Eine besonderheit nimmt dabei der Handler für das Event "FormClosing" ein. Da wir zu Demonstrationszwecken alle möglichkeiten zum Beenden der Applikation deaktiviert haben und dies nur über den entsprechenden Eintrag im Kontextmenü durchgeführt werden kann, haben wir das Event "FormClosing" auf den Handler für das Verstecken des Hauptfensters gesetzt. In diesem muss nun überprüft werden, ob es sich bei den übergebenen Objekt vom Typ "EventArgs" in wirklichkeit um ein "FormClosingEventArgs" handelt. Sollte dies der Fall sein, so wurden dieser durch einen Versuch die Applikation zu beenden aufgerufen - und diese muss abgebrochen werden. Leider beginnt hier aber eine andere Tatsache zum Tragen, nämlich dass dieser Handler auch dann aufgerufen wird, wenn wir unsere TrayApp regulär durch das Kontextmenü beenden wollen. Aus diesem Grund wird im EventHandler für den entsprechenden Menüeintrag zuerst eine Boolsche Variable auf "true" gesetzt und diese dann im Handler abgefragt. Das hört sich jetzt etwas kompliziert an, ein Blick auf den Sourcecode ab Zeile 94, respektive 117 verrät uns aber dass es gar nicht so wild ist.

3. Die Benachrichtigung

Wie eingangs bereits erwähnt wollen wir eine Mitteilung sobald das Hauptfenster minimiert wurde. Wie es sich für eine TrayApp gehört werden wir diese in einem kleinen, transparenten Fenster überhalb der Tray darstellen. Die dazu notwendigen Schritte sind folgende:
  • Im EventHandler welcher sich um das Minimieren kümmert, ein neues Objekt vom Typ "NotifyBox" instantiieren
  • Einen Thread starten welcher sich um die Darstellung der Mitteilung kümmert
Das darstellen der Mitteilung beschränkt sich im Endeffekt auf das errechnen der Position überhalb des Tray Bereichs und das erstellen einer neuen, transparenten Form an der entsprechenden Position und das schliessen derselben nach einer gewissen Zeitspanne. Um die Position korrekt berechnen zu können, müssen wir auf native Win32-Api Funktionalität mittels P/Invoke zurückgreifen da .NET hier leider kein mir bekanntes Bordmittel bietet. (Sourcecode ab Zeile 128, P/Invoke ab Zeile 163)

Quellcode der Applikation:

C#-Code:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using System.Runtime.InteropServices;

namespace TrayApp1
{
    class Program
    {
        public static void Main( string[] args )
        {
            NotifyApplication notifyApp = new NotifyApplication();
            notifyApp.FormClosed += new FormClosedEventHandler( TrayAppExitHandler );

            Application.Run();
        }

        protected static void TrayAppExitHandler( object sender, FormClosedEventArgs e )
        {
            Application.Exit();
        }
    }

    class NotifyApplication : Form
    {
        private NotifyIcon m_NotifyIcon;
        private ContextMenu m_ContextMenu;
        private bool m_ReallyClose;

        public NotifyApplication()
        {
            m_ReallyClose = false;

            this.SuspendLayout();

            // Allgemeine Form Parameter setzen
            this.FormBorderStyle = FormBorderStyle.FixedSingle;
            this.Text = "Tray Application";
            this.Visible = false;
            this.MinimizeBox = false;
            this.MaximizeBox = false;
            this.FormClosing += new FormClosingEventHandler( HideMainFormHandler );

            // Ein paar Controls zum Form hinzufügen
            // Hide-Button
            Button buttonHide = new Button();
            buttonHide.Text = "Verstecken";
            buttonHide.Location = new Point( 10, this.ClientRectangle.Height - 10 - buttonHide.Size.Height );
            buttonHide.Anchor = ( AnchorStyles.Bottom | AnchorStyles.Left );
            buttonHide.Click += new EventHandler( HideMainFormHandler );
            this.Controls.Add( buttonHide );

            // Label
            string labelText = "Hello Tray App!";
            Font drawFont = new Font( FontFamily.GenericSansSerif, 20.0f );
            SizeF textSize = CreateGraphics().MeasureString( labelText, drawFont );
            Label labelHello = new Label();
            labelHello.Text = labelText;
            labelHello.Anchor = ( AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right );
            labelHello.Location = new Point(( int ) (( this.ClientRectangle.Width - ( int ) textSize.Width ) / 2 ), 20 );
            labelHello.Font = drawFont;
            labelHello.Size = textSize.ToSize();
            this.Controls.Add( labelHello );

            // Erstellen des Menüs für das Tray Icon
            m_ContextMenu = new ContextMenu();
            MenuItem menuEntryExit = new MenuItem( "Beenden", new EventHandler( this.ExitApplicationHandler ));
            MenuItem menuEntryShow = new MenuItem( "Zeigen", new EventHandler( this.ShowMainFormHandler ));
            m_ContextMenu.MenuItems.Add( menuEntryShow );
            m_ContextMenu.MenuItems.Add( menuEntryExit );

            // Display the Tray Icon
            m_NotifyIcon = new NotifyIcon();
            m_NotifyIcon.Icon = new Icon( "trayicon.ico" );
            m_NotifyIcon.Text = "Tray Application";
            m_NotifyIcon.ContextMenu = m_ContextMenu;
            m_NotifyIcon.Visible = true;
            m_NotifyIcon.DoubleClick += new EventHandler( ToggleMainFormHandler );
        }

        public void ToggleMainFormHandler( object sender, EventArgs e )
        {
            if( this.Visible )
            {
                HideMainFormHandler( sender, e );
            }
            else
            {
                ShowMainFormHandler( sender, e );
            }
        }

        public void HideMainFormHandler( object sender, EventArgs e )
        {
            this.Visible = false;

            if( e is FormClosingEventArgs && ! m_ReallyClose )
            {
                (( FormClosingEventArgs ) e ).Cancel = true;
            }

            // Notify Form anzeigen
            if( ! m_ReallyClose )
            {
                NotifyBox notifyBox = new NotifyBox();
                Thread notifyBoxThread = new Thread( new ParameterizedThreadStart( notifyBox.ShowBox ));
                notifyBoxThread.Start( ( object ) "Minimized!" );
            }
        }

        public void ShowMainFormHandler( object sender, EventArgs e )
        {
            this.Visible = true;
        }

        public void ExitApplicationHandler( object sender, EventArgs e )
        {
            m_ReallyClose = true;
            this.Close();
        }
    }

    class NotifyBox
    {
        private Form m_Notify;

        public void ShowBox( object message )
        {
            // Erstellen der Form welche die Benachrichtigung einblendet
            m_Notify = new Form();
            m_Notify.FormBorderStyle = FormBorderStyle.None;
            m_Notify.AllowTransparency = true;
            m_Notify.BackColor = Color.Magenta;
            m_Notify.TransparencyKey = m_Notify.BackColor;
            m_Notify.ShowInTaskbar = false;

            // Label mit Mitteilung
            Label textLabel = new Label();
            textLabel.Text = ( string ) message;
            textLabel.Font = new Font( FontFamily.GenericSansSerif, 20.0f, FontStyle.Regular );
            Graphics textGraphics = m_Notify.CreateGraphics();
            textLabel.Size = textGraphics.MeasureString( ( string ) message, textLabel.Font ).ToSize();
            textLabel.Location = new Point( 0, 0 );

            m_Notify.Controls.Add( textLabel );
            m_Notify.Size = textLabel.Size;

            // Herausfinden an welche Position wir die Form setzen müssen, damit diese überhalb der Tray eingeblendet wird
            m_Notify.StartPosition = FormStartPosition.Manual;
            m_Notify.DesktopLocation = new Point( Screen.PrimaryScreen.Bounds.Width - m_Notify.Size.Width,
                                           Screen.PrimaryScreen.Bounds.Height - WindowHelper.GetTaskbarHeight() - m_Notify.Size.Height );

            // Quick and Dirt: Da wir anschliessend den Thread anhalten, müssen wir noch alle Window Messages
            // abarbeiten, damit unsere NotifyBox richtig gezeichnet wird.
            m_Notify.Show();
            Application.DoEvents();
            Thread.Sleep( 1000 );
            m_Notify.Close();
        }
    }

    class WindowHelper
    {
        [DllImport("user32.dll")]
        private static extern IntPtr FindWindowEx( IntPtr hwndParent, IntPtr hwndChildAfter, string lpszClass, string lpszWindow );

        [DllImport("user32.dll")]
        private static extern bool GetWindowRect( IntPtr hWnd, out Rectangle lpRect );

        public static int GetTaskbarHeight()
        {
            IntPtr handleTaskbar = FindWindowEx( IntPtr.Zero, IntPtr.Zero, "Shell_TrayWnd", "" );
            if( handleTaskbar == IntPtr.Zero ) return( 0 );

            Rectangle taskbarRect;
            GetWindowRect( handleTaskbar, out taskbarRect );

            return( taskbarRect.Height - taskbarRect.Y );
        }
    }
}

/edit:
Im anhang befindet sich nun die komplette Solution mit allen Icons die verwendet wurden.
/edit:
Das Tutorial wurde überarbeitet, ein Tipp von Herbivore bezüglich der Windows Messages wurde eingearbeitet.
/edit:
Ich möchte gerne auch noch auf das Tray/Notify Template von Herbivore verweisen:  Vorlage für Tray-/NotifyIcon-Anwendung


Dateianhang:
unknown TrayApp1.zip (23,23 KB, 3.669 mal heruntergeladen)

Dieser Beitrag wurde 8 mal editiert, zum letzten Mal von egrath am 18.07.2007 12:47.

29.08.2006 08:45 Beiträge des Benutzers | zu Buddylist hinzufügen
Borg
myCSharp.de-Mitglied

Dabei seit: 23.08.2006
Beiträge: 1.529
Entwicklungsumgebung: VS2005
Herkunft: Berlin, Germany


Borg ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Sehr schöner Artikel.

Ich wollte dich nur darauf hinweisen, dass sich die Taskbar an jedem Bildschirmrand befinden kann. Daher funktioniert deine Methode GetTaskbarHeight() sowie die entsprechende Koordinatenberechnung nicht allgemeingültig.
29.08.2006 11:13 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
egrath egrath ist männlich
myCSharp.de-Mitglied

avatar-2119.jpg


Dabei seit: 24.07.2005
Beiträge: 871
Entwicklungsumgebung: MonoDevelop, NetBeans, Vi
Herkunft: Österreich / Steyr

Themenstarter Thema begonnen von egrath

egrath ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo Borg,

danke für deine Anregung, ich werd bei gelegenheit das Tutorial erweitern um dies zu berücksichtigen.

Grüsse, Egon
29.08.2006 12:06 Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegt mehr als ein Monat.
antopa antopa ist männlich
myCSharp.de-Mitglied

Dabei seit: 22.02.2006
Beiträge: 3
Entwicklungsumgebung: Visual C# 2005 Express Edition
Herkunft: Kassel


antopa ist offline

NotifyIcon

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Vielen Dank für den Ansatz! Habe erst nach Taskbar-Applikation gesuchtsmile

Übrigens gibt es in der Klasse NotifyIcon eine sehr schöne Möglichkeit eine Sprechblase zum Icon zu öffnen - inkl. Timer, Titel und Nachricht! Die Methode hierzu heisst "ShowBalloonTip" mit der das Problem der genauen Platzierung des Minimieren-Fenster dann erledigt wäreAugenzwinkern

Ciao und Danke noch mal
Antonio
29.09.2006 15:31 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
progger progger ist männlich
myCSharp.de-Mitglied

avatar-2094.gif


Dabei seit: 05.08.2005
Beiträge: 1.271
Entwicklungsumgebung: Visual Studio 2005; #develop 2
Herkunft: Nähe von München


progger ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hallo antopa,

Du solltest mit den BalloonTips aber nichts wichtiges anzeigen, denn man kann das Anzeigen von BalloonTips systemweit unterbinden. Siehe dazu auch  toolTip.IsBallon funktioniert nicht

Gruß,
progger
30.09.2006 11:20 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 2 Monate.
a_maier56 a_maier56 ist männlich
myCSharp.de-Mitglied

avatar-160.jpg


Dabei seit: 14.03.2006
Beiträge: 38
Entwicklungsumgebung: SharpDevelop und VSS.NET 03/05
Herkunft: BW


a_maier56 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hi,
Erstmal Daumen hoch ! Echt super Artikel!!! Konnte die Sourcen einwandfrei in mein Programm integrieren! Nur eins hast du vergessen: Im

C#-Code:
ExitApplicationHandler

muss vor der Zeile

C#-Code:
this.Close();

noch die Zeile

C#-Code:
m_NotifyIcon.Dispose();

eingefügt werden.
Ansonsten verschwindet das NotifyIcon nicht automtisch, nachdem die Anwendung über den Klick auf den Context-Menu-Eintrag "Beenden" geschlossen wurde. Erst wenn man nochmal mit der Maus über das Icon fährt verschwindet es.

Gruß, Andi!
02.12.2006 01:10 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Zwischen diesen beiden Beiträgen liegen mehr als 5 Monate.
MysticEmpires
myCSharp.de-Mitglied

Dabei seit: 21.06.2004
Beiträge: 302


MysticEmpires ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ich hatte mit dem Code ein kleines Problem und zwar fährt Windows nicht runter solange die AW im Try ist da die Anwendung immer ein .Cancel zurück giebt.

Es müssen folgenden Sachen geändert werden um dieses Problem zu umgehen:

C#-Code:
public void HideMainFormHandler( object sender, EventArgs e )
        {
            this.Visible = false;

            if( e is FormClosingEventArgs && ! m_ReallyClose )
            {
                (( FormClosingEventArgs ) e ).Cancel = true;
            }

            // Notify Form anzeigen
            if( ! m_ReallyClose )
            {
                NotifyBox notifyBox = new NotifyBox();
                Thread notifyBoxThread = new Thread( new ParameterizedThreadStart( notifyBox.ShowBox ));
                notifyBoxThread.Start( ( object ) "Minimized!" );
            }
        }

Ersetzen durch

C#-Code:
public void HideMainFormHandler( object sender, EventArgs e )
        {
            this.Visible = false;

            if( e is FormClosingEventArgs && ! m_ReallyClose){
                if((( FormClosingEventArgs ) e ).CloseReason == System.Windows.Forms.CloseReason.UserClosing)
                    (( FormClosingEventArgs ) e ).Cancel = true;
            }

            // Notify Form anzeigen
            if( ! m_ReallyClose )
            {
                NotifyBox notifyBox = new NotifyBox();
                Thread notifyBoxThread = new Thread( new ParameterizedThreadStart( notifyBox.ShowBox ));
                notifyBoxThread.Start( ( object ) "Minimized!" );
            }
        }

So hat es bei mir ohne Probleme funktioniert.
04.05.2007 12:27 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 13 Jahre.
Der letzte Beitrag ist älter als 12 Jahre.
Antwort erstellen


© Copyright 2003-2019 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 14.11.2019 09:16