Dev C++ WinAPI+CSV实现Excel数据合并工具

Vibe Coding 使用 AI 回答

代码问题:

请帮我编写一个Dev C++程序,需求:分别读取excel文件A、B,抓取文件A中的A1和文件B中的A2,生成excel文件C(内容:A1=文件A中的A1、A2=文件B中的A2,A3=A1+A2) 设计成一个用户界面,点击“选择”按钮可以选择输入excel文件A和B,点击“输出”按钮可以选择输出的excel文件目录和命名文件名,其他功能不变。请形成代码。

代码解决方案:

好的,为了在Dev C++中实现这个功能,我们需要使用一些库来处理Excel文件和创建用户界面。

主要挑战和解决方案:

  1. Excel文件操作: C++标准库本身不直接支持Excel文件操作。

    • 解决方案: 我们可以使用第三方库。对于简单的读取和写入,一个常见的选择是使用libxlslibxlsxwriterlibxls用于读取旧版.xls文件,libxlsxwriter用于写入新版.xlsx文件。如果需要同时处理.xls.xlsx,这会变得有些复杂。
    • 另一种更简单的解决方案(但有局限性): 将Excel文件另存为CSV(逗号分隔值)文件。C++可以很容易地读取和写入CSV文件,这会大大简化代码。但是,这要求用户在操作前手动将Excel文件转换为CSV。
    • 更强大的解决方案(但更复杂): 使用COM(Component Object Model)接口(仅限Windows),通过Microsoft Office的自动化功能来操作Excel。这需要更深入的Windows API知识。
  2. 用户界面(GUI): Dev C++自带的编译器是MinGW,它不直接支持Visual Studio的MFC或.NET WinForms。

    • 解决方案: 我们可以使用WinAPI(Windows API)来手动创建窗口、按钮和文本框。这会比较繁琐,但可以实现基本的用户界面。
    • 更高级的解决方案: 使用跨平台GUI库,例如Qt或wxWidgets。但这需要额外的配置和学习曲线,超出了Dev C++默认的简单项目范围。

鉴于Dev C++的常见使用场景和简化问题,我将提供一个基于以下假设的方案:

  • Excel文件处理: 采用将Excel文件另存为 CSV文件 的方式来处理。这意味着用户需要将他们的Excel文件A和B手动另存为CSV文件,然后程序读取这些CSV文件。输出也将是一个CSV文件。
  • 用户界面: 使用 WinAPI 来创建基本的窗口、按钮和文本框。

代码实现步骤:

  1. 项目设置:

    • 创建一个新的Win32 GUI项目。
    • 你可能需要链接一些库,例如gdi32.lib, user32.lib
  2. 头文件和全局变量:

    • 包含必要的WinAPI头文件。
    • 定义一些全局变量来存储文件路径和控件句柄。
  3. 窗口过程函数(WndProc):

    • 处理窗口消息,如按钮点击、文本输入等。
    • 实现文件选择对话框(GetOpenFileNameGetSaveFileName)。
    • 实现CSV文件的读取和写入逻辑。
  4. CSV文件处理函数:

    • read_csv_cell(const std::string& filename, int row, int col):读取CSV文件中指定单元格的内容。
    • write_csv_file(const std::string& filename, const std::string& a1_val, const std::string& a2_val, const std::string& a3_val):将结果写入新的CSV文件。

完整代码(WinAPI + CSV):

#include <windows.h>
#include <commdlg.h> // For Open/Save File Dialogs
#include <string>
#include <vector>
#include <fstream>
#include <sstream>
#include <iomanip> // For std::fixed and std::setprecision

// 全局变量
HWND hEditA, hEditB, hEditOutput; // 文件路径编辑框句柄
HWND hBtnSelectA, hBtnSelectB, hBtnOutputFolder, hBtnProcess; // 按钮句柄

std::string fileA_path;
std::string fileB_path;
std::string output_file_path; // 完整输出文件路径

// CSV文件处理函数
// 读取CSV文件中指定行和列的单元格内容
// 注意:行和列都是从0开始的索引
std::string read_csv_cell(const std::string& filename, int target_row, int target_col) {
    std::ifstream file(filename);
    if (!file.is_open()) {
        return ""; // 文件无法打开
    }

    std::string line;
    int current_row = 0;
    while (std::getline(file, line)) {
        if (current_row == target_row) {
            std::stringstream ss(line);
            std::string cell;
            int current_col = 0;
            while (std::getline(ss, cell, ',')) { // 假设逗号分隔
                if (current_col == target_col) {
                    file.close();
                    return cell;
                }
                current_col++;
            }
        }
        current_row++;
    }
    file.close();
    return ""; // 未找到指定单元格
}

// 将结果写入新的CSV文件
bool write_csv_file(const std::string& filename,
                     const std::string& a1_val,
                     const std::string& a2_val,
                     const std::string& a3_val) {
    std::ofstream file(filename);
    if (!file.is_open()) {
        return false;
    }

    // 写入CSV头部(可选)
    file << "Field,Value" << std::endl;

    file << "A1," << a1_val << std::endl;
    file << "A2," << a2_val << std::endl;
    file << "A3," << a3_val << std::endl;

    file.close();
    return true;
}

// 窗口过程函数
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
        case WM_CREATE: {
            // 创建控件
            // 静态文本
            CreateWindow("STATIC", "文件A (CSV):", WS_VISIBLE | WS_CHILD, 20, 20, 100, 20, hwnd, NULL, NULL, NULL);
            hEditA = CreateWindow("EDIT", "", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL, 130, 20, 300, 20, hwnd, NULL, NULL, NULL);
            hBtnSelectA = CreateWindow("BUTTON", "选择", WS_VISIBLE | WS_CHILD, 440, 20, 80, 20, hwnd, (HMENU)101, NULL, NULL);

            CreateWindow("STATIC", "文件B (CSV):", WS_VISIBLE | WS_CHILD, 20, 50, 100, 20, hwnd, NULL, NULL, NULL);
            hEditB = CreateWindow("EDIT", "", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL, 130, 50, 300, 20, hwnd, NULL, NULL, NULL);
            hBtnSelectB = CreateWindow("BUTTON", "选择", WS_VISIBLE | WS_CHILD, 440, 50, 80, 20, hwnd, (HMENU)102, NULL, NULL);

            CreateWindow("STATIC", "输出文件 (CSV):", WS_VISIBLE | WS_CHILD, 20, 80, 100, 20, hwnd, NULL, NULL, NULL);
            hEditOutput = CreateWindow("EDIT", "", WS_VISIBLE | WS_CHILD | WS_BORDER | ES_AUTOHSCROLL, 130, 80, 300, 20, hwnd, NULL, NULL, NULL);
            hBtnOutputFolder = CreateWindow("BUTTON", "选择", WS_VISIBLE | WS_CHILD, 440, 80, 80, 20, hwnd, (HMENU)103, NULL, NULL);

            hBtnProcess = CreateWindow("BUTTON", "处理并生成", WS_VISIBLE | WS_CHILD, 200, 120, 150, 30, hwnd, (HMENU)104, NULL, NULL);
            break;
        }
        case WM_COMMAND: {
            int wmId = LOWORD(wParam);
            switch (wmId) {
                case 101: { // 选择文件A
                    OPENFILENAME ofn;
                    char szFile[MAX_PATH] = "";
                    ZeroMemory(&ofn, sizeof(ofn));
                    ofn.lStructSize = sizeof(ofn);
                    ofn.hwndOwner = hwnd;
                    ofn.lpstrFile = szFile;
                    ofn.nMaxFile = sizeof(szFile);
                    ofn.lpstrFilter = "CSV Files (*.csv)\0*.csv\0All Files (*.*)\0*.*\0";
                    ofn.nFilterIndex = 1;
                    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

                    if (GetOpenFileName(&ofn) == TRUE) {
                        fileA_path = szFile;
                        SetWindowText(hEditA, fileA_path.c_str());
                    }
                    break;
                }
                case 102: { // 选择文件B
                    OPENFILENAME ofn;
                    char szFile[MAX_PATH] = "";
                    ZeroMemory(&ofn, sizeof(ofn));
                    ofn.lStructSize = sizeof(ofn);
                    ofn.hwndOwner = hwnd;
                    ofn.lpstrFile = szFile;
                    ofn.nMaxFile = sizeof(szFile);
                    ofn.lpstrFilter = "CSV Files (*.csv)\0*.csv\0All Files (*.*)\0*.*\0";
                    ofn.nFilterIndex = 1;
                    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

                    if (GetOpenFileName(&ofn) == TRUE) {
                        fileB_path = szFile;
                        SetWindowText(hEditB, fileB_path.c_str());
                    }
                    break;
                }
                case 103: { // 选择输出文件路径和名称
                    OPENFILENAME sfn; // Note: Use OPENFILENAME for Save dialog as well, just set Flags differently
                    char szFile[MAX_PATH] = "output.csv"; // Default file name
                    ZeroMemory(&sfn, sizeof(sfn));
                    sfn.lStructSize = sizeof(sfn);
                    sfn.hwndOwner = hwnd;
                    sfn.lpstrFile = szFile;
                    sfn.nMaxFile = sizeof(szFile);
                    sfn.lpstrFilter = "CSV Files (*.csv)\0*.csv\0All Files (*.*)\0*.*\0";
                    sfn.nFilterIndex = 1;
                    sfn.Flags = OFN_OVERWRITEPROMPT; // Prompt if file exists

                    if (GetSaveFileName(&sfn) == TRUE) {
                        output_file_path = szFile;
                        SetWindowText(hEditOutput, output_file_path.c_str());
                    }
                    break;
                }
                case 104: { // 处理并生成
                    if (fileA_path.empty() || fileB_path.empty() || output_file_path.empty()) {
                        MessageBox(hwnd, "请选择所有文件路径!", "错误", MB_OK | MB_ICONERROR);
                        break;
                    }

                    // 读取文件A中的A1 (CSV中的第一行第一列,索引为0,0)
                    std::string a1_str = read_csv_cell(fileA_path, 0, 0);
                    if (a1_str.empty()) {
                        MessageBox(hwnd, "无法从文件A中读取A1,请检查文件内容或路径。", "错误", MB_OK | MB_ICONERROR);
                        break;
                    }

                    // 读取文件B中的A2 (CSV中的第一行第二列,索引为0,1)
                    std::string a2_str = read_csv_cell(fileB_path, 0, 1);
                    if (a2_str.empty()) {
                        MessageBox(hwnd, "无法从文件B中读取A2,请检查文件内容或路径。", "错误", MB_OK | MB_ICONERROR);
                        break;
                    }

                    // 尝试将A1和A2转换为数字进行加法
                    std::string a3_str;
                    try {
                        double val_a1 = std::stod(a1_str);
                        double val_a2 = std::stod(a2_str);
                        double val_a3 = val_a1 + val_a2;

                        std::stringstream ss;
                        ss << std::fixed << std::setprecision(2) << val_a3; // 格式化为2位小数
                        a3_str = ss.str();
                    } catch (const std::invalid_argument& e) {
                        MessageBox(hwnd, "A1或A2不是有效的数字,将按字符串拼接。", "警告", MB_OK | MB_ICONWARNING);
                        a3_str = a1_str + a2_str; // 如果不是数字,则简单拼接
                    } catch (const std::out_of_range& e) {
                        MessageBox(hwnd, "A1或A2的数字超出范围,将按字符串拼接。", "警告", MB_OK | MB_ICONWARNING);
                        a3_str = a1_str + a2_str; // 如果超出范围,则简单拼接
                    }


                    // 写入结果到输出文件
                    if (write_csv_file(output_file_path, a1_str, a2_str, a3_str)) {
                        MessageBox(hwnd, "文件处理成功!结果已保存。", "成功", MB_OK | MB_ICONINFORMATION);
                    } else {
                        MessageBox(hwnd, "无法写入输出文件,请检查路径和权限。", "错误", MB_OK | MB_ICONERROR);
                    }
                    break;
                }
            }
            break;
        }
        case WM_DESTROY: {
            PostQuitMessage(0);
            break;
        }
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

// 主函数
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
    const char CLASS_NAME[] = "ExcelProcessorWindowClass";

    WNDCLASS wc = {};
    wc.lpfnWndProc = WndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = CLASS_NAME;
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1); // 设置背景颜色

    RegisterClass(&wc);

    HWND hwnd = CreateWindowEx(
        0,                              // Optional window style
        CLASS_NAME,                     // Window class
        "Excel数据处理工具 (CSV模式)", // Window text
        WS_OVERLAPPEDWINDOW,            // Window style

        // Size and position
        CW_USEDEFAULT, CW_USEDEFAULT, 550, 200,

        NULL,       // Parent window
        NULL,       // Menu
        hInstance,  // Instance handle
        NULL        // Additional application data
    );

    if (hwnd == NULL) {
        return 0;
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    // Run the message loop
    MSG msg = {};
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

如何编译和运行 (Dev C++):

  1. 新建项目:
    • 在Dev C++中,选择 文件 -> 新建 -> 项目...
    • 选择 Windows ApplicationWindows GUI (取决于你的Dev C++版本,通常是Win32 GUI)。
    • 给项目命名,然后点击确定。
  2. 替换代码: 将上述所有代码复制粘贴到生成的 .cpp 文件中,替换掉所有原有内容。
  3. 编译: 点击 执行 -> 编译 (或按 F9)。
  4. 运行: 点击 执行 -> 运行 (或按 F10)。

使用说明:

  1. 准备CSV文件:
    • 文件A.csv: 确保你的Excel文件A中的A1单元格(即第一行第一列)包含你要抓取的数据。将其另存为文件A.csv
      • 示例 文件A.csv 内容:
        100.5,some_other_data,another_value
        row2_data,row2_data
        
        (程序会读取 100.5)
    • 文件B.csv: 确保你的Excel文件B中的A2单元格(即第一行第二列)包含你要抓取的数据。将其另存为文件B.csv
      • 示例 文件B.csv 内容:
        data_ignored,200.3,more_data
        row2_data,row2_data
        
        (程序会读取 200.3)
  2. 运行程序:
    • 点击“选择”按钮分别选择文件A.csv文件B.csv
    • 点击“选择”按钮为输出文件选择一个保存路径和文件名(例如output.csv)。
    • 点击“处理并生成”按钮。
  3. 查看结果: 程序会生成一个名为output.csv的文件,其内容类似:
    Field,Value
    A1,100.5
    A2,200.3
    A3,300.80
    
    (如果A1和A2是数字,A3会是它们的和;如果不是,A3会是它们的字符串拼接。)

重要提示和局限性:

  • CSV格式假设: 此代码假设CSV文件使用逗号作为分隔符。如果你的CSV文件使用分号或其他字符,你需要修改read_csv_cell函数中的std::getline(ss, cell, ',')这一行。
  • 单元格位置: read_csv_cell函数中的 target_rowtarget_col 是基于0的索引。所以:
    • Excel中的A1 -> CSV中的 (0, 0)
    • Excel中的A2 -> CSV中的 (0, 1)
    • 如果你想抓取Excel中的B2,它在CSV中是 (1, 1) (第二行第二列)。
  • 数据类型: 程序会尝试将A1和A2转换为double进行加法。如果转换失败(例如,单元格内容是文本),它会尝试将它们作为字符串进行拼接,并弹出一个警告。
  • 错误处理: 错误处理相对简单,主要通过MessageBox提示用户。
  • Excel原生支持: 这种方法要求用户手动将Excel文件另存为CSV。如果你需要直接读取.xls.xlsx文件,这将需要更复杂的第三方库(如libxlslibxlsxwriter),并且在Dev C++中配置这些库可能比较麻烦。如果是在Visual Studio中,使用COM接口会是更强大的选择。
  • GUI的简陋性: WinAPI直接构建的界面比较简陋,没有现代GUI库的漂亮外观和复杂控件。

这个解决方案在Dev C++环境下,兼顾了易用性和功能实现,但请务必理解其基于CSV的假设。