Is there a way to drawstring and then remove it?
I've used following classes to Undo/Redo Rectangle, Circle, Line, Arrow type shapes but cant figure how i can remove drawn string.
https://github.com/Muhammad-Khalifa/Free-Snipping-Tool/blob/master/Free%20Snipping%20Tool/Operations/UndoRedo.cs
https://github.com/Muhammad-Khalifa/Free-Snipping-Tool/blob/master/Free%20Snipping%20Tool/Operations/Shape.cs
https://github.com/Muhammad-Khalifa/Free-Snipping-Tool/blob/master/Free%20Snipping%20Tool/Operations/ShapesTypes.cs
Here is how i'm adding Rectangle in shape list: This works well when i undo or redo from the list.
DrawString
Shape shape = new Shape();
shape.shape = ShapesTypes.ShapeTypes.Rectangle;
shape.CopyTuplePoints(points);
shape.X = StartPoint.X;
shape.Y = StartPoint.Y;
shape.Width = EndPoint.X;
shape.Height = EndPoint.Y;
Pen pen = new Pen(new SolidBrush(penColor), 2);
shape.pen = pen;
undoactions.AddShape(shape);
This is how i'm drawing text:
var fontFamily = new FontFamily("Calibri");
var font = new Font(fontFamily, 12, FontStyle.Regular, GraphicsUnit.Point);
Size proposedSize = new Size(int.MaxValue, int.MaxValue);
TextFormatFlags flags = TextFormatFlags.WordEllipsis | TextFormatFlags.NoPadding | TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.WordBreak;
Size size = TextRenderer.MeasureText(e.Graphics, textAreaValue, font, proposedSize, flags);
Shape shape = new Shape();
shape.shape = ShapesTypes.ShapeTypes.Text;
shape.X = ta.Location.X;
shape.Y = ta.Location.Y;
shape.Width = size.Width;
shape.Height = size.Height;
shape.Value = textAreaValue;
Pen pen = new Pen(new SolidBrush(penColor), 2);
shape.pen = pen;
undoactions.AddShape(shape);
But this does not work with undo-redo list. Maybe problem is with pen and font-size but i cant figure it out how to use pen with DrawString.
Edit: Here's how i'm drawing in paint event
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
foreach (var item in undoactions.lstShape)
{
if (item.shape == ShapesTypes.ShapeTypes.Line)
{
e.Graphics.DrawLine(item.pen, item.X, item.Y, item.Width, item.Height);
}
else if (item.shape == ShapesTypes.ShapeTypes.Pen)
{
if (item.Points.Count > 1)
{
e.Graphics.DrawCurve(item.pen, item.Points.ToArray());
}
}
else if (item.shape == ShapesTypes.ShapeTypes.Text)
{
var fontFamily = new FontFamily("Calibri");
var font = new Font(fontFamily, 12, FontStyle.Regular, GraphicsUnit.Point);
e.Graphics.TextRenderingHint = TextRenderingHint.AntiAlias;
e.Graphics.DrawString(item.Value, font, new SolidBrush(item.pen.Color), new PointF(item.X, item.Y));
}
}
}
Shape.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Drawing
{
public class Shape : ICloneable
{
public ShapesTypes.ShapeTypes shape { get; set; }
public List<Point> Points { get; }
public int X { get; set; }
public int Y { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public Pen pen { get; set; }
public String Value { get; set; }
public Shape()
{
Points = new List<Point>();
}
public void CopyPoints(List<Point> points)
{
for (int i = 0; i < points.Count; i++)
{
Point p = new Point();
p.X = points[i].X;
p.Y = points[i].Y;
Points.Add(p);
}
}
public void CopyCopyPoints(List<List<Point>> points)
{
for (int j = 0; j < points.Count; j++)
{
List<Point> current = points[j];
for (int i = 0; i < current.Count; i++)
{
Point p = new Point();
p.X = current[i].X;
p.Y = current[i].Y;
Points.Add(p);
}
}
}
public void CopyTuplePoints(List<Tuple<Point, Point>> points)
{
foreach (var line in points)
{
Point p = new Point();
p.X = line.Item1.X;
p.Y = line.Item1.Y;
Points.Add(p);
p.X = line.Item2.X;
p.Y = line.Item2.Y;
Points.Add(p);
}
}
public object Clone()
{
Shape shp = new Shape();
shp.X = X;
shp.Y = Y;
shp.Width = Width;
shp.Height = Height;
shp.pen = pen;
shp.shape = shape;
shp.Value = Value;
for (int i = 0; i < Points.Count; i++)
{
shp.Points.Add(new Point(Points[i].X, Points[i].Y));
}
return shp;
}
}
}
DrawCircle
if (currentshape == ShapesTypes.ShapeTypes.Circle)
{
Shape shape = new Shape();
shape.shape = ShapesTypes.ShapeTypes.Circle;
shape.CopyTuplePoints(cLines);
shape.X = StartPoint.X;
shape.Y = StartPoint.Y;
shape.Width = EndPoint.X;
shape.Height = EndPoint.Y;
Pen pen = new Pen(new SolidBrush(penColor), 2);
shape.pen = pen;
undoactions.AddShape(shape);
}
Undo
if (currentshape != ShapesTypes.ShapeTypes.Undo)
{
oldshape = currentshape;
currentshape = ShapesTypes.ShapeTypes.Undo;
}
if (undoactions.lstShape.Count > 0)
{
undoactions.Undo();
this.Invalidate();
}
if (undoactions.redoShape.Count > 0)
{
btnRedo.Enabled = true;
}
UndoRedo
public class UndoRedo
{
public List<Shape> lstShape = new List<Shape>();
public List<Shape> redoShape = new List<Shape>();
public void AddShape(Shape shape)
{
lstShape.Add(shape);
}
public void Undo()
{
redoShape.Add((Shape)lstShape[lstShape.Count - 1].Clone());
lstShape.RemoveAt(lstShape.Count - 1);
}
public void Redo()
{
lstShape.Add((Shape)redoShape[redoShape.Count - 1].Clone());
redoShape.RemoveAt(redoShape.Count - 1);
}
}
you can create a TextShape
deriving from Shape
, having Text
, Font
, Location
and Color
properties and treat it like other shapes, so redo and undo will not be a problem.
Here are some tips which will help you to solve the problem:
Shape
class or interface containing basic methods like Draw
, Clone
, HitTest
, etc.TextShape
should derive from Shape
. TextShape
is also a shape, having Text
, Font
, Location
and Color
properties.Shape
has its implementation of base methods.INotifyPropertyChanged
in all your shapes, then you can listen to changes of properties and for example, add something to undo buffer after change of color, border width, etc.IClonable
or base class Clone
method. All shapes should be clonable when adding to undo buffer.Pen
and Brush
. It's not optional.INotifyPropertyChanged
, then by each change in the shapes or this class properties, you can add a clone of this class to undo buffer.Shape
Here is an example of Shape
class:
public abstract class Shape : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string name = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
public abstract void Draw(Graphics g);
public abstract Shape Clone();
}
TextShape
Pay attention to the implementation of properties to raise PropertyChanged
event and also Clone
method to clone the object for undo buffer, also the way that GDI object have been used in Draw
:
public class TextShape : Shape {
private string text;
public string Text {
get { return text; }
set {
if (text != value) {
text = value;
OnPropertyChanged();
}
}
}
private Point location;
public Point Location {
get { return location; }
set {
if (!location.Equals(value)) {
location = value;
OnPropertyChanged();
}
}
}
private Font font;
public Font Font {
get { return font; }
set {
if (font!=value) {
font = value;
OnPropertyChanged();
}
}
}
private Color color;
public Color Color {
get { return color; }
set {
if (color!=value) {
color = value;
OnPropertyChanged();
}
}
}
public override void Draw(Graphics g) {
using (var brush = new SolidBrush(Color))
g.DrawString(Text, Font, brush, Location);
}
public override Shape Clone() {
return new TextShape() {
Text = Text,
Location = Location,
Font = (Font)Font.Clone(),
Color = Color
};
}
}
DrawingContext
This class in fact contains all shapes and some other properties like back color of drawing surface. This is the class which you need to add its clone to undo buffer:
public class DrawingContext : INotifyPropertyChanged {
public DrawingContext() {
BackColor = Color.White;
Shapes = new BindingList<Shape>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string name = "") {
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
private Color backColor;
public Color BackColor {
get { return backColor; }
set {
if (!backColor.Equals(value)) {
backColor = value;
OnPropertyChanged();
}
}
}
private BindingList<Shape> shapes;
public BindingList<Shape> Shapes {
get { return shapes; }
set {
if (shapes != null)
shapes.ListChanged -= Shapes_ListChanged;
shapes = value;
OnPropertyChanged();
shapes.ListChanged += Shapes_ListChanged;
}
}
private void Shapes_ListChanged(object sender, ListChangedEventArgs e) {
OnPropertyChanged("Shapes");
}
public DrawingContext Clone() {
return new DrawingContext() {
BackColor = this.BackColor,
Shapes = new BindingList<Shape>(this.Shapes.Select(x => x.Clone()).ToList())
};
}
}
DrawingSurface
This class is in fact the control which has undo and redo functionality and also draws the current drawing context on its surface:
public class DrawingSurface : Control {
private Stack<DrawingContext> UndoBuffer = new Stack<DrawingContext>();
private Stack<DrawingContext> RedoBuffer = new Stack<DrawingContext>();
public DrawingSurface() {
DoubleBuffered = true;
CurrentDrawingContext = new DrawingContext();
UndoBuffer.Push(currentDrawingContext.Clone());
}
DrawingContext currentDrawingContext;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[Browsable(false)]
public DrawingContext CurrentDrawingContext {
get {
return currentDrawingContext;
}
set {
if (currentDrawingContext != null)
currentDrawingContext.PropertyChanged -= CurrentDrawingContext_PropertyChanged;
currentDrawingContext = value;
Invalidate();
currentDrawingContext.PropertyChanged += CurrentDrawingContext_PropertyChanged;
}
}
private void CurrentDrawingContext_PropertyChanged(object sender, PropertyChangedEventArgs e) {
UndoBuffer.Push(CurrentDrawingContext.Clone());
RedoBuffer.Clear();
Invalidate();
}
public void Undo() {
if (CanUndo) {
RedoBuffer.Push(UndoBuffer.Pop());
CurrentDrawingContext = UndoBuffer.Peek().Clone();
}
}
public void Redo() {
if (CanRedo) {
CurrentDrawingContext = RedoBuffer.Pop();
UndoBuffer.Push(CurrentDrawingContext.Clone());
}
}
public bool CanUndo {
get { return UndoBuffer.Count > 1; }
}
public bool CanRedo {
get { return RedoBuffer.Count > 0; }
}
protected override void OnPaint(PaintEventArgs e) {
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
using (var brush = new SolidBrush(CurrentDrawingContext.BackColor))
e.Graphics.FillRectangle(brush, ClientRectangle);
foreach (var shape in CurrentDrawingContext.Shapes)
shape.Draw(e.Graphics);
}
}
In the future, please follow the guidelines for a Minimal, Complete, and Verifiable example. This will help us to help you. For example, you could have excluded all of the code related to cloning, since it's not related to your problem.
I refactored your code a little and created a small, reproducible example. This example works with the general approach you outlined, so I can't tell you exactly why your code doesn't work unless you could also post a similar example that I can copy / paste into my environment. Please do not link to external code - it must be hosted here.
I refactored it to highlight some language features which could help to make your code more maintainable. Please let me know if you have any questions about what I put here. Please let me know if this helps. If not, please use it as a template and replace my code with yours so I can assist you.
public partial class Form1 : Form
{
private EntityBuffer _buffer = new EntityBuffer();
private System.Windows.Forms.Button btnUndo;
private System.Windows.Forms.Button btnRedo;
public Form1()
{
this.btnUndo = new System.Windows.Forms.Button();
this.btnRedo = new System.Windows.Forms.Button();
this.SuspendLayout();
this.btnUndo.Location = new System.Drawing.Point(563, 44);
this.btnUndo.Name = "btnUndo";
this.btnUndo.Size = new System.Drawing.Size(116, 29);
this.btnUndo.TabIndex = 0;
this.btnUndo.Text = "Undo";
this.btnUndo.UseVisualStyleBackColor = true;
this.btnUndo.Click += new System.EventHandler(this.btnUndo_Click);
this.btnRedo.Location = new System.Drawing.Point(563, 79);
this.btnRedo.Name = "btnRedo";
this.btnRedo.Size = new System.Drawing.Size(116, 29);
this.btnRedo.TabIndex = 0;
this.btnRedo.Text = "Redo";
this.btnRedo.UseVisualStyleBackColor = true;
this.btnRedo.Click += new System.EventHandler(this.btnRedo_Click);
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.btnRedo);
this.Controls.Add(this.btnUndo);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
protected override void OnLoad(EventArgs e)
{
_buffer.Add(new Rectangle(10, 10, 10, 10, Color.Red));
_buffer.Add(new Rectangle(20, 20, 10, 10, Color.Red));
_buffer.Add(new Rectangle(30, 30, 10, 10, Color.Red));
_buffer.Add(new Text(40, 40, "Test", Color.Black));
_buffer.Add(new Rectangle(50, 50, 10, 10, Color.Red));
_buffer.Add(new Text(60, 60, "Test", Color.Black));
base.OnLoad(e);
}
protected override void OnPaint(PaintEventArgs e)
{
foreach (var entity in _buffer.Entities)
entity.Draw(e.Graphics);
base.OnPaint(e);
}
private void btnUndo_Click(object sender, EventArgs e)
{
if (!_buffer.CanUndo)
return;
_buffer.Undo();
Invalidate();
}
private void btnRedo_Click(object sender, EventArgs e)
{
if (!_buffer.CanRedo)
return;
_buffer.Redo();
Invalidate();
}
}
public abstract class Entity
{
public int X { get; set; }
public int Y { get; set; }
public Color Color { get; set; }
public abstract void Draw(Graphics g);
public Entity(int x, int y, Color color)
{
X = x;
Y = y;
Color = color;
}
}
public class Text : Entity
{
private static Font _font = new Font(new FontFamily("Calibri"), 12, FontStyle.Regular, GraphicsUnit.Point);
public string Value { get; set; }
public Text(int x, int y, string value, Color color) : base(x,y,color) => Value = value;
public override void Draw(Graphics g) => g.DrawString(Value, _font, new SolidBrush(Color), new PointF(X, Y));
}
public abstract class Shape : Entity
{
public int Width { get; set; }
public int Height { get; set; }
public Pen Pen { get; set; }
public Shape(int x, int y, int width, int height, Color color) : base(x, y, color)
{
Width = width;
Height = height;
}
}
public class Rectangle : Shape
{
public Rectangle(Point start, Point end, Color color) : this(start.X, start.Y, end.X, end.Y, color) { }
public Rectangle(int x, int y, int width, int height, Color color) : base(x, y, width, height, color) { }
public override void Draw(Graphics g) => g.DrawRectangle(new Pen(new SolidBrush(Color)), X, Y, Width, Height);
}
public class EntityBuffer
{
public Stack<Entity> Entities { get; set; } = new Stack<Entity>();
public Stack<Entity> RedoBuffer { get; set; } = new Stack<Entity>();
public bool CanRedo => RedoBuffer.Count > 0;
public bool CanUndo => Entities.Count > 0;
public void Add(Entity entity)
{
Entities.Push(entity);
RedoBuffer.Clear();
}
public void Undo() => RedoBuffer.Push(Entities.Pop());
public void Redo() => Entities.Push(RedoBuffer.Pop());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With