Search code examples
c++urlwinapitextcreatewindow

Why my SysLink control appears as plain text?


I'm trying to add a clickable URL on my window. I'm using the Win32 API, C++, and Visual Studio.

When I try the example from Microsoft, the link appears as plain text, with the <a> tags.

They put the Hyperlink in the title bar, but they use WS_CHILD, which hides it. So I used WS_CAPTION to show it.

What am I missing? Is it supposed to work only in dialogs?

screenshot behavior

Here are the related code parts:

wWinMain():

INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_LINK_CLASS;
InitCommonControlsEx(&icex);

MyRegisterClass2():

WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc2;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = NULL;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = NULL;
wcex.lpszClassName = WC_LINK;
wcex.hIconSm = NULL;
return RegisterClassExW(&wcex);

InitInstance():

hWndMain = CreateWindowW(
    szWindowClass, 
    szTitle, 
    WS_OVERLAPPED | WS_MINIMIZEBOX | WS_SYSMENU,
    xPos,
    yPos,
    windowWidth,
    windowHeight,
    nullptr,
    nullptr,
    hInstance,
    nullptr
);
RECT rectLink = {
    30,
    30,
    300,
    40
};
hWndLink = CreateWindowEx(
    0,
    WC_LINK,
    L"link: <A HREF=\"https://www.google.com\">click</A>",
    WS_VISIBLE | WS_CHILD | WS_TABSTOP | WS_CAPTION,
    rectLink.left,
    rectLink.top,
    rectLink.right,
    rectLink.bottom,
    hWndMain,
    NULL,
    hInstance,
    NULL);
if (!hWndLink)
{
    return FALSE;
}
ShowWindow(hWndMain, nCmdShow);
ShowWindow(hWndLink, nCmdShow);
UpdateWindow(hWndMain);
UpdateWindow(hWndLink);

WndProc2():

LRESULT CALLBACK WndProc2(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
        case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            EndPaint(hWnd, &ps);
        }
        break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

Solution

  • It's a little hard to be sure what you have wrong.

    The main steps you need to get a functioning SysLink control are:

    1. Add support for Common Controls 6 to the manifest. I usually do this with a pragma:
    #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
    

    You didn't mention anything about doing this, but without it, InitCommonControlsEx will fail, and the control won't be created or displayed at all (at least on older versions of Windows--I guess I've never tested what happens if you skip that on a recent version).

    1. Call InitCommonControlsEx, usually right in WinMain, before creating your main window:
        INITCOMMONCONTROLSEX ex{};
        ex.dwSize = sizeof(ex);
        ex.dwICC = ICC_LINK_CLASS;
        if (!InitCommonControlsEx(&ex)) {
            MessageBox(NULL, L"InitCommonControlsEx", L"Registration failed", MB_OK);
            PostQuitMessage(0);
        }
    
    1. In your main Window's WndProc, create the SysLink:
        case WM_CREATE:
            link = CreateWindow(
                WC_LINK,
                L"<a href=\"www.google.com\">Google</a>",
                WS_CHILD | WS_VISIBLE | LWS_USEVISUALSTYLE,
                10, 10, 400, 20,
                hWnd,
                NULL,
                hInst,
                0);
            break;
    
    1. To actually make use of the control, add a WM_NOTIFY handler as well, something along this really general order:
        case WM_NOTIFY: {
            NMHDR *nmh = (NMHDR*)lParam;
            switch (nmh->code) {
            case NM_CLICK:
                if (nmh->hwndFrom == link) {
                    NMLINK* lnkHdr = (NMLINK*)lParam;
                    MessageBox(NULL, lnkHdr->item.szUrl, L"Here we open", MB_OK);
                }
                break;
            }
            break;
        }
    
    1. Link with ComCtl32.lib

    Here's a quick demo program that puts all of the above together. The skeleton is just what Visual Studio generates for an empty Windows program, with the above bits added in appropriate places.

    #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
    
    #include "framework.h"
    #include "SysLink2.h"
    #include <CommCtrl.h>
    
    #define MAX_LOADSTRING 100
    
    // Global Variables:
    HINSTANCE hInst;                                // current instance
    WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
    WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name
    
    // Forward declarations of functions included in this code module:
    ATOM                MyRegisterClass(HINSTANCE hInstance);
    BOOL                InitInstance(HINSTANCE, int);
    LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
    INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
    
    HWND link;
    
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                         _In_opt_ HINSTANCE hPrevInstance,
                         _In_ LPWSTR    lpCmdLine,
                         _In_ int       nCmdShow)
    {
        UNREFERENCED_PARAMETER(hPrevInstance);
        UNREFERENCED_PARAMETER(lpCmdLine);
    
        // TODO: Place code here.
        INITCOMMONCONTROLSEX ex{};
        ex.dwSize = sizeof(ex);
        ex.dwICC = ICC_LINK_CLASS | ICC_WIN95_CLASSES;
        if (!InitCommonControlsEx(&ex)) {
            MessageBox(NULL, L"InitCommonControlsEx", L"Registration failed", MB_OK);
            PostQuitMessage(0);
        }
    
        // Initialize global strings
        LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
        LoadStringW(hInstance, IDC_SYSLINK2, szWindowClass, MAX_LOADSTRING);
        MyRegisterClass(hInstance);
    
        // Perform application initialization:
        if (!InitInstance (hInstance, nCmdShow))
        {
            return FALSE;
        }
    
        HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_SYSLINK2));
    
        MSG msg;
    
        // Main message loop:
        while (GetMessage(&msg, nullptr, 0, 0))
        {
            if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
    
        return (int) msg.wParam;
    }
    
    
    
    //
    //  FUNCTION: MyRegisterClass()
    //
    //  PURPOSE: Registers the window class.
    //
    ATOM MyRegisterClass(HINSTANCE hInstance)
    {
        WNDCLASSEXW wcex;
    
        wcex.cbSize = sizeof(WNDCLASSEX);
    
        wcex.style          = CS_HREDRAW | CS_VREDRAW;
        wcex.lpfnWndProc    = WndProc;
        wcex.cbClsExtra     = 0;
        wcex.cbWndExtra     = 0;
        wcex.hInstance      = hInstance;
        wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_SYSLINK2));
        wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
        wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
        wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_SYSLINK2);
        wcex.lpszClassName  = szWindowClass;
        wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    
        return RegisterClassExW(&wcex);
    }
    
    //
    //   FUNCTION: InitInstance(HINSTANCE, int)
    //
    //   PURPOSE: Saves instance handle and creates main window
    //
    //   COMMENTS:
    //
    //        In this function, we save the instance handle in a global variable and
    //        create and display the main program window.
    //
    BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
    {
       hInst = hInstance; // Store instance handle in our global variable
    
       HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
          CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
    
       if (!hWnd)
       {
          return FALSE;
       }
    
       ShowWindow(hWnd, nCmdShow);
       UpdateWindow(hWnd);
    
       return TRUE;
    }
    
    //
    //  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
    //
    //  PURPOSE: Processes messages for the main window.
    //
    //  WM_COMMAND  - process the application menu
    //  WM_PAINT    - Paint the main window
    //  WM_DESTROY  - post a quit message and return
    //
    //
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
        switch (message)
        {
        case WM_COMMAND:
            {
                int wmId = LOWORD(wParam);
                // Parse the menu selections:
                switch (wmId)
                {
                case IDM_ABOUT:
                    DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                    break;
                case IDM_EXIT:
                    DestroyWindow(hWnd);
                    break;
                default:
                    return DefWindowProc(hWnd, message, wParam, lParam);
                }
            }
            break;
        case WM_CREATE:
            link = CreateWindow(
                WC_LINK,
                L"<a href=\"www.google.com\">Google</a>",
                WS_CHILD | WS_VISIBLE | LWS_USEVISUALSTYLE,
                10, 10, 400, 20,
                hWnd,
                NULL,
                hInst,
                0);
            break;
        case WM_NOTIFY: {
            // 
            NMHDR *nmh = (NMHDR*)lParam;
            switch (nmh->code) {
            case NM_CLICK:
                if (nmh->hwndFrom == link) {
                    NMLINK* lnkHdr = (NMLINK*)lParam;
                    MessageBox(NULL, lnkHdr->item.szUrl, L"Here we open", MB_OK);
                }
                break;
            }
            break;
        }
        case WM_PAINT:
            {
                PAINTSTRUCT ps;
                HDC hdc = BeginPaint(hWnd, &ps);
                // TODO: Add any drawing code that uses hdc here...
                EndPaint(hWnd, &ps);
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    }
    
    // Message handler for about box.
    INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
    {
        UNREFERENCED_PARAMETER(lParam);
        switch (message)
        {
        case WM_INITDIALOG:
            return (INT_PTR)TRUE;
    
        case WM_COMMAND:
            if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
            {
                EndDialog(hDlg, LOWORD(wParam));
                return (INT_PTR)TRUE;
            }
            break;
        }
        return (INT_PTR)FALSE;
    }
    

    In action, it looks like this:

    enter image description here

    When clicked, it pops up a message box like this:

    enter image description here

    The exact look you get for the window frames and such will depend a bit on how you build it, the OS Version you run it on, and so on. You indicated an interest in Windows XP. I don't have a copy of that handy to test on any more, but I ran this on the oldest I still have around (Windows 7).