I am currently working on my matlab final project for school. I consider myself fairly knowledgeable and proficient when it comes to programming.. but Matlab simply has TOO many oddities.
The fundamental question (realized this after finding the answer)! How can I use a variable in a call to a gui handle object
without the name of the variable being used instead of the value?
In other words: Use a variable in a field name (If I had known it was this simple I wouldn't of asked)
My project is building a simple rendition of the age old 'Battleship' game.
My issue: I currently 5 objects (axes) for the ship pieces. They are selected one at a time to be moved into another location(the grid). I am able to use the setpixelposition to move each object after a button click.
Right now under the button click, I have something like this
function btnPlaceShip_Callback(hObject, eventdata, handles)
%Store the current selected ship(passed from an onclick to a label)
ship = get(handles.lblSelectedShip,'string');
%I have tried everything I could think of, but basically I want to achieve the
%following
setpixelposition(handles.ship, [0 250 50 250])
%where the variable 'ship' contains the name of the object.
In other words, the var ship = 'shipAircraftCarrier', and..
setpixelposition(handles.shipAircraftCarrier, [0 250 50 250])
works!(sets the position of the specific ship indicated). Using the variable ship, however, matlab takes the string literally and not for its value. It would be extremely convenient to use the variable instead!
If anyone have any solutions, I would be grateful. I have scoured the web but perhaps I am missing some fundamental understanding of the Matlab GUI stuff - The matlab help documents are very non-descriptive and are not of much help.
As mentioned by others use dynamic fieldnames. Whilst not having the code to test it I believe that simply putting brackets around the ship will substitute the string in to the structure name, thus
setpixelposition(handles.(ship), [0 250 50 250])
Try to avoid the use of eval() if at all possible.
You can do this using the eval function, but you need to be careful about string injections:
setpixelposition(eval(strcat('handles.',ship)), [0 250 50 250])
You can use dynamic fieldnames or getfield. Field indexing using identifiers vs strings is quite similar in Matlab structs and Javascript objects.
Matlab:
fromId = handles.shipAircraftCarrier; %identifier
fromString = handles.('shipAircraftCarrier'); %string
Javascript:
var fromId = handles.shipAircraftCarrier; //identifier
var fromString = handles["shipAircraftCarrier"]; //string
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 4 years ago.
Improve this question
I want to get the content of a variable from its name as a string:
%suppose that I have structures variables in the workspace:
struct_1=struct('field1' ,"str",'field2',0:5)
struct_2=struct('field1' ,"str",'field2',0:10)
struct_3=struct('field1' ,"str",'field2',0:20)
%and I have an other variable like:
a=5
var2='hello'
%and I want to concatenate all these structures in the same structure
%So i wan to get their name.
%I don't know if there is an other way to get just the structure variables
structures=who(str*)
array=[]
for i=1:length(structures)
array=[array; structures(i). field2]; % here I get an error because the content of structures are string representing the variable name
end
%create the new struct
str='newstr_value'
struct_4=struct('field1',str, 'field2',array)
How to fix this error and is there any way to do this better ?
While I would still highly recommend going back to the point of origin of these dynamically named structures (shame 🔔🔔🔔 on the toolbox creator if this is actually the output...) and fixing this problem there, there is an approach that does not require the use of eval.
Similar to my approach for a tangentially related question, you can save the desired structures to a temporary *.mat file and take advantage of load's optional structure output to consolidate the data structures for access in a more robust programmatic matter.
For example:
struct_1=struct('field1' ,"str",'field2',0:5);
struct_2=struct('field1' ,"str",'field2',0:10);
struct_3=struct('field1' ,"str",'field2',0:20);
save('tmp.mat', '-regexp', '^struct_');
clear
oldworkspace = load('tmp.mat');
varnames = fieldnames(oldworkspace);
nvars = numel(varnames);
% Get original structure fields
% The subsequent steps assume that all input structs have the same fields
fields = fieldnames(oldworkspace.(varnames{1}));
nfields = numel(fields);
% Initialize output struct
for ii = 1:nfields
newstruct.(fields{ii}) = [];
end
newstruct(nvars) = newstruct;
for ii = 1:nvars
for jj = 1:nfields
newstruct(ii).(fields{jj}) = oldworkspace.(varnames{ii}).(fields{jj});
end
end
Gives us:
Yay.
Not sure if your situation absolutely requires dynamic calls of variables by their string names. eval can solve your problem. However, eval is slow. At times, it is unreliable. You can easily have conflicting code as you build various components of your project. And in my experience, there have always been alternatives to eval. In your case, Adriaan outlined an example in his comment.
Since you are only trying to interface with a third-party toolbox, if the number of variables you read is reasonable, you can do the followings with eval.
array=struct;
for i=1:length(structures)
eval(['array(',num2str(i),').field2=',structures{i},';'])
end
If you know all the structs are named struct_#, you can also do
array=struct;
num=3; % the number of outputs from your toolbox
for i=1:num
eval(['array(',num2str(i),').field2=struct_',num2str(i),'.field2;'])
end
Note that your field2 values are all arrays. You can't just create an array and expect each "cell" to carry another array value. Arrays, or rather matrices, in Matlab are a special data type. They only take numerical values. (Matrices are highly optimized in Matlab in various ways. That is why we use Matlab.)
Also note that when you set equality of fields of structs, the equality is by reference. There is no deep copy of the data. Modifying one will modify another. (Just beware of this but don't necessarily rely on it. Deciding when to deep copy things is one of the things we let Matlab handle.)
As well, be careful with the result from who. You need to be absolutely clear with what is in the local scope. Above is assuming your result from calling structures=who(str*) is correct for your application.
Mathworks has done it again: my ancient R2012 (as bestowed by my company) returns a nice set of doubles identifying the figure window numbers in response to
currhandles=findall(0,'type','figure');
Now I got a fellow remotely IM-ing me 'cause the code I gave him fails under R2015 because findall now returns a structure for the figure handle. I can't play w/ his system (no RDC) and the mathworks documentation pages don't appear to specify the elements of the figure handle structure. In particular, I'd like to know if I can still retrieve the figure window number. Anyone know?
Of course.
currhandles(:).Number
will return all numbers as a comma-separated list.
Or specify a number you want:
currhandles(1).Number
The order appears to be the inverse order of initialization.
Alternatively you can define two anonymous functions to get an array directly:
figure(1); figure(2); figure(42);
getNumbers = #(x) [x.Number];
getFigureNumbers = #() getNumbers([findall(0,'type','figure')]);
getFigureNumbers()
ans =
42 2 1
For debugging, I have some plots of vectors in an embedded matlab function in Simulink. Up to Matlab R2013b, everything works just fine with the following minimum example code:
function fcn
%#minimum example for plot within for-loop of embedded matlab function
coder.extrinsic('delete');
coder.extrinsic('quiver');
coder.extrinsic('gobjects');
numSteps=4;
persistent hFig;
persistent hVector;
if isempty(hFig)
hFig = figure;
hold on;
hVector=zeros(numSteps,1);
hVector=gobjects(numSteps,1);
for i=1:numSteps
hVector(i) = quiver(0,0,0,0);
end
end
set(0, 'CurrentFigure', hFig);
delete(hVector);
for i=1:numSteps
startInc=[1 1;1 1].*i;
endInc=[2 3;2 -3].*i;
hVector(i) = quiver(startInc(1,:),startInc(2,:),endInc(1,:),endInc(2,:));
end
For the handle array hVector, an initialization is necessary due to its use in the for-loop. However, for an Initialization of a graphics handle object, the function gobjects is needed and in turn the initialization as double with zeros(numSteps,1) becomes necessary, since matlab cannot correctly determine the data type of an output of an extrinsic function.
As I said, this code snippet works just fine if copied to an embedded matlab function block in simulink without anything else in the model.
My problem: Mathworks changed a lot of the plotting functions in R2014a, one of the changes being the datatype of the graphics handles which are no of type quiver for my quiver plot. Thus, the initialization with zeros(numSteps,1) initializes the wrong data type for the handle array. However leaving it out still does not work, because of the problem mentioned above. Neither does a init loop or anything similar compile without errors.
I'd greatly appreciate any help on that issue.
You can try to remove the gobject initialisation and use double() to wrap your call to any matlab graphic object. Ex:
hVector(i) = double( quiver(startInc(1,:),startInc(2,:),endInc(1,:),endInc(2,:)) ) ;
I suggest reading Loren's article about the compatibility issues which can arise when switching to HG2 versions of Matlab.
A quick quote from it which apply more to your issue:
Graphics Functions Return Objects, not Numeric Handles
Prior to R2014b, you could store a set of handles to graphics objects
in an array and then add some numeric data to that array. In R2014b,
that will cause an error.
[...]
If you find yourself really stuck, it is possible to cast object
handles to numeric handles using the double function. You can then
cast the number back to an object handle using the handle function. We
don't recommend this as a long term solution. Be aware that we may
choose to remove this feature in a future version of MATLAB. If we do,
we'll let you know in advance.
Now if you really have to use this solution, note that both functions works on single elements but also on arrays. So
hVector_HG = handle( hVector_Num ) ; %// works if "hVector_Num" is an array of numeric handles
%// and ...
hVector_Num = double( hVector_HG ) ; %// works if "hVector_HG" is an array of HG2 graphic handles
That may simplify the round trips between a format or another if they often become necessary.
Edit:
I put this at the bottom of the post for the moment because the beginning is already accepted answer but please try the following and let me know if it works. It may solve your problem in a better (more future-proof) way.
Another way to initialise an array of handle of a given graphic object is to create one (empty is good enough) and replicate it. For example:
hqNaN = quiver(NaN,NaN) ; %// create an empty quiver
hVector = repmat( hqNaN , numSteps , 1 ) ; %// replicate it in an array
will give you an array hVector containing numSteps HG2 graphic object handles. At this point they all point to the very same object but it is perfectly legal to overwrite each element with a handle of the same type. So a later :
hVector(i) = quiver( ... , etc ... ) ; %// overwrite handle "i" with a new quiver plot handle
will (should) work without problem.
A few things to take care with this approach:
where will the empty quiver be created ?
you may already have a "current" figure and you don't want it to be messed up. If not a new empty plot will be created. So to make sure the empty quiver do not cause problem (just a flicker on the screen), you could wrap it this way:
figure ; hqNaN = quiver(NaN,NaN) ; close(gcf) ;
or you can also park it in a figure (it will be invisible anyway) in case you need to re-use a handle of this type for other array initialisation. Just don't forget that once you close the figure it is on, or you delete the graphic object, the hqNaN variable is still there but it is not the same type of handle any more (so not useful to replicate if you want this type exactly).
What if you do not overwrite all you initial handle array ?
Remember all the initial handles of the array point to the same graphic object. So in case your array contains 12 elements but let's say by mistake you only overwrite 10 of them, then 2 elements will be handles pointing to the same object (which you may already have deleted)). This means that calling:
delete(hVector)
will throw you the annoying:
Error using handle.handle/delete. Invalid or deleted object.
gna gna gna ... luckily you can easily prepare for that by programming defensively and using instead:
delete( hVector(ishandle(hVector)) )
Matlab will do the check automatically and only delete the right handles.
While trying to execute generation of a series of text boxes in a post-zoom callback function in Matlab, a number of errors are generated, the trace of which is unhelpful in diagnosing and resolving the problem. When the text generation is in the main body of code as follows, the code executes properly and there is no issue executing the minimal post-zoom callback function:
string='ABCDEFGHIJKLMNOPQRSTUVWXYZ';
offset=8;
h=figure;
z=zoom(h);
set(z,'ActionPostCallback',#post_callback);
[ax,plt1,plt2]=plotyy(14:20,1:7,15:17,1:3,#bar,#bar)
ylim([0 10]);
hold on;
set(plt2,'FaceColor','r');
xLim=xlim(gca);
for i=xLim(1)+1:xLim(2)-1
text(i,8,string(i-offset+1),'Clipping','on');
end
function post_callback(obj,evd)
xLim = get(evd.Axes,'XLim');
However, if the text box generation is moved to the #post_callback function, a series of errors result:
string='ABCDEFGHIJKLMNOPQRSTUVWXYZ';
offset=8;
h=figure;
z=zoom(h);
set(z,'ActionPostCallback',#post_callback);
[ax,plt1,plt2]=plotyy(14:20,1:7,15:17,1:3,#bar,#bar)
ylim([0 10]);
hold on;
set(plt2,'FaceColor','r');
function post_callback(obj,evd)
xLim = get(evd.Axes,'XLim');
for i=xLim(1)+1:xLim(2)-1
text(i,8,string(i-offset+1),'Clipping','on');
end
Those errors are, as described in this problem:
Warning: An error occurred during the mode callback.
> In uitools.uimode.fireActionPostCallback at 14
In zoom>local2DButtonUpFcn at 1332
In hgfeval at 63
In uitools.uimode.modeWindowButtonUpFcn at 46
In uitools.uimode.setCallbackFcn>localModeWindowButtonUpFcn at 58
Could someone please enlighten me as to the root cause of the errors?
The error seems to be the same. You should try to use the debugger here. Set a breakpoint in your post callback function. However, there are a few problems in your code. Some are implementation issues, some other problems does not do what you ask them too. You seems to have some programming experience, so I guess that most implementation issue are just bugs. I will list problems and solutions.
1) I cry when I see that you use string as a variable. String is an abstract type (obselete in matlab now since later versions uses char vectors). use instead some other name like str or myString.
2) Unless offset and your variable string is not declared global, they will not be inside the scope of post_callback. Different from C or C++, a variable does not become global when defined in "main" (since you do not even define a main method). If you want to use string or offset inside of post_callback, you must define them inside their scope (inside the function).
3) the loop variable i must not be a cell. Use the method from the previous question to convert it to a double vector.
4) The loop variable i must be an integer in:
text(i,8,string(i-offset+1),'Clipping','on');
since string is defined a variable and variable indices must be integers, xLim does not necessary need to be an integer.
5) I do not think text will give the expected output here. What text does is creating a string and linking it to a point on the plot. This means that every time you zoom you will have a new text in the plot. Unless the texts are located at exactly the same place (which may be possible, but will reqire a lot of work) the plot will look really ugly. If possible, place the text strings in the way that you do in example 1 and remove it from post_callback.
let's assume the following easy example:
f = figure;
plot(-10:10, (-10:10).^3, '*-r');
x = 1;
y = 1;
set(f, 'ResizeFcn', {#resizeCallback2, x, y});
while 1
[x, y, button] = ginput(1);
if(button ~= 1)
break;
end
set(f, 'ResizeFcn', {#resizeCallback2, x, y});
end
%%--------------------------
function resizeCallback2(hFig, ~, foo, bar)
foo
bar
end
Is there any easier way to always pass the ACTUAL* values for x and y to the callback function instead of having to always update it in the loop?
Thanks!
It looks like you are trying to store the value of a mouse click position, and then use those values as a part of the resize function (which would be called at a later time). There are a few changes that I would make.
First, instead of the while loop, use another callback to capture the mouse click. For example, you could use the figure ButtonDownFcn call back to trigger a function which was designed to capture the mouse position into some location.
Second, there are better ways to store the mouse position, and the right way will depend on your skill level and the needs of your program. Some of these methods of storing data are:
In the arguments to another callback, like you are doing now. This is pretty painful, but it probably works. So you could keep it if it is good enough for your needs.
The 'userdata' field in most Matlab objects. A few people have brought this up, and it will work fine. I don't like to rely on this because I'm always afraid that some other tool will also want to use the userdata field, and the tools will overwrite data.
A global variable value. I don't like to use globals either, for the same reason I don;t like to use the userdata field. But globals are sometimes the best solution anyway. This is probably the easiest, lowest effort solution to your problem if you only have one figure at a time. (Multiple figures would drive you towards the userdata solution as the easiest solution.)
Provide a handle class to store some data (i.e. x and y) and give a copy of that class to each of the two callbacks (ButtonDownFcn and ResizeFcn). This allows the two functions to pass data, without polluting anyone else's namespace. This is my favorite solution to this sort of problem, so I'll give it a more detailed description below.
To execute option (4) above would need a class to store the data that looks something like this:
classdef ApplicationData < handle
properties (SetAccess = public, GetAccess = public)
x = [];
y = [];
end
end
Note that since ApplicationData extends handle, Matlab treats it as a pass-by-reference object, which is useful to us.
Then you can create an instance of this class, and give it to each callback function.
dataPassing = ApplicationData;
set(f, 'ButtonDownFcn', #(x,y) mouseClickCapture(x,y,dataPassing));
set(f, 'ResizeFcn', #(x,y) resizeCallback2(x,y, dataPassing));
Where mouseClickCapture looks something like this:
function mouseClickCapture(hAxis, ignored, dataPassingClass)
mousePositionData = get(hAxis,'CurrentPoint');
dataPassingClass.x = mousePositionData(1,1);
dataPassingClass.y = mousePositionData(1,2);
And your resizeCallback2 looks something like this:
function resizeCallback2(h, ignored, dataPassingClass)
%Do something here using
%dataPassingClass.x
%and
%dataPassingClass.y
I'm not sure what you're actually trying to do - perhaps your simple example has obscured your real intentions - but rather than repeatedly setting a new version of the ResizeFcn, could you just store something in the UserData property of the figure, and have your ResizeFcn read that when it's executed?