Windows Forms Multiple Controls In One - c#

I would like to ask for some advice. I don't have enough experience in windows forms programming so I don't know what is the appropriate way of dealing with this task.
I am currently creating a rectangle from four panels. (these rectangles do not represent the code below, they are using different sizing)
private System.Windows.Forms.Panel rectangleLeftVertical;
private System.Windows.Forms.Panel rectangleRightVertical;
private System.Windows.Forms.Panel rectangleTopHorizontal;
private System.Windows.Forms.Panel rectangleBottomHorizontal;
// ...
this.rectangleLeftVertical.Location = new System.Drawing.Point(100, 100);
this.rectangleLeftVertical.Size = new System.Drawing.Size(3, 100);
this.rectangleRightVertical.Location = new System.Drawing.Point(200, 100);
this.rectangleRightVertical.Size = new System.Drawing.Size(3, 100);
this.rectangleTopHorizontal.Location = new System.Drawing.Point(100, 100);
this.rectangleTopHorizontal.Size = new System.Drawing.Size(100, 3);
this.rectangleBottomHorizontal.Location = new System.Drawing.Point(100, 200);
this.rectangleBottomHorizontal.Size = new System.Drawing.Size(103, 3);
It is working exactly as I want it to, I would just like to encapsulate everything into a custom Windows Control.
The custom component size should of course resize the panels. It will also have a property that dictates border size.
I do not want to change anything, I don't want to do the drawing differently (using graphics.paint solutions). It is not appropriate for my use case.
I tried using a UserControl, however, it is not appropriate because it paints the entire inside of the rectangle - which is what I am trying to avoid.
What would be even better is if it could also be used in the Windows Forms Designer - but this is very unnecessary. It would be really nice though.
How do you guys recommend I tackle this? Any help would be much appreciated. It is likely not a difficult problem, I just lack experience. Thanks!

You can achieve this by creating a GraphicsPath object representing the areas of the form you want to keep/discard. Start with a Rectangle that covers the entire form, then remove the middles of the panels, leaving just the borders. Then build a Region from that GraphicsPath and assign it to the Region property of your Form. This will result in a Form that only exists where the borders are. The middle areas that were removed from the GraphicsPath will literally NOT EXIST in your form, thus anything below your overlay will show right on through.
Here's a quick example that makes a four pane "window" that can be dragged around by the borders of the panes:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.TopMost = true;
this.BackColor = Color.Red; // just so you can see it better
this.FormBorderStyle = FormBorderStyle.None;
}
private void Form1_Shown(object sender, EventArgs e)
{
GraphicsPath path = new GraphicsPath();
// add the main rectangle:
path.AddRectangle(new Rectangle(new Point(0, 0), this.Size));
// punch some holes in our main rectangle
// this will make a standard "windowpane" with four panes
// and a border width of ten pixels
Size sz = new Size((this.Width - (3 * 10))/2, (this.Height - (3 * 10))/2);
path.FillMode = FillMode.Alternate;
path.AddRectangle(new Rectangle(new Point(10, 10), sz));
path.AddRectangle(new Rectangle(new Point(20 + sz.Width, 10), sz));
path.AddRectangle(new Rectangle(new Point(10, 20 + sz.Height), sz));
path.AddRectangle(new Rectangle(new Point(20 + sz.Width, 20 + sz.Height), sz));
// build a region from our path and set the forms region to that:
this.Region = new Region(path);
}
public const int HTCAPTION = 0x2;
public const int WM_NCHITTEST = 0x84;
public const int HTCLIENT = 1;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (m.Msg == WM_NCHITTEST)
{
if (m.Result.ToInt32() == HTCLIENT)
m.Result = (IntPtr)HTCAPTION;
}
}
}
Example of it running on top of this page as I edit it:
If your overlay can be resized and/or adjusted, just rebuild a new GraphicsPath, change up the rectangles and re-assign the Region built from the new GraphicsPath.
You could also drop the .Opacity of the main form and you'd be able to partially see through your borders.

Related

PaintEvent on DataGridView does not paint on the ScrollBar

i'm trying to make a graphical enhancement/makeup to my current module. Admittedly, i like Windows 10's 1px (i think?) borders for application windows. The thing is, the facility i'm working at is still utilizing computers/laptops running Windows 7/8/8.1 Operating Systems - which i'd not fancy with their old thick-borders. So what i do is i'm recreating this UI Candy towards my WinForm-based module.
Basically, i removed the borders on my Form. Then i made use of the Paint Event Handler to paint lines with 1px on height or width. I have been successful so far, specially in stacked controls like Form->Panel->Panel - with each of them i've individually painted lines to the top, left, right, or bottom sides.
Now my next struggle is with DataGrid Control (i would also forecast that other controls with ScrollBars might have the same case) - which doesn't paint the line in the whole control. Basically, when i paint the line, it only paints it on the Grid's Cells, excluding the scroll bar on the right (probably if there's a bottom scrollbar too). I've tried looking for solutions to this but to no avail, which one solution said was to slap a panel atop the control which i think is not a good idea.
Refer to the below image for the problem i'm looking for a solution, you can notice that the DataGridView's ScrollBar doesn't seem to have had borders painted.
i would gladly appreciate if someone can lend me a hand here, thanks.
below is my code for painting the 1px border, i actually turned it into a static method so i just had to call it when needed - reducing repeated codes:
public static void DrawBorder(Control control, Color penColor, params BorderLocation[] borderLocations)
{
var pen = new Pen(penColor);
var pointA = new Point(0, 0);
var pointB = new Point(0, 0);
control.Paint += (sender, e) => {
foreach(BorderLocation borderLocation in borderLocations)
{
switch(borderLocation)
{
case BorderLocation.Left:
pointA = new Point(0, 0);
pointB = new Point(0, control.Height);
break;
case BorderLocation.Right:
pointA = new Point(control.Width - 2, 0);
pointB = new Point(control.Width - 2, control.Height);
break;
case BorderLocation.Top:
pointA = new Point(0, 0);
pointB = new Point(control.Width, 0);
break;
case BorderLocation.Bottom:
pointA = new Point(0, control.Height - 2);
pointB = new Point(control.Width, control.Height - 2);
break;
default:
pointA = new Point(-1, -1);
pointB = new Point(-1, -1);
break;
}
e.Graphics.DrawLine(pen, pointA, pointB);
}
};
}
and i would call it like:
UITools.DrawBorder(dGridWipedPhones, Color.FromArgb(76, 175, 80), BorderLocation.Right, BorderLocation.Bottom);

How to make the underlying control visible instead of form when set the trasparent backcolor

I have added two custom controls in the form as one control placed over the another control. Set the backcolor as trasparent for control1 but it shows the back color as form color instead of underlying control(control2) color. Please share your ideas . Thanks in advance.
Note : For example i have mentioned as picturebox but same problem raises for any controls such as richtextbox or placing the custom controls.
Image link : IssueImage
private void InitializeComponent()
{
#region picturebox
this.BackColor = Color.Aquamarine;
this.WindowState = FormWindowState.Normal;
var selectBtn = new Button();
selectBtn.Size = new Size(100, 30);
selectBtn.Location = new Point(10, 10);
selectBtn.Text = "Click";
//selectBtn.Click += selectBtn_Click;
var picturebox = new PictureBox();
picturebox.Size = new Size(140, 110);
picturebox.Location = new Point(4, 4);
picturebox.SizeMode = PictureBoxSizeMode.StretchImage;
picturebox.Image = new Bitmap(#"..\..\Data\graphic1.png");
var picturebox2 = new PictureBox();
picturebox2.Size = new Size(140, 110);
picturebox2.Location = new Point(4, 4);
picturebox2.SizeMode = PictureBoxSizeMode.StretchImage;
picturebox2.Image = new Bitmap(#"..\..\Data\graphic1.png");
graphiccell = new GraphicCellControl();
graphiccell.Location = new Point(50, 200);
graphiccell.BackColor = Color.Transparent;
graphiccell.Size = new Size(160, 130);
var graphiccell2 = new GraphicCellControl();
graphiccell2.Location = new Point(100, 250);
graphiccell2.BackColor = Color.Red;
graphiccell2.Size = new Size(160, 130);
// graphiccell2.BackColor = Color.Transparent;
graphiccell.Controls.Add(picturebox);
graphiccell2.Controls.Add(picturebox2);
this.Controls.Add(graphiccell);
this.Controls.Add(graphiccell2);
this.Controls.Add(selectBtn);
#endregion
}
public class GraphicCellControl : Control
{
public GraphicCellControl()
{
SetStyle(ControlStyles.SupportsTransparentBackColor, true);
}
}
Transparency in Windows Forms follows the rules for Windows windows (ugh). This means that by default, you only get "proper" transparency when you're dealing with the parent-child relationship. Controls behind a transparent control will only be drawn if they are parents of said transparent control.
If this isn't feasible for you for some reason, you can always override the OnPaint or OnPaintBackground methods, and explicitly render whatever control you need to render regardless of the parent-child relationship. Of course, you'll quickly find why this isn't done by default if you can't do a few simplifying assumptions :)
As a quick list of what you need to do when there aren't any simplifications possible:
Find controls that are behind the transparent control - this is especially tricky when there isn't any spatial partitioning (like the mentioned parent-child relationship)
Render the relevant surfaces of the controls from 1. in back to front order on the current control's surface
Optimally, ensure that all the overlapping transparent controls avoid rendering the same controls multiple times
Ensure that all operations that can potentially change the background of the transparent control cause an invalidation (and thus re-render) of the transparent control's background

Custom control onPaint event not working

Hey people I have a problem I am writing a custom control. My control inherits from Windows.Forms.Control and I am trying to override the OnPaint method. The problem is kind of weird because it works only if I include one control in my form if I add another control then the second one does not get draw, however the OnPaint method gets called for all the controls. So what I want is that all my custom controls get draw not only one here is my code:
If you run the code you will see that only one red rectangle appears in the screen.
public partial class Form1 : Form
{
myControl one = new myControl(0, 0);
myControl two = new myControl(100, 0);
public Form1()
{
InitializeComponent();
Controls.Add(one);
Controls.Add(two);
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class myControl:Control
{
public myControl(int x, int y)
{
Location = new Point(x, y);
Size = new Size(100, 20);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Pen myPen = new Pen(Color.Red);
e.Graphics.DrawRectangle(myPen, new Rectangle(Location, new Size(Size.Width - 1, Size.Height - 1)));
}
}
I'm guessing you are looking for something like this:
e.Graphics.DrawRectangle(Pens.Red, new Rectangle(0, 0,
this.ClientSize.Width - 1,
this.ClientSize.Height - 1));
Your Graphic object is for the interior of your control, so using Location isn't really effective here. The coordinate system starts at 0,0 from the upper-left corner of the client area of the control.
Also, you can just use the built-in Pens for colors, otherwise, if you are creating your own "new" pen, be sure to dispose of them.
LarsTech beat me to it, but you should understand why:
All drawing inside of a control is made to a "canvas" (properly called a Device Context in Windows) who coordinates are self-relative. The upper-left corner is always 0, 0.
The Width and Height are found in ClientSize or ClientRectangle. This is because a window (a control is a window in Windows), has two areas: Client area and non-client area. For your borderless/titlebar-less control those areas are one and the same, but for future-proofing you always want to paint in the client area (unless the rare occasion occurs where you want to paint non-client bits that the OS normally paints for you).

How to draw position sign?

I writing my own textbox control in C# for Winforms.
One thing i can't figure out: how to draw the text position sign in various sizes?
It is called the 'caret'. The winapi functions are not wrapped by winforms, you'll have to pinvoke them. Start reading here. You'll find code in my answer here.
Try this.
I created a method which is meant to be called from the paint handler of whichever control you're drawing in. For simplicity I just used the form itself in mine. You probably have a panel or some other control.
The method accepts the graphics object, the scale of the cursor and the upper/left position of where to start drawing. The scale is just the height but all the math is performed relative to the height. You can tweak those numbers any way you want.
private void Form1_Paint(object sender, PaintEventArgs e)
{
DrawCaret(e.Graphics, 30, new Point(20, 20));
DrawCaret(e.Graphics, 50, new Point(100, 100));
}
private static void DrawCaret(Graphics g, int scale, Point loc)
{
g.SmoothingMode = SmoothingMode.HighQuality;
int height = scale;
int width = scale/10;
int rectBase = scale/5;
g.FillRectangle(Brushes.Black, loc.X, loc.Y, width, height);
var path = new GraphicsPath();
path.AddPolygon(new[]
{
new Point(loc.X+width, loc.Y),
new Point(loc.X+width+rectBase/2, loc.Y+rectBase/2),
new Point(loc.X+width, loc.Y+rectBase),
});
g.FillPath(Brushes.Black, path);
}
This sample produces the following output:

Drop shadow in Winforms Controls?

is there a way to add a drop shadow to controls?
are there any controls out there with this feature?
You have to overwrite the CreateParamsproperty like this:
private const int CS_DROPSHADOW = 0x00020000;
protected override CreateParams CreateParams
{
get
{
// add the drop shadow flag for automatically drawing
// a drop shadow around the form
CreateParams cp = base.CreateParams;
cp.ClassStyle |= CS_DROPSHADOW;
return cp;
}
}
This question has been around for 6 years and needs an answer. I hope that anyone who needs to do this can extrapolate an answer for any control set from my solution. I had a panel and wanted to draw a drop shadow underneath every child control - in this instance one or more panels (but the solution should hold good for other control types with some minor code changes).
As the drop shadow for a control has to be drawn on the surface of that control's container we start by adding a function to the container's Paint() event.
Container.Paint += dropShadow;
dropShadow() looks like this:
private void dropShadow(object sender, PaintEventArgs e)
{
Panel panel = (Panel)sender;
Color[] shadow = new Color[3];
shadow[0] = Color.FromArgb(181, 181, 181);
shadow[1] = Color.FromArgb(195, 195, 195);
shadow[2] = Color.FromArgb(211, 211, 211);
Pen pen = new Pen(shadow[0]);
using (pen)
{
foreach (Panel p in panel.Controls.OfType<Panel>())
{
Point pt = p.Location;
pt.Y += p.Height;
for (var sp = 0; sp < 3; sp++)
{
pen.Color = shadow[sp];
e.Graphics.DrawLine(pen, pt.X, pt.Y, pt.X + p.Width - 1, pt.Y);
pt.Y++;
}
}
}
}
Clearly you can pick a different control type from the container's collection and you can vary the colour and depth of the shadow with some minor tweaks.
The top answer does in fact generate a shadow, but I personally wasn't satisfied with it for a few reasons:
It only works for rectangles (granted, WinForms controls are all rectangles, but we might want to use this in other cases)
More importantly: It's not smooth. It doesn't look as natural as other shadows in other programs look.
Finally, it's slightly annoying to configure.
So, because of all these things, I ended up writing my own for my project and I thought I'd share it here:
public partial class Form1 : Form
{
List<Control> shadowControls = new List<Control>();
Bitmap shadowBmp = null;
public Form1()
{
InitializeComponent();
shadowControls.Add(panel1);
this.Refresh();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (shadowBmp == null || shadowBmp.Size != this.Size)
{
shadowBmp?.Dispose();
shadowBmp = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppArgb);
}
foreach (Control control in shadowControls)
{
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddRectangle(new Rectangle(control.Location.X, control.Location.Y, control.Size.Width, control.Size.Height));
DrawShadowSmooth(gp, 100, 60, shadowBmp);
}
e.Graphics.DrawImage(shadowBmp, new Point(0, 0));
}
}
private static void DrawShadowSmooth(GraphicsPath gp, int intensity, int radius, Bitmap dest)
{
using (Graphics g = Graphics.FromImage(dest))
{
g.Clear(Color.Transparent);
g.CompositingMode = CompositingMode.SourceCopy;
double alpha = 0;
double astep = 0;
double astepstep = (double)intensity / radius / (radius / 2D);
for (int thickness = radius; thickness > 0; thickness--)
{
using (Pen p = new Pen(Color.FromArgb((int)alpha, 0, 0, 0), thickness))
{
p.LineJoin = LineJoin.Round;
g.DrawPath(p, gp);
}
alpha += astep;
astep += astepstep;
}
}
}
}
In this implementation, all Controls added to the shadowControls will be painted with a smooth shadow. You should be able to implement this for non-rectangular shapes because the main function to generate the shadows takes a GraphicsPath. Please note that it's important you draw the shadow to another bitmap before drawing it to the form because the main function requires a compositing mode of SourceCopy to work, which means if you don't draw it to another surface first anything behind the shadow will be completely replaced and the transparency aspect is useless. I'm on a roll of answering 10-year-old questions, but hopefully, this helps someone!
There is in WPF if you can stretch to using that instead, I don't believe there is an alternative in Windows Forms due to the limited capabilities of GDI+.
Here's a controversial opinion, you do it without code.
Set your main panel Border Style to Fixed Single.
Create 3 panels below it, each 1 pixel larger in every direction.
Each of the 3 panels is of a lighter shade of gray.
Not perfect but cheap and easy.
panel with pseudo-shadow

Categories