Contrôle Carrousel en XAML : de C# à C++/CX

Dans ce billet nous nous sommes amusé à transposer de C# vers C++/CX le contrôle carrousel  de Sébastien Pertus, qu’il décrit dans son excellent article Create a custom user control using Xaml and C# for Windows 8 comme il n’y a rien de nouveau dans l’algorithme, je ne vais pas détailler ce qui a déjà été fait.

En faite l’idée est tout simplement de montrer les différences entre les deux langages si l’envie vous prenez de développer en C++/CX et de voir qu’en fin de compte il n’y pas énormément de différence et que l’écriture est assez similaire.

Pour ma part, la méthode que j’ai utilisé, c’est de copier le code C# dans le projet C++/CX que j’ai ensuite transposé dans cette dernière syntaxe. Temps d’écriture 1 grosse journée

Vous trouverez le code source de cette exemple ici :

Petit Rappel :

Une application Windows 8 (Quel que soit le langage utilisé C++/CX,C#, VB ou Javascript) est basée sur un nouveau Jeu d’API native, le Windows Runtime connue sous le nom de WinRT.

C# est quant à lui basé sur une machine Virtuelle le CLR (Common Langage Runtime) qui fera le pont entre le monde .NET et le monde Natif, on appelle cela la projection.

Cette projection permet de mapper à peu près tous les types .NET vers la WinRT. Les primitives (String, int etc..), les interfaces, les enums, les structs, les delegates, , par exemple un IList<T> en .NET sera mappée en IVector<T> etc..

Cela veut dire comme nous le verrons plus tard, qu’il est possible d’utiliser directement des jeux d’API .NET dans son application sans se soucier s’il y a un pendant en WinRT (comme nous le verrons plus tard), il faudra donc le prendre en compte et trouver une correspondance en WinRT pour C++/CX.

Si la transposition n’est pas automatique, il est possible d’utiliser des méthodes d’extensions disponibles dans .NET pour explicitement utiliser ou convertir des types .NET vers des types WinRT.

Par exemple en incluant l’espace de nom System.Runtime.InteropServices.WindowsRuntime vous avez accès à la méthode d’extension .AsBuffer(), qui permet de convertir un tableau de byte[] en IBuffer<Byte>, interface utilisée massivement dans la WinRT.

 

Transposition du code C# vers du C++/CX

Le contrôle de Sébastien, permet d’afficher des images, qui pourront défiler à  droite et à  gauche avec la souris ou en tactile,  avec une petite animation. Ces images seront liées via un pseudo modèle MVVM.

 

Notre première tâche est donc de construire le modèle MVVM pour C++/CX

C#

    public class MainPageViewModel
    {
        public ObservableCollection<Data> Datas { get; set; }
        public MainPageViewModel()
        {
            this.Datas = new ObservableCollection<Data>();
            this.Datas.Add(new Data { BitmapImage = new BitmapImage(new Uri("ms-appx:///Assets/pic01.jpg", UriKind.Absolute)), Title = "Wall 05" });
            this.Datas.Add(new Data { BitmapImage = new BitmapImage(new Uri("ms-appx:///Assets/pic02.jpg", UriKind.Absolute)), Title = "Wall 06" });
            this.Datas.Add(new Data { BitmapImage = new BitmapImage(new Uri("ms-appx:///Assets/pic03.jpg", UriKind.Absolute)), Title = "Wall 07" });
            this.Datas.Add(new Data { BitmapImage = new BitmapImage(new Uri("ms-appx:///Assets/pic04.jpg", UriKind.Absolute)), Title = "Wall 08" });
            this.Datas.Add(new Data { BitmapImage = new BitmapImage(new Uri("ms-appx:///Assets/pic05.jpg", UriKind.Absolute)), Title = "Wall 09" });
            this.Datas.Add(new Data { BitmapImage = new BitmapImage(new Uri("ms-appx:///Assets/pic06.jpg", UriKind.Absolute)), Title = "Wall 10" });
     
        
        }
    }
    public class Data
    {
        public BitmapImage BitmapImage { get; set; }
        public String Title { get; set; }
    }
}

C++/CX

namespace YellowStone
{
    [Windows::UI::Xaml::Data::Bindable]
    public ref class Data sealed
    {
    public :
        Data(Windows::UI::Xaml::Media::Imaging::BitmapImage^ bmp,Platform::String^ title);

        property Windows::UI::Xaml::Media::Imaging::BitmapImage^ BmpImage;
        property Platform::String^ Title;
    };
    
    [Windows::UI::Xaml::Data::Bindable]
    public ref class MainPageViewModel sealed
    {
             
    public:    
        MainPageViewModel(void);
        virtual ~MainPageViewModel(void);
        
        //Expose only public Type through ABI
        property Windows::Foundation::Collections::IObservableVector<Object^>^  Datas;
    };    
}

Tout d’abord une classe C++/CX  doit être notée avec l’attribut Windows::UI::Xaml::Data::Bindable afin que le type soit inclu dans le fichier XAML généré. (cela suffit pour que cela fonctionne dans notre exemple  mais pour aller plus loin vous devriez également implémenter l’interface INotifyPropertyChanged cf l’article MVVM in C++/CX )

Vous noterez que dans le code C#, il utilise une liste .NET  ObservableCollection<Data>   pour la propriété Datas, alors que nous utilisons l’interface WinRT Windows::Foundation::Collections::IObservableVector<Object^> en lui passant un Object^ plutôt qu’un type Data^ . Si nous avions utilisé le type Data^ nous aurions eu une erreur de transtypage à l’exécution lors du binding, alors que la projection de C# fait le transtypage pour nous.
Vous noterez également que nous pouvons utiliser une syntaxe simplifiée en C++/CX comme en C# pour la déclaration de la propriété.

Propriété en C++/CX

property int Depth {
            int get() {return static_cast<int>(DependencyObject::GetValue(DepthProperty));}
            void set (int value){DependencyObject::SetValue(DepthProperty,value);}
            }

 

Pour instancier la liste Datas (IObservableVector<T>) nous utiliserons le type natif à la WinRT Platform::Collections::Vector<Object^>, comme dans le code suivant du constructeur de la classe MainPageViewModel.

C++/CX

MainPageViewModel::MainPageViewModel(void)
{
    this->Datas=ref new Platform::Collections::Vector<Object^>();
      BitmapImage bmp1(ref newUri("ms-appx:///Assets/pic01.jpg"));
    auto data1=ref new Data(%bmp1,"Wall 05");
    
      BitmapImage bmp2(ref newUri("ms-appx:///Assets/pic02.jpg"));
    auto data2=ref new Data(%bmp2,"Wall 06");

      BitmapImage bmp3(ref newUri("ms-appx:///Assets/pic03.jpg"));
    auto data3=ref new Data(%bmp3,"Wall 07");

      BitmapImage bmp4(ref newUri("ms-appx:///Assets/pic04.jpg"));
    auto data4=ref new Data(%bmp4,"Wall 08");

      BitmapImage bmp5(ref newUri("ms-appx:///Assets/pic05.jpg"));
    auto data5=ref new Data(%bmp5,"Wall 09");

      BitmapImage bmp6(ref newUri("ms-appx:///Assets/pic06.jpg"));
    auto data6=ref new Data(%bmp6,"Wall 10");

    
    this->Datas->Append(data1);    
    this->Datas->Append(data2);
    this->Datas->Append(data3);
    this->Datas->Append(data4);
    this->Datas->Append(data5);

Remarque : Pourquoi ne pas utiliser dans la déclaration de la propriété Datas directement le type Platform::Collections::Vector<Object^> ?
C’est très simple, parce que cette propriété est déclarée publique, et qu’il n’est pas possible d’émettre dans le fichier winmd des types autres que des types WinRT (c’est à dire visible dans les autres langages)

 

Plusieurs chose à noter par rapport au code C#.

  1. C++/CX utilise la notion de référence symbolisée par le caractère ^, pour initialiser une référence on utilise ref new pour l’instanciation de la classe alors que C# utilise une référence implicite instanciée par le mot clé new
    T^ t=ref new T();
    L’opérateur de portée pour une référence est la flèche –> alors qu’en C# c’est le point. Jusqu’ici rien de bien trop compliqué.
    t->MethodA();
  2. Néanmoins,  C++/CX, permet également comme dans du code ISO C++ traditionnel, d’avoir une initialisation sur la pile. C’est à dire que la référence à l’objet disparait lorsque la méthode se termine. (pas besoin alors de faire de delete)
    T t;
    L’opérateur de portée devient le point.
    t.MethodA();
  3. Du coup pour déférencerune référence on utilise l’opérateur %.
  4. Au même titre que le mot clé var en C#, on utilise le mot clé auto pour simplifier et clarifier la syntaxe . le résultat est le même dans les deux langages, les compilateurs font de l’inférence de type.
  5. Anecdotique, mais je le signale quand même, la liste Vector<T>, ne possède pas les mêmes méthodes que la liste .NET ObservableCollection<T>.
    Pour ajouter un élément il faut utiliser la méthode Append() et  pas la méthode Add(), pour compter les éléments il faut utiliser la propriété Size plutot que Count, etc…

 

Une fois le modèle défini, nous allons nous attaquer au contrôle proprement dit.

Un contrôle XAML, expose pour le DataBinding des DependencyProperty, en C# une syntaxe simplifiée est d’utiliser un champ publique statique associée à une propriété classique.

 

C#

/// <summary>
      /// Items source : Better if ObservableCollection :)
      /// </summary>
      public IEnumerable<Object> ItemsSource
      {
          get { return (IEnumerable<Object>)GetValue(ItemsSourceProperty); }
          set { SetValue(ItemsSourceProperty, value); }
      }

      // Using a DependencyProperty as the backing store for ItemsSource.  
      //This enables animation, styling, binding, etc...
      public static readonly DependencyProperty ItemsSourceProperty =
          DependencyProperty.Register("ItemsSource",
                      typeof(IEnumerable<Object>),
                      typeof(LightStone),
                      new PropertyMetadata(0, ItemsSourceChangedCallback));

C#

private static void ItemsSourceChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
 {
     
     if (args.NewValue == null)
         return;

     if (args.NewValue == args.OldValue)
         return;

     LightStone lightStone = dependencyObject as LightStone;

     if (lightStone == null)
         return;

     var obsList = args.NewValue as INotifyCollectionChanged;

     if (obsList != null)
     {
         obsList.CollectionChanged += (sender, eventArgs) =>
             {
                 switch (eventArgs.Action)
                 {
                     case NotifyCollectionChangedAction.Remove:
                         foreach (var oldItem in eventArgs.OldItems)
                         {
                             for (int i = 0; i < lightStone.internalList.Count; i++)
                             {
                                 var fxElement = lightStone.internalList[i] as FrameworkElement;
                                 if (fxElement == null || fxElement.DataContext != oldItem) continue;
                                 lightStone.RemoveAt(i);
                             }
                         }

                         break;
                     case NotifyCollectionChangedAction.Add:
                         foreach (var newItem in eventArgs.NewItems)
                             lightStone.CreateItem(newItem, 0);
                         break;
                 }
             };
     }

     lightStone.Bind();
 }

En C++, cela ce fait en 2 temps. Tout d’abord dans le fichier d’entête on déclare la propriété statique ItemsSourceProperty, la propriété qui invoquera la DependencyProperty et la méthode statique qui sera invoquée

C++/CX

static property Windows::UI::Xaml::DependencyProperty^ ItemsSourceProperty
        {
            Windows::UI::Xaml::DependencyProperty^ get();
        }
        property Windows::Foundation::Collections::IObservableVector<Object^>^ ItemsSource {
            Windows::Foundation::Collections::IObservableVector<Object^>^ get(){
                return safe_cast<Windows::Foundation::Collections::IObservableVector<Object^>^>(DependencyObject::GetValue(ItemsSourceProperty));}
            void set (Windows::Foundation::Collections::IObservableVector<Object^>^ value)
            {
                DependencyObject::SetValue(ItemsSourceProperty,value);
            }
        }

C++/CX

  1. static void OnItemsSourcePropertyChanged(Windows::UI::Xaml::DependencyObject^ d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs^ e);

Puis dans le fichier CPP on déclare le champ statique qui sera exposé à l’extérieure par l’intermédiaire de la propriété ItemsSourceProperty, et on implémente la méthode get de la propriété et la méthode statique de rappel.

C++/CX

static DependencyProperty^ m_ItemsSourceProperty=
            DependencyProperty::Register("ItemsSource",TypeName(Object::typeid),TypeName(Carroussel::typeid),
        ref new PropertyMetadata(nullptr, ref new PropertyChangedCallback(
        &Carroussel::OnItemsSourcePropertyChanged)));
DependencyProperty^ Carroussel::ItemsSourceProperty::get()
{
    return m_ItemsSourceProperty;
}
void Carroussel::OnItemsSourcePropertyChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    
    if (e->NewValue == nullptr)   return;

    if (e->NewValue == e->OldValue)  return;

     auto target = dynamic_cast<Carroussel^>(d);
    
    if (target == nullptr)  return;
    IObservableVector<Object^>^ source=dynamic_cast<IObservableVector<Object^>^>(e->NewValue);
    if (source==nullptr)
        return;
    target->ItemsSource=source;
    target->ItemsSource->VectorChanged+=ref new VectorChangedEventHandler<Object^>([target,source](Object^sender, IVectorChangedEventArgs^e)
    {
        auto action=e->CollectionChange;
        auto index=e->Index;
        if (action==CollectionChange::ItemInserted)
        {
            target->CreateItem(source->GetAt(index),0.0);
        }
        else
        {
        }
    });    
    target->Bind(source);        
}

 

Plusieurs choses à noter par rapport au code C#

  1. Pour obtenir le type pour le paramètre TypeName, on utilise le mot clé typeid. (N’apparait pas avec IntelliSense). TypeName(Object::typeid)
  2. La syntaxe de la méthode statique de callback ne s’invente pas elle est issue de la normalisation C++, il faut la préfixer par & suivie du nom la classe puis du nom de la méthode. &Carroussel::OnItemsSourcePropertyChanged
  3. En C++ on n’utilise plus l’opérateur (T) pour caster (même si on peut toujours le faire), mais les opérateurs, static_cast<T>, safe_cast<T> et dynamic_cast<T> à la place.
    Par exemple dynamic_cast<T> correspond à l’opérateur as de C# : auto target = dynamic_cast<Carroussel^>(d)
  4. Pour tester si un type est nul, l’opérateur nullptr est désormais disponible. if (target==nullptr)
  5. En C# on utilise un ObservableCollection<T>, qui implémente l’interface INotifyCollectionChange et son évènement CollectionChanged , en C++/CX, on utilise la collection Platform::Collection::Vector<T> qui implémente l’interface IObservableVector et sont évènement VectorChanged
  6. Il est possible d’utiliser des lambdas (depuis C++11), avec une syntaxe qui diffère de celle de C#
    Comme on peut le voir ici, une lambda C++ se construit comme suit : [](){}; comme dans l’exemple ci-dessus alors  qu’en C# c’est plutôt ()=>{};
    Vous noterez qu’entre crochet, j’utilise les variables target et source, qui sont passées par valeur au corps de la lambda (le compilateur en fait une copie).
  7. Pour récupérer un élèment de la liste source, on utilise pas un indexer mais directement la méthode source->GetAt(index).

 

Une méthode qui mérite notre attention car il y à des différences c’est ArrangeOverride, mais en les comparants vous vous apercevrez qu’en faite il n’y pas beaucoup de différence dans l'a syntaxe.

 

C#

protected override Size ArrangeOverride(Size finalSize)
    {
        if (isUpdatingPosition)
            return finalSize;

        Double centerLeft = 0;
        Double centerTop = 0;
        this.Clip = new RectangleGeometry { Rect = new Rect(0, 0, finalSize.Width, finalSize.Height) };
        // Set
        Canvas.SetZIndex(rectangle, 1);
        // Storyboard for all Items to appear
        var localStoryboard = new Storyboard();

        for (int i = 0; i < this.internalList.Count; i++)
        {
            UIElement container = internalList[i];
            Size desiredSize = container.DesiredSize;
            
            if (double.IsNaN(desiredSize.Width) || double.IsNaN(desiredSize.Height)) continue;

            // get the good center and top position
            if (centerLeft == 0 && centerTop == 0 && desiredSize.Width > 0 && desiredSize.Height > 0)
            {
                desiredWidth = desiredSize.Width;
                desiredHeight = desiredSize.Height;

                centerLeft = (finalSize.Width / 2) - (desiredWidth / 2);
                centerTop = (finalSize.Height - desiredHeight) / 2;
            }

            // Get position from SelectedIndex
            var deltaFromSelectedIndex = Math.Abs(this.SelectedIndex - i);

            // Get rect position
            var rect = new Rect(centerLeft, centerTop, desiredWidth, desiredHeight);

            container.Arrange(rect);
            Canvas.SetLeft(container, centerLeft);
            Canvas.SetTop(container, centerTop);

            // Apply Transform
            PlaneProjection planeProjection = container.Projection as PlaneProjection;

            if (planeProjection == null)
                continue;

            // Get properies
            var depth = (i == this.SelectedIndex) ? 0 : -(this.Depth);
            var rotation = (i == this.SelectedIndex) ? 0 : ((i < this.SelectedIndex) ? Rotation : -(Rotation));
            var translateX = (i == this.SelectedIndex) ? 0 : ((i < this.SelectedIndex) ? -TranslateX : TranslateX);
            var offsetX = (i == this.SelectedIndex) ? 0 : (i - this.SelectedIndex) * desiredWidth;
            var translateY = TranslateY;

            // CenterOfRotationX
            // to Get good center of rotation for SelectedIndex, must know the animation behavior
            int centerOfRotationSelectedIndex = isIncrementing ? 1 : 0;
            var centerOfRotationX = (i == this.SelectedIndex) ? centerOfRotationSelectedIndex : ((i > this.SelectedIndex) ? 1 : 0);
           
            // Apply on current item
            planeProjection.CenterOfRotationX = centerOfRotationX;
            planeProjection.GlobalOffsetY = translateY;
            planeProjection.GlobalOffsetZ = depth;
            planeProjection.GlobalOffsetX = translateX;
            planeProjection.RotationY = rotation;
            planeProjection.LocalOffsetX = offsetX;

            // calculate zindex and opacity
            int zindex = (this.internalList.Count * 100) - deltaFromSelectedIndex;
            double opacity = 1d - (Math.Abs((Double)(i - this.SelectedIndex) / (MaxVisibleItems + 1)));

            // Items appears
            if (container.Visibility == Visibility.Visible && container.Opacity == 0d)
            {
                localStoryboard.AddAnimation(container, TransitionDuration, rotation, "(UIElement.Projection).(PlaneProjection.RotationY)", this.EasingFunction);
                localStoryboard.AddAnimation(container, TransitionDuration, depth, "(UIElement.Projection).(PlaneProjection.GlobalOffsetZ)", this.EasingFunction);
                localStoryboard.AddAnimation(container, TransitionDuration, translateX, "(UIElement.Projection).(PlaneProjection.GlobalOffsetX)", this.EasingFunction);
                localStoryboard.AddAnimation(container, TransitionDuration, offsetX, "(UIElement.Projection).(PlaneProjection.LocalOffsetX)", this.EasingFunction);
                localStoryboard.AddAnimation(container, TransitionDuration, translateY, "(UIElement.Projection).(PlaneProjection.GlobalOffsetY)", this.EasingFunction);
                localStoryboard.AddAnimation(container, TransitionDuration, 0, opacity, "Opacity", this.EasingFunction);
            }
            else
            {

                container.Opacity = opacity;
            }

            Canvas.SetZIndex(container, zindex);

        }

        rectangle.Fill = new SolidColorBrush(Colors.Black);
        rectangle.Opacity = 0.9;
        Canvas.SetLeft(rectangle, 0);
        Canvas.SetTop(rectangle, (this.ActualHeight / 2));
        Canvas.SetZIndex(rectangle, 1);
        rectangle.Width = this.ActualWidth;
        rectangle.Height = this.ActualHeight;

        if (localStoryboard.Children.Count > 0)
            localStoryboard.Begin();

        return finalSize;
    }

 

C++/CX

Size Carroussel::ArrangeOverride(Size finalSize)
{
        if (isUpdatingPosition)
                return finalSize;

            float centerLeft = 0;
            float centerTop = 0;
            RectangleGeometry rectGeo;
            Rect rect(0,0,finalSize.Width,finalSize.Height);
            rectGeo.Rect=rect;
            this->Clip=%rectGeo;

            // Set
            Canvas::SetZIndex(rectangle, 1);
            
            // Storyboard for all Items to appear
            auto localStoryboard =ref new Storyboard();
            unsigned int count=this->internalList->Size;
            for (int i = 0; i < count; i++)
            {
                UIElement^ container = internalList->GetAt(i);
                Size desiredSize = container->DesiredSize;
                                                                
                if (_isnan(desiredSize.Width) || _isnan(desiredSize.Height)) continue;

                // get the good center and top position
                if (centerLeft == 0 && centerTop == 0 && desiredSize.Width > 0 && desiredSize.Height > 0)
                {
                    desiredWidth = desiredSize.Width;
                    desiredHeight = desiredSize.Height;

                    centerLeft = (finalSize.Width / 2) - (desiredWidth / 2);
                    centerTop = (finalSize.Height - desiredHeight) / 2;
                }

                // Get position from SelectedIndex
                int currentSelectedIndex=this->SelectedIndex;
                auto deltaFromSelectedIndex = ::abs(static_cast<int>(currentSelectedIndex - i));
                
                // Get rect position
                Rect rect(centerLeft, centerTop, desiredWidth, desiredHeight);

                container->Arrange(rect);
                Canvas::SetLeft(container, centerLeft);
                Canvas::SetTop(container, centerTop);

                // Apply Transform
                PlaneProjection^ planeProjection = dynamic_cast<PlaneProjection^>(container->Projection);

                if (planeProjection == nullptr)
                    continue;

                // Get properies
                
                auto depth = (i == currentSelectedIndex) ? 0 : -(this->Depth);
                double currentRotation=this->Rotation;
                int currentTranslateX=this->TranslateX;

                auto rotation = (i == currentSelectedIndex) ? 0 : ((i < currentSelectedIndex) ? currentRotation : -(currentRotation));
                auto translateX = (i == currentSelectedIndex) ? 0 : ((i < currentSelectedIndex) ? -currentTranslateX : currentTranslateX);
                auto offsetX = (i == currentSelectedIndex) ? 0 : (i - currentSelectedIndex) * desiredWidth;
                auto translateY =this->TranslateY;

                // CenterOfRotationX
                // to Get good center of rotation for SelectedIndex, must know the animation behavior
                int centerOfRotationSelectedIndex = isIncrementing ? 1 : 0;
                auto centerOfRotationX = (i == currentSelectedIndex) ? centerOfRotationSelectedIndex : ((i > currentSelectedIndex) ? 1 : 0);
               
                // Apply on current item
                planeProjection->CenterOfRotationX = centerOfRotationX;
                planeProjection->GlobalOffsetY = translateY;
                planeProjection->GlobalOffsetZ = depth;
                planeProjection->GlobalOffsetX = translateX;
                planeProjection->RotationY = rotation;
                planeProjection->LocalOffsetX = offsetX;

                // calculate zindex and opacity
                int zindex = (count * 100) - deltaFromSelectedIndex;
                
                auto currentMaxVisibleItems=this->MaxVisibleItems;
                double opacity=0.0;
                opacity = 1.0 - (::abs(static_cast<double>((i - currentSelectedIndex))/ (currentMaxVisibleItems + 1)));
                
                auto currentEasing=this->EasingFunction;
                
                // Items appears
                if (container->Visibility == Windows::UI::Xaml::Visibility::Visible && container->Opacity == 0.0)
                {
                    this->AddAnimation(localStoryboard,container, TransitionDuration, rotation, "(UIElement.Projection).(PlaneProjection.RotationY)", currentEasing);
                    this->AddAnimation(localStoryboard,container, TransitionDuration, depth, "(UIElement.Projection).(PlaneProjection.GlobalOffsetZ)", currentEasing);
                    this->AddAnimation(localStoryboard,container, TransitionDuration, translateX, "(UIElement.Projection).(PlaneProjection.GlobalOffsetX)", currentEasing);
                    this->AddAnimation(localStoryboard,container, TransitionDuration, offsetX, "(UIElement.Projection).(PlaneProjection.LocalOffsetX)", currentEasing);
                    this->AddAnimation(localStoryboard,container, TransitionDuration, translateY, "(UIElement.Projection).(PlaneProjection.GlobalOffsetY)", currentEasing);
                    this->AddAnimation(localStoryboard,container, TransitionDuration, 0, opacity, "Opacity", currentEasing);
                }
                else
                {

                    container->Opacity = opacity;
                }

                Canvas::SetZIndex(container, zindex);

            }
            Color color;
            color.R=0;
            color.B=0;
            color.G=0;
            rectangle->Fill = ref new SolidColorBrush(color);
            rectangle->Opacity = 0.9;
            Canvas::SetLeft(rectangle, 0);
            Canvas::SetTop(rectangle, (this->ActualHeight / 2));
            Canvas::SetZIndex(rectangle, 1);
            
            rectangle->Width = this->ActualWidth;
            rectangle->Height = this->ActualHeight;

            if (localStoryboard->Children->Size > 0)
                localStoryboard->Begin();
            return finalSize;
}

 

Tout d’abord comme en C# on utilise des objets XAML pure WinRT (Espace de nom Windows), on les utilise de la même manière en C# et en C++/CX.

Par exemple l’objet RectangleGeometry que j’utilise ici avec une syntaxe sur la pile (sans ref new) est passé à la propriété Clip avec l’opérateur de déférencement % c’est la seule différence avec C#, si j’avais utilisé une référence à la place RectangleGeometry^ rectGeo=ref new RectangleGeometry(), j’aurais directement utilisé l’affectation this->Clip=rectGeo;

En C# pour récupérer la valeur absolue, on utilise la méthode Math.Abs, qui est une classe pure .NEt et non pas WinRT, en C++/CX, comme nous pouvons mixer du code ISO C++ avec du code C++/CX, j’ai utilisé la méthode abs() de la runtime C.

Dans tous les cas, si il n’y pas de correspondance .NET <–> WinRT, il est possible d’utiliser d’autres librairies C ou C++. Par exemple, un Double en C# qui est un type (ValueType) possède une méthode IsNaN. Pas en C++/CX, ou il est possible d’utiliser la runtime C _isnan(double x);

Vous trouverez le code source de cette exemple ici :

 

Pour aller plus loin avec C++/CX je vous conseille fortement la lecture de Developing an end-to-end Windows Store app using C++ and XAML:hilo