Custom slideshow

Hi,
I just joined this forum so hopefully I am posting in the right area and according to the guidelines.

I am an electronic engineer and program embedded systems in C/C++ but have not used Visual C++/MFC in 18 years... So the simplest and fastest solution is what I am looking for. Not even menu, open/save or anything else. Something I can make ideally in a day or even just few hours...

In summary I want to make (using VC++) the following:

1) a slideshow that displays a set of images in a random order. Such images are located in the same folder where the EXE file is located

2) the images are displayed in RANDOM sequence for XX number of times at RANDOM YY time intervals (from tens of milliseconds to seconds)

3) the ONLY two UI items are the Play Slideshow button and a small table where in each row I specify a) the number that a full set of images in the folder is displayed, b) minimum display time in milliseconds and c) maximum display time again in millisecond. (note, it will be always at least 10 milliseconds display time minimum).

4) it is just for me so a single EXE file with NO installer, no fancy UI, no open file, no images location folder choosing, no save of settings or anything else. I can fill in the table every time I run the software.

5) to run only in Windows desktop PC

The goal is to make it as fast as possible. Hopefully just few hours??

The main issue I have not used Visual C++ for over 18 years so don't remember much about it.

Can you please give me some pointers as to how to implement it.

Many thanks :)
Last edited on
if the primary goal is just to crank it out quickly, find a working example close to what you want and hack on it for a few min.
Thank you Jonnin,
no idea where to find an extremely basic example nor which part to tweak.
Where can I find one?

Thank you :)
What type of image formats are you wanting to display? Bitmaps? jpegs? pngs? Win32 GDI can process bitmaps. Other formats you could use GDI+, Direct2D or the Windows Imaging Component.

Do you want this to be a desktop (Win32 SDK) only app, or one that is usable on multiple devices including phones? Visual Studio can create projects/solutions for either.

You have a lot of learning and catching up to do after 18 years. The technology has changed quite a lot.

I have some old GDI source code (multiple files) that displays a Win32 GUI slide-show of known set images, not however many are in a directory, built as the beginning part of a game engine. The source code could be something to learn from and hopefully adapt.

I could post the files here, along with instructions how to set up a usable VS 2019 (Community) project if you want.

Windows apps are not anything as simple as C++ apps.

C++ app:
1
2
3
4
5
6
#include <iostream>

int main()
{
   std::cout << "Hello World!\n";
}

Minimal Win32 app:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
// INCLUDES ====================================================================
#include <windows.h>

// FUNCTION PROTOTYPES =========================================================
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


// entry point for a Windows application =======================================
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nWinMode)
{
   // define the window class name
   static const TCHAR szAppName[]  = TEXT("1-3_MinimalWindowsApp");

   // create an instance of the window class structure
   WNDCLASSEX wc;

   wc.cbSize        = sizeof(WNDCLASSEX);
   wc.style         = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc   = WndProc;
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = 0;
   wc.hInstance     = hInstance;
   wc.hIcon         = (HICON)   LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
   wc.hIconSm       = (HICON)   LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
   wc.hCursor       = (HCURSOR) LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
   wc.hbrBackground = (HBRUSH)  (COLOR_WINDOW + 1);
   wc.lpszMenuName  = NULL;
   wc.lpszClassName = szAppName;

   if (0 == RegisterClassEx(&wc))
   {
      MessageBox(NULL, TEXT("Can't Register the Window Class!"), szAppName, MB_OK | MB_ICONERROR);
      return E_FAIL;
   }

   // define the application title
   static const TCHAR szAppTitle[] = TEXT("Win32 API Skeletal Application");

   // create the window
   HWND hwnd = CreateWindow(szAppName, szAppTitle,
                            WS_OVERLAPPEDWINDOW,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            CW_USEDEFAULT, CW_USEDEFAULT,
                            NULL, NULL, hInstance, NULL);

   // check if the window was created, exit if fail
   if (NULL == hwnd)
   {
      MessageBox(NULL, TEXT("Unable to Create the Main Window!"), szAppName, MB_OK | MB_ICONERROR);
      return E_FAIL;
   }

   // show and update the window
   ShowWindow(hwnd, nWinMode);
   UpdateWindow(hwnd);

   static BOOL bRet;
   static MSG  msg;

   // enter the main message loop
   while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) // 0 = WM_QUIT
   {
      // check for error
      if (-1 == bRet)
      {
         // handle the error and possibly exit

         // for this app simply report error and exit
         MessageBox(NULL, TEXT("Unable to Continue!"), szAppName, MB_OK | MB_ICONERROR);
         return E_FAIL;
      }
      else
      {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
   }

   // the app is done, exit normally, return control to Windows
   return (int) msg.wParam;
}


// processes the messages that Windows sends to the application ================
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   // choose which Windows messages you want to process
   switch(message)
   {
   case WM_PAINT:
      HDC         hdc;
      PAINTSTRUCT ps;
      hdc = BeginPaint(hwnd, &ps);

      // draw some text centered in the client area
      RECT rect;
      GetClientRect(hwnd, &rect);
      DrawText(hdc, TEXT("Hello, Windows!"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

      EndPaint(hwnd, &ps);
      return S_OK;

   case WM_DESTROY:
      // exit the application
      PostQuitMessage(0);
      return S_OK;
   }

   // let Windows process any unhandled messages
   return DefWindowProc(hwnd, message, wParam, lParam);
}
Thank you Furry guy, very much appreciated! :)

Yes if you could post/share the code that would be great.

In terms of type of images, it doesn't matter, even a single type is enough. Then I can make convert them all to that specific file format before putting them wherever they the software needs to reload them from.

It needs to run only on a desktop PC running Windows (I just updated the OP too).

And if you can think of putting even more constraints to make it even simpler, easier and faster, I am open to suggestions. I am the only one to use it so it does not need to be pretty or particularly user friendly either. It could even be a command prompt solution if there is a way to pass the parameters that in my OP I suggested to input in a small table.

Thank you again. :)
Last edited on
Fair warning, this is a lot of code.

First off, I am using Visual Studio 2019 (Community version). Other editions won't matter.

I start off by creating a "New Project". That bring up the "Create a new project" dialog. There are a lot of wizards to choose from on the right hand side.

For C++ apps I choose "Empty Project" so there are no files added. A dialog to set the exe name and files location, and you select "Create". All the project/solution settings for a console app are done using this brief wizard, and all source files I add manually.

For Windows GUI-based apps I choose "Windows Desktop Wizard." The same name and directory dialog is shown. "Create", and another dialog (Windows Desktop Project) is shown. Choose the "Application type" pull-down menu, select "Desktop Application (.exe)". With the 4 "additional options" select the "Empty project" check box, unselect the other 3. Select "Ok".

(You can use the "Windows Desktop Wizard" to create a console app without files, I don't because using the "Empty Project" wizard is less steps.)

Your project/solution is now configured for a Windows GUI app.

FYI, I chose to name my app "Slideshow". You can choose whatever name you want.

This apps loads 6 bitmaps, displays 7 pictures. All 6 bitmaps are 640 by 480.

Let's add the source files now.

resource.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// resource.h - Resource IDs

#pragma once

#define  IDI_ICON          1001
#define  IDI_ICON_SM       1002

#define  IDR_MENU          2001
#define  IDM_GAME_EXIT     2002
#define  IDM_HELP_ABOUT    2003

#define  IDR_ACCELERATORS  3001

#define  IDD_ABOUT         4001
#define  IDC_STATIC        -1

#define  IDB_IMAGE1        5001
#define  IDB_IMAGE2        5002
#define  IDB_IMAGE3        5003
#define  IDB_IMAGE4        5004
#define  IDB_IMAGE5        5005
#define  IDB_IMAGE6        5006 


Slideshow.rc:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// Slideshow.rc - Resource Declarations

#include <windows.h>
#include "resource.h"

IDI_ICON    ICON     DISCARDABLE    "Res/Slideshow.ico"
IDI_ICON_SM ICON     DISCARDABLE    "Res/Slideshow_sm.ico"

IDB_IMAGE1  BITMAP   DISCARDABLE    "Res/Image1.bmp"
IDB_IMAGE2  BITMAP   DISCARDABLE    "Res/Image2.bmp"
IDB_IMAGE3  BITMAP   DISCARDABLE    "Res/Image3.bmp"
IDB_IMAGE4  BITMAP   DISCARDABLE    "Res/Image4.bmp"
IDB_IMAGE5  BITMAP   DISCARDABLE    "Res/Image5.bmp"
IDB_IMAGE6  BITMAP   DISCARDABLE    "Res/Image6.bmp"

IDD_ABOUT   DIALOG   DISCARDABLE    32, 32, 180, 102
CAPTION "About..."
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
{
   ICON           IDI_ICON, IDC_STATIC, 7, 7, 20, 20, SS_SUNKEN
   CTEXT          "Slideshow", IDC_STATIC, 40, 12, 100, 8

   CTEXT          "Drawing Graphical Images", IDC_STATIC, 7, 35, 166, 8
   CTEXT          "© This Year, Your Name", IDC_STATIC, 7, 47, 166, 8

   DEFPUSHBUTTON  "OK", IDOK, 66, 81, 50, 14
}

IDR_MENU MENU
{
   POPUP "&Game"
   {
      MENUITEM "E&xit\tALT + F4", IDM_GAME_EXIT
   }

   POPUP "&Help"
   {
      MENUITEM "&About...\tF1",   IDM_HELP_ABOUT
   }
}

IDR_ACCELERATORS ACCELERATORS
{
   VK_F1,   IDM_HELP_ABOUT,   VIRTKEY
}


The resource script embeds all 6 images into the .exe. The program when run loads several images from the resources and several images from disk files. I've located the resources into a sub-directory. They can be located in the same directory as the source files. The image files also need to be located in the same directory where the .exe resides so it can load the images when the app is running.

The resources are the 6 bitmap images, a custom menu and accelerators (keyboard shortcuts for menu items) as well as an "About...." dialog.

More to come...
Now the Bitmap class, the code that loads (and creates) and displays bitmap files:

Bitmap.hpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// Bitmap.hpp - Bitmap Header

#pragma once

#include <windows.h>

// Bitmap CLASS ================================================================
class Bitmap
{
public:
   // constructor(s)/destructor
   Bitmap();
   Bitmap(LPCTSTR szFileName);
   Bitmap(UINT uiResID, HINSTANCE hInstance);
   Bitmap(HWND hWnd, LONG iWidth, LONG iHeight, COLORREF crColor = RGB(0, 0, 0));
   virtual ~Bitmap();

public:
   // general methods
   BOOL Create(LPCTSTR szFileName);
   BOOL Create(UINT uiResID, HINSTANCE hInstance);
   BOOL Create(HWND hWnd, LONG iWidth, LONG iHeight, COLORREF crColor);

public:
   void Draw(HDC hDC, int x, int y) const;

public:
   LONG GetWidth() const;
   LONG GetHeight() const;

protected:
   // helper methods
   void Free();

protected:
   // member variables
   HBITMAP m_hBitmap;
   LONG    m_iWidth;
   LONG    m_iHeight;
};


inline LONG Bitmap::GetWidth() const
{
   return m_iWidth;
}


inline LONG Bitmap::GetHeight() const
{
   return m_iHeight;
}


Bitmap.cpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// Bitmap.cpp - Bitmap Source

#include "Bitmap.hpp"


// default bitmap constructor
Bitmap::Bitmap() :
   m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
}


// bitmap filename constructor
Bitmap::Bitmap(LPCTSTR szFileName) :
   m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
   Create(szFileName);
}


// bitmap resource constructor
Bitmap::Bitmap(UINT uiResID, HINSTANCE hInstance) :
   m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
   Create(uiResID, hInstance);
}


// bitmap blank constructor
Bitmap::Bitmap(HWND hWindow, LONG iWidth, LONG iHeight, COLORREF crColor)
   : m_hBitmap(NULL), m_iWidth(0), m_iHeight(0)
{
   Create(hWindow, iWidth, iHeight, crColor);
}


// bitmap destructor
Bitmap::~Bitmap()
{
   Free();
}


// creates a bitmap from a file
BOOL Bitmap::Create(LPCTSTR szFileName)
{
   // free any previous bitmap info
   Free();

   // load the bitmap file
   m_hBitmap = (HBITMAP) LoadImage(NULL, szFileName, IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION | LR_LOADFROMFILE);

   if (m_hBitmap == NULL)
   {
      // something went wrong, so cleanup everything
      Free();

      return FALSE;
   }

   BITMAP bitmap;

   GetObject(m_hBitmap, sizeof(BITMAP), &bitmap);

   // store the width and height of the bitmap
   m_iWidth  = bitmap.bmWidth;
   m_iHeight = bitmap.bmHeight;

   return TRUE;
}


// creates a bitmap from a resource
BOOL Bitmap::Create(UINT uiResID, HINSTANCE hInstance)
{
   // free any previous bitmap info
   Free();

   // load the bitmap resource
   m_hBitmap = (HBITMAP) LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(uiResID), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION);

   if (m_hBitmap == NULL)
   {
      // something went wrong, so cleanup everything
      Free();

      return FALSE;
   }

   BITMAP bitmap;

   GetObject(m_hBitmap, sizeof(BITMAP), &bitmap);

   // store the width and height of the bitmap
   m_iWidth  = bitmap.bmWidth;
   m_iHeight = bitmap.bmHeight;

   return TRUE;
}


// creates a blank bitmap with the included color
BOOL Bitmap::Create(HWND hWindow, LONG iWidth, LONG iHeight, COLORREF crColor)
{
   HDC hDC = GetDC(hWindow);

   // create a blank bitmap
   m_hBitmap = CreateCompatibleBitmap(hDC, iWidth, iHeight);

   if (m_hBitmap == NULL)
   {
      return FALSE;
   }

   // set the width and height
   m_iWidth  = iWidth;
   m_iHeight = iHeight;

   // create a memory device context to draw on the bitmap
   HDC hMemDC = CreateCompatibleDC(hDC);

   // create a solid brush to fill the bitmap
   HBRUSH hBrush = CreateSolidBrush(crColor);

   // select the bitmap into the device context
   HBITMAP hOldBitmap = (HBITMAP) SelectObject(hMemDC, m_hBitmap);

   // fill the bitmap with a solid color
   RECT rcBitmap = { 0, 0, m_iWidth, m_iHeight };
   FillRect(hMemDC, &rcBitmap, hBrush);

   // cleanup
   SelectObject(hMemDC, hOldBitmap);
   DeleteDC(hMemDC);
   DeleteObject(hBrush);

   return TRUE;
}


// draws a bitmap
void Bitmap::Draw(HDC hDC, int x, int y) const
{
   if (m_hBitmap != NULL)
   {
      // create a memory device context for the bitmap
      HDC hMemDC = CreateCompatibleDC(hDC);

      // select the bitmap into the device context
      HBITMAP hOldBitmap = (HBITMAP) SelectObject(hMemDC, m_hBitmap);

      // draw the bitmap to the destination device context
      BitBlt(hDC, x, y, GetWidth(), GetHeight(), hMemDC, 0, 0, SRCCOPY);

      // restore and delete the memory device context
      SelectObject(hMemDC, hOldBitmap);
      DeleteDC(hMemDC);
   }
}


// deletes a bitmap
void Bitmap::Free()
{
   // delete the bitmap graphics object
   if (m_hBitmap != NULL)
   {
       DeleteObject(m_hBitmap);
       m_hBitmap = NULL;
   }
}


FYI, using Win32's LoadImage function made the code for loading and creating bitmaps considerably shorter. Here's the original code for reading a bitmap from a file:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
BOOL Bitmap::Create(HDC hDC, LPCTSTR szFileName)
{
   // free any previous bitmap info
   Free();

   // open the bitmap file
   HANDLE hFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,
                             OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

   if (hFile == INVALID_HANDLE_VALUE)
   {
      return FALSE;
   }

   // read the bitmap file header
   BITMAPFILEHEADER bmfHeader;
   DWORD            dwBytesRead;
   BOOL             bOK = ReadFile(hFile, &bmfHeader, sizeof(BITMAPFILEHEADER),
                                   &dwBytesRead, NULL);

   if ((!bOK) || (dwBytesRead != sizeof(BITMAPFILEHEADER)) || (bmfHeader.bfType != 0x4D42))
   {
      CloseHandle(hFile);
      return FALSE;
   }

   BITMAPINFO* pBitmapInfo = (BITMAPINFO*) (new BITMAPINFO_256);

   if (pBitmapInfo != NULL)
   {
      // read the bitmap info header
      bOK = ReadFile(hFile, pBitmapInfo, sizeof(BITMAPINFOHEADER), &dwBytesRead, NULL);

      if ((!bOK) || (dwBytesRead != sizeof(BITMAPINFOHEADER)))
      {
         CloseHandle(hFile);
         Free();

         return FALSE;
      }

      // store the width and height of the bitmap
      m_iWidth  = (int) pBitmapInfo->bmiHeader.biWidth;
      m_iHeight = (int) pBitmapInfo->bmiHeader.biHeight;

      // skip (forward or backward) to the color info, if necessary
      if (pBitmapInfo->bmiHeader.biSize != sizeof(BITMAPINFOHEADER))
      {
         SetFilePointer(hFile, pBitmapInfo->bmiHeader.biSize - sizeof(BITMAPINFOHEADER), NULL, FILE_CURRENT);
      }

      // read the color info
      bOK = ReadFile(hFile, pBitmapInfo->bmiColors, pBitmapInfo->bmiHeader.biClrUsed * sizeof(RGBQUAD),
                     &dwBytesRead, NULL);

      // get a handle to the bitmap and copy the image bits
      PBYTE pBitmapBits;

      m_hBitmap = CreateDIBSection(hDC, pBitmapInfo, DIB_RGB_COLORS, (PVOID*) &pBitmapBits, NULL, 0);

      if ((m_hBitmap != NULL) && (pBitmapBits != NULL))
      {
         SetFilePointer(hFile, bmfHeader.bfOffBits, NULL, FILE_BEGIN);

         bOK = ReadFile(hFile, pBitmapBits, pBitmapInfo->bmiHeader.biSizeImage, &dwBytesRead, NULL);

         if (bOK)
         {
            return TRUE;
         }
      }
   }

   // something went wrong, so cleanup everything
   Free();
   return FALSE;
}

OUCH! The other bitmap creation functions were just as bloated.

Even more to come....
The Game Engine class.

GameEngine.hpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// GameEngine.hpp - Game Engine Header

#pragma once

#include <windows.h>
#include <strsafe.h>
#include "resource.h"
#include "random_toolkit.hpp"

const static UCHAR str_length = 64;

int WINAPI       WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow);
LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK    DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);


HRESULT GameInitialize(HINSTANCE hInstance);
void    GameStart(HWND hWindow);
void    GameEnd();
void    GameActivate(HWND hWindow);
void    GameDeactivate(HWND hWindow);
void    GamePaint(HDC hDC);
void    GameCycle();
void    GameMenu(WPARAM wParam);


class GameEngine
{
public:
            GameEngine(HINSTANCE hInstance, LPCTSTR szWindowClass, LPCTSTR szTitle,
                       WORD wIcon, WORD wSmallIcon, UINT iWidth = 640, UINT iHeight = 480);
   virtual ~GameEngine();

public:
   static GameEngine* GetEngine();
   HRESULT            Initialize(int iCmdShow);
   LRESULT            HandleEvent(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam);
   void               ErrorQuit(LPCTSTR szErrorMsg);

public:
   HINSTANCE GetInstance() const;
   HWND      GetWindow() const;
   void      SetWindow(HWND hWindow);
   PTSTR     GetTitle();
   WORD      GetIcon() const;
   WORD      GetSmallIcon() const;
   UINT      GetWidth() const;
   UINT      GetHeight() const;
   UINT      GetFrameDelay() const;
   void      SetFrameRate(UINT iFrameRate);
   BOOL      GetSleep() const;
   void      SetSleep(BOOL bSleep);

protected:
   static GameEngine* m_pGameEngine;
   HINSTANCE          m_hInstance;
   HWND               m_hWindow;
   TCHAR              m_szWindowClass[str_length];
   TCHAR              m_szTitle[str_length];
   WORD               m_wIcon;
   WORD               m_wSmallIcon;
   UINT               m_iWidth;
   UINT               m_iHeight;
   UINT               m_iFrameDelay;
   BOOL               m_bSleep;
};


// notifies the user of an unrecoverable error and quits the program
inline void GameEngine::ErrorQuit(LPCTSTR szErrorMsg)
{
   MessageBox(GetWindow(), szErrorMsg, TEXT("Critical Error"), MB_OK | MB_ICONERROR);
   PostQuitMessage(0);
}



inline GameEngine* GameEngine::GetEngine()
{
   return m_pGameEngine;
}


inline HINSTANCE GameEngine::GetInstance() const
{
   return m_hInstance;
}


inline HWND GameEngine::GetWindow() const
{
   return m_hWindow;
}


inline void GameEngine::SetWindow(HWND hWindow)
{
   m_hWindow = hWindow;
}


inline PTSTR GameEngine::GetTitle()
{
   return m_szTitle;
}


inline WORD GameEngine::GetIcon() const
{
   return m_wIcon;
}


inline WORD GameEngine::GetSmallIcon() const
{
   return m_wSmallIcon;
}


inline UINT GameEngine::GetWidth() const
{
   return m_iWidth;
}


inline UINT GameEngine::GetHeight() const
{
   return m_iHeight;
}


inline UINT GameEngine::GetFrameDelay() const
{
   return m_iFrameDelay;
}


inline void GameEngine::SetFrameRate(UINT iFrameRate)
{
   m_iFrameDelay = 1000 / iFrameRate;
}


inline BOOL GameEngine::GetSleep() const
{
   return m_bSleep;
}


inline void GameEngine::SetSleep(BOOL bSleep)
{
   m_bSleep = bSleep;
}


GameEngine.cpp next......
GameEngine.cpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
// GameEngine.cpp - Game Engine Source

#include "Bitmap.hpp"
#include "GameEngine.hpp"

GameEngine* GameEngine::m_pGameEngine = NULL;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
   if (GameInitialize(hInstance) == S_OK)
   {
      if (GameEngine::GetEngine()->Initialize(iCmdShow) != S_OK)
      {
         return E_FAIL;
      }

      HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATORS));
      if (hAccel == NULL)
      {
         MessageBox(NULL, TEXT("Unable to Load the Accelerators!"), GameEngine::GetEngine()->GetTitle(), MB_OK | MB_ICONERROR);
         return E_FAIL;
      }

      MSG msg;

      while (TRUE)
      {
         if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) != 0)
         {
            if (msg.message == WM_QUIT)
            {
               break;
            }

            if (0 == TranslateAccelerator(GameEngine::GetEngine()->GetWindow(), hAccel, &msg))
            {
               TranslateMessage(&msg);
               DispatchMessage(&msg);
            }
         }
         else
         {
            if (GameEngine::GetEngine()->GetSleep() == FALSE)
            {
               static UINT iTickTrigger = 0;
               UINT        iTickCount = GetTickCount();
               if (iTickCount > iTickTrigger)
               {
                  iTickTrigger = iTickCount + GameEngine::GetEngine()->GetFrameDelay();
                  GameCycle();
               }
            }
         }
      }
      return (int) msg.wParam;
   }

   GameEnd();
   return S_OK;
}


LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
   return GameEngine::GetEngine()->HandleEvent(hWindow, msg, wParam, lParam);
}


BOOL CALLBACK DlgProc(HWND hDialog, UINT message, WPARAM wParam, LPARAM lParam)
{
   switch (message)
   {
   case WM_COMMAND:
      switch (LOWORD(wParam))
      {
      case IDOK:
         EndDialog(hDialog, 0);
         return TRUE;
      }
   }

   return FALSE;
}



GameEngine::GameEngine(HINSTANCE hInstance, LPCTSTR szWindowClass, LPCTSTR szTitle,
                       WORD wIcon, WORD wSmallIcon, UINT iWidth, UINT iHeight)
{
   m_pGameEngine = this;
   m_hInstance   = hInstance;
   m_hWindow     = NULL;
   m_wIcon       = wIcon;
   m_wSmallIcon  = wSmallIcon;
   m_iWidth      = iWidth;
   m_iHeight     = iHeight;
   m_iFrameDelay = 50;
   m_bSleep      = TRUE;

   size_t  pcch = 0;
   HRESULT hRes = StringCchLength(szWindowClass, str_length, &pcch);

   if (pcch > 0)
   {
      StringCchCopy(m_szWindowClass, str_length, szWindowClass);
   }
   else
   {
      StringCchCopy(m_szWindowClass, str_length,TEXT(""));
   }

   hRes = StringCchLength(szTitle, str_length, &pcch);

   if (pcch > 0)
   {
      StringCchCopy(m_szTitle, str_length, szTitle);
   }
   else
   {
      StringCchCopy(m_szTitle, str_length, TEXT(""));
   }
}


GameEngine::~GameEngine()
{
}



HRESULT GameEngine::Initialize(int iCmdShow)
{
   WNDCLASSEX wc;

   wc.cbSize        = sizeof(WNDCLASSEX);
   wc.style         = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc   = WndProc;
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = 0;
   wc.hInstance     = m_hInstance;
   wc.hIcon         = (HICON)   LoadImage(m_hInstance, MAKEINTRESOURCE(IDI_ICON), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
   wc.hIconSm       = (HICON)   LoadImage(m_hInstance, MAKEINTRESOURCE(IDI_ICON_SM), IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);;
   wc.hCursor       = (HCURSOR) LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
   wc.hbrBackground = (HBRUSH)  (COLOR_WINDOW + 1);
   wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MENU);
   wc.lpszClassName = m_szWindowClass;

   if (RegisterClassEx(&wc) == 0)
   {
      MessageBox(NULL, TEXT("Unable to initialize Main Window!"), TEXT("ERROR"), MB_ICONERROR | MB_OK);
      return E_FAIL;
   }

   UINT iWindowWidth  = m_iWidth  + GetSystemMetrics(SM_CXFIXEDFRAME) * 2;
   UINT iWindowHeight = m_iHeight + GetSystemMetrics(SM_CYFIXEDFRAME) * 2 + GetSystemMetrics(SM_CYCAPTION);

   iWindowWidth  += 10;
   iWindowHeight += 10;

   if (wc.lpszMenuName != NULL)
   {
      iWindowHeight += GetSystemMetrics(SM_CYMENU);
   }

   UINT iWindowPosX = (GetSystemMetrics(SM_CXSCREEN) - iWindowWidth)  / 2;
   UINT iWindowPosY = (GetSystemMetrics(SM_CYSCREEN) - iWindowHeight) / 2;

   m_hWindow = CreateWindow(m_szWindowClass, m_szTitle,
                            WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX,
                            iWindowPosX, iWindowPosY,
                            iWindowWidth, iWindowHeight,
                            NULL, NULL, m_hInstance, NULL);

   if (m_hWindow == NULL)
   {
      MessageBox(NULL, TEXT("Unable to create Main Window!"), TEXT("ERROR"), MB_ICONERROR | MB_OK);
      return E_FAIL;
   }

   ShowWindow(m_hWindow, iCmdShow);
   UpdateWindow(m_hWindow);

   return S_OK;
}


LRESULT GameEngine::HandleEvent(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch (msg)
   {
   case WM_CREATE:
      SetWindow(hWindow);
      GameStart(hWindow);
      return 0;

   case WM_ACTIVATE:
      if (wParam != WA_INACTIVE)
      {
         GameActivate(hWindow);
         SetSleep(FALSE);
      }
      else
      {
         GameDeactivate(hWindow);
         SetSleep(TRUE);
      }
      return 0;

   case WM_COMMAND:
      GameMenu(wParam);
      return 0;

   case WM_PAINT:
      HDC         hDC;
      PAINTSTRUCT ps;
      hDC = BeginPaint(hWindow, &ps);

      GamePaint(hDC);

      EndPaint(hWindow, &ps);
      return 0;

   case WM_DESTROY:
      GameEnd();
      PostQuitMessage(0);
      return 0;
   }
   return DefWindowProc(hWindow, msg, wParam, lParam);
}


Why have the Game Engine class?

Games have a lot of "behind the scenes" features in common no matter what the game is. By having a game engine class all the details of drawing images to the screen, processing input from the keyboard or a joystick, playing sounds and background music, etc are handled by the Game Engine.

With Windows based games a game engine hides all the messy details of Windows-specific code that doesn't necessarily have anything to with a game, but is required for every Windows application. Game or not.

With a fully fleshed out game engine class code you only have to deal with the logic specific to the game you are creating.

FYI, this Game Engine class is not even remotely usable for games at this point. :)

The Slideshow code next....
Last edited on
Now, the real meat of the Slideshow app, the logic for running the slideshow.

Slideshow.hpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Slideshow.hpp - game header

#pragma once

#include <memory>
#include <vector>
#include "Bitmap.hpp"
#include "GameEngine.hpp"

const UINT _iNUMSLIDES = 7;

GameEngine* g_pGame;
Bitmap*     g_pSlides[_iNUMSLIDES];
UINT        g_iCurSlide;


Yes, I "broke" an OOP principle by having globals. *Meh* :)

Slideshow.cpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Slideshow.cpp - game source

#include "Slideshow.hpp"

HRESULT GameInitialize(HINSTANCE hInstance)
{
   g_pGame = new GameEngine(hInstance, TEXT("Slideshow"), TEXT("Drawing Graphical Images"),
                           IDI_ICON, IDI_ICON_SM);

   if (g_pGame == NULL)
   {
      return E_FAIL;
   }

   g_pGame->SetFrameRate(1);

   return S_OK;
}


void GameStart(HWND hWindow)
{
   // create/load the slide bitmaps
   HINSTANCE hInstance = g_pGame->GetInstance();

   // create a blank bitmap
   g_pSlides[0] = new Bitmap(g_pGame->GetWindow(), 640, 480, RGB(128, 128, 64));

   // load bitmaps from a file
   g_pSlides[1] = new Bitmap(TEXT("Res/Image1.bmp"));
   g_pSlides[2] = new Bitmap(TEXT("Res/Image2.bmp"));
   g_pSlides[3] = new Bitmap(TEXT("Res/Image3.bmp"));

   // load bitmaps as resources
   g_pSlides[4] = new Bitmap(IDB_IMAGE4, hInstance);
   g_pSlides[5] = new Bitmap(IDB_IMAGE5, hInstance);
   g_pSlides[6] = new Bitmap(IDB_IMAGE6, hInstance);

   // set the index to the second slide
   g_iCurSlide = 1;
}


void GameEnd()
{
   // cleanup the slide bitmaps
   for (auto itr : g_pSlides)
   {
      delete itr;
   }


   // cleanup the game engine
   delete g_pGame;
}


void GameActivate(HWND hWindow)
{
}


void GameDeactivate(HWND hWindow)
{
}


void GamePaint(HDC hDC)
{
   // draw the current slide bitmap
   g_pSlides[g_iCurSlide]->Draw(hDC, 0, 0);
}


void GameCycle()
{
   static UINT iDelay = 0;

   // establish a 3-second delay before moving to the next slide
   if (++iDelay > 3)
   {
      // reset the delay counter
      iDelay = 0;

      // move to the next slide
      if (++g_iCurSlide == _iNUMSLIDES)
      {
         g_iCurSlide = 0;
      }

      // force a repaint to draw the next slide
      InvalidateRect(g_pGame->GetWindow(), NULL, FALSE);
   }
}


void GameMenu(WPARAM wParam)
{
   switch (LOWORD(wParam))
   {
   case IDM_GAME_EXIT:
      GameEnd();
      PostQuitMessage(0);
      return;

   case IDM_HELP_ABOUT:
      DialogBox(g_pGame->GetInstance(), MAKEINTRESOURCE(IDD_ABOUT), g_pGame->GetWindow(), (DLGPROC) DlgProc);
      return;
   }
}

Instead of embedding the images into the .exe and reading them all from files the size of the app would be reduced considerably.

Instead of loading the custom bitmaps into an array use a vector so different number of bitmaps could be used in the app.

Using C++17's <filesystem> you could iterate through the current directory at run time and get the number of image files to process.

Using GDI+ or the Windows Imaging Component you can load and display images other than bitmaps.

*Whew* This is a LOT of code to digest. Hopefully it will prove to be a good starting point to create an app that is closer to what you envision.
Last edited on
FYI, one really cool feature of using this Game Engine class is when the slideshow app loses focus (it is no longer the active app) the app's timer pauses. The images are not updated. When the app gains focus the timer starts counting down again.

The app pauses also when the "About...." box is displayed, or one of the menu items are displayed yet not selected.
Thank you SOOOO very much Furry Guy!
Very much appreciated!!!! :)

Now, about the requirement to choose a random image to display. Of course you can use C's srand/rand functions. Since the code is already C++ code why not use the<random>/<chrono> libraries?

Yes, they require more work and code to set up properly, but they produce less bias on the random number being generated.

The C standard itself recommends to not use srand/rand if there are alternatives available.

https://web.archive.org/web/20180123103235/http://cpp.indi.frih.net/blog/2014/12/the-bell-has-tolled-for-rand/
https://channel9.msdn.com/Events/GoingNative/2013/rand-Considered-Harmful

A C++ paper on Random Number Generation:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2013/n3551.pdf

Using the sample toolkit idea I created a header-only version I include in every project that needs basic random number generation.
Thank you :)
Displaying slides at random is a simple modification of the Slideshow.hpp and Slideshow.cpp code. Using the C library...

Include the following headers to Slideshow.hpp (I placed them after all the other headers:
1
2
#include <cstdlib>
#include <ctime> 


In Slideshow.cpp modify 3 functions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HRESULT GameInitialize(HINSTANCE hInstance)
{
   g_pGame = new GameEngine(hInstance, TEXT("Slideshow"), TEXT("Drawing Graphical Images"),
                           IDI_ICON, IDI_ICON_SM);

   if (g_pGame == NULL)
   {
      return E_FAIL;
   }

   g_pGame->SetFrameRate(1);

   // seed the random generator
   srand(time(0));

   return S_OK;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void GameStart(HWND hWindow)
{
   // create/load the slide bitmaps
   HINSTANCE hInstance = g_pGame->GetInstance();

   // create a blank bitmap
   g_pSlides[0] = new Bitmap(g_pGame->GetWindow(), 640, 480, RGB(128, 128, 64));

   // load bitmaps from a file
   g_pSlides[1] = new Bitmap(TEXT("Res/Image1.bmp"));
   g_pSlides[2] = new Bitmap(TEXT("Res/Image2.bmp"));
   g_pSlides[3] = new Bitmap(TEXT("Res/Image3.bmp"));

   // load bitmaps as resources
   g_pSlides[4] = new Bitmap(IDB_IMAGE4, hInstance);
   g_pSlides[5] = new Bitmap(IDB_IMAGE5, hInstance);
   g_pSlides[6] = new Bitmap(IDB_IMAGE6, hInstance);

   // set the index to a random slide
   g_iCurSlide = rand() % _iNUMSLIDES;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void GameCycle()
{
   static UINT iDelay     = 0;
   static UINT iNewSlide  = 0;

   // establish a 3-second delay before moving to the next slide
   if (++iDelay > 3)
   {
      // reset the delay counter
      iDelay = 0;

      // move to the next slide
      //if (++g_iCurSlide == _iNUMSLIDES)
      //{
      //   g_iCurSlide = 0;
      //}

      // get a random slide that isn't the current slide
      do
      {
         iNewSlide = rand() % _iNUMSLIDES;

      } while (iNewSlide == g_iCurSlide);

      g_iCurSlide = iNewSlide;

      // force a repaint to draw the next slide
      InvalidateRect(g_pGame->GetWindow(), NULL, FALSE);
   }
}

Slideshow should now begin with a random slide and cycle through randomly chosen slides every 3 seconds.
Topic archived. No new replies allowed.