8
$\begingroup$

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], {}]}}]}

Mathematica graphics

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}]

enter image description here

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}
 ]

enter image description here

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

  1. How to create a row/column selector effectively, that retains the economic layout of Grid?
  2. If this is not possible, can we programmatically get the optimal cell-sizes of a Grid to force on multiple one-row Grid-s?
  3. Is it a bug that EventHandler[Item[..., opts]] does not apply opts when inside Grid? Any workaround?
$\endgroup$
4
  • $\begingroup$ IMO avoid Item if you intend to use this on large Grids -- too much manual messing around -- control things via the Grid options; select columns with EventHandler in the headers. Looks like @kguler has already dealt with rows. $\endgroup$ Dec 27, 2012 at 22:03
  • $\begingroup$ @2013 I never intended to use Item manually on large grids, as you can see that 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 in Pane (instead of Item) 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$ Dec 28, 2012 at 15:04
  • $\begingroup$ My comment about using headers was specific for selecting a column -- which is still mentioned in your question title. The comment about item was more of a general comment for anyone actually. I don't know of an easy fool proof way of selecting an entire cell. I posted on this a few months ago about my interactions with tech support on this and the upshot was that all methods seem to contain some bug/flaw. this was the tech support interaction: mathematica.stackexchange.com/questions/13713/… Mike (2013 for the new year) $\endgroup$ Dec 28, 2012 at 21:44
  • $\begingroup$ @2013 Holy god, and I have even commented on that! Obviously I have a very forgetful mind. Thanks for the reminder, Mike! $\endgroup$ Dec 29, 2012 at 0:44

2 Answers 2

5
$\begingroup$

In version 9, there is the new built-in function ListPicker. It can be exploited for tabular data easily:

ListPicker[{}, Riffle[
    Thread[Range@Length@data ->
    (Grid[{#}, Dividers -> Center, ItemSize -> {{5, 8, 12}, 2}] & /@ data)],
  Delimiter], 
Spacings -> 0]

Mathematica graphics

$\endgroup$
3
$\begingroup$

I think the first issue is related to the fact that (docs >> Item >> Possible Issues)

"If Item is not the top-most item in the child of a function that supports Item, it will not work.

So a partial answer for the row selection problem can be obtained by moving Item outside EventHandler and using Framed with FrameStyle->None inside the EventHandler as follows:

 Grid@MapIndexed[
 Item[EventHandler[
   Framed[#1, FrameStyle -> None, 
    Background ->  Dynamic@If[over[[First@#2]], LightBlue,  White]], 
   {"MouseEntered" :> (over[[First@#2]] = True), 
   "MouseExited" :> (over[[First@#2]] = False)}]] &, data, {2}]

Update: Another partial step is perhaps modifying the undocumented Experimental RowSelector to accept grid options. As is, it takes only a List and Dynamic[var] as inputs and returns the selected row:

  Experimental`RowSelector[data, Dynamic[a]]

enter image description here

It may be worthwhile to do some spelunking to study the code -- my first impression is that it should not be too difficult to make the necessary modifications to make a version that accepts all Grid options.

$\endgroup$
2
  • $\begingroup$ Thanks kguler, +1 for RowSelector I forgot about! $\endgroup$ Dec 27, 2012 at 12:02
  • $\begingroup$ Unfortunately, RowSelector has some problems as well. By spelunking, I've reconstructed the function, and I can tell you that it works by wrapping entries in Pane, so that's why it has fixed column widths (something I don't want to see). Also, if one wraps a Framed around this internal Pane it becomes obvious, that not each Pane fills up the cell completely which causes that sometimes mouse movements are not captured even if inside row but above the actual Pane entry. $\endgroup$ Dec 28, 2012 at 15:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.