Artykuł

freeimages.com freeimages.com
maj 29 2015
0

Efektywne tworzenie GRIDów w XAMLu

XAML - czyli tzw. rozszerzalny język znaczników, jest motorem napędowym wielu technologi stworzonych przez Microsoft. Spotkać go można m.in. w Silverlighcie, WPFie, czy też katowanych ostatnio przeze mnie na blogu aplikacjach uniwersalnych. I choć między tymi technologiami jest sporo istotnych różnic, to w praktyce są one częściowo ze sobą zbieżne właśnie dzięki XAMLowi.

Tworząc frontend w WPF, można bardzo szybko złapać o co chodzi w aplikacjach uniwersalnych - i vice versa. Oczywiście poszczególne kontrolki mogą się różnić pomiędzy konkretnymi implementacjami, ale jest też kilka takich elementów, które albo są takie same, albo zachowują się bardzo podobnie. Do tej właśnie grupy można zaliczyć tytułowego GRIDa, który w praktyce jest moim ulubionym kontenerem do budowania efektywnych layoutów. W dzisiejszym tekście postaram się Wam przedstawić kluczowe jego elementy, które można wykorzystać w codziennej pracy.

Grid - podstawy

Zasadniczą ideą Grida jest tworzenie układów przypominających siatkę. W praktyce układ ten bardzo często przypomina HTMLową tabelę. W nasze ręce dostajemy więc kontrolkę, w której możemy tworzyć kolumny i wiersze. Do Grida możemy wrzucić dowolną ilość elementów pozycjonowanych za pomocą tych dwóch właściwości.

Jeśli x elementów ma taką samą pozycję (ten sam numer wiersza i numer kolumny), to o tym co jest widoczne na samej górze decyduje kolejność ich dodania. Ta informacja może być bardzo przydatna w momencie gdy w tworzonej przez nas aplikacji, chcemy zrobić coś na kształt placeholdera. W ten sposób możemy umieścić w komórce domyślny obrazek, który później jest przykrywany obrazkiem zwróconym z sieci.

Ważną informacją jest również fakt, że w Gridzie możemy zagnieżdżać kolejne Gridy i nie ma w tym przypadku żadnych ograniczeń. Najważniejszą zaletą tego układu jest fakt, że wewnątrz kontrolki Grid, możemy umieścić dowolną ilość dokładnie wypozycjonowanych elementów;-)

Pierwszy Grid

Grid dostępny jest w standardowej przestrzeni nazw, dlatego też przy jego użyciu nie musimy stosować żadnych prefiksów. Użycie kontrolki w podstawowej postaci jest bardzo proste:

<Grid>
    <Button Content="1" />
</Grid>

W chwili obecnej tak naprawdę nie zrobiliśmy nic ciekawego - umieściliśmy po prostu przycisk wewnątrz układu. Możemy zaszaleć jeszcze bardziej i wrzucić do środka trzy przyciski:

<Grid>
    <Button Content="1" />
    <Button Content="2" />
    <Button Content="3" />
</Grid>

Nasza kontrolka jest również w stanie obsłużyć taką sytuację. Po uruchomieniu aplikacji, szybko okaże się jednak że coś tu nie gra. Przyczyną naszego problemu jest fakt, że nie powiedzieliśmy Gridowi, gdzie ma umieścić kolejne przyciski. Ten założył, że mają one wszystkie się znajdować w domyślnej lokalizacji (zadziałał tutaj przypadek, który opisywałem w poprzedniej sekcji), dlatego też każdy kolejny przycisk przykrył przycisk poprzedni. Na szczęście problem ten można łatwo rozwiązać wykorzystując wbudowane w układ AttachedProperties.

Grid definiuje 4 właściwości tego typu, które można wykorzystać w umieszczonych wewnątrz siatki kontrolkach:

  • Grid.Column - określa numer kolumny - domyślnie 0
  • Grid.ColumnSpan - określa ile kolumn ma zając określony element począwszy od miejsca określonego przez Grid.Column. Domyślna wartość tej właściwości to 1
  • Grid.Row - określa numer kolumny - domyślnie 0
  • Grid.RowSpan - określa ile kolumn ma zając określony element począwszy od miejsca określonego przez Grid.Row. Domyślna wartość tej właściwości to 1

By móc korzystać z kolumn i wierszy, należy je najpierw zdefiniować co czynimy za pomocą właściwości Grida RowDefinitions oraz ColumnDefinitions. Przykładowe ich użycie w połączeniu z przedstawionymi wyżej AttachedProperties, może wyglądać następująco:

<Grid>
    <Grid.Resources>
        <Style TargetType="Button">
            <Setter Property="HorizontalAlignment" Value="Stretch" />
            <Setter Property="VerticalAlignment" Value="Stretch" />
            <Setter Property="Margin" Value="10,0" />
        </Style>
    </Grid.Resources>
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
        <RowDefinition />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition />
        <ColumnDefinition />
        <ColumnDefinition />
    </Grid.ColumnDefinitions>

    <Button Content="1" />
    <Button Content="2" Grid.Column="1" />
    <Button Content="3" Grid.Column="2" />

    <Button Content="4" Grid.Row="1" />
    <Button Content="5" Grid.Row="1" Grid.Column="1" />
    <Button Content="6" Grid.Row="1" Grid.Column="2" />
        
    <Button Content="7" Grid.Row="2" />
    <Button Content="8" Grid.Row="2" Grid.Column="1" />
    <Button Content="9" Grid.Row="2" Grid.Column="2" Grid.RowSpan="2" />
        
    <Button Content="0" Grid.Row="3" Grid.ColumnSpan="2" />
</Grid>

Wizualnie zaś uzyskamy efekt jak na poniższym obrazku. Wykorzystaliśmy tutaj wszystkie omówione wcześniej właściwości oraz wartości domyślne.

Wysokość wiersza i szerokość kolumn

Powyższa sekcja nie zawierała jeszcze jednej ważnej informacji - w przykładzie tym zastosowałem domyślne szerokości kolumn (*) oraz wysokości wierszy (*). Układ zbudowany w ten sposób, pozwala na tworzenie skalowalnych rozwiązań, które będą dopasowywały się do rozdzielczości ekranu urządzenia docelowego. W naszym przypadku mieliśmy siatkę 4 wiersze na 3 kolumny. Ponieważ nie określiliśmy inaczej, XAML obliczył wysokość naszego Grida na 4* (każdy wiersz miał jedną gwiazdkę wysokości), a szerokość na 3* (analogicznie - każda kolumna miała szerokość jednej gwiazdki). Jeśli ktoś woli przeliczać to na procenty, to można powiedzieć że każdy wiersz zajął 25% dostępnej w Gridzie wysokości, natomiast kolumna 33,3...% szerokości. Oczywiście definicję wierszy można by zapisać bardziej wymownie, podkreślając wartości domyślne:

<Grid>
	...
	<Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
	...
</Grid>

Poniższy zapis również pozwoli na uzyskanie podobnego efektu. Czy gwiazdki zapisane w takiej postaci, nie przypominają Wam on odrobinę starych, dobrych procentów;-)?

<Grid>
	...
	<Grid.RowDefinitions>
        <RowDefinition Height="25*" />
        <RowDefinition Height="25*" />
        <RowDefinition Height="25*" />
        <RowDefinition Height="25*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="33.3*" />
        <ColumnDefinition Width="33.3*" />
        <ColumnDefinition Width="33.3*" />
    </Grid.ColumnDefinitions>
	...
</Grid>

Oczywiście możemy zmienić proporcje. Jeśli chcemy żeby np. żeby pierwszy wiersz był dwukrotnie wyższy niż pozostałe, to musimy doprowadzić właściwość RowDefinitions do następującej postaci:

<Grid>
	...
	<Grid.RowDefinitions>
        <RowDefinition Height="2*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
	...
</Grid>

Możemy również definiować wartości za pomocą pikseli, a także korzystając z predefiniowanej wartości Auto. Element który został określony przy pomocy tej ostatniej, zajmie dokładnie tyle miejsca ile potrzebuje. Jeśli w kolumnie będą elementy o różnych szerokościach, to jej szerokość zostanie wyznaczona na bazie najszerszego elementu. Przy użyciu różnych sposobów kalkulacji, elementy w których zastosowano *, wyliczane są na końcu według pozostałej przestrzeni.

Rozważmy następujący przykład:

<Grid Height="500">
	...
	<Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
		<RowDefinition Height="60" />
		<RowDefinition Height="2*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
	<Button Height="40" />
	...
</Grid>

W tej sytuacji pierwszy wiersz ma automatyczną wysokość wyliczoną na bazie jego zawartości, którą w tym przypadku jest przycisk o wysokości 40 pikseli. Drugi wiersz z definicji mierzy 60 pikseli. W puli zostaje nam więc 400 pikseli, a sumaryczna liczba gwiazdek wynosi 4. Stąd prosta logika * = 100 pikseli, co daje nam wiersze o wysokościach kolejno: 40, 60, 200, 100 oraz 100.

Pozycjonowanie w pionie i poziomie

Jeśli wiersze i kolumny wydają się być dla Was przesadą, to możecie spróbować pozycjonowania w pionie i poziomie. Służą do tego właściwości HorizontalAlignment i VerticalAlignment. Spójrzcie na poniższy przykład:

<Grid Height="150" Width="340">
    <Button Content="1" VerticalAlignment="Top" HorizontalAlignment="Left"  />
    <Button Content="2" VerticalAlignment="Top" HorizontalAlignment="Center" />
    <Button Content="3" VerticalAlignment="Top" HorizontalAlignment="Right"  />

    <Button Content="4" VerticalAlignment="Center" HorizontalAlignment="Left" />
    <Button Content="5" VerticalAlignment="Center" HorizontalAlignment="Center" />
    <Button Content="6" VerticalAlignment="Center" HorizontalAlignment="Right" />

    <Button Content="7" VerticalAlignment="Bottom" HorizontalAlignment="Left" />
    <Button Content="8" VerticalAlignment="Bottom" HorizontalAlignment="Center" />
    <Button Content="9" VerticalAlignment="Bottom" HorizontalAlignment="Right" />
</Grid>

W tym przypadku Grid stał się swego rodzaju pojemnikiem na 9 kontrolek typu Button. W rezultacie tego kodu, otrzymamy poniższy efekt:

Oczywiście takie rozwiązania wymaga innego podejścia. Osobiście preferuje pierwszy wariant, który daje mi większą elastyczność, co jest szczególnie ważne w przypadku urządzeń mobilnych.

Podsumowanie

Grid daje spore możliwości, ale warto pamiętać że w orężu mamy również inne rodzaje layoutów. StackPanel użyty w pionie/poziomie dobrze nada się do wyświetlania prostych menu. Z kolei używając Canvasa, będziemy mogli zastosować pozycjonowanie relatywne względem macierzystego kontenera. Warto z rozwagą dobierać układ do aktualnego problemu;-)

Stosując Grida warto również pamiętać o użyciu wartości domyślnych, co może przyczynić się do zwiększenia czystości tworzonego przez nas kodu.

Podoba Ci się ten wpis? Powiedz o tym innym!

Send to Kindle

Komentarze

blog comments powered by Disqus