拆分器 - 调整特定节点的大小

van*_*owm 5 javascript xul

拖动分割器时,如何在xul窗口中调整特定节点的大小?由于xul窗口的复杂性,无法使用resizebefore/resizeafter属性.

我曾尝试ondrag在分离器上使用事件,但它根本不会发射.ondragstart事件触发很好,我可以event.offsetY用来捕获分割器移动的像素数.使用该值我可以将它添加到need元素的高度,这可以正常工作,但遗憾的是,每个拖动会话此事件仅触发一次.

有任何想法吗?

谢谢.

用它来测试它的一个例子.由于我的原始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>
Run Code Online (Sandbox Code Playgroud)

Mak*_*yen 2

没有常用的方法来指定要<splitter>调整大小的特定节点。

与 XUL 中的所有大小调整一样,目的是您应该能够对 XUL 进行编码,以便您可以让 UI 使用<splitter>元素自动调整布局或其内部部分的大小,而不需要 JavaScript 侦听事件并执行调整大小。但是,您当然可以让 JavaScript 执行<splitter>大小调整。通常,当您正在做一些复杂的事情,遇到实现中的错误之一<splitter>,您只是发现它比微调 XUL 以使用库存功能更容易,或者如果您只是想要完整的功能时,您通常会这样做控制编写您自己的代码所提供的功能。关键是<splitter>底层系统应该为您执行整个大小调整。

但是,<splitter>元素确实有很大的限制和一些错误,这可能导致您需要编写自己的调整大小代码。这些限制包括:

  • 物业flex超载。它用于控制对象最初如何放置、当窗口大小调整时如何调整它们的大小以及所有<splitters>. 您很可能希望在每种情况下发生不同的事情。
  • 股票代码中存在错误<splitter>。我观察到至少有几个不同的元素,包括一些明确声明为不灵活的元素仍然会调整大小。IIRC,这些似乎主要是在尝试使用<splitter>容器内的对象来更改超出该容器的对象的大小时。
  • 无法显式指定(例如通过 ID)要<splitter>调整大小的元素。

[我正在考虑更多限制,但我现在不记得它们了。]

如果您打算使用 JavaScript 进行自己的处理,那么您似乎需要通过监听鼠标事件来完全实现该功能。的移动<splitter>似乎不会引发拖动事件。我认为这是因为移动 a<splitter>不被视为“拖放”操作的一部分(即您实际上并未将其拾取并将其放在放置目标上)。虽然我希望能够监听拖动事件,但很明显它们没有触发。

对我来说,缺少的最重要的功能<splitters>是无法通过 ID 指定要调整大小的两个元素。显然,从你的问题标题来看,很明显你也发现这是明显缺乏的。

将指定 ID 添加到<splitter>

以下代码实现并提供了使用元素的示例,这些元素指定要在XUL 内的和属性<splitter>中调整大小的元素的 ID 。resizebeforeresizeafter

为了在特定的 上使用它<splitter>,您需要调用公共函数之一来<splitter>使用 的<splitter>ID 或<splitter>元素来注册。例如,<spliter>示例 XUL 中的两个元素(对您问题中的代码进行了一些修改)注册为:

splitterById.registerSplitterById("firstSplitter");
splitterById.registerSplitterById("secondSplitter"); 
Run Code Online (Sandbox Code Playgroud)

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);
        }
    };
})();
Run Code Online (Sandbox Code Playgroud)

示例 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>
Run Code Online (Sandbox Code Playgroud)

这个例子看起来像:
使用 splitterById 调整大小

[注意:虽然编写的代码可以同时使用垂直和水平,但我在上面的示例中<splitters>仅使用垂直进行了测试。]<splitters>

正常使用<splitter>(不监听事件):
您最初在问题中使用的示例比您现在使用的示例复杂得多。完全有可能使用严​​格的 XUL 对其进行编码,以使它<splitter>能够按照您请求的方式运行。

有多种方法(其中许多以各种组合交互)可用于控制通过元素调整哪个对象或对象的大小<splitter>,或总体布局的一般调整大小。除此之外,这些包括将resizebefore和 的resizeafter属性与 XUL 中元素上<splitter>的属性的适当值结合使用,并可能包括仅用于分发“flex”的、或元素中的那些元素。此外,可能需要为正在使用XUL 元素可用的各种属性调整大小的区域内每个元素指定各种约束(其他 MDN 文档:1、2、3 flexboxhboxvbox

您似乎错过的一件事是该flex属性可以是除1或 之外的其他值0。该数值用于按比例指定相对于受调整大小影响的其他元素对特定元素执行的调整大小量(可以是由于 、<splitter>或容器元素的调整大小而导致的调整大小(例如<window><box><vbox><hbox>等)其中包括您感兴趣的元素)。

试验和错误:
为了在特定布局中准确获得您想要的功能,您可能需要执行一些试验和错误。您可能会发现 XUL 原型设计工具XUL Explorer对此很有帮助,具体取决于您正在做什么。例如,如果您的代码动态构建 XUL,那么 XUL Explorer 就没有多大帮助。然而,即使在动态构建我的 XUL 布局时,我也使用 XUL Explorer 来快速查看我正在构建的 XUL 的变体的外观/行为。

您的(原始)具体示例:
[注意:以下内容基于问题中包含的第一个示例。该示例比现在问题中的示例简单得多。特别是,它没有<splitter>容器内部(<grid>新示例中的 a),而容器内部没有需要在该容器外部调整元素大小的元素。]

对于您的具体示例,您描述的行为可以通过将flex绿色的值设置<vbox>为相对于其他元素较大的值来实现。

与许多 UI 问题一样,很难用语言表达您希望发生的一切。例如,在本例中,您没有指定其他<vbox>元素的起始大小。为了更多地显示和在绿色上<splitter>使用不同值时发生的情况,我为其他元素添加了起始/默认值。这将导致这些元素从该高度开始,然后只有在果岭收缩到其最小高度时才从该高度收缩到最小高度。flex<vbox>height<vbox><vbox>