Nana c++ gui库的对话框函数 -- 理解 nana 的运行机制

#include <nana/gui.hpp>                  // always include this
#include <nana/gui/widgets/button.hpp>
#include <nana/gui/widgets/group.hpp>
#include <nana/gui/widgets/label.hpp>
#include <nana/gui/msgbox.hpp>
int main()
{
    using namespace nana;
    form   fm;                             // Our main window
    fm.caption("My first NANA demo");       // (with this title)
    place  fm_place{ fm };                    // have automatic layout
    bool   really_quick{ false };             // and a default behavior
    label  hello{ fm,"Hello World" };         // We put a label on our window
    button btn{ fm,"Quit" };                // and a button
    btn.tooltip("I will ask first");       // that show a tip upon mouse hover
    group  act{ fm, "Actions" };            // Add a group of "options"
    act.add_option("Quick quickly")         // and two options that control quick behavior
        .events().click([&]() {   really_quick = true;
    btn.tooltip("Quick quickly");       });
    act.add_option("Ask first")
        .events().click([&]() {   really_quick = false;
    btn.tooltip("I will ask first");    });
    btn.events().click([&]()               // now the button know how to respond
        {
            if (!really_quick)     // not really quick !
            {
                msgbox m(fm, "Our demo", msgbox::yes_no);
                m.icon(m.icon_question);
                m << "Are you sure you want to quick?";
                auto response = m();
                if (response != m.pick_yes) return;   // return to the demo
            }
            API::exit();           // or really quick
        });
    act.radio_mode(true);                   // Set "radio mode" (only one option selected)
    // let divide fm into fields to holds the other controls.
    // for example, let split fm into two fields separated by a movable vertical barre.
    fm_place.div("vertical <label margin=10>|70% <actions>");
    fm_place["label"] << hello << btn;        // and place the controls there
    fm_place["actions"] << act;
    fm_place.collocate();                      // and collocate all in place
    fm.show();
    exec();
}

在上述代码中,关于 click 事件的代码中,有一段调用对话框函数 msgbox 的调用方法,表达了 nana 的设计风格。

btn.events().click([&]()               // now the button know how to respond
        {
            if (!really_quick)     // not really quick !
            {
                msgbox m(fm, "Our demo", msgbox::yes_no);
                m.icon(m.icon_question);
                m << "Are you sure you want to quick?";
                auto response = m();
                if (response != m.pick_yes) return;   // return to the demo
            }
            API::exit();           // or really quick
        });

当执行 m() 时,到底发生什么?

m() 在这里表示执行并弹出对话框。

在 C++ 中,这种写法利用了运算符重载(Operator Overloading),具体来说是重载了**函数调用运算符 ()**(也称为重载 operator())。

彻底地理解这几行代码,我们可以把它拆解开来看:

1. m() 背后发生了什么?

当你创建了一个 msgbox 对象 m 并设置好它的各种属性(如标题、图标、文本)后,它只是一个存在于内存中的对象,并没有真正显示在屏幕上。

当你调用 m() 时,实际上是触发了类似下面的过程:

  • 阻塞运行: 弹出一个模态对话框,并暂停(阻塞)后面代码的执行。
  • 等待交互: 等待用户点击 “是 (Yes)” 或 “否 (No)”。
  • 返回结果: 当用户点击了某个按钮关闭对话框后,m()返回一个值,代表用户的选择。

2. 代码逐行拆解

auto response = m();

  • m():显示对话框,等用户点击。
  • auto response:自动推导类型,用来接收 m() 返回的结果(在 Nana 库中,这个返回类型通常是 msgbox::pick_t 或者是它的内部枚举)。
if (response != m.pick_yes) return;

  • m.pick_yes:是 msgbox 预定义的一个常量,代表用户点击了“是”。
  • 整个 if 语句:如果用户的选择 response 不是!=)“是”,说明用户反悔了(点了“否”或者直接关闭了对话框),此时直接 return 退出当前 lambda 函数,取消退出程序,返回到主界面。

拓展:为什么不写成 m.show() 呢?

很多 GUI 库(比如 Qt 或 Windows API)通常会使用 m.exec() 或 m.showDialog()。

而 Nana 库选择重载 () 运算符,是一种更偏向现代 C++ 仿函数(Functor) 的设计风格。它把“显示对话框并获取结果”这整个动作,抽象得就像调用一个普通函数一样自然:

"我想从对话框 m 获取一个结果,那我就像调用函数一样调用它:m()"

为什么在 click 事件的匿名函数没有使用 if...esle 结构

这是一个非常好的编程习惯问题!你敏锐地察觉到了这里的代码结构。

之所以没有写成常见的 if...else 结构,而是让 API::exit(); 直接“裸奔”在函数底部,背后主要有两层考虑:代码设计的清晰度(提前返回模式) 以及 逻辑的必然结果


1. 核心原因:避免“嵌套地狱”的卫语句(Guard Clauses)

在软件工程中,这种写法被称为卫语句(Guard Clause),或者叫提前返回(Early Return)

如果按照传统的 if...else 逻辑,代码会写成这样:

// 传统 if...else 结构
btn.events().click([&]() {
    if (!really_quick) { // 情况 A:需要确认
        msgbox m(fm, "Our demo", msgbox::yes_no);
        m.icon(m.icon_question);
        m << "Are you sure you want to quick?";
        if (m() == m.pick_yes) {
            API::exit(); // 真正退出
        }
    } else {             // 情况 B:不需要确认
        API::exit();     // 真正退出
    }
});

为什么传统的 if...else 不够好?

  1. 代码重复:你必须在 if 块和 else 块里面写两次 API::exit();
  2. 嵌套变深:随着逻辑变复杂,你的代码会不断向右缩进,形成“金字塔”或“嵌套地狱”,变得极难阅读。

2. 执行路径的逻辑拆解(为什么不需要 else)

我们来看看原本的代码逻辑是如何像一个“安检闸机”一样工作的:

btn.events().click([&]() 
{
    // 拦截层:只有在 "需要弹出提示" 时才进入
    if (!really_quick) 
    {
        msgbox m(fm, "Our demo", msgbox::yes_no);
        // ... 省略设置 ...
        auto response = m();
        
        // 如果用户点了“否”,直接 return 结束这个 lambda 函数!
        // 后面所有的代码(包括 API::exit())都会被跳过。
        if (response != m.pick_yes) return; 
    }

    // 通关层:能走到这一行的,只有两种清白的情况
    // 情况 1: really_quick 为 true,直接跳过了上面的整个 if 块。
    // 情况 2: really_quick 为 false,但用户在弹窗里勇敢地点击了“是 (Yes)”。
    API::exit();           
});

因为 if 块内部的 return 具有强烈的截断性(只要条件不满足,函数直接在这里彻底结束,后面的代码根本没机会执行)。

所以,代码运行到最后一行 API::exit(); 时,在逻辑上已经天然形成了 else 的效果,根本不需要再刻意写一个 else 关键字来包裹它。


总结

这种“先排除异常/反悔情况(通过 return 拦截),最后执行核心动作”的编写背景,是现代 C++(以及绝大多数现代编程语言)非常推崇的干净风格。它让代码:

  • 扁平化:没有深层的缩进。
  • 意图明显:主流程(退出程序)一目了然地放在函数最底部。
posted @ 2026-06-19 18:19  立体风  阅读(14)  评论(0)    收藏  举报