Layout reattivi con XAML
Il sistema di layout XAML offre il ridimensionamento automatico degli elementi, i pannelli di layout e gli stati di visualizzazione per creare un'interfaccia utente reattiva. Grazie a questo layout reattivo, la tua app verrà visualizzata in modo ottimale su schermi con risoluzioni, densità in pixel, orientamenti e dimensioni delle finestre diversi. Puoi anche usare XAML per riposizionare, ridimensionare, adattare dinamicamente il contenuto, mostrare/nascondere, sostituire o riprogettare l'interfaccia utente della tua app, come illustrato in Tecniche di responsive design. Esamineremo ora come implementare layout reattivi con XAML.
Definire layout con proprietà e pannelli
Le basi di un layout reattivo sono rappresentate dall'uso appropriato di proprietà e pannelli di layout XAML per il riposizionamento, il ridimensionamento e l'adattamento dinamico del contenuto in modo fluido.
Il sistema di layout XAML supporta layout statici e fluidi. In un layout statico si assegnano controlli espliciti pixel e posizioni. Quando l'utente modifica la risoluzione o l'orientamento del dispositivo, l'interfaccia utente non cambia. I layout statici possono essere ritagliati in diversi fattori di forma e dimensioni di visualizzazione. D'altro canto, i layout fluidi riescono a ridursi, ingrandirsi e adattare dinamicamente il contenuto per rispondere allo spazio visivo disponibile in un dispositivo.
In pratica, puoi usare una combinazione di elementi statici e fluidi per creare l'interfaccia utente. Puoi ancora usare elementi e valori statici in alcune posizioni, ma accertati che l'interfaccia utente generale sia reattiva a risoluzioni, dimensioni dello schermo e visualizzazioni diverse.
Vedremo ora come usare le proprietà XAML e i pannelli di layout per creare un layout fluido.
Proprietà di layout
Le proprietà di layout gestiscono le dimensioni e la posizione di un elemento. Per creare un layout fluido, usa il ridimensionamento automatico o proporzionale per gli elementi e consenti ai pannelli di layout di posizionare i relativi elementi figlio in base alle esigenze.
Di seguito vengono esaminate alcune proprietà di layout comuni e viene descritto come utilizzarle per creare layout fluidi.
Altezza e larghezza
Le proprietà Height e Width specificano le dimensioni di un elemento. Puoi impostare valori fissi misurati in pixel effettivi o usare Auto o il ridimensionamento proporzionale.
Il ridimensionamento automatico ridimensiona gli elementi dell'interfaccia utente per adattarli al loro contenuto o al contenitore padre. È anche possibile usare il ridimensionamento automatico con le righe e le colonne di una griglia. Per usare il ridimensionamento automatico, impostare Height e/o Width degli elementi dell'interfaccia utente su Auto.
Nota
Il ridimensionamento di un elemento in base al relativo contenuto o al relativo contenitore dipende da come il contenitore padre gestisce il ridimensionamento degli elementi figlio. Per altre informazioni, vedi Pannelli di layout più avanti in questo articolo.
Il ridimensionamento proporzionale (Star) distribuisce lo spazio disponibile tra le righe e le colonne di una griglia in base a proporzioni ponderate. In XAML i valori delle unità di ridimensionamento proporzionale sono espressi come* (o n* per il ridimensionamento proporzionale Star ponderato). Ad esempio, per specificare che una colonna è cinque volte più larga rispetto alla seconda colonna in un layout a due colonne, usa "5*" e "*" per le proprietà Larghezza negli elementi ColumnDefinition.
Questo esempio combina il ridimensionamento fisso, automatico e proporzionale in una griglia con 4 colonne.
Colonna | Dimensionamento | Descrizione |
---|---|---|
Column_1 | Auto | La colonna verrà ridimensionata per adattarne il contenuto. |
Column_2 | * | Dopo aver calcolato le colonne Auto, la colonna ottiene parte della larghezza rimanente. Column_2 avrà una larghezza pari a metà del Column_4. |
Column_3 | 44 | La colonna avrà una larghezza di 44 pixel. |
Column_4 | 2* | Dopo aver calcolato le colonne Auto, la colonna ottiene parte della larghezza rimanente. Column_4 sarà il doppio del Column_2. |
La larghezza predefinita della colonna è "*", pertanto non è necessario impostare in modo esplicito questo valore per la seconda colonna.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="44"/>
<ColumnDefinition Width="2*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="Column 1 sizes to its content." FontSize="24"/>
</Grid>
Nella finestra di progettazione XAML di Visual Studio il risultato è simile al seguente.
Per ottenere le dimensioni di un elemento in fase di esecuzione, usa le proprietà di sola lettura ActualHeight e ActualWidth invece delle proprietà Height e Width.
Vincoli di dimensione
Quando si usa il ridimensionamento automatico nell'interfaccia utente, potrebbe essere comunque necessario inserire vincoli sulle dimensioni di un elemento. Impostare le proprietà MinWidth/MaxWidth e MinHeight/MaxHeight specificano i valori che vincolano le dimensioni di un elemento, consentendo il ridimensionamento fluido.
In un oggetto Grid è possibile usare MinWidth/MaxWidth anche con le definizioni di colonna e MinHeight/MaxHeight con le definizioni di riga.
Allineamento
Usare le proprietà HorizontalAlignment e VerticalAlignment specificano come deve essere posizionato un elemento all'interno del relativo contenitore padre.
- I valori per HorizontalAlignment sono Left, Center, Right e Stretch.
- I valori per VerticalAlignment sono Top, Center, Bottom e Stretch.
Con l'allineamento Allunga , gli elementi riempiono tutto lo spazio fornito nel contenitore principale. Stretch è l'impostazione predefinita per entrambe le proprietà di allineamento. Tuttavia, alcuni controlli, ad esempio Button, eseguono l'override di questo valore nello stile predefinito. Qualsiasi elemento che può avere elementi figlio può trattare il valore Stretch per le proprietà HorizontalAlignment e VerticalAlignment in modo univoco. Ad esempio, un elemento che usa i valori predefiniti Stretch posizionati in una griglia si estende per riempire la cella che lo contiene. Lo stesso elemento posizionato in un oggetto Canvas viene ridimensionato in base al relativo contenuto. Per altre info su come ogni pannello gestisce il valore Stretch, vedi l'articolo Pannelli di layout.
Per altre info, vedi l'articolo di allineamento, margine e spaziatura interna e le pagine di riferimento HorizontalAlignment e VerticalAlignment.
Visibilità
Puoi mostrare o nascondere un elemento impostandone la proprietà Visibility su uno dei valori dell'enumerazione Visibility: Visible o Collapsed. Quando un elemento è Collapsed, non occupa spazio nel layout dell'interfaccia utente.
È possibile modificare la proprietà Visibility di un elemento nel codice o in uno stato di visualizzazione. Quando viene modificata la proprietà Visibility di un elemento, vengono modificati anche tutti i relativi elementi figlio. È possibile sostituire le sezioni dell'interfaccia utente rivelando un pannello mentre si comprime un altro.
Suggerimento
Se hai elementi dell'interfaccia utente impostati in modo predefinito su Collapsed, gli oggetti vengono ancora creati, ma non visualizzati, all'avvio. È possibile posticipare il caricamento di questi elementi fino a quando non vengono visualizzati usando l'attributo x:Load per ritardare la creazione degli oggetti. Ciò può migliorare le prestazioni di avvio. Per altre info, vedi attributo x:Load.
Risorse di stile
Non è necessario impostare ogni valore di proprietà singolarmente in un controllo. In genere è più efficiente raggruppare i valori delle proprietà in una risorsa Style e applicare style a un controllo . Ciò vale soprattutto quando è necessario applicare gli stessi valori di proprietà a molti controlli. Per altre informazioni su come utilizzare lo stile, vedere Controlli di stile.
Pannelli di layout
Per posizionare gli oggetti visivi, è necessario inserirli in un pannello o in un altro oggetto contenitore. Il framework XAML fornisce varie classi di pannelli, ad esempio Canvas, Grid, RelativePanel e StackPanel, che fungono da contenitori e consentono di posizionare e disporre gli elementi dell'interfaccia utente all'interno di essi.
La cosa principale da considerare quando si sceglie un pannello di layout è il modo in cui il pannello posiziona e ridimensiona gli elementi figlio. Potrebbe anche essere necessario considerare il modo in cui gli elementi figlio sovrapposti vengono sovrapposti tra loro.
Ecco un confronto tra le funzionalità principali dei controlli pannello forniti nel framework XAML.
Controllo Panel | Descrizione |
---|---|
Canvas | Canvas non supporta l'interfaccia utente fluida. Puoi controllare tutti gli aspetti del posizionamento e del ridimensionamento degli elementi figlio. In genere viene usato per casi speciali come la creazione di grafica o per definire piccole aree statiche di un'interfaccia utente adattiva più grande. È possibile usare il codice o gli stati di visualizzazione per riposizionare gli elementi in fase di esecuzione. |
Griglia | Grid supporta il ridimensionamento fluido degli elementi figlio. È possibile usare il codice o gli stati di visualizzazione per riposizionare e rielaborare gli elementi. |
RelativePanel | |
StackPanel | |
VariableSizedWrapGrid |
Per informazioni dettagliate ed esempi di questi pannelli, vedere Pannelli di layout.
I pannelli di layout consentono di organizzare l'interfaccia utente in gruppi logici di controlli. Quando li usi con le impostazioni di proprietà appropriate, ottieni il supporto per il ridimensionamento automatico, il riposizionamento e il riflow degli elementi dell'interfaccia utente. Tuttavia, la maggior parte dei layout dell'interfaccia utente richiede ulteriori modifiche quando sono presenti modifiche significative alle dimensioni della finestra. A questo scopo, è possibile usare gli stati di visualizzazione.
Layout adattivi con stati di visualizzazione e trigger di stato
Usa gli stati di visualizzazione per apportare modifiche significative all'interfaccia utente in base alle dimensioni della finestra o ad altre modifiche.
Quando la finestra dell'app aumenta o si riduce oltre una determinata quantità, è possibile modificare le proprietà del layout per riposizionare, ridimensionare, riflow, rivelare o sostituire sezioni dell'interfaccia utente. È possibile definire diversi stati di visualizzazione per l'interfaccia utente e applicarli quando la larghezza della finestra o l'altezza della finestra supera una soglia specificata.
VisualState definisce i valori delle proprietà applicati a un elemento quando si trova in uno stato specifico. È possibile raggruppare gli stati di visualizzazione in un oggetto VisualStateManager che applica l'oggetto VisualState appropriato quando vengono soddisfatte le condizioni specificate. Un oggetto AdaptiveTrigger rappresenta un modo semplice per impostare la soglia (denominata anche 'punto di interruzione'), superata la quale viene applicato uno stato in XAML. Per applicare uno stato di visualizzazione, è possibile chiamare il metodo VisualStateManager.GoToState nel codice. Nelle sezioni successive sono riportati alcuni esempi di entrambi i modi.
Impostare gli stati di visualizzazione nel codice
Per applicare uno stato di visualizzazione, è possibile chiamare il metodo VisualStateManager.GoToState nel codice. Ad esempio, per applicare uno stato quando la finestra dell'app è una dimensione specifica, gestire l'evento SizeChanged e chiamare GoToState per applicare lo stato appropriato.
In questo caso, VisualStateGroup contiene due definizioni di VisualState. Il primo, DefaultState
, è vuoto. Quando viene applicata, vengono applicati i valori definiti nella pagina XAML. Il secondo, WideState
,modifica la proprietà DisplayMode di SplitView in Inline e apre il riquadro. Questo stato viene applicato al gestore dell'evento SizeChanged se la larghezza della finestra è maggiore di 640 pixel effettivi.
Nota
Windows non fornisce all'app un sistema per rilevare lo specifico dispositivo in cui è in esecuzione. Può indicare la famiglia di dispositivi (desktop e così via) in cui l'app è in esecuzione, la risoluzione effettiva e la quantità di spazio sullo schermo disponibile per l'app (le dimensioni della finestra dell'app). È consigliabile definire gli stati di visualizzazione per dimensioni dello schermo e punti di interruzione.
<Page ...
SizeChanged="CurrentWindow_SizeChanged">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState x:Name="DefaultState">
<Storyboard>
</Storyboard>
</VisualState>
<VisualState x:Name="WideState">
<Storyboard>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="SplitView.DisplayMode"
Storyboard.TargetName="mySplitView">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<SplitViewDisplayMode>Inline</SplitViewDisplayMode>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="SplitView.IsPaneOpen"
Storyboard.TargetName="mySplitView">
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<SplitView x:Name="mySplitView" DisplayMode="CompactInline"
IsPaneOpen="False" CompactPaneLength="20">
<!-- SplitView content -->
<SplitView.Pane>
<!-- Pane content -->
</SplitView.Pane>
</SplitView>
</Grid>
</Page>
private void CurrentWindow_SizeChanged(object sender, Windows.UI.Core.WindowSizeChangedEventArgs e)
{
if (e.Size.Width > 640)
VisualStateManager.GoToState(this, "WideState", false);
else
VisualStateManager.GoToState(this, "DefaultState", false);
}
// YourPage.h
void CurrentWindow_SizeChanged(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::SizeChangedEventArgs const& e);
// YourPage.cpp
void YourPage::CurrentWindow_SizeChanged(IInspectable const& sender, SizeChangedEventArgs const& e)
{
if (e.NewSize.Width > 640)
VisualStateManager::GoToState(*this, "WideState", false);
else
VisualStateManager::GoToState(*this, "DefaultState", false);
}
Impostare gli stati di visualizzazione nel markup XAML
Prima di Windows 10, le definizioni di VisualState richiedevano oggetti Storyboard per le modifiche alle proprietà e dovevi chiamare GoToState nel codice per applicare lo stato. come illustrato nell'esempio precedente. Verranno comunque visualizzati molti esempi che usano questa sintassi oppure si potrebbe avere codice esistente che lo usa.
A partire da Windows 10, puoi usare la sintassi semplificata Setter illustrata qui e puoi usare StateTrigger nel markup XAML per applicare lo stato. Si usano trigger di stato per creare regole semplici che attivano automaticamente modifiche dello stato di visualizzazione in risposta a un evento dell'app.
Questo esempio esegue la stessa operazione dell'esempio precedente, ma usa la sintassi Setter semplificata anziché uno Storyboard per definire le modifiche delle proprietà. Invece di chiamare GoToState, usa il trigger di stato AdaptiveTrigger predefinito per applicare lo stato. Quando si usano trigger di stato, non è necessario definire un oggetto vuoto DefaultState
. Le impostazioni predefinite vengono riapplicate automaticamente quando le condizioni del trigger di stato non vengono più soddisfatte.
<Page ...>
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup>
<VisualState>
<VisualState.StateTriggers>
<!-- VisualState to be triggered when the
window width is >=640 effective pixels. -->
<AdaptiveTrigger MinWindowWidth="640" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="mySplitView.DisplayMode" Value="Inline"/>
<Setter Target="mySplitView.IsPaneOpen" Value="True"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<SplitView x:Name="mySplitView" DisplayMode="CompactInline"
IsPaneOpen="False" CompactPaneLength="20">
<!-- SplitView content -->
<SplitView.Pane>
<!-- Pane content -->
</SplitView.Pane>
</SplitView>
</Grid>
</Page>
Importante
Nell'esempio precedente la proprietà associata VisualStateManager.VisualStateGroups viene impostata sull'elemento Grid. Quando si usa StateTriggers, assicurarsi sempre che VisualStateGroups sia associato al primo elemento figlio della radice in modo che i trigger abbiano effetto automaticamente. (In questo caso, Grid è il primo elemento figlio dell’elemento radice Page.)
Sintassi delle proprietà associate
In VisualState, in genere si imposta un valore per una proprietà del controllo o per una delle proprietà associate del pannello che contiene il controllo . Quando si imposta una proprietà associata, usare le parentesi intorno al nome della proprietà associata.
Questo esempio mostra come impostare la proprietà associata RelativePanel.AlignHorizontalCenterWithPanel in una proprietà TextBox denominata myTextBox
. Il primo XAML usa la sintassi ObjectAnimationUsingKeyFrames e la seconda usa la sintassi Setter .
<!-- Set an attached property using ObjectAnimationUsingKeyFrames. -->
<ObjectAnimationUsingKeyFrames
Storyboard.TargetProperty="(RelativePanel.AlignHorizontalCenterWithPanel)"
Storyboard.TargetName="myTextBox">
<DiscreteObjectKeyFrame KeyTime="0" Value="True"/>
</ObjectAnimationUsingKeyFrames>
<!-- Set an attached property using Setter. -->
<Setter Target="myTextBox.(RelativePanel.AlignHorizontalCenterWithPanel)" Value="True"/>
Trigger di stato personalizzati
È possibile estendere la classe StateTrigger per creare trigger personalizzati per un'ampia gamma di scenari. Ad esempio, è possibile creare uno StateTrigger per attivare stati diversi in base al tipo di input, quindi aumentare i margini intorno a un controllo quando il tipo di input è tocco. In alternativa, creare uno StateTrigger per applicare stati diversi in base alla famiglia di dispositivi in cui viene eseguita l'app. Per esempi di come creare trigger personalizzati e usarli per creare esperienze di interfaccia utente ottimizzate da una singola visualizzazione XAML, vedi l'esempio di trigger di stato.
Stati e stili di visualizzazione
È possibile usare le risorse style negli stati di visualizzazione per applicare un set di modifiche alle proprietà a più controlli. Per altre informazioni su come utilizzare lo stile, vedere Controlli di stile.
In questo codice XAML semplificato dell'esempio trigger di stato, una risorsa Style viene applicata a un pulsante per regolare le dimensioni e i margini per l'input tocco o del mouse. Per il codice completo e la definizione del trigger di stato personalizzato, vedere l'esempio di trigger di stato.
<Page ... >
<Page.Resources>
<!-- Styles to be used for mouse vs. touch/pen hit targets -->
<Style x:Key="MouseStyle" TargetType="Rectangle">
<Setter Property="Margin" Value="5" />
<Setter Property="Height" Value="20" />
<Setter Property="Width" Value="20" />
</Style>
<Style x:Key="TouchPenStyle" TargetType="Rectangle">
<Setter Property="Margin" Value="15" />
<Setter Property="Height" Value="40" />
<Setter Property="Width" Value="40" />
</Style>
</Page.Resources>
<RelativePanel>
<!-- ... -->
<Button Content="Color Palette Button" x:Name="MenuButton">
<Button.Flyout>
<Flyout Placement="Bottom">
<RelativePanel>
<Rectangle Name="BlueRect" Fill="Blue"/>
<Rectangle Name="GreenRect" Fill="Green" RelativePanel.RightOf="BlueRect" />
<!-- ... -->
</RelativePanel>
</Flyout>
</Button.Flyout>
</Button>
<!-- ... -->
</RelativePanel>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="InputTypeStates">
<!-- Second set of VisualStates for building responsive UI optimized for input type.
Take a look at InputTypeTrigger.cs class in CustomTriggers folder to see how this is implemented. -->
<VisualState>
<VisualState.StateTriggers>
<!-- This trigger indicates that this VisualState is to be applied when MenuButton is invoked using a mouse. -->
<triggers:InputTypeTrigger TargetElement="{x:Bind MenuButton}" PointerType="Mouse" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="BlueRect.Style" Value="{StaticResource MouseStyle}" />
<Setter Target="GreenRect.Style" Value="{StaticResource MouseStyle}" />
<!-- ... -->
</VisualState.Setters>
</VisualState>
<VisualState>
<VisualState.StateTriggers>
<!-- Multiple trigger statements can be declared in the following way to imply OR usage.
For example, the following statements indicate that this VisualState is to be applied when MenuButton is invoked using Touch OR Pen.-->
<triggers:InputTypeTrigger TargetElement="{x:Bind MenuButton}" PointerType="Touch" />
<triggers:InputTypeTrigger TargetElement="{x:Bind MenuButton}" PointerType="Pen" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="BlueRect.Style" Value="{StaticResource TouchPenStyle}" />
<Setter Target="GreenRect.Style" Value="{StaticResource TouchPenStyle}" />
<!-- ... -->
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Page>