Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Tile Grid Control

4.84/5 (23 votes)
5 Mar 2024CPOL8 min read 36.4K   3K  
An MFC CWnd derived grid of user definable tiles
This article introduces a CWnd derived MFC control for presenting dynamic grids of tiles, inspired by Windows Explorer's tile view, outlining its structure, usage, and integration within MFC applications for versatile display of data.

Image 1

Image 2

Introduction

This article presents a CWnd derived MFC control for presenting data as a dynamic grid of tiles. I use the term dynamic because the number of tiles in a row or column depends on the size of the window and the size of the tiles and the layout will change as the tile grid's window size changes. The tiles are layed out as a list where they fill one row or column at a time, rather than the traditional grid where tiles are layed out in designated rows and columns.

The idea for the control came from the tile view in Windows Explorer. Originally, I tried to twist the icon view of a CListCtrl to do what I wanted, but it was way too limited. First off, I could not get the icons big enough for what I wanted, and it was also impossible to get the icons to respond to user input in any intelligent way.

I tried asking my old friend Google to see if she knew of any MFC controls that would do what I wanted, but there was no love found there either. So the only option left was to write a control from scratch that did exactly what I wanted. Presented here is the result.

The Tile Grid Control

The Tile Grid Control is made up of six MFC classes that work together to make the control do what needs to be done. The classes are listed below. I will not list the entire API of the classes here in this article as there are a total of one hundred and sixty one individual functions between the six classes. Listing them here would make this article way too long and make looking up the API way too unwieldy. I have instead included a compiled help file that lists all of the public and protected member functions.

The classes that make up the tile grid control are:

  • CTileGrid
    • The CTileGrid class is the main window class for the tile grid control. It is derived directly from CWnd. This class can be used directly as is, or more likely will serve as the base class for your own custom grid control. The CTileGrid class is responsible for maintaining the layout of all the tiles and for handling all windows messages related to the users interactions with the control.
  • CTileBase
    • The CTileBase class is the base class from which you would derive your own tile classes. CTileBase is derived from CObject and is declared as an abstract base class. It has a protected constructor so you cannot directly construct a CTileBase class object . CTileBase::OnDraw is declared as a pure virtual function so in order to use this class, you have to override OnDraw in your derived class to draw the tiles onto the grid.
    • Any user input that happens in the tile grid gets forwarded to the CTileBase tile that the input was directed towards. So the tiles can respond to any mouse actions, and if the tile has the keyboard input focus, it can respond to any non-system key presses.
  • CWindowScroller
    • The CWindowScroller class is used to provide the middle mouse button panning control to the tile grid.
  • CTileGridTip
    • The CTileGridTip class is a simple CWnd derived class that acts as a single line tooltip window for the CTileGrid control. CTileGridTip can be used as is or it can be used as a base class for a more elaborate tooltip control.
  • CTileGridView
    • The CTileGridView class was written so the tile grid could be used in an SDI or MDI MFC Doc/View model application. CTileGridView is derived from CView. CTileGridView has a protected constructor so it can only be used as a base class for your own view class. It is a simple class that contains the CTileGrid control in its client area.
  • TileBaseReturn
    • The TileBaseReturn class is used as the return value from the CTileGrid::GetTileReturn function. Many of the CTileGrid functions will call into CTileBase functions and the only way for derived classes to get the data returned from the CTileBase functions from within your CTileGrid derived class is to use CTileGrid::GetTileReturn.

Using the Tile Grid Control

The Tile Grid control can be used in many different applications. It can be used as a control on a dialog template, as the main window in a simple application, or as a view in a multiple or single document interface program. No matter which way it is used, the first step to use the control in your own application is to add all the files to your project. The files are located in the TileGrid folder in the downloads.

The files needed are:

  • TileGrid.h, TileGrid.cpp: These files define the CTileGrid and TileBaseReturn classes.
  • TileBase.h, TileBase.cpp: These files define the CTileBase class.
  • TileGridTip.h, TileGridTip.cpp: These files define the CTileGridTip class.
  • WindowScroller.h, WindowScroller.cpp: These files define the CWindowScroller class.
  • TileGridView.h, TileGridView.cpp: These files define the CTileGridView class. The use of these files is optional as they are only needed if the tile grid is to be used as a view in a Doc/View application.

In a Dialog Template

To add the tile grid to a dialog template, simply add it as a custom control and specify the required TGS_* creation styles in the Style property. Add a CTileGrid or derived member variable to the dialog class and in your DoDataExchange function, add a DDX_Control function to associate your member variable with the custom control.

C++
void CMyDialog::DoDataExchange(CDataExchange* pDX)
{
    DDX_Control(pDX, IDC_CUSTOM1, m_TileGrid);
}

That is it, now the tile grid can be accessed though the CTileGrid member variable.

See the TileGridDialog demo application included in the demo download for an example.

As the Main Window

To use the tile grid as the main window of an application without Doc/View support, it is simply a matter of using the new app creation wizard to create an SDI app and make sure you clear the Document/View support checkbox. Then, it is simply a matter of making sure your CChildView class is derived from CTileGrid or a class derived from it. Then in CMainFrame::OnCreate, place a call to CChildView::Create to create the tile grid, specifying the TGS_* tile grid creation styles that are required.

C++
class CChildView : public CTileGrid
    {
        ...
C++
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
      return -1;

    // create a view to occupy the client area of the frame
    if (!m_wndView.Create(
        AFX_WS_DEFAULT_VIEW | TGS_RESIZABLE | TGS_MULTISELECT | TGS_PANNING,
        CRect(0, 0, 0, 0),
        this,
        AFX_IDW_PANE_FIRST,
        NULL))
    {
        TRACE0("Failed to create view window\n");
        return -1;
    }

See the TileGridView demo application included in the demo download for an example.

In a Doc/View Application

To use the CTileGrid control in a CView derived window that can be used in an SDI/MDI application, you have to derive your view class from CTileGridView. You then pass the RUNTIME_CLASS of the CTileGrid or derived class to the CTileGridView class constructor.

C++
class CMyAppView : public CTileGridView
{
    DECLARE_DYNCREATE(CMyAppView)
    ...
C++
CMyAppView::CMyAppView()
    : CTileGridView(RUNTIME_CLASS(CTileGrid))
    {
        ...

Set the TGS_* tile grid control styles from your view class' OnCreate member function.

C++
int CMyAppView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    lpCreateStruct->style |= TGS_HORIZONTAL;
    return CTileGridView::OnCreate(lpCreateStruct);
}

See the TileGridDV demo application included in the demo download for an example.

Adding Tiles

Once the tile grid window has been created, you can add the tiles you want to use. Each tile that you use has to be of a class derived from CTileBase. Every class derived from CTileBase has to, at a minimum, define its own OnDraw method that will draw the tile onto the tile grid.

The tiles are added to the grid using the CTileGrid::AddTile function. You will also want to set the size of the tiles using the CTileGrid::SetTileSize function. If you do not set the size, all the tiles will default to their minimum 2 by 2 pixel size. You may also want to space the tiles apart by using the CTileGrid::SetTileSpacing function. The tiles will be tight together otherwise.

C++
void CMyAppView::OnInitialUpdate()
{
    CTileGridView::OnInitialUpdate();

    CTileGrid& TheGrid = GetTileGrid();     // Get the tile grid
    TheGrid.SetTileSize(50, 50, false);     // all tiles are 50 by 50 pixels
    TheGrid.SetTileSpacing(2);              // 2 pixel space between tiles

    for (int x = 0; x < 50; ++x)            // add 50 tiles
    {
        CMyTile pTile = new CMyTile;        // CMyTile derived from CTileBase
        TheGrid.AddTile(pTile, true);
    }
}

Creating Derived Classes

Class Inheritable Runtime information macro
CTileBase Yes - Required DYNAMIC
CTileGrid Yes - Optional DYNCREATE
CTileGridTip Yes - Optional DYNCREATE
CTileGridView Yes - Required DYNCREATE
CWindowScroller No -
TileBaseReturn No -

In order to use the CTileBase and CTileGridView classes, one has to derive your own classes from them. CTileGrid and CTileGridTip can be used as-is or they can serve as a base class for your own derived classes. The CWindowScroller and TileBaseReturn classes are helper classes and should not be inherited from.

CTileBase has been created using the DECLARE_DYNAMIC and IMPLEMENT_DYNAMIC macros. While it is not necessary for any derived classes to use these same macros, it is probably not a bad idea to use them.

If you inherit a class from CTileGrid, CTileGridView or CTileGridTip, be sure to use the DECLARE_DYNCREATE and IMPLEMENT_DYNCREATE macros as these classes are created dynamically. In order to use a derived tooltip class as the tooltip control for the tile grid, you have to call CTileGrid::SetTooltip, before CTileGrid::Create is called, specifying the runtime class of your derived tooltip class. If you are using a derived tooltip in a CTileGridView derived view, then you would pass the runtime class of the tooltip as the second parameter to the CTileGridView constructor.

History

  • 10th May, 2016
    • Posted on CodeProject
  • 23rd June, 2019
    • Added to the CTileGrid class:
      • AddTile(CRuntimeClass *)
      • GetMinMaxTileSize
      • GetTGStyle
      • GetTileSizeDelta
      • IsResizingTiles
      • IsTileFullyVisible
      • ModifyTGStyle
      • OnEnable
      • OnPrintClient
      • RemoveSelectedTiles
      • SetMinMaxTileSize
      • SetTGStyle
    • Changed in the CTileGrid class:
      • GetTileFromPoint - Added the hit test parameter
      • IsStyleSet - Renamed to IsTGStyleSet
      • OnGetDlgCode - Added the WPARAM and LPARAM parameters
    • Changed the TGS_* styles
      • Removed TGS_RESIZABLE
      • Added TGS_RESIZABLEVERT, TGS_RESIZABLEHORZ, TGS_RESIZABLEKEYS, and TGS_RESIZABLEMOUSE
    • Added CTileGrid parent window notifications
    • Removed the CTGSortFunction and CTGFindFunction types
    • Added to the CTileBase class:
      • GetFont
      • GetSize
      • IsFullyVisible
      • OnAddedToGrid
      • OnGetDlgCode
      • OnLButtonClick
      • OnMButtonClick
      • OnRButtonClick
      • OnRemovedFromGrid
      • OnSize
      • OnXButtonClick
  • 6th March, 2024
    • Added to the CTileGrid class:
      • OnTimer
      • SetTileTimer
      • KillTileTimer
      • OnTileMsg
    • Added to the CTileBase class:
      • OnTimer
      • SetTimer
      • KillTimer
      • OnTileMsg
      • OnVisibility

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)