January 26, 2023, 10:26:25 PM

News:

Own IWBasic 2.x ? -----> Get your free upgrade to 3.x now.........


Splitter window

Started by sapero, May 23, 2006, 05:25:06 AM

Previous topic - Next topic

0 Members and 1 Guest are viewing this topic.

sapero

May 23, 2006, 05:25:06 AM Last Edit: May 24, 2006, 02:36:34 AM by sapero
A very simple CSplitter class derieved from CWindow
It supports cursor changing, while dragging a dotted brush line is XOR-ed on the screen.
On drop the next window is automatically resized.
Main concept:
window A has two childs: B and C (B and C must fit whole area of A)
One of childs has a splitter attached to left, right, top or bottom window side

Note: B or C can have next two childs, and so on...

It uses a lot of api's, so I do not declare all them every time, just include (latest download link in sig)
#include "windows.inc"

// external files: sizeV.cur, sizeH.cur

// primitive dotted bitmap
#emit dotPatternBmp: dw 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55
declare dotPatternBmp();


// splitter window style: where to attach
#define SPLITERSTYLE_LEFT   0x1
#define SPLITERSTYLE_TOP    0x2
#define SPLITERSTYLE_RIGHT  0x4
#define SPLITERSTYLE_BOTTOM 0x8
//private
#define SPLITERSTYLE_MASK   0xF
#define SPLITERSTYLE_HORIZONTAL 0x10
#define SPLITERSTYLE_VERTICAL   0x20

class CSpliter : CWindow
{
declare  CSpliter();
declare _CSpliter();

// Create method uses l,w,h,parent and style, where style = one of SPLITERSTYLE_*
// w - width of the splitter (valid if style is 'left' or 'right')
// h - height of the splitter (valid if style is 'top' or 'bottom')
declare virtual Create(int l,int t,int w,int h,int style,int exstyle,string title,CWindow *parent),int;
declare virtual OnSize(int nType,int cx,int cy),int;
// XOR the dotted line on screen
declare virtual DrawSplitter();
// on drag end this method resizes window C
declare virtual ResizeNextChild();

declare virtual OnLButtonDown(int x,int y, int flags),int;
declare virtual OnMouseMove(int x,int y,int flags),int;
declare virtual OnLButtonUp(int x,int y,int flags),int;

// m_hwndClip: window handle where drag-drop is alloved.
// default: parent of parent window of the splitter window
HWND     m_hwndClip;
int      m_style;
HBRUSH   m_hbr;        // do not change! m_hbr, m_hbm, m_ptClientsize
HBITMAP  m_hbm;
POINT    m_ptClientsize;// size of (B)
POINT    m_ptSplstart; // where in the splitter window the mouse was down
POINT    m_ptSplpos;   // current cursor position while dragging
POINT    m_ptClientPos;// updated in OnSize
HCURSOR  m_crsSize;
HWND     m_hwndParent; // B
HWND     m_hwndNotifySizeChanged; // window C
int      m_splitterW; // updated in OnSize
int      m_splitterH;
}

//---------------------------------------------

CSpliter::CSpliter()
{
m_hwndClip = null;
m_crsSize  = 0;
m_hwndNotifySizeChanged = 0;
}


CSpliter::_CSpliter()
{
if (m_crsSize) DestroyCursor(m_crsSize);
}

//---------------------------------------------

CSpliter::Create(int l,int t,int w,int h,int style,int exstyle,string title,CWindow *parent),int
{
if (parent)
{
m_hwndParent = parent->m_hwnd;
if (!m_hwndClip) m_hwndClip = GetParent(m_hwndParent);
if (!m_hwndClip) m_hwndClip = m_hwndParent;

style = (style & SPLITERSTYLE_MASK);

RECT rcParent;
::GetClientRect(m_hwndParent, rcParent);

if (style == SPLITERSTYLE_LEFT) {
l = 0;
t = 0;
// w = ;   no hanges
h = rcParent.bottom;
m_style = SPLITERSTYLE_VERTICAL | SPLITERSTYLE_LEFT;
}

if (style == SPLITERSTYLE_TOP) {
l = 0;
t = 0;
w = rcParent.right;
// h = ;   no hanges
m_style = SPLITERSTYLE_HORIZONTAL | SPLITERSTYLE_TOP;
}

if (style == SPLITERSTYLE_RIGHT) {
l = rcParent.right - w;
t = 0;
// w = ;   no hanges
h = rcParent.bottom;
m_style = SPLITERSTYLE_VERTICAL | SPLITERSTYLE_RIGHT;
}

if (style == SPLITERSTYLE_BOTTOM) {
l = 0;
t = rcParent.bottom-h;
w = rcParent.right;
// h = ;   no hanges
m_style = SPLITERSTYLE_HORIZONTAL | SPLITERSTYLE_BOTTOM;
}
// store splitter window size
m_splitterW = w;
m_splitterH = h;

// store parent(B) window position (seen from clip window)
::GetWindowRect(m_hwndParent, rcParent);
::ScreenToClient(m_hwndClip, rcParent);
m_ptClientPos.x = rcParent.left;
m_ptClientPos.y = rcParent.top;


if (m_style & SPLITERSTYLE_VERTICAL)
m_crsSize = LoadCursorFromFile("sizeV.cur")
else
m_crsSize = LoadCursorFromFile("sizeH.cur");


if (CWindow!!Create(l,t,w,h,AWS_CHILD|AWS_VISIBLE,exstyle,title,parent)) {

return true;
}
}
return false;
}


//---------------------------------------------

CSpliter::OnSize(int nType,int cx,int cy),int
{
if (m_style & SPLITERSTYLE_VERTICAL)
m_splitterH = cy
else
m_splitterW = cx;

RECT rcParent;
::GetClientRect(m_hwndParent, rcParent);
// store parent window position, looking from clip window
// position must be valid for DrawSplitter()
::GetWindowRect(m_hwndParent, rcParent);
::ScreenToClient(m_hwndClip, rcParent);
m_ptClientPos.x = rcParent.left;
m_ptClientPos.y = rcParent.top;
return true;
}


//---------------------------------------------


CSpliter::OnLButtonDown(int x,int y, int flags),int
{
// save click position
m_ptSplstart.x = x;
m_ptSplstart.y = y;
::GetCursorPos(&x);              // screen coordinates

// convert x,y to valid coordinates for m_hwndClip
GetCursorPos(&x);
::ScreenToClient(m_hwndClip, &x);
// save current cursor poosition for DrawSplitter method
m_ptSplpos.x = x;
m_ptSplpos.y = y;
// get size of splitter window (m_ptClientsize)
::GetClientRect(m_hwnd, &m_hbr);
m_hbm = ::CreateBitmap(8, 8, 1, 1, &dotPatternBmp);
m_hbr = ::CreatePatternBrush(m_hbm);
DrawSplitter();
SetCapture(m_hwnd);

::SetCursor(m_crsSize);

return;
}


//---------------------------------------------


CSpliter::OnMouseMove(int x,int y,int flags),int
{
RECT rcClip;

if (m_hwnd == GetCapture())
{
// convert clip window clientRECT to screen coordinates
::GetClientRect(m_hwndClip, rcClip);
::ClientToScreen(m_hwndClip, rcClip);
::ClientToScreen(m_hwndClip, &rcClip+8);

// and snap cursor inside this rectangle
if (m_style & SPLITERSTYLE_VERTICAL)
{
rcClip.right -=(m_splitterW-m_ptSplstart.x-1);
rcClip.left  += m_ptSplstart.x;
}
else {
rcClip.top   += m_ptSplstart.y;
rcClip.bottom-=(m_splitterH-m_ptSplstart.y-1);
}
::ClipCursor(rcClip);

::GetCursorPos(&x); // screen coordinates
::ScreenToClient(m_hwndClip, &x);
::SetCursor(m_crsSize);

DrawSplitter(); // remove splitter
m_ptSplpos.x = x;
m_ptSplpos.y = y;
DrawSplitter();
::ClipCursor(null); // not need on xp, but...
}
else
{
::SetCursor(m_crsSize);
}
return true;
}


//---------------------------------------------


CSpliter::OnLButtonUp(int x,int y,int flags),int
{
if (m_hwnd == GetCapture())
{
word wx = x;
word wy = y;
int offset_x = wx - m_ptSplstart.x;
int offset_y = wy - m_ptSplstart.y;

::ClipCursor(null);
::ReleaseCapture();
DrawSplitter();  // remove splitter
::DeleteObject(m_hbr);
::DeleteObject(m_hbm);

RECT rcParent;
::GetWindowRect(m_hwndParent, rcParent);
::ScreenToClient(m_hwndClip, rcParent);
::ScreenToClient(m_hwndClip, &rcParent+8);

RECT rcSplitter;
::GetWindowRect(m_hwnd, rcSplitter);
::ScreenToClient(m_hwndParent, rcSplitter);
::ScreenToClient(m_hwndParent, &rcSplitter+8);

if (m_style & SPLITERSTYLE_RIGHT)
{
rcParent.right += offset_x;
rcSplitter.right += offset_x;
rcSplitter.left += offset_x;
}
else if (m_style & SPLITERSTYLE_LEFT)
{
rcParent.left += offset_x;
}
else if (m_style & SPLITERSTYLE_BOTTOM)
{
rcParent.bottom += offset_y;
rcSplitter.bottom += offset_y;
rcSplitter.top += offset_y;
}
else if (m_style & SPLITERSTYLE_TOP)
{
rcParent.top += offset_y;
}
::MoveWindow(m_hwndParent, rcParent.left, rcParent.top,
rcParent.right-rcParent.left, rcParent.bottom-rcParent.top,true);

::MoveWindow(m_hwnd, rcSplitter.left, rcSplitter.top,
rcSplitter.right-rcSplitter.left, rcSplitter.bottom-rcSplitter.top,true);

ResizeNextChild();
}
return true;
}


//---------------------------------------------


// window A contains two windows B and C.
// window B and/or C have a splitter window
// if B is resized, C should be also resized
// m_hwndClip    <- A
// m_hwndParent  <- B  (parent of this splitter)
// hwndNextChild <- C
// note: B and C should fit A
CSpliter::ResizeNextChild()
{
if (!m_hwndNotifySizeChanged)
{
m_hwndNotifySizeChanged = ::GetNextWindow(m_hwndParent, GW_HWNDNEXT);
if (!m_hwndNotifySizeChanged)
m_hwndNotifySizeChanged = ::GetNextWindow(m_hwndParent, GW_HWNDPREV);
}
if (m_hwndNotifySizeChanged)
{

RECT rcA; // valid: right, bottom
::GetClientRect(m_hwndClip, rcA);

RECT rcB; // valid: all
::GetClientRect(m_hwndParent, rcB);

RECT rcC; // valid: all; the calculated rect of window C
::SubtractRect(rcC, rcA, rcB);

::MoveWindow(m_hwndNotifySizeChanged, rcC.left, rcC.top,
rcC.right-rcC.left, rcC.bottom-rcC.top,true);
}
return;
}

//---------------------------------------------



CSpliter::DrawSplitter()
{
POINT pt; // stores the original GDI brush origin
HDC hdc = ::GetDC(m_hwndClip);

::SetBrushOrgEx(hdc, m_ptSplpos.x-1, m_hbr, &pt);
HBRUSH hbr_old = ::SelectObject(hdc, m_hbr);

if (m_style & SPLITERSTYLE_VERTICAL)
{
::PatBlt(hdc, m_ptSplpos.x-m_ptSplstart.x, 0, m_splitterW, m_ptClientsize.y, PATINVERT);
}
else
{
::PatBlt(hdc, m_ptClientPos.x, m_ptSplpos.y-m_ptSplstart.y, m_ptClientsize.x, m_splitterH, PATINVERT);
}
::SelectObject(hdc, hbr_old);
::SetBrushOrgEx(hdc, pt.x, pt.y, null); // restore
::ReleaseDC(m_hwnd, hdc);
return;
}



demo application:

class CLeftWin : CWindow
{
CSpliter splitter;
}
class CRightWin : CWindow
{
CSpliter splitter;
}

class CMainWin : CWindow
{
declare virtual OnCreate(),int;
declare virtual OnClose(),int;

CLeftWin  winL;
CRightWin  winR;
}

CMainWin::OnCreate(),int
{
RECT rc;
::GetWindowRect(m_hwnd, rc);
::AdjustWindowRect(rc, ::GetWindowLong(m_hwnd, GWL_STYLE), ::GetMenu(m_hwnd));
::MoveWindow(m_hwnd, rc.left, rc.top, rc.right-rc.left,rc.bottom-rc.top, true);
}

CMainWin::OnClose(),int
{
Destroy();
return 0;
}

#define demo_horizontal

// info: window A is the main window
// we create two child windows B and C on A
// B has a splitter on left side
// splitter.m_hwndClip - handle of A [auto set]
//-------------------------------------
// A = CMainWin
// B = CLeftWin
// C = CRightWin
//-------------------------------------
// if you resize B, the C will be also resized automatically

global sub main()
{
CMainWin w;
w.Create(600,100,400,400,AWS_OVERLAPPEDWINDOW|AWS_VISIBLE,0,"",null);

#ifndef demo_horizontal
// open left window
w.winL.Create(0,0,200,400,AWS_CHILD|AWS_VISIBLE,0,"",w);
w.winL.splitter.Create(0,0,11,11,SPLITERSTYLE_RIGHT ,0,"",w.winL);
// open right window
w.winR.Create(200,0,200,400,AWS_CHILD|AWS_VISIBLE,0,"",w);
#else
// open upper window
w.winL.Create(0,0,400,200,AWS_CHILD|AWS_VISIBLE,0,"",w);
w.winL.splitter.Create(0,0,5,5,SPLITERSTYLE_BOTTOM ,0,"",w.winL);
// open bottom window
w.winR.Create(0,200,400,200,AWS_CHILD|AWS_VISIBLE,0,"",w);
#endif
w.winL.SetWindowColor(0x88FF88);
w.winR.SetWindowColor(0x8888FF);
w.winL.splitter.SetWindowColor(GetSysColor(COLOR_BTNFACE));
//w.winL.splitter.m_hwndClip = w.m_hwnd; // auto
//w.winR.splitter.m_hwndClip = w.m_hwnd; // auto

while w.IsValid() {wait();}
return;
}

Parker

This is just what I needed. Thank you so much!!

Bruce Peaslee

I'll check it out. What is #emit?
Bruce Peaslee
"Born too loose."
iTired (There's a nap for that.)
Well, I headed for Las Vegas
Only made it out to Needles

Ionic Wind Support Team

#emit inserts a single line of assembly  into that point of the code.  Useful for adding a breakpoint when debugging.

#emit int 3

Which is the same as the 'stop' statement in IBasic Pro.

Or for adding static data into the execuable a Sapero has done.
Ionic Wind Support Team

sapero

The first example has been hanged (small corrections), but main window resizing is still unhandled.

The attached zip file has not additional two projects:
first project demonstrates handling two windows (B,C) with splitter in one parent window (A)
Second has other layout: main window (A) has left and right window. The left one has a aplitter, the right is divided to next two windows, the upper has a splitter.

Added a template class for hosting the splitter - if "splitter container" window has been resized, the splitter is also resized

The first example file has been renamed to *_old.
Currently the splitter window has a black border - see csplitter.inc::DEFAULT_SPLITTER_WND_STYLE

If you create the splitter - specify the style (left, top, right, bottom, also where to attach) and width or heigth.
Left and right position is ignored.

Jeffers

Thanks Sapero, brilliant example. I have been trying to work out how to resize controls hosted in the containers. This works ok on main window frame resize, but I having trouble resizing the controls when the splitter position is moved. I am using CSplitter_3.src as a testbed. Can anyone help?  ???

code below.... Regards Jeff :)

#include "csplitter.inc"

struct NMLISTVIEW
{
def hwndFrom as unsigned int;
def idFrom as INT;
def code as INT;
    def iItem as INT;
    def iSubItem as INT ;
    def uNewState as unsigned int;
    def uOldState as unsigned int;
    def uChanged as unsigned int;
    def ptActionx as INT;
def ptActiony as INT;
    def lParam as INT;
}

// CMainWin has CLeftWin and CRightWin
// CRightWin has CRightBottomWin and CRightTopWin

//---------------------------------------------
// a template class for window with a splitter
// where the splitter is automatically resized
class CSplitterContainer : CWindow
{
declare virtual OnSize(int nType,int cx,int cy),int;
CSpliter splitter;
}

CSplitterContainer::OnSize(int nType,int cx,int cy),int
{
RECT rc;
::GetWindowRect(splitter.m_hwnd, rc);

if (splitter.m_style & SPLITERSTYLE_VERTICAL) {
// prevent width changing
cx = rc.right - rc.left;
}else {
// prevent height changing
cy = rc.bottom - rc.top;
}
ScreenToClient(m_hwnd, &rc);
splitter.SetSize(rc.left, rc.top, cx, cy);
return true;
}

//---------------------------------------------

class CLeftWin : CSplitterContainer // has a splitter
{
declare virtual OnCreate(),int;
}

CLeftWin::OnCreate(),int
{
//splitter.Create(0,0,5,5,SPLITERSTYLE_RIGHT ,0,"",this);
//splitter.SetWindowColor(GetSysColor(COLOR_BTNFACE));
return true;
}

class CRightTopWin : CSplitterContainer // has a splitter
{
declare virtual OnCreate(),int;
}

CRightTopWin::OnCreate()
{
splitter.Create(0,0,5,5,SPLITERSTYLE_BOTTOM ,0,"",this);
splitter.SetWindowColor(GetSysColor(COLOR_BTNFACE));
return true;
}

class CRightBottomWin : CWindow
{
// nothing here. This window has two childs
// One of them has a splitter
}

class CRightWin : CWindow
{
declare virtual OnCreate(),int;
declare virtual OnSize(int nType,int cx,int cy),int;
CRightTopWin    top;
CRightBottomWin bottom;
}

CRightWin::OnCreate()
{
RECT rc = GetClientRect();
top.Create   (0,0,rc.right,rc.bottom/2,AWS_CHILD|AWS_VISIBLE,0,"",this);
top.SetWindowColor(0xFFFF88);
top.AddControl(CTLISTVIEW,"",1,1,892,188,AWS_VISIBLE|ALVS_REPORT|AWS_BORDER,AWS_EX_CLIENTEDGE,1);
CListView *pList = top.GetControl(1);
if(pList)
{
    pList->InsertColumn(0,"Title");
    pList->InsertColumn(1,"Description");
    plist->InsertColumn(2,"Link");
    plist->SetColumnWidth(0,200);
    pList->SetColumnWidth(1,350);
    plist->SetColumnWidth(2,335);
pList->SetExtendedStyle(ALVS_EX_FLATSB|ALVS_EX_FULLROWSELECT|ALVS_EX_LABELTIP);
}
bottom.Create(0,rc.bottom/2,rc.right,rc.bottom/2,AWS_CHILD|AWS_VISIBLE,0,"",this);
bottom.SetWindowColor(0x88FFFF);
return true;
}
CRightWin::OnSize(int nType,int cx,int cy),int
{
// need to resize two childs
RECT rcChildTop = top.GetClientRect();
top.SetSize(0,0,cx, rcChildTop.bottom);
CListView *pList = top.GetControl(1);
if(pList)
{
pList->SetSize(1,1,cx,rcChildTop.bottom-12);
pList->SetColumnWidth(2,cx-570);
}
bottom.SetSize(0,rcChildTop.bottom, cx, cy - rcChildTop.bottom);
return true;
}

class CMainWin : CWindow
{
declare virtual OnCreate(),int;
declare virtual OnClose(),int;
declare virtual OnSize(int nType,int cx,int cy),int;

CLeftWin  left;
CRightWin right;
}

CMainWin::OnCreate(),int
{
RECT rc;
::GetWindowRect(m_hwnd, rc);
::AdjustWindowRect(rc, ::GetWindowLong(m_hwnd, GWL_STYLE), ::GetMenu(m_hwnd));
::MoveWindow(m_hwnd, rc.left, rc.top, rc.right-rc.left,rc.bottom-rc.top, true);
CenterWindow();

rc = GetClientRect();
//left.Create(0,0,rc.right/2,rc.bottom,AWS_CHILD|AWS_VISIBLE,0,"",this);
//left.SetWindowColor(0x88FF88);
right.Create(0,0,rc.right,rc.bottom,AWS_CHILD|AWS_VISIBLE,0,"",this);

return true;
}
CMainWin::OnClose(),int {
Destroy();
return 0;
}
CMainWin::OnSize(int nType,int cx,int cy),int
{
// update left.heigth and resize right window to fit this window
RECT rcLeft = left.GetClientRect();
RECT rcThis = GetClientRect();
//left.SetSize(0,0, rcLeft.right, cy);
right.SetSize(rcLeft.right,0, rcThis.right-rcLeft.right, cy);
}

global sub main()
{
CMainWin w;
w.Create(0,0,900,400,AWS_OVERLAPPEDWINDOW|AWS_VISIBLE,0,"",null);

while w.IsValid() {wait();}
return;
// CMainWin *pp = new(CMainWin, 1, 5);
}

sapero

June 09, 2006, 10:48:43 AM #6 Last Edit: June 09, 2006, 11:07:43 AM by sapero
I know, this is complicated, but you can snap OnSize event in the upper window, resize the list view and return control (event) to parent class

class CRightTopWin : CSplitterContainer // has a splitter and ListView
{
declare virtual OnCreate(),int;
declare virtual OnSize(int nType,int cx,int cy),int; // added here!
}
CRightTopWin::OnSize(int nType,int cx,int cy),int
{
// resize the list view
CWindow *pList = GetControl(1); // listview
if(pList) pList->SetSize(2,2,cx-4,cy-4 - splitter.m_splitterH);
// and return control to parent class
return CSplitterContainer!!OnSize(nType,cx,cy);
}


why CRightTopWin, or where is the right window :D

in this example the listview is rounded with 2px border space, SetSize uses splitter.m_splitterH to change heigth to proper value.
in CRightWin::OnSize you can comment out pList->SetSize

Jeffers

Thanks sapero, I was using the three child example because it expands on some of the techniques in Aurora,i.e classes and applying events in different classes. So its just me learning! :D

I will have a play.

Regards Jeff :)