代码改变世界

C语言Windows程序设计->第七天->TextOut与系统字体

2012-10-27 14:50  wid  阅读(13326)  评论(1编辑  收藏  举报

·TextOut函数

  TextOut函数的作用是使用系统当前选择的字体、背景颜色以及正文颜色将一个字符串输出到指定位置, 其函数的原型如下:

BOOL TextOut(
    HDC hdc,                 //设备环境句柄
    int nXStart,             //字符串开始输出的x坐标
    int nYStart,             // 字符串开始输出的y坐标   
    LPCTSTR lpString,        //需要输出的字符串
    int cbString             // 字符串的长度
);

当函数调用成功时返回一个非零的值, 调用失败时, 返回值为0。 

尝试下用TextOut函数在屏幕上输出些字符串, 输出一个4列3行的学生信息。 定义一个结构体, 成员为姓名, 年龄, 住址和联系电话, 相关的代码:

 

#include<windows.h>

LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM ) ;            //声明窗口过程函数

//待输出的信息
struct
{
    TCHAR *szName ;
    TCHAR *szAge ;
    TCHAR *szAddr ;
    TCHAR *szTel ;
}STU[] = {
    TEXT("李??"), TEXT("23"), TEXT("上海"), TEXT("123456"),
    TEXT("王??"), TEXT("18"), TEXT("安徽"), TEXT("654321"),
    TEXT("孙??"), TEXT("21"), TEXT("浙江"), TEXT("987654")
};

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow )
{
    static TCHAR szAppName[] = TEXT("PrintText") ;
    HWND hwnd ;
    MSG msg ;
    WNDCLASS wndclass;

    //窗口类成员属性
    wndclass.lpfnWndProc = WndProc ;
    wndclass.lpszClassName = szAppName ;
    wndclass.hInstance = hInstance ;
    wndclass.style = CS_HREDRAW | CS_VREDRAW ;
    wndclass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH) ;
    wndclass.hCursor = LoadCursor( NULL, IDC_ARROW ) ;
    wndclass.hIcon = LoadIcon( NULL, IDI_APPLICATION ) ;
    wndclass.cbClsExtra = 0 ;
    wndclass.cbWndExtra = 0 ;
    wndclass.lpszMenuName = NULL ;

    //注册窗口类
    if( !RegisterClass( &wndclass ) )
    {
        MessageBox( NULL, TEXT("错误, 窗口注册失败!"), TEXT("错误"), MB_OK | MB_ICONERROR ) ;
        return 0 ;
    }
    
    //创建窗口
    hwnd = CreateWindow(szAppName, TEXT("TextOut用法示例"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, hInstance, NULL );

    //显示窗口
    ShowWindow( hwnd, iCmdShow ) ;
    UpdateWindow( hwnd ) ;

    //获取、翻译、分发消息
    while( GetMessage( &msg, NULL, 0, 0 ) )
    {
        TranslateMessage( &msg ) ;
        DispatchMessage( &msg ) ;
    }
    return msg.wParam ;
}

LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
{
    HDC hdc ;
    PAINTSTRUCT ps ;
    int y = 0 ;        //记录x、y方向坐标
    int i ;

    switch(message)
    {
    case WM_PAINT:        //处理WM_PAINT消息
        hdc = BeginPaint( hwnd, &ps ) ;
        //输出信息标题文字
        TextOut( hdc, 0, y, TEXT("姓名:"), lstrlen(TEXT("姓名:")) ) ;
        TextOut( hdc, 0 + 200, y, TEXT("年龄:"), lstrlen(TEXT("年龄:")) ) ;
        TextOut( hdc, 0 + 400, y, TEXT("住址:"), lstrlen(TEXT("住址:")) ) ;
        TextOut( hdc, 0 + 600, y, TEXT("联系电话:"), lstrlen(TEXT("联系电话:")) ) ;
        y += 30 ;            //y方向坐标+25
        //输出信息
        for( i = 0; i < 3; i++ )
        {
            TextOut( hdc, 0, y, STU[i].szName, lstrlen(STU[i].szName) ) ;            //(0, 0)处输出姓名
            TextOut( hdc, 0 + 200, y, STU[i].szAge, lstrlen(STU[i].szAge) ) ;        //(200, y)处输出年龄
            TextOut( hdc, 0 + 400, y, STU[i].szAddr, lstrlen(STU[i].szAddr) ) ;      //(400, y)处输出住址
            TextOut( hdc, 0 + 600, y, STU[i].szTel, lstrlen(STU[i].szTel) ) ;        //(600, y)处输出联系电话
            y += 25 ;        //y方向坐标+25, 相当于换一行
        }
        EndPaint( hwnd, &ps ) ;
        return 0 ;

    case WM_DESTROY:    //处理WM_DESTROY消息
        PostQuitMessage(0) ;
        return 0 ;
    }

    return DefWindowProc( hwnd, message, wParam, lParam ) ;
}

 

运行的效果如下:

 

理清代码思路:

 

  包含头文件 -> 声明窗口过程函数 -> 定义WinMain函数 -> 声明相关变量 -> 为wndclass窗口类成员赋值 -> 注册窗口类 -> 建立窗口获取窗口句柄 -> 显示窗口 -> 获取、翻译、分发消息 -> 定义窗口过程函数。

 

•关于TextOut中的坐标

 

  在没有接触过任何GUI程序设计的情况下, 通常对窗口中的坐标不太了解, 与平时常见的坐标系感觉不太一样, 在一个窗口的客户区中, ( 0, 0 )坐标即为窗口客户区的左上角, 沿着竖直向下方向为y坐标, 水平向右方向为x坐标, 用图示来表示如下:

 

 

  在上面的例子中, 看起来一起都很好, 4列3行显示的整整齐齐, 这是因为我们对坐标的不断调整才使其显示成这样的效果, 在这篇文章的开头部分已经说明了, TextOut函数是使用系统当前选择的字体、背景颜色以及正文颜色将一个字符串输出到指定位置。

 

  要知道, 系统字体是可以根据用户的需求进行调节, 用户可以选择将用户字体调大或者调小, 当字体调大时我们就应该考虑到两行文字之间的间距问题, 如果调的很大, 而我们在程序中使用的y方向坐标每行之间的间隔留的过小, 那么两行文字就会挤在一起造成无法辨认, 当用户把系统字体调小时如果y方向间距留的过大则影响显示效果, 那么, 如何才能让显示效果达到最好呢?

 

 

·获取系统字体信息

 

  要使显示的文字能够根据用户的屏幕大小以及分辨率自动调整显示位置以及字体间的间距, 我们就要知道系统字体的字符的高度以及宽度, 还要知道一句话的总宽度, 防止这句话被输出到窗口外部, 当一行文字因过长输出的窗口外部, 多出的文字就会被截掉, 这可不是我们想要的结果。

 

获取系统当前字体信息的函数为GetTextMetrics, 该函数的原型如下:

BOOL GetTextMetrics(
  HDC hdc,                   // 设备环境句柄
  LPTEXTMETRIC lptm       // 指向一个TEXTMETRIC结构的指针, 该结构用于存放字体信息。 
);

如果函数调用成功, 返回值为非零, 如果函数调用失败, 返回值是0。 

 

关于TEXTMETRIC结构:

TEXTMETRIC结构的成员为当前字体的各种信息, 该结构定义在WINGDI.H头文件中, 定义如下:

typedef struct tagTEXTMETRIC {
  LONG tmHeight;              //字符高度
  LONG tmAscent;              //字符上部高度(基线以上)
  LONG tmDescent;             //字符下部高度(基线以下)
  LONG tmInternalLeading,     //由tmHeight定义的字符高度的顶部空间数目
  LONG tmExternalLeading,     //夹在两行之间的空间数目
  LONG tmAveCharWidth,        //平均字符宽度
  LONG tmMaxCharWidth,        //最宽字符的宽度
  LONG tmWeight;              //字体的粗细轻重程度
  LONG tmOverhang,            //加入某些拼接字体上的附加高度
  LONG tmDigitizedAspectX,    //字体设计所针对的设备水平方向
  LONG tmDigitizedAspectY,    //字体设计所针对的设备垂直方向
  BCHAR tmFirstChar;          //为字体定义的第一个字符
  BCHAR tmLastChar;           //为字体定义的最后一个字符
  BCHAR tmDefaultChar;        //字体中所没有字符的替代字符
  BCHAR tmBreakChar;          //用于拆字的字符
  BYTE tmItalic,              //字体为斜体时非零
  BYTE tmUnderlined,          //字体为下划线时非零
  BYTE tmStruckOut,           //字体被删去时非零
  BYTE tmPitchAndFamily,      //字体间距(低4位)和族(高4位)
  BYTE tmCharSet;             //字体的字符集
} TEXTMETRIC; 

 

可以看出, 在TEXTMETRIC结构中共有20位成员, 不过暂时我们只关心前7个成员, 这7个成员为:

  LONG tmHeight;               //字符高度
  LONG tmAscent;               //字符上部高度(基线以上)
  LONG tmDescent;             //字符下部高度(基线以下)
  LONG tmInternalLeading,     //由tmHeight定义的字符高度的顶部空间数目
  LONG tmExternalLeading,     //夹在两行之间的空间数目
  LONG tmAveCharWidth,        //平均字符宽度
  LONG tmMaxCharWidth,        //最宽字符的宽度

在《Windows程序设计》第五版一书中, 用图示来表示出了字符纵向尺寸的4个值:

 

 

  其中tmAscent是指字符上部高度(基线Baseline以上), tmDescent为字符下部高度(基线以下), tmHeight的值为tmAscenttmDescent的和, 表示整个字符的高度, tmInternalLeading为内部间距, 通常用来显示字母上部的重音符号。

 

 

  除此之外, TEXTMETRICT结构中的tmExternalLeading用来表示字体设计者建议在两行文本之间留出的空间大小; 

 

  tmAveCharWidth表示小写字符的加权平均宽度, 大写字符的加权平均宽度通常按小写字符的加权平均宽度的1.5倍来计算。

 

  tmMaxCharWidth为字体中最宽字符的宽度。

 

 

·使用系统字体信息

 

  在使用系统字体信息之前我们首先要获取系统字体的信息, 那么, 在何时获取呢? 通常的做法就是在收到WM_CREATE消息时, WM_CREATE是窗口过程函数接收到的第一条消息, 在这时获取系统字体消息再好不过了。

 

首先定义一个TEXTMETRIC结构的对象:

 

  TEXTMETRIC tm ;

 

然后再获取设备环境句柄, 并调用GetTextMetrics函数:

  hdc = GetDC(hwnd) ;
  GetTextMetrics( hdc, &tm ) ;
  ReleaseDC( hwnd, hdc ) ;

这样, tm中就有了我们需要的信息了。

 

下面我们尝试将文章开始部分的代码简单的更改为根据系统字体信息的方式进行输出, 仅改动窗口过程函数部分:

 

  LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )
  {
      HDC hdc ;
      PAINTSTRUCT ps ;
      TEXTMETRIC tm ;              //记录系统字体信息
      static int cxChar, cyChar ;    //系统字体的平均宽度、高度
      int y = 0 ;                  //记录x、y方向坐标
      int i ;
  
      switch(message)
      {
      case WM_CREATE:            //处理WM_CREATE消息
          hdc = GetDC( hwnd ) ;                //获取设备环境句柄
          GetTextMetrics( hdc, &tm ) ;         //获取系统字体信息
          ReleaseDC( hwnd, hdc ) ;             //设备环境句柄使用完毕, 释放
  
          cxChar = tm.tmAveCharWidth ;                     //得到字体平均宽度
          cyChar = tm.tmHeight + tm.tmExternalLeading ;    //字体高度, 总高度tmHeight + 两行文字之间的建议间距大小tmExternalLeading
          return 0 ;
          
      case WM_PAINT:        //处理WM_PAINT消息
          hdc = BeginPaint( hwnd, &ps ) ;
          //输出信息标题文字
          TextOut( hdc, 0, y, TEXT("姓名:"), lstrlen(TEXT("姓名:")) ) ;
          TextOut( hdc, 0 + 200, y, TEXT("年龄:"), lstrlen(TEXT("年龄:")) ) ;
          TextOut( hdc, 0 + 400, y, TEXT("住址:"), lstrlen(TEXT("住址:")) ) ;
          TextOut( hdc, 0 + 600, y, TEXT("联系电话:"), lstrlen(TEXT("联系电话:")) ) ;
          y += cyChar ;            //y方向增加一个字体高度的单位
          //输出信息
          for( i = 0; i < 3; i++ )
          {
              TextOut( hdc, 0, y, STU[i].szName, lstrlen(STU[i].szName) ) ;            //(0, 0)处输出姓名
              TextOut( hdc, 0 + 200, y, STU[i].szAge, lstrlen(STU[i].szAge) ) ;        //(200, y)处输出年龄
              TextOut( hdc, 0 + 400, y, STU[i].szAddr, lstrlen(STU[i].szAddr) ) ;      //(400, y)处输出住址
              TextOut( hdc, 0 + 600, y, STU[i].szTel, lstrlen(STU[i].szTel) ) ;        //(600, y)处输出联系电话
              y += cyChar ;        //y方向坐标cyChar, 相当于换一行
          }
          EndPaint( hwnd, &ps ) ;
          return 0 ;
  
      case WM_DESTROY:    //处理WM_DESTROY消息
          PostQuitMessage(0) ;
          return 0 ;
      }
  
      return DefWindowProc( hwnd, message, wParam, lParam ) ;
  }

 

修改后的代码运行效果如下:

 

一些问题: 

 

  1>. 上面的代码中, 我们虽说获取了字符的平均宽度, 但是我们并没有去使用它;

 

  2>. 修改后的代码仅仅是解决了多行文本的显示问题, 并没有解决当文本过长时的输出越界问题;

 

  3>. 当行数过多时, 多出的行数也会被截断不予显示。

 

 

解决思路:

 

  解决水平方向越界问题: 利用平均字体的宽度计算何时将会越界并进行相应的换行处理;

 

  解决水平方向越界问题: 使用滚动条, 滚动条的使用将在下次的学习中介绍。