Site icon Blog ARC Optimizer

Utilisation de l’application Angular in Windows Forms

Utilisation de l’application Angular in Windows Forms


Voir comment utiliser Kendo UI pour les composants angulaires dans Telerik UI pour les applications WinForms pour échanger la communication et les événements.

Dans cet article, je vais montrer comment utiliser les progrès Kendo ui pour angulaire composants dans UI Telerik pour winforms applications. Vous apprendrez les pièges et comment implémenter la communication à Angular depuis WinForms et récupérer les événements d’Angular.

Je partage le complet code sourcequi est entièrement fonctionnel à l’aide de Telerik UI, .NET 8 et Angular, sur mon github.

Note: Ce message a été écrit avant le lancement de .NET 9 ou Angular 19, mais n’oubliez pas que vous pouvez commencer avec ces deux nouvelles versions. 😊

Si vous vous demandez: pourquoi ferais-je cela?

Il y a plusieurs scénarios où cela pourrait être appliqué:

  • Démarrer la migration des applications de Winforms Legacy à Angular
  • L’intégration des ressources locales (bases de données sur site et autres ressources WinForms)
  • Créer des applications d’interface utilisateur légères dans des applications WinForms complexes
  • Créer des applications WinForms avec l’apparence d’une application Web
  • Exécuter des UIS distribuées en cours d’exécution dans Docker, IIS ou Cloud
  • Mettez à jour l’interface utilisateur sans mettre à jour l’application client de l’application WinForms

Ces scénarios de transition à partir de vos applications hérités peuvent vous aider à utiliser les ressources de production actives tout en développant le nouveau service / application. Une solution hybride peut conserver les WinForms actuels tout en permettant aux développeurs de créer l’application côté client.

Le ciel est la limite.

Faisons-le

Pour reproduire cet échantillon, vous devez créer une application WinForms et un projet angulaire pour héberger les contrôles souhaités. Si vous vous intégrez à une application WinForms Legacy, il vous suffit de créer le projet angulaire.

Installez la dernière version d’Angular sur l’invite du terminal:

1.	npm install -g @angular/cli.

Entrez dans un répertoire cible par exemple C: \ Telerik et créez le nouveau projet:

1.	ng new my-app

Choisissez les options pour CSS:

Activer ou non SSR (rendu côté serveur) ou SSG (génération de site statique). Je préfère SSG pour les petites applications pour éviter un trafic réseau constant:

Attendez la fin de l’installation:

À propos de l’objectif de cet échantillon d’application

Pour ce cas, je montre à l’utilisation de l’interface utilisateur de Telerik pour un contrôle du graphique angulaire et de traiter l’événement cliquez sur WinForms.

Note: Progress Telerik construit un environnement sur le Web, ce qui est fantastique; Des milliers d’échantillons sont disponibles en ligne pour démarrer sur Telerik Technologies. Dans ce cas, j’utilise une partie de la Code de ce post.

Kendo UI pour l’application angulaire

Suivez les étapes pour configurer et utiliser les contrôles de WinForms.

  1. Nous allons utiliser les composants de l’interface utilisateur de Kendo, en particulier le graphique Kendo UI pour Angular Tie. Avant de créer le graphique et ses fonctionnalités, installons-la pour que notre application y ait accès. À la racine de notre my-app Projet, saisissez cette commande d’installation:
ng add @progress/kendo-angular-charts

Il y a plusieurs choses que cette commande fait pour nous (comme installer les graphiques et ses dépendances. En savoir plus sur cette commande, mais sur d’autres Cas d’installation spéciaux dans les documents d’interface utilisateur Kendo.

Maintenant, ajouter kendo-angular-charts à votre principal.

Il est nécessaire de créer les pages et les hôtes composants et ajouter une interface (verb) et un CustomEvent Pour retourner les données.

  1. Commencez à créer les pages avec les contrôles que vous aimez utiliser. Générons un composant angulaire appelé graph-control Pour les abriter, le contrôle du graphique hébergera le composant du graphique angulaire:

    ng g c graph-control
  2. Ajoutez votre nouveau composant à l’application.Routes.ts l’itinéraire pour les pages qui hébergent les composants:
import { Routes } from '@angular/router';
import { GraphControlComponent } from './graph-control/graph-control.component';
 
export const routes: Routes = [
    {
     path: 'graph-control', 
     component: GraphControlComponent 
    }
];
  1. Créez le composant pour héberger le contrôle avec la ligne de commande. Dans cet échantillon, nous démontons actuellement uniquement le composant du graphique:
1.	ng g c win-chart
  1. Personnalisez le contrôle.

Ajoutez l’interface qui sera utilisée pour échanger les données (receiveData) intégré à WinForms. J’appelle ces verbes car vous pouvez ajouter plus d’une interface pour transférer des données:

1.	declare global {
2.	  interface Window {
3.	    receiveData: (data: any) => void;
4.	  }
5.	}

Maintenant, juste à l’intérieur de notre composant Winchart, nous devons créer un public winFormsData: any = null; variable pour maintenir nos données.

  1. Ensuite, incorporons le stockage local pour préserver nos données. Nous pouvons l’utiliser pour stocker nos données entre les rafraîchissements de page; Il n’y a rien de plus exaspérant à un utilisateur que de perdre des progrès. Dans notre fonction init, nous pouvons obtenir les données du stockage local et mettre à jour notre winFormsData valeur si savedData existe ici.
1.	public winFormsData: any = null;
2.	 constructor() {
3.	    window.receiveData = (data: any) => {
4.	      this.winFormsData = data;  
5.	      localStorage.setItem('winFormsData', JSON.stringify(data)); 
6.	    }; 
7.	  }
8.	  
9.	  ngOnInit() {
10.	    const savedData = localStorage.getItem('winFormsData');
11.	    if (savedData) {
12.	      this.winFormsData = JSON.parse(savedData);
13.	    }
14.	  }

Ajoutez un événement de clic pour le graphique à utiliser dans le composant.html:

1.	  onSeriesClick(event: SeriesClickEvent): void {
2.	    const category = event.category;
3.	    const value = event.value;
4.	    
5.	    console.log('Category:', category);
6.	    console.log('Value:', value); 
7.	
8.	    const message = JSON.stringify({ category, value });
9.	
10.	    
11.	    const eventClick = new CustomEvent('MyClick', {
12.	      detail: { message: message }, 
13.	    });  
14.	
15.	    window.dispatchEvent(eventClick);  
16.	  }

Conseil: Ceci est un piège; Faites attention au JSON que vous reviendrez. Le format incorrect pour le JSON effacera la livraison:

1.	const message = JSON.stringify({ category, value });

Supprimez le HTML par défaut de Win-chart.component.html et allons-y et ajoutons un tableau d’interface utilisateur Kendo qui utilisera cette série Click que nous venons de faire.

1.	<div *ngIf="winFormsData === null">Loading....</div>
2.	// check the var winFormsData 
3.	
4.	<div *ngIf="winFormsData !== null"> 
5.	  <kendo-chart    
6.	    (seriesClick)="onSeriesClick($event)">
7.	    <kendo-chart-title
8.	      color="black"
9.	      font="12pt sans-serif"
10.	      text="WinForms x Angular - Data integration"
11.	    >
12.	    </kendo-chart-title>
13.	    <kendo-chart-legend position="top"></kendo-chart-legend>
14.	    <kendo-chart-series>
15.	      <kendo-chart-series-item
16.	        [data]="winFormsData"
17.	        [labels]="{ visible: true, content: label}"       
18.	        [type]="typeChart"
19.	        categoryField="name"
20.	        colorField="color"
21.	        field="value">
22.	      </kendo-chart-series-item>
23.	    </kendo-chart-series>
24.	  </kendo-chart>
25.	</div>

Sur la page Graph-Control, ajoutez le HTML pour se lier:

1.	<app-win-chart></app-win-chart>

Pour nous accélérer, je vais fournir le fichier complet pour win-carter.component.ts (il est également disponible sur mon référentiel GitHub):

1.	import { Component } from '@angular/core';
2.	import { ChartsModule, LegendLabelsContentArgs, SeriesClickEvent, SeriesType } from "@progress/kendo-angular-charts";
3.	import { CommonModule } from '@angular/common';
4.	
5.	declare global {
6.	  interface Window {
7.	    receiveData: (data: any) => void;
8.	  }
9.	}
10.	
11.	@Component({
12.	  selector: 'app-win-chart',
13.	  standalone: true,
14.	  imports: [ChartsModule, CommonModule],
15.	  templateUrl: './win-chart.component.html',
16.	  styleUrls: ['./win-chart.component.css']
17.	})
18.	export class WinChartComponent {
19.	  public winFormsData: any = null;
20.	  public typeChart: SeriesType = "pie";
21.	  
22.	  constructor() {
23.	    window.receiveData = (data: any) => {
24.	      this.winFormsData = data;  
25.	      localStorage.setItem('winFormsData', JSON.stringify(data)); 
26.	    }; 
27.	  }
28.	  
29.	  ngOnInit() {
30.	    const savedData = localStorage.getItem('winFormsData');
31.	    if (savedData) {
32.	      this.winFormsData = JSON.parse(savedData);
33.	    }
34.	  }
35.	
36.	  public label(args: LegendLabelsContentArgs): string {
37.	    return `${args.dataItem.name}`;
38.	  } 
39.	
40.	  onSeriesClick(event: SeriesClickEvent): void {
41.	    const category = event.category;
42.	    const value = event.value;
43.	    
44.	    console.log('Category:', category);
45.	    console.log('Value:', value); 
46.	
47.	    const message = JSON.stringify({ category, value });
48.	
49.	    
50.	    const eventClick = new CustomEvent('MyClick', {
51.	      detail: { message: message }, 
52.	    });  
53.	
54.	    window.dispatchEvent(eventClick);  
55.	  }
56.	
57.	}

Maintenant que votre application angulaire est prête, commençons à utiliser l’application WinForms.

Configuration de l’application WinForms

Dans l’application WinForms, j’ai isolé le composant hôte webView2 sur UserControl, AngularWebControl.cs, de sorte que tous les composants ont la même base UserControl et partagent le même comportement.

Le WebView2 est nécessaire pour maintenir l’application angulaire à partir de l’URL et interagir avec les WinForms.

Il s’agit des fichiers de la solution du projet C # ressemblera à ceci:

L’Angulardefs.cs héberge les définitions qui détiennent le projet angulaire en un seul endroit. Cela pourrait également être des variables d’environnement pour éviter les données codées durs:

1.	namespace app_winforsm;
2.	internal static class AngularDefs
3.	{
4.	    
5.	    public const string Url = "https://aw.jsmotta.com/";
6.	
7.	    
8.	    public const string RouteGraph = "graph-control";
9.	
10.	    
11.	    public const string ChartVerb = "receiveData";
12.	}

L’angularwebcontrol.cs contient les tâches de l’interface. J’ai ajouté quelques explications au code ci-dessous. Il définit l’interface avec le composant, lit l’événement Click et le transmet au gestionnaire d’événements.

1.	using Microsoft.Web.WebView2.Core;
2.	using Microsoft.Web.WebView2.WinForms;
3.	using System.Text.Json;
4.	using Telerik.WinControls.UI;
5.	
6.	namespace app_winforsm;
7.	internal partial class AngularWebControl : UserControl
8.	{
9.	    
10.	    private WebView2? _webView;
11.	
12.	    
13.	    public event EventHandler? OnChartItemClick;
14.	
15.	    
16.	    private dynamic? Data { get; set; }
17.	
18.	    
19.	    
20.	    private RadLabel? Title { get; set; }
21.	
22.	    public AngularWebControl()
23.	    {
24.	        InitializeComponent();
25.	    }
26.	    public async void LoadData(string title, dynamic data)
27.	    {
28.	        if (Title == null)
29.	        {
30.	            Title = new RadLabel
31.	            {
32.	                Text = title,
33.	                Dock = DockStyle.Top,
34.	                Width = this.Width,
35.	                AutoSize = true,
36.	                Font = new Font("Arial", 12, FontStyle.Bold),
37.	                ThemeName = "Windows11"
38.	            };
39.	
40.	
41.	            this.Controls.Add(Title);
42.	
43.	            Title.MouseUp += Title_MouseUp;
44.	        }
45.	
46.	        this.Title.Text = title;
47.	
48.	        if (_webView == null)
49.	        {
50.	            _webView = new WebView2
51.	            {
52.	                Visible = true,
53.	                Dock = DockStyle.Fill
54.	            };
55.	
56.	            this.Controls.Add(_webView);
57.	
58.	            var userDataFolder1 = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), $"AngularWinFormsApp_{this.Name}");
59.	                    
60.	            var environment1 = await CoreWebView2Environment.CreateAsync(userDataFolder: userDataFolder1);
61.	
62.	            
63.	            await _webView.EnsureCoreWebView2Async(environment1);
64.	
65.	
66.	            _webView.CoreWebView2.NavigationCompleted += WebView_NavigationCompleted;
67.	           
68.	            
69.	            _webView.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
70.	
71.	            _webView.CoreWebView2.Navigate($"{AngularDefs.Url}{AngularDefs.RouteGraph}");
72.	
73.	            if (OnChartItemClick != null)
74.	            {
75.	                
76.	                await _webView.CoreWebView2.ExecuteScriptAsync(@"
77.	                    window.addEventListener('MyClick', function(event) {
78.	                        window.chrome.webview.postMessage(event.detail.message);
79.	                    });
80.	                ");
81.	            }
82.	        }
83.	
84.	        
85.	        this.Data = data;
86.	    }
87.	
88.	    private void Title_MouseUp(object? sender, MouseEventArgs e)
89.	    {
90.	        if (e.Button == MouseButtons.Right)
91.	        {
92.	            
93.	            
94.	            ShowWebViewConsole();
95.	        }
96.	    }
97.	
98.	    
99.	    private void CoreWebView2_WebMessageReceived(object? sender, CoreWebView2WebMessageReceivedEventArgs e)
100.	    {
101.	        
102.	        var message = e.TryGetWebMessageAsString();
103.	
104.	        
105.	        OnChartItemClick?.Invoke(message, EventArgs.Empty);
106.	    }
107.	    private async void WebView_NavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e)
108.	    {
109.	        if (_webView == null) return;
110.	
111.	        _webView.Visible = true;
112.	
113.	        if (!e.IsSuccess)
114.	        {
115.	            
116.	            switch (e.WebErrorStatus)
117.	            {
118.	
119.	                case CoreWebView2WebErrorStatus.ConnectionAborted:
120.	                    ShowErrorMessage("Connection refused. Please make sure the server is running and try again.");
121.	                    break;
122.	                case CoreWebView2WebErrorStatus.Unknown:
123.	                case CoreWebView2WebErrorStatus.CertificateCommonNameIsIncorrect:
124.	                case CoreWebView2WebErrorStatus.CertificateExpired:
125.	                case CoreWebView2WebErrorStatus.ClientCertificateContainsErrors:
126.	                case CoreWebView2WebErrorStatus.CertificateRevoked:
127.	                case CoreWebView2WebErrorStatus.CertificateIsInvalid:
128.	                case CoreWebView2WebErrorStatus.ServerUnreachable:
129.	                case CoreWebView2WebErrorStatus.Timeout:
130.	                case CoreWebView2WebErrorStatus.ErrorHttpInvalidServerResponse:
131.	                case CoreWebView2WebErrorStatus.ConnectionReset:
132.	                case CoreWebView2WebErrorStatus.Disconnected:
133.	                case CoreWebView2WebErrorStatus.CannotConnect:
134.	                case CoreWebView2WebErrorStatus.HostNameNotResolved:
135.	                case CoreWebView2WebErrorStatus.OperationCanceled:
136.	                case CoreWebView2WebErrorStatus.RedirectFailed:
137.	                case CoreWebView2WebErrorStatus.UnexpectedError:
138.	                case CoreWebView2WebErrorStatus.ValidAuthenticationCredentialsRequired:
139.	                case CoreWebView2WebErrorStatus.ValidProxyAuthenticationRequired:
140.	                default:
141.	                    ShowErrorMessage("An error occurred while loading the page.");
142.	                    break;
143.	            }
144.	            return;
145.	        }
146.	
147.	        var jsonData = JsonSerializer.Serialize(Data);
148.	
149.	        
150.	        var script = $"window.{AngularDefs.ChartVerb}({jsonData});";
151.	
152.	        await _webView.CoreWebView2.ExecuteScriptAsync(script);
153.	    }
154.	 
155.	}

Le message.cs est le modèle de l’événement de clic interagit à partir de l’application angulaire.

Voici le cas d’utilisation des contrôles dans FormMain.cs. J’ai ajouté un contrôle dynamiquement et un autre à l’aide de glisser-déposer à partir de la boîte à outils. Il est important de noter qu’un nom de propriété distinct est nécessaire pour éviter les collisions sur les sessions WebView2; Ceci est un piège.

J’utilise des données moquées dans cet échantillon, mais vous lirez probablement une source de données dans des applications réelles.

1.	using System.Text.Json;
2.	using Telerik.WinControls;
3.	using Telerik.WinControls.UI;
4.	
5.	namespace app_winforsm;
6.	
7.	public partial class FormMain : RadForm
8.	{
9.	    private readonly AngularWebControl? _angularWebControl;
10.	
11.	    public FormMain()
12.	    {
13.	        InitializeComponent();
14.	
15.	        
16.	
17.	        _angularWebControl = new AngularWebControl { Name = "_angularWebControl" };
18.	        _angularWebControl.Dock = DockStyle.Fill;
19.	
20.	        splitPanel1.Controls.Add(_angularWebControl);
21.	
22.	        
23.	        _angularWebControl.OnChartItemClick += AngularWebControl_OnChartItemClick;
24.	
25.	        LoadData();
26.	    }
27.	
28.	    private void AngularWebControl_OnChartItemClick(object? sender, EventArgs e)
29.	    {
30.	        if (sender is null)
31.	            return;
32.	
33.	        var message =
34.	            JsonSerializer.Deserialize<Message>(sender.ToString() ?? throw new Exception("Data is not a json."));
35.	
36.	        RadMessageBox.ThemeName = "Windows11";
37.	        RadMessageBox.Show($"You clicked on {message.Category} with value {message.Value}", "Chart Item Clicked",
38.	            MessageBoxButtons.OK, RadMessageIcon.Info);
39.	    }
40.	
41.	    private void LoadData()
42.	    {

Note: Dans un projet de production, vous chargerez les données de votre référentiel!

43.	
44.	        var data = new[]
45.	        {
46.	            new { name = "Gastroenteritis", value = 40, color = "red" },
47.	            new { name = "Appendicitis", value = 25, color = "blue" },
48.	            new { name = "Cholecystitis", value = 15, color = "green" },
49.	            new { name = "Pancreatitis", value = 10, color = "yellow" },
50.	            new { name = "Diverticulitis", value = 10, color = "orange" }
51.	        };
52.	
53.	        _angularWebControl?.LoadData("Common gastro deseases in hospitals", data);
54.	
55.	        var dataAges = new[]
56.	        {
57.	            new { name = "0-10", value = 1, color = "red" },
58.	            new { name = "11-20", value = 10, color = "blue" },
59.	            new { name = "21-30", value = 20, color = "green" },
60.	            new { name = "31-40", value = 25, color = "yellow" },
61.	            new { name = "41-50", value = 15, color = "orange" },
62.	            new { name = "51-60", value = 20, color = "purple" },
63.	            new { name = "61-70", value = 8, color = "brown" },
64.	            new { name = "71+", value = 7, color = "pink" }
65.	        };
66.	
67.	        this.angularWebControl1.LoadData("Patiant ages in gastro deseases", dataAges);
68.	    }
69.	}

Et c’est le résultat!

Comme vous pouvez le voir, les deux graphiques partagent la même interface et userControl. Vous ne pouvez pas les voir mais vous êtes dans une session Web distincte. La session est isolée pour préserver les données et pour la sécurité, le même UserControl pourrait utiliser des informations d’identification distinctes selon l’URL transmise en tant que paramètre.

Flux de travail de cet échantillon

Dans l’image ci-dessous, nous pouvons «voir» le flux de codage et lors de l’exécution jusqu’à ce que le rappel lorsque l’utilisateur final clique sur le graphique, téléchargez le code source de Github et essayez-le.

Conclusion

L’interopérabilité n’est pas complexe, ce qui peut tirer parti d’une équipe mixte et concevoir de meilleures interfaces avec une faible consommation de mémoire que les applications WinForms classiques.

Dans Edge Computing, j’imagine que l’interface s’exécute sur un serveur près de l’utilisateur final, même un Docker / Azure / AWS local près de la machine client, en évitant le long trafic Web.

Téléchargez le code et voyez comment cela fonctionne et toutes les possibilités que cette fonctionnalité peut apporter à votre entreprise et services.

N’hésitez pas à utiliser le code ou à me contacter sur LinkedIn. N’oubliez pas non plus que Progress Telerik offre un soutien gratuit pendant la période d’évaluation.

Essayez Telerik Devcraft aujourd’hui

Références




Source link
Quitter la version mobile