Click here to Skip to main content
15,885,278 members
Articles / Desktop Programming / Windows Forms

Winforms SkinFramework

Rate me:
Please Sign up or sign in to vote.
4.95/5 (54 votes)
18 Sep 2013LGPL38 min read 234K   14.8K   241   49
Give your WinForms Windows a unique look using a unique design
SkinFramework/office07luna.png

Introduction 

I do not maintain this project anymore. The code is available on Github. Feel ree to fork and extend it: https://github.com/Danielku15/SkinFramework  

The CoderLine SkinFramework allows you to add custom Form Borders to your WinForms .NET applications. Didn't you ever want to give your Windows a unique appearance like Microsoft does in their Office Suite (since 2007)?

Development Process

To get the expected result, I had to do much reading and “trial and error” work. The main reading sources are listed below:

The first result of my SkinFramework which I finished as a school project in 2008 was quite unusable. It had been very slow and didn't render well if I maximized my window. So the project suspended a long time till a few days ago when a developer contacted me and asked about the project state and I got interested in this project again. Meanwhile I have developed on some GUI controls and also used some third party libraries. There were some libraries which also provide skinning functionalities. The main idea I copied from those libraries is not to derive from a special Form class to enable skinning. In my library, I want to provide a component which can be added to a form which manages the whole skinning.

The result of a few days developing is a ~2700 code line small library which allows creating custom form borders alias Skins.

What is the Main Idea to Enable Skinning?

A form can be split up into two sections, the client area and the non-client area. Simplified the client area is the section where you place your controls and the non-client area is the window borders, the caption buttons (minimize, maximize/restore, close), the icon including the system menu and the caption text:

SkinFramework/nonclientarea.png

The .NET 2.0 Framework doesn’t provide the functionality to draw into the non-client area nor place controls in it. We have to “hook” into the Windows Message loop and override the native implementations of all relevant messages. We can do this via overriding the Form’s WndProc Method and a lot of native Win32 calls.

C#
/// <summary>
/// Processes Windows messages.
/// </summary>
/// <param name="m">The Windows <see cref="T:System.Windows.Forms.Message"/> 
/// to process.</param>
protected override void WndProc(ref Message m)

There is a huge list of Windows Messages (http://www.pinvoke.net/default.aspx/Constants.WM), but which ones are relevant for skinning? We can put our messages into 5 main sections:

General

  • WM_STYLECHANGED
    When the user changes the windows style via the control panel, we have to update some stuff and redraw our window.

Handle Form Activation

We have to determine whether our form is currently active and focused or not. Depending on this value, we have to draw an inactive border. Those messages are needed to catch the form activation:

  • WM_ACTIVATEAPP
  • WM_ACTIVATE
  • WM_MDIACTIVATE

Handle Mouse Processing

We have to handle some mouse events to determine whether the cursor is above any caption button:

  • WM_NCLBUTTONDOWN
    With this message, we can determine the button which is currently pressed.
  • WM_NCMOUSEMOVE
    With this message, we can determine the button which is currently hovered.
  • WM_NCLBUTTONUP
    As we handle the WM_NCLBUTTONDOWN to determine the pressed state, we need to handle this message to recognize if the button got released.
  • WM_NCMOUSELEAVE, WM_MOUSELEAVE, WM_MOUSEHOVER These three messages are needed to catch when the mouse leaves the non-client area so that we can reset all hover states.

Handle Form Sizing

To catch these events is very important. We have to update a lot of stuff as our form gets resized, minimized, maximized, etc.

  • WM_SHOWWINDOW
    As we show our window, we have to update the region of our form (to provide a custom form shape as round corners)
  • WM_SIZE
    After the size of a form has changed, we have to update our form region too, especially if we switch from a maximized state to the normal one.
  • WM_GETMINMAXINFO
    This little message allows us to determine the bounds of our form if it gets maximized. Without processing this message, our form would overlap the taskbar if it gets maximized.
  • WM_WINDOWPOSCHANGING, WM_WINDOWPOSCHANGED Two more messages to handle our form resizing.

Handle Non Client Processing

Overriding the following messages allows us to process special non-client actions.

  • WM_NCPAINT
    What could it be? Yes, using the parameters of this message, we can get a Graphics Context to paint our non-client area. Without overriding this message, we would only get some default painted windows.
  • WM_NCCALCSIZE
    Using this message, we can determine how big our non-client area is. As you know, we have different non-client area sizes on different FormBorderStyles.
  • WM_NCHITTEST
    This message determines where the cursor is currently positioned in the non-client area. If we set special result values, we enable window features as form-resizing, window moving, opening system menu…

Each message has special LPARAM and WPARAM values which contain important data to process the message (Cursor position, pointer to the graphics context...). Contact the MSDN to find out which message parameters contain which data.

IMPORTANT: I hope you recognized that the Message type is a struct and not a class. You have to pass it by reference (ref parameter) to other methods if you want to change result values.

The Native Window Class

The MSDN describes this class as follows:

"Provides a low-level encapsulation of a window handle and a window procedure." (http://msdn.microsoft.com/en-us/library/system.windows.forms.nativewindow.aspx - 23.02.2010)

But what can this little helper do for us? As described in the upper section, we have to override the windows messages by overriding the WndProc method of the Form class. This would enforce the end-user (developer) to use our SkinForm as the base class for his windows. But as I mentioned, I want to provide a component which enables this feature. This is the point where our NativeWindow comes into play. The NativeWindow class provides an awesome method called AssignHandle.

C#
/// <summary>
/// Assigns a handle to this window.
/// </summary>
/// <param name="handle">The handle to assign to this window.</param>
public void AssignHandle(IntPtr handle)
If we assign the handle of our Form (Form.Handle) to a NativeWindow via this method, we can handle all its Windows Messages. We simply derive an own listener class from NativeWindow where we assign the handle of our Form which should get skinned. We can override the WndProc method of our listener and handle all messages for our Form in our NativeWindow. But how can this work? All windows messages are sent to a specific Handle. As we use the AssignHandle method, the NativeWindow becomes the owner of the Forms handle and receives all Messages sent to the form. So we don’t have to register hooks to catch the Windows Messages.

Here a little sample:

C#
[System.Security.Permissions.PermissionSet
	(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
public class FormMessageListener : NativeWindow
{
    private Form _parentForm;

    public FormMessageListener(Form parent)
    {
        // catch when the handle is created
        parent.HandleCreated += OnHandleCreated;
        parent.HandleDestroyed += OnHandleDestroyed;
        _parentForm = parent;
    }
    private void OnHandleCreated(object sender, EventArgs e)
    {
        // As the handle gets created, take it over
        AssignHandle(((Form)sender).Handle);
    }
    private void OnHandleDestroyed(object sender, EventArgs e)
    {
        // release the handle as needed
        ReleaseHandle();
    }

    [System.Security.Permissions.PermissionSet
	(System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")]
    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case 0x00A0: //ncmousemove
                // Print the Cursor position (in screen coordinates)
                Point screenPoint = new Point(m.LParam.ToInt32());
                Debug.WriteLine(screenPoint);
                break;
        }
        base.WndProc(ref m);
    }
}

Quite useful, eh?

To enable our skinning, we have to implement actions for all mentioned Windows messages – DONE. Include some wrappers and dynamic skin loading and our framework is done. This is why this library only needs about 2700 lines of code.

Class Diagram

SkinFramework/classdiagram.png

Note: No relations are displayed in this class diagram.

Useful Painting Helpers

Painting Definitions

As we provide an interface for creating new skins, we shouldn’t provide the access to internal classes and properties too. So we create a wrapper class containing all data needed for painting the controls.

For example, the painting definition for our form contains the following data:

  • The Graphics to paint into
  • The Bounds to paint into
  • A list of painting definitions for our caption button
  • The icon size
  • The caption height
  • Is the form active?
  • Has the form a system menu /icon
  • The caption text

This prevents the skin developer from doing nasty stuff using internal properties.

ImageStrip

I got this idea from Axialis Software: http://www.axialis.com/tutorials/image-strip.html (23.02.2010). This library uses image strips for skins. For example, the following image is used to draw the caption of an Office2007 Luna skin.

SkinFramework/officeblue.png(150% Zoom)

You can read more about the ImageStrip class on my blog: http://www.coderline.net/desktopentwicklung/imagestrip-zeichnen/ (Currently in German only, sorry).

Control Paint Helper

As we read in the previous section, ImageStrips are used for drawing the Images itself. But now we need a class which handles the stretching of those images into target bounds. If we think of a button using an image as background source, we have to divide our source image into a 3x3 a matrix: 4 Corners (no stretching), 4 Sides (for horizontal and vertical stretching) and a content area.

SkinFramework/matrix.png

The ControlPaintHelper class manages all this dividing and drawing stuff. During creation, we specify the ImageStrip (the size of a single image, and the image itself) and the padding of the lines which divide our image into a 3x3 matrix. A little illustration how the paint helper works:

SkinFramework/matrix2.png

How To Use

Currently three default Skins are available:

SkinFramework/office07luna.pngOffice 2007 Luna Blue
SkinFramework/office07silver.pngOffice 2007 Silver
SkinFramework/office07obsidian.pngOffice 2007 Obsidian Black

To enable the skinning in your application, do these three simple steps:

  1. Add an instance of the SkinningManager component to your Form
  2. Set the ParentForm property to the owner Form
  3. Set the DefaultSkin property to load any default skin, or load any custom Skin using the SkinningManager.LoadSkin(SkinBase) method

What's Up Next?

The next step is to implement a skin which can load MsStyle documents. This will allow developers to use thousands of existing skins for their application.

Good resources for this task are:

Probably I will try to support alpha blending transparency. Resources for further reading:

The last part is to provide a skin builder application which allows creating new skins in a friendly user interface. I’m not sure if I will implement such an app.

You're all invited to improve and extend this framework.

History

  • 25th February, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Austria Austria
I'm Daniel from Austria.
I started Webdeveloping in 2001 with HTML/JS. 2004 I also started to develop PHP. Nowadays I'm into developing Web-Applications in PHP and Java(2007), GUI Controls in C#(since 2006).

Comments and Discussions

 
GeneralMy vote of 5 Pin
Bill SerGio, The Infomercial King4-May-11 20:31
Bill SerGio, The Infomercial King4-May-11 20:31 
GeneralI voted at excelent! Congratulations! Pin
TheRomanian18-Apr-11 4:20
TheRomanian18-Apr-11 4:20 
GeneralMy vote of 5 Pin
tgis.top23-Jan-11 18:14
tgis.top23-Jan-11 18:14 
GeneralAwesome job Pin
Member 394263120-Jan-11 5:58
Member 394263120-Jan-11 5:58 
GeneralSkinFramework Pin
danmor49819-Apr-10 16:39
danmor49819-Apr-10 16:39 
GeneralRe: SkinFramework Pin
sarthakganguly5-Aug-10 0:54
sarthakganguly5-Aug-10 0:54 
GeneralMDI child windows work very badly Pin
mikeduglas4-Mar-10 18:33
mikeduglas4-Mar-10 18:33 
SuggestionRe: MDI child windows work very badly [MDI Solved] Pin
comedgaji16-Sep-13 14:58
professionalcomedgaji16-Sep-13 14:58 
SkinningForm.cs File

C#
// This file is part of CoderLine SkinFramework.
//
// CoderLine SkinFramework is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// CoderLine SkinFramework is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with CoderLine SkinFramework.  If not, see <http://www.gnu.org/licenses/>.
//
// (C) 2010 Daniel Kuschny, (http://www.coderline.net)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Windows.Forms;
using FKHIS.ZZZ.Controls.SkinFramework.Painting;
using FKHIS.ZZZ.Controls.SkinFramework.Win32;
using System.Diagnostics;

namespace FKHIS.ZZZ.Controls.SkinFramework
{
    /// <summary>
    /// This NativeWindow implementation processes the window messages
    /// of a given Form to enable the skinning.
    /// </summary>
    public class SkinningForm : NativeWindow
    {
        #region Fields

        // form data
        private Form _parentForm;
        private readonly List<CaptionButton> _captionButtons;
        private bool _formIsActive;

        // graphics data
        private readonly BufferedGraphicsContext _bufferContext;
        private BufferedGraphics _bufferGraphics;
        private Size _currentCacheSize;

        // used for state resetting 
        private CaptionButton _pressedButton;
        private CaptionButton _hoveredButton;

        private SkinningManager _manager;


        #endregion

        #region Properties

        /// <summary>
        /// Gets a value indicating whether whe should process the nc area.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if we should process the nc area; otherwise, <c>false</c>.
        /// </value>
        private bool IsProcessNcArea
        {
            get
            {
                // check if we should process the nc area
                //return !(_parentForm == null || _parentForm.MdiParent != null && _parentForm.WindowState == FormWindowState.Maximized);
                return !(_parentForm == null);
            }
        }

        #endregion

        #region Constructor and Destructor
        /// <summary>
        /// Initializes a new instance of the <see cref="SkinningForm"/> class.
        /// </summary>
        /// <param name="parentForm">The parent form.</param>
        /// <param name="manager">The manager.</param>
        public SkinningForm(Form parentForm, SkinningManager manager)
        {
            _manager = manager;
            _parentForm = parentForm;
            _captionButtons = new List<CaptionButton>();

            _bufferContext = BufferedGraphicsManager.Current;
            _bufferGraphics = null;

            if (parentForm.Handle != IntPtr.Zero)
                OnHandleCreated(parentForm, EventArgs.Empty);

            RegisterEventHandlers();
        }

        /// <summary>
        /// Releases unmanaged resources and performs other cleanup operations before the
        /// <see cref="SkinningForm"/> is reclaimed by garbage collection.
        /// </summary>
        ~SkinningForm()
        {
            UnregisterEventHandlers();
        }
        #endregion

        #region Parent Form Handlers

        /// <summary>
        /// Registers all important eventhandlers.
        /// </summary>
        private void RegisterEventHandlers()
        {
            _parentForm.HandleCreated += OnHandleCreated;
            _parentForm.HandleDestroyed += OnHandleDestroyed;
            _parentForm.TextChanged += OnTextChanged;
            _parentForm.Disposed += OnParentDisposed;
        }

        /// <summary>
        /// Unregisters all important eventhandlers.
        /// </summary>
        private void UnregisterEventHandlers()
        {
            _parentForm.HandleCreated -= OnHandleCreated;
            _parentForm.HandleDestroyed -= OnHandleDestroyed;
            _parentForm.TextChanged -= OnTextChanged;
            _parentForm.Disposed -= OnParentDisposed;
        }

        /// <summary>
        /// Called when the handle of the parent form is created.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnHandleCreated(object sender, EventArgs e)
        {
            // this little line allows us to handle the windowMessages of the parent form in this class
            AssignHandle(((Form)sender).Handle);
            if (IsProcessNcArea)
            {
                UpdateStyle();
                UpdateCaption();
            }
        }

        /// <summary>
        /// Called when the handle of the parent form is destroyed
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnHandleDestroyed(object sender, EventArgs e)
        {
            // release handle as it is destroyed
            ReleaseHandle();
        }

        /// <summary>
        /// Called when the parent of the parent form is disposed
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnParentDisposed(object sender, EventArgs e)
        {
            // unregister events as the parent of the form is disposed
            if (_parentForm != null)
                UnregisterEventHandlers();
            _parentForm = null;
        }

        /// <summary>
        /// Called when the text on the parent form has changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnTextChanged(object sender, EventArgs e)
        {
            // Redraw on title change
            if (IsProcessNcArea)
                NcPaint(true);
        }

        #endregion

        #region Skinning Executors

        /// <summary>
        /// Invokes the default window procedure associated with this window.
        /// </summary>
        /// <param name="m">A <see cref="T:System.Windows.Forms.Message"/> that is associated with the current Windows message.</param>
        [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
        protected override void WndProc(ref Message m)
        {
            //Debug.Print(m + " : " + _parentForm.Name);
            bool supressOriginalMessage = false;
            if (IsProcessNcArea)
                switch ((Win32Messages)m.Msg)
                {
                    // update form data on style change
                    case Win32Messages.STYLECHANGED:
                        UpdateStyle();
                        _manager.CurrentSkin.OnSetRegion(_parentForm, _parentForm.Size);
                        break;

                    #region Handle Form Activation
                    case Win32Messages.ACTIVATEAPP:
                        // redraw
                        _formIsActive = (int)m.WParam != 0;
                        NcPaint(true);
                        break;

                    case Win32Messages.ACTIVATE:
                        // Set active state and redraw
                        _formIsActive = ((int)WAFlags.WA_ACTIVE == (int)m.WParam || (int)WAFlags.WA_CLICKACTIVE == (int)m.WParam);
                        NcPaint(true);
                        break;
                    //Added 김진일
                    case Win32Messages.CHILDACTIVATE:
                        _formIsActive = true;
                        NcPaint(true);
                        break;
                    case Win32Messages.MDIACTIVATE:
                        // set active and redraw on activation 
                        if (m.WParam == _parentForm.Handle)
                            _formIsActive = false;
                        else if (m.LParam == _parentForm.Handle)
                            _formIsActive = true;
                        NcPaint(true);
                        break;
                    #endregion

                    #region Handle Mouse Processing
                    // Set Pressed button on mousedown
                    case Win32Messages.NCLBUTTONDOWN:
                        supressOriginalMessage = OnNcLButtonDown(ref m);
                        break;
                    // Set hovered button on mousemove
                    case Win32Messages.NCMOUSEMOVE:
                        OnNcMouseMove(m);
                        break;
                    // perform button actions if a button was clicked
                    case Win32Messages.NCLBUTTONUP:
                        // Handle button up
                        if (OnNcLButtonUp(m))
                            supressOriginalMessage = true;
                        break;
                    // restore button states on mouseleave
                    case Win32Messages.NCMOUSELEAVE:
                    case Win32Messages.MOUSELEAVE:
                    case Win32Messages.MOUSEHOVER:
                        if (_pressedButton != null)
                            _pressedButton.Pressed = false;
                        if (_hoveredButton != null)
                        {
                            _hoveredButton.Hovered = false;
                            _hoveredButton = null;
                        }
                        NcPaint(true);
                        break;
                    #endregion

                    #region Size Processing

                    // Set region as window is shown                    
                    case Win32Messages.SHOWWINDOW:
                        _manager.CurrentSkin.OnSetRegion(_parentForm, _parentForm.Size);
                        break;
                    // adjust region on resize
                    case Win32Messages.SIZE:
                        OnSize(m);
                        break;
                    // ensure that the window doesn't overlap docked toolbars on desktop (like taskbar)
                    case Win32Messages.GETMINMAXINFO:
                        supressOriginalMessage = CalculateMaxSize(ref m);
                        break;
                    // update region on resize
                    case Win32Messages.WINDOWPOSCHANGING:
                        WINDOWPOS wndPos = (WINDOWPOS)m.GetLParam(typeof(WINDOWPOS));
                        if ((wndPos.flags & (int)SWPFlags.SWP_NOSIZE) == 0)
                        {
                            _manager.CurrentSkin.OnSetRegion(_parentForm, new Size(wndPos.cx, wndPos.cy));
                        }
                        break;
                    // remove region on maximize or repaint on resize
                    case Win32Messages.WINDOWPOSCHANGED:
                        if (_parentForm.WindowState == FormWindowState.Maximized)
                            _parentForm.Region = null;

                        WINDOWPOS wndPos2 = (WINDOWPOS)m.GetLParam(typeof(WINDOWPOS));
                        if ((wndPos2.flags & (int)SWPFlags.SWP_NOSIZE) == 0)
                        {
                            UpdateCaption();
                            NcPaint(true);
                        }
                        break;
                    #endregion

                    #region Non Client Area Handling
                    // paint the non client area
                    case Win32Messages.NCPAINT:
                        if (NcPaint(true))
                        {
                            m.Result = (IntPtr)1;
                            supressOriginalMessage = true;
                        }
                        break;
                    // calculate the non client area size
                    case Win32Messages.NCCALCSIZE:
                        if (m.WParam == (IntPtr)1)
                        {
                            //if (_parentForm.MdiParent != null)
                            if (_parentForm.MdiParent != null && _parentForm.WindowState == FormWindowState.Maximized)
                                break;

                            // add caption height to non client area
                            NCCALCSIZE_PARAMS p = (NCCALCSIZE_PARAMS)m.GetLParam(typeof(NCCALCSIZE_PARAMS));
                            p.rect0.Top += FormExtenders.GetCaptionHeight(_parentForm);
                            Marshal.StructureToPtr(p, m.LParam, true);
                        }
                        break;
                    // non client hit test
                    case Win32Messages.NCHITTEST:
                        if (NcHitTest(ref m))
                            supressOriginalMessage = true;
                        break;

                    case Win32Messages.NCACTIVATE:
                        if (_parentForm.FormBorderStyle == FormBorderStyle.Sizable || _parentForm.FormBorderStyle == FormBorderStyle.SizableToolWindow)
                        {
                            _formIsActive = ((int)m.WParam == 1);
                            if (NcPaint(true))
                            {
                                //Added 김진일
                                //다중MDICHILD활성화해결
                                //supressOriginalMessage = true;
                                m.Result = IntPtr.Zero;
                            }
                        }
                        break;
                    #endregion
                }

            if (!supressOriginalMessage)
                base.WndProc(ref m);
        }

        /// <summary>
        /// Handles the window sizing
        /// </summary>
        /// <param name="m">The m.</param>
        private void OnSize(Message m)
        {
            UpdateCaption();
            // update form styles on maximize/restore
            if (_parentForm.MdiParent != null)// && _parentForm.WindowState == FormWindowState.Maximized)
            {
                if ((int)m.WParam == 0)
                    UpdateStyle();
                if ((int)m.WParam == 2)
                    _parentForm.Refresh();
                //Added 김진일
                return;
            }

            // update region if needed
            bool wasMaxMin = (_parentForm.WindowState == FormWindowState.Maximized || _parentForm.WindowState == FormWindowState.Minimized);

            RECT rect1 = new RECT();
            Win32Api.GetWindowRect(_parentForm.Handle, ref rect1);

            Rectangle rc = new Rectangle(rect1.Left, rect1.Top, rect1.Right - rect1.Left, rect1.Bottom - rect1.Top - 1);


            //if (wasMaxMin && _parentForm.WindowState == FormWindowState.Normal && rc.Size == _parentForm.RestoreBounds.Size)
            if (rc.Size != _parentForm.RestoreBounds.Size)
            {
                _manager.CurrentSkin.OnSetRegion(_parentForm, new Size(rect1.Right - rect1.Left, rect1.Bottom - rect1.Top));
                NcPaint(true);
            }
        }

        /// <summary>
        /// Handles the mouse move event on the non client area.
        /// </summary>
        /// <param name="m">The m.</param>
        private void OnNcMouseMove(Message m)
        {
            // Check for hovered and pressed buttons
            if (Control.MouseButtons != MouseButtons.Left)
            {
                if (_pressedButton != null)
                {
                    _pressedButton.Pressed = false;
                    _pressedButton = null;
                }
            }
            CaptionButton button = CommandButtonByHitTest((HitTest)m.WParam);

            if (_hoveredButton != button && _hoveredButton != null)
                _hoveredButton.Hovered = false;
            if (_pressedButton == null)
            {
                if (button != null)
                    button.Hovered = true;
                _hoveredButton = button;
            }
            else
                _pressedButton.Pressed = (button == _pressedButton);
        }

        /// <summary>
        /// Handle a left mouse button down event.
        /// </summary>
        /// <param name="m">The m.</param>
        /// <returns></returns>
        private bool OnNcLButtonDown(ref Message m)
        {
            CaptionButton button = CommandButtonByHitTest((HitTest)m.WParam);
            if (_pressedButton != button && _pressedButton != null)
                _pressedButton.Pressed = false;
            if (button != null)
                button.Pressed = true;
            _pressedButton = button;
            if (_pressedButton != null)
            {
                m.Result = (IntPtr)1;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Ensure that the window doesn't overlap docked toolbars on desktop (like taskbar)
        /// </summary>
        /// <param name="m">The message.</param>
        /// <returns></returns>
        private bool CalculateMaxSize(ref Message m)
        {
            if (_parentForm.Parent == null)
            {
                // create minMax info for maximize data
                MINMAXINFO info = (MINMAXINFO)m.GetLParam(typeof(MINMAXINFO));
                Rectangle rect = SystemInformation.WorkingArea;
                Size fullBorderSize = new Size(SystemInformation.Border3DSize.Width + SystemInformation.BorderSize.Width,
                    SystemInformation.Border3DSize.Height + SystemInformation.BorderSize.Height);

                info.ptMaxPosition.x = rect.Left - fullBorderSize.Width;
                info.ptMaxPosition.y = rect.Top - fullBorderSize.Height;
                info.ptMaxSize.x = rect.Width + fullBorderSize.Width * 2;
                info.ptMaxSize.y = rect.Height + fullBorderSize.Height * 2;

                info.ptMinTrackSize.y += FormExtenders.GetCaptionHeight(_parentForm);


                if (!_parentForm.MaximumSize.IsEmpty)
                {
                    info.ptMaxSize.x = Math.Min(info.ptMaxSize.x, _parentForm.MaximumSize.Width);
                    info.ptMaxSize.y = Math.Min(info.ptMaxSize.y, _parentForm.MaximumSize.Height);
                    info.ptMaxTrackSize.x = Math.Min(info.ptMaxTrackSize.x, _parentForm.MaximumSize.Width);
                    info.ptMaxTrackSize.y = Math.Min(info.ptMaxTrackSize.y, _parentForm.MaximumSize.Height);
                }

                if (!_parentForm.MinimumSize.IsEmpty)
                {
                    info.ptMinTrackSize.x = Math.Max(info.ptMinTrackSize.x, _parentForm.MinimumSize.Width);
                    info.ptMinTrackSize.y = Math.Max(info.ptMinTrackSize.y, _parentForm.MinimumSize.Height);
                }

                // set wished maximize size
                Marshal.StructureToPtr(info, m.LParam, true);

                m.Result = (IntPtr)0;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Updates the window style for the parent form.
        /// </summary>
        private void UpdateStyle()
        {
            // remove the border style
            Int32 currentStyle = Win32Api.GetWindowLong(Handle, GWLIndex.GWL_STYLE);
            if ((currentStyle & (int)(WindowStyles.WS_BORDER)) != 0)
            {
                currentStyle &= ~(int)(WindowStyles.WS_BORDER);
                Win32Api.SetWindowLong(_parentForm.Handle, GWLIndex.GWL_STYLE, currentStyle);
                Win32Api.SetWindowPos(_parentForm.Handle, (IntPtr)0, -1, -1, -1, -1,
                                      (int)(SWPFlags.SWP_NOZORDER | SWPFlags.SWP_NOSIZE | SWPFlags.SWP_NOMOVE |
                                             SWPFlags.SWP_FRAMECHANGED | SWPFlags.SWP_NOREDRAW | SWPFlags.SWP_NOACTIVATE));
            }
        }


        /// <summary>
        /// Updates the caption.
        /// </summary>
        private void UpdateCaption()
        {
            //Added
            _captionButtons.Clear();

            // create buttons
            if (_captionButtons.Count == 0)
            {
                _captionButtons.Add(new CaptionButton(HitTest.HTCLOSE));
                if (FormExtenders.IsDrawMaximizeBox(_parentForm))
                {
                    CaptionButton button = new CaptionButton(HitTest.HTMAXBUTTON);
                    _captionButtons.Add(button);
                }
                if (FormExtenders.IsDrawMinimizeBox(_parentForm))
                {
                    CaptionButton button = new CaptionButton(HitTest.HTMINBUTTON);
                    _captionButtons.Add(button);
                }

                // add command handlers
                foreach (CaptionButton button in _captionButtons)
                    button.PropertyChanged += OnCommandButtonPropertyChanged;
            }

            // Calculate Caption Button Bounds
            RECT rectScreen = new RECT();
            Win32Api.GetWindowRect(_parentForm.Handle, ref rectScreen);
            Rectangle rect = rectScreen.ToRectangle();

            Size borderSize = FormExtenders.GetBorderSize(_parentForm);
            rect.Offset(-rect.Left, -rect.Top);

            Size captionButtonSize = FormExtenders.GetCaptionButtonSize(_parentForm);
            Rectangle buttonRect = new Rectangle(rect.Right - borderSize.Width - captionButtonSize.Width, rect.Top + borderSize.Height,
                                    captionButtonSize.Width, captionButtonSize.Height);

            foreach (CaptionButton button in _captionButtons)
            {
                button.Bounds = buttonRect;
                buttonRect.X -= captionButtonSize.Width;
            }
        }

        /// <summary>
        /// Called when a property of a CommandButton has changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void OnCommandButtonPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // if a button is hovered or pressed invalidate
            if (e.PropertyName == "Pressed" || e.PropertyName == "Hovered")
                NcPaint(true);
        }

        /// <summary>
        /// Gets the command button at the specified position.
        /// </summary>
        /// <param name="point">The position.</param>
        /// <returns>the CaptionButton instance or null if no button was found.</returns>
        private CaptionButton CommandButtonFromPoint(Point point)
        {
            foreach (CaptionButton button in _captionButtons)
                if (button.Bounds.Contains(point)) return button;
            return null;
        }

        /// <summary>
        /// Gets the command button with the specified HitTest.
        /// </summary>
        /// <param name="hitTest">The hitTest.</param>
        /// <returns>the CaptionButton instance or null if no button was found.</returns>
        private CaptionButton CommandButtonByHitTest(HitTest hitTest)
        {
            foreach (CaptionButton button in _captionButtons)
                if (button.HitTest == hitTest)
                    return button;
            return null;
        }


        /// <summary>
        /// Redraws the non client area..
        /// </summary>
        public void Invalidate()
        {
            if (IsProcessNcArea)
                NcPaint(true);
        }

        /// <summary>
        /// Redraws the non client area.
        /// </summary>
        /// <param name="invalidateBuffer">if set to <c>true</c> the buffer is invalidated.</param>
        /// <returns>true if the original painting should be suppressed otherwise false.</returns>
        private bool NcPaint(bool invalidateBuffer)
        {
            if (!IsProcessNcArea)
                return false;
            bool result = false;

            IntPtr hdc = (IntPtr)0;
            Graphics g = null;
            Region region = null;
            IntPtr hrgn = (IntPtr)0;

            try
            {
                // no drawing needed
                if (_parentForm.MdiParent != null && _parentForm.WindowState == FormWindowState.Maximized)
                {
                    _currentCacheSize = Size.Empty;
                    return false;
                }

                // prepare image bounds
                Size borderSize = FormExtenders.GetBorderSize(_parentForm);
                int captionHeight = FormExtenders.GetCaptionHeight(_parentForm);

                RECT rectScreen = new RECT();
                Win32Api.GetWindowRect(_parentForm.Handle, ref rectScreen);

                Rectangle rectBounds = rectScreen.ToRectangle();
                rectBounds.Offset(-rectBounds.X, -rectBounds.Y);

                // prepare clipping
                Rectangle rectClip = rectBounds;
                region = new Region(rectClip);
                rectClip.Inflate(-borderSize.Width, -borderSize.Height);
                rectClip.Y += captionHeight;
                rectClip.Height -= captionHeight;

                // create graphics handle
                hdc = Win32Api.GetDCEx(_parentForm.Handle, (IntPtr)0,
                    (DeviceContextValues.Window | DeviceContextValues.Cache | DeviceContextValues.ClipSiblings));
                //(DCXFlags.DCX_CACHE | DCXFlags.DCX_CLIPSIBLINGS | DCXFlags.DCX_WINDOW));
                g = Graphics.FromHdc(hdc);

                // Apply clipping
                region.Exclude(rectClip);
                hrgn = region.GetHrgn(g);
                Win32Api.SelectClipRgn(hdc, hrgn);

                // create new buffered graphics if needed
                if (_bufferGraphics == null || _currentCacheSize != rectBounds.Size)
                {
                    if (_bufferGraphics != null)
                        _bufferGraphics.Dispose();

                    _bufferGraphics = _bufferContext.Allocate(g, new Rectangle(0, 0,
                                rectBounds.Width, rectBounds.Height));
                    _currentCacheSize = rectBounds.Size;
                    invalidateBuffer = true;
                }

                if (invalidateBuffer)
                {
                    // Create painting meta data for form
                    SkinningFormPaintData paintData = new SkinningFormPaintData(_bufferGraphics.Graphics, rectBounds)
                    {
                        Borders = borderSize,
                        CaptionHeight = captionHeight,
                        Active = _formIsActive,
                        HasMenu = FormExtenders.HasMenu(_parentForm),
                        IconSize = SystemInformation.SmallIconSize,
                        IsSmallCaption =
                            captionHeight ==
                            SystemInformation.ToolWindowCaptionHeight,
                        Text = _parentForm.Text
                    };

                    // create painting meta data for caption buttons
                    if (_captionButtons.Count > 0)
                    {
                        paintData.CaptionButtons = new CaptionButtonPaintData[_captionButtons.Count];
                        for (int i = 0; i < _captionButtons.Count; i++)
                        {
                            CaptionButton button = _captionButtons[i];
                            CaptionButtonPaintData buttonData = new CaptionButtonPaintData(_bufferGraphics.Graphics, button.Bounds)
                            {
                                Pressed = button.Pressed,
                                Hovered = button.Hovered,
                                Enabled = button.Enabled,
                                HitTest = button.HitTest
                            };
                            paintData.CaptionButtons[i] = buttonData;
                        }
                    }

                    // paint
                    result = _manager.CurrentSkin.OnNcPaint(_parentForm, paintData);
                }

                // render buffered graphics 
                if (_bufferGraphics != null)
                    _bufferGraphics.Render(g);
            }
            catch (Exception)
            {// error drawing
                result = false;
            }

            // cleanup data
            if (hdc != (IntPtr)0)
            {
                Win32Api.SelectClipRgn(hdc, (IntPtr)0);
                Win32Api.ReleaseDC(_parentForm.Handle, hdc);
            }
            if (region != null && hrgn != (IntPtr)0)
                region.ReleaseHrgn(hrgn);

            if (region != null)
                region.Dispose();

            if (g != null)
                g.Dispose();

            return result;
        }

        /// <summary>
        /// Performs the non client HitTest
        /// </summary>
        /// <param name="m">The Message</param>
        /// <returns>true if the orginal handler should be suppressed otherwise false.</returns>
        private bool NcHitTest(ref Message m)
        {
            if (!IsProcessNcArea)
                return false;

            Point point = new Point(m.LParam.ToInt32());
            Rectangle rectScreen = FormExtenders.GetScreenRect(_parentForm);
            Rectangle rect = rectScreen;

            // custom processing
            if (rect.Contains(point))
            {
                Size borderSize = FormExtenders.GetBorderSize(_parentForm);
                rect.Inflate(-borderSize.Width, -borderSize.Height);

                // let form handle hittest itself if we are on borders
                if (!rect.Contains(point))
                    return false;

                Rectangle rectCaption = rect;
                rectCaption.Height = FormExtenders.GetCaptionHeight(_parentForm);

                // not in caption -> client
                if (!rectCaption.Contains(point))
                {
                    m.Result = (IntPtr)(int)HitTest.HTCLIENT;
                    return true;
                }

                // on icon?
                if (FormExtenders.HasMenu(_parentForm))
                {
                    Rectangle rectSysMenu = rectCaption;
                    rectSysMenu.Size = SystemInformation.SmallIconSize;
                    if (rectSysMenu.Contains(point))
                    {
                        m.Result = (IntPtr)(int)HitTest.HTSYSMENU;
                        return true;
                    }
                }

                // on Button?
                Point pt = new Point(point.X - rectScreen.X, point.Y - rectScreen.Y);
                CaptionButton sysButton = CommandButtonFromPoint(pt);
                if (sysButton != null)
                {
                    m.Result = (IntPtr)sysButton.HitTest;
                    return true;
                }

                // on Caption?
                m.Result = (IntPtr)(int)HitTest.HTCAPTION;
                return true;
            }
            m.Result = (IntPtr)(int)HitTest.HTNOWHERE;
            return true;
        }

        /// <summary>
        /// Handles the left button up message
        /// </summary>
        /// <param name="m">The message</param>
        /// <returns></returns>
        private bool OnNcLButtonUp(Message m)
        {
            if (!IsProcessNcArea)
                return false;

            // do we have a pressed button?
            if (_pressedButton != null)
            {
                // get button at wparam
                CaptionButton button = CommandButtonByHitTest((HitTest)m.WParam);
                if (button == null)
                    return false;

                if (button.Pressed)
                {
                    switch (button.HitTest)
                    {
                        case HitTest.HTCLOSE:
                            _parentForm.Close();
                            return true;
                        case HitTest.HTMAXBUTTON:
                            if (_parentForm.WindowState == FormWindowState.Maximized)
                            {
                                _parentForm.WindowState = FormWindowState.Normal;
                                _parentForm.Refresh();
                            }
                            else if (_parentForm.WindowState == FormWindowState.Normal ||
                                     _parentForm.WindowState == FormWindowState.Minimized)
                            {
                                _parentForm.WindowState = FormWindowState.Maximized;
                            }
                            break;
                        case HitTest.HTMINBUTTON:
                            _parentForm.WindowState = _parentForm.WindowState == FormWindowState.Minimized
                                                          ? FormWindowState.Normal
                                                          : FormWindowState.Minimized;
                            break;

                    }
                }

                _pressedButton.Pressed = false;
                _pressedButton.Hovered = false;
                _pressedButton = null;
            }
            return false;
        }
        #endregion
    }
}


modified 26-Sep-13 19:29pm.

GeneralRe: MDI child windows work very badly [MDI Solved] Pin
albert.dc17-Dec-13 19:35
albert.dc17-Dec-13 19:35 
GeneralRestore from System menu does not repaint client area Pin
mikeduglas4-Mar-10 5:29
mikeduglas4-Mar-10 5:29 
GeneralSkinningForm.OnSize Pin
mikeduglas3-Mar-10 6:41
mikeduglas3-Mar-10 6:41 
GeneralRe: SkinningForm.OnSize Pin
Danielku153-Mar-10 7:42
Danielku153-Mar-10 7:42 
GeneralRe: SkinningForm.OnSize Pin
mikeduglas3-Mar-10 18:28
mikeduglas3-Mar-10 18:28 
GeneralRe: SkinningForm.OnSize Pin
Danielku153-Mar-10 20:59
Danielku153-Mar-10 20:59 
GeneralNice Job Pin
Poul_L27-Feb-10 3:50
Poul_L27-Feb-10 3:50 
GeneralBorderLess window... Pin
Paulo Zemek26-Feb-10 2:07
mvaPaulo Zemek26-Feb-10 2:07 
GeneralRe: BorderLess window... Pin
Danielku153-Mar-10 21:02
Danielku153-Mar-10 21:02 
GeneralGreat! Pin
Druuler26-Feb-10 1:39
Druuler26-Feb-10 1:39 
Questionhow to disable skinning Pin
Huisheng Chen25-Feb-10 20:54
Huisheng Chen25-Feb-10 20:54 
AnswerRe: how to disable skinning Pin
Danielku1526-Feb-10 1:01
Danielku1526-Feb-10 1:01 
GeneralGood work. But expected a little more. Pin
Som Shekhar25-Feb-10 19:35
Som Shekhar25-Feb-10 19:35 
GeneralRe: Good work. But expected a little more. Pin
Danielku1526-Feb-10 1:00
Danielku1526-Feb-10 1:00 
GeneralRe: Good work. But expected a little more. Pin
Som Shekhar26-Feb-10 1:06
Som Shekhar26-Feb-10 1:06 
GeneralRe: Good work. But expected a little more. Pin
Nedim Sabic26-Feb-10 3:53
Nedim Sabic26-Feb-10 3:53 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.