Extracting contents of table's target cell?

As part of a programming project using Igor Pro v.8, I want to make sure that numeric data my user enters into a table (itself a subwindow within a panel), is properly bounded.  I've created hook functions for the parent window and for the table subwindow and plan to detect when the user moves away from the target cell (ex. Enter or Return, Arrow Keys, etc).  At that time, I want to read the content of the target cell and limit the entered value to within my [min, max] range.

In this circumstance, how can my code determine the contents of the target cell?  In some circumstances it seems sufficient to use the value of the corresponding point in the wave represented by the column in the table, but in other circumstances, that scheme doesn't seem to work.  For example: move to a cell already containing a value; enter a new numeric value; press Enter.  In this case, the point in the wave does not yet (in the hook function), contain the new numeric value -- instead it contains the previous point.

I would've thought that GetSelection table, ... would be approp. function to read the data directly from the table itself, rather than the 'backing wave', but I haven't yet found a way to get the value of that target cell.

Any suggestions?

Thanks,
Bruce

I recommend that you take a different approach. Check all of the entered data regardless of what the target cell is.

That said, here is a first-crack attempt at doing what you asked for. Note the caveats explained in the comments and that I have tested this very briefly.

(However, by the time your hook is called, that target cell may have already been changed.)

Menu "Macros"
    // For convenience in testing GetSelectedTableNumericValue
    "Print Selected Table Numeric Value/1", /Q, Print GetSelectedTableNumericValue("")
End

// This works only if the table contains only 1D waves and only if
// the data column from each wave is selected. If the index column for
// a wave is selected, it returns the corresponding data value.
Function GetSelectedTableNumericValue(tableName)
    String tableName    // "" for top table
    if (strlen(tableName) == 0)
        tableName = WinName(0, 2)
    endif
    if (WinType(tableName) != 2)
        return NaN      // Not a table
    endif
   
    GetSelection table, $tableName, 7
    if (V_Flag == 0)    // No selection?
        return NaN      // Should not happen
    endif
   
    // First selected table row, zero-based
    Variable firstSelectedRow = V_startRow
   
    // First selected table column, zero-based
    Variable firstSelectedColumn = V_startCol
   
    // Name of first selected column (e.g. wave0.d)
    String columnName = StringFromList(0, S_Selection)
   
    String wName        // Strip, e.g., ".d" from column name to get wave name
    SplitString/E="(.*)\..*$" columnName, wName
   
    // Path to first selected wave's data folder
    String dfPath = StringFromList(0, S_dataFolder)
   
    String fullDFPath = dfPath + wName 
    Wave/Z w = $fullDFPath
    if (!WaveExists(w))
        return NaN      // An empty cell is selected
    endif
    if (WaveType(w,1) != 1)
        return NaN      // Not a numeric wave
    endif
   
    Variable value = w[firstSelectedRow]
    return value
End

 

Hi, and thanks for your feedback w/ sample code.

If I understand correctly, you're recommending that the code perform a bounds-check on each element in the wave, each time the return key is pressed.  While that would be too slow for long waves, it could work for my application where the waves are likely to be short. 

However, that approach wouldn't solve the problem that seems to arise when the user presses 'Enter' immediately after editing the value of a cell.  In that case, my (quick) experiments suggest that the wave still contains the old (pre-editing) value, while the table's cell contains the new (post-editing) value.  Thus checking the data in the wave appears unreliable as a scheme to immediately catch users' errors.  

Please let me know if I should hook a different event to catch such changes, or if I've misunderstood something.

In reply to by BRUCE

Quote:
my (quick) experiments suggest that the wave still contains the old (pre-editing) value, while the table's cell contains the new (post-editing) value.

I would need to know exactly what you are doing in order to comment. If you will post a minimal, self-contained example that I can run and debug, I will look into this.

 

Please let me know if I should hook a different event to catch such changes

That suggests that you are using a window hook function, and perhaps you are using the mouse-down event to catch the edited value. Try the mouse-up event.

I don't see how a table hook helps with this. Below is a simple self-contained example. I get no event when I edit the value of a table cell.

Function TableWindowHook(s)
    STRUCT WMWinHookStruct &s
   
    Variable hookResult = 0 // 0 if we do not handle event, 1 if we handle it.
   
    String message = ""
   
    Wave tableHookWave
    Variable value = tableHookWave[0]

    switch(s.eventCode)
        case 3: // Mouse down event
            Printf "Mouse down event, value=%g\r", value
            break
        case 5: // Mouse up event
            Printf "Mouse up event, value=%g\r", value
            break
        case 8: // Modified event - sent to graph and notebook windows only
            Printf "Modified event, value=%g\r", value
            break
    endswitch
   
    return hookResult       // If non-zero, we handled event and Igor will ignore it.
End

Function DemoTableWindowHook()
    DoWindow/F TableEventsTable                     // Does table exist?
    if (V_flag == 0)
        // Create table
        Make/O/N=3 tableHookWave = p
        Edit /N=TableEventsTable tableHookWave as "Table Events"

        // Install hook
        SetWindow TableEventsTable, hook(MyHook)=TableWindowHook
    endif
End

 

Here is an example using a dependency. It works but I don't know how to get the dependency to run just once when the wave is modified instead of once for every point.

Function DemoTableDependency()
    DoWindow/F TableDependencyTable                 // Does table exist?
    if (V_flag == 0)
        // Create table
        Make/O/N=3 tableDependencyWave = p
        Edit /N=TableDependencyTable tableDependencyWave as "Table Dependency"
       
        // Set dependency
        SetFormula tableDependencyWave, "root:tableDependencyWave + CheckTableDependencyWave()"
    endif
End

Function CheckTableDependencyWave()
    // Print "In CheckTableDependencyWave"
   
    Wave w = root:tableDependencyWave
    // Print w
   
    Variable numPoints = numpnts(w)
    Variable i
    for(i=0; i<numPoints; i+=1)
        Variable val = w[i]
        if (val<0 || val>9)
            Printf "Point %d of tableDependencyWave is out of range - must be 0 to 9\r", i
        endif
    endfor
   
    return 0
End

 

Right. My comment wasn't well thought out; I was thinking of something else.

@BRUCE Are you using a window hook function? If so, better post some code because we can't figure out what you're doing :)

Hi All,

Thanks for the feedback, and sorry for my slow return to this thread.

I've attached a .pxp file that I think will demonstrate the puzzle.  Please see the comments at the top of the procedure window for more details.

Looking forward to your feedback,

bp

 

Ah, I see the problem. You're right- when the Enter key event comes in, the wave hasn't actually been updated. Which is what you told us before :). But it took some digging to uncover the fact that you are using a window hook. Window hooks can be very tricky; in this case I think it simply doesn't give you what you need. Perhaps in a future version?

I think you can, with effort, achieve what you need using a Listbox control. It is difficult because a Listbox deals only in text, not numbers, so you have to prepare a text wave with the contents of the wave you want to edit translated into text.

On the other hand, Listbox controls are made for editing and it's not hard to find out the selection and the new text. Then you can vet the new text before finalizing the value to put into the real numeric wave.

You might want to look at ModifyTable entryMode. There are commands to check whether values are being edited and not (yet) accepted.

There isn't a way to get the text of the current entry line in Igor until Igor 8.02: ModifyTable entryMode sets S_Value beginning with Igor 8.02.

In reply to by JimProuty

You could do it with a dependency if you create a one point dummy dependency wave:

function doCheck(w)
    wave w
   
    // keep wave points within bounds
    w=max(10*q,min(w, 10*(q+1)),0)
    return 1
end
make /n=(10,4) tablewave=p+10*q
edit tablewave
make /n=1 dependencywave
•dependencywave:=doCheck(tablewave)

 

In Igor 8.02, I added two things to help you: Event codes sent when the table entry area is completed or rejected, and a ModifyTable entryMode=num that always sets S_Value to the text in the table entry area.

You can use the event like this:

 

#pragma IgorVersion=8.02    // for ModifyTable entryMode and TableHook events 24 ("tableEntryAccepted") and 25 ("tableEntryCancelled").

function Func_TableHook(STRUCT WMWinHookStruct &s, string &parentWindowName)
    Variable hookResult = 0                         // 0 if we do not handle event, 1 if we handle it.

    switch(s.eventCode)
        case 24: // tableEntryAccepted in Igor 8.02+
            Print s.eventName   // "tableEntryAccepted"
            Func_WorkPosTableBoundsCheck()
            break
        case 25: // tableEntryCancelled in Igor 8.02+
            Print s.eventName   // "tableEntryCancelled"
            break
        default:
            break
    endswitch
    return hookResult
end

 And you can use the ModifyTable entry mode like this:

 

ModifyTable/W=demoPanel#tSeqPos entryMode=0 // query
Print "table entry text = \""+S_Value+"\""

 

TableHooks.pxp

Thanks for the suggestions, and the refinements in Igor v.8.02.

Hope to get back to this question later this week, after which I can give more specific feedback.

bp

The hints and revisions in v.8.02 seem to solve the puzzle.  The attached .ipf file shows the current versions of the table's hook function  & the bounds checker (filter).

The new 'tableEntryAccepted' event lets me easily pick up when the user clicks away from the cell they were editing.  By detecting that event, the mouse-down event, and a few keydown events, I can now call the filter no matter how the user terminates their edit.  The new 'tableEntryCancelled' event doesn't seem necessary (at least for now).

The new assignment of the cell's contents to S_Value lets my filter constrain the user's inputs to permitted values.  I'm not sure if there's a  way to write the filtered value directly back into the table, but writing the filtered value directly into the wave has seemed adequate so far.

Thanks again for the help,

bp

 

 

Writing a value into a wave displayed in a table is the same thing as writing "directly into the table". If your code does other things after changing the wave, and it's important for the table to immediately show the change in value, you may need to call DoUpdate.

Here's a very compact example of getting information about an edited table cell using a table hook and the new ModifyTable query:

#pragma IgorVersion=8.02

Macro MakeTableHookExample()
    make/O/N=(20,20) twod=p*q
    make/O/N=20 short=gnoise(1)
    Edit/N=Table0 short, twod
    SetWindow kwTopWin,hook(myhook)=Func_TableHook
    DoAlert 0, "Try editing any value, look at history output"
End

function Func_TableHook(STRUCT WMWinHookStruct &s)
    Variable hookResult = 0 // 0 if we do not handle event, 1 if we handle it.

    strswitch(s.eventName)
        case "tableEntryAccepted":
            ModifyTable/W=$s.winName entryMode=0 // query, result in S_Value
            Print "accepted: "+S_Value
            GetSelection table, $s.winName, 1+2+4
            // 1    Sets V_startRow, V_startCol, V_endRow, V_endCol based on the selected cells in the table.
            //      The top/left cell, not including the Point column, is (0, 0).
            // 2    Sets S_selection to a semicolon-separated list of column names.
            // 4    Sets S_dataFolder to a semicolon-separated list of data folders, one for each column.
            Print V_StartRow, V_StartCol, S_Selection, S_DataFolder
            break
        default:
            break
    endswitch
    return hookResult
End

 

Thanks Jim -- that's some customer support!

Hoping to get back to this someday,

Bruce