SetWindowSubclass将ANSI窗口更改为UNICODE。

7

SetWindowSubClass()是否可以将ANSI窗口转换为UNICODE窗口?我在文档和网络上没有找到任何关于此行为的信息。

我创建了一个测试应用程序(完整源代码),只是为了说明SetWindowSubclass(我相信)会将受影响窗口的类型从ANSI更改为UNICODE,但实际上不应该IsWindowUnicode()确认了这种变化。

 program TwoWaySubclassing;

 {$apptype gui}
 {$R Generic.res}                          

 {
 { I created this test application just to illustrate how SetWindowSubclass()
 { changes -- I believe -- the type of the affected window from ANSI to UNICODE,
 { as it shouldn't! IsWindowUnicode() confirms that.
 {
 { The Delphi 7 (all ANSI) application has 2 edit controls:
 {   1. The smaller, which is subclassed in 2 switchable ways (called Modes).
 {   2. The bigger, like a memo, not subclassed. Just for dumping info.
 {   3. A button for switching between modes, on-the-fly.
 {
 { The default subclassing Mode uses SetWindowLong (the classic way).
 { When pressing the button, the edit control is subclassed via SetWindowSubclass.
 { Pressing it again brings the edit control back to the default SetWindowLong mode.
 {
 { The main window (and all child controls) are created using the ANSI version
 { of the API procedure, so the message handler should receive, in "lParam",
 { a pointer to an ANSI text (along with the wm_SetText message), always!
 {
 { The problem is that's not happening when the edit control is subclassed using
 { the SetWindowSubclass mode! SetWindowSubclass() simply changes the window
 { from ANSI to UNICODE and starts sending a PWideChar(lParam) rather than the
 { expected PAnsiChar(lParam).
 {
 { Once back to the default SetWindowLong mode, the window becomes ANSI again!
 { Just run the application and try switching between modes. Look carefully at the
 { detailed info shown in the bigger edit control.
 {
 { Screenshots:
 {   1. http://imgh.us/mode1.png
 {   2. http://imgh.us/mode2.png
 {
 { Environment:
 {   Windows 7 32-bit
 {   Delphi 7 (all-ANSI)
 {
 { Regards,
 {   Paulo França Lacerda
 }

 uses
   Windows,
   Messages,
   SysUtils;

 type
   UINT_PTR  = Cardinal;
   DWORD_PTR = Cardinal;

   TSubClassProc = function (hWnd:HWND; uMsg:UINT; wParam:WPARAM; lParam:LPARAM; uIdSubclass:UINT_PTR; dwRefData:DWORD_PTR) :LRESULT; stdcall;

   TSubMode = (
     subSetWindowLong,
     subSetWindowSubclass);

 const
   LtBool    :Array[Boolean]  of String = ('False', 'True');
   LtSubMode :Array[TSubMode] of String = ('SetWindowLong', 'SetWindowSubclass');

   strTextUsingPAnsiChar = 'ANSI Text in PAnsiChar(lParam)';
   strTextUsingPWideChar = 'UNICODE Text in PWideChar(lParam)';

 const
   cctrl = Windows.comctl32;

 function SetWindowSubclass    (hWnd:Windows.HWND; pfnSubclass:TSubClassProc; uIdSubclass:UINT_PTR; dwRefData:DWORD_PTR) :BOOL; stdcall; external cctrl name 'SetWindowSubclass';
 function RemoveWindowSubclass (hWnd:Windows.HWND; pfnSubclass:TSubClassProc; uIdSubclass:UINT_PTR) :BOOL;                      stdcall; external cctrl name 'RemoveWindowSubclass';
 function DefSubclassProc      (hWnd:HWND; uMsg:UINT; wParam:WPARAM; lParam:LPARAM) :LRESULT;                                   stdcall; external cctrl name 'DefSubclassProc';

 var
   wc  :TWndClass;
   Msg :TMsg;

   hButton :HWnd;
   hEdit   :HWnd;
   hEdit2  :HWnd;
   hFont   :HWnd;
   hFont2  :HWnd;

   hMainHandle :HWnd;
   swl_OldProc :Pointer;  // Default Procedure for Subclassing #1 (via SetWindowLong)
   SubMode   :TSubMode;


 procedure Release_Resources;
 begin
   DestroyWindow (hButton);  hButton := 0;
   DestroyWindow (hEdit);    hEdit   := 0;
   DestroyWindow (hEdit2);   hEdit2  := 0;
   DeleteObject  (hFont);    hFont   := 0;
   DeleteObject  (hFont2);   hFont2  := 0;
 end;

 procedure MsgBox (S:String);
 begin
   MessageBox (hMainHandle, PChar(S), 'Information', mb_Ok or mb_IconInformation);
 end;

 procedure Reveal_Text (lParam:LPARAM);
 const
   lf  = #13#10;
   lf2 = lf+lf;
 var
   S :String;
   AnsiTxt :String;
   UnicTxt :String;
   Remarks :Array[1..3] of String;
 begin
   if   IsWindowUnicode(hEdit)
   then Remarks[1] := '    (Man! SetWindowSubclass changed it to Unicode!!)'
   else Remarks[1] := '    (great! as designed)';

   AnsiTxt := PAnsiChar(lParam);

   if  (Length(AnsiTxt) = 1)
   then Remarks[2] := '    (text is obviously truncated)'
   else Remarks[2] := '    (text is healthy and is ANSI, as it should)';

   UnicTxt := PWideChar(lParam);

   if  (Pos('?',UnicTxt) > 0)
   then Remarks[3] := '    (text is obviously garbaged)'
   else Remarks[3] := '    (text is healthy, but I want it to be ANSI)';

   S :=
          'Subclassed using: '
     +lf +'    '+LtSubMode[SubMode]+'()'
     +lf2+ 'IsUnicodeWindow(hEdit)? '
     +lf +'    '+LtBool[IsWindowUnicode(hEdit)]
     +lf +       Remarks[1]
     +lf2+'PAnsiChar(lParam):'
     +lf +'    "'+PAnsiChar(lParam)+'"'
     +lf +       Remarks[2]
     +lf2+ 'PWideChar(lParam):'
     +lf +'    "'+PWideChar(lParam)+'"'
     +lf +       Remarks[3];

   SetWindowText (hEdit2, PChar(S));
 end;

 function swl_EditWndProc (hWnd:HWnd; uMsg:UInt; wParam:WParam; lParam:LParam) :LResult; stdcall;
 begin
   Result := CallWindowProc (swl_OldProc, hWnd, uMsg, wParam, lParam);
   if (uMsg = wm_SetText) then Reveal_Text(lParam);
 end;

 function sws_EditWndProc (hWnd:HWND; uMsg:UINT; wParam:WPARAM; lParam:LPARAM; uIdSubclass:UINT_PTR; dwRefData:DWORD_PTR) :LRESULT; stdcall;
 begin
   Result := DefSubclassProc (hWnd, uMsg, wParam, lParam);
   if (uMsg = wm_SetText) then Reveal_Text(lParam);
 end;

 procedure do_SetWindowSubclass;
 begin
   if   not SetWindowSubclass (hEdit, @sws_EditWndProc, 1, dword_ptr($1234{whatever}))
   then RaiseLastOSError;

   SubMode := subSetWindowSubclass;
 end;

 procedure undo_SetWindowSubclass;
 begin
   if   not RemoveWindowSubclass (hEdit, @sws_EditWndProc, 1)
   then RaiseLastOSError;

   SubMode := subSetWindowLong;  // restored
 end;

 function AppWindowProc (hWnd:HWnd; uMsg:UInt; wParam:WParam; lParam:LParam) :LResult; stdcall;
 begin
   case uMsg of
     wm_Command:
     begin
       if (lParam = hButton) then
       case SubMode of
         subSetWindowLong:
         begin
           do_SetWindowSubclass;  // now using SetWindowSubclass()
           SetWindowText (hEdit,   PChar(strTextUsingPWideChar));
           SetWindowText (hButton, PChar('Switch back to SetWindowLong mode'));
         end;

         subSetWindowSubclass:
         begin
           undo_SetWindowSubclass;  // back to SetWindowLong()
           SetWindowText (hEdit,   PChar(strTextUsingPAnsiChar));
           SetWindowText (hButton, PChar('Switch to SetWindowSubclass mode'));
         end;
       end;
     end;

     wm_Destroy:
     begin
       Release_Resources;
       PostQuitMessage (0);
       Exit;
     end;
   end;

   Result := DefWindowProc (hWnd, uMsg, wParam, lParam);
 end;

 var
   W,H :Integer;

 begin
   wc.hInstance     := hInstance;
   wc.lpszClassName := 'ANSI_Wnd';
   wc.Style         := cs_ParentDC;
   wc.hIcon         := LoadIcon(hInstance,'MAINICON');
   wc.lpfnWndProc   := @AppWindowProc;
   wc.hbrBackground := GetStockObject(white_brush);
   wc.hCursor       := LoadCursor(0,IDC_ARROW);

   RegisterClass(wc);  // ANSI (using Delphi 7, so all Windows API is mapped to ANSI).

   W := 500;
   H := 480;

   hMainHandle := CreateWindow (  // ANSI (using Delphi 7, so all Windows API is mapped to ANSI).
     wc.lpszClassName,'2-Way Subclassing App',
     ws_OverlappedWindow or ws_Caption or ws_MinimizeBox or ws_SysMenu or ws_Visible,
     ((GetSystemMetrics(SM_CXSCREEN)-W) div 2),  //   vertically centered in screen
     ((GetSystemMetrics(SM_CYSCREEN)-H) div 2),  // horizontally centered in screen
     W,H,0,0,hInstance,nil);

   // create the fonts
   hFont := CreateFont (-14,0,0,0,0,0,0,0, default_charset, out_default_precis, clip_default_precis, default_quality, variable_pitch or ff_swiss, 'Tahoma');
   hFont2:= CreateFont (-14,0,0,0,0,0,0,0, default_charset, out_default_precis, clip_default_precis, default_quality, variable_pitch or ff_swiss, 'Courier New');

   // create the edits
   hEdit :=CreateWindowEx (WS_EX_CLIENTEDGE,'EDIT','some text', WS_VISIBLE or WS_CHILD or ES_LEFT or ES_AUTOHSCROLL,                10,35,W-40, 23,hMainHandle,0,hInstance,nil);
   hEdit2:=CreateWindowEx (WS_EX_CLIENTEDGE,'EDIT','details',   WS_VISIBLE or WS_CHILD or ES_LEFT or ES_AUTOHSCROLL or ES_MULTILINE,10,72,W-40,300,hMainHandle,0,hInstance,nil);
   SendMessage(hEdit, WM_SETFONT,hFont, 0);
   SendMessage(hEdit2,WM_SETFONT,hFont2,0);

   // create the button
   hButton:=CreateWindow ('Button','Switch to SetWindowSubclass mode', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT, 90,H-95,290,32,hMainHandle,0,hInstance,nil);
   SendMessage(hButton,WM_SETFONT,hFont,0);

   // subclass the Edit using the default method.
   swl_OldProc := Pointer(GetWindowLong(hEdit,GWL_WNDPROC));
   SetWindowLong (hEdit,GWL_WNDPROC,Longint(@swl_EditWndProc));

   SubMode := subSetWindowLong;
   SetWindowText (hEdit, PChar(strTextUsingPAnsiChar));

   // message loop
   while GetMessage(Msg,0,0,0) do
   begin
     TranslateMessage(Msg);
     DispatchMessage(Msg);
   end;
 end.

该应用程序有两个编辑控件:

  1. 较小的一个,可以通过两种可切换的方式进行子类化(这里称为模式)。
  2. 较大的一个,像备忘录一样,没有进行子类化。只是用于转储信息。

还有一个按钮用于在模式之间切换。

默认的子类化模式使用 SetWindowLong()(经典方式):

SetWindowLong mode

在Delphi 2007及更早版本中,主窗口(以及所有子控件)都是使用Win32 API过程的ANSI版本创建的,因此(子类化控件的)消息处理程序应始终接收ANSI文本(以及WM_SETTEXT消息)!
问题在于,当使用SetWindowSubclass()子类化编辑控件时,这种情况并未发生!SetWindowSubClass()将窗口从ANSI更改为UNICODE,并开始接收Unicode文本,而不是预期的ANSI文本。
按下按钮通过SetWindowSubclass()子类化编辑控件:

SetWindowSubclass mode

按下按钮后,通过 SetWindowLong() 再次对编辑控件进行子类化。

一旦回到 SetWindowLong() 模式,编辑控件将自动再次接收 ANSI 文本:

SetWindowLong mode

只需运行应用程序并尝试在模式之间切换。仔细查看在较大的编辑控件中显示的详细信息。

为了明确起见:我认为这是微软的一个错误。但是,如果这是一个“功能”,是否有人能够引导我到相应的文档?我无法在任何地方找到它。


1
你是调用 SetWindowSubClassA 还是 SetWindowSubClassW ? - Jonathan Potter
2
@JonathanPotter:我认为没有A和W版本。文档中没有提到它们,而且该函数不接受字符串,因此没有明显的理由存在这样的版本。 - Harry Johnston
运行时错误,尝试调用 SetWindowSubClassA 函数失败。 - Paulo França Lacerda
刚使用 Delphi 2005/2006/2007 进行编译(这三个版本都是基于 ANSI,就像 D7 一样),结果相同。 - Paulo França Lacerda
@kobik: TNT(强大的组件套件)适用于应用程序。我的目标不是一个应用程序,而是一个纯WinApi框架。我正在创建自己的(无VCL)可视化框架,只是为了好玩,没有什么大惊小怪的事情。它支持Unicode,只要使用Delphi 2009+编译,不需要任何黑客技巧(只需编译器本地的unicode支持)。我提到这个话题的原因是因为我对ComCtl32(现在已经证实的)"特性"感到惊讶,就是这样。谢谢你的建议。 :) - Paulo França Lacerda
显示剩余4条评论
1个回答

11

根据MSDN的说法:

使用ComCtl32.dll版本6子类化控件

注意 ComCtl32.dll版本6仅支持Unicode。不应该使用ANSI窗口过程对ComCtl32.dll版本6所支持的公共控件进行子类化(或超类化)。

...

请注意,即使没有将Unicode作为预处理器定义,传递给该过程的所有字符串都是Unicode字符串

因此,这似乎是设计如此

c:\windows\syswow64文件夹中的comctl32.dll版本为6.1。


哇!你说对了。回答非常清楚!我看了那么多文章,结果错过了你指出的这个。我想我将坚持使用经典的SetWindowLong方式来子类化我的ANSI窗口。 :) - Paulo França Lacerda
我很抱歉,我也没有找到任何替代方案。 - Lieven Keersmaekers
这是很有用的信息,谢谢。但是如果一个子类化的 Ansi 窗口接收 Unicode 消息,真的很重要吗?你知道它们将会是 Unicode 的,所以如果你需要将它们作为 Ansi 处理,你可以将它们从 Unicode 转换为 Ansi。但是为什么不直接将它们作为 Unicode 处理呢?另外,你的应用程序不会使用 ComCtrl v6,除非它明确地为此进行了显式清单(例如启用视觉样式)。 - Remy Lebeau
@RemyLebeau: "为什么不使用Unicode?" 我的应用实际上是一个框架,而不是最终应用程序。 我希望它依赖于编译器对Unicode的本地支持,因此如果用户需要Unicode,则必须使用Unicode准备就绪的编译器,例如Delphi 2009+。 - Paulo França Lacerda
@PauloFrançaLacerda: 我刚刚注意到SetWindowSubClass()在ComCtrl 5.8及更高版本中可用,所以您需要在运行时检测实际使用的ComCtrl版本(通过DllGetVersion())并相应调整您的子类处理。 - Remy Lebeau
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接