Une ProgressBar circulaire facile

November 8th at 8:59am PapyCasu

J'ai activé, en personne sensible de sa sécurité, pas mal de 2FA sur pas mal d'applications et sites web. Et beaucoup de 2FA utilisent des token TOTP.

D'habitude, j'utilise Authy sur mon téléphone. Mais j'avoue que ça me saoulait de devoir sortir mon téléphone, utiliser mon empreinte digitale pour déverrouiller mon téléphone, aller dans mes applications, ouvrir Authy, utiliser mon empreinte digitale pour déverrouiller Authy, et enfin cliquer sur l'application en question.

Donc je me suis dit "et si je me faisais une application de génération de codes TOTP sur mon PC?" Donc c'est ce que j'ai fait.

C'est une application toute bête, avec une base SQLite pour enregistrer les infos, et j'ai décidé pour une fois de faire une application WPF. Et mon dieu que c'est chiant WPF! Bref, j'y arrive tant bien que mal, et ça donne ça:
myotp

Et ce qui m'a donné le plus de mal, c'est de faire le petit camembert sur la droite. Il se réduit au fur et à mesure que le code TOTP va expirer. Et mine de rien, ce n'est pas si simple à faire.

En général, je fais des applications WinForms, et il existe pleins de composants gratuits pour faire une ProgressBar circulaire. Mais en WPF, pas tant que ça, et encore moins des gratuits.

J'avais bien trouvé sfCircularProgressBar, qui peut être gratuit pour un usage personnel, mais il faut importer pleins de packages Nuget, demander une licence, etc... C'est relou.

Alors j'ai cherché plutôt du côté des diagrammes camembert, ou pie charts en anglais, et j'ai trouvé une video youtube avec quelques milliers de vues, et un lien vers un repo GitHub avec le code, et je vous livre un snippet tout prêt:

        // shamelessly stolen from https://github.com/kareemsulthan07/Charts
        // no license info available
        public void DrawPie(float progress, float angleShift = -90f, Brush? colorBrush = null)
        {
            canvas1.Children.Clear();

            Debug.WriteLine($"Canvas Width = {canvas1.Width}");
            Debug.WriteLine($"Canvas Height = {canvas1.Height}");

            if (colorBrush == null) { colorBrush = Brushes.SteelBlue; }

            float pieWidth = 70;            
            canvas1.Width = grid1.ColumnDefinitions[1].ActualWidth;
            canvas1.Height = grid1.RowDefinitions[0].ActualHeight * 3;
            float centerX = (float)(canvas1.Width / 2);
            float centerY = (float)(canvas1.Height / 2);
            float radius = pieWidth / 2;

            float angle = angleShift;

            double line1X = (radius * Math.Cos(angle * Math.PI / 180)) + centerX;
            double line1Y = (radius * Math.Sin(angle * Math.PI / 180)) + centerY;

            angle = progress * (float)360 / 100 + angleShift;

            Debug.WriteLine(angle);

            double arcX = (radius * Math.Cos(angle * Math.PI / 180)) + centerX;
            double arcY = (radius * Math.Sin(angle * Math.PI / 180)) + centerY;

            var line1Segment = new LineSegment(new Point(line1X, line1Y), false);
            double arcWidth = radius, arcHeight = radius;
            bool isLargeArc = progress > 50;
            var arcSegment = new ArcSegment()
            {
                Size = new Size(arcWidth, arcHeight),
                Point = new Point(arcX, arcY),
                SweepDirection = SweepDirection.Clockwise,
                IsLargeArc = isLargeArc,
            };
            var line2Segment = new LineSegment(new Point(centerX, centerY), false);

            var pathFigure = new PathFigure(
                new Point(centerX, centerY),
                new List<PathSegment>()
                {
                        line1Segment,
                        arcSegment,
                        line2Segment,
                },
                true);

            var pathFigures = new List<PathFigure>() { pathFigure, };
            var pathGeometry = new PathGeometry(pathFigures);
            var path = new Path()
            {
                Fill = colorBrush,
                Data = pathGeometry,
            };

           canvas1.Children.Add(path);
        }

Donc, qu'est-ce qu'on a là?

D'abord la déclaration: il faut fournir le progress, un float en pourcent, donc une valeur de 0f à 100f.
Par défaut, le camembert a son zéro à l'horizontale, donc si on veut le zéro ailleurs, on peut fournir un autre angle (-90f pour que le zéro soit à la verticale).
Et enfin, on peut donner une couleur.

Le canvas1.Children.Clear() sert à nettoyer le canvas avant de redessiner un camembert.

Ensuite, un peu de déclaration, surtout la taille.
Dans mon cas, cet objet ce trouve sur un canvas qui se trouve dans une grille, d'où les références à grid1 et canvas1.

Et enfin, le calcul.
D'abord, on définit un premier segment (avec seulement un point, la beauté de WPF...), ensuite l'arc de cercle (impossible de comprendre comment ça marche...), et enfin un dernier segment. Le tout est assemblé dans un PathGeometry, puis dans un Path, qui est enfin remplit avec la couleur spécifiée au début.

Il suffit enfin de simplement appeler par exemple DrawPie(25.2f), et c'est tout. Et donc mon cas, DrawPie(100f * _totp.Remaining / _totp.Step);.

Le tout est dans un composant graphique personnalisé, qui est rafraichi toutes les 500 milisecondes depuis un autre thread. Si j'y pense, je mettrai ça sur un repo public un jour...

Voilà, voilà, rien de foufou, mais moi, ça me va.

Gravatar

papycasu est un quadra qui n'y connait pas grand chose au jeux video mais qui s'y met quand même.

0 Comments:

Ajouter un commentaire