Hello!
So I set myself this challenge as an introduction to HaxeUI (and a bit more of an introduction to Haxe). I’d basically like to have a cross platform CSV viewer which ideally works for reasonably large CSV files, and works something like the table UI for sqlitebrowser:
HaxeUI is quite impressive, with a lot of examples and an online builder which helped. It started off well, but then I ran straight into the problem in which I discovered that TableViews like to have their columns defined up-front. Constructing them on the fly to match the columns in a CSV file requires jumping through some hoops, as described here:
That post helped a lot: I’ve got dynamic columns working.
(At this point, I’ll add an aside: here’s an extra pointer to anyone else who tries this, which took me a while poring over the HaxeUI source code to work. Don’t use StringMaps to define the DataSource rows. Basically they won’t at all unless the HaxeUI code is refactored. In order to get dynamic DataSources to work, you have to use reflection to build anonymous structures with the right fields, matching the id
properties set on the columns (see example below). This is a bit weird - it seems to imply that dynamic TableView columns is not a normal use-case at all! When I work out where to make feature requests, this may be the first and most obvious one…)
But there’s more.
I’m still having problems customising the column headers in a way which works on the fly. I want to have column headers have labels with filters below them, as per sqlitebrowser. I’ve dug through the source code some more, but it’s starting to take silly amounts of time to make progress. Therefore I think it’s time to ask for help…
(This was also compounded a bit with problems to get Haxe building on a relatively fast platform with sys
, like Hashlink, on current LTS Ubuntu Linux. That’s another story though.)
The code as it is now (broken!) is here GitHub - wu-lee/haxe-csv at haxeui-community-wip
I’ll paste a specific part relevant to my question, which is the load()
method from src/MainView.hx.
My question: I haven’t worked out what I need to do to make my custom column headers expand to a sensible size - they’re appearing as 12 pixels width, but I want them to expand to the size of the header text which is set, at least. (Ideally to the size of the widest data field, up to some limit.)
How do I do that?
I’d also like to have the columns resizable by dragging the edges - is this something TableView can do? I’ve not seen any demos which seem to show that.
Likewise, I’d ideally like to have long fields truncated (which I seem to have managed) but then have tooltips which show the full content (which I haven’t - again, it’s not clear how to do this dynamically.)
Thanks in advance for any help anyone can offer.
MainView.hx
// ...
@:build(haxe.ui.ComponentBuilder.build("assets/main-view.xml"))
class MainView extends VBox {
public function new() {
super();
}
// ...
// Defines how we generate a colum ID from its index
private function colId(ix:Int) {
return "c" + ix;
}
private function loadString(data:String) {
trace("downloading "+data.substr(0,100));
var reader = new Reader();
reader.open(data);
load(reader);
}
private function loadFile(file:SelectedFileInfo) {
trace("load " + file.name);
this.topComponent.screen.title = file.name;
#if sys
var stream = sys.io.File.read(file.fullPath, false);
var reader = new Reader();
reader.open(stream);
load(reader);
#end
}
private function load(reader:Reader) {
// Populate a new datasource and headers array from the CSV first
var headers:Array<String> = null;
var ds = new ArrayDataSource<Dynamic>();
for (record in reader) {
if (headers == null) {
headers = record;
continue;
}
// We're forced by TableView's implementation to use anonymous objects and reflection
// rather than the probably preferable StringMap
var item:Dynamic = {};
for (ix in 0...record.length) {
var header = headers[ix];
Reflect.setField(item, colId(ix), record[ix]);
}
ds.add(item);
}
// Now add the headers we found as table columns
tv.clearContents(true);
for (ix in 0...headers.length) {
var header = headers[ix];
var col = tv.addColumn(header);
trace("size0 "+col.percentWidth+" / "+col.width);
//col.autoSize();
col.removeAllComponents();
col.sortable = true;
col.id = colId(ix);
var content = ComponentBuilder.fromFile("assets/column.xml");
var label = content.findComponent(Label, true);
if (label != null)
label.text = header;
// None of the following seems to work: the columns all get added
// with an unreadably narrow width.
trace("size1 "+col.percentWidth+" / "+col.width);
content.autoWidth = true;
//content.autoSize();
col.addComponent(content);
trace("size2 "+col.percentWidth+" / "+col.width+" / "+content.width);
//col.autoSize();
trace("size3 "+col.percentWidth+" / "+col.width);
col.autoWidth = true;
}
// Now reset the renderers for the columns.
// We have to do this or the renderers won't be appropriate.
// See https://community.haxeui.org/t/dynamic-tableview/299/6
tv.itemRenderer.removeAllComponents();
for (ix in 0...headers.length) {
var header = headers[ix];
var ir = new ItemRenderer();
var label = new Label();
label.percentWidth = 100;
label.verticalAlign = "center";
label.id = colId(ix);
label.autoHeight = false;
label.clip = true;
label.wordWrap = false;
label.onChange = (e) -> {
e.target.tooltip = e.target.value; // Doesn't work - would like to show the value
};
ir.addComponent(label);
tv.itemRenderer.addComponent(ir);
}
tv.dataSource = ds;
}
}
main-view.xml
<vbox width="100%" height="100%">
<style>
#vb1 {
}
</style>
<menubar width="100%">
<menu text="File">
<menuitem id="menuItemLoad" text="Load" shortcutText="Ctrl+L" />
<menuitem id="menuItemDownload" text="Download" shortcutText="Ctrl+D" />
<menuitem id="menuItemSave" text="Save" disabled="true" shortcutText="Ctrl+S" />
<menuitem id="menuItemSaveAs" text="Save As" disabled="true" shortcutText="Ctrl+Shift+S" />
</menu>
</menubar>
<vbox id="vb1" width="100%" height="100%">
<tableview id="tv" width="100%" height="100%" contentWidth="100%">
<header></header>
<data></data>
</tableview>
</vbox>
</vbox>
column.xml
<itemrenderer width="100%">
<column width="100%">
<vbox width="100%">
<label text="label" width="100%"/>
<textfield placeholder="Filter" width="100%" />
</vbox>
</column>
</itemrenderer>