Implementing an hgsetget subclass

I would like to create a subclass of hgsetget that would show this kind of behavior:
>>u=unit('SI');
>>set(u) % set behavior 1
System: '[{SI} | cgs]'
>>set(u,'System') % set behavior 2
[{SI} | cgs]
>>set(u,'System','cgs') % set behavior 3
>>get(u,'System')
ans =
cgs
In other words, much like the behavior you see for a figure handle. Following the example in Implementing a Set/Get Interface for Properties, I tried this code:
classdef unit < hgsetget
properties
System = '';
end
methods
function H = unit(str)
if nargin > 0
H.System = str;
end
end
function H = set.System(H,str)
H.System = str;
end
end
end
This gets set behavior 3 right, but how do I add the other two? The closest I have come so far is to simply program all the cases into an overloaded set method, and get rid of set.System. But if I add a few more properties, this gets quite ugly. Does anyone know an elegant way of doing this?

 Accepted Answer

O.k., this works:
classdef unit < hgsetget
% This is just a test class for using SET in an HGSETGET subclass.
properties
System = '';
end
methods
function H = unit(str)
if nargin > 0
H.System = str;
end
end
function varargout = set(H,varargin)
S = struct('System','[{SI} | cgs]');
switch nargin
case 1
if nargout < 1
disp(S)
else
varargout{1} = S;
end
case 2
if nargout < 1
disp(S.System)
else
varargout{1} = S.System;
end
case 3
H.System = varargin{2};
otherwise
error('unit:wrongNumberOfArguments', ...
'Wrong number of arguments to SET command.')
end% switch
end% set
end% methods
end% classdef
But I still wonder - is there an implementation that involves a function SET.SYSTEM?

More Answers (3)

This is interesting. It does seem like the documentation isn't complete. It mentions how calling the set with a particular syntax returns the possible values of a property, but it doesn't explain how to specify those possible values.
Here's something that I put together to try to achieve what you want, while making it somewhat extensible to multiple properties:
classdef MyClass < hgsetget
properties
Prop1
Prop2
end
properties (Hidden, Constant)
Prop1_values = {'SI', 'cgs'}
Prop2_values = {'C', 'F'}
end
methods
function varargout = set(obj, varargin)
% Allow only one output and no more than 3 input arguments
error(nargoutchk(0, 1, nargout, 'struct'));
error(nargchk(1, 3, nargin, 'struct'));
switch nargin
case 1 % set(h) syntax
propnames = properties(obj);
if nargout
s = cell2struct(cell(size(propnames)), ...
propnames, 1);
for id = 1:length(propnames)
s.(propnames{id}) = set(obj, propnames{id});
end
varargout{1} = s;
else
for id = 1:length(propnames)
set(obj, propnames{id});
end
end
case 2 % set(h, 'PropertyName') syntax
allprops = properties(obj);
if ismember(varargin{1}, allprops)
if nargout
varargout{1} = MyClass.([varargin{1}, '_values']);
else
fprintf('%10s: %s\n', varargin{1}, ...
toString(MyClass.([varargin{1}, '_values'])));
end
else
error('Invalid property "%s" for class "%s".', ...
varargin{1}, class(obj));
end
case 3 % set(h, 'PropertyName', value) syntax
allprops = properties(obj);
if ismember(varargin{1}, allprops)
val = validatestring(varargin{2}, ...
MyClass.([varargin{1}, '_values']), ...
mfilename, varargin{1});
obj.(varargin{1}) = val;
else
error('Invalid property "%s" for class "%s".', ...
varargin{1}, class(obj));
end
if nargout
varargout{1} = [];
end
end
end
end
end
function str = toString(in)
in{1} = ['{', in{1}, '}']; % first input is the default
str = sprintf('| %s ', in{:});
str = ['[', str(2:end), ']'];
end
I have Hidden, Constant properties that specify the possible property values, and the set method queries that information. So whenever I create a new property, I create a corresponding Prop#_values property.
EDIT: I've implemented a Setter for the properties. In this example, instead of creating a separate Setter method for each property, I made it as a part of the set method (see case 3 in the switch statement). Since the Prop#_values contain the valid string values, I use that to validate the input.
Test...
>> a = MyClass;
>> set(a)
Prop1: [ {SI} | cgs ]
Prop2: [ {C} | F ]
>> set(a, 'Prop2')
Prop2: [ {C} | F ]
>> set(a, 'Prop1', 'cgs')
>> get(a, 'Prop1')
ans =
cgs
>> set(a, 'Prop2', 'CF')
??? Error using ==> MyClass
Expected Prop2 to match one of these strings:
C, F
The input, 'CF', did not match any of the valid strings.
Error in ==> MyClass>MyClass.set at 46
val = validatestring(varargin{2}, ...
>>

7 Comments

@Jiro - nice touch using the hidden properties. I'd be interested to see how you would implement those Setter methods. It leaves me wondering, though - what's the advantage in subclassing from HGSETGET instead of HANDLE?
@Andrew: I modified my answers to include set validation. Instead of creating a separate setter method, I do it inside the "set" method. I use Prop#_value property to validate the input.
I see your question about the advantage of using hgsetget. I honestly don't know, and I'm asking around to see if someone has a good explanation. I'll post here once I hear something.
@Jiro: I have never used narg?chk or validatestring, so I'm learning some good stuff!
To me, it would make sense to have methods set.Prop? do the validation or return strings like '[ {SI} | cgs ]', 'string -or- function handle', or [], depending on the requirements for each property. Otherwise, it's very tricky to deal with all the different possibilities.
@Andrew: Yes, I would normally use set.Prop methods for each method. In this case, I assumed all the properties accept only strings from a cell array of strings. So I put it all in the set method.
"validatestring" is only for selecting from a selection of strings. You may also want to look at "validateattributes".
@Jiro: I have come up with a very nice approach to this problem. I have created an abstract subclass of hgsetget (call it hgsetgetplus). All the user has to do is create a method called setAttributes and populate it with commands like
S.Prop1.classes = {'char'};
S.Prop1.choices = {'SI', 'cgs'};
S.Prop1.default = {'SI'};
and you can get all the documented behavior of SET with all the possible constraints that VALIDATESTRING and VALIDATEATTRIBUTES can handle.
@Andrew: That's great! Would you care to share that on the File Exchange? It seems like there is no simple way of doing it with HGSETGET, so your approach might be the best currently.
@Jiro, I intend to do just that. It was a bit of work making SET capable of taking all the different combinations of arguments! I have submitted a new question, though, about making my class abstract.

Sign in to comment.

Here is a simple attempt to get the full functionality:
classdef unit < hgsetget
% This is just a test class for using SET in an HGSETGET subclass.
properties
System = '';
end
methods
function H = unit(str)
if nargin > 0
H.System = str;
end
end
function varargout = set(H,varargin)
switch nargin
case 1
varargout = {struct('System',{'SI','cgs'})};
case 2
varargout{1} = '[{SI} | cgs]';
case 3
H.System = varargin{2};
otherwise
error('unit:wrongNumberOfArguments', ...
'Wrong number of arguments to SET command.')
end% switch
end% set
end% methods
end% classdef
However, the behavior is not quite the same:
>> u = unit;
>> set(u)
ans =
1x2 struct array with fields:
System
>> set(u,'System')
ans =
[{SI} | cgs]
>> set(u,'System','cgs') >> get(u,'System')
ans =
cgs
Andrew Newell
Andrew Newell on 10 Mar 2011
I have submitted an extension of hgsetget to the FEX: handleGraphicsSetGet class.

Categories

Find more on Argument Definitions in Help Center and File Exchange

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!