I've got a WinForms app and I want to programatically draw circles on top of certain areas. I'm running into a couple problems and any insight would be appreciated!
1) I've got code for drawing and clearing the circles (see below), but the circles are being drawn behind all my controls. I want them to be drawn as "top-most" in every case. How do I do this?
2) When my app starts up, I'll have some circles that need to be drawn right away. I tried drawing them on the Form Load event to no avail. But as to here (Form graphics not set when form loads) I'm now drawing it on the Paint event. While this works reasonably well (with a bool to make sure it only does it the first time), It seems to have problems with the this.Invalidate(); (as no circles are getting drawn). Is there a better way? Here's my code (parseText runs on a the index change of a comboBox):
private void parseText()
{
this.Invalidate();
List<string> lines = new List<string>(richTextBoxRaw.Text.Split(new string[] { Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries));
foreach (string s in lines)
{
switch (s)
{
case "<draw1>":
drawCircle(107, 26, 25);
break;
default:
break;
}
}
}
private void drawCircle(int x, int y, int transparency)
{
if (transparency < 0)
transparency = 0;
else if (transparency > 255)
transparency = 255;
SolidBrush brush = new SolidBrush(Color.FromArgb(transparency, 255,0,0));
Graphics graphics = this.CreateGraphics();
graphics.FillEllipse(brush, new Rectangle(x, y, 25, 25));
brush.Dispose();
graphics.Dispose();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (starting)
parseText();
starting = false;
}
One of not so complicated and yet working approaches of accomplishing your requirement could be to create custom transparent panel and place it on top of the controls where the red circles will be drawn.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void DrawCircle(int x, int y, int transparency, Graphics graphics)
{
if (transparency < 0)
transparency = 0;
else if (transparency > 255)
transparency = 255;
SolidBrush brush = new SolidBrush(Color.FromArgb(transparency, 255, 0, 0));
graphics.FillEllipse(brush, new Rectangle(x, y, 25, 25));
brush.Dispose();
graphics.Dispose();
}
private void TransparentPanel1_Paint(object sender, PaintEventArgs e)
{
DrawCircle(10, 10, 255, e.Graphics);
}
private void Form1_Load(object sender, EventArgs e)
{
transparentPanel1.Enabled = false;
transparentPanel1.Paint += TransparentPanel1_Paint;
transparentPanel1.BringToFront();
}
}
public class TransparentPanel : Panel
{
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; // WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
//base.OnPaintBackground(e);
}
}
Related
I would like to design a chess board and drag over pieces (shown in pictureBox Controls) all child of the main board (pictureBox1).
Problem I encounter is the transparency is only set to the Parent pictureBox1.
Which shows this effect: The square is showing.
private void CommonPiece_Mouse_Move(object sender, MouseEventArgs e)
{
if (Piece_Selected)
{
int MousePositionX = pictureBox1.PointToClient(Cursor.Position).X;
int MousePositionY = pictureBox1.PointToClient(Cursor.Position).Y;
(sender as PictureBox).Left = MousePositionX - 35;
(sender as PictureBox).Top = MousePositionY - 25;
}
}
What would be a good way to go about it?
There are several solutions for this problem. The one in the following screen recording, is based on drawing png images as movable shapes; it uses the idea that I've explained in How to drag and move shapes in C#. Basically there's only one control -the drawing surface, and all the other stuff are movable drawings.
You can use any image for the pieces by passing an Image to the ImageShape
You can modify the size of the Pieces by setting Height of the shape
It supports snapping to the grid
You can customize the texture, easily by setting the BackgroundImage of the drawing surface
You can change the grid size, by setting GridSize property of the drawing surface
You can change the Color of White and Grid grids, by assigning the color to WhiteColor and BlackColor
It's of course a quick example showing how to draw movable objects, including png images keeping their transparency. You know how to improve it :)
Drawing and moving shapes - Chess pieces
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Linq;
using System.Windows.Forms;
public interface IShape
{
bool HitTest(Point p);
void Draw(Graphics g);
void Move(int dx, int dy);
Point Location { get; set; }
}
public class ImageShape : IShape
{
public int Height { get; set; } = 100;
public Point Location { get; set; }
private Image _image;
public ImageShape(Image image)
{
_image = image;
}
public void Draw(Graphics g)
{
var r = new Rectangle(Location, new Size(Height, Height));
r.Inflate(-5, -5);
g.DrawImage(_image, r);
}
public bool HitTest(Point p)
{
return new Rectangle(Location, new Size(Height, Height)).Contains(p);
}
public void Move(int dx, int dy)
{
Location = new Point(Location.X + dx, Location.Y + dy);
}
}
public class DrawingSurface : Control
{
public List<IShape> Shapes { get; private set; }
public int GridSize { get; set; } = 100;
public Color WhiteColor = Color.FromArgb(200, Color.White);
public Color BlackColor = Color.FromArgb(120, Color.Black);
IShape selectedShape;
bool moving;
Point previousPoint = Point.Empty;
public DrawingSurface()
{
DoubleBuffered = true;
ResizeRedraw = true;
Shapes = new List<IShape>();
}
protected override void OnMouseDown(MouseEventArgs e)
{
for (var i = Shapes.Count - 1; i >= 0; i--)
if (Shapes[i].HitTest(e.Location))
{
selectedShape = Shapes[i];
break;
}
if (selectedShape != null)
{
moving = true;
previousPoint = e.Location;
Invalidate();
}
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if (moving)
{
var dx = e.X - previousPoint.X;
var dy = e.Y - previousPoint.Y;
selectedShape.Move(dx, dy);
previousPoint = e.Location;
this.Invalidate();
}
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
if (moving)
{
int i = (e.X / GridSize) * GridSize;
int j = (e.Y / GridSize) * GridSize;
selectedShape.Location = new Point(i, j);
selectedShape = null;
moving = false;
this.Invalidate();
}
base.OnMouseUp(e);
}
protected override void OnPaint(PaintEventArgs e)
{
var g = e.Graphics;
g.InterpolationMode = InterpolationMode.High;
g.SmoothingMode = SmoothingMode.HighQuality;
g.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
g.CompositingQuality = CompositingQuality.HighQuality;
foreach (var shape in Shapes.Except(new[] { selectedShape }))
shape.Draw(g);
if (selectedShape != null)
selectedShape.Draw(g);
}
protected override void OnPaintBackground(PaintEventArgs e)
{
base.OnPaintBackground(e);
var g = e.Graphics;
using (var w = new SolidBrush(WhiteColor))
using (var b = new SolidBrush(BlackColor))
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
g.FillRectangle((i + j) % 2 == 0 ? b : w,
i * GridSize, j * GridSize, GridSize, GridSize);
}
}
To add pieces:
private void Form1_Load(object sender, EventArgs e)
{
this.drawingSurface1.Shapes.Add(new ImageShape(
Properties.Resources.KingWhite) { Location = new Point(0, 0) });
this.drawingSurface1.Shapes.Add(
new ImageShape(Properties.Resources.kingBlack) { Location = new Point(0, 100) });
}
Here's an example that makes the PictureBox Non-Rectangular. The PB will literally not exist where the transparent areas were:
using System.Runtime.InteropServices;
namespace CS_Scratch_WindowsFormsApp2
{
public partial class Form1 : Form
{
public const int HT_CAPTION = 0x2;
public const int WM_NCLBUTTONDOWN = 0xA1;
[System.Runtime.InteropServices.DllImportAttribute("user32.dll")]
public static extern bool ReleaseCapture();
[System.Runtime.InteropServices.DllImportAttribute("user32.dll")]
public static extern int SendMessage(IntPtr hWnd, int Msg, int wParam, int lParam);
public Form1()
{
InitializeComponent();
pbChessPiece.MouseMove += PbChessPiece_MouseMove;
}
private void PbChessPiece_MouseMove(object sender, MouseEventArgs e)
{
PictureBox pb = (PictureBox)sender;
if (!DesignMode && e.Button == MouseButtons.Left)
{
ReleaseCapture();
SendMessage(pb.Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);
}
}
private void button1_Click(object sender, EventArgs e)
{
Bitmap bmp = (Bitmap)pbChessPiece.Image;
Region rgn = new Region();
rgn.Union(new Rectangle(0, 0, bmp.Width, bmp.Height));
for(int x=0; x<bmp.Width; x++)
{
for(int y=0; y<bmp.Height; y++)
{
if (bmp.GetPixel(x, y).A == 0)
{
rgn.Exclude(new Rectangle(x, y, 1, 1));
}
}
}
pbChessPiece.Region = rgn;
}
}
}
Example run:
I did something similar, but I did not use Drag & Drop events
On MouseDown I assumed a drag operation started, I've opened a translucent unfocused window on top of the drop area, and started drawing dragged image on that form. Since the form is translucent it can show images with transparency on top of everything. I did not use images, but instead draw rectangles where users could drop. If my memory serves me correct, MouseMove events are forwarded to the dragged component, the original chess piece in your case, or the opened but unfocused form, so I can easily track the location. On MouseUp event I wrapped everything.
Since you can draw the image on the new form you can tilt image chess piece image to the left or right for a nice touch.
I developed a very strict forms designer application using this technique.
You can use the panel instead of using the pictureBox and display the image in the background of the controls. Remember that your images must be "png" and the surrounding of the image must be completely transparent. Also, the background color of the controls of the panels Set by events so that it becomes transparent during drag and returns to the cell color after drag and drop.
Good luck.
I'm trying to add a control to my Panel. At mouseDown on the panel the point is saved and at mouseUp the point is saved. But at panel mouseUp nothing is drawn. How to solve it?
Ellipse class:
class Ellipse : Control
{
private int x;
private int y;
private int width;
private int height;
public Ellipse(int x, int y, int width, int height)
{
setY(y);
setX(x);
setWidth(width);
setHeight(height);
}
public int getX() { return x;}
public int getY() { return y; }
public int getWidth() { return width; }
public int getHeight() { return height; }
public void setX(int newx) { x = newx; }
public void setY(int newy) { y = newy; }
public void setWidth(int newwidth) { width = newwidth; }
public void setHeight(int newheight) { height = newheight; }
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Call methods of the System.Drawing.Graphics object.
// Declare and instantiate a new pen.
System.Drawing.Pen myPen = new System.Drawing.Pen(Color.Aqua);
// Draw an aqua rectangle in the rectangle represented by the control.
e.Graphics.FillEllipse(Brushes.Black,x,y,width,height);
}
}
Form1 class
private void panel_MouseDown(object sender, MouseEventArgs e)
{
draw = true;
x = e.X;
y = e.Y;
}
private void panel_MouseUp(object sender, MouseEventArgs e)
{
draw = false;
xe = e.X;
ye = e.Y;
Item item;
Enum.TryParse<Item>(menuComboBoxShape.ComboBox.SelectedValue.ToString(), out item);
switch (item)
{
case Item.Pencil:
using (Graphics g = panel.CreateGraphics())
using (var pen = new Pen(System.Drawing.Color.Black)) //Create the pen used to draw the line (using statement makes sure the pen is disposed)
{
g.DrawLine(pen,new Point(x, y), new Point(xe, ye));
}
break;
case Item.Rectangle:
break;
case Item.Ellipse:
Ellipse el = new Ellipse(x,y,xe-x,ye-y);
panel.Controls.Add(el);
break;
default:
break;
}
}
You are inheriting your Ellipse class from Control, but in fact you're not using it as a control - you're not adding it in Controls collection of form, so in fact it is invisible, inactive and not receiving any events from form.
Also painting the control from outer code looks like a bad design. Control should paint itself, and you should set it bounds from outer code.
Here is snippet to drive you to the right way:
class Ellipse : Control
{
Point mDown { get; set; }
public Ellipse()
{
MouseDown += shape_MouseDown;
MouseMove += shape_MouseMove;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillEllipse(Brushes.Black, this.Bounds);
}
private void shape_MouseDown(object sender, MouseEventArgs e)
{
mDown = e.Location;
}
private void shape_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Location = new Point(e.X + Left - mDown.X, e.Y + Top - mDown.Y);
}
}
}
And in the form you should create it like:
el = new Ellipse();
el.Bounds = new Rectangle(0, 0, 100, 100);
Controls.Add(el);
Update
Based on your updated code, I can see a couple of issues:
You actually don't need x, y, width, height properties of your Ellipse class and according getter/setter methods, since it's Control, and it has its own Location and Width, Height public properties.
You are drawing your ellipse incorrectly. Assuming it should fill all the area, painting should be e.Graphics.FillEllipse(Brushes.Black,0,0,Width,Height) (here I assuming using Control.Width instead of your width and so on). Otherwise you're additionally shifting your painted ellipse.
Code in panel_MouseUp concerning ellipse creation should be something like
var el = new Ellipse();
panel.Controls.Add(el);
el.Location = new Point(x, y);
el.Width = (xe - x);
el.Height = (ye - y);
Or, if it should be one single ellipse (right now you're creating new one each time) - create this one outside of mouseUp handler and inside of handler just change it's size and location.
I use the following code to fill up a panel.But whenever i minimize the form, the filled up portion of the rectangle gets lost.
Any ideas what I'm doing wrong? Thanks
public static void populateTable(this Panel p, int x, int y)
{
Graphics g = p.CreateGraphics();
Brush b = new SolidBrush(Color.DarkCyan);
g.FillRectangle(b, x, y,100,40);
g.Dispose();
}
You need to repaint every time the panel repaints itself (For example when you restore the window from being minimized). The correct way to do this is use the Paint event and store the objects you need to draw in some form of collection and re-draw them every call.
public Form1()
{
InitializeComponent();
rectangles = new List<Rectangle>();
panel1.Paint += panel1_Paint;
}
public void PopuplateTable(int x, int y)
{
rectangles.Add(new Rectangle(x,y, 100, 40));
//Forces a redraw to happen.
panel1.Invalidate();
}
private List<Rectangle> rectangles;
void panel1_Paint(object sender, PaintEventArgs e)
{
foreach (var rectangle in rectangles)
{
using (var b = new SolidBrush(Color.DarkCyan))
{
e.Graphics.FillRectangle(b, rectangle);
}
}
}
Now this is not exactly the same as your current code, but it points you in the right direction so you can adapt your code to do the same. You may need to create a new class based on Panel to hold your PopulateTable call instead of using a extension method, if you do end up doing that you should override OnPaint instead of using the paint event.
class MyPanel : Panel
{
private Rectangle? paintedRectangle = null;
public void PopuplateTable(int x, int y)
{
paintedRectangle = new Rectangle(x, y, 100, 40);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (paintedRectangle.HasValue)
{
using (var b = new SolidBrush(Color.DarkCyan))
{
e.Graphics.FillRectangle(b, paintedRectangle.Value);
}
}
}
}
I'm making a simple game in winform (tic-tac-toe), and I'm having some problem to paint block control.
Here is the class I made, that represent a block in the game (without game logic, is only UI).
public class UI_Block : Control
{
private Rectangle block;
private SIGNS sign;
public SIGNS Sign
{
get {return sign;}
set
{
if (sign == SIGNS.EMPTY)
sign = value;
}
}
public UI_Block( ) {
sign = SIGNS.EMPTY;
}
public void SetBlockOnBoard(int x, int y)
{
this.Location = new Point( x , y );
this.Size = new Size(Parent.Width /3, Parent.Height / 3);
block = new Rectangle(this.Location, this.Size);
}
public void DrawSign(Graphics g)
{
Pen myPen = new Pen(Color.Red);
if (sign == SIGNS.O)
{
drawO(g,new Pen(Brushes.Black));
}
if (sign == SIGNS.X)
{
drawX(g, new Pen(Brushes.Red));
}
}
protected override void OnPaint(PaintEventArgs e)
{
DrawSign(e.Graphics);
base.OnPaint(e);
}
//Draw X
private void drawX(Graphics g, Pen myPen)
{
//draw first daignol
Point daignolStart = new Point { X = this.Location.X , Y = this.Location.Y };
Point daignolEnd = new Point { X = this.Size.Width , Y = this.Size.Height };
g.DrawLine(myPen, daignolStart, daignolEnd);
//draw second daignol
daignolStart = new Point { X = Size.Width , Y = this.Location.Y };
daignolEnd = new Point { X = Location.X, Y = Size.Height };
g.DrawLine(myPen, daignolEnd, daignolStart);
}
//Draw O
private void drawO(Graphics g, Pen myPen)
{
g.DrawEllipse(myPen, block);
}
}
I added them both to the winForm class and to see how it looks like when I paint them:
public partial class Form1 : Form
{
UI.UI_Block block;
UI.UI_Block blockX;
public Form1()
{
InitializeComponent();
block = new UI.UI_Block();
blockX = new UI.UI_Block();
Controls.Add(block);
Controls.Add(blockX);
}
protected override void OnLoad(EventArgs e)
{
block. SetBlockOnBoard(0, 0);
blockX.SetBlockOnBoard(0, block.Height);
block.Sign = SIGNS.X;
blockX.Sign = SIGNS.O;
base.OnLoad(e);
}
protected override void OnPaint(PaintEventArgs e)
{
//block.DrawSign(e.Graphics);
//block.DrawSign(e.Graphics);
base.OnPaint(e);
}
}
I tried few things, like not using the onPaint event and I still get the same result.
Here what I see when I run it:
Any idea why I can't paint both of them?
You are not drawing the contents of your control in it's visible area, so it is drawing fine but you can't see it.
Every control has it's own coordinate space (client coords), which starts at 0,0 regardless of where it is positioned within the parent control. You are placing the control in it's parent correctly by setting its Location, but then you are also using the Location to offset the graphics, so they are essentially offset twice.
(If you make your control bigger you'll be able to see the X being drawn further down the screen)
To fix this, do all your drawing in the client coordinate space of your control, i.e. draw in the area (0, 0, width, height)
(P.S. You could just draw all 9 tiles in the parent control, which is a more efficient approach than creating 9 child controls. But what you are doing will work fine)
I created a custom control which I want to use to overlay part of my form with status information on demand. It should display a text and have a background color depending on the type of information. Here is the still incomplete code.
public partial class StatusPanel : UserControl
{
public enum PanelStyle
{
Info,
Warning,
Error
}
public PanelStyle Style { get; set; }
public StatusPanel()
{
InitializeComponent();
this.imgGreen = new Bitmap(256, 256, PixelFormat.Format32bppArgb);
using(Graphics g = Graphics.FromImage(this.imgGreen))
{
g.Clear(Color.Transparent);
Brush bg = new SolidBrush(Color.FromArgb(200, Color.Green));
g.FillRectangle(bg, 0, 0, 256, 256);
}
this.imgYellow = new Bitmap(256, 256, PixelFormat.Format32bppArgb);
using(Graphics g = Graphics.FromImage(this.imgYellow))
{
g.Clear(Color.Transparent);
Brush bg = new SolidBrush(Color.FromArgb(10, Color.Yellow));
g.FillRectangle(bg, 0, 0, 256, 256);
}
}
protected readonly Font font = new Font("Arial", 12.0F);
protected readonly Brush textBrush = new SolidBrush(Color.Black);
protected readonly Image imgGreen;
protected readonly Image imgYellow;
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x00000020; //WS_EX_TRANSPARENT
return cp;
}
}
protected override void OnPaintBackground(PaintEventArgs e)
{
// do not draw background
}
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBilinear;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighQuality;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
Image img = GetImage();
e.Graphics.DrawImage(img, 0, 0, this.Width, this.Height);
e.Graphics.DrawString("ABC", this.font, this.textBrush, 1.0F, 1.0F);
}
protected Image GetImage()
{
return (this.Style == PanelStyle.Info) ? this.imgGreen : this.imgYellow;
}
}
This works quite fine. But when I put some buttons on a form and one of this controls infront of them, they will "overdraw" when the mouse moves over the button and the highlight effects redraws them.
How will my component be notified that it needs to redraw because underlying control have redrawn?
register a paint-handler to all controls under Parent.Control and a handler for Parent.ControlAdded that registers your paint-handler
something like this:
private void myDummyUserControl_Load(object sender, EventArgs e)
{
var uc = (DummyUserControl)sender;
uc.Parent.ControlAdded += new ControlEventHandler(Parent_ControlAdded);
foreach (Control c in uc.Parent.Controls)
{
if (uc == c)
continue;
c.Paint += new PaintEventHandler(c_Paint);
}
}
void c_Paint(object sender, PaintEventArgs e)
{
//checks & paint stuff here
}
void Parent_ControlAdded(object sender, ControlEventArgs e)
{
e.Control.Paint += new PaintEventHandler(c_Paint);
}
//edit
not sure if you need to use recursion to go down the tree of child controls and need to add handlers on those too ...