https://github.com/merlinz01/ztable
ZTable - the better Windows table widget
https://github.com/merlinz01/ztable
customizable filterable sortable table widget win32 win32-c
Last synced: 10 months ago
JSON representation
ZTable - the better Windows table widget
- Host: GitHub
- URL: https://github.com/merlinz01/ztable
- Owner: merlinz01
- License: mit
- Created: 2024-04-27T20:02:08.000Z (almost 2 years ago)
- Default Branch: main
- Last Pushed: 2024-04-27T20:10:14.000Z (almost 2 years ago)
- Last Synced: 2025-05-22T06:03:37.880Z (11 months ago)
- Topics: customizable, filterable, sortable, table, widget, win32, win32-c
- Language: C
- Homepage:
- Size: 62.5 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# ZTable - the better Windows table widget
ZTable is a easy-to-use Win32 table window with a more data-centric focus than Windows' builtin ListView.
Written in pure C with API exposed via window messages.

## Win32 Usage
Just call the `SetupClass` function to register the window class.
The class name of the table is "ZTable".
Message and notification constants are in `ztable.h`
## Python Usage
ZTable is geared toward Python usage and therefore includes a Python class wrapper.
### Note:
The Python class uses an unpublished home-made Win32 ctypes wrapper,
but it probably could be made to work with pywin32
with some tweaking.
Example usage:
```python
from gui32.toplevel import Toplevel
from ztable import *
class YourWindow(Toplevel):
def __init__(self):
...
self.table = ZTable(self)
self.table.SetColumns(
self.table.MakeColumn(headerText="Column One"),
self.table.MakeColumn(headerText="Column Two"),
...
)
for i in range(10):
self.table.InsertRow(-1, 'value one', 'value two', ...)
...
```
## Features
### Column alignment
Text can be aligned left, right, or center per column, except for the header text, which is always centered.
### Resizable columns
If a column does not have `ZTC_NORESIZE`, it can be resized by dragging their dividers with the mouse.
Or you can set the width programmatically:
```python
table.SetColumnWidth(column_index, 157)
```
The width of a column can be specified on creation, along with a minimum and maximum width:
```python
table.SetColumns(
table.MakeColumn(headerText='157 pixels wide', width=157, minWidth=20, maxWidth=350),
...
)
```
If a column has `ZTC_DEFSIZEONRCLICK`, it will snap to its default size when its header is right-clicked.
The default size is set on creation:
```python
table.SetColumns(
table.MakeColumn(headerText='Snaps to 100px width', defaultWidth=100),
...
)
```
### Table notifications
The table notifies of right-clicks anywhere on the window.
```python
class YourWindow(Toplevel):
def __init__(self):
...
self.table.OnRightClick = self.OnTableRightClicked
...
def OnTableRightClicked(self, row_index, column_index, x, y):
# Post a popup menu at (x, y)
...
```
### Column notifications
Columns can be set to report double-click notifications with `ZTC_NOTIFYDBLCLICKS`.
Columns that do not have `ZTC_DEFAULTSORT` report header clicks.
```python
class YourWindow(Toplevel):
def __init__(self):
...
self.table.OnDoubleClick = self.OnCellDoubleClicked
self.table.OnHeaderClicked = self.OnTableHeaderClicked
...
def OnTableHeaderClicked(self, x, y, column_index):
# Post a menu to select options
...
def OnCellDoubleClicked(self, row_index, column_index, x, y):
# Open a dialog to show more details
...
```
### Sorting
Columns with `ZTC_DEFAULTSORT` automatically sort the table when their header is clicked.
Otherwise, you can implement custom sorting by setting the `OnHeaderClicked` callback to a function that calls `Sort`, as shown:
```python
class YourWindow(Toplevel):
def __init__(self):
...
self.table.OnHeaderClicked = self.OnTableHeaderClicked
...
def OnTableHeaderClicked(self, row_index, column_index):
lParam = 0xc0ffee
reverse = -1 # Use opposite of current column reverse state
self.table.Sort(self.ComparisonCallback, column_index, reverse, lParam)
def ComparisonCallback(self, hwnd_table, itemptr_1, itemptr_2, column_index, lParam):
if lParam != 0xc0ffee:
print("I ordered coffee!")
text1 = itemptr_1.contents.items[column_index].text.value
text2 = itemptr_2.contents.items[column_index].text.value
if text1 == text2:
return 0
elif text1 > text2:
return 1
else:
return -1
```
### Editing
Columns with `ZTC_EDITABLE` can edited by double-clicking a cell in that column.
If the editing notifications are not handled, simple string editing is performed.
To customize the editing process, set the `OnEditStart` and/or `OnEditEnd` callbacks, as shown:
```python
from gui32.edit import Edit
class YourWindow(Toplevel):
def __init__(self):
...
self.table.OnEditStart = self.OnEditStart
self.table.OnEditEnd = self.OnEditEnd
...
def OnEditStart(self, row_index, column_index, edit_window_handle):
# The edit control currently has the text of the cell we are editing
edit = Edit.from_handle(edit_window_handle)
text = edit.Text
# We can set the text that appears in the edit control
edit.Text = "Text being edited: " + text.strip()
def OnEditEnd(self, row_index, column_index, edit_window_handle):
# The edit control currently has the text that the user entered
edit = Edit.from_handle(edit_window_handle)
text = edit.Text
# We can set the text that is set to the cell being edited
edit.Text = text.removeprefix("Text being edited: ")
# If we succeeded, return False
# Had we decided the text was invalid, we could return True
# to select the contents of the edit control and continue editing
return False
```
You can start an editing process programmatically:
```python
table.BeginEditing(row_index, column_index)
```
You can end an editing process:
```python
# This will call table.OnEditEnd
# It may or may not actually end the editing.
table.EndEditing()
```
You can cancel an editing process:
```python
# This will not call table.OnEditEnd
# The editing will end regardless of the text entered
table.CancelEditing()
```
You can see if an editing procedure is currently active:
```python
print(table.InEditing)
```
You can combine these to make a one-shot try at ending
the editing procedure gracefully (e.g., when closing the program):
```python
# Is there an active editing procedure?
if table.InEditing:
# If so, try to save the value
table.EndEditing()
# Did it work?
if table.InEditing:
# If not, cancel and go on
table.CancelEditing()
```
### Custom row colors
Any row can either have default colors, or a custom text color and background fill brush.
Set the colors as shown:
```python
# Set to default colors
table.SetRowColors(row_index)
# Set text color to a medium gray
table.SetRowColors(row_index, textColor=0x808080)
# Create a custom background brush
from gui32.backend import winapi, wintypes
red_checkered_brush = winapi.CreateBrushIndirect(
wintypes.LOGBRUSH(2, 0x0000ff, 5))
# Set background fill to red checkers
table.SetRowColors(row_index, fillBrush=red_checkered_brush)
# Set both text color and background fill
table.SetRowColors(row_index, textColor=0x808080, fillBrush=red_checkered_brush)
# You can also set these when creating the row
table.InsertRow(-1, 'Values', ..., textColor=0x808080, fillBrush=red_checkered_brush)
```
### Custom cell colors
If a column has `ZTC_CUSTOMBG`, the user-defined parameter for each cell
in that column is a win32 brush that is the background fill of the cell.
If the parameter is `0`, the default or row-specific background is used.
If a column has `ZTC_CUSTOMFG`, the user-defined parameter for each cell
in that column is a `COLORREF` value that is the text color of the cell.
If the parameter is `CLR_DEFAULT`, the default or row-specific text color is used.
A column cannot have both custom background and custom text color.
Example:
```python
table.SetColumns(
table.MakeColumn(headerText='Custom text color', flags=ZTC_CUSTOMFG),
table.MakeColumn(headerText='Custom background', flags=ZTC_CUSTOMBG),
)
from gui32.backend import winapi
colors = (
(0x101010, winapi.CreateSolidBrush(0xf0f0f0)),
(0x123456, winapi.CreateSolidBrush(0x654321)),
...
)
for color, brush in colors:
row_index = table.InsertRow(-1, 'howdee', 'do')
table.SetItemParam(row_index, 0, color)
table.SetItemParam(row_index, 1, brush)
```
### Row filtering
Rows can be hidden or shown arbitrarily, as shown:
```python
# Hide the row without losing any data
table.SetRowFiltered(row_index, False)
# Show the row again
table.SetRowFiltered(row_index, True)
```
This does not affect the internal row table or a row's associated index,
so
```python
for row_index in range(len(table)):
print(table.GetItemText(row_index, 0))
```
will print data for hidden rows also.
### Custom line height
You can set the height of rows in the table, in multiples of line height, as shown:
```python
# Make all the rows in the table 3 lines high
table.SetRowHeight(3)
```
### Multiline cells
If a column has `ZTC_MULTILINE`, the text in each cell will be drawn with `DT_WORDBREAK` flag set.
This means that `\r\n` line separators will cause line breaks.
Due to the limited capabilities of the Win32 `DrawText` function, the text will not be vertically centered.
This feature is obviously only useful in conjunction with custom line height.
### New row generation
If you turn on automatic row generation:
```python
table.SetAutoMakeNewRow(True)
```
then when an editing procedure has successfully finished in the last row in the table,
a new row is appended to the table.
The table sends a notification when it adds a row so that you can customize the contents of each new row.
To handle this notification:
```python
class YourWindow(Toplevel):
def __init__(self):
...
self.table.OnAutoNewRow = self.OnAutoNewRow
...
def OnAutoNewRow(self, row_index):
self.table.SetItemText(row_index, 0, 'This row was added.')
```
### Row selector column
If the first column has `ZTC_SELECTOR`, it is displayed without cell text, with a uniform fill color, and with an arrow in the currently selected row.
If new-row generation is enabled, it displays a `*` icon in the last row.
```python
table.SetColumns(
table.MakeColumn(flags=ZTC_SELECTOR),
...
)
```
## Features not implemented
- Keybinding to start editing
- Custom editing widgets - mostly done
- Icons and bitmaps (at least check marks)? - supported sort-of for selector column
- Allow setting default row colors?
- Combined custom background and text color per cell
- Custom borders per cell
- Allow text (line numbers etc) in selector column, highlighted selection there
## License
MIT License