【问题标题】:Architectural tips on implementing GUI logic实现 GUI 逻辑的架构技巧
【发布时间】:2012-01-04 09:04:35
【问题描述】:

所以我在这里工作的一个应用程序上实现了一个类似于 svg 编辑器的 GUI。以下是一些需要的逻辑示例:

  • 如果用户在画布上单击右键,则应创建一个新节点,并将后续节点与一条线“链接”起来,形成一个多边形
  • 如果用户在节点上单击左键,我应该将整个多边形集相应地移动到鼠标位置
  • 用户可以删除节点
  • 所选节点的颜色应不同
  • 用户可以通过按 SHIFT 并单击节点来选择多个节点

等等。

我已经实现了所有这些项目,但我不喜欢最终结果,主要是因为我不得不使用很多标志来操纵状态(鼠标单击 && 左键 && 不移动?这样做),当然,这段代码可以更优雅。因此,我进行了一些研究并得出了以下选择:

  • 管道模式:我将创建单独处理 每个 逻辑事件的类,并使用优先顺序来提供要做什么/首先处理什么,以及事件将如何处理传播到后续的 Pipeline 项。

  • MVC:这是最常见的响应,但我现在如何使用它来使代码更简洁却非常模糊。

  • 状态机:这很好,但管理状态机的粒度会很复杂

所以我要问 S.O.关于如何构建更好、更快乐的代码的技巧。

【问题讨论】:

    标签: design-patterns language-agnostic architecture


    【解决方案1】:

    我建议将 UI 输入映射到特定操作的逻辑分离到专用对象中。我们称它们为 Sensor 对象。不知道你的实现语言,我会对此很笼统,但你应该明白。

    OperationSensor
    + OnKeyDown
    + OnKeyPress
    + OnKeyUp
    + OnLeftMouseDown
    + OnLeftMouseUp
    + OnNodeSelect
    + OnNodeDeselect
    + OnDragStart
    + OnDragStop
    

    假设您有一个集中所有各种 UI 输入的中心类 UiInputManager。它使用特定于语言的机制来侦听键盘和鼠标输入。它还检测基本操作,例如检测鼠标是否被按下,然后移动,即逻辑“拖动”。

    UiInputManager
    // event listeners
    + keyboard_keydownHandler
    + keyboard_keyupHandler
    + mouse_leftdownHandler
    + mouse_rightdownHandler
    // active sensor list, can be added to or removed from
    + Sensors
    

    UiInputManager 不负责了解这些输入导致的操作。它只是以特定语言的方式通知其传感器。

    foreach sensor in Sensors
        sensor.OnDragStarted
    

    或者,如果传感器侦听 UiInputManager 发出的逻辑事件

    RaiseEvent DragStarted
    

    您现在拥有的是将输入路由到 OperationSensor 子类的管道。每个 OperationSensor 都具有仅与单个操作相关的逻辑。如果它检测到操作的条件已经满足,那么它会创建适当的 Command 对象并将其传递回去。

    // Ctrl + zooms in, Ctrl - zooms out
    ZoomSensor : OperationSensor
    
       override OnKeyDown
       {
          if keyDown.Char = '+' && keyDown.IsCtrlDepressed
             base.IssueCommand(new ZoomCommand(changeZoomBy:=10)
          elseif keyDown.Char = '-' && keyDown.IsCtrlDepressed
             base.IssueCommand(new ZoomCommand(changeZoomBy:=-10)                                 
       }
    

    我建议将命令对象从传感器传递到 UiInputManager。然后,管理器可以将它们传递给您的命令处理子系统。这使管理器有机会通知传感器操作已完成,允许它们在需要时重置其内部状态。

    可以通过两种不同的方式处理多步操作。您可以在 SensorOperation 中实现内部状态机,也可以让“第 1 步”传感器创建“第 2 步”传感器并将其添加到活动传感器列表中,甚至可能将其从列表中删除。当“第 2 步”完成后,它可以重新添加“第 1 步”传感器并自行移除。

    【讨论】:

    • 所以你是说他应该使用他在问题中所说的管道?
    • 我在建议一些新的东西。 (我认为)他说他希望使用管道来捕获和汇集事件,然后链接到后续事件处理程序,直到最终处理程序处理事件。我专注于正在执行的操作,而不是触发它们的事件。尽管如果 OP 澄清了他的 pipleine 想法,我可以进行更多比较/对比。我期待着 OP 回复,看看他的看法。
    • 事实上,我在想这种时尚,很好解释!很高兴看到我正在寻找正确的方向:)。我的想法导致我进入管道模式的一点是关于操作优先级以及当某些命令明确不会将事件沿链传播时需要“破坏”传感器通知。这可以很容易地使用具有优先级的集合而不是列表来实现,并使传感器事件返回一些代码标志,甚至是一个布尔值作为返回值来指示进程继续或中断。谢谢!
    • 顺便说一句:打破小型传感器中的多步操作的好主意。这使得状态机使用起来更加简单明了。
    【解决方案2】:

    有点迟到了,但我想补充一点,常见的模式是中介模式,您可以将各种节点之间交互的复杂性转移到单独的类中, 中介(比如 ConnectionCreator 类)。请参阅 Gamma 及其同事的设计模式:http://ebookbrowse.com/addison-wesley-gamma-helm-johnson-vlissides-design-patterns-elements-of-reusable-object-oriented-pdf-d11349017

    【讨论】:

      【解决方案3】:

      Martin Fowler 在 MVC 和相关模式上有 a good writeup。此外,您可能需要查看 command pattern 以让 UI 元素了解它们的行为方式(即,当单击节点时应将其移动或删除等)

      【讨论】:

      • 是的,我已经在使用命令模式来实现动作执行了。我现在更专注于修复逻辑混乱。
      【解决方案4】:

      如今,对于 UI,MVC 非常普遍。非常简单地说,M(模型)包含状态,V(视图)显示视觉元素,C(控制器)调度入站用户操作,例如鼠标点击。目标是模型不直接关心视图,除了可能触发事件。

      我可能会将“智能”放在模型中。该模型将知道何时选择了一个节点,它的相邻节点形成了多边形,状态机等。这种设计为您提供了几个好处。它独立于 UI 渲染的细节;因此,您可以在不中断核心功能的情况下进行重大视图更改。它还使单元测试变得更加容易。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-07
        • 1970-01-01
        • 2010-12-21
        • 2011-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多