A client recently asked me to extend one of Matlab’s built-in graphic containers (uiflowcontainer in this specific case) with automatic scrollbars that would enable the container to act as a scroll-panel. The basic idea would be to dynamically monitor the container’s contents and when it is determined that they overflow the container’s boundaries, then attach horizontal/vertical scrollbars to enable scrolling the contents into view:
This may sound simple, but there are actually quite a few undocumented hacks that make this possible, including listening to
ObjectChildAdded/ObjectChildRemoved events, location/size/visibility events, layout changes etc. Maybe I’ll blog about it in some future article.
Today’s post is focused on a specific aspect of this project, attaching dynamic properties to the builtin uiflowcontainer, that would enable users to modify the container’s properties directly, as well as control aspects of the scrolling using the new properties: handles to the parent container, as well as the horizontal and vertical scrollbars, and even a new refresh() method.
The “textbook” approach to this would naturally be to create a new class that extends (inherits) uiflowcontainer and includes these new properties and methods. Unfortunately, for some reason that escapes my understanding, MathWorks saw fit to make all of its end-use graphic object classes
Sealed, such that they cannot be extended by users. I did ask for this to be changed long ago, but the powers that be apparently decided that it’s better this way.
So the fallback would be to create our own dedicated class having all the new properties as well as those of the original container, and ensure that all the property values are synchronized in both directions. This is probably achievable, if you have a spare few days and a masochistic state of mind. Being the lazy bum and authority-rebel that I am, I decided to take an alternate approach that would simply add my new properties to the built-in container handle. The secret lies in the undocumented function schema.prop (for HG1, R2014a and older) and the fully-documented addprop function (for HG2, R2014b and newer).
In the examples below I use a panel, but this mechanism works equally well on any Matlab HG object: axes, lines, uicontrols, figures, etc.
HG2 – addprop function
The addprop function is actually a public method of the
dynamicprops class. Both the
dynamicprops class as well as its addprop function are fully documented. What is NOT documented, as far as I could tell, is that all of Matlab’s builtin handle graphics objects indirectly inherit
matlab.graphics.Graphics, which is a high-level superclass for all HG objects. The bottom line is that we can dynamically add run-time properties to any HG object, without affecting any other object. In other words, the new properties will only be added to the handles that we specifically request, and not to any others. This suits me just fine:
hProp = addprop(hPanel, 'hHorizontalScrollBar'); hPanel.hHorizontalScrollBar = hMyScrollbar; hProp.SetAccess = 'private'; % make this property read-only
The new property
hHorizontalScrollBar is now added to the
hPanel handle, and can be accessed just like any other read-only property. For example:
>> get(hPanel, 'hHorizontalScrollBar') ans = JavaWrapper >> hPanel.hHorizontalScrollBar ans = JavaWrapper >> hPanel.hHorizontalScrollBar = 123 You cannot set the read-only property 'hHorizontalScrollBar' of UIFlowContainer.
Adding new methods is more tricky, since we do not have a corresponding addmethod function. The trick I used was to create a new property having the requested new method’s name, and set its read-only value to a handle of the requested function. For example:
hProp = addprop(hPanel, 'refresh'); hPanel.refresh = @myRefreshFunc; hProp.SetAccess = 'private'; % make this property read-only
We can then invoke the new refresh “method” using the familiar dot-notation:
Note: if you ever need to modify the initial value in your code, you should revert the property’s SetAccess meta-property to
'public' before Matlab will enable you to modify the value:
try % This will raise an exception if the property already exists hProp = addprop(hPanel, propName); catch % Property already exists - find it and set its access to public hProp = findprop(hPanel, propName); hProp.SetAccess = 'public'; end hPanel.(propName) = newValue;
HG1 – schema.prop function
In HG1 (R2014a and earlier), we can use the undocumented schema.prop function to add a new property to any HG handle (which is a numeric value in HG1). Donn Shull wrote about schema.prop back in 2011, as part of his series of articles on UDD (Unified Data Dictionary, MCOS’s precursor). In fact, schema.prop is so useful that it has its own blog tag here and appears in no less than 15 separate articles (excluding today). With HG2’s debut 2 years ago, MathWorks tried very hard to rid the Matlab code corpus of all the legacy schema-based, replacing most major functionalities with MCOS-based HG2 code. But so far it has proven impossible to get rid of schema completely, and so schema code is still used extensively in Matlab to this day (R2015b). Search your Matlab path for “schema.prop” and see for yourself.
Anyway, the basic syntax is this:
hProp = schema.prop(hPanel, propName, 'mxArray');
'mxArray' specifies that the new property can accept any data type. We can limit the property to only accept certain types of data by specifying a less-generic data type, among those recognized by UDD (details).
Note that the meta-properties of the returned
hProp are somewhat different from those of HG2’s
hProp. Taking this into account, here is a unified function that adds/updates a new property (with optional initial value) to any HG1/HG2 object:
function addProp(hObject, propName, initialValue, isReadOnly) try hProp = addprop(hObject, propName); % HG2 catch try hProp = schema.prop(hObject, propName, 'mxArray'); % HG1 catch hProp = findprop(hObject, propName); end end if nargin > 2 try hProp.SetAccess = 'public'; % HG2 catch hProp.AccessFlags.PublicSet = 'on'; % HG1 end hObject.(propName) = initialValue; end if nargin > 3 && isReadOnly try % Set the property as read-only hProp.SetAccess = 'private'; % HG2 catch hProp.AccessFlags.PublicSet = 'off'; % HG1 end end end