Terminal.Gui icon indicating copy to clipboard operation
Terminal.Gui copied to clipboard

Add 'Visible' property to ColumnStyle in TableView

Open tznind opened this issue 2 years ago • 4 comments

This is a new ticket to come out of https://github.com/migueldeicaza/gui.cs/issues/1463 (I've created a new ticket since that one is closed and was more general).

It is to add the ability to hide columns in TableView:

I think a better solution would be to add a Visible property to the ColumnStyle class. I can look into this, it should be reasonably easy.

tznind avatar Sep 16 '21 08:09 tznind

I have taken a look at this. Its pretty hard and would increase complexity in the following areas:

  • A lot of operations e.g. EnsureSelectedCellIsVisible and EnsureValidScrollOffsets query Table.Columns directly. Making these consider hidden columns would increase code complexity
  • Fields like ColumnOffset and SelectedColumn are int fields with public setters. Quite a bit of complexity there (e.g. are hidden columns scrollable/selectable?). What does SelectedColumn++ do when the next col is hidden (does it stay the same or skip the hidden ones?) This could make users existing for loops unpredictable.
  • TableSelection (multi cell selections) describes a rectangle selected in the view. Allowing hidden columns could easily result in area selections including hidden cells.

I looked into how users can work with the current implementation of TableView and still get column show/hide. It can be done by combining void Remove(DataColumn col) and DataTable Copy(). Although this is not ideal it would be a route people could go down until (if) this feature is implemented:

[Fact]
public void TableView_RemoveCol_ThenReAdd ()
{
	var tv = SetUpMiniTable ();

	tv.Style.ExpandLastColumn = true;
	tv.Style.InvertSelectedCellFirstCharacter = true;

	// width exactly matches the max col widths
	tv.Bounds = new Rect (0, 0, 5, 4);

	tv.Redraw (tv.Bounds);

	string expected = @"
┌─┬─┐
│A│B│
├─┼─┤
│1│2│
";
	GraphViewTests.AssertDriverContentsAre (expected, output);

	// Take a copy of the original table because Remove will delete row data along with the column
	var orig = tv.Table.Copy ();

	// 'hide' a column
	var colB = tv.Table.Columns [1];
	tv.Table.Columns.Remove (colB);

	tv.Redraw (tv.Bounds);

	expected = @"
┌───┐
│A  │
├───┤
│1  │
";
	GraphViewTests.AssertDriverContentsAre (expected, output);

	// Column cannot just be re-added.  Since row data will be null if you do that.  Instead we have to 
	// go back to the original table.
	tv.Table = orig;
	tv.Redraw (tv.Bounds);

	expected = @"
┌─┬─┐
│A│B│
├─┼─┤
│1│2│
";
	GraphViewTests.AssertDriverContentsAre (expected, output);

	// Shutdown must be called to safely clean up Application if Init has been called
	Application.Shutdown ();
}

tznind avatar Oct 15 '21 08:10 tznind

A wrapper to get the ColumnStyle length based on a property IsHidden. If is true, then will return 0 and won't be redrawn. But as you said this will would increase code complexity.

BDisp avatar Oct 15 '21 09:10 BDisp

A wrapper to get the ColumnStyle length based on a property IsHidden. If is true, then will return 0 and won't be redrawn. But as you said this will would increase code complexity.

Not drawing the column is relatively easy but navigation (left/right arrow keys etc) is where it gets complicated and that is before you get to square selections (shift right etc)...

tznind avatar Oct 15 '21 11:10 tznind

I know what you meaning :-) An more easy workaround is do like you did but removing and re-adding within a controlled way. Maybe create a class to store the hidden columns after removing. You will need to add some more information about the position, etc., for when the column will be unhidden to recreate in the original positions.

BDisp avatar Oct 15 '21 11:10 BDisp

Hello,

for anyone looking for a workaround here is what worked for me: image

I used SQL to query data and then load it from datatable to tableview. Then using ColumnStyle I set MaxWidth to 0. The only difference is a double border at the end which is not a big deal - the border does not stack so if you have more hidden columns at the end there will be still one additional border (well at least for 2 hidden columns, have not tried it with more).

Also here is the code used to apply ColumnStyles:

tableView.Style.ColumnStyles.Add(tableView.Table.Columns["colname"], new TableView.ColumnStyle() { MaxWidth = 0 });

The user can still select it but it shouldn't be a big problem...

radek3911 avatar Nov 23 '22 22:11 radek3911

@tznind how about not draw if width is equal to zero?

BDisp avatar Nov 24 '22 10:11 BDisp

Thanks for sharing this trick @radek3911, very handy.

I see you are using Add and the ColumnStyle constructor. Another option which you might find tidier or more readable is to use GetOrCreateColumnStyle directly (both work fine though).

tableView.Style.GetOrCreateColumnStyle (tableView.Table.Columns ["colname"]);
colStyle.MaxWidth = 0;

@tznind how about not draw if width is equal to zero?

That would be a good idea. Once Visible is added we could do something like:

private bool _visible = true;

public bool Visible 
{
	get { return MaxWidth > 0 &&  _visible; }
	set { _visible = value; }
}

Proposed new code in ColumnStyle

The ability to hide columns is definetly a nice feature to have.

It has some complexity because keyboard navigation and mouse selection etc have to all consider invisible columns. So for example when you press right from A1 and column B is invisible the cursor needs to move to C1. So its not just a rendering issue.

I have been meaning to find time to dive deeper into a solution for this feature but I have also just started a new job so don't have as much time at the moment to tackle it.

tznind avatar Nov 26 '22 09:11 tznind

Ok I've started tinkering on this https://github.com/gui-cs/Terminal.Gui/pull/2243 I have added as many TODO items as I can think of but there will probably be more as I dig into this feature.

tznind avatar Nov 26 '22 23:11 tznind