Erik Wiggins
August 21st, 2004, 07:53 AM
There are alot of questions about making functioning SplitterBar using only Win32. I should know because I'm one of the people who have ask the questions. Well I've got one working and thaught I should share it with everyone. The largest problem you fight with is getting all of the coordanates in the correct space. I've added a small function to help with that and to shorten the code just a little bit.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// Foward declarations of functions included in this code module:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK SplitProc(HWND, UINT, WPARAM, LPARAM);
void RegisterClasses();
void CreateWindows();
void ScreenRectToClient(HWND, LPRECT);
WNDCLASSEX wcx;
HWND hMain, hLeft, hRight,hSplit;
RECT rect;
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
ZeroMemory(&wcx,sizeof(wcx));
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.hInstance=hInstance;
RegisterClasses();
CreateWindows();
// Main message loop:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//
// FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
// PURPOSE: Processes messages for the main window.
//
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
LRESULT CALLBACK SplitProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
SetCapture(hWnd); //Capture all mouse messages
break;
case WM_MOUSEMOVE:
if(wParam==MK_LBUTTON) //Make sure mouse botton is down
{
POINT ptMouseLoc; //The location of the mouse
RECT rtSplitter; //The enclosing rect for our SplitterBar
RECT rtLeft; //The enclosing rect for the left frame
RECT rtRight; //The enclosing rect for the right frame
//Retreive the Window Rect for out 3 windows and set the mouses location
GetWindowRect(hWnd,&rtSplitter);
GetWindowRect(hLeft,&rtLeft);
GetWindowRect(hRight,&rtRight);
ptMouseLoc.x=LOWORD(lParam);
ptMouseLoc.y=HIWORD(lParam);
//Convert all of our locations to Client coordanates for the parent window
ClientToScreen(hWnd,&ptMouseLoc);
ScreenToClient(GetParent(hWnd),&ptMouseLoc);
ScreenRectToClient(GetParent(hWnd),&rtSplitter);
ScreenRectToClient(GetParent(hWnd),&rtLeft);
ScreenRectToClient(GetParent(hWnd),&rtRight);
//Adjust our Rects to translated Posistions
rtSplitter.left=ptMouseLoc.x-5;
rtSplitter.right=ptMouseLoc.x+5;
rtLeft.right=rtSplitter.left;
rtRight.left=rtSplitter.right;
//Make sure the Splitter doesn'jump of the screen
if(rtRight.right-10 < ptMouseLoc.x && ptMouseLoc.x > 10)
{
//Move the windows all at once
HDWP hdwp=BeginDeferWindowPos(3);
hdwp=DeferWindowPos
(
hdwp,
hWnd,
(HWND)NULL,
rtSplitter.left,
rtSplitter.top,
rtSplitter.right-rtSplitter.left,
rtSplitter.bottom-rtSplitter.top,
SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW
);
hdwp=DeferWindowPos
(
hdwp,
hLeft,
(HWND)NULL,
rtLeft.left,
rtLeft.top,
rtLeft.right-rtLeft.left,
rtLeft.bottom-rtLeft.top,
SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW
);
hdwp=DeferWindowPos
(
hdwp,
hRight,
(HWND)NULL,
rtRight.left,
rtRight.top,
rtRight.right-rtRight.left,
rtRight.bottom-rtRight.top,
SWP_NOZORDER | SWP_SHOWWINDOW
);
EndDeferWindowPos(hdwp);
}
}
break;
case WM_LBUTTONUP:
ReleaseCapture();
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
void RegisterClasses()
{
ZeroMemory(&wcx,sizeof(wcx));
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = (WNDPROC)WndProc;
wcx.hIcon = NULL;
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground = (HBRUSH)(COLOR_ACTIVEBORDER+1);
wcx.lpszMenuName = (LPCSTR)NULL;
wcx.lpszClassName = "MainWndClass";
wcx.hIconSm = NULL;
RegisterClassEx(&wcx);
wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcx.lpszClassName = "FrameWndClass";
RegisterClassEx(&wcx);
wcx.lpfnWndProc = (WNDPROC)SplitProc;
wcx.hCursor = LoadCursor(NULL, IDC_SIZEWE);
wcx.hbrBackground = (HBRUSH)(COLOR_ACTIVEBORDER+1);
wcx.lpszClassName = "SplitWndClass";
RegisterClassEx(&wcx);
}
void CreateWindows()
{
hMain = CreateWindowEx
(
WS_EX_APPWINDOW|WS_EX_CONTROLPARENT,
"MainWndClass",
"Splitter",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
(HWND)NULL,
(HMENU)NULL,
wcx.hInstance,
NULL
);
ShowWindow(hMain,SW_SHOW);
UpdateWindow(hMain);
GetClientRect(hMain,&rect);
hLeft = CreateWindowEx
(
WS_EX_CLIENTEDGE,
"FrameWndClass",
"Left Frame",
WS_BORDER|WS_CHILD|WS_VISIBLE,
0,
0,
(rect.right/2)-5,
rect.bottom,
hMain,
(HMENU)NULL,
wcx.hInstance,
NULL
);
ShowWindow(hLeft,SW_SHOW);
UpdateWindow(hLeft);
hRight = CreateWindowEx
(
WS_EX_CLIENTEDGE,
"FrameWndClass",
"Right Frame",
WS_BORDER|WS_CHILD|WS_VISIBLE,
(rect.right/2)+5,
0,
rect.right-(rect.right/2)-5,
rect.bottom,
hMain,
(HMENU)NULL,
wcx.hInstance,
NULL
);
ShowWindow(hRight,SW_SHOW);
UpdateWindow(hRight);
hSplit = CreateWindowEx
(
NULL,
"SplitWndClass",
"Splitter Bar",
WS_BORDER|WS_CHILD|WS_VISIBLE,
(rect.right/2)-5,
0,
10,
rect.bottom,
hMain,
(HMENU)NULL,
wcx.hInstance,
NULL
);
ShowWindow(hSplit,SW_SHOW);
UpdateWindow(hSplit);
}
void ScreenRectToClient(HWND hWnd,LPRECT prect)
{
POINT pt;
pt.x=prect->left;
pt.y=prect->top;
ScreenToClient(hWnd,&pt);
prect->left=pt.x;
prect->top=pt.y;
pt.x=prect->right;
pt.y=prect->bottom;
ScreenToClient(hWnd,&pt);
prect->right=pt.x;
prect->bottom=pt.y;
}
It's not a short little block of code like you would think it would be but it's effecant and easy to follow. I had to add the check to make sure it didn't jump off the screen because when one of the nonclient areas of one of our windows receives the mouse move message it causes our window to jump right off the screen. I couldn't figure out how to convert the coordanate given effecantly so I choose to ignore any coordanate that would cause the splitter to jump off the screen.
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// Foward declarations of functions included in this code module:
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK SplitProc(HWND, UINT, WPARAM, LPARAM);
void RegisterClasses();
void CreateWindows();
void ScreenRectToClient(HWND, LPRECT);
WNDCLASSEX wcx;
HWND hMain, hLeft, hRight,hSplit;
RECT rect;
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
ZeroMemory(&wcx,sizeof(wcx));
wcx.cbSize = sizeof(WNDCLASSEX);
wcx.hInstance=hInstance;
RegisterClasses();
CreateWindows();
// Main message loop:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
//
// FUNCTION: WndProc(HWND, unsigned, WORD, LONG)
//
// PURPOSE: Processes messages for the main window.
//
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
LRESULT CALLBACK SplitProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_LBUTTONDOWN:
SetCapture(hWnd); //Capture all mouse messages
break;
case WM_MOUSEMOVE:
if(wParam==MK_LBUTTON) //Make sure mouse botton is down
{
POINT ptMouseLoc; //The location of the mouse
RECT rtSplitter; //The enclosing rect for our SplitterBar
RECT rtLeft; //The enclosing rect for the left frame
RECT rtRight; //The enclosing rect for the right frame
//Retreive the Window Rect for out 3 windows and set the mouses location
GetWindowRect(hWnd,&rtSplitter);
GetWindowRect(hLeft,&rtLeft);
GetWindowRect(hRight,&rtRight);
ptMouseLoc.x=LOWORD(lParam);
ptMouseLoc.y=HIWORD(lParam);
//Convert all of our locations to Client coordanates for the parent window
ClientToScreen(hWnd,&ptMouseLoc);
ScreenToClient(GetParent(hWnd),&ptMouseLoc);
ScreenRectToClient(GetParent(hWnd),&rtSplitter);
ScreenRectToClient(GetParent(hWnd),&rtLeft);
ScreenRectToClient(GetParent(hWnd),&rtRight);
//Adjust our Rects to translated Posistions
rtSplitter.left=ptMouseLoc.x-5;
rtSplitter.right=ptMouseLoc.x+5;
rtLeft.right=rtSplitter.left;
rtRight.left=rtSplitter.right;
//Make sure the Splitter doesn'jump of the screen
if(rtRight.right-10 < ptMouseLoc.x && ptMouseLoc.x > 10)
{
//Move the windows all at once
HDWP hdwp=BeginDeferWindowPos(3);
hdwp=DeferWindowPos
(
hdwp,
hWnd,
(HWND)NULL,
rtSplitter.left,
rtSplitter.top,
rtSplitter.right-rtSplitter.left,
rtSplitter.bottom-rtSplitter.top,
SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW
);
hdwp=DeferWindowPos
(
hdwp,
hLeft,
(HWND)NULL,
rtLeft.left,
rtLeft.top,
rtLeft.right-rtLeft.left,
rtLeft.bottom-rtLeft.top,
SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW
);
hdwp=DeferWindowPos
(
hdwp,
hRight,
(HWND)NULL,
rtRight.left,
rtRight.top,
rtRight.right-rtRight.left,
rtRight.bottom-rtRight.top,
SWP_NOZORDER | SWP_SHOWWINDOW
);
EndDeferWindowPos(hdwp);
}
}
break;
case WM_LBUTTONUP:
ReleaseCapture();
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
void RegisterClasses()
{
ZeroMemory(&wcx,sizeof(wcx));
wcx.style = CS_HREDRAW | CS_VREDRAW;
wcx.lpfnWndProc = (WNDPROC)WndProc;
wcx.hIcon = NULL;
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground = (HBRUSH)(COLOR_ACTIVEBORDER+1);
wcx.lpszMenuName = (LPCSTR)NULL;
wcx.lpszClassName = "MainWndClass";
wcx.hIconSm = NULL;
RegisterClassEx(&wcx);
wcx.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcx.lpszClassName = "FrameWndClass";
RegisterClassEx(&wcx);
wcx.lpfnWndProc = (WNDPROC)SplitProc;
wcx.hCursor = LoadCursor(NULL, IDC_SIZEWE);
wcx.hbrBackground = (HBRUSH)(COLOR_ACTIVEBORDER+1);
wcx.lpszClassName = "SplitWndClass";
RegisterClassEx(&wcx);
}
void CreateWindows()
{
hMain = CreateWindowEx
(
WS_EX_APPWINDOW|WS_EX_CONTROLPARENT,
"MainWndClass",
"Splitter",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
(HWND)NULL,
(HMENU)NULL,
wcx.hInstance,
NULL
);
ShowWindow(hMain,SW_SHOW);
UpdateWindow(hMain);
GetClientRect(hMain,&rect);
hLeft = CreateWindowEx
(
WS_EX_CLIENTEDGE,
"FrameWndClass",
"Left Frame",
WS_BORDER|WS_CHILD|WS_VISIBLE,
0,
0,
(rect.right/2)-5,
rect.bottom,
hMain,
(HMENU)NULL,
wcx.hInstance,
NULL
);
ShowWindow(hLeft,SW_SHOW);
UpdateWindow(hLeft);
hRight = CreateWindowEx
(
WS_EX_CLIENTEDGE,
"FrameWndClass",
"Right Frame",
WS_BORDER|WS_CHILD|WS_VISIBLE,
(rect.right/2)+5,
0,
rect.right-(rect.right/2)-5,
rect.bottom,
hMain,
(HMENU)NULL,
wcx.hInstance,
NULL
);
ShowWindow(hRight,SW_SHOW);
UpdateWindow(hRight);
hSplit = CreateWindowEx
(
NULL,
"SplitWndClass",
"Splitter Bar",
WS_BORDER|WS_CHILD|WS_VISIBLE,
(rect.right/2)-5,
0,
10,
rect.bottom,
hMain,
(HMENU)NULL,
wcx.hInstance,
NULL
);
ShowWindow(hSplit,SW_SHOW);
UpdateWindow(hSplit);
}
void ScreenRectToClient(HWND hWnd,LPRECT prect)
{
POINT pt;
pt.x=prect->left;
pt.y=prect->top;
ScreenToClient(hWnd,&pt);
prect->left=pt.x;
prect->top=pt.y;
pt.x=prect->right;
pt.y=prect->bottom;
ScreenToClient(hWnd,&pt);
prect->right=pt.x;
prect->bottom=pt.y;
}
It's not a short little block of code like you would think it would be but it's effecant and easy to follow. I had to add the check to make sure it didn't jump off the screen because when one of the nonclient areas of one of our windows receives the mouse move message it causes our window to jump right off the screen. I couldn't figure out how to convert the coordanate given effecantly so I choose to ignore any coordanate that would cause the splitter to jump off the screen.