I have a Windows Forms app where I add different figures(rectangles, circles, etc.) to the main form. The figure is a UserControl and it's shape I define with GraphicsPath.
Method for adding new figure:
void AddElement(ShapeType shape, string guid)
{
Shape newShape = new Shape();
newShape.Name = guid;
newShape.Size = new Size(100, 100);
newShape.Type = shape;
newShape.Location = new Point(100, 100);
newShape.MouseDown += new MouseEventHandler(Shape_MouseDown);
newShape.MouseMove += new MouseEventHandler(Shape_MouseMove);
newShape.MouseUp += new MouseEventHandler(Shape_MouseUp);
newShape.BackColor = this.BackColor;
this.Controls.Add(newShape);
}
In Shape (Figure) class:
private ShapeType shape;
private GraphicsPath path = null;
public ShapeType Type
{
get { return shape; }
set
{
shape = value;
DrawElement();
}
}
private void DrawElement()
{
path = new GraphicsPath();
switch (shape)
{
case ShapeType.Rectangle:
path.AddRectangle(this.ClientRectangle);
break;
case ShapeType.Circle:
path.AddEllipse(this.ClientRectangle);
break;
case ShapeType.Line:
path.AddLine(10,10,20,20);
break;
}
this.Region = new Region(path);
}
protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
if (path != null)
{
e.Graphics.DrawPath(new Pen(Color.Black, 4), path);
}
}
When resizing the figure, I redraw It:
protected override void OnResize(System.EventArgs e)
{
DrawElement();
this.Invalidate();
}
Everything works fine when I add shapes like rectangle and circle. But when I choose Line, nothing appears on my form. The breakpoint shows that the programs steps in all the methods and this.Controls.Add(newShape); as well.
I do not understand why this is not working.
I'd appreciate any advice.
You can draw an open GraphicsPath with a thin or a thick Pen. But a region must be set from a closed shape or else there is no place where your pixels could show up. This will help to keep your region intact; but you need to know, just what you want it to be:
if (shape != ShapeType.Line) this.Region = new Region(path);
If you want it to be something like a thick line you must create a polygon or a series of lines to outline the shape you want. And if you want your line to be inside that region you will need two paths: one closed polygon path to set the region and one open line path to draw the line inside the region.
Edit:
The best way to create the closed path is probably to use the Widen() method with the Pen you are using like this:
GraphicsPath path2 = path.Widen(yourPen);
This would get the thickness right as well as the line caps and also work for more complicated polylines; I haven't tried it though..
Maybe it's because the line has no area. Try to replace it with a very thin shape having a positive area. For instance:
const int thickness = 1;
path.AddLines(new[]
{
new Point(10, 10),
new Point(20, 20),
new Point(20 + thickness, 20 + thickness),
new Point(10 + thickness, 10 + thickness)
});
Related
I am creating a Graphics Form where objects with coordinates x,y are being drawn into the Graphics. It works properly for small x and y, but when I want to draw them in different place (f.e. x = 500, y = 300) they disappear.
public WindowHandler()
{
dc = this.CreateGraphics();
this.Size = new Size(sizeX, sizeY); // 800x600
startSimulation = new Button
{
// button properties
};
this.Controls.Add(startSimulation);
startSimulation.Click += new EventHandler(StartSimulationClick);
}
private void CreationsMethods()
{
creations.PaintAllAnimals(dc);
}
public void PaintAllAnimals(Graphics g)
{
foreach (var animal in ecoStructure.world.animals)
{
animal.PaintAnimal(g);
}
}
public void PaintAnimal(Graphics graphics)
{
Rectangle rectangle = new Rectangle(x, y, 3, 3);
Pen pen = new Pen(colour);
graphics.DrawRectangle(pen, rectangle);
graphics.FillRectangle(colour, rectangle);
}
I want to put all the objects onto the window. Is there any way to make the Graphics "bigger"? Do I need to make another one? Or should I use different tool to draw rectangles?
Thanks to #Chris Dunaway for posting an answer in comment.
So I deleted the CreateCraphics, and instead of that i am now using an OnPaint method. It works slowly, but works. So I will try to make it as fast as i can. For now, I just created this. NextStepClick is how I use the OnPaint to paint the rectangles.
private void CreationsMethods(object sender, PaintEventArgs e)
{
dc = e.Graphics;
base.OnPaint(e);
creations.PaintAllAnimals(dc);
}
private void NextStepClick(object sender, EventArgs e)
{
this.Refresh();
picBox.Paint += new System.Windows.Forms.PaintEventHandler(CreationsMethods);
}
I want to call OnPaint event in my custom control. I have created a class and inherited control to create custom control and want to call OnPaint or paint event when the object is initialized. But when i create a class paint event is not triggered. Se the below code.
internal class CallRectangle : Control
{
public CallRectangle()
{
this.Paint += CalloutRectangle_Paint;
}
void CalloutRectangle_Paint(object sender, PaintEventArgs e)
{
throw new NotImplementedException();
}
protected override void OnPaint(PaintEventArgs e)
{
}
}
// Create object to the custom control and call paint event using constructor
CallRectangle obj = new CallRectangle();
this.Controls.Add(obj);
Any one let me know how to call paint event.
Thanks,
Bharathi.
Calling Invalidate, Update or Refresh will not work for you.
Here is a downloadable Sample from Microsoft with a Step-By-Step Guide on how to Inherit Directly from Control and build the Draw Events, since inheriting from a existing control (Button let's say) is quite easy as it already draws it self.
What you'll see in the above example that you'll need to create multiple drawing events based on the complexity of your Control (like a List box that have items that need to be drawn). Sample code from above link:
/// <include file='DocumentationComments.xml' path='doc/members/member[#name="M:TwoLineListBox.OnPaint"]/*'/>
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// draw on memory bitmap
CreateMemoryBitmap();
// TODO: Figure out how to avoid doing this on every paint
// calculate fields required to layout and draw list
RecalcItems(e.Graphics);
Graphics g = Graphics.FromImage(m_bmp);
// Draw the background and raise the PaintBackground event
OnPaintBackground(new ListPaintEventArgs(g));
// draw list
if (m_list != null)
DrawItems(g);
// Draw the frame around the list
Rectangle rc = new Rectangle(0, 0, this.Width - m_scrollBarWidth, this.Height - 1);
g.DrawRectangle(new Pen(Color.Black), rc);
// blit memory bitmap to screen
e.Graphics.DrawImage(m_bmp, 0, 0);
}
// This prevents the base class from doing OnPaintBackground.
// Since the OnPaint method paints the entire window there's no reason
// to let this go through. If we do it'll cause flashing.
/// <include file='DocumentationComments.xml' path='doc/members/member[#name="M:TwoLineListBox.OnPaintBackground"]/*'/>
protected override void OnPaintBackground(PaintEventArgs e)
{
}
// Called when it is time to draw the background. To take complete control of background
// drawing override this. To get called after the background is drawn by the base class
// register for the PaintBackground event.
/// <include file='DocumentationComments.xml' path='doc/members/member[#name="M:TwoLineListBox.OnPaintBackgroundII"]/*'/>
protected virtual void OnPaintBackground(ListPaintEventArgs e)
{
// Fill the background with the background colour
e.Graphics.Clear(this.BackColor);
if (PaintBackground != null)
PaintBackground(this, e);
}
// Called to draw a line item. To take complete control of drawing an item override this method.
// To let the base class draw the item first and then do your own additional work register for the
// PaintItem event.
/// <include file='DocumentationComments.xml' path='doc/members/member[#name="M:TwoLineListBox.OnPaintItem"]/*'/>
protected virtual void OnPaintItem(ListPaintEventArgs e)
{
Graphics g = e.Graphics;
Rectangle destRect = new Rectangle(); // Destination for the item image, if any
Rectangle srcRect = new Rectangle(); // Source region of the image to draw
Rectangle textRect = new Rectangle(); // Destination for the text
int lineIndent = 0; // How far the text is moved in from the left margin before drawing
Image imageToDraw = null;
string line1Text;
string line2Text;
// On the null case skip everything and just draw the separator line
if (e.Item == null)
goto DrawSeparator;
line1Text = GetStringProperty(e.Item, this.m_line1Property);
if (line1Text == null)
line1Text = e.Item.ToString();
line2Text = GetStringProperty(e.Item, this.m_line2Property);
if (line2Text == null)
line2Text = e.Item.ToString();
// Figure out if we're drawing an image per item from the object, or one for
// everything
imageToDraw = GetImageProperty(e.Item, this.m_itemImageProperty);
if (imageToDraw == null)
imageToDraw = m_itemImage;
// Calculate the position of the item image, if we have one, and the line indents
if (imageToDraw != null)
{
srcRect.X = 0;
srcRect.Y = 0;
srcRect.Width = imageToDraw.Width;
srcRect.Height = imageToDraw.Height;
// int vertPos = (m_itemHeight - m_itemImage.Height) / 2;
destRect.X = e.ClipRectangle.X + IMAGE_PADDING_X;
destRect.Y = e.ClipRectangle.Y + IMAGE_PADDING_Y;
// destRect.Y = (vertPos < 0) ? 0 : vertPos; // Center the image vertically in the line height. Handle the image being larger than the item height
// Account for an image that is taller than the item height
destRect.Height = (imageToDraw.Height > m_itemHeight - IMAGE_PADDING_Y) ? m_itemHeight - (IMAGE_PADDING_Y * 2) : imageToDraw.Height;
destRect.Width = destRect.Height;
// Set the text indent based on the image
lineIndent = IMAGE_PADDING_X + imageToDraw.Width + TEXT_PADDING_X; // Two pixels for the left indent of the image
}
else
{
// Set the text indent without using the image
lineIndent = TEXT_PADDING_X;
}
// Calculate the text rectangle
textRect.X = e.ClipRectangle.X + lineIndent;
textRect.Width = e.ClipRectangle.Width - TEXT_PADDING_X - textRect.X; // Allows for padding on the right edge too
textRect.Y = e.ClipRectangle.Y + 2;
textRect.Height = this.m_textHeightLine1;
// From here on we actually draw things. First the selected background, if necessary
if (e.Selected)
g.FillRectangle(m_brushSelBack, e.ClipRectangle);
// Draw the icon, if we have one
if (imageToDraw != null)
{
if (m_useTransparent)
g.DrawImage(imageToDraw, destRect, srcRect.Y, srcRect.Y, srcRect.Height, srcRect.Height,
GraphicsUnit.Pixel, m_imageAttributes);
else
g.DrawImage(imageToDraw, destRect, srcRect, GraphicsUnit.Pixel);
}
// Draw the text in a bounding rect to force it to truncate if too long
g.DrawString(line1Text, m_fontLine1, e.Selected ? m_brushSelText : m_brushText, textRect);
// Draw the second line
textRect.Y += m_textHeightLine1 + 3;
textRect.Height = this.m_textHeightLine2;
g.DrawString(line2Text, m_fontLine2, e.Selected ? m_brushSelText : m_brushText, textRect);
DrawSeparator:
// Draw the separator line
g.DrawLine(m_penSep, e.ClipRectangle.X, e.ClipRectangle.Y + e.ClipRectangle.Height,
e.ClipRectangle.X + e.ClipRectangle.Width, e.ClipRectangle.Y + e.ClipRectangle.Height);
// Let other people know it's time for them to draw
if (PaintItem != null)
PaintItem(this, e);
}
// Draw all the items.
private void DrawItems(Graphics g)
{
ListPaintEventArgs ListPaintEventArgs = new ListPaintEventArgs(g);
// Calculate our actual drawing area, accounting for the scroll bar
Rectangle rc = new Rectangle(0, 0, this.Width - m_scrollBarWidth, this.Height - 1);
// draw items that are visible
int curItem = 0;
for (int i = 0; (i < m_visibleCount); i++)
{
curItem = i + m_topItem;
if (curItem < m_list.Count)
{
// Calculate the drawing area for the item
ListPaintEventArgs.ClipRectangle = new Rectangle(rc.X,
rc.Y + (i * m_itemHeight),
rc.Width,
this.m_itemHeight);
// Get the item we'll be drawing with and whether it is selected
ListPaintEventArgs.Item = m_list[curItem];
ListPaintEventArgs.Selected = (m_selItem == curItem);
// Draw the item
OnPaintItem(ListPaintEventArgs);
}
}
}
// Recalculates the heights and visible counts for assorted items
// in the listbox.
// TODO: Get rid of this method by moving the rest of the items into the assorted
// properties.
private void RecalcItems(Graphics g)
{
// The text heights for a single line of text is the height of the font.
m_textHeightLine1 = g.MeasureString("W", this.m_fontLine1).ToSize().Height;
m_textHeightLine2 = g.MeasureString("W", this.m_fontLine2).ToSize().Height;
// The height for an individual item is two lines plus some padding
m_itemHeight = m_textHeightLine1 + m_textHeightLine2 + 5;
m_visibleCount = this.Height / m_itemHeight;
// Set the top item to draw to the current scroll position
m_topItem = m_scrollValue;
}
// Creates all the objects we need for drawing
private void CreateGdiObjects()
{
m_brushText = new SolidBrush(this.ForeColor);
m_brushSelText = new SolidBrush(this.m_selForeColor);
m_brushSelBack = new SolidBrush(this.m_selBackColor);
m_penSep = new Pen(this.m_separatorColor);
m_imageAttributes = new ImageAttributes();
}
// Creates a bitmap in memory to do our drawing. We'll draw on this first
// and then splat it to the screen.
private void CreateMemoryBitmap()
{
// Only create if don't have one and the size hasn't changed
if (m_bmp == null || m_bmp.Width != this.Width || m_bmp.Height != this.Height)
{
m_bmp = new Bitmap(this.Width - m_scrollBarWidth, this.Height);
// TODO: Figure out why this is here.
m_scrollBar.Left = this.Width - m_scrollBarWidth;
m_scrollBar.Top = 0;
m_scrollBar.Width = m_scrollBarWidth;
m_scrollBar.Height = this.Height;
}
}
I Hope this helps
I try to make a graphics.
When I click on my label, I want to draw a line. It works, it draw my line but at the last point there is another line going at the left top corner.. I don't know why.
(It's useless, but it's for another project, I try to understand how works the drawing)
Here's my code :
public partial class Form1 : Form
{
Pen myPen = new Pen(Color.Blue);
Graphics g = null;
int start_x = 0, start_y;
Point[] points = new Point[1000];
int test = 0;
public Form1()
{
InitializeComponent();
start_y = canvas.Height / 2;
points[0] = new Point (start_x,start_y);
myPen.Width = 1;
}
private void drawLine()
{
g.DrawLines(myPen, points);
}
private void incrementation_Click(object sender, EventArgs e)
{
test = test + 1;
incrementation.Text = test.ToString();
if(test == 1)
{
points[1] = new Point(100, start_y);
}
if (test == 2)
{
points[test] = new Point(200, 90),new Point(220, 10);
}
if (test == 3)
{
points[test] = new Point(220, 10);
drawLine();
}
}
private void canvas_Paint(object sender, PaintEventArgs e)
{
g = canvas.CreateGraphics();
}
}
A couple of issues.
You don't assign any values to points after points[3].
Point is a structure and will have a value of [0,0] at all further elements
so your lines go there.. (all 996 of them ;-)
There is more you should change:
Do the drawing in the Paint event or trigger it from there.
Do not store the Paint e.Grahpics object. You can pass it out to use it, but don't try to hold on to it.
After adding or changing the points, write canvas.Invalidate() to trigger the Paint event. This will make your drawing persistent.
To learn about persistent drawing minimize & restore the form!
Also you should use a List<Point> instead of an array. This lets you add Points without having to decide on the number of Points you want to support..
To create a new Point you write something like this:
points.Add(new Point(100, start_y) );
To draw you then use this format in the Paint event::
e.Graphics.DrawLines(myPen, points.toArray());
In the constructor you're filling first point as
points[0] = new Point (start_x,start_y);
At this moment, start_x = 0 (since you're not assigned anything else to it after declaration int start_x = 0).
Then in incrementation_Click you're assigning points[1], points[2] and points[3], but you don't changing anywhere in your code points[0].
So when you calling g.DrawLines - first point will always be (0, canvas.Height / 2)
Aside from this:
You don't need to create graphics explicitly in _Paint event handler since it can accessed as e.Graphics.
It's better to move all paintings into canvas_Paint like:
private void canvas_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLines(myPen, points);
}
and in your _Click handler instead of calling drawLine you should only call canvas.Refresh()
I have the following custom Control:
public class Line : Control
{
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using (var p = new Pen(Color.Black, 3))
{
var point1 = new Point(234, 118);
var point2 = new Point(293, 228);
e.Graphics.DrawLine(p, point1, point2);
}
}
}
And a Form where I add as new control a new instance of the Line class:
Controls.Add(new Line());
The problem is that the method OnPaint isn't called and no line is drawn. Why? How can i fix it?
Your are not giving it a Size, try creating a Constructor and setting a default size there, you also seem to be using the parent controls coordinates, I would use the location of the Usercontrol to set your start position and only be concerned with the Width and Height of the control needed to contain your line.
public Line()
{
Size = new Size(500, 500);
}
My question : How to disable a User Control to draw it's background (or Region)
Note : I already tried to override and empty OnPaintBackground or set background color to transparent.
I'm trying to bypass winform paint for my custom user controls in a custom container.
For that I thought to give a try to this : Beginners-Starting-a-2D-Game-with-GDIplus
My setup is :
A Form containing:
A User control (DrawingBoard)
A Container with elements I can drag and drop to this DrawingBoard (it's a listbox).
My render loop is inside the DrawingBoard with all elements specified in the previous link.
public DrawingBoard()
{
InitializeComponent();
//Resize event are ignored
SetStyle(ControlStyles.FixedHeight, true);
SetStyle(ControlStyles.FixedWidth, true);
SetStyle(System.Windows.Forms.ControlStyles.AllPaintingInWmPaint, true);// True is better
SetStyle(System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer, true); // True is better
// Disable the on built PAINT event. We dont need it with a renderloop.
// The form will no longer refresh itself
// we will raise the paint event ourselves from our renderloop.
SetStyle(System.Windows.Forms.ControlStyles.UserPaint, false); // False is better
}
#region GDI+ RENDERING
public Timer t = new Timer();
//This is your BackBuffer, a Bitmap:
Bitmap B_BUFFER = null;
//This is the surface that allows you to draw on your backbuffer bitmap.
Graphics G_BUFFER = null;
//This is the surface you will use to draw your backbuffer to your display.
Graphics G_TARGET = null;
Size DisplaySize = new Size(1120, 630);
bool Antialiasing = false;
const int MS_REDRAW = 32;
public void GDIInit()
{
B_BUFFER = new Bitmap(DisplaySize.Width, DisplaySize.Height);
G_BUFFER = Graphics.FromImage(B_BUFFER); //drawing surface
G_TARGET = CreateGraphics();
// Configure the display (target) graphics for the fastest rendering.
G_TARGET.CompositingMode = CompositingMode.SourceCopy;
G_TARGET.CompositingQuality = CompositingQuality.AssumeLinear;
G_TARGET.SmoothingMode = SmoothingMode.None;
G_TARGET.InterpolationMode = InterpolationMode.NearestNeighbor;
G_TARGET.TextRenderingHint = TextRenderingHint.SystemDefault;
G_TARGET.PixelOffsetMode = PixelOffsetMode.HighSpeed;
// Configure the backbuffer's drawing surface for optimal rendering with optional
// antialiasing for Text and Polygon Shapes
//Antialiasing is a boolean that tells us weather to enable antialiasing.
//It is declared somewhere else
if (Antialiasing)
{
G_BUFFER.SmoothingMode = SmoothingMode.AntiAlias;
G_BUFFER.TextRenderingHint = TextRenderingHint.AntiAlias;
}
else
{
// No Text or Polygon smoothing is applied by default
G_BUFFER.CompositingMode = CompositingMode.SourceOver;
G_BUFFER.CompositingQuality = CompositingQuality.HighSpeed;
G_BUFFER.InterpolationMode = InterpolationMode.Low;
G_BUFFER.PixelOffsetMode = PixelOffsetMode.Half;
}
t.Tick += RenderingLoop;
t.Interval = MS_REDRAW;
t.Start();
}
void RenderingLoop(object sender, EventArgs e)
{
try
{
G_BUFFER.Clear(Color.DarkSlateGray);
UIPaint(G_BUFFER);
G_TARGET.DrawImageUnscaled(B_BUFFER, 0, 0);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
#endregion
Then my elements get the event fired and try to draw what I would like:
public override void UIPaint(Graphics g)
{
Pen p = new Pen(Color.Blue,4);
p.Alignment = System.Drawing.Drawing2D.PenAlignment.Inset;
g.DrawLines(p, new Point[] { new Point(Location.X, Location.Y), new Point(Location.X + Width, Location.Y), new Point(Location.X + Width, Location.Y + Height), new Point(Location.X, Location.Y + Height), new Point(Location.X, Location.Y - 2) });
g.DrawImageUnscaled(GDATA.GetWindowImage(), Location);
}
here is what happening on my DrawingBoard :
As I can't post image ... here is the link : http://s8.postimage.org/iqpxtaoht/Winform.jpg
The background is DarkSlateGray because my G_BUFFER state to clear each tick with that, Ok
The blue rectangle is what I paint, but it get cropped, KO
The Texture is cropped, KO
The region that crop my drawing is the control size.
So from there I've tried everything I could to disable WinForm to make some magic drawing in background. Tried to override and empty everything that got paint/update/refresh/invalidate/validate on Form/DrawingBoard/Elements but nothing allowed me to get my texture or drawing to not get cropped by the control background : (
I also tried to set the background of the Element as transparent and also to set Form.TransparencyKey = blabla with each element BackColor = blabla. But failed each time.
I'm certainly missing something : / But I don't know what.
Why don't you want to draw the background? To avoid problems with flickering in my custom control (derived from class Control), here's what I did:
(1) override OnPaintBackground:
protected override void OnPaintBackground(PaintEventArgs pevent)
{
}
(2) Draw in an offscreen Bitmap and then transfer it to the screen:
Bitmap _completeFrame;
protected override void OnPaint(PaintEventArgs pe)
{
DrawFrame(); // draws onto _completeFrame, calling new Bitmap()
// when _completeFrame is null or its Size is wrong
var g = pe.Graphics;
g.DrawImage(_completeFrame, new Point());
}