Search code examples
matlabuser-interfaceuser-controlscallbackmatlab-uitable

Is it possible to prevent an uitable popup menu from popping up? Or: How to get a callback by clicking a cell, returning the row & column index?


For an user interface I'm programming an uitable. The user chooses an option A,B or C in the first column and the suboption in the second column depends on what was chosen in the first, either A.1,A.2 or A.3 or B.1,B.2 or B.3 or the same for C

enter image description here

The code for the table can be found in Appendix A.

When the user first defines the main option, then automatically the suboptions are reduced accordingly to only valid choices. This is realized by evalulating the CellEditCallback for column 1 and resetting the ColumnFormat for column 2. (function modifySelection in Appendix B) If the user now realizes he made a mistake and needs to edit a suboption another time, then the ColumnFormat is still set according to the previous edited main option and the valid choices are not available unless he re-chooes the main option another time. (see the blue highlighting in picture).

To resolve this, I also implemented the CellSelectionCallback calling the function justifySelection (in Appendix B), which is checking by selection, which option was chosen in column 1 to offer again the right suboptions for column 2. But as this callback reacts on selection, I need to select twice, one time to trigger the CellSelectionCallback and another to actually get my choices. For large tables, this can be very annoying!

So my question is:

Is there a way to prevent the popup menu in column 2 from popping up, until it found out what's the content of the according column 1, so it immediately offers the valid choices?

Or:

How could I detect a mouse click on a cell and get the row and column-index? But without invoking the following selection and popping up action?

I was already raking all available properties but didn't found anything which could be useful. Maybe one could do something using the ButtonDownFcn, but how to get the cell indices? What about the BusyAction property, how can that be used for my purpose?

Any ideas?

I'm sorry in advance to bomb you with so much code, it's already the most minimal example, but fully executable, so you can try it out.


Appendix A/B

function fancyUitable 

selector_1 = { 'A'; 'B' ; 'C' };
selector_2 = { 'first select first row!' };

h = figure('Position',[200 100 268 120],'numbertitle','off','MenuBar','none');

defaultData =  repmat( {'select main option...', 'select suboption...'} ,5,1);
columnname =   {'Option                             ',...
                'Suboption                          '};
columnformat = { {selector_1{:}}, selector_2 };
columneditable =  [true true]; 
t = uitable(h,'Units','normalized','Position',[0 0 1 1],...
              'Data', defaultData,... 
              'ColumnName', columnname,...
              'ColumnEditable', columneditable,...
              'ColumnFormat', columnformat,...  
              'RowName',[],...
              'CellEditCallback',@modifySelection,...
              'CellSelectionCallback',@justifySelection);

set(h,'Tag','Config_figure')
set(t,'Tag','Config_table')
end

%   **Appendix B**
%   (part of the same function file)


function modifySelection(~,evt_edit)
if evt_edit.Indices(2) == 1
    modifyPopup( evt_edit.Indices(1) );
end
end

function justifySelection(~,evt_select)
try  %to surpress an unimportant error
    if evt_select.Indices(2) == 2
        modifyPopup( evt_select.Indices(1) );
    end
end
end

and finally the single function modifyPopup which rewrites the Columnformat:

function  modifyPopup( row )
    id_group_1 = {'A.1';'A.2';'A.3'};
    id_group_2 = {'B.1';'B.2';'B.3'};
    id_group_3 = {'C.1';'C.2';'C.3'};
    id_default = {'select main option first'};

    myfigure = findobj('Tag','Config_figure');
    config_data = get(findobj(myfigure,'Tag','Config_table'),'Data');
    selector = config_data(row,1);
    selector = selector{1};

    config_format = get(findobj(myfigure,'Tag','Config_table'),'ColumnFormat');
    switch selector
        case 'A'
            config_format{2} = id_group_1';
        case 'B'
            config_format{2} = id_group_2';
        case 'C'
            config_format{2} = id_group_3';
        otherwise
            config_format{2} = id_default;
    end
    set(findobj(myfigure,'Tag','Config_table'),'ColumnFormat',config_format)
end

Bounty: Why just +50? - I guess it's either not possible or the answer is easy, once one had the right initial idea. I'm not looking a for a complex workaround using java object properties etc. Thank you in advance!


I include the discussion from the comments here to keep the overview:

If you want to try it out, you can copy the code and follow these steps to reproduce the undesired behaviour:

  1. select main option A in the first row.
  2. the suboption in the first row then contains the choices A.1, A.2 and A.3.
  3. select main option B in the second row, therefore the choices for the suboption in the second row are B.1, B.2 and B.3
  4. BUT NOW you want to change the suboption in the first row (directly); you would expect to get the choices A.1, A.2 and A.3; but you don't. You get offered B.1, B.2 & B.3; - Because the last main option you selected was B (though in a diffrent row).

It seems that instead of looking for the last option, you should look at the relevant option. So either make sure that clicking on a suboption does a 'lookup' to see which main option there is,

Thats exactly what I'm looking for! But how could I do that? How to detect the click, get the column&row indices, set the right ColumnFormat and then finally let the cell pop up. The only possibility I see until now is the CellSelectionCallback, but it is executed after the cell already popped up with the invalid choices. I'd need a kind of ClickedCallback, like there is for pushbuttons

or make sure that selecting a main option only sets the suboptions for that row.

That's not possible, you can't set a suboption for a certain row as you need to modify ColumnFormat, which affects the whole table and not just one row.


Solution

  • Though I highly appreciate the effort and the solution of Rody Oldenhuis and he definetely deserved the award, his solution would have required a lot of changes in my code, so I kept trying to find a simpler solution. Here it is, finally 99% bug-free.

    (all code parts within on function script)

    function fancyUitable 
    
    close all
    
    %basic properties
    line_height = 21.32;
    table_height = 6*line_height;
    lh = line_height/table_height;
    cw = 200; %columnwidth
    
    h = figure('Position',[200 100 2*cw+2 table_height],...
               'numbertitle','off','MenuBar','none');
    
    %header
    uitable(h,'Units','normalized','Position',[0 1-lh 1 lh],...
                  'ColumnWidth', {cw cw},...              
                  'ColumnName', {'Option','Suboption'},...
                  'RowName',[]);
    
    %button (currently no icon) to store table
    tbar = uitoolbar(h);
    uipushtool(tbar,'ClickedCallback',@store);
    
    % addrow(figurehandle,number of row, percentage lineheight)
    % every function call creates a new row, later dynamically
    addRow(h,1,lh);
    addRow(h,2,lh);
    addRow(h,3,lh);
    addRow(h,4,lh);
    addRow(h,5,lh);
    end
    

    function edit(src,evt)
    
    if evt.Indices(2) == 1
        modifyPopup( src,evt.Indices(1) );
    end
    
    % disables cell selection highlighting, when one jumps to next table,
    % a bit laggy though
    fh = get(src,'parent');
    copyobj(src,fh);
    delete(src);
    
    end
    
    function  modifyPopup( src,row )
        id_group_1 = {'A.1';'A.2';'A.3'};
        id_group_2 = {'B.1';'B.2';'B.3'};
        id_group_3 = {'C.1';'C.2';'C.3'};
        id_default = {'select output file first'};
    
        config_data = get(src,'Data');
        selector = config_data(row,1);
        selector = selector{1};
    
        config_format = get(src,'ColumnFormat');
        switch selector
            case 'A'
                config_format{2} = id_group_1';
            case 'B'
                config_format{2} = id_group_2';
            case 'C'
                config_format{2} = id_group_3';
            otherwise
                config_format{2} = id_default;
        end
        config_data = { selector , 'select suboption...' };  %reset column 2
        set(src,'Data',config_data);
        set(src,'ColumnFormat',config_format);
    end
    

    function addRow(fh,k,lhp)
    selector_1 = { 'A'; 'B' ; 'C' };
    selector_2 = { 'first select first row!' };
    
    defaultData =  {'select main option...', 'select suboption...'};
    columnformat = { {selector_1{:}}, selector_2};
    columneditable =  [true true];
    
    th = uitable(fh,'Units','normalized','Position',[0 1-(k+1)*lhp 1 lhp],...
                  'Data', defaultData,... 
                  'ColumnName', [],...
                  'ColumnWidth', {200 200},...
                  'ColumnEditable', columneditable,...
                  'ColumnFormat', columnformat,...  
                  'RowName',[],...
                  'Tag','value',...
                  'UserData',k,...
                  'SelectionHighlight','off',...
                  'CellEditCallback',@edit);
    end
    

    function store(~,~)
    ui = findobj(0,'Type','uitable','Tag','value');
    L = numel(ui);
    output = cell(L,2);
    order = zeros(L,1);
    for ii=1:L;
        output(ii,:) = get(ui(ii),'Data');
        order(ii)    = get(ui(ii),'UserData');
    end
    [~,idx] = sort(order);    %as order of handles unequals displayed order
    assignin('base','output',output(idx,:));
    end
    

    brings up:

    finaltable