App Control Panel per UWP
Seguendo il gruppo Sviluppatori Windows Italia su Facebook, mi sono ritrovato nell’intento di voler ricreare una specie di Pannello di Controllo di Windows 10, applicando Adaptive UI per fare in modo che la visualizzazione cambi da griglia a lista, esattamente come fa il pannello di controllo nativo del sistema operativo. Perciò ho creato questo. Che ne dite?
Vedete come ridimensionando la finestra, la griglia si adatti in modo opportuno, e quando la dimensione della finestra scende al di sotto dei 600 pixel, la visualizzazione passa in modalità lista.
Passiamo alla parte tecnica. La visualizzazione a griglia è rappresentata dal seguente XAML:
1: <GridView ItemsSource="{Binding Items}" VerticalAlignment="Center"
2: HorizontalAlignment="Center" x:Name="ControlPanelGrid">
3: <GridView.ItemsPanel>
4: <ItemsPanelTemplate>
5: <ItemsWrapGrid Orientation="Horizontal" />
6: </ItemsPanelTemplate>
7: </GridView.ItemsPanel>
8: <GridView.ItemTemplate>
9: <DataTemplate>
10: <Grid Width="200" Height="200" Padding="16" Margin="2">
11: <Grid.RowDefinitions>
12: <RowDefinition Height="Auto" />
13: <RowDefinition Height="Auto" />
14: <RowDefinition Height="Auto" />
15: </Grid.RowDefinitions>
16: <TextBlock Text="{Binding Icon}" Foreground="Olive" FontFamily="Segoe UI Symbol"
17: FontSize="50" FontWeight="ExtraLight" TextAlignment="Center"
18: TextWrapping="Wrap" Grid.Row="0" VerticalAlignment="Bottom" />
19: <TextBlock Text="{Binding Name}" Foreground="Black" FontSize="18"
20: FontWeight="ExtraLight" TextAlignment="Center" TextWrapping="Wrap"
21: Grid.Row="1" VerticalAlignment="Bottom" />
22: <TextBlock Text="{Binding Description}" Foreground="DarkGray"
23: FontWeight="ExtraLight" FontSize="14" TextAlignment="Center"
24: TextWrapping="Wrap" Grid.Row="2" VerticalAlignment="Top" />
25: </Grid>
26: </DataTemplate>
27: </GridView.ItemTemplate>
28: </GridView>
La visualizzazione a lista invece è la seguente:
1: <GridView ItemsSource="{Binding Items}" x:Name="ControlPanelList"
2: HorizontalContentAlignment="Stretch">
3: <GridView.ItemTemplate>
4: <DataTemplate>
5: <Grid Margin="2" HorizontalAlignment="Stretch" Padding="4" BorderThickness="1">
6: <Grid.ColumnDefinitions>
7: <ColumnDefinition Width="60" />
8: <ColumnDefinition />
9: </Grid.ColumnDefinitions>
10: <Grid.RowDefinitions>
11: <RowDefinition />
12: <RowDefinition />
13: </Grid.RowDefinitions>
14: <TextBlock Grid.RowSpan="2" Text="{Binding Icon}" Foreground="Olive"
15: FontFamily="Segoe UI Symbol" FontWeight="ExtraLight" FontSize="32"
16: Grid.Row="0" VerticalAlignment="Bottom" Margin="8 0 0 0" />
17: <TextBlock Text="{Binding Name}" Grid.Column="1" Foreground="Black"
18: FontWeight="ExtraLight" FontSize="20" Grid.Row="0"
19: VerticalAlignment="Bottom" Margin="8 0 0 0" />
20: <TextBlock Text="{Binding Description}" Grid.Column="1" Foreground="DarkGray"
21: FontWeight="ExtraLight" FontSize="16" TextWrapping="Wrap"
22: Grid.Row="1" VerticalAlignment="Top" Margin="8 0 0 0" />
23: </Grid>
24: </DataTemplate>
25: </GridView.ItemTemplate>
26: <GridView.ItemsPanel>
27: <ItemsPanelTemplate>
28: <ItemsStackPanel Orientation="Vertical" />
29: </ItemsPanelTemplate>
30: </GridView.ItemsPanel>
31: <GridView.ItemContainerStyle>
32: <Style TargetType="GridViewItem">
33: <Setter Property="HorizontalContentAlignment" Value="Stretch" />
34: </Style>
35: </GridView.ItemContainerStyle>
36: </GridView>
Notare che in ambedue i casi si tratta sempre di una GridView, solo che nel secondo caso (modalità lista) ho cambiato il pannello di default, impostandolo su:
1: <GridView.ItemsPanel>
2: <ItemsPanelTemplate>
3: <ItemsStackPanel Orientation="Vertical" />
4: </ItemsPanelTemplate>
5: </GridView.ItemsPanel>
In questo modo, pur trattandosi di una GridView, in realtà gli Items internamente vengono visualizzati con uno StackPanel, e quindi verticalmente. Perchè questo?
L’effetto di “OnMouseOver” di una GridView renderizza il bordo dell’elemento, come si vede nel video, e questo è proprio l’effetto che fa il pannello di controllo di Windows, ed è quello che volevo. La ListView, invece, renderizza il backgrund dell’elemento, e non è l’effetto che cercavo. Quindi: GridView in tutti e due i casi, solo che la modalità a lista utilizza uno StackPanel verticale. Più semplice di così si muore.
Ma arriviamo al sodo. Come ho utilizzato gli AdaptiveTrigger per switchare da una modalità all’altra? Con questo blocco di XAML:
1: <VisualStateManager.VisualStateGroups>
2: <VisualStateGroup x:Name="VisualStateGroup">
3: <VisualState x:Name="VisualStateNarrow">
4: <VisualState.StateTriggers>
5: <AdaptiveTrigger MinWindowWidth="0" />
6: </VisualState.StateTriggers>
7: <Storyboard>
8: <DoubleAnimation Duration="0"
9: Storyboard.TargetProperty="Opacity"
10: Storyboard.TargetName="ControlPanelGrid"
11: To="0" d:IsOptimized="True" />
12: <ObjectAnimationUsingKeyFrames Duration="0"
13: Storyboard.TargetProperty="(UIElement.IsHitTestVisible)"
14: Storyboard.TargetName="ControlPanelGrid">
15: <DiscreteObjectKeyFrame KeyTime="0" Value="False" />
16: </ObjectAnimationUsingKeyFrames>
17: </Storyboard>
18: </VisualState>
19: <VisualState x:Name="VisualStateWide">
20: <VisualState.StateTriggers>
21: <AdaptiveTrigger MinWindowWidth="600" />
22: </VisualState.StateTriggers>
23: <Storyboard>
24: <DoubleAnimation Duration="0"
25: Storyboard.TargetProperty="Opacity"
26: Storyboard.TargetName="ControlPanelList"
27: To="0" d:IsOptimized="True" />
28: <ObjectAnimationUsingKeyFrames Duration="0"
29: Storyboard.TargetProperty="(UIElement.IsHitTestVisible)"
30: Storyboard.TargetName="ControlPanelList">
31: <DiscreteObjectKeyFrame KeyTime="0" Value="False" />
32: </ObjectAnimationUsingKeyFrames>
33: </Storyboard>
34: </VisualState>
35: </VisualStateGroup>
36: </VisualStateManager.VisualStateGroups>
Nel Visual State Manager ho definito due viste: VisualStateNarrow e VisualStateWide.
La prima interviene con una Width minima di 0 pixel, ed attiva la visualizzazione degli elementi a lista, quindi modalità verticale. La seconda interviene con una Width minima di 600 pixel, ed attiva la visualizzazione degli elementi a griglia. Due note importanti:
- per visualizzare o nascondere utilizzo la proprietà Opacity delle due GridView
- di conseguenza ho dovuto impostare anche IsHitTestVisible a False, per fare in modo che un oggetto che nel visual tree compare “sopra” un altro, in realtà non partecipi all’intercettazione degli eventi di input. Uao, che frase complicata. Se non andassi ad impostare IsHitTestVisible a False, il controllo ControlPanelGrid non funzionerebbe come voluto, perchè davanti ci sarebbe il ControlPanelList (nonostante la sua Opacity a 0). Non è frutto del mio sacco, ho visto l’utilizzo di questa proprietà in Template 10, ed in effetti è una bella comodità
Direi che è tutto.
Il codice sorgente di questo progetto è su GitHub, e lo trovate qui.