What I have:
In a Matlab-GUI I have a uicontextmenu connected to a plot (=axes). If I "activate" this via a mouse-click (right-button), I can use the usual "Callback" to do something, like highlighting the plot. If the user then selects one of the uimenu-elements of the menu, I can use the Callback of this uimenu-element and reset the highlighting.
But there is a problem, if the user does not select an element. The context-menu disappears and I cannot find a way to find out, if this happens. In my example, the highlighted plot stays highlighted.
What I tried so far:
Besides reading the docs, I appended listeners to the properties to some of the uimenu-elements, e.g.:
addlistener(mymenu_element, 'Visible', 'PostSet', #mytest);
but this property, as well as others, seems not to be changed or touched at any time - what suprises me a bit :o
So the question is:
Is there a way to execute a function after a uicontextmenu is executed (or however you call it, when a context-menu "disappears")? In other words: if the user does not select an element of a context-menu, how can this be identified?
Since you cant listen to these items (I've run a few tests and come to the same conclusion) you can work around this by creating and managing your uicontextmenu in a different way:
function yourFunction
% create a figure
hFig = figure;
% add a listener to the mouse being pressed
addlistener ( hFig, 'WindowMousePress', #(a,b)mouseDown(hFig) );
end
function mouseDown(hFig)
% react depening on the mouse selection type:
switch hFig.SelectionType
case 'alt' % right click
% create a uicontext menu and store in figure data
hFig.UserData.uic = uicontextmenu ( 'parent', hFig );
% create the menu items for the uicontextmenu
uimenu ( 'parent', hFig.UserData.uic, 'Label', 'do this', 'Callback', #(a,b)DoThis(hFig) )
% assign to the figure
hFig.UIContextMenu = hFig.UserData.uic;
% turn visible on and set position
hFig.UserData.uic.Visible = 'on';
hFig.UserData.uic.Position = hFig.CurrentPoint;
% uicontext menu will appear as desired
% the next mouse action is then to either select an item or
% we will capture it below
otherwise
% if the uic is stored in the userdata we need to run the clean up
% code since the user has not clicked on one of the items
if isfield ( hFig.UserData, 'uic' )
DoThis(hFig);
end
end
end
function DoThis(hFig)
% Your code
disp ( 'your code' );
% Clean up
CleanUp(hFig);
end
function CleanUp(hFig)
% delete the uicontextmenu and remove the reference to it
delete(hFig.UserData.uic)
hFig.UserData = rmfield ( hFig.UserData, 'uic' );
end
Related
I have a uibuttongroup with radio buttons defined in it. I have uipanels defined with their corresponding properties. What I want to do is to be able to click one radio button and have one uipanel appear, and then click my other radio button to have the other uipanel appear. Here are snippets of my code:
operation_type_1 = uibuttongroup(S.Test, 'Title', 'Operation Type', 'position', [0 0.3 panel_w/2 0.15]);
uicontrol('Parent',operation_type_1, 'Style', 'radiobutton',...
'String', 'invisible',...
'position', [0 0 0 0], 'Tag', 'invisibutton');
uicontrol('Parent',operation_type_1,'Style','radiobutton',...
'String', 'Time Operation',...
'Position', 100*[0.1 flooring(3.5, 'tp') 1.2 0.15], 'Tag', 'timeop1');
uicontrol('Parent',operation_type_1,'Style','radiobutton',...
'String', 'Volume Operation',...
'Position', 100*[0.1 flooring(2.5, 'tp') 1.2 0.15], 'Tag', 'volumeop1');
This defines my button group and the two radio buttons.
Then I have code which creates a volume panel:
As well as a Time Panel:
These are in the same position. What I want is to be able to click on the "Time Operation" radio button and have the time panel be visible, and when I click on the "Volume Operation" radio button, the volume panel is visible.
I've tried doing switch case statements. I don't get errors, but I don't get results either. For example, my case statements for the time and volume panels are:
switch str
case 'timeop1'
if U.Value; S.result_panel_time1.Visible = 'On';
else S.result_panel_time1.Visible = 'Off';
end
case 'volumeop1'
if U.Value; S.result_panel_volume1.Visible = 'On';
else S.result_panel_volume1.Visible = 'Off';
end
How do I get this to work? I'm not using GUIDE, just coding a MATLAB GUI.
UPDATE
I've tried implementing the callback suggested below, but I get a "Function definition is misplaced or improperly nested." error. I use the following function:
function button_callback(U, varargin{2})
switch get(get(operation_type_1, 'SelectedObject'), 'Tag')
case 'timeop1'
if U.Value; S.result_panel_time1.Visible = 'On';
else S.result_panel_time1.Visible = 'Off';
end
case 'volumeop1'
if U.Value; S.result_panel_volume1.Visible = 'On';
else S.result_panel_volume1.Visible = 'Off';
end
end
end
And I've added the callbacks "...'callback', {#pb_call, S}" to my timeop1 and volumeop1. (Because all of the other function I have are in a .m file called pb_call.m). The function appears to be nested fine but the error points at the exact one.
It seems to me you did not define callback for your RadioButton. For example, set callback for volumeop1:
uicontrol('Parent',operation_type_1,'Style','radiobutton',...
'String', 'Volume Operation',...
'Position', 100*[0.1 0.3 1.2 0.15], 'Tag', 'volumeop1', ...
'Callback', #switchPanel);
Then in function switchPanel, you will set corresponding panel visible, while set others invisible.
This is trying to answer your questions, but it seems to me what you want is uitab.
My partner ended up fixing it:
The callback was {callback, S} and S, U, and str were:
S = varargin{3}; %main figure handle
U = varargin{1}; %current uicontrol
str = char(U.String);
The problem occurred in the radiobutton creation, since the result panels were being created after the radiobuttons could be triggered, thus nothing was made invisible/visible and an error would occur.
However, it would be highly convenient if callbacks could affect all GUI parts, not just previously defined ones. I've tried using guidata in the past, but I had to use other, less straighforward methods to accomplish my goals. I'll try using working samples and building upon those in the future, but currently I am working on another part of the project and will get back to that later.
But using either guidata/setappdata or something related would work here as well as my own solution, which is making sure that the objects you are trying to change are already defined before the button triggering the callback.
(He also posted this answer to where I asked this same question in MATLAB Answers.)
I have an edit text in a MATLAB GUI. I want the user to be able to write only numerals and whenever they write a text character, this last character is immediately deleted. Moreover, I don't know in which kind of function to put this code(callback, keypress, etc.).
This is impossible without resorting to Java. That is because MATLAB has no way to access a uicontrol's typed string; you can only access its current string (i.e., after pressing Enter or changing focus).
Below is an imperfect workaround. It uses two identical edit boxes, one on top of the other, but the topmost box is initially hidden. The KeyPressFcn of the visible edit box:
filters the keypresses on numeric-only
accumulates valid keypresses in a string with global storage
sets that string to the current string of the invisible edit box
Makes that invisible edit box visible, so that it occludes the one you're typing in
Its CallBack function then
Takes the string of the normally-invisible box
Sets the always-visible box' string equal to that string
Hides the normally-invisible box again
Here's the implementation (liberally borrowed from here):
function GUI_tst
% Create new GUI
G.fh = figure('menubar' , 'none',...
'units' , 'normalized', ...
'position', [.4 .4 .2 .2]);
% The actual edit box
G.eh1 = uicontrol('style' , 'edit',...
'units' , 'normalized', ...
'position' , [.1 .4 .8 .2],...
'string' , '',...
'KeyPressFcn', #kpr,...
'Callback' , #cll);
% The "fake" edit box
G.eh2 = copyobj(G.eh1, G.fh);
set(G.eh2, 'Visible', 'off');
% Its string (global)
G.eh_str = '';
guidata(G.fh, G);
end
% "Real" edit box' KeyPressFcn()
function kpr(~, evt)
if isempty(evt.Character)
return; end
G = guidata(gcbf);
% Occlude the "real" editbox with the "fake" one
set(G.eh2, 'visible', 'on');
% Accumulate global string if keys are numeric
if strcmp(evt.Key,'backspace')
G.eh_str = G.eh_str(1:end-1);
elseif isempty(evt.Modifier) && ...
any(evt.Character == char((0:9)+'0') )
G.eh_str = [G.eh_str evt.Character];
end
% Set & save new string
set(G.eh2, 'string', G.eh_str);
guidata(gcbf,G);
end
% "Real" edit box' CallBack()
function cll(~,~)
G = guidata(gcbf);
% Set the "real" box' string equal to the "fake" one's,
% and make the "fake" one invisible again
set(G.eh1, 'String', get(G.eh2, 'String'));
set(G.eh2, 'visible', 'off');
end
This works reasonably well, but it has some drawbacks:
because you're typing somewhere you can't see, the cursor is hidden
selecting text and pressing backspace/delete does not work
it's not very resource efficient
Although it is possible using Java (see this post by MATLAB-god Yair Altman), the simpler and more common way to do it is to just accept that the user is typing invalid input, and only check/correct it in the Callback function (i.e., after pressing Enter).
I have an external function say "external_func" (seperate .m file)
Inside this function a while loop is called, and this while loop update a variabl named "update_prog"
Now I will pass this value into the GUIDE using
assignin('base', 'update_prog', update_prog); % passing to workspace
I am making this
"update_prog" as global variable and calling it into GUIDE .m file
function pb1_Callback(hObject, eventdata, handles)
global update_prog
% hObject handle to pb1 (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% else
% set(handles.pb1,'enable','on');
% end
% update_prog first value prints, but it wont updates as the loop in external_func goes on.
drawnow;
set(handles.slider1,'Value',update_prog)
external_func;
so here in the GUIDE .m file I can get the value
"update_prog" but it wont keep up with the while loop. I used "drawnow" but its of no use.
How can I refine this value "update_prog" as the while loop in the "external_func" goes through multiple iterations. [Note: The updated values are there in the callback function of GUIDE, but unless there is a callback the callback function wont update the "update_prog"], so how can I acheive this real-time update inside a call_back function.
[Note: passing the variables through function input is not possible here in my case, so I am looking for alternatives]
Edit1: please consider this link, which has an exampleWhich may clarify you what I am trying to acheive
What I am doing here is
Passing the variable(which is being updated in the while loop of the externel function) into GUI.
I will use this variable to show the progress on the progress bar(Slider).
What is the problem?
1. The variable inside the GUI callback(Consdier I will press a push button and then it will call the function with while loop) will put the updated values into the set(handles.slider,'Value',variable)
By doing this I cant move the slider.
Why?
Callback updates the variable only when I press the push button, next all the updates to the variable will not be updated, so progress bar/slider wont move.
I wouldn't recommend to pass your variable in 3 steps with an intermediate workspace (external->base workspace->GUI). I would rather recommend to pass your variable directly (external->GUI).
Each Matlab figure offers a space to store variables (Application Data) of any type. I would suggest reading the article Share Data Among Callbacks and read the documentation for the 3 functions:
guidata
setappdata
getappdata
This way will offer you much more control over the scope of your variables and you won't need any global declaration.
Below is an example of a simple gui. The gui declare the variable in it's user space (with setappdata), then uses a timer to periodically read this variable (with getappdata).
The external function do whatever you want it to do (just a random number in the example), then to update the variable you use the same setappdata. The only thing you need for that is the handle of the main GUI figure, so in the example I give it as input of the external function.
The GUI also has two buttons to start and stop the update.
The code for the main example GUI 'theGui.m' is :
function h = theGui
%// basic GUI with 2 buttons and 1 slider
h.fig = figure('Position',[433 434 500 100],'Menubar','none','CloseRequestFcn',#my_closefcn) ;
h.sld = uicontrol('Style','Slider','Position',[20 20 460 20]) ;
h.btnStart = uicontrol('Style','pushbutton','String','Start updating','Callback',#btnStart_callback,'Position',[20 50 200 30]);
h.btnStop = uicontrol('Style','pushbutton','String','Stop updating','Callback',#btnStop_callback,'Position',[280 50 200 30],'Max',1,'Min',0);
%// Define the timer
h.t = timer ;
h.t.Period = 0.1 ; %// 0.1s refresh interval
h.t.TimerFcn = {#timer_callback,h.fig} ;
h.t.ExecutionMode = 'fixedSpacing' ;
%// initialise the variable to update in the GUI appdata
update_prog = 0 ;
setappdata( h.fig , 'update_prog' , update_prog ) ;
%// save handles
guidata( h.fig , h );
function btnStart_callback(hobj,~)
h = guidata( hobj ) ; %// retrieve handles
if strcmp('off',h.t.Running) %// Start timer (only if not already running)
start(h.t)
end
function btnStop_callback(hobj,~)
h = guidata( hobj ) ; %// retrieve handles
stop(h.t) %// Stop timer
function timer_callback(~,~,hfig)
update_prog = getappdata( hfig , 'update_prog' ) ; %// retrieve the 'update_prog' variable value
h = guidata( hfig ) ; %// retrieve handles
set(h.sld , 'Value' , update_prog) ; %// update the slider object with the retrieved value
function my_closefcn(hobj,~)
%// this function is only to clean up when the GUI will be closed.
%// It is recommended to delete the timer manually
h = guidata( hobj ) ; %// retrieve handles
stop(h.t) %// Stop timer (in case it is still running)
delete(h.t) ; %// delete the timer
delete(h.fig) ; %// destroy the figure
And the code for external_func.m
function external_func( guiMainFigureHandle )
%// This function will only generate random numbers and push them into the
%// variable 'update_prog' contained in the GUI appdata.
%// This is why this function NEEDS the handle of the gui to be able to
%// access the Application Data space of the gui.
for k = 1:100
randomValue = rand(1) ; %// generate a random value
hfig = ancestor( guiMainFigureHandle , 'figure' ) ; %// make sure the handle provided is the top level figure
setappdata( hfig , 'update_prog' , randomValue) ; %// update the variable value
pause(0.1) ;
end
Edit:
I place this in edit instead of changing the code above because I don't recommend messing with the root object if you don't need to. But in your case it can be a way round your problem.
If your external function doesn't have access to the GUI, it can always update a part of memory which is available for all the programs running in a given Matlab session, namely the root object. The handle for it is reserved and is the same for any program: 0 (although since v2014b there is another way to invoke it : groot, it is still always the same handle for all Matlab).
So in the example above, in theGui.m, use instead:
setappdata( 0 , 'update_prog' , update_prog ) ;
in the main routine, then in the subfunction function timer_callback(~,~,hfig), use:
update_prog = getappdata( 0 , 'update_prog' ) ; %// retrieve the 'update_prog' variable
And your function external_func() doesn't need any extra argument, the update only needs one line:
setappdata( 0 , 'update_prog' , update_prog) ; %// update the variable value
I "suspect" that your update_prog variable in the base workspace is not a global (you must define it to be global in every workspace that you want to use it).
Since your using globals (there are many better ways to do this - but thats not your question) - why don't you simply define the update_prog variable to be global in your external_func function (replace the assign call).
edit put a drawnow in your external_func function. That way when you click on the button it will update.
edit 3
I think I know what you want to do, try this example and see if it does what you want - updated to show how you find the slider object in your code and update inside your loop:
function mygui
% create a figure
f = figure;
% create a uicontrol slider - note that I give it a tag.
uicontrol ( 'style', 'slider', 'Position', [0 200 200 40], 'tag', 'MYSLIDER', 'backgroundcolor', 'white', 'parent', f );
% create a push button which we can press to update
uicontrol ( 'string', 'Press 2 start', 'callback', #(a,b)myLoop(), 'Position', [0 0 200 50] )
end
% This replicates your "external_func" function.
function myLoop()
% since you cant pass in any var -> you need to find the slider object
% you can do this by using findobj and search for the tag of the item
uic = findobj ( 0, 'tag', 'MYSLIDER' );
% find the figure handle (only needed for this demo)
f = ancestor ( uic, 'figure' );
% initialise the value which will be used to update the slider
count = 0;
% create your loop
while ishandle(f)
% incrememt the count variable -> this will end up on the slider value
count = count + 1e-5;
% reset count if > 1 -> so the slider cant go out of range.
if count >= 1
count = 0;
end
set ( uic, 'Value', count );
% initiate a drawnow -> this allows matlab to process GUI events
drawnow();
end
end
The downside of this is you insert a drawnow in your loop -> which could slow it down somewhat.
If this doesn't fix your problem you need to explain better what you want to do... (in my view)
I have added uicontextmenu to the line object. uicontextmenu includes 3 check boxes. whenever I check any of them uicontextmenu disappears. I want uicontextmenu visible for sometime so that i can check multiple boxes and see the change (same as a button group but in uicontextmenu). Is there any solution to this or some other approach?
cmenu=uicontextmenu;
set(he,'uicontextmenu',cmenu);
item1=uimenu(cmenu,'label','Data A','checked','off','callback',#func_a);
item2=uimenu(cmenu,'label','Data B','checked','off','callback',#func_b);
item3=uimenu(cmenu,'label','Data C','checked','off','callback',#func_c);
basically, he is the line object created by plot(x,y) and func_a, func_b, func_c are function to convert property 'checked' to on|off.
This example is greatly inspired by Benoit_11 solution, but a bit refined. I was also under the impression that the 3 different functions in your callback were doing different things so I made the 3 different menus change different properties of the line (instead of changing the same property with different values).
I made the uimenu callback in one single nested function. It decides what to do based on the parameter what2do supplied at the uimenu definition (but feel free to keep 3 separate functions). However, note that the function that toggle the check mark is the same for all uimenu (you don't need a separate function for each of them).
function hf = TestUiContext2
%// Extension of Benoit_11 solution
clear ; clc ; close all
hf = figure ; %// return the handle of the figure
hax = axes; %// Create axes and save handle
plot(rand(20,3)); %// Plot three lines
hcmenu = uicontextmenu; %// Define a context menu; it is not attached to anything
%// Define the context menu items and install their callbacks
item1 = uimenu(hcmenu, 'Label','Bold line' , 'Callback' , {#uiCallback,'bold'} );
item2 = uimenu(hcmenu, 'Label','Dotted line' , 'Callback' , {#uiCallback,'dots'} );
item3 = uimenu(hcmenu, 'Label','Markers on' , 'Callback' , {#uiCallback,'mark'} );
hlines = findall(hax,'Type','line'); %// Locate line objects
for line = 1:length(hlines) %// Attach the context menu to each line
set(hlines(line),'uicontextmenu',hcmenu)
end
function uiCallback(obj,~,what2do)
hline = gco ;
switch what2do
case 'bold'
toggle_bold_line(hline)
case 'dots'
toggle_dotted_line(hline)
case 'mark'
toggle_markers(hline)
end
%// reposition the context menu and make it visible
set(hcmenu,'Position',get(gcf,'CurrentPoint'),'Visible','on')
toggle_checkmark(obj) %// toggle the checkmark
end
function toggle_checkmark(obj)
if strcmp(get(obj,'Checked'),'on')
set(obj,'Checked','off')
else
set(obj,'Checked','on')
end
end
function toggle_bold_line(hline)
if get(hline,'LineWidth')==0.5
set(hline,'LineWidth',2)
else
set(hline,'LineWidth',0.5)
end
end
function toggle_dotted_line(hline)
if strcmpi(get(hline,'LineStyle'),':')
set(hline,'LineStyle','-')
else
set(hline,'LineStyle',':')
end
end
function toggle_markers(hline)
if strcmpi(get(hline,'Marker'),'none')
set(hline,'Marker','o')
else
set(hline,'Marker','none')
end
end
end
Now you can enjoy ticking all your menu in one go ;)
Here is a workaround which might do the trick for you. That's not too elegant but it seems to work.
The trick is to set the menu 'Visible' property to 'on' in every callback you have (i.e. #func_a, #funct_b and #funct_c). When I run the following example (based on the demo on the Mathworks website) the menu does not disappear when the selection is changed. Notice that I created separate functions for each callback.
Here is the code:
function TestUiContext( ~)
%// Based on example from The Mathworks
%// http://www.mathworks.com/help/matlab/ref/uicontextmenu.html
clear
clc
close all
%// Create axes and save handle
hax = axes;
%// Plot three lines
plot(rand(20,3));
%// Define a context menu.
hcmenu = uicontextmenu;
%// Define the context menu items and install their callbacks
item1 = uimenu(hcmenu,'Label','dashed','Callback',#(s,e) hcb1);
item2 = uimenu(hcmenu,'Label','dotted','Callback',#(s,e) hcb2);
item3 = uimenu(hcmenu,'Label','solid','Callback',#(s,e) hcb3);
%// Locate line objects
hlines = findall(hax,'Type','line');
%// Attach the context menu to each line
for line = 1:length(hlines)
set(hlines(line),'uicontextmenu',hcmenu)
end
%// In the callback of every item/option, set the menu property 'Visible' to 'on'.
function hcb1
set(gco,'LineStyle','--');
set(hcmenu,'Visible','on')
end
function hcb2
set(gco,'LineStyle',':');
set(hcmenu,'Visible','on')
end
function hcb3
set(gco,'LineStyle','-');
set(hcmenu,'Visible','on')
end
end
And 2 screenshots to show what it looks like:
And moving the cursor down:
So as I said, not perfect but hopefully it will do the job for you!
I have built a GUI in matlab using GUIDE, and added keyboard shortcuts to some actions using the WindowKeyPressFcn callback function of my figure. my problem is that I want to use the spacebar as one of the shortcut keys, but it already has a preset usage: it activates the currently selected control (i.e. same as clicking the selected button).
While I can trigger my intended action just fine on the spacebar key through the callback, I found no way to remove this additional unwanted functionality. The result is two actions being performed - the one I programmed and the unintended button press, which creates a mess. The bottom line is that I can't use the spacebar as a shortcut. Is there any way to turn this functionality off or bypass it somehow? Perhaps something that will stop the key-press from reaching the GUI after it is handled by my callback?
I'd prefer a documented Matlab way but if that's not possible, java hacks are welcome as well.
AFAIK the unwanted behaviour is more OS releated than Matlab related and I know of no wway to remove it, however you can work around it.
One way to do this is to check in your pushbutton callback what was the last key pressed, you do this by adding a KeyPressFcn to your pushbutton. I have created a simple example below:
function spacebarTest
% create a figure
hFig = figure ( 'KeyPressFcn', #KeyPress );
% create a uicontrol
uicontrol ( 'style', 'pushbutton', 'Callback', #PushButton, 'KeyPressFcn', #KeyPress );
% store the last key pressed information in the fiure handle
userData.lastKey = '';
set ( hFig, 'userData', userData );
end
function KeyPress ( obj, event )
% obtain the handle for the figure
hFig = ancestor ( obj, 'figure' );
% extract the user data
userData = get ( hFig, 'userData' );
% store the last key in the user data
userData.lastKey = event.Key;
% update the figure handle user data
set ( hFig, 'userData', userData );
disp ( 'spacebar shortcut' );
end
function PushButton ( obj, event )
% get the figure handle
hFig = ancestor ( obj, 'figure' );
% extract the user data
userData = get ( hFig, 'userData' );
% check if spacebar was the last key pressed
if strcmp ( userData.lastKey, 'space' ); return; end
disp ( 'running callback - push me' );
end
One problem with this is that it still "looks" like your button has been pressed, although the callback has not run. To resolve this you would need to stop the button ever gaining focus which you would need to replace your uicontrol button with a JButton (java) and use the FocusGainedCallback