is it possible to use activeX controls with win32 Aplications? - winapi

I'm coding my Win32 application with MFC shared DLL and I was trying to use ActiveX controls on it , is that possible? or I'm just wasting my time?

When creating a new MFC project, make sure you activate ActiveX support (example using VS2008):
Often you want to use an ActiveX control from within a dialog window. Right click on the dialog in the designer view and select 'Insert ActiveX control' and select the ActiveX you want:
If you want to freely embed an ActiveX control outside a dialog, there is an ATL wrapper for MFC. But it's a bit more tricky and would look like this:
#include "stdafx.h"
#include "atlbase.h"
#include "oleidl.h"
#include "comdef.h"
...
...
AtlAxWinInit();
pPluginWnd = new CAxWindow();
CRect r = GetParent()->GetClientRect();
if (!pPluginWnd->Create(GetParent()->m_hWnd, r, "ActiveX Plugin Window", WS_VISIBLE
| WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VSCROLL | WS_HSCROLL)))
{
AfxMessageBox("Couldn't create the ActiveX host window");
return;
}
LPUNKNOWN pUnk;
pPluginWnd->QueryControl(&pUnk);
IDispatch *spDispatch;
HRESULT hRes = pUnk->QueryInterface(__uuidof(spDispatch), (void **) &spDispatch);
if (hRes != S_OK)
{
AfxMessageBox("Couldn't query the ActiveX interface");
return;
}
// get a method called 'Init' in the ActiveX to pass a long integer parameter to it
long nMyValueToPass;
DISPID dispid;
OLECHAR FAR szMember[5];
MultiByteToWideChar(CP_ACP, 0, "Init", -1, szMember, 5);
OLECHAR FAR *pszMember = szMember;
DISPPARAMS dispparams = { NULL, NULL, 0, 0 };
VARIANT vRet;
COleVariant vParam(nMyValueToPass,VT_I4);
EXCEPINFO excepinfo;
UINT nArgErr;
dispparams.rgvarg = (LPVARIANT)vParam;
dispparams.cArgs = 1;
dispparams.cNamedArgs = 0;
hRes = spDispatch->GetIDsOfNames(IID_NULL, &pszMember, 1, LOCALE_SYSTEM_DEFAULT, &dispid);
if (hRes != S_OK)
{
AfxMessageBox("Init method couldn't be found in ActiveX control");
return;
}
// call the Init method
hRes = spDispatch->Invoke(dispid, IID_NULL, LOCALE_SYSTEM_DEFAULT,
DISPATCH_METHOD, &dispparams, &vRet, &excepinfo, &nArgErr);

Related

Approaches for reducing rate of resource file growth as dialogs are enhanced/extended

As part of extending the functionality of a dialog in an old Windows WinAPI GUI application written in C, I was faced with once more adding multiple check boxes for each line of a six line data entry dialog. I just could not bear the hassle of repetitive resource file and source code file changes and decided to borrow a UI design approach from Java UIs of building up a UI from panes.
The Visual Studio tools, at least Visual Studio 2005, seem to discourage this approach and in this case I hand edited the resource file. Perhaps the resource editing tools of Visual Studio 2017 are more flexible.
My question is what is an alternative to this approach that would seem to be as easy to do and would better fit with the Visual Studio philosophy.
I am also wondering about the downside of this approach.
This approach seems unusual for a Visual Studio C WinAPI GUI application which troubles me. I can't claim to be especially innovative so I wonder what I am missing as the approach seems to work well at least when doing hand edits of the resource file.
I am considering doing another iteration in which I move the list of controls for each line that is repeated into the modeless dialog box template as well and just having the original dialog be a stack of 6 static windows, one for each line.
Benefits of this approach was fewer defines and being able to reuse defines. It was also easier to insert the new functionality into the existing dialog behavior source code though that was mostly because these were just simple auto check boxes.
The one issue I see is using the Visual Studio tools after doing this change. However this particular application's resource file doesn't work well with the Visual Studio resource editing tools anyway.
This approach has already had a payback when I needed to add some additional checkboxes to the modeless dialog template. The resource file changes I had to do to was to add the additional checkboxes to the new dialog template and adjust the original dialog size, the modeless dialog size, and the sizes of the static windows to make everything visible.
The Implementation
The alternative I have implemented is to:
create a dialog template with the set of checkboxes
modify the dialog template style of the modeless dialog to WS_CHILD
create a static window on each of the six lines of the original dialog for the new dialog template
place an instance of the modeless dialog box into the static window on each line
The new version of the dialog looks like
When the original dialog is displayed, the handler for the init dialog message creates a set of six modeless dialogs, one for each of the newly added static windows with the parent window for the dialogs being the static window. This places the modeless dialog into the static window and when the static window moves so does the modeless dialog.
All six of the modeless dialogs use the same dialog message handler. The message handler doesn't handle any messages itself.
The modeless dialog template is:
IDD_A170_DAYS DIALOG DISCARDABLE 0, 0, 240, 20
STYLE WS_CHILD | WS_VISIBLE
FONT 8, "MS Sans Serif"
BEGIN
CONTROL "Ovr",IDD_A170_STR1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,5,1,25,10
CONTROL "AND",IDD_A170_STR2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,35,1,40,10
CONTROL "S",IDD_A170_CAPTION1,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,75,1,20,10
CONTROL "M",IDD_A170_CAPTION2,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,100,1,20,10
CONTROL "T",IDD_A170_CAPTION3,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,125,1,20,10
CONTROL "W",IDD_A170_CAPTION4,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,150,1,20,10
CONTROL "T",IDD_A170_CAPTION5,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,175,1,20,10
CONTROL "F",IDD_A170_CAPTION6,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,195,1,20,10
CONTROL "S",IDD_A170_CAPTION7,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,220,1,20,10
END
and the main dialog with the static windows is:
IDD_A170 DIALOG DISCARDABLE 2, 17, 530, 190
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Set Sales Code Restriction Table of PLU (AC 170)"
FONT 8, "MS Sans Serif"
BEGIN
LTEXT "Address (PLU Sales Code)",IDD_A170_CAPTION1,14,10,64,20
LTEXT "Date",IDD_A170_CAPTION2,86,14,28,12
LTEXT "Day of week",IDD_A170_CAPTION3,115,10,33,21
LTEXT "Start hour",IDD_A170_CAPTION4,153,10,20,18
LTEXT "Minute",IDD_A170_CAPTION5,182,14,26,12
LTEXT "End hour",IDD_A170_CAPTION6,217,10,20,18
LTEXT "Minute",IDD_A170_CAPTION7,245,14,26,12
LTEXT "Override/Type",IDC_STATIC,290,14,50,12
LTEXT "Days To Restrict",IDC_STATIC,390,14,100,12
LTEXT "",IDD_A170_STR1,8,34,74,12 // first control on line 1
EDITTEXT IDD_A170_DATE1,87,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_DATESPIN1,104,33,8,12,SBS_VERT
EDITTEXT IDD_A170_WEEK1,119,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_WEEKSPIN1,136,33,8,12,SBS_VERT
EDITTEXT IDD_A170_SHOUR1,151,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_SHOURSPIN1,168,33,8,12,SBS_VERT
EDITTEXT IDD_A170_SMINUTE1,183,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_SMINUTESPIN1,200,33,8,12,SBS_VERT
EDITTEXT IDD_A170_EHOUR1,214,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_EHOURSPIN1,231,33,8,12,SBS_VERT
EDITTEXT IDD_A170_EMINUTE1,246,33,18,12,ES_AUTOHSCROLL
SCROLLBAR IDD_A170_EMINUTESPIN1,263,33,8,12,SBS_VERT
LTEXT "D1",IDD_A170_DAYS_1,281,33,240,20 // static window to contain the modeless dialog box from the template IDD_A170_DAYS above
// .. repeated sequence for 5 more lines
CONTROL "MDC 298 - Sales Restriction Type is AND",IDD_A170_MDC_PLU5_ADR,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,9,140,170,9
LTEXT "[Address : 1 - 6, Date : 0 - 31",IDD_A170_CAPTION8,9,154,99,9
LTEXT "Day of week : 0 - 7 (1 - Sunday, 7 - Saturday)]",IDD_A170_CAPTION9,110,154,167,9
LTEXT "[Hour : 0 - 24, Minute : 0 - 59 (For 0:00, enter 24:00)]",IDD_A170_CAPTION10,9,168,167,9
PUSHBUTTON "&Ok",IDOK,285,154,48,20
PUSHBUTTON "&Cancel",IDCANCEL,345,154,48,20
END
You may notice that I just reused some of the defines in the new modeless dialog box that were already being used in the original dialog box. I was able to do so because control identifiers are specific to the dialog box itself. So using the same define in different dialog boxes does not cause a problem since the use of GetDlgItem() to obtain the window handle of a control within a dialog box requires the window handle of a specific dialog instance.
I then created a set of helper functions that handle an instance of the modeless dialog.
static struct {
int iId;
HWND hWnd;
} A170DlgTabs[10] = { {0, 0} };
// modeless dialog box message handler which has nothing to do but the
// WinAPI requires it.
BOOL WINAPI A170DlgChildProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
return FALSE;
}
void A170ModeLessChildDialogClear ()
{
memset (A170DlgTabs, 0, sizeof(A170DlgTabs));
}
HWND A170ModeLessChildDialog (HWND hParentWnd, int nCmdShow, int iId)
{
int i;
HWND hWnd = DialogCreation(hResourceDll/*hActInst*/, //RPH 4-23-03 Multilingual
MAKEINTRESOURCEW(IDD_A170_DAYS),
hParentWnd,
A170DlgChildProc);
hWnd && ShowWindow (hWnd, nCmdShow);
for (i = 0; i < sizeof(A170DlgTabs)/sizeof(A170DlgTabs[0]); i++) {
if (A170DlgTabs[i].hWnd == 0) {
A170DlgTabs[i].iId = iId;
A170DlgTabs[i].hWnd = hWnd;
break;
}
}
return hWnd;
}
HWND A170ModeLessChildDialogFind (int iId)
{
int i;
HWND hWnd = NULL;
for (i = 0; i < sizeof(A170DlgTabs)/sizeof(A170DlgTabs[0]); i++) {
if (A170DlgTabs[i].iId == iId) {
hWnd = A170DlgTabs[i].hWnd;
break;
}
}
return hWnd;
}
USHORT A170ModeLessChildDialogSettings (int iId)
{
int i;
USHORT iBits = 0, kBits = 1;
HWND hWnd = A170ModeLessChildDialogFind (iId);
// least significant byte contains the bit mask for the days of the week.
// the next higher byte contains the indicators for the override type or
// whether MDC 298 is to be overriden or not.
for (i = IDD_A170_CAPTION1; i <= IDD_A170_CAPTION7; i++, (kBits <<= 1)) {
iBits |= IsDlgButtonChecked (hWnd, i) ? kBits : 0;
}
iBits |= iBits ? RESTRICT_WEEK_DAYS_ON : 0;
iBits |= IsDlgButtonChecked(hWnd, IDD_A170_STR1) ? KBITS_RESTRICT_OVERRIDE_ANDOR : 0;
iBits |= IsDlgButtonChecked(hWnd, IDD_A170_STR2) ? KBITS_RESTRICT_OVERRIDE_AND : 0;
return iBits;
}
USHORT A170ModeLessChildDialogSettingsSetMask (int iId, USHORT usMask)
{
int i;
USHORT k = 1;
HWND hWnd = A170ModeLessChildDialogFind (iId);
CheckDlgButton(hWnd, IDD_A170_STR1, (usMask & KBITS_RESTRICT_OVERRIDE_ANDOR) ? TRUE : FALSE);
CheckDlgButton(hWnd, IDD_A170_STR2, (usMask & KBITS_RESTRICT_OVERRIDE_AND) ? TRUE : FALSE);
for (i = IDD_A170_CAPTION1; i <= IDD_A170_CAPTION7; i++, (k <<= 1)) {
CheckDlgButton(hWnd, i, (usMask & k) ? TRUE : FALSE);
}
return usMask;
}
Using Visual Studio 2017 Community Edition made taking this approach to creating components from dialog templates which are then used to build a GUI easier.
I did this rough, proof of concept exercise using the About dialog that is automatically generated by the creation of a new Windows Desktop Application project since it was handy.
This was a simple component being expressed in a dialog template and that simplicity may make this proof of concept misleading.
I started with an initial Windows Desktop Application skeleton created as a new project. I then made the following modifications to the About dialog that is automatically generated with a New project. I was able to use the IDE and Resource Editor without doing any hand editing of the resource file.
The steps were as follows:
use the Resource Editor to modify the existing About dialog and create a new modeless dialog
add a new class to manage the new modeless dialog
modify the About dialog message handler to use the new class
Modifications to the dialog resources using the Resource Editor was fairly straightforward:
modify the automatically generated About dialog by making it larger and adding two static text windows in a column
specify actual control identifiers for each static text box added to the About dialog so that they could be referenced with GetDlgItem(), an important step as the IDE does not assign usable control identifiers by default to static windows
created a new dialog template using the Resource Editor after switching to Resource View
modified a couple of Appearance attributes in the Properties list of the dialog box to change the dialog to a modeless dialog with no border
added the checkboxes to the new dialog using the Resource Editor and removed the default buttons
The source code changes were also fairly straightforward as this is a simple control.
The new About dialog box looks like this. I added two static windows to the About dialog after making it larger. Each of the static windows has its own instance of the new dialog template based control. Notice that the size of the dialog template is larger than the size of the static windows which results in clipping of the dialog template to the display area of the static window.
The Details of What Was Done
Create and Modify the Dialog Styles
Using Resource View I added a new dialog. I clicked on the new dialog to bring it up in the Resource Editor. Then I modified the initial modal dialog template by changing the border attribute and the style attribute along with removing the default buttons the IDE added when it first created the dialog template, I turned the dialog template into a modeless dialog suitable for putting into a static window container.
Create the Code for the Behavior
I then created a class, CDialogChild to manage this new modeless dialog with the following source:
#pragma once
#include "resource.h"
class CDialogChild
{
private:
// static shared by all instances of this class
static LRESULT CALLBACK WndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);
enum {DIALOG_ID = IDD_DIALOG1};
static const UINT idCheckBox[3]; // identifiers for the controls of the modeless dialog.
// management data for each modeless dialog instance created.
HINSTANCE m_hinst; // handle to the instance resources
HWND m_hWnd; // handle to the modeless dialog window
HWND m_hParent; // handle to the parent of the modeless dialog, usually static window
// operational data displayed and captured by this modeless dialog
// this is the data for the various controls we have in the dialog template.
bool bCheckBox[3] = { false, false, false };
public:
CDialogChild();
~CDialogChild();
bool GetCheck(int iIndex); // for reading checkbox value
void GetCheckFinal(void); // for capturing final checkbox states
bool SetCheck(int iIndex, bool bValue); // for writing checkbox value
void SetCheckInitial(void); // for setting the initial checkbox states.
HWND Create(HWND hParent, HINSTANCE hinst);
};
with an implementation of:
#include "stdafx.h"
#include "CDialogChild.h"
const UINT CDialogChild::idCheckBox[3] = { IDC_CHECK1, IDC_CHECK2, IDC_CHECK3 };
CDialogChild::CDialogChild()
{
}
CDialogChild::~CDialogChild()
{
}
HWND CDialogChild::Create(HWND hParent, HINSTANCE hinst)
{
// called to create the modeless dialog using the dialog resource in the
// specified resource file instance. the hParent is the container we are
// going to put this modeless dialogbox into.
m_hinst = hinst;
m_hParent = hParent;
m_hWnd = CreateDialog(hinst, MAKEINTRESOURCE(DIALOG_ID), hParent, (DLGPROC)CDialogChild::WndProc);
ShowWindow(m_hWnd, SW_SHOW);
return m_hWnd;
}
bool CDialogChild::GetCheck(int iIndex)
{
if (iIndex > 0 && iIndex < sizeof(bCheckBox) / sizeof(bCheckBox[0])) {
iIndex = 0;
}
bCheckBox [iIndex] = IsDlgButtonChecked(m_hWnd, idCheckBox[iIndex]);
return bCheckBox [iIndex];
}
bool CDialogChild::SetCheck(int iIndex, bool bValue)
{
if (iIndex > 0 && iIndex < sizeof(bCheckBox) / sizeof(bCheckBox[0])) {
iIndex = 0;
}
CheckDlgButton (m_hWnd, idCheckBox[iIndex], bValue);
bCheckBox[iIndex] = bValue;
return bCheckBox [iIndex];
}
void CDialogChild::GetCheckFinal(void)
{
for (int iIndex = 0; iIndex < sizeof(bCheckBox) / sizeof(bCheckBox[0]); iIndex++) {
bCheckBox[iIndex] = IsDlgButtonChecked(m_hWnd, idCheckBox[iIndex]);
}
}
void CDialogChild::SetCheckInitial(void)
{
for (int iIndex = 0; iIndex < sizeof(bCheckBox) / sizeof(bCheckBox[0]); iIndex++) {
CheckDlgButton(m_hWnd, idCheckBox[iIndex], bCheckBox[iIndex]);
}
}
// CDialogChild class Windows message procedure to handle any messages sent
// to a modeless dialog window. This simple example there is not much to do.
LRESULT CALLBACK CDialogChild::WndProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
return (INT_PTR)FALSE;
}
Use the New Control in a Dialog
Finally I modified the About dialog to use the new dialog template based control. The first thing was to add the static windows to the About dialog template to provide containers for the new control which provided the placement for where I wanted the control instances to be.
Next I added the handling source code to use the new dialog template based control in the modified About dialog.
// other source code from the Windows Desktop Application main window handler
// is above this. We are only modifying the About dialog code which is at the
// bottom of the source file.
#include "CDialogChild.h"
CDialogChild myAbout1;
CDialogChild myAbout2;
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
myAbout1.Create(GetDlgItem(hDlg, IDC_STATIC4), hInst);
myAbout1.SetCheckInitial();
myAbout2.Create(GetDlgItem(hDlg, IDC_STATIC5), hInst);
myAbout2.SetCheckInitial();
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
if (LOWORD(wParam) == IDOK) {
myAbout1.GetCheckFinal();
myAbout2.GetCheckFinal();
}
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}

GDI - Can I use the new Windows 10 Segoe UI Emoji colored font with DrawText?

I'm creating a c++ project using Embarcadero RAD Studio (10.2 Tokyo starter) and the Windows GDI to draw text, via the DrawText() function.
I recently saw that Windows 10 provides a new "Segoe UI Emoji" font, that potentially allows text functions to draw colored emojis. I found several examples using Direct2D, but none with pure GDI functions.
I also tried a simple code, like this:
HDC hDC = ::GetDC(Handle);
std::auto_ptr<TCanvas> pCanvas(new TCanvas());
pCanvas->Handle = hDC;
pCanvas->Brush->Color = clWhite;
pCanvas->Brush->Style = bsSolid;
pCanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));
const std::wstring text = L"Test 😀 😬 😁 😂 😃 😄 😅 😆";
TRect textRect(10, 10, ClientWidth - 10, ClientHeight - 10);
hFont = ::CreateFont(-40,
0,
0,
0,
FW_DONTCARE,
FALSE,
FALSE,
FALSE,
DEFAULT_CHARSET,
OUT_OUTLINE_PRECIS,
CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY,
VARIABLE_PITCH,
L"Segoe UI Emoji");
::SelectObject(hDC, hFont);
::DrawTextW(hDC,
text.c_str(),
text.length(),
&textRect,
DT_LEFT | DT_TOP | DT_SINGLELINE);
::DeleteObject(hFont);
The output result sounds good in terms of symbols, but they are drawn in black&white, without colors, as you can see on the screenshot below:
I could not find any additional options that may allow the text to be drawn using colored symbols instead of black&white. Is there a way to activate the support of the color in GDI DrawText() function, and if yes, how to do that? Or only Direct2D may draw colored emojis?
EDITED on 30.10.2017
As the GDI cannot do the job (unfortunately, and as I thought) I publish here the Direct2D version of the above code, that worked for me.
const std::wstring text = L"Test 😀 😬 😁 😂 😃 😄 😅 😆";
HDC hDC = ::GetDC(Handle);
std::auto_ptr<TCanvas> pGDICanvas(new TCanvas());
pGDICanvas->Handle = hDC;
pGDICanvas->Brush->Color = clWhite;
pGDICanvas->Brush->Style = bsSolid;
pGDICanvas->FillRect(TRect(0, 0, ClientWidth, ClientHeight));
::D2D1_RECT_F textRect;
textRect.left = 10;
textRect.top = 10;
textRect.right = ClientWidth - 10;
textRect.bottom = ClientHeight - 10;
std::auto_ptr<TDirect2DCanvas> pCanvas(new TDirect2DCanvas(hDC, TRect(0, 0, ClientWidth, ClientHeight)));
// configure Direct2D font
pCanvas->Font->Size = 40;
pCanvas->Font->Name = L"Segoe UI Emoji";
pCanvas->Font->Orientation = 0;
pCanvas->Font->Pitch = System::Uitypes::TFontPitch::fpVariable;
pCanvas->Font->Style = TFontStyles();
// get DirectWrite text format object
_di_IDWriteTextFormat pFormat = pCanvas->Font->Handle;
if (!pFormat)
return;
pCanvas->RenderTarget->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE);
::D2D1_COLOR_F color;
color.r = 0.0f;
color.g = 0.0f;
color.b = 0.0f;
color.a = 1.0f;
::ID2D1SolidColorBrush* pBrush = NULL;
// create solid color brush, use pen color if rect is completely filled with outline
pCanvas->RenderTarget->CreateSolidColorBrush(color, &pBrush);
if (!pBrush)
return;
// set horiz alignment
pFormat->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
// set vert alignment
pFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
// set reading direction
pFormat->SetReadingDirection(DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
// set word wrapping mode
pFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP);
IDWriteInlineObject* pInlineObject = NULL;
::DWRITE_TRIMMING trimming;
trimming.delimiter = 0;
trimming.delimiterCount = 0;
trimming.granularity = DWRITE_TRIMMING_GRANULARITY_NONE;
// set text trimming
pFormat->SetTrimming(&trimming, pInlineObject);
pCanvas->BeginDraw();
pCanvas->RenderTarget->DrawText(text.c_str(), text.length(), pFormat, textRect, pBrush,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
pCanvas->EndDraw();
Of course this code will draw colored emojis only on the currently most recent versions of Windows 10, and above. On previous versions the text will be drawn as above (and the code may not compile).
Bonus Reading
MSDN: Color Fonts with DirectWrite, Direct2D, and Win2D
GDI does not support color fonts (even if you go the full Uniscribe route), you have to use Direct2D if you want color font support. It makes sense that the simpler GDI APIs don't support color fonts as color fonts require using OpenType tags and none of DrawText/TextOut provide that level of control, Uniscribe allows for such tags but has simply not been extended to support color fonts.
You can use DirectWrite to draw colored emojis onto a bitmap in memory DC, then BitBlt() to your destination DC.
Basically, you need to implement a custom IDWriteTextRenderer class and call IDWriteTextLayout::Draw() with your renderer, then copy the result.
In your class, you retrieve IDWriteGdiInterop from IDWriteFactory and call IDWriteGdiInterop::CreateBitmapRenderTarget() to get the bitmap render target; call IDWriteFactory::CreateMonitorRenderingParams() to get the rendering parameters, and call IDWriteFactory::CreateTextFormat() to set up your text format.
The only significant method is DrawGlyphRun(), where you get IDWriteColorGlyphRunEnumerator with IDWriteFactory2::TranslateColorGlyphRun() and with each color run, call IDWriteBitmapRenderTarget::DrawGlyphRun() to do the work for you.
Just remember to update the render target/parameters when the window size/position changes.
You may reference this MSDN documentation:
Render to a GDI Surface
https://msdn.microsoft.com/en-us/library/windows/desktop/ff485856(v=vs.85).aspx
As mentioned by #SoronelHaetir's answer above, the win32 graphics device interface (GDI) that is used when working with static window components (via WC_STATIC) doesn't support colorized fonts. In order to display colored emojis and/or "fancier" text (i.e. colored text, etc.), you'll need to use the Direct2D API.
The example above provided by original poster (OP) #Jean-Milost Reymond isn't something that is immediately able to be compiled and tried out by the reader. Also, it uses the TCanvas class, which isn't strictly needed when working directly with the Win32 API.
For people looking for a complete example that works on the bare-metal Win32 API and can be immediately copied and pasted and compiled, then here is the code that will compile in Visual Studio:
#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <wchar.h>
#include <math.h>
#include <d2d1.h>
#include <d2d1helper.h>
#include <dwrite.h>
#include <wincodec.h>
#include <string>
#include <cassert>
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "Dwrite.lib")
HWND WindowHandle = nullptr;
IDWriteFactory * DWriteFactory = nullptr;
ID2D1Factory * Direct2dFactory = nullptr;
ID2D1HwndRenderTarget * RenderTarget = nullptr;
ID2D1SolidColorBrush * TextBlackBrush = nullptr;
const std::wstring DISPLAY_TEXT = L"Test 😀 😬 😁 😂 😃 😄 😅 😆";
template<class Interface>
inline void SafeRelease (Interface ** ppInterfaceToRelease);
LRESULT CALLBACK WndProc (HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam);
HRESULT CreateDeviceIndependentResources ();
HRESULT InitInstance (HINSTANCE hInstance, int nCmdShow);
void DiscardDeviceResources ();
HRESULT OnRender ();
HRESULT CreateDeviceResources ();
template<class Interface>
inline void SafeRelease (Interface ** ppInterfaceToRelease)
{
if (*ppInterfaceToRelease != NULL)
{
(*ppInterfaceToRelease)->Release ();
(*ppInterfaceToRelease) = NULL;
}
}
HRESULT OnRender ()
{
HRESULT Result = S_OK;
D2D1_SIZE_F RenderCanvasArea = { 0 };
IDWriteTextFormat * TextFormat = nullptr;
D2D1_RECT_F TextCanvasArea = { 0 };
Result = CreateDeviceResources ();
if (SUCCEEDED (Result))
{
RenderTarget->BeginDraw ();
RenderCanvasArea = RenderTarget->GetSize ();
RenderTarget->Clear (D2D1::ColorF (D2D1::ColorF::White));
if (SUCCEEDED (Result))
{
Result = DWriteFactory->CreateTextFormat (L"Segoe UI",
nullptr,
DWRITE_FONT_WEIGHT_REGULAR,
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
25.0f,
L"en-us",
&TextFormat);
TextFormat->SetTextAlignment (DWRITE_TEXT_ALIGNMENT_LEADING);
TextFormat->SetParagraphAlignment (DWRITE_PARAGRAPH_ALIGNMENT_NEAR);
TextFormat->SetReadingDirection (DWRITE_READING_DIRECTION_LEFT_TO_RIGHT);
TextFormat->SetWordWrapping (DWRITE_WORD_WRAPPING_WRAP);
if (SUCCEEDED (Result) &&
TextFormat != nullptr)
{
TextCanvasArea = D2D1::RectF (0,
0,
RenderCanvasArea.width,
RenderCanvasArea.height);
RenderTarget->DrawTextW (DISPLAY_TEXT.c_str (),
static_cast <UINT32> (DISPLAY_TEXT.size ()),
TextFormat,
TextCanvasArea,
TextBlackBrush,
D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT);
}
}
Result = RenderTarget->EndDraw ();
}
if (Result == D2DERR_RECREATE_TARGET)
{
DiscardDeviceResources ();
Result = S_OK;
}
return Result;
}
HRESULT CreateDeviceResources ()
{
HRESULT Result = S_OK;
RECT rc = { 0 };
if (!RenderTarget)
{
GetClientRect (WindowHandle,
&rc);
D2D1_SIZE_U size = D2D1::SizeU (rc.right - rc.left,
rc.bottom - rc.top);
// Create a Direct2D render target.
Result = Direct2dFactory->CreateHwndRenderTarget (D2D1::RenderTargetProperties (),
D2D1::HwndRenderTargetProperties (WindowHandle, size),
&RenderTarget);
if (SUCCEEDED (Result))
{
// Create a blue brush.
Result = RenderTarget->CreateSolidColorBrush (D2D1::ColorF (D2D1::ColorF::Black),
&TextBlackBrush);
}
}
return Result;
}
void DiscardDeviceResources ()
{
SafeRelease (&RenderTarget);
SafeRelease (&TextBlackBrush);
}
HRESULT InitInstance (HINSTANCE hInstance,
int nCmdShow)
{
HRESULT Result = S_OK;
// Create the window.
WindowHandle = CreateWindow (L"D2DTextDemo",
L"Direct2D Text Demo Application",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
600,
200,
nullptr,
nullptr,
hInstance,
nullptr);
if (WindowHandle == nullptr)
{
Result = E_POINTER;
}
else
{
ShowWindow (WindowHandle,
nCmdShow);
UpdateWindow (WindowHandle);
}
return Result;
}
HRESULT CreateDeviceIndependentResources ()
{
HRESULT Result = S_OK;
Result = D2D1CreateFactory (D2D1_FACTORY_TYPE_SINGLE_THREADED,
&Direct2dFactory);
if (SUCCEEDED (Result))
{
Result = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
__uuidof (IDWriteFactory),
reinterpret_cast <IUnknown **> (&DWriteFactory));
}
return Result;
}
LRESULT CALLBACK WndProc (HWND hwnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
{
LRESULT Result = 0;
switch (message)
{
case WM_SIZE:
{
UINT width = LOWORD (lParam);
UINT height = HIWORD (lParam);
if (RenderTarget != nullptr)
{
// Note: This method can fail, but it's okay to ignore the
// error here, because the error will be returned again
// the next time EndDraw is called.
RenderTarget->Resize (D2D1::SizeU (width,
height));
}
}
break;
case WM_DISPLAYCHANGE:
{
InvalidateRect (hwnd, nullptr, FALSE);
}
break;
case WM_PAINT:
{
OnRender ();
ValidateRect (hwnd,
nullptr);
}
break;
case WM_DESTROY:
{
PostQuitMessage (0);
Result = 1;
}
break;
default:
{
Result = DefWindowProc (hwnd,
message,
wParam,
lParam);
}
break;
}
return Result;
}
int APIENTRY wWinMain (_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER (hInstance);
UNREFERENCED_PARAMETER (hPrevInstance);
UNREFERENCED_PARAMETER (lpCmdLine);
UNREFERENCED_PARAMETER (nCmdShow);
HRESULT ExitCode = S_OK;
MSG NextMessage = { 0 };
WNDCLASSEX wcex = { 0 };
ATOM WindowClassId = 0;
wcex.cbSize = sizeof (WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = sizeof (LONG_PTR);
wcex.hInstance = hInstance;
wcex.hbrBackground = nullptr;
wcex.lpszMenuName = nullptr;
wcex.hCursor = LoadCursor (nullptr, IDI_APPLICATION);
wcex.lpszClassName = L"D2DTextDemo";
if (SUCCEEDED (CoInitialize (nullptr)))
{
WindowClassId = RegisterClassEx (&wcex);
if (WindowClassId == 0)
{
ExitCode = HRESULT_FROM_WIN32 (GetLastError ());
}
if (SUCCEEDED (ExitCode))
{
ExitCode = CreateDeviceIndependentResources ();
}
if (SUCCEEDED (ExitCode))
{
ExitCode = InitInstance (hInstance,
nCmdShow);
}
if (SUCCEEDED (ExitCode))
{
while (GetMessage (&NextMessage,
nullptr,
0,
0))
{
TranslateMessage (&NextMessage);
DispatchMessage (&NextMessage);
}
}
CoUninitialize ();
SafeRelease (&Direct2dFactory);
SafeRelease (&DWriteFactory);
SafeRelease (&RenderTarget);
}
return ExitCode;
}
(The above example doesn't have perfect error handling, so it's important to audit this example code if the following is used in any project the reader is working on.)
Before attempting to compile this in Visual Studio, make sure your project has the "SubSystem" linker option set to Windows /SUBSYSTEM:WINDOWS.
Once compiled successfully, the following application window will appear:
I tested this coded example in Visual Studio 2022 Community Edition on Windows 11 with success.
Reference(s):
Creating a Simple Direct2D Application
Tutorial: Getting Started with DirectWrite

Windows, restoring window bug

I'm playing around with Windows and D2D1
However when restoring my minimized borderless/menuless window
sometimes I get this very ugly bug
For a frame (or a few more) before the window gets drawn with D2D1
it will display this title bar with the name of the window.
This happens on about 5-10% of the restore operations.
The window class style is set to
CS_DBLCLKS|CS_OWNDC
but Ive also tried other styles.
The Window is created with CreateWindow and WS_POPUP|WS_SYSMENU as dwStyle
My rendering method is called on WM_PAINT but I also tried to move it so it
gets called every time but that does not help.
Any help is appreciated :)
#
I've found a workaround which I'm not completely fine with
Instead of calling ShowWindow(hWnd, SW_RESTORE)
I call
ShowWindow(hWnd, SW_HIDE);
ShowWindow(hWnd, SW_RESTORE);
ShowWindow(hWnd, SW_SHOW);
This however results in the taskbar icon being "renewed" which I dont want either.
Short simplified example code which features this problem (when minimizing/restoring)
#include <Windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, LPSTR cmd, int cmdShow)
{
WNDCLASSEX TestWC = { 0 };
TestWC.cbSize = sizeof(WNDCLASSEX);
TestWC.hbrBackground = (HBRUSH)CreateSolidBrush(RGB(50, 50, 50));
TestWC.lpfnWndProc = DefWindowProc;
TestWC.lpszClassName = "Testklasse";
TestWC.style = CS_DBLCLKS | CS_OWNDC;
TestWC.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClassEx(&TestWC);
HWND htest = CreateWindow("Testklasse", "Test", WS_POPUP | WS_SYSMENU | WS_VISIBLE, 200, 200, 400, 248, 0, 0, 0, 0);
MSG wMsg = { 0 };
bool shown = true;
while (wMsg.message != WM_QUIT)
{
if (PeekMessage(&wMsg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&wMsg);
DispatchMessage(&wMsg);
}
if (GetAsyncKeyState(VK_INSERT) & 0x8000 && shown)
{
shown = false;
ShowWindow(htest, SW_MINIMIZE);
continue;
}
if (GetAsyncKeyState(VK_DELETE) & 0x8000 && !shown)
{
shown = true;
ShowWindow(htest, SW_RESTORE);
continue;
}
if (GetAsyncKeyState(VK_END) & 0x8000)
PostQuitMessage(1);
Sleep(50);
}
return 1;
}
Found the solution myself:
Minimizing and restoring a borderless window
is undefined behaviour.
A workaround is to catch the WM_NCPAINT and draw the WHOLE
window yourself (even if its borderless it has a non-client area).

Detect Button Press in Tab Page

I am using the WinAPI to create a GUI utility. I have two Tabs and each tab has some buttons. I am creating the buttons with this function:
CreateWindowEx(NULL,"button", "Clear", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
150, 175, 100, 25, tab_window_2, (HMENU) CLEAR_DATA, instance_handle, NULL);
In the Windows proc function, I don't know how to detect when the above button is pressed. I also tried handling CLEAR_DATA in the WM_COMMAND switch construct, as follows.
switch ( message ) {
case WM_COMMAND:{
switch(LOWORD(wparam)) {
case CLEAR_DATA : break
}
}
How can I detect and handle those buttons being pressed?
I am creating the tab_window_2 tab as follows :
class frame_window {
private:
LPCSTR window_class_name;
HINSTANCE instance_handle;
HCURSOR cursor_arrow;
HWND window_handle;
HWND tab_handle;
HWND tab_window_1;
HWND tab_window_2;
HWND current_tab_window;
RECT client_rectangle;
public:
frame_window(LPCSTR window_class_identity) : window_class_name(window_class_identity) {
INITCOMMONCONTROLSEX common_controls;
common_controls.dwSize = sizeof(INITCOMMONCONTROLSEX);
common_controls.dwICC = ICC_BAR_CLASSES;
InitCommonControlsEx(&common_controls);
int screen_width = GetSystemMetrics(SM_CXFULLSCREEN);
int screen_height = GetSystemMetrics(SM_CYFULLSCREEN);
instance_handle = GetModuleHandle(NULL);
WNDCLASS window_class = { CS_OWNDC, main_window_proc, 0, 0,
instance_handle, NULL,
NULL, NULL, NULL,
window_class_name };
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Create a standard frame window
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
RegisterClass(&window_class);
window_handle = CreateWindowEx(WS_EX_WINDOWEDGE | WS_EX_CLIENTEDGE,
window_class_name,
"IR Remote and Barcode Demo",
WS_OVERLAPPEDWINDOW |
WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
100, 100, screen_width-1600,
screen_height-490, NULL, NULL,
instance_handle, NULL);
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Get the size of the client rectangle for the window we have just created
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
RECT client_rect;
GetClientRect(window_handle, &client_rect);
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Create the tab control window.
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
tab_handle = CreateWindowEx(NULL, WC_TABCONTROL, NULL,
WS_CHILD | WS_VISIBLE,
10, 10, client_rect.right-client_rect.left-20,
client_rect.bottom-client_rect.top-10,
window_handle, NULL,
instance_handle, NULL);
// Create three tabs.
TCITEM tab_info;
memset(&tab_info, 0, sizeof(tab_info));
tab_info.mask = TCIF_TEXT;
tab_info.pszText = "tab#1";
tab_info.cchTextMax = 5;
SendMessage(tab_handle, TCM_INSERTITEM, 0, (LPARAM)&tab_info);
tab_info.pszText = "tab#2";
SendMessage(tab_handle, TCM_INSERTITEM, 1, (LPARAM)&tab_info);
RECT tab_rectangle;
GetClientRect(tab_handle, &tab_rectangle);
SendMessage(tab_handle, TCM_ADJUSTRECT, FALSE, (LPARAM)&tab_rectangle);
// Create the tab view windows
tab_window_1 = CreateWindowEx(NULL, "STATIC", " ",
WS_CHILD | WS_VISIBLE|SS_OWNERDRAW,
tab_rectangle.left+10, tab_rectangle.top+10,
tab_rectangle.right-tab_rectangle.left,
tab_rectangle.bottom -tab_rectangle.top,
tab_handle, (HMENU)1,
instance_handle, NULL);
SetParent(tab_window_1, window_handle);
current_tab_window = tab_window_1;
tab_window_2 = CreateWindowEx(NULL, "STATIC", " ",
WS_CHILD|SS_OWNERDRAW,
tab_rectangle.left+10, tab_rectangle.top+10,
tab_rectangle.right-tab_rectangle.left,
tab_rectangle.bottom -tab_rectangle.top,
tab_handle, (HMENU)2,
instance_handle, NULL);
CreateWindowEx(NULL,"button", "Clear", WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
150, 175, 100, 25, tab_window_2, (HMENU) CLEAR_DATA, instance_handle, NULL);
SetParent(tab_window_2, window_handle);
SetCursor(LoadCursor(NULL, IDC_ARROW));
SetWindowLongPtr(window_handle, GWL_USERDATA, (LONG)this);
ShowWindow(window_handle, SW_SHOW);
UpdateWindow(window_handle);
}
~frame_window() {
UnregisterClass(window_class_name, instance_handle);
}
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// Windows main entry point
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
int WINAPI wWinMain(HINSTANCE instance_handle, HINSTANCE, LPWSTR, INT) {
frame_window main_window("my base window");
main_window.run();
return 0;
}
The WM_COMMAND for handling the events from a Push Button must be in the Window Procedure of it's parent. Your code show tab_window_2 as the parent for the CLEAR_DATA button.
Move the WM_COMMAND code in the right Window Proc (or Dialog Proc).
EDIT: I think you didn't understand how the Tab Control works.
If you want each Tab View to contain more than a dummy static control, you should use modeless and borderless DialogBox, children of the Tab Control, and show/hide them accordingly to the TCN_SELCHANGE notification.
If you don't realy need the Dialog Box functionalities, you could create regular child windows (with RegisterClass/CreateWindow) and then add your controls as child windows.
In either case, the show/hide code must react to TCN_SELCHANGE.
With regular child windows or with Dialog Box the net effect is the same: you will have a Window Procedure, or a Dialog Procadure, in which WM_COMMAND will deal with user actions.
Don't play with SetParent. But, if you absolutly want it, then use:
HWND hWndPushClear = CreateWindowEx(NULL,"button", "Clear", [...]
SetParent( hWndPushClear, window_handle );
You may have weird Paint/Focus problems, however.
EDIT: Unfortunately, it seems that there is no good Tab Control tutorial in line.
I recommend updating your actual source code. As a starting point, do the following:
Replace CreateWindowEx by CreateDialog for tab_window_1 end tab_window_2 (only one Dialog must be visible)
Suppress all SetParent calls
Your CreateWindowEx() and WM_COMMAND code are seems correct.
So,
Check return value of CreateWindowEx() whether is NULL. NULL means which is failed to create control.
Check HWND of parent is valid. In your code, it is tab_window_2.
Also, if you do not want to any WS_EX_XXX style, you can just use CreateWindow().
This code is just sample.
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM lParam)
{
switch(iMessage) {
case WM_CREATE:
CreateWindow("button","Clear",WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
20,20,100,25,hWnd,(HMENU)0,g_hInst,NULL);
return 0;
case WM_COMMAND:
switch(LOWORD(wParam))
{
case 0:
MessageBox(hWnd,"Clear Button Clicked","Button",MB_OK);
break;
}
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}

How to create a resizable CDialog in MFC?

I have to create a dialog based application, instead of old CFormView type of design. But CDialog produces fixed-size dialogs. How can I create dialog based applications with resizable dialogs?
In the RC resource file if the dialog has this style similar to this it will be fixed size:
IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU
If the dialog has this style it will be sizeable:
IDD_DIALOG_DIALOG DIALOGEX 0, 0, 320, 201
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME
With these sizable frame options the dialog will be re-sizeable but you will still need to do a lot of work handling the WM_SIZE message to manage the sizing an positioning of the controls within the dialog.
In addition to setting the style to WS_THICKFRAME, you'll probably also want to have a system to move and resize the controls in a dialog as the dialog is resized. For my own personal use I've created a base class to replace CDialog that has this capability. Derive from this class and in your InitDialog function call the AutoMove function for each child control to define how much it should move and how much it should resize relative to the parent dialog. The size of the dialog in the resource file is used as a minimum size.
BaseDialog.h:
#if !defined(AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_)
#define AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_
#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#include <vector>
class CBaseDialog : public CDialog
{
// Construction
public:
CBaseDialog(UINT nIDTemplate, CWnd* pParent = NULL); // standard constructor
void AutoMove(int iID, double dXMovePct, double dYMovePct, double dXSizePct, double dYSizePct);
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CBaseDialog)
protected:
//}}AFX_VIRTUAL
protected:
//{{AFX_MSG(CBaseDialog)
virtual BOOL OnInitDialog();
afx_msg void OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI);
afx_msg void OnSize(UINT nType, int cx, int cy);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
public:
bool m_bShowGripper; // ignored if not WS_THICKFRAME
private:
struct SMovingChild
{
HWND m_hWnd;
double m_dXMoveFrac;
double m_dYMoveFrac;
double m_dXSizeFrac;
double m_dYSizeFrac;
CRect m_rcInitial;
};
typedef std::vector<SMovingChild> MovingChildren;
MovingChildren m_MovingChildren;
CSize m_szInitial;
CSize m_szMinimum;
HWND m_hGripper;
};
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
#endif // !defined(AFX_BASEDIALOG_H__DF4DE489_4474_4759_A14E_EB3FF0CDFBDA__INCLUDED_)
BaseDialog.cpp:
#include "stdafx.h"
#include "BaseDialog.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
CBaseDialog::CBaseDialog(UINT nIDTemplate, CWnd* pParent /*=NULL*/)
: CDialog(nIDTemplate, pParent),
m_bShowGripper(true),
m_szMinimum(0, 0),
m_hGripper(NULL)
{
}
BEGIN_MESSAGE_MAP(CBaseDialog, CDialog)
//{{AFX_MSG_MAP(CBaseDialog)
ON_WM_GETMINMAXINFO()
ON_WM_SIZE()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CBaseDialog::AutoMove(int iID, double dXMovePct, double dYMovePct, double dXSizePct, double dYSizePct)
{
ASSERT((dXMovePct + dXSizePct) <= 100.0); // can't use more than 100% of the resize for the child
ASSERT((dYMovePct + dYSizePct) <= 100.0); // can't use more than 100% of the resize for the child
SMovingChild s;
GetDlgItem(iID, &s.m_hWnd);
ASSERT(s.m_hWnd != NULL);
s.m_dXMoveFrac = dXMovePct / 100.0;
s.m_dYMoveFrac = dYMovePct / 100.0;
s.m_dXSizeFrac = dXSizePct / 100.0;
s.m_dYSizeFrac = dYSizePct / 100.0;
::GetWindowRect(s.m_hWnd, &s.m_rcInitial);
ScreenToClient(s.m_rcInitial);
m_MovingChildren.push_back(s);
}
BOOL CBaseDialog::OnInitDialog()
{
CDialog::OnInitDialog();
// use the initial dialog size as the default minimum
if ((m_szMinimum.cx == 0) && (m_szMinimum.cy == 0))
{
CRect rcWindow;
GetWindowRect(rcWindow);
m_szMinimum = rcWindow.Size();
}
// keep the initial size of the client area as a baseline for moving/sizing controls
CRect rcClient;
GetClientRect(rcClient);
m_szInitial = rcClient.Size();
// create a gripper in the bottom-right corner
if (m_bShowGripper && ((GetStyle() & WS_THICKFRAME) != 0))
{
SMovingChild s;
s.m_rcInitial.SetRect(-GetSystemMetrics(SM_CXVSCROLL), -GetSystemMetrics(SM_CYHSCROLL), 0, 0);
s.m_rcInitial.OffsetRect(rcClient.BottomRight());
m_hGripper = CreateWindow(_T("Scrollbar"), _T("size"), WS_CHILD | WS_VISIBLE | SBS_SIZEGRIP,
s.m_rcInitial.left, s.m_rcInitial.top, s.m_rcInitial.Width(), s.m_rcInitial.Height(),
m_hWnd, NULL, AfxGetInstanceHandle(), NULL);
ASSERT(m_hGripper != NULL);
if (m_hGripper != NULL)
{
s.m_hWnd = m_hGripper;
s.m_dXMoveFrac = 1.0;
s.m_dYMoveFrac = 1.0;
s.m_dXSizeFrac = 0.0;
s.m_dYSizeFrac = 0.0;
m_MovingChildren.push_back(s);
// put the gripper first in the z-order so it paints first and doesn't obscure other controls
::SetWindowPos(m_hGripper, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
}
}
return TRUE; // return TRUE unless you set the focus to a control
}
void CBaseDialog::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
CDialog::OnGetMinMaxInfo(lpMMI);
if (lpMMI->ptMinTrackSize.x < m_szMinimum.cx)
lpMMI->ptMinTrackSize.x = m_szMinimum.cx;
if (lpMMI->ptMinTrackSize.y < m_szMinimum.cy)
lpMMI->ptMinTrackSize.y = m_szMinimum.cy;
}
void CBaseDialog::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
int iXDelta = cx - m_szInitial.cx;
int iYDelta = cy - m_szInitial.cy;
HDWP hDefer = NULL;
for (MovingChildren::iterator p = m_MovingChildren.begin(); p != m_MovingChildren.end(); ++p)
{
if (p->m_hWnd != NULL)
{
CRect rcNew(p->m_rcInitial);
rcNew.OffsetRect(int(iXDelta * p->m_dXMoveFrac), int(iYDelta * p->m_dYMoveFrac));
rcNew.right += int(iXDelta * p->m_dXSizeFrac);
rcNew.bottom += int(iYDelta * p->m_dYSizeFrac);
if (hDefer == NULL)
hDefer = BeginDeferWindowPos(m_MovingChildren.size());
UINT uFlags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER;
if ((p->m_dXSizeFrac != 0.0) || (p->m_dYSizeFrac != 0.0))
uFlags |= SWP_NOCOPYBITS;
DeferWindowPos(hDefer, p->m_hWnd, NULL, rcNew.left, rcNew.top, rcNew.Width(), rcNew.Height(), uFlags);
}
}
if (hDefer != NULL)
EndDeferWindowPos(hDefer);
if (m_hGripper != NULL)
::ShowWindow(m_hGripper, (nType == SIZE_MAXIMIZED) ? SW_HIDE : SW_SHOW);
}
Since Visual Studio 2015, you can use MFC Dynamic Dialog Layout, but it seems, there is no way to restrict dialog size to minimal size (still only the old way by handling WM_GETMINMAXINFO).
Dynamic layout can be done:
at design time in resource editor by selecting the control and setting the Moving Type and Sizing Type properties (this emits new AFX_DIALOG_LAYOUT section into .rc file);
or programatically using the CMFCDynamicLayout class.
Documentation: Dynamic Layout
If your using a dialog template then open the dialog template in the resource editor and set the Style property to Popup and the Border property to Resizing. I'm pretty sure this will do the same as what jussij said and set the WS_POPUP and WS_THICKFRAME styles. To set these dynamically then override the PreCreateWindow function and add the following:
cs.style |= WS_POPUP | WS_THICKFRAME;
There is no easy way to do this. Basically, you will need to dynamically layout controls when the window size is changed.
See http://www.codeproject.com/KB/dialog/resizabledialog.aspx for an example
I have some blog instructions on how to create a very minimalist re-sizeable dialog in MFC.
It is basically an implementation of Paulo Messina's posting at CodeProject
but with as much extraneous stuff removed as possible, just to help clarify how to do it better.
It is fairly straightforward to implement once you've had a bit of practice: the important bits are to:
i. ensure you have his CodeProject libraries etc pulled into your project and it all compiles correctly.
ii. do the extra initialization required inside the OnInitDialog method: make the gripper visible, set the maximum dilog size, add anchor points to the dialog control items that you wish to 'stretch' etc.
iii. Replace usage of CDialog with CResizableDialog at the appropriate points: in the dialog class definition, constructor, DoDataExchange, BEGIN_MESSAGE_MAP, OnInitDialog etc.
I've tried many MFC layout libraries and found this one the best: http://www.codeproject.com/KB/dialog/layoutmgr.aspx. Check out the comments there for some bug fixes and improvements (disclaimer: some of them by me ;) ). When you use this library, setting the correct resize flags on your window will be handled for you.

Resources