Click here to Skip to main content
11,437,397 members (41,108 online)
Click here to Skip to main content
Add your own
alternative version

Custom Tab Controls, Tabbed Frame and Tabbed MDI

, 13 Jul 2005
An extensible framework for creating customized tabs in ATL/WTL, with a VS.NET-like tab control implementation, tabbed frames, tabbed MDI, and more.
tabbingframework_demo.zip
TabbedSDISplitter
Release
TabbedSDISplitter.exe
res
Doc.ico
folder_c.ico
folder_o.ico
TabbedSDISplitter.exe.manifest
TabbedSDISplitter.ico
toolbar.bmp
TabbedSDISplitter.dsp
TabbedSDISplitter.dsw
TabDemo
Release
TabDemo.exe
res
error.ico
folder_closed.ico
folder_open.ico
info.ico
log.ico
TabDemo.exe.manifest
TabDemo.ico
TabDemodoc.ico
toolbar.bmp
warning.ico
TabDemo.dsp
TabDemo.dsw
TabbingFramework_ScreenShot.png
DockingDemo
DockingDemo.dsp
DockingDemo.dsw
Release
DockingDemo.exe
res
DockingDemo.exe.manifest
DockingDemo.ico
DockingDemodoc.ico
error.ico
folder_closed.ico
folder_open.ico
info.ico
log.ico
msdev_tab_icons.bmp
toolbar.bmp
warning.ico
include
Sergey Klimov
SimpleTabbedMDIDemo
Release
SimpleTabbedMDIDemo.exe
res
SimpleTabbedMDIDemo.exe.manifest
SimpleTabbedMDIDemo.ico
SimpleTabbedMDIDemodoc.ico
toolbar.bmp
SimpleTabbedMDIDemo.dsp
SimpleTabbedMDIDemo.dsw
tabbingframework_history.zip
tabbingframework_priorhistory.zip
tabbingframework_src.zip
// Copyright (c) 2002
// Sergey Klimov (kidd@ukr.net)
// WTL Docking windows
//
// This code is provided "as is", with absolutely no warranty expressed
// or implied. Any use is at your own risk.
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed unmodified by any means PROVIDING it is
// not sold for profit without the authors written consent, and
// providing that this notice and the authors name is included. If
// the source code in  this file is used in any commercial application
// then a simple email woulod be nice.

#ifndef __WTL_DW__WNDFRMPKG_H__
#define __WTL_DW__WNDFRMPKG_H__

#pragma once

#ifndef __ATLMISC_H__
	#error WndFrmPkg.h requires atlmisc.h to be included first
#endif

#include <memory>
#include "ssec.h"
#include "DDTracker.h"
#ifdef USE_BOOST
#include<boost/smart_ptr.hpp>
#endif

namespace dockwins{

class CWndFrame
{
public:
	typedef long position;
	typedef long distance;

	class CCmp
	{
	public:
		CCmp(HWND hWnd)	:m_hWnd(hWnd)
		{
		}
		bool operator ()(const CWndFrame& frame) const
		{
			return (frame.hwnd() == m_hWnd);
		}
	protected:
		HWND m_hWnd;
	};

	CWndFrame(position pos=0,HWND hWnd=NULL)
		:m_hWnd(hWnd),m_pos(pos)
	{
	}

	HWND hwnd() const
	{
		return m_hWnd;
	}

	operator position() const
	{
		return (position)m_pos;
	}

	CWndFrame& operator += (position val)
	{
		m_pos+=val;
		return *this;
	}
	CWndFrame& operator -= (position val)
	{
		m_pos-=val;
		return *this;
	}

	CWndFrame& operator = (position pos)
	{
		m_pos=pos;
		return *this;
	}

	CWndFrame& operator = (double pos)
	{
		m_pos=pos;
		return *this;
	}

	double get_real()
	{
		return m_pos;
	}

	HDWP DeferFramePos(HDWP hdwp,long x1,long y1,long x2,long y2) const
	{
		return ::DeferWindowPos(hdwp,hwnd(),
								NULL,
								x1,y1,
								x2-x1,y2-y1,
								SWP_NOZORDER | SWP_NOACTIVATE);
	}
	void GetMinMaxInfo(LPMINMAXINFO pMinMaxInfo) const
	{
		::SendMessage(m_hWnd,WM_GETMINMAXINFO,NULL,reinterpret_cast<LPARAM>(pMinMaxInfo));
	}
	distance MinDistance() const
	{
		DFMHDR dockHdr;
		dockHdr.code=DC_GETMINDIST;
		dockHdr.hWnd=m_hWnd;
		dockHdr.hBar=::GetParent(m_hWnd);
		assert(::IsWindow(dockHdr.hBar));
		return (distance)::SendMessage(dockHdr.hBar,WMDF_DOCK,NULL,reinterpret_cast<LPARAM>(&dockHdr));
	}
protected:
	double m_pos;
	mutable HWND m_hWnd;
};

//template<class T,const T::distance TMinDist=0>
//struct CWndFrameTraits : ssec::spraits<T, T::position, T::distance/*,TMinDist*/>
//{
//	typedef ssec::spraits<T,position,position/*,TMinDist*/> baseClass;
//	static distance min_distance(const T& x)
//	{
//		const distance dist=TMinDist;
//		return dist+x.MinDistance();
//	}
//
//};

template< class TFrame = CWndFrame , class TTraits = CDockingFrameTraits>
class CWndFramesPackageBase
{
	typedef typename TFrame CFrame;
	typedef typename TTraits CTraits;
	typedef typename CTraits::CSplitterBar	CSplitterBar;
	typedef CWndFramesPackageBase<CFrame,TTraits> thisClass;
protected:
	enum {splitterSize=CSplitterBar::sbThickness};
	typedef typename CFrame::position	position;

#if _MSC_VER >= 1310
	template<class T,const typename T::distance TMinDist=0>
#else
	template<class T,const T::distance TMinDist=0>
#endif
	struct CWndFrameTraits : ssec::spraits<T, typename T::position, typename T::distance/*,TMinDist*/>
	{
		typedef ssec::spraits<T,position,position/*,TMinDist*/> baseClass;
		static distance min_distance(const T& x)
		{
			const distance dist=TMinDist;
			return dist+x.MinDistance();
		}

	};
	typedef CWndFrameTraits<CFrame,splitterSize> CFrameTraits;
	typedef ssec::ssection<CFrame,CFrameTraits> CFrames;

	typedef typename CFrames::iterator				iterator;
	typedef typename CFrames::reverse_iterator		reverse_iterator;
	typedef typename CFrames::const_iterator			const_iterator;
	typedef typename CFrames::const_reverse_iterator	const_reverse_iterator;

	typedef ssec::bounds_type<position> CBounds;
protected:
	struct CEmbeddedSplitterBar : public CSplitterBar
	{
		CEmbeddedSplitterBar(bool bHorizontal,position vertex,const CRect& rcClient)
				:CSplitterBar(bHorizontal)
		{
			if(IsHorizontal())
			{
					top=vertex;
					bottom=top+GetThickness();
					left=rcClient.left;
					right=rcClient.right;
			}
			else
			{
					left=vertex;
					right=left+GetThickness();
					top=rcClient.top;
					bottom=rcClient.bottom;
			}

		}
	};
	class CEmbeddedSplitterBarPainter
	{
	public:
		CEmbeddedSplitterBarPainter(CDC& dc,bool bHorizontal,const CRect& rc)
			:m_dc(dc),m_rc(rc),m_bHorizontal(bHorizontal)
		{
		}
		void operator()(const CFrame& ref) const
		{
			CEmbeddedSplitterBar splitter(m_bHorizontal,ref,m_rc);
			splitter.Draw(m_dc);
		}
	protected:
		const	CRect& m_rc;
		bool	m_bHorizontal;
		CDC&	m_dc;
	};

	class CSplitterMoveTrackerBase : public IDDTracker//CDDTrackerBaseT<CSizeTrackerBase>
	{
	protected:
		void SetPosition()
		{
			assert(m_bounds.bind(m_pos)==m_pos);
			m_frames.set_position(m_i,m_pos);
			m_owner.Arrange(m_rc);
			m_wnd.RedrawWindow(&m_rc,NULL,RDW_INVALIDATE | RDW_UPDATENOW);
		}
	public:
		CSplitterMoveTrackerBase(HWND hWnd,thisClass& owner,const CPoint& pt,const CRect& rc)
			:m_wnd(hWnd),m_owner(owner),m_frames(owner.m_frames),m_rc(rc)
		{
			position pos=owner.IsHorizontal() ? pt.x : pt.y;
			m_i=m_frames.locate(pos);
			if(m_i!=m_frames.end())
			{
				m_pos=(*m_i);
				m_offset=pos-m_pos;
				m_frames.get_effective_bounds(m_i,m_bounds);
			}
		}
		operator iterator() const
		{
			return m_i;
		}
		void OnMove(long x, long y)
		{
			position pos = m_owner.IsHorizontal() ? x : y;
			pos=m_bounds.bind(pos-m_offset);
			if(pos!=m_pos)
			{
				m_pos=pos;
				Move();
			}
		}
		virtual void Move()=0;

	protected:
		thisClass&		m_owner;
		CFrames&		m_frames;
		iterator		m_i;
		CBounds			m_bounds;
		position		m_pos;
		position		m_offset;
		const CRect&	m_rc;
		CWindow			m_wnd;
	};

	friend class CSplitterMoveTrackerBase;

	class CSplitterMoveTrackerFull : public CSplitterMoveTrackerBase
	{
	public:
		CSplitterMoveTrackerFull(HWND hWnd,thisClass& owner,const CPoint& pt,const CRect& rc)
			:CSplitterMoveTrackerBase(hWnd,owner,pt,rc)
		{
		}
		void Move()
		{
			SetPosition();
		}
	};

	class CSplitterMoveTrackerGhost : public CSplitterMoveTrackerBase
	{
		typedef CEmbeddedSplitterBar CSplitterBar;
		typedef CSimpleSplitterBarSlider<CSplitterBar> CSlider;
	public:
		CSplitterMoveTrackerGhost(HWND hWnd,thisClass& owner,
									const CPoint& pt,const CRect& rc)
			:CSplitterMoveTrackerBase(hWnd,owner,pt,rc),
			  m_dc(NULL),m_splitter(!owner.IsHorizontal(),m_pos,rc),m_slider(m_splitter)
		{
			CPoint point(rc.TopLeft ());
			::ClientToScreen(hWnd,&point);
			CSize offset(point.x-rc.left,point.y-rc.top);
			m_splitter+=offset;
			m_ghOffset=owner.IsHorizontal()
									?offset.cx
									:offset.cy;
		}
		void BeginDrag()
		{
			m_splitter.DrawGhostBar(m_dc);
		}
		void EndDrag(bool bCanceled)
		{
			m_splitter.CleanGhostBar(m_dc);
			if(!bCanceled)
				SetPosition();
		}
		void Move()
		{
			m_splitter.CleanGhostBar(m_dc);
			m_slider=m_pos+m_ghOffset;
			m_splitter.DrawGhostBar(m_dc);
		}

	protected:
		position		m_ghOffset;
		CSplitterBar	m_splitter;
		CSlider			m_slider;
		CWindowDC		m_dc;
	};
	template<long add=0>
	class CMinMaxInfoAccumulator
	{
		typedef LPMINMAXINFO (*CFunPtr)(MINMAXINFO& mmInfo,LPMINMAXINFO pMinMaxInfo);
	public:
		CMinMaxInfoAccumulator(bool bHorizontal)
		{
			m_pFun=bHorizontal ? &MinMaxInfoH : &MinMaxInfoV;
		}
		LPMINMAXINFO operator() (LPMINMAXINFO pMinMaxInfo,const CFrame& x) const
		{
			MINMAXINFO mmInfo;
			ZeroMemory(&mmInfo,sizeof(MINMAXINFO));
			x.GetMinMaxInfo(&mmInfo);
			return (*m_pFun)(mmInfo,pMinMaxInfo);
		}
	protected:
		static LPMINMAXINFO MinMaxInfoH(MINMAXINFO& mmInfo,LPMINMAXINFO pMinMaxInfo)
		{
			if(pMinMaxInfo->ptMinTrackSize.y<mmInfo.ptMinTrackSize.y)
				pMinMaxInfo->ptMinTrackSize.y=mmInfo.ptMinTrackSize.y;
			pMinMaxInfo->ptMinTrackSize.x+=mmInfo.ptMinTrackSize.x+add;
			return pMinMaxInfo;
		}
		static LPMINMAXINFO MinMaxInfoV(MINMAXINFO& mmInfo,LPMINMAXINFO pMinMaxInfo)
		{
			if(pMinMaxInfo->ptMinTrackSize.x<mmInfo.ptMinTrackSize.x)
				pMinMaxInfo->ptMinTrackSize.x=mmInfo.ptMinTrackSize.x;
			pMinMaxInfo->ptMinTrackSize.y+=mmInfo.ptMinTrackSize.y+add;
			return pMinMaxInfo;
		}
	protected:
		CFunPtr	m_pFun;
	};
protected:
	bool ArrangeH(const CRect& rc)
	{
		HDWP hdwp=reinterpret_cast<HDWP>(TRUE);
		const_iterator begin=m_frames.begin();
		const_iterator end=m_frames.end();
		if(begin!=end)
		{
			hdwp=BeginDeferWindowPos((int)m_frames.size());
			const_iterator next=begin;
			while((hdwp!=NULL)&&(++next!=end))
			{
				long x=(*begin)+splitterSize;
				hdwp=begin->DeferFramePos(hdwp,
											x,
											rc.top,
											(*next),
											rc.bottom);
				begin=next;
			}
			if(hdwp!=NULL)
			{
				long x=(*begin)+splitterSize;
				hdwp=begin->DeferFramePos(hdwp,
											x,
											rc.top,
											rc.right,
											rc.bottom);
				if(hdwp)
					::EndDeferWindowPos(hdwp);
			}
		}
		return hdwp!=NULL;
	}

	bool ArrangeV(const CRect& rc)
	{
		HDWP hdwp=reinterpret_cast<HDWP>(TRUE);
		const_iterator begin=m_frames.begin();
		const_iterator end=m_frames.end();
		if(begin!=end)
		{
			hdwp=BeginDeferWindowPos((int)m_frames.size());
			const_iterator next=begin;
			while((hdwp!=NULL)&&(++next!=end))
			{
				long y=(*begin)+splitterSize;
				hdwp=begin->DeferFramePos(hdwp,
											rc.left,
											y,
											rc.right,
											(*next));
				begin=next;
			}
			if(hdwp!=NULL)
			{
				long y=(*begin)+splitterSize;
				hdwp=begin->DeferFramePos(hdwp,
											rc.left,
											y,
											rc.right,
											rc.bottom);
				if(hdwp)
					::EndDeferWindowPos(hdwp);
			}
		}
		return hdwp!=NULL;
	}
	bool Arrange(const CRect& rcClient)
	{
		bool bRes;
		if(IsHorizontal())
			bRes=ArrangeH(rcClient);
		else
			bRes=ArrangeV(rcClient);
		return bRes;
	}

	CWndFramesPackageBase(bool bHorizontal)
		:m_bHorizontal(bHorizontal),m_frames(0,0)
	{
	}
public:
	bool IsHorizontal() const
	{
		return m_bHorizontal;
	}

	void SetOrientation(bool bHorizontal)
	{
		m_bHorizontal=bHorizontal;
	}

	HCURSOR GetCursor(const CPoint& pt,const CRect& rc) const
	{
		HCURSOR hCursor=NULL;
		position pos=IsHorizontal() ?pt.x :pt.y;
		const_iterator i=m_frames.locate(pos);
		if(i!=m_frames.end())
		{
			CEmbeddedSplitterBar splitter(!IsHorizontal(),(*i),rc);
			hCursor=splitter.GetCursor(pt);
		}
		return hCursor;
	}
/*
	bool StartSliding(HWND hWnd,const CPoint& pt,const CRect& rc,bool bGhostMove)
	{
		std::auto_ptr<CSplitterMoveTrackerBase> pTracker;

		if(bGhostMove)
			pTracker.reset(new CSplitterMoveTrackerGhost(hWnd,*this,pt,rc));
		else
			pTracker.reset(new CSplitterMoveTrackerFull(hWnd,*this,pt,rc));

		bool bRes=false;
		if(const_iterator(*pTracker)!=m_frames.end())
			 bRes=TrackDragAndDrop(*pTracker,hWnd);
		return bRes;
	}
*/
	bool StartSliding(HWND hWnd,const CPoint& pt,const CRect& rc,bool bGhostMove)
	{
		bool bRes=false;
		position pos=IsHorizontal() ?pt.x :pt.y;
		const_iterator i=m_frames.locate(pos);
		if(i!=m_frames.end())
		{
			CEmbeddedSplitterBar splitter(!IsHorizontal(),(*i),rc);
			if(splitter.IsPtIn(pt))
			{
				std::auto_ptr<CSplitterMoveTrackerBase> pTracker;

#if (_MSC_VER >= 1310)
				if(bGhostMove)
					pTracker.reset(new CSplitterMoveTrackerGhost(hWnd,*this,pt,rc));
				else
					pTracker.reset(new CSplitterMoveTrackerFull(hWnd,*this,pt,rc));
#else // (_MSC_VER < 1310)
				if(bGhostMove)
					pTracker=std::auto_ptr<CSplitterMoveTrackerBase>(
										new CSplitterMoveTrackerGhost(hWnd,*this,pt,rc));
				else
					pTracker=std::auto_ptr<CSplitterMoveTrackerBase>(
										new CSplitterMoveTrackerFull(hWnd,*this,pt,rc));
#endif

				if(const_iterator(*pTracker)!=m_frames.end())
					 bRes=TrackDragAndDrop(*pTracker,hWnd);
			}
		}
		return bRes;
	}

	bool UpdateLayout(const CRect& rc)
	{
		CBounds bounds;
		if(IsHorizontal())
		{
				bounds.low=rc.left;
				bounds.hi=rc.right;
		}
		else
		{
				bounds.low=rc.top;
				bounds.hi=rc.bottom;
		}
		bounds.low-=splitterSize;

		CBounds::distance_t limit=m_frames.distance_limit();
		if(bounds.distance()<limit)
						bounds.hi=bounds.low+limit;
		m_frames.set_bounds(bounds);
		return Arrange(rc);
	}

	void GetMinFrameSize(const DFMHDR* pHdr,CSize& sz) const
	{
		MINMAXINFO mmInfo;
		ZeroMemory(&mmInfo,sizeof(MINMAXINFO));
		::SendMessage(pHdr->hWnd,WM_GETMINMAXINFO,NULL,reinterpret_cast<LPARAM>(&mmInfo));
		sz.cx=mmInfo.ptMinTrackSize.x;
		sz.cy=mmInfo.ptMinTrackSize.y;
	}
	LRESULT GetMinFrameDist(const DFMHDR* pHdr) const
	{
		CSize sz;
		GetMinFrameSize(pHdr,sz);
		return (IsHorizontal()) ? sz.cx : sz.cy;
	}

	void GetMinMaxInfo(LPMINMAXINFO pMinMaxInfo) const
	{
		if(m_frames.size()!=0)
		{
			pMinMaxInfo=std::accumulate(m_frames.begin(),m_frames.end(),
											pMinMaxInfo,CMinMaxInfoAccumulator<splitterSize>(IsHorizontal()));
			if(IsHorizontal())
				pMinMaxInfo->ptMinTrackSize.x-=splitterSize;
			else
				pMinMaxInfo->ptMinTrackSize.y-=splitterSize;
		}
	}
	void Draw(CDC& dc,const CRect& rc) const
	{
		if(m_frames.begin()!=m_frames.end())
			std::for_each(++m_frames.begin(),m_frames.end(),CEmbeddedSplitterBarPainter(dc,!IsHorizontal(),rc));
	}

	bool AcceptDock(DFDOCKRECT* pHdr,const CRect& rc)
	{
		bool bRes=false;
		CSize sz;
		GetMinFrameSize(&(pHdr->hdr),sz);
		if(rc.PtInRect(CPoint(pHdr->rect.left,pHdr->rect.top))
			&& (sz.cx<=rc.Width())
				&& (sz.cy<=rc.Height())
					&& ((( (IsHorizontal()) ? sz.cx : sz.cy)+m_frames.distance_limit())<(m_frames.hi()-m_frames.low())))
		{
			if(IsHorizontal())
			{
				pHdr->rect.top=rc.top;
				pHdr->rect.bottom=rc.bottom;
			}
			else
			{
				pHdr->rect.left=rc.left;
				pHdr->rect.right=rc.right;
			}
			bRes=true;
		}
		return bRes;
	}
	bool Dock(CFrame& frame,const DFDOCKRECT* pHdr,const CRect& rc)
	{
		position		  pos;
		CFrames::distance len;

		if(IsHorizontal())
		{
			pos=pHdr->rect.left;
			len=pHdr->rect.right-pHdr->rect.left;
		}
		else
		{
			pos=pHdr->rect.top;
			len=pHdr->rect.bottom-pHdr->rect.top;
		}

		iterator i=m_frames.locate(pos);
		if(i!=m_frames.end())
		{
			iterator inext=i;
			++inext;
			CBounds fbounds((*i),(inext!=m_frames.end()) ? position(*inext) : m_frames.hi());
			if((fbounds.low+(fbounds.hi-fbounds.low)/3)<pos)
				i=inext;
		}
		frame=pos;
		m_frames.insert(i,frame,len);

		return Arrange(rc);
	}
	bool Undock(const DFMHDR* pHdr,const CRect& rc)
	{
		iterator i=std::find_if(m_frames.begin(),m_frames.end(),CFrame::CCmp(pHdr->hWnd));
		assert(i!=m_frames.end());
		m_frames.erase(i);
		return Arrange(rc);
	}
	bool Replace(const DFDOCKREPLACE* pHdr,const CRect& rc)
	{
		iterator i=std::find_if(m_frames.begin(),m_frames.end(),CFrame::CCmp(pHdr->hdr.hWnd));
		assert(i!=m_frames.end());
		m_frames.replace(i,CFrame(0,pHdr->hWnd));
		return Arrange(rc);
	}

protected:
	bool	m_bHorizontal;
	CFrames	m_frames;
};

template< class TTraits = CDockingFrameTraits >
class CWndFramesPackage : public CWndFramesPackageBase<CWndFrame,TTraits >
{
	typedef typename CWndFrame CFrame;
	typedef typename TTraits CTraits;
	typedef typename CTraits::CSplitterBar	CSplitterBar;
	typedef CWndFramesPackage<TTraits> thisClass;
	typedef CWndFramesPackageBase<CFrame,TTraits > baseClass;
public:
	CWndFramesPackage(bool bHorizontal):baseClass(bHorizontal)
	{
	}
	void PrepareForDocking(CWindow wnd,HDOCKBAR bar)
	{
		wnd.ShowWindow(SW_HIDE);
		DWORD style = wnd.GetWindowLong(GWL_STYLE);
		DWORD newStyle = style&(~WS_POPUP)|WS_CHILD;
		wnd.SetWindowLong( GWL_STYLE, newStyle);
		wnd.SetParent(bar);
		wnd.SendMessage(WM_NCACTIVATE,TRUE);
		wnd.SendMessage(WMDF_NDOCKSTATECHANGED,
					MAKEWPARAM(TRUE,IsHorizontal()),
					reinterpret_cast<LPARAM>(bar));
	}
	void PrepareForUndocking(CWindow wnd,HDOCKBAR bar)
	{
		wnd.ShowWindow(SW_HIDE);
		DWORD style = wnd.GetWindowLong(GWL_STYLE);
		DWORD newStyle = style&(~WS_CHILD)|WS_POPUP;
		wnd.SetWindowLong( GWL_STYLE, newStyle);
		wnd.SetParent(NULL);

		wnd.SendMessage(WMDF_NDOCKSTATECHANGED,
			FALSE,
			reinterpret_cast<LPARAM>(bar));
	}

	bool SetDockingPosition(const DFDOCKPOS* pHdr,const CRect& rc)
	{
		assert(::IsWindow(pHdr->hdr.hWnd));
		CWindow wnd(pHdr->hdr.hWnd);
		PrepareForDocking(wnd,pHdr->hdr.hBar);
		position pos=m_frames.low()+position((m_frames.hi()-m_frames.low())*pHdr->fPctPos);
		m_frames.insert(CFrame(pos,pHdr->hdr.hWnd),pHdr->nHeight);
//		wnd.ShowWindow(SW_SHOWNA);
		bool bRes=Arrange(rc);
		wnd.SetWindowPos(NULL,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
		return bRes;
	}

	bool GetDockingPosition(DFDOCKPOS* pHdr,const CRect& /*rc*/) const
	{
		assert(::IsWindow(pHdr->hdr.hWnd));
		const_iterator i=std::find_if(m_frames.begin(),m_frames.end(),CFrame::CCmp(pHdr->hdr.hWnd));
		bool bRes=(i!=m_frames.end());
		if(bRes)
		{
			position pos=*i-m_frames.low();
			pHdr->fPctPos=float(pos)/(m_frames.hi()-m_frames.low());
			pHdr->nHeight=m_frames.get_frame_size(i)-CSplitterBar::GetThickness();
//			pHdr->nWidth=IsHorizontal() ? rc.Height() : rc.Width();
//			pHdr->nWidth-=CSplitterBar::GetThickness();
			if(m_frames.size()==1)
				pHdr->dwDockSide|=CDockingSide::sSingle;
		}
		return bRes;
	}

	bool AcceptDock(DFDOCKRECT* pHdr,const CRect& rc)
	{
		return (!((m_frames.size()==1)
					&&(m_frames.begin()->hwnd()==pHdr->hdr.hWnd)))
						  &&baseClass::AcceptDock(pHdr,rc);
	}
	bool Dock(DFDOCKRECT* pHdr,const CRect& rc)
	{
		assert(::IsWindow(pHdr->hdr.hWnd));
		CWindow wnd(pHdr->hdr.hWnd);
		PrepareForDocking(wnd,pHdr->hdr.hBar);
		CFrame frame(0,pHdr->hdr.hWnd);
		bool bRes=baseClass::Dock(frame,pHdr,rc);
		wnd.SetWindowPos(NULL,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
//		wnd.ShowWindow(SW_SHOWNA);
		return bRes;

	}
	bool Undock(DFMHDR* pHdr,const CRect& rc)
	{
		bool bRes=baseClass::Undock(pHdr,rc);
		assert(bRes);
		PrepareForUndocking(pHdr->hWnd,pHdr->hBar);
		if(m_frames.size()==0)
		{
			DFMHDR dockHdr;
			dockHdr.hWnd = pHdr->hBar;
			dockHdr.hBar = ::GetParent(pHdr->hBar);
			assert(::IsWindow(dockHdr.hBar));
			dockHdr.code=DC_UNDOCK;
			::SendMessage(dockHdr.hBar,WMDF_DOCK,NULL,reinterpret_cast<LPARAM>(&dockHdr));
			::PostMessage(dockHdr.hWnd,WM_CLOSE,NULL,NULL);
		}
		return bRes;
	}

	bool Replace(const DFDOCKREPLACE* pHdr,const CRect& rc)
	{
		PrepareForUndocking(pHdr->hdr.hWnd,pHdr->hdr.hBar);
		PrepareForDocking(pHdr->hWnd,pHdr->hdr.hBar);
		bool bRes=baseClass::Replace(pHdr,rc);
		assert(bRes);
		::SetWindowPos(pHdr->hWnd,NULL,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
//		::ShowWindow(pHdr->hWnd,SW_SHOWNA);
		return bRes;
	}

};

template<class T>
class CPtrFrame
{
	typedef CPtrFrame<T> thisClass;
public:
	typedef typename T::position position;
	typedef typename T::distance distance;

	class CCmp
	{
	public:
		CCmp(HWND hWnd)	:m_hWnd(hWnd)
		{
		}
		bool operator ()(const thisClass& frame) const
		{
			return (frame.hwnd() == m_hWnd);
		}
	protected:
		HWND m_hWnd;
	};

	CPtrFrame(const CPtrFrame& x):m_pos(x.m_pos),m_ptr(x.m_ptr)
	{
	}
	CPtrFrame(position pos, T* ptr)
		:m_pos(pos),m_ptr(ptr)
	{
	}
	HWND hwnd() const
	{
		ATLASSERT(m_ptr.get() != NULL);
		return m_ptr->operator HWND();
	}

	operator position() const
	{
		return (position)m_pos;
	}

	thisClass& operator += (position val)
	{
		m_pos+=val;
		return *this;
	}
	thisClass& operator -= (position val)
	{
		m_pos-=val;
		return *this;
	}

	thisClass& operator = (position pos)
	{
		m_pos=pos;
		return *this;
	}

	thisClass& operator = (double pos)
	{
		m_pos=pos;
		return *this;
	}

	double get_real()
	{
		return m_pos;
	}

	HDWP DeferFramePos(HDWP hdwp,long x1,long y1,long x2,long y2) const
	{
		ATLASSERT(m_ptr.get() != NULL);
		return m_ptr->DeferFramePos(hdwp,x1,y1,x2,y2);
	}
	T* operator ->() const
	{
		return m_ptr.get();
	}
	void GetMinMaxInfo(LPMINMAXINFO pMinMaxInfo) const
	{
		ATLASSERT(m_ptr.get() != NULL);
		m_ptr->GetMinMaxInfo(pMinMaxInfo);
	}
	distance MinDistance() const
	{
		ATLASSERT(m_ptr.get() != NULL);
		return m_ptr->MinDistance();
	}
protected:
	double m_pos;
#ifdef USE_BOOST
	mutable boost::shared_ptr<T> m_ptr;
#else
	mutable std::auto_ptr<T> m_ptr;
#endif
};


struct IFrame
{
	typedef long position;
	typedef long distance;

	virtual  ~IFrame(){};
	virtual  HWND hwnd() const=0;
	operator HWND() const
	{
		return hwnd();
	}
	virtual bool AcceptDock(DFDOCKRECT* pHdr) const=0;
	virtual distance MinDistance() const=0;
	virtual void GetMinMaxInfo(LPMINMAXINFO pMinMaxInfo) const=0;

	virtual HDWP DeferFramePos(HDWP hdwp,long x1,long y1,long x2,long y2) const
	{
		return ::DeferWindowPos(hdwp,hwnd(),
								NULL,
								x1,y1,
								x2-x1,y2-y1,
								SWP_NOZORDER | SWP_NOACTIVATE);
	}
};


class CWindowPtrWrapper : public IFrame
{
public:
	CWindowPtrWrapper(HWND* phWnd):m_phWnd(phWnd)
	{
	}
	virtual HWND hwnd() const
	{
		return (*m_phWnd);
	}
	virtual bool AcceptDock(DFDOCKRECT* /*pHdr*/) const
	{
		return false;
	}
	virtual HDWP DeferFramePos(HDWP hdwp,long x1,long y1,long x2,long y2) const
	{
		if(*m_phWnd!=NULL)
			hdwp=::DeferWindowPos(hdwp,hwnd(),
									NULL,
									x1,y1,
									x2-x1,y2-y1,
									SWP_NOZORDER | SWP_NOACTIVATE);
		return hdwp;
	}
	void GetMinMaxInfo(LPMINMAXINFO pMinMaxInfo) const
	{
		if(*m_phWnd!=NULL)
		 ::SendMessage(hwnd(),WM_GETMINMAXINFO,NULL,reinterpret_cast<LPARAM>(pMinMaxInfo));
	}
	virtual distance MinDistance() const
	{
		MINMAXINFO mmInfo;
		ZeroMemory(&mmInfo,sizeof(MINMAXINFO));
		GetMinMaxInfo(&mmInfo);
		return mmInfo.ptMinTrackSize.x;;
	}
protected:
	HWND* m_phWnd;
};

//TImbeddedPackegeWnd
template<class TPackageFrame,class TTraits = CDockingWindowTraits >
class CSubWndFramesPackage :
		public CRect,
		public CWndFramesPackageBase<CPtrFrame<IFrame>,TTraits >
{
	typedef CPtrFrame<IFrame> CFrame;
	typedef TTraits CTraits;
	typedef typename CTraits::CSplitterBar	CSplitterBar;
	typedef CSubWndFramesPackage<TPackageFrame,TTraits> thisClass;
	typedef CWndFramesPackageBase<CFrame,TTraits >		baseClass;
	typedef typename TPackageFrame	CPackageFrame;
	enum {controlledLen=(15+CSplitterBar::sbThickness)};
	struct  CDockOrientationFlag
	{
		enum{low=0,high=1};
		static void SetHigh(DWORD& flag)
		{
			flag=high;
		}
		static void SetLow(DWORD& flag)
		{
			flag=low;
		}
		static bool IsLow(DWORD flag)
		{
			return (flag&high)==0;
		}
	};
protected:
	class CFrameWrapper : public IFrame
	{
	public:
		CFrameWrapper(thisClass* ptr):m_ptr(ptr)
		{
		}
		virtual ~CFrameWrapper()
		{
		}
		virtual  HWND hwnd() const
		{
			return NULL;
		}
/*
		virtual bool AcceptDock(DFDOCKRECT* pHdr) const
		{
			return m_ptr->AcceptDock(pHdr);
		}
*/
		virtual bool AcceptDock(DFDOCKRECT* /*pHdr*/) const
		{
			assert(false);
			return true;
		}

		virtual HDWP DeferFramePos(HDWP hdwp,long x1,long y1,long x2,long y2) const
		{
			m_ptr->UpdateLayout(CRect(x1,y1,x2,y2));
			return hdwp;
		}
		virtual void GetMinMaxInfo(LPMINMAXINFO pMinMaxInfo) const
		{
			m_ptr->GetMinMaxInfo(pMinMaxInfo);
		}
		virtual distance MinDistance() const
		{
			MINMAXINFO mmInfo;
			ZeroMemory(&mmInfo,sizeof(MINMAXINFO));
			GetMinMaxInfo(&mmInfo);
			return m_ptr->IsHorizontal() ? mmInfo.ptMinTrackSize.y : mmInfo.ptMinTrackSize.x;
		}
	protected:
		thisClass* m_ptr;
	};
public:
	CSubWndFramesPackage(bool bHorizontal)
		:baseClass(bHorizontal),m_pDecl(0)
	{
		SetRectEmpty();
	}
	HCURSOR GetCursor(const CPoint& pt) const
	{
		return baseClass::GetCursor(pt,*this);
	}
	bool StartSliding(HWND hWnd,const CPoint& pt,bool bGhostMove)
	{
		return baseClass::StartSliding(hWnd,pt,*this,bGhostMove);
	}

#ifndef DW_PROPORTIONAL_RESIZE
	bool UpdateLayout(const CRect& rc)
	{
		bool bRes=EqualRect(&rc)!=FALSE;
		if(!bRes)
		{
			CopyRect(rc);
			CBounds bounds;
			if(IsHorizontal())
			{
					bounds.low=rc.left;
					bounds.hi=rc.right;
			}
			else
			{
					bounds.low=rc.top;
					bounds.hi=rc.bottom;
			}
			bounds.low-=splitterSize;

			CBounds::distance_t limit=m_frames.distance_limit();
			if(bounds.distance()<limit)
							bounds.hi=bounds.low+limit;
			if(m_pDecl!=0)
				m_frames.set_bounds(bounds,CFrame::CCmp(m_pDecl->hwnd()));
			else
				m_frames.set_bounds(bounds);
			bRes=Arrange(rc);
		}
		return bRes;
	}
#else
	bool UpdateLayout(const CRect& rc)
	{
		bool bRes=EqualRect(&rc)!=FALSE;
		if(!bRes)
		{
			CopyRect(rc);
			bRes=baseClass::UpdateLayout(*this);
		}
		return bRes;
	}
#endif //DW_PROPORTIONAL_RESIZE
	void Draw(CDC& dc)
	{
		baseClass::Draw(dc,*this);
	}

	bool AcceptDock(DFDOCKRECT* pHdr)
	{
		CPoint pt(pHdr->rect.left,pHdr->rect.top);
		CSize sz(0,0);
		position pos;
		if(IsHorizontal())
		{
			pos=pt.x;
			sz.cx=controlledLen;
		}
		else
		{
			pos=pt.y;
			sz.cy=controlledLen;
		}
		bool bRes=PtInRect(pt)==TRUE;
		if(bRes)
		{
//			position pos=IsHorizontal() ? pHdr->rect.left :pHdr->rect.top;
			const_iterator i=m_frames.locate(pos);
			if( m_frames.get_frame_low(i)+controlledLen>pos)
				bRes=AcceptDock(i,pHdr,*this);
			else
			{
				if( m_frames.get_frame_hi(i)-controlledLen<pos)
					bRes=AcceptDock(++i,pHdr,*this);
				else
				{
					bRes=i->hwnd()!=m_pDecl->hwnd();
					if(bRes)
						bRes=(*i)->AcceptDock(pHdr);
				}
			}
		}
		else
		{
			CRect rc(this);
			rc.InflateRect(sz);
			bRes=rc.PtInRect(pt)==TRUE;
			if(bRes)
			{
				if(pos<m_frames.hi())
					bRes=AcceptDock(m_frames.begin(),pHdr,rc);
				else
					bRes=AcceptDock(m_frames.end(),pHdr,rc);
			}
		}
		return bRes;
	}

	bool AcceptDock(const_iterator i,DFDOCKRECT* pHdr,const CRect& rc)
	{
		assert(std::find_if(m_frames.begin(),m_frames.end(),CFrame::CCmp(m_pDecl->hwnd()))!=m_frames.end());
		const_iterator begin=m_frames.begin();
		if(std::find_if(begin,i,CFrame::CCmp(m_pDecl->hwnd()))==i)
			CDockOrientationFlag::SetLow(pHdr->flag);
		else
			CDockOrientationFlag::SetHigh(pHdr->flag);

		bool bRes=baseClass::AcceptDock(pHdr,rc);
		if(bRes)
		{
			if(IsHorizontal())
			{
				long len=(pHdr->rect.right-pHdr->rect.left);
				if(CDockOrientationFlag::IsLow(pHdr->flag))
				{
					assert(i!=m_frames.end());
					pHdr->rect.left=(*i)+CSplitterBar::GetThickness();
					pHdr->rect.right=pHdr->rect.left+len;
				}
				else
				{
					pHdr->rect.right=(i==m_frames.end()) ? m_frames.hi() : (*i);
					pHdr->rect.left=pHdr->rect.right-len;

				}
			}
			else
			{
				long len=(pHdr->rect.bottom-pHdr->rect.top);
				if(CDockOrientationFlag::IsLow(pHdr->flag))
				{
					assert(i!=m_frames.end());
					pHdr->rect.top=(*i)+CSplitterBar::GetThickness();
					pHdr->rect.bottom=pHdr->rect.top+len;
				}
				else
				{
					pHdr->rect.bottom=(i==m_frames.end()) ? m_frames.hi() : (*i);
					pHdr->rect.top=pHdr->rect.bottom-len;
				}
			}
		}
		return bRes;
	}
	bool Dock(CFrame& frame,const DFDOCKRECT* pHdr,const CRect& rc)
	{
		position		  pos;
		CFrames::distance len;

		if(IsHorizontal())
		{
			pos=pHdr->rect.left;
			len=pHdr->rect.right-pHdr->rect.left;
		}
		else
		{
			pos=pHdr->rect.top;
			len=pHdr->rect.bottom-pHdr->rect.top;
		}
		frame=pos;
		if(!CDockOrientationFlag::IsLow(pHdr->flag))
			pos+=len;
		iterator i;
		if(pos!=m_frames.hi())
			i=m_frames.locate(pos);
		else
			i=m_frames.end();
		m_frames.insert(i,CFrame::CCmp(m_pDecl->hwnd()),frame,len);
		return Arrange(rc);
	}

	bool Dock(DFDOCKRECT* pHdr)
	{
		CPackageFrame* ptr=CPackageFrame::CreateInstance(pHdr->hdr.hBar,!IsHorizontal());
		bool bRes=(ptr!=0);
		if(bRes)
		{
			HWND hDockWnd=pHdr->hdr.hWnd;
			pHdr->hdr.hWnd=ptr->hwnd();
			CFrame frame(0,ptr);
			bRes=Dock(frame,pHdr,*this);
			assert(bRes);
			if(bRes)
			{
				pHdr->hdr.hWnd=hDockWnd;
				pHdr->hdr.hBar=ptr->hwnd();
				CWindow bar(pHdr->hdr.hBar);
				bar.GetWindowRect(&(pHdr->rect));
				bRes=::SendMessage(ptr->hwnd(),WMDF_DOCK,NULL,reinterpret_cast<LPARAM>(pHdr))!=FALSE;
			}
		}
		return bRes;
	}

	bool Undock(const DFMHDR* pHdr)
	{
		iterator i=std::find_if(m_frames.begin(),m_frames.end(),CFrame::CCmp(pHdr->hWnd));
		bool bRes=(i!=m_frames.end());
		if(bRes)
		{
			m_frames.erase(i,CFrame::CCmp(m_pDecl->hwnd()));
			bRes=Arrange(*this);
		}
		return bRes;
	}

	bool GetDockingPosition(DFDOCKPOS* pHdr) const
	{
		const_iterator i=std::find_if(m_frames.begin(),m_frames.end(),CFrame::CCmp(pHdr->hdr.hBar));
		bool bRes=(i!=m_frames.end());
		if(bRes)
		{
			pHdr->nWidth=m_frames.get_frame_size(i)-CSplitterBar::GetThickness();
			CFrames::size_type dWnd=std::distance(m_frames.begin(),i);
			i=std::find_if(m_frames.begin(),m_frames.end(),CFrame::CCmp(m_pDecl->hwnd()));
			assert(i!=m_frames.end());
			CFrames::size_type dBWnd=std::distance(m_frames.begin(),i);
			if(dBWnd>dWnd)
			{
				pHdr->nBar=(unsigned long)dWnd;
				pHdr->dwDockSide|=CDockingSide::sTop;
			}
			else
			{
				pHdr->nBar=(unsigned long)(m_frames.size()-dWnd-1);
//				pHdr->dwDockSide|=0;
			}
			bRes=(::SendMessage(pHdr->hdr.hBar,WMDF_DOCK,NULL,reinterpret_cast<LPARAM>(pHdr))!=FALSE);
		}
		return bRes;
	}

//	bool SetDockingPosition(DFDOCKPOS* pHdr)
//	{
//		bool bRes=true;
//		unsigned long limit=GetMinFrameDist(&(pHdr->hdr));
//		if(pHdr->nWidth<limit)
//						pHdr->nWidth=limit;
//		CDockingSide side(pHdr->dwDockSide);
//		if(side.IsTop())
//		{
//			iterator i=ssec::search_n(m_frames.begin(),m_frames.end(),CFrame::CCmp(m_pDecl->hwnd()),pHdr->nBar);
//			assert(i!=m_frames.end());
//			if(i->hwnd()==m_pDecl->hwnd() || side.IsSingle())
//			{
//				CPackageFrame* ptr=CPackageFrame::CreateInstance(pHdr->hdr.hBar,!IsHorizontal());
//				bRes=(ptr!=0);
//				if(bRes)
//				{
////					i=m_frames.insert(i,CFrame(*i,ptr),pHdr->nWidth);
//					i=m_frames.insert(i,CFrame::CCmp(m_pDecl->hwnd()),CFrame(*i,ptr),pHdr->nWidth+splitterSize);
//				}
//			}
//			else
//				m_frames.set_frame_size(i,pHdr->nWidth+splitterSize);
//			pHdr->hdr.hBar=i->hwnd();
//		}
//		else
//		{
//			reverse_iterator ri=ssec::search_n(m_frames.rbegin(),m_frames.rend(),CFrame::CCmp(m_pDecl->hwnd()),pHdr->nBar);
//			assert(ri!=m_frames.rend());
//			iterator i=ri.base();
//			if(/*ri->hwnd()*/(*ri).hwnd()==m_pDecl->hwnd() || side.IsSingle())
//			{
//				CPackageFrame* ptr=CPackageFrame::CreateInstance(pHdr->hdr.hBar,!IsHorizontal());
//				bRes=(ptr!=0);
//				if(bRes)
//				{
//					position pos=(i==m_frames.end())? m_frames.hi():*i;
//					pos-=pHdr->nWidth+CSplitterBar::GetThickness();
//					if(pos<m_frames.low())
//						pos=m_frames.low();
////					i=m_frames.insert(i,CFrame(pos,ptr),pHdr->nWidth);
//					i=m_frames.insert(i,CFrame::CCmp(m_pDecl->hwnd()),CFrame(pos,ptr),pHdr->nWidth+splitterSize);
//				}
//			}
//			else
//			{
//				assert(i!=m_frames.begin());
//				m_frames.set_frame_size(--i,pHdr->nWidth+splitterSize);
//			}
//			pHdr->hdr.hBar=i->hwnd();
//		}
//		bRes=Arrange(*this);
//		assert(bRes);
//		if(bRes)
//		{
//			bRes=(::SendMessage(pHdr->hdr.hBar,WMDF_DOCK,NULL,reinterpret_cast<LPARAM>(pHdr))!=FALSE);
//			assert(bRes);
//		}
//		return bRes;
//	}

	bool SetDockingPosition(DFDOCKPOS* pHdr)
	{
		bool bRes=true;
		unsigned long limit=(unsigned long)GetMinFrameDist(&(pHdr->hdr));
		if(pHdr->nWidth<limit)
						pHdr->nWidth=limit;
		CDockingSide side(pHdr->dwDockSide);
		if(side.IsTop())
		{
			iterator i=ssec::search_n(m_frames.begin(),m_frames.end(),CFrame::CCmp(m_pDecl->hwnd()),pHdr->nBar);
			assert(i!=m_frames.end());
			if(i->hwnd()==m_pDecl->hwnd() || side.IsSingle())
			{
				CPackageFrame* ptr=CPackageFrame::CreateInstance(pHdr->hdr.hBar,!IsHorizontal());
				bRes=(ptr!=0);
				if(bRes)
				{
//					i=m_frames.insert(i,CFrame(*i,ptr),pHdr->nWidth);
					i=m_frames.insert(i,CFrame::CCmp(m_pDecl->hwnd()),CFrame(*i,ptr),pHdr->nWidth+splitterSize);
					bRes=Arrange(*this);
					assert(bRes);
				}
			}
			pHdr->hdr.hBar=i->hwnd();
		}
		else
		{
			reverse_iterator ri=ssec::search_n(m_frames.rbegin(),m_frames.rend(),CFrame::CCmp(m_pDecl->hwnd()),pHdr->nBar);
			assert(ri!=m_frames.rend());
			iterator i=ri.base();
			if(/*ri->hwnd()*/(*ri).hwnd()==m_pDecl->hwnd() || side.IsSingle())
			{
				CPackageFrame* ptr=CPackageFrame::CreateInstance(pHdr->hdr.hBar,!IsHorizontal());
				bRes=(ptr!=0);
				if(bRes)
				{
					position pos=(i==m_frames.end())? m_frames.hi():*i;
					pos-=pHdr->nWidth+CSplitterBar::GetThickness();
					if(pos<m_frames.low())
						pos=m_frames.low();
//					i=m_frames.insert(i,CFrame(pos,ptr),pHdr->nWidth);
					i=m_frames.insert(i,CFrame::CCmp(m_pDecl->hwnd()),CFrame(pos,ptr),pHdr->nWidth+splitterSize);
					bRes=Arrange(*this);
					assert(bRes);
					pHdr->hdr.hBar=i->hwnd();
				}
			}
			else
				pHdr->hdr.hBar=/*ri->hwnd()*/(*ri).hwnd();
		}
		if(bRes)
		{
			bRes=(::SendMessage(pHdr->hdr.hBar,WMDF_DOCK,NULL,reinterpret_cast<LPARAM>(pHdr))!=FALSE);
			assert(bRes);
		}
		return bRes;
	}

	bool Insert(HWND* ptr, const CRect& rc)
	{
		DFDOCKRECT dockHdr;
		::CopyRect(&dockHdr.rect,&rc);
		assert(m_pDecl==0);
		m_pDecl=new CWindowPtrWrapper(ptr);
		CFrame frame(0,m_pDecl);
		return baseClass::Dock(frame,&dockHdr,*this);
	}
	bool Insert(thisClass* ptr, const CRect& rc)
	{
		DFDOCKRECT dockHdr;
		::CopyRect(&dockHdr.rect,&rc);
		assert(m_pDecl==0);
		m_pDecl=new CFrameWrapper(ptr);
		CFrame frame(0,m_pDecl);
		return baseClass::Dock(frame,&dockHdr,*this);
	}
protected:
	IFrame* m_pDecl;
};

}//namespace dockwins

#endif // __WTL_DW__WNDFRMPKG_H__

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

Daniel Bowen
Architect
United States United States
Daniel Bowen used to work as a Software Engineer for Evans & Sutherland in Salt Lake City, Utah working on the modeling tools for high end flight simulators. He then worked for the startup company WiLife Inc. in Draper, Utah working on the software portion of an easy to use and affordable digital video surveillance system. WiLife Inc. is now part of Logitech Inc.

| Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.150428.2 | Last Updated 14 Jul 2005
Article Copyright 2002 by Daniel Bowen
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid