Le truc à la con du jour : Lancement de plusieurs fenêtres en série dans WPF

4. juin 2010

Un truc bien débile qui m’est arrivé aujourd’hui. Au sein d’une application WPF qui poutre (http://urzagatherer.codeplex.com), je voulais, lors du lancement ouvrir une première fenêtre avant la fenêtre principale.

Le code ressemble donc à ça dans le constructeur de mon App:

            InstallDatabaseWindow databaseWindow = new InstallDatabaseWindow();
            databaseWindow.ShowDialog();

            MainWindow mainWindow = new MainWindow();
            mainWindow.Show();

Rien de bien formidable me direz-vous? Et bien si vous faites un test, vous verrez que le Show sur votre mainWindow.Show() plantera généreusement avec le message suivant:

“Cannot set Visibility or call Show, ShowDialog, or WindowInteropHelper.EnsureHandle after a Window has closed.”

Bon, au premier abord, je me suis dit que j’ai du merdé dans mon constructeur.

En fait, pas du tout (je me disais aussi que ce n’était pas possible) l’explication est plus sioux : Par défaut, une application WPF possède une propriété ShutdownMode qui est par défaut réglée sur OnLastWindowClose. En gros dès qu’il n’y a plus de fenêtres en vie, l’application va se fermer gentillement.

De ce fait, dans le cas du lancement de plusieurs fenêtre en série, dès que la première se ferme, l’application vérifie sa propriété ShutdownMode et se retrouve à se fermer puisque la fenêtre suivante n’a pas encore été instanciée! Ce qui fait que lorsque l’on va faire le Show() suivant, comme l’application est en cours de fermeture, la fenêtre va recevoir un Close et donc ne pourra pas s’ouvrir puisqu’elle a déjà été fermée :)

Deux solutions:

  • Instancier ses fenêtres toutes en simultanée avant de faire apparaitre la première
  • Mettre l’application sur le mode ShutdownMode.OnExplicitShutdown et s’abonner à l’événement Closed de la dernière fenêtre pour appeler la méthode Shutdown() sur l’application

 

Et le tour est joué…

Bookmark and Share

.Net, WPF

De l’utilisation intelligente d’Entity Framework 4.0

31. mai 2010

Un framework a beau être super puissant, il ne peut pas empêcher l'a mauvaise utilisation. Il peut tenter par de nombreux moyens de la réduire mais il ne peut l’empêcher.

Du moins pas sans se brider par la suite.

J’en veux pour preuve l’utilisation d’Entity Framework 4.0. Ce framework permet de manière extrêmement élégante de requêter une source de données. Et, bien utilisé, il permet aussi d’être très performant.

Voici un exemple issu d’UrzaGatherer: Mon modèle possède une collection de Card qui, entre autres, contient une propriété Check qui permet de définir si l’utilisateur détient complètement la carte en question. Dans le cadre de la treeview qui affiche les collections de cartes, je voulais modifier le texte de chaque collection en y ajoutant le nombre de cartes manquantes le cas échéant.

Pour se faire et comme tout est issu du binding dans UrzaGatherer, j’ai développé un ValueConverter utilisé ainsi:

<TextBlock Text="{Binding Converter={StaticResource CountConverter}, Mode=OneWay
, IsAsync=True}"
/>

Le code du converter était le suivant:

    public class CountConverter : IValueConverter
    {
    public object Convert(object value, Type targetType, object parameter, 
CultureInfo culture) { Expansion expansion = (Expansion) value; var query = from card in MainWindow.Entities.Cards where card.ExpansionID == expansion.ID select card; int check = 0; List<Card> cards = query.ToList(); int count = cards.Count; foreach (Card card in cards) { if (card.Check) check++; } int missing = count - check; if (missing == 0) return string.Format("- {0} cards", count); return string.Format("({0}/{1} - Missing : {2})", check, count, missing); } public object ConvertBack(object value, Type targetType, object parameter,
CultureInfo culture) { return value; } }

Dans ce converter, on voit donc une requête LINQ sur les cartes en question, puis une transformation en liste pour faire notre comptage.

Et c’est là qu’apparait la mauvaise utilisation. En effet, la conversion en liste va effectivement faire la requête sur la base de données et créer les entités en mémoire. Ces entités qui peuvent être lourdes (c’est le cas ici ou chaque carte porte plusieurs images).

Tout ça pour juste obtenir un comptage.

Il faut garder à l’esprit que EF4 est efficace tant qu’on reste dans le monde des expressions LINQ, c’est à dire tant qu’on reste au final dans le monde SQL.

Ici par exemple, la solution pour être bien plus efficace est simple : Il ne faut pas utiliser de listes locales mais tout traiter en LINQ:

public object Convert(object value, Type targetType, object parameter, 
CultureInfo culture) { Expansion expansion = (Expansion) value; var query = from card in MainWindow.Entities.Cards where card.ExpansionID == expansion.ID select card; int check = query.Count(c => c.Check); int count = query.Count(); int missing = count - check; if (missing == 0) return string.Format("- {0} cards", count); return string.Format("({0}/{1} - Missing : {2})", check, count, missing); }

La différence est subtile mais ici tout se passera sur SQL Server. Aucune entité ne sera créée ni ramenée côté client. Les méthodes Count() issues de LINQ vont générer des requêtes efficaces côté serveur contrairement à la méthode ToList() qui va faire un bon gros SELECT des familles pour tout reconstruire en mémoire.

Donc en conclusion et en ce qui concerne Entity Framework 4.0 : Retardez toujours au plus tard la récupération des entités!!

Bookmark and Share

.Net, WPF ,

UrzaGatherer

31. mai 2010

Parmi mes nombreux vices, j’ai notamment le plaisir d’être collectionneur de cartes Magic.

Or cette collection est extrêmement riche et complexe à gérer. J’ai donc développé en WPF4.0, une application de gestion associée.

Elle utilise SQL Server 2008 R2 express pour stocker les dizaines de milliers d’images. Le système s’appuie sur Entity Framework 4.0 (qui d’ailleurs est une tuerie).

Je mettrai dans quelques temps le code source de l’application sur Codeplex.

image image

image image

Bookmark and Share

WPF, .Net, UrzaGatherer

Le truc à la con du jour : Sélectionner un item dans une treeview WPF

11. avril 2010

C’est ballot mais contrairement à Windows Forms, il n’y a pas un support direct de la sélection d’un item dans une treeview WPF.

Toutefois, la solution est relativement simple, puisqu’elle passe par la fonctionnalité des treeviews qui permet de récupérer les TreeViewItems depuis les objets liés : ItemContainerGenerator.

Ainsi, en respectant la hierarchie de la treeview on peut parcourir l’arbre à la recherche du container de l’item.

Cela donne donc le code suivant:

public static bool SetSelected(ItemsControl parent, object child)
{
    if (parent == null || child == null)
    {
        return false;
    }

    TreeViewItem childNode = parent.ItemContainerGenerator.ContainerFromItem(child) as TreeViewItem;

    if (childNode != null)
    {
        childNode.Focus();
        return childNode.IsSelected = true;
    }

    if (parent.Items.Count > 0)
    {
        foreach (object childItem in parent.Items)
        {
            ItemsControl childControl = parent.ItemContainerGenerator.ContainerFromItem(childItem) 
as ItemsControl; if (SetSelected(childControl, child)) { return true; } } } return false; }

Et le tour est joué.

Bookmark and Share

.Net, WPF

Composition d’images avec WPF

8. avril 2010

Dans le cadre d’un projet, j’ai eu besoin de générer une image contenant d’autres images composées ainsi que du texte. Ceci devait être fait dans un thread séparé pour des raisons de performances.

Pour cela, il existe une technologie bien sympatique en WPF, le RenderTargetBitmap. Ce dernier permet en effet de dessiner des Visual directement dans son contenu. Pour ce faire on s’appuie sur la classe DrawingVisual qui est un peu l’équivalent de la classe Graphics en Windows Forms/GDI+.

Cette dernière permet par exemple de dessiner des images (et donc de les redimensionner) ainsi que du texte (notamment).

Cela donne donc ceci:

FormattedText text = new FormattedText("Message à caractère informatif",
new CultureInfo("en-us"), FlowDirection.LeftToRight,
new Typeface(new FontFamily("Arial"), FontStyles.Normal, FontWeights.Bold, new FontStretch())
, 18, Brushes.Black); DrawingVisual drawingVisual = new DrawingVisual(); DrawingContext drawingContext = drawingVisual.RenderOpen(); drawingContext.DrawImage(image, new Rect(0, 0, renderTarget.PixelWidth, renderTarget.PixelHeight)); drawingContext.DrawText(text, new Point(renderTarget.PixelWidth - text.Width - 45, 350)); text.SetForegroundBrush(Brushes.White); drawingContext.DrawText(text, new Point(renderTarget.PixelWidth - text.Width - 45 - 1, 349)); drawingContext.Close(); renderTarget.Render(drawingVisual);

 

Par la suite, ce RenderTargetBitmap peut servir de source à un contrôle Image ou peut être sauvegarder sur le disque via la classe BmpBitmapEncoder:

string picturePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
, "urza.bmp"); FileStream stream = new FileStream(picturePath, FileMode.Create); BmpBitmapEncoder encoder = new BmpBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(renderTarget)); encoder.Save(stream);
 
Et le tour est joué!
Bookmark and Share

.Net, WPF

WPF : Chargement en asynchrone d’images dans un wrap panel

17. mars 2010

Déjà, juste le titre, ça calme bien. Un peu autant que le temps que j’ai passé à trouver la solution en fait.

Voici donc le problème : J’ai un wrap panel en WPF qui sert d’ItemPanel à une ListBox. Jusque là on ne se fatigue pas trop non plus:

<ListBox ItemsSource="{Binding}" x:Name="imagesList">
                    <ListBox.ItemsPanel>
                        <ItemsPanelTemplate>
                            <WrapPanel>
                            </WrapPanel>
                        </ItemsPanelTemplate>
                    </ListBox.ItemsPanel>
                </ListBox>

Toutefois, lorsque je veux binder une grosse liste d’images (plus de 300) en provenance de ma base de données à ma ListBox, je me récupére en supplément un bon gros lag car le gentil Wrap Panel ne sait pas virtualiser ses items et doit donc tous les créer pour s’afficher (contrairement par exemple à une ListView qui saura ne construire que les items qu’elles affichent en temps réel).

Donc, je me suis lancé à la recherche d’une solution qui me permettrait d’asynchroniser le remplissage de mon Wrap panel.

Première idée, on met benoitement notre imagesList.DataContext = maListe dans un ThreadPool.QueueUserWorkItem. Et là c’est le drame car WPF me bloque comme j’essaye d’accéder à un DispatcherObject depuis un autre thread que celui qui a servit à construire le contrôle.

La solution est un tantinet plus ardue:

Premièrement, il faut modifier notre objet qui porte l’image à afficher pour lui ajouter une propriété supplémentaire de type BitmapFrame (qui a l’énorme avantage de ne pas vérifier l’origine du thread appelant). Il est ici important de ne pas laisser le BitmapFrame se mettre en DelayLoad depuis le cache d’image d’ou l’utilisation du paramètre BitmapCacheOption.OnLoad:

    public partial class Card
    {
        BitmapFrame bitmapImage;

        public BitmapFrame Bitmap
        {
            get
            {
                return bitmapImage;
            }
        }

        public void GenerateBitmap()
        {
            if (bitmapImage != null)
            {
                return;
            }
            using (MemoryStream ms = new MemoryStream(Image))
            {
                bitmapImage = BitmapFrame.Create(ms, BitmapCreateOptions.None, 
BitmapCacheOption.OnLoad); } } }

Deuxièmement, avant d’affecter le DataContext de notre ListBox, nous allons appeler de manière asynchrone sur chacun de nos objets la méthode GenerateBitmap.

Finalement, il suffit de faire un petit Dispatcher.Invoke pour affecter notre DataContext:

ThreadPool.QueueUserWorkItem(o =>
          {
            foreach (Card card in list)
            {
               card.GenerateBitmap();
            }
            Dispatcher.Invoke(DispatcherPriority.Normal, new Action(()=>
            {
               imagesList.DataContext = source;
            }));
});

Le résultat fait plaisir à voir je vous l’assure :)

Bookmark and Share

.Net, WPF

Le truc à la con du jour : Gérer le double clic en WPF

14. mars 2010

J’ai eu plusieurs fois la question : Maiiiiiis pourquoi WPF ne gère pas le double clic!!! C’est trop nul WPF.
Alors en fait oui mais non. Effectivement il n’y a pas un événement qui s’appelle “MouseDoubleClick”. Toutefois le double clic est bien géré ma bonne dame!

Et c’est même plutôt bien gérer. Car après tout, que se passerait-il si je voulais un MouseTripleClick? Il faudrait que je chiale auprès de l’équipe de développement de WPF ?

Alors pour couper court à ce suspens insoutenable, voici la solution : Abonnez vous à l’événement MouseDown (par exemple) et regarder le gentil paramètre MouseButtonEventArgs qui vous est fait passé. Notez surtout la propriété ClickCount. Et paf miracle:) Cette petite propriété gère le double clic (et plus) pour nous!

Un exemple en direct:

private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
{
    if (e.ClickCount == 2)
    {
        if (FullImage.Visibility == Visibility.Hidden)
            FullImage.Visibility = Visibility.Visible;
        else
            FullImage.Visibility = Visibility.Hidden;
    }
}

 

Et voilou…
Bookmark and Share

WPF, .Net

BDC 2010 – C’est reparti!

4. mars 2010

A la demande générale, la Bewise Day Conference est de retour pour une 4ème édition ! Encore une fois, venez découvrir ce qui se fait de mieux dans les technologies Microsoft.

Cette année, beaucoup de nouveautés, puisque Microsoft lance la gamme 2010 de ses produits, notamment Visual Studio, Sharepoint, mais aussi la version 4.0 de son framework .Net, MVC 2, Azure, Silverlight 4 et bien d’autres choses bien croustillantes.

J’animerai les plénières cette année et je vous conseille d’y participer car vous y découvrirez le grand secret de Bewise :).

Pour les inscriptions, rendez-vous sur le site officiel de la BDC 2010.

Bookmark and Share

Bewise, .Net, 3D, Silverlight, Visual Studio, Windows Forms, Windows Mobile, WPF

Back From The TechDays

13. février 2009

Et voila, les TechDays 2009 sont finis. Ce fut une fois de plus un superbe événement ou j’ai eu beaucoup de plaisir à animer 3 sessions. Je pense notamment à ma session Coding For Fun avec mon camarade Mitsu Furuta ou l’on s’est bien amusé.

Pour ceux qui souhaitent retrouver les présentations et tout le code des démos, c’est par ici que ca se passe.

Bookmark and Share

.Net, DirectX, WPF, Windows

TechDays 2009

4. février 2009

Petite pub pour mes sessions aux TechDays 2009:

 DirectX10 pour les Windows Formstdspeakermsfv9

 Nouveautés de WPF 3.5 SP1

 Coding4Fun

 

Comme toujours mes sessions sont très orientées 3D et présentation. Nous parlerons donc de DirectX10, de pixels shaders et avec Mitsu nous vous montrerons que l’on peut s’amuser avec des petites applications “ludiques” ;)

Bookmark and Share

DirectX, .Net, Windows, WPF