Tab Control Question

Started by Richard Kelly, April 25, 2026, 04:07:26 AM

Previous topic - Next topic

Richard Kelly

I'm trying to get my form working without DDT and am unsuccessful in adding a listview to a page.

Creating the tab control

DIM pTabPage1 AS CTabPage PTR = NEW CTabPage
pTabPage1->InsertPage(hTab, 0, "Date Types", -1, @TabPage1_WndProc)
DIM pTabPage2 AS CTabPage PTR = NEW CTabPage
pTabPage2->InsertPage(hTab, 1, "Holidays", -1, @TabPage2_WndProc)
DIM pTabPage3 AS CTabPage PTR = NEW CTabPage
pTabPage3->InsertPage(hTab, 2, "Events", -1, @TabPage3_WndProc)
DIM pTabPage4 AS CTabPage PTR = NEW CTabPage
pTabPage4->InsertPage(hTab, 3, "Miscellaneous", -1, @TabPage4_WndProc) 

Adding the listview control to first page

FUNCTION TabPage1_WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT

   SELECT CASE uMsg

    CASE WM_CREATE
        DIM pTabPage AS CTabPage PTR = AfxCTabPagePtr(GetParent(hwnd), 0)
        DIM LVColumn AS LVCOLUMN
        DIM hListView AS HWND = pTabPage->AddControl("LISTVIEW", hwnd, IDC_LVPAGE1, "", 9, 408, 688, 20)
        DIM wColumnName AS WSTRING * 260
        LVColumn.mask = LVCF_TEXT
        wColumnName = "Name"
        LVColumn.pszText = @wColumnName
        ShowWindow(hListView,SW_SHOW)

The tab control shows up with all the tabs empty.

hajubu

#1
hi Richard,
I just tried to add a Listview as a  fourth-tabpage to the CW_TabCtrl_01.bas, (v2025+)

by adapting  an extract of the CW_Listview_01.bas to fit the "needs" in the Tab-sample.

Both original ones can be found in SDK-Templates of Jose's Github repos (AfxNova-main).

I do recommend to follow the template - and add your needs.

For an easy use I give you my results here.

b.r.

P.S. The TabCtrl_01b.bas ist the "newer 2026) with J.R. evolution for  Ctab, CListbox, CCombox. (see all #includes)

Hi, Jose  - Thanks - attachment exchanged - for improvement reason -
!!  Listview tabpage should (must)  fit to the "main Window's ClientSize. and shows the scrollbars"

José Roca

#2
Hi hajubu,

Thanks very much for your reply.

Your example works very well, but the reason why the ListView does not show its scrollbars is that the control is not being resized to the actual client area of the tab page. The fixed values: pTabPage->SetWindowPos hListView, NULL, 0, 0, 770, 365, SWP_NOZORDER only work by coincidence if the tab page happens to have exactly that size.

To make the ListView fill the entire tab page (and therefore show scrollbars when needed), you should calculate the real width and height of the page window:

Replace   pTabPage->SetWindowPos hListView, NULL, 0, 0, 770, 365, SWP_NOZORDER with:

' // Get the width and height of the tab page
DIM w AS LONG = pTabPage->Width
DIM h AS LONG = pTabPage->Height
' // Set the listview position and visible size
pTabPage->SetWindowPos hListView, NULL, 0, 0, w, h, SWP_NOZORDER

This ensures that:

* the ListView always fits the tab page

* scrollbars appear correctly

* the layout adapts to any window size or DPI setting

Everything else in your example is excellent — you adapted the ListView code exactly as intended. This is the only adjustment needed to make it behave perfectly.


hajubu

#3
Hi Jose,
Thanks - you are right - my fault - used the extracted snippet from the Listview_01.bas,
which is built  in the "main window"  with  Listview  parameter  of the pwindow.SETClientSize .

I did exchange my sample for Richard with the improvements.

b.r. Hans (hajubu)

- Think first -> work better ->  :)

José Roca

#4
One more important detail about the tab page lifecycle.

When the callback receives WM_CREATE, the tab control already exists, but the tab page itself has NOT yet been inserted into the tab control.

Therefore pTabPage->Width and pTabPage->Height are still invalid

Because of this, any attempt to resize the ListView during WM_CREATE will use incorrect dimensions, and the scrollbars will not appear.

The correct Win32 technique is to post a custom message and perform the initialization after the tab page has been inserted and sized:

CASE WM_CREATE
    PostMessageW hwnd, WM_USER + 1, 0, 0
    RETURN 0

Then handle the real initialization here:

CASE WM_USER + 1
    ' Now the tab page has been inserted and has its final size
    DIM pTabPage AS CTabPage PTR = AfxCTabPagePtr(GetParent(hwnd), 0)
    IF pTabPage = NULL THEN EXIT FUNCTION
    DIM hListView AS HWND = pTabPage->AddControl("ListView", hwnd, IDC_LISTVIEW)

    DIM w AS LONG = pTabPage->Width
    DIM h AS LONG = pTabPage->Height
    SetWindowPos hListView, NULL, 0, 0, w, h, SWP_NOZORDER

This ensures that:

* the tab page exists inside the tab control

* the final client size is known

* the ListView fills the page correctly

BTW you're doing very good work. It simply takes time to learn something as extensive as AfxNova. And this example is very tricky.

Richard Kelly

Thank you for that example. I'm still not getting a visible LV on the first tab page.

Creating the page

   DIM hTab AS HWND = pWindow.AddControl("TABCONTROL", hwndMain, IDC_TABCONTROL, "TabControl", 14, 167, 704, 448)
   ' // Anchor the control
   pWindow.AnchorControl(hTab, AFX_ANCHOR_HEIGHT_WIDTH)
   DIM pTabPage1 AS CTabPage PTR = NEW CTabPage
   pTabPage1->InsertPage(hTab, 0, "Date Types", -1, @TabPage1_WndProc)
   pTabPage1->SetBackColor(RGB_LIGHTGRAY)

The tab page does show up with a light gray background.

Creating the LV

FUNCTION TabPage1_WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
   
   SELECT CASE uMsg

    CASE WM_CREATE
        DIM pTabPage AS CTabPage PTR = AfxCTabPagePtr(GetParent(hwnd), 0)
        DIM hListView AS HWND = pTabPage->AddControl("LVSupportedCalendars", hwnd, IDC_LVPAGE1)
        ' // Get the width and height of the tab page
        DIM w AS LONG = pTabPage->Width
        DIM h AS LONG = pTabPage->Height
        ' // Set the listview position and visible size
        pTabPage->SetWindowPos hListView, NULL, 0, 0, w, h, SWP_NOZORDER
        ' // Anchor the ListView
        pTabPage->AnchorControl(IDC_LVPAGE1, AFX_ANCHOR_HEIGHT_WIDTH)
        DIM dwsText AS DWSTRING
        dwsText = "Name"
        ListView_AddColumn(hListView, 0, dwsText, pTabPage->ScaleX(110))
        dwsText = "Date"
        ListView_AddColumn(hListView, 1, dwsText, pTabPage->ScaleX(200))
        dwsText = "Name Column"
        ListView_SetItemText(hListView, 0, 0, dwsText)
        dwsText = "Date Column"
        ListView_SetItemText(hListView, 1, 0, dwsText)
        EXIT FUNCTION
         
   END SELECT
   
   FUNCTION = DefWindowProcW(hWnd, uMsg, wParam, lParam)

END FUNCTION

Do I need a show window somewhere?

José Roca

1.- The name of the ListView class is "ListView" or "SYSLISTVIEW32", not "LVSupportedCalendars".

2.- You're using ListView_SetItemText twice, but yopu aren't adding any item.

3.- As you're using the ListView_xxx macros, make sure that you use #define UNICODE

4.- Haven't you read my previous post?

      CASE WM_CREATE
         PostMessageW hwnd, WM_USER + 1, 0 , 0
         RETURN 0

      CASE WM_USER + 1
        DIM pTabPage AS CTabPage PTR = AfxCTabPagePtr(GetParent(hwnd), 0)
        DIM hListView AS HWND = pTabPage->AddControl("ListView", hwnd, IDC_LVPAGE1)

        ' // Get the width and height of the tab page
        DIM w AS LONG = pTabPage->Width
        DIM h AS LONG = pTabPage->Height
        ' // Set the listview position and visible size
        pTabPage->SetWindowPos hListView, NULL, 0, 0, w, h, SWP_NOZORDER
        ' // Anchor the ListView
        pTabPage->AnchorControl(IDC_LVPAGE1, AFX_ANCHOR_HEIGHT_WIDTH)
        DIM dwsText AS DWSTRING
        dwsText = "Name"
        ListView_AddColumn(hListView, 0, dwsText, pTabPage->ScaleX(110))
        dwsText = "Date"
        ListView_AddColumn(hListView, 1, dwsText, pTabPage->ScaleX(200))

        dwsText = "Name Column"
        ListView_AddItem(hListView, 0, 0, dwsText)
        dwsText = "Date Column"
        ListView_SetItemText(hListView, 0, 1, dwsText)

        EXIT FUNCTION

Richard Kelly

#7
I'm sorry to waste your time. I overlooked that part of your post. Here is the updated code that works. I switched over to using your CListView class.

========================================================================================
' Tab page 1 window procedure
' ========================================================================================
FUNCTION TabPage1_WndProc (BYVAL hwnd AS HWND, BYVAL uMsg AS UINT, BYVAL wParam AS WPARAM, BYVAL lParam AS LPARAM) AS LRESULT
   
   SELECT CASE uMsg
       
    CASE WM_CREATE
        PostMessageW hwnd, WM_USER + 1, 0, 0
       
    CASE WM_USER + 1
    ' Now the tab page has been inserted and has its final size

        DIM pTabPage AS CTabPage PTR = AfxCTabPagePtr(GetParent(hwnd), 0)
        DIM hListView AS HWND = pTabPage->AddControl("LISTVIEW", hwnd, IDC_LVPAGE1)
        ' // Get the width and height of the tab page
        DIM w AS LONG = pTabPage->Width
        DIM h AS LONG = pTabPage->Height
        ' // Set the listview position and visible size
        pTabPage->SetWindowPos hListView, NULL, 0, 0, w, h, SWP_NOZORDER
        ' // Anchor the ListView
        pTabPage->AnchorControl(IDC_LVPAGE1, AFX_ANCHOR_HEIGHT_WIDTH)
        DIM lvc AS LVCOLUMNW
        lvc.mask = LVCF_FMT OR LVCF_WIDTH OR LVCF_TEXT
        lvc.fmt = LVCFMT_LEFT
        lvc.cx = 110
        DIM wszText AS WSTRING * 260 = "Name"
        lvc.pszText = @wszText
        CListView.InsertColumn(hListView, 0, @lvc)
        lvc.cx = 200
        wszText = "Date"
        lvc.pszText = @wszText
        CListView.InsertColumn(hListView, 1, @lvc)
               
        EXIT FUNCTION
         
   END SELECT
   
   FUNCTION = DefWindowProcW(hWnd, uMsg, wParam, lParam)

END FUNCTION

Now that I can get my form built, I have a subroutine that adds all the data values. What technique would you recommend so that gets run the first time after the form is completely built? I have not yet put in any code to detect when my datepicker changes values for subsequent data refreshes.

José Roca

#8
The static classes like CListView are to save work, so instead of

DIM lvc AS LVCOLUMNW
        lvc.mask = LVCF_FMT OR LVCF_WIDTH OR LVCF_TEXT
        lvc.fmt = LVCFMT_LEFT
        lvc.cx = 110
        DIM wszText AS WSTRING * 260 = "Name"
        lvc.pszText = @wszText
        CListView.InsertColumn(hListView, 0, @lvc)
        lvc.cx = 200
        wszText = "Date"
        lvc.pszText = @wszText
        CListView.InsertColumn(hListView, 1, @lvc)

You can simply use:

CListView.AddColumn(hListView, 0, "Name", pWindow.ScaleX(110))
CListView.AddColumn(hListView, 1, "Date", pWindow.ScaleX(200))

CListView.AddColumn does this internally:

PRIVATE FUNCTION CListView.AddColumn (BYVAL hListView AS HWND, BYVAL iCol AS LONG, BYREF wszText AS WSTRING, BYVAL nWidth AS LONG, BYVAL nFormat AS LONG = LVCFMT_LEFT) AS LONG
  DIM lvc AS LVCOLUMNW
  lvc.mask = LVCF_FMT OR LVCF_WIDTH OR LVCF_TEXT OR LVCF_SUBITEM
  lvc.fmt = nFormat
  lvc.pszText = @wszText
  lvc.cx = nWidth
  RETURN SendMessageW(hListView, LVM_INSERTCOLUMNW, iCol, CAST(LPARAM, @lvc))
END FUNCTION

The use of pWindow.ScaleX is to make the width the same relative size depending of the DPI being used.

José Roca

#9
QuoteNow that I can get my form built, I have a subroutine that adds all the data values. What technique would you recommend so that gets run the first time after the form is completely built? I have not yet put in any code to detect when my datepicker changes values for subsequent data refreshes.

That depends of each control. You have to read the MSDN documentation: https://learn.microsoft.com/en-us/windows/win32/controls/date-and-time-picker-control-reference

See the notification messages in that link.

For example, to detect that the date time has changed, you have to process DTN_DATETIMECHANGE.

      CASE WM_NOTIFY
        ' // Notification messages
        DIM dtp AS NMDATETIMECHANGE
        CBNMTYPESET(dtp, wParam, lParam)
        IF dtp.nmhdr.idfrom = IDC_DTPICKER THEN
            IF dtp.nmhdr.code = DTN_DATETIMECHANGE THEN
              ' // Get the selected date
              DIM wszDate AS WSTRING * 260
              GetDateFormatW LOCALE_USER_DEFAULT, DATE_LONGDATE, @dtp.st, NULL, wszDate, SIZEOF(wszDate)\2
              SetWindowText(GetDlgItem(hwnd, IDC_LABEL), "Selected date: " & wszDate)
            END IF
        END IF

Full example: https://github.com/JoseRoca/AfxNova/blob/main/Templates/SDK%20Templates/CW_DTPicker_01b.bas

The Windows API always sends notification messages. Some languages, like .NET, intercept these messages and send "events".

Richard Kelly

Thank you Hajubu and Jose for the great assistance and support. I have the first of 4 tabs done and everything works perfectly.


hajubu

HI,
getting the hint from Jose for "the correct Listview  filling in /of the page"

I also adapted the snippet of listview_01b.bas now as a fourth Tabpage inside the TabCtrl01b.bas as my exercise using CListview.inc

Thanks ,  have fun !
Hans (hajubu)