72、wxWidgets:集成wxWidgets跨平台GUI库

说到跨平台GUI库,很多人第一反应是Qt。但我要说,wxWidgets其实是个被低估的好东西。我最早接触它是在一个嵌入式项目里,那时候Qt的授权问题让人头疼,wxWidgets的纯C++实现和原生控件风格让我眼前一亮。

说白了,wxWidgets最大的特点就是「用原生API画原生控件」。你在Windows上编译出来的程序,按钮就是Windows风格的;在macOS上编译,按钮就是macOS风格的。这一点,我个人觉得比Qt的模拟风格要舒服得多。

为什么选wxWidgets?

我总结了几点实际项目中的体会:

  • 原生外观:每个平台上的控件都是系统原生的,用户看着顺眼
  • 纯C++实现:没有额外的预处理步骤,CMake集成起来很直接
  • 许可证友好:wxWindows Library License,商业项目随便用
  • 轻量级:编译出来的体积比Qt小不少,我那个嵌入式项目最终只有8MB

核心思路:wxWidgets不是「画」界面,而是「调用」系统原生控件。你的代码在Windows上调的是Win32 API,在Linux上调的是GTK,在macOS上调的是Cocoa。

CMake中集成wxWidgets

嗯,这里要注意。wxWidgets官方提供了CMake模块,但版本不同用法略有差异。我建议用find_package的方式,这是最标准的做法。

基础配置

cmake_minimum_required(VERSION 3.16)
project(WxDemo VERSION 1.0 LANGUAGES CXX)

# 查找wxWidgets
find_package(wxWidgets REQUIRED COMPONENTS core base)

# 引入wxWidgets的配置
include(${wxWidgets_USE_FILE})

add_executable(wx_demo main.cpp)

# 链接wxWidgets库
target_link_libraries(wx_demo ${wxWidgets_LIBRARIES})

这段代码看起来简单,但我在项目中踩过坑。如果你需要更多组件,比如nethtmladv,记得在COMPONENTS里加上。我曾经漏掉了adv组件,结果编译死活过不去,查了半天才发现。

编译选项的坑

注意:wxWidgets的Debug和Release版本必须和你的项目一致。混用会导致链接错误,而且错误信息非常隐晦。

我个人习惯这样处理:

if(CMAKE_BUILD_TYPE STREQUAL "Debug")
    set(wxWidgets_USE_DEBUG ON)
else()
    set(wxWidgets_USE_DEBUG OFF)
endif()

find_package(wxWidgets REQUIRED COMPONENTS core base adv)

为什么要单独设这个?因为有些CMake版本不会自动判断wxWidgets的Debug/Release模式。你想想看,如果项目是Debug编译,链接了Release版的wxWidgets,那调试的时候断点都进不去,多难受。

一个完整的示例

来,我们写一个最简单的窗口程序。这是我在教学时经常用的例子,麻雀虽小五脏俱全。

// main.cpp
#include <wx/wx.h>

class MyApp : public wxApp {
public:
    virtual bool OnInit() override;
};

class MyFrame : public wxFrame {
public:
    MyFrame(const wxString& title);
};

wxIMPLEMENT_APP(MyApp);

bool MyApp::OnInit() {
    MyFrame* frame = new MyFrame("Hello wxWidgets");
    frame->Show(true);
    return true;
}

MyFrame::MyFrame(const wxString& title)
    : wxFrame(nullptr, wxID_ANY, title, wxDefaultPosition, wxSize(400, 300)) {
    
    // 创建一个面板,这是所有控件的容器
    wxPanel* panel = new wxPanel(this, wxID_ANY);
    
    // 创建一个按钮
    wxButton* button = new wxButton(panel, wxID_ANY, "点击我",
                                    wxPoint(150, 120), wxSize(100, 30));
    
    // 绑定事件
    button->Bind(wxEVT_BUTTON, [](wxCommandEvent&) {
        wxMessageBox("你好,wxWidgets!", "提示");
    });
}

这段代码在Windows、Linux、macOS上都能编译运行。你可能会问:「为什么还要创建一个面板?」嗯,这是wxWidgets的习惯——所有子控件最好放在面板上,而不是直接放在框架上。否则在某些平台上会出现奇怪的布局问题。

项目结构建议

我在实际项目中,一般这样组织代码:

project/
├── CMakeLists.txt
├── src/
│   ├── main.cpp
│   ├── MainFrame.h
│   ├── MainFrame.cpp
│   └── MyApp.h
└── resources/
    └── icon.png

对应的CMakeLists.txt:

cmake_minimum_required(VERSION 3.16)
project(WxApp VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(wxWidgets REQUIRED COMPONENTS core base adv)
include(${wxWidgets_USE_FILE})

# 收集源文件
file(GLOB_RECURSE SOURCES src/*.cpp src/*.h)

add_executable(${PROJECT_NAME} ${SOURCES})

target_include_directories(${PROJECT_NAME} PRIVATE src)
target_link_libraries(${PROJECT_NAME} ${wxWidgets_LIBRARIES})

# 如果是Windows,隐藏控制台窗口
if(WIN32)
    set_target_properties(${PROJECT_NAME} PROPERTIES
        WIN32_EXECUTABLE TRUE)
endif()

小技巧WIN32_EXECUTABLE这个属性很重要。如果不设置,Windows下会同时弹出控制台窗口和GUI窗口,用户体验很差。我刚开始做的时候就被用户吐槽过。

wxWidgets的核心知识体系

下面这张图是我自己总结的,涵盖了wxWidgets的主要模块和它们之间的关系:

wxWidgets 核心模块结构 wxApp 应用入口 wxFrame / wxDialog 窗口框架 wxPanel 容器面板 wxButton / wxTextCtrl wxListBox / wxTreeCtrl wxMenuBar / wxStatusBar wxEvent 事件处理系统 每个模块通过事件系统连接,形成完整的GUI框架

常见问题与避坑

做wxWidgets项目,有几个问题我几乎每次都会被问到:

  1. 找不到wxWidgets库:检查wx-config --cxxflagswx-config --libs的输出。如果这两个命令报错,说明wxWidgets没装好。
  2. 链接错误:八成是Debug/Release不匹配。我曾经花了一下午排查,最后发现是wxWidgets编译时用了Release,我的项目是Debug。
  3. 中文乱码:wxWidgets 3.0以后默认用UTF-8,但Windows控制台可能不认。建议所有字符串都用wxString::FromUTF8()包装一下。

避坑指南:我曾经在Linux上编译wxWidgets时忘了装GTK开发包,结果编译出来的程序窗口一片空白。记得先执行sudo apt-get install libgtk-3-dev

总结

wxWidgets是个老牌但可靠的跨平台GUI库。它的原生控件风格、友好的许可证、轻量级的体积,让它在很多场景下都是不错的选择。通过CMake集成其实不复杂,核心就是find_packagetarget_link_libraries这两步。记住Debug/Release要一致,组件要列全,基本上就不会出大问题。

我个人觉得,如果你不想被Qt的moc预处理折腾,又想要跨平台能力,wxWidgets值得一试。它没有那么多花哨的功能,但该有的都有,而且稳定。


公众号:蓝海资料掘金营,微信deep3321