Timers and Animation

theForger's Win32 API教程第二版(简体中文)

主页

基础
  1. 开始学习
  2. 一个简单的窗口
  3. 处理消息
  4. 理解消息循环
  5. 使用资源
  6. 菜单和图标
  7. 对话框
  8. 非模态对话框
  9. 标准控件
  10. 对话框常见问题
创建一个简单应用
  1. 在运行时创建控件
  2. 文件与常用对话框
  3. 工具栏与状态栏
  4. 多文档界面
图形设备接口
  1. 位图,设备上下文
  2. 透明位图
  3. 定时器与动画
  4. 文本,字体与顏色
工具与文档
  1. 参考
  2. 免费的Visual C++(最新更新)
附表
  1. 常见错误的解決方法
  2. API vs. MFC
  3. 关于资源文件的说明

应用 第二部分:使用文件和用对话框

[images/app_two.jpg]

范例:app_two

用文件对话框

  要打开或保存文件的第一步就是要找到要用的文件名...当然你总是可以把文件名字直接写到你的程序中去,但老实说那种做法一般不会使程序很有用.

  因为这是一个如此常见的任务,以致于有已经定义好的系统对话框可以用来允许用戶来选择一个文件名. 最常用的打开和保存文件的对话框分別通过GetOpenFileName()和GetSaveFileName()来调用,它们两个都要一个OPENFILENAME结构体作参数.

    OPENFILENAME ofn;
    char szFileName[MAX_PATH] = "";

    ZeroMemory(&ofn, sizeof(ofn));

    ofn.lStructSize = sizeof(ofn); // SEE NOTE BELOW
    ofn.hwndOwner = hwnd;
    ofn.lpstrFilter = "Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0";
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
    ofn.lpstrDefExt = "txt";

    if(GetOpenFileName(&ofn))
    {
        // Do something usefull with the filename stored in szFileName 
    }

  请注意我们用了ZeroMemory()对结构体清零.这通常是个好的习惯,因为有些API对你传给的不用的参数要设为NULL很挑剔.用这种方法就不需要对每个不用的成员进行设置了.

  你要是在你的文档中查每个成员的意思并不是很容易的一件事.LpstrFilter值指向一个双NULL终止符的字符串,并且你从例子中看到有数个 ''\0'',包括结尾的那个...编译器会在结尾加上第二个,就像它对字符串常量通常所做的一样(你一般不用你自己去加).这个字符串中的空字符把其分成多个过滤器,每个有两个部分.第一个过滤器有描述''Text Files (*.txt)'',通配符在这里不是必需的,放在这里只是因为我喜欢它.接下来的部分是第一个过滤器的实际的通配符,”*.txt”.我们对第二个过滤器做同样的处理,除了它是一个所有文件的通用过滤器之外.你可以随你喜欢加不同的过滤器.

  LpstrFile指向我们配置来保存文件名的內存,因为文件名不能长于MAX_PATH,所以我也把这个值当作了此內存的大小.

  几个标志表明了对话框对允许用戶输入已存在的文件(我们只想打开,不想创建)并隠藏了我们不打算支持的以只读方式打开的选项.最后我们提供了一个默认的文件扩展名,所以如果用戶输入了''foo'',而沒有找到,那么它会在最后放棄之前去试图打开''foo.txt''.

  选择保存文件的代码和打开文件的代码几乎一样,除了调用GetSaveFileName()中我们需要改变一些标志以使选项更适合于保存文件.

    ofn.Flags = OFN_EXPLORER | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;

  这种情下我们不再需要文件存在,但我们需要目錄存在,因为我们不想去先创建它.我们也要在用戶选择了一个已存在的文件时候提示他们是否想要覆盖它.

注意:MSDNlStructSize成员作了如下说明:

lSturctSize

指明结构体的长度,单位为Byte.

Windows NT 4.0在一个用WINVER_WIN32_WINNT>=0x500的定义编译的应用程序来说,用OPENFILENAME_SIZE_VERSION_400作它的成员.

  Windows 2000/XPsizeof(OPENFILENAME)成这个参数.

  简单说来是Windows2000他们对这个结构体加了一些成员,所以它的大小变了.如果上面的代码不能工作的话很可能是你的编译器所用的大小跟你的作业系统(比如Windows98,WindwsNT4)所期望的不同,以至调用失败.如果这样的话,试下用OPENFILENAME_SIZE_VERSION_400代替sizeof(ofn).感谢向我指出这一点的人们.

读写文件

  在windows系统中你有好几个方式来读写文件.可以用老式的io.h中的open()/read()/write(),可以用stdio.h中的fopen()/fread()/fwrite(),也可以用C++中的iostream

  但是在Windows系统中这些不同的方式最后都是调用的Win32 API函数,这也是我要在这里用的.如果你已经对用使用另外一种方法来做文件IO很熟悉的话,这是很容易适应的,或者你想用你自己的方法来读写文件也可以.

  打开文件,用OpenFile()或CreateFile().微软推荐只用CreateFile()因为OpenFile()现在已经过时了.CreateFile()是一个很灵活的函数,提供了用来打开文件的很多的控制.

  打个比方你用 GetOpenFileName()允许用戶选择文件..

BOOL LoadTextFileToEdit(HWND hEdit, LPCTSTR pszFileName)
{
    HANDLE hFile;
    BOOL bSuccess = FALSE;

    hFile = CreateFile(pszFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
        OPEN_EXISTING, 0, NULL);
    if(hFile != INVALID_HANDLE_VALUE)
    {
        DWORD dwFileSize;

        dwFileSize = GetFileSize(hFile, NULL);
        if(dwFileSize != 0xFFFFFFFF)
        {
            LPSTR pszFileText;

            pszFileText = GlobalAlloc(GPTR, dwFileSize + 1);
            if(pszFileText != NULL)
            {
                DWORD dwRead;

                if(ReadFile(hFile, pszFileText, dwFileSize, &dwRead, NULL))
                {
                    pszFileText[dwFileSize] = 0; // Add null terminator
                    if(SetWindowText(hEdit, pszFileText))
                        bSuccess = TRUE; // It worked!
                }
                GlobalFree(pszFileText);
            }
        }
        CloseHandle(hFile);
    }
    return bSuccess;
}

  有一个完整的函数来将一个文本文件读入到编辑框中.它将编辑框的句柄和要读入的文件的名字作为参数.这个很严格的函数有一大堆的出错检查,文件IO的确是容易出错的地方,所以你要记得检错.

  注意变量dwRead.我们在ReadFile()中不需要.但这个参数必须提供,否则就要出错.

  对createFile()的调用中,GENERIC_READ意即我们只想有读的权限.FILE_SHARE_READ的意思是如果其它的程序也在我们打开的时候打开它也可以,但是它们也要是只读才行,它们要是在我们读的时候在对其进行写操作就不行.OPEN_EXISTING 意思是我们只打开已经存在的文件,不要创建它,也不要覆盖它.

  一旦我们打开了文件并检查了CreateFile()调用成功后,我们再来检查文件的大小以便知道我们要配置多少內存来读入整个文件.我们于是配置內存,并检查配置的成功与否,再用ReadFile()把文件从硬盘读到內存中来.API函数不会知道什么文本文件之类的消息所以它们不能读入文本的一行,或在我们的字符串后加上一个NULL终止符.这就是为什么我们要额外地配置一个字节以便我们在读入整个文件后可以自己加上一个NULL,这样我们就可以把我们的整个缓冲区当作一个单字符串当为参数传给SetWindowText().

  一旦所有的操作都成功了我们就把成功变量设为TRUE,并在函数的末尾处作一些清除工作,在函数最终返回到调用者之前释放配置的內存并关闭文件的句柄.

BOOL SaveTextFileFromEdit(HWND hEdit, LPCTSTR pszFileName)
{
    HANDLE hFile;
    BOOL bSuccess = FALSE;

    hFile = CreateFile(pszFileName, GENERIC_WRITE, 0, NULL,
        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    if(hFile != INVALID_HANDLE_VALUE)
    {
        DWORD dwTextLength;

        dwTextLength = GetWindowTextLength(hEdit);
        // No need to bother if there's no text.
        if(dwTextLength > 0)
        {
            LPSTR pszText;
            DWORD dwBufferSize = dwTextLength + 1;

            pszText = GlobalAlloc(GPTR, dwBufferSize);
            if(pszText != NULL)
            {
                if(GetWindowText(hEdit, pszText, dwBufferSize))
                {
                    DWORD dwWritten;

                    if(WriteFile(hFile, pszText, dwTextLength, &dwWritten, NULL))
                        bSuccess = TRUE;
                }
                GlobalFree(pszText);
            }
        }
        CloseHandle(hFile);
    }
    return bSuccess;
}

  与读文件十分类似,写文件的函数只有一点不同.首先在调用CreateFile()的时候我们要求有读权限,而且文件应该总是新创建(如果已经存在则它将会被打开并清除),如果不存在则它会被以常规属性创建.

  接下来我们要获取编辑框所需要的內存缓冲区,因为这是数据源.一旦我们配置了內存,我用GetWindowText()从编辑框获取字符串并用WriteFile()写入文件.与ReadFile()类似,返回的值是实际被写入的字节数目,不过我们一般不用这个返回值.

Copyright © 1998-2008, Brook Miles (forgey). All rights reserved.