Text highlighting and UNDO in Richedit - c

I'm trying to implement a text highlighting and undo in RichEdit. The code below marks 5 and 6 chars but undo doesn't work (the text doesn't change) though Edit_CanUndo returns 1. If I turn off a updateHighlighting then undo works but it rolls back too much at once (e.g. input a long text, paste a text by Ctrl+V, input another text - it's required only 3 undo to rollback all).
What is wrong with my code? Maybe I should return a specific value from updateHighlighting?
#define UNICODE
#define _UNICODE
#include <tchar.h>
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <richedit.h>
#define IDC_EDITOR 1000
#define IDC_BUTTON 1001
HWND hEditorWnd;
bool updateHighlighting(HWND hWnd) {
int carriagePos = 0;
SendMessage(hWnd, EM_GETSEL, (WPARAM)&carriagePos, (WPARAM)&carriagePos);
CHARFORMAT cf = {0};
cf.cbSize = sizeof(CHARFORMAT) ;
cf.dwMask = CFM_COLOR | CFM_BOLD;
cf.crTextColor = RGB(0, 0, 200);
cf.dwEffects = CFM_BOLD;
SendMessage(hWnd, EM_SETSEL, 0, -1);
SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf);
cf.dwMask = CFM_COLOR | CFM_BOLD;
cf.dwEffects = cf.dwEffects & ~CFM_BOLD;
cf.crTextColor = RGB(200, 0, 0);
SendMessage(hWnd, EM_SETSEL, 4, 6);
SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf);
SendMessage(hWnd, EM_SETSEL, carriagePos, carriagePos);
_tprintf(TEXT("End highlight\n"));
return true;
}
LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_DESTROY:
PostQuitMessage (0);
break;
case WM_COMMAND: {
if (LOWORD(wParam) == IDC_EDITOR && HIWORD(wParam) == EN_CHANGE)
return updateHighlighting(hEditorWnd);
if (LOWORD(wParam) == IDC_BUTTON && HIWORD(wParam) == BN_CLICKED)
_tprintf(TEXT("UNDO: can %i => result: %i\n"), Edit_CanUndo(hEditorWnd), Edit_Undo(hEditorWnd));
}
break;
default:
return DefWindowProc (hWnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) {
InitCommonControls();
LoadLibrary(TEXT("msftedit.dll"));
HWND hWnd;
MSG msg;
WNDCLASSEX wincl;
wincl.hInstance = hThisInstance;
wincl.lpszClassName = TEXT("AppClass");
wincl.lpfnWndProc = WindowProcedure;
wincl.style = CS_DBLCLKS;
wincl.cbSize = sizeof (WNDCLASSEX);
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
if (!RegisterClassEx (&wincl))
return 0;
hWnd = CreateWindowEx (0, TEXT("AppClass"), TEXT("Test"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 300, 400, 310, 375, HWND_DESKTOP, NULL, hThisInstance, NULL);
CreateWindowEx(0, WC_BUTTON , L"UNDO", WS_VISIBLE | WS_CHILD | WS_TABSTOP, 10, 310, 280, 30, hWnd, (HMENU)IDC_BUTTON, hThisInstance, NULL);
hEditorWnd = CreateWindowEx(0, TEXT("RICHEDIT50W"), NULL, WS_VISIBLE | WS_CHILD | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP | ES_NOHIDESEL, 0, 0, 300, 300, hWnd, (HMENU)IDC_EDITOR, hThisInstance, NULL);
SendMessage(hEditorWnd, EM_SETEVENTMASK, 0, ENM_CHANGE | ENM_UPDATE | ENM_KEYEVENTS);
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
P.S. I use Code::Block 17.12 + gcc5.1 on Win7x64 (but build a 32bit app).

What you mean exactly with "doesn't work" or with when it "does work" is unclear, however, changing color is something that can be undone too so if you do not want that, then you have to turn off undo before coloring and resume it after coloring.
I don't know if there are easier ways to do it, but in C I use the following functions:
//#include <windows, richedit, ...>
#include <TOM.h>
extern HWND ghEditWnd;
static IUnknown *pUnk;
static ITextDocument *pDoc;
static const IID IID_ITextDocument = {
0x8CC497C0, 0xA1DF, 0x11CE,
{0x80,0x98,0x00,0xAA,0x00,0x47,0xBE,0x5D}
};
static int tomInit(void)
{
if (!pUnk) SendMessage(ghEditWnd, EM_GETOLEINTERFACE, 0, (LPARAM)&pUnk);
if (!pUnk || pUnk->lpVtbl->QueryInterface(pUnk,&IID_ITextDocument, &pDoc) != NOERROR)
{pUnk= 0; return FALSE;}
return TRUE;
}
void undoSuspend(void)
{
if (!pUnk) if (!tomInit()) return;
pDoc->lpVtbl->Undo(pDoc,tomSuspend,0); //Suspends Undo.
}
void undoResume(void)
{
if (!pUnk) if (!tomInit()) return;
pDoc->lpVtbl->Undo(pDoc,tomResume,0); // resume Undo.
}
void releaseTOM(void)
{
if (pUnk) {
pUnk->lpVtbl->Release(pUnk); pUnk= 0; pDoc= 0;
}
}
See also https://learn.microsoft.com/en-us/windows/win32/api/tom/
EDIT: in question How to disable Undo in Rich Text Box in WPF? the suggestion was to set UndoLimit to zero. It is not clear if that would clear the existing undo stack too.
Undo can be suspended and resumed in RichEdit 3.0, Microsoft says.

Here is the working example (undo removes symbols char-by-char as I want) based on Paul Ogilvie's reply and codepilot answer.
Hint: use freeze/unfreeze to prevent flickering. That is similar to SetWindowRedraw(hWnd, TRUE/FALSE) but the last operation arouse unnecessary EN_SELCHANGE notification messages.
#define UNICODE
#define _UNICODE
#include <tchar.h>
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <richedit.h>
// Code::Block doesn't have a tom.h :(
// https://github.com/kinke/mingw-w64-crt/blob/master/mingw-w64-headers/include/tom.h
#include "tom.h"
#include <richole.h>
#include <unknwn.h>
IUnknown *tr_code = NULL;
ITextDocument *td_code;
#define DEFINE_GUIDXXX(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) EXTERN_C const GUID name = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
DEFINE_GUIDXXX(IID_ITextDocument,0x8CC497C0,0xA1DF,0x11CE,0x80,0x98, 0x00,0xAA,0x00,0x47,0xBE,0x5D);
#define IDC_EDITOR 1000
#define IDC_BUTTON 1001
HWND hEditorWnd;
bool updateHighlighting(HWND hWnd) {
int carriagePos = 0;
SendMessage(hWnd, EM_GETSEL, (WPARAM)&carriagePos, (WPARAM)&carriagePos);
CHARFORMAT cf = {0};
cf.cbSize = sizeof(CHARFORMAT) ;
cf.dwMask = CFM_COLOR | CFM_BOLD;
cf.crTextColor = RGB(0, 0, 200);
cf.dwEffects = CFM_BOLD;
SendMessage(hWnd, EM_SETSEL, 0, -1);
SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf);
cf.dwMask = CFM_COLOR | CFM_BOLD;
cf.dwEffects = cf.dwEffects & ~CFM_BOLD;
cf.crTextColor = RGB(200, 0, 0);
SendMessage(hWnd, EM_SETSEL, 4, 6);
SendMessage(hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &cf);
SendMessage(hWnd, EM_SETSEL, carriagePos, carriagePos);
_tprintf(TEXT("End highlight\n"));
return true;
}
LRESULT CALLBACK WindowProcedure (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_DESTROY:
PostQuitMessage (0);
break;
case WM_COMMAND: {
if (LOWORD(wParam) == IDC_BUTTON && HIWORD(wParam) == BN_CLICKED)
_tprintf(TEXT("UNDO: can %i => result: %i\n"), Edit_CanUndo(hEditorWnd), Edit_Undo(hEditorWnd));
if (LOWORD(wParam) == IDC_EDITOR && HIWORD(wParam) == EN_CHANGE) {
long cnt = 0;
td_code->Undo(tomSuspend, NULL); // <---
td_code->Freeze(&cnt); // <---
updateHighlighting(hEditorWnd);
td_code->Unfreeze(&cnt); // <---
td_code->Undo(tomResume, NULL); // <---
return 1;
}
}
break;
default:
return DefWindowProc (hWnd, msg, wParam, lParam);
}
return 0;
}
int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) {
InitCommonControls();
LoadLibrary(TEXT("msftedit.dll"));
HWND hWnd;
MSG msg;
WNDCLASSEX wincl;
wincl.hInstance = hThisInstance;
wincl.lpszClassName = TEXT("AppClass");
wincl.lpfnWndProc = WindowProcedure;
wincl.style = CS_DBLCLKS;
wincl.cbSize = sizeof (WNDCLASSEX);
wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
wincl.lpszMenuName = NULL;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
if (!RegisterClassEx (&wincl))
return 0;
hWnd = CreateWindowEx (0, TEXT("AppClass"), TEXT("Test"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 300, 400, 310, 375, HWND_DESKTOP, NULL, hThisInstance, NULL);
CreateWindowEx(0, WC_BUTTON , L"UNDO", WS_VISIBLE | WS_CHILD | WS_TABSTOP, 10, 310, 280, 30, hWnd, (HMENU)IDC_BUTTON, hThisInstance, NULL);
hEditorWnd = CreateWindowEx(0, TEXT("RICHEDIT50W"), NULL, WS_VISIBLE | WS_CHILD | ES_MULTILINE | ES_AUTOVSCROLL | ES_AUTOHSCROLL | ES_WANTRETURN | WS_VSCROLL | WS_HSCROLL | WS_TABSTOP | ES_NOHIDESEL, 0, 0, 300, 300, hWnd, (HMENU)IDC_EDITOR, hThisInstance, NULL);
SendMessage(hEditorWnd, EM_SETEVENTMASK, 0, ENM_CHANGE | ENM_UPDATE | ENM_KEYEVENTS);
// \/
SendMessage(hEditorWnd, EM_GETOLEINTERFACE, 0, (LPARAM)&tr_code);
if(tr_code == (IRichEditOle*)NULL) {
MessageBox(0, TEXT("Error when trying to get RichEdit OLE Object"), NULL, 0);
return 0;
}
tr_code->QueryInterface(IID_ITextDocument,(void**)&td_code);
// /\
while (GetMessage (&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}

Related

winapi full screen leaves 3 tickles

here is a code I found here :
https://www.codeproject.com/Questions/108400/How-to-Set-Win32-Application-to-Full-Screen-C
. I had a little problem with it. In part 1 it queries the window style and then in the second 2 part it defines styles that we will remove in part 3. then in part 4 leaves three butts on two sides and bottom. what can i do?
//move to full screen
//1
DWORD dwStyle = ::GetWindowLong(hwnd, GWL_STYLE);
//2
DWORD dwRemove = WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
//3
DWORD dwNewStyle = dwStyle & ~dwRemove;
::SetWindowLong(hwnd, GWL_STYLE, dwNewStyle);
//4
::SetWindowPos(hwnd, NULL, 0, 0, ::GetDeviceCaps(hdc, HORZRES), ::GetDeviceCaps(hdc, VERTRES),SWP_FRAMECHANGED);
Raymond Chen gave the correct way to do this in his blog: How do I switch a window between normal and fullscreen?:
Here is the sample that you can refer to :
#include <Windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
WINDOWPLACEMENT g_wpPrev = { sizeof(g_wpPrev) };
void setFullWindow(HWND hwnd)
{
DWORD dwStyle = GetWindowLong(hwnd, GWL_STYLE);
if (dwStyle & WS_OVERLAPPEDWINDOW) {
MONITORINFO mi = { sizeof(mi) };
if (GetWindowPlacement(hwnd, &g_wpPrev) &&
GetMonitorInfo(MonitorFromWindow(hwnd,
MONITOR_DEFAULTTOPRIMARY), &mi)) {
SetWindowLong(hwnd, GWL_STYLE,
dwStyle & ~WS_OVERLAPPEDWINDOW);
SetWindowPos(hwnd, HWND_TOP,
mi.rcMonitor.left, mi.rcMonitor.top,
mi.rcMonitor.right - mi.rcMonitor.left,
mi.rcMonitor.bottom - mi.rcMonitor.top,
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
}
}
else {
SetWindowLong(hwnd, GWL_STYLE,
dwStyle | WS_OVERLAPPEDWINDOW);
SetWindowPlacement(hwnd, &g_wpPrev);
SetWindowPos(hwnd, NULL, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER |
SWP_NOOWNERZORDER | SWP_FRAMECHANGED);
}
}
int WINAPI WinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPSTR szCmdLine, _In_ int iCmdShow)
{
static TCHAR szAppName[] = TEXT("hello windows");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
}
hwnd = CreateWindow(szAppName,
TEXT("the hello program"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
setFullWindow(hwnd);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while (GetMessageW(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}

WinAPI, TreeView: a gray item by TVIS_CUT

I want to make a item gray. According to docs I set TVIS_CUT in stateMask and mask.
But it doesn't work (the item is black as usual). TVIS_BOLD works perfect.
I use CodeBlocks17 (gcc)/Win7x64. Also I tried VS2005 and another OS (WinXP) with the same result.
What did I miss?
#define _UNICODE
#define UNICODE
#include <windows.h>
#include <commctrl.h>
LRESULT CALLBACK cbMain (HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) {
MSG msg;
WNDCLASSEX wc{0};
wc.hInstance = hInstance;
wc.lpszClassName = TEXT("MyAppClass");
wc.lpfnWndProc = cbMain;
wc.style = CS_DBLCLKS;
wc.cbSize = sizeof (WNDCLASSEX);
wc.hIcon = LoadIcon(0, IDC_ICON);
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.lpszMenuName = 0;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
if (!RegisterClassEx (&wc))
return EXIT_FAILURE;
HWND hMainWnd = CreateWindowEx (0, TEXT("MyAppClass"), NULL, WS_OVERLAPPEDWINDOW | WS_VISIBLE, 500, 600, 160, 300, HWND_DESKTOP, NULL, hInstance, NULL);
HWND hTreeWnd = CreateWindowEx(0, WC_TREEVIEW, NULL, WS_VISIBLE | WS_CHILD | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_EDITLABELS, 0, 0, 150, 200, hMainWnd, (HMENU)100, hInstance, NULL);
TVITEM tvi{0};
tvi.mask = TVIF_TEXT | TVIF_STATE;
tvi.pszText = TEXT("Item");
tvi.cchTextMax = 40;
tvi.stateMask = TVIS_CUT;
tvi.state = TVIS_CUT;
TVINSERTSTRUCT tvins{0};
tvins.item = tvi;
tvins.hInsertAfter = TVI_ROOT;
tvins.hParent = TVI_ROOT;
SendMessage(hTreeWnd, TVM_INSERTITEM, 0, (LPARAM)(LPTVINSERTSTRUCT)&tvins);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK cbMain (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_DESTROY:
PostQuitMessage (0);
break;
default:
return DefWindowProc (hwnd, message, wParam, lParam);
}
return 0;
}
I used Custom Draw as Jonathan Potter said.
Here is one pitfall: while a current item changing, old item has CDIS_FOCUS and CDIS_SELECTED flags in pCustomDraw->nmcd.uItemState member, and new item doesn't have. So pCustomDraw->clrText = (pNMTVCD->nmcd.uItemState & CDIS_FOCUS) == CDIS_FOCUS) ? ... will works a little bit strange. I checked pCustomDraw->clrTextBk instead. But it can be broken when user used a custom theme.
I use this article and an applied code to build the example.
#define _UNICODE
#define UNICODE
#define _WIN32_WINNT 0x0501
#define _WIN32_IE 0x0500
#include <windows.h>
#include <commctrl.h>
#define IDC_TREEVIEW 100
LRESULT CALLBACK cbMain (HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow) {
MSG msg;
WNDCLASSEX wc{0};
wc.hInstance = hInstance;
wc.lpszClassName = TEXT("MyAppClass");
wc.lpfnWndProc = cbMain;
wc.style = CS_DBLCLKS;
wc.cbSize = sizeof (WNDCLASSEX);
wc.hIcon = LoadIcon(0, IDC_ICON);
wc.hCursor = LoadCursor (NULL, IDC_ARROW);
wc.lpszMenuName = 0;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
if (!RegisterClassEx (&wc))
return EXIT_FAILURE;
HWND hMainWnd = CreateWindowEx (0, TEXT("MyAppClass"), NULL, WS_OVERLAPPED | WS_VISIBLE | WS_SYSMENU, 500, 600, 160, 300, HWND_DESKTOP, NULL, hInstance, NULL);
HWND hTreeWnd = CreateWindowEx(0, WC_TREEVIEW, NULL, WS_VISIBLE | WS_CHILD | TVS_HASBUTTONS | TVS_HASLINES | TVS_LINESATROOT | TVS_EDITLABELS, 0, 0, 150, 200, hMainWnd, (HMENU)IDC_TREEVIEW, hInstance, NULL);
TVITEM tvi{0};
TVINSERTSTRUCT tvins{0};
tvi.mask = TVIF_TEXT | TVIF_STATE;
tvi.pszText = TEXT("Item1");
tvi.cchTextMax = 40;
tvi.stateMask = 0;
tvi.state = 0;
tvins.item = tvi;
tvins.hInsertAfter = TVI_ROOT;
tvins.hParent = TVI_ROOT;
SendMessage(hTreeWnd, TVM_INSERTITEM, 0, (LPARAM)(LPTVINSERTSTRUCT)&tvins);
tvi.pszText = TEXT("Item2");
tvins.item = tvi;
SendMessage(hTreeWnd, TVM_INSERTITEM, 0, (LPARAM)(LPTVINSERTSTRUCT)&tvins);
tvi.stateMask = TVIS_CUT;
tvi.state = TVIS_CUT;
tvi.pszText = TEXT("Item3");
tvins.item = tvi;
SendMessage(hTreeWnd, TVM_INSERTITEM, 0, (LPARAM)(LPTVINSERTSTRUCT)&tvins);
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK cbMain (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
switch (message) {
case WM_DESTROY: {
PostQuitMessage (0);
}
break;
case WM_NOTIFY: {
NMHDR* pHdr = (LPNMHDR)lParam;
if (pHdr->idFrom == IDC_TREEVIEW && pHdr->code == NM_CUSTOMDRAW) {
LPNMTVCUSTOMDRAW pCustomDraw = (LPNMTVCUSTOMDRAW)lParam;
if (pCustomDraw->nmcd.dwDrawStage == CDDS_PREPAINT)
return CDRF_NOTIFYITEMDRAW;
if (pCustomDraw->nmcd.dwDrawStage == CDDS_ITEMPREPAINT) {
BOOL isCut = TreeView_GetItemState(pHdr->hwndFrom, (HTREEITEM)pCustomDraw->nmcd.dwItemSpec, TVIS_CUT) & TVIS_CUT;
pCustomDraw->clrText = pCustomDraw->clrTextBk != RGB( 255, 255, 255) ? RGB( 255, 255, 255) :
isCut ? RGB( 128, 128, 128 ) : RGB( 0, 0, 0);
}
return CDRF_DODEFAULT;
}
}
break;
default:
return DefWindowProc (hWnd, message, wParam, lParam);
}
return 0;
}

Hiding and showing container windows based on tab

I have created a tab control that contains two tabs. Inside each tab there will be a container window to hold other controls (in the code example, a static control for instance). The idea is that when a new tab is selected, it will hide/show the correct container window that holds a bunch of controls. However I am struggling to get the container windows holding the static controls to show. This is the code so far:
#include <windows.h>
#include <commctrl.h>
#pragma comment(lib, "comctl32.lib")
#define ID_TABCTRL 1
#define ID_STATIC0 2
#define ID_STATIC1 3
#define ID_TAB0 4
#define ID_TAB1 5
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HWND hTab, hTab0, hTab1;
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
MSG msg;
WNDCLASS wc = { 0 };
wc.lpszClassName = TEXT("Tab control");
wc.hInstance = hInstance;
wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
wc.lpfnWndProc = WndProc;
wc.hCursor = LoadCursor(0, IDC_ARROW);
RegisterClass(&wc);
CreateWindow(wc.lpszClassName, TEXT("Tab control"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 250, 200, 0, 0, hInstance, 0);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
TCITEM tie;
INITCOMMONCONTROLSEX icex;
switch (msg)
{
case WM_CREATE:
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_TAB_CLASSES;
InitCommonControlsEx(&icex);
tie.mask = TCIF_TEXT;
///// Create Tab Control /////
hTab = CreateWindow(WC_TABCONTROL, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 200, 150, hwnd, (HMENU)ID_TABCTRL, NULL, NULL);
///// Create Individual Tabs /////
tie.pszText = TEXT("First");
SendMessage(hTab, TCM_INSERTITEM, 0, (LPARAM)(LPTCITEM)&tie);
tie.pszText = TEXT("Second");
SendMessage(hTab, TCM_INSERTITEM, 1, (LPARAM)(LPTCITEM)&tie);
///// Create Container windows for each tab /////
hTab0 = CreateWindow(0, NULL, WS_CHILD | WS_VISIBLE, 0, 0, 200, 150, hTab, (HMENU)ID_TAB0, NULL, NULL);
hTab1 = CreateWindow(0, NULL, WS_CHILD, 0, 0, 200, 150, hTab, (HMENU)ID_TAB1, NULL, NULL);
///// Add example control to one of the tab container windows /////
CreateWindow(TEXT("Static"), TEXT("Yay!"), WS_CHILD | WS_VISIBLE | SS_LEFT, 20, 30, 50, 25, hTab0, (HMENU)ID_STATIC0, NULL, NULL);
CreateWindow(TEXT("Static"), TEXT("It appears to be working"), WS_CHILD | WS_VISIBLE | SS_LEFT, 20, 30, 100, 50, hTab1, (HMENU)ID_STATIC1, NULL, NULL);
break;
case WM_NOTIFY:
switch (((LPNMHDR)lParam)->code)
{
case TCN_SELCHANGE:
switch (TabCtrl_GetCurSel(hTab))
{
///// Show or Hide the appropriate tabs /////
case 0:
ShowWindow(hTab1, SW_HIDE);
ShowWindow(hTab0, SW_SHOW);
case 1:
ShowWindow(hTab0, SW_HIDE);
ShowWindow(hTab1, SW_SHOW);
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return(DefWindowProc(hwnd, msg, wParam, lParam));
}
Is it just a case of the container windows hTab0 and hTab1 being stuck behind the tab window (hTab)?
First, you need to change the position of the tab and static form, otherwise it will block the generated content.
Then you can define the generated static text directly through CreateWindow, by using WC_STATIC.
#include <Windows.h>
#include <commctrl.h>
LRESULT CALLBACK WndProc(HWND, UINT,WPARAM,LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("windows");
HWND hwnd;
MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = hInstance;
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = szAppName;
if (!RegisterClass(&wndclass))
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR);
}
hwnd = CreateWindow(szAppName,
TEXT("the hello program"),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hwnd,iCmdShow);
UpdateWindow(hwnd);
while (GetMessageW(&msg,NULL,0,0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message,WPARAM wParam,LPARAM lParam)
{
static HINSTANCE hInstance;
static HWND hwndTab = 0 , hwndStatic1,hwndStatic2;
TCITEM tie;
RECT rcClient;
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_TAB_CLASSES;
TCHAR tabLBL1[256];
GetClientRect(hwnd, &rcClient);
switch (message)
{
case WM_CREATE:
{
hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
hwndTab = CreateWindow(WC_TABCONTROL, L"",
WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE,
0, 0, rcClient.right, rcClient.bottom,
hwnd, NULL, hInstance, NULL);
//Add tabs for each day of the week.
tie.mask = TCIF_TEXT | TCIF_IMAGE;
tie.iImage = -1;
wsprintf(tabLBL1, L"tab1");
tie.pszText = tabLBL1;
TabCtrl_InsertItem(hwndTab, 0, &tie);
wsprintf(tabLBL1, L"tab2");
TabCtrl_InsertItem(hwndTab, 1, &tie);
hwndStatic1 = CreateWindow(WC_STATIC, L"123",
WS_CHILD | WS_VISIBLE | WS_BORDER,
200, 200, 100, 100, // Position and dimensions; example only.
hwndTab, NULL, hInstance, // g_hInst is the global instance handle
NULL);
hwndStatic2 = CreateWindow(WC_STATIC, L"456",
WS_CHILD | WS_VISIBLE | WS_BORDER,
400, 200, 100, 100, // Position and dimensions; example only.
hwndTab, NULL, hInstance, // g_hInst is the global instance handle
NULL);
ShowWindow(hwndStatic1, TRUE);
ShowWindow(hwndStatic2, FALSE);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_NOTIFY:
if (((LPNMHDR)lParam)->code == TCN_SELCHANGE)
{
int tabID = TabCtrl_GetCurSel(hwndTab);
switch (tabID)
{
case 0:
ShowWindow(hwndStatic1, TRUE);
ShowWindow(hwndStatic2, FALSE);
break;
case 1:
ShowWindow(hwndStatic1, FALSE);
ShowWindow(hwndStatic2, TRUE);
break;
default:
break;
}
}
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
Zhu Song's answer helped solved the problem (the container windows needing to be a static control, and needing to be positioned inside the tab control's space, below the clickable tabs).
Here is the result, swapping the two container window creation lines:
hTab0 = CreateWindow(TEXT("Static"), NULL, WS_CHILD | WS_VISIBLE, 1, 25, 197, 123, hTab, (HMENU)ID_TAB0, NULL, NULL);
hTab1 = CreateWindow(TEXT("Static"), NULL, WS_CHILD, 1, 25, 197, 123, hTab, (HMENU)ID_TAB1, NULL, NULL);
Note how the container window sizes and position are small enough to just fit inside the main tab control window (you can add WS_BORDER to the style to see where exactly it fits).

No LVN_GETINFOTIP notifications in a LVS_REPORT Listview of text cells

I have a listview of texts and have total control over it, works like a dream.
Except I don't get any LVN_GETINFOTIP notifications by WM_NOTIFY. I get every other notifications message but not LVN_GETINFOTIP.
I have spent some time been Googling and trying whatever I can find. Some sites say that from Vista it works different but not what different. I don’t care if it works before Win10 (can live with it) and not at all if it works before Win7.
I really wonder what prevents the LVN_GETINFOTIP messages?
switch (((LPNMHDR)lParam)->code)
{
case LVN_GETINFOTIP:
CreateListView(
0,
LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP,
0,
3 * TAB_HIGHT,
OwnWindowWidth,
OwnWindowHight - (OBJ_WINDOW_HIGHT + (3 * TAB_HIGHT)),
hWnd,
(HMENU)IDC_MAIN_LISTVIEW,
(HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE));
static HWND CreateListView(
DWORD dwAddStyle,
DWORD dwAddExtendedListViewStyle,
int X,
int Y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU IdListWiew,
HINSTANCE hInst)
{
HWND hWndListView;
INITCOMMONCONTROLSEX icex;
memset(&icex, 0, sizeof(INITCOMMONCONTROLSEX));
// Ensure that the common control DLL is loaded.
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&icex);
// Create the list-view window in report view with label
// editing enabled.
hWndListView = CreateWindowEx(WS_EX_ACCEPTFILES, WC_LISTVIEW, L"",
WS_CHILD | LVS_REPORT | WS_VISIBLE | WS_BORDER | WM_NOTIFY | dwAddStyle,
X, Y, nWidth, nHeight,
hWndParent, IdListWiew, hInst, NULL);
if (hWndListView == NULL)
return NULL;
ListView_SetExtendedListViewStyle(hWndListView, dwAddExtendedListViewStyle);
return hWndListView;
}
Found the error myself!
The sample code for ListViews I once used hides the first column and so I can't get any TVN_GETINFOTIP because there is nothing to hover (Width of column 0 was 0). I managed to find this by reduction of code in a test program and read it real carefully. A bit embarrassed I asked. But this is coding life.
I have now a very compact sample code 160lines of a report ListView if it comes to use. (I rather this question to be removed).
#include <windows.h>
#include <CommCtrl.h>
#include <wchar.h>
#define IDC_MAIN_LISTVIEW 100
#define MAIN_LISTFIELDS 9
#define MAIN_LISTLINES 78
int iMainFieldsWidth[MAIN_LISTFIELDS] = { 50, 60, 78, 70, 100, 50, 50, 121, 112 };
WORD wMainFieldsAlign[MAIN_LISTFIELDS] = { LVCFMT_RIGHT,LVCFMT_LEFT, LVCFMT_LEFT,LVCFMT_LEFT,LVCFMT_LEFT, LVCFMT_RIGHT, LVCFMT_RIGHT, LVCFMT_LEFT, LVCFMT_LEFT };
WCHAR *pMainFieldsHeader[MAIN_LISTFIELDS] = { L"zero",L"one",L"Two",L"Three",L"Four",L"five",L"six",L"seven",L"eight" };
LRESULT WINAPI MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
CREATESTRUCT MainWindowCreateStruct;
WNDCLASS wc;
MSG msg;
HWND hWnd;
if (!hPrevInstance)
{
wc.lpszClassName = L"AppClass";
wc.lpfnWndProc = MainWndProc;
wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon(hInstance, L"1");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_ACTIVEBORDER + 1);
wc.lpszMenuName = L"AppMenu";
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
RegisterClass(&wc);
}
memset(&MainWindowCreateStruct, 0, sizeof(CREATESTRUCT));
hWnd = CreateWindowEx(WS_EX_ACCEPTFILES, L"AppClass",
L"LvTest",
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
&MainWindowCreateStruct
);
ShowWindow(hWnd, nCmdShow);
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT WINAPI MainWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CREATE:
{
// Create the list-view window in report view with label
// editing enabled.
HWND hLvWnd = CreateWindowEx(WS_EX_ACCEPTFILES, WC_LISTVIEW, L"",
WS_CHILD | LVS_REPORT | WS_VISIBLE | WS_BORDER,
0, 0, 1000, 500,
hWnd, (HMENU)IDC_MAIN_LISTVIEW, (HINSTANCE)GetWindowLongPtr(hWnd, GWLP_HINSTANCE), NULL);
if (hLvWnd == NULL)
return 0;
ListView_SetExtendedListViewStyle(hLvWnd, LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP);
ListView_DeleteAllItems(hLvWnd);
{
LVCOLUMN lvc;
int iCol = 0;
memset(&lvc, 0, sizeof(LVCOLUMN));
while (ListView_DeleteColumn(hLvWnd, 0)); // Nonvisual left column
// Initialize the LVCOLUMN structure.
// The mask specifies that the format, width, text, and
// subitem members of the structure are valid.
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
// Add the columns.
for (iCol = 0; iCol < MAIN_LISTFIELDS; iCol++)
{
lvc.iSubItem = iCol;
lvc.pszText = pMainFieldsHeader[iCol];
lvc.cchTextMax = MAX_PATH;
lvc.iImage = 0;
lvc.iOrder = 0;
lvc.cx = iMainFieldsWidth[iCol]; // width of column in pixels
lvc.fmt = wMainFieldsAlign[iCol]; // alignment of column
if (ListView_InsertColumn(hLvWnd, lvc.iSubItem, &lvc) == -1)
return 0;
}
}
{
LVITEM lvI;
DWORD index = 0;
memset(&lvI, 0, sizeof(LVITEM));
// Some code to create the list-view control.
// Initialize LVITEM members that are common to all
// items.
lvI.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM | LVIF_STATE | LVIF_INDENT;
lvI.state = 0;
lvI.stateMask = 0;
// Initialize LVITEM members that are different for each item.
for (index = 0; index < MAIN_LISTLINES; index++)
{
lvI.iItem = index;
lvI.iImage = 0; // Icon
lvI.iIndent = 0;
lvI.iSubItem = 0;
// lvI.lParam = (LPARAM) &rgPetInfo[index];
lvI.pszText = LPSTR_TEXTCALLBACK; // sends an
// LVN_GETDISPINFO
// message.
if (ListView_InsertItem(hLvWnd, &lvI) == -1)
return 0;
}
}
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_NOTIFY:
switch (((LPNMHDR)lParam)->code)
{
case LVN_GETINFOTIP:
if (lParam)
{ // This is only working it looks, or actually the LVS_EX_FULLROWSELECT | LVS_EX_INFOTIP | LVS_EX_LABELTIP of the CreateWindowEx isn't, would be nice to have
LPNMLVGETINFOTIP pGetInfoTip = (LPNMLVGETINFOTIP)lParam;
wcscpy(pGetInfoTip->pszText, L"This is an InfoTip Text\nand it takes newlines", pGetInfoTip->cchTextMax);
}
break;
case LVN_GETDISPINFO:
if (lParam)
{
NMLVDISPINFO *plvdi = ((NMLVDISPINFO *)lParam);
if ((plvdi->hdr.hwndFrom == GetDlgItem(hWnd, IDC_MAIN_LISTVIEW)) && (plvdi->item.iSubItem<MAIN_LISTFIELDS) && plvdi->item.cchTextMax)
wcsncpy(plvdi->item.pszText, pMainFieldsHeader[plvdi->item.iSubItem], plvdi->item.cchTextMax);
}
break;
default:
break;
}
break;
default:
return(DefWindowProc(hWnd, msg, wParam, lParam));
}
return 0;
}

Up-Down control taller than buddy

Plain C, winapi app.
On a dialog box created from a resource script it fits perfectly, but created with CreateWindowEx the Up-Down control is taller than the buddy window (edit control) by one pixel on either side.
It's not that big of a deal, but it's pretty nagging. I tried everything I could think of and couldn't get it fixed, any help is appreciated.
Here's the code:
#include <Windows.h>
#include <Commctrl.h>
#include <stdio.h>
#define print(...) sprintf(dbg, __VA_ARGS__);\
WriteConsoleA(h_con_out, dbg, strlen(dbg), NULL, NULL)
TCHAR *app_name = TEXT("ud");
HANDLE h_con_out;
char dbg[80];
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow){
HWND hwnd;
MSG msg;
WNDCLASSEX wcx;
INITCOMMONCONTROLSEX icx = {sizeof(icx), ICC_STANDARD_CLASSES | ICC_UPDOWN_CLASS};
AllocConsole();
h_con_out = GetStdHandle(STD_OUTPUT_HANDLE);
memset(&wcx, 0, sizeof(wcx));
wcx.cbSize = sizeof(wcx);
wcx.lpfnWndProc = WndProc;
wcx.hInstance = hInstance;
wcx.hCursor = LoadCursor(NULL, IDC_ARROW);
wcx.hbrBackground = (HBRUSH) (COLOR_BTNFACE + 1);
wcx.lpszClassName = app_name;
if(!RegisterClassEx(&wcx)){
MessageBox(NULL, TEXT("This program requires Windows 2000!"), app_name, MB_ICONERROR);
return 0;
}
InitCommonControlsEx(&icx);
hwnd = CreateWindowEx(
0, app_name, app_name,
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT, 200, 100,
NULL, NULL, hInstance, NULL
);
ShowWindow(hwnd, iCmdShow);
UpdateWindow(hwnd);
while((GetMessage(&msg, NULL, 0, 0)) != 0){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam){
static HWND hEd, hUd;
switch(msg){
case WM_CREATE:
hEd = CreateWindowEx(
WS_EX_CLIENTEDGE, WC_EDIT, NULL,
WS_VISIBLE | WS_CHILD | WS_BORDER |
ES_RIGHT | ES_NUMBER,
5, 5, 52, 23,
hwnd, NULL, ((LPCREATESTRUCT) lParam)->hInstance, NULL
);
hUd = CreateWindowEx(
0, UPDOWN_CLASS, NULL,
WS_VISIBLE | WS_CHILD |
UDS_ALIGNRIGHT | UDS_ARROWKEYS | UDS_NOTHOUSANDS | UDS_AUTOBUDDY | UDS_HOTTRACK | UDS_SETBUDDYINT | UDS_AUTOBUDDY,
0, 0, 0, 0,
hwnd, NULL, ((LPCREATESTRUCT) lParam)->hInstance, NULL
);
SendMessage(hUd, UDM_SETRANGE, 0, 10 | 1 << 16);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
Remove WS_BORDER from the edit controls style.

Resources