Próbuję napisać niestandardowy Panel
klasę dla WPF, nadrzędnymi MeasureOverride
i ArrangeOverride
, ale jednocześnie jest to głównie pracy mam przeżywa jeden dziwny problem nie mogę wyjaśnić.Niestandardowy automatycznego doboru WPF klasy panelu
W szczególności po tym, jak zadzwonić Arrange
na moich elementów podrzędnych w ArrangeOverride
po zastanawianie się, jakie są ich rozmiary powinny być, nie są one wielkości do wielkości daję im, i wydaje się być wielkości do wielkości przekazanego do ich Measure
metoda wewnątrz MeasureOverride
.
Czy brakuje mi czegoś, jak ten system powinien działać? Rozumiem, że wywołanie Measure
po prostu powoduje, że dziecko ocenia jego DesiredSize
na podstawie dostarczonej availableSize i nie powinno wpływać na jego rzeczywisty ostateczny rozmiar.
Oto mój pełny kod (Panel, btw, jest przeznaczony do zorganizowania dzieci w najbardziej efektywny sposób, dając mniej miejsca dla wierszy, które go nie potrzebują i dzieląc pozostałą przestrzeń równomiernie między resztę-- obecnie obsługuje tylko orientację pionową, ale planuję dodać poziomą, gdy tylko poprawi się jej działanie):
Edytuj: Dzięki za odpowiedzi. Za chwilę przyjrzę im się bliżej. Jednak pozwól mi wyjaśnić, jak działa mój zamierzony algorytm, ponieważ tego nie wyjaśniałem.
Po pierwsze, najlepszym sposobem na myślenie o tym, co robię, jest wyobrazić sobie Grid z każdym rzędem ustawionym na *. Równomiernie dzieli przestrzeń. Jednak w niektórych przypadkach element z rzędu może nie potrzebować całej tej przestrzeni; jeśli tak jest, chcę wziąć resztkę miejsca i oddać je do tych rzędów, które mogłyby wykorzystać tę przestrzeń. Jeśli żadne wiersze nie wymagają dodatkowej przestrzeni, staram się równomiernie rozmieścić rzeczy (to robi extraSpace
, tylko w tym przypadku).
Robię to w dwóch podaniach. Ostatecznym punktem pierwszego przejścia jest określenie ostatecznego "normalnego rozmiaru" rzędu - tj. rozmiar rzędów, które zostaną zmniejszone (biorąc pod uwagę rozmiar mniejszy niż jego pożądany rozmiar). Robię to, przechodząc przez najmniejszy przedmiot do największego i dostosowując obliczony normalny rozmiar na każdym kroku, dodając resztki przestrzeni od każdego małego przedmiotu do każdego kolejnego większego przedmiotu, aż żadna pozycja "nie pasuje", a następnie pęknie.
W następnym przebiegu używam tej wartości normalnej do określenia, czy dany element może się zmieścić, czy nie, po prostu pobierając Min
normalnego rozmiaru z pożądanym rozmiarem elementu.
(ja także zmieniła sposób anonimowy do funkcji lambda dla uproszczenia).
Edycja 2: Mój algorytm wydaje się doskonale przy określaniu właściwego rozmiaru dzieci. Jednak dzieci po prostu nie akceptują swojego rozmiaru. Próbowałem Goblina zasugerować MeasureOverride
, przekazując PositiveInfinity i zwracając Size (0,0), ale to powoduje, że dzieci rysują się tak, jakby w ogóle nie było żadnych ograniczeń przestrzennych. Częścią tego nie jest oczywiste, że dzieje się tak z powodu połączenia z numerem Measure
. Dokumentacja Microsoftu na ten temat nie jest wcale jasna, ponieważ kilkakrotnie przeczytałem każdą klasę i opis właściwości. Jednak teraz jest jasne, że wywołanie Measure
w rzeczywistości wpływa na renderowanie elementu potomnego, więc spróbuję podzielić logikę między dwie funkcje, jak sugeruje BladeWise.
Rozwiązany! Mam to działa.Jak podejrzewałem, musiałem dwukrotnie wywołać funkcję Measure() na każdym dziecku (jeden raz, aby ocenić DesiredSize, a drugi, aby nadać dziecku odpowiednią wysokość). Wydaje mi się dziwne, że układ w WPF byłby zaprojektowany w tak dziwny sposób, w którym jest podzielony na dwie części, ale Przełęcz Miarka faktycznie robi dwie rzeczy: miary i rozmiarów dzieci i przepustka Rozmieść nie ma obok niczego oprócz faktycznie fizycznie pozycjonują dzieci. Bardzo dziwne.
Opublikuję działający kod u dołu.
pierwsze, oryginalne (uszkodzony) Kod:
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;
}
A oto kod roboczych:
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;
}
To nie może być super-wydajny, co z konieczności wzywania Measure
dwukrotnie (i iteracji Children
trzy razy), ale działa. Należy docenić wszelkie optymalizacje algorytmu.
jeszcze jedno: Nie martwię się o wyjątek od powrotu 'availableSize' w' MeasureOverride' ponieważ panel ma sensu wewnątrz nieskończona przestrzeń jak "ScrollViewer". Ma sens tylko wtedy, gdy przestrzeń jest ograniczona. – devios1
Ja też mam ten problem. Każda zmiana rozmiaru w ArrangeOverride po prostu klipuje. Nie tego oczekiwałem od czytania dokumentacji lub jakichkolwiek książek WPF. Również uciekam się do wywołania Measure dwa razy, aby rozwiązać ten problem. Dzięki. –