I'm learning how to programmatically create an app in MATLAB with a graphical user interface. I'm using MATLAB 2015a.
I don't understand why I am getting this error:
Error using GuiTitle
The specified superclass 'uicontrol' contains a parse error, cannot be found on MATLAB's search path, or
is shadowed by another file with the same name.
I'm trying to make a class called GuiTitle that has uicontrol as the superclass. My class GuiTitle looks like this:
classdef GuiTitle < uicontrol
methods
function obj = GuiTitle(text)
if nargin == 0
text = '';
end
obj#uicontrol('Style', 'text', 'String', upper(text));
end
end
end
Here is my code:
function hello_gui
% Test GUI
GuiConstants % contains constants that
GuiTitle %%
f = figure('Visible','off','Position',[POS_FROM_LEFT,POS_FROM_BOTTOM,...
WINDOW_WIDTH,WINDOW_HEIGHT]);
set(f, 'MenuBar', 'none')
titleText = 'process variable names';
%title = uicontrol('Style', 'text', 'String', upper(titleText));
title = GuiTitle(titleText) %%
title.Position = [0, 0, WINDOW_WIDTH, WINDOW_HEIGHT];
title.FontSize = FONT_SIZE;
f.Visible = 'on';
end
When I comment out lines with %% and uncomment
title = uicontrol('Style', 'text', 'String', upper(titleText));
The window displays properly:
What am I missing?
uicontrol is a function that creates an object of type matlab.ui.control.UIControl:
h = uicontrol;
class(h) % returns 'matlab.ui.control.UIControl'
However, this class is sealed and cannot be used as a superclass:
classdef myclass < matlab.ui.control.UIControl
...
>> a=myclass
Error using myclass
Class 'matlab.ui.control.UIControl' is Sealed and may not be used as a superclass.
Note that GUIs in MATLAB are designed very differently from what you might be used in other languages. There is no need to derive from UI classes to change their behavior, you define their behavior by setting callback functions:
h = uicontrol;
h.String = 'button';
h.Callback = #(src,event) msgbox('pressed the button!');
Related
TL;DR
I am having an issue with the arguments functionality, wherein a subclass fails to instantiate if any name-value argument pairs are passed to it.
(Class definitions are given at the end.)
The arguments functionality, introduced in R2019b, brings great promise in terms of simplifying argument validation and removing boilerplate code from functions [1]. However, upon trying to implement name-value (NV) arguments taken from public class properties, I got the following error:
Invalid argument list. Check for wrong number of positional arguments or placement of positional arguments after
name-value pairs. Also, check for name-value pairs with invalid names or not specified in pairs.
before any subclass code is even executed. This message is confusing, because tab-completion appears to work as expected for NV pairs:
Moreover, if no arguments are passed at all, everything works fine:
>> FluidLayer()
ans =
FluidLayer with properties:
prop1: NaN
prop2: NaN
vs.
>> FluidLayer('prop1',1)
Error using FluidLayer
Invalid argument list. ...
Questions:
Why am I getting an error? I don't think I'm using the arguments mechanism in some unintended or undocumented way, so I'm doubly puzzled about what I might be doing wrong (assuming this is not a bug).
What can be done to resolve this, other than abandoning the whole arguments approach (I would like to keep argument name suggestions)? I have considered transitioning to varargin and/or using the functionSignatures.json approach - but these require significantly more work.
classdef Layer < handle & matlab.mixin.Heterogeneous
properties (GetAccess = public, SetAccess = protected)
prop1(1,1) double {mustBeNonempty} = NaN
prop2(1,1) double {mustBeNonempty} = NaN
end % properties
methods (Access = protected)
function layerObj = Layer(props)
% A protected/private constructor means this class cannot be instantiated
% externally, but only through a subclass.
arguments
props.?Layer
end
% Copy field contents into object properties
fn = fieldnames(props);
for idxF = 1:numel(fn)
layerObj.(fn{idxF}) = props.(fn{idxF});
end
end % constructor
end % methods
end
classdef FluidLayer < Layer
properties (GetAccess = public, SetAccess = protected)
% Subclass-specific properties
end % properties
methods
function layerObj = FluidLayer(props)
arguments
props.?FluidLayer
end
% Create superclass:
propsKV = namedargs2cell(props);
layerObj = layerObj#Layer(propsKV{:});
% Custom modifications:
end % constructor
end % methods
end
I have been able to simplify your example to this:
classdef Layer
properties (GetAccess = public, SetAccess = protected)
prop1
prop2
end % properties
methods
function layerObj = Layer(props)
arguments
props.?Layer
end
disp(props)
end % constructor
end % methods
end
Now Layer('prop1',1) throws an error as you describe.
Thus, it has nothing to do with subclassing or inheritance.
However, if we remove the SetAccess = protected restriction (leaving the properties with public get and set access), then it all works as you expected.
I don't know why restricting set access would limit this use case, as it has nothing to do with writing those properties, and a class method should have set access anyway. My guess is that this is a bug.
The documentation of R2020a states that the structName.?ClassName syntax can only be used with "settable properties defined by a class (that is, all properties with public SetAccess)". Thus, using it with protected properties is explicitly unsupported.
As such, if we want to use the "automatic" arguments validation mechanism, we have no choice but to set the SetAccess to public. However, this solution exposes the object properties to unwanted external changes, and so a workaround is suggested based on several principles:
properties now have public SetAccess, as required by the documentation.
Custom setters are added that perform access validation based on dbstack and meta.class comparison.
New Layer.m (notice the 2 new methods blocks):
classdef Layer < handle & matlab.mixin.Heterogeneous
properties (GetAccess = public, SetAccess = public)
prop1(1,1) double {mustBeNonempty} = NaN
prop2(1,1) double {mustBeNonempty} = NaN
end % properties
%% Constructor
methods (Access = protected)
function layerObj = Layer(props)
% A protected/private constructor means this class cannot be instantiated
% externally, but only through a subclass.
arguments
props.?Layer
end
% Copy field contents into object properties
fn = fieldnames(props);
for idxF = 1:numel(fn)
layerObj.(fn{idxF}) = props.(fn{idxF});
end
end % constructor
end % protected methods
%% Setters & Getters
methods
function set.prop1(obj, val)
if Layer.getCallerMetaclass() <= ?Layer
obj.prop1 = val;
else
Layer.throwUnprotectedAccess();
end
end
function set.prop2(obj, val)
if Layer.getCallerMetaclass() <= ?Layer
obj.prop2 = val;
else
Layer.throwUnprotectedAccess();
end
end
end % no-attribute methods
%% Pseudo-protected implementation
methods (Access = protected, Static = true)
function throwUnprotectedAccess()
stack = dbstack(1);
[~,setterName,~] = fileparts(stack(1).name);
throw(MException('Layer:unprotectedPropertyAccess',...
['Unable to set "', stack(1).name(numel(setterName)+2:end),...
'", as it is a protected property!']));
end
function mc = getCallerMetaclass()
stack = dbstack(2, '-completenames');
if isempty(stack)
mc = ?meta.class;
else
[~,className,~] = fileparts(stack(1).file);
mc = meta.class.fromName(className);
end
end
end % protected static methods
end % classdef
New FluidLayer.m (the foo method was added):
classdef FluidLayer < Layer
properties (GetAccess = public, SetAccess = protected)
% Subclass-specific properties
end % properties
methods
%% Constructor
function layerObj = FluidLayer(props)
arguments
props.?Layer
end
% Create superclass:
propsKV = namedargs2cell(props);
layerObj = layerObj#Layer(propsKV{:});
end % constructor
function obj = foo(obj)
obj.prop1 = obj.prop1 + 1;
end
end % methods
end % classdef
Here's a demonstration of how it works:
>> fl = FluidLayer('prop1', 2, 'prop2', 1)
fl =
FluidLayer with properties:
prop1: 2
prop2: 1
>> fl.prop1 = 5; % attempting to set property directly (unintended)
Error using Layer/throwUnprotectedAccess (line 51)
Unable to set "prop1", as it is a protected property!
Error in Layer/set.prop1 (line 32)
Layer.throwUnprotectedAccess();
>> fl.foo() % attempting to set property through method (intended)
ans =
FluidLayer with properties:
prop1: 3
prop2: 1
>>
In conclusion: it is possible to overcome the SetAccess = public limitation using setter methods, but convoluted modifications to class code are required.
Practical Notes:
The getCallerMetaclass is limited in that it doesn't correctly identify packages - resulting in potentially empty metaclass objects. So keep in mind that this function should be modified if that is the case.
dbstack is called multiple times, although it is not necessary (it can be called once in the setter and the result can then be passed around).
The setter code for different properties is 5-lines long and mostly replicated (with the exception of the property name) - this can be improved by grabbing the property name through dbstack.
I'm trying to implement a matlab class into a simulink MATLABSystem. When I implement superclass, the simulation works fine, but using the subclass I get the following error:
"Simulink cannot automatically infer the output signal properties of 'subclassSystem'. Simulink uses code generation technology to automatically determine the output signal properties from the System object. The System object 'subclass' contains code that does not support code generation.
The error is
'Undefined function or variable 'obj'. The first assignment to a local variable determines its class.'
The error occurred for MATLAB System block 'subclassSystem'. See line 29, column 28 in file 'superclass.m'. The error was detected during size propagation phase."
I commented this line in the code below
Do I have to specify something additional?
Here the subclass definition:
classdef subclass < superclass
properties(Access = protected) % These variables must be initialised. Here or in the setupImpl function
end
methods (Access=protected)
function resetImpl(obj)
end
%% Common functions
function setupImpl(obj)
% Perform one-time calculations, such as computing constants
setupImpl#superclass();
%obj.initFilter(obj.sampleTime, obj.delta_i, obj.delta_d, obj.g_mps2, obj.q0, obj.b_w0, obj.sigma_2_w, obj.sigma_2_a, obj.sigma_2_b_w, obj.sigma_2_yaw)
end
function attitude = stepImpl(obj,u, y)
% Implement algorithm.
attitude = 5;
end
end
methods
% Constructor must be empty for matlab.System. In Matlab call
% initFilter after the object was created. In simulink setupImpl()
% will be called
function obj = subclass()
obj#superclass();
end
end
end
Here the superclass definition:
classdef superclass < matlab.System
% These variables must be initialized in the simulink model
properties
sigma_2_w;
sigma_2_a;
sigma_2_b_w;
sigma_2_yaw;
end
properties(Access = protected) % These variables must be initialised. Here or in the setupImpl function
R;
Q;
end
methods (Access = protected)
function resetImpl(obj)
end
%% Common functions
function setupImpl(obj)
% Perform one-time calculations, such as computing constants
obj.Q = diag([obj.sigma_2_w',obj.sigma_2_b_w']); % this is line 29
obj.R = diag([obj.sigma_2_a',obj.sigma_2_yaw']);
end
function attitude = stepImpl(obj,u, y)
% Implement algorithm.
attitude = 5;
end
end
methods
% Constructor must be empty for matlab.System. In Matlab call
% initFilter after the object was created. In simulink setupImpl()
% will be called
function obj = superclass()
% Support name-value pair arguments when constructing object
end
end
end
I found the error. In the setupImpl function of the subclass I had to call setupImpl of the superclass with the obj as argument:
setupImpl#Superclass(obj);
This is my constructor. Here I didn't use the obj as return value
function obj = Subclass()
obj = obj#Superclass();
end
I have the following UIFigure:
classdef gui < matlab.apps.AppBase
...
function app = gui
% Construct app
end
...
properties (Access = public)
myFuncRef = #myFun
end
...
function myFun(app)
% do something
end
...
end
in which I have defined the method myFun.
If the figure is running (that is, it's showing a window), how can I invoke the method myFun from the Command Window of MATLAB ? I tried with
h = findobj(0, 'type', 'figure');
funcRef = get(h, 'myFuncRef');
funcRef(h);
but I get the error
An error occurred while running the simulation and the simulation was
terminated Caused by: Function 'subsindex' is not defined for values
of class 'matlab.graphics.GraphicsPlaceholder'.
Thanks in advance!
Try this one:
h = findobj(gcf,'-method','myFuncRef')
or
h = findobj(0,'class','gui')
let me know if it works
The probelm is probably that you get just your figure with findobj(0, 'type', 'figure'), this is just a Grahics Obejct which is manimulated by the App-Class.
First I'd like to address the error you're getting. The reason for it is that the h returned by your call to findobj() is empty. Instead you should use findall(0,'Type','Figure',...) [src].
I know this is possible when the method you're referencing is static. Considering the following class:
classdef q45062561 < matlab.apps.AppBase
properties (Access = public)
myFuncRef = #q45062561.myFun
end
methods (Access = private, Static = true)
function myFun()
disp('This works!')
end
end
end
Then, running the following would yield the desired result:
>> F = q45062561;
>> F.myFuncRef()
This works!
Notes:
Rather than finding the handle of the figure via findobj, I'm just storing it during creation.
The modifiers of myFun are unclear from the question so I can't know if this solution is suitable in your case.
Personally, I think it's a better idea to just define the method public and/or static, instead of using a function reference stored in a property.
I have a MATLAB object which is similar to:
classdef myObj < handle
properties (SetObservable)
% NOTE: we want this to be a struct, as written, its not
myStruct
end
methods
function self = myObj(fieldVal)
self.myStruct = fieldVal;
% WANT: self.myStruct.myField = fieldVal;
% add listener to update if change to self.myStruct
% WANT: add listener to update if change to
% self.myStruct.myField
addlistener(self, 'myStruct', 'PostSet', #self.callbackFnc);
end
function callbackFnc(self, varargin)
fprintf(['self.myStruct is now ', num2str(self.myStruct), '\n'])
end
end
end
Which has a listener to a property of an instance (optimistically named myStruct). This gives:
>> tempObj = myObj(3);
>> tempObj.myStruct = 4;
self.myStruct is now 4
How can I set the listener to a field of self.myStruct rather than self.myStruct itself?
You can't do what you're trying to do directly. You can only attach a listener to a property itself, not to a field of the property (if the property is a struct).
I would try to do something slightly different, using the set function for the myStruct property to detect which field was being set, and then firing a regular event rather than a property event:
classdef myObj2 < handle
properties
myStruct = struct('myField', []);
end
events
myFieldChanged
end
methods
function self = myObj2(fieldVal)
self.myStruct.myField = fieldVal;
addlistener(self, 'myFieldChanged', #self.callbackFnc);
end
function set.myStruct(obj, val)
oldProp = obj.myStruct;
obj.myStruct = val;
if obj.myStruct.myField ~= oldProp.myField
notify(obj,'myFieldChanged')
end
end
function callbackFnc(self, varargin)
fprintf(['self.myStruct.myField is now ', num2str(self.myStruct.myField), '\n'])
end
end
end
Hope that suggestion helps!
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.