General Articles and Tutorials

Custom scroll bars for ToolkitPro common controls

Author: Alexander Stoyan
Platform: Visual C++ MFC

The Problem

Prior to version 19.2 it was impossible to apply control's theme to scroll bars in ToolkitPro controls such as CXTPEdit, CXTPTree, CXTPListBox, CXTPListCtrl and CXTPPropertyGrid due to the way the underlying common Windows controls were showing and handling their own scroll bars. Overriding that behavior for the Windows common controls and maintaining full backward compatibility was quite challenging technically and led to solution that requires certain consideration for being able to use. This article describes the key points and tries to answer most common questions that may arise.

The Solution

In fact the following classes remained unchanged and still use standard Windows scroll bars without theme applied:

  • CXTPEdit
  • CXTPTreeCtrl
  • CXTPListBox
  • CXTPListCtrl

In order to be able to apply scrollbar theme or customize scroll bar class one has to use or derive new controls classes derived from a new special adaptor template class called CXTPScrollable<Base>.

For the most common use cases it will be sufficient to replace controls class names used in your application with their corresponding class names that are derived from CXTPScrollable:

  • CXTPScrollableEdit
  • CXTPScrollableTreeCtrl
  • CXTPScrollableListBox
  • CXTPScrollableListCtrl

CXTPPropertyGrid is a special case, it does not have another CXTPScrollable derived version, it simply uses new approach internally, remains fully backward compatible and does not require any special considerations unless it is confirmed that new additions cause a blocking issue. If this is the case starting from version 19.3 it is possible to disable PropertyGrid scroll bar theme support making it work

If this is the case starting from version 19.3 there is an option to fallback to pre-19.2 behavior at the price of disabling scroll bar themes. For this either uncomment the XTP_PROPERTY_GRID_DISABLE_SCROLLBAR_THEMES macro at the beginning of XTPPropertyGrid.h file (C:\Program Files (x86)\Codejock Software\MFC\Xtreme ToolkitPro v19.3.0\Source\PropertyGrid\XTPPropertyGrid.h) or specify that macro as defined in the C++ compiler properties of the ToolkitPro or the PropertyGrid library project for selected configurations then re-build the ToolkitPro or the PropertyGrid projects in order the change to take an effect.

Using those classes will ensure that controls will have proper scroll bar theme set automatically and make it possible to set a custom scroll bar theme using new method:

    void SetScrollBarTheme(XTPScrollBarTheme nTheme);

Using classes derived from CXTPScrollable also impose certain restrictions:

  1. If controls are created manually using Create or CreateEx method exposed by their MFC base class, one should call only CWnd::Create or CWnd::CreateEx overrides as due to lack of C++ support in old versions of Microsoft C++ compiler calling overloaded Create or CreateEx method from MFC CTreeCtrl, CEdit, CListBox and CListCtrl will lead to compilation errors when using those compilers.
    Example of code before:
        CXTPTreeCtrl m_tree;
        // As CXTPTreeCtrl is derived from CTreeCtrl it uses CTreeCtrl::Create overloaded
    // method which has signature different from CWnd::Create and thus should not be used for CXTPScrollableTreeCtrl
    m_tree.Create(WS_CHILD | TVS_LINESATROOT, rc, this, IDC_TREE);
        Example of the fixed code:
        CXTPScrollableTreeCtrl m_tree;
        // Call CWnd::Create overridden method to ensure it can be compiled using all Microsoft C++ compilers
        m_tree.Create(_T("SysTreeView32"), NULL, WS_CHILD | TVS_LINESATROOT, rc, this, IDC_TREE);
  2. Do not cache HWND handles early in initialization stage for any control derived from CXTPScrollable as they most likely will be invalidated.
    All controls the are derived from CXTPScrollable will be destroyed and re-created upon initial creation. This process is done absolutely transparent for regular use cases, but if the handle is cached by the client code early it may be soon invalidated, such places include but not limited to:
    • Window creation hooks
    • Any handle manipulations done prior to default CDialog::OnInitDialog call
  3. When designing custom controls derived from CXTPScrollable try to make control's initialization logic as simple and lightweight as possible as subsequent control re-creation may result in slowing down application startup if CPU and memory strain during control re-creation doubles.
  4. All classes that derive CXTPScrollable must be default constructible, i.e. expose a public constructor with no arguments or all arguments with their default values provided.
  5. In order to redraw a control derived from CXTPScrollable it is necessary to call RedrawWindow with RDW_ALLCHILDREN flag set as calling UpdateWindow will not have any effect.
  6. In case a control derived from CXTPScrollable needs to access its parent window after creation it has to call GetParent twice as it will be placed on an intermediate host control.
  7. Using controls derived from CXTPScrollable with Resizer control requires using CXTPResize::SetResize method that takes control's window handle in addition to control's ID value, for example:
        CXTPEdit m_edtSingleLine;
        CXTPScrollableEdit m_edtMultiline;
    
        // IDC_EDIT_SINGLELINE is NOT derived from CXTPScrollable and thus can be referenced by Resize control by ID only.
        SetResize(IDC_EDIT_SINGLELINE, XTP_ANCHOR_TOPLEFT, CXTPResizePoint(1.f / 3.f, 0));
    
        // IDC_EDIT_MULTILINE is derived from CXTPScrollable and thus must be referenced by Resize control by both ID and handle value.
        SetResize(IDC_EDIT_MULTILINE, m_edtMultiline, XTP_ANCHOR_TOPLEFT, CXTPResizePoint(1.f / 3.f, 0));

Deriving custom controls from CXTPScrollable

It is possible to use a custom control with CXTPScrollable in order to be able to apply to its scroll bars provided that the control is derived from supported classes and uses standard Windows scroll bars.

There are two possible use cases:

  1. Using controls derived from CEdit, CListBox, CTreeCtrl or CListCtrl
  2. Using controls other controls derived from CWnd

For the first case ToolkitPro provides adapter templates for the corresponding base classes:

  • CXTPScrollableEditT<Base>
  • CXTPScrollableListBoxT<Base>
  • CXTPScrollableTreeCtrlT<Base>
  • CXTPScrollableListCtrlT<Base>

Example:

    // Your existing classes
    class CCustomEdit : public CEdit { /*...*/ };
    class CCustomTreeCtrl : public CXTPTreeCtrl { /*...*/ };

    // Your new derived classes
    class CScrollableCustomEdit : public CXTPScrollableEditT<CCustomEdit> { /*...*/ };
    class CScrollableTreeCtrl : public CXTPScrollableEditT<CCustomTreeCtrl> { /*...*/ };

In other cases when you need to derive another kind of custom control you'll need to implement IXTPScrollable interface:

    // Your existing class
    class CCustomControl : public CWnd
    {
    public:
        void InitializeCustomState();
        // ...
    };

    // Your new derived class.
    // Some or all method may have default implementation, the example demonstrates overloading of all methods.
    class CScrollableCustomControl : public CXTPScrollable
    {
    public:
        // IXTPScrollable overrides

        virtual BOOL HasVScroll(DWORD dwStyle, DWORD dwExStyle) const
        {
            // Determine if control has vertical scroll.
            return 0 != (GetStyle() & WS_VSCROLL);
        }

        virtual BOOL HasHScroll(DWORD dwStyle, DWORD dwExStyle) const
        {
            // Determine if control has horizontal scroll.
            return 0 != (GetStyle() & WS_HSCROLL);
        }

        virtual BOOL HasLeftScrollbar(DWORD dwStyle, DWORD dwExStyle) const
        {
            // Determine if control has scroll bar on the lets.
            return 0 != (GetExStyle() & WS_EX_LEFTSCROLLBAR);
        }

        virtual void DisableScrollbars()
        {
            // Force default scroll bars to hide.
            DisableScrollbars(*this);
        }

        virtual void DisableScrollbars(CWnd& wnd)
        {
            // Force default scroll bars to hide for a specific window.
            wnd.ModifyStyle(WS_VSCROLL | WS_HSCROLL, 0);
        }

        virtual CScrollBar* CreateBar() const
        {
            // Create scroll bar instance.
            return new CXTPScrollBarCtrl();
        }

        virtual CWnd* CreateControl() const
        {
            // Re-create custom control instance and perform default initialization if necessary.
            CCustomControl* pNewCtrl = new CCustomControl();
            VERIFY(NULL != pNewCtrl);

            pNewCtrl->InitializeCustomState();

            return pNewCtrl;
        }

        virtual DWORD FilterStyle(DWORD dwStyle) const
        {
            return dwStyle;
        }

        virtual DWORD FilterExStyle(DWORD dwExStyle) const
        {
            return dwExStyle;
        }

        virtual BOOL RequiresMouseWheelOverriding() const
        {
            return true;
        }

        // ...
    };