需要帮助在 Win32 API 中编写带有文件保存和文件打开对话框的文本编辑器

问题描述 投票:0回答:1

我目前正在开发一个带有 C++

windows.h
内置 GUI 库的文本编辑器。

每次我尝试保存文件或打开文件并将其显示到窗口时,它都会返回错误,或者打开文件对话框失败。错误是

Exception: Segmentation Fault
,并且它总是发生在
GetOpenFileName();
线上。此外,如果它确实有效,
FATAL ERROR: Couldn't display file
FATAL ERROR: Couldn't write file
总是会发生。我使用的是 MINGW MSYS 的 g++,所以也许我的编译器不好?

另外,我见过有人使用

L"your text"
,但是当我这样做时它会返回错误并且无法编译,所以我求助于
_T("your text")
。这是因为没有启用 UNICODE。

顺便说一句,我的 VS Code Intellisense 告诉我,我在没有错误的地方出现了错误,并且程序编译正确(错误不在我打开/保存文件的位置)。

附注我还没有真正找到任何关于我正在尝试做的事情的文档。

这是我现在的代码:

#include <windows.h>
#include <commctrl.h>
#include <shellapi.h>
#include <tchar.h>

#define ID_BUTTON_1 1
#define ID_BUTTON_2 2
#define ID_BUTTON_3 3
#define ID_BUTTON_4 4
#define ID_BUTTON_5 5
#define ID_BUTTON_6 6
#define ID_BUTTON_7 7
#define ID_BUTTON_8 8
#define ID_BUTTON_9 9
#define ID_FILE_NEW 1000
#define ID_FILE_OPEN 1010
#define ID_FILE_SAVE 1020
#define ID_FILE_QUIT 1030


//Global Variables

//Main window class name
static TCHAR szWindowClass[] = _T("Desktop Application");

//Main window title bar text
static TCHAR szTitle[] = _T("C++ GUI Application");

//Instance handle for Win32 API calls
HINSTANCE hInst;
HWND hWnd;
HWND hWndFileText;
int hWndWidth = 600;
int hWndHeight = 400;

//Forward declarations of functions included in this code
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI WinMain(
    _In_ HINSTANCE hInstance, 
    _In_ HINSTANCE hPrevInstance, 
    _In_ LPSTR lpCmdLine, 
    _In_ int nCmdShow) {

    WNDCLASSEX wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(wcex.hInstance, IDI_APPLICATION);
    wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName = NULL;
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION);

    if (!RegisterClassEx(&wcex)) {
        MessageBox(NULL,
        _T("Call to RegisterClassEx Failed!"),
        _T("Error"), 
        MB_ICONERROR);
        
        return 1;
    }

    //Store instance handle in our global variable
    hInst = hInstance;

    HMENU hFileMenu = CreateMenu();
    HMENU hMenuBar = CreateMenu();
    AppendMenu(hFileMenu, MF_STRING, ID_FILE_NEW, _T("New"));
    AppendMenu(hFileMenu, MF_STRING, ID_FILE_OPEN, _T("Open"));
    AppendMenu(hFileMenu, MF_STRING, ID_FILE_SAVE, _T("Save"));
    AppendMenu(hFileMenu, MF_STRING, ID_FILE_QUIT, _T("Quit"));
    AppendMenu(hMenuBar, MF_POPUP, (UINT_PTR)hFileMenu, _T("File"));

    hWnd = CreateWindowEx(
        WS_EX_OVERLAPPEDWINDOW, //Extended window style
        szWindowClass,          //Name of application
        szTitle,                //Title bar text
        WS_OVERLAPPEDWINDOW,    //window characteristics
        CW_USEDEFAULT,          //Default Position x
        CW_USEDEFAULT,          //Default Position y
        hWndWidth,              //Window size (width)
        hWndHeight,             //Window size (height)
        NULL,                   //Window parent
        hMenuBar,               //Menu bar
        hInstance,              //first parameter from WinMain
        NULL
    );

    if (!hWnd) {
        MessageBox(NULL,
        _T("Call to CreateWindowEx Failed!"),
        _T("Error"),
        MB_ICONERROR);

        return 1;
    }

    HWND hWndFileText = CreateWindow(
        _T("EDIT"),  
        _T("File text goes here"), 
        WS_VISIBLE | WS_CHILD | ES_LEFT | WS_VSCROLL | ES_AUTOVSCROLL | ES_MULTILINE, 
        5, 30, hWndWidth - 5, hWndHeight - 30, 
        hWnd, NULL, hInst, NULL);
    
    if (!hWndFileText) {
        MessageBox(hWnd, 
        _T("Call to create texbox failed!"),
        _T("Error"), 
        MB_ICONERROR);
        
        return 1;
    }

    ShowWindow(hWnd, nCmdShow); //Value returned from CreateWindowEx, 
                                //fourth WinMain parameter
    UpdateWindow(hWnd);

    MSG msg; 
    while (GetMessage(&msg, NULL, 0, 0)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

//WndProc (window process)
//
//Processes messages for the main window
//
//WM_PAINT - paints the main window
//WM_DESTROY - posts a quit message
char fileData[100];

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {

    PAINTSTRUCT ps;
    HDC hdc; //handle to device context (hdc)
    TCHAR greeting[] = _T("Hello, world!"); //Text for window
    

    switch (message) {
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        
        //This is where stuff is put in the window
        //like text, buttons, etc.
        //For now, I'm putting a simple hello, world
        //at the top left corner of the window
        TextOut(hdc, 5, 5, greeting, _tcslen(greeting));

        EndPaint(hWnd, &ps); //releases device context and ends paint request
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_COMMAND: 
        switch (LOWORD(wParam)) {
        case ID_FILE_QUIT:
            DestroyWindow(hWnd);
            break;
        case ID_FILE_OPEN:
        {
            LPDWORD bytesRead = 0;
            OPENFILENAME fileName; //Dialog box structure
            HANDLE fileHandle;
            char szfileName[260];  //Max length of file name
            char* fileReadBuffer; //Place to put text read from file
            DWORD fileSize; 
            ZeroMemory(&fileName, sizeof(fileName));
            fileName.lStructSize = sizeof(fileName);
            fileName.hwndOwner = hWnd;
            fileName.lpstrFile = szfileName; 
            fileName.nMaxFile = sizeof(szfileName);
            fileName.lpstrFile[0] = '\0';
            fileName.lpstrFilter = _T("Text (.txt)\0*.TXT\0");
            fileName.nFilterIndex = 1;
            fileName.lpstrFileTitle = NULL;
            fileName.nMaxFileTitle = 0;
            fileName.lpstrInitialDir = NULL;
            fileName.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
            if (GetOpenFileName(&fileName)) {
                fileHandle = CreateFile(fileName.lpstrFile,
                                        GENERIC_READ,
                                        0,
                                        NULL,
                                        OPEN_EXISTING,
                                        FILE_ATTRIBUTE_NORMAL, 
                                        NULL);
            }
            else {
                MessageBox(hWnd, _T("Couldn't Open File!"), _T("ERROR"), MB_ICONERROR);
                break;
            } 

            fileSize = GetFileSize(fileHandle, NULL);
            fileReadBuffer = (char*)GlobalAlloc(GPTR, (fileSize+1));

            if (!ReadFile(fileHandle, (LPVOID)fileReadBuffer, fileSize, bytesRead, NULL)) {
                MessageBox(hWnd, _T("Fatal Error: Couldn't initialize file read"), _T("ERROR"), MB_ICONERROR);
            }
            else {
                MessageBox(hWnd, _T("File Read"), _T("Success"), MB_OK);
            }
            SetWindowText(hWndFileText, _T("Attempting to display file"));
            fileReadBuffer[fileSize] = '\0';
            if (!SetWindowText(hWndFileText, fileReadBuffer)) { //This never works
                MessageBox(hWnd, _T("Fatal Error: Couldn't display file"), _T("ERROR"), MB_ICONERROR);
            }
            else {
                MessageBox(hWnd, _T("File displayed"), _T("Success"), MB_OK);
            }

            CloseHandle(fileHandle);
            break;
        }
        case ID_FILE_SAVE:
        {
            LPDWORD bytesWritten;
            OPENFILENAME fileName; //Dialog box structure
            HANDLE fileHandle;
            char szfileName[260];  //Max length of file name
            char fileWriteBuffer[100]; //Place to put text from textbox
            fileName.lStructSize = sizeof(fileName);
            fileName.hwndOwner = hWnd;
            fileName.lpstrFile = szfileName; 
            fileName.nMaxFile = sizeof(szfileName);
            fileName.lpstrFile[0] = '\0';
            fileName.lpstrFilter = _T("Text (.txt)\0*.TXT\0");
            fileName.nFilterIndex = 1;
            fileName.lpstrFileTitle = NULL;
            fileName.nMaxFileTitle = 0;
            fileName.lpstrInitialDir = NULL;
            fileName.Flags = OFN_EXPLORER;
            if (GetOpenFileName(&fileName)) { //This is where error occurs
                fileHandle = CreateFile(fileName.lpstrFile,
                                        GENERIC_WRITE,
                                        0,
                                        NULL,
                                        OPEN_EXISTING,
                                        FILE_ATTRIBUTE_NORMAL, 
                                        NULL);
            }
            else {
                MessageBox(hWnd, _T("Couldn't Open File For Saving!"), _T("ERROR"), MB_ICONERROR);
                break;
            }

            GetWindowText(hWndFileText, fileWriteBuffer, 99);

            if(!WriteFile(fileHandle, fileWriteBuffer, sizeof(fileWriteBuffer), bytesWritten, NULL)){ //This never works
                MessageBox(hWnd, _T("FATAL ERROR: Couln't write to file!"), _T("ERROR"), MB_ICONERROR);
            }
            else {
                MessageBox(hWnd, _T("File saved successfully!"), _T("Success"), MB_OK);
            }
        }
        default:
            break;
        }
        break;
    case WM_CREATE:
           break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
        break;
    }
    return 0;
}

我知道我不需要第二个和第三个包含,我只是没有删除它们。按钮 1-9 是稍后使用的,我还没有做到这一点,但它们不应该引起问题。

我查看了一堆有关如何使用

CreateFile()
ReadFile()
WriteFile()
函数的文档,这就是我到目前为止所得到的,但似乎都不起作用。

另外,我不知道如何初始化保存文件对话框,所以它是两者的打开文件对话框(但由于某种原因,保存功能中打开文件对话框的窗口标题是

A[*62]<
或其他一些废话(垃圾?))。

请不要发表任何类似“这对于初学者来说太难了”之类的评论,因为这对于其他有类似问题的人来说并不是很有帮助。

这是完整的源代码,所以如果你想尝试一下,你应该能够按原样编译它。

c++ winapi text-editor win32gui
1个回答
0
投票

fileName.lpstrFilter = _T("Text (.txt)\0*.TXT\0");

不可否认,
文档

这里有点不清楚:

包含成对的以 null 结尾的过滤器字符串的缓冲区。缓冲区中的最后一个字符串必须以两个 NULL 字符终止。

如果我没记错的话(并且基于我刚刚检查的一些旧代码),在终止最后对的第二个字符串的空字符之后,实际上需要一对空字符(即一对空字符串)。所以缓冲区应该以三个空字符结束。

GetOpenFileName 和 GetSaveFileName 对话框非常古老且粗糙。通用项目对话框提供了更新的界面。在某些方面,它们更难使用,因为您需要了解一些有关 COM 编程的知识。一旦你克服了这个障碍,它们就有点难以使用了,因为有设置每个选项的方法,所以你可以获得更多的类型安全性。

© www.soinside.com 2019 - 2024. All rights reserved.