I got tired of dealing with the idiosyncrasies, limitations and problems with the Win32 toolbar, so I decided to roll my own. It was a lot of work, but I think it's been worth it.
Note: This is not a finished project! Still under construction. However, this is a working demo that I wanted to get out there so people could play with it and maybe offer some feedback, good or bad. It's actually working well enough for me to put it into one of my programs.
For now I'm just posting this executable. Later I'll be happy to make the full source available for anyone interested. It will be in the form of a single assembly-language module that could be assembled and linked to a program. I could make a DLL later if that seems appropriate.
Anyhow, here's what it's all about:
The toolstrip allows you to put any of several of the standard Windows controls within it, as well as "TS-type" buttons which are basically bitmaps that function as buttons. The standard Windows controls can be any of the following:
- Standard button
- Radio buttons
- Checkbox (also a button)
- Single-line edit controls
- Single-line static controls
Actually, there's nothing stopping you from putting
any type of Windows control in there, like a listbox, etc. It's just that anything that's taller than a single line won't work very well in a narrow strip. I haven't tried it but I'm sure a combobox would work OK.
You add Windows controls by giving the following info., in a structure (TSELEMENT):
- The classname of the control type
- The window styles
- The control's height
- The control's ID
- A pointer to the control's text
Notice that there's no width parameter required: the width of the control is computed for you from the length (GDI length, not # of characters) of the text. I think this is a really nice feature; the controls are automatically sized and spaced in the toolbar. Even if the control has no text displayed--say it's an output field, either a static or edit control--you supply a string of text which is used to size it.
For "TS-type" (non-Windows) buttons, you have to specify:
- The name of the bitmap file OR the resource ID for embedded resources
- The height and width of the button **
- Some flags that control the button's behavior
TS-type buttons can be either individual ones or a group of buttons that behaves like a set of radio buttons; push one and it stays selected, push another and it becomes selected. This works like the
BTNS_GROUP style for Windows toolbar buttons. (The demo has two groups of buttons so you can see how they operate.)
** I'm going to change this so the actual size of the bitmap is used to size it.
You can also add tooltips, as in the demo. These took a hell of a lot of work to get working as well as they do, which isn't perfect. But I must say it's a hell of a lot better than the Windows tooltips. I remember Steve (Hutch) used to rail against Win32 tooltips; he hated them, and for good reason.
They're super-flaky! At first I tried to implement them using the recommended interface, which is to use
TrackMouseEvent() to tell when the user hovers over a control with the mouse. I got this to work fairly well, but it was very unreliable: often you could move the mouse over a button and nothing would happen, or it would take f-o-r-e-v-e-r for the
WM_MOUSEHOVER message to arrive so you could put up your tooltip.
I just gave up on that method (I still use
TrackMouseEvent() to give me a
WM_MOUSELEAVE message so I can know when the user has moved off a button, at which point I get rid of the tooltip windows). My method is so much simpler, and it seems to work OK (well, about 98% of the time): I simply look at all
WM_MOUSEMOVE messages coming from my buttons and do hit tests on them to figure out when the mouse pointer is over one of them. If that happens, I put up the tooltip windows. (There are actually 2 windows, the little box with text in it and an underlying "shadow" window just for looks.)
I found in working on this that a lot of the work is just finessing stuff to make it work smoothly. Now, my method, being simpler than Windows, lacks some of the fine points, like a short delay in putting up the tooltip, but overall it's just so much more reliable. I never miss a tooltip. There are some cases where the tooltip stays up when it shouldn't (and I would really like to hear from you if you find this happening in any specific way). But even this isn't a show-stopper, just a minor annoyance. I found that adding a 50mS delay after creating the windows eliminated a lot of display problems, like a jittery oscillation if you landed between two buttons. There are a lot of similar tweaks in the code, mostly small positioning adjustments.
I checked, and there are no leaks, at least no GDI leaks. Shouldn't be any memory leaks either since I'm not allocating any memory here.
The box in the middle shows another toolstrip, this one vertical and sized to its contents. I don't have all the vertical-toolstrip stuff functional yet, but they do work.
Coding to use my toolstrips take a bit more work on the data side, not much, but you have to fill in a TSELEMENT structure for each button. Instead of having all the bitmap buttons in a strip like Win32 does, each button here is an individual bitmap, so there's a little more overhead, but not much. You don't have to send any special message to load the bitmaps: that's done automagically through the element list when you call the toolstrip-element creation function,
CreateToolstripContent(). Oh, and notice that I can handle bitmap buttons that aren't square (what a concept!).
Anyhow, play with it and let me know what you think.
Hi NoCforMe
I gave it a quick shot. Nice project! :thup:
What is your motivation? Is the MS control not good enough?
When running, I noticed 2 things.
First, an intense flickering when moving the mouse.
The other one is more of a cosmetic issue. When a tooltip is displayed and you move the main window, the tooltip stays where it is. A more consistent behaviour would be to hide it immediately when the parent window changes position.
Regards, Biterider
Quote from: Biterider on November 05, 2023, 05:43:14 PMHi NoCforMe
I gave it a quick shot. Nice project! :thup:
What is your motivation? Is the MS control not good enough?
Well, as I said, the MS toolbar has problems and limitations. Plus it's a pain in the ass to program. This version is more flexible in some ways.
QuoteWhen running, I noticed 2 things.
First, an intense flickering when moving the mouse.
The other one is more of a cosmetic issue. When a tooltip is displayed and you move the main window, the tooltip stays where it is. A more consistent behaviour would be to hide it immediately when the parent window changes position.
Yes, I just discovered that myself. Your reply gives me an idea of how to fix it, maybe: destroy the tooltip windows upon receipt of a WM_MOVE message. (BTW, the fact that you were able to move the window with the tooltip visible shouldn't have been possible: they're supposed to go away as soon as the mouse moves away from the button that triggered the tooltip in the first place.)
Flickering? That's not good. I don't see any myself. What OS are you using?
I do see times when the tooltips stay up even after mousing away from the button that brought them up, which isn't right. Still working on it.
Quote from: NoCforMe on November 05, 2023, 06:18:32 PMFlickering? That's not good. I don't see any myself. What OS are you using?
Windows 10 Home, 22H2
Regards, Biterider
Looks good :thumbsup:
A bit of flicker here on Win7-64, but nothing to worry about. Tooltips look non-standard, I have not yet decided whether I like it or no.
No source yet, ok, but let me ask: what's the interface for designing the toolbar? Something like the Excel table in the editor thread (https://masm32.com/board/index.php?topic=5858.0), 7 years ago?
> I remember Steve (Hutch) used to rail against Win32 tooltips; he hated them, and for good reason
Yes, in that same thread Steve argued quite a bit ;-)
Also a toolbar menu could be space saver.
(https://i.postimg.cc/9D93tdtd/ToolMenu.png) (https://postimg.cc/9D93tdtd)
I use TLPEView often, Timo. How did you insert the menu into the toolbar?
My bad, wrong example code.
EDIT: TBMenu code
Quote from: TimoVJL on November 05, 2023, 09:46:59 PMShort example:
We are talking about different things:
- to the left, TLPEView opens a standard menu when hovering over the toolbar - the menu is
integrated;
- to the right, what you posted right now: a menu
above the toolbar; and it opens only when you click on it.
I'd like to know how you did version 1 - I like it :thumbsup:
Quote from: NoCforMe on November 05, 2023, 11:15:28 AMThe toolstrip allows you to put any of several of the standard Windows controls within it
It took you a while (https://www.masmforum.com/board/index.php?topic=17788.0) ;-)
Well, what can I say? My wheels grind slow, but they grind fine.
So about that flickering (which I now see, when I move the mouse rapidly over the toolstrip): I'm pretty sure I know why it happens. Don't know how to fix it, though. Here's what's happening:
When I create those 2 little tooltip windows, they become activated, meaning that the windows behind them--the main application window and its contents--become deactivated, meaning their borders get repainted and all that. Which I don't want.
I tried using WS_EX_NOACTIVATE when creating the tooltip windows: that doesn't work. Tried using SWP_NOACTIVATE with SetWindowPos() when I resize and reposition these windows: that has no effect either.
The one thing that did work was to put a WM_ACTIVATE handler in the tooltip window proc:
CMP uMsg, WM_ACTIVATE
JE do_activate
.....
do_activate:
CMP WORD PTR wParam, WA_INACTIVE
JE dodefault
INVOKE SetActiveWindow, lParam
XOR EAX, EAX
RET
so that every time the tooltip windows get activated, they turn right around and re-activate the previously-activated window (the one whose handle is in lParam). It works, but I'm pretty sure that's where all that flicker is coming from.
I did a pretty extensive online search for solutions to this problem, on StackOverflow and friends; found a lot of discussions of it, but no real definitive solutions.
Does anyone here know how I can create a window and not have it take over activation?
All this makes me jealous of MS developers, who have access to all the hidden innards of the OS and can easily solve problems like this with all kinds of undocumented stuff. Us peons on the outside have to root around in the published interfaces for work-arounds ...
Quote from: jj2007 on November 05, 2023, 08:21:17 PMA bit of flicker here on Win7-64, but nothing to worry about. Tooltips look non-standard, I have not yet decided whether I like it or no.
Please do let me know the verdict. I'm interested in people's reactions to this admittedly non-standard control.
QuoteNo source yet, ok, but let me ask: what's the interface for designing the toolbar? Something like the Excel table in the editor thread (https://masm32.com/board/index.php?topic=5858.0), 7 years ago?
Interface?
Interface? We don't need no steenkin' interface!
No design interface; the toolstrips are constructed with an array of structures:
TSELEMENT <$tsBtnGroup, 0, 0, $mainTsTsBtnWidth, $mainTsTsBtnHeight, $TBbutton1, 500, Btn1text>
TSELEMENT <$tsBtnGroup, 0, 0, $mainTsTsBtnWidth, $mainTsTsBtnHeight, $TBbutton2, 501, Btn2text>
TSELEMENT <$tsBtnGroup, 0, 0, $mainTsTsBtnWidth, $mainTsTsBtnHeight, $TBbutton3, 502, Btn3text>
TSELEMENT <$tsSep>
TSELEMENT <$tsButton, 0, 0, $mainTsTsBtnWidth, $mainTsTsBtnHeight, $TBbutton4, 501, Btn4text>
TSELEMENT <$tsSep>
TSELEMENT <$tsBtnGroup, 0, 0, $mainTsTsBtnWidth, 14, $TBbutton5, 503, Btn5text>
TSELEMENT <$tsBtnGroup, 0, 0, $mainTsTsBtnWidth, 14, $TBbutton6, 504, Btn6text>
TSELEMENT <$tsBtnGroup, 0, 0, 24, 14, $TBbutton7, 505, Btn8text>
TSELEMENT <$tsSep>
TSELEMENT <0, ButtonClassname, $buttonStyles, BTNxtext, $mainTsBtnHeight, $TBbutton8, 0, Btn9text>
TSELEMENT <$tsNoText, StaticClassname, $staticStyles, ST1OutputText, $tsStaticHeight, $ST2>
DD -1
I've set up all the colors, line widths, element spacing, etc., by hand using EQUates:
$tsElementSpacing EQU 2
$tsButtonSpacing EQU 4
$TSbtnBtnOffset EQU 4
$TSselBoxMargin EQU 2
$tsVelementSpacing EQU 1
$tsElementPadding EQU 6
$tsSepWidth EQU 4
$tsSepHeight EQU 3
$tsStaticHeight EQU 14
$tsEditHeight EQU 18
$tsBtnPushedXoffset EQU 1
$tsBtnPushedYoffset EQU 1
$tsCheckboxAddon EQU 16
$tsSepPenWidth EQU 1
$tsSepPenHlWidth EQU 1
$tsSepPenColor EQU $colorGray
$tsSepPenHlColor EQU $colorWhite
$tsBackgroundColor EQU $colorVltBlue
Source coming soon, after some dust settles ...
Quote from: jj2007 on November 05, 2023, 10:10:42 PMQuote from: TimoVJL on November 05, 2023, 09:46:59 PMShort example:
We are talking about different things:
- to the left, TLPEView opens a standard menu when hovering over the toolbar - the menu is integrated;
- to the right, what you posted right now: a menu above the toolbar; and it opens only when you click on it.
I'd like to know how you did version 1 - I like it :thumbsup:
How to Create an Internet Explorer-Style Menu Bar (https://learn.microsoft.com/en-us/windows/win32/controls/cc-faq-iemenubar)
QuoteI got tired of dealing with the idiosyncrasies, limitations and problems with the Win32 toolbar, so I decided to roll my own. It was a lot of work, but I think it's been worth it.
Looks good.
I did a similar project a while ago, a clone of the Ms Office 2003 Commandbars.
And yes, if you want to implement it properly it's a lot of work and headaches.
Quote from: Greenhorn on November 06, 2023, 07:39:45 AMHow to Create an Internet Explorer-Style Menu Bar
Interesting, thanks, but not what Timo did in his TLPEView, where the menu is at the
same level as the toolbar, not above. It seems, though, that there are two toolbars displayed, so maybe it can work. The M$ site does not give an example, unfortunately. This will require some time and effort...
(https://learn.microsoft.com/en-us/windows/win32/controls/images/howto8.jpg)
Quote from: jj2007 on November 06, 2023, 07:48:45 AMQuote from: Greenhorn on November 06, 2023, 07:39:45 AMHow to Create an Internet Explorer-Style Menu Bar
Interesting, thanks, but not what Timo did in his TLPEView, where the menu is at the same level as the toolbar, not above. It seems, though, that there are two toolbars displayed, so maybe it can work. The M$ site does not give an example, unfortunately. This will require some time and effort...
There is no need to use a Rebar and nothing prevents you to add different toolbar buttons to your "Menu" toolbar. And yes, it will take a little time and effort to implement it.
EDIT:
Another way is to use Rebar controls without a gripper and then place a second Rebar right next to the "Menu" toolbar.
This way you can use TBSTYLE_FLAT with the "Menu" toolbar and for the regular buttons in the other toolbar the default style.
Quote from: Greenhorn on November 06, 2023, 08:04:49 AMThere is no need to use a Rebar and nothing prevents you to add different toolbar buttons to your "Menu" toolbar.
So are you talking about something like this? It's a menu bar (strip, whatever). I just dropped a bunch of bitmap buttons into it like so:
INVOKE AppendMenu, MenuHandle, MF_BITMAP, $menuUpcase, bmp1Handle
INVOKE AppendMenu, MenuHandle, MF_BITMAP, $menuDowncase, bmp2Handle
INVOKE AppendMenu, MenuHandle, MF_BITMAP, $menuToTop, bmp3Handle
INVOKE AppendMenu, MenuHandle, MF_BITMAP, $menuToEnd, bmp4Handle
INVOKE AppendMenu, MenuHandle, MF_BITMAP, $menuBoing, bmp6Handle
INVOKE AppendMenu, MenuHandle, MF_BITMAP, $menuViewExtraFile, bmp5Handle
Easy peasy. Unless I'm mistaken; apologies if I've misunderstood what you're after (well, JJ) here.
I wonder if you could somehow add child window controls to the menu bar?
Quote from: NoCforMe on November 06, 2023, 09:26:15 AMSo are you talking about something like this? It's a menu bar (strip, whatever). I just dropped a bunch of bitmap buttons into it like so:
No, it's about using Win32 toolbar control acting as a menu bar. Read the link I've posted. It describes what to do to make a toolbar acting as a menu bar, in this case placing the toolbar into a Rebar control, like Iexplorer does/did.
However, thread is drifting away ...
This is a PaintShopped demo of what I am after (and it's actually what Timo does in TLPEView), i.e. the menu inside the toolbar, not above. Thus, I can gain one line of vertical space.
Short example
// WSDIFrameTBMenu4.c
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <commctrl.h>
#pragma comment(lib, "comctl32.lib")
#define IDM_EXIT 6001
#define IDM_NEW 6002
#define IDM_OPEN 6003
#define ID_MENUF 7000
#define ID_MENUE 7001
#define ID_MENUT 7002
#define IDC_STATUS 4000
#define IDC_TOOLBAR 4001
#ifndef NELEMS
#define NELEMS(a) (sizeof(a) / sizeof(a[0]))
#endif
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct);
static void OnDestroy(HWND hwnd);
static void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify);
static void OnSize(HWND hwnd, UINT state, int cx, int cy);
static void OnNotify(HWND hwnd, int idCtrl, NMHDR *pNMHdr);
static void OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags);
static void OnEnterIdle(HWND hwnd, UINT source, HWND hwndSource);
static void OnSysKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags);
static void TBContextMenu(HWND hwnd, NMTOOLBAR *pNMTB, int iItem);
//static void TBContextMenu(HWND hwnd, NMTOOLBAR *pNMTB);
static TCHAR szAppName[] = TEXT("WSDIFrameTBMenu");
static TCHAR szFrameClass[] = TEXT("cWSDIFrame");
static HWND g_hFrame, g_hStatus, g_hToolbar;
static HANDLE g_hInst;
static HMENU g_hMenu;
#ifdef UNICODE
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPWSTR lpCmdLine, int nCmdShow)
{
#else
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
#endif // UNICODE
WNDCLASSEX wcx;
MSG msg;
wcx.cbSize = sizeof(WNDCLASSEX);
GetClassInfoEx(hInstance, MAKEINTRESOURCE(32770), &wcx);
wcx.lpfnWndProc = (WNDPROC)WndProc;
wcx.hInstance = hInstance;
wcx.hbrBackground = (HBRUSH)COLOR_3DSHADOW;
wcx.lpszClassName = szFrameClass;
if (!RegisterClassEx(&wcx))
return 0;
g_hInst = hInstance;
g_hFrame = CreateWindowEx(0, szFrameClass, szAppName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, NULL, hInstance, NULL);
if (!g_hFrame)
return 0;
ShowWindow(g_hFrame, nCmdShow);
UpdateWindow(g_hFrame);
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
static HWND MakeToolbar(HWND hWnd, UINT wID)
{
TBBUTTON tbb[] = {
{-1, ID_MENUF, TBSTATE_ENABLED, TBSTYLE_DROPDOWN|BTNS_SHOWTEXT, {0}, 0L, 0},
{-1, ID_MENUE, TBSTATE_ENABLED, TBSTYLE_DROPDOWN|BTNS_SHOWTEXT, {0}, 0L, 0},
{-1, ID_MENUT, TBSTATE_ENABLED, TBSTYLE_DROPDOWN|BTNS_SHOWTEXT, {0}, 0L, 0},
{0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, {0}, 0L, 0},
{STD_FILENEW, IDM_NEW, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
{STD_FILEOPEN, IDM_OPEN, TBSTATE_ENABLED, TBSTYLE_BUTTON, {0}, 0L, 0},
};
TBADDBITMAP tbBitmap;
HWND hToolbar = CreateWindowEx(0, TEXT("ToolbarWindow32"), NULL,
WS_CHILD | TBSTYLE_TOOLTIPS | TBSTYLE_FLAT | TBSTYLE_LIST
, 0, 0, 0, 0, hWnd,
(HMENU)wID, GetModuleHandle(NULL), NULL);
SendMessage(hToolbar, TB_BUTTONSTRUCTSIZE, sizeof(TBBUTTON), 0);
tbBitmap.hInst = HINST_COMMCTRL;
tbBitmap.nID = IDB_STD_SMALL_COLOR;
SendMessage(hToolbar, TB_ADDBITMAP, 0, (WPARAM)&tbBitmap);
for (int i = 0; i < 3; i++) {
TCHAR szBuf[50];
GetMenuString(g_hMenu, i, szBuf, 50, MF_BYPOSITION);
tbb[i].iString = (INT_PTR)SendMessage(hToolbar, TB_ADDSTRING, (WPARAM)0, (LPARAM)szBuf);
}
SendMessage(hToolbar, TB_ADDBUTTONS, NELEMS(tbb), (LPARAM)&tbb);
SendMessage(hToolbar, TB_SETEXTENDEDSTYLE, 0, TBSTYLE_EX_MIXEDBUTTONS);
ShowWindow(hToolbar, SW_SHOW);
return hToolbar;
}
static HWND MakeStatusbar(HWND hWnd, UINT wID)
{
HWND hStatus = CreateWindowEx(0, STATUSCLASSNAME, NULL,
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0,
hWnd, (HMENU)wID, GetModuleHandle(NULL), NULL);
return hStatus;
}
static LRESULT CALLBACK WndProc(HWND hwnd, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
switch (wMsg)
{
case WM_COMMAND:
return OnCommand((hwnd), (int)(LOWORD(wParam)), (HWND) (lParam), (UINT)HIWORD(wParam)), 0;
case WM_SIZE:
return OnSize((hwnd), (UINT) (wParam), (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam)), 0;
case WM_NOTIFY:
return OnNotify(hwnd,(int)(wParam),(NMHDR*)(lParam)), 0;
case WM_MOUSEMOVE:
return OnMouseMove(hwnd,(int)(short)LOWORD(lParam),(int)(short)HIWORD(lParam),(UINT)wParam),0;
case WM_ENTERIDLE:
return OnEnterIdle(hwnd,(UINT)wParam,(HWND)lParam),0;
case WM_SYSKEYUP:
return OnSysKey(hwnd,(UINT)(wParam),FALSE,(int)(short)LOWORD(lParam),(UINT)HIWORD(lParam)),0;
case WM_CREATE:
return OnCreate((hwnd), (LPCREATESTRUCT) (lParam)), 0;
case WM_DESTROY:
return OnDestroy(hwnd), 0;
default:
return DefWindowProc(hwnd, wMsg, wParam, lParam);
}
}
static BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
InitCommonControls();
g_hMenu = LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE(2001));
g_hToolbar = MakeToolbar(hwnd, IDC_TOOLBAR);
g_hStatus = MakeStatusbar(hwnd, IDC_STATUS);
return 0;
}
static void OnDestroy(HWND hwnd)
{
PostQuitMessage(0);
}
static void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
switch (id)
{
case IDM_EXIT:
PostMessage(hwnd, WM_CLOSE, 0, 0L);
return;
}
}
static void OnSize(HWND hwnd, UINT state, int cx, int cy)
{
if (state == SIZE_MINIMIZED)
return;
SendMessage(g_hToolbar, WM_SIZE, state, cx);
SendMessage(g_hStatus, WM_SIZE, state, cx);
}
static void OnNotify(HWND hwnd, int idCtrl, NMHDR *pNMHdr)
{
switch (pNMHdr->code)
{
case TTN_NEEDTEXT:
{
if (((LPTOOLTIPTEXT)pNMHdr)->hdr.idFrom < 7000) {
((LPTOOLTIPTEXT)pNMHdr)->hinst = g_hInst;
((LPTOOLTIPTEXT)pNMHdr)->lpszText = MAKEINTRESOURCE(((LPTOOLTIPTEXT)pNMHdr)->hdr.idFrom);
}
return;
}
case TBN_HOTITEMCHANGE:
if (((LPNMTBHOTITEM)pNMHdr)->idNew >= 7000)
TBContextMenu(hwnd, (LPNMTOOLBAR)pNMHdr, ((LPNMTBHOTITEM)pNMHdr)->idNew - 7000);
return;
case TBN_DROPDOWN:
{
TBContextMenu(hwnd, (LPNMTOOLBAR)pNMHdr, ((LPNMTOOLBAR)pNMHdr)->iItem - 7000);
return;
}
}
}
static void OnMouseMove(HWND hwnd, int x, int y, UINT keyFlags)
{
TCHAR szTmp[100];
wsprintf(szTmp, TEXT("%p x=%d,y=%d"), hwnd, x, y);
SetWindowText(g_hStatus, szTmp);
}
static void OnEnterIdle(HWND hwnd, UINT source, HWND hwndSource)
{
if (source == MSGF_MENU) {
POINT pt;
RECT rc;
RECT rcTB, rcTBB;
int iItem;
GetCursorPos(&pt); // mouse pos
GetWindowRect(hwndSource, &rc); // menu rect
GetWindowRect(g_hToolbar, &rcTB); // toolbar rect
rcTB.left = rc.left;
rcTB.right = rc.left;
iItem = 0;
while(1) {
if(!SendMessage(g_hToolbar, TB_GETITEMRECT, iItem, (LPARAM)&rcTBB)) break;
rcTB.right = rcTB.left + rcTBB.right - rcTBB.left; // add menu button width
if (pt.x < rcTB.right) break;
iItem++; // next menu button
}
//rcTB.bottom += 5; // avoid gap
SHORT sKBs = (GetKeyState(VK_LEFT) + GetKeyState(VK_RIGHT)) & 0xFF00;
if (sKBs || !(PtInRect(&rc, pt) || PtInRect(&rcTB, pt))) {
SendMessage(hwndSource, WM_KEYDOWN, VK_ESCAPE, 0);
SendMessage(hwndSource, WM_KEYUP, VK_ESCAPE, 0);
}
}
}
static void OnSysKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{
TBContextMenu(hwnd, NULL, 0);
}
static void TBContextMenu(HWND hwnd, NMTOOLBAR *pNMTB, int iItem)
{
RECT rcTB;
HMENU hMenu;
SendMessage(g_hToolbar, TB_GETITEMRECT, iItem, (LPARAM)&rcTB);
MapWindowPoints(hwnd, HWND_DESKTOP, (LPPOINT)&rcTB, 2);
if (!pNMTB) SetCursorPos(rcTB.left + 5 , rcTB.bottom + 5);
hMenu = GetSubMenu(g_hMenu, iItem);
if (hMenu)
TrackPopupMenu(hMenu, TPM_LEFTALIGN, rcTB.left , rcTB.bottom, 0, g_hFrame, NULL);
return;
}
Interesting.
Question: is this the part that creates the menu within the toolbar?
for (int i = 0; i < 3; i++) {
TCHAR szBuf[50];
GetMenuString(g_hMenu, i, szBuf, 50, MF_BYPOSITION);
tbb[i].iString = (INT_PTR)SendMessage(hToolbar, TB_ADDSTRING, (WPARAM)0, (LPARAM)szBuf);
I don't understand how that works. Does TB_ADDSTRING create the menu items? According to the documentation (https://learn.microsoft.com/en-us/windows/win32/controls/tb-addstring), that message "adds a new string to the toolbar's string pool". I don't see how that leads to a menu being created.
That only copy menu strings to buttons.
Quote from: TimoVJL on November 05, 2023, 09:46:59 PMMy bad, wrong example code.
EDIT: TBMenu code
Thanks a lot, Timo :thup:
Back on topic.
Here's the toolstrip module source for your programming pleasure. There's an .asm file, an .inc file and a text file explaining how to use it. (The instructions may look complicated--they're fairly long--but it really isn't hard to use this at all. Easier, I think, than Win32 toolbars.)
This has been minimally tested. No crashes here. Still slight problems w/tooltips (they don't always disappear like they're supposed to). If this is a problem, don't use them (it works fine without tooltips).
I couldnt download the test binary, windows defender kept removing it. However I had a glance at the code.
I would recommend a couple of things: handle the WM_ERASEBACKGROUND message and return TRUE or 1, this informs windows that you will handle the erasing of the background yourself in the WM_PAINT message. Without it, windows will handle the background painting and that might show as flickering as it paints it. This applies to all the controls, but most likely might help fix the flickering issue with the tooltip window.
.ELSEIF eax == WM_ERASEBKGND
mov eax, 1
ret
Add WS_CLIPSIBLINGS style to the tooltip control, again this might help reduce flickering as it instructs windows to not draw the stuff behind the control. Without out, some painting of underlying windows can show as flickering.
I would remove the shadow window for the tooltip as this also might be contributing to the flickering, but with WS_CLIPSIBLINGS style set on the tooltip, it might help.
Ideally i would add a class style when registering the tooltip class by setting the WNDCLASSEX.style flag to CS_DROPSHADOW - this will give the tooltip window the drop shadow that windows uses for dialogs/controls etc. So something like the following as an example:
TooltipRegister PROC
LOCAL wc:WNDCLASSEX
LOCAL hinstance:DWORD
Invoke GetModuleHandle, NULL
mov hinstance, eax
invoke GetClassInfoEx, hinstance, Addr szMyTooltipClass, Addr wc
.IF eax == 0 ; if class not already registered do so
mov wc.cbSize, SIZEOF WNDCLASSEX
lea eax, szMyTooltipClass
mov wc.lpszClassName, eax
mov eax, hinstance
mov wc.hInstance, eax
lea eax, MyTooltipWndProc
mov wc.lpfnWndProc, eax
Invoke LoadCursor, NULL, IDC_ARROW
mov wc.hCursor, eax
mov wc.hIcon, 0
mov wc.hIconSm, 0
mov wc.lpszMenuName, NULL
mov wc.hbrBackground, NULL
mov wc.style, CS_DROPSHADOW ; add drop shadow style
mov wc.cbClsExtra, 0
mov wc.cbWndExtra, 0
Invoke RegisterClassEx, addr wc
.ENDIF
ret
TooltipRegister ENDP
Also when you do this, there is a bug or glitch that sometimes prevents the drop shadow from showing the first time, but will show the next time onwards, so to fix that, maybe in WM_NCCREATE or WM_CREATE of the tooltip window proc add the following:
; Fix for drop shadow not showing first time, only on second time onwards
Invoke SetWindowPos, hWin, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOMOVE
Invoke SetWindowPos, hWin, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOMOVE
For the tooltip window, I would create it early on and then show it when needed, and hide it when not needed instead of creating and destroying it
Anyhow hope that helps
All good stuff. Thanks!
I like your suggestion of CS_DROPSHADOW: let the OS do it (it'll do a nicer job anyhow). So I tried it: couldn't get it to work.
My class styles were already CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNWINDOW or CS_DBLCLKS, so I tried ORing in CS_DROPSHADOW. Didn't work. Tried just setting that one style:
MOV WC.style, CS_DROPSHADOW
That didn't work either.
You didn't mention it, but you need to enable drop shadows through SystemParametersInfo(). I did that; still no joy:
INVOKE SystemParametersInfo, SPI_SETDROPSHADOW, 0, TRUE, 0
A subsequent SPI_GETDROPSHADOW inquiry confirms that this is set (to TRUE).
I assume this only needs to be done once, right?, as it changes a global setting. Tried setting this before registering the class, after, both, before creating the window, after, both: still didn't work.
Oh, and I'm definitely creating the tooltip window as a popup, not a child (drop shadows don't work on child windows):
$tooltipWinStyles EQU WS_POPUP or WS_BORDER or WS_VISIBLE
Any idea what I'm doing wrong here?
Hi,
There is actually a few gotchas in relation to creating the drop shadow. I took a deep dive myself last night and today to have a look into it, referring back to some of my other projects etc to see what i had done.
- The window has to be not visible initially, so no WS_VISIBLE style. After it is created it can be shown.
- The popup has to be a top level window to receive the drop shadow effect if the CS_DROPSHADOW class style is set, so tooltip window is created with WS_EX_TOPMOST or WS_EX_TOOLWINDOW or WS_EX_NOACTIVATE. The WS_EX_TOOLWINDOW style prevents it showing in the task bar.
- Any SetWindowPos must use HWND_TOPMOST on the tooltip window.
- To prevent dialog being inactive when tooltip is shown, the style of the tooltip control has to be changed just after it is created (WM_CREATE)
.ELSEIF eax == WM_CREATE
; Remove WS_POPUP style and replace with WS_CHILD
; this prevents losing focus from main dialog to the tooltip
Invoke GetWindowLong, hWin, GWL_STYLE
or eax, WS_CHILD or WS_CLIPSIBLINGS
and eax, (-1 xor WS_POPUP)
Invoke SetWindowLong, hWin, GWL_STYLE, eax
; Required when we change style to use this
Invoke SetWindowPos, hWin, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE or SWP_NOSENDCHANGING or SWP_NOSIZE or SWP_NOMOVE or SWP_DRAWFRAME
I looked at the issue with the tooltip still being visible sometimes if the mouse is moved fast enough away from the toolstrip. To handle this I placed a call to TrackMouseEvent inside the mousemove event with a flag to prevent it being called more than once:
do_mousemove:
.IF TSMouseOverTracking == FALSE
MOV tme.cbSize, SIZEOF TRACKMOUSEEVENT
MOV tme.dwFlags, TME_LEAVE
MOV EAX, hWin
MOV tme.hwndTrack, EAX
INVOKE TrackMouseEvent, ADDR tme
mov TSMouseOverTracking, TRUE
.ENDIF
With this in place, the toolstrip will now receive the WM_MOUSELEAVE message and and during that we can hide the tooltip.
I also added a new paint proc for the tooltip to double buffer the painting to eliminate any flickering from the tooltip - the same could be done for the toolstrip and inclusion of WS_CLIPCHILDREN in the toolstrip should also help prevent any flickering from it.
Attached is the modified Toolstrip.asm file with my changes/additions.
Also you can create a .lib library from your source instead of just the .obj file. I added the settings I used at the top of the Toolstrip.asm file
Hope that helps
Wow. I just saw this. You put a lot of work into that! You'll have to give me a little time to look at this, but I definitely will. Thanks!