I am working on a small project and packing it into GUI. The reference source code is DrawTools(Download source code - 61.1 Kb).
The reference source code demos a drawing tool in C# WinForms.
The function is to draw different figures like rectangle, ellipse, polygon, etc.
I want to use the location and size information of these figures to do further work, so if I draw a rectangle in the draw area, could C# WinForms returns the parameter of this figure(eg. x,y,width,height in the DrawRectangle.cs)?
Code as follow:
public DrawRectangle(int x, int y, int width, int height)
{
rectangle.X = x;
rectangle.Y = y;
rectangle.Width = width;
rectangle.Height = height;
Initialize();
}
Further more, How to get the returned parameters and then displayed in a new dialog?
when you draw these shapes: rectangle, ellipse, polygon, etc. you are using the location and width and height of them. if you want to save them to an object create one and save them in a list of some other structure...
for example:
List<object> shapes = new List<object>();
private void drawSquare(int x1, int y1, int x2, int y2)
{
shapes.Add(new Rectangle(x1, y1, x2, y2));
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Graphics g = e.Graphics;
foreach (var shape in shapes)
{
if (shape is Rectangle)
{
g.DrawRectangle(new Pen(Color.Black), (Rectangle)shape);
}
}
}
this is just a small example, you should check the OnPaint method and Graphics to get more information on what you can and should do
You can add some event to support notifying what is happening, something like this:
public class InitRectangleEventArgs : EventArgs {
public Rectangle Rectangle {get;set;}
}
public delegate void InitRectangleEventHandler(object sender, InitRectangleEventArgs e);
public event InitRectangleEventHandler InitRectangle;
public DrawRectangle(int x, int y, int width, int height)
{
rectangle.X = x;
rectangle.Y = y;
rectangle.Width = width;
rectangle.Height = height;
if(InitRectangle != null) InitRectangle(this, new InitRectangleEventArgs { Rectangle = new Rectangle(x,y,width,height)});
Initialize();
}
//To use it, just subscribe the event so that you can know the
//info of the Rectangle everytime it is initialized
InitRectangle += (s,e) => {
//Get the info from the Rectangle property of e: e.Rectangle
//....
};
Related
I am porting (or attempting to) legacy code from a very slow WinForms app to WPF. The WinForms app falls down at the rendering of large images, and it has to rerender very frequently as the user pans around so it has to go.
I have a system in place to draw a canvas in xaml that allows zooming and panning with a custom child of Border, in the xaml it is named imaginatively as "canvas". I am having issues drawing simple ellipses to the canvas from other classes.
namespace ZoomPan
{
public class DisplayManager
{
protected void DrawIcon(Locale locale, Color color)
{
Brush sensorBrush = new SolidColorBrush(color);
Brush sensorOutline = new SolidColorBrush(Colors.Black);
int x = locale.x;
int y = locale.y;
halfSize = 10;
DrawEllipse(locale.Name, sensorBrush, sensorOutline, x - halfSize, y - halfSize, 2 * halfSize, 2 * halfSize);
}
public void DrawEllipse(string name, Brush sensorBrush, Brush sensorPen, int x, int y, int width, int height)
{
double thickness = 5;
Ellipse ellipse = new Ellipse();
/*
Define ellipse
*/
canvas.Children.Add(ellipse);
}
}
}
This was my first attempt, which throws up errors around the final canvas.Children line, namely "The name 'canvas' does not exist in the current context"
I tried moving DrawEllipse to a separate class here, which throws up different errors
namespace ZoomPan
{
public class DisplayManager
{
protected void DrawIcon(Locale locale, Color color)
{
Brush sensorBrush = new SolidColorBrush(color);
Brush sensorOutline = new SolidColorBrush(Colors.Black);
int x = locale.x;
int y = locale.y;
halfSize = 10;
DrawEllipse(locale.Name, sensorBrush, sensorOutline, x - halfSize, y - halfSize, 2 * halfSize, 2 * halfSize);
}
}
public class MainWindow : Window
{
public void DrawEllipse(string name, Brush sensorBrush, Brush sensorPen, int x, int y, int width, int height)
{
double thickness = 5;
Ellipse ellipse = new Ellipse();
/*
Define ellipse
*/
canvas.Children.Add(ellipse);
}
}
}
These errors are around the call to DrawEllipse in the first class:
"The name 'DrawEllipse' does not exist in the current context"
and when I add MainWindow. before the DrawEllipse:
"An object reference is required for the non-static field, method, or property 'ZoomPan.MainWindow.DrawEllipse(string, System.Windows.Media.Brush, System.Windows.Media.Brush, int, int, int, int)'"
When I first played around with WPF, I could get it to display onto the canvas when the DrawEllipse method was inside the public class MainWindow:Window of the MainWindow.xaml.cs file, hence the second attempt above.
I feel I am missing something obvious, and couldn't find any exact dupes of this question on the site.
The other classes need to be passed the canvas object to be able to use it.
Either pass the canvas to the DisplayManager through the constructor once so it can be stored and then used in all methods:
public class DisplayManager
{
Canvas canvas;
public DisplayManager(Canvas canvas)
{
this.canvas = canvas;
}
}
Or pass it into each method each time as the first parameter:
public class DisplayManager
{
public void DrawEllipse(Canvas canvas, string name, Brush sensorBrush, Brush sensorPen, int x, int y, int width, int height)
{
}
}
I often need to draw items in a Graphics object and the way I've been doing it is to have a function DrawItem that receives the Graphics object and an offsetX and offsetY parameters, which determine at which point the item will be drawn.
The problem is that the code inside DrawItem would look a lot better if there would be a method in Graphics that would give me a version of the Graphics where the X and Y axis zeroes are at some other point, something like myGraphics.DisplacedGraphics(offsetX, offsetY). This way I would just pass this Graphics objects to my DrawItem method which wouldn't need to receive the other two parameters. Is there such function or what's the closest thing?
Edit: On the meanwhile this is what I wrote, but seems like such a basic requirement I still hope there already exists such functionality (I'd still need to add a bunch of methods but these are all I need for now) (note the DisplacedCanvas method):
public class Canvas
{
private readonly Graphics _Graphics;
private readonly int _OriginX = 0;
private readonly int _OriginY = 0;
public Canvas(Graphics graphics, int originX, int originY)
{
_Graphics = graphics;
_OriginX = originX;
_OriginY = originY;
}
public Canvas(Graphics graphics) : this(graphics, 0, 0) { }
public SizeF MeasureString(string text, Font font)
{
return _Graphics.MeasureString(text, font);
}
public void FillRectangle(Brush brush, int x, int y, int width, int height)
{
_Graphics.FillRectangle(brush, _OriginX + x, _OriginY + y, width, height);
}
public void DrawString(string s, Font font, Brush brush, float x, float y)
{
_Graphics.DrawString(s, font, brush, _OriginX + x, _OriginY + y);
}
public Canvas DisplacedCanvas(int x, int y)
{
return new Canvas(_Graphics, _OriginX + x, _OriginY + y);
}
}
I'm pretty sure that the TranslateTransform() method will do what you're asking for.
The origin is typically the upper-left-hand corner of the drawing surface. The translation operation consists of multiplying the transformation matrix by a matrix whose translation part is the dx and dy parameters. This method applies the translation by prepending the translation matrix to the transformation matrix.
So if you want the new origin to be at 100, 50, then you would first call graphics.TranslateTransform(100, 50) before drawing your image.
I'm designing an app that loads an image into a PictureBox and allows the user to draw a selection rectangle on it. Currently, I use the Paint event and a boolean to clear the previously-drawn rectangle (since it's a drag-able selection box).
Question:
The code fails because the previous rectangle isn't cleared from the image. Although each rectangle that is drawn is transparent, the effect is an opaque rectangle because the previous rectangles aren't cleared. How can I clear these rectangles?
Logic:
saveState defaults to true. When the Paint event is triggered the first time, the state containing the normal image is saved. When a MouseDown event is triggered, we register the starting position of the rectangle and a boolean that indicates a rectangle is being drawn.
When a MouseMove event is triggered, we draw a rectangle at the current coordinates. Since the Paint event is triggered (I think) when this is drawn and saveState is false, we restore the normal image before drawing the rectangle.
Finally, when a MouseUp event is triggered, saveState is set to true, so the graphics state, with the last rectangle drawn, is saved, and we're back at the beginning.
I read about ControlPaint.DrawReversibleFrame, but since this article and this question give me the impression that it isn't designed for drawing on images, but rather on the screen or the form directly, I'm not sure that's what I need.
Code:
public partial class MainWindow : Form
{
private bool drawingRectangle;
private int x1, y1, x2, y2;
private Image currentImage;
private GraphicsState previousState;
private bool saveState;
public MainWindow()
{
InitializeComponent();
this.drawingRectangle = false;
this.saveState = true;
}
private void EditorPictureBox_MouseDown(object sender, MouseEventArgs e)
{
this.x1 = e.X;
this.y1 = e.Y;
this.drawingRectangle = true;
}
private void EditorPictureBox_MouseMove(object sender, MouseEventArgs e)
{
if (this.drawingRectangle)
{
this.x2 = e.X;
this.y2 = e.Y;
Graphics g = Graphics.FromImage(this.currentImage);
int[] dim = ImageLibrary.CalculateRectangleDimensions(this.x1, this.y1, this.x2, this.y2);
g.FillRectangle(new SolidBrush(Color.FromArgb(100, 128, 255, 255)), dim[0], dim[1], dim[2], dim[3]);
this.Refresh();
}
}
private void EditorPictureBox_Paint(object sender, PaintEventArgs e)
{
if (this.saveState)
{
this.previousState = e.Graphics.Save();
this.saveState = false;
}
else
e.Graphics.Restore(this.previousState);
}
private void EditorPictureBox_MouseUp(object sender, MouseEventArgs e)
{
if (this.drawingRectangle)
{
this.drawingRectangle = false;
// When the mouse click is released, save the graphics state
this.saveState = true;
}
}
private void LoadImage2Button_Click(object sender, EventArgs e)
{
this.currentImage = Image.FromFile("goat2.jpg");
this.EditorPictureBox.Image = this.currentImage;
}
}
This is the code for CalculateRectangleDimensions (stored in a static library):
public static int[] CalculateRectangleDimensions(int x1, int y1, int x2, int y2)
{
int[] dimensions = new int[4]; // x1, y1, width, height
if (x1 <= x2) // Mouse was dragged to the right
{
dimensions[0] = x1;
dimensions[2] = x2 - x1;
}
else // Mouse was dragged to the right
{
dimensions[0] = x2;
dimensions[2] = x1 - x2;
}
if (y1 <= y2) // Mouse was dragged up
{
dimensions[1] = y1;
dimensions[3] = y2 - y1;
}
else // Mouse was dragged down
{
dimensions[1] = y2;
dimensions[3] = y1 - y2;
}
return dimensions;
}
Graphics.Save doesn't save the entire contents of the graphics when you call it, it just saves state information, like translations, scale, transforms, etc.
if you want to undo a draw you've already done, you'd have to do something like reversible paint does, or you'll have to redraw the original image when you want to undo your draw.
I have problem with my C# winform project.
I have function that draw squares:
public void DrawingSquares(int x, int y)
{
System.Drawing.Graphics graphicsObj;
graphicsObj = this.CreateGraphics();
Pen myPen = new Pen(System.Drawing.Color.Black, 5);
Rectangle myRectangle = new Rectangle(x, y, 100, 100);
graphicsObj.DrawRectangle(myPen, myRectangle);
}
private void button1_Click(object sender, EventArgs e)
{
z = Convert.ToInt16(textBox1.Text)-1;
k = Convert.ToInt16(textBox2.Text)-1;
DrawAllSquares();
}
private void DrawAllSquares()
{
int tempy = y;
for (int i = 0; i < z; i++)
{
DrawingSquares(x, y);
for (int j = 0; j < k - 1; j++)
{
tempy += 50;
DrawingSquares(x, tempy);
}
x += 50;
tempy = y;
}
}
In my project, I have a function that I use to move button around the form at runtime, but when the button is moved onto the drawing the drawing is deleted.
What can I do to make the drawing permanent?
If you need permanently (in terms of application life time), by any means, you need to use it inside you Control's (the Control where rectangle is have to be drawn), OnPaint method.
If you need an animation too: it could be resolved by using a timer and changing coordinates that you pass like a parameters to your DrawSquares.
Hope this helps.
EDIT
A pseudocode:
public class MyControl : Control
{
public override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
DrawingSquares(e.Graphics, valueX, valueY);
}
public void DrawingSquares(Graphics graphicsObj, int x, int y)
{
Pen myPen = new Pen(System.Drawing.Color.Black, 5);
Rectangle myRectangle = new Rectangle(x, y, 100, 100);
graphicsObj.DrawRectangle(myPen, myRectangle);
}
}
valueX and valueY are relative X and Y coordinates where you want the rectangle to be drawn.
These coordinates can be constant values, or you can change them from some timer and call Invalidate() on MyControl, so paint will be executed.
I like the ToolStripProfessionalRenderer style quite a lot, but I do not like the way it renders a ToolStripTextBox. Here, ToolStripSystemRenderer does a better job IMO. Now is there a way to combine both renderers' behaviour to use system style for text boxes and pro style for everything else? I have successfully managed to use pro style for buttons and system style for the rest (by deriving both classes). But text boxes in a ToolStrip don't seem to be handled by the renderer. Using .NET Reflector, those text boxes don't even seem to have a Paint event handler, although it's called by the ToolStrip.OnPaint method. I'm wondering where's the code to paint such a text box at all and how it can be configured to draw a text box like all other text boxes.
If you just want system rendering, the easiest approach is to use ToolStripControlHost instead:
class ToolStripSystemTextBox : ToolStripControlHost
{
public ToolStripSystemTextBox : base(new TextBox()) { }
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[TypeConverter(typeof(ExpandableObjectConverter))]
public TextBox TextBox { get { return Control as TextBox; } }
}
I've taken the easy way out here and exposed the underlying TextBox directly to the form designer, instead of delegating all its properties. Obviously you can write all the property delgation code if you want.
On the other hand, if anyone wants to do truly custom rendering, I'll tell you what ToolStripTextBox does. Instead of hosting a TextBox directly, it hosts a private derived class called ToolStripTextBoxControl. This class overrides its WndProc in order to directly handle WM_NCPAINT. And then instead of delegating the actual drawing to the Renderer, it checks the Renderer's Type, and then branches to different rendering code inside of ToolStripTextBoxControl. It's pretty ugly.
It may not be necessary to dive into "WndProc" either. This was done without it:
The Question really is how do you make a "nice looking" TextBox, because as described by j__m, you can just use ToolStripControlHost, to host a custom control in your tool strip.
More here:
http://msdn.microsoft.com/en-us/library/system.windows.forms.toolstripcontrolhost.aspx
And as documented, the control you use can be a Custom Control.
Firstly, It's insanely tricky to make a custom TextBox Control. If you want to go:
public partial class TextBoxOwnerDraw : TextBox
You are in for HUGE trouble! But it doesn't have to be. Here is a little trick:
If you make a custom control as a Panel, then add the TextBox to the Panel, then set the Textbox borders to None... you can achieve the result as above, and best of all, its just a regular old TextBox, so cut copy paste all works, right click works!
Ok, here is the code for a nice looking textbox:
public partial class TextBoxOwnerDraw : Panel
{
private TextBox MyTextBox;
private int cornerRadius = 1;
private Color borderColor = Color.Black;
private int borderSize = 1;
private Size preferredSize = new Size(120, 25); // Use 25 for height, so it sits in the middle
/// <summary>
/// Access the textbox
/// </summary>
public TextBox TextBox
{
get { return MyTextBox; }
}
public int CornerRadius
{
get { return cornerRadius; }
set
{
cornerRadius = value;
RestyleTextBox();
this.Invalidate();
}
}
public Color BorderColor
{
get { return borderColor; }
set
{
borderColor = value;
RestyleTextBox();
this.Invalidate();
}
}
public int BorderSize
{
get { return borderSize; }
set
{
borderSize = value;
RestyleTextBox();
this.Invalidate();
}
}
public Size PrefSize
{
get { return preferredSize; }
set
{
preferredSize = value;
RestyleTextBox();
this.Invalidate();
}
}
public TextBoxOwnerDraw()
{
MyTextBox = new TextBox();
this.Controls.Add(MyTextBox);
RestyleTextBox();
}
private void RestyleTextBox()
{
double TopPos = Math.Floor(((double)this.preferredSize.Height / 2) - ((double)MyTextBox.Height / 2));
MyTextBox.BackColor = Color.White;
MyTextBox.BorderStyle = BorderStyle.None;
MyTextBox.Multiline = false;
MyTextBox.Top = (int)TopPos;
MyTextBox.Left = this.BorderSize;
MyTextBox.Width = preferredSize.Width - (this.BorderSize * 2);
this.Height = MyTextBox.Height + (this.BorderSize * 2); // Will be ignored, but if you use elsewhere
this.Width = preferredSize.Width;
}
protected override void OnPaint(PaintEventArgs e)
{
if (cornerRadius > 0 && borderSize > 0)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
Rectangle cRect = this.ClientRectangle;
Rectangle safeRect = new Rectangle(cRect.X, cRect.Y, cRect.Width - this.BorderSize, cRect.Height - this.BorderSize);
// Background color
using (Brush bgBrush = new SolidBrush(MyTextBox.BackColor))
{
DrawRoundRect(g, bgBrush, safeRect, (float)this.CornerRadius);
}
// Border
using (Pen borderPen = new Pen(this.BorderColor, (float)this.BorderSize))
{
DrawRoundRect(g, borderPen, safeRect, (float)this.CornerRadius);
}
}
base.OnPaint(e);
}
#region Private Methods
private GraphicsPath getRoundRect(int x, int y, int width, int height, float radius)
{
GraphicsPath gp = new GraphicsPath();
gp.AddLine(x + radius, y, x + width - (radius * 2), y); // Line
gp.AddArc(x + width - (radius * 2), y, radius * 2, radius * 2, 270, 90); // Corner (Top Right)
gp.AddLine(x + width, y + radius, x + width, y + height - (radius * 2)); // Line
gp.AddArc(x + width - (radius * 2), y + height - (radius * 2), radius * 2, radius * 2, 0, 90); // Corner (Bottom Right)
gp.AddLine(x + width - (radius * 2), y + height, x + radius, y + height); // Line
gp.AddArc(x, y + height - (radius * 2), radius * 2, radius * 2, 90, 90); // Corner (Bottom Left)
gp.AddLine(x, y + height - (radius * 2), x, y + radius); // Line
gp.AddArc(x, y, radius * 2, radius * 2, 180, 90); // Corner (Top Left)
gp.CloseFigure();
return gp;
}
private void DrawRoundRect(Graphics g, Pen p, Rectangle rect, float radius)
{
GraphicsPath gp = getRoundRect(rect.X, rect.Y, rect.Width, rect.Height, radius);
g.DrawPath(p, gp);
gp.Dispose();
}
private void DrawRoundRect(Graphics g, Pen p, int x, int y, int width, int height, float radius)
{
GraphicsPath gp = getRoundRect(x, y, width, height, radius);
g.DrawPath(p, gp);
gp.Dispose();
}
private void DrawRoundRect(Graphics g, Brush b, int x, int y, int width, int height, float radius)
{
GraphicsPath gp = getRoundRect(x, y, width, height, radius);
g.FillPath(b, gp);
gp.Dispose();
}
private void DrawRoundRect(Graphics g, Brush b, Rectangle rect, float radius)
{
GraphicsPath gp = getRoundRect(rect.X, rect.Y, rect.Width, rect.Height, radius);
g.FillPath(b, gp);
gp.Dispose();
}
#endregion
}
Now for the ToolStripControlHost
public partial class ToolStripTextBoxOwnerDraw : ToolStripControlHost
{
private TextBoxOwnerDraw InnerTextBox
{
get { return Control as TextBoxOwnerDraw; }
}
public ToolStripTextBoxOwnerDraw() : base(new TextBoxOwnerDraw()) { }
public TextBox ToolStripTextBox
{
get { return InnerTextBox.TextBox; }
}
public int CornerRadius
{
get { return InnerTextBox.CornerRadius; }
set
{
InnerTextBox.CornerRadius = value;
InnerTextBox.Invalidate();
}
}
public Color BorderColor
{
get { return InnerTextBox.BorderColor; }
set
{
InnerTextBox.BorderColor = value;
InnerTextBox.Invalidate();
}
}
public int BorderSize
{
get { return InnerTextBox.BorderSize; }
set
{
InnerTextBox.BorderSize = value;
InnerTextBox.Invalidate();
}
}
public override Size GetPreferredSize(Size constrainingSize)
{
return InnerTextBox.PrefSize;
}
}
Then When you want to use it, just add it to the tool bar:
ToolStripTextBoxOwnerDraw tBox = new ToolStripTextBoxOwnerDraw();
this.toolStripMain.Items.Add(tBox);
or however you want to add it. If you are in Visual Studio, the preview window supports rendering this Control.
There is only one thing to remember, when accessing the TextBox with the actual text in it, its:
tBox.ToolStripTextBox.Text;