NopControls 1.1.0

29. mars 2011

Je suis inarrêtable. Vivement que je reprenne le travail ^^. Donc, sinon, nouvelle version des NopControls pour Silverlight.

Cette nouvelle version intègre:

  • Un nouveau contrôle : le ColorPicker
  • Un nouveau projet de test pour Windows Phone (en supplément de celui pour Silverlight Desktop)
  • La correction de quelques vilains bugs qui faisaient rien que de m’embêter

 

imageimageimage

 

Le tout toujours disponible sur Codeplex : http://nopcontrols.codeplex.com

D’ailleurs sur le conseil d’une future collègue, je suis en train d’essayer de voir pour intégrer les NopControls dans les Coding4Fun controls (http://coding4fun.codeplex.com)…Affaire à suivre donc.

.Net, NopControls, Silverlight

NopControls

28. mars 2011

Je viens de mettre en ligne un nouveau projet Codeplex destiné à héberger mes contrôles Silverlight (pour Windows Phone et Desktop).

Le projet s’appelle NopControls et se trouve ici:

http://nopcontrols.codeplex.com

Le but est de fournir le code de quelques contrôles sympathiques que vous pouvez réutiliser dans vos projets si le cœur vous en dit.

Au programme pour le moment deux contrôles:

  • PolygonTabControl : Ce contrôle mappe ses enfants sur la face d’un volume à n faces. Par exemple si il a 5 enfants, il crée un pentagone extrudé et met chaque enfant sur une face.
  • ExpandableTab : Ce contrôle permet de créer des fiches qui se docke sur un bord et qui peuvent apparaitre lorsque l’on clique sur l’onglet qui les représente.

 

Pour en savoir plus, reportez vous au site Codeplex, il y a un exemple en Silverlight Sourire

.Net, Silverlight, Windows Phone

Bien comprendre le fonctionnement du AllowTransparency en WPF

21. mars 2011

Lors d’un débat hautement philosophique dans un troquet parisien (mon dieu, je m’embourgeoise à une vitesse…), je discutais avec des amis (D**k et Mi**u, pour respecter leur anonymat) de la possibilité de changer la valeur de AllowTransparency au runtime.

Mais avant d’aller plus loin, remettons sur la table le fonctionnement de cette propriété. Son but est simple : activer l’opacité par pixel (per-pixel opacity) qui permet en plus de la couleur de chaque pixel d’y associer une valeur d’alpha qui permet de rendre ce dernier plus ou moins transparent.

Windows supporte deux possibilités pour faire de la transparence au niveau des fenêtres:

 

Les fenêtres Layered en mode System Redirected Content (SRC)


Dans ce mode, Windows crée une image pour la fenêtre et chaque appel au GDI effectué par la fenêtre ou les contrôles est redirigé vers l’image en question. Ce mode est transparent pour la fenêtre qui ne sait pas que ces ordres de dessins sont redirigés. Toutefois il n’est pas possible dans ce mode d’avoir de per-pixel opacity car les appels GDI n’en contiennent pas. Tout au plus on peut activer une transparence globale pour la fenêtre.

Dans notre cas, nous pourrions essayer ce code sur notre fenêtre WPF:

[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

[DllImport("user32.dll")]
static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, 
byte bAlpha, uint dwFlags); const int GWL_ID = -12; const int GWL_STYLE = -16; const int GWL_EXSTYLE = -20; const int LWA_ALPHA = 0x2; const int WS_EX_LAYERED = 0x80000; public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { WindowInteropHelper helper = new WindowInteropHelper(this); SetWindowLong(helper.Handle, GWL_EXSTYLE,
GetWindowLong(helper.Handle, GWL_EXSTYLE) ^ WS_EX_LAYERED); SetLayeredWindowAttributes(helper.Handle, 0, 128, LWA_ALPHA); }

Cela marcherait très bien pour une fenêtre Windows Forms ou n’importe quelle fenêtre Win32 mais cela n’a aucun effet en WPF. En effet, WPF surveille la valeur du style de sa fenêtre (au sens Win32 du terme) et donc rejette toutes modifications telles que le SetWindowLong ci-dessus.

Il n’est donc PAS POSSIBLE d’activer le AllowTransparency une fois qu’une fenêtre a été affichée. La seule solution consisterait à réinistancier la fenêtre courante et à modifier la valeur de AllowTransparency AVANT de faire le window.Show (ou à construire une fenêtre avec AllowTransparency=true et à transférer l’arbre des éléments WPF).

 

Les fenêtres Layered en mode Application Provided Content (APC)


Vous l’aurez compris, WPF a fait le choix de ce mode.

Ainsi quand on active AllowTransparency=true, la fenêtre Win32 qui sert de support à WPF est créée en mode Layered APC. Dans ce mode c’est l’application qui va crée le bitmap et qui sera responsable de le mettre à jour.

Ce mode supporte le per-pixel opacity et le bitmap est donc généré au format RGBA par WPF et envoyé à l’OS comme dessin de la fenêtre. Pour ce faire il est obligatoire que le WindowStyle soit égal à None car WPF ne saurait pas dessiner la zone non-cliente (la zone de titre, les bordures ou même les effets de distorsion d’Aero) qui sont à la charge de Windows.

Il n’y a donc plus aucun WM_PAINT et tout le mécanisme Win32 de dessin est totalement zappé. Ceci explique par exemple que si l’on a un ActiveX ou un contrôle Win32 dans sa fenêtre, il n’apparaitra pas puisque lui continue à attendre les WM_PAINT et à écrire dans le device context standard (Je referai un post pour contourner ce problème).

C’est un mode performant et qui correspond bien à WPF puisque ce dernier contrôle tout et peut envoyer ses données en RGBA (ce qui est son mode de fonctionnement interne).

Hélas, comme nous l’avons vu plus haut, ce mode n’est pas dynamique et doit être fixer une fois pour toute à cause de l’inflexibilité de WPF sur ce point.

 

Mettre en place une region


Si toutefois, l’effet recherché est de ne pas avoir une fenêtre rectangulaire, mais de faire une fenêtre aux formes bizarres de manière dynamique, il reste la solution de définir une région:

[DllImport("gdi32.dll")]
static extern IntPtr CreateRoundRectRgn(int x1, int y1, int x2, int y2,
    int cx, int cy);

[DllImport("user32.dll")]
static extern int SetWindowRgn(IntPtr hWnd, IntPtr hRgn, bool bRedraw);

public MainWindow()
{
    InitializeComponent();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    WindowInteropHelper helper = new WindowInteropHelper(this);
    SetWindowRgn(helper.Handle, CreateRoundRectRgn(0, 0, 300, 300, 8, 8), true);
}

En effet, même si WPF fait sa sauce pour dessiner le contenu, notre fenêtre de part le fait qu’elle vit sous Windows doit se conformer aux fonctionnements standards et doit donc passer par le Windows Manager. Elle peut donc avoir une région définie (ce que l’on appelle aussi “Airspace”).

 

En conclusion, si l’on veut faire du per-pixel opacity, la seule solution reste le AllowTransparency et ce dernier ne peut être activé qu’avant l’affichage de la fenêtre.

.Net, WPF

TechDays 2011–Les vidéos

17. mars 2011

Les vidéos des sessions sont disponibles sur le site des TechDays.

Voici un lien direct vers les miennes:

.Net

Fuites de mémoire avec WPF 4.0 et de grosses listes d’images

15. mars 2011

Dans le cadre d’UrzaGatherer (http://urzagatherer.codeplex.com), j’ai découvert un problème bien embêtant avec une liste d’images en haute définition.

En effet j’ai une ListBox qui me présente les cartes d’une collection dans un WrapPanel (qui porte une jolie animation):

image

Or, l’utilisateur en se promenant de collections en collections va faire changer de nombreuses fois la source de la liste et va donc déclencher de nombreuses fois le chargement de ces grosses images.

Et j’ai constaté que lorsque je réaffecte le DataContext de ma liste à une nouvelle source, les anciennes données semblent ne pas être nettoyées de la mémoire.

Ainsi, UrzaGatherer peut allègrement occuper 2Go de RAM après avoir visité une dizaine de collections (chaque collection peut peser jusqu’à 300 Mo).

Je me suis donc mis en quête de la fuite.

Mais avant d’en venir à cette dernière regardons déjà comment le binding est fait et comment la liste est construite:

 

1 - Notre ListBox


La liste est donc une ListBox avec comme ItemPanel un WrapPanel dans lequel j’ai glissé un behavior en provenance de Blend pour faire un effet sympa d’animations:

            <ListBox ItemsSource="{Binding}" x:Name="imagesList">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <Image Source="{Binding Bitmap, Mode=OneWay}"/>
                    </DataTemplate>
                </ListBox.ItemTemplate>
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel Orientation="Horizontal" ItemHeight="300">
                            <Interactivity:Interaction.Behaviors>
                                <Layout:FluidMoveBehavior Duration="00:00:00.5">
                                </Layout:FluidMoveBehavior>
                            </Interactivity:Interaction.Behaviors>
                        </WrapPanel>
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
            </ListBox>

 

On peut voir que l’ItemTemplate se branche sur la propriété Bitmap de notre source.

2 - La source de données


Cette dernière est effectivement composée d’une liste de cartes qui portent une propriété Bitmap que je construis ainsi:

public BitmapImage Bitmap
{
    get
    {
        if (CompletePath == null)
            return null;

        BitmapImage bitmapImage = new BitmapImage();
        bitmapImage.BeginInit();
        bitmapImage.CreateOptions = BitmapCreateOptions.None;
        bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
        bitmapImage.UriSource = new Uri(CompletePath);
        bitmapImage.EndInit();
        bitmapImage.Freeze();

        return bitmapImage;
    }
}

A partir du chemin CompletePath, je produis une BitmapImage. L’objectif étant de contrôler le cache et de pouvoir créer les images dans un thread séparé pour les affecter par la suite. Toutefois, dans notre exemple, je simplifie cette étape en branchant directement le contrôle Image sur mon Bitmap sans passer par la création asynchrone.

 

3 – La fuite (les fuites?)


A partir de ce code relativement simple, il me suffit de brancher 3 ou 4 collections de cartes pour voir que la mémoire n’est jamais libérée.

Mes investigations m’ont permis de voir deux soucis:

  • Le behavior de Blend garde des références sur les images et de ce fait bloque le Garbage Collector et donc laisse les images en mémoire
  • L’utilisation d’une BitmapImage semble également laisser des traces en mémoire et malgré mes efforts je n’ai pas pu libérer ces ressources. La seule solution : faire un binding vers le chemin (CompletePath) plutôt que vers ma Bitmap.

 

Je me suis donc résigner à ne plus utiliser ni le behavior ni le chargement asynchrone. Toutefois pour ce dernier point, j’ai pu faire un contournement ainsi:

  • Créer une variable BindPath sur mes cartes
  • L’initialiser à null et mettre le binding du contrôle Image dessus
  • Binder la liste (donc pour le moment aucune image ne s’affiche puisque BindPath = null)
  • Faire une boucle dans une Task qui toutes les 20 millisecondes prend une carte et met sa propriété BindPath à CompletePath. De ce fait via le fait que mes cartes sont INotityPropertyChanged, la ListBox se met à jour progressivement et donne un effet progressif agréable (plutot qu’un bon vieux figeage de l’interface à l’ancienne)

 

Si vous aussi vous trouvez des fuites dans l’utilisation de WPF 4.0, n’hésitez pas à me le signaler Sourire

WPF, .Net, UrzaGatherer

BDC 2011–Jeudi 7 avril 2011

14. mars 2011

Comme chaque année Bewise organise sa conférence technique sur Toulouse. Et même si je ne porte plus la casquette Bewise, je serai toujours présent cette année (en tant que Microsoftee) avec une session autour de Windows Phone 7 et un stand dédié au large sujet du framework 4.0 !

N’hésitez pas à visiter la page Facebook de la BDC juste ici.

Pour vous inscrire : http://bdc2011.bewise.fr

De plus cette année, la conférence se déroulera au casino de Toulouse et pour l’occasion, je vous livre ici un petit teaser bien sympathique:

Bewise

Porter une application SqlServerCE 3.5 vers la nouvelle version 4.0

13. mars 2011

Avec la sortie du SP1 de Visual Studio 2010, je me suis motivé pour porter UrzaGatherer vers SqlServerCE 4.0.

Voici donc les étapes que j’ai suivies:

using (SqlCeEngine sqlCeEngine = new SqlCeEngine(“connectionString”))
{
      sqlCeEngine.Upgrade();
}

Par la suite il est possible d’utiliser les nouveautés style la génération de clefs mais dans mon cas comme je ne veux pas modifier ma base, cela s’arrêtera la.

En ce qui concerne Entity Framework,j’ai juste eu à changer la chaine de connexion pour remplacer le numéro de version:

provider=System.Data.SqlServerCe.3.5—>provider=System.Data.SqlServerCe.4.0

Il faut également modifier le .edmx à la main pour modifier la référence au bon provider:

<Schema Namespace="UrzaGathererModel.Store" Alias="Self" Provider="System.Data.SqlServerCe.3.5" ProviderManifestToken="3.5" … >

—>

<Schema Namespace="UrzaGathererModel.Store" Alias="Self" Provider="System.Data.SqlServerCe.4.0" ProviderManifestToken="4.0" … >

 

Une fois toutes ces modifications effectuées, il est possible de se servir de SqlServerCE 4.0 sans soucis et en profitant de toutes les améliorations.

Parmi ces dernières, celle qui m’a intéressé c’est le déploiement privé qui permet d’embarquer tout ce qu’il faut pour SqlServerCE 4.0 sans avoir ainsi besoin d’installer quoi que ce soit. Il suffit pour cela de rajouter et d’embarquer dans notre projet l’intégralité du répertoire C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v4.0\Private et le tour est joué!

 

Pour retrouver tout ça en live, vous pouvez télécharger le SUBLIME UrzaGatherer : http://urzagatherer.codeplex.com

NB: Je viens de voir que le SKIP de Linq marche désormais! Super bonne nouvelle^^

.Net ,

Collecto 1.11

9. mars 2011

La version 1.10 (et son patch 1.11) de Collecto vient de sortir avec le support de la lecture de code barres.

Il est donc désormais possible pour ajouter un élément dans un collection de prendre en photo un code barre (bien éclairé et bien net). Collecto fera une reconnaissance de caractères pour en déduire le code barre qui sera par la suite envoyé aux webservices d’Amazon pour trouver à qui correspond ce dernier.

Le tout disponible gratuitement ici:http://social.zune.net/redirect?type=phoneApp&id=ef2b8553-0a18-e011-9264-00237de2db9e

large128x128

Collecto