Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

TaskbarNotifiers, Resizable Skinned MSN Messenger-Like Popups with Video

0.00/5 (No votes)
8 Jan 2012 1  
Resizable Skins Made from Web Pages with Video

Image 1

Introduction

I have always enjoyed playing with non-rectangular skins and this article is just about having fun making skins in different ways. John O'Byrne wrote an article here on CodeProject back in 2003 entitled TaskbarNotifier, a skinnable MSN Messenger-like popup in C# that was a nice article on creating a non-rectangular TaskbarNotifier. I thought I would take his code as a starting point, fix some minor bugs, and add a number of additional features such as creating skins from web pages for really cool animations using jquery, adding a non-rectangular video player, and making non-rectangular skins resizable by loading the non-rectangualr regions from files or resources and scalling the regions using XFORM and Transform. And unlike his original project, I created a DLL that can be loaded and used by other applications which made more sense to me. I am not an artist so some of the edges of the skins are a little rough but I hope the reader understands that this article is about just having fun with regions and not about my artwork!

Features

The MSN messenger like popup includes:

  • Skins created from the webbrowser control AND skins created from bitmaps
  • Made all skins resizable using XFORM and Transform
  • Using a web page as a skin for really cool-looking skins
  • A skinnable 3-State close button for bitmap skins
  • Clickable title text & Clickable content text
  • Custom fonts and colors for the different states of the text (normal/hover)
  • Animation speed parameters
  • Playing video in a non-rectangular window in a skin
  • Fast creation of a Region from bitmap using a tolerance factor
  • Loading a region from a file & Resizing the Region Dynamically
  • Saving a region to a file THE CORRECT WAY!
  • Loading a region from an embedded resource

How the DLL and Demo Work

I included a separte project called "NotifierDemo" that loads the DLL "TaskNotifier.dll" to illustrate how to use this DLL. In addition, NotifierDemo also allows you to create and save regions to files so we can use the saved regions as files or embedded resources. There are two types of skins in this demo. The first are those that are just a skinned WinForm painted with a bitmap, i.e. the "Bmp Skin" buttons. The other type is a skinned WinForm with a WebBrowser Control and an instance of a media player, i.e., the "WebSkin" buttons in the NotifierDemo screen shown below are "Cargo Door," "Stone TV," and "Stargate." The bottom half of the screen below allows you to create a region from a bitmap including a tolerance factor.  The button in the lower left of the screen below called "Create Form from Region File" will load the region frrom the region file you select and RESIZE the region to fit the dimensions you have typed into the Width and Height fields on the right of this button. I only set the code for an animated slide from the lower right-hand corner of the screen but the reader can easily modify the code to slide from any of the corners of the screen. It should be pointed out that some of the resources for these skins can be placed in the DLL as embedded resources or they can loose in any directory. In this demo to make things easier I put some skin resources in directories and others as embeddede resources to illustrate using both approaches.

TaskbarNotifiers/TaskbarNotifiers2.jpg

 

Creating A Region from A Bitmap with Tolerance

 

You can find dozens of examples of creating a region from a bitmap. I used the approach below in C# that includes using a "Tolerance Factor" to help to smooth out the rough curves. Speed is not critical here because we will be using only the regions created in our skins and not dynamically creating the regions from a bitmap. When I first started this article I resized the skins by just first resizing the bitmap image and then creating the region again from the resized bitmap--that also works fine if you prefer that approach. My own preference is to create the region and resize the regions from a file or embedded region resource which seems a bit faster.

C#
public Region getRegion(Bitmap inputBmp, Color transperancyKey, int tolerance)
{
    // Stores all the rectangles for the region
    GraphicsPath path = new GraphicsPath();

    // Scan the image
    for (int x = 0; x < inputBmp.Width; x++)
    {
        for (int y = 0; y < inputBmp.Height; y++)
        {
            if (!colorsMatch(inputBmp.GetPixel(x, y), transperancyKey, tolerance))
                path.AddRectangle(new Rectangle(x, y, 1, 1));
        }
    }

    // Create the Region
    Region outputRegion = new Region(path);

    // Clean up
    path.Dispose();

    return outputRegion;
}

private static bool colorsMatch(Color color1, Color color2, int tolerance)
{
    if (tolerance < 0) tolerance = 0;
    return Math.Abs(color1.R - color2.R) <= tolerance &&
           Math.Abs(color1.G - color2.G) <= tolerance &&
           Math.Abs(color1.B - color2.B) <= tolerance;
}

Saving A Region to A File

Saving a region file correctly is not so simple. What I doubt you will find anywhere is sample code in C# to save a region to a file correctly. Proably because to save a region to a file in C# is a bit tricky since the methods to get the combined region data AND region header all accept a pointer to the region structure. Since the skins in this project all load the regions from either a file or embedded resource we need to be able to save the region created from a bitmap to a file in C# as I do in Bmp2Rgn.cs. There are other ways to save or serialize a region to a file in C# without using unsafe pointers but this approach is just the way I prefer doing it.

C#
 // Create a region called "myRegion" by some means and pass the
// handle to the region, i.e., Hrgn, to "SaveRgn2File" like so:
using (Graphics g = this.CreateGraphics())
    SaveRgn2File(myRegion.GetHrgn(g), sSaveRgnFile);

[SuppressUnmanagedCodeSecurity()]
public unsafe void SaveRgn2File(IntPtr hRgn, string sSaveRgnFile)
{
    Win32.RECT[] regionRects = null;
    IntPtr pBytes = IntPtr.Zero;
    try
    {
        // See how much memory we need to allocate
        int regionDataSize = Win32.GetRegionData(new HandleRef(null, hRgn), 0, IntPtr.Zero);
        if (regionDataSize != 0)
        {
            pBytes = Marshal.AllocCoTaskMem(regionDataSize);
            // Get the pointer, i.e., pBytes, to BOTH the region header AND the region data!
            int ret = Win32.GetRegionData(new HandleRef(null, hRgn), regionDataSize, pBytes);
            if (ret == regionDataSize) // make sure we have RDH_RECTANGLES
            {
                // Cast to the structure
                Win32.RGNDATAHEADER* pRgnDataHeader = (Win32.RGNDATAHEADER*)pBytes;
                if (pRgnDataHeader->iType == 1)  // Make sure we have RDH_RECTANGLES
                {
                    using (FileStream writeStream = new FileStream(sSaveRgnFile, FileMode.Create, FileAccess.ReadWrite))
                    {
                        WriteToStream(writeStream, (void*)pBytes, (uint)ret);
                        writeStream.Close();
                    }
                }
            }
        }
    }
    finally
    {
        if (pBytes != IntPtr.Zero)
        {
            Marshal.FreeCoTaskMem(pBytes);
        }
    }
}

[SuppressUnmanagedCodeSecurity()]
public unsafe static void WriteToStream(FileStream output, void* pvBuffer, uint length)
{
    IntPtr hFile = output.SafeFileHandle.DangerousGetHandle();
    WriteToStream(hFile, pvBuffer, length);
    GC.KeepAlive(output);
}

[SuppressUnmanagedCodeSecurity()]
public unsafe static void WriteToStream(IntPtr hFile, void* pvBuffer, uint length)
{
    if (hFile == NativeConstants.INVALID_HANDLE_VALUE)
        throw new ArgumentException("output", "File is closed");

    void* pvWrite = pvBuffer;

    while (length > 0)
    {
        uint written;
        bool result = SafeNativeMethods.WriteFile(hFile, pvWrite, length, out written, IntPtr.Zero);

        if (!result)
            return;

        pvWrite = (void*)((byte*)pvWrite + written);
        length -= written;
    }
}
<h2loading>

My approach in creating skins is to let the user set the width and height of any skin dynamically by providing a "Grip Area" in the lower right corner of the skin that the user can drag to resize the skin. The method below converts a region file to a scaled region using XFORM & Transform & sets a region to object given it's handle.  One issue I ran into was that I found I had to remove the AnchorStyles before calling "SetBounds" and setting the region for the Video Player and then I had to reset the AnchorStyles to "Top" and "Left" afterwards. 

C#
 // Converts region file to scaled region using XFORM & Transform & sets region to object given it's handle
private void File2RgnStretch(System.IntPtr hWnd, string strRgnFile, int bmpWidth, int bmpHeight, int rgnWidth, int rgnHeight)
{
    using (FileStream fs = new FileStream(strRgnFile, FileMode.Open, FileAccess.Read, FileShare.Read))
    {
        byte[] regionData = null;
        BinaryReader reader = new BinaryReader(fs);
        regionData = reader.ReadBytes((int)fs.Length);
        using (Region region = Region.FromHrgn(ExtCreateRegion(0, regionData.Length, regionData)))
        {
            // The bounding rectangle of a region is usually smaller than the WinForm's height and width so we use the size
            // of the default bitmap we paint the WinForm's background with to calculate the xScale and yScale values
            float xScale = (float)rgnWidth / (float)bmpWidth;
            float yScale = (float)rgnHeight / (float)bmpHeight;

            Win32.XFORM xForm;
            xForm.eDx = 0;
            xForm.eDy = 0;
            xForm.eM11 = xScale;
            xForm.eM12 = 0;
            xForm.eM21 = 0;
            xForm.eM22 = yScale;

            // Scale the region
            region.Transform(xForm);

            Graphics g = this.CreateGraphics();
            IntPtr hRgn = region.GetHrgn(g);
            //IntPtr hDC = g.GetHdc();
            if (this.Handle == hWnd)  // Set the WinForm Region
            {
                // Get the Bounding Rectangle for this region
                RectangleF rect = region.GetBounds(g);
                rectSkin.Width = Convert.ToInt32(rect.Width) + 30;
                if((ZSkinName == "skin1") || (ZSkinName == "skin2") || (ZSkinName == "skin3"))
                    rectSkin.height = Convert.ToInt32(rect.Height);
                else
                    rectSkin.height = Convert.ToInt32(rect.Height) + 10;

                SetWindowRgn(hWnd, hRgn, false);
            }
            else if (dShowPlayer1.Handle == hWnd)  // Set the Video Player Region
            {
                rectVideo = new Win32.RGNRECT();
                rectVideo.x1 = 0;
                rectVideo.y1 = 0;
                rectVideo.Width = this.Width;
                rectVideo.Height = this.Height;
                dShowPlayer1.RectVideo = rectVideo;
                dShowPlayer1.Visible = true;

                // Remove AnchorStyles before calling "SetBounds"
                dShowPlayer1.Anchor = AnchorStyles.None;
                dShowPlayer1.Dock = DockStyle.None;
                dShowPlayer1.SetBounds(rectVideo.x1, rectVideo.y1, rectVideo.Width, rectVideo.Height);
                Win32.SetWindowRgn(dShowPlayer1.Handle, hRgn, true);
                // Set AnchorStyles
                dShowPlayer1.Anchor = AnchorStyles.Top | AnchorStyles.Left;
            }
            else
                SetWindowRgn(hWnd, hRgn, false); // Set the WebBrowser's Region

            // Clean Up
            region.ReleaseHrgn(hRgn);

            //g.ReleaseHdc(hDC);
            g.Dispose();
        }
    }
}

// Note: To load a region from a resourse we use:
global::System.Resources.ResourceManager rm =
new global::System.Resources.ResourceManager("TaskNotifier.Properties.Resources", typeof(Resources).Assembly);
regionData = (byte[])rm.GetObject(strRgnRes);

Basic Layout of WebBrowser Skins

Below is the basic structure and layout of the WebBrowser skin. It consists of a WinForm with a WebBrowser control docked on the form. We then set a region that is a "cookie cutter" for the overall shape of both the WinForm as illustrated below. The door below is just an ordinary web page in the wbbrowser control inside of a non-rectangualr region.  In the case of the "Cargo Door" skin the door is an html file where the door itself is made up of a lot of little images that are animated using plain old Javascript.

There are two main ways to create the Grip Area on the skin in the WeBrowser skins. One technique is to place an image of the Grip Area in a separate layer in the web page for the skin and position it where you want it and to fire a callback in the C# code the same way we do for the dragging of the web skin. But I decided to use an approach that we could also use with the non-WebBrowser skins, namely to paint the Grip Area directly on the back of the WinForm and re-paint the Grip when the form is resized. In order for the user to be able to click on the painted Grip Area on the WinForm it is necessary toi leave an area cut out from the WeBrowsr's region so that the underlying Grip Area is visible as show below.

TaskbarNotifiers/TaskbarNotifiers3.jpg

TaskbarNotifiers/TaskbarNotifiers4.gif

 

Scaling The Grip Image On Resize of Skin

When we first create the skin we calculate the sizeFactorX and sizeFactorY based on the form's size to the size of the bitmap we will use to paint the background of our form. We then apply these ratios to correctly resize the image of our Grip. The position of the Grip is also recalculated with these ratios as shown below:

C#
 // Note: In the method UpdateSkin() we calculate the Scale for the width and height as follows:
sizeFactorX = (double)this.Size.Width / backBmap.Size.Width;
sizeFactorY = (double)this.Size.Height / backBmap.Size.Height;

public void SetGripBitmap(Image image, Color transparencyColor, Point position)
{
    dGripBitmapW = (double)image.Width * (double)sizeFactorX;
    dGripBitmapH = (double)image.Height * (double)sizeFactorY;
    dGripBitmapX = (double)position.X * (double)sizeFactorX;
    dGripBitmapY = (double)position.Y * (double)sizeFactorY;

    GripBitmap = null;
    GripBitmap = new Bitmap(Convert.ToInt32(dGripBitmapW), Convert.ToInt32(dGripBitmapH));
    Graphics gr = Graphics.FromImage(GripBitmap);
    gr.SmoothingMode = SmoothingMode.None;
    gr.CompositingQuality = CompositingQuality.HighQuality;
    gr.InterpolationMode = InterpolationMode.HighQualityBilinear;
    gr.DrawImage(image, new Rectangle(0, 0, Convert.ToInt32(dGripBitmapW), Convert.ToInt32(dGripBitmapH)), new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);

    gr.Dispose();

    GripBitmap.MakeTransparent(transparencyColor);
    GripBitmapSize = new Size(GripBitmap.Width, GripBitmap.Height);
    GripBitmapLocation = new Point(Convert.ToInt32(dGripBitmapX), Convert.ToInt32(dGripBitmapY));
}

Scaling/Zooming The WebBrowser Control That Displays The HTML Skin

Since the non-rectangular WebBrowser Control contains our HTML Skin when the user resizes the WinForm by dragging the the Grip Area we must also zoom the WebBrowser control proportionally to match the change in size of the WinForm so I added the necessary code for the WebBrowser Control accomplish this as follows:

C#
[PermissionSet(SecurityAction.LinkDemand, Name = "FullTrust")]
public void Zoom(int zoomvalue)
{
    if ((zoomvalue < 10) || (zoomvalue > 1000))
        return;

    try
    {
        // In Windows Internet Explorer 8 or higher we can call OLECMDIDF_OPTICAL_ZOOM_NOPERSIST = 0x00000001 but this is BUGGY!
        // Windows Internet Explorer 8 does not automatically persist the specified zoom percentage.
        // But it is safer to just call extendedWebBrowser1.Zoom(100) to reset the zoom factor back to 100% when we create our skins
        this.axIWebBrowser2.ExecWB(NativeMethods.OLECMDID.OLECMDID_OPTICAL_ZOOM, NativeMethods.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER, zoomvalue, System.IntPtr.Zero);
        }
    catch
    {

    }
}

In that case of simple bitmap skins we can proportionally change both the width and height to the new dimensions of the WinForm BUT, in the case of the WebBrowser skins, we are limited because of how the WebBrowser's zoom works so we resize the WebBrowser skins by the change in the x-coordinate as follows:

C#
 // Original background bitmap for "Stargate" skin was: width: 372 and height: 380
// Where dx and dy are change in WinForm width and height after dragging the Grip area
double dHeight = (double)(380 * (this.Width + dx)) / (double)372;

// Change the WinForm size to the new dimensions
Win32.SetWindowPos(this.Handle, (System.IntPtr)Win32.HWND_TOPMOST, this.Location.X, this.Location.Y,
this.Width + dx, Convert.ToInt32(dHeight), Win32.SWP_SHOWWINDOW);

// Sets the new scaled regions and calculates the new "sizeFactorX"
UpdateSkin();

// Calculate the zoom percentage for the WebBrowser
double dZoom = 100 * (double)sizeFactorX;

// Zoom the WebBrowser Control display of our html skin
extendedWebBrowser1.Zoom(Convert.ToInt32(dZoom));

// Scale our painted Grip area to new dimensions
SetGripBitmap((Bitmap)Resources.stargate_grip, Color.FromArgb(255, 0, 255), new Point(270, 320));

Playing Video Using DirectShowNet Wrapper

As I mentioned at the beginning of the article I included the C# wrapper for DirectShow, namely, DirectShowLib-2005.dll, to allow users to play video messages in a popup. You can remove this reference if you don't want to play video. There are two ways you can add video, I added an instance of a C# video player I created using the wrapperer. Shown below is a skinned WinForm consisting of a WebBrowser Control and an instance of media player that is just a wrapper for the DirectShowLib-2005.dll. You can download this dll with sourcecode at http://directshownet.sourceforge.net/.

The WebBrowser Control is docked to the parent WinForm that has no border. The actual skin you see isn't a bitmap painted on the WinForm as in the case of the skins "Bmp Skin 1", "Bmp Skin 2" and "Bmp Skin 3" which are skins that only use the WinForm. In the case of the WebSkins the skin is just an ordinary html web page inside of the WebBrowser Control docked on the parent WinForm. Below is an illustration of the layers on the WebBrowser skin. We could add additional zooming for the video but this articloe is just about having some fun with regions and that would be a little too much!


TaskbarNotifiers/TaskbarNotifiers5.jpg


 

If the user right mouse clicks on the Video Player region I added a context menu that will appear that allows the user to play the video fullscreen so we need to subscribe to the "ToggleChange" event in the video player so we can handle when the user goes from fullscreen back to the normal size of the player. In this event we need to call "SetSkin()" to rebuild the skin as follows:

C#
// dShowPlayer1.ToggleChange += new ZToggleFullScreenHandler(dShowPlayer1_ToggleChange);
void dShowPlayer1_ToggleChange(object o, ZToggleEventArgs e)
{
    if (!e.ZIsFullScreen)
    {
        dShowPlayer1.menuFileClose_Click(this, null);
        SetSkin(ZSkinName, true);
    }
}

Details for Simple Bmp Skins:

C#
 SetBackgroundBitmap((Bitmap)Resources.skin1, Color.FromArgb(255, 0, 255));;0, 255));;
SetCloseBitmap((Bitmap)Resources.close1, Color.FromArgb(255, 0, 255), new Point(220, 8));
TitleRectangle = new Rectangle(60, 8, 70, 25);
ContentRectangle = new Rectangle(60, 8, 150, 140);

The first line sets the background bitmap skin and transparency color from the embedded resource bitmap, and the second line sets the optional 3-State close button with its transparency color and its location on the window. These two lines allow us to define the rectangles in which will be displayed, the title and content texts. You can set these properties for the simple bmp skins:

  • Title, content fonts and colors
  • Ability to click or not on the title/content/close button
  • You can disable the focus rect
  • Position the popup simple bmp skins where you want on the screen
C#
void SetCloseBitmap(string strFilename, Color transparencyColor, Point position)

Sets the 3-State close button bitmap, its transparency color and its coordinates for our plain bitmap skins.

Parameters

  • strFilename: Path of the 3-state close button bitmap on the disk where the width must be a multiple of 3
  • transparencyColor: Color of the bitmap which won't be visible
  • position: Location of the close button on the popup
C#
void SetCloseBitmap(Image image, Color transparencyColor, Point position)

Sets the 3-State close button bitmap, its transparency color and its coordinates.

Parameters

  • image: Image/Bitmap object which represents the 3-state close button bitmap (width must be a multiple of 3)
  • transparencyColor: Color of the bitmap which won't be visible
  • position: Location of the close button on the popup

Properties for All Skin Types

C#
 string TitleText (get/set)
string ContentText (get/set)
TaskbarStates TaskbarState (get)
Color NormalTitleColor (get/set)
Color HoverTitleColor (get/set)
Color NormalContentColor (get/set)
Color HoverContentColor (get/set)
Font NormalTitleFont (get/set)
Font HoverTitleFont (get/set)
Font NormalContentFont (get/set)
Font HoverContentFont (get/set)
Rectangle TitleRectangle (get/set) //must be defined before calling show())
Rectangle ContentRectangle (get/set) //must be defined before calling show())

bool TitleClickable (get/set) (default = false);
bool ContentClickable (get/set) (default = true);
bool CloseClickable (get/set) (default = true);
bool EnableSelectionRectangle (get/set) (default = true);

SkinDlg Class Documentation

Methods

public void Show(string strAction, string strTitle, string strContent, int nTimeToShow, int nTimeToStay, int nTimeToHide)

 I added an "Action" parameter, i.e., strAction, to the original code to indicate what the popup should do when launched, and the other parameters set the Title, Content and amount of time to display the popup.

Parameterss

  • strAction: The string determines the action of the popup, i.e., screen position and slide and noslide
  • strTitlee: The string which will be shown as the title of the popup
  • strContent: The string which will be shown as the content of the popup
  • nTimeToShow: Duration of the showing animation (in milliseconds)
  • nTimeToStay: Duration of the visible state before collapsing (in milliseconds)
  • nTimeToHide: Duration of the hiding animation (in milliseconds)
C#
void Hide()

 This Hides the popup.

Painting Background

The refresh() of the popup is done using double buffering technique from John O'Byrne's original article to avoid flickering with some minor changes:

 protected override void OnPaintBackground(PaintEventArgs e)
{
     if (m_alphaBitmap != null)
     {
         Graphics grfx = e.Graphics;
         grfx.PageUnit = GraphicsUnit.Pixel;

         Graphics offScreenGraphics;
         Bitmap offscreenBitmap;

         offscreenBitmap = new Bitmap(m_alphaBitmap.Width, m_alphaBitmap.Height);
         offScreenGraphics = Graphics.FromImage(offscreenBitmap);

         if (m_alphaBitmap != null)
             offScreenGraphics.DrawImage(m_alphaBitmap, 0, 0, m_alphaBitmap.Width, m_alphaBitmap.Height);

         DrawGrip(offScreenGraphics);
         DrawCloseButton(offScreenGraphics);
         DrawText(offScreenGraphics);

         grfx.DrawImage(offscreenBitmap, 0, 0);

         // The bitmap and "offScreenGraphics" object should be disposed.
         // BUT, The grfx object should NOT be disposed.
         offScreenGraphics.Dispose();
         offscreenBitmap.Dispose();

     }
 }

In addition to avoid flicking I added:

this.SetStyle(System.Windows.Forms.ControlStyles.DoubleBuffer, true);
this.SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint, false); 
this.SetStyle(System.Windows.Forms.ControlStyles.ResizeRedraw, true); 
this.SetStyle(System.Windows.Forms.ControlStyles.UserPaint, true); 
this.UpdateStyles();

Creating A DropShadow

The reader can tweak the code and easily change how the popups work. For example, you can embed the html and images in the DLL and load them as embedded resources.  You will notice the popup is shown using the Win32 function ShowWindow(SW_SHOWNOACTIVATE), to prevent the popup from stealing the focus.

To add a really nice dropshadow we need to create a bitmap with 32 bits per pixel with an alpha channel and add the droshadow to the bitmap itself as part of the image. But to add a slight dropshadow we can just add the code below.

C#
// Adds a slight dropshadow to our skin
    private const int WS_THICKFRAME = 0x40000;
    private const int WS_CAPTION = 0xC00000;
    private const int CS_DROPSHADOW = 0x20000;
    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ClassStyle |= CS_DROPSHADOW;
            cp.Style = cp.Style & ~WS_CAPTION;
            cp.Style = cp.Style & ~WS_THICKFRAME;
            return cp;
        }
    }

Dragging The Skins

There are a number of ways to drag a non-rectangular form without a tittle bar but I like to keep things simple so what I used was the code below. In the case of the simple bitmap skin we override OnMouseDown and just make sure that we are not over the painted Grip Area as shown below.

C#
// Dragging is achieved with the following in OnMouseDown(MouseEventArgs e)
Win32.ReleaseCapture();
Win32.SendMessage(Handle, Win32.WM_NCLBUTTONDOWN, Win32.HT_CAPTION, 0);

protected override void OnMouseDown(MouseEventArgs e)
{
    base.OnMouseDown(e);
    bIsMouseDown = true;

    if (e.Button == MouseButtons.Left)
    {
        if (bIsMouseOverClose)
        {
            Refresh();
        }
        else if (!bIsMouseOverGrip)
        {
            Win32.ReleaseCapture();
            Win32.SendMessage(Handle, Win32.WM_NCLBUTTONDOWN, Win32.HT_CAPTION, 0);
        }
        else if (bIsMouseOverGrip)
        {
            nMouseStartX = e.X;
            nMouseStartY = e.Y;
        }
    }
}

In the case of our WebBrowser skins there are two main methods I used in these skins and both work equally well. The first method is place a link in te javascript as follows so that when the user clicks on an image in html designated to be part of the frame images that users click on to drag the skin we Naviate to "EVENT:DRAG" and trap it in the C# "Navigating" event as follows:

C#
// METHOD #1. Dragging The Skin Using The Navigating Event in Our WebBrowser Control

// On the Click event of a drag image in Javascript we call the function dragimage()
<script language="javascript" type="text/jscript">
    function dragimage(){window.location.href ="EVENT:DRAG";}
</script>

// In C# we capture this Navigate event from our Javascript in the C# Navigating event
private void extendedWebBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
    string csTestUrl = string.Empty;
    string csEvent = string.Empty;
    string csAction = string.Empty;
    string csData = string.Empty;
    string csQuestionMark = string.Empty;
    char[] delimiterChars = { ':' };
    char[] delimiterChars2 = { '?' };
    try {
        csTestUrl = e.Url.ToString();
        string[] words = csTestUrl.Split(delimiterChars, StringSplitOptions.None);
        if (words.Length > 1) {
            csEvent = words[0];
            csAction = words[1];
        }
        if (words.Length > 2) csData = words[2];
        string[] words2 = csTestUrl.Split(delimiterChars2, StringSplitOptions.None);
        if (words2.Length > 1) csQuestionMark = words2[1];
    }
    catch { }

    csEvent = csEvent.ToUpper();

    if (csEvent != "EVENT") { }
    else
    {
        try
        {
            csAction = csAction.ToUpper();
            if (csAction == "DRAG")
            {
                e.Cancel = true;
                Win32.ReleaseCapture();
                Win32.SendMessage(Handle, Win32.WM_NCLBUTTONDOWN, Win32.HT_CAPTION, 0);
            }
        }
        catch { e.Cancel = true; }
    }
}

The other method which I also used in the sample project which works equally well is to use "window.external" in Javascript and to create an "External" Class that calls a method "SendDragData" in our skin as follows:

C#
// METHOD #2. Dragging The Skin Using "window.external" in Javascript

// On the Click event of a drag image in Javascript we call window.external in the function dragimage()
<script language="javascript" type="text/jscript">
    function HandleDrag(a) { window.external.SendDragData("EVENT:DRAG"); }
</script>


// In C# External Class we call SendDragData and pass the data, namely, "EVENT:DRAG", to our skin window
[System.Runtime.InteropServices.ComVisibleAttribute(true)]
public class External
{
    private static SkinDlg m_mainWindow = null;
    public void SendDragData(string zdata)
    {
        m_mainWindow.SendDragData(zdata);
    }
}

// In our skin we receive the data, namely, "EVENT:DRAG", sent from our External Class
public void SendDragData(string zdata)
{
    Win32.ReleaseCapture();
    Win32.SendMessage(Handle, Win32.WM_NCLBUTTONDOWN, Win32.HT_CAPTION, 0);
}

Conclusion

Basically I set out to see how well resizing of the skins might look and the overall effect isn't bad. The purpose of this article is to just have some fun palying around with skins.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here