Laden...

ContextMenuStrip ohne sichtbare Form --> erzeugt Eintrag in Taskbar

6 Antworten
5,814 Aufrufe
Letzter Beitrag: vor 10 Monaten
ContextMenuStrip ohne sichtbare Form --> erzeugt Eintrag in Taskbar

Hi!

Meine Anwendung hat zwar eine Form, die wird aber nicht angezeigt.
Wenn ich jetzt auf eine bestimmte Position am Bildschirm klicke, möchte ich ein Kontektmenü anzeigen. Deshalb hab ich einen LowLevel Mouse Hook, mit dem ich das Klicken an diese Position erkenne.
Mit menu.Show(menuPoint, ToolStripDropDownDirection.AboveLeft) zeige ich das KontextMenü dann an.
Problem: Da meine Hauptform nicht sichtbar ist, erzeugt das Kontextmenü einen Eintrag in der Taskleiste (Standardicon und kein Titel). Wenn ich im Kontextmenü Unterelemente habe und da drüber fahre kommt noch ein Eintrag in der Taskleiste.

Wie kann ich verhindern, dass ein Eintrag in der Taskleiste erzeugt wird?

Wenn meine Hauptform sichtbar ist, dann wird beim Kontextmenüaufruf kein neues Icon in der Taskbar erzeugt.

Soweit ich gelesen habe wird auch kein Icon in der Taskbar erzeugt, wenn ich das Kontextmenü aus einem NotifyIcon heraus aufrufe, aber ich habe/will kein NotifyIcon.

Ich will einfach nur ohne sichtbare Form ein Kontextmenü dass nicht in der Taskleiste sichtbar ist.

Weiß jemand wie ich so etwas realisieren kann?

Lg,
Christian

Mittels der WinAPI-Funktion 'SetWindowLong' mußt du das Flag WS_EX_APPWINDOW löschen, d.h. ungefähr so:


SetWindowLong(form.Handle, GWL_EXSTYLE,
      GetWindowLong(form.Handle, GWL_EXSTYLE) & ~WS_EX_APPWINDOW));

(s.a. http://www.pinvoke.net/default.aspx/coredll/SetWindowLong.html)
Nach den Werten für die Konstanten müßtest du mal suchen...

Ich denke es reicht, einfach nach dem Show-Aufruf diesen Code aufzurufen.

Hi!

Danke, klingt eigentlich nach einem ganz gutem Ansatz, funktioniert aber leider nicht, weil das WS_EX_APPWINDOW Flag gar nicht gesetzt ist.

Ich hab mir mal alle Styles und ExStyles ausgeben lassen:

menu.Show(menuPoint, ToolStripDropDownDirection.AboveLeft);

// folgenden code hab ich eine sekunden später in einem eigenes thread gemacht, damit das kontextmenü auch sicher schon offen ist
int style = Win32.GetWindowLong(menu.Handle, Win32.GWL_STYLE);
Console.WriteLine(" WS_OVERLAPPED: " + ((style & Win32.WS_OVERLAPPED) == Win32.WS_OVERLAPPED));
Console.WriteLine(" WS_POPUP: " + ((style & Win32.WS_POPUP) == Win32.WS_POPUP));
...

int exStyle = Win32.GetWindowLong(menu.Handle, Win32.GWL_EXSTYLE);
Console.WriteLine(" WS_EX_DLGMODALFRAME: " + ((style & Win32.WS_EX_DLGMODALFRAME) == Win32.WS_EX_DLGMODALFRAME));
Console.WriteLine(" WS_EX_NOPARENTNOTIFY: " + ((style & Win32.WS_EX_NOPARENTNOTIFY) == Win32.WS_EX_NOPARENTNOTIFY));
...

Folgende Styles sind gesetzt (true):
WS_OVERLAPPED, WS_POPUP, WS_VISIBLE, WS_CLIPSIBLINGS, WS_CLIPCHILDREN, WS_TILED
WS_EX_LEFT, WS_EX_RIGHTSCROLLBAR, WS_EX_LTRREADING, WS_EX_COMPOSITED

Und dementsprechend nicht gesetzt (false) sind:
WS_CHILD, WS_MINIMIZE, WS_DISABLED, WS_MAXIMIZE, WS_CAPTION, WS_BORDER, WS_DLGFRAME, WS_VSCROLL, WS_HSCROLL, WS_SYSMENU,
WS_THICKFRAME, WS_GROUP, WS_TABSTOP, WS_MINIMIZEBOX, WS_MAXIMIZEBOX, WS_ICONIC, WS_SIZEBOX
WS_EX_DLGMODALFRAME, WS_EX_NOPARENTNOTIFY, WS_EX_TOPMOST, WS_EX_ACCEPTFILES, WS_EX_TRANSPARENT, WS_EX_MDICHILD, WS_EX_TOOLWINDOW,
WS_EX_WINDOWEDGE, WS_EX_CLIENTEDGE, WS_EX_CONTEXTHELP, WS_EX_RIGHT, WS_EX_RTLREADING, WS_EX_LEFTSCROLLBAR, WS_EX_CONTROLPARENT,
WS_EX_STATICEDGE, WS_EX_APPWINDOW, WS_EX_OVERLAPPEDWINDOW, WS_EX_PALETTEWINDOW, WS_EX_LAYERED, WS_EX_NOINHERITLAYOUT, WS_EX_LAYOUTRTL, WS_EX_NOACTIVATE

Ich hab auch schon versucht alle gesetzten Styles außer WS_VISIBLE zu entfernen, und zwar in einem eigenen Thread und eine Sekunde verzögert. Das MenuHandle hab ich mir zum Testen global gemerkt, weil ich aus dem anderen Thread menu.Handle ja nicht aufrufen kann, und wenn ichs asynchrom mit einem Delegate mach, bekomm ich leider immer 0:

style = style & ~Win32.WS_POPUP & ~Win32.WS_TILED & ~Win32.WS_CLIPCHILDREN & ~Win32.WS_CLIPSIBLINGS;
exStyle = exStyle & ~Win32.WS_EX_LEFT & ~Win32.WS_EX_RIGHTSCROLLBAR & ~Win32.WS_EX_LTRREADING & ~Win32.WS_EX_COMPOSITED;
Win32.SetWindowLong(menuHandle, Win32.GWL_STYLE, newStyle);
Win32.SetWindowLong(menuHandle, Win32.GWL_EXSTYLE, newExStyle);

Wenn ich das aufrufe, tut sich überhaupt nichts. Und wenn ich auch das WS_VISIBLE noch entferne, dann verschwindet das Fenster zwar nicht, aber wenn ich mit der Maus drüber fahre, tut sich nichts mehr, also kein Hovereffekt. In der Taskleiste ist es aber immer nocht sichtbar.

Ich habe also jetzt praktisch alle Window-Styles entfernt und das Icon in der Taskleiste ist immer noch da. Vermutlich erzeugt also das Taskbaricon nicht das Kontextmenü sondern irgend ein anderes Fenster.

Aber selbst wenn ich jetzt das passende Fenster finden würde und das APPWINDOW entfernen könnte, hätte ich immer noch 2 Probleme:
Erstens blitzt das Taskbaricon kurz auf bevor ich es per API entfernen kann, und
zweitens müsste ich im Hintergrund andauernd checken ob man nicht im Kontextmenü auf einen Eintrag gefahren ist, der ein Untermenü erzeugt, weil dann kommt ja ein weiteres Icon in die Taskleiste, und das müsste ich dann auch wieder entfernen.

Der bessere/richtigere Ansatz wäre also, wenn es eine Funktion gäbe, mit der ich das Kontextmenü aufrufen kann, ohne dass das Taskbaricon überhaupt erzeugt wird. Gibt es da vielleicht irgendwelche privaten Methoden die ich "missbrauchen" könnte?

Beim Googeln nach habe ich nämlich folgendes entdeckt:
http://bytes.com/topic/c-sharp/answers/439172-how-show-contextmenustrip-without-displaying-taskbar

Hier geht es zwar drum aus einem NotifyIcon heraus ein Popup mit der linken Maustaste aufzurufen, aber dort wird per Reflections eine private Methode vom NotifyIcon aufgerufen, die dann das Kontextmenü ohne Eintrag in der Taskbar anzeigt.
Kann ich das in meinem Fall auch irgendwie anwenden? Bzw. kann ich irgendwie herausfinden, was in dieser privaten Methode vom NotifyIcon gemacht wird? Wenn ich im Visual Studio ein NotifyIcon erzeuge und dann zur Definition gehe (F12), sehe ich dort ja erstens nur die public Methoden und zweitens keine Implementierungen der Methoden sondern nur die Deklarationen. Kann ich mir den "Source" dazu auch irgendwo ansehen um so eventuell die private Method zu finden?

Oder weiß jemand noch einen ganz anderen Ansatz?

Lg,
Christian

Sorry, daß es doch etwas schwieriger ist...

Kennst du den .NET-Reflector (http://www.red-gate.com/products/reflector/)?
Mit diesem Tool kannst du dir den Source-Code beliebiger .NET-Assemblies ausgeben lassen.
Für dein Problem also nach "NotifyIcon.ShowContextMenu" suchen...

Danke, den .Net Reflector kannte ich noch nicht, aber mit diesem hab ich mir jetzt mal die ShowContextMenu-Methode angesehen:

private void ShowContextMenu()
{
    if ((this.contextMenu != null) || (this.contextMenuStrip != null))
    {
        NativeMethods.POINT pt = new NativeMethods.POINT();
        UnsafeNativeMethods.GetCursorPos(pt);
        UnsafeNativeMethods.SetForegroundWindow(new HandleRef(this.window, this.window.Handle));
        if (this.contextMenu != null)
        {
            this.contextMenu.OnPopup(EventArgs.Empty);
            SafeNativeMethods.TrackPopupMenuEx(new HandleRef(this.contextMenu, this.contextMenu.Handle), 0x48, pt.x, pt.y, new HandleRef(this.window, this.window.Handle), null);
            UnsafeNativeMethods.PostMessage(new HandleRef(this.window, this.window.Handle), 0, IntPtr.Zero, IntPtr.Zero);
        }
        else if (this.contextMenuStrip != null)
        {
            this.contextMenuStrip.ShowInTaskbar(pt.x, pt.y);
        }
    }
}

Da das contextMenu eh nicht gesetzt ist, sondern nur das contextMenuStrip, ist der mittlere Teil eh schon mal hinfällig (hoffe ich).
Im unteren Teil wird das Kontextmenü angezeigt, also ist der einzige Teil, der erkären würde warum kein Icon in der Taskleiste erzeugt wird, der obere Teil.
Hier wird anscheinend einfach nur das NotifyIcon "aktiviert", und deshalb vielleicht kein Icon in der Taskbar erzeugt (weil es eben ein aktives zugehöriges Fenster gibt --> das NotifyIcon).

Ich wollte dann mal testen ob es wirklich daran liegt und hab temporär ein NotifyIcon auf meine Form gezogen.
Zum Testen müsste ich also jetzt bevor ich mein Kontextmenü anzeige das NotifyIcon aktivieren bzw. als ForegroundWindow setzen.
Das Problem dabei ist aber, dass ich dazu das Handle vom NotifyIcon brauche. Das ist in einer privaten Variable "window" gespeichert, die aber leider vom PRIVATEN Typ NotifyIconNativeWindow ist. Kann ich diese Variable dann per Reflections ermitteln? Es gibt ja keine (public) Klasse NotifyIconNativeWindow.
Wenn ich diese Klasse selbst erzeuge, würde dass dann funktionieren, dass ich das "window" darauf caste?
Innerhalb der NotifyIconNativeWindow werden dann auch noch weitere private Methoden aufgerufen, die ich dann vermutlich auch per Reflections aufrufen müsste.
Aber wäre das ein möglicher Ansatz? Oder komm ich irgendwie anders an den privaten Typ ran?

Naja, selbst wenn es wirklich funktionieren würde, dass kein Taskbaricon erzeugt werden würde, wenn das NotifyIcon den Fokus hat, ist mir ja noch immer nicht geholfen, da ich bei mir ja kein NotifyIcon habe.

Gäbe es noch andere Ansätze? Was mir grad noch einfällt wäre eine "unsichtbare" sichtbare Form, also mit 100% Transparenz und ShowInTaskbar = false .. dann hätte ich ja ein sichtbares Fenster das ich aktivieren könnte bevor ich mein Kontextmenü erzeugt, aber ich vermute mal dass dann wieder ein Eintrag in der Taskbar erzeugt wird, weil ja keiner vorhanden ist ... ich werd's am Abend mal probieren.


edit/nachtrag:

Ich hab es jetzt mal kurz ausprobiert:
Eine Form ohne Border und Opacity auf 0 gesetzt (zur Sicherheit) und ShowInTaskbar = false, und dann beim Aufruf des Kontextmenüs:

form.Show();
contextMenuStrip.Show(menuPoint);
form.Hide();

Dann ist die Hautpform nur so kurz sichtbar, dass man sie gar nicht sehen würde, und falls es doch mal länger dauern sollte, ist sie ja noch 100% transparent.
Auf jeden Fall wird so kein Icon in der Taskbar angezeigt - Problem gelöst. Ich habs gerade unter Windows Vista probiert, mal schauen ob es zu Hause auch auf Windows7 klappt.
Auf jeden Fall danke für die Hilfe.

Hallo,
ich hatte das selbe Problem und hab viel gesucht und ENDLICH die richtige Lösung hier gefunden:
ContextMenuStrip from a left click on a Notify Icon - Post.Byes

Zusammengefasst: der Standard Rechts-Klick auf das NotifyIcon führt vor dem Öffnen noch ein p/invoke SetForegroundWindow aus.

Wenn man das in den Links-Klick integriert, verhält sich das Menü genauso wie bei einem Rechts-Klick:


[DllImport("User32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern bool SetForegroundWindow(HandleRef hWnd);

private void notifyIcon1_MouseClick(object sender, MouseEventArgs e)
{
   if (e.Button == MouseButtons.Left)
   {
       SetForegroundWindow(new HandleRef(this, this.Handle));
       this.contextMenuStrip1.Show(this, this.PointToClient(Cursor.Position));
   }
}

Gruß,
Stephan