پیاده سازی پروژه نقاشی (Paint) به صورت شی گرا 3#
نویسنده: صابر فتح الهی
تاریخ: ۱۳۹۱/۱۱/۱۸ ۲:۱۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
ابتدا یک کلاس کمکی به صورت استاتیک تعریف میکنیم که متدی جهت پیش نمایش رسم شی در حالت جابجایی ، رسم، و تغییر اندازه دارد.
using System;
using System.Drawing;
namespace PWS.ObjectOrientedPaint.Models
{
/// <summary>
/// Helpers
/// </summary>
public static class Helpers
{
/// <summary>
/// Draws the preview.
/// </summary>
/// <param name="g">The g.</param>
/// <param name="startPoint">The start point.</param>
/// <param name="endPoint">The end point.</param>
/// <param name="foreColor">Color of the fore.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="isFill">if set to <c>true</c> [is fill].</param>
/// <param name="backgroundBrush">The background brush.</param>
/// <param name="shapeType">Type of the shape.</param>
public static void DrawPreview(Graphics g, PointF startPoint, PointF endPoint, Color foreColor, byte thickness, bool isFill, Brush backgroundBrush, ShapeType shapeType)
{
float x = 0, y = 0;
float width = Math.Abs(endPoint.X - startPoint.X);
float height = Math.Abs(endPoint.Y - startPoint.Y);
if (startPoint.X <= endPoint.X && startPoint.Y <= endPoint.Y)
{
x = startPoint.X;
y = startPoint.Y;
}
else if (startPoint.X >= endPoint.X && startPoint.Y >= endPoint.Y)
{
x = endPoint.X;
y = endPoint.Y;
}
else if (startPoint.X >= endPoint.X && startPoint.Y <= endPoint.Y)
{
x = endPoint.X;
y = startPoint.Y;
}
else if (startPoint.X <= endPoint.X && startPoint.Y >= endPoint.Y)
{
x = startPoint.X;
y = endPoint.Y;
}
switch (shapeType)
{
case ShapeType.Ellipse:
if (isFill)
g.FillEllipse(backgroundBrush, x, y, width, height);
//else
g.DrawEllipse(new Pen(foreColor, thickness), x, y, width, height);
break;
case ShapeType.Rectangle:
if (isFill)
g.FillRectangle(backgroundBrush, x, y, width, height);
//else
g.DrawRectangle(new Pen(foreColor, thickness), x, y, width, height);
break;
case ShapeType.Circle:
float raduis = Math.Max(width, height);
if (isFill)
g.FillEllipse(backgroundBrush, x, y, raduis, raduis);
//else
g.DrawEllipse(new Pen(foreColor, thickness), x, y, raduis, raduis);
break;
case ShapeType.Square:
float side = Math.Max(width, height);
if (isFill)
g.FillRectangle(backgroundBrush, x, y, side, side);
//else
g.DrawRectangle(new Pen(foreColor, thickness), x, y, side, side);
break;
case ShapeType.Line:
g.DrawLine(new Pen(foreColor, thickness), startPoint, endPoint);
break;
case ShapeType.Diamond:
var points = new PointF[4];
points[0] = new PointF(x + width / 2, y);
points[1] = new PointF(x + width, y + height / 2);
points[2] = new PointF(x + width / 2, y + height);
points[3] = new PointF(x, y + height / 2);
if (isFill)
g.FillPolygon(backgroundBrush, points);
//else
g.DrawPolygon(new Pen(foreColor, thickness), points);
break;
case ShapeType.Triangle:
var tPoints = new PointF[3];
tPoints[0] = new PointF(x + width / 2, y);
tPoints[1] = new PointF(x + width, y + height);
tPoints[2] = new PointF(x, y + height);
if (isFill)
g.FillPolygon(backgroundBrush, tPoints);
//else
g.DrawPolygon(new Pen(foreColor, thickness), tPoints);
break;
}
if (shapeType != ShapeType.Line)
{
g.DrawString(String.Format("({0},{1})", x, y), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(foreColor), x - 20, y - 25);
g.DrawString(String.Format("({0},{1})", x + width, y + height), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(foreColor), x + width - 20, y + height + 5);
}
else
{
g.DrawString(String.Format("({0},{1})", startPoint.X, startPoint.Y), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(foreColor), startPoint.X - 20, startPoint.Y - 25);
g.DrawString(String.Format("({0},{1})", endPoint.X, endPoint.Y), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(foreColor), endPoint.X - 20, endPoint.Y + 5);
}
}
}
}using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Net;
namespace PWS.ObjectOrientedPaint.Models
{
/// <summary>
/// Shape (Base Class)
/// </summary>
public abstract partial class Shape
{
#region Constructors (2)
/// <summary>
/// Initializes a new instance of the <see cref="Shape" /> class.
/// </summary>
/// <param name="startPoint">The start point.</param>
/// <param name="endPoint">The end point.</param>
/// <param name="zIndex">Index of the z.</param>
/// <param name="foreColor">Color of the fore.</param>
/// <param name="thickness">The thickness.</param>
/// <param name="isFill">if set to <c>true</c> [is fill].</param>
/// <param name="backgroundColor">Color of the background.</param>
protected Shape(PointF startPoint, PointF endPoint, int zIndex, Color foreColor, byte thickness, bool isFill, Color backgroundColor)
{
CalulateLocationAndSize(startPoint, endPoint);
Zindex = zIndex;
ForeColor = foreColor;
Thickness = thickness;
IsFill = isFill;
BackgroundColor = backgroundColor;
}
/// <summary>
/// Initializes a new instance of the <see cref="Shape" /> class.
/// </summary>
protected Shape() { }
#endregion Constructors
#region Methods (10)
// Public Methods (9)
/// <summary>
/// Draws the specified g.
/// </summary>
/// <param name="g">The g.</param>
public virtual void Draw(Graphics g)
{
if (!IsSelected) return;
float diff = Thickness + 4;
Color myColor = Color.DarkSeaGreen;
g.DrawString(String.Format("({0},{1})", StartPoint.X, StartPoint.Y), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(myColor), StartPoint.X - 20, StartPoint.Y - 25);
g.DrawString(String.Format("({0},{1})", EndPoint.X, EndPoint.Y), new Font(new FontFamily("Tahoma"), 10), new SolidBrush(myColor), EndPoint.X - 20, EndPoint.Y + 5);
if (ShapeType != ShapeType.Line)
{
g.DrawRectangle(new Pen(myColor), X, Y, Width, Height);
// 1 2 3
// 8 4
// 7 6 5
var point1 = new PointF(StartPoint.X - diff / 2, StartPoint.Y - diff / 2);
var point2 = new PointF((StartPoint.X - diff / 2 + EndPoint.X) / 2, StartPoint.Y - diff / 2);
var point3 = new PointF(EndPoint.X - diff / 2, StartPoint.Y - diff / 2);
var point4 = new PointF(EndPoint.X - diff / 2, (EndPoint.Y + StartPoint.Y) / 2 - diff / 2);
var point5 = new PointF(EndPoint.X - diff / 2, EndPoint.Y - diff / 2);
var point6 = new PointF((StartPoint.X - diff / 2 + EndPoint.X) / 2, EndPoint.Y - diff / 2);
var point7 = new PointF(StartPoint.X - diff / 2, EndPoint.Y - diff / 2);
var point8 = new PointF(StartPoint.X - diff / 2, (EndPoint.Y + StartPoint.Y) / 2 - diff / 2);
g.FillRectangle(new SolidBrush(myColor), point1.X, point1.Y, diff, diff);
g.FillRectangle(new SolidBrush(myColor), point2.X, point2.Y, diff, diff);
g.FillRectangle(new SolidBrush(myColor), point3.X, point3.Y, diff, diff);
g.FillRectangle(new SolidBrush(myColor), point4.X, point4.Y, diff, diff);
g.FillRectangle(new SolidBrush(myColor), point5.X, point5.Y, diff, diff);
g.FillRectangle(new SolidBrush(myColor), point6.X, point6.Y, diff, diff);
g.FillRectangle(new SolidBrush(myColor), point7.X, point7.Y, diff, diff);
g.FillRectangle(new SolidBrush(myColor), point8.X, point8.Y, diff, diff);
}
else
{
var point1 = new PointF(StartPoint.X - diff / 2, StartPoint.Y - diff / 2);
var point2 = new PointF(EndPoint.X - diff / 2, EndPoint.Y - diff / 2);
g.FillRectangle(new SolidBrush(myColor), point1.X, point1.Y, diff, diff);
g.FillRectangle(new SolidBrush(myColor), point2.X, point2.Y, diff, diff);
}
}
/// <summary>
/// Points the in sahpe.
/// </summary>
/// <param name="point">The point.</param>
/// <param name="tolerance">The tolerance.</param>
/// <returns>
/// <c>true</c> if [has point in sahpe] [the specified point]; otherwise, <c>false</c>.
/// </returns>
public virtual bool HasPointInSahpe(PointF point, byte tolerance = 5)
{
return point.X > (StartPoint.X - tolerance) && point.X < (EndPoint.X + tolerance) && point.Y > (StartPoint.Y - tolerance) && point.Y < (EndPoint.Y + tolerance);
}
/// <summary>
/// Moves the specified location.
/// </summary>
/// <param name="location">The location.</param>
/// <returns></returns>
public virtual PointF Move(Point location)
{
StartPoint = new PointF(location.X, location.Y);
EndPoint = new PointF(location.X + Width, location.Y + Height);
return StartPoint;
}
/// <summary>
/// Moves the specified dx.
/// </summary>
/// <param name="dx">The dx.</param>
/// <param name="dy">The dy.</param>
/// <returns></returns>
public virtual PointF Move(int dx, int dy)
{
StartPoint = new PointF(StartPoint.X + dx, StartPoint.Y + dy);
EndPoint = new PointF(EndPoint.X + dx, EndPoint.Y + dy);
return StartPoint;
}
/// <summary>
/// Resizes the specified dx.
/// </summary>
/// <param name="dx">The dx.</param>
/// <param name="dy">The dy.</param>
/// <returns></returns>
public virtual SizeF Resize(int dx, int dy)
{
EndPoint = new PointF(EndPoint.X + dx, EndPoint.Y + dy);
return new SizeF(Width, Height);
}
/// <summary>
/// Resizes the specified start point.
/// </summary>
/// <param name="startPoint">The start point.</param>
/// <param name="currentPoint">The current point.</param>
public virtual void Resize(PointF startPoint, PointF currentPoint)
{
var dx = (int)(currentPoint.X - startPoint.X);
var dy = (int)(currentPoint.Y - startPoint.Y);
if (startPoint.X >= X - 5 && startPoint.X <= X + 5)
{
StartPoint = new PointF(currentPoint.X, StartPoint.Y);
if (ShapeType == ShapeType.Circle || ShapeType == ShapeType.Square)
{
Height = Width;
}
}
else if (startPoint.X >= EndPoint.X - 5 && startPoint.X <= EndPoint.X + 5)
{
Width += dx;
if (ShapeType == ShapeType.Circle || ShapeType == ShapeType.Square)
{
Height = Width;
}
}
else if (startPoint.Y >= Y - 5 && startPoint.Y <= Y + 5)
{
Y = currentPoint.Y;
if (ShapeType == ShapeType.Circle || ShapeType == ShapeType.Square)
{
Width = Height;
}
}
else if (startPoint.Y >= EndPoint.Y - 5 && startPoint.Y <= EndPoint.Y + 5)
{
Height += dy;
if (ShapeType == ShapeType.Circle || ShapeType == ShapeType.Square)
{
Width = Height;
}
}
}
/// <summary>
/// Sets the background brush as hatch.
/// </summary>
/// <param name="hatchStyle">The hatch style.</param>
public virtual void SetBackgroundBrushAsHatch(HatchStyle hatchStyle)
{
var brush = new HatchBrush(hatchStyle, BackgroundColor);
BackgroundBrush = brush;
}
/// <summary>
/// Sets the background brush as linear gradient.
/// </summary>
public virtual void SetBackgroundBrushAsLinearGradient()
{
var brush = new LinearGradientBrush(StartPoint, EndPoint, ForeColor, BackgroundColor);
BackgroundBrush = brush;
}
/// <summary>
/// Sets the background brush as solid.
/// </summary>
public virtual void SetBackgroundBrushAsSolid()
{
var brush = new SolidBrush(BackgroundColor);
BackgroundBrush = brush;
}
// Private Methods (1)
/// <summary>
/// Calulates the size of the location and.
/// </summary>
/// <param name="startPoint">The start point.</param>
/// <param name="endPoint">The end point.</param>
private void CalulateLocationAndSize(PointF startPoint, PointF endPoint)
{
float x = 0, y = 0;
float width = Math.Abs(endPoint.X - startPoint.X);
float height = Math.Abs(endPoint.Y - startPoint.Y);
if (startPoint.X <= endPoint.X && startPoint.Y <= endPoint.Y)
{
x = startPoint.X;
y = startPoint.Y;
}
else if (startPoint.X >= endPoint.X && startPoint.Y >= endPoint.Y)
{
x = endPoint.X;
y = endPoint.Y;
}
else if (startPoint.X >= endPoint.X && startPoint.Y <= endPoint.Y)
{
x = endPoint.X;
y = startPoint.Y;
}
else if (startPoint.X <= endPoint.X && startPoint.Y >= endPoint.Y)
{
x = startPoint.X;
y = endPoint.Y;
}
StartPoint = new PointF(x, y);
EndPoint = new PointF(X + width, Y + height);
}
#endregion Methods
}
}سپس به تشریح متدهای کلاس Shape میپردازیم:
تذکر: متدهای Move، Resize و HasPointInShape به صورت virtual تعریف شده تا کلاسهای مشتق شده در صورت نیاز خود کد رفتار مورد نظر خود را override کرده یا از همین رفتار استفاده نمایند.
خوشحال میشم در صورتی که در Refactoring کد نوشته شده با من همکاری کنید.
در پستهای آینده به بررسی و پیاده سازی دیگر کلاسها خواهیم پرداخت.
case ShapeType.Ellipse:
if (isFill)
g.FillEllipse(new SolidBrush(backgroundColor), x, y, width, height);
//else
g.DrawEllipse(new Pen(foreColor, thickness), x, y, width, height);
break;
//--------------------------------[Change to]-------=>
case ShapeType.Ellipse:
if (isFill)
g.FillEllipse(Brush, x, y, width, height);
//else
g.DrawEllipse(new Pen(foreColor, thickness), x, y, width, height);
break;
public static void DrawPreview(Graphics g, PointF startPoint, PointF endPoint, Color foreColor, byte thickness, bool isFill, Color backgroundColor, ShapeType shapeType)
//--------------------------------[Change to]-------=>
public static void DrawPreview(Graphics g, PointF startPoint, PointF endPoint, Color foreColor, byte thickness, bool isFill, Brush backgroundBrush , ShapeType shapeType)
//---------------------------------------------------
case ShapeType.Ellipse:
if (isFill)
g.FillEllipse(new SolidBrush(backgroundColor), x, y, width, height);
//else
g.DrawEllipse(new Pen(foreColor, thickness), x, y, width, height);
break;
//--------------------------------[Change to]-------=>
case ShapeType.Ellipse:
if (isFill)
g.FillEllipse(backgroundBrush, x, y, width, height);
//else
g.DrawEllipse(new Pen(foreColor, thickness), x, y, width, height);
break;
چرا drawpreview به صورت استاتیک تعریف شده؟ چرا دوبار تعریف شده؟ و چرا این کلاس پایه اطلاعات زیادی در مورد رسم زیر مجموعههای خودش داره؟ آیا بهتر نیست جرئیات ترسیم هر شیء با override شدن به زیر کلاسهای مشتق شده واگذار بشن؟ چرا این متدها از خاصیتهای کلاس تعریف شده استفاده نمیکنن و دوباره این خاصیتها رو به صورت پارامتر دریافت کردن؟ (همون بحث اعلام استقلال متد تعریف شده به صورت استاتیک و اینکه چرا؟) و اگر این کلاس پایه تا این اندازه لازم هست در مورد رسم دایره و سایر اشکال اطلاعات داشته باشد، چه ضرورتی به abstarct بودن آن هست؟ اصلا چه ضرورتی به تعریف اشیاء مشتق شده از آن هست؟
public class ShapeSpecification
{
public PointF StartPoint{get;set;}
public PointF EndPoint{get;set;}
public Color ForeColor{get;set;}
public byte Thickness{get;set;}
public bool IsFill{get;set;}
public Brush BackgroundBrush{get;set;}
} public class StartPoints
{
public float XPoint { get; set; }
public float YPoint { get; set; }
public float Width { get; set; }
public float Height { get; set; }
} public interface IPeiview
{
void Draw(ShapeSpecification shapeScepification);
} public class Helpers
{
private readonly IPeiview peiview;
public Helpers(IPeiview peiview)
{
this.peiview = peiview;
}
public void Draw(ShapeSpecification shapeSpecification)
{
peiview.Draw(shapeSpecification);
}
} public static class Helpers
{
public static void Draw(ShapeSpecification shapeSpecification, IPeiview peiview)
{
peiview.Draw(shapeSpecification);
}
} public static class AreaParser
{
public static StartPoints Parse(PointF startPoint, PointF endPoint)
{
var startPoints = new StartPoints();
startPoints.Width = Math.Abs(endPoint.X - startPoint.X);
startPoints.Height = Math.Abs(endPoint.Y - startPoint.Y);
if (startPoint.X <= endPoint.X && startPoint.Y <= endPoint.Y)
{
startPoints.XPoint = startPoint.X;
startPoints.YPoint = startPoint.Y;
}
else if (startPoint.X >= endPoint.X && startPoint.Y >= endPoint.Y)
{
startPoints.XPoint = endPoint.X;
startPoints.YPoint = endPoint.Y;
}
else if (startPoint.X >= endPoint.X && startPoint.Y <= endPoint.Y)
{
startPoints.XPoint = endPoint.X;
startPoints.YPoint = startPoint.Y;
}
else if (startPoint.X <= endPoint.X && startPoint.Y >= endPoint.Y)
{
startPoints.XPoint = startPoint.X;
startPoints.YPoint = endPoint.Y;
}
return startPoints;
}
} public class CirclePreview:IPeiview
{
private readonly Graphics graphics;
public CirclePreview(Graphics graphics)
{
this.graphics = graphics;
}
public void Draw(ShapeSpecification shapeScepification)
{
var startPoints = AreaParser.Parse(shapeScepification.StartPoint, shapeScepification.EndPoint);
float raduis = Math.Max(startPoints.Width, startPoints.Height);
if (shapeScepification.IsFill)
this.graphics.FillEllipse(shapeScepification.BackgroundBrush, startPoints.XPoint, startPoints.YPoint, raduis, raduis);
else
this.graphics.DrawEllipse(new Pen(shapeScepification.ForeColor, shapeScepification.Thickness), startPoints.XPoint, startPoints.YPoint, raduis, raduis);
}
}