JUCE复合控件之StretchableLayoutManager

StretchableLayoutManager的功能:鼠标拖拉组件与组件之间的边框(调整栏),可实时改变一个或多个组件(操作区)的大小。要实现此功能,需用到两个类:StretchableLayoutManager和StretchableLayoutResizerBar。简而言之,要实现布局分栏与拖拽调整,需:布局器与调整栏。

实现流程

  • StretchableLayoutManager布局器是一个独立的类,相当于一个容器,容器的元素为2到3个必须的子组件,其中第一个可为要调整大小的内容组件的子组件,第二个可为调整栏对象,第三个可选,如有,可设置一个空的子组件(尽管为空,但该子组件有其原点坐标,可利用该坐标联动调整内容组件中的其他子组件的定位)。布局器保存每个元素的一组数据,比如子组件可调整的宽度或高度的上限值、下限值、默认值,并根据设置自动调整这些元素的定位。
  • StretchableLayoutResizerBar调整栏继承自Component类,按普通的控件办理即可,其构造1参为布局器对象,构造2参为自身在布局器中的索引位置,构造3参确定是垂直的,还是水平的。
  • 布局器和调整栏的创建与初始化设置在内容组件类的构造函数中进行,具体的布局和联动功能在内容组件类的resized()函数中进行。
  • 尽管一对布局器和调整栏可完成1到多个子组件的联动调整。但如果内容组件的分栏布局过于复杂,最好还是声明并使用多对布局器和调整栏。
  • 如需定制调整器的外观和颜色,则派生StretchableLayoutResizerBar的子类,重写其paint()函数。

示例代码(步骤有三):

1、声明对象。内容组件类private两个对象:布局管理器对象和布局调整栏对象。本例前者为普通栈对象,后者为使用作用域指针的堆对象:

// 伸缩式布局管理器普通对象
StretchableLayoutManager verticalLayout;

// 伸缩式布局调整栏指针对象,作用域指针
ScopedPointer<StretchableLayoutResizerBar> verticalDividerBar;

2、关联组件。内容组件类构造函数中添加如下语句:

/* 布局管理器对象verticalLayout至少需设置内容组件中3个子组件的初始布局和默认大小(一是实际要调整的子组件,二是调整栏,三是一个空组件,该组件用于在调整器和其它子组件之间预留一些区域,并可用来在拖拽调整后确定内容组件中其它子组件的定位)。核心函数setItemLayout()有4个参数:1参:要伸缩的子组件(索引值). 2参:该子组件可伸缩的最小宽度或高度,正数为绝对大小,负数为可用区域百分比,比如-0.5即为50%. 3参:该子组件可伸缩的最大宽度或高度,正数为绝对大小(像素),负数为可用区域百分比(相对大小). 4参:默认值,如果空间足够,则优先使用此大小。正数为绝对大小,负数为可用区域的百分比。*/
// 设置第1个子组件的布局和优先大小
verticalLayout.setItemLayout (0, -0.2, -0.8, -0.5);  

// 设置第2个子组件的布局和优先大小(通常为调整栏)
verticalLayout.setItemLayout (1, 8, 8, 8);  

// 设置第3个子组件的布局和优先大小(空组件,用于定位其它子组件)
verticalLayout.setItemLayout (2, 150, -1.0, -0.5); 

/* 创建调整栏对象verticalDividerBar。其构造参数有三:
1参为伸缩式布局管理器的指针(即该类栈对象的内存地址)
2参为调整栏是布局中的哪个组件(按布局对象中的索引值找到该组件)。
3参为调整栏的样式。垂直的(true),还是水平的(false)。 */
verticalDividerBar = new StretchableLayoutResizerBar (&verticalLayout, 
                                                      1, true);
addAndMakeVisible (verticalDividerBar);

3、组件布局。内容组件的resized()方法中对可调整的子组件进行布局。这里有个小小的绕弯要理清思路。布局管理器负责管理3个子组件,第一个子组件无需多说,要调整的就是它;第二个为调整栏(它本身也是一个子组件),第三个为空子组件。这个空子组件很关键,尽管为空,但它有左边缘(或顶边缘,视排列方式而定),而这个边缘的坐标值将决定内容组件中联动改变的其他子组件的定位点。这么一来,拖动调整栏之后,就实现两个或多个子组件的联动了。本例实现的是:拖动列表框右侧的调整栏,列表框改变大小,同时,调整栏右侧的文本编辑器也改变大小。

/* 定义并初始化用于保存子组件指针的普通数组,3个元素分别为:列表框,调整栏,空组件. 注意,该数组保存的元素是组件的指针 */
Component* vcomps[] = { listBox, verticalDividerBar, nullptr };
  
/* 布局管理器对象对指针数组vcomps中保存的3个组件进行布局。共8个参数: 1参:要布局的数组. 2参:1参数组的大小. 3~6参:布局器所管理的范围。前面设置子组件的最大最小可用区域的百分比就以此值为准. 7参:是否垂直排列,即是否仅影响子组件的x坐标(false为只影响y坐标)。8参:要布局的组件是否自动调整以匹配可用区域。*/
VerticalLayout.layOutComponents (vcomps, 3, 4, 4, 
              getWidth() - 8, getHeight() - 8, false, true);

/* 定义一个整型变量x,用于确定内容组件中联动改变大小的其它子组件的布局定位点。利用这个思路,可以同时联动多个内容组件中的子组件,尽管只有一个调整栏,但可以一并改变多个子组件的大小。getItemCurrentPosition()返回第2个组件的当前位置,即该组件的左边缘x坐标值(本例是第三个垂直式布局的空组件)。如果上一个方法中的7参设置为true,则返回该组件的顶边缘y坐标值。返回的值+4,而后赋值给整型变量x。这4个像素,预留给调整栏使用。*/
int x = verticalLayout.getItemCurrentPosition (2) + 4;

// 内容组件中的文本编辑器组件即从第2个空子组件的左边缘+4处作为起点,开始布局。
//这样可确保调整列表框后,文本编辑器重新布局定位,用户的视觉效果就是二者同时改变了大小
textBox.setBounds (x, 0, getWidth() - x, getHeight() - 110);

StretchableLayoutManager类和StretchableLayoutResizerBar类的成员函数(略)。