Introduction
In Visual Studio, you can choose between the "old fashioned" Windows Forms and the better looking WPF. If you prefer to work with Windows Forms however,
you can still make your application look very fancy. In this article, I will show a tool to create some graphical parts programmatically. It is limited to a box that
can show text or a picture, but it might give you some inspiration to create your own graphical objects.
I think this code is not very difficult if you have ever used GDI+. To freshen up: if an Image is a painting, then a Bitmap is the canvas with the paint,
and Graphics are your brushes (and the artist is you, of course).
The class GraphicTools
is able to create boxes like the one on the left in this picture:
Using the Code
Parameters include the image where you would like to draw the box, the text inside the box, the location and size of the box, the width of the border (here 16),
and finally the four colors you want to use. (This function is overloaded with a very similar one that puts an image in the box.)
public static Image DrawBox(this Image image, string text, Point p, Size size, int border,
Color foreColor, Color backColor, Color fillColor, Color textColor)
{
image.CreateBorders(p, size, border, foreColor, backColor);
using (var g = Graphics.FromImage(image))
{
g.FillRectangle(new SolidBrush(fillColor),
new Rectangle(p.X + border, p.Y + border, size.Width
- (2 * border), size.Height - (2 * border)));
StringFormat stringFormat = new StringFormat();
stringFormat.Alignment = StringAlignment.Center;
stringFormat.LineAlignment = StringAlignment.Center;
g.DrawString(text, new Font("Arial", 10, FontStyle.Bold),
new SolidBrush(textColor), p.X + (size.Width / 2),
p.Y + (size.Height / 2), stringFormat);
}
return image;
}
The most important part is the creation of the borders. The four corner pieces are created using a gradient filled circle (foreColor
in the middle,
backColor
on the outside).
private static Image CornerBox(int border, Color foreColor, Color backColor)
{
Image cornerImage = CreateImage(new Size(border * 2, border * 2), backColor);
using (var g = Graphics.FromImage(cornerImage))
{
var p = new GraphicsPath();
p.AddEllipse(0, 0, border * 2, border * 2);
var pgb = new PathGradientBrush(p);
pgb.SurroundColors = new Color[] { backColor };
pgb.CenterColor = foreColor;
g.FillRectangle(pgb, 0, 0, border * 2, border * 2);
}
return cornerImage;
}
Each quadrant of this circle forms one of the corners of the box, so in the CreateBorders
function, we can use:
private static Image CreateBorders(this Image image, Point p, Size size,
int border, Color foreColor, Color backColor)
{
Image cornerBox = CornerBox(border, foreColor, backColor);
image.Merge(p, cornerBox,
new Rectangle(0, 0, border, border)); image.Merge(new Point(p.X + size.Width - border, p.Y), cornerBox,
new Rectangle(border, 0, border, border)); image.Merge(new Point(p.X, p.Y + size.Height - border), cornerBox,
new Rectangle(0, border, border, border)); image.Merge(new Point(p.X + size.Width - border,
p.Y + size.Height - border), cornerBox,
new Rectangle(border, border, border, border));
The box also contains four rectangular pieces, so this function continues:
if (size.Width > (2 * border))
{
var rectangle = new Rectangle(0, 0, size.Width - (2 * border), border);
using (var img = CreateImage(
new Size(rectangle.Width, rectangle.Height), backColor))
{
using (var g = Graphics.FromImage(img))
{
g.FillRegion(new LinearGradientBrush(rectangle,
backColor, foreColor, 90f), new Region(rectangle));
image.Merge(new Point(p.X + border, p.Y), img, rectangle);
g.FillRegion(new LinearGradientBrush(rectangle,
foreColor, backColor, 90f), new Region(rectangle));
image.Merge(new Point(p.X + border, p.Y + size.Height - border)
, img, rectangle);
}
}
}
The way the different parts of the box are created might seem a little bit strange: instead of immediately drawing on the image of the box, smaller images are created, drawn upon,
and then merged with the image of the box. This is because of the behavior of the LinearGradientBrush
: it does not start with the first color on the place where you start drawing,
but at the beginning of the image on which you draw. This seemed the most practical solution, but I admit it might not be the most elegant one.
There are two rather technical functions used: CreateImage
, which creates an image of a certain size by creating a bitmap of that size (as Image
is in fact an abstract class), and Merge
, which puts the smaller image parts on the box. You can find them in the download of course.
Can the result indeed be as fancy as the title of this article predicts? Well, let's make a little application that creates a visual representation of a spider graph.
I made a class of animal (how original), with the name of the animal and a picture as fields. Let's fill up a list of animals:
private void FillAnimalList()
{
string fileLoc = Application.StartupPath;
Animals.Add(new Animal("Tiger",
Image.FromFile(fileLoc + "\\tiger.bmp")));
Animals.Add(new Animal("Black panther",
Image.FromFile(fileLoc + "\\black panther.bmp")));
Animals.Add(new Animal("Lion",
Image.FromFile(fileLoc + "\\lion.bmp")));
Animals.Add(new Animal("Leopard",
Image.FromFile(fileLoc + "\\leopard.bmp")));
Animals.Add(new Animal("Bobcat",
Image.FromFile(fileLoc + "\\bobcat.bmp")));
Animals.Add(new Animal("Jaguar",
Image.FromFile(fileLoc + "\\jaguar.bmp")));
}
"Tiger" is my main actor here, the others are his friends. I want each animal to be represented by a box containing its picture and a smaller box with its name.
private static Image DrawAnimal(Image imageToDrawOn, Image imageOfAnimal,
string name, Point location, Color color)
{
imageToDrawOn.DrawBox(imageOfAnimal,
new Point(location.X-80, location.Y-96),
new Size(160, 160), 16, color, Color.Black);
imageToDrawOn.DrawBox(name,
new Point(location.X-80, location.Y + 64),
new Size(160, 32), 16, color, Color.Black, color, Color.Black);
return imageToDrawOn;
}
"Tiger" has to be in the middle, with a connection to each friend. We draw this on a PictureBox
:
private void frmMain_Load(object sender, EventArgs e)
{
FillAnimalList();
pictureBox.Image = GraphicTools.CreateImage(pictureBox.Size, Color.Black);
int mX = pictureBox.Width/2;
int mY = pictureBox.Height/2;
List<Point> pointList = new List<Point>();
pointList.Add(new Point(mX, mY));
int distance = pictureBox.Height / 3;
double cornerBetweenAnimals = (2 * Math.PI) / (Animals.Count - 1);
using(var g = Graphics.FromImage(pictureBox.Image))
{
g.SmoothingMode = SmoothingMode.AntiAlias;
for (int i = 0; i < 5; i++)
{
Point p = new Point(
(int)(mX - (distance * Math.Cos((Math.PI/4)
+ (cornerBetweenAnimals * i)))),
(int)(mY - (distance * Math.Sin((Math.PI/4)
+ (cornerBetweenAnimals * i)))));
pointList.Add(p);
var pen = new Pen(new LinearGradientBrush(
pointList[0],p, Color.Blue, Color.Black));
pen.Width = 10;
g.DrawLine(pen, pointList[0], p);
}
}
for (int i = 0; i < 6; i++)
{
pictureBox.Image = DrawAnimal(pictureBox.Image,
Animals[i].image, Animals[i].name,
pointList[i], Color.Blue);
}
}
And that results in this beautiful friendship:
Points of Interest
I did learn something about big cats while making the example.
History
First version.