I need a simple way to display tabular data (preferably with Grid
) and also be able to select a row or column of the table. I've tried different approaches, but I ran into dead ends.
Let's define an example table that contains entries of various types and sizes, and a dynamic variable, over
, that holds whether the mouse is over a particular row or not (it is easy to extend this approach to inclue setter/toggler controls, or for selecting columns, but that is not the direct aim of this post):
data = {
{11111111, 2, 3}, {4, "55555\n55555\n55555", 6666666.6666},
{11111111, 2, 3}, {4, "55555\n55555\n55555", 6666666.6666}
};
over = False & /@ data; (* no row is selected at initialization *)
1. With multiple Item
-s
My first approach is the one I thought would work best: treating elements of the grid as Item
-s, and dynamically update the background of only those entries which are in the given row. It is important to wrap entries individually in Item[..., Frame -> True]
, so that mouse-movements are captured in the whole cell of the grid and not just where the number is. Unfortunately though when Item
-s are wrapped in EventHandler
, Item
-specific options are lost. Note how frames are missing in the displayed grid on the right:
(* simple example showing that Item options are not forwarded by EventHandler *)
{Grid[{{1, 2}, {Item[3, Frame -> True], Item[4, Frame -> True]}}],
Grid[{{1, 2}, {EventHandler[Item[3, Frame -> True], {}],
EventHandler[Item[3, Frame -> True], {}]}}]}
Accordingly, the full row-selector construct fails too, as neither Frame
, nor Background
options are evaluated at all. As a consequence, the EventHandler
is restricted to the numbers only, and not to the full cell of the grid (see the dynamicaly displayed over
), and no background is updated.
Dynamic@over
Grid@MapIndexed[EventHandler[
Item[#1, Frame -> True,
Background -> Dynamic@If[over[[First@#2]], LightBlue, White]],
{"MouseEntered" :> (over[[First@#2]] = True),
"MouseExited" :> (over[[First@#2]] = False)}] &, data, {2}]
2. Multiple one-row Grid
-s
Ok, so if Item
cannot be used, one has to treat rows as individual objects, for example by assigning same column-sizes for all the rows as individual Grid
-s. But specifying fixed column sizes is both tedious and prone to break when resized/pagebreaked/etc.
Dynamic@over
Column[MapIndexed[EventHandler[
Grid[{#1},
Background -> Dynamic@If[over[[First@#2]], LightBlue, White],
Dividers -> All, ItemSize -> 7],
{"MouseEntered" :> (over[[First@#2]] = True),
"MouseExited" :> (over[[First@#2]] = False)}
] &, data],
Spacings -> {0, -.2}
]
Note that I had to use a fixed ItemSize
value (which cannot be calculated programmatically prior to displaying the Grid
), and also negative spacing, as the individual row-Grid
-s are layered on top of each other and alignment is not perfect. This shows when the window is resized or in any way the output is forced to resize in the page. At least now the mouse is captured in the whole row-space.
Questions
- How to create a row/column selector effectively, that retains the economic layout of
Grid
? - If this is not possible, can we programmatically get the optimal cell-sizes of a
Grid
to force on multiple one-rowGrid
-s? - Is it a bug that
EventHandler[Item[..., opts]]
does not applyopts
when insideGrid
? Any workaround?
Item
if you intend to use this on largeGrids
-- too much manual messing around -- control things via theGrid
options; select columns withEventHandler
in the headers. Looks like @kguler has already dealt with rows. $\endgroup$MapIndexed
does it for me. Could you please elaborate on your idea about handling headers (especially, when I only want to mouseover rows)? Perhaps even turning it to an answer? For your last sentence: no, I wouldn't say that kguler's answer is a solution.RowSelector
has problems as well, it wraps items inPane
(instead ofItem
) tha causes fixed column sizes and also fails to capture mouse movements when not over the number (as in my first example). See my comment under kguler's answer. $\endgroup$