I happen to be trying to do the following:
A side menu with white background color, semi-transparent, but for example, if you click on an option (if selected) it becomes completely transparent allowing you to see the background image through it.
In the image, the Label1 option is selected, where the label box must be transparent, Label1 must continue to be displayed and the rest of the panel must continue with its corresponding semi-transparent white color ...
Well, to achieve this I first tried drawing two boxes on the Form, but it turns out that the transparent is never drawn:
public partial class Form1 : Form
{
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
var brush = new SolidBrush(Color.FromArgb(25, Color.White));
e.Graphics.FillRectangle(brush, new Rectangle(0, 0, 50, this.Height));
var brush2 = new SolidBrush(Color.Transparent);
e.Graphics.FillRectangle(brush2, new Rectangle(0, 50, 50, 20));
}
}
In addition to the previous problem, another problem with this method is that whenever I want to move the transparent position box, I would be forced to invalidate the control using this.Invalidate();
Then I decided to use a panel:
public partial class Form1 : Form
{
protected override void OnLoad(EventArgs ev)
{
base.OnLoad(ev);
this.panel1.Paint += (s, e) =>
{
var brush = new SolidBrush(Color.FromArgb(25, Color.White));
e.Graphics.FillRectangle(brush, new Rectangle(0, 0, 50, this.Height));
var brush2 = new SolidBrush(Color.Transparent);
e.Graphics.FillRectangle(brush2, new Rectangle(0, 50, 50, 20));
};
}
}
BUT... The result is the same...
In summary, the selected option must be completely transparent allowing to see the background image but leaving the label visible, and the rest must be semi-transparent white.
How can I do this?
Related
I've been asked to carry out some modernisation work on a C# WinForms application, and I'm using VS2019 with C#.Net 4.7.2.
The project owner wants to change the border colour of all the legacy Windows controls that were used originally. The initial idea was - using the MetroIT framework - to keep the original Windows controls in the Form Designer, but override them by defining new classes which extend the MetroIT equivalents but changing the class type of the declarations in the Designer's InitializeComponent() method.
That approach doesn't really work as a "drop-in" replacement because the MetroIT controls tend to subclass Control whereas existing code expects the legacy Windows properties/methods to be available.
Instead, therefore, I went down the route of trying to override the OnPaint method. That worked fine for CheckBox and RadioButton, but I'm now struggling to get it to work for a ListBox. The following is as far as I've got; it certainly isn't correct, as I'll explain, but it feels sort of close ?
public class MyListBox : ListBox
{
public MyListBox()
{
SetStyle(ControlStyles.UserPaint, true);
this.DrawMode = DrawMode.OwnerDrawVariable;
BorderStyle = BorderStyle.None;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Brush textBrush = new SolidBrush(Color.Black);
float y = 1;
foreach (String strItem in Items)
{
e.Graphics.DrawString(strItem, DefaultFont, textBrush, 1, y);
y += DefaultFont.Height;
}
Rectangle borderRectangle = this.ClientRectangle;
ControlPaint.DrawBorder(e.Graphics, borderRectangle, Color.Blue, ButtonBorderStyle.Solid);
}
}
Initially, with no data in the ListBox, the control is drawn correctly.
However, as soon as a scroll bar appears, the base Windows code draws the scroll bar over the top of my border instead of just inside it (whereas an unmodified ListBox re-draws the border around the top, bottom and right-hand side of the scroll bar):
Is there a way to change my code so that the border draws itself around the edges of the scroll bar instead of excluding it ?
The other way in which my code is wrong is that, as soon as I start to scroll, the border painting code is applying itself to some of the ListBox items:
Is there a way I can complete this, or am I wasting my time because the base scroll bar cannot be modified ?
Thanks
EDIT 1:
Further to the suggestions of #GuidoG:
Yes, to be clear, I really only want to change the border to blue. Happy to leave the listbox items as they would ordinarily be painted WITHOUT any border.
So, to achieve that, I have removed your suggested code to do DrawItem, and now I only have the code to paint the border - but clearly I must be doing something wrong, because the listbox has now reverted to looking like the standard one with the black border.
So here's what I have now:
In my Dialog.Designer.cs InitializeComponent():
this.FieldsListBox = new System.Windows.Forms.ListBox();
this.FieldsListBox.FormattingEnabled = true;
this.FieldsListBox.Location = new System.Drawing.Point(7, 98);
this.FieldsListBox.Name = "FieldsListBox";
this.FieldsListBox.Size = new System.Drawing.Size(188, 121);
this.FieldsListBox.Sorted = true;
this.FieldsListBox.TabIndex = 11;
this.FieldsListBox.SelectedIndexChanged += new System.EventHandler(this.FieldsListBox_SelectedIndexChanged);
In my Dialog.cs Form declaration:
public SelectByAttributesDialog()
{
InitializeComponent();
BlueThemAll(this);
}
private void BlueThemAll(Control parent)
{
foreach (Control item in parent.Controls)
{
Blue(item);
if (item.HasChildren)
BlueThemAll(item);
}
}
private void Blue(Control control)
{
Debug.WriteLine("Blue: " + control.Name.ToString());
Rectangle r = new Rectangle(control.Left - 1, control.Top - 1, control.Width + 1, control.Height + 1);
using (Graphics g = control.Parent.CreateGraphics())
{
using (Pen selPen = new Pen(Color.Blue))
{
g.DrawRectangle(selPen, r);
}
}
}
I know that Blue() is being called for all the controls in this dialog, in the sense that the Debug.WriteLine() is outputting every control name, but no borders are being changed to blue (and certainly not the listbox).
I've been away from Windows Form programming for a very long time so I apologise for that, and I'm clearly doing something wrong, but have no idea what.
Solution 1: let the form drawn blue borders around everything:
This means you dont have to subclass any controls, but you put some code on the form that will draw the borders for you around each control.
Here is an example that works for me
// method that draws a blue border around a control
private void Blue(Control control)
{
Rectangle r = new Rectangle(control.Left - 1, control.Top - 1, control.Width + 1, control.Height + 1);
using (Graphics g = control.Parent.CreateGraphics())
{
using (Pen selPen = new Pen(Color.Blue))
{
g.DrawRectangle(selPen, r);
}
}
}
// recursive method that finds all controls and call the method Blue on each found control
private void BlueThemAll(Control parent)
{
foreach (Control item in parent.Controls)
{
Blue(item);
if (item.HasChildren)
BlueThemAll(item);
}
}
where to call this ?
// draw the blue borders when the form is resized
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
panel3.Invalidate(); // on panel3 there is a control that moves position because it is anchored to the bottom
Update();
BlueThemAll(this);
}
// draw the borders when the form is moved, this is needed when the form moved off the screen and back
protected override void OnMove(EventArgs e)
{
base.OnMove(e);
BlueThemAll(this);
}
// draw the borders for the first time
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
BlueThemAll(this);
}
If you also want blue borders around each item of the listbox:
To get some border around each item in your listbox use the DrawItem event in stead of the paint event, maybe this link could help.
I modified the code to get it drawing blue borders
listBox1.DrawMode = DrawMode.OwnerDrawFixed;
listBox1.DrawItem += ListBox1_DrawItem;
private void ListBox1_DrawItem(object sender, DrawItemEventArgs e)
{
try
{
e.DrawBackground();
Brush myBrush = new SolidBrush(e.ForeColor);
Rectangle r = new Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width - 1, e.Bounds.Height);
using (Pen selPen = new Pen(Color.Blue))
{
e.Graphics.DrawRectangle(selPen, r);
}
e.Graphics.DrawString(((ListBox)sender).Items[e.Index].ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
e.DrawFocusRectangle();
}
catch { }
}
and it looks like this
Solution 2: Subclassing the listbox:
If you need this in a subclassed listbox, then you can do this.
However, this will only work if the listbox has at least one pixel space left around it, because to get around the scrollbar you have to draw on the parent of the listbox, not on the listbox itself. The scrollbar will always draw itself over anything you draw there.
public class myListBox : ListBox
{
public myListBox(): base()
{
this.DrawMode = DrawMode.OwnerDrawFixed;
BorderStyle = BorderStyle.None;
this.DrawItem += MyListBox_DrawItem;
}
private void MyListBox_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
Brush myBrush = new SolidBrush(e.ForeColor);
e.Graphics.DrawString(this.Items[e.Index].ToString(), e.Font, myBrush, e.Bounds, StringFormat.GenericDefault);
e.DrawFocusRectangle();
Rectangle r = new Rectangle(this.Left - 1, this.Top - 1, this.Width + 2, this.Height + 2);
using (Graphics g = this.Parent.CreateGraphics())
{
ControlPaint.DrawBorder(g, r, Color.Blue, ButtonBorderStyle.Solid);
}
}
}
and this will look like so
I'm trying to understand the graphics, and in the Graphics.FromImage documentation, it has this as an example:
private void FromImageImage(PaintEventArgs e)
{
// Create image.
Image imageFile = Image.FromFile("SampImag.jpg");
// Create graphics object for alteration.
Graphics newGraphics = Graphics.FromImage(imageFile);
// Alter image.
newGraphics.FillRectangle(new SolidBrush(Color.Black), 100, 50, 100, 100);
// Draw image to screen.
e.Graphics.DrawImage(imageFile, new PointF(0.0F, 0.0F));
// Dispose of graphics object.
newGraphics.Dispose();
}
I'm new to C# and Windows Forms and am struggling to understand how this all fits together. How is this code run, say when the form first loads or when a button is pressed?
Maybe this will help. I have an example of both drawing on Paint events but also drawing on top of an existing Image. I created a form with two picture boxes. One for each case. pictureBox1 has an event handler for the .Paint event, while pictureBox2 is only drawn when a button is pressed.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
pictureBox1.BackColor=Color.Black;
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
// The code below will draw on the surface of pictureBox1
// It gets triggered automatically by Windows, or by
// calling .Invalidate() or .Refresh() on the picture box.
DrawGraphics(e.Graphics, pictureBox1.ClientRectangle);
}
private void toolStripButton1_Click(object sender, EventArgs e)
{
// The code below will draw on an existing image shown in pictureBox2
var img = new Bitmap(pictureBox2.Image);
var g = Graphics.FromImage(img);
DrawGraphics(g, pictureBox2.ClientRectangle);
pictureBox2.Image=img;
}
void DrawGraphics(Graphics g, Rectangle target)
{
// draw a red rectangle around the moon
g.DrawRectangle(Pens.Red, 130, 69, 8, 8);
}
}
So when you launch the application a red rectangle appears on the left only, because the button hasn't been pressed yet.
and when the button is pressed, the red rectangle appears on top of the image displayed in pictureBox2.
Nothing dramatic, but it does the job. So depending on the mode of operation you need (user graphics, or image annotations) use the example code to understand how to do it.
I have created a button using the code below. Adding the start.Text="Start"; statement does nothing. How do I add a label to my button?
public MainForm()
{
InitializeComponent();
myButtonObject start = new myButtonObject();
EventHandler myHandler = new EventHandler(start_Click);
start.Click += myHandler;
start.Location = new System.Drawing.Point(200, 500);
start.Size = new System.Drawing.Size(101, 101);
start.Text="Start";
// start.TextAlign = new System.Drawing.ContentAlignment.MiddleCenter;
this.Controls.Add(start);
}
public class myButtonObject : UserControl
{
// Draw the new button.
protected override void OnPaint(PaintEventArgs e)
{
Graphics graphics = e.Graphics;
Pen myPen = new Pen(Color.Black);
// Draw the button in the form of a circle
graphics.FillEllipse(Brushes.Goldenrod, 0, 0, 100, 100);
graphics.DrawEllipse(myPen, 0, 0, 100, 100);
myPen.Dispose();
}
}
You have implemented your own button as a user control. Since you are implementing OnPaint to provide your own draw functionality, you need to implement all the functionality like drawing the text too.
If you want to go down this route then you also need to add the logic to draw the text on the control in your OnPaint method. This can be done using the graphics.DrawString method.
See http://msdn.microsoft.com/en-us/library/system.drawing.graphics.drawstring.aspx
You also need to call graphics.dispose.
If you aren't familiar with this, then it might be simpler to use a UserControl and add a label to it, or something similar, and then to draw your circle shape on the top of that.
You should draw the button's text yourself in the OnPaint method:
TextRenderer.DrawText(graphics, Text, Font, new Point(5, 5), SystemColors.ControlText);
Where new Point(5, 5) - is a top left position of the text.
You paint the button ok but you don't draw the text.
A usercontrol doesn't do this by itself.
Just add something like this to the paint commands:
graphics.DrawString(Text, yourfont, yourBrush, x, y);
you must and may have to decide freely on x, y font and brush.
I have two panels on top of each other. The one below is a bit larger than the one on top. I am painting an image by using CreateGraphics() method on the top most panel. (To be clear this image is a connect four grid with transparent holes). Now what I need to do is to add a picture box to the bottom panel and have it show from behind this grid.
I am adding controls of picture box to the bottom grid. And I'm using the BringToFront() method too. What can I do to have the picture box show underneath the grid?
In the following code: chipHolder is the bottom panel, grid is the topmost panel and picBox is the picture box respectively
public void addControl()
{
chipHolder.Controls.Add(picBox);
picBox.BringToFront();
}
// This piece of code is in a mouse_click event of grid
Graphics g = grid.CreateGraphics();
addControl();
// to make the picture move downwards
for (int i = 0; i < newYloc; i++)
{
picBox.Location = new Point(newXloc, picBox.Top + 1);
picBox.Show();
}
// drawing the grid image on the grid panel
protected virtual void grid_Paint(object sender, PaintEventArgs e)
{
Image img = Properties.Resources.grid_fw;
gridGraphics = grid.CreateGraphics();
gridGraphics.DrawImage(img, 0, 0, 650, 550);
}
To have a better picture this is how my panels are. the one selected is the chipHolder panel.
You could try a different approach: don't use a Panel and use a single PictureBox, this way you draw everything in that PictureBox. Thus, you use PictureBox's MouseDown event handler to calculate the (virtual) cell the user has clicked (you need to perform a simple division) and then you draw the chip on the PictureBox. If you want to show the chip falling, you'd need to save a copy of the current Bitmap (the Image property of the PictureBox) and draw the chip on different y coordinates (from 0 to its final position on the grid), this would be just like the double-buffer technique.
Here is a small example (you need a Form with a PictureBox, in this example it's named "pictureBox2"):
public partial class Form1 : Form
{
Bitmap chip = new Bitmap(40, 40, PixelFormat.Format32bppArgb);
public Form1()
{
InitializeComponent();
using (Graphics g = Graphics.FromImage(chip))
{
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
g.FillEllipse(new SolidBrush(Color.FromArgb(128, 255, 80, 0)), 1, 1, 38, 38);
}
pictureBox2.Image = new Bitmap(pictureBox2.Width, pictureBox2.Height, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(pictureBox2.Image))
{
g.Clear(Color.Yellow);
}
}
private void pictureBox2_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Text = e.Location.ToString();
using (Graphics g = Graphics.FromImage(pictureBox2.Image))
{
g.DrawImage(chip, e.Location.X - 20, e.Location.Y - 20);
}
pictureBox2.Invalidate();
}
}
}
If you want controls with real transparency, you should use WPF (it provides better graphics and uses hardware acceleration).
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).