📄 splitter.html.primary
字号:
<pre>LRESULT CALLBACK WndProcSplitter (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
SplitController * pCtrl = GetWinLong<SplitController *> (hwnd);
switch (message)
{
case WM_CREATE:
try
{
pCtrl = new SplitController (hwnd, reinterpret_cast<CREATESTRUCT *>(lParam));
SetWinLong<SplitController *> (hwnd, pCtrl);
}
catch (char const * msg)
{
MessageBox (hwnd, msg, "Initialization",
MB_ICONEXCLAMATION | MB_OK);
return -1;
}
catch (...)
{
MessageBox (hwnd, "Unknown Error", "Initialization",
MB_ICONEXCLAMATION | MB_OK);
return -1;
}
return 0;
case WM_SIZE:
pCtrl->Size (LOWORD(lParam), HIWORD(lParam));
return 0;
case WM_PAINT:
pCtrl->Paint ();
return 0;
case WM_LBUTTONDOWN:
pCtrl->LButtonDown (MAKEPOINTS (lParam));
return 0;
case WM_LBUTTONUP:
pCtrl->LButtonUp (MAKEPOINTS (lParam));
return 0;
case WM_MOUSEMOVE:
if (wParam & MK_LBUTTON)
pCtrl->LButtonDrag (MAKEPOINTS (lParam));
return 0;
case WM_CAPTURECHANGED:
pCtrl->CaptureChanged ();
return 0;
case WM_DESTROY:
SetWinLong<SplitController *> (hwnd, 0);
delete pCtrl;
return 0;
}
return ::DefWindowProc (hwnd, message, wParam, lParam);
}</pre>
<!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
This is all pretty much standard code. The details, as usual, are in the controller's methods. The constructor is very simple.
<hr>
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="#000000">
<pre>SplitController::SplitController (HWND hwnd, CREATESTRUCT * pCreat)
: _hwnd (hwnd),
_hwndParent (pCreat->hwndParent)
{}</pre>
<!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
Painting is more interesting. We have to imitate Windows 2.5-dimensional effects. We do it with a carfuly chosen selection of pens.
<hr>
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="#000000">
<pre>class Pens3d
{
public:
Pens3d ();
Pen & Hilight () { return _penHilight; }
Pen & Light () { return _penLight; }
Pen & Shadow () { return _penShadow; }
Pen & DkShadow () { return _penDkShadow; }
private:
Pen _penHilight;
Pen _penLight;
Pen _penShadow;
Pen _penDkShadow;
};
Pens3d::Pens3d ()
:
_penLight (<font color="#000099"><b>GetSysColor</b></font> (<font color="#009966">COLOR_3DLIGHT</font>)),
_penHilight (<font color="#000099"><b>GetSysColor</b></font> (<font color="#009966">COLOR_3DHILIGHT</font>)),
_penShadow (<font color="#000099"><b>GetSysColor</b></font> (<font color="#009966">COLOR_3DSHADOW</font>)),
_penDkShadow (<font color="#000099"><b>GetSysColor</b></font> (<font color="#009966">COLOR_3DDKSHADOW</font>))
{}
void SplitController::Paint ()
{
PaintCanvas canvas (_hwnd);
{
PenHolder pen (canvas, _pens.Light ());
canvas.Line (0, 0, 0, _cy - 1);
}
{
PenHolder pen (canvas, _pens.Hilight ());
canvas.Line (1, 0, 1, _cy - 1);
}
{
PenHolder pen (canvas, _pens.Shadow ());
canvas.Line (_cx - 2, 0, _cx - 2, _cy - 1);
}
{
PenHolder pen (canvas, _pens.DkShadow ());
canvas.Line (_cx - 1, 0, _cx - 1, _cy - 1);
}
}</pre>
<!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
The tricky part is the processing of mouse messages, although most of this code is pretty standard. We have to be able to deal with button-down, mouse drag and button-up.
<hr>
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="#000000">
<pre>void <font color="#cc0066"><b>SplitController::LButtonDown</b></font> (POINTS pt)
{
_hwnd.CaptureMouse ();
// Find x offset of splitter
// with respect to parent client area
POINT ptOrg = {0, 0 };
_hwndParent.ClientToScreen (ptOrg);
int xParent = ptOrg.x;
ptOrg.x = 0;
_hwnd.ClientToScreen (ptOrg);
int xChild = ptOrg.x;
_dragStart = xChild - xParent + _cx / 2 - pt.x;
_dragX = _dragStart + pt.x;
// Draw a divider using XOR mode
UpdateCanvas canvas (_hwndParent);
ModeSetter mode (canvas, R2_NOTXORPEN);
canvas.Line (_dragX, 0, _dragX, _cy - 1);
}</pre>
<!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
When the left mouse button is clicked over the client area of the splitter, we perform the following tasks. First, we capture the mouse. The user might, and probably will, drag the mouse cursor outside of the splitter bar. Capturing the mouse will ensure that all mouse messages will now be directed to us, even though the mouse cursor may wander all over the screen.
<p>Next, we convert the local splitter-bar coordinates to the parent window coordinates. We do it by converting the parent's origin to screen coordinates and converting our (splitter's) origin to screen coordinates. The difference gives us our origin with respect to parent's client area. We do some more arithmetics in order to find the x coordinate of the center of the splitter, because that's what we'll be dragging.
<p>To give the user dragging feedback, we draw a single vertical line that will be dragged across the client area of the parent window. Notice two important details. The canvas that we are creating are associated with the parent--we are using the parent's window handle. The drawing mode is logical <b>xor</b> (exclusive or). That means that the pixels we are drawing are xor'd with the original pixels. Logical xor has this useful property that if you apply it twice, you restore the original. It's the oldest trick in computer graphics--the poor man's animation technique. You draw something using the xor mode and erase it simply by drawing it again in the xor mode. Just look at the dragging code below.
<hr>
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="#000000">
<pre>void <font color="#cc0066"><b>SplitController::LButtonDrag</b></font> (POINTS pt)
{
// Erase previous divider and draw new one
UpdateCanvas canvas (_hwndParent);
ModeSetter mode (canvas, R2_NOTXORPEN);
canvas.Line (_dragX, 0, _dragX, _cy - 1);
_dragX = _dragStart + pt.x;
canvas.Line (_dragX, 0, _dragX, _cy - 1);
}</pre>
<!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
We draw the vertical line in the xor mode using the previous remembered position. Since this is the second time we draw this line in the same place, the net result will be the restoration of the original pixels from under the line. Then we draw the new line in the new position, still in the xor mode. We remember this position, so that next time <b>LButtonDrag</b> is called we'll erase it too. And so on, until finally the user releases the mouse button.
<hr>
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="#000000">
<pre>void <font color="#cc0066"><b>SplitController::LButtonUp</b></font> (POINTS pt)
{
// Calling ReleaseCapture will send us the WM_CAPTURECHANGED
_hwnd.ReleaseMouse ();
_hwndParent.SendMessage (MSG_MOVESPLITTER, _dragStart + pt.x);
}</pre>
<!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
At this point we should erase the vertical line for the last time. However, we don't do it directly--we use a little trick here. We release the mouse capture. As a side effect, Windows sends us the <b>WM_CAPTURECHANGED</b> message. It's during the processing of that message that we actually erase the vertical line.
<hr>
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="#000000">
<pre>void <font color="#cc0066"><b>SplitController::CaptureChanged</b></font> ()
{
// We are losing capture
// End drag selection -- for whatever reason
// Erase previous divider
UpdateCanvas canvas (_hwndParent);
ModeSetter mode (canvas, R2_NOTXORPEN);
canvas.Line (_dragX, 0, _dragX, _cy - 1);
}</pre>
<!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
Why do we do that? It's because Windows can force us to release the mouse capture before the user releases the mouse button. It might happen, for instance, when another application suddenly decides to pop up its window while the user is in the middle of dragging. In such a case our window would never get the button-up message and, if we weren't clever, wouldn't be able to cleanly finish the drag. Fortunately, before taking away the mouse capture, Windows manages to send us the <b>WM_CAPTURECHANGED</b> message, and we take it as a clue to do our cleanup.
<p>Going back to <b>LButtonUp</b>--once the user finishes dragging, we send the parent our special message <b>MSG_MOVESPLITTER</b>, passing it the new position of the center of the splitter bar measured in parent's client area coordinates. You've already seen the client code response to this message.
<p>This is how one might define a client message.
<hr>
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="#000000">
<pre>// Reserved by Reliable Software Library
const UINT MSG_RS_LIBRARY = WM_USER + 0x4000;
// wParam = new position wrt parent's left edge
const UINT MSG_MOVESPLITTER = MSG_RS_LIBRARY + 1;
</pre>
<!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
Finally, here's an excerpt from a very useful class <font color="#cc0066"><b>HWnd</b></font> that encapsulates a lot of basic Windows APIs that deal with windows. In particular, look at the methods <font color="#cc0066"><b>MoveDelayPaint</b></font> and <font color="#cc0066"><b>ForceRepaint</b></font> that we used in repainting the splitter bar.
<hr>
<!--Yellow background-->
<table cellpadding=10 cellspacing=0 width="100%">
<tr>
<td width=20> </td>
<td bgcolor="#e0e080"><font color="#000000">
<pre>class <font color="#cc0066"><b>HWnd</b></font>
{
public:
void <font color="#cc0066"><b>Update</b></font> ()
{
::<font color="#000099"><b>UpdateWindow</b></font> (_hwnd);
}
// Moving
void <font color="#cc0066"><b>Move</b></font> (int x, int y, int width, int height)
{
::<font color="#000099"><b>MoveWindow</b></font> (_hwnd, x, y, width, height, TRUE);
}
void <font color="#cc0066"><b>MoveDelayPaint</b></font> (int x, int y, int width, int height)
{
::<font color="#000099"><b>MoveWindow</b></font> (_hwnd, x, y, width, height, FALSE);
}
// Repainting
void <font color="#cc0066"><b>Invalidate</b></font> ()
{
::<font color="#000099"><b>InvalidateRect</b></font> (_hwnd, 0, TRUE);
}
void <font color="#cc0066"><b>ForceRepaint</b></font> ()
{
Invalidate ();
Update ();
}
private:
<font color="#009966">HWND</font> _hwnd;
};</pre>
<!--End Code--></font>
</td>
<td width=20> </td>
</tr>
</table>
<!--End of yellow background-->
<hr>
<img src="images/brace.gif" width=16 height=16 border=0 alt="">As usual, you can <a href="source/splitter.zip">download</a> the complete source code of the application that was used in this example.
<hr>
<!-- end main box -->
</td></tr>
</table>
</td>
<td width=10><!-- Right margin --> </td>
</tr>
</table> <!-- End main table -->
</body>
</html>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -