IonicWind Software

Aurora Compiler => Tips and Tricks => Topic started by: sapero on May 23, 2006, 05:25:06 AM

Title: Splitter window
Post by: sapero on May 23, 2006, 05:25:06 AM
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;
}
Title: Re: Splitter window
Post by: Parker on May 23, 2006, 09:22:58 AM
This is just what I needed. Thank you so much!!
Title: Re: Splitter window
Post by: Bruce Peaslee on May 23, 2006, 09:52:54 AM
I'll check it out. What is #emit?
Title: Re: Splitter window
Post by: Ionic Wind Support Team on May 23, 2006, 09:57:12 AM
#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.
Title: Re: Splitter window
Post by: sapero on May 24, 2006, 02:49:58 AM
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.
Title: Re: Splitter window
Post by: Jeffers on June 09, 2006, 04:58:54 AM
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);
}
Title: Re: Splitter window
Post by: sapero on June 09, 2006, 10:48:43 AM
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
Title: Re: Splitter window
Post by: Jeffers on June 09, 2006, 11:41:09 AM
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 :)