Building a Win32 App, Part 4: Applying Resources to a Window
Purpose
The purpose of this tutorial is to demonstrate the basics of resource creation and utilization. By the end of this tutorial, readers should be able to load resources from a predefined file and apply it to a window.
Part 1: Intro to Visual Studio
Part 2: the WinMain Function
Part 3: Creating a Window from Scratch
Intended audience
This tutorial requires basic knowledge of Visual Studio, proficiency in C/C++, as this tutorial does not cover the C++ programming language.
Objectives
- Preparing the project.
- A gentle introduction to resources.
- Learn and use Visual Studio's resource editor to create resources.
- Apply resources in code.
Preparations
Source files
Note:
Assumptions are made that readers are comfortable with creating, compiling, building, launching projects, along with adding items such as files, new projects to solutions.
Open up Visual Studio and create a new Win32 Project. In Additional setting, check Empty. Once the Win32 application wizard has finished creating the solution, add the following code to the project.
// Resources.cpp
// Header required to help detect window version
#include <sdkddkver.h>
// Macro used to reduce namespace pollution
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
// Reference for various Win32 API functions and
// structure declarations.
#include <Windows.h>
// Header needed for unicode adjustment support
#include <tchar.h>
//
//
// WndProc - Window procedure
//
//
LRESULT
CALLBACK
WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
::PostQuitMessage(0);
break;
default:
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
//
//
// WinMain - Win32 application entry point.
//
//
int
APIENTRY
wWinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPWSTR lpCmdLine,
int nShowCmd)
{
// Setup window class attributes.
WNDCLASSEX wcex;
ZeroMemory(&wcex, sizeof(wcex));
wcex.cbSize = sizeof(wcex); // WNDCLASSEX size in bytes
wcex.style = CS_HREDRAW | CS_VREDRAW; // Window class styles
wcex.lpszClassName = TEXT("MYFIRSTWINDOWCLASS"); // Window class name
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // Window background brush color.
wcex.hCursor = LoadCursor(hInstance, IDC_ARROW); // Window cursor
wcex.lpfnWndProc = WndProc; // Window procedure associated to this window class.
wcex.hInstance = hInstance; // The application instance.
// Register window and ensure registration success.
if (!RegisterClassEx(&wcex))
return 1;
// Setup window initialization attributes.
CREATESTRUCT cs;
ZeroMemory(&cs, sizeof(cs));
cs.x = 0; // Window X position
cs.y = 0; // Window Y position
cs.cx = 640; // Window width
cs.cy = 480; // Window height
cs.hInstance = hInstance; // Window instance.
cs.lpszClass = wcex.lpszClassName; // Window class name
cs.lpszName = TEXT("My First Window"); // Window title
cs.style = WS_OVERLAPPEDWINDOW; // Window style
// Create the window.
HWND hWnd = ::CreateWindowEx(
cs.dwExStyle,
cs.lpszClass,
cs.lpszName,
cs.style,
cs.x,
cs.y,
cs.cx,
cs.cy,
cs.hwndParent,
cs.hMenu,
cs.hInstance,
cs.lpCreateParams);
// Validate window.
if (!hWnd)
return 1;
// Display the window.
::ShowWindow(hWnd, SW_SHOWDEFAULT);
::UpdateWindow(hWnd);
// Main message loop.
MSG msg;
while (::GetMessage(&msg, hWnd, 0, 0) > 0)
::DispatchMessage(&msg);
// Unregister window class, freeing the memory that was
// previously allocated for this window.
::UnregisterClass(wcex.lpszClassName, hInstance);
return (int)msg.wParam;
}
A brief overview about resources
What is a resource?
In Windows operating system, a resource constitutes a binary data that is embedded in programs, or dynamic-linked libraries (DLL). By default, system resources include:
Benefits
- Ability to be embedded in EXE;DLL;MUI files.
- Are Read Only, which prevent against unwanted modifications.
- Ideal for internationalization and localization, as well as for multilingual user interfaces.
Inconveniences
- Embedded resources increase file size.
Creating resource
Standard resource menu
There are various ways to create resources. In this tutorial, we will create them using Visual Studio's resource editor. It is possible to create resources in code, however these will be covered in future tutorials, as understanding the basics of resources is needed before attempting to do harder problems.
In the solution explorer, right-click on Resource Files.
Add a new resource.
After selecting Add Resource, in the solution explorer, you will notice two new files under both Header Files & Resource Files: resource.h, Resource.rc. This is perfectly normal, as Visual Studio is creating these files to store information about that kind of resource we use, the value attributed to these resources, etc.
You should see a similar window of the following:
This is where we will create the necessary resources to make use in this tutorial. There are three types of resources to be created for the tutorial, cursors, icons, strings.
Cursors
Add a new cursor by expanding the section Cursor and add a resource of type IDC_POINTER
. Then click New. This will represent the the cursor to be used by our defined window class.
Icons
In Windows, icons can be represented as small, medium, large. For this reason, we need to create multiple sizes of a same icon for optimal display. In this tutorial, we will use two icons, one 32x32 and another 16x16.
Start by selecting icon and click New.
Large icon
By default, your icon's resolution will be set to 32x32, 4 bits, BMP.
Change it to a 32 bit by selecting 32x32, 32 bits, BMP. Then save it by pressing Ctrl+S or right-click on the icon1.ico tab and save icon1.ico
Small icon
For this step, proceed the same way as seen in Large Icon, but set the size of the icon type to 16x16, 32 bits, BMP. Don't forget to save it.
String table
Finally, the string table, which will contain literal definitions for our window to make use of. To add a string table, add a new resource and select String Table.
You should see the following menu.
This is the string resource editor. You can edit string captions, values as well of names. Before doing so, add a new string by pressing INS or right-click and select New String.
NOTE: It is possible that Visual Studio does only increment the string value instead of adding a second record in the string table, in this case, right click on the string table resource in the solution explorer to bring up the Resource Symbols window. Make sure to check Read only symbols and click New. You should then be able to enter both new string name and value.
You should now have two strings in your string table. In order to enhance clarity, we will edit these values so that we will not accidently mix system-defined values with our own.
For the first string, IDS_STRINGX (where X is a digit), we will change it's ID to IDS_MYAPP_TITLE
. Give this string Value 101 and change it's Caption to Hello World!
As for the second string, change it's ID to IDS_MYAPP_CLASS
, set it's Value to 102, change the Caption to MYAPPCLASS.
Resource view & designer
In order to edit resources, you will be required to switch from Solution Explorer to Resource View. To make the switch, proceed to the footer of the left pane, precisely: the Solution Explorer. You should see several other tabs such as Class view, Property manager, Resource View, Team Explorer, etc.
Select Resource View.
The pane content will then be replaced with the following:
Click on the left arrow of each folder and subfolder. Expand all sections. You should now have a menu of the following:
Resources such as IDC_POINTER
, IDI_ICON1
, IDI_ICON2
are the ones we will need to edit. We do not need to edit the String Table since we have done this already in the previous step.
Cursor
Select IDC_POINTER, and a file named arrow.cur should be opened, revealing a cursor image.
Locate the resource Properties pane, usually located at the bottom-right of the IDE.
If you do not see this window, you can always enable it by going in the View section of the menu bar.
Select Other Windows.
Select Properties Window.
Alternately, you can use shortcut Alt+Enter to enable this window.
Back to the Properties panel.
It is possible to see a field named ID. We will replace this ID with our own. This will help in identifying resources, rather than using suffixes such as 1, 2, 3, etc.
To change the value simply replace IDC_POINTER
with, let's say, IDC_MYAPP_POINTER
. Now the prefix MYAPP
will be used to indicate that this resource belongs to our application. Of note that this simply is a naming convention.
Icons
Do the same as stated above, and replace both large & small icons with IDI_MYAPP_ICON
& IDI_MYAPP_ICON_SMALL
.
Large icon | Small icon |
---|---|
Before | Before |
After | After |
The file resource.h
In the Solution Explorer, you should be able to see a file named resource.h. This file contains the resource IDs that were assigned during creation. Open it, as we will examine it a bit more in details.
You should be able to see a bunch of definitions.
#define IDS_MYAPP_TITLE 101
#define IDS_MYAPP_CLASS 102
#define IDI_MYAPP_ICON 103
#define IDI_MYAPP_ICON_SMALL 104
#define IDC_MYAPP_POINTER 105
If the numbers you have do not match those seen above, do not worry you haven't done wrong. This probably happened because you either deleted a resource or accidently added the wrong resource type before deleting it again, causing the next resource value to increase. By default, resource IDs will increment by one to become the next higher resource values. as seen in this line:
#define _APS_NEXT_RESOURCE_VALUE 105
Note:
If you wish to edit resource.h outside of the IDE,
you can unload the project, which will put the specified project out
of context, and then reload it later.
To unload a project:
Go to the Solution Explorer, right-click on the project and select Unload Project
To reload a project:
Right-click again on the same project, from the Solution Explorer and select Reload Project.
That's it! All necessary resources are created and setup, so that we can actually begin to implement them.
The code
Headers
Since we require access to resource IDs, we must include resource.h. In Resources.cpp, add the following:
// Include required for resources
#include "resource.h"
Declarations
At first, we will declare two fixed buffers to store string values representing the window's class name and title. In WinMain
, add the following lines:
const size_t MAX_LOADSTRING = 100;
TCHAR className[MAX_LOADSTRING];
TCHAR title[MAX_LOADSTRING];
Since we will use our own cursor for the window, we will declare a cursor handle. The type HCURSOR
is used to specify a handle to a cursor.
HCURSOR cursor;
We have created two icon resources, one to use as a large version and an other for the small version. To declare an icon, we use HICON
.
HICON icon, iconSmall;
Loading resources
As declarations are done, we require to load resources before using them.
Loading strings from the table
Starting with string resources, we will load each of these and copy their content to className
and title
respectfully. To load a string, we use LoadString
. Here is it's declaration:
int WINAPI LoadString(
_In_opt_ HINSTANCE hInstance,
_In_ UINT uID,
_Out_ LPTSTR lpBuffer,
_In_ int nBufferMax
);
Parameter | Description |
---|---|
hInstance |
A handle to the module that contains the resource, our application in this case. |
uID |
The resource ID. These are generally referenced in resource.h. |
lpBuffer |
The destination where to copy at most nBufferMax characters. |
nBufferMax |
The maximum amount of characters to copy. (Usually the size in bytes of the destination) |
LoadString(hInstance, IDS_MYAPP_CLASS, className, sizeof(TCHAR) * MAX_LOADSTRING);
LoadString(hInstance, IDS_MYAPP_TITLE, title, sizeof(TCHAR) *MAX_LOADSTRING);
We utilize our application instance, hInstance
, which is WinMain
function's first parameter. as a first argument for LoadString. Then we pass IDS_MYAPP_CLASS
as the second argument, which must be a valid ID to identify the resource we want to load. The third argument, className
, represents the destination buffer where string resource characters will be copied. The final argument is simply the maximum amount of characters to be.
Loading cursors
Aside from strings, we created a cursor. To load a resource cursor, we use LoadCursor
. Here is the function declaration:
HCURSOR WINAPI LoadCursor(
_In_opt_ HINSTANCE hInstance,
_In_ LPCTSTR lpCursorName
);
Parameter | Description |
---|---|
hInstance |
A handle to the module that contains the resource, our application in this case. |
lpCursorName |
A string representing the resource name associated to the cursor . |
cursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYAPP_POINTER));
At first, we need to specify a module handle that contains the cursor resource. As usual, we pass hInstance
as the first argument. The second argument is bit more confusing. MAKEINTRESOURCE
is a macro that converts an integral data type to a string.
#define MAKEINTRESOURCEA(i) ((LPSTR)((ULONG_PTR)((WORD)(i))))
#define MAKEINTRESOURCEW(i) ((LPWSTR)((ULONG_PTR)((WORD)(i))))
MAKEINTRESOURCE
is a macro that has two versions. MAKEINTRESOURCEA
is for the multibyte strings, while MAKEINTRESOURCEW
is used for unicodes'. We substitute IDC_MYAPP_POINTER
for i
and pass this as the second argument of LoadCursor.
Loading icons
The last two remaining resources to load are icons. We must be aware that we have two icons, a large one and a small one, which represent the same bitmap. To load an icon, we use the function LoadIcon
. As usual, here is it's declaration:
HICON WINAPI LoadIcon(
_In_opt_ HINSTANCE hInstance,
_In_ LPCTSTR lpIconName
);
Parameter | Description |
---|---|
hInstance |
A handle to the module that contains the resource, our application in this case. |
lpIconName |
A string representing the resource name associated to the icon . |
icon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYAPP_ICON));
iconSmall = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYAPP_ICON_SMALL));
As usual, we use hInstance
as the first argument, which specifies the module that contains the resources to load. The second argument is exactly like the one from previous step. We substitute both resources IDI_MYAPP_ICON
and IDI_MYAPP_ICON_SMALL
in MAKEINTRESOURCE
and pass these as arguments.
Window class
Finally, we will replace programmatically implemented values from our previously defined WNDCLASSEX
structure and replace them with the new variables we declared earlier in this section.
wcex.lpszClassName = className; // Window class name
wcex.hCursor = cursor; // Window cursor
wcex.hIcon = icon; // Application icon.
wcex.hIconSm = iconSmall; // Application small icon.
That's it.
Project build
If we build the application and launch, we get a result, which is very close to the same as the one seen in previous tutorials.
The result should ressemble to the following:
As seen in this capture, the string caption Hello World! is used as the window's name. The Small icon resource we created now serves as the application's icon. It is also possible to see this icon from within output directory. Assuming you have been building in Debug mode, proceed to your build output directory, which should be named Debug.
In this folder you will be able to find your compiled and linked .EXE, the executable application you've built using Visual Studio.
To properly view the small version of the icon, you must ensure that your view configuration is set to Small icons.
You can also alter the view to display larger icons. To enable large icons, simply select the Large icons from the view options.
From this point, the Large version is the one used as the application icon. There are as well other versions of icons, such as Medium icons and Extra Large icons, but the point of utilizing only large and small icons was to illustrate the difference between these two concepts.
Conclusion
Readers should now have a better understanding of the representation and utilization of resources, concepts of embedded data and how to load various types of resources in order to achieve objectives.
Readers are encouraged to read this tutorial again to ensure correct understanding, as further tutorials will cover and expand concepts viewed throughout this lecture.
Complete example
Header files
resource.
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by Resources.rc
//
#define IDS_MYAPP_TITLE 101
#define IDS_MYAPP_CLASS 102
#define IDI_MYAPP_ICON 103
#define IDI_MYAPP_ICON_SMALL 104
#define IDC_MYAPP_POINTER 105
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE 105
#define _APS_NEXT_COMMAND_VALUE 40001
#define _APS_NEXT_CONTROL_VALUE 1001
#define _APS_NEXT_SYMED_VALUE 101
#endif
#endif
Resource files
Resources.rc
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "winres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_MYAPP_ICON ICON "icon1.ico"
IDI_MYAPP_ICON_SMALL ICON "icon2.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Cursor
//
IDC_MYAPP_POINTER CURSOR "arrow.cur"
/////////////////////////////////////////////////////////////////////////////
//
// String Table
//
STRINGTABLE
BEGIN
IDS_MYAPP_TITLE "Hello World!"
IDS_MYAPP_CLASS "MYAPPCLASS"
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////
// English (Canada) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENC)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_CAN
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
#endif // English (Canada) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
Source files
main.cpp
// CompleteExample.cpp
// Header required to help detect window version
#include <sdkddkver.h>
// Macro used to reduce namespace pollution
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
// Reference for various Win32 API functions and
// structure declarations.
#include <Windows.h>
// Header needed for unicode adjustment support
#include <tchar.h>
// Include required for resources
#include "resource.h"
//
//
// WndProc - Window procedure
//
//
LRESULT
CALLBACK
WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
::PostQuitMessage(0);
break;
default:
return ::DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
//
//
// WinMain - Win32 application entry point.
//
//
int
APIENTRY
_tWinMain(
_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nShowCmd)
{
const size_t MAX_LOADSTRING = 100;
TCHAR className[MAX_LOADSTRING];
TCHAR title[MAX_LOADSTRING];
HCURSOR cursor;
HICON icon, iconSmall;
::LoadString(hInstance, IDS_MYAPP_CLASS, className, sizeof(TCHAR) * MAX_LOADSTRING);
::LoadString(hInstance, IDS_MYAPP_TITLE, title, sizeof(TCHAR) * MAX_LOADSTRING);
cursor = LoadCursor(hInstance, MAKEINTRESOURCE(IDC_MYAPP_POINTER));
icon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYAPP_ICON));
iconSmall = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MYAPP_ICON_SMALL));
// Setup window class attributes.
WNDCLASSEX wcex;
ZeroMemory(&wcex, sizeof(wcex));
wcex.cbSize = sizeof(wcex); // WNDCLASSEX size in bytes
wcex.style = CS_HREDRAW | CS_VREDRAW; // Window class styles
wcex.lpszClassName = className; // Window class name
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // Window background brush color.
wcex.hCursor = cursor; // Window cursor
wcex.lpfnWndProc = WndProc; // Window procedure associated to this window class.
wcex.hInstance = hInstance; // The application instance.
wcex.hIcon = icon; // Application icon.
wcex.hIconSm = iconSmall; // Application small icon.
// Register window and ensure registration success.
if (!RegisterClassEx(&wcex))
return 1;
// Setup window initialization attributes.
CREATESTRUCT cs;
ZeroMemory(&cs, sizeof(cs));
cs.x = 0; // Window X position
cs.y = 0; // Window Y position
cs.cx = 640; // Window width
cs.cy = 480; // Window height
cs.hInstance = hInstance; // Window instance.
cs.lpszClass = wcex.lpszClassName; // Window class name
cs.lpszName = title; // Window title
cs.style = WS_OVERLAPPEDWINDOW; // Window style
// Create the window.
HWND hWnd = ::CreateWindowEx(
cs.dwExStyle,
cs.lpszClass,
cs.lpszName,
cs.style,
cs.x,
cs.y,
cs.cx,
cs.cy,
cs.hwndParent,
cs.hMenu,
cs.hInstance,
cs.lpCreateParams);
// Validate window.
if (!hWnd)
return 1;
// Display the window.
::ShowWindow(hWnd, SW_SHOWDEFAULT);
::UpdateWindow(hWnd);
// Main message loop.
MSG msg;
while (::GetMessage(&msg, hWnd, 0, 0) > 0)
::DispatchMessage(&msg);
// Unregister window class, freeing the memory that was
// previously allocated for this window.
::UnregisterClass(wcex.lpszClassName, hInstance);
return (int)msg.wParam;
}
Great tutorials!
I did find some inconsistencies though. When I tiried to add the ID’s for the string table as indicated in the tutorial, I received an error message about them containing illegal symbols. I removed the backslashes from the string ID’s , as I did not see them elsewhere and then it worked fine. Not sure if this is normal/ expected behavior, but I thought I’d let you know.
@joep Klijs
Thanks for pointing this out.
If I remember correctly, I added these backslashes after each underscore symbol due to possible rendering issues with the markdown language (markdown is used to add colors and help format code snippets). I have not been online very often for a few months and it is possible that markdown has changed a bit.
After removing the backslashes, there appears to be no rendering issues anymore, so I will leave it like this. I also redid the tutorial over and noticed a small issue that could potentially make users unable to continue correctly the tutorial. This has been fixed and everything should be good.
Again, thanks for notifying me about this, it is very appreciated.
As a programmer do you need to remember all that code or there’s libraries or dictionaries of C++ programming language
@Balma Pro
Greetings.
In regards to your question : If you are developing software using low level API, then you will mostly have to memorize a huge amount of functions, classes, etc.
Please be aware that if performance is an important factor, then low level API such as Win32 may be required. However, if an application layer you are working on does not require such access to such API, there are many good C++ libraries that can ease your life. Here is a link to a page listing the most widely known:
http://en.cppreference.com/w/cpp/links/libs
Very nice. Never found a better explanation of how to create windows using win32 than this one.
@Daniel Velásquez
You are welcome.