ControlPaint.DrawBorder3D vs. ControlPaint.DrawBorder

I investigated a strange issue this week and also asked the question on Stackoverflow: ControlPaint.DrawBorder3D wrong location and size when zooming. Thank you to the nice people who read my question, had patience until I explained what I meant and posted answers that lead to the final solution.

The problem

There is a difference in the position and size of the border with the methods ControlPaint.DrawBorder3D and ControlPaint.DrawBorder from System.Windows.Forms when scale transformation (or any other transformation for that matter, e.g. rotation) is applied on the Graphics instance.

The 3D border drawn by ControlPaint.DrawBorder3D stays at 100% independent of the scale factor in comparison to the border drawn by the method ControlPaint.DrawBorder which is adapted correctly according to the scale factor.

It seems that the method ControlPaint.DrawBorder3D uses always the classic design and ignores the transformations on the Graphic instance. This method doesn’t use the GDI+ Graphics object but instead only the GDI-compatible context.

I have made a small Windows Forms application to test this out. An image is displayed in the picture box and borders are drawn with the image’s bounds using the methods ControlPaint.DrawBorder3D and ControlPaint.DrawBorder. I can zoom in and out with the buttons.


private void pictureBox_Paint(object sender, PaintEventArgs e)
{
e.Graphics.ScaleTransform(zoom, zoom);
e.Graphics.DrawImage(image, 0, 0, image.Width, image.Height);
var units = GraphicsUnit.Pixel;
var rectangle = Rectangle.Round(image.GetBounds(ref units));
ControlPaint.DrawBorder3D(e.Graphics, rectangle, Border3DStyle.Sunken);
ControlPaint.DrawBorder(e.Graphics, rectangle, Color.Red, ButtonBorderStyle.Solid);

}

As you can see the image and the red border (ControlPaint.DrawBorder) is adjusted to the zoom factor but the 3D border stays the same (ControlPaint.DrawBorder3D).

Capture1

The solution

Solution 1: Scaling the border rectangle manually for ControlPaint.Draw3DBorder because the Windows’ renderer does not apply the transformation


private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
e.Graphics.ScaleTransform(zoom, zoom);
e.Graphics.DrawImage(image, 0, 0, image.Width, image.Height);
var units = GraphicsUnit.Pixel;
var rectangle = Rectangle.Round(image.GetBounds(ref units));
var scaledRectangle = ScaleMatrix(rectangle, e.Graphics);
ControlPaint.DrawBorder3D(e.Graphics, scaledRectangle, Border3DStyle.Sunken);
}
private static Rectangle ScaleMatrix(Rectangle rectangle, Graphics graphics)
{
if (rectangle.IsEmpty)
{
return rectangle;
}
float scaleFactor;
using (Matrix transform = graphics.Transform)
{
scaleFactor = transform.Elements[0];
}
rectangle.X = (int)(rectangle.X * scaleFactor);
rectangle.Y = (int)(rectangle.Y * scaleFactor);
rectangle.Width = (int)(rectangle.Width * scaleFactor);
rectangle.Height = (int)(rectangle.Height * scaleFactor);
return rectangle;
}

The picture box on the right shows the new code with the 3D border adapting correctly to the scale factor.

Untitled

Solution 2: Use ControlPaint.DrawBorder to draw the 3D border

This code sample below draws a 3D-looking border using the Inset option of the ButtonBorderStyle. Inset should be used for a sunken border and Outset is for a raised border. The colors that I used here are for the example only, using the system colors of Windows is recommended.


private void pictureBox2_Paint(object sender, PaintEventArgs e)
{
e.Graphics.ScaleTransform(zoom, zoom);
e.Graphics.DrawImage(image, 0, 0, image.Width, image.Height);
var units = GraphicsUnit.Pixel;
var rectangle = Rectangle.Round(image.GetBounds(ref units));
ControlPaint.DrawBorder(e.Graphics, rectangle,
Color.FromArgb(160, 160, 160), 2, ButtonBorderStyle.Inset,
Color.FromArgb(160, 160, 160), 2, ButtonBorderStyle.Inset,
Color.FromArgb(227, 227, 227), 2, ButtonBorderStyle.Inset,
Color.FromArgb(227, 227, 227), 2, ButtonBorderStyle.Inset);

}

Capture1