Matlab enables two types of GUI container types, via the Units property: fixed-size ('pixels'
, 'chars'
, etc.) and flexible ('normalized'
). In many cases, we need something in between: a panel that expands dynamically when its container grows (i.e., flexible/normalized
), and displays scroll-bars when the container shrinks (i.e., fixed size, with a scrollable viewport). This functionality is relatively easy to achieve using a bit of undocumented magic powder. Today’s post will show how to do this with legacy (Java-based) figures, and next week’s post will do the same for web-based (JavaScript) uifigures.
Scrollable Matlab GUI panel
Technical description
The basic idea is that in HG2 (Matlab release R2014b onward), uipanels are implemented using standard Java JPanel
components. This enables all sorts of interesting customizations. For the purposes of today’s discussion, the important thing to note is that the underlying JPanel
object can be re-parented to reside inside a Java JScrollPanel.
So, the idea is to get the Matlab panel’s underlying JPanel
object reference, then embed it within a new JScrollPanel
object that is placed at the exact same GUI coordinates as the original panel. The essential Matlab code snippet is this:
% Create the Matlab uipanel in the GUI hPanel = uipanel(...); drawnow % Get the panel's underlying JPanel object reference jPanel = hPanel.JavaFrame.getGUIDEView.getParent; % Embed the JPanel within a new JScrollPanel object jScrollPanel = javaObjectEDT(javax.swing.JScrollPane(jPanel)); % Remove the JScrollPane border-line jScrollPanel.setBorder([]); % Place the JScrollPanel in same GUI location as the original panel pixelpos = getpixelposition(hPanel); hParent = hPanel.Parent; [hjScrollPanel, hScrollPanel] = javacomponent(jScrollPanel, pixelpos, hParent); hScrollPanel.Units = 'norm'; % Ensure that the scroll-panel and contained panel have linked visibility hLink = linkprop([hPanel,hScrollPanel],'Visible'); setappdata(hPanel,'ScrollPanelVisibilityLink',hLink); |
Note that this code will only work with panels created in legacy figures, not web-based uifigures (as I mentioned above, a similar solution for uifigures will be presented here next week).
Also note that the new scroll-panel is created with javaObjectEDT, in order to avoid EDT synchronization problems
We also want to link the visibility of the scroll-panel and its contained Matlab panel (hPanel
), so that when the panel is set to be non-visible (hPanel.Visible='off'
), the entire scroll-panel (scrollbars included) will become invisible, and vice-versa. We can do this by linking the Visible property of the Matlab panel and the scroll-panel container (hScrollPanel
) using the linkprop function at the bottom of the script above. Note that we must persist the resulting hLink
otherwise it becomes defunct – this is done by using setappdata to store the link in the panel (this way, when the panel is deleted, so does the link).
Resizing the container
The scroll-panel is created with a specific pixelpos
location and size, and then its container is made to have normalized
units. This ensures that when the container (hParent
) grows, the scroll-panel grows as well, and no scrollbars appear (since they are not needed). But when the container shrinks in the X and/or Y direction, corresponding scrollbars appear as-needed. It sounds complicated, but it’s actually very intuitive, as the animated image above shows.
When the container resizes, the displayed viewport image may “jump” sideways. To fix this we can attach a simple repaint callback function to the scroll-panel’s SizeChangedFcn property:
% Attach a repaint callback function hScrollPanel.SizeChangedFcn = @repaintScrollPane; % Define the callback function: function repaintScrollPane(hScrollPanel, varargin) drawnow jScrollPanel = hScrollPanel.JavaPeer; offsetX = 0; %or: jScrollPanel.getHorizontalScrollBar.getValue; offsetY = 0; %or: jScrollPanel.getVerticalScrollBar.getValue; jOffsetPoint = java.awt.Point(offsetX, offsetY); jViewport = jScrollPanel.getViewport; jViewport.setViewPosition(jOffsetPoint); jScrollPanel.repaint; end |
Viewport position/offset
It would be convenient to have an easy-to-use ViewOffset property in the hScrollPanel
object, in order to be able to programmatically query and set the current viewport position (i.e., scrollbars offset). We can add this property via the addprop function:
% Add a new Viewoffset property to hSCrollPanel object hProp = addprop(hScrollPanel, 'ViewOffset'); hProp.GetMethod = @getViewOffset; %viewOffset = getViewOffset(hScrollPanel) hProp.SetMethod = @setViewOffset; %setViewOffset(hScrollPanel, viewOffset) % Getter method for the dynamic ViewOffset property function viewOffset = getViewOffset(hScrollPanel, varargin) jScrollPanel = hScrollPanel.JavaPeer; jPoint = jScrollPanel.getViewport.getViewPosition; viewOffset = [jPoint.getX, jPoint.getY]; end % Setter method for the dynamic ViewOffset property function setViewOffset(hScrollPanel, viewOffset) jPoint = java.awt.Point(viewOffset(1), viewOffset(2)); jScrollPanel = hScrollPanel.JavaPeer; jScrollPanel.getViewport.setViewPosition(jPoint); jScrollPanel.repaint; end |
This enables us to both query and update the scroll-panel’s view position – [0,0]
means top-left corner (i.e., no scroll); [12,34]
mean scrolling 12 to the right and 34 down:
>> offset = hScrollPanel.ViewOffset % or: get(hScrollPanel,'ViewOffset') offset = 0 0 >> offset = hScrollPanel.ViewOffset % or: get(hScrollPanel,'ViewOffset') offset = 12 34 % Scroll 30 pixels right, 50 pixels down >> hScrollPanel.ViewOffset = [30,50]; % or: set(hScrollPanel,'ViewOffset',[30,50]) |
attachScrollPanelTo utility
I have prepared a utility called attachScrollPanelTo (downloadable from the Matlab File Exchange), which encapsulates all of the above, plus a few other features: inputs validation, Viewport property in the output scroll-pane object, automatic encasing in a new panel for input object that are not already a panel, etc. Feel free to download the utility, use it in your program, and modify the source-code to fit your needs. Here are some usage examples:
attachScrollPanelTo(); % display the demo attachScrollPanelTo(hPanel) % place the specified hPanel in a scroll-panel hScroll = attachScrollPanelTo(hPanel); hScroll.ViewOffset = [30,50]; % set viewport offset (30px right, 50px down) set(hScroll, 'ViewOffset',[30,50]); % equivalent alternative |
If you’d like me to add flare to your Matlab GUI, don’t hesitate to contact me on my Consulting page.
See also uix.ScrollingPanel in GUI Layout Toolbox. This works for figures (not uifigures) from R2014b onwards.
Hi,
Thanks for a great function! I was trying to put the main panel of my figure into a scrollable panel with the goal of zooming into the panel using shift+scroll and scrolling up and down using normal scroll. I realised quickly however, that the zoom function I added to the WindowScrollWheelFcn property of my figure does not execute on scrolling because the scrolling is captured by the scroll panel.
I’m not familiar enough with java to find out how to capture the scroll events and “re-route” them to my figure if the shift button is down, but I thought of an easier solution, i.e to detach the main panel from the scroll panel whenever I press the shift button and reattach it when I release the shift button. However, I got stuck here too, as I have no idea what to reparent the “jParent” to in what would be the reverse of this:
I guess what I am trying to write is a detachScrollPanel function (similar to the decorateFig/undecorateFig functions). Would appreciate any help!
jPanel1.setParent(jPanel2)
will reparent jPanel1 (and similarly any Java component/container) inside jPanel2 (any Java container).While this might work, it seems to me to be an overly-complex solution. A much simpler one is to set your jScrollPanel’s
MouseWheelMovedCallback
callback as explained here: https://undocumentedmatlab.com/articles/uicontrol-callbacks