Laden...

Systemmenü abfangen

Erstellt von steffen_dec vor 15 Jahren Letzter Beitrag vor 14 Jahren 6.848 Views
S
steffen_dec Themenstarter:in
322 Beiträge seit 2007
vor 15 Jahren
Systemmenü abfangen

Hallo,

wie kann man in einer Form das Systemmenü abfangen? (Rechtsklick in der Taskleiste, Rechtsklick auf das Icon, oder Alt+Leertaste)

Ich möchte zur Laufzeit entscheiden ob das Menü aufgehen darf oder nicht (abhängig vom angemeldeten Benutzer).

FormBorderStyle möchte ich nämlich nicht verändern, da es dann ohne Icon doof aussieht.

Hat jemand eine Idee?

Danke
Steffen

915 Beiträge seit 2006
vor 15 Jahren

Hrm, das ist schwer zu unterbinden und es gibt mehrere Wege dafür das zu lösen.
Aber erstmal zu den Problem, du brauchst dafür Kenntnisse im Umgang mit der Windows API.

Windows Fenster besitzen zwei größen, eine Clientgröße und eine wirkliche Fenstergröße. Die .NET Hausmittel geben dir über die MouseEvents nur den Clientbereich zurück nicht aber die äußeren Randbereiche. Sprich, wenn ein MouseMove mal bindest und auf die Titlebar des Fensters huscht kommt plötzlich kein Event. Des weiteren, das Standardverhallten von Fenstern wird in den WindowsNachrichten verarbeitet, das heisst klickst du auf einen Randbereich so wird in den WindowsNachrichten die Nachricht versendet, NonClientArea (Borders) MouseMove MouseLeftDown MouseLeftUp und eine Nachricht für den HitTestCode. Um nun zu vermeiden das alle Nachrichten durchgehen musst und diese zu immitieren, verändere nur die Nahcricht die ausschlaggebend ist und das ist WM_NCHITTEST - diese liefert als WindowsMessage.Result einen Integerwert zurück der schlussendlich das Verhallten weidergibt. Siehe hierzu www.pinvoke.net NCHITTEST. Du kannst also nun das Problem einfach Lösen indem die WndProc überschreibst die WindowsNachricht WM_NCHITTEST abfängst, schaust ob die NCHITTEST Returnwert = HTMENU (5) ist und gibst stattdessen wenn dein Kriterium sagt nee Menü soll nicht erlaubt sein stattdessen ein HTBORDER (18) oder HTOBJECT (19) zurück an das Message.Result.

So einfach, die anderen Lösungen dauern länger und man muss mehr beachten. Kannst dir auch mal den Artikel CustomBorders durchsehen, dort gibt es ein Projekt von dr4g0n76 der genau das dort nur in einen anderne zusammenhang behandelt. Oder kannst dir die UtilitiesLib mal von mir ansehen, mache das dort auch.

Wenn einen anderen Weg gehen möchtest sag bescheid, gibt wie gesagt noch mehr Möglichkeiten. Ist nur immer so viel zu tippen 😉

Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(

S
steffen_dec Themenstarter:in
322 Beiträge seit 2007
vor 15 Jahren

Hallo Andreas,

danke für deine Antwort.

ich kriege dass irgendwie nicht hin, was mache ich falsch?

hier meine Funktion:


        protected override void WndProc(ref Message m)
        {
            const int WM_NCHITTEST = 0x84;
            const int HTCAPTION = 0x02;
            const int HTCLIENT = 0x01;
            const int HTMENU = 5;

            base.WndProc(ref m);

            //Verschiebung des Hauptformulars abfangen (bei allen Usern außer Admin!)
            if (m.Msg == WM_NCHITTEST && (int)m.Result == HTCAPTION && (oBenutzer.Userlevel != CvsInSightSecurityAccess.Full))
            {
                m.Result = (IntPtr)HTCLIENT;
            }

            //Systemmenü abfangen
            if (m.Msg == WM_NCHITTEST && (int)m.Result == HTMENU && (oBenutzer.Userlevel != CvsInSightSecurityAccess.Full))
            {
                m.Result = (IntPtr)HTCLIENT;
            }

            //Doppelklick auf die Titelleiste abfangen
            if (m.Msg == 0x00A3 && (oBenutzer.Userlevel != CvsInSightSecurityAccess.Full)) // WM_NCLBUTTONDBLCLK
            {
                return;
            }
        }
2.921 Beiträge seit 2005
vor 15 Jahren

Ich verweise auf:

AxWebBrowser: Seite drucken

Seit der Erkenntnis, dass der Mensch eine Nachricht ist, erweist sich seine körperliche Existenzform als überflüssig.

915 Beiträge seit 2006
vor 15 Jahren

... Hier stand ein Fehlerhafter Code 😉

Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(

S
steffen_dec Themenstarter:in
322 Beiträge seit 2007
vor 15 Jahren

Ich verweise auf:


>

Hi,

ich sehe da keine Gemeinsamkeiten zum Thema hier!?
Kannst du mich mal aufklären 🤔

Steffen

915 Beiträge seit 2006
vor 15 Jahren

Sorry der Code den ich gepostet habe ist falsch, korrigiere das grade .-)

Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(

S
steffen_dec Themenstarter:in
322 Beiträge seit 2007
vor 15 Jahren

ich habe jetzt mal ein leeres Projekt erstellt und es versucht:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace Era
{
    public partial class Form1 : Form
    {

        protected override void WndProc(ref Message m)
        {
            const int WM_NCHITTEST = 0x84;
            const int HTCAPTION = 0x02;
            const int HTCLIENT = 0x01;
            const int HTBORDER = 18;
            const int HTOBJECT = 19;
            const int HTMENU = 5;
            const int HTSYSMENU = 3;
            const int WM_NCLBUTTONDBLCLK = 0xA3;
            const int WM_NCRBUTTONDOWN = 0xA4;

            //base.WndProc(ref m);

            //Systemmenü abfangen
            if (m.Msg == WM_NCHITTEST && (int)m.Result == HTSYSMENU)
            {
                m.Result = (IntPtr)HTCLIENT;
                return;
            }

            if (m.Msg == WM_NCHITTEST && (int)m.Result == HTMENU)
            {
                m.Result = (IntPtr)HTCLIENT;
                return;
            }

            //Verschiebung des Hauptformulars abfangen (bei allen Usern außer Admin!)
            if (m.Msg == WM_NCHITTEST && (int)m.Result == HTCAPTION)
            {
                m.Result = (IntPtr)HTCLIENT;
                return;
            }

            //Doppelklick auf die Titelleiste abfangen
            //if (m.Msg == WM_NCLBUTTONDBLCLK)
            //{
            //    //m.Result = (IntPtr)HTCLIENT;
            //    return;
            //}

            base.WndProc(ref m);
        }

        public Form1()
        {
            InitializeComponent();
        }
    }
}

das funktioniert so nicht, es geht nur wenn ich base.WndProc(ref m); vor den IF-Bedingungen ausführe.
Linksklick (kontextmenu) wird dabei dennoch nicht abgefangen, genauso wie Alt+Leertaste

Gibt es eventuell eine Möglichkeit die einzelnen Menüpunkte zu deaktivieren?

915 Beiträge seit 2006
vor 15 Jahren

So jetzt aber:


		private const int WM_NCHITTEST = 0x84;
		private const int HTCAPTION = 0x02;
		private const int HTCLIENT = 0x01;
		private const int HTMENU = 5;
		private const int WM_NCRBUTTONDOWN = 0xA4;

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

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

		protected override void WndProc(ref Message m)
		{

			if (m.Msg == WM_NCRBUTTONDOWN)
			{
				Point pt = new Point((int)m.LParam);

				Rectangle ClientRect;
				Rectangle WindowRect;
				GetClientRect(m.HWnd, out ClientRect);
				GetWindowRect(m.HWnd, out WindowRect);

				// berechne die caption (wird um 2-3px größer sein als nötig, bereitet aber keine Probleme)
				Rectangle CaptionRect = new Rectangle(WindowRect.Location,
					new Size(WindowRect.Width, WindowRect.Height - WindowRect.Y - ClientRect.Height));

				if (CaptionRect.Contains(pt)) // + deine abfragen
					return;
				
			}

			if (m.Msg == WM_NCHITTEST)
			{
				Point pt = new Point((int)m.LParam);

				Rectangle ClientRect;
				Rectangle WindowRect;
				GetClientRect(m.HWnd, out ClientRect);
				GetWindowRect(m.HWnd, out WindowRect);

				// berechne die caption (wird um 2-3px größer sein als nötig, bereitet aber keine Probleme)
				Rectangle CaptionRect = new Rectangle(WindowRect.Location,
					new Size(WindowRect.Width, WindowRect.Height - WindowRect.Y - ClientRect.Height));

				if (CaptionRect.Contains(pt)) // + deine abfragen
					return;
			}

			base.WndProc(ref m);
		}
	}

Musst halt nur noch das von dir miteinfügen und kannst das ein oder andere noch vereinfachen - war nur zu faul 😉

Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(

S
steffen_dec Themenstarter:in
322 Beiträge seit 2007
vor 15 Jahren

Danke für deine Mühe ich hab es nun verstanden.

Kann mir jemand sagen warum ich die Menüeinträge (außer Schließen) nicht deaktivieren kann?

Ich habe es so probiert:


        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern int GetMenuItemCount(IntPtr hMenu);
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern bool DrawMenuBar(IntPtr hWnd);
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern bool RemoveMenu(IntPtr hMenu, uint uPosition, uint uFlags);
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);

        private const Int32 MF_BYPOSITION = 0x400;
        internal const UInt32 MF_BYCOMMAND = 0x00000000;
        private const Int32 MF_REMOVE = 0x1000;
        internal const UInt32 MF_ENABLED = 0x00000000;
        internal const UInt32 MF_GRAYED = 0x00000001;
        internal const UInt32 MF_DISABLED = 0x00000002;
        
        enum SysCommands : uint
        {
            SC_SIZE = 0xF000,
            SC_MOVE = 0xF010,
            SC_MINIMIZE = 0xF020,
            SC_MAXIMIZE = 0xF030,
            SC_NEXTWINDOW = 0xF040,
            SC_PREVWINDOW = 0xF050,
            SC_CLOSE = 0xF060,
            SC_VSCROLL = 0xF070,
            SC_HSCROLL = 0xF080,
            SC_MOUSEMENU = 0xF090,
            SC_KEYMENU = 0xF100,
            SC_ARRANGE = 0xF110,
            SC_RESTORE = 0xF120,
            SC_TASKLIST = 0xF130,
            SC_SCREENSAVE = 0xF140,
            SC_HOTKEY = 0xF150,
            //#if(WINVER >= 0x0400) //Win95
            SC_DEFAULT = 0xF160,
            SC_MONITORPOWER = 0xF170,
            SC_CONTEXTHELP = 0xF180,
            SC_SEPARATOR = 0xF00F,
            //#endif /* WINVER >= 0x0400 */

            //#if(WINVER >= 0x0600) //Vista
            SCF_ISSECURE = 0x00000001,
            //#endif /* WINVER >= 0x0600 */

            /*
              * Obsolete names
              */
            SC_ICON = SC_MINIMIZE,
            SC_ZOOM = SC_MAXIMIZE,
        }
public static void EnableSystemMenuItems(Form frm, bool Enable)
        {
            IntPtr hMenu;
            int n;
            uint wFlags;

            if (!Enable)
                wFlags = MF_BYCOMMAND | MF_DISABLED;
            else
                wFlags = MF_BYCOMMAND | MF_ENABLED;
    
            hMenu = GetSystemMenu(frm.Handle, false);
            if (hMenu != IntPtr.Zero)
            {
                n = GetMenuItemCount(hMenu);
                if (n > 0)
                {
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_CLOSE, wFlags);
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_MAXIMIZE, wFlags);
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_MINIMIZE, wFlags);
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_SIZE, wFlags);
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_MOVE, wFlags);
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_RESTORE, wFlags);

                    DrawMenuBar(frm.Handle);
                }
            }
        }

Es wird dann immer nur der "Schließen" Eintrag ausgegraut (disabled).
warum kann man die anderen Einträge nicht deaktivieren?

ich glaube, ich mach es bei mir nun vorerst mit ControlBox = true / false.... 😁

915 Beiträge seit 2006
vor 15 Jahren

Für das Deaktivieren bzw ausgrauen: MF_BYCOMMAND | MF_DISABLED| MF_GRAYED verwenden. Warum schließen überhaupt ohne MF_GRAYED nur Disabled wird ist mir grade nen Rätsel😉

/PS DrawMenuBar(frm.Handle); brauchst eigentlich nur nach nen DELETE eines Menüeintrags - schaden sollte es aber sonst auch nicht.

Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(

S
steffen_dec Themenstarter:in
322 Beiträge seit 2007
vor 15 Jahren

Hast du es getestet? irgendwie tut es bei mir nicht 🙁


public static void EnableSystemMenuItems(Form frm, bool Enable)
        {
            IntPtr hMenu;
            int n;
            uint wFlags;

            if (!Enable)
                wFlags = MF_BYCOMMAND | MF_DISABLED | MF_GRAYED;
            else
                wFlags = MF_BYCOMMAND | MF_ENABLED;
    
            hMenu = GetSystemMenu(frm.Handle, false);
            if (hMenu != IntPtr.Zero)
            {
                n = GetMenuItemCount(hMenu);
                if (n > 0)
                {
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_CLOSE, wFlags);
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_MAXIMIZE, wFlags);
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_MINIMIZE, wFlags);
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_SIZE, wFlags);
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_MOVE, wFlags);
                    EnableMenuItem(hMenu, (uint)SysCommands.SC_RESTORE, wFlags);

                    DrawMenuBar(frm.Handle);
                }
            }
        }

915 Beiträge seit 2006
vor 15 Jahren

Hrhr, dein code stimmt nur der Aufruf nicht .-)


		protected override void WndProc(ref Message m)
		{
			base.WndProc( ref m);
	

			EnableSystemMenuItems(this, false);
		}

Warum muss das so sein? - Ich kenne nicht die verantwortlichen WindowsNachrichten in welchen das dynamische verhallten der jeweiligen Menüeinträge gesetzt wird.

Sprich, wenn du nur nen Aufruf im Konstruktor z.B. hattest folgte spätestens beim Handle Create nen Add MenüItem für z.b. den Menüeintrag MinimizedBox mit der Eigenschaft Enabled = true usw..eine änderung deiner gesetzten Werte und das passiert bei irgendwelchen WindowsNachrichten.

Wie gesagt in welchen WindowsMessages das überall aufgerufen wird weis ich nicht, aber es werden schon einige sein 🙂

Wie vernichtet stand Andreas unter den flammenden Augen seiner Kunden.
Ihm war's, als stünde des Schicksals dunkle Wetterwolke über seinem Haupte X(

S
steffen_dec Themenstarter:in
322 Beiträge seit 2007
vor 15 Jahren

Hi,

danke nun klappts endlich 🙂

Gruß
Steffen

I
279 Beiträge seit 2008
vor 14 Jahren

Hallo hatte bereits einen anderen threead eröffnet bis ich diesen gefunden hatte!

Also mein Problem: ich will das contextmenu das sich beim öffnen auf das icon der form öffnet abfangen und dessen öffnen verhindern. habs schon mit


if(NCHITTEST && 3 || NCHITTEST && 5)
{
 m.Result = new IntPtr(0x01); //geklickter beriech wird auf client gesetzt
}
base.wndproc(ref m);

Das hat jedoch nicht geklappt, hat jemand vllt. nen plan worans liegen könnte?

Danke für eure Beiträge