叶落归根(纯技术)

 
2006/6/27

效率最好的排序方法

Sort(int a[],int left,int right)
{
   int value,l,r,temp;
   l=left;
   r=right;
   value = a[(left+right)/2];
   while(l<r)
   {
      while(a[l]<value)
       l++;
      while(a[r]>value)
       r--;
      if(l>=r)
         break;
      temp = a[l];
      a[l] = a[r];
      a[r] = temp;
      l++;
      r--;

   }
   if(l==r)
      l++;
   if(left<r)
      Sort(a,left,l-1);
   if(l<right)
      Sort(a,r+1,right);
}

2006/5/10

vc鼠标消息与键盘消息

鼠标消息与键盘消息

  在Microsoft Windows 中,键盘和鼠标是两个标准的用户输入源,在一些交叠的操作中通常相互补充使用。当然,鼠标在今天的应用程序中比10年前使用得更为广泛。甚至在一些应用程序中,我们更习惯于使用鼠标,例如在游戏、画图程序、音乐程序,以及Web创览器等程序中就是这样。然而,我们可以不使用鼠标,但绝对不能从一般的PC中拆掉键盘。
  相对于个人计算机的其他组件,键盘有非常久远的历史,它起源于1874年的第一台Remington打字机。早期的计算机程序员用键盘在Hollerith卡片上打孔,以后在哑终端上用键盘直接与大型主机通讯。PC上的键盘在某些方面进行了扩展,包括了功能键、光标定位键和(通常都带有的)单独的数字键盘,但它们的输入原理基本相同。

键盘基础

  Windows程序获得键盘输入的方式:键盘输入以消息的形式传递给程序的窗口过程。实际上,第一次学习消息时,键盘就是一个明显的例子:消息应该传递给应用程序的信息类型。
  Windows用8种不同的消息来传递不同的键盘事件。这好像太多了,但是(就像我们所看到的一样)程序可以忽略其中至少一半的消息而不会有任何问题。并且,在大多数情况下,这些消息中包含的键盘信息会多于程序所需要的。处理键盘的部分工作就是识别出哪些消息是重要的,哪些是不重要的。
一、键盘基础知识
  虽然应用程序在很多情况下可以通过鼠标实现信息的输入,但到现在为止键盘仍然是PC机中不可替代的重要输入设备。
  用键盘当作输入设备,每当用户按下或释放某一个键时,会产生一个中断,该中断激活键盘驱动程序KEYBOARD.DRV来对键盘中断进行处理。KEYBOARD.DRV程序会根据用户的不同操作进行编码,然后调用Windows用户模块USER.EXE生成键盘消息,并将该消息发送到消息队列中等候处理。
1.扫描码和虚拟码
  扫描码对应着键盘上的不同键,每一个键被按下或释放时,都会产生一个唯一的扫描码作为本身的标识。扫描码依赖于具体的硬件设备,即当相同的键被 按下或释放时,在不同的机器上可能产生不同的扫描码。在程序中通常使用由Windows系统定义的与具体设备无关的虚拟码。在击键产生扫描码的同时,键盘驱动程序KEYBOARD.DRV截取键的扫描码,然后将其翻译成对应的虚拟码,再将扫描码和虚拟码一齐编码形成键盘消息。所以,最后发送到消息队列的键盘消息中,既包含了扫描码又包含了虚拟码。
  经常使用的虚拟码在WINDOWS.H文件中定义,常用虚拟码的数值、常量符号和含义如表所示。

取值(16进制) 常量符号 含义
01 VK_LBUTTON 鼠标左键
02 VK_RBUTTON 鼠标右键
03 VK_CANCEL Break中断键
04 VK_MBUTTON 鼠标中键
05-07 -- 未定义
08 VK_BACK (BackSpace)键
09 VK_TAB Tab键
0A-0B -- 未定义
0C VK_CLEAR Clear键
0D VK_RETURN Enter键
0E-0F -- 未定义
10 VK_SHIFT Shift键
11 VK_CONTROL Ctrl键
12 VK_MENU Alt键
13 VK_PAUSE Pause键
14 VK_CAPTIAL CapsLock键
15-19 -- 汉字系统保留
1A -- 未定义
1B VK_ESCAPE Esc键
1C-1F -- 汉字系统保留
20 VK_SPACE 空格键
21 VK_PRIOR PageUp键
22 VK_NEXT PageDown键
23 VK_END End键
24 VK_HOME Home键
25 VK_LEFT ←(Left Arrow)键
26 VK_UP ↑(Up Arrow)键
27 VK_RIGHT →(Right Arrow)键
28 VK_DOWN ↓(Down Arrow)键
29 VK_SELECT Select键
2A -- OEM保留
2B VK_EXECUTE Execute键
2C VK_SNAPSHOT Print Screen键
2D VK_INSERT Insert键
2E VK_DELETE Delete键
2F VK_HELP Help键
30-39 VK_0-VK_9 数字键0-9
3A-40 -- 未定义
41-5A VK_A-VK_Z 字母键A-Z
5B-5F -- 未定义
60-69 VK_NUMPAD0-VK_NUMPAD9 小键盘数字键0-9
6A VK_MULTIPLY *(乘号)键
6B VK_ADD +(加号)键
6C VK_SEPAPATOR 分隔符键
6E VK_SUBTRACT -(减号)键
6F VK_DECIMAL .(小数点)键
70-87 VK_DIVIDE /(除号)键
88-8F VK_F1-VK_F24 F1-F24功能键
90 VK_NUMBERLOCK Number lock键
91 VK_SCROLL Scroll lock键
92-B9 -- 未定义
BA-C0 -- OEM保留
C1-DA -- 未定义
DB_E4 -- OEM保留
E5 -- 未定义
E6 -- OEM保留
E7-E8 -- 未定义
E9-F5 -- OEM保留
F6-FE -- 未定义


2.输入焦点
  同一时刻,Windows中可能有多个不同的程序在运行,也就是说有多个窗口同时存在。这时,键盘由多个窗口共享,但只有一个窗口能够接收到键盘消息,这个能够接收键盘消息的窗口被称为拥有输入焦点的窗口。
  拥有输入焦点的窗口应该是当前的活动窗口,或者是活动窗口的子窗口,其标题和边框会以高亮度显示,以区别于其他窗口。拥有输入焦点的也可以是图标而不是窗口,此时,Windows也将消息发送给图标,只是消息的格式略有不同。
  窗口过程可以通过发送WM_SETFOCUS和 WM_KILLFOCUS消息使窗体获得或失去输入焦点。程序也可以通过捕获WM_SETFOCUS和WM_KILLFOCUS消息来判断窗体何时获得或失去输入焦点。其中WM_SETFOCUS消息表示窗口正获得输入焦点,WM_ KILLFOCUS消息表示窗口正失去输入焦点。
3.键盘消息
  键盘消息分为系统键消息和非系统键消息。系统键消息是指由Aft键和其他键组合而产生的按键消息。当系统键被按下时产生WM_ SYSKEYDOWN消息,当系统键被释放时产生WM_SYSKEYUP消息。 Aft键与其他键形成的组合键通常用于对程序菜单和系统菜单进行选择,或用于在不同的程序之间进行切换。因此,系统键消息应该交由Windows进行处理,用户所编制的程序一般不处理系统键消息,而是将这些消息交由DefWindowProc函数进行处理。如果用户想对系统键消息进行处理,应该在处理完这些消息后,再将其发送给DefWindowProc函数,使得Windows系统能够正常工作。
  某些击键消息可以被转换成字符消息,例如字母键、数字键等。而有些键只能产生按键消息而没有字符消息,例如 Shift键、Insert键等。消息循环中的 TranslateMessage函数可以实现从击键消息向字符消息的转化。当GetMessage函数捕获一个WM_SYSKEYDOWN消息或WM_KEYDOWN消息后,TranslateMessage函数判断产生该消息的键是否能够被转换成字符消息,如果能,就将该消息转换成字符消息,再通过DispatchMessape函数将转换后的字符消息发送到消息队列中去。字符消息共有以下四种,如表所示。

字符 系统字符 非系统字符
普通字符 WM_SYSCHAR WM_CHAR
死字符 WM_SYSDEADCHAR WM_DEADCHAR

  其中死字符是由某些特殊键盘上的按键所造成的,Windows一般忽略死字符所产生的消息。
  Windows的消息一般是通过一个MSG结构体变量传送给消息处理函数的。对于键盘消息, MSG结构体变量的各个域中较重要的是lParam域和 wParam域。wParam域用于保存按键的虚拟键代码或字符的ASCII码。对于非字符消息,wParam域保存按键的虚拟健代码;对于字符消息,wParam域不保存字符的ASCII码。lParam域则用于保存击键时产生的附加信息,实际上一个32位的lParam变量被分为六部分,记录了以下相关信息:重复次数、OEM扫描码、扩展键标志、关联键标志、前一击键状态和转换状态。lParam域各位的含义如表所示。

位数 含义
0-15 击键重复次数累加
16-23 OEM扫描码
24 是否为扩展键
25-28 未定义
29 是否便用关联键,及Alt键是否同时按下。
30 前一次击键状态,0表示该键前一次状态为抬起,1表示前一次状态为按下
31 转换状态

  按键的次序不同,产生的消息也不相同。例如,按下并释放1键,读过程依次产生如表所示三条消息。按下1键所产生的消息和wParam的取值

消息 wParam变量取值
WM_KEYDOWN 虚拟码1
WM_CHAR ASCII码“1”
WM_KEYUP 虚拟码1

  如果按下Shift键后再按下1键并释放,则依次产生如表所示的消息。按下 Shift键后按 1健所产生的消息和 wParam的取值

消息 wParam变量取值
WM_KEYDOWN 虚拟码 VK_SHIFT
WM_KEYDOWN 虚拟码 VK_1
WM_CHAR ASCII码 “1”
WM_KEYUP 虚拟码 VK_1
WM_KEYUP 虚拟码 VK_SHIFT

二、键盘应用实例
  下面通过一个应用程序实例来说明在实际编程中如何处理键盘消息。
#include <windows.h>
#include <stdio.h>
// 全局变量
RECT rc; //记录滚屏的矩形区域
?
int xChar, yChar; //文本输入点坐标

WNDCLASSEX wnd; //窗口类结构变量

char szAppName[] = "键盘消息监视程序"; //窗口类名
//函数声明
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
BOOL MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE hInstance,int iCmdShow);
//函数:WinMain
//作用:入口函数
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR szCmdLine,int iCmdShow)
{
  MSG msg;
  if(!MyRegisterClass(hInstance))
  {
    return FALSE;
  }
  
   if(!InitInstance(hInstance,iCmdShow))
  {
    return FALSE;
  }
  
  while (GetMessage (&msg, NULL, 0, 0))
  {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
  }
  return msg.wParam;
}
//函数:ShowKey
//作用:实现在窗口中显示按键信息
void ShowKey (HWND hwnd, int iType,char *szMessage,WPARAM wParam,LPARAM lParam)
{
  static char *szFormat[2] ={"%-14s %3d %c %6u %4d %5s %5s %6s %6s",
                "%-14s %3d %c %6u %4d %5s %5s %6s %6s" };
  char szBuffer[80];
  HDC hdc;
  ScrollWindowEx(hwnd, 0, -yChar, &rc,&rc,NULL,NULL,SW_INVALIDATE);
  hdc = GetDC (hwnd);
  SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT));
  TextOut (hdc,
       xChar,
       rc.bottom - yChar,
       szBuffer,
       wsprintf szBuffer,
       szFormat[iType],
       szMessage, //消息
       wParam, //虚拟键代码
       (BYTE) (iType ? wParam :‘ ’),//显示字符值
       LOWORD (lParam), // 重复次数
       HIWORD (lParam) & 0xFF, // OEM键盘扫描码
       //判断是否为增强键盘的扩展键
       (PSTR) (0x01000000 & lParam ? “是” : “否”),
       //判断是否同时使用了ALT键
       (PSTR) (0x20000000 & lParam ? “是” : “否”),
       (PSTR) (0x40000000 & lParam ? “按下” : “抬”),
       //判断前一次击键状
       (PSTR)(0x80000000 & lParam ? “按下” : “抬起”))
       //判断转换状态?
       );
  ReleaseDC (hwnd, hdc); ?
  ValidateRect (hwnd, NULL); ?
}
//函数:WndProc
//作用:处理主窗口的消息
LRESULT CALLBACK WndProc (HWND hwnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
{
  static char szTop[] ="消息键 字符 重复数 扫描码 扩展码 ALT 前一状态 转换状态";
  static char szUnd[] ="_______ __ ____ _____ ______ ______ ___ _______ ______";

  //在窗口中输出文字作为信息标题
  HDC hdc;
  PAINTSTRUCT ps;
  TEXTMETRIC tm;

  switch (iMsg)
  {
    case WM_CREATE://处理窗口创建的消息
    hdc = GetDC (hwnd); //设定字体
    SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)); //检取当前字体的度量数据
    GetTextMetrics (hdc, &tm);
    xChar = tm.tmAveCharWidth;//保存字体平均宽度
    yChar = tm.tmHeight; //保存字体高度
    ReleaseDC (hwnd, hdc);
    rc.top = 3 * yChar / 2;
    return 0;

    case WM_SIZE://处理窗口大小改变的消息
    //窗体改变后保存新的滚屏区域右下角坐标
    rc.right = LOWORD (lParam);
    rc.bottom = HIWORD (lParam);
    UpdateWindow (hwnd);
    return 0;

    case WM_PAINT: //处理窗口重绘消息
    InvalidateRect (hwnd, NULL, TRUE);
    hdc = BeginPaint (hwnd, &ps);
    SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
    SetBkMode (hdc, TRANSPARENT) ;
    TextOut (hdc, xChar, yChar / 2, szTop, (sizeof szTop) - 1) ;
    TextOut (hdc, xChar, yChar / 2, szUnd, (sizeof szUnd) - 1) ;
    EndPaint (hwnd, &ps);
    return 0;

    case WM_KEYDOWN:
    //处理键盘上某一键按下的消息
    ShowKey (hwnd, 0, "WM_KEYDOWN",wParam, lParam);
    return 0;

    case WM_KEYUP:
    //处理键盘上某一按下键被释放的消息
    ShowKey (hwnd, 0, "WM_KEYUP", wParam, lParam);
    return 0;

    case WM_CHAR:
    //处理击键过程中产生的非系统键的可见字符消息
    howKey (hwnd, 1, "WM_CHAR", wParam, lParam);
    return 0;

    case WM_DEADCHAR:
    //处理击键过程中产生的非系统键"死字符"消息
    ShowKey (hwnd, 1, "WM_DEADCHAR", wParam, lParam);
    return 0;

    case WM_SYSKEYDOWN:
    //处理系统键按下的消息
    ShowKey (hwnd, 0, "WM_SYSKEYDOWN",wParam, lParam);
    break;

    case WM_SYSKEYUP:
    //处理系统键抬起的消息
    ShowKey (hwnd, 0, "WM_SYSKEYUP", wParam, lParam);
    break;

    case WM_SYSCHAR://处理系统键可见字符消息
    ShowKey (hwnd, 1, "WM_SYSCHAR", wParam, lParam);
    break;

    case WM_SYSDEADCHAR://处理系统键"死字符"消息
    ShowKey (hwnd, 1, "WM_SYSDEADCHAR", wParam, lParam);
    break;

    case WM_DESTROY:
    //处理结束应用程序的消息
    PostQuitMessage (0);
    return 0;
  }
  return DefWindowProc (hwnd, iMsg, wParam, lParam);
}
//函数:MyRegisterClass
//作用:注册窗口类
BOOL MyRegisterClass(HINSTANCE hInstance)
{
  wnd.cbSize= sizeof (wnd);
  wnd.style = CS_HREDRAW | CS_VREDRAW;
  wnd.lpfnWndProc = WndProc;
  wnd.cbClsExtra = 0;
  wnd.cbWndExtra = 0;
  wnd.hInstance = hInstance;
  wnd.hIcon = LoadIcon (NULL, IDI_APPLICATION);?
  wnd.hCursor = LoadCursor (NULL, IDC_ARROW);
  wnd.hbrBackground = (HBRUSH)
  GetStockObject (WHITE_BRUSH);
  wnd.lpszMenuName = NULL;
  wnd.lpszClassName = szAppName;
  wnd.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
  return RegisterClassEx (&wnd);
}
//函数:InitInstance
//作用:创建主窗口
BOOL InitInstance(HINSTANCE hInstance,int iCmdShow)
{
  HWND hwnd;
  hwnd = CreateWindow (szAppName,
             "键盘消息监视程序",
             WS_OVERLAPPEDWINDOW,
             CW_USEDEFAULT,CW_USEDEFAULT,
             CW_USEDEFAULT,CW_USEDEFAULT,
             NULL,NULL,hInstance,NULL
             );
  if(!hwnd)
  {
    return FALSE;
  }

  ShowWindow (hwnd, iCmdShow);
  UpdateWindow (hwnd);
  return TRUE;
}

  本实例的作用是通过程序捕获键盘消息,然后将wParam参数所包含的数据进行分解,最后将各项信息通过窗口显示出来。实例的源文件包含了Initlnstance、MyRegisterClass、ShowKey、WinMain和WndProc五个函数。程序的基本思路是以WinMain函数作为程序入口,再调用 MyRegisterClass函数和 InitInstance函数注册窗口类并创建和保存窗日,然后创建和显示窗口,最后进入消息循环。
  下面重点分析函数WndProc和 ShowKey。
1.WndProc函数
在本实例中WndProc函数处理的消息主要有WM_CREATE、WM_SIZE、WM_PAINT和键盘消息。
  case WM_CREATE://处理窗口创建的消息
  hdc = GetDC (hwnd);//设定字体
  SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT));//检取当前字体的度量数据
  GetTextMetrics (hdc, &tm);
  xChar = tm.tmAveCharWidth;//保存字体平均宽度
  yChar = tm.tmHeight;//保存字体高度
  ReleaseDC (hwnd, hdc);
  rc.top = 3 * yChar / 2;
  return 0;
  这一程序段的主要作用是将字体对象选入当前窗体的设备描述表中,同时取得字体高度和平均宽度,再初始化编辑区的滚屏区域的右上角Y坐标。进入该程序段后,首先通过GetDC函数获得当前窗体的设备描述表,再通过GetStockObject函数获得系统字体,然后用 SelectObject函数将字体对家选入窗体的设备描述表中。其中,hdc为设备描述表句柄。在完成所有操作后,程序还必须通过ReleaseDC函数释放设备描述表。在该程序段中使用了GetTextMetrics函数来获得字体的几何尺寸。GetTextMetrics函效的原型定义如下:
BOOL GetTextMetrics(HDC hdc,// 指向设备描述表的句柄
          LPTEXTMETRIC lptm // TEXTMETRIC结构体变量的指针
          // 所获得的所有信息保存在TEXTMETRIC结构体变量中
          );
  其中lptm是一个指向 TEXTMETRIC结构体的指针。TEXTMETRIC结构体包含了与字体的几何尺寸相关的基本信息。该结构体的具体定义如下:
typedef struct tagTEXTMETRIC
{ // tm
  LONG tmHeight;// 字体高度
  LONG tmAscent;//字体高于基准线的高度
  LONG tmDescent;// 字体低于基准线的高度
  LONG tmInternalLeading;// 给大写字母留出的空间
  LONG tmExtenalLeading; // 由字体设计者推荐的附加行距
  LONG tmAveCharWidth;// 字体平均宽度
  LONG tmMaxCharWidth;// 字体最大宽度
  LONG tmWeight; // 字体黑度
  LONG tmOverhang; // 在合成斜体或黑体时加在字符上的附加宽度值
  LONG tmDigitizedAspectX;// 字体所适合的高宽比的宽
  LONG tmDigitizedAspectY; // 字体所适合的高宽比的高
  BCHAR tmFirstChar; // 字体中定义的第一个字符
  BCHAR tmLastChar; //字体中定义的最后一个字符
  BCHAR trnDefaultChar; //字体中的默认字符
  BCHAR trnBreakChar; // windows在调整文本时用于分裂词的字符
  BYTE tmItalic; // 取非零值时表示斜体字体
  BYTE tmUnderLined; // 取非零值时表示下划线字体
  BYTE tmStruckOut;// 取非零值时为删除线字体
  BYTE tmPitchAndFamily; // 低二位为字符间距,高四位为系列值
  BYTE tmCharSet; // 指定字符集
} TEXTMETRIC;
  该结构中所有的字体大小都是按逻辑单位给出的,这就是说字体的大小取决于当前显示设备的映射模式。
  在例中,所获得的字体几何尺寸保存在TEXTMETRIC结构体变量tm中。滚屏区域的范围是通过RECT结构体变量re保存的,RECT结构体变量可以通过记录矩形区域的右上角和左下角的坐标来确定一个矩形区域。
RECT结构的原型定义如下:
typedef struc RECT{
  LONG left; // 矩形左上角 X坐标
  LONG top; // 左上角 Y坐标
  LONG right; // 右下角 X坐标
  LONG bottom; // 右下角Y坐标
} RECT;
  该结构定义了一个矩形区域的左上角和右下角的坐标。由结构的原型定义我们可以知道该结构包括四个域,其中left域表示矩形的左上角X坐标,top域表示左上角Y坐标,right域表示右下角X坐标,bottom域表示右下角Y坐标。通常用于一个矩形区域范围的记录和传递。
  例如,通过RECT结构的变量将一个矩形区域范围的四个角的值传递FillRect函数,则调用该函数后,矩形区域除了最下方的一行和最右方一列外都被填充。在本实例中,初始化编辑区的滚屏区域的左上角Y坐标时,使用了如下程序:
  rc.top= 3 * yChar/2;
  这是因为在窗口中首先要输出两行的题头信息,一行为中文,一行为下划线。中文字符的高度为1个字体高度单位,而下划线的高度为半个字体高度单位。这两行信息是一直保持,不参与滚屏的。因此,滚屏区域的左上角Y坐标从3/2个字体高度处开始。
在WndProc函数中,处理WM_ SIZE
消息的程序段如下:
  case WM_SIZE: //处理窗口大小改变的消息
  //窗体改变后保存新的滚屏区域右下角坐标
  rc.right = LOWORD (lParam);
  rc.bottom = HIWORD (lParam);
  UpdateWindow (hwnd);
  return 0;
  该程序段比较简单,只是当窗口的尺寸改变时重新设定滚屏区域的右下角坐标,并更新窗口。值得注意的是, WM_SIZE消息的wParam变量保存了窗体新尺寸的左上角坐标,变量的32位分为两个部分,低16位保存X坐标,高16位保存Y坐标。 lParam变量保存了窗体新尺寸的右下角坐标,保存方式与wParam变量相同。在编程过程中,通常通过LOWORD宏定义来获得32位变量的低16位数值,通过HIWORD宏定义来获得32位变量的高历位数值。
  该程序段比较简单,只是当窗口的尺寸改变时重新设定滚屏区域的右下角坐标,并更新窗口。值得注意的是,WM_SIZE消息的wParam变量保存了窗体新尺寸的左上角坐标,变量的32位分为两个部分,低16位保存X坐标,高16位保存Y坐标。 lParam变量保存了窗体新尺寸的右下角坐标,保存方式与wParam变量相同。在编程过程中,通常通过LOWORD宏定义来获得32位变量的低16位数值,通过HIWORD宏定义来获得32位变量的高历位数值。
WndProc函数中,处理WM_PAINT消息的程序段如下:
  case WM_PAINT: //处理窗口重绘消息 ?
  InvalidateRect (hwnd, NULL, TRUE); ?
  hdc = BeginPaint (hwnd, &ps); ?
  SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; ?
  SetBkMode (hdc, TRANSPARENT) ; ?
  TextOut (hdc, xChar, yChar / 2, szTop, (sizeof szTop) - 1) ; ?
  TextOut (hdc, xChar, yChar / 2, szUnd, (sizeof szUnd) - 1) ; ?
  EndPaint (hwnd, &ps); ?
  return 0;
  该程序段首先调用InvalidateRect函数使窗口无效,InvalidateRect函数的功能是使窗口的某一部分无效,也就是通知Windows该部分需要被刷新和重画。
  在InvalidateRect函数之后,程序调用函数BeginPaint准备重画窗口。
BeginPaint 函数的原型定义如下:
HDC BeginPaint (HWND hwnd , // 重画窗口的句柄
        LPPAINTSTRUCT lpPaint // 指向一个用于保存所有重
        // 画信息的 PAINTSTRUCT 结构体变量的指针);
  BeginPaint 函数的作用是完成重画窗体之前的准备,并将重画窗体 的数据保存在一个 PAINTSTRUCT 结构体变量中。 PAINTSTRUCT 结构体可以用于保存窗口重画时的数据以方便以后使用。
PAINTSTRUCT结构体的定义如下:
typedef struct tagPAINTSTRUCT{ // ps
  HDC hdc; // 重画区域所在窗口的句柄
  BOOL fErase;// 是否擦去背景
  RECT rcPaint; // 指定重画窗体的范围
  BOOL fRestore; // 系统保留域
  BOOL fIncUpdate;// 系统保留域
  BYTE rgbReserved[32];// 系统保留
}PA INTSTRU CT;
  BeginPaint函数如果操作成功会返回一个被操作窗口的设备描述表的句柄。如果操作不成功则函数返回NULL值,表明显示设备不可用。该函数在运行过程中,会进行自动调整,使得所有区域都包含在刷新区域的范围内。而原有需要刷新的区域是由InvalidateRect函数或 InvalidateRgn函数指定的。一般来说,只有当程序处理 WM_PAINT消息时才调用BeginPaint函数,而且,每次调用BeginPaint函数都需要对应调用一个EndPaint函数来结束重画过程。在BeginPaint函数调用后,会将插入符光标自动隐藏。EndPaint函数原型定义如下:
BOOL EndPaint ( HWND hWnd, // 窗口句柄
        CONST PAINTSTRUCT* lpPaint // 指向 PAINTSTRUCT结构体变量的指针
       );
  EndPaint函数标志着窗口重画过程的结束。该函数执行后总返回一个非零值。如果在BeginPaint函数执行时将插入符号隐藏了,那么EndPaint函数会重新显示插入符号。
消息处理函数 WndProc处理的键盘消息有:
WM_ KEYDOWN、WM_KEYUP
WM_CHAR、WM_DEADCHAR、
WM_SYSKEYDOWN、WM_SYSKEYUP、
WM_SYSCHAR 和 WM_SYSDEADCHAR。
  根据不同的消息,程序会用不同的参数调用 ShowKey函数在窗口中显示各键盘消息的相关信息。
2.ShowKey函数
ShowKey函数是用户自定义函数,其作用是从键盘消息的各域中提取信息并显示在窗口中。
ShowKey函数的具体定义如下:
// 作用:实现在窗口中显示按键信息
void ShowKey (HWND hwnd, int iType, char *szMessage,WPARAM wParam, LPARAM lParam)
{
  static char *szFormat[2] = {"%-14s %3d %c %6u %4d %5s %5s %6s %6s",
                "%-14s %3d %c %6u %4d %5s %5s %6s %6s" } ;
  char szBuffer[80];
  HDC hdc;
  SelectObject( hdc,
         GetStockObject(SYSTEM_FIXED_FONT));
         TextOut (hdc, xChar, rc.bottom - yChar,
         szBuffer,wsprintf (szBuffer, szFormat [iType],
         szMessage, //消息
         wParam, //虚拟键代码
         (BYTE) (iType ? wParam : ' '),//显示字符值
         LOWORD (lParam), //重复次数
         HIWORD (lParam) & 0xFF, //OEM键盘扫描码
         //判断是否为增强键盘的扩展键
         (PSTR) (0x01000000 & lParam ? "是" : "否"),
         //判断是否同时使用了ALT键
         (PSTR) (0x20000000 & lParam ? "是" : "否"),
         (PSTR) (0x40000000 & lParam ? "按下" : "抬起"),
         //判断前一次击键状态
         (PSTR) (0x80000000 & lParam ? "按下" : "抬起"))
         //判断转换状态
         );
}

  ShowKey函数首先定义了szFormat字符串,并在其中针对字符消息和非字符消息定义了两种不同的输出格式。 然后调用ScrollWindowEx函数使显示区域滚屏,为信息输出作准备。ScrollWindowEx函数的主要功能是使窗口编辑区中的某一矩形区域产生滚屏效果。
ScrollWindowEx函数的原型定义如下:
int ScrollWindowEx (HWND hwnd, // 发生滚屏的窗口的句柄
          int dx, // 水平滚屏的数值
          int dy, // 垂直滚屏的数值
          CONST RECT*prcScroll,//记录发生滚屏的矩形区域的RECT结构体的地址
          CONST RECT* prcClip, //记录发生剪切的矩形区域的 RECT结构体的地址
          HRGN hrgnUpdate,// 需要更新区域的句柄
          LPRECT prcUpdate, // 记录需要更新矩形区域的 RECT结构体的地址
          UINT flags // 滚屏控制标志
          );
  其中,dx参数给出了以设备单位尺寸(对于显示器为像素)为单位的每一次水平滚屏的度量值。dx参数取正值表示向右滚屏,取负值表示向左滚屏。如参数给出了以设备单位尺寸(对于显示器为像素)为单位的每一次垂直滚屏的度量值。如参数取正值表示向下滚屏,取负值表示向上滚屏。dx和dy两个参数不能同时取非零值,也就是说,ScrollWindowEx函数不能使编辑区同时向水平和垂直方向滚屏。
  prcScroll参数为一个指向记录滚屏的矩形区域的RECT结构体变量的指针,如果取值为NULL,则整个编辑区发生滚屏。
  hrgnUpdate参数为因滚屏而变得无效的矩形区域的句柄,多数情况下可以取NULL。 prcUpdate参数指向一个记录因为滚屏而变得无效的矩形区域的 RECT结构体变量。多数情况下取NULL。
flags变量可以通过不同的取值来控制滚屏的状况,其取值和意义如下所示。
  SW_ ERASE当和 SW_INVALIDATE值同时使用时,会通过向 window发送一个WM_ ERASEBKGND消息将最近变得无效的区域抹去;
  SW_INVALIDATE在发生滚屏后使由hrgnUpdate参数指定的区域无效;
  SW_SCROLLCHILDREN使所有的子窗口都发生滚屏;
  SW_ SMOOTHSCROLL在 Windows 95及以后的版本中使窗口发生平滑滚屏。如果ScrollWindowEx函数执行成功,则返回值为以下三者之一:
  SIMPLEREGION表示有一个矩形的无效区域;
  COMPLEXREGION表示没有无效区域和重叠区域;
  NULLREGION表示没有无效区域。
  如果ScrollWindowEx函数执行不成功,则返回ERROR。
ScrollWindowEx函数的功能也可以通过ScrollWindow函数来实现,ScrollWindow 函数的原型定义如下:
BOOL Scrollwindow(HWND hwnd //窗口句柄
         int XAmount, // 水平滚屏的数值
         int YAmount, // 垂直滚屏的数值
         CONST RECT* lpReCt, //记录发生滚屏的矩形区域的 RECT结构体的地址
         CONST RECT* lpClipRect, //记录发生剪切的矩形区域的 RECT结构体的地址
         );
  可以看出,ScrollWindow函数与ScrollWindowEx函数十分相似,其参数的意义也基本相同。事实上,ScrollWindow函数是为了保持对较低版本的Windows兼容而设计的,用户在编程时,除非需要考虑程序的向下兼容,否则一般都应使用ScrollWindowEx函数。
  在滚屏后,函数开始调用TextOut函数进行信息输出。TextOut函数的原型定义如下:
BOOL TextOut( HDC hdc,// 设备描述表句柄
       int nXStart, // 文本输出起始点 X坐标
       int nYStart, // 文本输出起始点 Y坐标
       LPCTSTR lpString, // 指向输出字符串的指针
       int cbString // 字符串中字符的数目
       );
  TextOut函数能够用当前设定的字体在窗口的指定部位输出一段文本信息。如果操作成功则返回一非零值,否则返回零值。捕获键盘消息的信息主要根据表中的描述,通过使用按位操作确定某些特定位的值,然后再判断具体的状态。
  在TextOut函数调用过程中,还调用了wsprintf函数,并使其返回值作为TextOut函数的一个参数值。wsprintf函数的原型定义如下:
int wsprintf (LPTSTR lpOut,// 指向需要输出的字符串的指针
       LPCTSTR lpFmt, //指向格式控制字符串的指针
       …… // 其他可选参数
       );
  wsprintf函数能够将一组字符序列按lpFmt参数指定的格式转换,然后保存在lpOut参数指定的字符缓冲区中等待输出。其中,字符序列由可选参数决定,而可选参数的数目和具体内容应该与lpFmt所指定的格式一致。
  如果wsprintf函数操作成功,则返回输出字符的数目,但这个字符数目不包括表示结束的NULL标志。如果操作失败,返回的整数值将与输出的字符数目不相符。
  实例主要说明了如何处理键盘消息,读者应该着重理解各种信息在MSG结构体变量中是如何保存的,怎样才能够对其中的具体信息进行识别和提取。程序运行后将产生一个背景色为灰色的简单窗口,并在窗口的顶部出现标题提示信息。这时用户如果进行键盘操作,则窗体中便会显示该操作所产生的键盘消息,每显示一条消息程序都会滚屏和重绘窗口,滚屏区域的颜色为白色。

键盘消息实例2:
#include <windows.h>
int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int);
LRESULT CALLBACK WndProc( HWND,UINT, WPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
  WNDCLASSEX wcex;
  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
  wcex.lpfnWndProc = (WNDPROC)WndProc;
  wcex.cbClsExtra = 0;
  wcex.cbWndExtra = 0;
  wcex.hInstance = hInstance;
  wcex.hIcon= LoadIcon(NULL, (LPCTSTR)IDI_APPLICATION);
  wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
  wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
  wcex.lpszMenuName = NULL;
  wcex.lpszClassName = "SeeKeyMessage";
  wcex.hIconSm = LoadIcon(NULL,(LPCTSTR)IDI_APPLICATION);

  if(!RegisterClassEx(&wcex)) return FALSE;

  int SW_XFS = GetSystemMetrics(SM_CXSCREEN);
  int SW_YFS = GetSystemMetrics(SM_CYSCREEN);
  HWND hWnd;
  hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,
             "SeeKeyMessage",
             "Trace Key Operation",
             WS_OVERLAPPEDWINDOW,
             0, 0, SW_XFS, SW_YFS-25,
             NULL,
             NULL,
             hInstance,
             NULL);
  if(!hWnd) return FALSE;

  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);
  MSG msg;

  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;
  static char Buffer[256];
  switch(message)
  {
    case WM_KEYDOWN:
    hDC = GetDC(hWnd);
    wsprintf(Buffer," ");
    TextOut(hDC,20,40,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_KEYDOWN %3d %3d%3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,40,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC);
    break;

    case WM_KEYUP:
    hDC = GetDC(hWnd);
    wsprintf(Buffer," ");
    TextOut(hDC,20,60,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_KEYUP %3d %3d %3d",wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,60,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC);
    break;

    case WM_PAINT:
    hDC = BeginPaint(hWnd,&ps);
    wsprintf(Buffer," ");
    TextOut(hDC,20,20,Buffer,strlen(Buffer));
    wsprintf(Buffer," Message wParam LOWORD(lParam) HIWORD(lParam)");
    TextOut(hDC,20,20,Buffer,strlen(Buffer));
    EndPaint(hWnd,&ps);
    break;

    case WM_DESTROY:
    PostQuitMessage(0);
    break;

    default:
    return DefWindowProc(hWnd,message,wParam,lParam);
  }
  return 0;
}

鼠标消息

  随着 Windows 操作系统的流行,鼠标因为其精确定位和操作方便的优点而成为计算机不可缺少的输入设备。
一、鼠标的基础知识
  本节将介绍在程序中用鼠标作为输入设备的方法和技巧。 1 .鼠标操作和鼠标消息用户在使用鼠标操作的过程中,经常会使用的主要方式有五种 ,如表所示。

操作名称 描述
单击(Click) 按下并迅速释放鼠标按钮。
双击(Double Click) 连续快速完成两次单击操作。
移动(Move) 鼠标光标移动。
拖动(Drag) 按下鼠标一键不放,同时执行鼠标移动操作。
与键盘的特殊键组合 在按下Ctrl键或Shift键的同时执行鼠标单击操作。

  其中,前三种操作是最为基本的操作,可以产生Windows内部定义的消息,并通过这些消息来判断用户具体执行了哪种操作。
  Windows定义的鼠标消息共有20条,其中非编辑区的鼠标消息一般交由系统处理,程序只处理编辑区内的鼠标消息。编辑区内的鼠标消息共有10条,如表所示。

消息常量 操作描述
WM_MOUSEMOVE 移动鼠标
WM_LVBUTTONDOWN 按下鼠标左键
WM_LBUTTONUP 释放鼠标左键
WM_LBUTTONDBLCLK 双击鼠标左键
WM_RVBUTTONDBLCLK 按下鼠标右键
WM_RBUTTONUP 释放鼠标右键
WM_RBUTTONDBLCLK 双击鼠标右键
WM_MVBUTTONDOWM 按下鼠标中键
WM_MBUTTONUP 释放鼠标中键
WM_MBUTTONDBLCLK 双击鼠标中键

  对于前表所列的鼠标操作中的最后两种,不能直接使用Windows定义的消息来判断,只能通过编程,将多种消息和数据组合之后判断。例如,判断用户是否按下鼠标左键之后进行拖动操作可以通过以下程序段来实现,用case语句来实现:
case WM_MOUSEMOVE:

if (wParam&MK_LBUTTON) //只处理鼠标拖动的消息
{ …… // 处理程序
}

  在处理鼠标消息的过程中,消息的wParam参数和lParam参数起了重要的作用。wParam参数中保存了在消息产生时其他操作进行的状态;用户可以通过位屏蔽操作来判断在该消息产生的同时,其余操作是否正在进行。这正是在程序中判断复杂鼠标操作的基本方法。例如,上面判断拖动操作的程序段就用了位操作 wParam& MK_LBUTTON, 判断在鼠标移动(WM_MOUSEMOVE)的同时鼠标左键是否同时被接下。如果,鼠标左键同时按下,则位操作的结果为TRUE,说明当前操作为拖动操作,程序可以继续进行下一步处理。又如需要判断单击鼠标左键时是否同时按下了Ctrl键或Shift键,可以用以下程序段来处理:
case WM_ LBUTTONDOWN:
if(wParam& MK_CTROL)
{//Ctrl键同时按下
  if (wParam&MK_ SHIFT)
  {// Ctrl 键和Shift键都同时按下
    …… // 处理程序
  }
  else { // Ctrl健同时按下,但 Shift键没有被按下
    ……. // 处理程序
  }
}
else if(wParam&MK_ SHIFT)
{ // Shift键同时按下,但 Ctrl键没有被接下
  …… // 处理程序
}
else
{// Shift 键和Ctrl键都未按下
  …… // 处理程序
}
  lParam参数保存了消息产生时鼠标所在点的坐标,其中低16位为X坐标,高16位为Y坐标。
  在处理鼠标消息的时候,如果需要处理鼠标双击消息,则在注册窗口类时,窗口的风格必须包括CS_DBCLCKS。否则即使执行了双击操作,窗口也只能收到两条WM_ BUTTONUP和 WM_BUTTONDOWN消息。区分双击操作和两次单击操作是以两次击键的时间间隔为标准的。当两次击键的时间间隔小于 500毫秒时, Windows将其视为双击操作:如果两次击键的时间间隔大于500毫秒,Windows将其视为两次单击操作。500毫秒为默认的时间间隔,用户可以通过调用SetDoubleClickTime函数来修改这一时间间隔。SetDoubleClickTime函数的原型定义如下:
BOOL SetDoubleClickTime(UINT uInterval // 新的击键时间间隔)
2.鼠标捕捉
  在通常情况下,只有当鼠标位于窗体内时,窗体才能接收到鼠标的消息。如果需要接收所有的鼠标消息而不论鼠标是否在窗口内,这时可以调用SetCapture函数来实现。SetCapture函数的原型定义如下:
HWND SetCapture (
  HWND hwnd // 窗口句柄
);
  调用SetCapture函数后,所有鼠标操作所产生的消息都直接发送到指定窗口。因为此时鼠标可能位于窗口之外,所以鼠标的坐标可能为负值。由于调用该函数会使其他窗口不能接收到键盘和鼠标的消息,因此在完成操作后应及时调用ReleaseCapture 函数释放鼠标捕获。ReleaseCapture函数的原型定义如下:
BOOL ReleaseCapture(VOID);
二、鼠标应用实例
下面是一个在程序设计中如何捕获鼠标消息的实例。
#include <windows.h>
//全局变量
WNDCLASSEX wnd;
static char szAppName[] = "mouse";//窗口类名
//函数声明
long WINAPI WndProc (HWND, UINT, WPARAM, LPARAM);
BOOL MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE hInstance, int iCmdShow);
//函数:WinMain
//作用:入口函数
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow) ?
{
  MSG msg;
  if(!MyRegisterClass(hInstance))
  {
    return FALSE;
  }

  if(!InitInstance(hInstance,iCmdShow))
  {
    return FALSE;
  }

  while (GetMessage (&msg, NULL, 0, 0))
  {
    TranslateMessage (&msg);
    DispatchMessage (&msg);
  }
  return msg.wParam;
}
//函数:WndProc
//作用:处理主窗口的消息
long WINAPI WndProc (HWND hwnd,UINT msg,WPARAM wParam,LPARAM lParam)
{
  static POINT points[256];//保存点坐标
  static POINT center;//保存中心点坐标
  static int iCount;//点数目累加值
  HDC hdc;
  PAINTSTRUCT ps;
  int i;//循环计数
  RECT rect;
  switch (msg)
  {
    case WM_MBUTTONDOWN:
    //处理鼠标中键按下的消息
    iCount = 0;//重新初始化点数目
    InvalidateRect (hwnd, NULL, TRUE);
    //通知系统重画窗口
    hdc = BeginPaint (hwnd,&ps);
    GetClientRect(hwnd,&rect);
    if(wParam&MK_CONTROL)//判断Shift键和Ctrl键是否被按下
    {
      if(wParam&MK_SHIFT)
      { //根据不同的情况给出不同的提示
        DrawText(hdc,"Ctrland Shift", -1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER?);
      }
      else
      {
        DrawText(hdc,"Ctrl Only" ,-1,&rect, DT_SINGLELINE|DT_CENTER|DT_VCENTER);
      }
    }
    else if(wParam&MK_SHIFT)
    {
      DrawText(hdc,"Shift Only",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
    }
    else
    {
      DrawText(hdc,
           "Middle Button of mouse only",
           -1,
           &rect,
           DT_SINGLELINE|DT_CENTER|DT_VCENTER);
    }
    EndPaint(hWnd,&ps);
    return 0;

    case WM_RBUTTONDOWN:
    //处理鼠标右键按下的消息
    iCount = 0;//重新初始化点数目
    center.x=LOWORD (lParam);
    //保存新的中心点坐标
    center.y=HIWORD (lParam);
    InvalidateRect (hwnd, NULL, TRUE);//通知系统重画窗口
    return 0;

    case WM_MOUSEMOVE://处理鼠标移动的消息
    if (wParam & MK_LBUTTON && iCount < 256)//只处理鼠标拖动的消息
    {
      points[iCount].x = LOWORD (lParam);//保存点的X坐标
      points[iCount++].y = HIWORD (lParam);//保存点的Y坐标
      hdc = GetDC (hwnd);//获得窗口的设备描述表句柄
      SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0L);//绘点
      ReleaseDC (hwnd, hdc);//释放设备描述表句柄
    }?return 0;

    case WM_LBUTTONUP:
    //处理鼠标左键抬起的消息
    InvalidateRect (hwnd, NULL, FALSE);
    //通知系统重画窗口
    return 0;

    case WM_PAINT://处理窗口重画的消息
    hdc = BeginPaint (hwnd, &ps);//获得设备描述表句柄
    SetCursor (LoadCursor (NULL, IDC_WAIT));//设置新的鼠标光标
    ShowCursor (TRUE);//显示鼠标光标
    for (i = 0 ; i < iCount ; i++)
    {
      MoveToEx(hdc, center.x, center.y,NULL);//绘制直线
      LineTo(hdc, points.x, points.y);
    }
    ShowCursor(FALSE);//隐藏鼠标
    SetCursor(LoadCursor (NULL, IDC_ARROW));
    //恢复原来的鼠标光标 ?
    EndPaint(hwnd, &ps);
    return 0;

    case WM_DESTROY://处理销毁窗口的消息
    PostQuitMessage (0);
    return 0;
  }
  return DefWindowProc (hwnd, msg, wParam, lParam);
}
//函数:MyRegisterClass
//作用:注册窗口类
BOOL MyRegisterClass(HINSTANCE hInstance)
{
  wnd.cbSize= sizeof (wnd);
  wnd.style= CS_HREDRAW | CS_VREDRAW;
  wnd.lpfnWndProc = WndProc;
  wnd.cbClsExtra = 0;
  wnd.cbWndExtra = 0;
  wnd.hInstance = hInstance;
  wnd.hIcon = LoadIcon (NULL, IDI_APPLICATION);
  wnd.hCursor = LoadCursor (NULL, IDC_ARROW);
  wnd.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);
  wnd.lpszMenuName= NULL;
  wnd.lpszClassName = szAppName;
  wnd.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
  return RegisterClassEx (&wnd);
}
//函数:InitInstance
//作用:创建窗口
BOOL InitInstance(HINSTANCE hInstance, int iCmdShow)
{
  HWND hwnd;
  hwnd = CreateWindow(szAppName,
            "跟踪鼠标移动",
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT,
            CW_USEDEFAULT, CW_USEDEFAULT,
            NULL, NULL, hInstance, NULL);
  if(!hwnd) return FALSE;
  ShowWindow (hwnd, iCmdShow);
  UpdateWindow (hwnd);
  return TRUE;
}
  例题的主要功能是在窗口中某个位置单击鼠标右键时,程序保存捕获的鼠标点处的坐标。紧接着按下鼠标左键在窗口中拖动,程序会记录下鼠标运动的轨迹,并以刚才右击鼠标时确定的点为中心绘制一簇射线。本实例最多可以绘制256条射线。程序的另一目的是为了让读者进一步了解如何捕获鼠标与Ctrl键或Shift键组合时的复杂鼠标消息。如果在窗口中单击鼠标中键,程序会在窗口中央显示文本信息说明用户是否同时按下Ctrl键和Shift键。
  源文件与本书前面所介绍的其他实例一样,都具有基本的 Windows API 程序的结构。即以WinMain函数作为程序入口,调用MyRegisterClass函数和InitInstance函数注册窗口类和创建窗口,再进入消息循环。并在消息循环中调用WndProc函数处理鼠标消息。下面主要介绍WndProc函数处理鼠标消息的方法和技巧。
  在例中WndProc函数能够处理的消息包括 WM_MBUTTONDOWN、WM_RBUTTONDOWN、WM_MOUSEMOVE、WM_LBUTTONUP和WM_PAINT。
处理WM_ MBUTTONDOWN消息的程序段如下:
case WM_MBUTTONDOWN:
//处理鼠标中键按下的消息
iCount = 0;//重新初始化点数目
InvalidateRect (hwnd, NULL, TRUE);//通知系统重画窗口
hdc = BeginPaint (hwnd,&ps);
GetClientRect(hwnd,&rect);
if(wParam&MK_CONTROL)//判断Shift键和Ctrl键是否被按下
{
  if(wParam&MK_SHIFT)
  { //根据不同的情况给出不同的提示
    DrawText(hdc,"Ctrland Shift", -1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
  }
  else
  {
    DrawText(hdc,"Ctrl Only" ,-1,&rect,
    DT_SINGLELINE|DT_CENTER|DT_VCENTER);
  }
}
else
{
  if(wParam&MK_SHIFT)
  {
    DrawText(hdc,"Shift Only",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
  }
  else
  {
    DrawText(hdc,"Middle Button of mouse only",-1,&rect,DT_SINGLELINE|DT_CENTER|DT_VCENTER);
  }
}
EndPaint(hWnd,&ps);
return 0;
  这种判断复杂鼠标操作的方法在基础知识部分已经详细介绍过,这里不再赘述。这一段程序的作用是根据在按下鼠标中键的同时,再按Ctrl键或Shift键的不同情况而在窗口中输出不同的信息。
处理WM_ RBUTTONDOWN消息的程序段如下:
case WM_RBUTTONDOWN://处理鼠标右键按下的消息
iCount = 0;//重新初始化点数目
center.x=LOWORD (lParam);//保存新的中心点坐标
center.y=HIWORD (lParam);
InvalidateRect (hwnd, NULL, TRUE);//通知系统重画窗口
return 0;
  这一段程序的主要作用是将鼠标右击点的坐标保存在POINT结构体变量center中,再将计数变量iCount归零,为绘制一簇射线作准备。完成以上工作后,程序调用InvalidateRect函数通知系统重画窗口,将窗口中原有的射线族擦去。处理WM_MOUSEMOVE消息的程序段如下:
case WM_MOUSEMOVE://处理鼠标移动的消息
if (wParam & MK_LBUTTON && iCount<256)//只处理鼠标拖动的消息
{
  points[iCount].x = LOWORD (lParam);//保存点的X坐标
  points[iCount++].y = HIWORD (lParam);//保存点的Y坐标
  hdc = GetDC (hwnd);//获得窗口的设备描述表句柄
  SetPixel (hdc, LOWORD (lParam), HIWORD (lParam), 0L);
  //绘点
  ReleaseDC (hwnd, hdc);//释放设备描述表句柄
}
  事实上,该程序只是处理鼠标左键拖动操作的消息。在执行拖动操作的过程中,程序不断将鼠标点的坐标记录在points数组中。points数组是WndProc函数中定义的一个静态的POINT结构体数组。在记录点坐标的同时,程序调用了SetPixel函数在窗口上的绘制点,对被记录的点进行标志。对于WM_ LBUTTONUP消息,程序只调用了IvalidateRect函数通知系统拖动操作已经结束,可以开始绘制射线族了。
处理WM _PAINT消息的程序段如下:
case WM_PAINT://处理窗口重画的消息
hdc = BeginPaint (hwnd, &ps);//获得设备描述表句柄
SetCursor (LoadCursor (NULL, IDC_WAIT));//设置新的鼠标光标
ShowCursor (TRUE);//显示鼠标光标
for (i = 0 ; i < iCount ; i++)
{
  MoveToEx(hdc, center.x, center.y,NULL);//绘制直线
  LineTo(hdc, points.x, points.y);
}
ShowCursor(FALSE);//隐藏鼠标
SetCursor(LoadCursor (NULL, IDC_ARROW));
//恢复原来的鼠标光标
EndPaint(hwnd, &ps);
return 0;
  以上程序的功能是实现射线族的绘制。程序使用了一个for循环,循环次数为iCoun变量记录的点数。在循环体中反复调用 MoveToEx函数和 LineTo函数绘制直线。MoveToEx函数使直线的起点回到有center变量记录的中点,LineTo函数实现由中点向points数组中的各点绘制直线。
  在处理WM_PAINT消息的程序中还使用了SetCursor函数和ShowCursor函数。SetCursor函数能设定一个鼠标图标,ShowCursor函数能将设定好的图标显示出来。在本实例中,调用这两个函数的目的是当程序绘制射线族的时候将鼠标图标转换成沙漏图案,表示程序正在执行某次操作。当给制完成后,又重新设置鼠标图像为箭头图标。
  程序运行后,首先生成一个窗口,等待用户执行鼠标操作。用户右击后,再按下鼠标左键并拖动,则程序会绘制出一簇美丽的射线。运行结果如图所示。



鼠标消息实例2
#include <windows.h>
int WINAPI WinMain(HINSTANCE, HINSTANCE,LPSTR,int);
LRESULT CALLBACK WndProc(HWND,UINT, WPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
  WNDCLASSEX wcex;
  wcex.cbSize = sizeof(WNDCLASSEX);
  wcex.style = CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS;
  wcex.lpfnWndProc = (WNDPROC)WndProc;
  wcex.cbClsExtra = 0;
  wcex.cbWndExtra = 0;
  wcex.hInstance = hInstance;
  wcex.hIcon = LoadIcon (NULL,(LPCTSTR)IDI_APPLICATION);
  wcex.hCursor = LoadCursor (NULL,IDC_ARROW);
  wcex.hbrBackground = (HBRUSH) (COLOR_WINDOW+1);
  wcex.lpszMenuName = NULL;
  wcex.lpszClassName = "SeeMouseMessage";
  wcex.hIconSm = LoadIcon(NULL,(LPCTSTR)IDI_APPLICATION);
  if(!RegisterClassEx(&wcex)) return FALSE;
  int SW_XFS = GetSystemMetrics(SM_CXSCREEN);
  int SW_YFS = GetSystemMetrics(SM_CYSCREEN);
  HWND hWnd;
  hWnd = CreateWindowEx(WS_EX_CLIENTEDGE,
             "SeeMouseMessage",
             "Trace Mouse Operation",
             WS_OVERLAPPEDWINDOW,
             0,
             0,
             SW_XFS,
             SW_YFS-25,
             NULL,
             NULL,
             hInstance,
             NULL);
  if(!hWnd) return FALSE;
  ShowWindow(hWnd, nCmdShow);
  UpdateWindow(hWnd);
  MSG msg;

  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;
  static char Buffer[256];
  switch(message)
  {
    case WM_MOUSEMOVE:
    hDC = GetDC(hWnd);
    wsprintf(Buffer," ");
    TextOut(hDC,20,40,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_MOUSEMOVE %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,40,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC);
    break;

    case WM_LBUTTONDOWN:
    hDC = GetDC(hWnd);
    wsprintf(Buffer," ");
    TextOut(hDC,20,60,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_LBUTTONDOWN %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,60,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC);
    break;

    case WM_LBUTTONUP:
    hDC = GetDC(hWnd);
    wsprintf(Buffer," ");
    TextOut(hDC,20,80,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_LBUTTONUP %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,80,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC);
    break;

    case WM_LBUTTONDBLCLK:
    hDC = GetDC(hWnd);
    wsprintf(Buffer," ");
    TextOut(hDC,20,100,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_LBUTTONDBLCLK %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,100,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC);
    break;

    case WM_RBUTTONDOWN:
    hDC = GetDC(hWnd);
    wsprintf(Buffer," ");
    TextOut(hDC,20,120,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_RBUTTONDOWN %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,120,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC);
    break;

    case WM_RBUTTONUP:
    hDC = GetDC(hWnd);
    wsprintf(Buffer," ");
    TextOut(hDC,20,140,Buffer,strlen(Buffer));
    wsprintf(Buffer," WM_RBUTTONUP %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,140,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC);
    break;

    case WM_RBUTTONDBLCLK:
    hDC = GetDC(hWnd);
    wsprintf(Buffer," ");
    TextOut(hDC,20,160,Buffer,strlen(Buffer));
    wsprintf(Buffer,"WM_RBUTTONDBLCLK %04x %3d %3d", wParam,LOWORD(lParam),HIWORD(lParam));
    TextOut(hDC,20,160,Buffer,strlen(Buffer));
    ReleaseDC(hWnd,hDC);
    break;

    case WM_PAINT:
    hDC = BeginPaint(hWnd,&ps);
    wsprintf(Buffer," ");
    TextOut(hDC,20,20,Buffer,strlen(Buffer));
    wsprintf(Buffer," Message wParam x y");
    TextOut(hDC,20,20,Buffer,strlen(Buffer));
    EndPaint(hWnd,&ps);
    break;

    case WM_DESTROY:
    PostQuitMessage(0);
    break;

    default:
    return DefWindowProc(hWnd,message,wParam,lParam);
  }
  return 0;
}
文章查看页面广告2
2006/4/30

c#TreeView

 private void button2_Click(object sender, System.EventArgs e)
  {
  
   string file=Application.StartupPath + "//datamining//dm.mdb";
   connString = "Provider=Microsoft.jet.OLEDB.4.0;data source="+file;
  
   //OleDbConnection cnn = new OleDbConnection(connString);
   conn=new OleDbConnection(connString);
   conn.Open();
   OleDbDataAdapter adapter=new OleDbDataAdapter("select * from test",connString);
   DataSet ds=new DataSet();
   adapter.Fill(ds,"one");
  
  
   this.treeView1.Nodes.Add ("grandpa");
   Loadchild (this.treeView1.Nodes[0],ds,"grandpa");
  }
  private void Loadchild(TreeNode tn,DataSet ds,string mine)//ds includes all the data in the table.
   {
   DataRow[] drs= ds.Tables[0].Select("pkey='" + mine + "'");
   for(int i=0;i<drs.Length;i++)
   {
   string getvstr =drs[i]["vstr"].ToString();
   //string getpkey=drs[i]["pkey"].ToString();
   string getckey=drs[i]["ckey"].ToString();
   tn.Nodes.Add (getvstr);
  
   if (ds.Tables[0].Select("pkey='" + getckey + "'").Length>0)
   {
   drs= ds.Tables[0].Select("pkey='" + mine + "'");
   Loadchild (tn.Nodes[i],ds,getckey );
   }
   }
   }

2006/4/25

C#技术内幕 学习笔记

C#技术内幕 学习笔记
.Net教程-C#语言

 

引用类型是类型安全的指针,它们的内存是分配在堆(保存指针地址)上的。
String、数组、类、接口和委托都是引用类型。


强制类型转换与as类型转换的区别:当类型转换非法时,强制类型转换将抛出一个System.InvalidCastException异常,
而as不会抛出异常,它返回一个null值。

用using创建别名:using console = System.Console;

访问限定符:
public  该成员可以被其他任何类访问
protected 该成员只能被其派生类访问
private  该成员只能被本类的其他成员访问
internal 该成员只能在当前编译单元的其他成员访问

带参数列表和返回值的Main方法:
class Test
{
  public static int Main(string[] args)
  {
    foreach (string arg in args)
    {
    ...
    }
  }
}


构造函数(constructor)包括实例构造函数和静态构造函数。
构造函数与类名相同,且不能有返回值。例:
class TestClass
{
  TestClass()  //实例构造函数:可以访问静态成员和实例成员,用于初始化实例成员
  {
  ...
  }

  static TestClass() //静态构造函数:只能访问静态成员,用于初始化静态成员
  {
  ...
  }
}

类的静态成员属于类所有,不必生成实例就可以访问,它是在载入包含类的应用程序时创建的,
但静态方法不能访问类的实例变量和方法。通常,静态变量是在定义时就赋初始值的。
类的实例成员属于类的实例所有,不创建实例对象就无法对其进行访问,实例成员可以访问类的
静态成员和其它实例成员。


调用基类的析构函数:
class A
{
  public A()
  {
  ...
  }
}

class B
{
  public B(): base()  //调用基类的析构函数
  {
  ...
  }
}


常量:其值是在编译时设定的,必须是数值文字。默认状态下常量是静态的。例:
class A
{
  public const double pi = 3.1415;
}


常量是编译时就确定的值,只读字段是在运行才能确定的值。比如运行时才能确定的屏幕分辨率。
只读字段只能在类的析构函数中赋值。

静态只读字段:
class A
{
  public static readonly int ScreenWidth;  //静态只读字段
  static A()   //静态析构函数
  {
    ScreenWidth = 1024;  //在静态析构函数中初始化
  }
}


在类的继承中,类的析构函数是不会被继承的。
一个派生类只能从一个基类继承,不能同时从多个基类继承,但可以通过继承多个接口来
达到相同目的。实现多继承的唯一方法就是使用接口。例:
class MyFancyGrid: Control, ISerializable, IDataBound
{
...
}


密封类是不能继承的类,抽象类不能被定义为密封类,且密封类的私有成员不能用protected修饰,
只能用private。例:
sealed class A
{
...
}


关键字ref和out用于指定用引用方式传递方法的参数。
它们的区别是:ref参数必须初始化,而out参数不需要初始化。所以在方法处理代码依赖参数的
初始化值时使用ref,不依赖初始化值时使用out。
对out参数即使在传递前对其进行了初始化,其值也不会传递到方法处理函数内部。传递时系统
会将其设为未初始化。所以在方法内部必须对out参数进行初始化。


方法重载时,必须参数数目和参数类型其中之一不同,返回值不同不能作为重载。
C#不支持方法的默认值,只能通过方法重载来实现。例:
class A
{
  int Method(int a)
  {
  ...
  }

  void Method(int a, int b) //参数数目不同
  {    //返回值不同不能作为重载
  ...
  }
}


params参数用于一个不定数目参数的方法,一般后面跟一个数组。例:
class A
{
  public void Method(params int[] i)
  {
  ...
  }
}


方法的覆盖:指派生类覆盖基类的同名方法,有二种方法
1)第一种是在派生类要覆盖的方法前面加new修饰,而基类不需要作任何改动。
这种方法的缺点是不能实现多态。例:
class A
{
  public void Method()  //无需任何修饰
  {
  ...
  }
}

class B: A   //从基类继承
{
  new public void Method() //覆盖基类的同名方法
  {
  ...
  }
}

class TestClass
{
  A Instance = new B();
  Instance.Method();  //这时将调用类A的Method方法,而不是类B的Method方法
}


2)第二种是在派生类要覆盖的方法前面加override修饰,而基类的同名方法前面加virtual修饰。
这样就能实现多态,例:

class A
{
  virtual public void Method()   //基类定义虚方法
  {      //虚拟方法不能定义为private,因为private成员对派生类是无法访问的
  ...
  }
}

class B: A     //从基类继承
{
  override public void Method()   //派生类覆盖基类的同名虚方法
  {
  ...
  }
}

class TestClass
{
  protected void Test()
  {
    A Instance = new B();   //定义一个实例,类型为基类,从派生类创建
      //派生类总是能够向上转换为其基类
    Instance.Method();    //将调用派生类B的Method方法,而不是基类的,这就是多态
  }
}

说明:new修饰的方法覆盖不能实现多态的原因,是因为使用new时编译器只会实现早期绑定(early binding)。
即调用的方法在编译时就决定了:编译器看到Instance.Method()而Instance的类是A,就会调用类A的Method()方法。
override修饰的方法覆盖可以实现多态的原因,是因为实现了后期绑定(late binding)。
使用override时强制编译器在运行时根据类的真正类型正确调用相应的方法,而不是在编译时。
而基类的同名方法必须加virtual修饰。


类的静态方法可能通过 类名.静态方法名 这种格式来调用,不能使用 实例名.静态方法名 这种方法调用。
因为类的静态方法为类所有(是属于类本身的),而非实例所有(不是属于类的实例的)。
类的静态方法可以访问类的任何静态成员,但不能访问类的实例成员。


C#中类的变量称为字段。类的public变量称为类的公共字段。
类的属性由一个protected(也可以是private)字段和getter和setter方法构成:
class Address
{
  protected string zipCode; //protected字段,注意大小写
  public string ZipCode
  {
    get    //getter方法
    {
      return zipCode;
    }
    set    //setter方法
    {
      zipCode = value;  //被传递的值自动被在这个value变量中
    }
  };
}

只读属性是指省略setter方法的属性,只读属性只能读取,不能设置。
属性也可以用限定符virtual,override和abstract修饰,功能同其他类的方法。

属性有一个用处称为懒惰的初始化(lazy initialization)。即在需要类成员时才对它们进行
初始化。如果类中包含了很少被引用的成员,而这些成员的初始化又会花费大量的时候和系统
资源的话,懒惰的初始化就很有用了。


C#中数组对象共同的基类是System.Array。
将数组声明为类的一个成员时,声明数组与实例化数组必须分开,这是因为只能在运行时创建了
类的实例对象之后,才能实例化数组元素值。
声明:
int[] intArray;  //一维数组
int[,,] int3Array; //三维数组
初始化:
intArray = new int[3] {1,2,3};
int[,] int2Array = new int[2,3] {{1,2,3},{4,5,6}}; //声明时可以初始化
遍历:
1)一维数组
for (int i = 0; i < intArray.Length; i++); //Array.Length返回数组所有元素的个数
foreach (int i in intArray);
for (int i = 0; i < intArray.GetLength(0); i++);//Array.GetLength(0)返回数组第一维的个数
2)多维数组
for (int i = 0; i < int3Array.GetLength(0); i++) //遍历三维数组
  for (int j = 0; j < int3Array.GetLength(1); j++)
    for (int k = 0; k < int3Array.GetLength(2); k++)
    {
    ...
    }
数组的维数就是该数组的秩(Rank)。Array.Rank可以返回数据的秩。
锯齿数组(jagged Array)是元素为数组的数组,例:
int[][] jaggedArray = new int[2][]; //包含二个元素,每个元素是个数组
jaggedArray[0] = new int[2];  //每个元素必须初始化
jaggedArray[1] = new int[3];
for (int i = 0; i < jaggedArray.Length; i++) //遍历锯齿数组
  for (int j = 0; j < jaggedArray[i].Length; j++)
  {
  ...
  }


类的属性称为智能字段,类的索引器称为智能数组。由于类本身作数组使用,所以用
this作索引器的名称,索引器有索引参数值。例:
using System;
using System.Collections;

class MyListBox
{
  protected ArrayList data = new ArrayList();
  public object this[int idx]  //this作索引器名称,idx是索引参数
  {
    get
    {
      if (idx > -1 && idx < data.Count)
      {
        return data[idx];
      }
      else
      {
        return null;
      }
    }
    set
    {
      if (idx > -1 && idx < data.Count)
      {
        data[idx] = value;
      }
      else if (idx = data.Count)
      {
        data.Add(value);
      }
      else
      {
        //抛出一个异常
      }
    }
  }
}


接口是二段不同代码之间约定,通过约定实现彼此之间的相互访问。
C#并不支持多继承,但通过接口可实现相同功能。
当在接口中指定了实现这个接口的类时,我们就称这个类“实现了该接口”或“从接口继承”。
一个接口基本上就是一个抽象类,这个抽象类中除了声明C#类的其他成员类型——例如属性、
事件和索引器之外,只声明了纯虚拟方法。
接口中可以包含方法、属性、索引器和事件——其中任何一种都不是在接口自身中来实现的。例:
interface IExampleInterface
{
  //property declaration
  int testProperty { get; }

  //event declaration
  event testEvevnt Changed;

  //mothed declaration
  function void testMothed();

  //indexer declaration
  string this[int index] { get; set; }
}
说明:定义接口时,在方法、属性、事件和索引器所有这些接口成员都不能用public之类的访问限定符,
因为所有接口成员都是public类型的。


因为接口定义了一个约定,任何实现一个接口的类都必须定义那个接口中每一个成员,否则将编译失败。例:
using System;
public class FancyControl
{
  protected string data;
  public string Data
  {
    get {return this.data;}
    set {data = value;}
  }
}

interface IValidate
{
  bool Validate(); //接口方法
}

public class MyControl: FancyControl, IValidate
{
  public MyControl()
  {
    data = "my control data";
  }

  public bool Validate()  //实现接口
  {
    if (data == "my control data")
      return true;
    else
      return false;
  }
}

class InterfaceApp
{
  MyControl myControl = new MyControl();
 
  IValidate val = (IValidate)myControl;  //可以将一个实现某接口的类,转换成该接口
  bool success = val.Validate();  //然后可调用该接口的方法
}

也可以用:
bool success = myControl.Validate();
这种方法来调用Validate方法,因为Validate在类MyControl中是被定义成public的,如果去除public,Validate方法被隐藏,
就不能用这种方法调用了,这样隐藏接口方法称为名字隐藏(name hiding)。

可以用:类实例 is 接口名 来判断某个类是否实现了某接口,例:
myControl is IValidate  //MyControl类的实例myControl是否实现了IValidate接口
当然,也可用as来作转换,根据转换结果是否为null来判断某个类是否实现了某接口,例:
IValidate val = myControl as IValidate;
if (null == val)
{
...  //没有实现IValidate接口
}
else
{
...  //实现了IValidate接口
}

如果一个类从多个接口继承,而这些接口中如果定义的同名的方法,则实现接口的方法时,必须加接口名来区别,
写成 接口名.方法名。假设Test类从IDataStore和ISerializable二个接口继承,而这二个接口都有SaveData()方法,
实现SaveData()方法时必须写成:
class Test: ISerializable, IDataStore
{
  void ISerializable.SaveData()
  {
  ...
  }

  void IDataStore.SaveData()
  {
  ...
  }
}

如果一个类从多个接口继承,为了方便可以定义一个新的接口,这个接口继续多个接口,然后类直接从这个接口继承就
可以了,这个叫合并接口。例:
interface ISaveData: ISerializable, IDataStore
{  //不需要定义任何方法或成员,只是用作合并
}
class Test: ISaveData  //只要继承ISaveData就可以了
{
...
}


C# 操作符优先级(从高到低)
初级操作符 () x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked
一元操作符 + - | ~ ++x --x (T)x
乘除操作符 * / %
加减操作符 + -
位移操作符 << >>
关系操作符 < > <= >= is
等于操作符 ==
逻辑与  &
逻辑异或 ^
逻辑或  |
条件与  &&
条件或  ||
条件操作符 ?:
赋值操作符 = *= /= %= += -= <<= >>= &= ^= |=


所有的二元操作符除赋值符外都是左联合的,即从左到右计算。

typeof()运算符可以从一个类名得到一个System.Type对象,而从System.Object对象继承来的GetType()方法
则可从一个类实例来得到一个System.Type对象。例:
Type t1 = typeof(Apple); //Apple是一个类名
Apple apple = new Apple(); //apple是Apple类的一个实例
Type t2 = apple.GetType(); //t1与t2是相同的

通过反射得到一个类的所有成员和方法:
Type t = typeof(Apple);
string className = t.ToString(); //得到类名
MethodInfo[] methods = t.GetMethods(); //得到所有方法
foreach (MethodInfo method in methods)
{
//用method.ToString()得到方法名
}
MemberInfo[] members = t.GetMembers(); //得到所有成员
foreach (MemberInfo member in members)
{
//用member.ToString()得到成员名
}


sizeof()操作符用来计算值类型变量在内存中占用的字节数(Bytes),并且它只能在unsafe(非安全)
代码中使用。例:
static unsafe public void ShowSizes()
{
  int i, j;
  j = sizeof(short);
  j = sizeof(i);
}

尽可能使用复合赋值操作符,它比不用复合赋值操作符的效率高。


for语句的语法为:
for (initialization; Boolean-expression; step)
  embedded-statement
在initialization和step部份还可以使用逗号操作符,例:
for (int i = '0', j = 1; i <= '/xFF'; i++, j++)

for (int i = 1, j = 1; i < 1000; i += j, j = i - j) //输出斐波那契数列
 Console.Write("{0} ", i);

在switch语句中执行一个分支的代码后还想执行另一个分支的代码,可以用:
goto case 分支;


操作符重载是为了让程序更加自然,容易理解。想要为一个类重新定义一个操作符,使用以下语法:
public static 返回值 operator 操作符 (操作对象1[,操作对象2])
说明:
1)所有重载的操作符方法都必须定义为public和static
2)从技术上说返回值可以是任何类型,但通常是返回所定义方法使用的类型
3)操作对象的数目取决于重载是一元操作符还是二元操作符,一元操作符只要一个操作对象,二元操作符则需要二个。
4)不管重载是一元操作符还是二元操作符,第一个操作对象的类型都必须与返回值的类型一致;而对于二元操作符的第二个
操作对象的类型则可以是任何类型。
5)只有下列操作符可以被重载:
一元:+ - ! ~ ++ -- true false
二元:+ - * / % & | ^ << >> == != > < >= <=
赋值操作符(+=,-=,*-,/=,%=等等)无法被重载。
[]和()操作符也无法被重载。
6)操作符的优先级是无法改变的,运算优先级的规则是静态的。

例:假设一个Invoice发票类由多个InvoiceDetailLine类(成员只有一个Double类型的Amount金额属性)组成,
我们重载+操作符,使之可以将InvoiceDetailLine类的内容(注意不是金额合计)加在一起。
class Invoice
{
  public ArrayList DetailLine;
 
  public Invoice   //类的析构函数
  {
    DetailLine = new ArrayList(); //ArrayList存放多个InvoiceDetailLine类的实例
  }

  public static Invoice operator+ (Invoice Invoice1, Invoice Invoice2) //参数与返回值的类型一致
  {
    //Invoice1与Invoice2的内容合并
    Invoice ReturnInvoice = new Invoice();
    foreach(InvoiceDetailLine detailLine in Invoice1.DetailLines)
      ReturnInvoice.DetailLine.Add(detailLine);
    foreach(InvoiceDetailLine detailLine in Invoice2.DetailLines)
      ReturnInvoice.DetailLine.Add(detailLine);
    return ReturnInvoice;
  }
}

class InvoiceAddApp  //调用示例
{
  public static void main()
  {
    Invoice i1 = new Invoice();
    for(int i = 0; i < 3; i++)
      i1.DetailLine.Add(new InvoiceDetailLine(i + 1));

    Invoice i2 = new Invoice();
    for(int i = 0; i < 3; i++)
      i2.DetailLine.Add(new InvoiceDetailLine(i + 1));

    Invoice summaryInvoice = i1 + i2;  //调用重载的操作符+方法
  }
}


自定义类型转换可以编写代码实际二个不同的类、结构体之间的转换。
语法:public static implicite/explicite operator 输出类型 (输入类型)
说明:
1)转换方法必须是静态的。
2)implicite表示隐式转换,explicite表示显式转换。
3)输入类型和输出类型其中之一必须与包含转换的类或结构体类型。即转换必须与本类相关。
例:
struct Celisus
{
  public float t;

  public Celisus(float t)
  {
    this.t = t;   //this.t是结构体的字段,t是参数
  }

  public static implicite operator Celisus(float t) //float=>Celisus
  {
    return new Celisus(t);
  }

  public static implicite operator float(Celisus c) //Celisus=>float
  {
    return ((c.t - 32) / 9) * 5;
  }
}

 

代表的(delegate)目的与C++中的函数指针相同,代表不是在编译时被定义的,而是在运行时被定义的。
代表主要有二个用途:回调(Callback)和事件处理(event)
回调通常用于异步处理和自定义处理。例:
class DBManager
{
  static DBConnection[] activeConnections;
  //声明回调函数
  public void delegate EnumConnectionCallback(DBConnection connection);

  public static void EnumConnections(EnumConnectionCallback callback)
  {
    foreach (DBConnection connection in activeConnections)
    {
      callback(connection);  //执行回调函数
    }
  }
}

//调用
class DelegateApp
{
  public static void ActiveConncetionCallback(DBConnection connection) //处理函数
  {
  ...
  }

  public void main()
  {
    //创建指向具体处理函数的代表实例(新建一个代表,让它指向具体的处理函数)
    DBManager.EmnuConnectionCallback myCallback = new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
    DBManager.EnumConnections(myCallback);
  }
}

//使用静态代表,上面的调用改为
class DelegateApp
{
  //创建一个指向处理函数的静态代表
  public static DBManager.EmnuConnectionCallback myCallback
    = new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
  public static void ActiveConncetionCallback(DBConnection connection)
  {
  ...
  }

  public void main()
  {
    DBManager.EnumConnections(myCallback);
  }
}

//在需要时才创建代表,上面的调用改为
class DelegateApp
{
  //将创建代表放在属性的getter方法中
  public static DBManager.EmnuConnectionCallback myCallback
  {
    get
    {
      retun new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
    }
  }
  public static void ActiveConncetionCallback(DBConnection connection)
  {
  ...
  }

  public void main()
  {
    DelegateApp app = new DelegateApp(); //创建应用程序
    DBManager.EnumConnections(myCallback);
  }
}


可以将多个代表整合成单个代表,例:
class CompositeDelegateApp
{
  public static void LogEvent(Part part)
  {
  ...
  }

  public static void EmailPurchasingMgr(Part part)
  {
  ...
  }

  public static void Main()
  {
    //定义二个代表
    InventoryManager.OutOfStockExceptionMethod LogEventCallback
      = new InventoryManager.OutOfStockExceptionMethod(LogEvent);
    InventoryManager.OutOfStockExceptionMethod EmailPurchasingMgrCallback
      = new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr);
    //整合为一个代表,注意后加的代表先执行(这里是先执行LogEventCallback)
    InventoryManager.OutOfStockExceptionMethod onHandExceptionEventsCallback
      = EmailPurchasingMgrCallback + LogEventCallback;
    //调用代表
    InventoryManager mgr = new InventoryManager();
    mgr.ProcessInventory(onHandExceptionEventsCallback);
    //InventoryManager类的ProcessInventory方法的原型为:
    //public void ProcessInventory(OutOfStockExceptionMethod exception);
  }
}

可以根据需要将多个代表自由地组合成单个代表,例:
class CompositeDelegateApp
{
  //代表指向的处理函数(三个代表三个函数)
  public static void LogEvent(Part part)
  {
  ...
  }

  public static void EmailPurchasingMgr(Part part)
  {
  ...
  }

  public static void EmailStoreMgr(Part part)
  {
  ...
  }

  public static void Main()
  {
    //通过数组定义三个代表
    InventoryManager.OutOfStockExceptionMethod[] exceptionMethods
      = new InventoryManager.OutOfStockExceptionMethod[3];
    exceptionMethods[0] = new InventoryManager.OutOfStockExceptionMethod(LogEvent);
    exceptionMethods[1] = new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr);
    exceptionMethods[2] = new InventoryManager.OutOfStockExceptionMethod(EmailStoreMgr);

    int location = 1;
    //再定义一个代表(用于组合成单代表)
    InventoryManager.OutOfStockExceptionMethod compositeDelegate;
    //根据需要组合
    if (location = 2)
    {
      compositeDelegate = exceptionMethods[0] + exceptionMethods[1];
    }
    else
    {
      compositeDelegate = exceptionMethods[0] + exceptionMethods[2];
    }
    //调用代表
    InventoryManager mgr = new InventoryManager();
    mgr.ProcessInventory(compositeDelegate);
  }
}


C#的事件遵循“发布——预订”的设计模式。在这种模式中,一个类公布能够出现的所有事件,
然后任何的类都可以预订这些事件。一旦事件产生,运行环境就负责通知每个订户事件已经发生了。
当代表作为事件的处理结果时(或者说定义具有代表的事件),定义的代表必须指向二个参数的方法:
一个参数是引发事件的对象(发布者),另一个是事件信息对象(这个对象必须从EventArgs类中派生)。
例:
using System;

class InventoryChangeEventArgs: EventArgs //事件信息对象,从EventArgs类派生
{
... //假设定义二个public属性string Sku和int Change
}

class InventoryManager    //事件的发布者
{
  //声明代表
  public delegate void InventoryChangeEventHander(object source, InventoryChangeEventArgs e);
  //发布事件,event关键字可将一个代表指向多个处理函数
  public event InventoryChangeEventHandler onInventoryChangeHander;
 
  public void UpdateInventory(string sku, int change)
  {
    if (change == 0)
      return;
    InventoryChangeEventArgs e = new InventoryChangeEventArgs(sku, change);
    //触发事件
    if (onInventoryChangeHandler != null) //如果有预订者就触发
      onInventoryChangeHandler(this, e); //执行代表指向的处理函数
  }
}

class InventoryWatcher    //事件的预订者
{
  public InventoryWatcher(InventoryManager mgr) //mgr参数用于联结发布者
  {
    this.inventoryManager = mgr;
    //预订事件,用 += 调用多个处理函数
    mgr.onInventroyChangeHandler += new InventoryManager.InventoryChangeEventHandler(onInventoryChange);
    //事件处理函数
    void onInventroyChange(object source, InventroyChangeEventArgs e)
    {
    ...
    }

    InventoryManager inventoryManager;
  }
}

class EventsApp     //主程序
{
  public static void Main()
  {
    InventoryManager inventoryManager = new InventoryManager();
    InventoryWatcher inventoryWatcher = new InventoryWatcher(inventoryManager);

    inventoryManager.UpdateInventory("111 006 116", -2);
    inventoryManager.UpdateInventory("111 006 116", 5);
  }
}


Microsoft Windows NT和IBM OS/2等操作系统都支持占先型多任务。在占先型多任务执行中,处理器负责
给每个线程分配一定量的运行时间——一个时间片(timeslice)。处理器接着在不同的线程之间进行切换,
执行相应的处理。在单处理器的计算机上,并不能真正实现多个线程的同时运行,除非运行在多个处理器
的计算机上。操作系统调度的多线程只是根据分配给每个线程时间片进行切换执行,感觉上就像同时执行。

上下文切换(context switching)是线程运行的一部分,处理器使用一个硬件时间来判断一个指定线程的时间片
何时结束。当这个硬件计时器给出中断信号时,处理器把当前运行的线程所用的所有寄存器(registers)数据
存储到堆栈中。然后,处理器把堆栈里那些相同的寄存器信息存放到一种被称为“上下文结构”的数据结构中。
当处理器要切换回原来执行的线程时,它反向执行这个过程,利用与该线程相关的上下文结构,在寄存器里
重新恢复与这一线程相关的信息。这样的一个完整过程称为“上下文切换”。

多线程允许应用程序把任务分割为多个线程,它们彼此之间可以独立地工作,最大限度地利用了处理器时间。

using System;
using System.Threading;

class SimpleThreadApp
{
  public static void WorkerThreadMethod() //线程的执行体
  {
  ...      //执行一些操作
  }

  public static void Main()
  {
    //创建一个线程代表指向线程的执行体,ThreadStart是创建新线程必须用到的代表
    ThreadStart worker = new ThreadStart(WorkerThreadMethod);
    Thread t = new Thread(worker);  //用线程代表创建线程
    t.Start();     //执行线程
  }
}

可以通过两种方式来得到一个Thread对象:一种是通过创建一个新线程来得到,如上例;另一种在正在执行的线程调用
静态的Thread.CurrentThread方法。
静态方法Thread.Sleep(int ms)可以让当前线程(它自动调用Thread.CurrentThread)暂停指定毫秒的时间。
如果使用Thread.Sleep(0)那么当前线程将一直处于等待中,直到另一个线程调用这个线程的实例方法Thread.Interrupt方法,
等待才会结束。
使用Thread.Suspend方法也能挂起线程,Thread.Suspend方法可以被当前线程或其他线程调用,而Thread.Sleep(0)
只能由当前线程在执行体中调用。当线程用Thread.Suspend挂起时,必须用Thread.Resume方法恢复。不论Thread.Suspend
方法调用了多少次,只要调用Thread.Resume方法一次就可以线程恢复执行。用Thread.Suspend方法并不会阻塞线程,
调用立即返回。而Thread.Sleep(0)则会阻塞线程。所以确切地说Thread.Sleep(0)暂停线程,而不是挂起线程。
使用Thread.Abort方法可以终止正在执行的线程。当Thread.Abort方法被调用时,线程不会立即终止执行。运行环境将会
等待,直到线程到达文档中所描述的“安全点”。如果要确保线程已经完全停止,可以使用Thread.Join方法。这是一个同步
调用,同步调用意味着直到线程完全停止,调用才会返回。
Thread.Priority属性用于设置的线程的优先级。其值是Thread.ThreadPriority枚举值,可以设为Highest, AboveNormal,
Normal, BelowNormal, Lowest。缺省值是Thread.ThreadPriority.Normal。

线程的同步是为了解决多个线程同时使用同一对象产生的一些问题。通过同步,可以指定代码的临界区(critical section),
一次只有一个线程可以进入临界区。

使用System.Monitor类(锁定与信号量)进行线程同步:
using System;
using System.Threading;

public void SaveData(string text) //线程执行函数或线程执行函数调用的对象的方法
{
  ...   //执行其他一些不需要同步的处理

  Monitor.Enter(this); //获取对象的Monitor锁
  ...   //执行需要同步的处理
  Monitor.Exit(this); //释放对象的Monitor锁

  ...   //执行其他一些不需要同步的处理
}

说明:当执行Monitor.Enter方法时。这个方法会试图获取对象上的Monitor锁,如果另一个线程已经拥有了
这个锁,这个方法将会阻塞(block),直到这个锁被释放。
也可用C#的lock语句来获得和释放一个Monitor锁。上面同步写成:
public void SaveData(string text) //线程执行函数或线程执行函数调用的对象的方法
{
  ...   //执行其他一些不需要同步的处理

  lock(this)  //获取对象的Monitor锁,代码块执行完成后释放Monitor锁
  {
  ...   //执行需要同步的处理
  }

  ...   //执行其他一些不需要同步的处理
}

也可以使用System.Threading名称空间的Mutex类(互斥类)进行线程同步。与Monitor锁一样,一次只有一个线程
能获得一个给定的互斥。但Mutex要慢得多,但它增加了灵活性。例:
using System;
using System.Threading;

class Database
{
  Mutex mutex = new Mutex(false); //创建一个互斥,但不立即获得它
     //注意:创建互斥在需要同步的方法之外,实际上它只要创建一个实例
  public void SaveData(string text) //需要同步的方法
  {
    mutex.WaitOne();  //等待获得互斥
    ...    //需要同步的处理
    mntex.Close();  //释放互斥
  }
}

Mutex类重载了三个构造函数:
Mutex()       //创建并使创建类立即获得互斥
Mutex(bool initiallyOwned)    //创建时可指定是否要立即获得互斥
Mutex(bool initiallyOwned, string muterName)  //还可以指定互斥的名称

Mutex.WaitOne方法也重载了三次:
Mutex.WaitOne()      //一直等待
Mutex.WaitOne(TimeSpan time, bool exitContext)  //等待TimeSpan指定的时间
Mutex.WaitOne(int milliseconds, bool exitContext) //等待指定的毫秒

线程的用法:
1)并发操作:比如一个程序监视多个COM口,当每个COM接到信息时执行一段处理时。
2)复杂长时间操作:一个长时间的复杂操作可能会使界面停滞,停止用户响应,如果还允许用户停止它,
或者显示进度条、显示操作执行进程信息时。

 


反射(Reflection)就是能够在运行时查找类型信息,这是因为.NET编译的可执行(PE)文件中包括MSIL和元数据(metadata)。
反射的中心是类System.Type。System.Type是一个抽象类,代表公用类型系统(Common Type System, CTS)中的一种类型。

using System;
using System.Reflection; //反射命名空间,必须引用

public static void Main(string[] args)
{
  int i = 6;
  Type t = i.GetType();   //根据实例得到类型
  t = Type.GetType("System.Int32"); //根据类型的字符名称得到类型
}

通过Assembly类可以得到已经编译.NET Framework程序的中所有类型,例:
using System;
using System.Diagnostics;  //为了使用Process类
using System.Reflection;  //为了使用Assembly类

class GetTypesApp
{
  protected static string GetAssemblyName(string[] args)
  {
    string assemblyName;
    if (0 == args.Length) //如果参数为空,取当前进程的名称
    {
      Process p = Process.GetCurrentProcess();
      assemblyName = p.ProcessName + ".exe";
    }
    else
      assemblyName = args[0]; //取第一个参数,即当前运行程序名

    return assemblyName;
  }

  public static void Main(string[] args)
  {
    string assemblyName = GetAssemblyName(args);
    Assembly a = Assembly.LoadFrom(assemblyName); //调用编译程序集
    Type[] types = a.GetTypes();   //得到多个类型
    foreach (Type t in types)    //遍历类型数组
    {
    ...  //取得t.FullName,t.BaseType.FullName等类型信息
    }
  }
}

一个应用程序可以包括多个代码模块。若要将一个cs文件编译一个模块,只要执行下面的命令:
csc /target:module 要编译的模块.cs  //csc是C Sharp Compiler(C#编译器)
然后在应用程序中using编译的模块.cs中的NameSpace即可应用了。
要反射应用程序中所有代码模块(Module),只要:
Assembly a = Assembly.LoadFrom(assemblyName); //应用程序的物理文件名
Module[] modules = a.GetModules();
foreach(Module m in modules)
{
... //显示m.Name等
}


后期绑定(latebinding),例:
string[] fileNames = Directory.GetFiles(Environment.CurrentDirectory, "*.dll");
foreach (string fileName in fileNames)
{
  Assembly a = Assembly.LoadFrom(fileName);
  Type[] types = a.GetTypes();
  foreach(Type t in types)
  {
    if (t.IsSubclassOf(typeof(CommProtocol)))  //判断是否有CommProtocol的派生类
    {
      object o = Activator.CreateInstance(t);  //生成实例
      MethodInfo mi = t.GetMethod("DisplayName");
      mi.Invoke(o, null);    //调用方法
    }
  }
}

//带参数的例子
namespace Programming_CSharp
{
  using System;
  using System.Reflection;
 
  public class Tester
  {
    public static void Main( )
    {
      Type t = Type.GetType("System.Math");
      Object o = Activator.CreateInstance(t);

      // 定义参数类型
      Type[] paramTypes = new Type[1];
      paramTypes[0]= Type.GetType("System.Double");

      MethodInfo CosineInfo = t.GetMethod("Cos", paramTypes);

      //设置参数数据
      Object[] parameters = new Object[1];
      parameters[0] = 45;

      //执行方法
      Object returnVal = CosineInfo.Invoke(o, parameters);
      Console.WriteLine("The cosine of a 45 degree angle {0}", returnVal);
    }
  }
}


动态生成代码和动态调用的完整例子:
//动态生成代码的部分
using System;
using System.Reflection;
using System.Reflection.Emit;  //动态生成代码必须引用

namespace ILGenServer
{
  public class CodeGenerator
  {
    public CodeGenerator()
    {
      currentDomain = AppDomain.CurrentDomain;  //得到当前域
      assemblyName = new AssemblyName();  //从域创建一个程序集
      assemblyName.Name = "TempAssembly";
      //得到一个动态编译生成器,AssemblyBuilerAccess.Run表示只在内存中运行,不能保存
      assemblyBuilder = currentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilerAccess.Run);
      //从编译生成器得到一个模块生成器
      moduleBuilder = assemblyBuilder.DefineDynamicModule("TempModule");
      //模块生成器得到类生成器
      typeBuilder = moduleBuilder.DefineType("TempClass", TypeAttributes.Public);
      //为类添加一个方法
      methodBuilder = typeBuilder.DefineMethod("HelloWord", MethodAttributes.Public, null, null);
      //为方法写入代码,生成代码必须使用到IL生成器
      msil = methodBuilder.GetILGenerator();
      msil.EmitWriteLine("Hello World");
      msil.Emit(OpCodes.Ret);
      //最后还需要编译(build)一下类
      t = typeBuilder.CreateType();
    }

    AppDomain currentDomain;
    AssemblyName assemblyName;
    AssemblyBuilder assemblyBuilder;
    ModuleBuilder moduleBuilder;
    TypeBuilder typeBuilder;
    MethodBuilder methodBuilder;
    ILGenerator msil;
    object o;
    Type t;
    public Type T
    {
      get
      {
        return this.t;
      }
    }
  }
}

//动态调用的部分
using System;
using System.Reflection;
using ILGenServer;  //引用动态生成代码的类

public class ILGenClientApp
{
  public static void Main(
  {
    CodeGenerator gen = new CodeGenerator(); //创建动态生成类
    Type t = gen.T;
    if (null != t)
    {
      object o = Activator.CreateInstance(t);
      MethodInfo helloWorld = t.GetMethod("HelloWorld"); //为调用方法创建一个MethodInfo
      if (null != helloWorld)
      {
        helloWorld.Invoke(o, null);  //调用方法
      }
    }
  }
}


调用DLL
using System;
using System.Runtime.InteropServices; //为了使用DLLImport特性

class PInvokeApp
{
  [DllImport("user32.dll", CharSet=CharSet.Ansi)] //CharSet.Ansi指定Ansi版本的函数(MessageBoxA),CharSet.Unicode指定Unicode版本的函数(MessageBoxW)
  static extern int MessageBox(int hWnd, string msg, string caption, int type);  //声明DLL中的函数
 
  //[DllImport("user32.dll", EntryPoint="MessageBoxA")] //用这种方法使用不同的函数名
  //static extern int MsgBox(int hWnd, string msg, string caption, int type);
 
  //[DllImport("user32.dll", CharSet=CharSet.Unicode)]  //调用Unicode版的DLL函数
  //static extern int MessageBox(int hWnd, [MarshalAs(UnmanagedType.LPWStr)]string msg,
  // [MarshalAs(UnmanagedType.LPWStr)]string caption, int type); //将LPWStr翻译为string型,缺省情况系统只将LPStr翻译成string
  public static void Main()
  {
    MessageBox(0, "Hello, World!", "CaptionString", 0);  //调用DLL中的函数
  }
}

例2,使用回调:
class CallbackApp
{
  [DllImport("user32.dll")]
  static extern int GetWindowText(int hWnd, StringBuilder text, int count);

  delegate bool CallbackDef(int hWnd, int lParam);

  [DllImport("user32.dll")]
  static extern int EnumWindows(CallbackDef callback, int lParam);

  static bool PrintWindow(int hWnd, int lParam)
  {
    StringBuilder text = new StringBuilder(255);
    GetWindowText(hWnd, text, 255);
    Console.WriteLine("Window Caption: {0}", text);
    return true;
  }

  static void Main()
  {
    CallbackDef callback = new CallbackDef(PrintWindow);
    EnumWindows(callback, 0);
  }
}


关键字unsafe指定标记块在非控环境中运行。该关键字可以用于所有的方法,包括构造函数和属性,
甚至还有方法中的代码块。关键字fixed负责受控对象的固定(pinning)。Pinning是一种动作,向
垃圾收集器(Garbage Collector, GC)指定一些不能被移动的对象。为了不在内存中产生碎片,.NET
运行环境把对象四处移动,以便于最有效地利用内存。使用fixed后指定对象将不会被移动,所以就
可以用指针来访问它。
C#中只能得到值类型、数组和字符串的指针。在数组的情况下,第一个元素必须是值类型,因为C#
实际上是返回一个指向数组第一个元素的指针,而不是返回数组自身。
& 取一个变量的内存地址(即指向该变量的指针)
* 取指针所指变量的值
-> 取成员
例:
using System;

class UnsafeApp
{
  public static unsafe void GetValues(int* x, int* y)
  {
    *x = 6;
    *y = 42;
  }

  public static unsafe void Main()
  {
    int a = 1;
    int b = 2;
    GetValues(&a, &b);
  }
}

fixed语法为:fixed(type* ptr = expression) statements
其中type也可以为非控类型,也可是void;expression是任何产生一个type指针的表达式;
statements是应用的代码块。例:
fixed (int* f = &foo.x)  //foo是Foo类的一个实例,x是Foo类的一个int属性
{
  SetFooValue(f);  //SetFooValue方法的定义为unsafe static void SetFooValue(int* x)
}


传统的COM组件可以通过互操作层(COM Interop)与.NET运行环境交互。互操作层处理在托管运行环境和非托管区域
中的COM组件操作之间传递所有的消息。
要使COM组件能在.NET环境中使用,必须为COM组件生成元数据。.NET运行环境用元数据层业判断类型信息。在运行时刻
使用类型信息,以便生成RCW(Runtime Callable Wrapper,运行时可调用包装)。当.NET应用程序与COM对象交互时,
RCW处理对COM对象的装载和调用。RCW还完成许多其他的工作,如管理对象标识、对象生存周期以及接口缓冲区。
对象生存周期管理十分关键,因为.NET GC把对象到处移动,并且当对象不再使用时,自动处理这些对象。RCW服务告诉
.NET,应用程序正与托管.NET组件交互,同时又使非托管COM组件“觉得”COM对象是被传统的COM客户端调用的。

为了为COM组件生成元数据包装,必须使用tlbimp.exe(TypeLib Importer)工具:
tlbimp some_COM.tlb /out:som_COM.dll

Vsiaul C#如何读取注册信息(转)

Vsiaul C#如何读取注册信息(转)
.Net教程-Windows开发

Vsiaul C#如何读取注册信息
(王天 2001年11月26日 13:47)

从视窗95开始,微软公司就在视窗系统中引入了注册表这个概念。注册表到底是什么东东呢?它是视窗系统的一个核心的数据库,在这个数据库中存放中与系统相关的各种参数,这些参数直接控制中系统的启动、硬件的驱动程序安装信息以及在视窗系统上运行的各种应用程序的注册信息等。这就意味着,如果注册表因为某些原因受到了破坏,轻者是视窗系统启动过程出现异常,重者就有可能导致整个系统的完全瘫痪。所以正确的认识注册表,及时的备份注册表,对于视窗用户就显得相当重要。
Vsiaul C#就可以十分方便、简洁的开发出操作注册表的程序。本文就是介绍如何利用VisualC#来读取注册表中的信息。

一.初步认识注册表:
单击"开始/运行",在"打开"的后面填入"regedit"。就可以看到注册表的数据结构了。如下图。注:Regedit文件是微软公司提供给用户编辑注册表的一个工具。

点击小图放大,图01:注册表结构图示

如上图左边的部分在注册表中称为"主键",据图可见,"主键"是有层次结构的。主键的下一级主键称为该主键的"子键"。每一个主键可以对拥有多个子键。如图所示,右边的这些值就是所谓的键值了。每一个主键或者子键都可以拥有多个键值。注册表是一个庞大的数据库,在其中每一个主键,每一个键值都赋予了不同的功能。

二.Visual C#如何读取注册表中的主键和键值:
在.Net FrameWork SDK Beta 2版中,有一个Microsoft.Win32的名称空间,在此名称空间中提供了二个用于注册表操作的类:Registry类、RegistryKey类。这二个类都是封闭类,不可以继承。在这二个类,定义了许多关于注册表的方法和属性,通过调用这二个类,在Visual C#中就可以比较轻松的处理关于注册表的各种操作了。
(1).Registry类:
此类主要封装了七个公有的静态域,而这些静态域分别代表这视窗注册表中的七个基本的主键,具体如下所示:
Registry.ClassesRoot 对应于HKEY_CLASSES_ROOT主键
Registry.CurrentUser 对应于HKEY_CURRENT_USER主键
Registry.LocalMachine 对应于 HKEY_LOCAL_MACHINE主键
Registry.User 对应于 HKEY_USER主键
Registry.CurrentConfig 对应于HEKY_CURRENT_CONFIG主键
Registry.DynDa 对应于HKEY_DYN_DATA主键
Registry.PerformanceData 对应于HKEY_PERFORMANCE_DATA主键
(2).RegistryKey类:
此类中主要封装了对视窗系统注册表的基本操作。在程序设计中,首先通过Registry类找到注册表中的基本主键,然后通过RegistryKey类,来找其下面的子键和处理具体的操作的。

三.通过一个读取注册表信息例子来具体说明这二个来的用法:

(1).程序设计和运行的环境:
I视窗系统2000服务器版

II Net FrameWork SDK Beta 2版
(2)在运行程序前的一些必要的处理工作:
在程序设计时,主要功能是读取已经存在的主键键值,用户可以按照下图所示的结构新建若干个主键和对应的键值:

点击小图放大,图02:程序设计中要读取的注册表的信息

这里有必要说明的是上图只显示了"新项 #3"子键对应的键值。在"新项 #2"子键也有键值,对应的键值是:"新值#1"为"001","新值 #2"为"002"。在"新项 #1"子键中对应的键值是:"新值 #1"为"aaa","新值 #2"为"bbb"。
(3).程序的主要功能:
程序的主要功能是读取指定主键下面的所有子键和子键拥有的键值,并以列表的形式按层次显示出来,下图是本程序运行后界面:

点击小图放大,图03:读取注册表信息并以列表形式显示出来

(4).程序设计过程中的重要步骤以及应该注意的一些问题:

I 程序中读取主键、子键和键值所使用到的方法:
程序中为了读取指定主键下面的子键和子键中拥有的键值,主要使用了RegistryKey类中的四个方法:OpenSubKey,GetSubKeyNames,GetValueNames,GetValue。具体的用法和意思如下:
OpenSubKey ( string name )方法主要是打开指定的子键。
GetSubKeyNames ( )方法是获得主键下面的所有子键的名称,它的返回值是一个字符串数组。
GetValueNames ( )方法是获得当前子键中的所有的键名称,它的返回值也是一个字符串数组。
GetValue ( string name )方法是指定键的键值。
程序中具体的使用语句如下:
RegistryKey hklm = Registry.LocalMachine ;
//打开"SYSTEM"子键
RegistryKey software = hklm.OpenSubKey ( "SYSTEM" ) ;
//打开"001"子键
RegistryKey no1 = software.OpenSubKey ( "001" ) ;
//打开"002"子键
RegistryKey no2 = no1.OpenSubKey ( "002" ) ;


其中listBox1是程序中定义了的列表名称。
II 如何用列表形式显示注册信息:
由于GetSubKeyNames ( )方法和GetValueNames ( )方法的返回值是字符串数组,所以在程序中是通过foreach语句实现遍历这些字符串数组的。并且在遍历的时候,就通过列表形式显示出来,程序中具体实现语句如下:
foreach ( string site in no2.GetSubKeyNames ( ) )
//开始遍历由子键名称组成的字符串数组
{
listBox1.Items.Add ( site ) ;
//在列表中加入子键名称
RegistryKey sitekey = no2.OpenSubKey ( site ) ;
//打开此子键
foreach ( string sValName in sitekey.GetValueNames ( ) )
//开始遍历由指定子键拥有的键值名称组成的字符串数组
{
listBox1.Items.Add ( "" + sValName + ": " + sitekey.GetValue ( sValName ) ) ;
//在列表中加入键名称和对应的键值
}
}


(5).源程序代码:
通过以上的论述,我们可以得到程序的源程序代码,具体如下:
using System ;
using System.Drawing ;
using System.Collections ;
using System.ComponentModel ;
using System.Windows.Forms ;
using System.Data ;
using Microsoft.Win32 ; public class Form1 : Form
{
private System.ComponentModel.Container components ;
private ListBox listBox1 ;
private Button button1 ;
public Form1 ( )
{
InitializeComponent ( ) ;
}
//清除在程序中使用过的资源
public override void Dispose ( )
{
base.Dispose ( ) ;
components.Dispose ( ) ;
}
//初始化程序中使用到的组件
private void InitializeComponent ( )
{
this.components = new System.ComponentModel.Container ( ) ;
this.button1 = new Button ( ) ;
this.listBox1 = new ListBox ( ) ;
button1.Location = new System.Drawing.Point ( 16 , 320 ) ;
button1.Size = new System.Drawing.Size ( 75 , 23 ) ;
button1.TabIndex = 0 ;
button1.Text = "读取注册表" ;
button1.Click += new System.EventHandler( this.button1_Click ) ;
listBox1.Location = new System.Drawing.Point ( 16 , 32 ) ;
listBox1.Size = new System.Drawing.Size ( 496 , 264 ) ;
listBox1.TabIndex = 1 ;
this.Text = "读取主测表信息" ;
this.AutoScaleBaseSize = new System.Drawing.Size ( 5 , 13 ) ;
this.ClientSize = new System.Drawing.Size ( 528 , 357 ) ;
this.Controls.Add( this.listBox1 ) ;
this.Controls.Add ( this.button1 ) ;
}
protected void button1_Click ( object sender , System.EventArgs e )
{
listBox1.Items.Clear ( ) ;
RegistryKey hklm = Registry.LocalMachine ;
RegistryKey software = hklm.OpenSubKey ( "SYSTEM" ) ;
//打开"SYSTEM"子键
RegistryKey no1 = software.OpenSubKey ( "001" ) ;
//打开"001"子键
RegistryKey no2 = no1.OpenSubKey ( "002" ) ;
//打开"002"子键
foreach ( string site in no2.GetSubKeyNames ( ) )
//开始遍历由子键名称组成的字符串数组
{
listBox1.Items.Add ( site ) ;
//在列表中加入子键名称
RegistryKey sitekey = no2.OpenSubKey ( site ) ;
//打开此子键
foreach ( string sValName in sitekey.GetValueNames ( ) )
//开始遍历由指定子键拥有的键值名称组成的字符串数组
{
listBox1.Items.Add ( "" + sValName + ": " + sitekey.GetValue ( sValName ) ) ;
//在列表中加入键名称和对应的键值
}
}
}
public static void Main ( )
{
Application.Run ( new Form1 ( ) ) ;
}
}


四.总结:
用Visual C#来读取注册表中的注册信息是通过名称空间Micorsoft.Win32中的二个类来实现的。在这二个类中还定义了对注册表信息的删除、修改和重命名的一些方法。这些方法比起本文介绍的读取方法、打开方法来说,更具有破坏性,但也更实用。对应这些方法的介绍将在以后的文章中进行。
通过以上的介绍,我们发现用Visual C#来处理注册表,其实是一件比较轻松而简单的事情。事情虽然是轻松的,但我也要提醒各位,由于注册表在视窗系统中的重要作用,所以在每一次对注册表进行操作之前,一定要备份,在操作的时候也要非常小心,因为你的每一次的误操作都可能导致你的系统崩溃。

2006/4/24

链表举例

#include <stdlib.h>
#include <memory.h>
#include <stdio.h>
#include <string.h>

struct node /*节点的数据结构*/
{
int num;
char str[20];
struct node *next;
};

struct node *creat(struct node *head);
struct node *insert(struct node *head, char *pstr, int n);
struct node *delet(struct node *head, char *pstr);
void print(struct node *head);

/* * * * * * * * * * * * * * * * * * * * * * * * * * * */
main()
{
/*函数声明*/
struct node *head;
char str[20];
int n;


head = NULL; /*做空表*/
head=creat(head); /*调用函数创建以head 为头的链表*/
print(head); /*调用函数输出节点*/
printf("/n input inserted num,name:/n");
gets(str); /*输入学号*/
n = atoi(str);
gets(str); /*输入姓名*/
head = insert(head, str, n); /*将节点插入链表*/
print(head); /*调用函数输出节点*/
printf("/n input deleted name:/n");
gets(str); /*输入被删姓名*/
head=delet(head,str); /*调用函数删除节点*/
print(head); /*调用函数输出节点*/
return;
}
/* * * * * * * * * * * * * * * * * * * * * */



/* * * 创建链表* * * * * * * * * * * */
struct node *creat(struct node *head)
{
char temp[30];
struct node *p1,*p2;
p1 = p2 = (struct node*) malloc(sizeof(struct node));
printf ("input num, name: /n") ;
printf("exit:double times Enter!/n");
gets(temp);
gets(p1->str);
p1->num = atoi(temp);
p1->next = NULL;
while (strlen(p1->str)>0)
{
if (head==NULL)head=p1;
else p2->next = p1;
p2 = p1;
p1 = (struct node *)malloc(sizeof(struct node));
printf ("input num, name: /n");
printf("exit:double times Enter!/n");
gets(temp) ;
gets(p1->str);
p1->num=atoi(temp);
p1->next=NULL;
}
return head;
}




/* * * * * * * * * * * * * * * * * * * */
/* * * * * * * * * * 插入节点* * * * * * * * * */
struct node *insert(struct node *head, char *pstr, int n)
{
struct node *p1, *p2, *p3;
p1=(struct node*)malloc(sizeof(struct node));
strcpy(p1->str, pstr);
p1->num = n;
p2 = head;
if(head == NULL)
{
head = p1;
p1->next = NULL;
}
else
{
while ((n > p2->num) && (p2->next != NULL))
{
p3 = p2;
p2 = p2->next;
}
if (n <= p2->num)
if (head==p2)
{
head = p1 ;
p1->next = p2;
}
else
{
p3->next = p1;
p1->next = p2;
}
else
{
p2->next = p1;
p1->next = NULL;
}
}
return(head);
}
/* * * * * * * * * * * * * * * * * * * * * * * * */
/* * * * * 删除节点* * * * * * * * * * * * */
struct node *delet(struct node *head, char *pstr)
{
struct node *temp, *p;
temp = head;
if(head == NULL)
printf("/nList is null!/n");
else
{
temp = head;
while((strcmp(temp->str, pstr) != 0) && (temp->next != NULL))
{
p = temp;
temp = temp->next;
}
if(strcmp(temp->str, pstr) == 0)
{
if(temp == head)
{
head = head->next;
free(temp);
}
else
{
p->next = temp->next;
printf("delete string : %s/n", temp->str);
free(temp);
}
}
else printf("/nno find string!/n");
}
return(head);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* * * * * * * * * * 链表各节点的输出* * * * * * * * * */
void print (struct node *head)
{
struct node *temp;
temp = head;
printf("/n output strings:/n");
while(temp!=NULL)
{
printf("/n%d----%s/n", temp->num, temp->str);
temp = temp->next;
}
return;
}
2006/4/18

DataGrid中的DataColumn应用技巧

 生成表格的方法如下:

    DataTable UserInfoTable=new DataTable("用户信息表");
   DataColumn  column;
   column=new DataColumn("选择",System.Type.GetType("System.Boolean"));
   column.ReadOnly=false;
   UserInfoTable.Columns.Add(column);//把这一行加入Table中

   column=new DataColumn("姓名",System.Type.GetType("System.String"));
   column.ReadOnly=true;
   
   UserInfoTable.Columns.Add(column);

   column=new DataColumn("学号",System.Type.GetType("System.String"));
   column.ReadOnly=true;
   UserInfoTable.Columns.Add(column);

   column=new DataColumn("手机号码",System.Type.GetType("System.String"));
   column.ReadOnly=true;
   UserInfoTable.Columns.Add(column);

  int startNo=1398904;

   for(int i=1;i<=10;i++)
   {
    DataRow row=UserInfoTable.NewRow();//生成一行
    row["选择"]=true;
    row["姓名"]="笨蛋"+i.ToString();
    row["学号"]=startNo.ToString();
    row["手机号码"]=startNo.ToString();

    startNo++;
    UserInfoTable.Rows.Add(row);
   }

DataGrid应用

 

DataGrid删除确认及Item颜色交替
http://dev.csdn.net/develop/article/26/26768.shtm

DataGrid常见解决方案(三)--在DataGrid中选择,确认,删除多行复选框列表
http://dev.csdn.net/develop/article/26/26613.shtm

使用DataGrid动态绑定DropDownList
http://dev.csdn.net/develop/article/26/26590.shtm

DataGrid在分页状态下删除纪录的问题
http://dev.csdn.net/develop/article/26/26589.shtm

怎样使用DataGrid控件
http://dev.csdn.net/develop/article/26/26459.shtm

DataGrid Web控件深度历险(3) part1
http://dev.csdn.net/develop/article/26/26249.shtm

DataGrid Web控件深度历险(3) part2
http://dev.csdn.net/develop/article/26/26250.shtm

DataGrid Web控件深度历险(3) part3
http://dev.csdn.net/develop/article/26/26403.shtm

datagrid分页《非控件版》
http://dev.csdn.net/develop/article/26/26002.shtm

datagrid分页问题(前后跳页)《控件版》
http://dev.csdn.net/develop/article/26/26001.shtm

DataGrid Web控件深度历险(2) Part1
http://dev.csdn.net/develop/article/25/25948.shtm

DataGrid Web控件深度历险(1)
http://dev.csdn.net/develop/article/25/25816.shtm

DataGrid Web控件深度历险(2) Part2
http://dev.csdn.net/develop/article/25/25949.shtm

如何在DataGrid里面产生滚动条而不滚动题头
http://dev.csdn.net/develop/article/25/25538.shtm

使用在.net 框架上的DataGrid數據分頁控件
http://dev.csdn.net/develop/article/25/25474.shtm

将某一目录下的所有相同格式的 XML文件绑定到不同的DataGrid
http://dev.csdn.net/develop/article/25/25469.shtm

DataGrid连接Access的快速分页法(5)——实现快速分页
http://dev.csdn.net/develop/article/25/25294.shtm

DataGrid连接Access的快速分页法(4)——动态生成SQL语句
http://dev.csdn.net/develop/article/25/25293.shtm

DataGrid连接Access的快速分页法(3)——SQL语句的选用(降序)
http://dev.csdn.net/develop/article/25/25291.shtm

DataGrid连接Access的快速分页法(2)——SQL语句的选用(升序)
http://dev.csdn.net/develop/article/25/25290.shtm

DataGrid连接Access的快速分页法(1)——需求与现状
http://dev.csdn.net/develop/article/25/25288.shtm

显示DataGrid序号的一个适用的方法
http://dev.csdn.net/develop/article/25/25113.shtm

常见 Datagrid 错误(other)
http://dev.csdn.net/develop/article/24/24460.shtm

XP 风格的可拖动列、可排序、可改变宽度的DataGrid的例子
http://dev.csdn.net/develop/article/24/24441.shtm

利用radio实现Datagrid的单选
http://dev.csdn.net/develop/article/24/24295.shtm

重画系列:DataGridColumnStyle之测试代码
http://dev.csdn.net/develop/article/24/24191.shtm

将OleDbDataAdapter绑定到Winform下的DataGrid
http://dev.csdn.net/develop/article/24/24148.shtm

去除Asp:DataGrid中无用ViewState的方法(2)
http://dev.csdn.net/develop/article/23/23671.shtm

去除Asp:DataGrid中无用ViewState的方法(1)
http://dev.csdn.net/develop/article/23/23670.shtm

datagrid保存时无法提交更新的问题
http://dev.csdn.net/develop/article/23/23376.shtm

将DBF,XLS,XML,MDB文件导入C#DataGrid的方法
http://dev.csdn.net/develop/article/23/23036.shtm

动态创建DataGrid的模版列
http://dev.csdn.net/develop/article/22/22942.shtm

DataTable中数据记录的统计
http://dev.csdn.net/develop/article/22/22710.shtm

(ASP.NET)用动态属性和DataView实现DataGrid的双向排序
http://dev.csdn.net/develop/article/22/22513.shtm

如何同步滚动两个相同的DataGrid
http://dev.csdn.net/develop/article/22/22438.shtm

asp.net中DataGrid性能测试
http://dev.csdn.net/develop/article/22/22405.shtm

让Asp.NET的DataGrid可排序、可选择、可分页
http://dev.csdn.net/develop/article/22/22297.shtm

DataGrid传统分页方式
http://dev.csdn.net/develop/article/21/21997.shtm

在后代码里创建DataGrid控件
http://dev.csdn.net/develop/article/21/21934.shtm

实现类似Windows资源管理器的DataGrid
http://dev.csdn.net/develop/article/21/21933.shtm

ASP.net中的Datagrid自定义分页功能
http://dev.csdn.net/develop/article/21/21875.shtm

在Pocket PC应用程序中使用DataGrid控件
http://dev.csdn.net/develop/article/21/21844.shtm

创建可拖动列的DataGrid(2)
http://dev.csdn.net/develop/article/21/21594.shtm

创建可拖动列的DataGrid
http://dev.csdn.net/develop/article/21/21593.shtm

DataGrid和CheckBox的混合使用
http://dev.csdn.net/develop/article/21/21585.shtm

利用Session纪录datagrid模板列中CheckBox的状态
http://dev.csdn.net/develop/article/21/21294.shtm

DataGrid模板列中TextBox的焦点相应键盘事件
http://dev.csdn.net/develop/article/21/21290.shtm

给DataGrid添加确定删除的功能
http://dev.csdn.net/develop/article/20/20892.shtm

如何给DataGrid添加自动增长列
http://dev.csdn.net/develop/article/20/20887.shtm

如何利用RadioButtonList实现datagrid列的单选
http://dev.csdn.net/develop/article/20/20789.shtm

锦上添花DataGrid!
http://dev.csdn.net/develop/article/20/20770.shtm

ASP.Net WebMatrix中Datagrid使用模板列对数据显示进行排版
http://dev.csdn.net/develop/article/20/20576.shtm

格式化 DataGrid 输出
http://dev.csdn.net/develop/article/20/20307.shtm

如何实现单击在DATALIST(DATAGRID)的HEADER加入的CHECKBOX,进行DATALIST(DATAGRID)中的CHECKBOX列全选或全不选
http://dev.csdn.net/develop/article/20/20290.shtm

向datagrid中加横向 纵向的合计 (在datatable中实现,datatable间倒数据)
http://dev.csdn.net/develop/article/20/20221.shtm

基于ADO+Adodc控件+DataGrid控件制作的一个数据库编辑程序(完整原程序)
http://dev.csdn.net/develop/article/19/19600.shtm

如何实现自定义及自动逐页打印DataGrid显示的内容
http://dev.csdn.net/develop/article/19/19353.shtm

合并datagrid中内容相同的单元格
http://dev.csdn.net/develop/article/19/19122.shtm

创建固定表头、表格体滚动的DataGrid
http://dev.csdn.net/develop/article/18/18972.shtm

创建跨多列、多行表头的DataGrid
http://dev.csdn.net/develop/article/18/18971.shtm

在DataGrid中添加一个合计字段
http://dev.csdn.net/develop/article/18/18856.shtm

在DataGrid里添加确认删除的对话框
http://dev.csdn.net/develop/article/18/18838.shtm

为DataGrid添加自动编号功能
http://dev.csdn.net/develop/article/18/18783.shtm

格式化DataGrid的例子【将数据源中的0,1值转换成实际的文字】
http://dev.csdn.net/develop/article/18/18782.shtm

Henry手记—Web Form中的Datagrid的自定义分页
http://dev.csdn.net/develop/article/18/18760.shtm

创建完全可编辑的 DataGrid
http://dev.csdn.net/develop/article/18/18744.shtm

DataGrid控件通用打印类
http://dev.csdn.net/develop/article/18/18711.shtm

为DataGrid添加CheckBox控件
http://dev.csdn.net/develop/article/18/18615.shtm

VB.NET中关于DataGrid颜色的自定义
http://dev.csdn.net/develop/article/18/18512.shtm

在DataGrid快速添加新行
http://dev.csdn.net/develop/article/18/18487.shtm

Henry手记-Datagrid事件响应(二)
http://dev.csdn.net/develop/article/18/18315.shtm

datagrid技巧之一:代码控制选中行的颜色
http://dev.csdn.net/develop/article/17/17852.shtm

在C#里实现DATAGRID的打印预览和打印
http://dev.csdn.net/develop/article/17/17851.shtm

Binding a DataGrid to an ADO Recordset
http://dev.csdn.net/develop/article/17/17850.shtm

Creating DataGrid Templated Columns Dynamically - Part II
http://dev.csdn.net/develop/article/17/17846.shtm

Creating DataGrid Templated Columns Dynamically - Part I
http://dev.csdn.net/develop/article/17/17845.shtm

DataTable,DataView和DataGrid中一些容易混淆的概念
http://dev.csdn.net/develop/article/17/17840.shtm

动态的管理ASP.NET DataGrid数据列
http://dev.csdn.net/develop/article/17/17599.shtm

Henry手记-Datagrid键盘事件响应(二)
http://dev.csdn.net/develop/article/17/17424.shtm

Henry手记—从Datagrid的标题居中说起
http://dev.csdn.net/develop/article/17/17053.shtm

用嵌套的DataGrid实现主从式表的显示
http://dev.csdn.net/develop/article/16/16960.shtm

DataGrid中嵌套使用Repeater
http://dev.csdn.net/develop/article/16/16834.shtm

Henry手记 - Datagrid键盘事件响应(一)
http://dev.csdn.net/develop/article/16/16572.shtm

2006/4/7

VC中的数据类型


VC的数据类型
点击数:221    发布日期:2006-3-27 20:27:00  
【评论】   【打印】  【编程爱好者论坛】  【关闭】

 
我们先定义一些常见类型变量借以说明
int i = 100;
long l = 2001;
float f=300.2;
double d=12345.119;
char username[]="女侠程佩君";
char temp[200];
char *buf;
CString str;
_variant_t v1;
_bstr_t v2;
一、其它数据类型转换为字符串
短整型(int)
itoa(i,temp,10);///将i转换为字符串放入temp中,最后一个数字表示十进制
itoa(i,temp,2); ///按二进制方式转换
长整型(long)
ltoa(l,temp,10);
二、从其它包含字符串的变量中获取指向该字符串的指针
CString变量
str = "2008北京奥运";
buf = (LPSTR)(LPCTSTR)str;
BSTR类型的_variant_t变量
v1 = (_bstr_t)"程序员";
buf = _com_util::ConvertBSTRToString((_bstr_t)v1);
三、字符串转换为其它数据类型
strcpy(temp,"123");
短整型(int)
i = atoi(temp);
长整型(long)
l = atol(temp);
浮点(double)
d = atof(temp);
四、其它数据类型转换到CString
使用CString的成员函数Format来转换,例如:
整数(int)
str.Format("%d",i);
浮点数(float)
str.Format("%f",i);
字符串指针(char *)等已经被CString构造函数支持的数据类型可以直接赋值
str = username;
五、BSTR、_bstr_t与CComBSTR
CComBSTR、_bstr_t是对BSTR的封装,BSTR是指向字符串的32位指针。
char *转换到BSTR可以这样: BSTR b=_com_util::ConvertStringToBSTR("数据");///使用前需要加上头文件comutil.h
反之可以使用char *p=_com_util::ConvertBSTRToString(b);
六、VARIANT 、_variant_t 与 COleVariant
VARIANT的结构可以参考头文件VC98/Include/OAIDL.H中关于结构体tagVARIANT的定义。
对于VARIANT变量的赋值:首先给vt成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:
VARIANT va;
int a=2001;
va.vt=VT_I4;///指明整型数据
va.lVal=a; ///赋值
对于不马上赋值的VARIANT,最好先用Void VariantInit(VARIANTARG FAR* pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:
unsigned char bVal; VT_UI1
short iVal; VT_I2
long lVal; VT_I4
float fltVal; VT_R4
double dblVal; VT_R8
VARIANT_BOOL boolVal; VT_BOOL
SCODE scode; VT_ERROR
CY cyVal; VT_CY
DATE date; VT_DATE
BSTR bstrVal; VT_BSTR
IUnknown FAR* punkVal; VT_UNKNOWN
IDispatch FAR* pdispVal; VT_DISPATCH
SAFEARRAY FAR* parray; VT_ARRAY|*
unsigned char FAR* pbVal; VT_BYREF|VT_UI1
short FAR* piVal; VT_BYREF|VT_I2
long FAR* plVal; VT_BYREF|VT_I4
float FAR* pfltVal; VT_BYREF|VT_R4
double FAR* pdblVal; VT_BYREF|VT_R8
VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL
SCODE FAR* pscode; VT_BYREF|VT_ERROR
CY FAR* pcyVal; VT_BYREF|VT_CY
DATE FAR* pdate; VT_BYREF|VT_DATE
BSTR FAR* pbstrVal; VT_BYREF|VT_BSTR
IUnknown FAR* FAR* ppunkVal; VT_BYREF|VT_UNKNOWN
IDispatch FAR* FAR* ppdispVal; VT_BYREF|VT_DISPATCH
SAFEARRAY FAR* FAR* pparray; VT_ARRAY|*
VARIANT FAR* pvarVal; VT_BYREF|VT_VARIANT
void FAR* byref; VT_BYREF
_variant_t是VARIANT的封装类,其赋值可以使用强制类型转换,其构造函数会自动处理这些数据类型。
例如:
long l=222;
ing i=100;
_variant_t lVal(l);
lVal = (long)i;
COleVariant的使用与_variant_t的方法基本一样,请参考如下例子:
COleVariant v3 = "字符串", v4 = (long)1999;
CString str =(BSTR)v3.pbstrVal;
long i = v4.lVal;
七、其它
对消息的处理中我们经常需要将WPARAM或LPARAM等32位数据(DWORD)分解成两个16位数据(WORD),例如:
LPARAM lParam;
WORD loValue = LOWORD(lParam);///取低16位
WORD hiValue = HIWORD(lParam);///取高16位
对于16位的数据(WORD)我们可以用同样的方法分解成高低两个8位数据(BYTE),例如:
WORD wValue;
BYTE loValue = LOBYTE(wValue);///取低8位
BYTE hiValue = HIBYTE(wValue);///取高8位


CString str;int i;float f;
//将字符串转换为整型
str="123";
i=atoi(str);
//将字符串转换为单精度型
str="123.123";
f=atof(str);
//将整型和单精度型转换为字符串。
i=123;
f=123.123;
 
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页