vc问答
  问:(1) 在Windows 98中可以设置一个窗口的标题栏有颜色过渡的效果,比如由蓝色过渡到黑色,Windows 95就不行。我想知道,应该如何编程才能实现这种美化程序的效果呢?最好能给出一个范例。 (2) 还是在Windows 95中,我觉得以前Windows 3.1中带有CTL3D效果的窗体很漂亮,能不能在Windows 95的程序中也加入这种效果,使得窗体的框架丰满一些呢?
  
答: (1) 我以前发表在本刊上的一篇文章曾经讨论过自行绘制标题栏的问题,但是那篇文章的重点是如何设置自己的字体。如果使自己的标题栏有颜色过渡的效果,还需要处理其他一些问题。解决这个问题的核心仍然是处理客户区和非客户区的重绘问题。在自己的WndProc中,需要对WM_NCPAINT、WM_ACTIVATE、WM_DEACTIVATE和WM_ERASEBKGND四个消息进行处理。判断当前窗口的激活状态就可以绘制自己的标题栏,从而加上想要的效果。在窗口的WndProc过程中这样写:
  switch( uMsg ) {
  case WM_NCPAINT:
  case WM_ACTIVATE:
  case WM_ACTIVATEAPP:
  case WM_ERASEBKGND:
   DefWindowProc( hDlg, uMsg, wParam, lParam );
   DrawTitleBar( hDlg );
   break;
  }
  先调用DefWindowProc的原因是我们需要让Windows先把标准的窗体边框画好。DrawTitleBar过程负责绘制自己的标题栏。
  void DrawTitleBar( HWND hWnd )
  {
   RECT r;
   WORD Left, Top, TitleHeight, TitleWeight, IconWeight;
   HDC MemDC;
   COLORREF SysTitleBkg, SysTitleClr;
   HFONT HF;
  
   MemDC = GetWindowDC( hWnd );
   GetClientRect( hWnd, &r );
   IconWeight = GetSystemMetrics( SM_CXSIZE );
   Left = 1;
   Top = 1;
   TitleHeight = GetSystemMetrics( SM_CYCAPTION );
   TitleWeight = r.right - r.left;
   SysTitleClr = GetSysColor( COLOR_CAPTIONTEXT );
   if( GetActiveWindow() == hWnd )
   SysTitleBkg = 0xf0a080;
   else
   SysTitleBkg = 0;
   DrawGradientBar( MemDC, 0x800000, SysTitleBkg, Left + 1, Top + 1,
   TitleWeight + 1, TitleHeight + 1 );
   SetTextColor( MemDC, SysTitleClr );
   SetBkColor( MemDC, SysTitleBkg );
   SetBkMode( MemDC, TRANSPARENT );
   HF = CreateFont( 10, 0, 0, 0, FW_BOLD, 0, 0, 0,
   ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
   DEFAULT_QUALITY, DEFAULT_PITCH, "MS Sans Serif" );
   SelectObject( MemDC, HF );
   TextOut( MemDC, 4, Top + ( TitleHeight - 10 ) / 2, APPNAME, strlen( APPNAME ) );
   DeleteObject( HF );
   DeleteObject( MemDC );
  }
  其中,APPNAME是程序的标题栏内容。在调用TextOut函数时要注意,一定要把背景设置成透明色,否则过渡效果就表现不出来了。最后我们要实现如何绘制带有颜色过渡效果的背景。实现的关键是如何调配RGB三原色的搭配。另外,绘制的速度也很重要。这里给出的代码采用每隔几个像素进行一次颜色渐变的方法来实现。
  void DrawGradientBar( HDC hdc, COLORREF co1, COLORREF co2, int x, int y, int cx, int cy )
  {
   int r = GetRValue( co1 );
   int g = GetGValue( co1 );
   int b = GetBValue( co1 );
   int n, m;
   int r2 = GetRValue( co2 );
   int g2 = GetGValue( co2 );
   int b2 = GetBValue( co2 );
   RECT rect;
   HBRUSH hbr;
   rect.left = x;
   rect.right = x + GRADLEVEL;
   rect.top = y;
   rect.bottom = y + cy;
   m = x + cx;
   if( cx <= 0 || cy <= 0 )
   return;
   for( n = cx; n > 0; n -= GRADLEVEL ) {
   hbr = CreateSolidBrush( RGB( r, g, b ) );
   FillRect( hdc, &rect, hbr );
   DeleteObject( hbr );
   rect.left += GRADLEVEL;
   rect.right += GRADLEVEL;
   if( rect.right > m )
   rect.right = m;
   r += ( r2 - r + n / 2 ) / n * GRADLEVEL;
   g += ( g2 - g + n / 2 ) / n * GRADLEVEL;
   b += ( b2 - b + n / 2 ) / n * GRADLEVEL;
   }
  }
  其中,GRADLEVEL是颜色渐变的等级:等级越小,绘制出来的背景就越接近于自然过渡,然而速度越慢;反之则可以清楚地看到过渡的过程,同时速度也越快。你可以根据实际需求来决定取舍。
  (2) CTL3D效果一般也是通过上面的方法来进行绘制的。下面的代码也位于WndProc过程中,当遇到上述四个消息时加厚窗体的框架。
  BOOL FAR PASCAL MyWndProc(HWND hDlg, WORD iMessage, WORD wParam,
  LONG lParam)
  {
   HDC hDlgdc;
   HPEN hWhitePen, hOld;
   static LOGPEN lpDarkGray = { PS_SOLID, 1, 1, RGB( 128, 128, 128 ) };
   static LOGPEN lpLightGray = { PS_SOLID, 1, 1, RGB( 192, 192, 192 ) };
   RECT rc, rc1;
   HBRUSH hGrayBrush;
   int i, TitleHeight, cyMenu, cxDlgFrame, cyDlgFrame;
   static HBRUSH hbrGray;
   switch( iMessage )
   {
   case WM_ACTIVATE:
   case WM_NCPAINT:
   case WM_ERASEBKGND:
   case WM_PAINT:
   hDlgdc = GetWindowDC(hDlg); // 获取窗口DC
   GetWindowRect(hDlg, &rc); // 获取窗口矩形
   rc.right -= rc.left; // 计算宽度
   rc.bottom -= rc.top; // 计算高度
   hWhitePen = GetStockObject( WHITE_PEN ); // 白色 PEN
   hOld = SelectObject( hDlgdc, hWhitePen ); // 设置 PEN
   MoveTo( hDlgdc, 1, 1 ); //
   LineTo( hDlgdc, rc.right - 2, 1 ); // 画上边框
   MoveTo( hDlgdc, 1, 1 ); //
   LineTo(hDlgdc, 1, rc.bottom - 2); // 画左边框
   TitleHeight = GetSystemMetrics( SM_CYCAPTION );
   cyMenu = GetSystemMetrics( SM_CYMENU );
   cxDlgFrame = GetSystemMetrics( SM_CXDLGFRAME );
   cyDlgFrame = GetSystemMetrics( SM_CYDLGFRAME );
   MoveTo( hDlgdc, cxDlgFrame + 1, TitleHeight + cyDlgFrame - 1 );
   LineTo( hDlgdc, rc.right - cxDlgFrame - 2,
   TitleHeight + cyDlgFrame - 1 ); // 画 TITLE 下边框
   LineTo( hDlgdc, rc.right - cxDlgFrame - 2,
   cyDlgFrame ); // 画 TITLE 右边框
   hWhitePen = CreatePenIndirect( &lpDarkGray ); // 深灰色 PEN
   SelectObject( hDlgdc, hWhitePen );
   myRectangle( hDlgdc, 0, 0, rc.right -2, rc.bottom - 2 );
   MoveTo( hDlgdc, cxDlgFrame + 1, TitleHeight + cyDlgFrame - 1 );
   LineTo( hDlgdc, cxDlgFrame + 1, cyDlgFrame); //画TITLE左边框
   LineTo( hDlgdc, rc.right - cxDlgFrame - 2, cyDlgFrame); //画TITLE上边框
   SelectObject( hDlgdc, hOld );
   DeleteObject( hWhitePen );
   hWhitePen = CreatePenIndirect( &lpLightGray );
   SelectObject( hDlgdc, hWhitePen );
   myRectangle( hDlgdc, 2, 2, rc.right - 3, rc.bottom - 3 );
   for( i = 0; i < cxDlgFrame - 2; i ++ ) { // 填充边框
   myRectangle(hDlgdc, i + 3, i + 2, rc.right - i - 4, rc.bottom - i - 4);
   }
   SelectObject( hDlgdc, hOld );
   DeleteObject( hWhitePen );
   ReleaseDC( hDlg, hDlgdc );
   return FALSE;
   case WM_CTLCOLOR:
   switch( HIWORD( lParam ) ) {
   case CTLCOLOR_DLG:
   return (LRESULT)hbrGray;
   case CTLCOLOR_STATIC:
   SetTextColor((HDC)wParam, RGB( 0, 0, 255 ) );
   SetBkColor((HDC)wParam, RGB( 192, 192, 192) );
   return (LRESULT)hbrGray;
   }
   return NULL;
   case WM_INITDIALOG:
   hbrGray = CreateSolidBrush( RGB( 192, 192, 192 ) );
   GetWindowRect( GetDesktopWindow(), &rc );
   GetWindowRect( hDlg, &rc1 );
   rc1.left = ( rc.right / 2 ) - ( rc1.right - rc1.left ) / 2;
   rc1.top = ( rc.bottom / 2 ) - ( rc1.bottom - rc1.top ) / 2;
   SetWindowPos( hDlg, NULL, rc1.left, rc1.top, 0, 0,
  SWP_NOSIZE | SWP_NOZORDER );
   if( wParam == GetDlgItem( hDlg, ID_INPUT_PASS )) {
   return TRUE; //设置热点
   }
   return FALSE;
   case WM_COMMAND:
   switch( wParam ) {
   case IDCANCEL:
   EndDialog( hDlg, TRUE );
   DeleteObject( hbrGray );
   return FALSE;
   }
   break;
   }
   return FALSE;
  }
  《电子与电脑》99年2期 问与答


问:我由于工作需要,有几个程序必须使用汇编语言来编制,如果是DOS程序倒也容易,但是项目要求这些程序工作在Windows 95或者Windows NT下,也就是说,我必须用汇编语言来编制Windows应用程序。虽然我有好几年的Windows下C程序开发经验,但是用汇编写Win32程序我从来没试过,也不知道从何下手,我想我缺少的只是一个通用的Win32汇编程序的框架,能否满足我的要求?
答:确实,有很多程序是不得不用汇编语言编写的,诸如各种底层开发工具等等。我首先建议您自己评估自己的项目要求,如果不是特别要求速度(算法上已经优化过,唯一的可能是从汇编级再次优化),我仍然认为目前C已经足够使用,而且就Visual C++来说,它的编译代码质量相当高。另外,汇编语言写出的Win32程序一般只能够使用SoftICE等专业调试工具来调试,即使这样,代码也可能存在许多安全问题上的漏洞。如果您确实需要使用汇编语言来写程序,我下面就通过讲述一个最简单的Win32汇编程序来让您了解一下一个Win32汇编程序的框架。
  我们这个例子程序的功能是判断当前Windows版本是否是测试版,基本方法是使用GetSystemMetrics函数,配合SM_DEBUG参数调用。下面是汇编程序。注意几点:
  
  第一,所有需要使用的Win32 API函数必须在程序一开头时声明为外部函数;第二,所有需要引用的常量应该用equ关键字来定义;第三,编译模式应该为flat, stdcall模式;第四,参数调用顺序一般按照Pascal习惯。
  
  另外,这个例子程序是使用TASM 5.0编制的,还需要一个编译BAT文件,也附在后面。您需要把TASM32.EXE和TLINK32.EXE的路径换成您自己的。
  
  [MAKE.BAT]
  @echo off
  echo.
  echo IFDB Compiler - Written by Jiang Hong.
  echo.
  echo Compiling ......
  echo.
  d:\tasm5\bin\tasm32 /ml /m3 /z /q IFDB
  d:\tasm5\bin\tlink32 -x /Tpe /ap /c IFDB,IFDB,, import32.lib
  for %%a in (*.obj) do del %%a
  for %%a in (*.map) do del %%a
  echo.
  echo Finished.
  echo.
  
  [IFDB.ASM]
   .386p
   locals
   jumps
   .model flat, stdcall
  
  SM_DEBUG equ 22
  
   ;
   ; Function of Win32 API to be imported
   ;
   extrn ExitProcess : proc
   extrn GetSystemMetrics : proc
   extrn MessageBeep : proc
   extrn GetStdHandle : proc
   extrn WriteConsoleA : proc
  
   .data
  
  ConHandle dd 0
  cchWritten dd 0
  
  Msg1 db 'IFDB ?Judge if Windows 95/98/NT is Debug Version/Mode', 0dh, 0ah
   db 'Written by Jiang Hong, Egis Computing.', 0dh, 0ah, 0dh, 0ah
   db 'This is a '
  Msg1Len equ $ - Msg1
  
  Msg2 db ' of Win32.', 0dh, 0ah
  Msg2Len equ $ - Msg2
  
  DbgInfo db 'Debug version'
  DbgInfoLen equ $ - DbgInfo
  
  RelInfo db 'Release version'
  RelInfoLen equ $ - RelInfo
  
  ; ----------------------------------------------------------------------------
  
   .code
  
  InitConsole proc near
   pusha
   push -11 ; nStdHandle [ (dword) -11 ]
   call GetStdHandle
   cmp eax, -1
   je Console_Error
   mov [ConHandle], eax
   popa
   ret
  Console_Error:
   popa
   call MessageBeep
   push LARGE -1
   call ExitProcess
  InitConsole endp
  WriteConsole macro oText, oNum_of_Chars
   pusha
   pushf
   push 0
   push offset cchWritten
   push oNum_of_Chars
   push oText
   push [ConHandle]
   call WriteConsoleA
   popf
   popa
   endm
  
  Main:
   ; Initialize Win32 console interface
   call InitConsole
  
   ; Print copyright information
   WriteConsole
   ; Get debugging flag via SM_DEBUG
   ; Zero=Release mode, Non-Zero=Debug mode
   push SM_DEBUG
   call GetSystemMetrics
   test eax, eax
   jz Rel
   ; This is a debugging version
   WriteConsole
   jmp Exit
  Rel: ; This is a release version
   WriteConsole
  Exit:
   WriteConsole
   ; Exit
   push LARGE -1
   call ExitProcess
   end Main