[Windows 8] Créer un timer rond en XAML

15. April 2013 21:27 by Renaud in Expression Blend, Windows 8, XAML  //  Tags: , , , ,   //   Comments (0)

Pour le développement du jeu WordDefy pour Windows 8, Matthieu (@MatthieuVdh) et moi avons demandé l'aide d'une amie graphiste. Et comme elle nous a rapidement pondu un super truc, la moindre des choses c'était de tenter de le reproduire à l'identique ! Il y a plusieurs bons côtés à cela : premièrement on a une app avec un look plutôt sympa, et deuxièmement j'ai pu jouer avec Blend et m'amuser avec le XAML, ce qui fait que j'ai appris pas mal de choses!

Une des parties consistait à reproduire un Timer en forme de cercle. Pour que vous y voyez plus clair, voici le compteur original tel que designé et le résultat final (qui est assez ressemblant :)) en XAML :

Design original
Design original

Résultat en XAML
XAML

Conception d'un RoundedTimer

Il existe plein d'exemples de timer sur le web, mais aucun fonctionnant sous WinRT. Et évidemment, le XAML sur Windows 8 a quelques particularités qui font que l'exemple ne sont pas toujours utilisables. J'en ai donc recréé un avec Blend !

Générer un Path

La première étape consiste à créer le cercle du compteur. Ce cercle doit être transparent au milieu. Pour faire cela, on va utiliser deux Ellipses et les combiner (ou plus précisément les soustraire). Placez donc deux Ellipses de manière à ce qu'elles soient centrées sur le même point, et avec l'une d'elle légèrement plus petite que l'autre.

Le code jusqu'à présent:

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    	<Ellipse Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="200" Margin="160,240,0,0" Stroke="Black" VerticalAlignment="Top" Width="200"/>
    	<Ellipse Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="140" Margin="190,270,0,0" Stroke="Black" VerticalAlignment="Top" Width="140"/>
    </Grid>

Pour obtenir le résultat voulu, on peut donc soustraire la deuxième Ellipse à la première ou exclure les zones de chevauchement (lisez aussi Combiner des formes et des tracés (Blend pour Visual Studio)). Faites un clique droit sur l'arbre visuel et les Ellipses sélectionnées et choisissez l'opération qui vous convient dans le menu Combiner

Le résultat de cette opération est un Path généré en remplacement des deux Ellipses précédemment créées. Le résultat devrait ressembler à ceci:

Et le code correspondant : 

    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    	<Path Data="M100,30.5 C61.6162,30.5 30.5,61.6162 30.5,100 C30.5,138.384 61.6162,169.5 100,169.5 C138.384,169.5 169.5,138.384 169.5,100 C169.5,61.6162 138.384,30.5 100,30.5 z M100,0.5 C154.952,0.5 199.5,45.0477 199.5,100 C199.5,154.952 154.952,199.5 100,199.5 C45.0477,199.5 0.5,154.952 0.5,100 C0.5,45.0477 45.0477,0.5 100,0.5 z" Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="200" Margin="160,240,0,0" Stretch="Fill" Stroke="Black" UseLayoutRounding="False" VerticalAlignment="Top" Width="200"/>
    </Grid>

Ce cercle est la base de notre RoundedTimer... Passons à l'étape suivante: le clipping ! :)

Clipping Path

Le principe du clipping path est d'utiliser un élément pour délimiter la zone visible d'un autre élément. Malheureusement dans les apps Windows Store on ne peut pas utiliser un Path pour "masquer" un autre élément. Par contre on peut utiliser des Ellipse et des Rectangle pour délimiter une zone. 

Ajoutons par exemple un Rectangle dans sur un coin de notre cercle : 

<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
    <Rectangle Margin="160,240,1106,428" Width="100" Height="100"/>
    <Path Data="M100,30.5 C61.6162,30.5 30.5,61.6162 30.5,100 C30.5,138.384 61.6162,169.5 100,169.5 C138.384,169.5 169.5,138.384 169.5,100 C169.5,61.6162 138.384,30.5 100,30.5 z M100,0.5 C154.952,0.5 199.5,45.0477 199.5,100 C199.5,154.952 154.952,199.5 100,199.5 C45.0477,199.5 0.5,154.952 0.5,100 C0.5,45.0477 45.0477,0.5 100,0.5 z" Fill="#FFF4F4F5" HorizontalAlignment="Left" Height="200" Margin="160,240,0,0" Stretch="Fill" Stroke="Black" UseLayoutRounding="False" VerticalAlignment="Top" Width="200"/>
</Grid>

 

Le résultat obtenu est le suivant : 

On se retrouve avec un quart de cercle, ce qui va en fait représenter un quart de notre RoundedTimer ! Ce qu'il reste à faire est d'animer ce quart de Timer pour représenter le temps qui s'écoule.

Animer le Timer

Pour cela on va utiliser une Transformation sur l'élément RectangleGeometry. A vrai dire il y a sans doute plusieurs façon d'arriver au même résultat ! Pour ma part j'y suis arrivé en utilisant un SkewTransform.

Si l'on reprend le quart de cercle précédent, on peut l'animer en utilisant la transformation correcte : 

<Path Data="M100,30.5 C61.6162,30.5 30.5,61.6162 30.5,100 C30.5,138.384 61.6162,169.5 100,169.5 C138.384,169.5 169.5,138.384 169.5,100 C169.5,61.6162 138.384,30.5 100,30.5 z M100,0.5 C154.952,0.5 199.5,45.0477 199.5,100 C199.5,154.952 154.952,199.5 100,199.5 C45.0477,199.5 0.5,154.952 0.5,100 C0.5,45.0477 45.0477,0.5 100,0.5 z" Fill="White" HorizontalAlignment="Left" Height="200" Margin="160,240,0,0" Stretch="Fill" Stroke="Black" UseLayoutRounding="False" VerticalAlignment="Top" Width="200">
	<Path.Clip>
		<RectangleGeometry Rect="0,0,100,100">
			<RectangleGeometry.Transform>
                <!-- AngleY : De 0 à 90 -->
                <SkewTransform CenterX="100" CenterY="100" AngleY="0"/>
            </RectangleGeometry.Transform>
		</RectangleGeometry>
	</Path.Clip>
</Path>

En modifiant la valeur de la propriété AngleY, on peut animer joliment le timer ! Ainsi, avec un angle de 40°, on retrouve la forme suivante :

Il ne reste donc plus qu'à créer trois autres quarts du timer, et à les animer en changeant la valeur d'un des angles toutes les secondes grâce à un DispatcherTimer. L'important étant de voir comment appliquer la SkewTransformation, en jouant sur les propriétés Center et Angle !

Téléchargement / Sources

Vous pouvez télécharger le résultat final sous la forme d'une contrôle sur GitHub ! À vous de le customiser ! :)

Pour l'intégrer dans votre app, rien de plus simple :

<Page
    x:Class="TestApp.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    xmlns:roundedTimer="using:RoundedTimer"
    mc:Ignorable="d">
    <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
        <roundedTimer:RoundedTimer x:Name="MyTimer" Width="200" Height="200" Duration="00:01:00" />
    </Grid>
</Page>

On instancie le timer dans le XAML, et on démarre le décompte avec un appel à la méthode Start() dans le code-behind :

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            MyTimer.Start();
        }

 

[Windows 8] HTML/JS : VariableSize List View and Template Selection

24. January 2013 00:01 by Renaud in Windows 8  //  Tags: , ,   //   Comments (0)

Here is my first attempt to achive a VariableSize List View in HTML /JS. I tried to apply the same concept as what I knew about XAML and the TemplateSelector, so I started with a function returning the right template, and finally I come to this satisfaying solution :) I just made a first try to achieve this : http://sdrv.ms/Yosvcr Instead of giving a Template to the ListView, I gave it a function that selects the right template according to the index of the item. Then you have different template, with different style, but all the items style have the same size. Then, if you read the end of this page, the paragraph about cell-spanning, you'll see that you need to change two more things to achieve what you want : change the groupInfo or itemInfo properties of the GridLayout used in your ListView. Here are the key parts of the sample I provided : 1/ First, you declare two or more templates + your listview:

    <div id="mediumListIconTextTemplate" data-win-control="WinJS.Binding.Template">
        <div class="mediumItem">
            <h4 data-win-bind="innerText: title"></h4>
            <h6 data-win-bind="innerText: text"></h6>
        </div>
    </div>

    <div id="largeListIconTextTemplate" data-win-control="WinJS.Binding.Template">
        <div class="largeItem">
            <h2 data-win-bind="innerText: title"></h2>
            <h3 data-win-bind="innerText: text"></h3>
        </div>
    </div>

    <div id="listView"
        data-win-control="WinJS.UI.ListView"
        data-win-options="{ 
                itemDataSource: myData.items.dataSource, 
                itemTemplate:myData.template, 
                selectionMode: 'none', 
                tapBehavior: 'none', 
                swipeBehavior: 'none'
            }">
    </div>

Notice the itemTemplate property. It refers to myData.template, which is a function I defined in my JS file. Then you can add some CSS :

.mediumItem {
    width:100px;
    height:100px;
    background-color:cornflowerblue;
}

.largeItem {
    width:100px;
    height:150px;
    background-color:green;
}

Then, some javascript in a separated file :

(function () {
    "use strict";

    // Create a new bindable list
    var list = new WinJS.Binding.List([]);
    // some fake data
    list.push({ title: "Titre 1", text: "Texte 1" });
    list.push({ title: "Titre 2", text: "Texte 2" });
    list.push({ title: "Titre 3", text: "Texte 3" });
    list.push({ title: "Titre 4", text: "Texte 4" });

    // my function that selects the template
    function selectTemplate(itemPromise) {
        // it receives a promise of an item
        return itemPromise.then(function (item) {

            // Then I can get the item
            // and based on its index property I can choose a templatE.
            var container = window.document.createElement("div");
            var itemTemplate;
            switch (item.index) {
                case 0:
                    itemTemplate = WinJS.Utilities.query("#mediumListIconTextTemplate")[0];
                    break;
                case 1:
                    itemTemplate = WinJS.Utilities.query("#largeListIconTextTemplate")[0];
                    break;
                default:
                    itemTemplate = WinJS.Utilities.query("#largeListIconTextTemplate")[0];
                    break;
            }

            // I put my item in a container and style it with the choosen template.
            itemTemplate.winControl.render(item.data, container);
            return container;
        });
    }

    // The selection function has to be marked as supported for processing or you get an error.
    WinJS.Utilities.markSupportedForProcessing(selectTemplate);

    // declare the model and expose the selectTemplate function.
    WinJS.Namespace.define("myData",
    {
        items: list,
        template: selectTemplate
    });

})();

At this point, it uses the right template. But I still have to change the Layout, as explained in the msdn doc. So when the page gets processed by WinJS to display the controls :

           args.setPromise(WinJS.UI.processAll().then(function () {
                var listView = WinJS.Utilities.query('#listView')[0].winControl;
                listView.layout = new WinJS.UI.GridLayout;

                // Enable CellSpanning and give base size.
                listView.layout.groupInfo = function () {
                    return {
                        enableCellSpanning: true,
                        cellWidth: 50,
                        cellHeight: 50
                    };
                }
            }));

With those settings, the cell containing your item will be sized to fit best your template!

 

[Windows 8] C# / XAML : FlipView, Context Indicator et Prévisualisation

4. December 2012 16:12 by Renaud in Windows 8  //  Tags: , ,   //   Comments (2)

Dans cet article on va voir comment reproduire le Context Indicator que l'on peut voir dans le Store. Cet indicateur, c'est ce que vous pouvez voir en dessous du flipview montrant les différents screenshots de l'application, et qui vous donne une idée de l'endroit où vous vous trouvez dans la liste.

Indicateur dans le store

Reproduire l'indicateur du store

Pour y arriver, on va modifier légèrement le template du FlipView. Pour ça, il suffit d'utiliser Blend et de modifier une copie du template existant.

On va ensuite  y ajouter une ListView :

Une fois qu'on a cette ListView, on va faire plusieurs choses :

  • Binder la propriété ItemsSource avec le FlipView pour avoir le même nombre d'éléments dans chaque liste
ItemsSource="{TemplateBinding ItemsSource}"
  • Binder la propriété SelectedItem avec le FlipView  pour les synchroniser
SelectedItem="{Binding SelectedItem, Mode=TwoWay, RelativeSource={RelativeSource Mode=TemplatedParent}}"
  • Modifier l'orientation de la liste en activant le scroll horizontal et en changeant l'orientation du StackPanel qui va contenir les items.
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollMode="Disabled"

Le panel :

    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel  Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
  • Et pour finir, on va sauvagement modifier le style des items et des items containers. On va vraiment faire un gros nettoyage pour éviter d'avoir l'habituelle bordure mauve lorsqu'un élément est sélectionné. On va garder le strict minium et faire en sorte d'avoir un rectangle gris par défaut, et blanc lorsque l'élément est sélectionné. Je vous épargne le code, mais vous pouvez le trouver dans le projet téléchargeable à la fin de l'article :)

Et voilà le résultat :

Aller plus loin et créer une barre de preview !

L'idée est d'utiliser cet indicateur pour donner plus d'infos à l'utilisateur, et lui permettre de l'utiliser même pour naviguer dans le flipview ! Pour vous donner une idée du résultat :)

Pour faire ça, on va principalement modifier le ItemTemplate de la ListView :

<ListView.ItemTemplate>
    <DataTemplate>
        <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
            <Image Source="{Binding ImageUri}" Stretch="UniformToFill"/>
        </Grid>
    </DataTemplate>
</ListView.ItemTemplate>

Si l'on s'arrête là, lorsque l'on se déplace à partir du FlipView, on modifie bien l'item sélectionné dans la ListView. Mais si l'on se déplace trop loin, il se peut que cet item se retrouve en dehors du champs de vision ! Pour éviter ça, on va écouter l'événement SelectionChanged de la ListView, et faire en sorte que l'item sélectionné soit toujours visible.

private void ListViewSelectionChanged1(object sender, Windows.UI.Xaml.Controls.SelectionChangedEventArgs e)
{
    // Si un nouvel item est bien sélectionné
    if (e.AddedItems.Count > 0){
        // On scroll vers cet item !
        ((ListView)sender).ScrollIntoView(e.AddedItems.First());    }
}

Et voilà :) Les sources des deux projets sont téléchargeables sur mon Skydrive : http://sdrv.ms/SJv8PX

 

[Windows 8] C# / XAML : FlipView, Context Indicator & Items Preview

4. December 2012 15:01 by Renaud in Windows 8  //  Tags: , , ,   //   Comments (3)

Let's see how we can reproduce the Context Indicator that you can see in the Store application. This indicator is what you see behind the flipview that shows you the screenshots of an app, and that tells what is your position in that list.

 Indicator for the Wikipedia app in the Store.

Reproduce the Store's Indicator

To achieve this, we will have to slighty modify the FlipView template. Open Blend and use the command "Edit Template > Edit a copy...

 

Add a ListView to it in the visual tree of the FlipView. 

 

Now that we have that list, we can do a few things :

  • Bind the ItemsSource with the parent FlipView so that we have the same quantity of elements in each list
ItemsSource="{TemplateBinding ItemsSource}"
  • Bind the SelectedItem property with the FlipView to make sure both are syncrhonized together
SelectedItem="{Binding SelectedItem, Mode=TwoWay, RelativeSource={RelativeSource Mode=TemplatedParent}}"
  • Set the orientiation and the scrolling horizontal
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollMode="Enabled"
ScrollViewer.VerticalScrollMode="Disabled"

The stackpanel :

    <ListView.ItemsPanel>
        <ItemsPanelTemplate>
            <StackPanel  Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </ListView.ItemsPanel>
  • And finally, we will dramaticaly change the items style and the items containers style. We clean them up to remove the purple border on the selected item. We just keep what we need : a gray-filled border which becomes white when the item is selected. I wont copy/paste the code here but you can grab it at the end of this post.

And here is the result : 

Go further with a preview bar !

Now the idea is to use that indicator to give more information to the user. Let's say you have a big list and you want to show a preview with a thumbnail, so that it's easier to navigate throught the flipview. Here is what it would look like :) 

To achieve that, we just have to modify the ItemTemplate of the ListView :

<ListView.ItemTemplate>
    <DataTemplate>
        <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
            <Image Source="{Binding ImageUri}" Stretch="UniformToFill"/>
        </Grid>
    </DataTemplate>
</ListView.ItemTemplate>

If we stop here, we will just miss a thing : when you navigate in the list using the FlipView itself, you can see that the selected item in the ListView is well syncrhonized. But if you go to far in the list, the selected item may be out of the field of view. It's easy to avoid that, by listening to the SelectionChanged event on the ListView, and each time an item is selected you ask the ListView to scroll and make sure the item is visible !

private void ListViewSelectionChanged1(object sender, Windows.UI.Xaml.Controls.SelectionChangedEventArgs e)
{
    // Si un nouvel item est bien sélectionné
    if (e.AddedItems.Count > 0){
        // On scroll vers cet item !
        ((ListView)sender).ScrollIntoView(e.AddedItems.First());    }
}

And there you are :) You can grab the bits of those two projects on my Skydrive : http://sdrv.ms/SJv8PX

[Windows 8] C# / XAML : Drag & Drop (fr)

1. October 2012 15:12 by Renaud in Windows 8  //  Tags: , ,   //   Comments (6)
Cet article présente une manière d'utiliser le Drag and drop entre deux listes différentes. Si vous voulez simplement permettre de réarranger les items au sein d'une même liste, il vous suffit de mettre les propriétés CanReorderItems et AllowDrop à True sur votre GridViewSample : http://sdrv.ms/U8tPem. Lisez ce commentaire.
À la recherche d'une idée d'application Windows 8 à développer, je voulais quelque chose qui me permette de tester le Drag & Drop (en partie parce que j'avais été intrigué en voyant les propriétés CanDragItems et DragItemsStarting sur les listes en WinRT). Et comme j'en avais assez de faire des applications classées 16+, pour être sûr de passer la certif, je voulais également faire une application sans équivoque qui soit notée 3+ ! Une app pour les tout-petits :) Du coup j'ai développé "Les animaux de la ferme", une sorte de puzzle en bois dédiée à l'apprentissage: disponible sur le Windows Store.

Le Drag & Drop sur WinRT

Indiquer si un event doit être déclenché ou pas

Il est très simple de déplacer des items provenant d'une liste. On va voir avec quelques lignes de code comment démarrer le drag, garder une trace des éléments manipulés, et finalement faire quelque chose de ces informations au moment du drop, si nécessaire. Si vous n'avez pas remarqué, il y'a généralement dans WinRT la possibilité de dire si un event doit être utilisé ou pas. On retrouve par exemple les propriétés suivantes : IsItemClickEnabled, IsRightTapEnabled, ... Ce qui va réellement nous faciliter la vie, c'est la propriété CanDragItems couplée à l'event DragItemsStarting. Tout cela étant hérité de la classe ListViewBase.

This post explains a way to use Drag & drop across two different lists. If you're looking for rearranging items within the same list, you just have to set the properties CanReorderItems and AllowDrop to True on your GridViewSample : http://sdrv.ms/U8tPem. Read this comment.
While looking for a new Windows 8 app idea, I wanted to make something using drag & drop, just for fun. I also wanted something that could be rated 3+ on the Store. An app for kids. And I suddenly remembered my childhood and that kind of puzzles with big pieces of wood. That's how I built "Les animaux de la ferme" (Farm's animals), which is available on the Windows Store for free.

Ce qu'on va déplacer

Pour faire simple, on va utiliser deux GridView. Cela marcherait tout aussi bien avec des ListView, mais peu importe. On va simplement afficher deux collections, et faire passer les items de l'une à l'autre grâce au drag and drop. Dans une page basique, je vais donc ajouter ces deux GridViews, plutôt similaires :

<GridView   ItemTemplate="{StaticResource ItemTemplate1}"
            ItemsSource="{Binding FirstCollection}"
            AllowDrop="True" CanDragItems="True" 
            DragItemsStarting="GridViewDragItemsStarting"
            Drop="GridViewDrop" Margin="10">
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </GridView.ItemsPanel>
</GridView>

<GridView   ItemTemplate="{StaticResource ItemTemplate2}"
            ItemsSource="{Binding SecondCollection}"
            AllowDrop="True" CanDragItems="True" 
            DragItemsStarting="GridViewDragItemsStarting"
            Drop="GridViewDrop" Margin="10">
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </GridView.ItemsPanel>
</GridView>

Les seules différences notables entre ces deux grilles sont les templates utilisés (ItemTemplate1 et ItemTemplate2), et les collections bindées (FirstCollection et SecondCollection). Notez ici les propriétés utilisées:

  • CanDragItems / DragItemStarting
  • AllowDrop / Drop
On va concrètement activer la possibilité de déplacer des items tout en écoutant l'événement indiquant le commencement d'un déplacement. Et on va également activer le drop, donc le fait de pouvoir lâcher un item sur ce contrôle, et écouter l'événement correspondant.

Première collection visible, deuxième collection encore vide.

Gestion du drag et du drop !

On va maintenant implémenter les eventhandlers pour DragItemsStarted et Drop. Dans ce cas-ci, on va faire simple et faire ça de manière assez générique pour pouvoir utiliser les mêmes eventhandlers sur chaque liste en permettant de déplacer les items de l'une à l'autre.

Lors du début du déplacement d'un élément, on se retrouve avec un DragItemsStartingEventArgs avec quelques propriétés intéressantes:

  • Items, une collection contenant les éléments déplacés.
  • Data, qui n'est autre qu'un DataPackage, le même que celui utilisé par le charm Share.
        private void GridViewDragItemsStarting(object sender, DragItemsStartingEventArgs e)
        {
            var item = e.Items.FirstOrDefault();
            if (item == null)
                return;

            e.Data.Properties.Add("item", item);
            e.Data.Properties.Add("gridSource", sender);
        }

On récupère ici le premier élément déplacé, et on va stocker quelques infos dans notre DataPackage, qui va nous service de contexte durant toute l'opération. On va ainsi garder une trace de l'item déplacé, ainsi que du grid duquel il provient !

Ensuite vient le moment de lâcher l'élément. Et si l'utilisateur le lâche par-dessus l'un des grids, l'eventhandler suivant sera appelé :

private void GridViewDrop(object sender, DragEventArgs e)
{
    object gridSource;
    e.Data.Properties.TryGetValue("gridSource", out gridSource);

    if (gridSource == sender)
        return;

    object sourceItem;
    e.Data.Properties.TryGetValue("item", out sourceItem);
    if (sourceItem == null)
        return;

    _mainViewModel.SwitchItem((DemoItem)sourceItem);
}

Dans ce code, on va comparer le grid de source, et le grid cible. Si les deux sont les mêmes, on ne fait rien, et l'élément reste dans sa collection. Dans le cas contraire, on va récupérer l'élément qui a été déplacé depuis notre DataPackage, et on va le changer de collection. Ici, je fais appel à une méthode de mon viewmodel qui fait passer les éléments d'une liste à l'autre. Et c'est tout ! Rien que ça. Il n'en faut pas plus pour faire du drag&drop entre deux listes. Les animations sont présentes automatiquement, et le tout forme un ensemble assez sympathique. 

Conclusion

Cela n'a jamais été aussi simple ! N'hésitez pas à télécharger les sources si quelque chose ne vous semble pas clair :

 

 

[Windows 8] C# / XAML : Drag & Drop (English)

1. October 2012 11:10 by Renaud in Windows 8  //  Tags: , , ,   //   Comments (19)

 It's really easy to move items from a list in a Windows 8 app. We'll see in this blog how we can start dragging items in a few lines of code, keep track of the item being dragged, and finally how to handle the drop of that item. You probably noticed that in the XAML now (something that didn't exist in Silverlight for Windows Phone 7), you can say, for some events, whether you want them enabled or not. For example, on a GridView, you'll find properties such as IsItemClickEnabled, IsRightTapEnabled, ... But there is also one property that will be really useful for now. It's called CanDragItems, and we will use it in addition to the DragItemsStarting event. Those properties are  inherited from the ListViewBase class.

 

What are we going to drag ?

To make it as simple as possible, we'll use two GridView. We could make it also with ListView as it also inherits from ListViewBase. We will simply display two collections, and make it possible for the user to move items from a list to the other using drag and drop. In a blank Page, I add those two GirdViews, that are quite similar :

<GridView   ItemTemplate="{StaticResource ItemTemplate1}"
            ItemsSource="{Binding FirstCollection}"
            AllowDrop="True" CanDragItems="True" 
            DragItemsStarting="GridViewDragItemsStarting"
            Drop="GridViewDrop" Margin="10">
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </GridView.ItemsPanel>
</GridView>

<GridView   ItemTemplate="{StaticResource ItemTemplate2}"
            ItemsSource="{Binding SecondCollection}"
            AllowDrop="True" CanDragItems="True" 
            DragItemsStarting="GridViewDragItemsStarting"
            Drop="GridViewDrop" Margin="10">
    <GridView.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel Orientation="Horizontal" />
        </ItemsPanelTemplate>
    </GridView.ItemsPanel>
</GridView>

The only differences are the templates used (ItemTemplate1 and ItemTemplate2), and the bound collections (FirstCollection and SecondCollection). Notice alose those properties :

  • CanDragItems / DragItemStarting
  • AllowDrop / Drop
We explicitly enable the possibility to move items, while listening to the event fired the user actually starts moving an object. And then, we also activate the ability to "drop", and we listen to the corresponding event to know when an item is dropped on one of the GridViews.
 

Handle the drag and the drop !

Now that the UI can display our collections, we will implement the eventhandlers for DragItemsStarted and Drop. In this case, we will make it simple and we will use the same handlers for both grid so that the user can move items from one grid to the other and vice versa.

When you start moving an item, you end up with a DragItemsStartingEventArgs, which has some interesting properties :

  • Items, a collection containing the dragged items.
  • Data, which is nothing less than a DataPackage object. The same as the one used by the Share charm for data transfer between apps.
        private void GridViewDragItemsStarting(object sender, DragItemsStartingEventArgs e)
        {
            var item = e.Items.FirstOrDefault();
            if (item == null)
                return;

            e.Data.Properties.Add("item", item);
            e.Data.Properties.Add("gridSource", sender);
        }

Here we retrieve the first dragged item, if any, and we store it in the DataPackage, which will act as a context during the whole operation. We also keep track of the "sender" grid, to know where that item comes from.

Then comes the moment you drop the item. And if the user drop it on one of the grid, the following event handler will be executed :

private void GridViewDrop(object sender, DragEventArgs e)
{
    object gridSource;
    e.Data.Properties.TryGetValue("gridSource", out gridSource);

    if (gridSource == sender)
        return;

    object sourceItem;
    e.Data.Properties.TryGetValue("item", out sourceItem);
    if (sourceItem == null)
        return;

    _mainViewModel.SwitchItem((DemoItem)sourceItem);
}

In that piece of code, we compare the source grid with the target grid. If they are the same, then we don't have nothing to do, the item will simply stays where it is. Otherwise, we will get back the item that was dragged, thanks to the DataPackage, and we will put it in the other collection (that's what does the SwitchItem method). And that's all. You don't need nothing more to use drag & drop between two collections. Animations are automatically added, and the result is quite nice ! 

Conclusion

It has never been that easy to use drag and drop ! Don't hesitate to download the sample project if something seems fluzzy :

 

 

[Windows 8 / XAML] Modifier le style d'un FlipView

22. September 2012 19:09 by Renaud in Windows 8  //  Tags: , , ,   //   Comments (7)

Lors d'un Excellence Lab passé récemment, on m'a conseillé de modifier légèrement mon FlipView de sorte que les flèches de navigation vers les items suivants ou précédents ne se superposent pas au contenu. Il suffisait donc de légèrement ré-hausser les flèches pour régler ce problème. Un collègue m'a également posé une question pour savoir comment modifier la couleur de ces flèches, pour répondre à la demande d'un client. J'imagine que la personne a cherché après une propriété "NavigationTemplate", ou quelque chose du genre, un peu comme on retrouve un ItemTemplate En fait, les boutons de navigation font directement partie du template du FlipView. Pour s'en rendre compte, il suffit d'utiliser Blend !

En créant un projet simple, contenant un FlipView et en l'ouvrant dans Blend, il est possible d'éditer une "copie" du template actuel:

Éditer une copie du template

Si on regarde ensuite le XAML, on peut voir que Blend a généré un Style représentant le FlipView, avec tous les Visual States nécessaires pour faire le FlipView tel qu'on le connait. Voici le code complet :

      <Style x:Key="FlipViewStyle1" TargetType="FlipView">
        	<Setter Property="Background" Value="Transparent"/>
        	<Setter Property="BorderThickness" Value="0"/>
        	<Setter Property="TabNavigation" Value="Once"/>
        	<Setter Property="IsTabStop" Value="False"/>
        	<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden"/>
        	<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden"/>
        	<Setter Property="ScrollViewer.IsHorizontalRailEnabled" Value="False"/>
        	<Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="False"/>
        	<Setter Property="ScrollViewer.IsHorizontalScrollChainingEnabled" Value="True"/>
        	<Setter Property="ScrollViewer.IsVerticalScrollChainingEnabled" Value="True"/>
        	<Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False"/>
        	<Setter Property="ScrollViewer.BringIntoViewOnFocusChange" Value="True"/>
        	<Setter Property="ItemsPanel">
        		<Setter.Value>
        			<ItemsPanelTemplate>
        				<VirtualizingStackPanel AreScrollSnapPointsRegular="True" Orientation="Horizontal"/>
        			</ItemsPanelTemplate>
        		</Setter.Value>
        	</Setter>
        	<Setter Property="Template">
        		<Setter.Value>
        			<ControlTemplate TargetType="FlipView">
        				<Grid>
        					<VisualStateManager.VisualStateGroups>
        						<VisualStateGroup x:Name="FocusStates">
        							<VisualState x:Name="Focused">
        								<Storyboard>
        									<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisualWhite"/>
        									<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisualBlack"/>
        								</Storyboard>
        							</VisualState>
        							<VisualState x:Name="Unfocused"/>
        							<VisualState x:Name="PointerFocused"/>
        						</VisualStateGroup>
        					</VisualStateManager.VisualStateGroups>
        					<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Margin="3">
        						<Border.Resources>
        							<ControlTemplate x:Key="HorizontalNextTemplate" TargetType="Button">
        								<Border x:Name="Root" BorderBrush="{StaticResource FlipViewButtonBorderThemeBrush}" BorderThickness="{StaticResource FlipViewButtonBorderThemeThickness}" Background="{StaticResource FlipViewButtonBackgroundThemeBrush}">
        									<VisualStateManager.VisualStateGroups>
        										<VisualStateGroup x:Name="CommonStates">
        											<VisualState x:Name="Normal"/>
        											<VisualState x:Name="PointerOver">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        											<VisualState x:Name="Pressed">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        										</VisualStateGroup>
        									</VisualStateManager.VisualStateGroups>
        									<Path x:Name="Arrow" Data="M4.12,0 L9.67,5.47 L4.12,10.94 L0,10.88 L5.56,5.47 L0,0.06 z" Fill="{StaticResource FlipViewButtonForegroundThemeBrush}" HorizontalAlignment="Center" Height="10.94" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Center" Width="9.67"/>
        								</Border>
        							</ControlTemplate>
        							<ControlTemplate x:Key="HorizontalPreviousTemplate" TargetType="Button">
        								<Border x:Name="Root" BorderBrush="{StaticResource FlipViewButtonBorderThemeBrush}" BorderThickness="{StaticResource FlipViewButtonBorderThemeThickness}" Background="{StaticResource FlipViewButtonBackgroundThemeBrush}">
        									<VisualStateManager.VisualStateGroups>
        										<VisualStateGroup x:Name="CommonStates">
        											<VisualState x:Name="Normal"/>
        											<VisualState x:Name="PointerOver">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        											<VisualState x:Name="Pressed">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        										</VisualStateGroup>
        									</VisualStateManager.VisualStateGroups>
        									<Path x:Name="Arrow" Data="M5.55,0 L9.67,0.06 L4.12,5.47 L9.67,10.88 L5.55,10.94 L0,5.48 z" Fill="{StaticResource FlipViewButtonForegroundThemeBrush}" HorizontalAlignment="Center" Height="10.94" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Center" Width="9.67"/>
        								</Border>
        							</ControlTemplate>
        							<ControlTemplate x:Key="VerticalNextTemplate" TargetType="Button">
        								<Border x:Name="Root" BorderBrush="{StaticResource FlipViewButtonBorderThemeBrush}" BorderThickness="{StaticResource FlipViewButtonBorderThemeThickness}" Background="{StaticResource FlipViewButtonBackgroundThemeBrush}">
        									<VisualStateManager.VisualStateGroups>
        										<VisualStateGroup x:Name="CommonStates">
        											<VisualState x:Name="Normal"/>
        											<VisualState x:Name="PointerOver">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        											<VisualState x:Name="Pressed">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        										</VisualStateGroup>
        									</VisualStateManager.VisualStateGroups>
        									<Path x:Name="Arrow" Data="M0.06,0 L5.47,5.56 L10.88,0 L10.94,4.12 L5.48,9.67 L0,4.12 z" Fill="{StaticResource FlipViewButtonForegroundThemeBrush}" HorizontalAlignment="Center" Height="9.67" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Center" Width="10.94"/>
        								</Border>
        							</ControlTemplate>
        							<ControlTemplate x:Key="VerticalPreviousTemplate" TargetType="Button">
        								<Border x:Name="Root" BorderBrush="{StaticResource FlipViewButtonBorderThemeBrush}" BorderThickness="{StaticResource FlipViewButtonBorderThemeThickness}" Background="{StaticResource FlipViewButtonBackgroundThemeBrush}">
        									<VisualStateManager.VisualStateGroups>
        										<VisualStateGroup x:Name="CommonStates">
        											<VisualState x:Name="Normal"/>
        											<VisualState x:Name="PointerOver">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        											<VisualState x:Name="Pressed">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        										</VisualStateGroup>
        									</VisualStateManager.VisualStateGroups>
        									<Path x:Name="Arrow" Data="M5.63,0 L11.11,5.55 L11.05,9.67 L5.64,4.12 L0.23,9.67 L0.17,5.55 z" Fill="{StaticResource FlipViewButtonForegroundThemeBrush}" HorizontalAlignment="Center" Height="9.67" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Center" Width="10.94"/>
        								</Border>
        							</ControlTemplate>
        						</Border.Resources>
        						<Grid>
        							<ScrollViewer x:Name="ScrollingHost" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalSnapPointsType="MandatorySingle" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsTabStop="False" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}" IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" Padding="{TemplateBinding Padding}" TabNavigation="{TemplateBinding TabNavigation}" VerticalSnapPointsType="MandatorySingle" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="Disabled">
        								<ItemsPresenter/>
        							</ScrollViewer>
        							<Button x:Name="PreviousButtonHorizontal" HorizontalAlignment="Left" Height="40" IsTabStop="False" Template="{StaticResource HorizontalPreviousTemplate}" VerticalAlignment="Center" Width="70"/>
        							<Button x:Name="NextButtonHorizontal" HorizontalAlignment="Right" Height="40" IsTabStop="False" Template="{StaticResource HorizontalNextTemplate}" VerticalAlignment="Center" Width="70"/>
        							<Button x:Name="PreviousButtonVertical" HorizontalAlignment="Center" Height="40" IsTabStop="False" Template="{StaticResource VerticalPreviousTemplate}" VerticalAlignment="Top" Width="70"/>
        							<Button x:Name="NextButtonVertical" HorizontalAlignment="Center" Height="40" IsTabStop="False" Template="{StaticResource VerticalNextTemplate}" VerticalAlignment="Bottom" Width="70"/>
        						</Grid>
        					</Border>
        					<Rectangle x:Name="FocusVisualWhite" IsHitTestVisible="False" Opacity="0" StrokeDashOffset="1.5" StrokeEndLineCap="Square" Stroke="{StaticResource FocusVisualWhiteStrokeThemeBrush}" StrokeDashArray="1,1"/>
        					<Rectangle x:Name="FocusVisualBlack" IsHitTestVisible="False" Opacity="0" StrokeDashOffset="0.5" StrokeEndLineCap="Square" Stroke="{StaticResource FocusVisualBlackStrokeThemeBrush}" StrokeDashArray="1,1"/>
        				</Grid>
        			</ControlTemplate>
        		</Setter.Value>
        	</Setter>
        </Style>

Dans ce style, on retrouve:

  •  4 boutons : deux pour le flip horizontal, et deux pour le flip vertical.
    • PreviousButtonHorizontal
    • NextButtonHorizontal
    • PreviousButtonVertical
    • NextButtonVertical
  • 4 ControlTemplate : un pour chaque bouton
    • HorizontalNextTemplate
    • HorizontalPreviousTemplate
    • VerticalNextTemplate
    • VerticalPreviousTemplate

Vous n'avez plus qu'à jouer sur ce XAML pour arriver à ce que vous voulez :) Encore une fois, vous allez pouvoir apprécier mes talents et mon bon goût digne des graphistes les plus skillés !

Navigation ré-haussée avec du texte

 

Vous pouvez télécharger ce sample ici:

 

[Windows 8 / XAML] Customize the FlipView style

22. September 2012 15:20 by Renaud in Windows 8  //  Tags: , , ,   //   Comments (0)

During an Excellence Lab that I recently passed for a Windows 8 app, the Microsoft Expert told me it would be better to modify my FlipView to make sur the previous/next arrows don't override the content. The only thing I had to do for solving the problem, was to move the arrows upper. Then, a colleague ask me how to modify the color of those arrows... so I decided to write a post :) I can imagine that my colleague looked for a property such as "NavigationTemplate" or whatever, because that's how it works usually when you want to modify how some part of a control look like. For example, for the ItemTemplate property... But in this case, the buttons are directly part of the FlipView template itself ! To realize it, we will simply use Blend !

Create a simple project, using a FlipView and open it in Blend. Then, edit a "copy" of the template of your FlipView :

Edit a copy of the template

Looking at the XAML, you can see that Blend generated a Style element for the FlipView. It contains everything the the FlipView needs to look how it uses to, including the Visual States. Here is the complete default flipview style:

      <Style x:Key="FlipViewStyle1" TargetType="FlipView">
        	<Setter Property="Background" Value="Transparent"/>
        	<Setter Property="BorderThickness" Value="0"/>
        	<Setter Property="TabNavigation" Value="Once"/>
        	<Setter Property="IsTabStop" Value="False"/>
        	<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden"/>
        	<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Hidden"/>
        	<Setter Property="ScrollViewer.IsHorizontalRailEnabled" Value="False"/>
        	<Setter Property="ScrollViewer.IsVerticalRailEnabled" Value="False"/>
        	<Setter Property="ScrollViewer.IsHorizontalScrollChainingEnabled" Value="True"/>
        	<Setter Property="ScrollViewer.IsVerticalScrollChainingEnabled" Value="True"/>
        	<Setter Property="ScrollViewer.IsDeferredScrollingEnabled" Value="False"/>
        	<Setter Property="ScrollViewer.BringIntoViewOnFocusChange" Value="True"/>
        	<Setter Property="ItemsPanel">
        		<Setter.Value>
        			<ItemsPanelTemplate>
        				<VirtualizingStackPanel AreScrollSnapPointsRegular="True" Orientation="Horizontal"/>
        			</ItemsPanelTemplate>
        		</Setter.Value>
        	</Setter>
        	<Setter Property="Template">
        		<Setter.Value>
        			<ControlTemplate TargetType="FlipView">
        				<Grid>
        					<VisualStateManager.VisualStateGroups>
        						<VisualStateGroup x:Name="FocusStates">
        							<VisualState x:Name="Focused">
        								<Storyboard>
        									<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisualWhite"/>
        									<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="FocusVisualBlack"/>
        								</Storyboard>
        							</VisualState>
        							<VisualState x:Name="Unfocused"/>
        							<VisualState x:Name="PointerFocused"/>
        						</VisualStateGroup>
        					</VisualStateManager.VisualStateGroups>
        					<Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Margin="3">
        						<Border.Resources>
        							<ControlTemplate x:Key="HorizontalNextTemplate" TargetType="Button">
        								<Border x:Name="Root" BorderBrush="{StaticResource FlipViewButtonBorderThemeBrush}" BorderThickness="{StaticResource FlipViewButtonBorderThemeThickness}" Background="{StaticResource FlipViewButtonBackgroundThemeBrush}">
        									<VisualStateManager.VisualStateGroups>
        										<VisualStateGroup x:Name="CommonStates">
        											<VisualState x:Name="Normal"/>
        											<VisualState x:Name="PointerOver">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        											<VisualState x:Name="Pressed">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        										</VisualStateGroup>
        									</VisualStateManager.VisualStateGroups>
        									<Path x:Name="Arrow" Data="M4.12,0 L9.67,5.47 L4.12,10.94 L0,10.88 L5.56,5.47 L0,0.06 z" Fill="{StaticResource FlipViewButtonForegroundThemeBrush}" HorizontalAlignment="Center" Height="10.94" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Center" Width="9.67"/>
        								</Border>
        							</ControlTemplate>
        							<ControlTemplate x:Key="HorizontalPreviousTemplate" TargetType="Button">
        								<Border x:Name="Root" BorderBrush="{StaticResource FlipViewButtonBorderThemeBrush}" BorderThickness="{StaticResource FlipViewButtonBorderThemeThickness}" Background="{StaticResource FlipViewButtonBackgroundThemeBrush}">
        									<VisualStateManager.VisualStateGroups>
        										<VisualStateGroup x:Name="CommonStates">
        											<VisualState x:Name="Normal"/>
        											<VisualState x:Name="PointerOver">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        											<VisualState x:Name="Pressed">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        										</VisualStateGroup>
        									</VisualStateManager.VisualStateGroups>
        									<Path x:Name="Arrow" Data="M5.55,0 L9.67,0.06 L4.12,5.47 L9.67,10.88 L5.55,10.94 L0,5.48 z" Fill="{StaticResource FlipViewButtonForegroundThemeBrush}" HorizontalAlignment="Center" Height="10.94" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Center" Width="9.67"/>
        								</Border>
        							</ControlTemplate>
        							<ControlTemplate x:Key="VerticalNextTemplate" TargetType="Button">
        								<Border x:Name="Root" BorderBrush="{StaticResource FlipViewButtonBorderThemeBrush}" BorderThickness="{StaticResource FlipViewButtonBorderThemeThickness}" Background="{StaticResource FlipViewButtonBackgroundThemeBrush}">
        									<VisualStateManager.VisualStateGroups>
        										<VisualStateGroup x:Name="CommonStates">
        											<VisualState x:Name="Normal"/>
        											<VisualState x:Name="PointerOver">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        											<VisualState x:Name="Pressed">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        										</VisualStateGroup>
        									</VisualStateManager.VisualStateGroups>
        									<Path x:Name="Arrow" Data="M0.06,0 L5.47,5.56 L10.88,0 L10.94,4.12 L5.48,9.67 L0,4.12 z" Fill="{StaticResource FlipViewButtonForegroundThemeBrush}" HorizontalAlignment="Center" Height="9.67" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Center" Width="10.94"/>
        								</Border>
        							</ControlTemplate>
        							<ControlTemplate x:Key="VerticalPreviousTemplate" TargetType="Button">
        								<Border x:Name="Root" BorderBrush="{StaticResource FlipViewButtonBorderThemeBrush}" BorderThickness="{StaticResource FlipViewButtonBorderThemeThickness}" Background="{StaticResource FlipViewButtonBackgroundThemeBrush}">
        									<VisualStateManager.VisualStateGroups>
        										<VisualStateGroup x:Name="CommonStates">
        											<VisualState x:Name="Normal"/>
        											<VisualState x:Name="PointerOver">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPointerOverForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        											<VisualState x:Name="Pressed">
        												<Storyboard>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBackgroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="Root">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedBorderThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        													<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="Arrow">
        														<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource FlipViewButtonPressedForegroundThemeBrush}"/>
        													</ObjectAnimationUsingKeyFrames>
        												</Storyboard>
        											</VisualState>
        										</VisualStateGroup>
        									</VisualStateManager.VisualStateGroups>
        									<Path x:Name="Arrow" Data="M5.63,0 L11.11,5.55 L11.05,9.67 L5.64,4.12 L0.23,9.67 L0.17,5.55 z" Fill="{StaticResource FlipViewButtonForegroundThemeBrush}" HorizontalAlignment="Center" Height="9.67" Stretch="Fill" UseLayoutRounding="False" VerticalAlignment="Center" Width="10.94"/>
        								</Border>
        							</ControlTemplate>
        						</Border.Resources>
        						<Grid>
        							<ScrollViewer x:Name="ScrollingHost" BringIntoViewOnFocusChange="{TemplateBinding ScrollViewer.BringIntoViewOnFocusChange}" HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}" HorizontalSnapPointsType="MandatorySingle" HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}" IsTabStop="False" IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}" IsHorizontalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsHorizontalScrollChainingEnabled}" IsVerticalScrollChainingEnabled="{TemplateBinding ScrollViewer.IsVerticalScrollChainingEnabled}" IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}" IsDeferredScrollingEnabled="{TemplateBinding ScrollViewer.IsDeferredScrollingEnabled}" Padding="{TemplateBinding Padding}" TabNavigation="{TemplateBinding TabNavigation}" VerticalSnapPointsType="MandatorySingle" VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}" VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}" ZoomMode="Disabled">
        								<ItemsPresenter/>
        							</ScrollViewer>
        							<Button x:Name="PreviousButtonHorizontal" HorizontalAlignment="Left" Height="40" IsTabStop="False" Template="{StaticResource HorizontalPreviousTemplate}" VerticalAlignment="Center" Width="70"/>
        							<Button x:Name="NextButtonHorizontal" HorizontalAlignment="Right" Height="40" IsTabStop="False" Template="{StaticResource HorizontalNextTemplate}" VerticalAlignment="Center" Width="70"/>
        							<Button x:Name="PreviousButtonVertical" HorizontalAlignment="Center" Height="40" IsTabStop="False" Template="{StaticResource VerticalPreviousTemplate}" VerticalAlignment="Top" Width="70"/>
        							<Button x:Name="NextButtonVertical" HorizontalAlignment="Center" Height="40" IsTabStop="False" Template="{StaticResource VerticalNextTemplate}" VerticalAlignment="Bottom" Width="70"/>
        						</Grid>
        					</Border>
        					<Rectangle x:Name="FocusVisualWhite" IsHitTestVisible="False" Opacity="0" StrokeDashOffset="1.5" StrokeEndLineCap="Square" Stroke="{StaticResource FocusVisualWhiteStrokeThemeBrush}" StrokeDashArray="1,1"/>
        					<Rectangle x:Name="FocusVisualBlack" IsHitTestVisible="False" Opacity="0" StrokeDashOffset="0.5" StrokeEndLineCap="Square" Stroke="{StaticResource FocusVisualBlackStrokeThemeBrush}" StrokeDashArray="1,1"/>
        				</Grid>
        			</ControlTemplate>
        		</Setter.Value>
        	</Setter>
        </Style>

In this style, you can find :

  •  4 buttons: two for the horizontal flip, and two for the vertical flip.
    • PreviousButtonHorizontal
    • NextButtonHorizontal
    • PreviousButtonVertical
    • NextButtonVertical
  • 4 ControlTemplate : one for each button
    • HorizontalNextTemplate
    • HorizontalPreviousTemplate
    • VerticalNextTemplate
    • VerticalPreviousTemplate

Then, you just have to play with the XAML to achieve what you want to :) Once again, you can appreciate my incredible talent and good taste, that would make the most skilled designer jealous !

Upper navigation with colors and text"

You can download the sample here :

TextBox

About the author

I'm a developer, blog writer, and author, mainly focused on Microsoft technologies (but not only Smile). I'm Microsoft MVP Client Development since July 2013.

Microsoft Certified Professional

I'm currently working as an IT Evangelist with an awesome team at the Microsoft Innovation Center Belgique, where I spend time and energy helping people to develop their projects. I also give training to enthusiastic developers and organize afterworks with the help of the Belgian community.

MIC Belgique

Take a look at my first book (french only): Développez en HTML 5 pour Windows 8

Développez en HTML5 pour Windows 8

Membre de l'association Fier d'être développeur

TextBox

Month List