Okay then, well where to begin?
I looked at this problem from a bunch of angles, I must've pondered and tried 1/2 a dozen combinations of approaches, but I think I've found one that is workable.
Basically, the idea is to do all the drawing on the dialog/window itself and then use button controls that are invisble, to simplify the handling of mouse/keyboard messages. Actually, I've only just considered keyboard messages just now - if you want to be able to navigate through the various controls on the window using the keyboard, you really need to provide some kind of visual indication of state - whether it be focus, hover or active. Understand that my code hasn't taken this into consideration, though it could easily be adapted..
First, I tried drawing the orange square with the 3 buttons and the map into a memDC and blasting that to screen via a subclassed static's hdc. This worked okay - there was no longer any flicker or problem in the drawing of the map - the problem though was that there was a brief moment when the main window's background was visible where the orange square should be.
I also realized that the way that windows can allow you to attach so many pieces of data to the window itself is really convenient and it simplifies the code greatly, since each function can be written in a generic way before being re-used with differing input data. This caused me to wrap the blue buttons into a c++ class. This being an approach quite similar to mfc/win32++/wxWidgets/Qt - this made it very easy to cleanly attach the window text, font and image to the blue buttons. It also helps simplyfy the way the data is stored in the source code. Problem was though there was still the dreaded flicker - flicker that occurs between the time the dialog background has been drawn and the time that all of the controls have been drawn.
Hmm.
So then I resigned myself to custom-drawing the whole interface as the main window's background + looking for old code I had that would handle the mouse messages.
But then I had an idea..
Couldn't I just subclass some button controls and in the WindowProc, just pass everything to the default window proc except for the drawing code? Wouldn't that do what I want?
So, I did just that. Made a simple struct for the data each button would need, updating the position of each whenever the window is moved. When it comes time to paint the window, I draw buttons at the calculated positions and move the invisible buttons to the same position. So with a few iines of code to move the controls and a few more to subclass the original WndProc, we've done away with the need to do dirty, lowish-level mouse-work. You may wish to handle mouse-events for the buttons by drawing directly to the window hdc, rather than the (full-size) off-screen one. If this wasn't quite quick enough, you could create a memDC the size of the button, draw the button with it's altered state (hover, active, focus) to this DC, before BitBlt to the screen.
I've (over)simplified a few things and just whipped up a 'short' sample that I hope will point you in a better direction.
Bear in mind, I've written the code to be clear and simple, rather than efficient. Improvements would certainly include
e.g
load EMF file just once
only create memDC and memBmp when window size changes
I suggest you just do the same trick for the 'buttons' on the orange panel with the map. That is, write a function that will draw a button covering a specified rect, on a specified hdc, with specified image and text. Keep the position of each one somewhere, update it when you re-size the window, re-position the invisible buttons in same positions, done!
Lastly (whew!) I noticed that the window could me made so small that the interface became very ugly, with some elements overlapping others. In this case, you may wish to look at the WM_GETMINMAXINFO message. By doing so, you can tell window the largest and smallest size the window can be made by the user. I've handled that message, using the size that avoided this.
So, the code!
resource.rc
// Generated by ResEdit 1.5.11
// Copyright (C) 2006-2012
// http://www.resedit.net
#include <windows.h>
#include <commctrl.h>
#include <richedit.h>
#include "resource.h"
//
// Dialog resources
//
LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
DLG_MAIN DIALOG 0, 0, 186, 95
STYLE DS_3DLOOK | DS_CENTER | DS_SHELLFONT | WS_CAPTION | WS_VISIBLE | WS_POPUP | WS_THICKFRAME | WS_SYSMENU
EXSTYLE WS_EX_WINDOWEDGE
CAPTION "Dialog"
FONT 8, "Ms Shell Dlg"
{
}
resource.h
#ifndef IDC_STATIC
#define IDC_STATIC (-1)
#endif
#define DLG_MAIN 100
main.cpp
#define UNICODE
#define WINVER 0x0500
#include <windows.h>
#include <commctrl.h>
#include <stdio.h>
#include <gdiplus.h>
#include "resource.h"
#define firstBigBtnId 5000
using namespace Gdiplus;
HINSTANCE hInst;
WNDPROC oldBtnProc;
typedef struct
{
wchar_t *text;
RECT rect;
HBITMAP img;
} bigBtn_t, *pBigBtn_t;
bigBtn_t bigBtns[4] =
{
{ L"УНОС ПОДАТАКА" },
{ L"ПРЕГЛЕД ПОДАТАКА" },
{ L"ИЗВЕШТАЈ" },
{ L"ПРЕТРАГА" }
};
void GradientFillTriangle(HDC hdc, POINT p1, POINT p2, POINT p3, COLORREF col1, COLORREF col2, COLORREF col3)
{
TRIVERTEX vertex[3];
GRADIENT_TRIANGLE gTri = {0,1,2};
vertex[0].x = p1.x;
vertex[0].y = p1.y;
vertex[0].Red = GetRValue(col1) << 8;
vertex[0].Green = GetGValue(col1) << 8;
vertex[0].Blue = GetBValue(col1) << 8;
vertex[0].Alpha = 255;
vertex[1].x = p2.x;
vertex[1].y = p2.y;
vertex[1].Red = GetRValue(col2) << 8;
vertex[1].Green = GetGValue(col2) << 8;
vertex[1].Blue = GetBValue(col2) << 8;
vertex[1].Alpha = 255;
vertex[2].x = p3.x;
vertex[2].y = p3.y;
vertex[2].Red = GetRValue(col3) << 8;
vertex[2].Green = GetGValue(col3) << 8;
vertex[2].Blue = GetBValue(col3) << 8;
vertex[2].Alpha = 255;
GradientFill(hdc, vertex, 3, &gTri, 1, GRADIENT_FILL_TRIANGLE );
}
void squareRadialFillRect(HDC hdc, RECT fillMe, COLORREF colInner, COLORREF colOuter)
{
POINT p1, p2, p3;
p3.x = fillMe.left + (fillMe.right - fillMe.left) / 2;
p3.y = fillMe.top + (fillMe.bottom - fillMe.top) / 2;
p1.x = fillMe.left; p1.y = fillMe.top;
p2.x = fillMe.right; p2.y = fillMe.top;
GradientFillTriangle(hdc, p1,p2,p3, colOuter, colOuter, colInner);
p1 = p2;
p2.x = fillMe.right;
p2.y = fillMe.bottom;
GradientFillTriangle(hdc, p1,p2,p3, colOuter, colOuter, colInner);
p1 = p2;
p2.x = fillMe.left;
p2.y = fillMe.bottom;
GradientFillTriangle(hdc, p1,p2,p3, colOuter, colOuter, colInner);
p1 = p2;
p2.x = fillMe.left;
p2.y = fillMe.top;
GradientFillTriangle(hdc, p1,p2,p3, colOuter, colOuter, colInner);
}
void diagGradFillRect(HDC hdc, RECT fillMe, COLORREF colDiag, COLORREF colCorners, bool isDiagLeftToRight)
{
POINT p1,p2,p3;
if (isDiagLeftToRight == true)
{
p1.x = fillMe.left;
p1.y = fillMe.top;
p2.x = fillMe.right;
p2.y = fillMe.bottom;
p3.x = fillMe.right;
p3.y = fillMe.top;
GradientFillTriangle(hdc, p1,p2,p3,colDiag,colDiag,colCorners);
p3.x = fillMe.left;
p3.y = fillMe.bottom;
GradientFillTriangle(hdc, p1,p2,p3,colDiag,colDiag,colCorners);
}
}
void GradientFillRect(HDC hdc, RECT fillMe, COLORREF colStart, COLORREF colEnd, bool isVertical)
{
TRIVERTEX vertex[2];
GRADIENT_RECT gRect;
gRect.UpperLeft = 0;
gRect.LowerRight = 1;
vertex[0].x = fillMe.left;
vertex[0].y = fillMe.top;
vertex[0].Red = GetRValue(colStart) << 8;
vertex[0].Green = GetGValue(colStart) << 8;
vertex[0].Blue = GetBValue(colStart) << 8;
vertex[0].Alpha = 255;
vertex[1].x = fillMe.right;
vertex[1].y = fillMe.bottom;
vertex[1].Red = GetRValue(colEnd) << 8;
vertex[1].Green = GetGValue(colEnd) << 8;
vertex[1].Blue = GetBValue(colEnd) << 8;
vertex[1].Alpha = 255;
if (isVertical)
GradientFill(hdc, vertex, 2, &gRect, 1, GRADIENT_FILL_RECT_V );
else
GradientFill(hdc, vertex, 2, &gRect, 1, GRADIENT_FILL_RECT_H );
}
void drawBigBtn(HDC dst, RECT btnRect, wchar_t *wndText)
{
RECT topRect, botRect;
topRect = botRect = btnRect;
topRect.bottom = botRect.top = ((btnRect.bottom - btnRect.top)/2) + btnRect.top;
GradientFillRect(dst, topRect, RGB(0x95,0xb3,0xd7), RGB(0x4f,0x8b,0xb0), true);
GradientFillRect(dst, botRect, RGB(0x4f,0x8b,0xb0), RGB(0x95,0xb3,0xd7), true);
HBRUSH blueBrush, oldBrush;
blueBrush = CreateSolidBrush(RGB(79,129,189));
oldBrush = (HBRUSH) SelectObject(dst, blueBrush);
FrameRect(dst, &btnRect, blueBrush);
SelectObject(dst, oldBrush);
DeleteObject(blueBrush);
RECT textRect = btnRect;
textRect.top = textRect.bottom - 40;
SetBkMode(dst, TRANSPARENT);
int textLen = wcslen(wndText);
DrawTextEx( dst, wndText, textLen, &textRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE | DT_WORDBREAK, 0 );
delete(wndText);
}
void drawFooter(HDC dst, RECT footerRect)
{
RECT topRect, botRect;
topRect = botRect = footerRect;
topRect.bottom = botRect.top = ((footerRect.bottom - footerRect.top)/2) + footerRect.top;
GradientFillRect(dst, topRect, RGB(0x31,0x83,0x99), RGB(0x45,0xa7,0xc1), true);
GradientFillRect(dst, botRect, RGB(0x45,0xa7,0xc1), RGB(0x31,0x83,0x99), true);
}
void drawHeader(HDC dst, RECT headerRect)
{
HBRUSH b1;
int i,j,headerHeight = (headerRect.bottom - headerRect.top)+1;
b1 = CreateSolidBrush(RGB(230,230,230));
FillRect(dst, &headerRect,b1);
DeleteObject(b1);
HPEN oldPen, curPen;
curPen = CreatePen(PS_SOLID, 1, RGB(216,216,216));
oldPen = (HPEN)SelectObject(dst, curPen);
for (j=headerRect.top;j<headerRect.bottom;j+=10)
{
MoveToEx(dst, headerRect.left, j, NULL);
LineTo(dst, headerRect.right, j);
}
for (i=headerRect.left;i<headerRect.right;i+=10)
{
MoveToEx(dst, i, headerRect.top, NULL);
LineTo(dst, i, headerRect.bottom);
}
SelectObject(dst, oldPen);
DeleteObject(curPen);
MoveToEx(dst, headerRect.left,headerRect.bottom,NULL);
LineTo(dst, headerRect.right,headerRect.bottom);
}
void onPaint(HWND hwndDlg, WPARAM wParam, LPARAM lParam)
{
RECT mRect, topRect, midRect, botRect;
RECT btnRects[4], orangeRect;
PAINTSTRUCT ps;
HDC hdc;
const int bannerHeight = 120, footerHeight=32;
int btnSize=150, margin=16;
int i, j;
GetClientRect(hwndDlg, &mRect);
topRect = midRect = botRect = mRect;
topRect.bottom = topRect.top + bannerHeight;
botRect.top = botRect.bottom - footerHeight;
midRect.top = topRect.bottom;
midRect.bottom = botRect.top;
RECT tmpRect;
HWND tmpWnd;
int n = 4;
for (i=0; i<n; i++)
{
tmpRect = bigBtns[i].rect;
tmpWnd = GetDlgItem(hwndDlg, firstBigBtnId+i);
SetWindowPos(tmpWnd,HWND_TOP,tmpRect.left,tmpRect.top,btnSize,btnSize,SWP_NOZORDER);
}
orangeRect.top = midRect.top + margin;
orangeRect.bottom = midRect.bottom - margin;
orangeRect.right = midRect.right - margin;
orangeRect.left = orangeRect.right - (mRect.right-mRect.left)/4;
HBRUSH b1,b2,b3;
hdc = BeginPaint(hwndDlg, &ps);
HBITMAP memBmp, oldMemBmp;
HDC memDC;
memDC = CreateCompatibleDC(hdc);
memBmp = CreateCompatibleBitmap(hdc, mRect.right,mRect.bottom);
oldMemBmp = (HBITMAP)SelectObject(memDC, memBmp);
diagGradFillRect(memDC, midRect, RGB(218,228,240), RGB(149,179,215), true);
Graphics *graphics;
Image *mapImg;
mapImg = Image::FromFile(L"map2.emf");
graphics = Graphics::FromHDC(memDC);
graphics->SetSmoothingMode( SmoothingModeHighQuality );
graphics->SetInterpolationMode( InterpolationModeHighQualityBicubic );
const int o_height = mapImg->GetHeight(), o_width = mapImg->GetWidth();
float scale = 0.5;
int mapPosX, mapPosY;
scale = (float)orangeRect.right / o_width;
if ( (float)(orangeRect.bottom-orangeRect.left)/o_height < scale)
scale = (float)(orangeRect.bottom-orangeRect.left)/o_height;
int marginX = (orangeRect.right - orangeRect.left) - (o_width*scale);
int marginY = (orangeRect.bottom - orangeRect.top) - (o_height*scale);
marginX /= 2;
marginY /= 2;
mapPosX = orangeRect.left + marginX; mapPosY = orangeRect.top + marginY;
squareRadialFillRect(memDC, orangeRect, RGB(0xff,0xc8,0xaa), RGB(0xff,0x96,0x48));
graphics->DrawImage(mapImg,
mapPosX, mapPosY, (int)(o_width*scale),(int)(o_height*scale) );
delete graphics;
delete mapImg;
drawHeader(memDC, topRect);
drawFooter(memDC, botRect);
for (i=0;i<4;i++)
drawBigBtn(memDC, bigBtns[i].rect, bigBtns[i].text);
BitBlt(hdc,0,0,mRect.right,mRect.bottom,memDC,0,0,SRCCOPY);
SelectObject(memDC, oldMemBmp);
DeleteObject(memBmp);
DeleteDC(memDC);
EndPaint(hwndDlg, &ps);
}
LRESULT CALLBACK invisibleBtnProc(HWND hwndBtn, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
ValidateRect(hwndBtn, NULL);
return 0;
case WM_ERASEBKGND:
return 1;
}
return CallWindowProc(oldBtnProc, hwndBtn, uMsg, wParam, lParam);
}
void onSize(HWND hwndDlg, WPARAM wParam, LPARAM lParam)
{
RECT mRect, topRect, botRect, midRect;
const int btnSize=150, margin=16;
GetClientRect(hwndDlg, &mRect);
bigBtns[0].rect.left = ( 3 * ( mRect.right - mRect.left ) / 4 - 340 ) / 3;
bigBtns[0].rect.top = 120 + mRect.top + ( mRect.bottom - mRect.top - 450 ) / 3;
bigBtns[0].rect.right = bigBtns[0].rect.left + btnSize;
bigBtns[0].rect.bottom = bigBtns[0].rect.top + btnSize;
bigBtns[1].rect.left = 150 + 2 * ( 3 * ( mRect.right - mRect.left ) / 4 - 340 ) / 3;
bigBtns[1].rect.top = 120 + ( mRect.bottom - mRect.top - 450 ) / 3;
bigBtns[1].rect.right = bigBtns[1].rect.left + btnSize;
bigBtns[1].rect.bottom = bigBtns[1].rect.top + btnSize;
bigBtns[2].rect.left = ( 3 * ( mRect.right - mRect.left ) / 4 - 340 ) / 3;
bigBtns[2].rect.top = 270 + 2 * ( mRect.bottom - mRect.top - 450 ) / 3;
bigBtns[2].rect.right = bigBtns[2].rect.left + btnSize;
bigBtns[2].rect.bottom = bigBtns[2].rect.top + btnSize;
bigBtns[3].rect.left = 150 + 2 * ( 3 * ( mRect.right - mRect.left ) / 4 - 340 ) / 3;
bigBtns[3].rect.top = 270 + 2 * ( mRect.bottom - mRect.top - 450 ) / 3;
bigBtns[3].rect.right = bigBtns[3].rect.left + btnSize;
bigBtns[3].rect.bottom = bigBtns[3].rect.top + btnSize;
InvalidateRect(hwndDlg,NULL,false);
}
LRESULT CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_INITDIALOG:
{
HWND btn;
int i, n=4;
SetWindowPos(hwndDlg, HWND_TOP, 200,150,996,547,SWP_NOZORDER);
for (i=0; i<n; i++)
{
btn = CreateWindow(WC_BUTTON, L"", WS_VISIBLE|WS_CHILD, 0,0,0,0, hwndDlg, (HMENU)(firstBigBtnId+i), hInst, NULL);
oldBtnProc = (WNDPROC)SetWindowLong(btn, GWL_WNDPROC, (long) invisibleBtnProc);
printf("%ld\n", oldBtnProc);
}
}
return TRUE;
case WM_CLOSE:
{
EndDialog(hwndDlg, 0);
}
return TRUE;
case WM_ERASEBKGND:
return 1;
case WM_PAINT:
onPaint(hwndDlg, wParam, lParam);
return 0;
case WM_GETMINMAXINFO:
MINMAXINFO *lpmmi;
lpmmi = (LPMINMAXINFO) lParam;
lpmmi->ptMinTrackSize.x = 996;
lpmmi->ptMinTrackSize.y = 548;
return 0;
case WM_SIZE:
onSize(hwndDlg, wParam, lParam);
return 0;
case WM_COMMAND:
{
switch(LOWORD(wParam))
{
case firstBigBtnId+0:
case firstBigBtnId+1:
case firstBigBtnId+2:
case firstBigBtnId+3:
MessageBeep(MB_ICONEXCLAMATION);
break;
}
}
return TRUE;
}
return FALSE;
}
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdiplusToken;
hInst=hInstance;
InitCommonControls();
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
int result = DialogBox(hInst, MAKEINTRESOURCE(DLG_MAIN), NULL, (DLGPROC)DlgMain);
GdiplusShutdown(gdiplusToken);
return result;
}