Click here to Skip to main content
11,432,427 members (70,708 online)
Click here to Skip to main content

WTL bugs

, 27 Nov 2000
Rate this:
Please Sign up or sign in to vote.
Known WTL & ATL bugs

This article illustrates bugs that are present in the current WTL (3.1) & ATL (3.0) implementation.

Last Modified : 11/29/2000




AtlGDI.h, line 2355-2414, instead of:

void DrawDragRect(LPCRECT lpRect, SIZE size, LPCRECT lpRectLast, SIZE sizeLast, 
                  HBRUSH hBrush = NULL, HBRUSH hBrushLast = NULL)
...
if(hBrush == NULL)
    hBrush = CDCHandle::GetHalftoneBrush();
...
...
// cleanup DC
if(hBrushOld != NULL)
    SelectBrush(hBrushOld);
SelectClipRgn(NULL);

It can be something like

void DrawDragRect(LPCRECT lpRect, SIZE size, LPCRECT lpRectLast, 
                  SIZE sizeLast, HBRUSH hBrushIn = NULL, 
                  HBRUSH hBrushLast = NULL)
...
if(hBrushIn == NULL)
    hBrush = CDCHandle::GetHalftoneBrush();
else
    hBrush = hBrushIn;
...
...
// cleanup DC
if(hBrushOld != NULL)
    SelectBrush(hBrushOld);
SelectClipRgn(NULL);
if(NULL == hBrushIn)
    DeleteObject(hBrush); //Free our halftone brush
DeleteObject(hRgnNew);
DeleteObject(hRgnOutside);
DeleteObject(hRgnInside);
if (NULL != hRgnLast)
    DeleteObject(hRgnLast);
if (NULL != hRgnUpdate)
    DeleteObject(hRgnUpdate);

Six memory leaks in one place.

The bug has been posted to wtl@egroups.com by Peter Datsichin



AtlDlgs.h, line 2362, instead of:

case PSN_WIZFINISH:
lResult = !pT->OnWizardFinish();

It can be something like

case PSN_WIZFINISH:
lResult = pT->OnWizardFinish();

If one looks at the MS documentation on PSN_WIZFINISH it is said that with version 5.80 of "comctl32.dll" you can return a window handle to 1) prevent the wizard from finishing and 2) set the focus on the window handle returned by the function. However, WTL negates the result so that returning TRUE allow the wizard to finish while returning FALSE prevent it. By doing so it is not possible to return a window handle.

Thanks to Simon-Pierre Cadieux. See comment "Wizard Property Sheet " below



AtlDlgs.h, line 2146-2153, instead of:

LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
    LRESULT lRet = DefWindowProc(uMsg, wParam, lParam);
    if(HIWORD(wParam) == BN_CLICKED && (LOWORD(wParam) == IDOK || 
                                          LOWORD(wParam) == IDCANCEL) &&
       ((m_psh.dwFlags & PSH_MODELESS) != 0) && (GetActivePage() == NULL))
        DestroyWindow();
    return lRet;
}

It can be something like

LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& /*bHandled*/)
{
    LRESULT lRet = DefWindowProc(uMsg, wParam, lParam);
    if(HIWORD(wParam) == BN_CLICKED && ((m_psh.dwFlags & PSH_MODELESS) != 0) &&
    ((LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) ||
#if (_WIN32_IE >= 0x0500) && defined(PSH_WIZARD_LITE)
    ((m_psh.dwFlags & (PSH_WIZARD | PSH_WIZARD97 | PSH_WIZARD_LITE)) != 0)) &&
#elif (_WIN32_IE >= 0x0400) && defined(PSH_WIZARD97)
    ((m_psh.dwFlags & (PSH_WIZARD | PSH_WIZARD97)) != 0)) &&
#else
    ((m_psh.dwFlags & PSH_WIZARD) != 0)) &&
#endif
    (GetActivePage() == NULL))
        DestroyWindow();
    return lRet;
}

For modeless wizard property sheet, once you clicked on the "Terminate" button the sheet should be destroyed by calling DestroyWindow. However the WTL instructions that handle that only check for IDOK and IDCANCEL not the identifier of the "Terminate Button"

Thanks to Simon-Pierre Cadieux. See comment "Wizard Property Sheet " below



AtlCtrls.h, Lines 5807-5811 instead of:

int CharFromPos(POINT pt) const
{
    ATLASSERT(::IsWindow(m_hWnd));
    return (int)::SendMessage(m_hWnd, EM_CHARFROMPOS, 0, 
                                  MAKELPARAM(pt.x, pt.y));
}

It can be something like

int CharFromPos(POINTL pt) const
{
    ATLASSERT(::IsWindow(m_hWnd));
    return (int)::SendMessage(m_hWnd, EM_CHARFROMPOS, 0, 
                                  (LPARAM)&pt);
}

The bug was published by Richard L. Melton

See help on EM_CHARFROMPOS for details



AtlDdx.h, lines 39-51 instead of:

#define DDX_TEXT(nID, var) \
        if(nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            if(!DDX_Text(nID, var, sizeof(var), bSaveAndValidate)) \
                return FALSE; \
        }

#define DDX_TEXT_LEN(nID, var, len) \
        if(nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            if(!DDX_Text(nID, var, sizeof(var), bSaveAndValidate, TRUE, len)) \
                return FALSE; \
        }

It can be something like

#define DDX_TEXT(nID, var) \
        if(nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            if(!DDX_Text(nID, var, sizeof(var)/sizeof(var[0]), \
                bSaveAndValidate)) \
                return FALSE; \
        }

#define DDX_TEXT_LEN(nID, var, len) \
        if(nCtlID == (UINT)-1 || nCtlID == nID) \
        { \
            if(!DDX_Text(nID, var, sizeof(var)/sizeof(var[0]), \
                bSaveAndValidate, TRUE, len)) \
                return FALSE; \
        }

It is much better to use CString version of DDX_Text



AtlCtrlw.h Line 1639, instead of:

AtlGetCommCtrlVersion(&dwMajor, &dwMinor);

It can be something like

#ifndef _ATL_DLL
AtlGetCommCtrlVersion(&dwMajor, &dwMinor);
#else
// Do it in some other way, there is no AtlGetCommCtrlVersion in atl.dll
#endif

It is recommended to not use _ATL_DLL anyway.

The bug was published by Peter N Burgess



AtlFrame.h Lines 571-592, instead of:

void UpdateBarsPosition(RECT& rect, BOOL bResizeBars = TRUE)
{
    // resize toolbar
    if(m_hWndToolBar != NULL && ((DWORD)::GetWindowLong(m_hWndToolBar, 
                              GWL_STYLE) & WS_VISIBLE))
    {
        if(bResizeBars)
            ::SendMessage(m_hWndToolBar, WM_SIZE, 0, 0);
        RECT rectTB;
        ::GetWindowRect(m_hWndToolBar, &rectTB);
        rect.top += rectTB.bottom - rectTB.top;
    }

    // resize status bar
    if(m_hWndStatusBar != NULL &&
          ((DWORD)::GetWindowLong(m_hWndStatusBar, GWL_STYLE) & WS_VISIBLE))
    {
        if(bResizeBars)
            ::SendMessage(m_hWndStatusBar, WM_SIZE, 0, 0);
        RECT rectSB;
        ::GetWindowRect(m_hWndStatusBar, &rectSB);
        rect.bottom -= rectSB.bottom - rectSB.top;
    }
}

It can be something like

void UpdateBarsPosition(RECT& rect, BOOL bResizeBars = TRUE)
{
    // resize toolbar
    if(m_hWndToolBar != NULL && 
            ((DWORD)::GetWindowLong(m_hWndToolBar, GWL_STYLE) & WS_VISIBLE))
    {
        if(bResizeBars)
            ::SendMessage(m_hWndToolBar, WM_SIZE, 0, 0);
        RECT rectTB;
        ::GetWindowRect(m_hWndToolBar, &rectTB);
        if( dwStyles & CCS_VERT )
            rect.left += rectTB.right - rectTB.left;
        else
            rect.top += rectTB.bottom - rectTB.top;
    }

    // resize status bar
    if(m_hWndStatusBar != NULL && ((DWORD)::GetWindowLong(m_hWndStatusBar, 
                                                          GWL_STYLE) & WS_VISIBLE))
    {
        if(bResizeBars)
            ::SendMessage(m_hWndStatusBar, WM_SIZE, 0, 0);
        RECT rectSB;
        ::GetWindowRect(m_hWndStatusBar, &rectSB);
        rect.bottom -= rectSB.bottom - rectSB.top;
        // Force redraw of statusbar on top of possible vertical toolbar.
        if( dwStyles & CCS_VERT )
            ::SetWindowPos(m_hWndStatusBar , HWND_TOP, 0, 0, 0, 0, 
                                      SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE );
    }
}

Otherwise your vertical toolbars+rebars will not showing up properly

The bug was published by Carlos A. Ferraro Cavallini

See comment below.



AtlFrame.h Lines 1073-1087, instead of:

static HMENU GetStandardWindowMenu(HMENU hMenu)
{
    int nCount = ::GetMenuItemCount(hMenu);
    if(nCount == -1)
        return NULL;
    int nLen = ::GetMenuString(hMenu, nCount - 2, NULL, 0, MF_BYPOSITION);
    if(nLen == 0)
        return NULL;
    LPTSTR lpszText = (LPTSTR)_alloca((nLen + 1) * sizeof(TCHAR));
    if(::GetMenuString(hMenu, nCount - 2, lpszText, nLen + 1, MF_BYPOSITION) != nLen)
        return NULL;
    if(lstrcmp(lpszText, _T("&Window")))
        return NULL;
    return ::GetSubMenu(hMenu, nCount - 2);
}

It can be something like

static HMENU GetStandardWindowMenu(HMENU hMenu)
{
    int nCount = ::GetMenuItemCount(hMenu);
    if(nCount == -1)
        return NULL;
    int nLen = ::GetMenuString(hMenu, nCount - 2, NULL, 0, MF_BYPOSITION);
    if(nLen == 0)
        return NULL;
    LPTSTR lpszText = (LPTSTR)_alloca((nLen + 1) * sizeof(TCHAR));
    if(::GetMenuString(hMenu, nCount - 2, lpszText, nLen + 1, MF_BYPOSITION) != nLen)
        return NULL;
    if(lstrcmp(lpszText, LOCALE_INDEPENDED_STRING))
        return NULL;
    return ::GetSubMenu(hMenu, nCount - 2);
}

or just

static HMENU GetStandardWindowMenu(HMENU hMenu)
{
    int nCount = ::GetMenuItemCount(hMenu);
    if(nCount == -1)
        return NULL;
    return ::GetSubMenu(hMenu, nCount - 2);
}

Similar that the author has overlooked that there are other languages except for English.

The bug was published by Toshihiro Sato

See comment below.



AtlBase.h, lines 498-511, 652, 798 instead of:

bool IsEqualObject(IUnknown* pOther)
{
    if (p == NULL && pOther == NULL)
        return true; // They are both NULL objects

    if (p == NULL || pOther == NULL)
        return false; // One is NULL the other is not

    CComPtr<IUnknown> punk1;
    CComPtr<IUnknown> punk2;
    p->QueryInterface(IID_IUnknown, (void**)&punk1);
    pOther->QueryInterface(IID_IUnknown, (void**)&punk2);
    return punk1 == punk2;
}

It can be something like

bool IsEqualObject(IUnknown* pOther)
{
    if (p ==  pOther)
        return true; // They are both NULL objects  or the same object!

    if (p == NULL || pOther == NULL)
        return false; // One is NULL the other is not

    CComPtr<IUnknown> punk1;
    CComPtr<IUnknown> punk2;
    p->QueryInterface(IID_IUnknown, (void**)&punk1);
    pOther->QueryInterface(IID_IUnknown, (void**)&punk2);
    return punk1 == punk2;
}

It's not a real bug, but it hits the performance.

Anyway you can code something like this:

if (m_pObject == pOtherObject || m_pObject.IsEqualObject(pOtherObject))
{
// Do smth
}


AtlCom.h, lines 3731-3756 instead of:

//Helper for invoking the event
HRESULT InvokeFromFuncInfo(void (__stdcall T::*pEvent)(), _ATL_FUNC_INFO& info, 
                           DISPPARAMS* pdispparams, VARIANT* pvarResult)
{
    T* pT = static_cast<T*>(this);
    VARIANTARG** pVarArgs = info.nParams ? 
                       (VARIANTARG**)alloca(sizeof(VARIANTARG*)*info.nParams) : 0;
    for (int i=0; i<info.nParams; i++)
        pVarArgs[i] = &pdispparams->rgvarg[info.nParams - i - 1];

    CComStdCallThunk<T> thunk;
    thunk.Init(pEvent, pT);
    CComVariant tmpResult;
    if (pvarResult == NULL)
        pvarResult = &tmpResult;

    HRESULT hr = DispCallFunc(
        &thunk,
        0,
        info.cc,
        info.vtReturn,
        info.nParams,
        info.pVarTypes,
        pVarArgs,
        pvarResult);
    ATLASSERT(SUCCEEDED(hr));
    return hr;
}

It can be something like

//Helper for invoking the event
HRESULT InvokeFromFuncInfo(void (__stdcall T::*pEvent)(), 
                           _ATL_FUNC_INFO& info, DISPPARAMS* pdispparams, 
                           VARIANT* pvarResult)
{
    T* pT = static_cast<T*>(this);
    VARIANTARG** pVarArgs = info.nParams ? 
                     (VARIANTARG**)alloca(sizeof(VARIANTARG*)*info.nParams) : 0;
    VARTYPE * pVarTypes = info.nParams ? 
                      (VARTYPE *)alloca(sizeof(VARTYPE)*info.nParams) : 0;
    for (int i=0; i<info.nParams; i++)
    {
        pVarArgs[i] = &pdispparams->rgvarg[info.nParams - i - 1];
        pVarTypes[i] = info.pVarTypes[info.nParams - i - 1];
    }

    CComStdCallThunk<T> thunk;
    thunk.Init(pEvent, pT);
    CComVariant tmpResult;
    if (pvarResult == NULL)
        pvarResult = &tmpResult;

    HRESULT hr = DispCallFunc(
        &thunk,
        0,
        info.cc,
        info.vtReturn,
        info.nParams,
        pVarTypes,
        pVarArgs,
        pvarResult);
    ATLASSERT(SUCCEEDED(hr));
    return hr;
}


Atlcom.h, Line 2604, instead of:

STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
{
    HRESULT hr = OuterQueryInterface(iid, ppvObject);
    if (FAILED(hr) && _GetRawUnknown() != m_pOuterUnknown)
        hr = _InternalQueryInterface(iid, ppvObject);
    return hr;
}

It can be something like

STDMETHOD(QueryInterface)(REFIID iid, void ** ppvObject)
{
    return OuterQueryInterface(iid, ppvObject);
}

CComContainedObject shouldn't call _InternalQueryInterface()

The bug was published by World Od ATL



ATLHOST.H Line 1489, instead of:

STDMETHOD(GetDC)(LPCRECT /*pRect*/, DWORD /*grfFlags*/, HDC* phDC)
{
    if (phDC)
        return E_POINTER;
    *phDC = CWindowImpl<CAxHostWindow>::GetDC();
    return S_OK;
}

It can be something like

STDMETHOD(GetDC)(LPCRECT /*pRect*/, DWORD /*grfFlags*/, HDC* phDC)
{
    if (!phDC)
        return E_POINTER;
    *phDC = CWindowImpl<CAxHostWindow>::GetDC();
    return S_OK;
}

No comments. Seems to be a mistype

The bug was published by Claus Michelsen



Some useful links

Clipcode.com and Clipcode.com WTL Doc + Samples
IDevResource.Com WTL Bugs and Issues
World of ATL, bugs and fixes page
DISCUSS.MICROSOFT.COM Mailing List Archives
microsoft.public.vc.atl newsgroup

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

Paul Bludov

Russian Federation Russian Federation
No Biography provided

Comments and Discussions

 
GeneralSDI problem Pin
Dennis Rebentrost9-Feb-01 11:46
memberDennis Rebentrost9-Feb-01 11:46 
GeneralCritical bugs Pin
MB18-Jan-01 2:41
memberMB18-Jan-01 2:41 
QuestionWTL 3.1: where is it ? Pin
MrG16-Jan-01 4:47
memberMrG16-Jan-01 4:47 
Is it the WTL given with Platform SDK Nov 2000 (Whistler Beta 1) ?

Thxks.

AnswerRe: WTL 3.1: where is it ? Pin
Petru Marginean5-Feb-01 6:30
memberPetru Marginean5-Feb-01 6:30 
GeneralFound another BUG Pin
Anonymous8-Jan-01 19:46
memberAnonymous8-Jan-01 19:46 
GeneralAssertion in dialog-based application Pin
Anonymous6-Jan-01 20:01
memberAnonymous6-Jan-01 20:01 
GeneralRe: Assertion in dialog-based application Pin
Anonymous5-Mar-01 3:42
memberAnonymous5-Mar-01 3:42 
GeneralWTL::CString destructor bug on multiprocessor machine Pin
MrG3-Jan-01 8:43
memberMrG3-Jan-01 8:43 
GeneralRe: WTL::CString destructor bug on multiprocessor machine Pin
Paul Bludov3-Jan-01 18:50
memberPaul Bludov3-Jan-01 18:50 
GeneralWTL::CString bug in Unicode Pin
Anonymous3-Jan-01 8:21
memberAnonymous3-Jan-01 8:21 
GeneralRe: WTL::CString bug in Unicode Pin
Paul Bludov3-Jan-01 18:58
memberPaul Bludov3-Jan-01 18:58 
QuestionATL 7.0 beta: where is it ? Pin
Anonymous3-Jan-01 7:27
memberAnonymous3-Jan-01 7:27 
AnswerRe: ATL 7.0 beta: where is it ? Pin
Paul Bludov3-Jan-01 16:40
memberPaul Bludov3-Jan-01 16:40 
GeneralAtlCompactPath Fixed Pin
MB20-Dec-00 19:57
memberMB20-Dec-00 19:57 
GeneralUpdateBarsPosition Pin
Anonymous19-Dec-00 4:49
memberAnonymous19-Dec-00 4:49 
GeneralRe: UpdateBarsPosition Pin
Anonymous19-Dec-00 4:56
memberAnonymous19-Dec-00 4:56 
GeneralAbout _IsDBCSTrailByte that AtlCompactPath uses Pin
MB8-Dec-00 15:06
memberMB8-Dec-00 15:06 
GeneralRe: About _IsDBCSTrailByte that AtlCompactPath uses Pin
Michael Dunn8-Dec-00 20:27
memberMichael Dunn8-Dec-00 20:27 
GeneralRe: About _IsDBCSTrailByte that AtlCompactPath uses Pin
MB9-Dec-00 10:08
memberMB9-Dec-00 10:08 
GeneralMore command bar bugs Pin
MB8-Dec-00 8:17
memberMB8-Dec-00 8:17 
GeneralCommand bar problems Pin
MB7-Dec-00 9:22
memberMB7-Dec-00 9:22 
GeneralRe: Command bar problems Pin
Paul Bludov7-Dec-00 17:34
memberPaul Bludov7-Dec-00 17:34 
QuestionSending TB_BUTTONSTRUCTSIZE missing? Pin
Vadim Philippov29-Nov-00 22:06
memberVadim Philippov29-Nov-00 22:06 
AnswerSelf-refinement Pin
Vadim Philippov29-Nov-00 22:39
memberVadim Philippov29-Nov-00 22:39 
GeneralIsEqualObject Pin
Jens Nilsson28-Nov-00 9:01
memberJens Nilsson28-Nov-00 9:01 
GeneralRe: IsEqualObject Pin
Anonymous28-Nov-00 15:17
memberAnonymous28-Nov-00 15:17 
GeneralSolution to bug regarding vertical toolbars Pin
Carlos A. Ferraro Cavallini26-Nov-00 2:19
memberCarlos A. Ferraro Cavallini26-Nov-00 2:19 
GeneralRe: Solution to bug regarding vertical toolbars Pin
Carlos A. Ferraro Cavallini26-Nov-00 3:39
memberCarlos A. Ferraro Cavallini26-Nov-00 3:39 
GeneralCannot refresh MDI window menu on not english environment Pin
Toshihiro Sato23-Nov-00 15:45
memberToshihiro Sato23-Nov-00 15:45 
GeneralRe: Cannot refresh MDI window menu on not english environment Pin
Paul Bludov23-Nov-00 16:21
memberPaul Bludov23-Nov-00 16:21 
GeneralRe: Cannot refresh MDI window menu on not english environment Pin
Toshihiro Sato23-Nov-00 18:09
memberToshihiro Sato23-Nov-00 18:09 
General:-D WTL 3.1 released Pin
Paul Bludov10-Nov-00 22:08
memberPaul Bludov10-Nov-00 22:08 
GeneralRe: :-D WTL 3.1 released Pin
Jan Bares6-Dec-00 5:43
memberJan Bares6-Dec-00 5:43 
GeneralCString::Replace(TCHAR chOld, TCHAR chNew) limitation Pin
Simon-Pierre Cadieux2-Nov-00 1:05
sussSimon-Pierre Cadieux2-Nov-00 1:05 
GeneralCopyTo on CComVariant Pin
bviksoe1-Nov-00 22:34
sussbviksoe1-Nov-00 22:34 
GeneralWTL::CString bugs under Unicode Pin
Daniel Bowen1-Nov-00 8:12
sussDaniel Bowen1-Nov-00 8:12 
GeneralRe: WTL::CString bugs under Unicode Pin
Simon-Pierre Cadieux2-Nov-00 0:44
sussSimon-Pierre Cadieux2-Nov-00 0:44 
GeneralRe: WTL::CString bugs under Unicode Pin
Daniel Bowen2-Nov-00 6:57
sussDaniel Bowen2-Nov-00 6:57 
GeneralMSG_WM_TIMER crack Pin
Simon-Pierre Cadieux31-Oct-00 14:44
sussSimon-Pierre Cadieux31-Oct-00 14:44 
QuestionCSimpleArray bug ?!? Pin
Igor Ostriz25-Oct-00 1:38
sussIgor Ostriz25-Oct-00 1:38 
AnswerRe: CSimpleArray bug ?!? Pin
Carlos A. Ferarro Cavallini31-Oct-00 4:45
sussCarlos A. Ferarro Cavallini31-Oct-00 4:45 
GeneralRe: CSimpleArray bug ?!? Pin
Igor Ostriz31-Oct-00 5:25
sussIgor Ostriz31-Oct-00 5:25 
GeneralRe: CSimpleArray bug ?!? Pin
Carlos A. Ferraro Cavallini1-Nov-00 12:08
sussCarlos A. Ferraro Cavallini1-Nov-00 12:08 
GeneralOnIdle processing Pin
Jan Bares24-Oct-00 1:14
sussJan Bares24-Oct-00 1:14 
GeneralCCommandBarCtrl again Pin
Paul Bludov5-Oct-00 16:08
sussPaul Bludov5-Oct-00 16:08 
GeneralFixed WTL files (24 oct 2000) Pin
Benjamin Mayrargue23-Oct-00 23:41
sussBenjamin Mayrargue23-Oct-00 23:41 
GeneralFixed WTL files (2 oct 2000) Pin
Benjamin Mayrargue2-Oct-00 3:06
sussBenjamin Mayrargue2-Oct-00 3:06 
GeneralRe: Fixed WTL files (6 oct 2000) Pin
Benjamin Mayrargue6-Oct-00 1:12
sussBenjamin Mayrargue6-Oct-00 1:12 
GeneralWizard Property Sheet Pin
Simon-Pierre Cadieux10-Sep-00 23:45
sussSimon-Pierre Cadieux10-Sep-00 23:45 
GeneralRe: Wizard Property Sheet Pin
Simon-Pierre Cadieux17-Oct-00 1:08
sussSimon-Pierre Cadieux17-Oct-00 1:08 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.150428.2 | Last Updated 28 Nov 2000
Article Copyright 2000 by Paul Bludov
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid