c++ - 创建具有专业外观(和行为!)的表单设计器

标签 c++ windows winapi mfc form-designer

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

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

  • 那时,我接受了所有这些作为生活的事实。我能够制作自己的专用程序,但是“使我的程序工作的程序”,代码编辑器和表单编辑器是上帝制作的,我无法弄乱它们。

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

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

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

    Visual Basic 6's form designer

    我特别想模仿它我最喜欢的两个特点:
  • 正在设计的表单位于容器内。因此,通过简单地将容器调整到适当的大小,可以设计任意大的表单而不会浪费太多屏幕空间。
  • “对齐网格”选项使设计具有专业外观的用户界面少很多
    令人沮丧。事实上,我什至会说使用 Visual Basic 的表单设计器创建具有专业外观的用户界面实际上是简单、有趣和令人愉快的。即使是像我这样的左脑程序员。

  • 所以,我有以下问题:
  • 如何制作表单设计器,其中正在设计的表单位于容器内?正在设计的窗体是否包含在另一个窗口中的实际窗口?或者它只是表单设计师“手动”绘制的模型?
  • Windows API 和/或 MFC 是否包含函数、类,以及任何可以轻松创建“可选”项目的东西(当它们被选择时被白色或蓝色小框包围,当它们被其中之一“捕获”时可调整大小)边缘”)?
  • 如何实现“对齐网格”功能?
  • 最佳答案

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

    绘制控件

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

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

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

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

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

    表格本身...

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

    可拖动 handle

    这里最简单的方法是让设计人员创建八个小方形无标题栏弹出窗口,它们位于所有其他元素之上 - 单击它们时会启动适当的调整大小代码。当用户在控件之间单击时,只需将拖动 handle 窗口移动到当前事件的控件即可。 (请注意,在上述所有内容中,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 一起安装)交 friend 。如果您打算对 HWND(托管或非托管)进行任何操作,那么了解如何使用此工具是一个很好的主意:您可以将其指向 Windows 上的任何 UI 部分,并查看它是如何从一棵树中构建的不同类型的 HWND。把它指向 VB 设计器,看看你自己真正发生了什么。 (单击工具栏上的“双筒望远镜”图标,然后将十字准线拖动到您感兴趣的窗口。)

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

    其他注意事项...
    以上大部分内容都特定于 Windows - 特别是因为我们使用 Window 自己的构建块 - HWNDs - 我们可以让 Windows 本身为我们做一些艰苦的工作:它为我们提供了重用控件本身的便利在设计时,所以我们不必绘制模型;拦截其他控件上的输入,这样我们就可以点击进入移动或任何其他我们想要的 Action ,或者找出哪个控件被点击,而无需自己进行位置数学运算。如果这是某个其他 UI 框架(例如 Flash)的设计者,该框架在内部不使用 HWND,则它可能会使用该框架自己的内部设施来完成类似的工作。

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

    玩得开心探索!

    关于c++ - 创建具有专业外观(和行为!)的表单设计器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5533131/

    相关文章:

    c++ - 为什么 C++ 引入 duration_cast 而不是使用 static_cast?

    c++ - const 成员函数的重载解析 C++

    windows - Windows 上的网络摄像头正在使用通知

    delphi - 处理模态形式的 flash

    c++ - 为什么我不能捕捉到这个异常?

    c++ - 给定数据类型的字节对齐要求是否保证为 2 的幂?

    windows - 了解 MAKEINTRESOURCEW 定义

    windows - CouchDB 不会作为服务安装在 Azure Windows VM 上

    windows - x64 调用约定(堆栈)和可变参数

    c - 如何使用 va_list 在 FormatMessage() 中添加多个参数?