splitter - 调整特定节点的大小
splitter - resize specific node
如何在拖动拆分器时调整 xul window 中特定节点的大小?
由于 xul window.
的复杂性,无法使用 resizebefore/resizeafter 属性
我试过在拆分器上使用 ondrag
事件,但它根本没有触发。 ondragstart
事件正常触发,我可以使用 event.offsetY
来捕获分离器移动了多少像素。
使用该值,我可以将它添加到需要元素的高度,这工作正常,但不幸的是,每个拖动会话仅触发一次此事件。
有什么想法吗?
谢谢。
用于测试的示例。由于我原来的 xul 的复杂性,我无法改变 xul 结构(用户可以隐藏和更改行的顺序),所以可能只有 javascript 解决方案是可行的:
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window id="testWindow"
title="testing resizing element by splitter"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
style="color: white;"
>
<vbox id="resizeme" flex="1" style="background-color: yellow; color: black;">
<hbox flex="1">
<label value="#1"/>
<hbox flex="1" align="center" pack="center">
<label value="Resizable by top and bottom splitter"/>
</hbox>
</hbox>
</vbox>
<splitter tooltiptext="Top splitter"/>
<grid flex="1">
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row style="background-color: black;">
<label value="#2"/>
<vbox flex="1" pack="center" align="center">
<label value="Must stay constant size at all times"/>
</vbox>
</row>
<row flex="1" style="background-color: blue;">
<label value="#3"/>
<vbox flex="1" pack="center" align="center">
<label value="Resizable by top splitter only"/>
</vbox>
</row>
<row style="background-color: black;">
<label value="#4"/>
<hbox flex="1" pack="center" align="center">
<label value="Must stay constant size at all times, content must fit"/>
<button label="blah"/>
</hbox>
</row>
<splitter tooltiptext="Bottom splitter"/>
<row flex="1" style="background-color: green;">
<label value="#5"/>
<vbox flex="1" pack="center" align="center">
<label value="Resizable by bottom splitter only"/>
</vbox>
</row>
<row style="background-color: black;">
<label value="#6"/>
<vbox flex="1" pack="center" align="center">
<label value="Must stay constant size at all times"/>
</vbox>
</row>
</rows>
</grid>
</window>
没有为 <splitter>
调整大小指定特定节点的常用方法。
与 XUL 中的所有调整大小一样,意图 是您应该能够对 XUL 进行编码,这样您就可以 UI 调整布局大小,或者它的内部部分自动使用 <splitter>
元素,无需让 JavaScript 监听事件并执行调整大小。但是,您当然可以让 JavaScript 执行 <splitter>
调整大小。你通常会这样做,当你正在做一些复杂的事情时,你有 运行 进入 <splitter>
实现中的一个错误,你只是发现它比微调你的 XUL 更容易使用股票功能,或者如果您只想要编写自己的代码所提供的完整控制。关键是 <splitter>
和底层系统 应该 为您执行全部调整大小。
但是,<splitter>
元素确实有很大的局限性和一些错误,这可能会导致您需要编写自己的调整大小代码。这些限制包括:
flex
属性 过载。它用于控制 object 的初始放置方式、window 调整大小时它们的大小调整方式以及所有 <splitters>
调整它们大小的方式。您很可能希望在每种情况下发生不同的事情。
- 股票
<splitter>
代码中存在错误。我观察到至少有几个不同的,包括一些明确声明为不灵活的元素仍然调整大小的地方。 IIRC,这些似乎主要是在尝试使用容器内的 <splitter>
来更改该容器的 object 超大尺寸时。
- 无法明确指定(例如通过 ID)
<splitter>
要调整大小的元素。
[我在想更多的限制,但我现在不记得了。]
如果您要使用 JavaScript 进行自己的处理,您似乎需要通过监听 mouse events. The movement of a <splitter>
does not appear to fire drag events 来完全实现功能。我假设这是因为移动 <splitter>
不被认为是 "drag-and-drop" 动作的一部分(即你实际上并没有拿起它并将它放在放置目标上)。虽然我希望能够听到拖动事件,但很明显它们没有触发。
对我来说,<splitters>
中缺少的最重要的功能是无法通过 ID 指定要调整大小的两个元素。显然,从你的问题标题来看,很明显,这也是你发现明显缺乏的东西。
添加指定 ID 到 <splitter>
:
以下代码实现并提供了使用 <splitter>
元素的示例,这些元素在 XUL 的 resizebefore
和 resizeafter
属性中指定要调整大小的元素的 ID .
为了在特定的 <splitter>
上使用它,您需要调用 public 函数之一来使用 <splitter>
' 注册 <splitter>
s ID 或 <splitter>
元素。例如,示例 XUL 中的两个 <spliter>
元素(根据您问题中的代码进行了一些修改)注册为:
splitterById.registerSplitterById("firstSplitter");
splitterById.registerSplitterById("secondSplitter");
splitterById.js:
/******************************************************************************
* splitterById *
* *
* XUL <splitter> elements which are registered will resize only the two *
* specific elements for which the ID is contained in the <splitter>'s *
* resizebefore and resizeafter attributes. The orient attribute is used to *
* specify if the <splitter> is resizing in the "vertical" or "horizontal" *
* orientation. "vertical" is the default. *
* *
* For a particular <splitter> this is an all or nothing choice. In other *
* words, you _must_ specify both a before and after element (e.g. You can not *
* mix using an ID on the resizebefore and not on resizeafter with the *
* expectation that the after will be resized with the normal <splitter> *
* functionality. *
* *
* On both elements, the attributes minheight, maxheight, minwidth, and *
* maxwidth will be obeyed. It may be necessary to explicitly set these *
* attributes in order to prevent one or the other element from growing or *
* shrinking when the other element is prevented from changing size by other *
* XUL UI constraints. For example, an element can not be reduced in size *
* beyond the minimum needed to display it. This code does not check for these *
* other constraints. Thus, setting these attributes, at least the ones *
* specifying the minimum height or minimum width will almost always be *
* desirable. *
* *
* Public methods: *
* registerSplitterById(id) : registers the <splitter> with that ID *
* registerSplitterByElement(element) : registers the <splitter> element *
* unregisterSplitterById(id) : unregisters the <splitter> with that ID *
* unregisterSplitterByElement(element) : unregisters the <splitter> element *
* *
******************************************************************************/
var splitterById = (function(){
let beforeER = {};
let afterER = {};
let splitIsVertical = true;
let origClientY = -1;
let origClientX = -1;
function ElementRec(_el) {
this.element = _el;
this.origHeight = getElementHeight(_el);
this.origWidth = getElementWidth(_el);
//The .minHeight and .maxHeight attributes/properties
// do not appear to be valid when first starting, so don't
// get them here.
//this.minHeight = getMinHeightAsValue(_el);
//this.maxHeight = getMaxHeightAsValue(_el);
}
function getElementHeight(el) {
//.height can be invalid and does not indicate the actual
// height displayed, only the desired height.
let boundingRec = el.getBoundingClientRect();
return boundingRec.bottom - boundingRec.top;
}
function getElementWidth(el) {
//.width can be invalid and does not indicate the actual
// width displayed, only the desired width.
let boundingRec = el.getBoundingClientRect();
return boundingRec.right - boundingRec.left;
}
function getMaxHeightAsValue(el) {
return asValueWithDefault(el.maxHeight,99999999);
}
function getMinHeightAsValue(el) {
return asValueWithDefault(el.minHeight,0);
}
function getMaxWidthAsValue(el) {
return asValueWithDefault(el.maxHeight,99999999);
}
function getMinWidthAsValue(el) {
return asValueWithDefault(el.minHeight,0);
}
function asValueWithDefault(value,myDefault) {
if(value === null || value === "" || value === undefined) {
value = myDefault;
}
//What is returned by the various attributes/properties is
// usually text, but not always.
value++;
value--;
return value;
}
function storeSplitterStartingValues(el) {
//Remember if the splitter is vertical or horizontal,
// references to the elements being resized and their initial sizes.
splitIsVertical = true;
if(el.getAttribute("orient") === "horizontal") {
splitIsVertical = false;
}
beforeER=new ElementRec(document.getElementById(el.getAttribute("resizebefore")));
afterER=new ElementRec(document.getElementById(el.getAttribute("resizeafter")));
if(beforeER.element === undefined || afterER.element === undefined) {
//Did not find one or the other element. We must have both.
return false;
}
return true;
}
function mousedownOnSplitter(event) {
if(event.button != 0) {
//Only drag with the left button.
return;
}
//Remember the mouse position at the start of the resize.
origClientY = event.clientY;
origClientX = event.clientX;
//Remember what we are acting upon
if(storeSplitterStartingValues(event.target)) {
//Start listening to mousemove and mouse up events on the whole document.
document.addEventListener("mousemove",resizeSplitter,true);
document.addEventListener("mouseup",endResizeSplitter,true);
}
}
function endResizeSplitter(event) {
if(event.button != 0) {
//Only drag with the left button.
return;
}
removeResizeListeners();
}
function removeResizeListeners() {
//Don't listen to document mousemove, mouseup events when not
// actively resizing.
document.removeEventListener("mousemove",resizeSplitter,true);
document.removeEventListener("mouseup",endResizeSplitter,true);
}
function resizeSplitter(event) {
//Prevent the splitter from acting normally:
event.preventDefault();
event.stopPropagation();
//Get the new size for the before and after elements based on the
// mouse position relative to where it was when the mousedown event fired.
let newBeforeSize = -1;
let newAfterSize = -1;
if(splitIsVertical) {
newBeforeSize = beforeER.origHeight + (event.clientY - origClientY);
newAfterSize = afterER.origHeight - (event.clientY - origClientY);
} else {
newBeforeSize = beforeER.origWidth + (event.clientX - origClientX);
newAfterSize = afterER.origWidth - (event.clientX - origClientX);
}
//Get any maximum and minimum sizes defined for the elements we are changing.
//Get these here because they may not have been populated/valid
// when the drag was first initiated (i.e. we should have been able
// to do this only once when the mousedown event fired, but testing showed
// the values are not necessarily valid at that time.
let beforeMinSize;
let beforeMaxSize;
let afterMinSize;
let afterMaxSize;
if(splitIsVertical) {
beforeMinSize = getMinHeightAsValue(beforeER.element);
beforeMaxSize = getMaxHeightAsValue(beforeER.element);
afterMinSize = getMinHeightAsValue(afterER.element);
afterMaxSize = getMaxHeightAsValue(afterER.element);
} else {
beforeMinSize = getMinWidthAsValue(beforeER.element);
beforeMaxSize = getMaxWidthAsValue(beforeER.element);
afterMinSize = getMinWidthAsValue(afterER.element);
afterMaxSize = getMaxWidthAsValue(afterER.element);
}
//Apply the limits to sizes we want to change to.
//These do appear to work better sequentially rather than optimized.
if(newBeforeSize < beforeMinSize) {
//Set to beforeMinSize limit if have passed.
let diff = beforeMinSize - newBeforeSize;
newBeforeSize += diff;
newAfterSize -= diff;
}
if(newBeforeSize > beforeMaxSize) {
//Set to beforeMaxSize limit if have passed.
let diff = beforeMaxSize - newBeforeSize;
newBeforeSize += diff;
newAfterSize -= diff;
}
if(newAfterSize < afterMinSize) {
//Set to afterMinSize limit if have passed.
let diff = afterMinSize - newAfterSize;
newAfterSize += diff;
newBeforeSize -= diff;
}
if(newAfterSize > afterMaxSize) {
//Set to afterMaxSize limit if have passed.
let diff = afterMaxSize - newAfterSize;
newAfterSize += diff;
newBeforeSize -= diff;
}
//Don't make any changes if we are still violating the limits.
//There are some pathological cases where we could still be violating
// a limit (where limits are set such that it is not possible to have
// a valid height).
if(newBeforeSize < beforeMinSize || newBeforeSize > beforeMaxSize
|| newAfterSize < afterMinSize || newAfterSize > afterMaxSize) {
return;
}
//Make the size changes
if(splitIsVertical) {
beforeER.element.height = newBeforeSize;
afterER.element.height = newAfterSize;
} else {
beforeER.element.width = newBeforeSize;
afterER.element.width = newAfterSize;
}
}
function _registerSplitterById(id) {
_registerSplitterByElement(document.getElementById(id));
}
function _registerSplitterByElement(el) {
el.addEventListener("mousedown",mousedownOnSplitter,false);
}
function _unregisterSplitterById(id) {
_unregisterSplitterByElement(document.getElementById(id));
}
function _unregisterSplitterByElement(el) {
el.removeEventListener("mousedown",mousedownOnSplitter,false);
removeResizeListeners();
}
return {
registerSplitterById : function(id) {
_registerSplitterById(id);
},
registerSplitterByElement : function(el) {
_registerSplitterByElement(el);
},
unregisterSplitterById : function(id) {
_unregisterSplitterById(id);
},
unregisterSplitterByElement : function(el) {
_unregisterSplitterByElement(el);
}
};
})();
XUL 示例(根据问题修改):
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window id="testWindow"
title="testing resizing element by splitter"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
style="color: white;"
>
<vbox id="resizeme" height="120" minheight="30" maxheight="250"
style="background-color: yellow; color: black;">
<hbox flex="1">
<label value="#1"/>
<hbox flex="1" align="center" pack="center">
<label id="yellowLabel" value="Resizable by top and bottom splitter"/>
</hbox>
</hbox>
</vbox>
<splitter id="firstSplitter" tooltiptext="Top splitter" orient="vertical"
resizebefore="resizeme" resizeafter="blueVbox"/>
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row style="background-color: black;">
<label value="#2"/>
<vbox pack="center" align="center">
<label value="Must stay constant size at all times"/>
</vbox>
</row>
<row id="blueRow" style="background-color: blue;">
<label value="#3"/>
<vbox id="blueVbox" height="120" minheight="30" pack="center" align="center">
<label id="blueLabel" value="Resizable by top splitter only"/>
</vbox>
</row>
<row style="background-color: black;">
<label value="#4"/>
<hbox pack="center" align="center">
<label value="Must stay constant size at all times, content must fit"/>
<button label="blah"/>
</hbox>
</row>
<splitter id="secondSplitter" tooltiptext="Bottom splitter" orient="vertical"
resizebefore="resizeme" resizeafter="greenVbox"/>
<row id="greenRow" style="background-color: green;">
<label value="#5"/>
<vbox id="greenVbox" height="120" minheight="30" pack="center" align="center">
<label id="greenLabel" value="Resizable by bottom splitter only"/>
</vbox>
</row>
<row style="background-color: black;">
<label value="#6"/>
<vbox pack="center" align="center">
<label value="Must stay constant size at all times"/>
</vbox>
</row>
</rows>
</grid>
<script type="application/x-javascript" src="file://b:/SplitterById.js"/>
<script type="application/javascript">
<![CDATA[
splitterById.registerSplitterById("firstSplitter");
splitterById.registerSplitterById("secondSplitter");
]]>
</script>
</window>
[注意:虽然代码被编写为同时适用于垂直和水平 <splitters>
,但我在上面的示例中仅使用垂直 <splitters>
对其进行了测试。]
正常使用<splitter>
(不监听事件):
您最初在问题中使用的示例比您现在使用的示例复杂得多。完全可以使用严格的 XUL 对其进行编码,以使 <splitter>
能够按照您要求的方式运行。
有多种方法(其中许多以各种组合相互作用)可用于控制通过 <splitter>
元素调整哪些 object 或 object 的大小,或总体布局的一般调整大小。其中包括使用 resizebefore
and resizeafter
attributes of the <splitter>
in combination with appropriate values for the flex
attribute on the elements in your XUL and potentially including those elements in box
, hbox
, or vbox
elements which are used only to distribute the "flex". In addition, it may be desirable to specify a variety of constraints for each element within the area which is being resized using the various attributes available to an XUL element (additional MDN docs: 1, 2, 3).
您似乎错过的一件事是 flex
属性可以是其他值,而不仅仅是 1
或 0
。该数值用于按比例指定相对于受调整大小影响的其他元素(是由于 <splitter>
或容器元素的调整大小)在特定元素上完成的调整大小(例如 <window>
、<box>
、<vbox>
、<hbox>
等)其中包括您感兴趣的元素。
反复试验:
要在特定布局中准确获得您想要的功能,您可能需要进行一些试验和错误。您可能会发现 XUL 原型制作工具 XUL Explorer 对此有所帮助,具体取决于您在做什么。例如,如果您的代码动态构建 XUL,那么 XUL Explorer 就没有那么大的帮助。豪呃,即使在动态构建我的 XUL 布局时,我也使用 XUL Explorer 快速查看我正在构建的 XUL 的变化 look/behave.
你的(原)具体例子:
[注意:以下内容基于问题中包含的第一个示例。该示例远没有现在问题中的示例复杂。特别是,它在容器内没有 <splitter>
(新示例中的 <grid>
),需要在该容器外调整元素的大小。]
对于您的具体示例,您描述的行为可以通过将绿色 <vbox>
上的 flex
值设置为相对于其他元素较大的值来实现。
与许多 UI 问题一样,很难用语言表达您希望发生的一切。例如,在本例中,您没有为其他 <vbox>
元素指定起始大小。为了显示更多 <splitter>
发生的情况并在绿色 <vbox>
上使用不同的 flex
值,我为 starting/default height
添加了一个其他 <vbox>
个元素。这将导致这些元素从该高度开始,然后仅在绿色 <vbox>
sh运行k 达到其最小高度后才从该高度缩小到它们的最小高度。
注意:您正在使用 style
attribute with a portion of it being min-height: 30px;
. Unless you are going to put this in a CSS class, then it might be better/easier to use the XUL attribute minheight
。如果您愿意,这样做可以更轻松地以编程方式进行更改。鉴于这是示例代码,您可能只有 in-lined,以便不必还包含 CSS 文件。
代码:
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window id="testWindow"
title="testing resizing element by splitter"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
<hbox flex="1">
<vbox flex="1">
<vbox flex="1" height="80" pack="center" align="center"
style="background-color: blue; min-height: 30px; color: white;">
<label value="this should stay constant size until green element reached its minimum size"/>
</vbox>
<vbox id="resizeme" flex="10000" height="80" pack="center" align="center"
style="background-color: green; min-height: 30px; color: white;">
<label value="only this should be resized until it reached minimum size of 30px"/>
</vbox>
<vbox flex="1" height="80" pack="center" align="center"
style="background-color: red; min-height: 30px; color: white;">
<label value="this should stay constant size until green element reached its minimum size"/>
</vbox>
</vbox>
</hbox>
<splitter/>
<vbox flex="1"/>
</window>
如何在拖动拆分器时调整 xul window 中特定节点的大小? 由于 xul window.
的复杂性,无法使用 resizebefore/resizeafter 属性我试过在拆分器上使用 ondrag
事件,但它根本没有触发。 ondragstart
事件正常触发,我可以使用 event.offsetY
来捕获分离器移动了多少像素。
使用该值,我可以将它添加到需要元素的高度,这工作正常,但不幸的是,每个拖动会话仅触发一次此事件。
有什么想法吗?
谢谢。
用于测试的示例。由于我原来的 xul 的复杂性,我无法改变 xul 结构(用户可以隐藏和更改行的顺序),所以可能只有 javascript 解决方案是可行的:
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window id="testWindow"
title="testing resizing element by splitter"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
style="color: white;"
>
<vbox id="resizeme" flex="1" style="background-color: yellow; color: black;">
<hbox flex="1">
<label value="#1"/>
<hbox flex="1" align="center" pack="center">
<label value="Resizable by top and bottom splitter"/>
</hbox>
</hbox>
</vbox>
<splitter tooltiptext="Top splitter"/>
<grid flex="1">
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row style="background-color: black;">
<label value="#2"/>
<vbox flex="1" pack="center" align="center">
<label value="Must stay constant size at all times"/>
</vbox>
</row>
<row flex="1" style="background-color: blue;">
<label value="#3"/>
<vbox flex="1" pack="center" align="center">
<label value="Resizable by top splitter only"/>
</vbox>
</row>
<row style="background-color: black;">
<label value="#4"/>
<hbox flex="1" pack="center" align="center">
<label value="Must stay constant size at all times, content must fit"/>
<button label="blah"/>
</hbox>
</row>
<splitter tooltiptext="Bottom splitter"/>
<row flex="1" style="background-color: green;">
<label value="#5"/>
<vbox flex="1" pack="center" align="center">
<label value="Resizable by bottom splitter only"/>
</vbox>
</row>
<row style="background-color: black;">
<label value="#6"/>
<vbox flex="1" pack="center" align="center">
<label value="Must stay constant size at all times"/>
</vbox>
</row>
</rows>
</grid>
</window>
没有为 <splitter>
调整大小指定特定节点的常用方法。
与 XUL 中的所有调整大小一样,意图 是您应该能够对 XUL 进行编码,这样您就可以 UI 调整布局大小,或者它的内部部分自动使用 <splitter>
元素,无需让 JavaScript 监听事件并执行调整大小。但是,您当然可以让 JavaScript 执行 <splitter>
调整大小。你通常会这样做,当你正在做一些复杂的事情时,你有 运行 进入 <splitter>
实现中的一个错误,你只是发现它比微调你的 XUL 更容易使用股票功能,或者如果您只想要编写自己的代码所提供的完整控制。关键是 <splitter>
和底层系统 应该 为您执行全部调整大小。
但是,<splitter>
元素确实有很大的局限性和一些错误,这可能会导致您需要编写自己的调整大小代码。这些限制包括:
flex
属性 过载。它用于控制 object 的初始放置方式、window 调整大小时它们的大小调整方式以及所有<splitters>
调整它们大小的方式。您很可能希望在每种情况下发生不同的事情。- 股票
<splitter>
代码中存在错误。我观察到至少有几个不同的,包括一些明确声明为不灵活的元素仍然调整大小的地方。 IIRC,这些似乎主要是在尝试使用容器内的<splitter>
来更改该容器的 object 超大尺寸时。 - 无法明确指定(例如通过 ID)
<splitter>
要调整大小的元素。
[我在想更多的限制,但我现在不记得了。]
如果您要使用 JavaScript 进行自己的处理,您似乎需要通过监听 mouse events. The movement of a <splitter>
does not appear to fire drag events 来完全实现功能。我假设这是因为移动 <splitter>
不被认为是 "drag-and-drop" 动作的一部分(即你实际上并没有拿起它并将它放在放置目标上)。虽然我希望能够听到拖动事件,但很明显它们没有触发。
对我来说,<splitters>
中缺少的最重要的功能是无法通过 ID 指定要调整大小的两个元素。显然,从你的问题标题来看,很明显,这也是你发现明显缺乏的东西。
添加指定 ID 到 <splitter>
:
以下代码实现并提供了使用 <splitter>
元素的示例,这些元素在 XUL 的 resizebefore
和 resizeafter
属性中指定要调整大小的元素的 ID .
为了在特定的 <splitter>
上使用它,您需要调用 public 函数之一来使用 <splitter>
' 注册 <splitter>
s ID 或 <splitter>
元素。例如,示例 XUL 中的两个 <spliter>
元素(根据您问题中的代码进行了一些修改)注册为:
splitterById.registerSplitterById("firstSplitter");
splitterById.registerSplitterById("secondSplitter");
splitterById.js:
/******************************************************************************
* splitterById *
* *
* XUL <splitter> elements which are registered will resize only the two *
* specific elements for which the ID is contained in the <splitter>'s *
* resizebefore and resizeafter attributes. The orient attribute is used to *
* specify if the <splitter> is resizing in the "vertical" or "horizontal" *
* orientation. "vertical" is the default. *
* *
* For a particular <splitter> this is an all or nothing choice. In other *
* words, you _must_ specify both a before and after element (e.g. You can not *
* mix using an ID on the resizebefore and not on resizeafter with the *
* expectation that the after will be resized with the normal <splitter> *
* functionality. *
* *
* On both elements, the attributes minheight, maxheight, minwidth, and *
* maxwidth will be obeyed. It may be necessary to explicitly set these *
* attributes in order to prevent one or the other element from growing or *
* shrinking when the other element is prevented from changing size by other *
* XUL UI constraints. For example, an element can not be reduced in size *
* beyond the minimum needed to display it. This code does not check for these *
* other constraints. Thus, setting these attributes, at least the ones *
* specifying the minimum height or minimum width will almost always be *
* desirable. *
* *
* Public methods: *
* registerSplitterById(id) : registers the <splitter> with that ID *
* registerSplitterByElement(element) : registers the <splitter> element *
* unregisterSplitterById(id) : unregisters the <splitter> with that ID *
* unregisterSplitterByElement(element) : unregisters the <splitter> element *
* *
******************************************************************************/
var splitterById = (function(){
let beforeER = {};
let afterER = {};
let splitIsVertical = true;
let origClientY = -1;
let origClientX = -1;
function ElementRec(_el) {
this.element = _el;
this.origHeight = getElementHeight(_el);
this.origWidth = getElementWidth(_el);
//The .minHeight and .maxHeight attributes/properties
// do not appear to be valid when first starting, so don't
// get them here.
//this.minHeight = getMinHeightAsValue(_el);
//this.maxHeight = getMaxHeightAsValue(_el);
}
function getElementHeight(el) {
//.height can be invalid and does not indicate the actual
// height displayed, only the desired height.
let boundingRec = el.getBoundingClientRect();
return boundingRec.bottom - boundingRec.top;
}
function getElementWidth(el) {
//.width can be invalid and does not indicate the actual
// width displayed, only the desired width.
let boundingRec = el.getBoundingClientRect();
return boundingRec.right - boundingRec.left;
}
function getMaxHeightAsValue(el) {
return asValueWithDefault(el.maxHeight,99999999);
}
function getMinHeightAsValue(el) {
return asValueWithDefault(el.minHeight,0);
}
function getMaxWidthAsValue(el) {
return asValueWithDefault(el.maxHeight,99999999);
}
function getMinWidthAsValue(el) {
return asValueWithDefault(el.minHeight,0);
}
function asValueWithDefault(value,myDefault) {
if(value === null || value === "" || value === undefined) {
value = myDefault;
}
//What is returned by the various attributes/properties is
// usually text, but not always.
value++;
value--;
return value;
}
function storeSplitterStartingValues(el) {
//Remember if the splitter is vertical or horizontal,
// references to the elements being resized and their initial sizes.
splitIsVertical = true;
if(el.getAttribute("orient") === "horizontal") {
splitIsVertical = false;
}
beforeER=new ElementRec(document.getElementById(el.getAttribute("resizebefore")));
afterER=new ElementRec(document.getElementById(el.getAttribute("resizeafter")));
if(beforeER.element === undefined || afterER.element === undefined) {
//Did not find one or the other element. We must have both.
return false;
}
return true;
}
function mousedownOnSplitter(event) {
if(event.button != 0) {
//Only drag with the left button.
return;
}
//Remember the mouse position at the start of the resize.
origClientY = event.clientY;
origClientX = event.clientX;
//Remember what we are acting upon
if(storeSplitterStartingValues(event.target)) {
//Start listening to mousemove and mouse up events on the whole document.
document.addEventListener("mousemove",resizeSplitter,true);
document.addEventListener("mouseup",endResizeSplitter,true);
}
}
function endResizeSplitter(event) {
if(event.button != 0) {
//Only drag with the left button.
return;
}
removeResizeListeners();
}
function removeResizeListeners() {
//Don't listen to document mousemove, mouseup events when not
// actively resizing.
document.removeEventListener("mousemove",resizeSplitter,true);
document.removeEventListener("mouseup",endResizeSplitter,true);
}
function resizeSplitter(event) {
//Prevent the splitter from acting normally:
event.preventDefault();
event.stopPropagation();
//Get the new size for the before and after elements based on the
// mouse position relative to where it was when the mousedown event fired.
let newBeforeSize = -1;
let newAfterSize = -1;
if(splitIsVertical) {
newBeforeSize = beforeER.origHeight + (event.clientY - origClientY);
newAfterSize = afterER.origHeight - (event.clientY - origClientY);
} else {
newBeforeSize = beforeER.origWidth + (event.clientX - origClientX);
newAfterSize = afterER.origWidth - (event.clientX - origClientX);
}
//Get any maximum and minimum sizes defined for the elements we are changing.
//Get these here because they may not have been populated/valid
// when the drag was first initiated (i.e. we should have been able
// to do this only once when the mousedown event fired, but testing showed
// the values are not necessarily valid at that time.
let beforeMinSize;
let beforeMaxSize;
let afterMinSize;
let afterMaxSize;
if(splitIsVertical) {
beforeMinSize = getMinHeightAsValue(beforeER.element);
beforeMaxSize = getMaxHeightAsValue(beforeER.element);
afterMinSize = getMinHeightAsValue(afterER.element);
afterMaxSize = getMaxHeightAsValue(afterER.element);
} else {
beforeMinSize = getMinWidthAsValue(beforeER.element);
beforeMaxSize = getMaxWidthAsValue(beforeER.element);
afterMinSize = getMinWidthAsValue(afterER.element);
afterMaxSize = getMaxWidthAsValue(afterER.element);
}
//Apply the limits to sizes we want to change to.
//These do appear to work better sequentially rather than optimized.
if(newBeforeSize < beforeMinSize) {
//Set to beforeMinSize limit if have passed.
let diff = beforeMinSize - newBeforeSize;
newBeforeSize += diff;
newAfterSize -= diff;
}
if(newBeforeSize > beforeMaxSize) {
//Set to beforeMaxSize limit if have passed.
let diff = beforeMaxSize - newBeforeSize;
newBeforeSize += diff;
newAfterSize -= diff;
}
if(newAfterSize < afterMinSize) {
//Set to afterMinSize limit if have passed.
let diff = afterMinSize - newAfterSize;
newAfterSize += diff;
newBeforeSize -= diff;
}
if(newAfterSize > afterMaxSize) {
//Set to afterMaxSize limit if have passed.
let diff = afterMaxSize - newAfterSize;
newAfterSize += diff;
newBeforeSize -= diff;
}
//Don't make any changes if we are still violating the limits.
//There are some pathological cases where we could still be violating
// a limit (where limits are set such that it is not possible to have
// a valid height).
if(newBeforeSize < beforeMinSize || newBeforeSize > beforeMaxSize
|| newAfterSize < afterMinSize || newAfterSize > afterMaxSize) {
return;
}
//Make the size changes
if(splitIsVertical) {
beforeER.element.height = newBeforeSize;
afterER.element.height = newAfterSize;
} else {
beforeER.element.width = newBeforeSize;
afterER.element.width = newAfterSize;
}
}
function _registerSplitterById(id) {
_registerSplitterByElement(document.getElementById(id));
}
function _registerSplitterByElement(el) {
el.addEventListener("mousedown",mousedownOnSplitter,false);
}
function _unregisterSplitterById(id) {
_unregisterSplitterByElement(document.getElementById(id));
}
function _unregisterSplitterByElement(el) {
el.removeEventListener("mousedown",mousedownOnSplitter,false);
removeResizeListeners();
}
return {
registerSplitterById : function(id) {
_registerSplitterById(id);
},
registerSplitterByElement : function(el) {
_registerSplitterByElement(el);
},
unregisterSplitterById : function(id) {
_unregisterSplitterById(id);
},
unregisterSplitterByElement : function(el) {
_unregisterSplitterByElement(el);
}
};
})();
XUL 示例(根据问题修改):
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window id="testWindow"
title="testing resizing element by splitter"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
style="color: white;"
>
<vbox id="resizeme" height="120" minheight="30" maxheight="250"
style="background-color: yellow; color: black;">
<hbox flex="1">
<label value="#1"/>
<hbox flex="1" align="center" pack="center">
<label id="yellowLabel" value="Resizable by top and bottom splitter"/>
</hbox>
</hbox>
</vbox>
<splitter id="firstSplitter" tooltiptext="Top splitter" orient="vertical"
resizebefore="resizeme" resizeafter="blueVbox"/>
<grid>
<columns>
<column/>
<column flex="1"/>
</columns>
<rows>
<row style="background-color: black;">
<label value="#2"/>
<vbox pack="center" align="center">
<label value="Must stay constant size at all times"/>
</vbox>
</row>
<row id="blueRow" style="background-color: blue;">
<label value="#3"/>
<vbox id="blueVbox" height="120" minheight="30" pack="center" align="center">
<label id="blueLabel" value="Resizable by top splitter only"/>
</vbox>
</row>
<row style="background-color: black;">
<label value="#4"/>
<hbox pack="center" align="center">
<label value="Must stay constant size at all times, content must fit"/>
<button label="blah"/>
</hbox>
</row>
<splitter id="secondSplitter" tooltiptext="Bottom splitter" orient="vertical"
resizebefore="resizeme" resizeafter="greenVbox"/>
<row id="greenRow" style="background-color: green;">
<label value="#5"/>
<vbox id="greenVbox" height="120" minheight="30" pack="center" align="center">
<label id="greenLabel" value="Resizable by bottom splitter only"/>
</vbox>
</row>
<row style="background-color: black;">
<label value="#6"/>
<vbox pack="center" align="center">
<label value="Must stay constant size at all times"/>
</vbox>
</row>
</rows>
</grid>
<script type="application/x-javascript" src="file://b:/SplitterById.js"/>
<script type="application/javascript">
<![CDATA[
splitterById.registerSplitterById("firstSplitter");
splitterById.registerSplitterById("secondSplitter");
]]>
</script>
</window>
[注意:虽然代码被编写为同时适用于垂直和水平 <splitters>
,但我在上面的示例中仅使用垂直 <splitters>
对其进行了测试。]
正常使用<splitter>
(不监听事件):
您最初在问题中使用的示例比您现在使用的示例复杂得多。完全可以使用严格的 XUL 对其进行编码,以使 <splitter>
能够按照您要求的方式运行。
有多种方法(其中许多以各种组合相互作用)可用于控制通过 <splitter>
元素调整哪些 object 或 object 的大小,或总体布局的一般调整大小。其中包括使用 resizebefore
and resizeafter
attributes of the <splitter>
in combination with appropriate values for the flex
attribute on the elements in your XUL and potentially including those elements in box
, hbox
, or vbox
elements which are used only to distribute the "flex". In addition, it may be desirable to specify a variety of constraints for each element within the area which is being resized using the various attributes available to an XUL element (additional MDN docs: 1, 2, 3).
您似乎错过的一件事是 flex
属性可以是其他值,而不仅仅是 1
或 0
。该数值用于按比例指定相对于受调整大小影响的其他元素(是由于 <splitter>
或容器元素的调整大小)在特定元素上完成的调整大小(例如 <window>
、<box>
、<vbox>
、<hbox>
等)其中包括您感兴趣的元素。
反复试验:
要在特定布局中准确获得您想要的功能,您可能需要进行一些试验和错误。您可能会发现 XUL 原型制作工具 XUL Explorer 对此有所帮助,具体取决于您在做什么。例如,如果您的代码动态构建 XUL,那么 XUL Explorer 就没有那么大的帮助。豪呃,即使在动态构建我的 XUL 布局时,我也使用 XUL Explorer 快速查看我正在构建的 XUL 的变化 look/behave.
你的(原)具体例子:
[注意:以下内容基于问题中包含的第一个示例。该示例远没有现在问题中的示例复杂。特别是,它在容器内没有 <splitter>
(新示例中的 <grid>
),需要在该容器外调整元素的大小。]
对于您的具体示例,您描述的行为可以通过将绿色 <vbox>
上的 flex
值设置为相对于其他元素较大的值来实现。
与许多 UI 问题一样,很难用语言表达您希望发生的一切。例如,在本例中,您没有为其他 <vbox>
元素指定起始大小。为了显示更多 <splitter>
发生的情况并在绿色 <vbox>
上使用不同的 flex
值,我为 starting/default height
添加了一个其他 <vbox>
个元素。这将导致这些元素从该高度开始,然后仅在绿色 <vbox>
sh运行k 达到其最小高度后才从该高度缩小到它们的最小高度。
注意:您正在使用 style
attribute with a portion of it being min-height: 30px;
. Unless you are going to put this in a CSS class, then it might be better/easier to use the XUL attribute minheight
。如果您愿意,这样做可以更轻松地以编程方式进行更改。鉴于这是示例代码,您可能只有 in-lined,以便不必还包含 CSS 文件。
代码:
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<window id="testWindow"
title="testing resizing element by splitter"
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
>
<hbox flex="1">
<vbox flex="1">
<vbox flex="1" height="80" pack="center" align="center"
style="background-color: blue; min-height: 30px; color: white;">
<label value="this should stay constant size until green element reached its minimum size"/>
</vbox>
<vbox id="resizeme" flex="10000" height="80" pack="center" align="center"
style="background-color: green; min-height: 30px; color: white;">
<label value="only this should be resized until it reached minimum size of 30px"/>
</vbox>
<vbox flex="1" height="80" pack="center" align="center"
style="background-color: red; min-height: 30px; color: white;">
<label value="this should stay constant size until green element reached its minimum size"/>
</vbox>
</vbox>
</hbox>
<splitter/>
<vbox flex="1"/>
</window>