Related
I'm creating a graphic interface (manually) and I would like to have a reset button which reset the default values.
I already code this
H.but(3) = uicontrol('Units','normalized', ...
'BackgroundColor',[1 0.7 1], ...
'Callback','MyFunction(''Reset'');', ...
'FontSize',12, ...
'Position',[0.04 0.54 0.1 0.05 ], ...
'String','Reset');
case 'Reset'
clear all % Is not working and I think that isn't that I expect
set(findobj(H.fig,'style','edit', '-or','style','text'),'string','') % H is a global variable. This trial don't give the default value, it just clear certain boxes
I usually prefer to create a specific reset_gui function for my GUIs that resets all the relevant control properties (like checkbox states, strings in editable text boxes, etc.) to appropriate default values, as well as setting all relevant variable values to their defaults, clearing plots, etc..
If you'd prefer a generic option for resetting all UI control properties to their initial state, here's an example of one possible solution:
function example_reset_gui
% Initialize GUI:
hFigure = figure();
uicontrol('Style', 'edit', 'Position', [20 100 100 25]);
uicontrol('Style', 'edit', 'Position', [20 65 100 25]);
uicontrol('Style', 'push', 'Position', [20 20 60 30], ...
'String', 'Reset', 'Callback', #reset_fcn);
drawnow
% Collect default states:
[defaultState{1:3}] = get_default_state(hFigure);
% Nested reset function:
function reset_fcn(~, ~)
set(defaultState{:});
end
end
% Local function:
function [hArray, propArray, valueArray] = get_default_state(hFigure)
hArray = findall(hFigure, 'Type', 'uicontrol');
propArray = fieldnames(set(hArray(1)));
valueArray = get(hArray, propArray);
end
This creates a figure with 2 editable text boxes and a reset button. You can type whatever you want into the text boxes, and when you push the reset button it will clear them (i.e. set them to the default empty string they first contained).
The local function get_default_state will find all the uicontrol objects in the figure, then get all of their set-able properties (i.e. all properties that are not read-only), then get all the initial values for those properties. The three outputs are stored in the 1-by-3 cell array defaultState, which is accessible by the nested function reset_fcn. When the reset button is pressed, all of the set-able UI control properties are set to the values they had when first created.
It should be noted that any changes made to the Position property (such as due to resizing of the figure) could be undone by this approach. Using 'normalized' units would avoid this.
If you truly want to start your GUI over from scratch the easiest way would be to just close it open it again. You can do that from your button's callback. Which I pointed at a new function restartGUI. That could be a subfunction of your main gui or its own m-file, your choice.
Your question is pretty light on details so I can't help with some of the specifics but this should give you the general idea.
IF closing and opening isn't what your want then in the restartGUI function you would have to just manually reset the state of each of your uicontrols, etc. (whatever else is in your GUI that we can't see).
H.but(3) = uicontrol('Units','normalized', ...
'BackgroundColor',[1 0.7 1], ...
'Callback',#restartGUI, ...
'FontSize',12, ...
'Position',[0.04 0.54 0.1 0.05 ], ...
'String','Reset');
% <<<< THE rest of your code >>>
function restartGUI(hObject,varargin)
global H
close(H.fig) %Assuming H.fig the main GUI window.
%Call the GUI again which will restart it.
yourGUIFunction
Edit: added the use of the global H to close.
This post is a continuation of the thread: "Matlab imline snapping" which was resolved. The below is the working code that snaps the imline object to a curve.
function calc_slope(handle,event)
axis_h = findobj(gcf,'Type','axes');
obj_h = get(axis_h,'Children');
obj_type = get(obj_h,'Type');
if ~iscell(obj_type),obj_type = cellstr(obj_type);end
for i=1:length(obj_type)
if strcmpi(obj_type{i},'line'),data = obj_h(i);end
end
xdata = get(data,'XData');
ydata = get(data,'YData');
on = get(handle,'State');
if strcmpi(on,'on') || strcmpi(on,'off'),
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)]);
xy = imline(axis_h, 'PositionConstraintFcn', fcn_constr);
addNewPositionCallback(xy,#(pos) disp_slope(pos));
end
function constr_pos = imline_snap(new_pos, positions)
[~, ind1] = min(sum(bsxfun(#minus, new_pos(1,:), positions).^2, 2));
[~, ind2] = min(sum(bsxfun(#minus, new_pos(2,:), positions).^2, 2));
constr_pos = [positions(ind1,:); positions(ind2,:)];
function disp_slope(pos)
delete(findobj(gca,'Type','text'));
text((pos(1)+pos(2))/2,(pos(3)+pos(4))/2,['\DeltaY/\DeltaX = ',num2str((pos(4)-pos(3))/(pos(2)-pos(1))),...
' [\DeltaX = ',num2str(pos(2)-pos(1)),', \DeltaY = ',num2str((pos(4)-pos(3))),']']);
Every time the toggle button on the toolbar of a figure is toggled (on and off), a new imline object is thrown in. There are many figures with different parameters so that the data has to be extracted from the figure. In a given figure, there can be multiple objects: imline objects, text, and/or line; thus, the first seven lines in the calc_slope function.
The imline objects snaps to the nearest data point of a curve and it's beautifully done by imline_snap function which is an answer by "Luis Mendo". Thank you so much. This has been a biggest headache.
The final problem is now to show the slope of an imline object in a text box (instead of the title or the floating box). It's attempted in the disp_slope function (and it's miserable).
I'm doing "delete(findobj(gca,'Type','text'));" only because without something like that, as the imline object is moved around, it will leave millions of text boxes. I only want to show one most current slope calculation.
There are multiple problems with "delete(findobj(gca,'Type','text'));". If I stop moving the line around, it will nicely show the last slope calculation. However, as soon as I throw in another imline object and move the new one around, the text box in the first imline object will get deleted and of course.
Another problem is that even if I delete the imline object, the associated text box will remain.
In summary,
I want to show the current calculated slope in a text box
I want the text box for each imline object to remain even if there are multiple imline objects.
Finally, I want the corresponding text box to disappear as well when a particular imline object is deleted.
Can this be done? Help please.
Thanks,
Eric
Don't create a new text object every time. Create one initially
ht = text(.45, .85, ''); %// modify coordinates to place it where you want
and then update its content ('String' property) when the imline changes. To do the updating, modify the imline_snap function to accept ht as a third input and add the following line at the end:
set(ht, 'String', ...
num2str((constr_pos(2,2)-constr_pos(1,2))/(constr_pos(2,1)-constr_pos(1,1))));
So the function becomes
function constr_pos = imline_snap(new_pos, positions, ht)
[~, ind1] = min(sum(bsxfun(#minus, new_pos(1,:), positions).^2, 2));
[~, ind2] = min(sum(bsxfun(#minus, new_pos(2,:), positions).^2, 2));
constr_pos = [positions(ind1,:); positions(ind2,:)];
set(ht, 'String', ...
num2str((constr_pos(2,2)-constr_pos(1,2))/(constr_pos(2,1)-constr_pos(1,1))));
Then, when defining fcn_contr, pass the reference ht to the text object:
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)], ht);
Here's an example, borrowing the curve from my previous answer:
h = plot(0:.01:1, (0:.01:1).^2); %// example curve. Get a handle to it
a = gca; %// handle to current axes
ht = text(.45, .85, ''); %// create text
xdata = get(h,'XData'); %// x values of points from the curve
ydata = get(h,'YData'); %// y values of points from the curve
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)], ht); %// particularize function
imline(a, 'PositionConstraintFcn', fcn_constr); %// create imline
You can also update the text position ('Position' property). Just change the last statement of imline_snap to include that. For example:
set(ht, 'String', ...
num2str((constr_pos(2,2)-constr_pos(1,2))/(constr_pos(2,1)-constr_pos(1,1))), ...
'Position', ...
mean(constr_pos) + [.03 -.03]); %// manually adjust offset if needed
The offset [.03 -.03] is intended to avoid the text overlapping with the line. You may need to change it. Also, it may help to create the text object with boldface. The line becomes
ht = text(.45, .85, '', 'Fontweight', 'bold'); %// create text, in boldface
Here's an example with text position updating:
To delete the associated text object when you delete the imline object you need and event listener. This is an object with three main properties: a cell array of source objects, an event, and a callback function. When the indicated event happens to one of the source objects the callback function of the event listener is executed.
To create an event listener for the imline object's deletion, use that object's addEventListener method and specify the event name and callback function. The callback function is specified by means of a function handle, and it should expect two inputs, which correspond to the source object and the event (this is how the callback function will know "why" it's being called). Even if those inputs won't actually be used, the function needs to be defined that way.
In this case, the event we want to listen to is ObjectBeingDestroyed, the source object is the imline object, and the callback function is delete(ht) (to delete the text object). So, the code in the above example becomes
h = plot(0:.01:1, (0:.01:1).^2); %// example curve. Get a handle to it
a = gca; %// handle to current axes
ht = text(.45, .85, '', 'Fontweight', 'bold'); %// create text, in boldface
xdata = get(h,'XData'); %// x values of points from the curve
ydata = get(h,'YData'); %// y values of points from the curve
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)], ht); %// particularize function
hi = imline(a, 'PositionConstraintFcn', fcn_constr); %// create imline and get a handle
addlistener(hi, 'ObjectBeingDestroyed', #(obj,event) delete(ht))
where only the last two lines are new.
Now whenever the imline object is deleted the action delete(ht) will be performed, thus deleting the text object.
In some GUI of my own I have created many controls (using uicontrol) to allow users configuring a filter used during later processing stage.
The filter edition consist in a combobox to select the filter type, plus many editboxes that update upon selected filter type and many callbacks to react upon user inputs.
I now need to add this filter selection in another GUI, and, of course, I don't want to copy-paste all the logic I have already done and would prefer to create some custom control that I can reuse as easily as:
filterEditor = uifilter('Parent', gcf);
set(filterEditor, 'FilterDescription', 'Cylinder (r = 45 cm, h = 1 m)');
set(filterEditor, 'Callback', #onFilterEditModified);
Is there a standard procedure to create custom "uicontrol" objects ? I searched the internet and matlab's documentation but did not find any good pointer yet ...
Currently I'm thinking to create custom class deriving from hgsetget:
classdef uifilter < hgsetget
properties
% Local properties
FilterDescription;
Callback;
end
properties(SetAccess=private, GetAccess=private)
% Internal controls
globalContainer;
comboFilterType;
edit1;
end
methods
function [this] = uifilter(varargin)
% Create a global `uicontainer` to hold my controls
[localPVpairs, genericPVpairs] = separatePVpairs(varargin{:});
this.container = uicontainer(genericPVpairs{:});
% Create my own controls and logic
this.comboFilterType = uicontrol('Parent', this.container, ...);
this.edit1 = ...
end
end
end
in order to mimic uicontrol behavior (set, get, findobj, etc...) but maybe there's more standard approach or some base class other than hgsetget to start from (i.e. some base class with Visible, Enable, HitTest etc... already defined with default implementation)?
I think this would be the right approach.
To do it properly, you'll probably need to implement your own set and get methods for each uicontrol property. These set and get methods will mostly just pass through values to and from the underlying uicontrol. You can probably get away without implementing some of the less-used properties in your first draft (e.g. FontAngle), adding them in as necessary and just living with the uicontrol defaults until then.
In some cases though, they'll need to do more, and you'll need to exercise a bit of care when you implement things such as set for the Parent property (it may need to destroy the original uicontrol and create a new one for the new parent). You'll also need to exercise care when implementing set for the Position and Units properties - for normal uicontrols they interact in quite a complicated way, and I think the outcome can sometimes depend on which is set first.
I'd also suggest that for the internal properties, as well as setting them to private, you might also set them to Hidden, to prevent users from trying to meddle with them.
One last point - I think, from some of your other questions, that you're making use of GUI Layout Toolbox. I haven't thought it through much, but you might need to think ahead about whether you need to do anything special to enable that.
Coming back on the issue, a very simple approach (equivalent to define a custom class inheriting from hgsetget or possibly some uicontrolbase class to have default behavior for Enable, Position, etc...) is to create a class inheriting from uiextras.Container in the GUI Layout toolbox.
Indeed this class is fully equivalent to the idea of having a uicontrolbase class. It exposes a protected UIContainer property which is the panel in which to put all child elements, so it is very easy to build reusable compound component from it:
classdef uimyfilter < uiextras.Container
%% --- Exposed properties
% NB: Can be accessed with set/get routines
properties(Dependent, Transient)
FilterDescription;
Callback;
end
methods
{% ... own custom set/get logic for exposed properties ... %}
end
%% --- Lifetime
methods
function [this] = uimyfilter(varargin)
% Consume or init local properties from varargin list
[c, otherPvPairs] = uimyfilter.extractOrInitPvPairs(varargin, { ...
'FilterDescription', #()'Cylinder (r = 10 cm, h = 42 cm)'; ...
'Callback', #()[]; ...
});
% Call superclass with other pv pairs
this#uiextras.Container(otherPvPairs{:});
% Build interface
grid = uiextras.Grid('Parent', this.UIContainer, 'Spacing', 5, 'Padding', 5);
c.handles.cbFilterType = uicontrol('Parent', grid, 'Style', 'Popup', 'String', { 'Cylinder', 'Sphere' }, 'Callback', #(s,e)onFilterTypeChanged(this,s,e));
uiextras.Empty('Parent', grid);
c.handles.cardFilterParams = uiextras.CardPanel('Parent', grid);
uiextras.Empty('Parent', grid);
set(grid, 'ColumnSizes', [90, -1]);
set(grid, 'RowSizes', [23, -1]);
uicontrol('Parent', c.handles.cardFilterParams, 'Style', 'Text', 'String', '... todo: params for cylinder ...', 'BackgroundColor', 'r');
uicontrol('Parent', c.handles.cardFilterParams, 'Style', 'Text', 'String', '... todo: params for sphere ...', 'BackgroundColor', 'r');
% Store local properties and handles for later calls
this.state = c;
% Init Gui
this.refresh();
end
end
%% --- Internal logic
methods(Access=private)
function [] = refresh(this)
set(this.state.handles.cardFilterParams, 'SelectedChild', get(this.state.handles.cbFilterType, 'Value'));
end
function [] = onFilterTypeChanged(this, s, e) %#ok
this.refresh();
if (~isempty(this.state.Callback)),
this.state.Callback(this);
end
end
end
methods(Access = protected)
function [] = redraw(this) %#ok
end
end
properties(GetAccess=private, SetAccess=private)
state;
end
%% --- Helpers
methods(Static, Access=protected)
function [c, otherPvPairs] = extractOrInitPvPairs(pvPairs, consumeDescriptor)
% Check arguments
if (nargin < 2),
error('Not enough input arguments.');
end
if (~isempty(consumeDescriptor) && ...
(~iscell(consumeDescriptor) || ~ismatrix(consumeDescriptor) || ...
~iscellstr(consumeDescriptor(:, 1)) || ~all(cell2mat(cellfun(#(x)isa(x, 'function_handle'), consumeDescriptor(:,2), 'UniformOutput', false)))))
error('Invalid descriptor for properties to consume.');
end
if (~iscell(pvPairs) || (~isvector(pvPairs) && ~isempty(pvPairs)) || (length(pvPairs(1:2:end)) ~= length(pvPairs(2:2:end))) || ~iscellstr(pvPairs(1:2:end)))
error('Invalid list or property names/values pairs.');
end
% Consume local properties
c = struct();
otherNames = pvPairs(1:2:end);
otherValues = pvPairs(2:2:end);
for ki = 1:size(consumeDescriptor, 1),
pname = consumeDescriptor{ki,1};
pinit = consumeDescriptor{ki,2};
idx = strcmpi(otherNames, pname);
if (isempty(idx)),
c.(pname) = pinit();
elseif (isscalar(idx)),
c.(pname) = otherValues{idx};
otherNames(idx) = []; otherValues(idx) = [];
else
error('Property `%s` appears more than once.', pname);
end
end
% Recompose other pv
otherPvPairs = cell(1, 2*length(otherNames));
otherPvPairs(1:2:end) = otherNames(:);
otherPvPairs(2:2:end) = otherValues(:);
end
end
end
Exposed properties and internal logic is of course fully tied to the compound component to have anyway, building interface is as simple as adding uicontrol or uiextras.(...) objects to this.UIContainer.
PS: For R2014b and later, you have to inherit from uix.Container in GUI Layout toolbox for HG2 anyway the idea is similar.
I have some sliders on figure 1, and I have some images on figure 2. I want to do the callbacks for the sliders in a way that, when I change the sliders in figure 1 , the threshold changes and images update automatically in figure 2.
I'm using addlistener to send values for callback function. The problem is when you move slider the active figure is figure 1, and you want to do changes on figure 2.
adding some code for clarification:
M.rgbImage = imread('euhedral-mag-on-po-edge-pseudo-sub-ophitic-rl-fov-4-8mm.jpg');
[rows, columns, numberOfColorBands] = size(M.rgbImage);
F.f = figure; % This is the figure which has the axes to be controlled.
% Now create the other GUI
S.fh = figure('units','pixels',...
'position',[400 400 500 100],...
'menubar','none',...
'name','Image control',...
'numbertitle','off',...
'resize','off');
S.sl = uicontrol('style','slide',...
'unit','pix',...
'position',[60 10 270 20],...
'min',0,'max',255,'val',100,...
'callback',{#sl_call2,S},'deletefcn',{#delete,F.f});
....
lis = addlistener(S.sl,'Value','PostSet',#(e,h) sl_call3(S,F,M));
function sl_call3(S,F,M)
v = get(S.sl,'value');
figure(F.f), subplot(4, 4, 13);
M.redMask = (M.redPlane > v);
imshow(M.redObjectsMask, []);
set(S.ed(2),'string',v);
Create reference to both your figures:
f1=figure(1);
f2=figure(2);
And then when doing the callback pass f2 as a parameter.
In the callback, you'll have get the handle to the second figure.
There's various ways to do that.
You can specify the handle to the second figure at the time of callback-definition:
figure2 = ...;
addlistener(hSlider, ..., #(a,b) changeStuffOn(figure2));
Or during the callback:
function callbackFunction(hObject, evt)
% get the handle to the second figure, e.g. by a tag, or its name
fig2 = findobj(0, 'type', 'figure', 'tag', 'figure2'); %
% do whatever you want with fig2
end
The latter might be somewhat worse in performance, but e.g. has the benefit of working reliably even if figure2 was deleted and recreated and some point.
To avoid the change of focus you'll have to get rid of this line your callback:
figure(F.f)
This explicitly moves the focus to the second figure.
You'll have to use e.g. the imshow(axes_handle, ...) syntax, in order to show the image not in the "current axes".
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
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:
select main option A in the first row.
the suboption in the first row then contains the choices A.1, A.2 and
A.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
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.
I would not use a uitable; it's just not suited for this sort of thing.
Here's how I would do it:
function GUIdemo
%%// Construct GUI
%// Main figure
mainFig = figure;
set(mainFig, 'Color', get(0, 'DefaultUicontrolBackgroundColor'));
%// Create as many blocks as needed. The only thing you have to do is
%// figure out the "right" positions for each block
popupHandles = create_ui_blocks([
0.00 0.50 1.00 0.35
0.00 0.15 1.00 0.35]);
%// This OK button gathers all selected options, and just prints them.
uicontrol(...
'style' , 'pushbutton',...
'units' , 'normalized',...
'parent' , mainFig,...
'position', [0.4 0.01 0.2 0.1],...
'callback', #(~,~)getData(popupHandles),...
'string' , 'OK'...
);
%%// Helper functions
%// Create control blocks. Each block is composed of:
%// - a uipanel as container
%// - three radio buttons for the main selection
%// - a corresponding popup or the secondary selection
function popupHandles = create_ui_blocks(positions)
%// initialize
numBlocks = size(positions,1);
panels = zeros(numBlocks,1);
groups = zeros(numBlocks,1);
radios = zeros(numBlocks,3);
popups = zeros(numBlocks,1);
%// Build each block
for ii = 1:numBlocks
%// The container
panels(ii) = uipanel(...
'parent' , mainFig,...
'position', positions(ii,:)...
);
%// The radio buttons
groups(ii) = uibuttongroup(...
'parent' , panels(ii),...
'position', [0.05 0.05 0.45 0.9]...
);
radios(ii,1) = uicontrol(...
'style' , 'radio',...
'units' , 'normalized',...
'string' , 'A',...
'parent' , groups(ii),...
'position', [0.05 0.66 0.9 0.25]...
);
radios(ii,2) = uicontrol(...
'style' , 'radio',...
'units' , 'normalized',...
'string' , 'B',...
'parent' , groups(ii),...
'position', [0.05 0.33 0.9 0.25]...
);
radios(ii,3) = uicontrol(...
'style' , 'radio',...
'units' , 'normalized',...
'string' , 'C',...
'parent' , groups(ii),...
'position', [0.05 0.0 0.9 0.25]...
);
%// Initially, nothing's selected
set(groups(ii), 'SelectedObject',[]);
%// The popups
popups(ii) = uicontrol(...
'style' , 'popup',...
'units' , 'normalized',...
'parent' , panels(ii),...
'position', [0.55 0.4 0.4 0.2],...
'string' , 'Select main option',...
'enable' , 'off'...
);
%// On changing radiobutton, correct the string list of the popups
set(groups(ii),'SelectionChangeFcn', #(~,~)selectionChangeCallback(ii));
%// This is needed by the OK button callback
popupHandles = popups;
end
%// What happens when clicking a radio button?
%// NOTE: this is a doubly-nested function
function selectionChangeCallback(num)
switch get(groups(num), 'SelectedObject')
case radios(num,1)
set(popups(num), 'string', {'A.1', 'A.2', 'A.3'}, 'enable', 'on');
case radios(num,2)
set(popups(num), 'string', {'B.1', 'B.2', 'B.3'}, 'enable', 'on');
case radios(num,3)
set(popups(num), 'string', {'C.1', 'C.2', 'C.3'}, 'enable', 'on');
otherwise
%// ...
end
end
end
%// What happens when pressing the OK button?
function data = getData(popupHandles)
data = char(cellfun(#(x,y)x{y}, ...
get(popupHandles, 'String'),...
get(popupHandles, 'Value'),...
'UniformOutput', false)) %#ok<NOPRT> //
end
end
Output in the MATLAB command window when pressing "OK":
data =
A.1
B.1
The layout is of course still crude, but you get the idea. Of course, the radio buttons can also be replaced by a popup (more compact), three pushbuttons, or whatever else you like.
The contents of the popups are not related to each other, which is exactly the problem with the uitable approach. In this GUI, changes in the popup's contents can be instantaneous when changing a radio button, simply because you have better control over how to deal with changes.
A programming note: I personally don't like it when handles of individual components in what I treat as a "block" are floating around in the top-level function, which is why I use doubly-nested functions -- it's kind of like encapsulation. Now, when used outside of classes, this is not everyone's cup of tea, so you might want to convert them. Of course, all nested functions are trivially converted to subfunctions; you just have to manually pass a lot more information around.
With this approach, you lose some functionality (the ability to re-size your UI elements), but you gain intuitive behavior of the GUI controls. When these are the choices, I've been trained to develop towards the latter option. The nice bells and whistles will only impress the end-user the first few times round, but the program's basic functionality will become more and more important with increased usage. As you noted yourself, this buggy behavior gets annoying when you have to use the tool a lot; I'd say, drop the resizability in favor of improved control behavior.
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:
The solution is to use the Cell Selection Callback with two UITables in your GUI. Make the first table hold the data of {'a','b','c'} then in the cell selection callback, make the second UITable visible and set its data based on the cell selection properties of the first UITable. This link should have everything you need if you look down at the line 'A little hack not needing findjobj'
http://www.mathworks.com/matlabcentral/newsreader/view_thread/306392