Sto cercando di scrivere una classe personalizzata Panel
per WPF, sovrascrivendo MeasureOverride
e ArrangeOverride
ma, mentre è in gran parte lavoro che sto vivendo uno strano problema che non riesco a spiegare.Un'usanza auto-sizing WPF Pannello classe
In particolare, dopo aver chiamato Arrange
sugli articoli di mio figlio in ArrangeOverride
dopo aver scoperto quali dovrebbero essere le dimensioni, non stanno ridimensionando le dimensioni che ho dato loro e sembrano ridimensionare le dimensioni passate al loro Metodo Measure
all'interno di MeasureOverride
.
Mi manca qualcosa nel modo in cui questo sistema dovrebbe funzionare? La mia comprensione è che chiamare Measure
semplicemente fa in modo che il bambino valuti il suo DesiredSize
in base al valore disponibile fornito, e non dovrebbe influire sulla sua dimensione finale effettiva.
Ecco il mio codice completo (il Pannello, btw, ha lo scopo di organizzare i bambini nel modo più efficiente in termini di spazio, dando meno spazio alle file che non ne hanno bisogno e dividendo lo spazio rimanente in modo uniforme tra il resto-- al momento supporta solo l'orientamento verticale, ma ho intenzione di aggiungere orizzontale una volta che funziona correttamente):
Modifica: Grazie per le risposte. Li esaminerò più da vicino in un momento. Tuttavia, mi permetta di chiarire come funziona il mio algoritmo previsto poiché non l'ho spiegato.
Prima di tutto, il modo migliore per pensare a ciò che sto facendo è immaginare una griglia con ogni riga impostata su *. Questo divide lo spazio in modo uniforme. Tuttavia, in alcuni casi l'elemento di una riga potrebbe non aver bisogno di tutto questo spazio; se questo è il caso, voglio prendere qualsiasi spazio rimanente e darlo a quelle file che potrebbero utilizzare lo spazio. Se nessuna riga ha bisogno di spazio aggiuntivo, cerco semplicemente di distanziare le cose in modo uniforme (è quello che sta facendo extraSpace
, è solo per quel caso).
Lo faccio in due passaggi. Il punto finale del primo passaggio è determinare la "dimensione normale" finale di una riga - vale a dire. la dimensione delle righe che verranno ridotte (data una dimensione inferiore alla dimensione desiderata). Lo faccio passando dall'elemento più piccolo a quello più grande e regolando le dimensioni normali calcolate ad ogni passaggio, aggiungendo lo spazio rimanente da ciascun piccolo oggetto a ciascun oggetto più grande successivo fino a quando non ci sono più elementi "adatti" e quindi interrotti.
Nel passaggio successivo, utilizzo questo valore normale per determinare se un articolo può essere adattato o meno, semplicemente prendendo lo Min
della dimensione normale con la dimensione desiderata dell'articolo.
(ho anche cambiato il metodo anonimo a una funzione lambda per semplicità.)
Edit 2: mio algoritmo sembra funzionare grande a determinare la dimensione corretta dei bambini. Tuttavia, i bambini non stanno accettando le loro dimensioni. Ho provato Goblin's suggerito MeasureOverride
passando PositiveInfinity e restituendo Size (0,0), ma questo fa sì che i bambini si disegnino come se non ci fossero limiti di spazio. La parte che non è ovvia su questo è che sta accadendo a causa di una chiamata a Measure
. La documentazione di Microsoft su questo argomento non è del tutto chiara, dato che ho letto più volte su ogni classe e descrizione della proprietà. Tuttavia, ora è chiaro che chiamare Measure
influenza effettivamente il rendering del figlio, quindi tenterò di suddividere la logica tra le due funzioni come suggerito da BladeWise.
Risolto !! Ho funzionato.Come sospettavo, dovevo chiamare Measure() due volte su ogni bambino (una volta per valutare DesiredSize e un secondo per dare ad ogni bambino la giusta altezza). Mi sembra strano che il layout di WPF sia progettato in un modo così strano, in cui è diviso in due passaggi, ma il passaggio Misura in realtà fa due cose: misure e dimensioni bambini e il passaggio di Arrange non fa altro posiziona fisicamente i bambini. Molto bizzarro.
Inserirò il codice di lavoro in fondo.
In primo luogo, il (rotto) codice originale:
protected override Size MeasureOverride(Size availableSize) {
foreach (UIElement child in Children)
child.Measure(availableSize);
return availableSize;
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) {
double extraSpace = 0.0;
var sortedChildren = Children.Cast<UIElement>().OrderBy<UIElement, double>(child=>child.DesiredSize.Height;);
double remainingSpace = finalSize.Height;
double normalSpace = 0.0;
int remainingChildren = Children.Count;
foreach (UIElement child in sortedChildren) {
normalSpace = remainingSpace/remainingChildren;
if (child.DesiredSize.Height < normalSpace) // if == there would be no point continuing as there would be no remaining space
remainingSpace -= child.DesiredSize.Height;
else {
remainingSpace = 0;
break;
}
remainingChildren--;
}
// this is only for cases where every child item fits (i.e. the above loop terminates normally):
extraSpace = remainingSpace/Children.Count;
double offset = 0.0;
foreach (UIElement child in Children) {
//child.Measure(new Size(finalSize.Width, normalSpace));
double value = Math.Min(child.DesiredSize.Height, normalSpace) + extraSpace;
child.Arrange(new Rect(0, offset, finalSize.Width, value));
offset += value;
}
return finalSize;
}
Ed ecco il codice di lavoro:
double _normalSpace = 0.0;
double _extraSpace = 0.0;
protected override Size MeasureOverride(Size availableSize) {
// first pass to evaluate DesiredSize given available size:
foreach (UIElement child in Children)
child.Measure(availableSize);
// now determine the "normal" size:
var sortedChildren = Children.Cast<UIElement>().OrderBy<UIElement, double>(child => child.DesiredSize.Height);
double remainingSpace = availableSize.Height;
int remainingChildren = Children.Count;
foreach (UIElement child in sortedChildren) {
_normalSpace = remainingSpace/remainingChildren;
if (child.DesiredSize.Height < _normalSpace) // if == there would be no point continuing as there would be no remaining space
remainingSpace -= child.DesiredSize.Height;
else {
remainingSpace = 0;
break;
}
remainingChildren--;
}
// there will be extra space if every child fits and the above loop terminates normally:
_extraSpace = remainingSpace/Children.Count; // divide the remaining space up evenly among all children
// second pass to give each child its proper available size:
foreach (UIElement child in Children)
child.Measure(new Size(availableSize.Width, _normalSpace));
return availableSize;
}
protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize) {
double offset = 0.0;
foreach (UIElement child in Children) {
double value = Math.Min(child.DesiredSize.Height, _normalSpace) + _extraSpace;
child.Arrange(new Rect(0, offset, finalSize.Width, value));
offset += value;
}
return finalSize;
}
Esso non può essere super-efficiente quello che con dover chiamare Measure
due volte (e l'iterazione Children
tre volte), ma funziona. Sarebbe gradita qualsiasi ottimizzazione dell'algoritmo.
Ancora una cosa: io non sono preoccupato per un'eccezione di tornare 'availableSize' in' MeasureOverride' perché il pannello non ha senso all'interno di un infinito spazio come un 'ScrollViewer'. Ha senso solo quando lo spazio è confinato. – devios1
Anche io ho questo problema. Qualsiasi modifica delle dimensioni all'interno di ArrangeOverride è invece solo per le clip. Non è quello che mi aspettavo leggendo la documentazione o qualsiasi libro del WPF. Inoltre, è stato necessario chiamare Measure due volte per risolvere questo problema. Grazie. –