Technology Experience
.NET World

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.

Send to Kindle

Igor Damiani

La sua passione per l'informatica nasce nella prima metà degli anni '80, quando suo padre acquistò un Texas Instruments TI-99. Da allora ha continuato a seguire l'evoluzione sia hardware che software avvenuta nel corso degli anni. E' un utente, un videogiocatore ed uno sviluppatore software a tempo pieno. Igor ha lavorato e lavora anche oggi con le più moderne tecnologie Microsoft per lo sviluppo di applicazioni: .NET Framework, XAML, Universal Windows Platform, su diverse piattaforme, tra cui spiccano Windows 10 piattaforme mobile. Numerose sono le app che Igor ha creato e pubblicato sul marketplace sotto il nome VivendoByte, suo personale marchio di fabbrica. Adora mantenere i contatti attraverso Twitter e soprattutto attraverso gli eventi delle community .NET.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.