📄 faq76.htm
字号:
<HTML>
<HEAD>
<TITLE>Sublcass a windows control</TITLE>
<META NAME="Author" CONTENT="Harold Howe">
</HEAD>
<BODY BGCOLOR="WHITE">
<CENTER>
<TABLE BORDER=0 CELLPADDING=0 CELLSPACING=0 WIDTH="640">
<TR>
<TD>
<H3>
Sublcass a windows control
</H3>
<P>
The term <I>subclass</I> can mean different things to different people. In C++ language terms, subclassing equates to
deriving a new class from a base class. In Windows API terms, subclassing is the act of hooking the window procedure
of a window so you can intercept messages. This FAQ describes how to subclass a window to intercept messages. There
are several ways to accomplish this. You can use the windows API, you can assign a VCL property, or you can derive
a new VCL class that overrides the behavior of the base control.
</P>
<H4>
Subclassing with the Windows API
</H4>
<P>
The API contains a function called <TT>SetWindowLong</TT>. Among other things,
this function allows you to set the window procedure (<TT>WNDPROC</TT>) of any window handle in your program's
address space. The function <TT>GetWindowLong</TT> allows you to retrieve the window procedure of a window handle.
The code listing below uses <TT>GetWindowLong</TT> and <TT>SetWindowLong</TT> to sublcass a <TT>TListView</TT>. The
program intercepts the <TT>WM_ERASEBKGND</TT> message to paint a custom background in the list view. Figure 1
illustrates what the code is doing.
<pre>
<font color="navy">//-------------------------------------------------------</font>
<font color="navy">// header file</font>
<b>class</b> TForm1 <b>:</b> <b>public</b> TForm
<b>{</b>
<b>__published</b><b>:</b> <font color="navy">// IDE-managed Components</font>
TListView <b>*</b>ListView1<b>;</b>
TImageList <b>*</b>ImageList1<b>;</b>
<b>void</b> <b>__fastcall</b> FormResize<b>(</b>TObject <b>*</b>Sender<b>)</b><b>;</b>
<b>private</b><b>:</b> <font color="navy">// User declarations</font>
WNDPROC FOriginalProc<b>;</b>
<b>void</b> <b>__fastcall</b> PaintListView<b>(</b>HDC <b>&</b>dc<b>)</b><b>;</b>
<b>public</b><b>:</b> <font color="navy">// User declarations</font>
<b>__fastcall</b> TForm1<b>(</b>TComponent<b>*</b> Owner<b>)</b><b>;</b>
<b>__fastcall</b> <b>~</b>TForm1<b>(</b><b>)</b><b>;</b>
LRESULT <b>__fastcall</b> ListViewProc<b>(</b>HWND hWnd<b>,</b>
UINT uMsg<b>,</b>
WPARAM wParam<b>,</b>
LPARAM lParam<b>)</b><b>;</b>
<b>}</b><b>;</b>
<font color="navy">//-------------------------------------------------------</font>
<font color="navy">// source file</font>
<font color="navy">//-------------------------------------------------------</font>
<font color="green">#include <vcl.h></font>
<font color="green">#pragma hdrstop</font>
<font color="green">#include "main.h"</font>
<font color="navy">//-------------------------------------------------------</font>
<font color="green">#pragma package(smart_init)</font>
<font color="green">#pragma resource "*.dfm"</font>
TForm1 <b>*</b>Form1<b>;</b>
<font color="navy">//-------------------------------------------------------</font>
LRESULT CALLBACK ControlWndProc<b>(</b>HWND hWnd<b>,</b>
UINT uMsg<b>,</b>
WPARAM wParam<b>,</b>
LPARAM lParam<b>)</b>
<b>{</b>
<b>return</b> Form1<b>-></b>ListViewProc<b>(</b>hWnd<b>,</b> uMsg<b>,</b> wParam<b>,</b> lParam<b>)</b><b>;</b>
<b>}</b>
<font color="navy">//-------------------------------------------------------</font>
<b>__fastcall</b> TForm1<b>:</b><b>:</b>TForm1<b>(</b>TComponent<b>*</b> Owner<b>)</b>
<b>:</b> TForm<b>(</b>Owner<b>)</b>
<b>{</b>
<font color="navy">// Call GetWindowLong to retrieve the original window proc</font>
FOriginalProc <b>=</b> <b>(</b>WNDPROC<b>)</b> GetWindowLong<b>(</b>ListView1<b>-></b>Handle<b>,</b> GWL_WNDPROC<b>)</b><b>;</b>
<font color="navy">// Call SetWindowLong to assign the new window proc for the listview</font>
SetWindowLong<b>(</b>ListView1<b>-></b>Handle<b>,</b> GWL_WNDPROC<b>,</b> <b>(</b>LONG<b>)</b>ControlWndProc<b>)</b><b>;</b>
<b>}</b>
<font color="navy">//-------------------------------------------------------</font>
<b>__fastcall</b> TForm1<b>:</b><b>:</b><b>~</b>TForm1<b>(</b><b>)</b>
<b>{</b>
<font color="navy">// Set the window proc back to the original function</font>
SetWindowLong<b>(</b>ListView1<b>-></b>Handle<b>,</b> GWL_WNDPROC<b>,</b> <b>(</b>LONG<b>)</b> FOriginalProc<b>)</b><b>;</b>
<b>}</b>
<font color="navy">//-------------------------------------------------------</font>
LRESULT <b>__fastcall</b> TForm1<b>:</b><b>:</b>ListViewProc<b>(</b>HWND hWnd<b>,</b>
UINT uMsg<b>,</b>
WPARAM wParam<b>,</b>
LPARAM lParam<b>)</b>
<b>{</b>
LRESULT lResult <b>=</b> <font color="blue">0</font><b>;</b>
<b>switch</b><b>(</b>uMsg<b>)</b>
<b>{</b>
<b>case</b> WM_ERASEBKGND<b>:</b>
PaintListView<b>(</b><b>(</b>HDC<b>)</b>wParam<b>)</b><b>;</b>
lResult <b>=</b> <font color="blue">0</font><b>;</b>
<b>break</b><b>;</b>
<b>case</b> WM_HSCROLL<b>:</b>
<b>case</b> WM_VSCROLL<b>:</b>
LockWindowUpdate<b>(</b>hWnd<b>)</b><b>;</b>
lResult <b>=</b> CallWindowProc <b>(</b><b>(</b>FARPROC<b>)</b>FOriginalProc<b>,</b> hWnd<b>,</b> uMsg<b>,</b>
wParam<b>,</b> lParam<b>)</b><b>;</b>
InvalidateRect <b>(</b>hWnd<b>,</b> <font color="blue">0</font><b>,</b> <b>true</b><b>)</b><b>;</b>
LockWindowUpdate<b>(</b>NULL<b>)</b><b>;</b>
<b>break</b><b>;</b>
<b>default</b><b>:</b>
lResult <b>=</b> CallWindowProc <b>(</b><b>(</b>FARPROC<b>)</b>FOriginalProc<b>,</b> hWnd<b>,</b> uMsg<b>,</b>
wParam<b>,</b> lParam<b>)</b><b>;</b>
<b>break</b><b>;</b>
<b>}</b>
<b>return</b> lResult<b>;</b>
<b>}</b>
<font color="navy">//-------------------------------------------------------</font>
<b>void</b> <b>__fastcall</b> TForm1<b>:</b><b>:</b>PaintListView<b>(</b>HDC <b>&</b>dc<b>)</b>
<b>{</b>
TRect rect <b>=</b> ListView1<b>-></b>BoundsRect<b>;</b>
Graphics<b>:</b><b>:</b>TBitmap <b>*</b>MemBitmap <b>=</b> <b>new</b> Graphics<b>:</b><b>:</b>TBitmap<b>;</b>
MemBitmap<b>-></b>Width <b>=</b> rect<b>.</b>Right <b>-</b> rect<b>.</b>Left<b>;</b>
MemBitmap<b>-></b>Height<b>=</b> rect<b>.</b>Bottom<b>-</b> rect<b>.</b>Top<b>;</b>
MemBitmap<b>-></b>Canvas<b>-></b>Brush<b>-></b>Style <b>=</b> bsSolid<b>;</b>
MemBitmap<b>-></b>Canvas<b>-></b>Brush<b>-></b>Color <b>=</b> clAqua<b>;</b>
MemBitmap<b>-></b>Canvas<b>-></b>FillRect<b>(</b>Rect<b>(</b><font color="blue">0</font><b>,</b><font color="blue">0</font><b>,</b>
MemBitmap<b>-></b>Width<b>,</b>
MemBitmap<b>-></b>Height<b>)</b><b>)</b><b>;</b>
MemBitmap<b>-></b>Canvas<b>-></b>Pen<b>-></b>Color <b>=</b> clNavy<b>;</b>
MemBitmap<b>-></b>Canvas<b>-></b>Pen<b>-></b>Width <b>=</b> <font color="blue">20</font><b>;</b>
MemBitmap<b>-></b>Canvas<b>-></b>MoveTo<b>(</b><font color="blue">0</font><b>,</b><font color="blue">0</font><b>)</b><b>;</b>
MemBitmap<b>-></b>Canvas<b>-></b>LineTo<b>(</b>MemBitmap<b>-></b>Width<b>,</b> MemBitmap<b>-></b>Height<b>)</b><b>;</b>
<b>:</b><b>:</b>BitBlt<b>(</b>dc<b>,</b><font color="blue">0</font><b>,</b><font color="blue">0</font><b>,</b>MemBitmap<b>-></b>Width<b>,</b> MemBitmap<b>-></b>Height<b>,</b>
MemBitmap<b>-></b>Canvas<b>-></b>Handle<b>,</b><font color="blue">0</font><b>,</b><font color="blue">0</font><b>,</b>SRCCOPY<b>)</b><b>;</b>
<b>delete</b> MemBitmap<b>;</b>
<b>}</b>
<font color="navy">//-------------------------------------------------------</font>
<b>void</b> <b>__fastcall</b><b>/</b> TForm1<b>:</b><b>:</b>FormResize<b>(</b>TObject <b>*</b>Sender<b>)</b>
<b>{</b>
ListView1<b>-></b>Repaint<b>(</b><b>)</b><b>;</b>
<b>}</b>
<font color="navy">//-------------------------------------------------------</font>
</pre>
<IMG SRC="images/subclass.gif" ALT="An MDI Window menu" BORDER=0 HSPACE="0" ALIGN="CENTER" VALIGN="TOP">
<P><B>Figure 1: Painting the background of a TListView control.</B></P>
<P>
<B>Note 1:</B> Windows is picky about the kind of function that you pass to <TT>SetWindowLong</TT>. Unfortunately,
it waits until runtime to let you know if anything is wrong. <TT>SetWindowLong</TT> passes the
callback function as a <TT>LONG</TT> variable. The problem is that you can cast just about anything as a <TT>LONG</TT>.
If you prototype the callback function incorrectly, the compiler cannot catch your mistake at compile time. Make sure
that the function that you pass to <TT>SetWindowLong</TT> takes four arguments, returns a <TT>LONG</TT> value, and uses
the <TT>__stdcall</TT> calling convention. If you prototype the function like this, you shouldn't have any problems.
</P>
<pre>
LRESULT CALLBACK ControlWndProc<b>(</b>HWND hWnd<b>,</b>
UINT uMsg<b>,</b>
WPARAM wParam<b>,</b>
LPARAM lParam<b>)</b>
</pre>
<P>
<B>Note 2:</B> The window procedure that you pass to <TT>SetWindowLong</TT> cannot be a non-static method of class.
Non-static member functions take a hidden <TT>this</TT> pointer as their first argument. The hidden pointer identifies
the object whose method you are calling. This is a result of how compilers implement classes and objects in C++.
</P>
<P>
For example, look at the <TT>ListViewProc</TT> method of <TT>TForm1</TT>. The function takes four arguments, an
<TT>HWND</TT>, an integer, a <TT>WPARAM</TT>, and an <TT>LPARAM</TT>. In reality, the function takes a fifth argument
that is a pointer to a <TT>TForm1</TT> object. The form pointer identifies the form whose method is being invoked. You
can see the hidden pointer argument by inspecting the assembly language generated by a call to the
<TT>ListViewProc</TT> function.
</P>
<P>
Windows has no concept of <TT>this</TT> pointers, objects and classes. When you subclass a window procedure, the
operating system expects to see a function that takes four arguments: an <TT>HWND</TT>, an integer, a <TT>WPARAM</TT>,
and an <TT>LPARAM</TT>. It doesn't expect
to find a function that takes a fifth pointer argument. If you subclass a window procedure with a non-static member
function, Windows will not pass arguments to the function correctly. If you attempt to dereference any members of the
class from inside your window procedure, you could cause an access violation because the hidden <TT>this</TT> pointer
will probably be a garbage value. Also you run the risk of corrupting your stack because the wrong number of arguments
will be popped off when the function returns.
</P>
<P>
You can subclass a window procedure with a static member function of a class, because static member functions don't
have a hidden pointer argument. If you decide to try this technique, make sure that your static member function follows
the same calling convention that Windows will use to call the function. The code below shows how you would declare
the function.
</P>
<pre>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -