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

时间:2016-07-02 17:43:30

标签: 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>

1 个答案:

答案 0 :(得分:1)

没有库存方式来指定<splitter>调整大小的特定节点。

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

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

  • <splitter>属性已超载。它用于控制对象的初始放置方式,调整窗口大小时的大小调整方式以及所有flex的大小调整大小。您很可能希望在每种情况下都能发生不同的事情。
  • 库存<splitters>代码中存在错误。我观察过至少几个不同的,包括一些明确声明为不灵活的元素仍然调整大小。 IIRC,这些似乎主要是在试图使用容器内的<splitter>来改变该容器特大对象的大小时。
  • 无法明确指定(例如,通过ID)<splitter>要调整大小的元素。

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

如果您打算使用JavaScript进行自己的处理,则需要通过收听mouse events来完全实现该功能。 <splitter>的移动似乎无法触发drag events。我认为这是因为移动<splitter>不被视为&#34;拖放&#34;动作(即你实际上没有把它捡起并放在掉落目标上)。虽然我希望能够听到拖拽事件,但显然他们没有开火。

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

添加为<splitters>指定ID:

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

要在特定的resizeafter上使用它,您需要使用<splitter>的ID调用其中一个公共函数来注册<splitter><splitter>元素。例如,示例XUL中的两个<splitter>元素(在您的问题中的代码中有所修改)已注册:

<spliter>

splitterById.js

splitterById.registerSplitterById("firstSplitter");
splitterById.registerSplitterById("secondSplitter"); 

示例XUL(从问题修改):

 /******************************************************************************
 * 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);
        }
    };
})();

这个例子看起来像:
Resizing using splitterById

[注意:虽然编写的代码适用于垂直和水平<?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>(不听事件):
您最初在您的问题中提供的示例远比您现在的示例复杂得多。很可能使用严​​格的XUL对其进行编码,以使<splitter>能够按照您的要求运行。

有多种方式(其中许多方式以各种组合进行交互),可用于控制通过<splitter>元素调整哪个对象或对象,或者整体布局的一般调整大小。除此之外,还包括使用<splitter>的{​​{3}}和resizebefore属性以及XUL中元素的resizeafter属性的适当值,并可能包含这些属性flexboxhbox元素中仅用于分发&#34; flex&#34;的元素。此外,可能需要为使用各种vbox(其他MDN文档:attributes available to an XUL element1,{{调整大小的区域内的每个元素指定各种约束。 3}})。

您错过的一件事是,<splitter>属性可以是除flex1之外的其他值。数值用于按比例指定对特定元素执行的调整大小相对于受调整大小影响的其他元素(由于0调整大小,或调整容器元素大小) (例如2<splitter><box><vbox>等),其中包含您感兴趣的元素。)

试错:
要在特定布局中获得所需的功能,您可能需要执行一些试验和错误。您可能会发现XUL原型制作工具3有助于这样做,具体取决于您正在做什么。例如,如果您的代码动态构建您的XUL,则XUL Explorer没有那么多帮助。但是,即使在动态构建我的XUL布局时,我也使用XUL Explorer快速查看我正在构建的XUL上的变体的外观/行为。

您的(原始)具体示例:
[注意:以下内容基于问题中包含的第一个示例。这个例子明显不如现在的问题复杂。特别是,它在容器内部没有<hbox>(新示例中为<window>),希望在该容器外部调整元素大小。]

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

与许多UI问题一样,很难用文字说出你想要发生的一切。例如,在这种情况下,您没有为其他<vbox>元素指定任何起始大小。为了更详细地展示<vbox>发生的事情并在绿色<splitter>上使用flex的不同值,我为其他人添加了一个开始/默认XUL Explorer <vbox>元素。一旦绿色<vbox>缩小到最小高度,这将导致那些元素从该高度开始,然后仅从那个元素收缩到最小高度。

注意:您使用的是<grid>属性,其中一部分属于<vbox>。除非您打算将它放在CSS类中,否则使用XUL属性height可能更好/更容易。如果您愿意,这样做可以更容易地以编程方式进行更改。鉴于这是示例代码,您可能只是内联,以便不必包含CSS文件。

代码:

min-height: 30px;

使用<?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> 调整大小时的样子:
style