Irregular Shaped Windows Form (C#)

Irregular shaped Windows Form (C#)

this.BackgroundImage = //Image
this.FormBorderStyle = FormBorderStyle.None;
this.Width = this.BackgroundImage.Width;
this.Height = this.BackgroundImage.Height;
this.TransparencyKey = Color.FromArgb(0, 255, 0); //Contrast Color

This allows you to create a form based on an image, and use transparency index to make it seem as though the form is not rectangular.

Create a custom shaped windows form instead of 'default' rectangle?

The TransparencyKey approach, discussed here on MSDN is the simplest way to do this. You set your form's BackgroundImage to an image mask. The image mask has the regions to be transparent filled with a certain color—fuchsia is a popular choice, since no one actually uses this horrible color. Then you set your form's TransparencyKey property to this color, and it is essentially masked out, rendering those portions as transparent.

But I guess in a color picker, you want fuchsia to be available as an option, even if no one ever selects it. So you'll have to create custom-shaped forms the other way—by setting a custom region. Basically, you create a Region object (which is basically just a polygon) to describe the desired shape of your form, and then assign that to the form's Region property.

Do note that you are changing the shape of the entire window when you do this, not just the client area, so your design needs to account for that. Also, regions cannot be anti-aliased, so the result tends to be pretty ugly if you're using a shape that does not have straight edges.

And another caveat…I strongly recommend not doing this. It takes quite a bit of work to get it right, and even once you get finished, the result is usually gaudy and user-hostile. Even when everything goes just right, you'll end up with something that looks like this—and no one wants that. Users are quite accustomed to boring old rectangular application windows. Applications shouldn't try to be exact digital replicas of real-world widgets. It seems like that would make them intuitive or easy to use, but it really doesn't. The key to good design is identifying the user's mental model for your application and figuring out a good way of meshing that with the standards set by your target windowing environment.


I noticed this tab still open and had a few spare moments, so I tried to bang out a quick sample. I made the "form" consist of two randomly-sized circles, just to emphasize the custom shape effect and the transparency—don't read anything into the design or get any crazy ideas! Here's what I came up with:

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;

public class MyCrazyForm : Form
{
private Size szFormSize = new Size(600, 600);
private Size szCaptionButton = SystemInformation.CaptionButtonSize;
private Rectangle rcMinimizeButton = new Rectangle(new Point(330, 130), szCaptionButton);
private Rectangle rcCloseButton = new Rectangle(new Point(rcMinimizeButton.X + szCaptionButton.Width + 3, rcMinimizeButton.Y), SystemInformation.CaptionButtonSize);

public MyCrazyForm()
{
// Not necessary in this sample: the designer was not used.
//InitializeComponent();

// Force the form's size, and do not let it be changed.
this.Size = szFormSize;
this.MinimumSize = szFormSize;
this.MaximumSize = szFormSize;

// Do not show a standard title bar (since we can't see it anyway)!
this.FormBorderStyle = FormBorderStyle.None;

// Set up the irregular shape of the form.
using (GraphicsPath path = new GraphicsPath())
{
path.AddEllipse(0, 0, 200, 200);
path.AddEllipse(120, 120, 475, 475);
this.Region = new Region(path);
}
}

protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);

// Force a repaint on activation.
this.Invalidate();
}

protected override void OnDeactivate(EventArgs e)
{
base.OnDeactivate(e);

// Force a repaint on deactivation.
this.Invalidate();
}

protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);

// Draw the custom title bar ornamentation.
if (this.Focused)
{
ControlPaint.DrawCaptionButton(e.Graphics, rcMinimizeButton, CaptionButton.Minimize, ButtonState.Normal);
ControlPaint.DrawCaptionButton(e.Graphics, rcCloseButton, CaptionButton.Close, ButtonState.Normal);
}
else
{
ControlPaint.DrawCaptionButton(e.Graphics, rcMinimizeButton, CaptionButton.Minimize, ButtonState.Inactive);
ControlPaint.DrawCaptionButton(e.Graphics, rcCloseButton, CaptionButton.Close, ButtonState.Inactive);
}
}

private Point GetPointFromLParam(IntPtr lParam)
{
// Handle 64-bit builds, which we detect based on the size of a pointer.
// Otherwise, this is functionally equivalent to the Win32 MAKEPOINTS macro.
uint dw = unchecked(IntPtr.Size == 8 ? (uint)lParam.ToInt64() : (uint)lParam.ToInt32());
return new Point(unchecked((short)dw), unchecked((short)(dw >> 16)));
}

protected override void WndProc(ref Message m)
{
const int WM_SYSCOMMAND = 0x112;
const int WM_NCHITTEST = 0x84;
const int WM_NCLBUTTONDOWN = 0xA1;
const int HTCLIENT = 1;
const int HTCAPTION = 2;
const int HTMINBUTTON = 8;
const int HTCLOSE = 20;

// Provide additional handling for some important messages.
switch (m.Msg)
{
case WM_NCHITTEST:
{
base.WndProc(ref m);

Point ptClient = PointToClient(GetPointFromLParam(m.LParam));
if (rcMinimizeButton.Contains(ptClient))
{
m.Result = new IntPtr(HTMINBUTTON);
}
else if (rcCloseButton.Contains(ptClient))
{
m.Result = new IntPtr(HTCLOSE);
}
else if (m.Result.ToInt32() == HTCLIENT)
{
// Make the rest of the form's entire client area draggable
// by having it report itself as part of the caption region.
m.Result = new IntPtr(HTCAPTION);
}

return;
}
case WM_NCLBUTTONDOWN:
{
base.WndProc(ref m);

if (m.WParam.ToInt32() == HTMINBUTTON)
{
this.WindowState = FormWindowState.Minimized;
m.Result = IntPtr.Zero;
}
else if (m.WParam.ToInt32() == HTCLOSE)
{
this.Close();
m.Result = IntPtr.Zero;
}

return;
}
case WM_SYSCOMMAND:
{
// Setting the form's MaximizeBox property to false does *not* disable maximization
// behavior when the caption area is double-clicked.
// Since this window is fixed-size and does not support a "maximized" mode, and the
// entire client area is treated as part of the caption to enable dragging, we also
// need to ensure that double-click-to-maximize is disabled.
// NOTE: See documentation for WM_SYSCOMMAND for explanation of the magic value 0xFFF0!
const int SC_MAXIMIZE = 0xF030;
if ((m.WParam.ToInt32() & 0xFFF0) == SC_MAXIMIZE)
{
m.Result = IntPtr.Zero;
}
else
{
base.WndProc(ref m);
}
return;
}
}
base.WndProc(ref m);
}
}

Here it is running on Windows XP and 7, side-by-side:

     

Whew! It does work, but it's a long way from complete. There are lots of little things that still need to be done. For example:

  • The caption buttons do not "depress" when clicked. There is a built-in state for that that can be used with the DrawCaptionButton method, but you need to either force a redraw when one of the buttons is clicked, or do the repaint directly on the form right then and there.
  • It doesn't support Visual Styles. This is a limitation of the ControlPaint class; it was written before Visual Styles were invented. Implementing support for this will be a lot more work, but there is a WinForms wrapper. You will have to make sure that you write fallback code to handle the case where Visual Styles are disabled, too.
  • The caption buttons aren't actually centered—I just eyeballed it. And even if your eyeballs are better than mine, this is still a bad approach, because the caption buttons can be different sizes, depending on system settings and which version of the OS you're running (Vista changed the button shapes).
  • Other windows invoke the actions when the mouse goes up over the caption bar buttons. But when you try to use WM_NCLBUTTONUP (instead of WM_NCLBUTTONDOWN), you have to double-click the caption buttons to make them work. This is because the non-client area is capturing the mouse. I'm sure there's a solution, but I ran out of patience before I discovered what it was.
  • You don't get the pretty animation effects when the window is minimized (or restored), nor do you have the glow-on-hover for the caption buttons. There are tons of visual niceties that you get for free with the default styles that are missing-in-action here. Some of them can be easily added by writing more code, but for each line you write, the maintenance burden skyrockets—newer versions of Windows are likely to break things. And worse, some things are far from trivial to implement, so it probably isn't even worth it. And all this effort for what, again?
  • Repainting the entire form on activation/deactivation just to update the caption buttons is probably a bad idea. If you are painting anything else more complicated on the form, this is likely to slow down the entire system.
  • Once you start adding controls to the form, you might run into a problem. For example, even with a Label control, you won't be able to drag the form around by clicking and holding on top of that Label control. Label controls don't return HTTRANSPARENT in response to the WM_NCHITTEST message, so the message doesn't get passed on to the parent form. You can subclass Label to do so and use your subclass instead.
  • The code is completely untested with Windows 8, since I don't have a copy. Custom non-client areas tend to blow up with new OS updates that change the way the non-client area is rendered, so you're on your own to adapt the code accordingly. Even if it works, it certainly won't have the right Windows 8 look-and-feel.
  • Et cetera, et cetera.

You can also see that, like I cautioned above, the circular border is not anti-aliased, so it looks jagged. Unfortunately, that is unfixable.

How to create a clickable irregularly-shaped region in c#

These are two approaches to this problem:

  • Work with Regions.

  • Work with transparent Images.

The first method involves creating controls, e.g PictureBoxes or Panels , which have the shape of the image and are only clickable inside that shape.

This is nice, provided you have access to the vector outline that makes up the shape.

Here is an example that restricts the visible&clickable Region of a Panel to an irregularly-shaped blob created from a list of trace points:

Sample Image

List<Point> points = new List<Point>();
points.Add(new Point(50,50));points.Add(new Point(60,65));points.Add(new Point(40,70));
points.Add(new Point(50,90));points.Add(new Point(30,95));points.Add(new Point(20,60));
points.Add(new Point(40,55));

using (GraphicsPath gp = new GraphicsPath())
{
gp.AddClosedCurve(points.ToArray());
panel1.Region = new Region(gp);
}

Unfortunately making a Region from the points contained in it will not work; imagine a Region as a list of vector shapes, these are made up of points, but only to create containing vectors, not pixels..

You could trace around the shapes but that is a lot of work, and imo not worth it.

So if you don't have vector shapes: go for the second method:

This will assume that you have images (probably PNGs), which are transparent at all spots where no clicks should be accepted.

The simplest and most efficient way will be to put them in a list together with the points where they shall be located; then, whenever they have changed, draw them all into one image, which you can assign to a PictureBox.Image.

Here is a Mouseclick event that will search for the topmost Image in a List of Images to find the one that was clicked. To combine them with their locations I use a Tuple list:

List<Tuple<Image, Point>> imgList = new List<Tuple<Image, Point>>();

We search through this list in each MouseClick:

private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
int found = -1;
// I search backward because I drew forward:
for (int i = imageList1.Images.Count - 1; i >= 0; i--)
{
Bitmap bmp = (Bitmap) imgList[i].Item1;
Point pt = (Point) imgList[i].Item2;
Point pc = new Point(e.X - pt.X, e.Y - pt.Y);
Rectangle bmpRect = new Rectangle(pt.X, pt.Y, bmp.Width, bmp.Height);
// I give a little slack (11) but you could also check for > 0!
if (bmpRect.Contains(e.Location) && bmp.GetPixel(pc.X, pc.Y).A > 11)
{ found = i; break; }
}

// do what you want with the found image..
// I show the image in a 2nd picBox and its name in the form text:
if (found >= 0) {
pictureBox2.Image = imageList1.Images[found];
Text = imageList1.Images.Keys[found];
}

}

Here is how I combined the images into one. Note that for testing I had added them to an ImageList object. This has serious drawbacks as all images are scaled to a common size. You probably will want to create a proper list of your own!

Bitmap patchImages()
{
Bitmap bmp = new Bitmap(pictureBox1.ClientSize.Width, pictureBox1.ClientSize.Height);
imgList.Clear();
Random R = new Random(1);

using (Graphics G = Graphics.FromImage(bmp) )
{
foreach (Image img in imageList1.Images)
{
// for testing: put each at a random spot
Point pt = new Point(R.Next(333), R.Next(222));
G.DrawImage(img, pt);
// also add to the searchable list:
imgList.Add(new Tuple<Image, Point>(img, pt));
}
}
return bmp;
}

I called it at startup :

private void Form1_Load(object sender, EventArgs e)
{
pictureBox1.Image = patchImages();
}

Aside: This way of drawing all the images into one, is also the only one that lets you overlap the images freely. Winforms does not support real transparency with overlapping Controls.. And testing one Pixel (at most) for each of your shapes is also very fast.

How to make a shaped windows form always position itself in the same place on another form?

I have solve the issue in the following code:

frm8.Location = themeButton.PointToScreen(new Point(Point.Empty.X-(int)(0.680*frm8.Size.Width), Point.Empty.Y+24));

see reference

The above code is better than the following code by which the form would change its position a little bit in accordance with the exact position being clicked.

this.Cursor = new Cursor(Cursor.Current.Handle);
frm8.Location = new Point(Cursor.Position.X - (int)(0.726 * frm8.Size.Width),
Cursor.Position.Y+15);

How can I make a windows application that doesn't have a square form shape?

Here's a tutorial on Creating Irregular Non-Rectangle Windows using WPF.

Consider this tutorial for creating Shaped Windows in WPF. It's got sample code in C#.

Redrawing and custom shaped windows question

Two ways to solve --

Using transparency: Irregular shaped Windows Form (C#)

Or using Control.Region which is the actual shaping of the window. Plenty of samples or:
How do I make a genuinely transparent Control?

protected override void OnSizeChanged(EventArgs e)
{
base.OnSizeChanged(e);

const int ArrowSize = 25;

Point[] points = new[] {
new Point(ArrowSize, 0),
new Point(this.Width, 0),
new Point(this.Width, this.Height),
new Point(ArrowSize, this.Height),
new Point(ArrowSize, ArrowSize),
new Point(0, ArrowSize/2)
// don't need - autocloses
// ,new Point(ArrowSize, 0)
};

GraphicsPath path = new GraphicsPath();
path.AddLines(points);
this.Region = new Region(path);
}

C#, WPF, Visual Studio 4: irregular shape color filling

Flood Fill Algoritms look like the way to go -

http://en.wikipedia.org/wiki/Flood_fill



Related Topics



Leave a reply



Submit