27

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

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

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

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

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

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

Visual Basic 6 的表单设计器

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

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

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

所以,我有以下问题:

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

  2. Windows API 和/或 MFC 是否包含函数、类以及任何可以轻松创建“可选”项的内容(当它们被选中时被白色或蓝色小框包围,当它们被其中之一“抓取”时可调整大小“边缘”)?

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

4

3 回答 3

30

这里的两个答案都很好,但忽略了我认为真正有趣的部分(包括一些你没有直接问的,但无论如何你可能会感兴趣),所以这是我的 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 方,或您在另一个项目中使用过的;您通常首先需要某种方法来“注册”该控件,以便设计人员首先知道它是可用的。您可能还需要一些方法来发现它在工具栏中使用的图标、它的名称、它支持的属性等等。

尽情探索吧!

于 2011-04-04T04:52:29.697 回答
6

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


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

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

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

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


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

于 2011-04-03T23:20:04.490 回答
2

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

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

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

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

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

其次,由于您已标记此 C++,您可能想查看CodeProject 上实际实现对话框编辑器的一些代码。虽然它不像一些商业的那样复杂,但这是一个相当完整的表单/对话框编辑器,包含您所询问的大部分内容。

于 2011-04-04T02:08:18.727 回答