MSDN example for scrolling large image malfunctions - c++

INTRODUCTION AND RELEVANT INFORMATION:
I am trying to paint the image in my main window in its natural size. Currently I need to render EMF.
After browsing through Internet, I found this MSDN example.
PROBLEM:
I have given it a try, and found out it doesn't work well.
After user right clicks, as instructions say, desktop screenshot is properly painted in the main window.
However, when user resizes the window, paint artifacts occur like the ones on the image below. The below effect also occurs when user scrolls a little and then resizes the window.
Here is the image:
This is my first time trying to scroll an image, and using scrollbars, so I really have a hard time figuring out a solution.
QUESTIONS:
How to fix article code to remove this visual artifact?
Can you recommend another example, like tutorial/code example/etc that
I can study?
Can you provide "verbal" instructions/guidelines that could help me?
To make your task even easier, here is the smallest code possible that reproduces the problem. Take note that I have simply copy/pasted the window procedure and added minimal code for program to create working demonstration:
#include <windows.h>
LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
SCROLLINFO si;
// These variables are required by BitBlt.
static HDC hdcWin; // window DC
static HDC hdcScreen; // DC for entire screen
static HDC hdcScreenCompat; // memory DC for screen
static HBITMAP hbmpCompat; // bitmap handle to old DC
static BITMAP bmp; // bitmap data structure
static BOOL fBlt; // TRUE if BitBlt occurred
static BOOL fScroll; // TRUE if scrolling occurred
static BOOL fSize; // TRUE if fBlt & WM_SIZE
// These variables are required for horizontal scrolling.
static int xMinScroll; // minimum horizontal scroll value
static int xCurrentScroll; // current horizontal scroll value
static int xMaxScroll; // maximum horizontal scroll value
// These variables are required for vertical scrolling.
static int yMinScroll; // minimum vertical scroll value
static int yCurrentScroll; // current vertical scroll value
static int yMaxScroll; // maximum vertical scroll value
switch (uMsg)
{
case WM_CREATE:
// Create a normal DC and a memory DC for the entire
// screen. The normal DC provides a snapshot of the
// screen contents. The memory DC keeps a copy of this
// snapshot in the associated bitmap.
hdcScreen = CreateDC(L"DISPLAY", (PCTSTR)NULL,
(PCTSTR)NULL, (CONST DEVMODE *) NULL);
hdcScreenCompat = CreateCompatibleDC(hdcScreen);
// Retrieve the metrics for the bitmap associated with the
// regular device context.
bmp.bmBitsPixel =
(BYTE)GetDeviceCaps(hdcScreen, BITSPIXEL);
bmp.bmPlanes = (BYTE)GetDeviceCaps(hdcScreen, PLANES);
bmp.bmWidth = GetDeviceCaps(hdcScreen, HORZRES);
bmp.bmHeight = GetDeviceCaps(hdcScreen, VERTRES);
// The width must be byte-aligned.
bmp.bmWidthBytes = ((bmp.bmWidth + 15) &~15) / 8;
// Create a bitmap for the compatible DC.
hbmpCompat = CreateBitmap(bmp.bmWidth, bmp.bmHeight,
bmp.bmPlanes, bmp.bmBitsPixel, (CONST VOID *) NULL);
// Select the bitmap for the compatible DC.
SelectObject(hdcScreenCompat, hbmpCompat);
// Initialize the flags.
fBlt = FALSE;
fScroll = FALSE;
fSize = FALSE;
// Initialize the horizontal scrolling variables.
xMinScroll = 0;
xCurrentScroll = 0;
xMaxScroll = 0;
// Initialize the vertical scrolling variables.
yMinScroll = 0;
yCurrentScroll = 0;
yMaxScroll = 0;
break;
case WM_SIZE:
{
int xNewSize;
int yNewSize;
xNewSize = LOWORD(lParam);
yNewSize = HIWORD(lParam);
if (fBlt)
fSize = TRUE;
// The horizontal scrolling range is defined by
// (bitmap_width) - (client_width). The current horizontal
// scroll value remains within the horizontal scrolling range.
xMaxScroll = max(bmp.bmWidth - xNewSize, 0);
xCurrentScroll = min(xCurrentScroll, xMaxScroll);
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
si.nMin = xMinScroll;
si.nMax = bmp.bmWidth;
si.nPage = xNewSize;
si.nPos = xCurrentScroll;
SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
// The vertical scrolling range is defined by
// (bitmap_height) - (client_height). The current vertical
// scroll value remains within the vertical scrolling range.
yMaxScroll = max(bmp.bmHeight - yNewSize, 0);
yCurrentScroll = min(yCurrentScroll, yMaxScroll);
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE | SIF_POS;
si.nMin = yMinScroll;
si.nMax = bmp.bmHeight;
si.nPage = yNewSize;
si.nPos = yCurrentScroll;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
break;
}
case WM_PAINT:
{
PRECT prect;
hdc = BeginPaint(hwnd, &ps);
// If the window has been resized and the user has
// captured the screen, use the following call to
// BitBlt to paint the window's client area.
if (fSize)
{
BitBlt(ps.hdc,
0, 0,
bmp.bmWidth, bmp.bmHeight,
hdcScreenCompat,
xCurrentScroll, yCurrentScroll,
SRCCOPY);
fSize = FALSE;
}
// If scrolling has occurred, use the following call to
// BitBlt to paint the invalid rectangle.
//
// The coordinates of this rectangle are specified in the
// RECT structure to which prect points.
//
// Note that it is necessary to increment the seventh
// argument (prect->left) by xCurrentScroll and the
// eighth argument (prect->top) by yCurrentScroll in
// order to map the correct pixels from the source bitmap.
if (fScroll)
{
prect = &ps.rcPaint;
BitBlt(ps.hdc,
prect->left, prect->top,
(prect->right - prect->left),
(prect->bottom - prect->top),
hdcScreenCompat,
prect->left + xCurrentScroll,
prect->top + yCurrentScroll,
SRCCOPY);
fScroll = FALSE;
}
EndPaint(hwnd, &ps);
break;
}
case WM_HSCROLL:
{
int xDelta; // xDelta = new_pos - current_pos
int xNewPos; // new position
int yDelta = 0;
switch (LOWORD(wParam))
{
// User clicked the scroll bar shaft left of the scroll box.
case SB_PAGEUP:
xNewPos = xCurrentScroll - 50;
break;
// User clicked the scroll bar shaft right of the scroll box.
case SB_PAGEDOWN:
xNewPos = xCurrentScroll + 50;
break;
// User clicked the left arrow.
case SB_LINEUP:
xNewPos = xCurrentScroll - 5;
break;
// User clicked the right arrow.
case SB_LINEDOWN:
xNewPos = xCurrentScroll + 5;
break;
// User dragged the scroll box.
case SB_THUMBPOSITION:
xNewPos = HIWORD(wParam);
break;
default:
xNewPos = xCurrentScroll;
}
// New position must be between 0 and the screen width.
xNewPos = max(0, xNewPos);
xNewPos = min(xMaxScroll, xNewPos);
// If the current position does not change, do not scroll.
if (xNewPos == xCurrentScroll)
break;
// Set the scroll flag to TRUE.
fScroll = TRUE;
// Determine the amount scrolled (in pixels).
xDelta = xNewPos - xCurrentScroll;
// Reset the current scroll position.
xCurrentScroll = xNewPos;
// Scroll the window. (The system repaints most of the
// client area when ScrollWindowEx is called; however, it is
// necessary to call UpdateWindow in order to repaint the
// rectangle of pixels that were invalidated.)
ScrollWindowEx(hwnd, -xDelta, -yDelta, (CONST RECT *) NULL,
(CONST RECT *) NULL, (HRGN)NULL, (PRECT)NULL,
SW_INVALIDATE);
UpdateWindow(hwnd);
// Reset the scroll bar.
si.cbSize = sizeof(si);
si.fMask = SIF_POS;
si.nPos = xCurrentScroll;
SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
break;
}
case WM_VSCROLL:
{
int xDelta = 0;
int yDelta; // yDelta = new_pos - current_pos
int yNewPos; // new position
switch (LOWORD(wParam))
{
// User clicked the scroll bar shaft above the scroll box.
case SB_PAGEUP:
yNewPos = yCurrentScroll - 50;
break;
// User clicked the scroll bar shaft below the scroll box.
case SB_PAGEDOWN:
yNewPos = yCurrentScroll + 50;
break;
// User clicked the top arrow.
case SB_LINEUP:
yNewPos = yCurrentScroll - 5;
break;
// User clicked the bottom arrow.
case SB_LINEDOWN:
yNewPos = yCurrentScroll + 5;
break;
// User dragged the scroll box.
case SB_THUMBPOSITION:
yNewPos = HIWORD(wParam);
break;
default:
yNewPos = yCurrentScroll;
}
// New position must be between 0 and the screen height.
yNewPos = max(0, yNewPos);
yNewPos = min(yMaxScroll, yNewPos);
// If the current position does not change, do not scroll.
if (yNewPos == yCurrentScroll)
break;
// Set the scroll flag to TRUE.
fScroll = TRUE;
// Determine the amount scrolled (in pixels).
yDelta = yNewPos - yCurrentScroll;
// Reset the current scroll position.
yCurrentScroll = yNewPos;
// Scroll the window. (The system repaints most of the
// client area when ScrollWindowEx is called; however, it is
// necessary to call UpdateWindow in order to repaint the
// rectangle of pixels that were invalidated.)
ScrollWindowEx(hwnd, -xDelta, -yDelta, (CONST RECT *) NULL,
(CONST RECT *) NULL, (HRGN)NULL, (PRECT)NULL,
SW_INVALIDATE);
UpdateWindow(hwnd);
// Reset the scroll bar.
si.cbSize = sizeof(si);
si.fMask = SIF_POS;
si.nPos = yCurrentScroll;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
break;
}
case WM_RBUTTONDOWN:
{
// Get the compatible DC of the client area.
hdcWin = GetDC(hwnd);
// Fill the client area to remove any existing contents.
RECT rect;
GetClientRect(hwnd, &rect);
FillRect(hdcWin, &rect, (HBRUSH)(COLOR_WINDOW + 1));
// Copy the contents of the current screen
// into the compatible DC.
BitBlt(hdcScreenCompat, 0, 0, bmp.bmWidth,
bmp.bmHeight, hdcScreen, 0, 0, SRCCOPY);
// Copy the compatible DC to the client area.
BitBlt(hdcWin, 0, 0, bmp.bmWidth, bmp.bmHeight,
hdcScreenCompat, 0, 0, SRCCOPY);
ReleaseDC(hwnd, hdcWin);
fBlt = TRUE;
break;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
// register main window class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = GetSysColorBrush(COLOR_WINDOW);
wc.lpszMenuName = NULL;
wc.lpszClassName = L"Main_Window";
wc.hIconSm = LoadIcon(hInstance, IDI_APPLICATION);
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
// create main window
hwnd = CreateWindowEx(0, L"Main_Window", L"Scrollable map",
WS_OVERLAPPEDWINDOW | WS_VSCROLL | WS_HSCROLL,
50, 50, 400, 400, NULL, NULL, hInstance, 0);
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}

From your description the problem seems to be simply that your window isn't being repainted when it's resized.
Either use InvalidateRect to force a repaint of the newly uncovered areas when your window is enlarged, or set the CS_VREDRAW and CS_HREDRAW styles on your window class to have the client area repainted automatically.

Related

how to scroll a painted region?

I want to scroll a painted region. I use the CS_OWNDC style with WS_HSCROLL | WS_VSCROLL.My WM_PAINT block draws a line outgoing from the visible region (I suspect that I need to increase the buffer somehow).How can I make this?Please give me an example
PS. Block of code WM_HSCROLL incorrect working i don't know how to fix this.
My code:
// C++ WINAPI LEARN
#include <Windows.h>
WNDCLASS windowClass;
HDC hdc;
PAINTSTRUCT ps;
int hScroll, vScroll;
bool first = true;
LRESULT CALLBACK WndProc(HWND hWindow, UINT typeMessage, WPARAM wParam, LPARAM lParam)
{
switch (typeMessage)
{
case WM_PAINT:
hdc = BeginPaint(hWindow, &ps);
//HERE ANYTHING DRAW CODE (words, line any figure)
//(draw line out visible region)
if (first == true)
{
first = false;
MoveToEx(hdc, 5, 5, NULL);
LineTo(hdc, 900, 500);
}
EndPaint(hWindow, &ps);
break;
case WM_HSCROLL:
//SCOLLBAR NOT JOB!
if (LOWORD(wParam) == SB_LINERIGHT) // this block not job!!!
{
hScroll++;
}
else if (LOWORD(wParam) == SB_LINELEFT) // this block not job!!!
{
hScroll--;
}
else if (LOWORD(wParam) == SB_THUMBTRACK)
{
hScroll=HIWORD(wParam);
}
SetScrollPos(hWindow, SB_HORZ, hScroll, FALSE);
ScrollWindow(hWindow, hScroll, vScroll, NULL, NULL);
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}
return DefWindowProc(hWindow, typeMessage, wParam, lParam);
}
int WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstanse, LPSTR LpszCmdParam, int nCmdShow)
{
//CREATE WINDOW
windowClass = { 0 };
windowClass.lpszClassName = "ScrollTest";
windowClass.hInstance = hInstance;
windowClass.lpfnWndProc = WndProc;
windowClass.style = CS_OWNDC;
//REGISTER CLASS
RegisterClass(&windowClass);
HWND myWindow = ::CreateWindowEx(
0,
"ScrollTest",
"Scroll Paint",
WS_VISIBLE | WS_OVERLAPPEDWINDOW | WS_HSCROLL | WS_VSCROLL,
100, 100,
500, 500,
NULL,
0,
hInstance,
0
);
//TRANSLATE MESSAGE
for (MSG windowMessage; GetMessage(&windowMessage, NULL, 0, 0);)
{
DispatchMessage(&windowMessage); //TRANSLATE MESSAGES
TranslateMessage(&windowMessage); //TRANSLATE KEYS
}
}
Based on the demo, I modified part of the code and it works normally.
Some code:
LRESULT CALLBACK MyTextWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
TEXTMETRIC tm;
SCROLLINFO si;
// These variables are required to display text.
static int xChar; // horizontal scrolling unit
static int yChar; // vertical scrolling unit
static int xPos; // current horizontal scrolling position
static int yPos; // current vertical scrolling position
int x, y; // horizontal and vertical coordinates
// Create an array of lines to display.
switch (uMsg)
{
case WM_CREATE:
// Get the handle to the client area's device context.
hdc = GetDC(hwnd);
// Extract font dimensions from the text metrics.
GetTextMetrics(hdc, &tm);
xChar = tm.tmAveCharWidth;
yChar = tm.tmHeight + tm.tmExternalLeading;
// Free the device context.
ReleaseDC(hwnd, hdc);
return 0;
case WM_HSCROLL:
// Get all the vertial scroll bar information.
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
// Save the position for comparison later on.
GetScrollInfo(hwnd, SB_HORZ, &si);
xPos = si.nPos;
switch (LOWORD(wParam))
{
// User clicked the left arrow.
case SB_LINELEFT:
si.nPos -= 1;
break;
// User clicked the right arrow.
case SB_LINERIGHT:
si.nPos += 1;
break;
// User clicked the scroll bar shaft left of the scroll box.
case SB_PAGELEFT:
si.nPos -= si.nPage;
break;
// User clicked the scroll bar shaft right of the scroll box.
case SB_PAGERIGHT:
si.nPos += si.nPage;
break;
// User dragged the scroll box.
case SB_THUMBTRACK:
si.nPos = si.nTrackPos;
break;
default:
break;
}
// Set the position and then retrieve it. Due to adjustments
// by Windows it may not be the same as the value set.
si.fMask = SIF_POS;
SetScrollInfo(hwnd, SB_HORZ, &si, TRUE);
GetScrollInfo(hwnd, SB_HORZ, &si);
// If the position has changed, scroll the window.
if (si.nPos != xPos)
{
ScrollWindow(hwnd, xChar * (xPos - si.nPos), 0, NULL, NULL);
}
return 0;
case WM_VSCROLL:
// Get all the vertial scroll bar information.
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_VERT, &si);
// Save the position for comparison later on.
yPos = si.nPos;
switch (LOWORD(wParam))
{
// User clicked the HOME keyboard key.
case SB_TOP:
si.nPos = si.nMin;
break;
// User clicked the END keyboard key.
case SB_BOTTOM:
si.nPos = si.nMax;
break;
// User clicked the top arrow.
case SB_LINEUP:
si.nPos -= 1;
break;
// User clicked the bottom arrow.
case SB_LINEDOWN:
si.nPos += 1;
break;
// User clicked the scroll bar shaft above the scroll box.
case SB_PAGEUP:
si.nPos -= si.nPage;
break;
// User clicked the scroll bar shaft below the scroll box.
case SB_PAGEDOWN:
si.nPos += si.nPage;
break;
// User dragged the scroll box.
case SB_THUMBTRACK:
si.nPos = si.nTrackPos;
break;
default:
break;
}
// Set the position and then retrieve it. Due to adjustments
// by Windows it may not be the same as the value set.
si.fMask = SIF_POS;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
GetScrollInfo(hwnd, SB_VERT, &si);
// If the position has changed, scroll window and update it.
if (si.nPos != yPos)
{
ScrollWindow(hwnd, 0, yChar * (yPos - si.nPos), NULL, NULL);
UpdateWindow(hwnd);
}
return 0;
case WM_PAINT:
// Prepare the window for painting.
hdc = BeginPaint(hwnd, &ps);
// Get vertical scroll bar position.
si.cbSize = sizeof(si);
si.fMask = SIF_POS;
GetScrollInfo(hwnd, SB_VERT, &si);
yPos = si.nPos;
// Get horizontal scroll bar position.
GetScrollInfo(hwnd, SB_HORZ, &si);
xPos = si.nPos;
x = xChar * (1 - xPos);
y = yChar * (1 - yPos);
MoveToEx(hdc, 5 + x, 5 + y, NULL);
LineTo(hdc, 900 + x, 500 + y);
// Indicate that painting is finished.
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Debug:

Splash Screen Make Wait box move

I have the following code that is supposed to paint a small box located on the bottom-left of a window, move 50 pixels to the right every 200 milliseconds, then reappear on the left once it reaches the right side.
Why doesn't my little rectangle move? It is painted in the same location all the time.
case WM_PAINT:
if (hBitmap != NULL)
{
// Paint the bitmap.
PAINTSTRUCT ps;
HDC hdc;
HDC hdcMem;
HGDIOBJ oldBitmap;
//
hdc = BeginPaint(hwnd, &ps);
// Create a dc in memory to paint on.
hdcMem = CreateCompatibleDC(hdc);
// Select the bitmap.
oldBitmap = SelectObject(hdcMem, hBitmap);
// Copy bitmap to splash screen window.
BitBlt(hdc, 0, 0, bmWidth, bmHeight, hdcMem, 0, 0, SRCCOPY);
// Fill rectangle.
HBRUSH hbr = CreateSolidBrush(RGB(42, 59, 87));
SelectObject(hdc, hbr);
FillRect(hdc, &rc, hbr);
// Cleanup.
SelectObject(hdcMem, oldBitmap);
DeleteObject(hbr);
DeleteObject(oldBitmap);
DeleteDC(hdcMem);
EndPaint(hwnd, &ps);
}
break;
case WM_TIMER:
timeCount++;
addLeft += 50;
if (addLeft == 300)
{
addLeft = 0;
}
// Move rectangle.
rc.left += addLeft;
rc.right += addLeft;
// Refresh the window.
UpdateWindow(hwnd);
// Timer and RECT from the top of the code page, and WinMain:
UINT_PTR ptrTimer;
const int TIMER_INTERVAL = 200;
const int MAX_TIME_COUNT = 100;
int timeCount;
// the timer works, but here's the code anyway.
ptrTimer = SetTimer(hwnd, 1, TIMER_INTERVAL, (TIMERPROC)NULL);
RECT rc;
rc.left = 141;
rc.top = 232;
rc.right = rc.left + 15;
rc.bottom = rc.top + 15;
Thanks for any replies,
Matt
Edit: Thanks hf.enigma, for the reply. This is what I ended up doing before I read your post. This works if anyone else wants to do this, but there are a couple more handles and GDI objects to clear. I am new to C++, so if anyone sees a memory leak here, please let me know. Thanks.
case WM_PAINT:
if (hBitmap != NULL)
{
// Paint the bitmap.
PAINTSTRUCT ps;
HDC hdc;
HDC hdcMem;
HGDIOBJ oldBitmap;
//
hdc = BeginPaint(hwnd, &ps);
hdcMem = CreateCompatibleDC(hdc); // a device context (dc) in memory to paint on.
oldBitmap = SelectObject(hdcMem, hBitmap);
// Copy bitmap to splash screen window.
BitBlt(hdc, 0, 0, bmWidth, bmHeight, hdcMem, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
// Cleanup.
SelectObject(hdcMem, oldBitmap);
DeleteObject(oldBitmap);
DeleteDC(hdcMem);
HBRUSH hbr ;
// Fill rectangle.
RECT f;
GetClientRect(hwndBox, &f);
hdc = BeginPaint(hwndBox, &ps);
hdcMem = CreateCompatibleDC(hdc);
hbr = CreateSolidBrush(RGB(42, 59, 87));
SelectObject(hdc, hbr);
FillRect(hdc, &f, hbr);
EndPaint(hwnd, &ps);
// Cleanup.
SelectObject(hdcMem, oldBitmap);
DeleteObject(oldBitmap);
DeleteDC(hdcMem);
DeleteObject(hbr);
}
case WM_TIMER:
timeCount++;
if (addLeft == 60)
{
addLeft = 10000;
ival = 2;
}
// 'Hide' the box for 2/3 of timer interval when
// it reaches the right side.
if (addLeft == 10000)
{
addLeft = 0;
ival = 1;
}
//
switch (ival)
{
case 2:
addLeft += 12;
break;
case 3:
ival = 0;
break;
}
ival++;
if (ival == 2)
{
// Move rectangle.
MoveWindow(hwndBox, rcleft + addLeft, rctop, 12, 12, true);
}
if (timeCount == MAX_TIME_COUNT)
{
DestroyWindow(hwnd);
}
break;
You have to copy the bitmap after you have finished the painting, and you should paint with hdcMem, like this:
...
// Fill rectangle.
HBRUSH hbr = CreateSolidBrush(RGB(42, 59, 87));
SelectObject(hdcMem, hbr);
FillRect(hdcMem, &rc, hbr);
// Copy bitmap to splash screen window.
BitBlt(hdc, 0, 0, bmWidth, bmHeight, hdcMem, 0, 0, SRCCOPY);
// Cleanup.
....
This can be painted like hf.enigma has stated, but FillRect() must be called twice for each paint event. Without 'erasing' the previous block, the window ended up having a long rectangle along the bottom, similar to a progress bar.
I ended up making a sub-window the size of the block I wanted, and calling the MoveWindow() function. For me, this is the simplest and most maintainable way to accomplish this without having to use MFC or CLI.
If this can help anyone else, Here is all of the code that I am using for this.
//This displays a splash screen, and starts
//another application that takes a long time
//to load.
#pragma once
#include "resource.h"
#include "PathStatus.h"
#include "StartApp.h"
#include "CenterWindow.h"
#include <string>
#include <stdlib.h>
#include <iostream>
using namespace std;
const LPWSTR CLASS_NAME(L"TMS_Logs");
const LPWSTR PATH_SUFFIX (L"\\bin\\tl.exe");
const int LEN_APP_NAME = 12; // length of "TMS_Logs.exe".
bool b;
int ival;
char *appPath;
HWND hwnd; // Main window.
HBITMAP hBitmap = NULL; // Bitmap painted in window.
HWND hwndBox; // Moving box shows app. is loading.
HWND hwndButton; // Close box.
int bmWidth;
int bmHeight;
UINT_PTR ptrTimer;
const int TIMER_INTERVAL = 50;
const int MAX_TIME_COUNT = 650;
int timeCount;
int addLeft;
int rcleft, rctop, rcright, rcbottom;
RECT rcMover;
RECT rcMover2;
bool exitApp = false;
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
rcleft = 157;
rctop = 220;
rcright = rcleft + 15;
rcbottom = rctop + 15;
//
rcMover.left = rcleft;
rcMover.bottom = rcbottom;
rcMover.top = rctop;
rcMover.right = rcright;
//
rcMover2.bottom = rcbottom;
rcMover2.top = rctop;
rcMover.left + rcleft + 50;
rcMover.right = rcleft + 50 + 15;
// Get the commandline.
const wchar_t *args = GetCommandLineW();
wstring s(args);
// With no external arguments, GetCommandLineW() returns the
// full path with quotes around it, plus one space after the
// end qoute, e.g., '"c:\fullpath.exe" '.
// Remove the starting and ending quote chars, the space at
// the end, and and the exe path.
//
int len = s.length();
s = s.substr(1, len - 3 - LEN_APP_NAME) + PATH_SUFFIX;
// Convert the wide string to a regular char array.
// Convert the wstring to c-string.
const wchar_t *path = s.c_str();
// Convert the WCHAR to char* and store it in the
// 'appPath' variable.
const size_t BUFFER_SIZE = 200;
size_t i;
appPath = (char*)malloc(BUFFER_SIZE);
wcstombs_s(&i, appPath, BUFFER_SIZE, path, BUFFER_SIZE);
// Create window class objects.
WNDCLASSEX wc;
MSG msg;
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc; // Sets callback function.
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(NULL, MAKEINTRESOURCE(IDICN_WSDOT));
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszMenuName = NULL;
wc.lpszClassName = CLASS_NAME;
wc.hIconSm = wc.hIcon;
if (!RegisterClassEx(&wc))
{
MessageBox(NULL, L"Window Registration Failed!", L"Error!",
MB_ICONEXCLAMATION | MB_OK);
return -1;
}
// Load the resource bitmap.
hBitmap = LoadBitmap(hInstance, MAKEINTRESOURCE(IDBMP_TL7));
// Make a bitmap to hold the returned width and height.
// This bitmap will be deleted when DeleteObject is called
// for the hBitmap handle.
BITMAP bm;
if (hBitmap != NULL && GetObject(hBitmap, sizeof(bm), &bm))
{
bmWidth = bm.bmWidth;
bmHeight = bm.bmHeight;
}
else
{
MessageBox(hwnd, L"Error loading splash screen bitmap.",
L"Error", MB_ICONEXCLAMATION | MB_OK);
return -1;
}
// Center the window on the monitor
// where the mouse is.
RECT rc;
CenterRectToMonitor(&rc, bmWidth, bmHeight);
//
hwnd = CreateWindowEx(WS_EX_PALETTEWINDOW, wc.lpszClassName, // Uses the properties set in Window Class wc.
L"TMS Logs", WS_POPUP, rc.left, rc.top, bmWidth, bmHeight, NULL, NULL, hInstance, NULL);
if (hwnd == NULL)
{
MessageBox(NULL, L"Error creating Window.", L"Error",
MB_ICONEXCLAMATION | MB_OK);
return -1;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
//bool started = true;
bool started = false;
bool ret;
int x, w, y, h;
x = 369;
y = 10;
w = 19;
h = 19;
// Make close box in upper-right corner.
hwndButton = CreateWindow(
L"BUTTON", // Predefined class.
L"X", // Button text
WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles
369, // x position
10, // y position
19, // Button width
19, // Button height
hwnd, // Parent window
NULL, // No menu.
(HINSTANCE)GetWindowLong(hwnd, GWL_HINSTANCE),
NULL); // Pointer not needed.
// Make a box that moves along the bottom of
// the window while the other app. loads.
x = rcleft;
y = rctop;
w = 10;
h = 10;
hwndBox = CreateWindow(L"static", 0, WS_CHILD | WS_VISIBLE, x, y, w, h, hwnd, NULL,
(HINSTANCE) GetWindowLong (hwnd, GWL_HINSTANCE), NULL);
// Create a timer to close this window after
// several seconds, in case the main app. does
// not send the close window message to this app.
// the WM_TIMER message is handled in the WndProc
// callback function.
ptrTimer = SetTimer(hwnd, 1, TIMER_INTERVAL, (TIMERPROC)NULL);
// Start message Loop.
while ((ret = GetMessage(&msg, NULL, 0, 0)) != 0)
{
if (ret == -1)
{
MessageBox(NULL,
L"Error in Splash screen window message loop, at:\n\n'GetMessage(&msg, NULL, 0, 0))'.\n\nMessage return value was -1.",
L"TMS Logs", MB_ICONEXCLAMATION | MB_OK);
// Exit this app.
return -1;
}
// TranslateMessage(&Msg); // Not needed with what I have here.
DispatchMessage(&msg);
// Check app. path and start the other app.
if (!started)
{
started = true;
if (Path_Accessible(appPath))
{
// Start the main app.
if (!StartApplication(appPath))
{
// The folder or file is missing, or
// the user does not have security
// permissions to the sub-folder.
MessageBox(hwnd, L"Unexpected error at: StartApplication().",
L"Error", MB_ICONEXCLAMATION | MB_OK);
if (appPath)
{
free(appPath);
}
return -1;
}
}
else
{
// Display an error message.
char msg[350];
char* prefix = "Error: Can't find 'tl.exe'.\n\n It's missing from: '";
//char* suffix = " '\ncannot be accessed, or does not exist.";
int count = sizeof(msg);
strncpy(msg, prefix, count);
strncat(msg, appPath, count);
//strncat(msg, suffix, count);
// Convert c-string to wide char. string.
count = strlen(msg) + 1;
wchar_t* wMsg = new wchar_t[count];
size_t returnCount;
int ret = mbstowcs_s(&returnCount, wMsg, count, msg, _TRUNCATE);
if (ret == 0 && returnCount == count)
{
MessageBox(hwnd, wMsg, L"Error", MB_ICONEXCLAMATION | MB_OK);
}
else
{
// Error
MessageBox(hwnd, L"Error: The application path cannot be accessd, or does not exist.", L"Error", MB_ICONEXCLAMATION | MB_OK);
}
delete[] wMsg;
if (appPath)
{
free(appPath);
}
return -1;
}
}
}
if (appPath)
{
free(appPath);
}
return msg.wParam;
}
// Process messages.
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_COMMAND:
// The close button is the only child window,
// so no need to check wparam.
DestroyWindow(hwnd);
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
DestroyWindow(hwndButton);
DestroyWindow(hwndBox);
if (hBitmap != NULL)
{
if (!DeleteObject(hBitmap))
{
MessageBox(hwnd, L"Error at: WndProc(). Failed to delete the application bitmap.",
L"Error", MB_OK);
}
}
if (ptrTimer)
{
if (!KillTimer(hwnd, ptrTimer))
{
MessageBox(hwnd, L"Error at :WndProc(). Failed to free the application timer.",
L"Error", MB_OK);
}
}
PostQuitMessage(0);
break;
case WM_PAINT:
if (hBitmap != NULL)
{
// Paint the bitmap.
PAINTSTRUCT ps;
HDC hdc;
HDC hdcMem;
HGDIOBJ oldBitmap;
//
hdc = BeginPaint(hwnd, &ps);
hdcMem = CreateCompatibleDC(hdc); // a device context (dc) in memory to paint on.
oldBitmap = SelectObject(hdcMem, hBitmap);
// Copy bitmap to splash screen window.
BitBlt(hdc, 0, 0, bmWidth, bmHeight, hdcMem, 0, 0, SRCCOPY);
EndPaint(hwnd, &ps);
// Cleanup.
SelectObject(hdcMem, oldBitmap);
DeleteObject(oldBitmap);
DeleteDC(hdcMem);
HBRUSH hbr ;
// Fill rectangle.
RECT f;
GetClientRect(hwndBox, &f);
hdc = BeginPaint(hwndBox, &ps);
hdcMem = CreateCompatibleDC(hdc);
hbr = CreateSolidBrush(RGB(42, 59, 87));
SelectObject(hdc, hbr);
FillRect(hdc, &f, hbr);
EndPaint(hwnd, &ps);
// Cleanup.
SelectObject(hdcMem, oldBitmap);
DeleteObject(oldBitmap);
DeleteDC(hdcMem);
DeleteObject(hbr);
}
break;
case WM_TIMER:
timeCount++;
if (addLeft == 60)
{
addLeft = 10000;
ival = 2;
}
// 'Hide' the box for 2/3 of timer interval when
// it reaches the right side.
if (addLeft == 10000)
{
addLeft = 0;
ival = 1;
}
//
switch (ival)
{
case 2:
addLeft += 12;
break;
case 3:
ival = 0;
break;
}
ival++;
if (ival == 2)
{
// Move rectangle.
MoveWindow(hwndBox, rcleft + addLeft, rctop, 12, 12, true);
}
if (timeCount == MAX_TIME_COUNT)
{
DestroyWindow(hwnd);
}
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
Here is the code for referenced H files.
StartApp.H
#pragma once
#define WIN32_LEAN_AND_MEAN // Exclude DDE, RPC, Shell, Sockets, etc.
#define NOCOMM // Exclude serial communication APIs.
#include <Windows.h>
#include <cstdlib>
// Calls the CreateProcess function to start another app.
// ref: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682512(v=vs.85).aspx
bool StartApplication(const char* appPath)
{
// The CreateProcess function requires
// wide char. array, so convert appPath.
size_t count = strlen(appPath) + 1;
wchar_t* path = new wchar_t[count];
size_t returnCount;
int ret = mbstowcs_s(&returnCount, path, count, appPath, _TRUNCATE);
if (ret != 0 || returnCount != count)
{
// Error converting C-string.
delete[] path;
return false;
}
// Required objects.
STARTUPINFO si;
PROCESS_INFORMATION pi;
// Set the size of the objects.
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
// Start the program at 'path'.
bool ok = CreateProcess(
path, // module name/app. path.
NULL, // Command line/path, if null app. path is used.
NULL, // Process handle, use null.
NULL, // Thread handle, use null.
FALSE, // Set handle inheritance to FALSE.
0, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi); // Pointer to PROCESS_INFORMATION structure.
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
delete[] path;
return ok;
}
CenterWindow.H
void CenterRectToMonitor(LPRECT prc, int rcWidth, int rcHeight)
{
POINT pt;
GetCursorPos(&pt);
HMONITOR mon;
mon = MonitorFromPoint(pt, MONITOR_DEFAULTTONEAREST);
MONITORINFO mi;
mi.cbSize = sizeof(mi);
GetMonitorInfo(mon, &mi);
*prc = mi.rcMonitor;
//
// Center the window rectangle to the monitor rectangle.
prc->left = prc->left + (prc->right - prc->left - rcWidth) / 2;
prc->top = prc->top + (prc->bottom - prc->top - rcHeight) / 2;
prc->right = prc->left + rcWidth;
prc->bottom = prc->top + rcHeight;
}
PathStatus.H
#include <sys/stat.h>
const int SUCCESS = 0; // Indicates stat buffer was successfully set.
// Method to check if a file or folder
// exists and is accessible, using the stat
// structure and stat function, ref:
// http://pubs.opengroup.org/onlinepubs/009695399/functions/stat.html
bool Path_Accessible(const char* path)
{
if (path == 0)
{
return false;
}
struct stat path_status;
int result = stat(path, &path_status);
//
// The _S_IFREG flag indicates the path is a
// regular file and not a directory.
//
// To check for a directory, use '_S_IFDIR'.
return result == SUCCESS &&
(path_status.st_mode & _S_IFREG);
}
This is what my splash screen looks like. Note that the small box starts a little to the left of where it is now, moves a little to the right, and this repeats until the other app. this one started sends the WM_CLOSE message to this window.

WINAPI CreateWindow Displaying Strange Results

I'm having trouble with a WINAPI project. There are two problems, when I launch a window with the below code, the height parameter behaves strangely. It seems to cap off at 1092, 18 pixels below where I need it on my computer. The second problem is that the window has no edges nor the top menu bar, until I use the Windows+Up/Down key combination to minimize and maximize it, then it behaves normally. I'm using the below code to initialize the window (the only code that runs before this initializes the options.pxXRes and other variables used below):
//Set up the window class
WNDCLASSEX wndClass;
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
wndClass.lpfnWndProc = &WndHandleInput;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = GetModuleHandle(nullptr);
wndClass.hIcon = nullptr;
wndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
wndClass.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1);
wndClass.lpszMenuName = nullptr;
wndClass.lpszClassName = "ToastCatClass";
wndClass.hIconSm = nullptr;
RegisterClassEx(&wndClass);
RECT wndRect;
if (options.fullscreen) {
wndRect.left = 0;
wndRect.right = options.pxXRes;
wndRect.top = 0;
wndRect.bottom = options.pxYRes;
AdjustWindowRect(&wndRect, WS_OVERLAPPEDWINDOW, false);
} else {
wndRect.left = (GetPXXRes() - options.pxXRes) / 2;
wndRect.right = options.pxXRes;
wndRect.top = (GetPXYRes() - options.pxYRes) / 2;
wndRect.bottom = options.pxYRes;
AdjustWindowRect(&wndRect, WS_OVERLAPPEDWINDOW, false);
}
hWnd = CreateWindowEx(
0,
wndClass.lpszClassName,
"ToastCat",
WS_OVERLAPPEDWINDOW,
wndRect.left,
wndRect.top,
wndRect.right - wndRect.left,
wndRect.bottom - wndRect.top,
nullptr,
nullptr,
wndClass.hInstance,
nullptr
);
assert(hWnd != nullptr, "Failure to launch window.");
ShowWindow(hWnd, SW_SHOWDEFAULT);
UpdateWindow(hWnd);
The Window procedure is as follows:
LRESULT __stdcall WndHandleInput(HWND hWndParam, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_NCCREATE:
return true;
case WM_KEYDOWN:
switch (wParam) {
//TODO: Update controls
}
break;
case WM_KEYUP:
switch (wParam) {
//TODO: Update controls
}
break;
case WM_CLOSE:
case WM_QUIT:
Cleanup();
break;
default:
return DefWindowProc(hWnd, msg, wParam, lParam);
}
return 0;
}
Windows have two different rectangles:
the client rectangle which is the area of the window to which the application can draw or create child windows within and
the window rectangle which is the outer rectangle of the window (including borders, menu, ...).
By calling AdjustWindowRect you're converting a client rectangle to a window rectangle. Therefore the borders and the menu cannot be visible if you're setting up a client rectangle as big as the entire screen, converting it to a window rectangle and creating a window with such size. If you look at wndRect after the call to AdjustWindowRect(..) using the debugger you will see that top and left are negative.
That the window height is off by some pixels is the default windows behavior. Windows by default does not allow windows to have a height bigger than the screen height because this will move the caption out of the area the mouse can get to. To change this you have to process message WM_GETMINMAXINFO:
case WM_GETMINMAXINFO:
DefWindowProc(hWnd, msg, wParam, lParam);
MINMAXINFO *pmmi = (MINMAXINFO*)lParam;
pmmi->ptMaxTrackSize.x *= 2; // just make it bigger...
pmmi->ptMaxTrackSize.y *= 2; //
return 0;
If you just want to start your window either maximized (with all controls and border visible) or at some other defined location just do the following: Create your window normally with the wndRect initialized within the else-part of if (options.fullscreen) and change the call of ShowWindow(..) as follows:
if (options.fullscreen)
{
ShowWindow(hWnd, SW_MAXIMIZE);
}
else
{
ShowWindow(hWnd, SW_SHOWDEFAULT);
}
UpdateWindow(hWnd);

Win32 Scrollbar clips text

When I resize my window and then use the scollbar, the text gets clipped. When I resize the window again, the clipped text shows properly again.
Why does this happen, and how can I prevent it?
Screenshot:
http://i.imgur.com/CLHgowx.png
main.cpp:
#include <Windows.h>
#include "sysmets.h"
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("Scratch");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if(!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("Program requires Windows NT!"), szAppName, MB_ICONERROR);
return 0;
}
hwnd = CreateWindow(szAppName, TEXT("Scratch"),
WS_OVERLAPPEDWINDOW | WS_VSCROLL,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
TEXTMETRIC tm;
PAINTSTRUCT ps;
SCROLLINFO si;
static int cxChar, cyChar, cxClient, cyClient, cxCaps;
int i, y, iPaintBeg, iPaintEnd, iVertPos;
TCHAR buffer[10];
switch(message)
{
case WM_CREATE:
hdc = GetDC(hwnd);
GetTextMetrics(hdc, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight + tm.tmExternalLeading;
cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2;
ReleaseDC(hwnd, hdc);
return 0;
case WM_SIZE:
cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_PAGE | SIF_RANGE;
si.nMin = 0;
si.nMax = NUMLINES - 1;
si.nPage = cyClient / cyChar;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_POS;
GetScrollInfo(hwnd, SB_VERT, &si);
iVertPos = si.nPos;
iPaintBeg = max(0, iVertPos + ps.rcPaint.top / cyChar);
iPaintEnd = min(NUMLINES - 1, iVertPos + ps.rcPaint.bottom / cyChar);
for (i = iPaintBeg; i < iPaintEnd; i++)
{
y = cyChar * (i - iVertPos);
TextOut(hdc, 0, y, sysmetrics[i].szLabel, lstrlen(sysmetrics[i].szLabel));
TextOut(hdc, 22 * cxCaps, y, sysmetrics[i].szDesc, lstrlen(sysmetrics[i].szDesc));
SetTextAlign(hdc, TA_TOP | TA_RIGHT);
TextOut(hdc, 60 * cxCaps, y, buffer, wsprintf(buffer, TEXT("%5d") ,GetSystemMetrics(sysmetrics[i].iIndex)));
SetTextAlign(hdc, TA_TOP | TA_LEFT);
}
EndPaint(hwnd, &ps);
return 0;
case WM_VSCROLL:
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_VERT, &si);
iVertPos = si.nPos;
switch (LOWORD(wParam))
{
case SB_LINEUP:
si.nPos -= 1;
break;
case SB_LINEDOWN:
si.nPos += 1;
break;
case SB_PAGEUP:
si.nPos -= si.nPage;
break;
case SB_PAGEDOWN:
si.nPos += si.nPage;
break;
case SB_THUMBTRACK:
si.nPos = si.nTrackPos;
break;
default:
break;
}
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
GetScrollInfo(hwnd, SB_VERT, &si);
if(iVertPos != si.nPos)
{
ScrollWindow(hwnd, 0, cyChar * (iVertPos - si.nPos), NULL, NULL);
}
UpdateWindow(hwnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
sysmets.h (defines NUMLINES and sysmetrics struct)
#ifndef SYSMETS_H_
#define SYSMETS_H_
#include <windows.h>
#define NUMLINES ((int) sizeof(sysmetrics) / sizeof(sysmetrics[0]))
struct
{
int iIndex;
TCHAR *szLabel;
TCHAR *szDesc;
}
sysmetrics [] =
{
SM_CXSCREEN, TEXT("SM_CXSCREEN"), TEXT("Screen Width in pixels."),
SM_CYSCREEN, TEXT("SM_CYSCREEN"), TEXT("Screen Height in pixels."),
SM_CXVSCROLL, TEXT("SM_CXVSCROLL"), TEXT("Vertical Scroll Width"),
SM_CYHSCROLL, TEXT("SM_CYHSCROLL"), TEXT("Horizontal Scroll Height"),
SM_CYCAPTION, TEXT("SM_CYCAPTION"), TEXT("Caption Bar Height"),
SM_CXBORDER, TEXT("SM_CXBORDER"), TEXT("Window Border Width"),
SM_CYBORDER, TEXT("SM_CYBORDER"), TEXT("Window Border Height"),
SM_CXFIXEDFRAME, TEXT("SM_CXFIXEDFRAME"), TEXT("Dialog Window Frame Width"),
SM_CYFIXEDFRAME, TEXT("SM_CYFIXEDFRAME"), TEXT("Dialog Window Frame Height"),
SM_CYVTHUMB, TEXT("SM_CYVTHUMB"), TEXT("Vertical scroll thumb height"),
SM_CXHTHUMB, TEXT("SM_CXHTHUMB"), TEXT("Horizontal scroll thumb width"),
SM_CXICON, TEXT("SM_CXICON"), TEXT("Icon width"),
SM_CYICON, TEXT("SM_CYICON"), TEXT("Icon height"),
SM_CXCURSOR, TEXT("SM_CXCURSOR"), TEXT("Cursor width"),
SM_CYCURSOR, TEXT("SM_CYCURSOR"),TEXT("Cursor height"),
SM_CYMENU, TEXT("SM_CYMENU"), TEXT("Menu bar height"),
SM_CXFULLSCREEN, TEXT("SM_CXFULLSCREEN"), TEXT("Full screen client area width"),
SM_CYFULLSCREEN, TEXT("SM_CYFULLSCREEN"), TEXT("Full screen client area height"),
SM_CYKANJIWINDOW, TEXT("SM_CYKANJIWINDOW"), TEXT("Kanji window height"),
SM_MOUSEPRESENT, TEXT("SM_MOUSEPRESENT"), TEXT("Mouse present flag"),
SM_CYVSCROLL, TEXT("SM_CYVSCROLL"), TEXT("Vertical scroll arrow height"),
SM_CXHSCROLL, TEXT("SM_CXHSCROLL"), TEXT("Horizontal scroll arrow width"),
SM_DEBUG, TEXT("SM_DEBUG"), TEXT("Debug version flag"),
SM_SWAPBUTTON, TEXT("SM_SWAPBUTTON"), TEXT("Mouse buttons swapped flag"),
SM_CXMIN, TEXT("SM_CXMIN"), TEXT("Minimum window width"),
SM_CYMIN, TEXT("SM_CYMIN"), TEXT("Minimum window height"),
SM_CXSIZE, TEXT("SM_CXSIZE"), TEXT("Min/Max/Close button width"),
SM_CYSIZE, TEXT("SM_CYSIZE"), TEXT("Min/Max/Close button height"),
SM_CXSIZEFRAME, TEXT("SM_CXSIZEFRAME"), TEXT("Window sizing frame width"),
SM_CYSIZEFRAME, TEXT("SM_CYSIZEFRAME"), TEXT("Window sizing frame height"),
SM_CXMINTRACK, TEXT("SM_CXMINTRACK"), TEXT("Minimum window tracking width"),
SM_CYMINTRACK, TEXT("SM_CYMINTRACK"), TEXT("Minimum window tracking height"),
SM_CXDOUBLECLK, TEXT("SM_CXDOUBLECLK"), TEXT("Double click x tolerance"),
SM_CYDOUBLECLK, TEXT("SM_CYDOUBLECLK"), TEXT("Double click y tolerance"),
SM_CXICONSPACING, TEXT("SM_CXICONSPACING"), TEXT("Horizontal icon spacing"),
SM_CYICONSPACING, TEXT("SM_CYICONSPACING"), TEXT("Vertical icon spacing"),
SM_MENUDROPALIGNMENT, TEXT("SM_MENUDROPALIGNMENT"), TEXT("Left or right menu drop"),
SM_PENWINDOWS, TEXT("SM_PENWINDOWS"), TEXT("Pen extensions installed"),
SM_DBCSENABLED, TEXT("SM_DBCSENABLED"), TEXT("Double-Byte Char Set enabled"),
SM_CMOUSEBUTTONS, TEXT("SM_CMOUSEBUTTONS"), TEXT("Number of mouse buttons"),
SM_SECURE, TEXT("SM_SECURE"), TEXT("Security present flag"),
SM_CXEDGE, TEXT("SM_CXEDGE"), TEXT("3-D border width"),
SM_CYEDGE, TEXT("SM_CYEDGE"), TEXT("3-D border height"),
SM_CXMINSPACING, TEXT("SM_CXMINSPACING"), TEXT("Minimized window spacing width"),
SM_CYMINSPACING, TEXT("SM_CYMINSPACING"), TEXT("Minimized window spacing height"),
SM_CXSMICON, TEXT("SM_CXSMICON"), TEXT("Small icon width"),
SM_CYSMICON, TEXT("SM_CYSMICON"), TEXT("Small icon height"),
SM_CYSMCAPTION, TEXT("SM_CYSMCAPTION"), TEXT("Small caption height"),
SM_CXSMSIZE, TEXT("SM_CXSMSIZE"), TEXT("Small caption button width"),
SM_CYSMSIZE, TEXT("SM_CYSMSIZE"), TEXT("Small caption button height"),
SM_CXMENUSIZE, TEXT("SM_CXMENUSIZE"), TEXT("Menu bar button width"),
SM_CYMENUSIZE, TEXT("SM_CYMENUSIZE"), TEXT("Menu bar button height"),
SM_ARRANGE, TEXT("SM_ARRANGE"), TEXT("How minimized windows arranged"),
SM_CXMINIMIZED, TEXT("SM_CXMINIMIZED"), TEXT("Minimized window width"),
SM_CYMINIMIZED, TEXT("SM_CYMINIMIZED"), TEXT("Minimized window height"),
SM_CXMAXTRACK, TEXT("SM_CXMAXTRACK"), TEXT("Maximum draggable width"),
SM_CYMAXTRACK, TEXT("SM_CYMAXTRACK"), TEXT("Maximum draggable height"),
SM_CXMAXIMIZED, TEXT("SM_CXMAXIMIZED"), TEXT("Width of maximized window"),
SM_CYMAXIMIZED, TEXT("SM_CYMAXIMIZED"), TEXT("Height of maximized window"),
SM_NETWORK, TEXT("SM_NETWORK"), TEXT("Network present flag"),
SM_CLEANBOOT, TEXT("SM_CLEANBOOT"), TEXT("How system was booted"),
SM_CXDRAG, TEXT("SM_CXDRAG"), TEXT("Avoid drag x tolerance"),
SM_CYDRAG, TEXT("SM CYDRAG"), TEXT("Avoid drag y tolerance"),
SM_SHOWSOUNDS, TEXT("SM_SHOWSOUNDS"), TEXT("Present sounds visually"),
SM_CXMENUCHECK, TEXT("SM_CXMENUCHECK"), TEXT("Menu check-mark width"),
SM_CYMENUCHECK, TEXT("SM_CYMENUCHECK"), TEXT("Menu check-mark height"),
SM_SLOWMACHINE, TEXT("SM_SLOWMACHINE"), TEXT("Slow processor flag"),
SM_MIDEASTENABLED, TEXT("SM_MIDEASTENABLED"), TEXT("Hebrew and Arabic enabled flag"),
SM_MOUSEWHEELPRESENT, TEXT("SM_MOUSEWHEELPRESENT"), TEXT("Mouse wheel present flag"),
SM_XVIRTUALSCREEN, TEXT("SM_XVIRTUALSCREEN"), TEXT("Virtual screen x origin"),
SM_YVIRTUALSCREEN, TEXT("SM_YVIRTUALSCREEN"), TEXT("Virtual screen y origin"),
SM_CXVIRTUALSCREEN, TEXT("SM_CXVIRTUALSCREEN"), TEXT("Virtual screen width"),
SM_CYVIRTUALSCREEN, TEXT("SM_CYVIRTUALSCREEN"), TEXT("Virtual screen height"),
SM_CMONITORS, TEXT("SM_CMONITORS"), TEXT("Number of monitors"),
SM_SAMEDISPLAYFORMAT, TEXT("SM_SAMEDISPLAYFORMAT"), TEXT("Same color format flag")
};
#endif
MSDN about UpdateWindow:
The function sends a WM_PAINT message directly to the window procedure
of the specified window, bypassing the application queue. If the
update region is empty, no message is sent.
To force an update you can manually set the update region with
InvalidateRect(hwnd, NULL, TRUE)
This adds a WM_PAINT message to the message queue of hwnd. If you want to force an instant repaint you can call UpdateWindow right afterwards but that shouldn't be necessary.

Smooth window resizing in Windows (using Direct2D 1.1)?

It annoys me that the resizing of windows in Windows is not as "smooth" as it I'd like it to be (this is the case with Windows programs in general, not just my own. Visual Studio is a good example). It makes the OS and its programs feel "flimsy" and "cheap" (yes, I care about how programs and user interfaces feel, in the same way I care about the sound and feel of closing a car door. It's a reflection of build quality), which in my view affects the overall UX and ultimately the perception of the brand.
The redrawing of window contents simply does not keep up with mouse movement during resize. Whenever I resize a window, there is a "stuttering" / "flickering" effect, seemingly due to the previous-size-contents of the window being redrawn in the new, resized window frame before the new, resized contents are drawn.
I am building a Win32 application (x64) that uses Direct2D 1.1 to draw its UI, and given the speed of Direct2D, i think it should be unnecessary to suffer such artifacts in an OS in 2014. I am on Windows 8.1 myself, but targeting Windows 7 and up with this application.
The "previous size" effect is especially discernible when maximizing a small window (since the difference in window size is sufficiently great to easily contrast the image of the old content as it flashes briefly in the upper left corner of the larger window with the new content subsequently being painted over it).
This is what appears to be going on:
(Let's say there's a fully rendered window on screen, size 500 x 500 pixels).
I maximize the window:
The window frame is maximized
The old 500 x 500 content is drawn in the new frame, before..
..the maximized window is repainted with properly sized content.
I'm wondering if there's any way to mitigate this (i.e. get rid of step 4) - via intercepting a Windows Message, for example - and avoid the window being repainted at the new size with the old content before the final re-rendering of the new content happens. It's like Windows does the window redrawing itself, using whatever graphics it already has available, BEFORE it bothers to ask me to provide updated content with a WM_PAINT message or similar.
Can it be done?
Edit: It seems that WM_WINDOWPOSCHANGING / WM_SIZING provides "early access" to the new size data, but I still haven't managed to suppress the painting of the old content.
My WndProc looks like this:
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
PAINTSTRUCT ps;
BeginPaint(hWnd, &ps);
D2DRender();
EndPaint(hWnd, &ps);
return 0;
case WM_SIZE:
if (DeviceContext && wParam != SIZE_MINIMIZED)
{
D2DResizeTargetBitmap();
D2DRender();
}
return 0;
case WM_DISPLAYCHANGE:
D2DRender();
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, message, wParam, lParam);
}
The window does not have CS_HREDRAW or CS_VREDRAW set. The swapchain is double buffered and the Present call is made with SyncInterval = 0.
I am aware that recreating the swapchain buffers every time the window size changes does create some overhead compared to plain redrawing on a static window surface. However, the "stuttering" is not caused by that, as it happens even when buffer resizing is disabled and existing window contents are simply scaled during window resizing (although that does make it keep up better with mouse movement).
There is a way to prevent the needless BitBlt mentioned in step 4 above.
Until Windows 8 it could be done either by creating your own custom implementation of WM_NCCALCSIZE to tell Windows to blit nothing (or to blit one pixel on top of itself), or alternately you could intercept WM_WINDOWPOSCHANGING (first passing it onto DefWindowProc) and set WINDOWPOS.flags |= SWP_NOCOPYBITS, which disables the BitBlt inside the internal call to SetWindowPos() that Windows makes during window resizing. This has the same eventual effect of skipping the BitBlt.
However, nothing can be so simple. With the advent of Windows 8/10 Aero, apps now draw into an offscreen buffer which is then composited by the new, evil DWM.exe window manager. And it turns out DWM.exe will sometimes do its own BitBlt type operation on top of the one already done by the legacy XP/Vista/7 code. And stopping DWM from doing its blit is much harder; so far I have not seen any complete solutions.
So you need to get through both layers. For sample code that will break through the XP/Vista/7 layer and at least improve the performance of the 8/10 layer, see:
How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?
What if you have a borderless childwindow (the type that only renders inside the parent) at a fixed size (same as fullscreen resolution), you should get a lot smoother results because there is no memory reallocation (which i think is what causes the jitterieness).
If it's still not perfect, look into both WM_SIZE and WM_SIZING and check if you can do some magic with them. For instance, on WM_SIZING you could return true telling Windows you handled the message (leaving the window as is) and you re-render your UI to a buffer with the size provided by WM_SIZING and when that is done, you send your own WM_SIZING but with a manipulated unused bit in WPARAM (along with its previous content) that tells you you have a pre-rendered buffer for this that you can just blit out. From the WM_SIZING documentation on msdn it looks like WPARAM should have a couple of bits at your disposal.
Hope this helps.
When calling CreateSwapChainForHwnd, ensure that you have set the swap chain description Scaling property to DXGI_SCALING_NONE. This is only supported on Windows 7 with the Platform Update, so you may need to fall back to the default DXGI_SCALING_STRETCH (the latter is what is causing the flickering).
Set WM_SETREDRAW to FALSE, do your resizing, then reenable drawing, invalidate the window and the OS will blit it.
I've done this for button enabling and disabling buttons when selecting different items from a list, never for an entire window.
This is the best I've come up with and resizes great although the backbuffer blitting causes some edge flickering, haven't tested with DX or OGL yet but it should work even better with hardware acceleration. It's a bit bulky but will do as a proof of concept.
If the canvas could be clipped without using MDI that would be even better, like using a bitmask buffer.
One thing i'm not happy about is the position coords of the child window because they might not work on all systems, but a combination of GetSystemMetrics calls to get border and caption sizes should fix that.
/* Smooth resizing of GDI+ MDI window
*
* Click window to resize, hit Escape or Alt+F4 to quit
*
* Character type is set to multibyte
* Project->Properties->Config Properties->General->Character Set = Multibyte
*
* Pritam 2014 */
// Includes
#include <Windows.h>
#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
using namespace Gdiplus;
// Max resolution
#define XRES 1600
#define YRES 900
// Globals
bool resizing = false;
HWND parent, child; // child is the canvas window, parent provides clipping of child
Bitmap * buffer;
// Render
void Render() {
// Get parent client size
RECT rc;
GetClientRect(parent, &rc);
// Draw backbuffer
Graphics * g = Graphics::FromImage(buffer);
// Clear buffer
g->Clear(Color(100, 100, 100));
// Gray border
Pen pen(Color(255, 180, 180, 180));
g->DrawRectangle(&pen, 10, 10, rc.right - 20, rc.bottom - 20);
pen.SetColor(Color(255, 0, 0, 0));
g->DrawRectangle(&pen, 0, 0, rc.right - 1, rc.bottom - 1);
// Draw buffer to screen
PAINTSTRUCT ps;
HDC hdc = BeginPaint(child, &ps);
Graphics graphics(hdc);
graphics.DrawImage(buffer, Point(0, 0));
// Free
EndPaint(child, &ps);
}
// MDI Callback
LRESULT CALLBACK MDICallback(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
switch(message) {
case WM_LBUTTONDOWN:
resizing = true; // Start resizing
return 0;
break;
case WM_KEYDOWN:
if(wparam == VK_ESCAPE) { // Exit on escape
PostQuitMessage(0);
}
TranslateMessage((const MSG *)&message);
return 0;
break;
case WM_PAINT:
Render();
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}
return DefMDIChildProc(hwnd, message, wparam, lparam);
}
// Parent window callback
LRESULT CALLBACK WndCallback(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
return DefFrameProc(hwnd, child, message, wparam, lparam);
}
// Create windows
bool CreateWindows(void) {
// Parent class
WNDCLASSEX wndclass;
ZeroMemory(&wndclass, sizeof(wndclass)); wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_NOCLOSE;
wndclass.lpfnWndProc = WndCallback;
wndclass.hInstance = GetModuleHandle(NULL);
wndclass.lpszClassName = "WNDCALLBACKPARENT";
wndclass.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
if(!RegisterClassEx(&wndclass)) return false;
// MDI class
wndclass.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
wndclass.lpfnWndProc = MDICallback;
wndclass.lpszClassName = "MDICALLBACKCANVAS";
if(!RegisterClassEx(&wndclass)) return false;
// Parent window styles
DWORD style = WS_POPUP | WS_CLIPCHILDREN;
DWORD exstyle = 0;
// Set initial window size and position
RECT rc;
rc.right = 640;
rc.bottom = 480;
AdjustWindowRectEx(&rc, style, false, exstyle);
rc.left = 20;
rc.top = 20;
// Create window
if(!(parent = CreateWindowEx(exstyle, "MDICLIENT", "MDI Resize", style, rc.left, rc.top, rc.right, rc.bottom, NULL, NULL, wndclass.hInstance, NULL))) return false;
// MDI window styles
style = MDIS_ALLCHILDSTYLES;
exstyle = WS_EX_MDICHILD;
// Set MDI size
rc.left = - 8; // The sizes occupied by borders and caption, if position is not correctly set an ugly caption will appear
rc.top = - 30;
rc.right = XRES;
rc.bottom = YRES;
AdjustWindowRectEx(&rc, style, false, exstyle);
// Create MDI child window
if(!(child = CreateWindowEx(exstyle, "MDICALLBACKCANVAS", "", style, rc.left, rc.top, rc.right, rc.bottom, parent, NULL, wndclass.hInstance, NULL))) return 8;
// Finalize
ShowWindow(child, SW_SHOW);
ShowWindow(parent, SW_SHOWNORMAL);
// Success
return true;
}
// Resize
void Resize(void) {
// Init
RECT rc, rcmdi;
GetClientRect(child, &rcmdi); // Use mdi window size to set max resize for parent
GetWindowRect(parent, &rc);
// Get mouse position
POINT mp;
GetCursorPos(&mp);
// Set new size
rc.right = mp.x - rc.left + 10;
rc.bottom = mp.y - rc.top + 10;
// Apply min & max size
if(rc.right < 240) rc.right = 240; if(rc.bottom < 180) rc.bottom = 180;
if(rc.right > rcmdi.right) rc.right = rcmdi.right; if(rc.bottom > rcmdi.bottom) rc.bottom = rcmdi.bottom;
// Update window size
SetWindowPos(parent, NULL, rc.left, rc.top, rc.right, rc.bottom, SWP_NOZORDER | SWP_NOMOVE);
// Make sure client is entirely repainted
GetClientRect(child, &rc);
InvalidateRect(child, &rc, false);
UpdateWindow(child);
// Stop resizing if mousebutton is up
if(!(GetKeyState(VK_LBUTTON) & 1 << (sizeof(short) * 8 - 1)))
resizing = false;
}
// Main
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE pinstance, LPSTR cmdline, int cmdshow) {
// Initiate GDI+
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
buffer = new Bitmap(XRES, YRES, PixelFormat24bppRGB);
// Create windows
if(!CreateWindows()) return 1;
// Main loop
bool running = true;
MSG message;
while(running) {
// Check message or pass them on to window callback
if(PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
if(message.message == WM_QUIT) {
running = false;
} else {
if(!TranslateMDISysAccel(child, &message)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
}
}
// Resize
if(resizing)
Resize();
// Sleep a millisecond to spare the CPU
Sleep(1);
}
// Free memmory and exit
delete buffer;
GdiplusShutdown(gdiplusToken);
return 0;
}
Edit: Another example using "bitmask"/layered window.
// Escape to quit, left mousebutton to move window, right mousebutton to resize.
// And again char set must be multibyte
// Include
#include <Windows.h>
#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
using namespace Gdiplus;
// Globals
Bitmap * backbuffer;
int xres, yres;
bool move, size;
POINT framePos, frameSize, mouseOffset;
// Renders the backbuffer
void Render(void) {
if(!backbuffer) return;
// Clear window with mask color
Graphics * gfx = Graphics::FromImage(backbuffer);
gfx->Clear(Color(255, 0, 255));
// Draw stuff
SolidBrush brush(Color(120, 120, 120));
gfx->FillRectangle(&brush, framePos.x, framePos.y, frameSize.x, frameSize.y);
}
// Paints the backbuffer to window
void Paint(HWND hwnd) {
if(!hwnd) return;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Graphics gfx(hdc);
gfx.DrawImage(backbuffer, Point(0, 0));
EndPaint(hwnd, &ps);
}
void HandleMove(HWND hwnd) {
// Get mouse position
POINT mouse;
GetCursorPos(&mouse);
// Update frame position
framePos.x = mouse.x - mouseOffset.x;
framePos.y = mouse.y - mouseOffset.y;
// Redraw buffer and invalidate & update window
Render();
InvalidateRect(hwnd, NULL, false);
UpdateWindow(hwnd);
// Stop move
if(!(GetKeyState(VK_LBUTTON) & 1 << (sizeof(short) * 8 - 1)))
move = false;
}
void HandleSize(HWND hwnd) {
// Get mouse position
POINT mouse;
GetCursorPos(&mouse);
// Update frame size
frameSize.x = mouse.x + mouseOffset.x - framePos.x;
frameSize.y = mouse.y + mouseOffset.y - framePos.y;
//frameSize.x = mouse.x + mouseOffset.x;
//frameSize.y = mouse.y + mouseOffset.y;
// Redraw buffer and invalidate & update window
Render();
InvalidateRect(hwnd, NULL, false);
UpdateWindow(hwnd);
// Stop size
if(!(GetKeyState(VK_RBUTTON) & 1 << (sizeof(short) * 8 - 1)))
size = false;
}
LRESULT CALLBACK WindowCallback(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
POINTS p;
switch(msg) {
case WM_KEYDOWN:
if(wparam == VK_ESCAPE) PostQuitMessage(0);
return 0;
break;
case WM_LBUTTONDOWN:
p = MAKEPOINTS(lparam); // Get mouse coords
mouseOffset.x = p.x - framePos.x;
mouseOffset.y = p.y - framePos.y;
move = true;
break;
case WM_RBUTTONDOWN:
p = MAKEPOINTS(lparam);
mouseOffset.x = framePos.x + frameSize.x - p.x;
mouseOffset.y = framePos.y + frameSize.y - p.y;
size = true;
break;
case WM_PAINT:
Paint(hwnd);
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
// Main
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE pinstance, LPSTR cmdline, int cmdshow) {
// Init resolution, frame
xres = GetSystemMetrics(SM_CXSCREEN);
yres = GetSystemMetrics(SM_CYSCREEN);
move = false; size = false;
framePos.x = 100; framePos.y = 80;
frameSize.x = 320; frameSize.y = 240;
mouseOffset.x = 0; mouseOffset.y = 0;
// Initiate GDI+
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// Init backbuffer
backbuffer = ::new Bitmap(xres, yres, PixelFormat24bppRGB);
Render();
// Window class
WNDCLASSEX wc; ZeroMemory(&wc, sizeof(wc)); wc.cbSize = sizeof(wc);
wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = WindowCallback;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = "SingleResizeCLASS";
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
if(!RegisterClassEx(&wc)) return 1;
// Create window
HWND hwnd;
DWORD style = WS_POPUP;
DWORD exstyle = WS_EX_LAYERED;
if(!(hwnd = CreateWindowEx(exstyle, wc.lpszClassName, "Resize", style, 0, 0, xres, yres, NULL, NULL, wc.hInstance, NULL)))
return 2;
// Make window fully transparent to avoid the display of unpainted window
SetLayeredWindowAttributes(hwnd, 0, 0, LWA_ALPHA);
// Finalize
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
// Make window fully opaque, and set color mask key
SetLayeredWindowAttributes(hwnd, RGB(255, 0, 255), 0, LWA_COLORKEY);
// Main loop
MSG msg;
bool running = true;
while(running) {
// Check message
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if(msg.message == WM_QUIT) {
running = false;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Move or size frame
if(move) { HandleMove(hwnd); }
if(size) { HandleSize(hwnd); }
Sleep(1);
}
// Free memory
::delete backbuffer;
backbuffer = NULL;
GdiplusShutdown(gdiplusToken);
// Exit
return 0;
}
While your aim is laudable, I suspect that any attempt to do this will just end up as a fight between you and Windows - which you will not win (though you might manage to fight your way to an honourable draw). Sorry to be negative.