Renseigner les champs d'une forme avec des requêtes asynchrones
MGD Software
Messages postés186Date d'inscriptionvendredi 1 septembre 2006StatutMembreDernière intervention23 avril 2022
-
Modifié le 10 déc. 2020 à 19:11
Whismeril
Messages postés18638Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention 3 octobre 2023
-
12 déc. 2020 à 18:19
Bonjour,
Je sais, le problème est très connu, seulement voilà, je n'ai trouvé aucune réponse claire à mon problème, même en écumant le net. On trouve beaucoup de choses sur les méthodes asynchrone, mais je n'ai pas trouvé ou pas compris comment on en "sortait".
Voici mon problème (mais je souhaiterait une réponse générique):
J'ai un forme dans laquelle figure un DataGridView dont plusieurs champs doivent être remplis par une requête internet.
Cela fonctionne très bien avec des requêtes synchrones, mais quand il y a beaucoup de lignes cela prend un temps fou, d'autant qu'une des colonnes du DataGridView est remplie avec des images issues du web.
Le problème est que lorsque j'utilise une tâche (Task), je n'ai pas accès au thread appelant et donc je ne peux pas remplir ma grille. J'ai tenté un invoke + delegate, mais ça ne fonctionne pas, le "await" n'est pas accepté. En voici cependant le code pour montrer ce que je voudrais faire :
// Remplissage des images en asynchrone
this.Invoke(new MethodInvoker(delegate
{
foreach (DataGridViewRow Row in dgvList.Rows)
{
FilmInfo Film = Liste[Row.Index];
if (Film.poster_path != null && !Film.poster_path.Equals(""))
{
DataGridViewImageCell ImgCell = (DataGridViewImageCell)Row.Cells["Affiche"];
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create(Film.poster_path);
Request.Method = "GET";
HttpWebResponse Response = (HttpWebResponse)(await Request.GetResponseAsync());
if (Response != null)
{
Bitmap bmp = new Bitmap(Response.GetResponseStream());
Response.Close();
Film.poster = bmp;
// Redimensionnement de l'image à la largeur de la colonne
int PosterWidth = dgvList.Columns["Affiche"].Width;
int PosterHeight = PosterWidth * bmp.Height / bmp.Width;
Size NewSize = new Size(PosterWidth, PosterHeight);
ImgCell.Value = new Bitmap(bmp, NewSize);
}
}
}
}));
Film est une classe, le membre poster_path contient l'URL de l'image à récupérer et le membre poster est une variable image destinée à contenir l'affiche du film.
Quelqu'un peut-il m'indiquer une routine simple (?!) permettant de mettre à jour un champ d'une forme à partir d'une requête asynchrone, ou de renseigner une variable extérieure à la tâche ?
Merci
Edit : en enlevant le "await", en utilisant GetResponse à la place de GetResponseAsync , et en mettant une ligne dgvList.Refresh() en fin de boucle, cela semble fonctionner, les images s'affichent après le reste du tableau. Mais je ne suis pas sur que ce soit la bonne solution...
MGD Software
Messages postés186Date d'inscriptionvendredi 1 septembre 2006StatutMembreDernière intervention23 avril 20222 11 déc. 2020 à 10:13
Bonjour Whismeril,
En fait, cela ne fonctionne pas si bien que ça. Si c'est moins frustrant pour l'utilisateur de ne pas rester longtemps sur une grille vide, il n'en reste pas moins que le programme ne rend pas la main avant que toutes les images ne soient chargées. On ne peut donc pas faire défiler la grille, ni faire quoi que ce soit pendant un bon moment.
J'ai essayé d’utiliser le backgroundworker, mais tout ce qu'il sait faire en matière de communication avec les autres threads c'est d'envoyer deux évènements, un de pourcentage d'exécution et l'autre de fin de process. Ce n'est pas utilisable dans mon cas.
Il doit bien y avoir un moyen de faire communiquer deux threads en échangeant un objet commun, bon sang !
Une solution provisoire est de placer un Application.DoEvents() dans la boucle, mais alors là ce n'est VRAIMENT PAS la bonne solution.
Whismeril
Messages postés18638Date d'inscriptionmardi 11 mars 2003StatutContributeurDernière intervention 3 octobre 2023629 12 déc. 2020 à 18:19
Bonsoir
concernant le backgroundworker.
La méthode ReportProgress dispose de 2 surcharges, la seconde te permet de passer l'argument UserState de type object (donc n'importe quoi)
Que tu récupères dans le paramètre e de la méthode abonnée
Il te faut juste le caster comme il faut pour t'en servir.
Mais je vais te montrer une autre méthode.
Comme je ne sais pas ce que tu vas chercher comme image, j'en ai pris une dans "Mes Images"
J'ai d'abord écrit une classe.
Elle implémente InotifyPropertyChanged pour le binding.
using System.ComponentModel;
using System.Drawing;
namespace test
{
class TestMGD : INotifyPropertyChanged
{
private Bitmap image;
/// <summary>
/// Image
/// </summary>
public Bitmap Image
{
get { return image; }
set
{
if (image != value)
{
image = value;
GenerePropertyChanged("Image");
}
}
}
private int id;
/// <summary>
/// ID
/// </summary>
public int ID
{
get { return id; }
set
{
if (id != value)
{
id = value;
GenerePropertyChanged("ID");
}
}
}
private string nom;
/// <summary>
/// Nom
/// </summary>
public string Nom
{
get { return nom; }
set
{
if (nom != value)
{
nom = value;
GenerePropertyChanged("Nom");
}
}
}
private string cheminImage;
/// <summary>
/// Chemin de l'image
/// </summary>
public string CheminImage
{
get { return cheminImage; }
set
{
if (cheminImage != value)
{
cheminImage = value;
GenerePropertyChanged("CheminImage");
}
}
}
#region INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
private void GenerePropertyChanged(string Propriete)
{
if (this.PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(Propriete));
}
#endregion
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
using System.IO;
using System.Threading;
namespace test
{
public partial class Form3 : Form
{
public Form3()
{
InitializeComponent();
}
List<TestMGD> lesItemsDeMDG;
BackgroundWorker bgw;
private void Form3_Load(object sender, EventArgs e)
{
//initialisation des la collection source de données
lesItemsDeMDG = new List<TestMGD>
{
new TestMGD{ ID = 1, Nom = "Item 1", CheminImage = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Wismerhill.jpg")},
new TestMGD{ ID = 2, Nom = "Item 2", CheminImage = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Wismerhill.jpg")},
new TestMGD{ ID = 3, Nom = "Item 3", CheminImage = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Wismerhill.jpg")},
new TestMGD{ ID = 4, Nom = "Item 4", CheminImage = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Wismerhill.jpg")},
};
testMGDBindingSource.DataSource = lesItemsDeMDG;//binding
bgw = new BackgroundWorker();
bgw.DoWork += Bgw_DoWork;
bgw.RunWorkerAsync();
}
/// <summary>
/// Chargement des images en arrière plan
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Bgw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < lesItemsDeMDG.Count; i++)
{
TestMGD item = lesItemsDeMDG[i];
Thread.Sleep(2000);//pour simuler un temps long
Bitmap bmp = new Bitmap(item.CheminImage);
item.Image = new Bitmap(bmp, new Size(bmp.Width / 10, bmp.Height / 10));//mon image est beaucoup trop grande
//seul le thread principal peut agrandir la ligne pour s'adapter à l'image, alors on invoke
dataGridView1.Invoke
(
new Action(() =>
{
dataGridView1.Rows[i].Height = item.Image.Height;
}
)
);
}
}
}
}