Imgui显示菜单-保存对话框-实现一个txt记事本例子

本节课,我们来学习菜单的显示,我们要开发一个类似txt记事本的功能,顶部有一个菜单,菜单中有一个保存的子菜单,点击保存子菜单,则保存下方文本框中输入的文本。这里会出现乱码,所以读者会学到如何设置语言,如果不设置,可能非英文的字体会乱码的。

保存的时候,弹出一个文件保存对话框,读者会选择保存目录,然后设置文件名。之后就可以保存文件。

下面看最后运行的界面截图:

和我们的windows的记事本类似,基本编辑功能,保存。程序编译后非常小巧

在release下编译,只有645kb

我们看程序的代码

#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma execution_character_set("utf-8")

#include <GLFW/glfw3.h>
#include "imgui.h"
#include "backends/imgui_impl_glfw.h"
#include "backends/imgui_impl_opengl3.h"
#include <cstdio>
#include <string>
#include <fstream>
#include <windows.h>
#include <io.h>
#include <commdlg.h>  // 需要这个头文件

// 文本缓冲区配置
const int TEXT_BUFFER_MAX = 4096;
std::string editor_text;

// 文件保存函数 UTF8带BOM
// 把文件写入到文件中
bool SaveTextToFile(const char* filepath, const std::string& text)
{
    std::ofstream out_file(filepath, std::ios::out | std::ios::binary);
    if (!out_file.is_open())
        return false;
    const unsigned char utf8_bom[] = { 0xEF, 0xBB, 0xBF };
    out_file.write((char*)utf8_bom, sizeof(utf8_bom));
    out_file << text.c_str(); // 只输出到\0为止的有效文本
    out_file.close();
    return true;
}

// 显示保存文件对话框,返回用户选择的文件路径 
bool ShowSaveFileDialog(char* out_path, DWORD out_size)
{
    OPENFILENAMEA ofn;   // 注意这里用 A 版本
    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = nullptr;
    ofn.lpstrFilter = "Text Files\0*.txt\0All Files\0*.*\0"; //后缀是txt或者所有类型
    ofn.lpstrFile = out_path;
    ofn.nMaxFile = out_size;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_OVERWRITEPROMPT;
    ofn.lpstrDefExt = "txt";

    return GetSaveFileNameA(&ofn);   // 调用 ANSI 版本
}

// 设置字体,如果读者输入文本的时候是??的乱码
// 则需要在这里增加你所在环境的语言字体路径,或者使用系统默认字体路径
void SetupFont(ImGuiIO& io)
{
    // 常见中文字体路径(Windows)
    const char* font_path = "C:/Windows/Fonts/msyh.ttc"; // 微软雅黑
    float font_size = 18.0f;

    // 加载简体中文常用字范围
    ImVector<ImWchar> ranges;
    ImFontGlyphRangesBuilder builder;
    builder.AddRanges(io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
    builder.BuildRanges(&ranges);

    // 添加字体
    io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, ranges.Data);

    // 如果需要多语言支持,可以额外添加:
    // io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, io.Fonts->GetGlyphRangesJapanese());
    // io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, io.Fonts->GetGlyphRangesKorean());
}


int main()
{
    SetConsoleOutputCP(65001);
    // 关键:扩容并全部填充0,清空内存脏数据,解决????问号
    editor_text.resize(TEXT_BUFFER_MAX, '\0');

    if (!glfwInit())
    {
        printf("GLFW init failed\n");
        return -1;
    }

    const char* glsl_version = "#version 330";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "Text Note Editor", nullptr, nullptr);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    ImGui::StyleColorsLight();

    SetupFont(io); // 设置字体,解决乱码问题

    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init(glsl_version);

    bool show_save_success = false;
    bool show_save_fail = false;

	char path[MAX_PATH] = "";// 保存文件路径缓冲区

    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();

        // Ctrl+S 保存快捷键
        if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_S))
        {
            if (ShowSaveFileDialog(path, MAX_PATH))
            {
                if (SaveTextToFile(path, editor_text))
                    show_save_success = true;
                else
                    show_save_fail = true;
            }
        }

        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        ImGui::SetNextWindowPos(ImVec2(0, 0));
        ImGui::SetNextWindowSize(io.DisplaySize);

        ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration
            | ImGuiWindowFlags_NoMove
            | ImGuiWindowFlags_NoResize
            | ImGuiWindowFlags_NoSavedSettings
            | ImGuiWindowFlags_MenuBar;   // 显示菜单栏 必须设置,否则不显示

        ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);

        ImGui::Begin("Text Note Editor", nullptr, window_flags);

        // 顶部菜单栏
        if (ImGui::BeginMenuBar())
        {
            if (ImGui::BeginMenu("File"))
            {
				if (ImGui::MenuItem("Save", "Ctrl+S")) // 菜单栏保存 前面是文字 后面是快捷键提示
                {
					//和前面学的按钮一样,点击了菜单会进入到这里
					if (ShowSaveFileDialog(path, MAX_PATH)) //显示保存文件对话框
                    {
						if (SaveTextToFile(path, editor_text)) // 保存文件
                            show_save_success = true; //保存成功 设置show_save_success 为true 后面根据这个进行提示
                        else
                            show_save_fail = true;
                    }
                }

                ImGui::Separator();

                if (ImGui::MenuItem("Exit", "Esc")) // 退出菜单
                {
					glfwSetWindowShouldClose(window, true); // 设置窗口关闭标志,退出程序
                }
                ImGui::EndMenu();
            }
            ImGui::EndMenuBar();
        }

        ImGui::Spacing();

        ImVec2 multiline_size = ImGui::GetContentRegionAvail();
        // 修复问号核心:传入固定缓冲区最大值,内存已全置0无脏数据
        ImGui::InputTextMultiline("##text_editor", &editor_text[0], TEXT_BUFFER_MAX, multiline_size);

	// 根据前面的结果显示保存成功或失败的弹窗
        if (show_save_success)
        {
            //这行代码不会绘制任何窗口、不会执行 BeginPopupModal
	    ImGui::OpenPopup("Save Success");// 给 ImGui 内部状态打标记 告诉它要显示一个弹窗 传入字符串 "Save Success" 作为弹窗唯一 ID
            show_save_success = false; 
        }
        if (show_save_fail)
        {
			ImGui::OpenPopup("Save Failed");// 同上 设置弹窗标记,准备下面显示
            show_save_fail = false;
        }

	// 成功的弹窗显示
        if (ImGui::BeginPopupModal("Save Success", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
        {
            ImGui::Text("Text saved successfully!");
            if (ImGui::Button("OK", ImVec2(120, 0)))
                ImGui::CloseCurrentPopup();
            ImGui::EndPopup();
        }

	// 失败的弹窗显示
        if (ImGui::BeginPopupModal("Save Failed", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
        {
            ImGui::Text("Path invalid or no write permission!");
            if (ImGui::Button("OK", ImVec2(120, 0)))
                ImGui::CloseCurrentPopup();
            ImGui::EndPopup();
        }

        ImGui::End();
        ImGui::PopStyleVar(2);

        ImGui::Render();
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);
        glViewport(0, 0, width, height);
        glClearColor(0.12f, 0.12f, 0.12f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());

        glfwSwapBuffers(window);
    }

    ImGui_ImplOpenGL3_Shutdown();
    ImGui_ImplGlfw_Shutdown();
    ImGui::DestroyContext();
    glfwDestroyWindow(window);
    glfwTerminate();
    return 0;
}Code language: C++ (cpp)

小编已经把基本的功能封装成函数了,上面几个函数是用来写入文件,文件选择,和设置语言的。

然后界面的显示,和之前相比,主要是多了菜单和多行文本编辑框

菜单栏

if (ImGui::BeginMenuBar())
{
    if (ImGui::BeginMenu("File"))
    {Code language: C++ (cpp)

菜单显示在这里,里面一个一级菜单if (ImGui::BeginMenu(“File”)) 一个if

里面是子菜单项,下面是官方例子

if (ImGui::BeginMenuBar())
{
    if (ImGui::BeginMenu("File"))
    {
        if (ImGui::MenuItem("Open..", "Ctrl+O")) { /* Do stuff */ }
        if (ImGui::MenuItem("Save", "Ctrl+S"))   { /* Do stuff */ }
        if (ImGui::MenuItem("Close", "Ctrl+W"))  { my_tool_active = false; }
        ImGui::EndMenu();
    }
    ImGui::EndMenuBar();
}

如果要多个菜单

if (ImGui::BeginMenuBar())
{
    if (ImGui::BeginMenu("File"))
    {
	if (ImGui::MenuItem("Save", "Ctrl+S")) // 菜单栏保存 前面是文字 后面是快捷键提示
        {
	    //和前面学的按钮一样,点击了菜单会进入到这里
	   if (ShowSaveFileDialog(path, MAX_PATH)) //显示保存文件对话框
            {
		if (SaveTextToFile(path, editor_text)) // 保存文件
                    show_save_success = true; //保存成功 设置show_save_success 为true 后面根据这个进行提示
                else
                    show_save_fail = true;
            }
        }

        ImGui::Separator();

        if (ImGui::MenuItem("Exit", "Esc")) // 退出菜单
        {
			glfwSetWindowShouldClose(window, true); // 设置窗口关闭标志,退出程序
        }
        ImGui::EndMenu();
    }

    if (ImGui::BeginMenu("Editor"))
    {
        if (ImGui::MenuItem("Copy", "Ctrl + c")) 
        {

        }
        ImGui::EndMenu();
    }

    ImGui::EndMenuBar();
}Code language: C++ (cpp)

并排写多个if (ImGui::BeginMenu(“xxxx”)) 就可以。

提示

ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration
    | ImGuiWindowFlags_NoMove
    | ImGuiWindowFlags_NoResize
    | ImGuiWindowFlags_NoSavedSettings
    | ImGuiWindowFlags_MenuBar;   // 显示菜单栏 必须设置,否则不显示Code language: JavaScript (javascript)

最后的ImGuiWindowFlags_MenuBar 一定要加上,否则菜单是不会显示的。

多行文本

多行文本,很简单

ImVec2 multiline_size = ImGui::GetContentRegionAvail();
// 修复问号核心:传入固定缓冲区最大值,内存已全置0无脏数据
ImGui::InputTextMultiline("##text_editor", &editor_text[0], TEXT_BUFFER_MAX, multiline_size);Code language: C++ (cpp)

调用InputTextMultiline 就可以。multiline_size 是尺寸,根据区域自动设置。

提示框

然后是提示框,提示框是通过给imgui设置标记,然后再后面根据标记显示出来 。例如

if (show_save_success)
{
    ImGui::OpenPopup("Save Success");Code language: CSS (css)

这里根据show_save_success的值,然后给ImGui::OpenPopup(“Save Success”); 设置弹窗标记”Save Success”

下方根据标记显示提示框

if (ImGui::BeginPopupModal("Save Success", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
{
    ImGui::Text("Text saved successfully!");
    if (ImGui::Button("OK", ImVec2(120, 0)))
        ImGui::CloseCurrentPopup();
    ImGui::EndPopup();
}Code language: PHP (php)

ok,本节课就给大家介绍到这里。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注