Laden...

[Artikel] Custom Window Border für Form's

Erstellt von egrath vor 17 Jahren Letzter Beitrag vor 15 Jahren 80.990 Views
egrath Themenstarter:in
871 Beiträge seit 2005
vor 17 Jahren
[Artikel] Custom Window Border für Form's

Eigene Window Rahmen zeichnen

Dieser Artikel beschreibt wie man in den Non-Client Bereich einer Form zeichnet um beispielsweise das aussehen des Rahmens und der Buttons so zu verändern dass diese einen eigenen Look and Feel erhalten.

Grundlegendes:

Ein Windows Fenster besteht sobald eine Titelleiste vorhanden ist immer aus zwei Bereichen:*Dem Client Bereich (Client Area) *Dem Non-Client Bereich (Non-Client Area)

Untenstehende Grafik zeigt wo sich diese Bereiche befinden:

Im Normalfall wird eine Applikation nur in die CA zeichnen, die NCA wird vom Framework (genauer gesagt eigentlich von der darunter liegenden Win32-API) gezeichnet. Um dies selbst zu übernehmen müssen wir verschiedene Windows Messages bearbeiten. So kann man beispielsweise Fenster erzeugen die folgendermassen aussehen:

Implementierung

Das Zeichnen in die NCA wird durch verschiedene Windows Messages initiiert. Diese sind:


Message  Name    	Beschreibung
WM_NCPAINT              Der NCA muss neu gezeichnet werden
WM_NCACTIVATE           Das Window wurde aktiviert/deaktiviert. Der Rahmen muss 
                        dementsprechend gezeichnet werden (bsp. Rahmen abdunkeln)
WM_ACTIVATE             s.o.
WM_NCLBUTTONDOWN        Die linke Maustaste wurde in der NCA gedrückt
WM_NCCALCSIZE           Die grösse der NCA muss neu berechnet werden da es eine Grössenänderung gab


Der erste Schritt besteht darin, statische Konstanten zu definieren welche die numerischen Windows Messages in sprechenden Namen abbilden. Anschliessend überschreiben wir die Methode "WndProc" und bearbeiten die folgenden Messages:

WM_NCPAINT:

Wir müssen den gesamten NCA neu zeichnen. Dazu erstellen wir uns eine entsprechende Methode welche sich mittels der nativen API Funktion "GetWindowDC" einen Handle auf den Drawing Context des Fensters holt und anschliessend ein Graphics Objekt für diesen erstellt. In dieses Graphics Objekt zeichnen wir anschliessend den NCA. Ebenso müssen wir alle benötigten Steuerelemente welche im NCA erscheinen sollen (in unserem Beispiel nur Minimize/Maximize und Close). zeichnen.  

WM_ACTIVATE und WM_NCACTIVATE:

Wenn diese Messages empfangen werden, so senden wir eine neue Message welche WM_NCPAINT antriggert.  

WM_NCLBUTTONDOWN:

Wir müssen überprüfen ob sich die Maus über einem der Steuerelemente befindet und entsprechend darauf reagieren. Wird auf den Close-Button geklickt so senden wir eine neue Message WM_CLOSE welche die Applikation dazu veranlasst sich zu beenden. Für Minimize und Maximize rufen wir die entsprechenden Methoden des Frameworks auf.  

WM_NCCALCSIZE:

Wir müssen die Grösse der NCA neu berechnen. Dies gestaltet sich relativ einfach, da wir als Message Parameter einen Zeiger auf eine native Struktur vom Typ NCCALCSIZE_PARAMS übergeben bekommen die wir dafür benutzen können.  

Native Calls und andere besonderheiten:

Wir benötigen zwei native Win32-API Funktionen um die Funktionalität implementieren zu können:*GetWindowDC - liefert einen Handle auf den Drawing Context des angegebenen Fensters zurück *SendMessage - eine neue Message in den Message Loop schicken

Weitere Dinge die beachtet werden müssen:*Wir müssen die nativen Icons der Titlebar deaktivieren damit diese uns nicht in die Quere kommen. Dies geschieht durch "ControlBox = false" in der initialisierung des Fensters *Die nativen Win32 Funktionen geben oftmals ein DWORD zurück welches im Low-Word und High-Word jeweils einen Parameter enthalten. Dafür müssen wir einen entsprechende Methode schreiben welche diese als einzelne Paramter zurückgibt (HighWord und LowWord im Source)

915 Beiträge seit 2006
vor 17 Jahren

Prima Beitrag 👍

Hätte mir vor einigen Wochen eine menge Arbeit erspaart 🙂


#region NCHITTEST enum
    /// <summary>
    /// Location of cursor hot spot returnet in WM_NCHITTEST.
    /// </summary>
    public enum NCHITTEST
    {
        /// <summary>
        /// On the screen background or on a dividing line between windows 
        /// (same as HTNOWHERE, except that the DefWindowProc function produces a system beep to indicate an error).
        /// </summary>
        HTERROR = (-2),
        /// <summary>
        /// In a window currently covered by another window in the same thread 
        /// (the message will be sent to underlying windows in the same thread until one of them returns a code that is not HTTRANSPARENT).
        /// </summary>
        HTTRANSPARENT = (-1),
        /// <summary>
        /// On the screen background or on a dividing line between windows.
        /// </summary>
        HTNOWHERE = 0,
        /// <summary>In a client area.</summary>
        HTCLIENT = 1,
        /// <summary>In a title bar.</summary>
        HTCAPTION = 2,
        /// <summary>In a window menu or in a Close button in a child window.</summary>
        HTSYSMENU = 3,
        /// <summary>In a size box (same as HTSIZE).</summary>
        HTGROWBOX = 4,
        /// <summary>In a menu.</summary>
        HTMENU = 5,
        /// <summary>In a horizontal scroll bar.</summary>
        HTHSCROLL = 6,
        /// <summary>In the vertical scroll bar.</summary>
        HTVSCROLL = 7,
        /// <summary>In a Minimize button.</summary>
        HTMINBUTTON = 8,
        /// <summary>In a Maximize button.</summary>
        HTMAXBUTTON = 9,
        /// <summary>In the left border of a resizable window 
        /// (the user can click the mouse to resize the window horizontally).</summary>
        HTLEFT = 10,
        /// <summary>
        /// In the right border of a resizable window 
        /// (the user can click the mouse to resize the window horizontally).
        /// </summary>
        HTRIGHT = 11,
        /// <summary>In the upper-horizontal border of a window.</summary>
        HTTOP = 12,
        /// <summary>In the upper-left corner of a window border.</summary>
        HTTOPLEFT = 13,
        /// <summary>In the upper-right corner of a window border.</summary>
        HTTOPRIGHT = 14,
        /// <summary>	In the lower-horizontal border of a resizable window 
        /// (the user can click the mouse to resize the window vertically).</summary>
        HTBOTTOM = 15,
        /// <summary>In the lower-left corner of a border of a resizable window 
        /// (the user can click the mouse to resize the window diagonally).</summary>
        HTBOTTOMLEFT = 16,
        /// <summary>	In the lower-right corner of a border of a resizable window 
        /// (the user can click the mouse to resize the window diagonally).</summary>
        HTBOTTOMRIGHT = 17,
        /// <summary>In the border of a window that does not have a sizing border.</summary>
        HTBORDER = 18,

        HTOBJECT = 19,
        /// <summary>In a Close button.</summary>
        HTCLOSE = 20,
        /// <summary>In a Help button.</summary>
        HTHELP = 21,
    }

Was man noch ergänzen könnte ist das man beim eigenen gezeichneten "Icon" noch NCHITTEST.HTMENU über SendMessage mitgeben kann um die Systemmenü Eigenschaften zu realisieren.

Wer noch das Systemmenu etwas erweitern möchte kann das hiermit:

Klasse:


public class CSystemMenu
    {

        [DllImport("USER32", EntryPoint = "GetSystemMenu", SetLastError = true,
                   CharSet = CharSet.Unicode, ExactSpelling = true,
                   CallingConvention = CallingConvention.Winapi)]
        private static extern IntPtr apiGetSystemMenu(IntPtr WindowHandle,
                                                      int bReset);

        [DllImport("USER32", EntryPoint = "AppendMenuW", SetLastError = true,
                   CharSet = CharSet.Unicode, ExactSpelling = true,
                   CallingConvention = CallingConvention.Winapi)]
        private static extern int apiAppendMenu(IntPtr MenuHandle, int Flags,
                                                 int NewID, String Item);

        [DllImport("USER32", EntryPoint = "InsertMenuW", SetLastError = true,
                   CharSet = CharSet.Unicode, ExactSpelling = true,
                   CallingConvention = CallingConvention.Winapi)]
        private static extern int apiInsertMenu(IntPtr hMenu, int Position,
                                                  int Flags, int NewId,
                                                  String Item);

        // Remove menu
        [DllImport("user32.dll")]
        private static extern bool RemoveMenu(IntPtr hMenu, int uPosition, int uFlags);

        public CSystemMenu()
        {
        }

    

        private IntPtr m_SysMenu = IntPtr.Zero;    // Handle to the System Menu

        public CSystemMenu()
        {
        }

        /// <summary>
        /// Remove an menu entry
        /// </summary>
        /// <param name="Pos"></param>
        /// <param name="Flags"></param>
        /// <returns></returns>
        public bool RemoveMenu(int Pos, ItemFlags Flag)
        {
            return RemoveMenu(m_SysMenu, Pos, (int) Flag);
        }
    
        public bool InsertSeparator(int Pos)
        {
            return (InsertMenu(Pos, ItemFlags.mfSeparator |
                                ItemFlags.mfByPosition, 0, ""));
        }


        public bool InsertMenu(int Pos, int ID, String Item)
        {
            return (InsertMenu(Pos, ItemFlags.mfByPosition |
                                ItemFlags.mfString, ID, Item));
        }

        public bool InsertMenu(int Pos, ItemFlags Flags, int ID, String Item)
        {
            return (apiInsertMenu(m_SysMenu, Pos, (Int32)Flags, ID, Item) == 0);
        }

  
        public bool AppendSeparator()
        {
            return AppendMenu(0, "", ItemFlags.mfSeparator);
        }

     
        public bool AppendMenu(int ID, String Item)
        {
            return AppendMenu(ID, Item, ItemFlags.mfString);
        }
   
        public bool AppendMenu(int ID, String Item, ItemFlags Flags)
        {
            return (apiAppendMenu(m_SysMenu, (int)Flags, ID, Item) == 0);
        }

   
        public static CSystemMenu FromForm(Form Frm)
        {
            CSystemMenu cSysMenu = new CSystemMenu();

            cSysMenu.m_SysMenu = apiGetSystemMenu(Frm.Handle, 0);
            if (cSysMenu.m_SysMenu == IntPtr.Zero)
            { // Throw an exception on failure
                throw new NoSystemMenuException();
            }

            return cSysMenu.;
        }

        public static void ResetSystemMenu(Form Frm)
        {
            apiGetSystemMenu(Frm.Handle, 1);
        }

        public static bool VerifyItemID(int ID)
        {
            return (bool)(ID < 0xF000 && ID > 0);
        }
    }

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

915 Beiträge seit 2006
vor 17 Jahren

Das einzige was man dazu ergänzen muss, passt gut auf bei MDI Childs dort muss man etwas verändern bei PointToScreen.

Was in deinem Code noch fehlt, ist das verhinden von Windows Themes welches man mit folgenden Codeschnipsel verhindert.


        [DllImport("uxtheme.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
        public static extern int SetWindowTheme(IntPtr hwnd, String pszSubAppName,
                                         String pszSubIdList);

Einfach beim entsprechenden Formular die OnHandleCreated Methode überschreiben bzw. ergänzen um die Windows Themes zu verhinden.


		protected override void OnHandleCreated(EventArgs e)
		{
			SetWindowTheme(this.Handle, "", "");
			base.OnHandleCreated(e);
		}

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

915 Beiträge seit 2006
vor 17 Jahren

Hier mal ein paar Bilder dazu.

Hier erkennt man das es sich um keinen typischen WindowsBorder handelt und auch die Breite nicht dem eines WindowsBorder entspricht.

Und hier das gleiche als Überblick:

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

egrath Themenstarter:in
871 Beiträge seit 2005
vor 17 Jahren

Hallo Andreas.May,

Danke für die Ergänzung des Artikels!

Grüsse, Egon

A
3 Beiträge seit 2006
vor 17 Jahren

Hallo,
ausgehend von borderoverride.zip möchte ich dieses Fenster als MDI Container laufen lassen und darin Fenster dieses Typs nutzen. Wie würde das gehen? Bei mir bricht da immer Visual Studio ab:

private void MainWindow_Load(object sender, EventArgs e)
        {
            
            Form2 oForm = new Form2();
            oForm.MdiParent = this;
            oForm.Show();
        }

.

Form2 ist ein vom MainWindow geerbtes Windows Form und die Eigenschaft isMdiContainer wurde bei MainWindow auf true und bei oForm auf false gesetzt.

Beim Maximieren und Minimieren erscheint bei mir in Win XP prof. in einer Animation dieses Fenster mit der ursprünglichen blauen Leiste und hat danach erst wieder die dunkelgraue Farbe. Wie kann ich diese Animation abschalten oder dafür sorgen, daß auch während dieses Vorgangs die Leiste in der richtigen Farbe erscheint?

Alles Gute!
Andreas

1.985 Beiträge seit 2004
vor 17 Jahren

Hallo egrath,

und mal wieder ein sehr guter Artikel von Dir!

Gruß,
Fabian

"Eine wirklich gute Idee erkennt man daran, dass ihre Verwirklichung von vornherein ausgeschlossen erscheint." (Albert Einstein)

Gefangen im magischen Viereck zwischen studieren, schreiben, lehren und Ideen umsetzen…

Blog: www.fabiandeitelhoff.de

915 Beiträge seit 2006
vor 17 Jahren

Hallo Andreas,
denk dran Windows Themes zu verhindern, der Code dazu steht etwas weiter oben 🙂

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

2.921 Beiträge seit 2005
vor 16 Jahren

Schön, dass hier von Andreas mein Fenster gepostet wurde, lol.

Dann ist es ja auch nicht schlecht hier den Code dazu zu posten:

EDIT: Achtung, dazu muss in MDI-Windows noch etwas Code für das Bewegen dieser Art von Fenstern programmiert werden...

Es empfiehlt sich auch eigene Forms von LonghornForm abzuleiten und dann meine SkinLib zu benutzen.

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

D
386 Beiträge seit 2007
vor 16 Jahren

Mahlzeit.

Ich find das Thema hochinteressant, hab aber mit beiden hier geposteten Beispielen noch ein Problem:


[EDIT=herbivore]Die Bilder leider nicht mehr verfügbar. Daher Bilder statt sie über [****IMG] einzubinden bitte immer als Dateianhang hochladen. Vielen Dank![/EDIT]

Warum erscheinen dort dann doch manchmal die Originalbuttons von Windows?
Beim ersten Beispiel ist das trivial zu demonstrieren, es passiert bei mir immer wenn ich zum ersten Mal mit der Maus ueber diese Region fahre.
Beim zweiten Beispiel ist es schwieriger zu triggern, scheint erst nach Aktivierung/Deaktivierung von Buttons (Beispiel: Maximieren) zu passieren, ebenfalls dann sobald man mit der Maus ueber diesen Bereich faehrt.

Ideen, wieso das passiert und wie man das unterdrueckt?

Pound for pound, plutonium is about as toxic as caffeine when eaten.

2.921 Beiträge seit 2005
vor 16 Jahren

Das passiert, weil in gewisser Weise doch nur darüber gezeichnet wird, aber nur für den Fall das bestimmte Windows-Nachrichten nicht korrekt über die API abgefangen werden, d.h. ganz einfach mein Beispiel wird ja dann wohl noch Fehler enthalten.

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

K
165 Beiträge seit 2007
vor 16 Jahren

Hallo 🙂

Das alles klingt wirklich sehr interessant, gerade deshalb weil ich mich in dieses Thema einarbeiten möchte.

Nun aber mal meine Frage:

Woher zum Teufel, wisst Ihr z.B. diese Parameter :


        private const int WM_NCPAINT = 0x0085;
        private const int WM_NCLBUTTONDOWN = 0x00A1;
        private const int WM_NCLBUTTONUP = 0x00A2;
        private const int WM_NCHITTEST = 0x0084;
        private const int WM_NCCALCSIZE = 0x0083;
        private const int WM_NCACTIVATE = 0x0086;
        private const int WM_CLOSE = 0x0010;

Gibt es irgendwo in den weiten unseres Netzes, gute (kann auch englisch sein) Tutorials die einem diese Art der Programmierung näher bringt?

Gruß
Kalleberlin

If u want to finish first, u have to finish first.
738 Beiträge seit 2007
vor 16 Jahren
egrath Themenstarter:in
871 Beiträge seit 2005
vor 16 Jahren

Original von Kalleberlin
Woher zum Teufel, wisst Ihr z.B. diese Parameter :

  
        private const int WM_NCPAINT = 0x0085;  
        private const int WM_NCLBUTTONDOWN = 0x00A1;  
        private const int WM_NCLBUTTONUP = 0x00A2;  
        private const int WM_NCHITTEST = 0x0084;  
        private const int WM_NCCALCSIZE = 0x0083;  
        private const int WM_NCACTIVATE = 0x0086;  
        private const int WM_CLOSE = 0x0010;  
  

Die meisten dieser Konstanten kann man im Normalfall nachlesen indem man zuerst die Win32 API Dokumentation (MSDN) nachliest und sich anschliessend die Werte der Konstanten aus den entsprechenden C/C++ Header Dateien des Platform SDK's raussucht (wird beides installiert wenn man eine voll Visual Studio installation hat)

Grüsse,
Egon

C
7 Beiträge seit 2007
vor 16 Jahren

Erstmal möchte ich mich für dieses Artikel wirklich bedanken! Der hat mich sehr viel weitergebracht.

Allerding scheint der Code für Windows Vista nicht mehr richtig zu funktionieren. Als ich das Projekt zuerst Debugged habe, fiel mir auf, dass zwischen der Titlebar und dem Client-Bereich ein Teil war, wo nichts gezeichnet war. Der Grund war "borderWidth" welches zu klein berechnet wurde.
Dann fiel mir aber ein, dass ich ja den Aero-Style (Theme) aktiviert habe und baute das Snippet von Andreas ein, jetzt wird alles schön gezeichnet.
Jedoch, wenn ich das Fenster verkleiner/vergrößer treten unschöne Fehler auf (Bildfetzen von darunterliegenden Fenstern werden in dieses gezeichnet), und wenn ich das Fenster maximiere, wird das gesamte Layout des Fensters zerstört, dass heißt die Buttons werden, wenn überhaupt, irgendwo gezeichnet, und die Titlebar überlappt sich mit dem Client-Bereich.

Wisst ihr an was das vielleicht liegen könnte? Oder hat sich in Vista in diesem Bereich so viel geändert, dass man da alles neu schreiben müsste?
Mein Problem ist nur, dass ich zurzeit keinen Rechner mit XP zur Verfügung habe, mit welchem ich das ganze paralell testen könnte.

lg
CGE

PS: Wenn Screenshots von den Fehlern erwünscht sind, kann ich gern welche posten!

49.485 Beiträge seit 2005
vor 16 Jahren

Hallo CGE500,

wenn jemand eine definitive Antwort weiß, ist es ja gut, aber bitte in einem Artikel-Thread nur Fragen und Hinweise zum Artikel; keine Diskussionen und keine Problemlösungsversuche zu Problemen in eigenen Anwendungen. Vor allem keine langen Diskussionen. Eine Frage, eine Antwort, mehr nicht. Mach bei Bedarf einen eigenen Thread auf.

Bitte auch diesen Hinweis nicht diskutieren, sondern hinnehmen und beachten. Vielen Dank!

herbivore

T
3 Beiträge seit 2007
vor 16 Jahren

Erstmal vielen dank für den schönen Artikel 😉

Woher zum Teufel, wisst Ihr z.B. diese Parameter :C#-Code:
private const int WM_NCPAINT = 0x0085;
private const int WM_NCLBUTTONDOWN = 0x00A1;
private const int WM_NCLBUTTONUP = 0x00A2;
private const int WM_NCHITTEST = 0x0084;
private const int WM_NCCALCSIZE = 0x0083;
private const int WM_NCACTIVATE = 0x0086;
private const int WM_CLOSE = 0x0010;

Das liegt daran, dass viele vermutlich vor ihrer C# Zeit wie ich C++ oder so programmiert haben und teilweise wohl immer noch programmieren und dort Kontakt zur Win32API hatten/haben.

Programmieren ist keine Tätigkeit. Es ist eine Lebenseinstellung!

915 Beiträge seit 2006
vor 16 Jahren

Hier eine Lösung um das Flackern zu unterbinden:


private void PaintNonClientArea(IntPtr hWnd, IntPtr hRgn)
        {
            CWin32.RECT rWnd = new CWin32.RECT();
            if (CWin32.GetWindowRect(hWnd, ref rWnd) == 0)
                return;

            Rectangle rClip = new Rectangle(0, 0, rWnd.Width, rWnd.Height);
            IntPtr hDC = CWin32.GetWindowDC(hWnd);

            IntPtr hCDC = CWin32.CreateCompatibleDC(hDC);
            IntPtr hBitmap = CWin32.CreateCompatibleBitmap(hDC, rClip.Width, rClip.Height);
            CWin32.SelectObject(hCDC, hBitmap);
            CWin32.BitBlt(hCDC, 0, 0, rClip.Width, rClip.Height, hDC, 0, 0, (uint)CWin32.TernaryRasterOperations.SRCCOPY);

            using (Graphics g = Graphics.FromHdc(hCDC))
            {
                this.OnNonClientAreaPaint(new PaintEventArgs(g, rClip));
            }

            CWin32.BitBlt(hDC, 0, 0, rClip.Width, rClip.Height, hCDC, 0, 0, (uint)CWin32.TernaryRasterOperations.SRCCOPY);
            CWin32.DeleteObject(hBitmap);
            CWin32.DeleteDC(hCDC);
            CWin32.ReleaseDC(hWnd, hDC);
        }

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

X
38 Beiträge seit 2008
vor 15 Jahren

Also eigentlich kann man das doch auch über eine etwas kürzere variante machen nämlich man setzt als BAckground image einen Rahmen den man lebst editiert hat
sollte mit Möglichkeit eine bessere auflösung haben und gibt Stretch an für das Background image verhalten an dann setzt man ein Panel zur sicherheit damit man auch schön beim klicken auf den oberen rand eine Dropdown menu bekommt wie beim normalen NCA setzt darauf dann Pictureboxen die dann beim Enter-/LeaveEvent und beim klicken eine andere Optik annehemen und schreibt dann noch im code was beim klicken auf die Pictureboxen passieren soll
an den seiten und unten macht man dann auch ein panel auf der sichtbaren Fläche des Rahmens und legt dann noch bei den rändern fest das die zieh und Schiebe Mouse kommt und dass die ganze sache sich auf ihren Locations bzw Sizes ändert jenbach schub oder ZUg

Das wars. 😜 🙂 =) 8o 😁 :evil:

ein Code ist nur so lange Spaghetti wie du keine Ahnung von ihm hast

915 Beiträge seit 2006
vor 15 Jahren

Hier noch eine kleine ergänzung:

Die Buttonpossitionen wie Close / Minimize / Maximize / Help müssen nicht unbedingt ausgerechnet werden sollte man diese beibehalten wollen. Hier reicht ein NCHITTEST.HTMENU als rückgabewert.

Und hier noch ein Link zum SystemMenü.

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

915 Beiträge seit 2006
vor 15 Jahren

[Noch nicht getestet daher pure Theorie]

Ein weiterer Tipp fürs "Globale" Design auf mehrere Controls wie ComboBoxen, ListViews, Forms usw.

Wenn man vor hat ein Skinning Tool für alle Controls zu schreiben, so kann man eine Klasse erstellen, diese von System.ComponentModel.Component ableitenund das Interface System.ComponentModel.IExtenderProvider einbinden (siehe hierzu die deutsche MSDN, ist etwas besser), Anhand der IExtenderProvider.CanExtend kann man die verschiedenen Typen unterteilen.
Anhand des Attributes [System.ComponentModel.ProvideProperty(„SubClassing“), typeof(bool))] kann man eine Set und Get Methode einbinden in dem Fall wäre das hier SetSubClassing(Control control, bool value); und GetSubclassing(Control control);. Anhand des boolischen Wertes kann man nun festlegen ob ein Subclassign vorgenommen werden soll oder nicht. Das Subclassing kann man einfach über einbinden des Interface System.Windows.Forms.IMessageFilter erstellen und via Application.AddMessageFilter(new SkiningClass()); realisieren. Bei eingehenden Nachrichten filtert man dann diese anhand des Control.Handel und beachtet dann nur die üblichen wichtigen Nachrichten (siehe dazu vorherige Posts). Um nun die Skins den jeweiligen Controls zuziweisen macht es Sinn einen Assembly Loader zu schreiben in der die jeweiligen resourcen für die verschiedenen Controls bereitgestellt werden. Anhand eines Tools könnte man diese Assemblys erstellen lassen (SkiningTool) und diese einbinden.

Kostet zwar etwas arbeit aber man könnte somit auf Fremdanbieter-Programme verzichten und seine eigene Library aufbauen mit modernen Look and Feel für Windows Komponenten.

[Edit] Vergessen zu erklären warum IMessageFilter 😉
Da man ja vorhat ein "globales" Skinning Tool zu haben das alle Cntrols den selben Style besitzen. Ist es natürlich vorteilhaft wenn alle Controls sich zur Laufzeit dem gewählten Style anpassen. Somit müssen alle Nachrichten gleichzeitig verarbeitet werden. Da kommt die Application ins spiel und die damit verbundenen globalen Windows Nachrichten.

Natürlich sollten unterschiedliche GUI Threads exestieren (warum auch immer) würden diese voneinander im Style untershciedlich sein. Sollte man unterschiedliche Styles für jedes einzelne Control wünschen so kann man das Klassische SubClassing verwenden.

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
238 Beiträge seit 2004
vor 15 Jahren

Wow, toller Artikel! 👍 Bin ich jetzt gerade erst drauf gestoßen (leider nach einer Menge Arbeit...na ja, besser spät als nie).

Eine Ergänzung und eine Frage hab ich aber, hoffe, das sprengt den Rahmen nicht...

Ergänzung: Maximieren

Wenn man in borderoverride.zip (von egrath) doppelt auf die Titelleiste klickt, passiert - anders als normalerweise - nichts. Könnte mir vorstellen, dass das den ein oder anderen stört und möchte da mal meinen Senf dazu abgeben...

Das Verhalten kann man ganz leicht realiseren, indem man der Methode WndProc(ref Message) den folgenden case-Block hinzufügt:

case WM_NCLBUTTONDBLCLK:
    this.WindowState = this.WindowState.Equals(FormWindowState.Maximized) ? FormWindowState.Normal : FormWindowState.Maximized;
    break;

Die neue Konstante muss man natürlich noch angeben:

private const int WM_NCLBUTTONDBLCLK = 0x00A3;

So, nun zu meiner Frage.

Wenn die Form maximiert ist, kann man sie immer noch verschieben. Das widerspricht IMHO dem Verhalten einer "normalen" Windows-Form. Muss man dieses Verhalten einfach nachbauen (nach dem Motto "wenn maximiert, dann nicht bewegbar") oder gibt es da einen nativeren bzw. eleganteren Weg über die API?

Würde mich wirklich interessieren, da ich momentan (nebenbei) eine Form alá Office 2007 bastele und dazu diese Techniken benutzen möchte.

Greets - SK

Sagte ich schon Danke? Nein? ...kommt noch...

K
165 Beiträge seit 2007
vor 15 Jahren

Gerade eben beim Surfe 😉


[DllImport("user32.dll")]
private static extern Int32 EnableMenuItem ( System.IntPtr hMenu , Int32 uIDEnableItem, Int32 uEnable);
private const Int32 HTCAPTION = 0×00000002;
private const Int32 MF_BYCOMMAND =0×00000000;
private const Int32 MF_ENABLED =0×00000000;
private const Int32 MF_GRAYED =0×00000001;
private const Int32 MF_DISABLED =0×00000002;
private const Int32 SC_MOVE = 0xF010;
private const Int32 WM_NCLBUTTONDOWN = 0xA1;
private const Int32 WM_SYSCOMMAND = 0×112;
private const Int32 WM_INITMENUPOPUP = 0×117;

protected override void WndProc(ref System.Windows.Forms.Message m )
{
if( m.Msg == WM_INITMENUPOPUP )
{
//handles popup of system menu
if ((m.LParam.ToInt32() / 65536) != 0 ) // 'divide by 65536 to get hiword
{
Int32 AbleFlags = MF_ENABLED;
if (!Moveable)
{
AbleFlags = MF_DISABLED | MF_GRAYED; // disable the move
}
EnableMenuItem(m.WParam, SC_MOVE, MF_BYCOMMAND | AbleFlags);
}
}if(!Moveable)
{
if(m.Msg==WM_NCLBUTTONDOWN) //cancels the drag this is IMP
{if(m.WParam.ToInt32()==HTCAPTION) return;
}
if (m.Msg==WM_SYSCOMMAND) // Cancels any clicks on move menu
{
if ((m.WParam.ToInt32() & 0xFFF0) == SC_MOVE) return;
}
}
base.WndProc(ref m);
}

If u want to finish first, u have to finish first.