在使用GDI+绘图时,不得不说QT中的QPainter有些功能是让人羡慕的,比如它的画笔笔刷颜色状态可以用去painter的save和restore功能存储起来,我们就不必要在同一段绘制代码中不停的为同样的笔刷色彩等参数进行设置,我们可以形象的称之为painter的“现场保存和复原”功能,但这些功能在GDI+绘图中也不是可望不可求,我们可以自己动手在GDI+中是实现。
一、实现思路
1、功能设计
为了说明更清楚,我们以列表的方式呈现给大家,为了称呼的方便,我们将一次绘制所使用的Rectangle,GraphicPath等绘制对象及Color,Pen,Brush为绘制样式Paintstyle:
函数名 | 功能 |
---|---|
Save | 存储当前一次绘制使用的PaintStyle,即:rectangle,path等绘制对象及color,pen,brush |
Restore | 恢复上一次的绘制对象及色彩画笔等 |
getPaintStyleByName | 通过名称索引链表中的某个PaintStyle |
getPaintStyleByIndex | 通过索引值索引链表中的某个PaintStyle |
Clear | 清除所有存储PaintStyle |
2、大体实现思路
新建一个实现了IList的类,专门存放pen和brush上一次的颜色大小等设置,甚至可以把rectangle的设置也一并存储下来,需要时可以随意回退。那么问有两个题来了,一是采用静态类还是非静态类?二是绘制对象(如矩形、椭圆、路径等)是否要和画笔同步,如果同步,是以绘制对象(矩形)作为主对象还是pen或brush作为主对象?
解决这个问题,其实只要看我们绘图的目标即可,绘图的目标就是绘图对象(线段,矩形,椭圆,路径等)而不是笔刷颜色样式等。所以,初步考虑以绘制对象做住对象最好,因为我们画笔的改变不会无缘无故改变,肯定是随绘制对象的要求而改变。
这里为什么要用实现了IList的类,因为我们需要存储的数据成员有多个,而且需要查询、索引和存储、恢复功能。除了恢复restore功能其余功能IList中都有,比如save功能直接用IList的Add功能即可,所以我们选择IList接口来实现。
二、代码实现
1、实现IList
首先我们来实现一个实现了IList接口的简单List实例,这里不详细介绍,直接上代码,要详细了解IList实现的同学可以参考我前面的博文《C#快速实现IList非泛型类接口的自定义类作为数据源》
实现的代码分为两部分,我这里将Ilist接口必须实现的成员和自定义方法的实现分开列出来。以下是Ilist必须实现的成员
List<PaintStyle> stack=new List<PaintStyle>() ;
public PaintStyle CurrentPainter=new PaintStyle("none");
public Graphics gpainter;
public GPainters(Graphics gp)
{
gpainter= gp;
}
#region IList成员属性
public object? this[int index]
{
get { return stack[index]; }
set { stack[index] = (Painter)value!; }
}
public bool IsFixedSize => throw new NotImplementedException();
public bool IsReadOnly => throw new NotImplementedException();
public int Count { get { return stack.Count; } }
public bool IsSynchronized => throw new NotImplementedException();
public object SyncRoot => throw new NotImplementedException();
#endregion
#region 实现IList成员函数
public int Add(object? value)
{
if (value == null) throw new ArgumentNullException("value");
else
{
stack.Add((PaintStyle)value);
return stack.Count;
}
}
public void Clear()
{
stack.Clear();
//throw new NotImplementedException();
}
public bool Contains(object? value)
{
throw new NotImplementedException();
}
public void CopyTo(Array array, int index)
{
throw new NotImplementedException();
}
public IEnumerator GetEnumerator()
{
return stack.GetEnumerator();
// throw new NotImplementedException();
}
public int IndexOf(object? value)
{
throw new NotImplementedException();
}
public void Insert(int index, object? value)
{
throw new NotImplementedException();
}
public void Remove(object? value)
{
stack.Remove((PaintStyle)value!);
//throw new NotImplementedException();
}
public void RemoveAt(int index)
{
stack.RemoveAt(index);
// throw new NotImplementedException();
}
#endregion
2、实现功能函数
下面是自定义方法save和restore,其中下面的save方法实际是对currentPainter进行的深层拷贝,这里我们没有采用序列化和实现ICloneable接口的方式来拷贝,而是直接对每个值进行拷贝。
#region 自定义方法
public void Save()
{
PaintStyle pt = new PaintStyle(CurrentPainter.Name);
pt.StartPoint = new Point(CurrentPainter.StartPoint.X, CurrentPainter.StartPoint.Y); ;
pt.Rect = new Rectangle(CurrentPainter.Rect.X, CurrentPainter.Rect.Y, CurrentPainter.Rect.Width, CurrentPainter.Rect.Height);
pt.EndPoint = CurrentPainter.EndPoint;
pt.StartPoint = CurrentPainter.EndPoint;
pt.Rect = CurrentPainter.Rect;
pt.EndPoint = CurrentPainter.EndPoint;
pt.Brush= new SolidBrush(Color.RebeccaPurple);
pt.Pen= new Pen(CurrentPainter.Pen.Color, CurrentPainter.Pen.Width);
pt.Color = Color.FromArgb(CurrentPainter.Color.A, CurrentPainter.Color.R, CurrentPainter.Color.G, CurrentPainter.Color.B);
Add(pt);
}
public void Restore()
{
//CurrentPainter=stack.Last();
if (stack.Count > 0)
{
stack.Remove(stack.Last());
}
if(stack.Count>0)
CurrentPainter = stack.Last();
}
// 通过名称索引链表中的某个PaintStyle
public PaintStyle getPaintStyleByName
{
}
public PaintStyle getPaintStyleByIndex(int index)
{
return stack.indexof(index);
}
#endregion
3、调用测试
如果Graphic类不是Seal类,我们可以将Graphic封装在我们新建的类,直接在书写中更方便。现在不能实现封装,我们只能通过二级类来调用绘制方法。
以下是在窗体paint事件中的调用代码:
GPainters gp=new GPainters(e.Graphics);
gp.CurrentPainter.Color = Color.Blue;
gp.CurrentPainter.Rect = Rectangle.Inflate(ClientRectangle, -300, -200); ;
gp.CurrentPainter.Brush = Brushes.Violet;
gp.CurrentPainter.Pen=new Pen(Color.Red,3);
gp.CurrentPainter.Name = "p1";
// gp.gpainter.DrawEllipse(gp.CurrentPainter.Pen, gp.CurrentPainter.Rect);
gp.Save();
gp.CurrentPainter.Color = Color.Red;
gp.CurrentPainter.Rect = new Rectangle(100,100,200,200);
gp.CurrentPainter.Rect.Offset(50, 50);
gp.CurrentPainter.Brush = Brushes.Yellow;
gp.CurrentPainter.Pen = Pens.Blue;
gp.CurrentPainter.Name = "p2";
gp.gpainter.DrawEllipse(gp.CurrentPainter.Pen, gp.CurrentPainter.Rect);
gp.Save();
gp.CurrentPainter.Color = Color.Red;
gp.CurrentPainter.Rect = Rectangle.Inflate(ClientRectangle, -200, -100);
gp.CurrentPainter.Brush = Brushes.Peru;
gp.CurrentPainter.Pen = Pens.DeepPink;
gp.gpainter.DrawEllipse(gp.CurrentPainter.Pen, gp.CurrentPainter.Rect);
gp.CurrentPainter.Name = "other";
gp.Save();
gp.Restore();
gp.Restore();
gp.gpainter.DrawArc(gp.CurrentPainter.Pen, gp.CurrentPainter.Rect, 0, 90);
上面的代码,经过两次Restore,我们就可以回退到第一次的painterStyle,最后一次绘制的arc实际是p1的样式配置,如下图:
下面是我们使用的Paintstyle类的大体结构
public class Paintstyle
{
Rectangle rect=new Rectangle();
GraphicsPath path = new GraphicsPath();
Pen pen=Pens.Red;
Brush brush=Brushes.Blue;
Color color=Color.Blue;
int width=0;
int height=0;
Point startPoint=new Point(0,0);
Point endPoint = new Point(0, 0);
Point centerPoint = new Point(0, 0);
string name = string.Empty;
public Paintstyle(string name) {
this.name = name;
}
public Rectangle Rect { get => rect; set => rect = value; }
public Color Color { get => color; set => color = value; }
public Brush Brush { get => brush; set => brush = value; }
public int Width { get => width; set => width = value; }
public int Height { get => height; set => height = value; }
public Point StartPoint { get => startPoint; set => startPoint = value; }
public Point EndPoint { get => endPoint; set => endPoint = value; }
public Point CenterPoint { get => centerPoint; set => centerPoint = value; }
public GraphicsPath Path { get => path; set => path = value; }
public Pen Pen { get => pen; set => pen = value; }
public string Name { get => name; set => name = value; }
}
码字不易,注明出处:https://blog.csdn.net/haigear/article/details/129116662