我最近开始创建一个图像编辑工具,它将迎合一个非常具体的需求。这对那些打算使用它的人来说就像我自己的娱乐一样多。然而,我在早期遇到了一些架构上的障碍。
像任何图像编辑器一样,用户将使用“工具”来绘制和操作图像。我的第一次尝试由一个简单的界面组成:
public interface IDrawingTool
{
void DrawEffect( Graphics g );
// other stuff
}这(我认为)将是好的和干净的,并将允许易于维护和扩展。只需在中添加接口对象,并在运行时调用所选接口对象的DrawEffect方法。
这种方法的问题是,不同的绘图工具不能清晰地附着在单个界面上。例如,钢笔工具只需知道要绘制的点即可工作。但是,矩形需要单击的第一个点,以及当前位置。多边形工具需要跟踪多次鼠标单击。
我想不出一个好的方法来实现这一点。我现在能想到的最好的方法是为每个工具包含一个switch语句和一个case,这意味着绘制逻辑将位于Canvas类中,而不是由工具类型对象封装。因为这是练习,我想以正确的方式来做这件事。提前感谢您的帮助。
发布于 2009-04-05 04:41:39
好的,经验法则:如果您在代码草图中看到一条switch语句,则表明您需要改用多态性。因此,在这种情况下,您希望能够进行各种操作,并且您发现自己需要一个switch,因此您应该思考“我如何使用多态性使其成为某种东西?”
现在,让我们来看看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){ // ...
}
}现在,考虑一下如果您想要实现撤销,您会做些什么
更新
好的,让我们再扩展一下。使用此模式的重点是确保客户不需要知道太多,除非您正在进行原始构造。因此,在这个例子中,让我们考虑一下画一个矩形。当您选择一个矩形工具时,您将在按钮单击事件处理程序上有一些代码(这都是伪代码btw)
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)然后这些事情就完成了。现在应该很明显了,您可以通过向命令添加"undoThis“方法来实现撤消。当你创建一个命令时,你必须编译代码,这样对象才能知道如何撤销它自己。然后撤消意味着只从列表中删除最后一个命令对象,并执行其undoThis方法。
发布于 2009-04-06 22:47:59
你可以把你的界面设计得复杂一点吗?让我们从一些代码开始,然后我将解释它是如何工作的。
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的实现将负责创建命令接口的实例,并可能在操作完成后将它们放在堆栈上(即在画布上放置一个点)。
发布于 2009-04-05 19:29:36
当我试图重新设计我的mapping SW以同时支持GDI+和Cairo图形库时,我遇到了类似的问题。我通过将绘图接口减少到一些常见的操作/原语来解决这个问题,请参阅下面的代码。
在这之后,你想要绘制的“效果”就是命令(就像查利说的那样)。它们使用IPainter接口进行绘制。这种方法的好处是,效果与GDI+这样的具体绘图引擎完全解耦。这对我来说很方便,因为我可以通过切换到Cairo engine将我的绘图导出为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,
}https://stackoverflow.com/questions/718342
复制相似问题