Beauty is only Skin-Deep: Skinning your WinForms Application

Published 09 May 08 11:00 AM | jacobl 

We all want our Windows applications to stand out, and I think most people would like to believe that there are a few distinct approaches to achieving this. You can either have an app that does great things, one that does things great, or one that does things while looking great. The ideal approach is to combine all three and make great applications that do things very well while looking amazing. The first impression is everything, right? So this post is all about how to skin your application. While I can't turn you into Monet, I can show you what a paint brush looks like.

Eat Your Cake Without Having to Wait for Your Oven to Preheat

There are toolkits out there that allow you to skin your apps very easily like DotNetSkin or Sunisoft's IrisSkin, but most of them cost money and all you might want to do is change a few things without overhauling everything that is Windows UI.

Replacing Your Lamp Because Your Light Bulb Burnt Out

Alek Trebec - "Answer: All the things that make windows behave like windows."

Contestant - "What are 'things I took for granted'?" 

Just a forewarning: you are opening somewhat of a can of worms here; albeit a small one. When you skin an app, there will be many things that you got for free before but will now have to do yourself. Window-close, -minimize, and -maximize are gone but easy enough to correct.

Window dragging requires a little bit of finesse and window resizing requires a bit more. I'll explain all that with sample code below, but first, let's talk about getting rid of that awful XP-blue menu-bar.

Removing the Menu-Bar

After you have your project set up and are looking at your form in design-time. Open the Properties pane form the main form object. Here, you need to find the Appearance labeled "FormBorderStyle". Change it from "Sizable" to "None". You will notice that in the design, your top menu bar and the border of the window are now gone! Excellent, a clean slate.

Readding Basic Window Controls

You can add all or none of these, but I suggest (for the sake of your users) that you at least add a close button. Here's what you do.

Close

Create a button in the upper right hand corner of your form; the location is by convention, you can put it anywhere you'd like of course. Change the name of the button to closeButton and double-click it so that Visual Studio brings you to the code view and creates you a OnClick event handler. Add the following code to it:

private void closeButton_Click(object sender, EventArgs e)
{
   Application.Exit();
}

Very simple.

Minimize

Create another button on your form and change its name to minButton. Double click it to open code view to its OnClick event handler. Add the following code to that one:

private void minButton_Click(object sender, EventArgs e) {
   WindowState = FormWindowState.Minimized;
}

Maximized / Normal

There are two ways to do a "Maximize" button because you either have to change the button after it is maximized to a "Normal" button or you have to make the button's label generic enough that the one button can be used for both tasks. To keep things simple, we will do the latter.

Create a button and change its name to maxNormButton and double click it like the last two examples. Add the following code to your event handler:

private void maxNormButton_Click(object sender, EventArgs e) {

   if (WindowState == FormWindowState.Maximized)
   {
      WindowState = FormWindowState.Normal;
   }
   else
   {
      WindowState = FormWindowState.Maximized;
   }
}

I will leave creating that generic-enough label up to you because I can't really think of anything right now. How about a ~ (tilde). That's never used enough.

Moving that Window Around

To move your window around, you will need to handle the mouse down event as well as the mouse move event. We'll have to do some simple math to determine the offset from where the mouse was clicked to where it is after it has moved. But before we can do anything with that, we need to add something to the form to grab on to.

You can either add a PictureBox or a Panel to your form. If you're looking to put something other than a solid color in the area, you'll want to go with the PictureBox, otherwise just drop a simple Panel up there and change it's BackColor property to something other than Control-gray. I will be using the Panel in this example.

Add a Panel to your form and stretch it the length of your form placing it along your top edge. You can even Dock it on the top if you'd like, but if you want to add rounded corners, you wont want to do that. Change the name of the panel windowbarPanel.

Right click on your form and choose View Code. We need to capture when the user mouses down on your menu bar. Add a MouseDown event handler to the constructor of your form.

windowbarPanel.MouseDown += new MouseEventHandler(CaptureMouseDown);

The function that will handle the event, void CaptureMouseDown, needs to save the mouse's position relative to your form's upper left hand corner. And it should only do this if the left mouse button is pressed. Create a private member variable _OffsetPoint of type Point to store your mouse position in. In the end, it should look like this:

void CaptureMouseDown(object sender, MouseEventArgs e)
{
   if (e.Button == MouseButtons.Left)
      _OffsetPoint = new Point(e.X, e.Y);
}

Next we need to handle when the mouse is moved. Add a MouseMove event handler to the constructor of your form.

windowbarPanel.MouseMove += new MouseEventHandler(MoveWindow);

The function that will be handling this event, void MoveWindow, needs to offset the mouse's new position against it's MouseDown one and set the location of the window to that point. Again, only if the left mouse button is being pressed. Here it is:

void MoveWindow(object sender, MouseEventArgs e)
{
   if (e.Button == MouseButtons.Left)
   {
      Point mousePos = Control.MousePosition;
      mousePos.Offset(-_OffsetPoint.X, -_OffsetPoint.Y);
      Location = mousePos;
   }
}

Save it and build it and you've got yourself a form that doesn't have traditional Windows form borders but behaves a lot like one! Now, if only we could resize this thing...

Resizing It

In traditional WinForms, you can grab any edge of a window and resize it. That's more than I want to get into in this post, so I'll only go over resizing your form from the lower left hand corner of the screen.

In design-view, create a Panel in the lower left making it the size of your desired click area. Mine is about the size of an eraser head. Name it resizePanel. 

In code-view, you need to capture the position of the mouse relative to the screen with a mouse down event handler and save the current size of the window. You'll have to create new member variables for the window's width and height (they're of type int) but we can reuse the _OffsetPoint from before since you can't resize and move a window at the same time. In the constructor of the form, add the following:

resizePanel.MouseDown += new MouseEventHandler(CaptureMousePosition);

The CaptureMousePosition method should look like this:

int _Width;
int _Height;

void CaptureMousePosition(object sender, MouseEventArgs e)
{
   if (e.Button == MouseButtons.Left)
   {
      _Width = Width;
      _Height = Height;
      _OffsetPoint = new Point(Control.MousePosition.X, Control.MousePosition.Y);
   }
}

Next, we need to set the width and height of the window every time the MouseMove event is fired. Add the following line to your form's constructor:

resizePanel.MouseMove += new MouseEventHandler(ResizeWindow);

The ResizeWindow method needs to figure out how far the mouse has moved and apply that to the width and height of the form. It should end up looking like this:

void ResizeWindow(object sender, MouseEventArgs e)
{
   if (e.Button == MouseButtons.Left)
   {
      Point mousePos = new Point(Control.MousePosition.X, Control.MousePosition.Y);
      mousePos.Offset(-_OffsetPoint.X, -_OffsetPoint.Y);
      Width = _Width + mousePos.X;
      Height = _Height + mousePos.Y;
   }
}

That's about it. It could definitely use some work, but it's a great start.

Just to Recap

With the information above, you can now create applications that have custom border elements. We had to rewrite the most basic window functions, but that's OK. It wasn't that painful. I hope to see better-looking WinForm applications from all of you. The only piece that's up to you is embedding beautiful Photoshopped graphics for all of your buttons and borders.

Update

I've added some pictures that go along with the process. Also, here are a few notes about anchoring your new elements correctly. If you don't correctly anchor, your resizing will not work correctly.

Your close, minimize, and maximize buttons should be anchored to the upper right. Your windowBarPanel should be anchored to the left-top-right so it stretches accordingly. Lastly, your resizePanel should be anchored to the bottom right so that it moves with your mouse as you resize.

Here's a sample project that does everything we've talked about in this blog:

SkinFormsBlog.zip
 

Filed under: ,

Comments

# DotNetKicks.com said on May 9, 2008 11:14 AM:

You've been kicked (a good thing) - Trackback from DotNetKicks.com

# Jake Opines said on May 19, 2008 7:54 AM:

If it looks like a duck, quacks like a duck, and waddles like a duck, then it's probably a duck. But

# Jake Opines said on May 19, 2008 7:59 AM:

If it looks like a duck, quacks like a duck, and waddles like a duck, then it's probably a duck. But

Anonymous comments are disabled