add first win32 listview/table code

Wed, 19 Nov 2025 11:41:33 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Wed, 19 Nov 2025 11:41:33 +0100
changeset 917
ca3918f9c96b
parent 916
6fdcf1cbbec9
child 918
7d3dd5aacfda

add first win32 listview/table code

application/main.c file | annotate | diff | comparison | revisions
ui/ui/tree.h file | annotate | diff | comparison | revisions
ui/win32/list.c file | annotate | diff | comparison | revisions
ui/win32/list.h file | annotate | diff | comparison | revisions
ui/win32/objs.mk file | annotate | diff | comparison | revisions
ui/win32/toolkit.c file | annotate | diff | comparison | revisions
--- a/application/main.c	Wed Nov 19 09:20:18 2025 +0100
+++ b/application/main.c	Wed Nov 19 11:41:33 2025 +0100
@@ -1196,38 +1196,34 @@
     printf("button clicked\n");
 }
 
+typedef struct Person {
+    char *name;
+    char *email;
+} Person;
+
+Person person1 = { "Alice", "alice@localhost" };
+Person person2 = { "Bob", "bob@localhost" };
+
+void* person_getvalue(void *elm, int col) {
+    Person *p = (Person *) elm;
+    if (col == 0) {
+        return p->name;
+    } else {
+        return p->email;
+    }
+}
+
 void application_startup(UiEvent *event, void *data) {
 	UiObject *obj = ui_window("Test w32", NULL);
 
-    ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10, .fill = TRUE) {
-        ui_button(obj, .label = "Test", .hfill = TRUE, .hexpand = TRUE, .colspan = 3, .margin = 10);
-        ui_newline(obj);
-        ui_textfield(obj, .varname = "text1", .hfill = TRUE, .hexpand = TRUE, .colspan = 2);
-        ui_newline(obj);
-
-        ui_button(obj, .label = "Test 2-1");
-        ui_button(obj, .label = "Test 2-2");
-        ui_button(obj, .label = "Test 2-3");
-        ui_newline(obj);
-
-        ui_button(obj, .label = "Test 3XX", .colspan = 3, .fill = TRUE, .onclick = action_button);
-        ui_newline(obj);
+    UiList *list = ui_list_new(obj->ctx, "persons");
+    ui_list_append(list, &person1);
+    ui_list_append(list, &person2);
 
-        ui_button(obj, .label = "End");
-        ui_newline(obj);
-
-        ui_togglebutton(obj, .label = "Test");
-        ui_newline(obj);
-
-        ui_checkbox(obj, .label = "Checkbox");
-        ui_newline(obj);
-
-        ui_hbox(obj, .spacing = 10, .colspan = 3, .hexpand = TRUE, .hfill = TRUE) {
-            ui_radiobutton(obj, .label = "Option 1", .varname = "radio1");
-            ui_radiobutton(obj, .label = "Option 2", .varname = "radio1");
-            ui_radiobutton(obj, .label = "Option 3", .varname = "radio1");
-        }
-
+    ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10, .fill = TRUE) {
+        UiModel *model = ui_model(obj->ctx, UI_STRING, "Name", UI_STRING, "Email", -1);
+        ui_table(obj, .fill = TRUE, .varname = "persons", .model = model, .getvalue = person_getvalue);
+        ui_model_free(obj->ctx, model);
     }
 
 
--- a/ui/ui/tree.h	Wed Nov 19 09:20:18 2025 +0100
+++ b/ui/ui/tree.h	Wed Nov 19 11:41:33 2025 +0100
@@ -132,9 +132,9 @@
 
     const char *name;
     const char *style_class;
-    UiList* list;
+    UiList *list;
     const char* varname;
-    UiModel* model;
+    UiModel *model;
     char **static_elements;
     size_t static_nelm;
     ui_getvaluefunc getvalue;
@@ -143,15 +143,15 @@
     ui_getstylefunc getstyle;
     void *getstyledata;
     ui_callback onactivate;
-    void* onactivatedata;
+    void *onactivatedata;
     ui_callback onselection;
-    void* onselectiondata;
+    void *onselectiondata;
     ui_callback ondragstart;
-    void* ondragstartdata;
+    void *ondragstartdata;
     ui_callback ondragcomplete;
-    void* ondragcompletedata;
+    void *ondragcompletedata;
     ui_callback ondrop;
-    void* ondropdata;
+    void *ondropdata;
     UiBool multiselection;
     UiMenuBuilder *contextmenu;
     ui_list_savefunc onsave;
@@ -300,10 +300,10 @@
 #define ui_breadcrumbbar(obj, ...) ui_breadcrumbbar_create(obj, &(UiListArgs) { __VA_ARGS__ } )
 #define ui_sourcelist(obj, ...) ui_sourcelist_create(obj, &(UiSourceListArgs) { __VA_ARGS__ } )
 
-UIEXPORT UIWIDGET ui_listview_create(UiObject* obj, UiListArgs *args);
-UIEXPORT UIWIDGET ui_table_create(UiObject* obj, UiListArgs *args);
-UIEXPORT UIWIDGET ui_combobox_create(UiObject* obj, UiListArgs *args);
-UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject* obj, UiListArgs *args);
+UIEXPORT UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args);
+UIEXPORT UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args);
+UIEXPORT UIWIDGET ui_combobox_create(UiObject *obj, UiListArgs *args);
+UIEXPORT UIWIDGET ui_breadcrumbbar_create(UiObject *obj, UiListArgs *args);
 
 UIEXPORT void ui_listview_select(UIWIDGET listview, int index);
 UIEXPORT void ui_combobox_select(UIWIDGET dropdown, int index);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/list.c	Wed Nov 19 11:41:33 2025 +0100
@@ -0,0 +1,237 @@
+/*
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "list.h"
+#include "container.h"
+
+
+
+static W32WidgetClass listview_widget_class = {
+    .eventproc = ui_listview_eventproc,
+    .enable = w32_widget_default_enable,
+    .show = w32_widget_default_show,
+    .get_preferred_size = ui_listview_get_preferred_size,
+    .destroy  = w32_widget_default_destroy
+};
+
+static void* strmodel_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
+    return col == 0 ? elm : NULL;
+}
+
+static void* getvalue_wrapper(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
+    ui_getvaluefunc getvalue = (ui_getvaluefunc)userdata;
+    return getvalue(elm, col);
+}
+
+static void* null_getvalue(UiList *list, void *elm, int row, int col, void *userdata, UiBool *freeResult) {
+    return NULL;
+}
+
+
+static UiListView* create_listview_widget(UiObject *obj, HWND hwnd, UiListArgs *args, UiBool table) {
+    UiListView *listview = w32_widget_create(&listview_widget_class, hwnd, sizeof(UiWidget));
+    listview->widget.hwnd = hwnd;
+    listview->preferred_width = args->width ? args->width : 300;
+    listview->preferred_height = args->height ? args->height : 300;
+    listview->onactivate = args->onactivate;
+    listview->onactivatedata = args->onactivatedata;
+    listview->onselection = args->onselection;
+    listview->onselectiondata = args->onselectiondata;
+    listview->ondragstart = args->ondragstart;
+    listview->ondragstartdata = args->ondragstartdata;
+    listview->ondragcomplete = args->ondragcomplete;
+    listview->ondragcompletedata = args->ondragcompletedata;
+    listview->ondrop = args->ondrop;
+    listview->ondropdata = args->ondropdata;
+    listview->istable = table;
+
+    ui_getvaluefunc2 getvalue = args->getvalue2;
+    void *getvaluedata = args->getvalue2data;
+    if(!getvalue) {
+        if(args->getvalue) {
+            getvalue = getvalue_wrapper;
+            getvaluedata = (void*)args->getvalue;
+        } else {
+            getvalue = table ? null_getvalue : strmodel_getvalue;
+        }
+    }
+    listview->getvalue = getvalue;
+    listview->getvaluedata = getvaluedata;
+
+    listview->var = uic_widget_var(obj->ctx, obj->ctx, args->list, args->varname, UI_VAR_LIST);
+
+    return listview;
+}
+
+static UIWIDGET listview_create(UiObject *obj, UiListArgs *args, UiBool table) {
+    HINSTANCE hInstance = GetModuleHandle(NULL);
+    UiContainerPrivate *container = ui_obj_container(obj);
+    HWND parent = ui_container_get_parent(container);
+    UiLayout layout = UI_ARGS2LAYOUT(args);
+
+    int type = table ? LVS_REPORT : LVS_LIST;
+    HWND hwnd = CreateWindowEx(
+            WS_EX_CLIENTEDGE,
+            WC_LISTVIEW,
+            "",
+            WS_CHILD | WS_VISIBLE | LVS_REPORT,
+            0, 0, 100, 100,
+            parent,
+            (HMENU)1,
+            hInstance,
+            NULL);
+    ui_win32_set_ui_font(hwnd);
+
+    ListView_SetExtendedListViewStyle(
+        hwnd,
+        LVS_EX_FULLROWSELECT //| LVS_EX_GRIDLINES
+    );
+
+    UiListView *listview = create_listview_widget(obj, hwnd, args, table);
+    ui_container_add(container, (W32Widget*)listview, &layout);
+
+    // model
+    int numcolumns = 0;
+    if (table) {
+        if (args->model) {
+            listview->model = ui_model_copy(obj->ctx, args->model);
+            numcolumns = listview->model->columns;
+        } else {
+            listview->model = ui_model_new(obj->ctx);
+        }
+    } else {
+        UiModel *model = ui_model_new(obj->ctx);
+        ui_model_add_column(obj->ctx, model, UI_STRING, "Test", -1);
+        listview->model = model;
+        numcolumns = 1;
+    }
+
+    ///*
+    UiModel *model = listview->model;
+    for (int i=0;i<numcolumns;i++) {
+        LVCOLUMN col;
+        UiModelType type = model->types[i];
+        char *title = model->titles[i];
+        size_t titlelen = title ? strlen(title) : 0;
+        int size = model->columnsize[i];
+        switch (type) {
+            default: {
+                col.mask = LVCF_TEXT | LVCF_WIDTH;
+                col.pszText = title;
+                col.cx = size > 0 ? size : titlelen*10+5;
+                break;
+            }
+            case UI_ICON: {
+                break; // TODO
+            }
+        }
+        ListView_InsertColumn(hwnd, i, &col);
+    }
+
+    if (listview->var) {
+        UiList *list = listview->var->value;
+        list->obj = listview;
+        list->update = ui_listview_update;
+        list->getselection = ui_listview_getselection;
+        list->setselection = ui_listview_setselection;
+
+        ui_listview_update(list, -1);
+    }
+
+    return (W32Widget*)listview;
+}
+
+void ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
+
+}
+
+W32Size ui_listview_get_preferred_size(W32Widget *widget) {
+    UiListView *listview = (UiListView*)widget;
+    W32Size size;
+    size.width = listview->preferred_width;
+    size.height = listview->preferred_height;
+    return size;
+}
+
+void ui_listview_update(UiList *list, int row) {
+    UiListView *listview = (UiListView*)list->obj;
+    HWND hwnd = listview->widget.hwnd;
+    UiModel *model = listview->model;
+    if (row < 0) {
+        ListView_DeleteAllItems(hwnd);
+        void *elm = list->first(list);
+        int row = 0;
+        while (elm) {
+            LVITEM item;
+            item.mask = LVIF_TEXT;
+            item.iItem = row;
+            item.iSubItem = 0;
+            int idx = -1;
+            for (int col=0;col<model->columns;col++) {
+                UiBool freeResult = FALSE;
+                char *str = listview->getvalue(list, elm, row, col, listview->getvaluedata, &freeResult);
+                if (col == 0) {
+                    item.pszText = str;
+                    idx = ListView_InsertItem(hwnd, &item);
+                } else {
+                    ListView_SetItemText(hwnd, idx, col, str);
+                }
+
+                if (freeResult) {
+                    free(str);
+                }
+            }
+
+            elm = list->next(list);
+            row++;
+        }
+
+        for (int i=0;i<model->columns;i++) {
+            ListView_SetColumnWidth(hwnd, i, LVSCW_AUTOSIZE);
+        }
+    } else {
+        // TODO
+    }
+}
+
+UiListSelection ui_listview_getselection(UiList *list) {
+    UiListSelection sel = { 0, NULL };
+    return sel;
+}
+
+void ui_listview_setselection(UiList *list, UiListSelection selection) {
+
+}
+
+UIWIDGET ui_listview_create(UiObject *obj, UiListArgs *args) {
+    return listview_create(obj, args, FALSE);
+}
+
+UIWIDGET ui_table_create(UiObject *obj, UiListArgs *args) {
+    return listview_create(obj, args, TRUE);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ui/win32/list.h	Wed Nov 19 11:41:33 2025 +0100
@@ -0,0 +1,75 @@
+/*
+* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2025 Olaf Wintermann. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ *   1. Redistributions of source code must retain the above copyright
+ *      notice, this list of conditions and the following disclaimer.
+ *
+ *   2. Redistributions in binary form must reproduce the above copyright
+ *      notice, this list of conditions and the following disclaimer in the
+ *      documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef LIST_H
+#define LIST_H
+
+#include "toolkit.h"
+#include "../ui/tree.h"
+#include "win32.h"
+#include <commctrl.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct UiListView {
+    W32Widget widget;
+    UiVar *var;
+    ui_getvaluefunc2 getvalue;
+    void *getvaluedata;
+    ui_getstylefunc getstyle;
+    void *getstyledata;
+    UiModel *model;
+    UiBool istable;
+    int preferred_width;
+    int preferred_height;
+    ui_callback onactivate;
+    void* onactivatedata;
+    ui_callback onselection;
+    void* onselectiondata;
+    ui_callback ondragstart;
+    void* ondragstartdata;
+    ui_callback ondragcomplete;
+    void* ondragcompletedata;
+    ui_callback ondrop;
+    void* ondropdata;
+} UiListView;
+
+void ui_listview_eventproc(W32Widget *widget, HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+W32Size ui_listview_get_preferred_size(W32Widget *widget);
+
+void ui_listview_update(UiList *list, int row);
+UiListSelection ui_listview_getselection(UiList *list);
+void ui_listview_setselection(UiList *list, UiListSelection selection);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //_LIST_H
\ No newline at end of file
--- a/ui/win32/objs.mk	Wed Nov 19 09:20:18 2025 +0100
+++ b/ui/win32/objs.mk	Wed Nov 19 11:41:33 2025 +0100
@@ -38,6 +38,7 @@
 WIN32OBJ += button.obj
 WIN32OBJ += grid.obj
 WIN32OBJ += text.obj
+WIN32OBJ += list.obj
 
 TOOLKITOBJS += $(WIN32OBJ:%=$(WIN32_OBJPRE)%)
 TOOLKITSOURCE += $(WIN32OBJ:%.obj=win32/%.c)
--- a/ui/win32/toolkit.c	Wed Nov 19 09:20:18 2025 +0100
+++ b/ui/win32/toolkit.c	Wed Nov 19 11:41:33 2025 +0100
@@ -64,7 +64,10 @@
 
     ui_window_init();
 
-    INITCOMMONCONTROLSEX icex = { sizeof(icex), ICC_WIN95_CLASSES };
+    INITCOMMONCONTROLSEX icex = {
+        sizeof(icex),
+        ICC_WIN95_CLASSES | ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES | ICC_BAR_CLASSES | ICC_TAB_CLASSES
+    };
     InitCommonControlsEx(&icex);
 
     SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

mercurial