An Example of using the Panelbox Minimize/Maximize Function from MATLAB GUI Layout Toolbox

Feb. 26, 2023

Introduction

MATLAB GUI Layout Toolbox1所提供的Panel类uix.BoxPanel具有“折叠/展开”(即minimize/maximize)的功能,并且提供了一个该属性的示例:minimizeexample。该示例创建了如下图所示的GUI界面:

image-20230225231031216

如果点击Panel左上角的三角形,则界面会变成:

image-20230225231104238

程序以此来实现Panel折叠与展开的功能。该示例的代码中包含着很多非常意思的细节,也解决了一些之前我没有理解的问题,因此写下这篇博客以记录一下。


Main Function: minimizexample()

该示例仍然采用嵌套函数的方式共享数据(共享主函数中定义的变量),即在主函数minimizeexample下定义多个子函数(在这个示例中,仅包含一个子函数,即回调函数nMinimize)。

在主函数中,首先定义了一些创建GUI组件的常数和GUI组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function minimizeexample()

width = 200;
pheightmin = 20;
pheightmax = 100;

% Create the window and main layout
fig = figure('Name','Collapsable GUI', ...
    'NumberTitle','off',...
    'Toolbar','none', ...
    'MenuBar','none');
box = uix.VBox('Parent',fig);

panel{1} = uix.BoxPanel('Title','Panel 1','Parent',box);
panel{2} = uix.BoxPanel('Title','Panel 2','Parent',box);
panel{3} = uix.BoxPanel('Title','Panel 3','Parent',box);
set(box,'Heights',pheightmax*ones(1,3));

% Add some contents
uicontrol('Parent',panel{1},'Style','PushButton','String','Button 1');
uicontrol('Parent',panel{2},'Style','PushButton','String','Button 2');
uicontrol('Parent',panel{3},'Style','PushButton','String','Button 3');

% Resize the window
pos = get(fig,'Position');
set(fig,'Position',[pos(1,1:2),width,sum(box.Heights)]);
...
end 

image-20230225232409783

其中,创建Panel时使用的是MATLAB GUI Layout Toolbox所提供的Panel类uix.BoxPanel。需要注意的是,这三个Panel是保存在Cell类型的Panel变量中的:

1
2
3
4
5
6
7
8
9
>> panel
panel =
  1×3 cell array
  Column 1
    {1×1 BoxPanel}
  Column 2
    {1×1 BoxPanel}
  Column 3
    {1×1 BoxPanel}

Panel变量中的每个元素是BoxPanel类的变量。

之后,为每一个Panel变量中的每个BoxPanel类元素创建回调函数:

1
2
3
4
5
6
...
% Hook up the minimize callback
set(panel{1},'MinimizeFcn',{@nMinimize, 1});
set(panel{2},'MinimizeFcn',{@nMinimize, 2});
set(panel{3},'MinimizeFcn',{@nMinimize, 3});
...

回调函数nMinimize的具体定义为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function minimizeexample()
...
    function nMinimize(eventSource, eventData, whichpanel) %#ok<INUSL>
        % A panel has been maximized/minimized
        s = get(box,'Heights');
        pos = get(fig,'Position');
        panel{whichpanel}.Minimized = ~panel{whichpanel}.Minimized;
        if panel{whichpanel}.Minimized
            s(whichpanel) = pheightmin;
        else
            s(whichpanel) = pheightmax;
        end
        set(box,'Heights',s);

        % Resize the figure, keeping the top stationary
        delta_height = pos(1,4) - sum(box.Heights);
        set(fig,'Position',pos(1,:)+[0 delta_height 0 -delta_height]);
    end
end

创建回调函数和绑定回调函数的过程中就有一些需要注意的地方,下面就对它们进行介绍。


Callbacks nMinimize()

Hook up callbacks

由于三个Panel是类似的,并且折叠/展开的功能也是类似,因此在程序中仅仅创建了一个回调函数nMinimize,将其通过输入不同的参数将其绑定到不同的uix.BoxPanel上。绑定回调函数时使用的语法是:

1
2
3
...
set(panel{1},'MinimizeFcn',{@nMinimize, 1});
...

'MinimizeFcn'(以及回调函数中需要用到的Minimized)都是uix.BoxPanel中所定义的属性:

image-20230225234636017

而对于向'MinimizeFcn'绑定回调函数(即传入参数{@nMinimize, 1})的方式,我不是很理解。因为如果在单独的一个脚本文件中仅仅写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
% test.m
a = {@nMinimize, 1};

function nMinimize(eventSource, eventData, whichpanel) %#ok<INUSL>
% A panel has been maximized/minimized
s = get(box,'Heights');
pos = get(fig,'Position');
panel{whichpanel}.Minimized = ~panel{whichpanel}.Minimized;
if panel{whichpanel}.Minimized
    s(whichpanel) = pheightmin;
else
    s(whichpanel) = pheightmax;
end
set(box,'Heights',s);

% Resize the figure, keeping the top stationary
delta_height = pos(1,4) - sum(box.Heights);
set(fig,'Position',pos(1,:)+[0 delta_height 0 -delta_height]);
end

最后得到的变量a是一个cell数组:

1
2
3
4
>> a
a =
  1×2 cell array
    {@nMinimize}    {[1]}

数组的第一个元素是一个匿名函数,第二个元素是一个double类型的值。

尝试了一个普通的函数,结果也是类似的:

1
2
3
4
5
6
7
clc,clear,close all

a = {@xsquare,5}

function x = xsquare(x)
x = x.^2;
end
1
2
3
a =
  1×2 cell array
    {@xsquare}    {[5]}

我在官方文档MATLAB关于Anonymous Functions的官方文档23中也没有找到类似的用法和有关这一特性的介绍。因此,我就暂且将其理解为一种为“多个类似的”组件创建类似回调函数的特殊用法,cell数组中的123当作形参whichpanel传入到回调函数nMinimize中。

Callbacks formal parameters: eventSource and eventData

观察回调函数nMinimize的形参,可以看到在上面提到的whichpanel之前还有两个形参eventSourceeventData

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function nMinimize(eventSource, eventData, whichpanel) %#ok<INUSL>
% A panel has been maximized/minimized
s = get(box,'Heights');
pos = get(fig,'Position');
panel{whichpanel}.Minimized = ~panel{whichpanel}.Minimized;
if panel{whichpanel}.Minimized
    s(whichpanel) = pheightmin;
else
    s(whichpanel) = pheightmax;
end
set(box,'Heights',s);

% Resize the figure, keeping the top stationary
delta_height = pos(1,4) - sum(box.Heights);
set(fig,'Position',pos(1,:)+[0 delta_height 0 -delta_height]);
end

eventSourceeventData分别是UIControl类和ActionData类的变量:

1
2
3
4
5
6
7
8
9
10
eventSource = 
  UIControl () with properties:
              Style: 'checkbox'
             String: '▴'
    BackgroundColor: [1×3 double]
           Callback: {2×1 cell}
              Value: 1
           Position: [1×4 double]
              Units: 'pixels'
  Show all properties
1
2
3
4
eventData = 
  ActionData with properties:
       Source: [1×1 UIControl]
    EventName: 'Action'

但可以看到,在整个回调函数的定义中,并没有使用这两个变量,但是作为回调函数的定义,这两个变量是必须的!!!

如果删去这两个变量,在调用回调函数时会提示Error:

1
2
3
4
Error using minimizeexample/nMinimize
Too many input arguments.
 
Error while evaluating UIControl Callback.

Code annotation %#ok<INUSL>

在函数定义中没有使用形参eventSource and eventData,但同时MATLAB Code Analyzer并没有进行提示,是因为在函数声明的后面添加了一条特殊的注释%#ok<INUSL>

image-20230226174152490

用以提示Code Analyzer: It means MATLAB editor will not issue warning at this line of code. It does not have any effect at the run time. (参考4),删除掉这条注释后,可以看到Code Analyzer就会像往常一样提示“输入参数可能未在函数定义中进行使用”:

image-20230226174326526

不同的代码开发者会采用不同的方式来处理这种“没有使用但必要”的情况,比如说博客5中示例在回调函数定义时采用的方式是使用~进行替代:

image-20230226174830073

image-20230226174935333

以及博客6中直接选择忽略Code Analyzer警告的处理方式:

image-20230226175152657

总的来讲,我认为本博客中这个示例的处理方式是更加规范的。

Whether Resize the Figure

回调函数定义的部分没有什么复杂的地方,需要提一点的是,原代码会根据Panel的折叠情况来调整figure的大小:

image-20230226180336598

但我个人认为固定住figure的大小是更合理的,只需要注释掉下面的代码即可:

1
2
3
4
5
    ...
    % Resize the figure, keeping the top stationary
    delta_height = pos(1,4) - sum(box.Heights);
    set(fig,'Position',pos(1,:)+[0 delta_height 0 -delta_height]);
    ...

image-20230226180518858


Conclusion

最后,需要提醒一点,GUI Layout Toolbox所提供的uix.VBox类和uix.VBoxFlex类也具有类似折叠/展开的功能:

Note that minimizing a panel to its title-bar only really makes sense inside a uix.VBox or uix.VBoxFlex.


Appendix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
function minimizeexample()
%MINIMIZEEXAMPLE: An example of using the panelbox minimize/maximize
%  Copyright 2009-2020 The MathWorks, Inc.

width = 200;
pheightmin = 20;
pheightmax = 100;

% Create the window and main layout
fig = figure('Name','Collapsable GUI', ...
    'NumberTitle','off',...
    'Toolbar','none', ...
    'MenuBar','none');
box = uix.VBox('Parent',fig);

panel{1} = uix.BoxPanel('Title','Panel 1','Parent',box);
panel{2} = uix.BoxPanel('Title','Panel 2','Parent',box);
panel{3} = uix.BoxPanel('Title','Panel 3','Parent',box);
set(box,'Heights',pheightmax*ones(1,3));

% Add some contents
uicontrol('Style','PushButton','String','Button 1','Parent',panel{1});
uicontrol('Style','PushButton','String','Button 2','Parent',panel{2});
uicontrol('Style','PushButton','String','Button 3','Parent',panel{3});

% Resize the window
pos = get(fig,'Position');
set(fig,'Position',[pos(1,1:2),width,sum(box.Heights)]);

% Hook up the minimize callback
set(panel{1},'MinimizeFcn',{@nMinimize, 1});
set(panel{2},'MinimizeFcn',{@nMinimize, 2});
set(panel{3},'MinimizeFcn',{@nMinimize, 3});

    function nMinimize(eventSource, eventData, whichpanel) %#ok<INUSL>
        % A panel has been maximized/minimized
        s = get(box,'Heights');
        pos = get(fig,'Position');
        panel{whichpanel}.Minimized = ~panel{whichpanel}.Minimized;
        if panel{whichpanel}.Minimized
            s(whichpanel) = pheightmin;
        else
            s(whichpanel) = pheightmax;
        end
        set(box,'Heights',s);
        
         % Resize the figure, keeping the top stationary
         delta_height = pos(1,4) - sum(box.Heights);
         set(fig,'Position',pos(1,:)+[0 delta_height 0 -delta_height]);
    end % nMinimize

end % EOF


References