News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

MyToolstrip: A possible replacement for the Windows toolbar

Started by NoCforMe, November 05, 2023, 11:15:28 AM

Previous topic - Next topic

Greenhorn

Quote from: jj2007 on November 06, 2023, 07:48:45 AM
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...

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.
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

NoCforMe

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?
Assembly language programming should be fun. That's why I do it.

Greenhorn

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 ...
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

jj2007

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.

TimoVJL

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;
}
May the source be with you

NoCforMe

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, that message "adds a new string to the toolbar's string pool". I don't see how that leads to a menu being created.
Assembly language programming should be fun. That's why I do it.

TimoVJL

May the source be with you

jj2007


NoCforMe

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).
Assembly language programming should be fun. That's why I do it.

fearless

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

NoCforMe

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?

Assembly language programming should be fun. That's why I do it.

fearless

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

NoCforMe

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!
Assembly language programming should be fun. That's why I do it.