Borderless Window with Drop Shadow - c++

I'm trying to achieve something like Visual Studio installer does with borderless window and drop shadow:
I tried various options like CS_DROPSHADOW and DWM API, but as soon as I apply the WS_THICKFRAME style the shadow disappears.
This is my code for creating and centering a window:
RECT R = {0, 0, _clientWidth, _clientHeight};
AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
_mainWnd = CreateWindow(L"D3DWndClassName", _mainWndCaption.c_str(), WS_OVERLAPPEDWINDOW, 100, 100, R.right, R.bottom, nullptr, nullptr, _appInst, nullptr);
if(!_mainWnd){
MessageBox(nullptr, L"CreateWindow FAILED", nullptr, 0);
PostQuitMessage(0);
}
RECT rc;
GetWindowRect(_mainWnd, &rc);
LONG lStyle = GetWindowLong(_mainWnd, GWL_STYLE);
lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU );
SetWindowLong(_mainWnd, GWL_STYLE, lStyle);
int xPos = (GetSystemMetrics(SM_CXSCREEN) - rc.right) / 2;
int yPos = (GetSystemMetrics(SM_CYSCREEN) - rc.bottom) / 2;
SetWindowPos(_mainWnd, 0, xPos, yPos, _clientWidth, _clientHeight, SWP_NOZORDER);
ShowWindow(_mainWnd, SW_SHOW);
UpdateWindow(_mainWnd);

You can create this effect by using a combination of DwmExtendFrameIntoClientArea() and setting 0 as the message result of WM_NCCALCSIZE if wParam is TRUE. Detailed steps below.
Window style should be such that normally the whole frame would be shown (WS_CAPTION|WS_POPUP works well for me), but don't include any of WS_MINIMIZE, WS_MAXIMIZE, WS_SYSMENU.
Call DwmExtendFrameIntoClientArea() with MARGINS{0,0,0,1}. We don't really want a transparent frame, so setting the bottom margin only is enough.
Call SetWindowPos(hWnd, nullptr, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOOWNERZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_FRAMECHANGED) to let the system recalculate NC area.
Return 0 from WM_NCCALCSIZE if wParam is TRUE. This has the effect of extending the client area to the window size including frame, but excluding the shadow. See remarks section of the documentation.
In WM_PAINT draw your frame and content area as you like but make sure to use an opaque alpha channel (value of 255) for the margin area defined by the DwmExtendFrameIntoClientArea() call. Otherwise part of regular frame would be visible in this area. You may use GDI+ for that as most regular GDI functions ignore alpha channel. BitBlt() with a 32bpp source bitmap containing an opaque alpha channel also works.
You may handle WM_NCHITTEST if you want a resizable window.
The effect of all this is, that you paint "over" the regular window frame which is now inside the client area due to the DWM calls, but keep the regular window shadow. Don't worry the "paint over" doesn't create any flickering, even if you make the window resizable.
You can put any standard or user-defined controls into this window. Just make sure child controls don't overlap the margin defined by the DwmExtendFrameIntoClientArea() call because most GDI-based controls ignore the alpha channel.
Here is a minimal, self-contained example application:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <dwmapi.h>
#include <unknwn.h>
#include <gdiplus.h>
#pragma comment( lib, "dwmapi" )
#pragma comment( lib, "gdiplus" )
namespace gdip = Gdiplus;
INT_PTR CALLBACK MyDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam );
int APIENTRY wWinMain( _In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow )
{
// Initialize GDI+
gdip::GdiplusStartupInput gdiplusStartupInput;
ULONG_PTR gdipToken = 0;
gdip::GdiplusStartup( &gdipToken, &gdiplusStartupInput, nullptr );
struct MyDialog : DLGTEMPLATE {
WORD dummy[ 3 ] = { 0 }; // unused menu, class and title
}
dlg;
dlg.style = WS_POPUP | WS_CAPTION | DS_CENTER;
dlg.dwExtendedStyle = 0;
dlg.cdit = 0; // no controls in template
dlg.x = 0;
dlg.y = 0;
dlg.cx = 300; // width in dialog units
dlg.cy = 200; // height in dialog units
DialogBoxIndirectW( hInstance, &dlg, nullptr, MyDialogProc );
gdip::GdiplusShutdown( gdipToken );
return 0;
}
INT_PTR CALLBACK MyDialogProc( HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam )
{
switch( message )
{
case WM_INITDIALOG:
{
SetWindowTextW( hDlg, L"Borderless Window with Shadow" );
// This plays together with WM_NCALCSIZE.
MARGINS m{ 0, 0, 0, 1 };
DwmExtendFrameIntoClientArea( hDlg, &m );
// Force the system to recalculate NC area (making it send WM_NCCALCSIZE).
SetWindowPos( hDlg, nullptr, 0, 0, 0, 0,
SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED );
return TRUE;
}
case WM_NCCALCSIZE:
{
// Setting 0 as the message result when wParam is TRUE removes the
// standard frame, but keeps the window shadow.
if( wParam == TRUE )
{
SetWindowLong( hDlg, DWLP_MSGRESULT, 0 );
return TRUE;
}
return FALSE;
}
case WM_PAINT:
{
PAINTSTRUCT ps{ 0 };
HDC hdc = BeginPaint( hDlg, &ps );
// Draw with GDI+ to make sure the alpha channel is opaque.
gdip::Graphics gfx{ hdc };
gdip::SolidBrush brush{ gdip::Color{ 255, 255, 255 } };
gfx.FillRectangle( &brush,
static_cast<INT>( ps.rcPaint.left ), static_cast<INT>( ps.rcPaint.top ),
static_cast<INT>( ps.rcPaint.right - ps.rcPaint.left ), static_cast<INT>( ps.rcPaint.bottom - ps.rcPaint.top ) );
EndPaint( hDlg, &ps );
return TRUE;
}
case WM_NCHITTEST:
{
// Setting HTCAPTION as the message result allows the user to move
// the window around by clicking anywhere within the window.
// Depending on the mouse coordinates passed in LPARAM, you may
// set other values to enable resizing.
SetWindowLong( hDlg, DWLP_MSGRESULT, HTCAPTION );
return TRUE;
}
case WM_COMMAND:
{
WORD id = LOWORD( wParam );
if( id == IDOK || id == IDCANCEL )
{
EndDialog( hDlg, id );
return TRUE;
}
return FALSE;
}
}
return FALSE; // return FALSE to let DefDialogProc handle the message
}

Related

Create custom window resize functionality

I'm trying to create a borderless, captionless, and resizeable without using the WS_THICKFRAME, WS_BORDER and WS_SIZEBOX styles.
I don't want to use these styles because they create a border but more importantly, when I use the SetWindowCompositionAttribute function to enable an acrylic blur behind on the window, the blur goes past the window for some reason .
I checked out this repo and I'm currently using their hit testing logic.
So essentially I believe I need to implement my own resizing ability but I don't know where to really start.
There is a lot of nuance to exactly replicating all standard window resizing behavior but the main thing you need to do is implement your own handler of WM_NCHITTEST, which basically tells Windows which part of the window a given point is in.
Here's an example that will allow dragging via a custom title bar area and resizing via dragging the left, right, and bottom of the window.
#include <windows.h>
#include <tuple>
#include "windowsx.h"
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
MSG msg = { 0 };
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND);
wc.lpszClassName = L"customwindowresizing";
if (!RegisterClass(&wc))
return 1;
if (!CreateWindow(wc.lpszClassName,
L"",
WS_POPUP | WS_VISIBLE,
0, 0, 640, 480, 0, 0, hInstance, NULL))
return 2;
while (GetMessage(&msg, NULL, 0, 0) > 0)
DispatchMessage(&msg);
return 0;
}
LRESULT HandleNonclientHitTest(HWND wnd, LPARAM lparam, int title_bar_hgt, int resizing_border_wd)
{
RECT wnd_rect;
GetWindowRect(wnd, &wnd_rect);
int wd = wnd_rect.right - wnd_rect.left;
int hgt = wnd_rect.bottom - wnd_rect.top;
RECT title_bar = { 0,0, wd, title_bar_hgt };
RECT left = { 0, title_bar_hgt , resizing_border_wd , hgt - title_bar_hgt - resizing_border_wd };
RECT right = {wd - resizing_border_wd , title_bar_hgt , wd, hgt - title_bar_hgt - resizing_border_wd };
RECT bottom = { 0, hgt - resizing_border_wd, wd, hgt };
std::tuple<RECT, LRESULT> rects[] = {
{title_bar, HTCAPTION},
{left, HTLEFT},
{right, HTRIGHT},
{bottom, HTBOTTOM}
};
POINT pt = { GET_X_LPARAM(lparam) - wnd_rect.left, GET_Y_LPARAM(lparam) - wnd_rect.top };
for (const auto& [r, code] : rects) {
if (PtInRect(&r, pt))
return code;
}
return HTCLIENT;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CLOSE:
PostQuitMessage(0);
break;
case WM_NCHITTEST:
return HandleNonclientHitTest(hWnd, lParam, 25, 10);
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
So essentially I believe I need to implement my own resizing ability
Actually, you don't. If you only want to resize via the bottom-right hand corner of the window, just catch WM_NCHITTEST and return HTGROWBOX when the mouse is in the area of the window that you consider to be your resizing handle.
Other, more complex, return values from WM_NCHITTEST are possible (including making it possible for the user to drag the window around on the screen), but you get the idea.

How to place a toolbar inside an edit control?

My original goal was to place an X button inside an edit control (it later turned out that this edit control by itself must be a part of the combo box.) I've been suggested to use this article as a guidance.
To make it look a little bit "prettier" I decided to use a toolbar for that X button instead. So I came up with the following code. Here's how the control is initialized inside the ComboBox:
//Initialization
#define EDIT_X_TOOLBAR_BTN_W 10
#define EDIT_X_TOOLBAR_BTN_H 11
//Find edit control handle using GetComboBoxInfo() API
COMBOBOXINFO cbi2 = {0};
cbi2.cbSize = sizeof(cbi2);
GetComboBoxInfo(hComboWnd, &cbi2);
HWND hEditCtrl = cbi2.hwndItem;
HINSTANCE hInst = ::GetModuleHandle(NULL);
//Create toolbar as a child of the edit control
hWndToolbar = ::CreateWindowEx(0, TOOLBARCLASSNAME, NULL,
WS_CHILD | WS_VISIBLE |
CCS_NODIVIDER | CCS_NOPARENTALIGN |
CCS_NORESIZE | CCS_NOMOVEY | TBSTYLE_FLAT | TBSTYLE_TRANSPARENT,
0, 0, 0, 0,
hEditCtrl,
(HMENU)ID_CMD_EDIT_X_TOOLBAR, hInst, 0);
::SendMessage(hWndToolbar, TB_BUTTONSTRUCTSIZE, (WPARAM) sizeof(TBBUTTON), 0);
::SendMessage(hWndToolbar, TB_SETBITMAPSIZE, 0, MAKELONG(EDIT_X_TOOLBAR_BTN_W, EDIT_X_TOOLBAR_BTN_H));
::SendMessage(hWndToolbar, TB_SETBUTTONSIZE, 0, MAKELONG(EDIT_X_TOOLBAR_BTN_W, EDIT_X_TOOLBAR_BTN_H));
//Load bitmap
TBADDBITMAP tbab = {0};
tbab.hInst = hInst;
tbab.nID = IDB_BITMAP_TOOLBAR_X;
VERIFY(::SendMessage(hWndToolbar, TB_ADDBITMAP, 1, (LPARAM)&tbab) != -1);
TBBUTTON tbbs[1] = {0};
tbbs[0].iBitmap = 0;
tbbs[0].idCommand = CMD_EDIT_X_TOOLBAR_CLOSE;
tbbs[0].fsState = TBSTATE_ENABLED;
tbbs[0].fsStyle = TBSTYLE_BUTTON;
tbbs[0].iString = -1;
VERIFY(::SendMessage(hWndToolbar, TB_ADDBUTTONS, SIZEOF(tbbs), (LPARAM)tbbs));
//Subclass edit control
oldProc = (WNDPROC)::SetWindowLong(hEditCtrl, GWL_WNDPROC, (LONG)wndProcEditCtrl);
//And redraw the edit control
::SetWindowPos(hEditCtrl, NULL, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOZORDER);
And this is the subclassed window proc for the edit control:
CRect rcToolbar;
LRESULT CALLBACK wndProcEditCtrl(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_NCCALCSIZE:
{
if(wParam)
{
//First let edit box process it
::CallWindowProc(oldProc, hWnd, msg, wParam, lParam);
RECT* pRc = (RECT*)lParam;
//Define toolbar size & move it
int nTB_w = pRc->bottom - pRc->top;
int nTB_h = nTB_w;
//Adjust the edit's client area
pRc->right -= nTB_w;
//Set toolbar rect
rcToolbar.SetRect(pRc->right, pRc->top, pRc->right + nTB_w, pRc->top + nTB_h);
//And move the toolbar
::MoveWindow(hWndToolbar, pRc->right, pRc->top, nTB_w, nTB_h, TRUE);
return 0;
}
}
break;
case WM_NCHITTEST:
{
POINT pnt;
pnt.x = GET_X_LPARAM(lParam);
pnt.y = GET_Y_LPARAM(lParam);
if(::ScreenToClient(hWnd, &pnt))
{
if(rcToolbar.PtInRect(pnt))
{
return HTBORDER;
}
}
}
break;
}
return ::CallWindowProc(oldProc, hWnd, msg, wParam, lParam);
}
The issue is that I can see that the toolbar is created (via Spy++) but it remains inactive and I don't see my button (all I get is a gray rectangle on the right):
To make sure that I can create the toolbar itself, if I change its parent window from the edit control (i.e. hEditCtrl) to the main dialog window, it is displayed (in a wrong place) and responds to clicks just fine. So my conclusion is that I must be blocking some messages to it in my subclass. The question is which ones?
Any idea what am I missing here?

Win32: Add black borders to fullscreen window

I am trying to preserve content aspect ratio in fullscreen mode in Windows. I'd like to hide the rest of the desktop behind black borders if the display aspect ratio differs from the content aspect ratio. Is it possible to create fullscreen window with centered content and black borders with Win32 api?
In OS X this can be achieved quite easily with the following code:
CGSize ar;
ar.width = 800;
ar.height = 600;
[self.window setContentAspectRatio:ar];
[self.window center];
[self.window toggleFullScreen:nil];
If I run the above code in 16:9 display, my app goes to fullscreen mode, the content is centered (since it is 4:3) and I have black borders on both sides of the screen.
I have tried to implement the same functionality in Windows but I begin to wonder if it is even possible. My current fullscreen code maintains the aspect ratio and
centers the content, but shows the desktop on the both sides of the window if the fullscreenWidth and fullscreenHeight are not equal to displayWidth and displayHeight:
bool enterFullscreen(int fullscreenWidth, int fullscreenHeight)
{
DEVMODE fullscreenSettings;
bool isChangeSuccessful;
int displayWidth = GetDeviceCaps(m_hDC, HORZRES);
int displayHeight = GetDeviceCaps(m_hDC, VERTRES);
int colourBits = GetDeviceCaps(m_hDC, BITSPIXEL);
int refreshRate = GetDeviceCaps(m_hDC, VREFRESH);
EnumDisplaySettings(NULL, 0, &fullscreenSettings);
fullscreenSettings.dmPelsWidth = fullscreenWidth;
fullscreenSettings.dmPelsHeight = fullscreenHeight;
fullscreenSettings.dmBitsPerPel = colourBits;
fullscreenSettings.dmDisplayFrequency = refreshRate;
fullscreenSettings.dmFields = DM_PELSWIDTH |
DM_PELSHEIGHT |
DM_BITSPERPEL |
DM_DISPLAYFREQUENCY;
SetWindowLongPtr(m_hWnd, GWL_EXSTYLE, WS_EX_APPWINDOW | WS_EX_TOPMOST);
SetWindowLongPtr(m_hWnd, GWL_STYLE, WS_POPUP | WS_VISIBLE);
SetWindowPos(m_hWnd, HWND_TOPMOST, 0, 0, displayWidth, displayHeight, SWP_SHOWWINDOW);
isChangeSuccessful = ChangeDisplaySettings(&fullscreenSettings, CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL;
ShowWindow(m_hWnd, SW_MAXIMIZE);
RECT rcWindow;
GetWindowRect(m_hWnd, &rcWindow);
// calculate content position
POINT ptDiff;
ptDiff.x = ((rcWindow.right - rcWindow.left) - fullscreenWidth) / 2;
ptDiff.y = ((rcWindow.bottom - rcWindow.top) - fullscreenHeight) / 2;
AdjustWindowRectEx(&rcWindow, GetWindowLong(m_hWnd, GWL_STYLE), FALSE, GetWindowLong(m_hWnd, GWL_EXSTYLE));
SetWindowPos(m_hWnd, 0, ptDiff.x, ptDiff.y, displayWidth, displayHeight, NULL);
return isChangeSuccessful;
}
The easiest way to accomplish what you are looking for is to create a child window (C) to render your content, leaving any excess space to the parent (P).
P should be created using a black brush for its background. Specify (HBRUSH)GetStockObject(BLACK_BRUSH) for the hbrBackground member of the WNDCLASS structure when registering the window class (RegisterClass). To prevent flicker while erasing the background, P should have the WS_CLIPCHILDREN Window Style.
Whenever P changes its size, a WM_SIZE message is sent to P's window procedure. The handler can then adjust C's position and size to maintain the aspect ratio.
To create a borderless child window C, use the WS_CHILD | WS_VISIBLE window styles in the call to CreateWindow. If you want to handle mouse input in the parent P instead, add the WS_DISABLED window style.
Sample code (error checking elided for brevity):
#define STRICT 1
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// Globals
HWND g_hWndContent = NULL;
// Forward declarations
LRESULT CALLBACK WndProcMain( HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK WndProcContent( HWND, UINT, WPARAM, LPARAM );
int APIENTRY wWinMain( HINSTANCE hInstance,
HINSTANCE /*hPrevInstance*/,
LPWSTR /*lpCmdLine*/,
int nCmdShow ) {
Both main and content window classes need to be registered. The registration is almost identical, with the exception of the background brush. The content window uses a white brush so that it's visible without any additional code:
// Register main window class
const wchar_t classNameMain[] = L"MainWindow";
WNDCLASSEXW wcexMain = { sizeof( wcexMain ) };
wcexMain.style = CS_HREDRAW | CS_VREDRAW;
wcexMain.lpfnWndProc = WndProcMain;
wcexMain.hCursor = ::LoadCursorW( NULL, IDC_ARROW );
wcexMain.hbrBackground = (HBRUSH)::GetStockObject( BLACK_BRUSH );
wcexMain.lpszClassName = classNameMain;
::RegisterClassExW( &wcexMain );
// Register content window class
const wchar_t classNameContent[] = L"ContentWindow";
WNDCLASSEXW wcexContent = { sizeof( wcexContent ) };
wcexContent.style = CS_HREDRAW | CS_VREDRAW;
wcexContent.lpfnWndProc = WndProcContent;
wcexContent.hCursor = ::LoadCursorW( NULL, IDC_ARROW );
wcexContent.hbrBackground = (HBRUSH)::GetStockObject( WHITE_BRUSH );
wcexContent.lpszClassName = classNameContent;
::RegisterClassExW( &wcexContent );
With the window classes registered we can move on and create an instance of each. Note that the content window is initially zero-sized. The actual size is calculated in the parent's WM_SIZE handler further down.
// Create main window
HWND hWndMain = ::CreateWindowW( classNameMain,
L"Constant AR",
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT, CW_USEDEFAULT, 800, 800,
NULL,
NULL,
hInstance,
NULL );
// Create content window
g_hWndContent = ::CreateWindowW( classNameContent,
NULL,
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0,
hWndMain,
NULL,
hInstance,
NULL );
The remainder is boilerplate Windows application code:
// Show application
::ShowWindow( hWndMain, nCmdShow );
::UpdateWindow( hWndMain );
// Main message loop
MSG msg = { 0 };
while ( ::GetMessageW( &msg, NULL, 0, 0 ) > 0 )
{
::TranslateMessage( &msg );
::DispatchMessageW( &msg );
}
return (int)msg.wParam;
}
The behavior for a window class is implemented inside it's Window Procedure:
LRESULT CALLBACK WndProcMain( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
switch ( message ) {
case WM_CLOSE:
::DestroyWindow( hWnd );
return 0;
case WM_DESTROY:
::PostQuitMessage( 0 );
return 0;
default:
break;
In addition to standard message handling, the main window's window procedure resizes the content to fit whenever the main window's size changes:
case WM_SIZE: {
const SIZE ar = { 800, 600 };
// Query new client area size
int clientWidth = LOWORD( lParam );
int clientHeight = HIWORD( lParam );
// Calculate new content size
int contentWidth = ::MulDiv( clientHeight, ar.cx, ar.cy );
int contentHeight = ::MulDiv( clientWidth, ar.cy, ar.cx );
// Adjust dimensions to fit inside client area
if ( contentWidth > clientWidth ) {
contentWidth = clientWidth;
contentHeight = ::MulDiv( contentWidth, ar.cy, ar.cx );
} else {
contentHeight = clientHeight;
contentWidth = ::MulDiv( contentHeight, ar.cx, ar.cy );
}
// Calculate offsets to center content
int offsetX = ( clientWidth - contentWidth ) / 2;
int offsetY = ( clientHeight - contentHeight ) / 2;
// Adjust content window position
::SetWindowPos( g_hWndContent,
NULL,
offsetX, offsetY,
contentWidth, contentHeight,
SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER );
return 0;
}
}
return ::DefWindowProcW( hWnd, message, wParam, lParam );
}
The content window's window procedure doesn't implement any custom behavior, and simply forwards all messages to the default implementation:
LRESULT CALLBACK WndProcContent( HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam ) {
return ::DefWindowProcW( hWnd, message, wParam, lParam );
}

Common controls are not properly painted when I resize window

INTRODUCTION:
I am creating tab control with child dialog boxes as pages.
I have Visual Styles enabled via #pragma comment. I have also called InitCommonControlsEx and #pragma comment( lib, "comctl32.lib" ) as well.
Initially, when window loads, dialog and its common controls have proper background, please see image below:
During resizing things are not so consistent -> background starts to mismatches visibly. I will provide screenshot below:
You can clearly see that checkbox and static control have improper background, while it seems to me that dialog box ( created to act as a child control ) has proper background.
Edited on November 24th, 2014:
After enclosing controls into group boxes there seems to be no painting problems. My monitor is old CRT ( Samsung SyncMaster 753s ), and I have bad eyesight, but again, it seems that everything paints properly. The window still flickers horribly on resize, but I have tried everything in my power to fix it.
QUESTION:
How can I fix this?
MY EFFORTS TO SOLVE THIS:
I haven't found anything yet but I am still Goggling while typing this question...
RELEVANT INFORMATION:
Here are the instructions for creating demo that illustrates the problem:
1.) Create empty C++ project in Visual Studio;
2.) add header file, name it pomocne_funkcije.h and copy/paste following:
#include <windows.h>
#include <windowsx.h>
#include <comutil.h>
#include <commctrl.h>
#include <stdio.h>
#include <vector>
#include <ole2.h>
#include <string>
#include <stdlib.h>
#include <locale.h>
#include <Uxtheme.h>
#pragma comment( linker, "/manifestdependency:\"type='win32' \
name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
language='*'\"")
#pragma comment( lib, "comctl32.lib")
#pragma comment( lib,"Msimg32.lib")
#pragma comment( lib, "comsuppw.lib")
#pragma comment( lib, "UxTheme.lib")
3.) Create dialog box in resource editor, add checkbox static control.
Set the following for dialog box:
Border : none
Control : true
Control parent : true
Style : child
System menu : false
4.) Here is the code for main.cpp :
#include "pomocne_funkcije.h"
static HINSTANCE hInst;
// dialog procedure for firts tab
INT_PTR CALLBACK Ugovori(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
{
EnableThemeDialogTexture( hDlg, ETDT_ENABLETAB );
}
return (INT_PTR)TRUE;
}
return (INT_PTR)FALSE;
}
// main window procedure
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
static HWND hDlgFirstTab; // handle to the first page dialog box
switch(msg)
{
case WM_CREATE:
{
RECT rcClient = {0};
::GetClientRect( hwnd, &rcClient );
HWND hwndTab = CreateWindowEx( 0, WC_TABCONTROL,
L"Ugovori", WS_CHILD | WS_VISIBLE,
10, 10, rcClient.right - rcClient.left - 20,
rcClient.bottom - rcClient.top - 63,
hwnd, (HMENU)3000,
((LPCREATESTRUCT)lParam)->hInstance, 0 );
TCITEM tci = {0};
tci.mask = TCIF_TEXT;
tci.pszText = L"Основни подаци";
TabCtrl_InsertItem( hwndTab, 0, &tci );
// set font so cyrilic symbols can be properly displayed instead of ???
SendMessage( hwnd, WM_SETFONT,
(WPARAM)(HFONT)GetStockObject(DEFAULT_GUI_FONT),
(LPARAM)TRUE );
SendMessage( hwndTab, WM_SETFONT,
(WPARAM)(HFONT)GetStockObject(DEFAULT_GUI_FONT),
(LPARAM)TRUE );
// create page ( dialog box )
hDlgFirstTab = CreateDialog( ((LPCREATESTRUCT)lParam)->hInstance,
MAKEINTRESOURCE(IDD_DIALOG1),
hwnd,
(DLGPROC)Ugovori ); // dialog procedure
ShowWindow( hDlgFirstTab, SW_SHOW );
}
return 0L;
case WM_MOVE:
case WM_MOVING:
case WM_SIZING:
case WM_SIZE:
{
RECT rcClient = {0};
GetClientRect( hwnd, &rcClient );
SetWindowPos( GetDlgItem( hwnd, 3000 ), NULL,
rcClient.left + 10, // move it away from window edge by 10 pixels
rcClient.top + 10, // move it away from window edge by 10 pixels
rcClient.right - rcClient.left - 20,
// - 63 was the size of the button,
// but I have deleted that button here to preserve space
rcClient.bottom - rcClient.top - 63,
SWP_NOZORDER | SWP_NOCOPYBITS );
// get tab control's client rectangle
GetClientRect( GetDlgItem( hwnd, 3000 ), &rcTab );
//============= place dialog box into tab's client area
MapWindowPoints( GetDlgItem( hwnd, 3000 ), hwnd,
(LPPOINT)(&rcTab), 2 );
// get tab's display area
TabCtrl_AdjustRect( GetDlgItem( hwnd, 3000 ),
FALSE, &rcTab );
// move dialog box
SetWindowPos(hDlgFirstTab, NULL,
rcTab.left, rcTab.top,
rcTab.right - rcTab.left,
rcTab.bottom - rcTab.top,
SWP_NOZORDER);
//========================= done
// repaint window
InvalidateRect( hwnd, NULL, FALSE );
}
return 0L;
case WM_ERASEBKGND:
return 1L;
case WM_PAINT:
{
PAINTSTRUCT ps = {0};
HDC hdc = BeginPaint( hwnd, &ps );
SendMessage( hwnd, WM_PRINTCLIENT, (WPARAM)hdc, 0 );
EndPaint( hwnd, &ps );
}
return 0L;
case WM_PRINTCLIENT:
{
RECT rcClient = {0};
GetClientRect( hwnd, &rcClient );
FillRect( (HDC)wParam, &rcClient,
(HBRUSH)GetStockObject(WHITE_BRUSH) );
}
return 0L;
case WM_CLOSE:
::DestroyWindow(hwnd);
return 0L;
case WM_DESTROY:
::PostQuitMessage(0);
return 0L;
default:
return ::DefWindowProc( hwnd, msg, wParam, lParam );
}
return 0;
}
// WinMain
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
int nCmdShow)
{
// store hInstance in global variable for later use
hInst = hInstance;
WNDCLASSEX wc;
HWND hwnd;
MSG Msg;
// initialize common controls
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_LISTVIEW_CLASSES | ICC_UPDOWN_CLASS |
ICC_STANDARD_CLASSES | ICC_TAB_CLASSES;
InitCommonControlsEx(&iccex);
// register main window class
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = 0;
wc.lpfnWndProc = WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadIcon( hInstance, IDI_APPLICATION );
wc.hCursor = LoadCursor( NULL, IDC_ARROW );
wc.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
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"Contract manager",
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL, NULL, hInstance, 0 );
if(hwnd == NULL)
{
MessageBox(NULL, L"Nemogu da napravim prozor!", L"Greska!",
MB_ICONEXCLAMATION | MB_OK);
return 0;
}
ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
I am working in Visual Studio 2008 on Windows XP using C++ and WinAPI.
Your tab dialogs are below the tab control in the Z order. This causes crazy overdraw issues on first resizing. After adding the tab dialogs as childs to your main window, call SetWindowPos(tab, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE) to move them to the top of the Z order.
Had this exact issue; took me days to figure it out. I created a simple application in Visual Studio:
manifest reference to ComCtrl v 6
InitCommonControlsEx()
a dialog with a tab control
a sub-dialog for the tab content (sibling to the tab)
in the sub-dialog, a radio button and a static
no fancy handling of WM_PRINTCLIENT or anything alike
just EnableThemeDialogTexture() in WM_INITDIALOG of the sub-dialog
I went to check it on XP and … it worked perfect and looked beautiful, apart from flickering.
I added WS_CLIPCHILDREN and suddenly was able to reproduce your screenshots perfectly. I was first thinking this was the cause.
I kept pushing and added WS_COMPOSITED to prevent flickering, and the tab window suddenly was gone – entirely covered by the tab’s background. Now I realized the Z order was at fault.
Child windows are always created at the bottom of the Z order, i.e. below your tab control. Your code never moves them upwards. The tabs only ever displayed by pure luck/overdraw, producing the artifacts you observe. Fix the Z order and there will be no more artifacts.

Show tooltip on invalid input in edit control

I have subclassed edit control to accept only floating numbers. I would like to pop a tooltip when user makes an invalid input. The behavior I target is like the one edit control with ES_NUMBER has :
So far I was able to implement tracking tooltip and display it when user makes invalid input.
However, the tooltip is misplaced. I have tried to use ScreenToClient and ClientToScreen to fix this but have failed.
Here are the instructions for creating SCCE :
1) Create default Win32 project in Visual Studio.
2) Add the following includes in your stdafx.h, just under #include <windows.h> :
#include <windowsx.h>
#include <commctrl.h>
#pragma comment( lib, "comctl32.lib")
#pragma comment(linker, \
"\"/manifestdependency:type='Win32' "\
"name='Microsoft.Windows.Common-Controls' "\
"version='6.0.0.0' "\
"processorArchitecture='*' "\
"publicKeyToken='6595b64144ccf1df' "\
"language='*'\"")
3) Add these global variables:
HWND g_hwndTT;
TOOLINFO g_ti;
4) Here is a simple subclass procedure for edit controls ( just for testing purposes ) :
LRESULT CALLBACK EditSubProc ( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch (message)
{
case WM_CHAR:
{
POINT pt;
if( ! isdigit( wParam ) ) // if not a number pop a tooltip!
{
if (GetCaretPos(&pt)) // here comes the problem
{
// coordinates are not good, so tooltip is misplaced
ClientToScreen( hwnd, &pt );
/************************** EDIT #1 ****************************/
/******* If I delete this line x-coordinate is OK *************/
/*** y-coordinate should be little lower, but it is still OK **/
/**************************************************************/
ScreenToClient( GetParent(hwnd), &pt );
/************************* Edit #2 ****************************/
// this adjusts the y-coordinate, see the second edit
RECT rcClientRect;
Edit_GetRect( hwnd, &rcClientRect );
pt.y = rcClientRect.bottom;
/**************************************************************/
SendMessage(g_hwndTT, TTM_TRACKACTIVATE,
TRUE, (LPARAM)&g_ti);
SendMessage(g_hwndTT, TTM_TRACKPOSITION,
0, MAKELPARAM(pt.x, pt.y));
}
return FALSE;
}
else
{
SendMessage(g_hwndTT, TTM_TRACKACTIVATE,
FALSE, (LPARAM)&g_ti);
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
}
break;
case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, EditSubProc, 0 );
return DefSubclassProc( hwnd, message, wParam, lParam);
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
5) Add the following WM_CREATE handler :
case WM_CREATE:
{
HWND hEdit = CreateWindowEx( 0, L"EDIT", L"edit", WS_CHILD | WS_VISIBLE |
WS_BORDER | ES_CENTER, 150, 150, 100, 30, hWnd, (HMENU)1000, hInst, 0 );
// try with tooltip
g_hwndTT = CreateWindow(TOOLTIPS_CLASS, NULL,
WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
0, 0, 0, 0, hWnd, NULL, hInst, NULL);
if( !g_hwndTT )
MessageBeep(0); // just to signal error somehow
g_ti.cbSize = sizeof(TOOLINFO);
g_ti.uFlags = TTF_TRACK | TTF_ABSOLUTE;
g_ti.hwnd = hWnd;
g_ti.hinst = hInst;
g_ti.lpszText = TEXT("Hi there");
if( ! SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&g_ti) )
MessageBeep(0); // just to have some error signal
// subclass edit control
SetWindowSubclass( hEdit, EditSubProc, 0, 0 );
}
return 0L;
6) Initialize common controls in MyRegisterClass ( before return statement ) :
// initialize common controls
INITCOMMONCONTROLSEX iccex;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
iccex.dwICC = ICC_BAR_CLASSES | ICC_WIN95_CLASSES |
ICC_TAB_CLASSES | ICC_TREEVIEW_CLASSES | ICC_STANDARD_CLASSES ;
if( !InitCommonControlsEx(&iccex) )
MessageBeep(0); // signal error
That's it, for the SSCCE.
My questions are following :
How can I properly position tooltip in my main window? How should I manipulate with caret coordinates?
Is there a way for tooltip handle and toolinfo structure to not be global?
Thank you for your time.
Best regards.
EDIT #1:
I have managed to achieve quite an improvement by deleting ScreenToClient call in the subclass procedure. The x-coordinate is good, y-coordinate could be slightly lower. I still would like to remove global variables somehow...
EDIT #2:
I was able to adjust y-coordinate by using EM_GETRECT message and setting y-coordinate to the bottom of the formatting rectangle:
RECT rcClientRect;
Edit_GetRect( hwnd, &rcClientRect );
pt.y = rcClient.bottom;
Now the end-result is much better. All that is left is to remove global variables...
EDIT #3:
It seems that I have cracked it! The solution is in EM_SHOWBALLOONTIP and EM_HIDEBALLOONTIP messages! Tooltip is placed at the caret position, ballon shape is the same as the one on the picture, and it auto-dismisses itself properly. And the best thing is that I do not need global variables!
Here is my subclass procedure snippet:
case WM_CHAR:
{
// whatever... This condition is for testing purpose only
if( ! IsCharAlpha( wParam ) && IsCharAlphaNumeric( wParam ) )
{
SendMessage(hwnd, EM_HIDEBALLOONTIP, 0, 0);
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
else
{
EDITBALLOONTIP ebt;
ebt.cbStruct = sizeof( EDITBALLOONTIP );
ebt.pszText = L" Tooltip text! ";
ebt.pszTitle = L" Tooltip title!!! ";
ebt.ttiIcon = TTI_ERROR_LARGE; // tooltip icon
SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt);
return FALSE;
}
}
break;
After further testing, I have decided to put this as an answer so others can clearly spot it.
The solution is in using EM_SHOWBALLOONTIP and EM_HIDEBALLOONTIP messages. You do not need to create tooltip and associate it to an edit control! Therefore, all I need to do now is simply subclass edit control and everything works :
LRESULT CALLBACK EditSubProc ( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
switch (message)
{
case WM_CHAR:
{
if( ! isdigit( wParam ) ) // if not a number pop a tooltip!
{
EDITBALLOONTIP ebt;
ebt.cbStruct = sizeof( EDITBALLOONTIP );
ebt.pszText = L" Tooltip text! ";
ebt.pszTitle = L" Tooltip title!!! ";
ebt.ttiIcon = TTI_ERROR_LARGE; // tooltip icon
SendMessage(hwnd, EM_SHOWBALLOONTIP, 0, (LPARAM)&ebt);
return FALSE;
}
else
{
SendMessage(hwnd, EM_HIDEBALLOONTIP, 0, 0);
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
}
break;
case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, EditSubProc, 0 );
return DefSubclassProc( hwnd, message, wParam, lParam);
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
That's it!
Hopefully this answer will help someone too!
I'm giving the comment as an answer (I should have done that earlier) so that it's clear that the question has been answered:
MSDN Docs for TTM_TRACKPOSITION says that the x/y values are "in screen coordinates".
I'm not totally sure, but the y-coordinate probably corresponds to the top of the caret, you could add half of the edit box height if you want to position your tooltip in the middle of the edit box.
EDIT
re Global variables, you could bundle all your global variables into a structure, allocate memory for the structure and pass the pointer of the structure using the SetWindowLongPtr API call for the edit window using the GWLP_USERDATA, the window proc can then retrieve the values using GetWindowLongPtr...
As a follow-up to comments regarding the use of the SetProp function to remove the need to hold onto a pair of globals for the tool-tip data, I present the following solution.
Note: By error-checking on calls to GetProp, I've designed a WndProc for the subclassed edit control that would function regardless of whether or not it was desired to make use of tool-tips. If the property isn't found, I simply omit any tool-tip handling code.
Note 2: One downside to all of the available approaches to making the tooltip info non-global is that it introduces coupling between the subclassed WndProc and the parent window's wndProc.
By using dwRefData, one must check that it holds a non-NULL
pointer.
By using SetWindowLongPtr, one must remember an index into the
user-data.
By using SetProp, one must remember a textual property name. I find
this easier.
Removing the call to SetProp removes the tool-tip functionality. I.e you could use the same subclassed wndProc for edit controls whether they took advantage of tooltips or not.
Anyhoo, on with the (Code::Blocks) code.
#define _WIN32_IE 0x0500
#define _WIN32_WINNT 0x0501
#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif
#include <tchar.h>
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <ctype.h>
#include <cstdio>
/* Declare Windows procedure */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
/* Make the class name into a global variable */
TCHAR szClassName[ ] = _T("CodeBlocksWindowsApp");
HWND g_hwndTT;
TOOLINFO g_ti;
typedef struct mToolTipInfo
{
HWND hwnd;
TOOLINFO tInfo;
} * p_mToolTipInfo;
LRESULT CALLBACK EditSubProc ( HWND hwnd, UINT message,
WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
p_mToolTipInfo tmp = (p_mToolTipInfo)GetProp(hwnd, _T("tipData"));
switch (message)
{
case WM_CHAR:
{
POINT pt;
if( ! isdigit( wParam ) ) // if not a number pop a tooltip!
{
if (GetCaretPos(&pt)) // here comes the problem
{
// coordinates are not good, so tooltip is misplaced
ClientToScreen( hwnd, &pt );
RECT lastCharRect;
lastCharRect.left = lastCharRect.top = 0;
lastCharRect.right = lastCharRect.bottom = 32;
HDC editHdc;
char lastChar;
int charHeight, charWidth;
lastChar = (char)wParam;
editHdc = GetDC(hwnd);
charHeight = DrawText(editHdc, &lastChar, 1, &lastCharRect, DT_CALCRECT);
charWidth = lastCharRect.right;
ReleaseDC(hwnd, editHdc);
//pt.x += xOfs + charWidth; // invalid char isn't drawn, so no need to advance xPos to reflect width of last char
pt.y += charHeight;
if (tmp)
{
SendMessage(tmp->hwnd, TTM_TRACKACTIVATE, TRUE, (LPARAM)&tmp->tInfo);
SendMessage(tmp->hwnd, TTM_TRACKPOSITION, 0, MAKELPARAM(pt.x, pt.y));
}
}
return FALSE;
}
else
{
if (tmp)
SendMessage(tmp->hwnd, TTM_TRACKACTIVATE,
FALSE, (LPARAM)&tmp->tInfo );
return ::DefSubclassProc( hwnd, message, wParam, lParam );
}
}
break;
case WM_DESTROY:
{
p_mToolTipInfo tmp = (p_mToolTipInfo)GetProp(hwnd, _T("tipData"));
if (tmp)
{
delete(tmp);
RemoveProp(hwnd, _T("tipData"));
}
}
return 0;
case WM_NCDESTROY:
::RemoveWindowSubclass( hwnd, EditSubProc, 0 );
return DefSubclassProc( hwnd, message, wParam, lParam);
break;
}
return DefSubclassProc( hwnd, message, wParam, lParam);
}
HINSTANCE hInst;
int WINAPI WinMain (HINSTANCE hThisInstance,
HINSTANCE hPrevInstance,
LPSTR lpszArgument,
int nCmdShow)
{
HWND hwnd; /* This is the handle for our window */
MSG messages; /* Here messages to the application are saved */
WNDCLASSEX wincl; /* Data structure for the windowclass */
/* The Window structure */
wincl.hInstance = hThisInstance;
wincl.lpszClassName = szClassName;
wincl.lpfnWndProc = WindowProcedure; /* This function is called by windows */
wincl.style = CS_DBLCLKS; /* Catch double-clicks */
wincl.cbSize = sizeof (WNDCLASSEX);
/* Use default icon and mouse-pointer */
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL; /* No menu */
wincl.cbClsExtra = 0; /* No extra bytes after the window class */
wincl.cbWndExtra = 0; /* structure or the window instance */
/* Use Windows's default colour as the background of the window */
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
/* Register the window class, and if it fails quit the program */
if (!RegisterClassEx (&wincl))
return 0;
/* The class is registered, let's create the program*/
hwnd = CreateWindowEx (
0, /* Extended possibilites for variation */
szClassName, /* Classname */
_T("Code::Blocks Template Windows App"), /* Title Text */
WS_OVERLAPPEDWINDOW, /* default window */
CW_USEDEFAULT, /* Windows decides the position */
CW_USEDEFAULT, /* where the window ends up on the screen */
544, /* The programs width */
375, /* and height in pixels */
HWND_DESKTOP, /* The window is a child-window to desktop */
NULL, /* No menu */
hThisInstance, /* Program Instance handler */
NULL /* No Window Creation data */
);
/* Make the window visible on the screen */
ShowWindow (hwnd, nCmdShow);
/* Run the message loop. It will run until GetMessage() returns 0 */
while (GetMessage (&messages, NULL, 0, 0))
{
/* Translate virtual-key messages into character messages */
TranslateMessage(&messages);
/* Send message to WindowProcedure */
DispatchMessage(&messages);
}
/* The program return-value is 0 - The value that PostQuitMessage() gave */
return messages.wParam;
}
/* This function is called by the Windows function DispatchMessage() */
LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) /* handle the messages */
{
case WM_CREATE:
{
HWND hEdit = CreateWindowEx( 0, _T("EDIT"), _T("edit"), WS_CHILD | WS_VISIBLE |
WS_BORDER | ES_CENTER, 150, 150, 100, 30, hWnd, (HMENU)1000, hInst, 0 );
p_mToolTipInfo tmp = new mToolTipInfo;
SetProp(hEdit, _T("tipData"), tmp);
// try with tooltip
//g_hwndTT = CreateWindow(TOOLTIPS_CLASS, NULL,
tmp->hwnd = CreateWindow(TOOLTIPS_CLASS, NULL,
WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON,
0, 0, 0, 0, hWnd, NULL, hInst, NULL);
//if( !g_hwndTT )
if( !tmp->hwnd )
MessageBeep(0); // just to signal error somehow
// g_ti.cbSize = sizeof(TOOLINFO);
// g_ti.uFlags = TTF_TRACK | TTF_ABSOLUTE;
// g_ti.hwnd = hWnd;
// g_ti.hinst = hInst;
// g_ti.lpszText = _T("Hi there");
tmp->tInfo.cbSize = sizeof(TOOLINFO);
tmp->tInfo.uFlags = TTF_TRACK | TTF_ABSOLUTE;
tmp->tInfo.hwnd = hWnd;
tmp->tInfo.hinst = hInst;
tmp->tInfo.lpszText = _T("Hi there");
// if( ! SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&g_ti) )
if( ! SendMessage(tmp->hwnd, TTM_ADDTOOL, 0, (LPARAM)&tmp->tInfo) )
MessageBeep(0); // just to have some error signal
// subclass edit control
SetWindowSubclass( hEdit, EditSubProc, 0, 0 );
}
return 0L;
case WM_DESTROY:
PostQuitMessage (0); /* send a WM_QUIT to the message queue */
break;
default: /* for messages that we don't deal with */
return DefWindowProc (hWnd, message, wParam, lParam);
}
return 0;
}