Click here to Skip to main content
Click here to Skip to main content

Property Sheet View

By , 11 Jun 2001
 

[Sample 1 Image - 27K]

fig. 1: Sample with normal dialog template

[Sample 2 Image - 26K]

fig. 2: Sample with a resizable dialog template

Version 1.3

This version includes the following new features and fixes:

  • Made construction methods more compatible with CPropertySheet in order to permit arrays of pages
  • Added demo project for SDI application

Version 1.2

This version includes the following new features and fixes:

  • Made CPropertyView::PreCreateTabControl virtual
  • Fixed a potential crash in CPropertyView::OnSize
  • Improved page name handling (more methods)
  • Made the base class of CPropertyView customizable

Version 1.1

This version includes the following new features and fixes:

  • Support for scrolling.
  • Drawing and sizing stuff now works correctly if the tab control works with tabs at bottom (TCS_BOTTOM).
  • The message filter doesn't kill some important keystrokes (like CTRL+V) any more.

Introduction

Sometimes it may be very useful to organize the data of your document in a view that behaves like a property sheet. While writing an application that permits a user to modify the properties of very complex objects, I created a generic CView-derived class that permits, to create with very little work, a "Property Sheet"-like representation of the data inside a view.

In my implementation of a "Property Sheet"-like view, I have tried to reflect the interface of the standard classes CPropertySheet and CPropertyPage, in order to keep the usage of those classes very simple. This view behaves like a property sheet, providing also support for Ctrl+TAB, Shift+Ctrl+Tab and all typical keyboard combinations to switch between the pages.

Although they are very similar to their examples, there are some slight differences in the usage of some methods. These differences are based mainly upon the different data handling concepts between a dialog and a view: in a modal dialog, the data is edited in a modal window that will be closed by a specific action of the user (e.g., clicking OK or Cancel). On the other hand, a view is a living representation of the data stored in a document. While modifying the data in the view, the document is also modified and the framework permits binding of more than one view to a document. An optimal implementation of a view either updates itself if it receives the signal that the document content was changed by another view, or signals the change to the other views when the user modifies the data.

Usage

First of all, create a dialog template for each page of your view, like if you were creating pages for a property sheet. As I have tested it, there is no limitation in the type of controls that you can place on your page. As in a property page, the caption of each dialog template will become the text of the corresponding tab. Feel free to use accelerator mnemonics in the static members, since they will work correctly.

Use Class Wizard to create a class derived from CDialog for each dialog template. Now we need to replace some things in our source code:

  • Derive your view from CPropertyView instead of CView
  • Derive your dialogs from CPropertyViewPage instead of CDialog

It is a very good idea to do this by a case sensitive search and replace, since in this manner also, all special inline comments for Class View and Class Wizard will be correctly updated.

Now exit your workspace, delete the .ncb and .clw files of your project, reopen the workspace and regenerate the .clw file by invoking Class Wizard. By default, each automatically-generated view contains an implementation for the methods OnDraw and PreCreateWindow. You can safely remove them both from your view class.

Typically, a property sheet defines its pages as member variables and calls the AddPage method in its constructor. The property sheet then creates the pages only if the user switches on a page by selecting the corresponding tab. This is one of the considerable differences in my CPropertyView implementation: by calling AddPage, the corresponding page is immediately created. This means that AddPage can only be called if the view still exists. A good place to do this is in the OnCreate method of the view:

int CTabSample1View::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if ( CPropertyView::OnCreate(lpCreateStruct) == -1 ) 
    {
        TRACE(_T("Failed to create the property view\n"));
        return -1;
    }

    if ( !m_ImgList.Create (IDB_TABICONS, 16, 1, RGB(0, 255, 255)) ) 
    {
        TRACE(_T("Failed to create the image list\n"));
        return -1;
    }

    SetImageList (&m_ImgList);

    AddPage (&m_pageGeneral, 0);
    AddPage (&m_pagePersonal, 1);

    return 0;
}

In this example, you can see another slight difference between CPropertySheet and CPropertyView: The method SetImageList is used to specify an image list for the tab icons and the method AddPage can contain an additional parameter specifying the index of the used image from the image list.

CPropertyView implements nearly all methods of CPropertySheet except for these:

  • Construct (not needed)
  • SetTitle (it makes no sense in a view)
  • SetFinishText (it also makes no sense in a view since there are no OK, Apply or Cancel buttons)
  • SetWizardButtons (see above)
  • SetWizardMode (see above)
  • DoModal (see above)
  • EndDialog (see above)

No other work has to be done in the view class, since all typical overrides for a view are implemented in CPropertyView and they are passed as needed to the specific page classes.

Our pages are implemented as classes derived from CPropertyViewPage. This class is nearly identical in its interface to CPropertyPage with a few slight differences. Also here, some methods are not available:

  • Construct (not needed)
  • CancelToClose (Since there are no buttons, it makes no sense)
  • QuerySiblings (I have never used it. If needed, I will implement it)
  • OnCancel (There is no such situation)
  • OnReset (see above)
  • OnQueryCancel (see above)
  • OnWizardBack (see above)
  • OnWizardNext (see above)
  • OnWizardFinish (see above)

Unlike a property sheet, OnInitDialog is not the right place to initialize the controls in your page with the contents. Override OnInitDialog only if you need to configure your controls on a one-time basis like specifying an image list, setting a font or filling a combo box with a fixed number of choices (the data in your document is the index of the selected choice, not the choices themselves).

For this purpose, CPropertyViewPage implements also all typical functions of a view, like OnInitialUpdate and OnUpdate. After the view (and all its pages) are successfully created, the OnInitialUpdate function is also called for each existing page. The default implementation calls OnUpdate (NULL, 0, NULL) like in CView. If you wish to support dynamic update between different views of your document, you also need to implement OnUpdate and obviously call UpdateAllViews on each modification of the document.

Here is an example on how to initialize the controls with the contents of the document:

void CPageGeneral::OnInitialUpdate ()
{
    CTabSample1Doc *pDoc = ((CTabSample1View *) GetView ())->GetDocument ();
    m_csFirstName   = pDoc->m_csFirstName;
    m_csLastName    = pDoc->m_csLastName;
    m_csAddress     = pDoc->m_csAddress;
    m_csZipCode     = pDoc->m_csZipCode;
    m_csCity        = pDoc->m_csCity;
    m_csCountry     = pDoc->m_csCountry;
    m_csRemarks     = pDoc->m_csRemarks;

    // write back to the controls
    UpdateData (FALSE);
}

When saving the document, we also need a method that permits saving changed data in the window controls to the document. Saving a document is somewhat similar to clicking the "Apply" button in a property sheet. In each page, the OnApply method should be implemented to copy the data back to the document:

BOOL CPageGeneral::OnApply ()
{
    if ( !CPropertyViewPage::OnApply () ) 
    {
        return FALSE;
    }

    CTabSample1Doc *pDoc = ((CTabSample1View *) GetView ())->GetDocument ();
    pDoc->m_csFirstName = m_csFirstName;
    pDoc->m_csLastName  = m_csLastName;
    pDoc->m_csAddress   = m_csAddress;
    pDoc->m_csZipCode   = m_csZipCode;
    pDoc->m_csCity      = m_csCity;
    pDoc->m_csCountry   = m_csCountry;
    pDoc->m_csRemarks   = m_csRemarks;

    return TRUE;
}

We also need to modify our document to call the OnApply method. A good place to do this is in the override OnSaveDocument:

BOOL CTabSample1Doc::OnSaveDocument(LPCTSTR lpszPathName) 
{
    POSITION pos = GetFirstViewPosition ();
    if ( pos ) 
    {
        CPropertyView *pView = (CPropertyView *) GetNextView (pos);
        if ( pView ) 
        {
            ASSERT_KINDOF(CPropertyView,pView);
            if ( !pView->Apply () ) 
            {
                return FALSE;
            }
        }
    }

    return CDocument::OnSaveDocument(lpszPathName);
}

If you support more than one view per document in your application, you must obviously implement quite a different approach. You must call Apply either when a property view loses the focus (in order to update the document) or in OnSaveDocument for the last view that has the focus.

More Advanced Usage

The second example shows a more advanced usage based on the very nice resizable dialog classes programmed by Hans Bühler. Since the use of a modified dialog class is very probable, the base class of CPropertyViewPage is the define baseCPropertyViewPage set by default to CDialog. This has the advantage that you can easily customize your CPropertyViewPage with your favorite dialog-based class. To change the base class of CPropertyViewPage, you simply need to redefine baseCPropertyViewPage. A good place to do this is in StdAfx.h:

// ...

#include "cdxCSizingDialog.h"

#define baseCPropertyViewPage	cdxCSizingDialog

//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional
// declarations immediately before the previous line.

In the second example, the implementation of OnInitDialog for each page configures the resizing characteristics of each control in the property page:

BOOL CPageGeneral::OnInitDialog() 
{
    // do not add a size gripper to the dialog box
    CPropertyViewPage::OnInitDialog (10, m_InitFreedom, FALSE);

    // configure the controls to resize properly
    AddSzControl (m_ctrlFirstName, mdResize, mdNone);
    AddSzControl (m_ctrlLastName, mdResize, mdNone);
    AddSzControl (m_ctrlAddress, mdResize, mdNone);
    AddSzControl (m_ctrlCity, mdResize, mdNone);
    AddSzControl (m_ctrlCountry, mdResize, mdNone);
    AddSzControl (m_ctrlRemarks, mdResize, mdResize);

    return TRUE;
}

The resizable dialog class included in the second sample is neither the last version of Hans Bühler's classes nor the original version. I had to make some modifications since his cdxCSizingDialog did not implement all constructors of CDialog. The last version of these classes I found had some more features and a slightly modified interface. I did not have the time to test it in conjunction with my property view classes, but someone told me that it did not work correctly. In any case, the included version works very well for me, so I felt no desire to update to the latest version. You can find the original classes in the article Dynamic child window positioning.

Essential Reference

Since the usage is very similar to a standard property sheet and a standard view, I will document only some methods specific to these classes.

virtual void CPropertyView::PreCreateTabControl (CRect &rect, DWORD &dwStyle);

Override this function if you wish to modify the style of the tab control.

virtual void CPropertyView::SetModified (BOOL bChanged = TRUE)

Override this function to provide special handling in case of modification. The default implementation calls GetDocument()->SetModifiedFlag(bChanged).

CImageList* CPropertyView::GetImageList() const;

This function returns a pointer to the image list associated with the tab control.

CImageList* CPropertyView::SetImageList (CImageList *pImageList)

This function sets an image list for the tab control.

void CPropertyView::EnableScrollView (BOOL bScrollView);

By default, the property view enables dynamic scroll bars that appear if the view is sized smaller than needed to display the active property page. To disable or enable this feature, use this function.

virtual void CPropertyViewPage::SetModified (BOOL bModified = TRUE)
virtual BOOL CPropertyViewPage::GetModified();

You can override these two functions to provide your own implementation of modification handling. The default implementation calls CPropertyView::SetModified (bModified) or returns the current modification state.

History

  • 17 May 2001 - updated source and demos
  • 12 June 2001 - updated source and demos

License

This article, along with any associated source code and files, is licensed under The zlib/libpng License

About the Author

Leo Moll
Product Manager PSI Logistics GmbH
Germany Germany
Member
I started programming computers in 1979. After years of management and administration application programming, I started to work mainly in the telecommunication sector in 1986. While working at Cycos AG (formerly known as PP-COM GmbH) from 1992 to 1999 I was involved in the development of core and GUI components of their telematic server MRS for ISDN. After that I worked for a telecommunication company named Infitel International N.V. that produces software for open telecommunication services. After a long stop at Cycos AG, I worked for several other companies like Siemens Enterprise Communications, Axxom Software AG and PSI Logistics GmbH.
 
If you are curious about my other Computer related activities and previous projects, visit YeaSoft International

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionSizing the property view to size of the pagememberwilliam basser13 Feb '13 - 7:20 
Is there a way to cause the View to size to the property page?
GeneralthanksmemberL_L18 May '08 - 21:21 
It's very useful.
thanks a lot!
GeneralPorting to windows CE (Pocket PC)memberDaniel Zuppinger18 Nov '06 - 7:00 
I have found this class useful and wanted to include it in a Pocket PC Application. It does not compile, but is easy to adapt:
1. You need to exclude the methods AssertValid and Dump. I used the compile directive WINCE to do this.
Sample below shows the header file (PropertyView.h), the same needs to be done in the cpp file.
 
// Implementation
protected:
	virtual ~CPropertyView		();
#ifdef _DEBUG
	#ifndef WINCE  // dazu: added to support Windows CE
		virtual void				AssertValid				() const;
		virtual void				Dump					(CDumpContext& dc) const;
	#endif // dazu: added to support Windows CE
#endif
 
2. The base class constructor baseCPropertyViewPage(UNIT nIDTemplate, UNIT nIDCaption) is not supported. Use baseCPropertyViewPage(UNIT nIDTemplate, UNIT nIDCaption, CWin pParent) instead :
 
#ifndef WINCE  // dazu: added to support Windows CE (Base method not supported under WINCE)
CPropertyViewPage::CPropertyViewPage (UINT nIDTemplate, UINT nIDCaption /* = 0 */, CWnd* pParent /* = NULL */) : baseCPropertyViewPage (nIDTemplate, NULL)
{
	ASSERT(nIDTemplate != 0);
	CommonConstruct(MAKEINTRESOURCE(nIDTemplate), nIDCaption);
}
#else // dazu: Windows CE Version 
CPropertyViewPage::CPropertyViewPage (UINT nIDTemplate, UINT nIDCaption /* = 0 */, CWnd* pParent) : baseCPropertyViewPage (nIDTemplate, nIDTemplate, pParent)
{
	ASSERT(nIDTemplate != 0);
	CommonConstruct(MAKEINTRESOURCE(nIDTemplate), nIDCaption);
}
#endif // dazu: added to support Windows CE 
 
Now it can be used under Windows CE.
 
Thanks for this really useful class !

GeneralDebug Vs. Releasememberpicazo14 Apr '06 - 6:34 
Is there anything additional that must be done in Release mode? I included the files and it works fine in debug mode, but when I change to release mode, I get the following error:
 
My ProjView.obj : error LNK2001: unresolved external symbol "protected: int __thiscall CMyProjView::OnCreate(struct tagCREATESTRUCTA *)" (?OnCreate@CMyProjView@@IAEHPAUtagCREATESTRUCTA@@@Z)
Release/My Proj.exe : fatal error LNK1120: 1 unresolved externals
Error executing link.exe.
 
Can anyone help with this?? Thanks a lot,
 
-----------------
Genaro
QuestionIs there a way to use this as MDI view AND as a dialog in same app?memberc0d3m@n31 Mar '06 - 6:54 
Hi experts,
 
This is a great control and I've been using it for over a year in an MDI app I wrote. I now find myself wanting to use it in one of my popup CDialogs as well. Is there a way to have them both used differently at the same time?
 
Thank you,
Mike
GeneralMore than one viewmemberpank00727 Sep '05 - 3:57 
Hi,
I want to put more than one view in my app with SDI and use menu to switch between those views. How to do it?
Thanks,
Tony
GeneralRe: More than one viewmemberbugDanny3 Oct '05 - 3:37 
Isn't it the very nature of an SDI (Single Document Interface) that you only have one view? Aren't you looking for an MDI (Multiple Document Interface)?
 
Danny
 
The stupidity of others amazes me!
GeneralTrouble, please help!memberT. Rajeswari28 Aug '05 - 21:48 
I am developing an SDI application using Property Sheet View framework. I wish to use CDocument derived class to read/write data and wish to use Ptoperty View Pages to display the data from the Document. I created SEPARATE classes for each Property View Page (i.e., the pages are not coded in a single cpp file). Of course each Property View Page has a few controls (Edit boxes, Buttons, etc.)
 
The problems I face are:
 
1. If I add any function now in the Document derived class, the compiler gives errors 'local function definitions are illegal'. I checked braces '{ and }', brackets, etc by automatic means also!
 
2. If I fire a NEW project to keep the Property View Pages code in a single .cpp file, then it is becoming difficult to add message maps (such as 'OnSaveData'.
 
I believe it is better to reframe the Property Sheet View code such that the View Pages (dialog boxes) are kept in separate .h and .cpp files. I request you to give a solution to the problems.
GeneralA useful note can be addedmemberT. Rajeswari12 Aug '05 - 19:57 
Thanks for contributing such a nice article. A note can be added to the users/readers of your code: When a programmer makes a new dialog box, he must select the dialog box as a whole (not controls inside it), right click to get the dialog's properties and should change from overlapped/popup to "Child" and "Thin" (I don't have Visual Studio on the computer from which I am sending this message to write where to change to "Thin", however, you can make out easily). Then only can one get desired user interface.
GeneralColored Dialog BoxesmemberJohnBoyWalton17 May '04 - 5:01 
I am trying to get away from the microsoft grey color and have colored dialog boxes. I am using OnEraseBkgnd in the dialog and the view but end up with a grey border where the tab is that I cannot change - does anyone know how to remedy this. I can color the dialog box and the background view easily but not the tab and its associated border.
 
JohnBoyWalton
QuestionAdding a toolbar above the tabcontrol?membermace15 Mar '04 - 3:26 
Hi,
 
does anybody know how one can add a toolbar above the tabcontrol? My attempts so far have all failed, I get no toolbar in my view.
 
I really, badly need this in my application.
 
Any hints on how to do this are much appreciated.
 
TIA.
 
mace
QuestionChanging Tab-Icon during runtime?memberRalph15 Feb '04 - 3:47 
Does anybody has an idea how to change the tab-icon of a certain tab during runtime, leaving the other icons unchanged?
 
Thanks a lot
Ralph
AnswerRe: Changing Tab-Icon during runtime?membermace15 Mar '04 - 3:37 
Try accessing the SetIcon method of the tab you want to change, e.g.
 
((MyCPropertyViewDerivedClass*)GetView())->m_MyFirstPage.SetIcon(AfxGetApp()->LoadIcon(IDI_ICONHERE));
 
Something like that Smile | :)
 
I haven't tested it but this seems the obvious way to go.
 
Good luck.
 
mace
GeneralRe: Changing Tab-Icon during runtime?memberRalph15 Mar '04 - 4:31 
Thanks a lot for your suggestion, but it doesn't work!Frown | :(
 
Ralph
GeneralRe: Changing Tab-Icon during runtime?membermace15 Mar '04 - 23:46 
Ralph wrote:
Thanks a lot for your suggestion, but it doesn't work!
 
Hm, I looked at it more closely and this was too easy thought of me D'Oh! | :doh: .
 
I assume you have an imagelist attached to your CPropertyView class? Usually, each icon's index corresponds with the tab index. So what you need to do is to call the GetTabControl() function of the CPropertyView class to get a pointer to CTabCtrl. Now, if you look at your MSDN for that class you can see a function SetItem(). Basically what you need to do to change your image is to call this function with a filled in TCITEM struct. Here's what it looks like:
 
typedef struct tagTCITEM {
UINT mask;
#if (_WIN32_IE >= 0x0300)
DWORD dwState;
DWORD dwStateMask;
#else
UINT lpReserved1;
UINT lpReserved2;
#endif
LPTSTR pszText;
int cchTextMax;
int iImage;
LPARAM lParam;
} TCITEM, FAR *LPTCITEM;

 
Here you can see the "iImage" member, you need to change this one to a valid index in your imagelist.
 
Don't forget to supply a valid mask, you need the TCIF_IMAGE flag if you want to change the image.
 
What's even more easier is to call the GetItem() member so you can get a completely filled in TCITEM structure. Then all you need to do is to change the "iImage" member. If you already use images then the TCIF_IMAGE flag must've been already set. If not, you can set it as well to be sure.
 
Again, I haven't tested it (busy busy) but this *must* be the trick, I don't have a complete source code sample ready at the moment but I think you can figure this one out, right?
 
Good luck,
 
mace
GeneralIt works!memberRalph16 Mar '04 - 5:34 
Dear mace,
 
thanks a lot for your great tip!
 
Here is my code snippet which works:
 
void CPropertyView::SetIcon( WORD wTabIdx, WORD wIconIdx )
{
CTabCtrl *ptabctrl = GetTabControl();
 
TCITEM ti;
 
ti.mask = TCIF_IMAGE;
ti.iImage = wIconIdx; // select the icon
 
ptabctrl->SetItem(wTabIdx, &ti );
}


Big Grin | :-D Ralph

GeneralTabbing to the tab controlmemberBrad Bruce7 Nov '03 - 7:50 
Tabbing does not go to the tab control when this is used in a view. Any suggestions? (It works in a dialog)
GeneralAdding controls by handsussAngel Cervantes30 Sep '03 - 8:07 
Hi, I'm making an application wich have to create controls with code in the property page, can somebody help me please?D'Oh! | :doh:
QuestionHow can I use the Property Sheet View within class of type CFrameView?membersi197278 Jul '03 - 1:27 
Hello!
Someone know how can I use the features of this Property Sheet View within a class derived of CFrameView?
 
I want a form with some other controls outside of Property Sheet View, however within the same form.
 
Can someone help me?
I´m a portuguese boy and I don´t write english very well.
I supose that you understand what I mean.
 
thanks! I hope for a brefly help...
 
Nelson
GeneralWinCE 3.0 PocketPC Portmembermel@endlessquests.com1 Jul '03 - 6:49 
Greate Job!
 
I just finished porting this to PocketPC using eVC++ 3.0 and it works great! Only had to make a couple minior changes. I had to subclass CTabCtrl to get the reflected OnSelchange() and OnSelchanging() then call the control's parent (CPropertyView) to implement them. The only other change was to use GetStockObject (SYSTEM_FONT) in the OnCreate() method because eVC++ 3.0 does not support DEFAULT_GUI_FONT. I havn't tried using image list parameter to AddPage().Smile | :) Smile | :)
 


GeneralDialog Title bar visiblememberpablo_chimi16 Jun '03 - 5:47 
I'm trying to use your class
but in the tab view the dialog is Shown with the title bar !!!!
and so it's movable !!!
I think I do the right steps to integrate the PropertyView Class to my project
I put the Dialog properties to Child and thin
but I had the same problem
 
what must I do ?
 
thanks for help
 

 
Hatem
GeneralRe: Dialog Title bar visiblemembermace15 Mar '04 - 3:48 
Hi,
 
try setting your dialog resource to "disabled" (Dialog properties, More styles under VC++ 6.0).
 
What you also can try is the following, I use these settings by default:
 
In your OnInitDialog handler of your dialog class change the constructor
to reflect the following line:
 
CPropertyViewPage::OnInitDialog(10, m_InitFreedom, FALSE);
 
(It has no parameters initially).
 
This causes the dialog not to use resizing grippers anymore.
 
In your main class (the "placeholder" of your dialogs (derived from CPropertyView)), add the following virtual function: OnInitialUpdate().
 
Within the function body, set the following line:
 
EnableScrollView(FALSE);
 
For me, all this does the trick, the window cannot be resized and it has no sizing gripper in the lower-right corner of the dialog.
 
If this does not solve your problem, please be more specific on how you have created your classes and how you call them.
 
Good luck.
 
mace
GeneralRe: Dialog Title bar visiblememberbugDanny3 Oct '05 - 3:41 
I've always had luck with changing the properties of the Dialog and deselecting 'Title Bar', but maybe this isn't what you're looking for.
 
Danny
 
The stupidity of others amazes me!
GeneralResizing Problemmembersivakumar.balakrishnan@honeywell.com13 May '03 - 19:27 
Thanx a lot for ur code... I've been using it in my project. I had some problem with resizing... I have 2 pages in the propertyview. The size of the dialog templates of both the pages are different. Initially when the pages are created and set active, there isn't any prob. once i switch to the second page and switch back to the first page (first page size is larger in size than the second page), it gets resized to the second page's size and i'm not able to see all the controls as the visible area is getting truncated. After this, if i resize the window frame, the page gets its original size. Can someone help me out?
 

 
Thanx,
Shiva
GeneralRe: Resizing Problemmemberabarnett29 Oct '04 - 4:43 
There is a problem in the CPropertyView::OnSize function, it calculates the size from the GetActivePage function. Unfortunately, the OnSize is called before the OnTabChanged function (this sets m_nActivePage). The solution is to replace the 'CPropertyViewPage *pPage = GetActivePage ();' line of code in the OnSize function with the two lines 'int nPage = m_TabCtrl.GetCurSel(); CPropertyViewPage *pPage = GetPage (nPage);'

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 12 Jun 2001
Article Copyright 2000 by Leo Moll
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid