【问题标题】:Creating a professional-looking (and behaving!) form designer创建具有专业外观(和行为!)的表单设计器
【发布时间】:2011-07-28 20:02:56
【问题描述】:

当我开始编程时(大约 10 多年前),三件事让我感到惊讶:

  • 编译器/解释器(当时我知道它们是“使我的程序工作的程序”,通常后面跟着限定词“不管它们是什么”)
  • 代码编辑器
  • 表单设计者

那时,我接受了所有这些作为生活的事实。我可以自己制作特殊用途的程序,但是“让我的程序工作的程序”、代码编辑器和表单编辑器都是神造的,我没法弄乱它们。

然后我上了大学,学习了形式语言处理的课程。在学习了形式语法、解析器、抽象语法树等之后;编译器、解释器和代码编辑器的所有魔力很快就消失了。编译器和解释器可以用理智和简单的方式编写,而语法高亮代码编辑器可能需要的唯一不理智的东西是 Windows API hack。

然而,直到今天,表单编辑器对我来说仍然是个谜。要么我缺乏制作表单设计器所需的技术知识,要么我有这样的知识,但找不到使用它来实现表单设计器的方法。

使用 Visual C++ 和 MFC,我想实现一个受有史以来最好的表单设计器启发的表单设计器:

特别想模仿一下它最喜欢的两个特点:

  • 正在设计的表单位于容器内。因此,只需将容器的大小调整为适当的大小,就可以设计任意大的表单,而不会浪费太多的屏幕空间。

  • “对齐网格”选项使设计具有专业外观的用户界面变得少很多 令人沮丧。事实上,我什至会说使用 Visual Basic 的表单设计器创建具有专业外观的用户界面实际上是简单、有趣和令人愉快的。即使是像我这样的左脑程序员。

所以,我有以下问题:

  1. 如何制作一个表单设计器,其中要设计的表单位于容器内?正在设计的表单是包含在另一个窗口中的实际窗口吗?还是只是表单设计者“手动”绘制的模型?

  2. Windows API 和/或 MFC 是否包含函数、类以及任何便于创建“可选”项的内容(选中时被白色或蓝色小框包围,“抓取”时可调整大小” 通过这些“边”之一)?

  3. 如何实现“对齐网格”功能?

【问题讨论】:

  • 有史以来最好的?你用过德尔福吗? 95 年的 Borlands 表单设计器比今天的许多人都好,可惜他们的工具链的其余部分太糟糕了。
  • @Erik:我用过 Delphi,虽然类系统完全踢了 VB 的屁​​股,但它的表单设计器很差。正如 Bruce McKinney 所说:“VB 让简单的事情变得简单”,这适用于 95% 的情况。
  • @Erik - 同意,除了它的工具链的其余部分。海事组织,德尔福是/很棒!是什么让它“可怕”?
  • @David M:从 D3-4 开始:编译器错误和 IDE 不稳定。虽然没有使用当前版本。
  • @Erik:你知道那是,什么,十二年前?真的需要很长时间才能继续提出这样的主张。 (D2010 和 XE 应该很棒——我只用过 2010,但确实如此。)

标签: c++ windows winapi mfc form-designer


【解决方案1】:

这里的两个答案都很好,但遗漏了我认为真正有趣的部分(包括一些你没有直接询问,但无论如何你可能会感兴趣的部分),所以这是我的 2c:

绘制控件

理想情况下,您只需继续创建控件的常规实例。你想要一个看起来像按钮的东西吗?创建一个真正的按钮。棘手的事情是阻止它像按钮一样表现:您希望单击以激活它以进行移动,而不是实际“单击”它。

处理此问题的一种方法 - 假设所讨论的控件是“基于 HWND”的(例如,按钮、编辑、静态、列表框、树视图等的标准窗口集) - 是创建控件,然后将其子类化-即。使用 SetWindowLongPtr(GWLP_WNDPROC, ...) 覆盖 wndproc,以便设计器代码可以拦截鼠标和键盘输入并使用它来启动移动,例如,而不是让鼠标输入通过实际的按钮代码,这而是将其解释为“点击”事件。

子类化的另一种方法是在按钮上方放置一个不可见的窗口来捕获输入。拦截输入的想法相同,只是实现方式不同。

以上内容适用于托管(VB.Net、C#)和非托管(C/C++)控件;它们本质上都是股票窗口 HWND;托管版本只是将托管包装代码传递给底层非托管控件。

.Net VB 之前使用的旧(预托管代码)ActiveX 控件是完全不同的球类游戏。 ActiveX 容器和其中的 ActiveX 控件之间存在相当复杂的关系,许多 COM 接口处理诸如属性协商、事件、绘制等事务。 (有一组接口允许 ActiveX 控件接收输入并在没有自己的 HWND 的情况下自行绘制。)但是,您从这种复杂性中获得的一个好处是 ActiveX 控件具有明确的“设计模式”。所以控件知道在这种情况下做出适当的响应,并且可以与整个过程合作。

表单本身...

所以基本上这些控件只是常规控件。所以您希望表单本身是常规表单? - 几乎。据我所知,它只是另一个基于 HWND 的窗口,它是设计器的子窗口(因此它会被剪辑,并且可以在其中滚动);但我认为设计师在这里做了一些“作弊”,因为通常 Windows 只绘制框架,例如 - 带有用于实际顶级窗口的标题栏和最小/最大按钮。我不知道他们在这里使用的确切技术,但一些选项可能包括:手动绘制以模仿 Windows 外观;使用 Windows“主题”API,它允许您访问用于标题栏的点点滴滴的图形元素,并在任何您想要的地方绘制它们;或者,可能不太可能将窗口设置为“MDI 子窗口” - 这是窗口将围绕嵌套窗口绘制框架的一种例外情况。

可拖动手柄

这里最简单的方法是让设计人员创建八个没有标题栏的小方形弹出窗口,这些窗口位于所有其他元素之上——当它们被点击时,它们会启动适当的调整大小代码。当用户从一个控件单击到另一个控件时,只需将拖动句柄窗口移动到当前活动的控件。 (请注意,在上述所有内容中,Windows 本身会确定谁被点击了,您不必实际将鼠标坐标与元素矩形坐标进行比较并自己计算出来。)

保存和重新创建

对于非托管 C/C++ 使用的普通 Windows 系统控件,它相对容易:有一种众所周知的基于文本的文件格式 - .rc - 描述控件和位置。让设计人员吐出(也可能是 resource.h 文件),您就完成了:任何 C/C++ 项目都可以获取这些文件并编译它们。托管代码(C#、VB.Net)有更多复杂的方案,但它仍然是相同的基本思想:以托管工具所期望的风格写出描述,他们会很高兴地编译并使用它。

(ActiveX 控件 - 你已经猜到了 - 完全是另一回事。我知道没有标准格式,因此使用数据的表单编辑器和运行时将紧密联系在一起 -例如,来自 pre-.Net VB6 的表单编辑器生成只有 VB 才能使用的表单。-我想。这是前一段时间了......)

至于重新创建表单:如果您有一个 .rc 文件,它会被编译为对话框资源,Windows 已内置支持重新创建这些文件。同样,托管代码支持库知道如何从特定格式重新创建表单。两者都基本上解析描述,并为每个项目创建适当类的元素,并按指定设置适当的样式、文本和其他属性。它没有做任何你自己做不到的事情,它只是帮助实用程序代码。

处理焦点

对于任何容器中的 HWND 集合,无论是在“测试”模式下还是在实际应用程序中实际运行,无论您是让 Windows 或 Winforms 处理表单创建还是您自己创建每个 HWND,您都可以添加通过在消息循环中调用 IsDialogMessage 来支持选项卡:有关详细信息,请参阅 MSDN 页面备注部分。 (虽然 WinForms 可以做到这一点,但我认为它实际上做了自己的焦点处理,因此它可以有独立于视觉堆叠 Z-Order 的 Tab 键顺序。)

其他值得探索的事情...

与 Spy++ 应用(SDK 的一部分,随 Visual Studio 一起安装)结交朋友。如果您打算使用托管或非托管 HWND 做任何事情,那么了解如何使用此工具是一个非常好的主意:您可以将它指向 Windows 上的任何 UI 片段,并查看它是如何从一棵树构建的不同类型的 HWND。将它指向 VB 设计器,看看自己真正发生了什么。 (单击工具栏上的“双筒望远镜”图标,然后将十字准线拖动到您感兴趣的窗口。)

还可以看看设计师吐出的资源文件。您可以在表单设计器中调整、移动或编辑的所有内容都对应于其中一个资源文件中的某个项目。复制它们,调整一些设置,然后对两组进行文件比较,看看有什么变化。尝试手动更改文件中的一些内容(我认为它们几乎都是文本),重新加载,看看设计师是否采纳了您的更改。

其他注意事项...

以上大部分内容都是针对 Windows 的——特别是因为我们使用的是 Window 自己的构建块——HWND——我们可以让 Windows 本身为我们做一些艰苦的工作:它为我们提供了重用控件本身的工具在设计时,我们不必绘制模型;拦截其他控件上的输入,以便我们可以单击移动或我们想要的任何其他操作,或者找出单击了哪个控件,而无需自己进行位置数学运算。如果这是其他一些 UI 框架(比如 Flash)的设计者,它在内部不使用 HWND,它可能会改为使用 那个 框架自己的内部设施来完成类似的工作。

此外,如果您将调色板中的控件数量限制为一个小的有限集,至少在开始时会更容易。如果您想允许任何控件被拖入 - 例如。第 3 方,或您在另一个项目中使用过的;您通常首先需要某种方法来“注册”该控件,以便设计人员首先知道它是可用的。您可能还需要一些方法来发现它在工具栏中使用的图标、它的名称、它支持的属性等等。

尽情探索吧!

【讨论】:

  • 哇,这个答案太棒了。
【解决方案2】:

只是在@Xeo 已经说过的内容上加一两点:

首先,不,您并不总是自己绘制所有内容。在正常设计阶段,您基本上只是在绘制看起来像控件的东西,但是(至少 IIRC)它还可以让您在测试模式下“运行”一个表单(当然 VC++ 对话框设计器会这样做,即使 VB 更原始,我认为它也确实具有这种特殊能力)。测试模式是您可以在(必须)附加任何代码之前“运行”表单 - 即使单击按钮(例如)在周围程序中没有执行任何操作,控件本身也可以正常工作-- 一个按钮正常点击,一个编辑控件让你编辑等等。

这是通过实际实例化一个控件来完成的,告诉它正确的位置、大小和属性。 ActiveX 控件在支持这一点上做了很多工作,以前的“Windows 自定义控件”也做了很多工作,但复杂程度要低得多。从控件的角度来看,它的工作方式与正常情况完全相同,接收输入、向其父级发送通知等。唯一改变的是父级最忽略了它收到的大部分通知,但控件没有真的不知道。

有两种基本方法可以做到这一点。一种是自己创建一个控件集合,以及它们的位置、大小等,并使用CreateWindow(或CreateWindowEx等)创建正确类的窗口。虽然相对容易处理,但它的缺点是它把所有的制表符处理都留给了你。

另一种可能性是创建一个DLGTEMPLATE 结构来保存有关对话框的数据,以及一些DLGITEMTEMPLATES 用于各个控件,最后使用CreateDialogIndirect 创建一个具有这些规范的对话框,保存那些控制。这很乏味,但它可以工作,当你完成后,它会自动处理控件之间的选项卡(并且与任何其他对话框的工作方式相同,因为它是相同的 Windows 代码创建它)。

其次,既然你已经标记了这个 C++,你可能想看看 CodeProject 上的一些code,它实际上实现了一个对话框编辑器。虽然它不像某些商业版本那么复杂,但这是一个相当完整的表单/对话框编辑器,包含您所询问的大部分内容。

【讨论】:

    【解决方案3】:

    您实现一个表单设计器几乎就像一个普通的 GUI。你有可以拖动的东西(你的小部件),你有你可以点击的东西(你的按钮),你有你可以选择的东西(你放置的小部件),就是这样。


    问:现在,如何在 GUI 中显示窗口?
    A:你画它,就这么简单。

    问:你如何把东西放在那个窗口里?
    答:您检查“父”对象的边界。你几乎可以说表单设计器就像一个小游戏,你有一个包含所有小部件的场景图,通过父子关系连接。

    问:那么,如何在 GUI 中选择内容?
    答:检查当前鼠标点击位置是否与所有(近)小部件的边界(场景图仅在此处有帮助,如四叉树)。

    问:如何在网格上对齐小部件?
    A:对于网格对齐,我们举一个简单的例子:假设你的实际分辨率是 x 轴上的100px,但你希望你的网格在 x 上只有10px 的分辨率。现在假设您以真实分辨率将您的小部件移动28px。要获得网格分辨率,您只需除以10,得到2.8,四舍五入,最后将小部件3 平铺在x 上。四舍五入是这里的关键。只有当网格移动为>= ?.5 时,您才会捕捉到下一个图块。否则你就留在旧的。


    希望这可以为您提供有关如何启动表单设计器的一般提示。玩得开心。 :)
    (PS:不知道有什么具体的 WinAPI/MFC 函数/类来帮助你,抱歉。)

    【讨论】:

    • 我知道我可以检测到 WM_LBUTTONDOWN 和 WM_LBUTTONUP 消息来选择对象;并除、舍入和乘回以实现对齐网格。我只是想知道是否有一个 API 可以让创建“可选”对象变得不那么乏味。否则,我将不得不逐个像素地分析 Office 自动形状周围的“边缘”,以便能够重新创建它们。
    猜你喜欢
    • 2015-03-08
    • 2014-03-06
    • 1970-01-01
    • 2011-10-13
    • 1970-01-01
    • 2010-11-22
    • 2012-12-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多