| Autor |
Beitrag |
janoscharlipp
Hält's aus hier
Beiträge: 8
|
Verfasst: Do 12.06.08 22:10
Hallo,
weiß jemand eine Möglichkeit, wie auch aus einem in C# geschriebenen Programm heraus das normale Kontext-Menu des Windows Explorers für einen bestimmten Ordner / eine bestimmte Datei anzeigen kann?
Meine Suchen führen alle nur zu Leuten, die das Kontext-Menu erweitern möchten ...
Im MSDN habe ich msdn.microsoft.com/e...bb776389(VS.85).aspx gefunden, sieht sehr kryptisch aus, aber die Beschreibung klingt richtig, ich hab nur keinen Plan, wie ich sowas aus C# heraus aufrufen soll.
Bin für jeden Hinweis dankbar, Janosch
|
|
Christian S.
      
Beiträge: 20451
Erhaltene Danke: 2264
Win 10
C# (VS 2019)
|
Verfasst: Do 12.06.08 22:19
Hallo und  !
Das hier sieht recht vielversprechend aus.
janoscharlipp hat folgendes geschrieben: | Meine Suchen führen alle nur zu Leuten, die das Kontext-Menu erweitern möchten ...  |
Scheint nicht nur Dir so gegangen zu sein
| Steven Roebert @ CodeProject hat folgendes geschrieben: | | The first thing you'll notice when trying to find a nice article about getting the Shell ContextMenu in your program, is that almost all articles are about making extensions to the menu and not about retrieving it for your own program. |
Grüße
Christian
_________________ Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
|
|
janoscharlipp 
Hält's aus hier
Beiträge: 8
|
Verfasst: Do 12.06.08 22:33
Hui, das ging fix, vielen Dank für die Antwort, das sieht ausgezeichnet aus.
Ich werde mich mal reinlesen und dann hier das Ergebnis posten, schönen Abend noch 
|
|
janoscharlipp 
Hält's aus hier
Beiträge: 8
|
Verfasst: Di 17.06.08 23:15
Okey, ich war schon mal so weit, das es funktionierte, hier die Funktion, die ich mir aus dem oben verlinkten Dokument zusammengestückelt habe:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167:
| protected IContextMenu iContextMenu; protected IContextMenu2 iContextMenu2; protected IContextMenu3 iContextMenu3;
public void ShowContextMenu(string[] paths, Point position) { Stack<IntPtr> relativePidls = new Stack<IntPtr>(); IShellFolder shellParent = null;
foreach (string path in paths) { IntPtr absolutePidl; uint iAttribute;
ShellAPI.SHParseDisplayName(path, IntPtr.Zero, out absolutePidl, 0, out iAttribute);
if (absolutePidl != IntPtr.Zero) { if (shellParent is IShellFolder) Marshal.ReleaseComObject(shellParent);
IntPtr ptrParent = IntPtr.Zero; IntPtr pidlRelative = IntPtr.Zero; ShellAPI.SHBindToParent(absolutePidl, ref ShellAPI.IID_IShellFolder, out ptrParent, out pidlRelative);
if (pidlRelative != IntPtr.Zero) { shellParent = (IShellFolder)Marshal.GetTypedObjectForIUnknown(ptrParent, typeof(IShellFolder));
relativePidls.Push(pidlRelative); } } }
if ((relativePidls.Count > 0) && (shellParent is IShellFolder)) { IntPtr icontextMenuPtr;
int resultPointer = shellParent.GetUIObjectOf( IntPtr.Zero, (uint)relativePidls.Count, relativePidls.ToArray(), ref ShellAPI.IID_IContextMenu, IntPtr.Zero, out icontextMenuPtr);
if (resultPointer == ShellAPI.S_OK) { iContextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(icontextMenuPtr, typeof(IContextMenu));
IntPtr context2Ptr = IntPtr.Zero; Marshal.QueryInterface(icontextMenuPtr, ref ShellAPI.IID_IContextMenu2, out context2Ptr); iContextMenu2 = (IContextMenu2)Marshal.GetTypedObjectForIUnknown(context2Ptr, typeof(IContextMenu2));
IntPtr context3Ptr = IntPtr.Zero; Marshal.QueryInterface(icontextMenuPtr, ref ShellAPI.IID_IContextMenu3, out context3Ptr); iContextMenu3 = (IContextMenu3)Marshal.GetTypedObjectForIUnknown(context3Ptr, typeof(IContextMenu3));
IntPtr contextMenu = ShellAPI.CreatePopupMenu();
iContextMenu.QueryContextMenu( contextMenu, 0, ShellAPI.CMD_FIRST, ShellAPI.CMD_LAST, ShellAPI.CMF.EXPLORE | ShellAPI.CMF.CANRENAME | ((Control.ModifierKeys & Keys.Shift) != 0 ? ShellAPI.CMF.EXTENDEDVERBS : 0));
uint selected = ShellAPI.TrackPopupMenuEx( contextMenu, ShellAPI.TPM.RETURNCMD, position.X, position.Y, this.Handle, IntPtr.Zero);
if (selected >= ShellAPI.CMD_FIRST) { uint cmd = selected - ShellAPI.CMD_FIRST;
string info = string.Empty; byte[] bytes = new byte[256]; int index = 0;
iContextMenu.GetCommandString(cmd, ShellAPI.GCS.VERBW, 0, bytes, ShellAPI.MAX_PATH);
while ((index < bytes.Length - 1) && ((bytes[index] != 0) || (bytes[index + 1] != 0))) index += 2;
if (index < bytes.Length - 1) info = Encoding.Unicode.GetString(bytes, 0, index + 1);
DataEvent de = new DataEvent(EventType.Command, ShellPluginEvents.ContextMenuSelection, info);
EventManager.DispatchEvent(plugin, de);
if (!de.Handled) { string parentDir = Path.GetDirectoryName(paths[0]);
ShellAPI.CMINVOKECOMMANDINFOEX invoke = new ShellAPI.CMINVOKECOMMANDINFOEX(); invoke.cbSize = ShellAPI.cbInvokeCommand; invoke.lpVerb = (IntPtr)cmd; invoke.lpDirectory = parentDir; invoke.lpVerbW = (IntPtr)cmd; invoke.lpDirectoryW = parentDir; invoke.fMask = ShellAPI.CMIC.UNICODE | ShellAPI.CMIC.PTINVOKE | ((Control.ModifierKeys & Keys.Control) != 0 ? ShellAPI.CMIC.CONTROL_DOWN : 0) | ((Control.ModifierKeys & Keys.Shift) != 0 ? ShellAPI.CMIC.SHIFT_DOWN : 0); invoke.ptInvoke = new ShellAPI.POINT(position.X, position.Y); invoke.nShow = ShellAPI.SW.SHOWNORMAL;
iContextMenu.InvokeCommand(ref invoke); } } } else { icontextMenuPtr = IntPtr.Zero; iContextMenu = null; } }
}
protected override void WndProc(ref Message m) { if (iContextMenu2 != null && (m.Msg == (int)ShellAPI.WM.INITMENUPOPUP || m.Msg == (int)ShellAPI.WM.MEASUREITEM || m.Msg == (int)ShellAPI.WM.DRAWITEM)) { if (iContextMenu2.HandleMenuMsg( (uint)m.Msg, m.WParam, m.LParam) == ShellAPI.S_OK)
if (m.Msg != (int)ShellAPI.WM.DRAWITEM) return; }
if (iContextMenu3 != null && m.Msg == (int)ShellAPI.WM.MENUCHAR) { if (iContextMenu3.HandleMenuMsg2( (uint)m.Msg, m.WParam, m.LParam, IntPtr.Zero) == ShellAPI.S_OK) return; } } |
Damit es läuft, bedarf es noch einiger Interfaces und der ShellAPI, die ich zu 99% aus dem Dokument übernommen habe, ich musste aber einige Anpassungen machen, weil der Compiler meckerte, daher im Anhang die benötigen und bei mir funktionstüchtigen Dateien.
Wie man an dem Kommentar am Ende sieht, fehlt noch die Freigabe von verbrauchtem Speicher. Generell muss man alles wofür man einen Pointer von der Shell bekommen hat, wieder freigeben, erstmal habe ich aber noch ein anderes Problem.
Und zwar lief der Code solange gut, wie ich ihn in eine Komponente (eine Erweiterung der TreeView-Komponente) eingebettet hatte.
Nun möchte ich den Code aber gerne von der Komponente trennen, da die Kontext-Menus eventuell auch von anderen Stellen her ausgelöst werden können sollen. In dem Programm an dem ich arbeite, macht man dafür Plugins, die das Programm dann automatisch beim Programmstart läd. Nun habe ich das Problem, dass ich ja zum erstellen des Kontext-Menus mit TrackPopupMenuEx einen Handle brauche. So ganz klar ist mir nicht, was so ein "Handle" ist, aber wenn ich das richtig verstehe haben erstmal nur graphische Komponenten sowas. Nun soll mein Plugin aber (bis auf das Kontext-Menu) unsichtbar sein, es muss ja nur diese Methode zur Verfügung stellen, daher weiß ich nicht, wie ich an einen solchen Handle komme. Ich habe testweise von NativeWindow geerbt, und dann im Konstruktor CreateHandle aufgerufen (so war das in dem Projekt vom obigen Dokument gelöst), dann bekomme ich aber beim Starten den Fehler "Fehler beim Erstellen des Fensterhandles".
Hat jemand Rat für mich?
Einloggen, um Attachments anzusehen!
|
|
Th69
      

Beiträge: 4805
Erhaltene Danke: 1061
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: Mi 18.06.08 10:25
Am besten ist es, wenn du das Handle als Parameter deiner Methode zur Verfügung stellst.
Das Handle (quasi eine Art Zeiger bzw. Referenz) dient dabei als Parent des Kontextmenüs.
So kannst du dann dein Kontextmenü für beliebige Komponenten aufrufen (z.B. auch mittels GetDesktopHandle() oder Form.Handle).
|
|
janoscharlipp 
Hält's aus hier
Beiträge: 8
|
Verfasst: Mi 18.06.08 10:28
Ok, danke für die Antwort.
Verstehe ich das richtig, dass das Handle entscheidet, bei wem die WndProc-Methode für die Messages des Kontext-Menus aufgerufen wird?
Denn dann wäre es natürlich gut, wenn ich meinen eigenen Handle hätte, da ich mich ja in der WndProc-Methode umd die Untermenus kümmern muss, was nach außen hin eigentlich nicht sichtbar sein soll.
|
|
Th69
      

Beiträge: 4805
Erhaltene Danke: 1061
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: Mi 18.06.08 12:29
Ja, dieses Handle entscheidet anscheinend über den Empfang der Messages. Du mußt also doch dann ein Fenster mit einem Handle bereitstellen (evtl. ein unsichtbares / transparentes o.ä.), um die Nachrichten selber zu empfangen.
Nähere s. msdn.microsoft.com/e...ms648003(VS.85).aspx
|
|
janoscharlipp 
Hält's aus hier
Beiträge: 8
|
Verfasst: Mi 18.06.08 13:29
Jo, genau das wollte ich machen. Aber wenn ich in meinem NativeWindow-Konstruktor CreateHandle aufrufe, bekomme ich eben die (sehr vielsagende) Fehlermeldung "Fehler beim Erstellen des Fensterhandles".
Wenn ich stattdessen von Control erbe, und dann versuche auf den Handle einfach zuzugreifen ist der Handle nicht definiert (und verweist im Debugger ebenfalls auf obige Fehlermeldung).
Wie komm ich denn nun zu so einem Handle?
|
|
Th69
      

Beiträge: 4805
Erhaltene Danke: 1061
Win10
C#, C++ (VS 2017/19/22)
|
Verfasst: Mi 18.06.08 15:19
Dann laß dir mal die Eigenschaft Win32Exception.NativeErrorCode ausgeben bzw. zeige mal den Code zum Aufruf von CreateHandle().
|
|
janoscharlipp 
Hält's aus hier
Beiträge: 8
|
Verfasst: Mi 18.06.08 21:02
Mein erster Versuch war einfach so, wie es in dem Projekt aus obigem Dokument gemacht wurde:
C#-Quelltext 1: 2: 3: 4: 5:
| public ContextMenuHandler() { this.CreateHandle(new CreateParams()); } |
Heraus kommt eben der Fehler "Fehler beim erstellen des Fensterhandles" und der NativeErrorCode ist nicht definiert (0)
Weil mir das mit den leeren Parametern seltsam aussah (und im msdn auch nicht so ist) hab ich es dann mal so probiert:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13:
| CreateParams cp = new CreateParams();
cp.Caption = "Click here"; cp.ClassName = "Button";
cp.X = 100; cp.Y = 100; cp.Height = 100; cp.Width = 100;
cp.Parent = PluginBase.MainForm.Handle;
this.CreateHandle(cp); |
So bekomme ich den Fehler "Klasse ist bereits vorhanden" mit NativeErrorCode 1410.
Wenn ich die Zeile mit dem ClassName auskommentiere erscheint wieder die obige Meldung.
Das mit dem Parent ändert nichts, PluginBase.MainForm.Handle ist aber ein Pointer != 0.
Irgendwie blicke ich nicht, was der von mir will, und warum es in dem anderen Projekt mit leeren CreateParams funktioniert.
Wie zur Hölle komm ich denn nun an einen eigenen Handle?
|
|
Christian S.
      
Beiträge: 20451
Erhaltene Danke: 2264
Win 10
C# (VS 2019)
|
Verfasst: Mi 18.06.08 21:12
Eins verstehe ich nicht: Wie wird denn so ein Kontextmenü aufgerufen, wenn es keine GUI gibt? Ich meine, für ein Kontextmenü muss doch irgendwer irgendwo klicken 
_________________ Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
|
|
janoscharlipp 
Hält's aus hier
Beiträge: 8
|
Verfasst: Mi 18.06.08 21:18
Olé, ich habs!
Ich hatte in der Klasse die WndProc-Methode überschrieben, dort aber nicht base.WndProc bei Messages die mich nichts angehen aufgerufen. Und da wird wohl eine essenzielle Message am Anfang geschickt, die ich einfach mal totgeschwiegen habe.
Soweit so gut, Thema erledigt danke euch beiden!
|
|
janoscharlipp 
Hält's aus hier
Beiträge: 8
|
Verfasst: Mi 18.06.08 21:23
Ok, nochmal zur Klärung warum meine Klasse unsichtbar sein soll.
Es ist so, dass es in dem Programm (übrigens FlashDevelop.org) sehr wohl eine graphische Oberfläche gibt, und dort gibt es eben einen ProjectManager, der eine Baumstruktur für das Dateisystem anzeigt. Wenn man dort mit der rechten Maustaste klickt, schmeißt er ein Event ("Bitte Kontextmenu anzeigen") und mein Plugin kümmert sich drum. Das hat den Vorteil, dass auch der Integrierte Filebrowser (listenbasiert) ohne großes Aufheben ein solches Kontextmenu anzeigen kann und der (schreckliche) Shell-Code relativ gut in dem Plugin versteckt ist.
|
|
|