2016-07-02 15 views
5

Come posso ridimensionare un nodo specifico nella finestra xul quando viene trascinato uno splitter? Impossibile utilizzare gli attributi resizebefore/resizeafter a causa della complessità della finestra xul.- ridimensionare il nodo specifico

Ho provato a utilizzare l'evento ondrag sullo splitter, ma non sta sparando affatto. L'evento ondragstart si attiva e posso utilizzare event.offsetY per catturare il numero di pixel spostati dallo sdoppiatore. Usando quel valore potrei aggiungerlo all'altezza dell'elemento need, che funziona bene, ma sfortunatamente questo evento si attiva solo una volta per sessione di trascinamento.

Qualche idea?

Grazie.

Un esempio per testarlo con. A causa della complessità della mia XUL originale non riesco a modificare la struttura XUL (l'utente può nascondere e ordine delle righe cambiare), quindi probabilmente solo javascript soluzione è praticabile):

<?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> 
+0

Ok, ad esempio aggiunto. – vanowm

+0

Il mio male, mescolato entrambi gli esempi ... risolto ora. Entrambi gli splitter dovrebbero ridimensionare la casella gialla, tuttavia lo splitter superiore dovrebbe interessare solo la riga blu e lo splitter inferiore dovrebbe interessare solo la riga verde. Nota a margine, si verifica uno strano comportamento se dopo aver aperto il file xul e spostato lo splitter in basso, salta giù, ridimensionando in modo imprevisto la riga n. 4 nera. – vanowm

risposta

1

non c'è stock modo per specificare un nodo specifico per un <splitter> da ridimensionare.

Come con tutti ridimensionamento in XUL, il intento è che si dovrebbe essere in grado di codificare il XUL in modo tale che si può avere l'interfaccia utente ridimensionare il layout, o una parte interna di esso utilizzando <splitter> elementi, automaticamente, senza alcuna è necessario che il tuo JavaScript ascolti gli eventi e esegua il ridimensionamento. Tuttavia, è possibile che JavaScript esegua il ridimensionamento <splitter>. In genere, quando esegui qualcosa che è complesso, ti imbatti in uno degli errori nell'implementazione <splitter>, trovi semplicemente più semplice ottimizzare il tuo XUL per utilizzare la funzionalità di serie o se desideri semplicemente controllo completo che fornisce la scrittura del proprio codice. Il punto è che il <splitter> e il sistema sottostante devono eseguire l'intero valore di ridimensionamento per l'utente.

Tuttavia, gli elementi <splitter> presentano limitazioni significative e alcuni bug che possono comportare la necessità di scrivere il proprio codice di ridimensionamento. Tali limitazioni includono:

  • La proprietà flex è sovraccaricata. Viene usato per controllare come gli oggetti vengono inizialmente posizionati, come vengono ridimensionati quando la finestra viene ridimensionata e come vengono ridimensionati da tutti <splitters>. È possibile che tu voglia che accadano cose diverse in ogni caso.
  • Ci sono errori nel codice <splitter>. Ho osservato almeno un paio di diversi, compresi alcuni in cui elementi che sono dichiarati esplicitamente come non flessibili sono ancora ridimensionati. IIRC, queste appaiono per lo più quando si tenta di utilizzare un <splitter> all'interno di un contenitore per modificare le dimensioni di un oggetto fuori misura di quel contenitore.
  • Non è possibile specificare esplicitamente (ad es. Per ID) gli elementi che devono essere ridimensionati da <splitter>.

[pensavo a più limitazioni, ma io non li sto ricordando in questo momento.]

Se avete intenzione di utilizzare JavaScript per fare la propria elaborazione, sembra è necessario per l'attuazione del funzionalità completamente ascoltando mouse events. Il movimento di un <splitter> non sembra sparare drag events.Presumo che ciò sia dovuto al fatto che lo spostamento di <splitter> non è considerato parte di un'azione "trascina e rilascia" (ad esempio, in realtà non lo si preleva e lo si rilascia su una destinazione di rilascio). Mentre mi aspettavo di essere in grado di ascoltare gli eventi di trascinamento, è chiaro che non stanno sparando.

Per me, la funzionalità più significativa che manca in <splitters> è la mancanza della possibilità di specificare per ID i due elementi che devono essere ridimensionati. Ovviamente, dal titolo della tua domanda, è chiaro che anche questo è qualcosa di cui manchi anche in modo significativo.

calcolata specificando un ID di <splitter>:

I seguenti attrezzi di codice, e fornisce un esempio di utilizzo, <splitter> elementi che specificano l'ID degli elementi siano ridimensionate nelle resizebefore e resizeafter attributi all'interno del XUL .

Al fine di utilizzarlo su un particolare <splitter>, è necessario chiamare una delle funzioni pubbliche a registrare il <splitter> utilizzando ID 's la <splitter> o l'elemento <splitter>. Ad esempio, i due elementi <spliter> nell'esempio XUL (un po 'modificato dal codice nella tua domanda) sono registrati con:

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

Esempio XUL (modificato da la questione):

<?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> 

Questo esempio appare come:
Resizing using splitterById

[Nota: Mentre il codice è scritto per funzionare con sia verticale che orizzontale <splitters>, ho solo provato con la verticale <splitters> nell'esempio di cui sopra.]

Utilizzando <splitter> normalmente (senza ascoltare per eventi):
L'esempio che avevi inizialmente nella tua domanda era significativamente meno complesso rispetto all'esempio che hai ora. Era abbastanza possibile codificarlo usando rigorosamente XUL per abilitare il <splitter> a funzionare come richiesto.

Esistono diversi modi (molti dei quali interagiscono in varie combinazioni) che possono essere utilizzati per controllare quali oggetti o oggetti vengono ridimensionati tramite un elemento <splitter> o un ridimensionamento generale del layout generale. Tra le altre cose, questi includono usando i resizebefore e resizeafter attributi del <splitter> in combinazione con i valori appropriati per l'attributo flex sugli elementi nel XUL e potenzialmente compresi quegli elementi in box, hbox o vbox elementi che vengono utilizzati solo per distribuire la "flettere". Inoltre, potrebbe essere opportuno specificare una varietà di vincoli per ciascun elemento all'interno dell'area che viene ridimensionato utilizzando i vari attributes available to an XUL element (documenti MDN aggiuntivi: 1, 2, 3).

Una delle cose che sembra mancare è che l'attributo flex può essere altri valori oltre a 1 o 0.Il valore numerico viene utilizzato per specificare proporzionalmente la quantità di ridimensionamento eseguita su un elemento specifico rispetto agli altri elementi che sono interessati dal ridimensionamento (che sia ridimensionato a causa di un <splitter> o un ridimensionamento di un elemento contenitore (ad esempio <window>, <box>, <vbox>, <hbox>, ecc.) Che include l'elemento in cui si è interessati).

Tentativi ed errori:
per ottenere esattamente le funzionalità che desiderate in un particolare layout che sarà probabilmente necessario eseguire alcuni tentativi ed errori. È possibile trovare lo strumento di prototipazione XUL XUL Explorer in modo che sia utile, a seconda esattamente di ciò che si sta facendo. Ad esempio, se il tuo codice crea dinamicamente il tuo XUL, allora XUL Explorer non è di grande aiuto. Comunque, anche quando ho creato dinamicamente il mio layout XUL, ho usato XUL Explorer per vedere rapidamente come le variazioni sul XUL che stavo costruendo sembrassero/comportassero.

tuo (originale) esempio specifico:
[Nota: Le seguenti è basato sul primo esempio che è stata inclusa nella domanda. Quell'esempio era significativamente meno complesso di quello che ora è nella domanda. In particolare, non aveva unall'interno di un contenitore (un <grid> nel nuovo esempio) che si desiderava ridimensionare gli elementi all'esterno di quel contenitore.]

Per il tuo esempio specifico, il comportamento che descrivi può essere raggiunto da impostando il valore sul verde <vbox> su un valore elevato rispetto agli altri elementi.

Come per molti problemi di interfaccia utente, è difficile dire a parole tutto ciò che desideri avere. Ad esempio, in questo caso, non hai specificato dimensioni iniziali per gli altri elementi <vbox>. Per mostrare più di ciò che accade con lo <splitter> e utilizzando un valore diverso per <vbox>, è disponibile height per gli altri elementi <vbox>. Ciò si tradurrà in quegli elementi che iniziano a quell'altezza e quindi si ridurranno da quello in basso alla loro altezza minima una volta che il verde <vbox> si è ridotto alla sua altezza minima.

Nota: si utilizza l'attributo style con una parte di esso pari a min-height: 30px;. A meno che non lo si stia mettendo in una classe CSS, potrebbe essere meglio/più facile usare l'attributo XUL minheight. In questo modo sarebbe più facile cambiare a livello di codice, se lo desideri. Dato che questo è un codice di esempio, potresti averlo appena inserito per non dover includere anche un file CSS.

Il codice:

<?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> 

Come appare quando si utilizza la <splitter> per ridimensionare:
Resizing <vbox> elements with flex

+0

Grazie per la spiegazione dettagliata e l'esempio di lavoro! Sfortunatamente dopo alcuni test aggiuntivi il mio esempio non era completo ... Non mi aspettavo una soluzione pura xul, quindi l'esempio si è rivelato troppo limitato. Ho aggiornato la mia domanda originale con un esempio più completo, che temo non possa essere risolto esclusivamente in xul. – vanowm

+0

@vanowm: ho aggiornato in modo significativo la mia risposta. Ora include il codice JavaScript che funziona con l'esempio corrente nella tua domanda. Infatti, il codice implementa un metodo generico di utilizzo degli ID negli attributi XUL di "" per specificare esplicitamente i due elementi "" verrà ridimensionato. – Makyen

+0

Grazie mille! Questo è perfetto! – vanowm

Problemi correlati