Autor Beitrag
janoscharlipp
Hält's aus hier
Beiträge: 8



BeitragVerfasst: 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 ... :roll:

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.
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: Do 12.06.08 22:19 
Hallo und :welcome:!

Das hier sieht recht vielversprechend aus.

user profile iconjanoscharlipp hat folgendes geschrieben:
Meine Suchen führen alle nur zu Leuten, die das Kontext-Menu erweitern möchten ... :roll:

Scheint nicht nur Dir so gegangen zu sein :D
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 Threadstarter
Hält's aus hier
Beiträge: 8



BeitragVerfasst: 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 Threadstarter
Hält's aus hier
Beiträge: 8



BeitragVerfasst: 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:
ausblenden volle Höhe C#-Quelltext
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)
        {
            // collect all relative pidls
            Stack<IntPtr> relativePidls = new Stack<IntPtr>();
            IShellFolder shellParent = null;

            foreach (string path in paths)
            {
                IntPtr absolutePidl;
                uint iAttribute;

                // get the normal (absolute) pidl
                ShellAPI.SHParseDisplayName(path, IntPtr.Zero, out absolutePidl, 0out iAttribute);

                // get the relative pidl and the parent from it
                if (absolutePidl != IntPtr.Zero)
                {
                    // SHBindToParent gives us a new ptrParent every time, so we have to free all of
                    // them but the last (we need one to show the menu later)
                    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);

                    // did it work? (it should! :))
                    if (pidlRelative != IntPtr.Zero)
                    {
                        shellParent = (IShellFolder)Marshal.GetTypedObjectForIUnknown(ptrParent, typeof(IShellFolder));

                        relativePidls.Push(pidlRelative);
                    }
                }
            }

            // did the shell give us at least one relative pidl and the parent-Folder?
            if ((relativePidls.Count > 0) && (shellParent is IShellFolder))
            {
                IntPtr icontextMenuPtr;

                // get the context menu data
                int resultPointer = shellParent.GetUIObjectOf(
                            IntPtr.Zero,
                            (uint)relativePidls.Count,
                            relativePidls.ToArray(),
                            ref ShellAPI.IID_IContextMenu,
                            IntPtr.Zero,
                            out icontextMenuPtr);

                if (resultPointer == ShellAPI.S_OK)
                {
                    // we got a pointer, so make an object out of it
                    iContextMenu = (IContextMenu)Marshal.GetTypedObjectForIUnknown(icontextMenuPtr, typeof(IContextMenu));

                    // these are needed for submenus like send to, open with, and tortoise svn
                    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));

                    // create a context menu
                    IntPtr contextMenu = ShellAPI.CreatePopupMenu();

                    // fill the context menu
                    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));

                    // show it and wait for users joice
                    uint selected = ShellAPI.TrackPopupMenuEx(
                                        contextMenu,
                                        ShellAPI.TPM.RETURNCMD,
                                        position.X,
                                        position.Y,
                                        this.Handle,
                                        IntPtr.Zero);

                    // invoke the command if there is one
                    if (selected >= ShellAPI.CMD_FIRST)
                    {
                        uint cmd = selected - ShellAPI.CMD_FIRST;

                        // get the command string, so event listeners can decide whether they want to
                        // take care of a command or not
                        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]);

                            // invoke the command!
                            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;
                }
            }

            // free memory is for noobs ;)
        }

        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)

                    // this messages crashes the base class
                    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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4806
Erhaltene Danke: 1061

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: 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 Threadstarter
Hält's aus hier
Beiträge: 8



BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4806
Erhaltene Danke: 1061

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: 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 Threadstarter
Hält's aus hier
Beiträge: 8



BeitragVerfasst: 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4806
Erhaltene Danke: 1061

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: 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 Threadstarter
Hält's aus hier
Beiträge: 8



BeitragVerfasst: Mi 18.06.08 21:02 
Mein erster Versuch war einfach so, wie es in dem Projekt aus obigem Dokument gemacht wurde:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
// Konstruktor
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:
ausblenden 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.
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 20451
Erhaltene Danke: 2264

Win 10
C# (VS 2019)
BeitragVerfasst: 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 :gruebel:

_________________
Zwei Worte werden Dir im Leben viele Türen öffnen - "ziehen" und "drücken".
janoscharlipp Threadstarter
Hält's aus hier
Beiträge: 8



BeitragVerfasst: 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 Threadstarter
Hält's aus hier
Beiträge: 8



BeitragVerfasst: 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.