Customizing web-GUI uipanel

I would like to introduce guest blogger Khris Griffis. Today, Khris will continue the series of posts on web-based uifigure customization with an article showing how to create scrollable/customizable panels in web-based uifigures. This post follows last-week's article, about placing controls/axes within a scroll-panel in non-web (Java-based) figures. Users interested in advanced aspects and insights on the development roadmap of web-based Matlab GUI should also read Loren Shure's blog post from last week.

Motivation

As a retinal physiologist, I spend a lot of time in Matlab creating GUIs to visualize and analyze electrophysiological data. The data often requires a lot of processing and quality control checks before it can be used for interpretation and publication. Consequently, I end up with many control elements taking up precious space on my GUI.
In Java-based (legacy/GUIDE) figures, this wasn't a huge problem because, depending on what GUI components I needed, I could use a pure Matlab approach (a child panel within a parent panel, with a couple of control sliders moving the child panel around), or a number of Java approaches (which are always more fun; Yair described such an approach last week).
Unfortunately, the web-based (App-Designer) figure framework doesn't support Java, and the pure/documented Matlab approach just doesn't look good or function very well:

AppDesigner uislider is not a good scrollbar, no matter what we do to it!
AppDesigner uislider is not a good scrollbar, no matter what we do to it!

Fortunately, the new web framework allows us to use HTML, CSS and JavaScript (JS) to modify elements in the uifigure , i.e. its web-page DOM. It turns out that the uipanel object is essentially just a <div> with a Matlab-designed CSS style. We can customize it with little effort.
The main goal here is to create a scrollable customizable uipanel containing many uicontrol elements, which could look something like this:

Running app demo

A simple command-window example

Since we are building on the series of uifigure customizations, I expect you have already read the preceding related Undocumented Matlab posts:

  1. Customizing uifigures part 1
  2. Customizing uifigures part 2
  3. Customizing uifigures part 3

Also, I highly recommend cloning (or at least downloading) the mlapptools toolbox repo on Github (thanks Iliya Romm et al.). We will use it to simplify life today.

Using the mlapptools toolbox, we need just a few lines of code to set up a scrollable panel. The important thing is knowing how big the panel needs to be to hold all of our control objects. Once we know this, we simply set the panel's Position property accordingly. Then we can use simple CSS to display scrollbars and define the viewing dimensions.
For example, if we need 260px in width by 720px in height to hold our control widgets, but only have 200px height available, we can solve this problem in 3 steps:

  1. Set the uipanel 's Dimension property to be 260px wide by 720px tall.
  2. Set the viewing height using mlapptools.setStyle(scrollPane,'height','200px') for the panel's CSS height style attribute.
  3. Display the vertical scrollbar by calling mlapptools.setStyle(scrollPane,'overflow-y','scroll') for the panel's CSS overflow-y='scroll' style attribute.
                  % Create the figure                  fig = uifigure(                  'Name',                  'Scroll Panel Test'                  );                  % Place a uipanel on the figure, color it for fun                  scrollPane = uipanel(fig,                  'BackgroundColor',                  [                  0.5,0.4,1                  ]                  );                  % Define the space requirements for the controls                  totalWidth  =                  260;                  %px                  totalHeight =                  720;                  %px                  viewHeight  =                  200;                  %px                  % STEP 1: set the uipanel's Position property to the required dimensions                  scrollPane.Position                  (                  3                  )                  = totalWidth;                  % WIDTH                  scrollPane.Position                  (                  4                  )                  = totalHeight;                  % HEIGHT                  % STEP 2: set the viewing height                  mlapptools.setStyle                  (scrollPane,                  'height',                  sprintf                  (                  '%dpx', viewHeight)                  );                  % STEP 3: display the vertical scrollbar                  mlapptools.setStyle                  (scrollPane,                  'overflow-y',                  'scroll'                  );                  % Add control elements into the uipanel                  ...                

% Create the figure fig = uifigure('Name', 'Scroll Panel Test'); % Place a uipanel on the figure, color it for fun scrollPane = uipanel(fig, 'BackgroundColor', [0.5,0.4,1]); % Define the space requirements for the controls totalWidth = 260; %px totalHeight = 720; %px viewHeight = 200; %px % STEP 1: set the uipanel's Position property to the required dimensions scrollPane.Position(3) = totalWidth; % WIDTH scrollPane.Position(4) = totalHeight; % HEIGHT % STEP 2: set the viewing height mlapptools.setStyle(scrollPane, 'height', sprintf('%dpx', viewHeight)); % STEP 3: display the vertical scrollbar mlapptools.setStyle(scrollPane, 'overflow-y', 'scroll'); % Add control elements into the uipanel ...

Example scrollable uipanel in a Matlab uifigure
Example scrollable uipanel in a Matlab uifigure

Because this is a web-based GUI, notice that you can simply hover your mouse over the panel and scroll with your scroll wheel. Awesome, right?
Note that the CSS height/width style attributes don't affect the actual size of our panel, just how much of the panel we can see at once ("viewport").
The CSS overflow style attribute has a number of options. For example, setStyle(scrollPane,'overflow','auto') causes scrollbars to automatically hide when the viewing area is larger than panel's dimensions. Review the CSS overflow attribute to learn about other related settings that affect the panel's behavior.

Styling the scrollbars

The mlapptools toolbox utilizes dojo.js to query the DOM and set styles, which is essentially setting inline styles on the DOM element, i.e. <div ... style="color: red;background:#FEFEFE;..."> ... </div>. This has the added benefit of overriding any CSS classes Matlab is imposing on the DOM (see CSS precedence). We can inject our own classes into the page's <head> tag and then use dojo.setClass() to apply our classes to specific GUI elements. For example, we can style our bland scrollbars by using CSS to define a custom scrollpane style class:

                  /* CSS To stylize the scrollbar */                  .scrollpane                  ::-webkit-scrollbar                  {                  width                  :                  20px                  ;                  }                  /* Track */                  .scrollpane                  ::-webkit-scrollbar-track                  {                  background-color                  :                  white                  ;                  box-shadow                  :                  inset                  0                  0                  5px                  white                  ;                  border-radius                  :                  10px                  ;                  }                  /* Handle */                  .scrollpane                  ::-webkit-scrollbar-thumb                  {                  background                  :                  red                  ;                  border-radius                  :                  10px                  ;                  }                  /* Handle on hover */                  .scrollpane                  ::-webkit-scrollbar-thumb:                  hover                  {                  background                  :                  #b30000                  ;                  }                

/* CSS To stylize the scrollbar */ .scrollpane::-webkit-scrollbar { width: 20px; } /* Track */ .scrollpane::-webkit-scrollbar-track { background-color: white; box-shadow: inset 0 0 5px white; border-radius: 10px; } /* Handle */ .scrollpane::-webkit-scrollbar-thumb { background: red; border-radius: 10px; } /* Handle on hover */ .scrollpane::-webkit-scrollbar-thumb:hover { background: #b30000; }

To get the CSS into the document header, we need to first convert ("stringify") it in Matlab:

                  % CSS styles 'stringified' for MATLAB                  %  note that the whole style tag is wrapped in single quotes, that is required!                  %  i.e. '<style>...</style>' which prints to the command window as:                  %   ''<style>...</style>''                  cssText =                  [                  ...                  ''                  '<style>\n',                  ...                  '  .scrollpane::-webkit-scrollbar {\n',                  ...                  '    width: 20px;\n',                  ...                  '  }\n',                  ...                  '  /* Track */\n',                  ...                  '  .scrollpane::-webkit-scrollbar-track {\n',                  ...                  '    background-color: white;\n',                  ...                  '    box-shadow: inset 0 0 5px white;\n',                  ...                  '    border-radius: 10px;\n',                  ...                  '  }\n',                  ...                  '  /* Handle */\n',                  ...                  '  .scrollpane::-webkit-scrollbar-thumb {\n',                  ...                  '    background: red; \n',                  ...                  '    border-radius: 10px;\n',                  ...                  '  }\n',                  ...                  '  /* Handle on hover */\n',                  ...                  '  .scrollpane::-webkit-scrollbar-thumb:hover {\n',                  ...                  '    background: #b30000; \n',                  ...                  '  }\n',                  ...                  '</style>'                  ''                  ...                  ];

% CSS styles 'stringified' for MATLAB % note that the whole style tag is wrapped in single quotes, that is required! % i.e. '<style>...</style>' which prints to the command window as: % ''<style>...</style>'' cssText = [... '''<style>\n', ... ' .scrollpane::-webkit-scrollbar {\n', ... ' width: 20px;\n', ... ' }\n', ... ' /* Track */\n', ... ' .scrollpane::-webkit-scrollbar-track {\n', ... ' background-color: white;\n', ... ' box-shadow: inset 0 0 5px white;\n', ... ' border-radius: 10px;\n', ... ' }\n', ... ' /* Handle */\n', ... ' .scrollpane::-webkit-scrollbar-thumb {\n', ... ' background: red; \n', ... ' border-radius: 10px;\n', ... ' }\n', ... ' /* Handle on hover */\n', ... ' .scrollpane::-webkit-scrollbar-thumb:hover {\n', ... ' background: #b30000; \n', ... ' }\n', ... '</style>''' ... ];

As explained in Customizing uifigures part 1, we can execute JavaScript (JS) commands through the webWindow object. To simplify it, we use the method from Customizing uifigures part 2 to obtain the webWindow object for our GUI window and and use the executeJS() method to add our HTML into the document's <head> tag. It is worth checking out the properties and methods of the JS document object.

                  % get the webWindow object                  webWindow = mlapptools.getWebWindow                  (fig);                  % inject the CSS                  webWindow.executeJS                  (                  [                  'document.head.innerHTML += ',cssText]                  );

% get the webWindow object webWindow = mlapptools.getWebWindow(fig); % inject the CSS webWindow.executeJS(['document.head.innerHTML += ',cssText]);

Now the tricky part is that we have to assign our new CSS scrollpane class to our uipanel . We need 2 things: the webWindow object and the data-tag (our panel's unique ID) attribute.

                  % get the uipanel data-tag attr.                  [webWindow, panelID]                  = mlapptools.getWebElements                  (scrollPane);                  %make a dojo.js statement (use addClass method)                  setClassString =                  sprintf                  (                  ...                  'dojo.addClass(dojo.query("[%s = '                  '%s'                  ']")[0], "%s")',...                  panelID.ID_attr, panelID.ID_val,                  'scrollpane'                  );                  % add class to DOM element                  webWindow.executeJS                  (setClassString);

% get the uipanel data-tag attr. [webWindow, panelID] = mlapptools.getWebElements(scrollPane); %make a dojo.js statement (use addClass method) setClassString = sprintf(... 'dojo.addClass(dojo.query("[%s = ''%s'']")[0], "%s")',... panelID.ID_attr, panelID.ID_val, 'scrollpane'); % add class to DOM element webWindow.executeJS(setClassString);

The results of applying our scrollpane CSS style on our scroll-pane's scrollbars
The results of applying our scrollpane CSS style on our scroll-pane's scrollbars

Note: Unfortunately, because of CSS precedence rules, we may have to use the dreaded !important CSS qualifier to get the desired effect. So if the result doesn't match your expectations, try adding !important to the CSS class attributes.

Styling the uipanel

Each uipanel appears to be composed of 4 <div> HTML elements: a wrapper, internal container for the panel title, a separator, and the panel's body (contents). We first use mlapptools.getWebElements() to get the data-tag ID for the wrapper node. We can then apply styles to the wrapper, or any child node, with CSS and JS.
For example, we can use CSS3 patterns (such as one in this CSS3 gallery) to liven up our panel. We can also use CSS to define a block element that will replace the title container with some static text. The CSS below would be appended to the cssText string we made for styling the scrollbars above:

                  /* DECORATE THE BACKGROUND */                  /* Stars modified from: http://lea.verou.me/css3patterns/#stars */                  .scrollpane                  {                  overflow                  :                  auto                  ;                  background                  :                  linear-gradient(                  324deg                  ,                  #232927                  4%                  ,                  transparent                  4%                  )                  -70px                  43px                  ,                  linear-gradient(                  36deg                  ,                  #232927                  4%                  ,                  transparent                  4%                  )                  30px                  43px                  ,                  linear-gradient(                  72deg                  ,                  #e3d7bf                  8.5%                  ,                  transparent                  8.5%                  )                  30px                  43px                  ,                  linear-gradient(                  288deg                  ,                  #e3d7bf                  8.5%                  ,                  transparent                  8.5%                  )                  -70px                  43px                  ,                  linear-gradient(                  216deg                  ,                  #e3d7bf                  7.5%                  ,                  transparent                  7.5%                  )                  -70px                  23px                  ,                  linear-gradient(                  144deg                  ,                  #e3d7bf                  7.5%                  ,                  transparent                  7.5%                  )                  30px                  23px                  ,                  linear-gradient(                  324deg                  ,                  #232927                  4%                  ,                  transparent                  4%                  )                  -20px                  93px                  ,                  linear-gradient(                  36deg                  ,                  #232927                  4%                  ,                  transparent                  4%                  )                  80px                  93px                  ,                  linear-gradient(                  72deg                  ,                  #e3d7bf                  8.5%                  ,                  transparent                  8.5%                  )                  80px                  93px                  ,                  linear-gradient(                  288deg                  ,                  #e3d7bf                  8.5%                  ,                  transparent                  8.5%                  )                  -20px                  93px                  ,                  linear-gradient(                  216deg                  ,                  #e3d7bf                  7.5%                  ,                  transparent                  7.5%                  )                  -20px                  73px                  ,                  linear-gradient(                  144deg                  ,                  #e3d7bf                  7.5%                  ,                  transparent                  7.5%                  )                  80px                  73px                  !important;                  background-color                  :                  #232977                  !important;                  background-size                  :                  100px                  100px                  !important;                  }                  /* STYLES TO CENTER A TEXT BLOCK ON A WHITE SEMI-TRANSPARENT BACKGROUND BLOCK */                  /* White block div */                  .blockdiv                  {                  color                  :                  black                  ;                  font                  :                  25px                  Arial,                  sans-serif                  !important;                  background                  :                  rgba                  (                  255                  ,                  255                  ,                  255                  ,                  0.75                  )                  ;                  width                  :                  100%                  ;                  height                  :                  30px                  ;                  }                  /* Text container centered in .blockdiv */                  .textdiv                  {                  position                  :                  relative                  ;                  float                  :                  left                  ;                  top                  :                  50%                  ;                  left                  :                  50%                  ;                  transform                  :                  translate                  (                  -50%                  ,                  -50%                  )                  ;                  }                

/* DECORATE THE BACKGROUND */ /* Stars modified from: http://lea.verou.me/css3patterns/#stars */ .scrollpane { overflow: auto; background: linear-gradient(324deg, #232927 4%, transparent 4%) -70px 43px, linear-gradient( 36deg, #232927 4%, transparent 4%) 30px 43px, linear-gradient( 72deg, #e3d7bf 8.5%, transparent 8.5%) 30px 43px, linear-gradient(288deg, #e3d7bf 8.5%, transparent 8.5%) -70px 43px, linear-gradient(216deg, #e3d7bf 7.5%, transparent 7.5%) -70px 23px, linear-gradient(144deg, #e3d7bf 7.5%, transparent 7.5%) 30px 23px, linear-gradient(324deg, #232927 4%, transparent 4%) -20px 93px, linear-gradient( 36deg, #232927 4%, transparent 4%) 80px 93px, linear-gradient( 72deg, #e3d7bf 8.5%, transparent 8.5%) 80px 93px, linear-gradient(288deg, #e3d7bf 8.5%, transparent 8.5%) -20px 93px, linear-gradient(216deg, #e3d7bf 7.5%, transparent 7.5%) -20px 73px, linear-gradient(144deg, #e3d7bf 7.5%, transparent 7.5%) 80px 73px !important; background-color: #232977 !important; background-size: 100px 100px !important; } /* STYLES TO CENTER A TEXT BLOCK ON A WHITE SEMI-TRANSPARENT BACKGROUND BLOCK */ /* White block div */ .blockdiv { color: black; font: 25px Arial, sans-serif !important; background: rgba(255,255,255,0.75); width: 100%; height: 30px; } /* Text container centered in .blockdiv */ .textdiv { position: relative; float: left; top: 50%; left: 50%; transform: translate(-50%, -50%); }

To insert a static text element and its container, we can create a small JS routine that creates parent and child nodes that replace the panel's title container:

                  % Make a nodeID string to make the JS call more generic                  nodeID =                  sprintf                  (                  ''                  '[%s="%s"]'                  '', panelID.ID_attr, panelID.ID_val                  );                  % JS that creates a div within a div, each with their own classes                  % The inner div gets the text and is centered within the outer div                  % These elements are added before the node MATLAB will use for any controls                  % added to scrollPane                  js =                  sprintf                  (                  ...                  [                  ...                  'var d = document.createElement("div");',                  ...                  'var t = document.createElement("div");',                  ...                  'd.classList.add("blockdiv");',...                  't.classList.add("textdiv");',                  ...                  't.innerHTML= "Some Static Text";',                  ...                  'd.appendChild(t);',                  ...                  'document.querySelectorAll(%s)[0]',...                  '.replaceChild(d,document.querySelectorAll(%s)[0].firstChild);'                  ...                  ],                  ...                  nodeID, nodeID                  ...                  );                  % execute the JS                  webWindow.executeJS                  (js);

% Make a nodeID string to make the JS call more generic nodeID = sprintf('''[%s="%s"]''', panelID.ID_attr, panelID.ID_val); % JS that creates a div within a div, each with their own classes % The inner div gets the text and is centered within the outer div % These elements are added before the node MATLAB will use for any controls % added to scrollPane js = sprintf( ... [ ... 'var d = document.createElement("div");', ... 'var t = document.createElement("div");', ... 'd.classList.add("blockdiv");',... 't.classList.add("textdiv");', ... 't.innerHTML= "Some Static Text";', ... 'd.appendChild(t);', ... 'document.querySelectorAll(%s)[0]',... '.replaceChild(d,document.querySelectorAll(%s)[0].firstChild);' ... ], ... nodeID, nodeID ... ); % execute the JS webWindow.executeJS(js);

Panel background and static elements
Panel background and static elements

It seems to me that this approach might help to make lighter-weight apps, instead of having to make all those app.Label objects in Matlab's App-Designer.

Quick recap

So let's restate the process:

  1. Create a uipanel with the Position property set accordingly large enough for your control elements.
  2. Use mlapptools.setStyle() to set the overflow style attribute as desired.
  3. Use mlapptools.setStyle() to set the width and/or height style attributes to the viewing size (this is how big the viewing area of the panel needs to be in order to fit nicely in your app).
  4. Add your control elements with the scrolling uipanel as the parent.
  5. If you want some special styles, create a stylesheet and inject it into the <head> and be sure to add the class to your panel's HTML classList.

The order of items 2-4 are not really important. You just need to ensure that the panel is large enough (via its Position property) to include all your elements/controls.
I really hope that one day soon MathWorks will add CSS and JS hooks to uifigure GUI components (perhaps as settable CSS/JS properties that accept strings?), so that Matlab users could attach their own CSS and JS customizations directly within AppDesigner, without having to go through such undocumented hoops as I've shown here. In Loren Shure's latest blog post, Matlab product manager Dave Garisson indicated that this is indeed planned for a near-future Matlab release (at least for JS, but hopefully also for CSS):

"we are also investigating ways to provide a documented solution for integrating 3rd party JavaScript components in MATLAB apps."

A complete working example

I created a complete working example in Matlab's App Designer while figuring this whole thing out. The code (CWE.m) can be downloaded here, and then run directly from Matlab's command window. Alternatively, the corresponding App Designer file (CWE.mlapp) can be downloaded here. You are welcome to use/adapt the code in your own project. Just to be clear, I love wild colors and crazy themes, but I don't recommend going this overboard for a real project.

Running app demo
Running app demo

I can't thank Yair enough for suggesting that I turn this tip into a guest post for you readers. And I want to give a huge thank you to you, the reader, for persevering all the way to the end of this post…
Cheers!
-Khris
Addendum September 17, 2018 : Scrolling panels in uifigures are now a fully-supported documented functionality via the new scroll function and Scrollable property, starting with Matlab release R2018b. You can still use the mechanism described above, which also works for older Matlab releases.

Print Print
Leave a Reply
HTML tags such as <b> or <i> are accepted.
Wrap code fragments inside <pre lang="matlab"> tags, like this:
<pre lang="matlab">
a = magic(3);
disp(sum(a))
</pre>
I reserve the right to edit/delete comments (read the site policies).
Not all comments will be answered. You can always email me (altmany at gmail) for private consulting.