Udostępnij przez


BoxPanel, przykładowy panel niestandardowy

Dowiedz się, jak pisać kod dla niestandardowej klasy Panel, implementując metody ArrangeOverride oraz MeasureOverride, i używając właściwości Children.

ważne interfejsy API: Panel, ArrangeOverride,MeasureOverride

Przykładowy kod przedstawia niestandardową implementację panelu, ale nie poświęcamy dużo czasu na wyjaśnienie koncepcji układu mających wpływ na sposób dostosowywania panelu dla różnych scenariuszy układu. Jeśli potrzebujesz więcej informacji na temat koncepcji układu i sposobu ich zastosowania do określonego scenariusza, zobacz omówienie paneli niestandardowych XAML.

Panel to obiekt, który zapewnia zachowanie układu dla elementów podrzędnych, które zawiera, gdy system układu XAML jest uruchamiany, a interfejs użytkownika aplikacji jest renderowany. Możesz zdefiniować niestandardowe panele dla układu XAML, tworząc klasę niestandardową z klasy Panel . Zapewniasz zachowanie panelu, przesłaniając metody ArrangeOverride i MeasureOverride, dostarczając logikę, która mierzy i rozmieszcza elementy podrzędne. Ten przykład pochodzi z Panel. Kiedy zaczynasz od Panel, metody ArrangeOverride i MeasureOverride nie mają wstępnego zachowania. Kod udostępnia bramę, za pomocą której elementy podrzędne stają się znane systemowi układu XAML i są renderowane w interfejsie użytkownika. Tak więc naprawdę ważne jest, aby kod uwzględniał wszystkie elementy podrzędne i był zgodny ze wzorcami oczekiwanymi przez system układu.

Twój scenariusz układu

Podczas definiowania panelu niestandardowego definiujesz scenariusz układu.

Scenariusz układu jest wyrażany za pomocą następujących elementów:

  • Co zrobi panel, gdy ma elementy podrzędne
  • Gdy panel ma ograniczenia dotyczące własnego miejsca
  • Jak logika panelu określa wszystkie wymiary, rozmieszczenie, pozycje i rozmiary, które ostatecznie skutkują renderowaniem układu interfejsu użytkownika elementów dzieci

Mając to na uwadze, pokazany tutaj BoxPanel dotyczy określonego scenariusza. W interesie utrzymania kodu przede wszystkim w tym przykładzie nie wyjaśnimy jeszcze scenariusza i zamiast tego skoncentrujemy się na krokach potrzebnych i wzorcach kodowania. Jeśli chcesz najpierw dowiedzieć się więcej na temat scenariusza, przejdź do sekcji "Scenariusz dla BoxPanel", a następnie wróć do kodu.

Zacznij od wyprowadzenia z panelu

Zacznij od stworzenia niestandardowej klasy z Panel. Prawdopodobnie najprostszym sposobem jest zdefiniowanie oddzielnego pliku kodu dla tej klasy przy użyciu Dodajnowy elementKlasy opcji menu kontekstowego Eksploratora rozwiązań w programie Microsoft Visual Studio. Nadaj klasie nazwę (i plikowi) BoxPanel.

Plik szablonu dla klasy nie zaczyna się od wielu przy użyciu instrukcji, ponieważ nie jest przeznaczony specjalnie dla aplikacji systemu Windows. Najpierw dodaj przy użyciu instrukcji. Plik szablonu rozpoczyna się również od kilku przy użyciu instrukcji, których prawdopodobnie nie potrzebujesz i można je usunąć. Poniżej przedstawiono sugerowaną listę przy użyciu instrukcji, które mogą rozwiązać typy, których potrzebujesz dla typowego niestandardowego kodu panelu:

using System;
using System.Collections.Generic; // if you need to cast IEnumerable for iteration, or define your own collection properties
using Windows.Foundation; // Point, Size, and Rect
using Windows.UI.Xaml; // DependencyObject, UIElement, and FrameworkElement
using Windows.UI.Xaml.Controls; // Panel
using Windows.UI.Xaml.Media; // if you need Brushes or other utilities

Teraz, gdy możesz rozwiązać Panel, ustaw ją jako klasę bazową BoxPanel. Udostępnij BoxPanel publicznie.

public class BoxPanel : Panel
{
}

Na poziomie klasy zdefiniuj niektóre i dwukrotnie wartości, które będą współużytkowane przez kilka funkcji logiki, ale które nie będą musiały być uwidocznione jako publiczny interfejs API. W tym przykładzie są one nazwane: maxrc, , rowcountcolcount, cellwidth, cellheight, , . maxcellheightaspectratio

Po wykonaniu tej czynności pełny plik kodu wygląda następująco (usunięcie komentarzy dotyczących using, teraz gdy wiesz, dlaczego je mamy):

using System;
using System.Collections.Generic;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;

public class BoxPanel : Panel 
{
    int maxrc, rowcount, colcount;
    double cellwidth, cellheight, maxcellheight, aspectratio;
}

Od teraz będziemy pokazywać po jednej definicji elementu, czy to przesłonięcie metody, czy coś wspomagającego, jak właściwość zależności. Można je dodać do szkieletu powyżej w dowolnej kolejności.

PrzeciążenieMiary

protected override Size MeasureOverride(Size availableSize)
{
    // Determine the square that can contain this number of items.
    maxrc = (int)Math.Ceiling(Math.Sqrt(Children.Count));
    // Get an aspect ratio from availableSize, decides whether to trim row or column.
    aspectratio = availableSize.Width / availableSize.Height;

    // Now trim this square down to a rect, many times an entire row or column can be omitted.
    if (aspectratio > 1)
    {
        rowcount = maxrc;
        colcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc;
    } 
    else 
    {
        rowcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc;
        colcount = maxrc;
    }

    // Now that we have a column count, divide available horizontal, that's our cell width.
    cellwidth = (int)Math.Floor(availableSize.Width / colcount);
    // Next get a cell height, same logic of dividing available vertical by rowcount.
    cellheight = Double.IsInfinity(availableSize.Height) ? Double.PositiveInfinity : availableSize.Height / rowcount;
           
    foreach (UIElement child in Children)
    {
        child.Measure(new Size(cellwidth, cellheight));
        maxcellheight = (child.DesiredSize.Height > maxcellheight) ? child.DesiredSize.Height : maxcellheight;
    }
    return LimitUnboundedSize(availableSize);
}

Wymaganym wzorcem implementacji MeasureOverride jest pętla przez każdy element w Panel.Children. Zawsze należy wywołać metodę Measure dla każdego z tych elementów. Measure ma parametr typu Size. Rozmiar, który tutaj przekazujesz, to rozmiar, który panel zobowiązuje się zapewnić konkretnemu elementowi podrzędnemu. Dlatego zanim będzie można wykonać pętlę i rozpocząć wywoływanie Measure, musisz wiedzieć, ile miejsca może poświęcić każda komórka. Z samej metody MeasureOverride masz wartość availableSize. Jest to rozmiar używany przez obiekt nadrzędny panelu w przypadku wywołaniamiary , który był wyzwalaczem dla tej MeasureOverride wywoływanej w pierwszej kolejności. W związku z tym typową logiką jest opracowanie schematu, w którym każdy element podrzędny dzieli przestrzeń ogólnego availableSize. Następnie należy przekazać każdy podział rozmiaru do Miara każdego elementu podrzędnego.

Sposób, w jaki BoxPanel dzieli rozmiar, jest stosunkowo prosty: dzieli przestrzeń na liczbę pól, która jest w dużej mierze kontrolowana przez liczbę elementów. Pola mają rozmiar na podstawie liczby wierszy i kolumn oraz dostępnego rozmiaru. Czasami jeden wiersz lub kolumna w kwadracie nie jest potrzebny, więc jest pomijany, a panel staje się prostokątem, a nie kwadratem pod względem proporcji wierszy : kolumn. Aby uzyskać więcej informacji na temat sposobu, w jaki doszliśmy do tej logiki, przewiń do "Scenariusz dla BoxPanel".

Cóż więc oznacza uchwalenie ustawy? Ustawia wartość dla właściwości tylko do odczytu DesiredSize na każdym elemencie, gdzie wywołano Measure. Posiadanie wartości DesiredSize jest prawdopodobnie ważne w procesie rozmieszczania, ponieważ DesiredSize określa, jaki może lub powinien być rozmiar podczas rozmieszczania i w końcowym renderowaniu. Nawet jeśli nie używasz żądanego rozmiaru we własnej logice, system nadal go potrzebuje.

Można użyć tego panelu, gdy składnik wysokości availableSize jest niezwiązany. Jeśli tak jest, panel nie ma znanej wysokości do podzielenia. W takim przypadku logika etapu pomiaru informuje każde dziecko, że nie ma jeszcze ograniczonej wysokości. Przekazuje rozmiar do wywołania miary dla elementów podrzędnych, gdzie Size.Height jest nieskończony. To jest legalne. Po wywołaniu Measure, logika zakłada, że DesiredSize jest ustawione jako minimum z dwóch wartości: to, co zostało przekazane do Measure, lub naturalny rozmiar tego elementu określony przez czynniki takie jak jawnie ustawiona Height i Width.

Uwaga / Notatka

Wewnętrzna logika StackPanel ma również to zachowanie: StackPanel przekazuje na elementach podrzędnych nieskończoną wartość wymiaru do metody Measure, co oznacza, że nie ma ograniczenia dla elementów podrzędnych w wymiarze orientacji. StackPanel zazwyczaj dopasowuje swój rozmiar dynamicznie, aby pomieścić wszystkie dzieci w stosie, który rośnie w tym wymiarze.

Jednak sam panel nie może zwrócić Size z nieskończoną wartością z MeasureOverride; powoduje wyjątek podczas układania. Dlatego częścią logiki jest znalezienie maksymalnej wysokości, jakiej żąda jakiekolwiek dziecko, i ustawienie tej wysokości jako wysokości komórki, w przypadku gdy nie wynika to już z ograniczeń rozmiaru samego panelu. Oto funkcja pomocnicza LimitUnboundedSize, do której odniesiono się w poprzednim kodzie, która następnie pobiera tę maksymalną wysokość komórki i używa jej, aby określić skończoną wysokość panelu do zwrócenia, a także zapewnia, że cellheight jest liczbą skończoną przed zainicjowaniem przekazywania rozmieszczania:

// This method limits the panel height when no limit is imposed by the panel's parent.
// That can happen to height if the panel is close to the root of main app window.
// In this case, base the height of a cell on the max height from desired size
// and base the height of the panel on that number times the #rows.
Size LimitUnboundedSize(Size input)
{
    if (Double.IsInfinity(input.Height))
    {
        input.Height = maxcellheight * colcount;
        cellheight = maxcellheight;
    }
    return input;
}

RozmieszczaniePrzesłonięcia

protected override Size ArrangeOverride(Size finalSize)
{
     int count = 1;
     double x, y;
     foreach (UIElement child in Children)
     {
          x = (count - 1) % colcount * cellwidth;
          y = ((int)(count - 1) / colcount) * cellheight;
          Point anchorPoint = new Point(x, y);
          child.Arrange(new Rect(anchorPoint, child.DesiredSize));
          count++;
     }
     return finalSize;
}

Niezbędny wzorzec implementacji ArrangeOverride to pętla między poszczególnymi elementami w Panel.Children. Zawsze należy wywołać metodę Rozmieść dla każdego z tych elementów.

Zwróć uwagę, że nie ma tak wielu obliczeń, jak w MeasureOverride; to typowe. Rozmiar dzieci jest już znany dzięki własnej logice MeasureOverride panelu lub z wartości DesiredSize każdego dziecka ustalonej podczas etapu pomiaru. Jednak nadal musimy zdecydować, gdzie w panelu będzie pojawiał się każdy element. W typowym panelu każde dziecko powinno być renderowane na innej pozycji. Panel, który tworzy nakładające się elementy, nie jest pożądany w typowych scenariuszach (choć nie jest wykluczone, że można zaprojektować panele z celowymi nakładkami, jeśli jest to naprawdę zamierzony scenariusz).

Ten panel jest rozmieszczany według koncepcji wierszy i kolumn. Liczba wierszy i kolumn została już obliczona (to było konieczne do pomiarów). Teraz kształt wierszy i kolumn oraz znane rozmiary każdej komórki przyczyniają się do logiki definiowania pozycji renderowania () anchorPointdla każdego elementu, który zawiera ten panel. Ten Point, wraz z Size już znanym z pomiaru, są używane jako dwa składniki, które tworzą prostokąt Rect. Rect jest typem danych wejściowych dla rozmieszczanie.

Panele czasami muszą przycinać swoją zawartość. Jeśli tak, przycięty rozmiar jest rozmiarem, który znajduje się w DesiredSize, ponieważ logika pomiaru ustawia go jako minimalną wartość przekazaną do Measurelub w oparciu o inne czynniki naturalnego rozmiaru. Więc zazwyczaj nie trzeba specjalnie sprawdzać przycinania podczas Rozmieszczanie; przycinanie odbywa się po prostu na podstawie przekazywania wartości DesiredSize do każdego wywołania Rozmieszczanie.

Nie zawsze potrzebujesz liczby podczas przechodzenia przez pętlę, jeśli wszystkie informacje potrzebne do zdefiniowania pozycji renderowania są znane w inny sposób. Na przykład w logice układu kanwy pozycja w kolekcji Podrzędne nie ma znaczenia. Wszystkie informacje potrzebne do pozycjonowania każdego elementu w Canvas są znane przez odczytywanie Canvas.Left i Canvas.Top wartości elementów podrzędnych w ramach logiki rozmieszczania. Logika BoxPanel potrzebuje liczby do porównania z kolcount, aby wiedzieć, kiedy rozpoczynać nowy wiersz i przestawiać wartość y.

Zazwyczaj dane wejściowe finalSize i rozmiar zwracane z implementacji ArrangeOverride są takie same. Aby uzyskać więcej informacji na temat tego, dlaczego, zobacz sekcję "ArrangeOverride" (Omówienie paneli niestandardowych XAML).

Uściślenie: kontrolowanie liczby wierszy a liczby kolumn

Możesz skompilować ten panel i użyć go tak samo jak teraz. Dodamy jednak jeszcze jedno uściślenie. W pokazanym kodzie logika umieszcza dodatkowy wiersz lub kolumnę po najdłuższej stronie zgodnie z proporcjami obrazu. Ale w celu większej kontroli nad kształtami komórek, może być pożądane wybranie układu 4x3 zamiast 3x4, nawet jeśli współczynnik proporcji panelu jest "portretowy". Dodamy więc opcjonalną właściwość zależności, którą konsument panelu może ustawić, aby kontrolować to zachowanie. Oto definicja właściwości zależności, która jest bardzo podstawowa:

// Property
public Orientation Orientation
{
    get { return (Orientation)GetValue(OrientationProperty); }
    set { SetValue(OrientationProperty, value); }
}

// Dependency Property Registration
public static readonly DependencyProperty OrientationProperty =
        DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(BoxPanel), new PropertyMetadata(null, OnOrientationChanged));

// Changed callback so we invalidate our layout when the property changes.
private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
    if (dependencyObject is BoxPanel panel)
    {
        panel.InvalidateMeasure();
    }
}

Poniżej przedstawiono, jak sposób używania Orientation wpływa na logikę miary w MeasureOverride. Naprawdę wszystko, co robi, to zmiana sposobu, w jaki rowcount i colcount są wyprowadzane z maxrc i rzeczywistego współczynnika proporcji, co powoduje odpowiednie różnice w rozmiarach każdej komórki. Gdy Orientation jest pionowa (wartość domyślna), odwraca wartość rzeczywistego współczynnika proporcji przed użyciem go dla liczby wierszy i kolumn dla naszego układu prostokąta "pionowy".

// Get an aspect ratio from availableSize, decides whether to trim row or column.
aspectratio = availableSize.Width / availableSize.Height;

// Transpose aspect ratio based on Orientation property.
if (Orientation == Orientation.Vertical) { aspectratio = 1 / aspectratio; }

Scenariusz dla boxPanel

Konkretny scenariusz dla BoxPanel polega na tym, że jest to panel, w którym jednym z głównych czynników określających sposób dzielenia przestrzeni jest znajomość liczby elementów podrzędnych i podzielenie znanego dostępnego miejsca dla panelu. Panele mają z natury kształt prostokątny. Wiele paneli działa przez podzielenie tego prostokąta na kolejne prostokąty; to jest to, co Grid robi dla swoich komórek. W przypadku Gridrozmiar komórek jest ustawiany przez ColumnDefinition i RowDefinition, a elementy deklarują dokładną komórkę, do której przechodzą, za pomocą Grid.Row i Grid.Column właściwości dołączonych. Uzyskanie dobrego układu z Grid zwykle wymaga wcześniejszej znajomości liczby elementów podrzędnych, dzięki czemu istnieje wystarczająca liczba komórek, a każdy element podrzędny ustawia dołączone właściwości, aby zmieścić się we własnej komórce.

Ale co zrobić, jeśli liczba dzieci jest dynamiczna? To z pewnością możliwe; kod aplikacji może dodawać elementy do kolekcji w odpowiedzi na dowolny dynamiczny warunek w czasie wykonywania, który jest wystarczająco istotny, aby uzasadniać aktualizację interfejsu użytkownika. Jeśli używasz powiązania danych do tworzenia kopii zapasowych kolekcji/obiektów biznesowych, pobieranie takich aktualizacji i aktualizowanie interfejsu użytkownika jest obsługiwane automatycznie, dlatego często jest to preferowana technika (zobacz Powiązanie danych w głębi systemu).

Ale nie wszystkie scenariusze aplikacji nadają się do powiązania danych. Czasami należy utworzyć nowe elementy interfejsu użytkownika w czasie wykonywania i uwidocznić je. BoxPanel jest przeznaczony dla tego scenariusza. Zmienna liczba elementów podrzędnych nie stanowi problemu dla BoxPanel, ponieważ korzysta z liczby elementów podrzędnych w obliczeniach oraz dostosowuje zarówno istniejące, jak i nowe elementy podrzędne do nowego układu, zapewniając ich pełne dopasowanie.

Zaawansowany scenariusz rozszerzania BoxPanel dalej (nie pokazany w tym miejscu) może pomieścić dynamiczne dzieci i użyć DesiredSize dziecka jako silniejszy czynnik rozmiaru poszczególnych komórek. W tym scenariuszu mogą być używane różne rozmiary wierszy lub kolumn albo kształty nie będące siatką, co zmniejsza "marnowaną" przestrzeń. Wymaga to strategii, jak wiele prostokątów o różnych rozmiarach i współczynnikach proporcji może mieścić się w prostokącie zawierającym, zarówno pod względem estetyki, jak i minimalizacji rozmiaru. BoxPanel nie robi tego; używa prostszej techniki dzielenia przestrzeni. BoxPaneltechniką jest określenie najmniejszej liczby kwadratowej, która jest większa niż liczba dzieci. Na przykład 9 elementów zmieści się w kwadratu 3x3. 10 elementów wymaga kwadratu 4x4. Jednak często można zmieścić elementy, choć nadal usuwasz jeden wiersz lub kolumnę kwadratu początkowego, aby zaoszczędzić miejsce. W przykładzie, w którym count=10, co odpowiada prostokątowi 4x3 lub 3x4.

Możesz się zastanawiać, dlaczego panel nie wybierze 5x2 na 10 elementów, ponieważ dokładnie pasuje do liczby elementów. Jednak w praktyce panele mają kształt prostokątów i rzadko mają wyraźnie zorientowane proporcje. Technika najmniejszych kwadratów jest sposobem na dostosowanie logiki dopasowywania rozmiaru, aby dobrze pracować z typowymi kształtami w układzie i aby uniknąć określania rozmiaru, gdzie kształty komórek uzyskują dziwne współczynniki proporcji.

Referencja

Pojęcia