For “some things” about the graphical interface that you describe as “Drawing, event-based updating ...”, that is, basic concepts, consider the Windows API.
It is easy, it is concrete, and it allows your students to move forward to wrap it all in an OO way that is very educated.
An example that does “Drawing, event-based updating” is drawing a dynamic-sized ellipse (you need to define the three headers included at the top):
#include <winapi/wrapper/windows_h.h> #include <cppSupport/error_handling.h> // cppSupport::throwX, cppSupport::ExitCode #include <cppSupport/anti_warnings.h> // cppSupport::suppressUnusedWarningFor #include <iostream> #include <string> // std::wstring using cppSupport::throwX; using cppSupport::ExitCode; using cppSupport::suppressUnusedWarningFor; RECT clientRectOf( HWND window ) { RECT result; GetClientRect( window, &result ); return result; } void drawEllipse( HDC dc, RECT const& boundingRect ) { RECT const& r = boundingRect; Ellipse( dc, r.left, r.top, r.right, r.bottom ); } namespace mainWindow { namespace detail { void paint( HWND window, HDC dc ) { drawEllipse( dc, clientRectOf( window ) ); } void onWmDestroy( HWND window ) { suppressUnusedWarningFor( window ); PostQuitMessage( ExitCode::success() ); } void onWmPaint( HWND window ) { PAINTSTRUCT info; HDC const deviceContext = BeginPaint( window, &info ); paint( window, deviceContext ); EndPaint( window, &info ); } LRESULT CALLBACK messageHandler( HWND window, UINT messageId, WPARAM wParam, LPARAM lParam ) { switch( messageId ) { case WM_DESTROY: return HANDLE_WM_DESTROY( window, wParam, lParam, onWmDestroy ); case WM_PAINT: return HANDLE_WM_PAINT( window, wParam, lParam, onWmPaint ); default: return DefWindowProc( window, messageId, wParam, lParam ); } } ATOM registerClass() { WNDCLASS const info = { CS_HREDRAW | CS_VREDRAW, // UINT style; &messageHandler, // WNDPROC lpfnWndProc; 0, // int cbClsExtra; 0, // int cbWndExtra; GetModuleHandle( 0 ), // HINSTANCE hInstance; 0, // HICON hIcon; LoadCursor( 0, IDC_ARROW ), // HCURSOR hCursor; reinterpret_cast<HBRUSH>( COLOR_WINDOW + 1 ), // HBRUSH hbrBackground; 0, // LPCTSTR lpszMenuName; L"MainWindowClass" // LPCTSTR lpszClassName; }; ATOM const result = RegisterClass( &info ); (result != 0) || throwX( "registerWindowClass: RegisterClass failed" ); return result; } ATOM classAtom() { static ATOM const theClassAtom = registerClass(); return theClassAtom; } } // namespace mainWindow::detail HWND create( std::wstring const& title ) { HWND const window = CreateWindow( MAKEINTATOM( detail::classAtom() ), // LPCTSTR lpClassName, title.c_str(), // LPCTSTR lpWindowName, WS_OVERLAPPEDWINDOW, // DWORD dwStyle, CW_USEDEFAULT, // int x, CW_USEDEFAULT, // int y, CW_USEDEFAULT, // int nWidth, CW_USEDEFAULT, // int nHeight, 0, // HWND hWndParent, 0, // HMENU hMenu, GetModuleHandle( 0 ), // HINSTANCE hInstance, 0 // LPVOID lpParam ); (window != 0) || throwX( "createMainWindow: CreateWindow failed" ); return window; } } // namespace mainWindow bool getMessage( MSG& message, HWND window = 0 ) { int const result = GetMessage( &message, window, 0, 0 ); (result != -1) || throwX( "getMessage: GetMessage failed" ); return (result != 0); } ExitCode dispatchWindowMessages() { MSG message; while( getMessage( message ) ) { TranslateMessage( &message ); DispatchMessage( &message ); } assert( message.message == WM_QUIT ); return ExitCode( message.wParam ); } ExitCode cppMain() { HWND const window = mainWindow::create( L"My main window" ); ShowWindow( window, SW_SHOWDEFAULT ); return dispatchWindowMessages(); } int main() { try { return cppMain(); } catch( std::exception const& x ) { std::cerr << "!" << x.what() << std::endl; } return ExitCode::failure(); }
EDIT : Well, perhaps the best place is to place these three headings. This is not a good (complete) answer without them. So.
[WinAPI / wrapper / windows_h.h] :
// Copyright (c) 2010 Alf P. Steinbach #ifndef WINAPI_WRAPPER_WINDOWSH_H #define WINAPI_WRAPPER_WINDOWSH_H //#include <progrock/cppx/devsupport/better_experience.h>
[cppsupport / error_handling.h] :
#ifndef CPPSUPPORT_ERROR_HANDLING_H #define CPPSUPPORT_ERROR_HANDLING_H //-------------------------------- Dependencies: #include <assert.h> // assert #include <stdexcept> // std::runtime_error, std::exception #include <stdlib.h> // EXIT_SUCCESS, EXIT_FAILURE #include <string> // std::string, std::wstring //-------------------------------- Interface: namespace cppSupport { inline bool throwX( std::string const& s ) { throw std::runtime_error( s ); } struct ExitCode { int value; explicit ExitCode( int v ): value( v ) {} operator int() const { return value; } static ExitCode success() { return ExitCode( EXIT_SUCCESS ); } static ExitCode failure() { return ExitCode( EXIT_FAILURE ); } }; } // namespace cppSupport #endif
[cppsupport / anti_warnings.h] :
#ifndef CPPSUPPORT_ANTI_WARNINGS_H #define CPPSUPPORT_ANTI_WARNINGS_H
Greetings and hth.