Dashed shapes drawing in unexpected scale - c#

I am using a control paint event to draw graphic objects in my application. The sizes of all the object is stored with size unit of millimeters and therefore i use 'millimeter' as PageUnit for the graphic object. For some reason when i draw a shape with a DashStyle other than solid, it gets drawn in a very unexpected scale.
In the code example below I expect to see both lines being drawn one on top of the other, but what i get is the red dashed line being drawn elsewhere in larger scale.
Any idea what I am missing?
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication6
{
public partial class Form1 : Form
{
private Pen solidBlackPen = new Pen(Color.Black, 1);
private Pen dashedRedPen = new Pen(Color.Red, 1) {
DashStyle = DashStyle.Dash
};
private Point point1 = new Point(5, 5);
private Point point2 = new Point(35, 5);
public Form1()
{
InitializeComponent();
this.BackColor = Color.White;
this.Paint += new PaintEventHandler(Form1_Paint);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.PageUnit = GraphicsUnit.Millimeter;
e.Graphics.DrawLine(solidBlackPen, point1, point2);
e.Graphics.DrawLine(dashedRedPen, point1, point2);
}
}
}
Since I am new i cant upload a screenshot.

After few tests this looks to me like some kind of bug that occures mybe only on specific Os/framework.
What managed to fix me this was adding the following line before drawing the shapes:
e.Graphics.ScaleTransform(1, 1);

Related

Drawing a new circle bitmap at the click location while preserving previously drawn circles

I am trying to draw circles using Bitmap.
Each time I click the mouse, the circle I previously drew is moved to the new position.
What I want to happen is: Each time I click the mouse, a new circle is created/drawn at the position I clicked and all previously drawn circles remain without moving.
I am working with the following code:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace multirectangle
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
}
Bitmap background;
Graphics scG;
Rectangle rectangleObj;
private Point clickCurrent = Point.Empty;
private Point clickPrev = Point.Empty;
private void Form1_Load(object sender, EventArgs e)
{
background = new Bitmap(Width, Height);
rectangleObj = new Rectangle(10, 10, 30, 30);
scG = Graphics.FromImage(background);
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
clickCurrent = PointToClient(Cursor.Position);
clickPrev = clickCurrent;
if (clickPrev == Point.Empty) return;
rectangleObj.X = clickPrev.X - rectangleObj.Height / 2;// +radius;
rectangleObj.Y = clickPrev.Y - rectangleObj.Width / 2;
Refresh();
}
protected override void OnPaint(PaintEventArgs pe)
{
pe.Graphics.DrawImage(Draw(), 0, 0);
}
public Bitmap Draw()
{
Graphics scG = Graphics.FromImage(background);
Pen myPen = new Pen(System.Drawing.Color.Red, 3);
scG.Clear(SystemColors.Control);
scG.DrawEllipse(myPen, rectangleObj);
return background;
}
}
}
Your English was a little confusing. If I'm understanding your problem correctly, right now the only thing that's being drawn is the new circle where the click was, and you want all the old ones to persist as well? In which case, there are two options:
Don't clear the bitmap before you draw. scG.Clear(SystemColors.Control); will clear the bitmap you just drew. If you remove that line and don't clear the bitmap, then the next time you click, it will then draw the new ellipse right on top of the last bitmap.
If you want a fresh drawing/bitmap everytime, you would need a list of your rectangleObj . Each time you click, you add that point to your rectangleObj collection. Then in your draw method, you would iterate through the collection and draw all of them.
I notice a few things. First, in Form1_MouseDown(), you have this:
clickCurrent = PointToClient(Cursor.Position);
clickPrev = clickCurrent;
You are overwriting the old position (clickPrev) before you even save it. If you want to keep both positions, you should put them in a simple structure, like a List. When you get a new point, just Add() it to the list. Then, in your Draw() routine, loop over all the elements in the list and draw them all.
If you just want two positions--and only two--just swap your statements like this:
clickPrev = clickCurrent;
clickCurrent = PointToClient(Cursor.Position);
And you'll have to allocate another rectangle object for the drawing, although it would make more sense to take care of this in the Draw() routine.
Swap the position of the following statements
clickCurrent = PointToClient(Cursor.Position);
clickPrev = clickCurrent;
I think you are assigning the clickCurrent to clickPrevious after you initialize clickCurrent. It needs to be the other way.
Please try this
Rectangle rectangleObj;
Bitmap background;
Graphics scG;
Pen myPen;
private void Form1_Load(object sender, EventArgs e)
{
rectangleObj = new Rectangle(10, 10, 30, 30);
background = new Bitmap(Width, Height);
scG = Graphics.FromImage(background);
myPen = new Pen(Color.Red, 3);
BackgroundImage = background;
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
var point = PointToClient(Cursor.Position);
rectangleObj.X = point.X - rectangleObj.Height / 2;
rectangleObj.Y = point.Y - rectangleObj.Width / 2;
scG.DrawEllipse(myPen, rectangleObj);
Refresh();
}
OnPaint and Draw methods removed. As well as clickCurrent and clickPrev fields.
When you change the form size (for example, maximize it), Bitmap and Graphics remain the same, so you get this effect. To avoid this, you need to add the event handler
private void Form1_SizeChanged(object sender, EventArgs e)
{
background = new Bitmap(Width, Height);
scG = Graphics.FromImage(background);
BackgroundImage = background;
}
Note that each time you resize the form, all previously drawn is erased. If this is undesirable, a different approach is needed for drawing. Let me know, I will give another example.

Rounded picturebox with antialias C#

I used an advise from this topic: Rounded edges in picturebox C# to make my picturebox edges rounded. So, I claimed a new control using this code:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
class OvalPictureBox : PictureBox {
public OvalPictureBox() {
this.BackColor = Color.DarkGray;
}
protected override void OnResize(EventArgs e) {
base.OnResize(e);
using (var gp = new GraphicsPath()) {
gp.AddEllipse(new Rectangle(0, 0, this.Width-1, this.Height-1));
this.Region = new Region(gp);
}
}
}
But there is my question: how could I add antialias to my control? I read some topics like this: Possible to have anti-aliasing when drawing a clipped image? but there is just drawing functions are used, and I need to implement antialias in my own control, which parent is PictureBox.
I also tried to override OnPaint method to get PaintEventArgs to use the SmoothingMode, but my code sucked and didn`t work properly.
So I deleted it and this is what I have now:
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
using (var gp = new GraphicsPath())
{
gp.AddEllipse(new Rectangle(0, 0, this.Width - 1, this.Height - 1));
this.Region = new Region(gp);
}
}
What should I add to this method?

Get coordinates of a drawing point from Chart Values in TextBoxes?

In the designer I have two TextBoxes.
And also a Chart control.
I want that when I type in the first textBox the number 120 and in the second one type the number 1 it will draw a point on the chart in 120,1 but I mean 120 and 1 as axis x and axis y values.
The red filled circle is not at 120 and 1.
I mean that the red circle should be drawn on the left axis on 120.
And if I will put instead 120 116 and instead 1 25 then the circle should be drawn at the left axis 116 and on the bottom axis on 25.
But now the circle is drawn out of the chart.
This is my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using System.Drawing.Drawing2D;
using System.Collections;
namespace Test
{
public partial class Form1 : Form
{
private Point startPoint = new Point();
private Point endPoint = new Point();
private int X = 0;
private int Y = 0;
private List<Point> points = new List<Point>();
private Point lastPoint = Point.Empty;
private ArrayList myPts = new ArrayList();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Random rdn = new Random();
for (int i = 120; i > 0; i--)
{
chart1.Series["Series1"].Points.AddXY
(rdn.Next(0, 10), rdn.Next(0, 10));
}
chart1.Series["Series1"].ChartType = SeriesChartType.FastLine;
chart1.Series["Series1"].Color = Color.Red;
ChartArea area = chart1.ChartAreas[0];
area.AxisX.Minimum = 1;
area.AxisX.Maximum = 30;
area.AxisY.Minimum = 1;
area.AxisY.Maximum = 120;
LineAnnotation line = new LineAnnotation();
Point p1 = new Point(1, 120);
chart1.Annotations.Add(line);
line.AxisX = area.AxisX;
line.AxisY = area.AxisY;
line.IsSizeAlwaysRelative = false;
line.X = 1; line.Y = 120;
line.Right = 30; line.Bottom = 1;
line.LineColor = Color.Blue;
line.LineWidth = 3;
}
SolidBrush myBrush = new SolidBrush(Color.Red);
private void chart1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
foreach (Point p in myPts)
g.FillEllipse(myBrush, p.X, p.Y, 10, 10);
}
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
myPts.Add(new Point(X,Y));
chart1.Invalidate();
}
private void txtT_TextChanged(object sender, EventArgs e)
{
X = int.Parse(txtWeight.Text);
}
private void txtDays_TextChanged(object sender, EventArgs e)
{
Y = int.Parse(txtDays.Text);
}
}
}
What I did is that after I enter both textBoxes values then when I click anywhere on the Chart control area with the mouse it should draw the circule on the coordinates from the TextBoxes.
But the circle is not drawing on the right place.
The textBox name txtT is the left one the axis on the left values.
The textBox txtDays should is the axis on the bottom values.
The task of translating drawing coordinates into DataPoints and back is not exactly intuitive.
It is possible but you need to know the rules and pay a certain price.
I have outlined the way in this post and it is worth looking into..
But as the problem is coming up repeatedly, here is a more general solution.
Here is how to call it:
private void button11_Click(object sender, EventArgs e)
{
valuePoints.Add(new PointF(640, 1));
valuePoints.Add(new PointF(670, 10));
paintToCalaculate = true;
chart1.Invalidate();
}
And here is the result: Two red points drawn at the values 640, 1 and 670, 10:
The points are placed correctly event though I have zoomed and scrolled in the chart and also resized it..
To make it work we need three class level variables: A flag and two point lists. One list is the input with the values in the chart where the points are, the other is the output, receiving the current pixel coordinates.
bool paintToCalaculate = false;
List<Point> drawPoints = new List<Point>();
List<PointF> valuePoints = new List<PointF>();
I use a flag to avoid recalculating the Points when the system causes redraws. And after setting it I trigger the Paint event by Invalidating the Chart. During the Paint event I reset the flag.
Please note that these values are very volatile: They change:
Whenever you zoom or scroll
Whenever you resize the chart
Whenever the layout changes, maybe because new points need room or trigger a change in a label format..
Therefore those drawing coordinates will have to be updated on each such event!
Here is one example, that takes care of zoom and scrolling:
private void chart1_AxisViewChanged(object sender, ViewEventArgs e)
{
paintToCalaculate = true;
chart1.Invalidate();
}
You need to add these two lines, or a function to wrap them, to a few other spots in your program, depending what things you allow to happen in the chart.. Resize is also an obvious candidate..
Now for the actual caculation. It is using the ValueToPixelPosition, which does all the work. Unfortunately is only works inside of any of the three paint events of a chart (PrePaint,Paint and PostPaint) . Here I use the normal Paint event.
private void chart1_Paint(object sender, PaintEventArgs e)
{
if (paintToCalaculate)
{
Series s = chart1.Series.FindByName("dummy");
if (s == null) s = chart1.Series.Add("dummy");
drawPoints.Clear();
s.Points.Clear();
foreach (PointF p in valuePoints)
{
s.Points.AddXY(p.X, p.Y);
DataPoint pt = s.Points[0];
double x = chart1.ChartAreas[0].AxisX.ValueToPixelPosition(pt.XValue);
double y = chart1.ChartAreas[0].AxisY.ValueToPixelPosition(pt.YValues[0]);
drawPoints.Add(new Point((int)x, (int)y));
s.Points.Clear();
}
paintToCalaculate = false;
chart1.Series.Remove(s);
}
//..
// now we can draw our points at the current positions:
foreach (Point p in drawPoints)
e.Graphics.FillEllipse(Brushes.Red, p.X - 2, p.Y - 2, 4, 4);
}
Note that I add and remove a dummy Series and add and clear one Point to it for each data point, just to calculate its pixel coordinates. Yes, a bit involved, but the results are worth it..
I assume you can change the Button_Click code to read in the values from your TextBoxes instead of the using my hard-coded numbers..

How to draw using trackbar?

well how do you draw in C# using variables?
ive managed to draw some shapes but only when i hardcode in the lengths. i need to draw shapes using a trackbar to get the lengths.
public abstract class Shape
{
//private String shape;
private int length;
}
public virtual void setLength(int newLength)
{
this.length = newLength;
}
public virtual int getLength()
{
return length;
}
//public String getShape()
//{
// return shape;
//}
//abstract public double getLength(float length);
abstract public float getPerimeter(int length);
abstract public float getArea(int length);
only showing square class but this project also includes triangle and square.
using System;
using System.Drawing;
public class Square : Shape
{
private float perimeter, area;
public override float getPerimeter(int length)
{
perimeter = length*4;
return perimeter;
}
public override float getArea(int length)
{
area = length*length;
return area;
}
}
this is the class with all my event handlers
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace shapes
{
//private System.Windows.Forms.TrackBar trackBar1;
public partial class Form1 : Form
{
private Shape shape;
private int length = 0;
private int shapeL = 0;
public Form1()
{
InitializeComponent();
}
private void panel2_Paint(object sender, PaintEventArgs e)
{
}
private void menuStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
}
private void trackBar1_Scroll(object sender, EventArgs e)
{
label3.Text = "Length Slider: " + trackBar1.Value;
textBox1.Text = shape.getPerimeter(shape.getLength()).ToString("0.00");
textBox2.Text = shape.getArea(shape.getLength()).ToString("0.00");
textBox1.Refresh();
textBox2.Refresh();
length = trackBar1.Value;
shape.setLength(length);
}
private void onCircleClick(object sender, EventArgs e)
{
shape = new Circle();
//length = trackBar1.Value;
length = shape.getLength();
this.Refresh();
using (Graphics g = this.panel1.CreateGraphics())
{
Pen pen = new Pen(Color.Black, 2);
Graphics formGraphics;
formGraphics = this.panel1.CreateGraphics();
formGraphics.DrawEllipse(pen, 50, 50, length, length);
//g.DrawEllipse(pen, 100, 100, length, length);
}
}
private void onSquareClick(object sender, EventArgs e)
{
shape = new Square();
length = trackBar1.Value;
using (Graphics g = this.panel1.CreateGraphics())
{
Pen pen = new Pen(Color.Black, 2);
g.DrawRectangle(pen, 50, 50, length, length);
System.Windows.Forms.MessageBox.Show("lenght is: " + length);
}
}
private void onTriangleClick(object sender, EventArgs e)
{
shape = new Triangle();
length = trackBar1.Value;
using (Graphics g = this.panel1.CreateGraphics())
{
SolidBrush blueBrush = new SolidBrush(Color.Blue);
// Create points that define polygon.
Point point1 = new Point(50, 50);
Point point2 = new Point(50, 100);
Point point3 = new Point(100, 50);
Point[] curvePoints = { point1, point2, point3};
// Draw polygon to screen.
g.FillPolygon(blueBrush, curvePoints);
}
}
private void shapeToolStripMenuItem_Click(object sender, EventArgs e)
{
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
Pen pen = new Pen(Color.Black, 2);
Graphics g = pe.Graphics;
g = this.CreateGraphics();
g.DrawRectangle(pen, 50, 50, length, length);
}
private void OnPaint(object sender, PaintEventArgs e)
{
}
}
}
yes its very messy, as you can see ive tried various things.
whats the difference between the panel1_paint and onPaint?
as you can see im not too sure how to use eventhandlers, the onCircleClick is basically a menu item button but how do i activate a different eveenthandler(panel1_Paint) from another eventhandler(onCircleClick)?
do graphics need to drawn in a *_paint/OnPaint method? ive gotten mine to draw in just the normal panel.
next is whats the best course of action to get the trackbar value to the shape object and back again to the method? yes the data is being saved (i think) when i use displayMessage(shape.getLength) it displays the length and is usually one off.
whats the equilent to repaint() in java for c#? ive tried this.Refresh(); but it doesnt work itll draw the shape then makes it disappear.
am i writing my setters/getters properly? or should i use
public int X
{
get {return x;}
set {x = value;}
}
in java, graphics will draw on any panel, in c# does it need to be in a specific container?
this is very simple, lets say that you want to draw on panel2.
all you have to do is to write this inside your private void panel2_Paint(object sender, PaintEventArgs e) body.
{
e.Graphics.Clear(panel1.BackgroundColor);
int length = trackBar1.Value;
Pen pen = new Pen(Color.Black, 2);
e.Graphics.DrawRectangle(pen, 50, 50, length, length);
}
and whenever you want to refresh the drawing, you can call either panel2.Refresh() or panel2.Invalidate(). both would do the job.
note that if you did this, panel2 won't get cleared after drawing the shape as it happened with you before.
note also that the panel would flicker as you change the trackbar value. i know how to handle this, but i don't want to complicate the solution for now.

How to print rectangle with exact size in c#?

I am new to Dot Net, I want to print a rectangle with 20mm width and 8mm height exactly if I measure with scale. I also want to print text exactly in the middle of rectangle.Can anyone suggest me how can I achieve this?
I am really sorry for not being clear earlier. I have tried using "PageUnits" its working fine. However, I have problem with the margins.
I am able to print correct margins(8.8mm left and 22mm Top) if I am using the printer "HP LaserJet P2035n". If print using "Canon iR2020 PCL5e" I am getting incorrect margins(8.1mm left and 8.0mm Top) where I should get 8.8mm left and 22mm top margins. Can someone explain me where I am doing wrong.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Printing;
namespace ConsoleApplication6
{
class DrawShape
{
public static void DrawRec()
{
PrintDocument doc = new PrintDocument();
doc.PrintPage += doc_PrintPage;
doc.Print();
}
static void doc_PrintPage(object sender, PrintPageEventArgs e)
{
Graphics g = e.Graphics;
PageSettings PageSet = new PageSettings();
float MarginX = PageSet.PrintableArea.X;
float MarginY = PageSet.PrintableArea.Y;
float x = (float)(8.8-((MarginX/100)*25.4));
float y = (float)(22-((MarginY/100)*25.4));
g.PageUnit = GraphicsUnit.Millimeter;
g.DrawRectangle(Pens.Black, x, y, 20, 8);
}
}
}
You may want to start with this:
private void button1_Click(object sender, EventArgs e)
{
using (Graphics formGraphics = this.CreateGraphics())
{
formGraphics.PageUnit = GraphicsUnit.Millimeter;
formGraphics.DrawRectangle(Pens.Blue, 0, 0, 20, 80);
}
}
You can then using DrawString on the Graphics object to draw text inside your rectangle.

Categories

Resources