我最近开始创建一个图像编辑工具,以满足非常具体的需求。这对于那些打算使用它的人来说同样适合我自己的娱乐。然而,我在早期遇到了一些建筑障碍。
与任何图像编辑器一样,用户将使用“工具”来绘制和操纵图像。我对此的第一次尝试包括一个简单的界面:
public interface IDrawingTool
{
void DrawEffect( Graphics g );
// other stuff
}
这(我认为)会很干净,并且易于维护和扩展。只需添加接口对象并在运行时调用所选接口对象的DrawEffect方法即可。
这种方法的问题在于不同的绘图工具不能干净地粘附到单个界面上。例如,钢笔工具只需知道要绘制的点即可工作。然而,矩形需要点击第一个点,以及当前位置。多边形工具需要跟踪多次鼠标点击。
我无法想出一个很好的方法来实现它。我现在能想到的最好的方法是涉及每个工具的switch语句和case,这意味着绘图逻辑将在Canvas类中,而不是由Tool类型对象封装。因为这是练习,我想以正确的方式做到这一点。感谢您提前提供任何帮助。
答案 0 :(得分:1)
好的,经验法则:如果您在代码草图中看到switch
语句,则表示您需要使用多态。所以,在这种情况下,你希望能够进行各种操作,并且你发现自己想要一个switch
,所以你应该想“如何使用多态来制作这个东西?”
现在,看看Command模式,你的对象是动词而不是名词。每个Command实现一个doThis()
方法;在构造对象时,您将确定命令将执行的操作。
public interface Command {
public void doThis(Graphics g); // I don't promise returning
// void is the best choice
// Would it be better to return a Graphics object?
}
public class DrawRectangle implements Command {
public DrawRectagle( Point topLeft, Point btmRight) { // ...
}
public void doThis(Graphics g){ // ...
}
}
现在,考虑一下如果要实现撤消?
,您会怎么做好的,让我们再扩展一下。使用此模式的目的是确保客户端不需要知道所有这些,除非您正在进行原始构造。因此,对于此示例,让我们考虑绘制一个矩形。当您选择矩形工具时,您将在按钮单击事件处理程序上有一些代码(这是所有伪代码顺便说一句)
cmdlist = [] // empty list
bool firstClick = true
Point tl = br = new Point(0,0)
onClick:
if firstClick:
get mouse position into tl
firstClick = false
else:
get mouse position into br
cmdlist.append(new DrawRectangle(tl, br))
firstClick = true
现在,当您选择矩形时,将DrawRectangle对象添加到命令列表结构中。稍后,您将浏览列表
for cmd in cmdlist:
cmd.doThis(Graphics g)
这些事情就完成了。现在应该很明显,您可以通过向Command添加“undoThis”方法来实现撤消。创建命令时,必须使用buuild代码,以便对象知道如何撤消自身。然后撤消意味着只需从列表中取出最后一个Command对象并执行undoThis方法。
答案 1 :(得分:1)
你的界面设计有点复杂吗?让我们从一些代码开始,然后我将解释它应该如何工作。
public class AbstractDrawingTool {
private Graphics g;
void AbstractDrawingTool( Graphics g ) {
this.g = g;
}
void keyDown(KeyEvent e);
void keyUp(KeyEvent e);
void mouseMove(MouseEvent e);
void mouseClick(MouseEvent e);
void drop();
// other stuff
}
一旦用户开始使用特定实现,我们的想法是将用户输入传递给工具。这样,您可以使用相同的界面创建许多不同的绘图工具。例如,一个简单的PointDrawingTool只会实现mouseClick事件以在画布上放置一个点。 PolygonDrawingTool还会实现keyUp事件,以便在按下特定键(即转义键)时停止绘制线条。
一种特殊情况是drop方法。它将被称为“删除”当前选定的工具。如果从工具栏或类似工具中选择了另一个实现,则会发生这种情况。
您还可以将此定义与命令模式结合使用。在这种情况下,AbstractDrawingTool的实现将负责创建Command接口的实例,并且可能在操作完成后将它们放在堆栈上(即在画布上放置一个点)。
答案 2 :(得分:0)
在尝试重新设计我的mapping SW以支持GDI +和Cairo图形库时,我遇到了类似的问题。我通过将绘图界面减少到一些常见的操作/原语来解决它,请参阅下面的代码。
在此之后,你想要绘制的“效果”是命令(就像查理所说)。他们使用IPainter
界面绘制。这种方法的好处在于效果与GDI +之类的具体绘图引擎完全分离。这对我来说很方便,因为我可以通过切换到Cairo引擎将我的绘图导出到SVG。
当然,如果您需要一些额外的图形操作,则必须使用它扩展IPainter
接口,但基本原理保持不变。点击此处了解详情:http://igorbrejc.net/development/c/welcome-to-cairo
public interface IPainter : IDisposable
{
void BeginPainting ();
void Clear ();
void DrawLines (int[] coords);
void DrawPoint (int x, int y);
void EndPainting ();
void PaintCurve (PaintOperation operation, int[] coords);
void PaintPolygon (PaintOperation operation, int[] coords);
void PaintRectangle (PaintOperation operation, int x, int y, int width, int height);
void SetHighQualityLevel (bool highQuality);
void SetStyle (PaintingStyle style);
}
public class PaintingStyle
{
public PaintingStyle()
{
}
public PaintingStyle(int penColor)
{
this.penColor = penColor;
}
public int PenColor
{
get { return penColor; }
set { penColor = value; }
}
public float PenWidth
{
get { return penWidth; }
set { penWidth = value; }
}
private int penColor;
private float penWidth;
}
public enum PaintOperation
{
Outline,
Fill,
FillAndOutline,
}