IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

MFC Discussion :

Synchronisation dans la création des ressources MFC (problème)


Sujet :

MFC

  1. #1
    Membre régulier
    Profil pro
    Inscrit en
    Octobre 2007
    Messages
    157
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2007
    Messages : 157
    Points : 78
    Points
    78
    Par défaut Synchronisation dans la création des ressources MFC (problème)
    Bonjour,

    depuis que je suis passé sous VS2010 (c++), j'ai de gros problèmes de fiabilité concernant tout ce qui est ressources graphiques (contrôles, dialogues).

    En effet, dans mes applis récentes il arrive assez souvent que des contrôles s'initialisent mal (ce sont des contrôles "faits maison" à dérivés de CWnd).

    Ce que j'entends par "s'initialisent mal" c'est que l'initialisation a l'air de se faire avant que toutes les propriétés des dialogues soient mises à jour (taille, position, ce genre de chose). Donc comme je récupère par exemple les informations de taille et de position client (ou autre), si elles ne sont pas correctes, mes contrôles n'apparaissent tout simplement pas (car ils sont par exemple attachés à des "static" de dimension 0,0 ... enfin, c'est ce que j'avais pu comprendre en essayant de débugger, mais pas évident)

    Le problème est que ce comportement est plus ou moins aléatoire, d'où le titre de mon post faisant référence à la synchro de création de toutes ces ressources graphiques...

    Est-ce qu'il existerait un tuto ou une doc sur ce sujet précis? Ou bien est-ce que quelqu'un peut m'indiquer une méthodologie robuste à suivre?

    Merci d'avance. Gorgo13.

    [EDIT] PS: c'est un peu le même problème que mon précédent post "Création CDialog OK mais m_hWnd=NULL (VC++2010)" mais là il ne s'agit absolument pas d'un projet importé... il est tout neuf de VC2010.

  2. #2
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 380
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 380
    Points : 41 576
    Points
    41 576
    Par défaut
    l'"initialisation", tu la fais quand? Dans ton OnCreate?

    Aussi, toute ta GUI est bien dans le même thread, hein?

  3. #3
    Membre régulier
    Profil pro
    Inscrit en
    Octobre 2007
    Messages
    157
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2007
    Messages : 157
    Points : 78
    Points
    78
    Par défaut
    Bonsoir et merci pour la réponse.

    Citation Envoyé par Médinoc Voir le message
    l'"initialisation", tu la fais quand? Dans ton OnCreate?
    Par exemple dans mon projet de test, je crée une nouvelle instance de mon contrôle (un contrôle de graph pour faire des plots en tout genres basé sur un CWnd) dans OnInitDialog. Dans ce contrôle, je définis 2 plots (je passe les détails). De temps en temps, un des deux graphes n'apparaît tout simplement pas. Comme c'est un comportement aléatoire, ça me donne vraiment pas envie d'essayer de débugger, donc j'en sais pas plus.

    Voici comment je procède dans le OnInitialUpdate():
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    void CDev_MultiCODEView::OnInitialUpdate()
    {
    	...
    	CRect tRect;
    	this->m_GraphContainer.GetClientRect(&tRect);
    	MyGraph->Create(tRect,&m_GraphContainer,this,1974L);
    	...
    }
    et le Create de MCODE_Graph est:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    BOOL MCODE_Graph::Create(const CRect& FullRect,
    		CWnd* _pParentContainer, CWnd* _pTopMostParentWnd, mcINT32 nID,
    		mcINT32 SetProperties,
    		mcINT32 RemProperties)
    {
    	...
     
    	static CString className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS,0,(HBRUSH)BkgBrush);
     
    	DWORD dwStyle = WS_CHILD | WS_VISIBLE;
     
    	int result = -999;
    	result = CWnd::CreateEx(WS_EX_TOPMOST,className, NULL, dwStyle, 
    		FullRect.left, FullRect.top, FullRect.Width(), FullRect.Height(),
    						pParentContainer->GetSafeHwnd(), (HMENU)nID);
     
    	Hdc_Screen = GetDC()->GetSafeHdc();
    	Hdc_Memory = CreateCompatibleDC(Hdc_Screen);
     
    	...
     
    	this->GetClientRect(&m_ClientRect);
    	hBitmapFullPlot = CreateCompatibleBitmap(Hdc_Screen,m_ClientRect.Width(),m_ClientRect.Height());
     
    	return TRUE;
    }
    Citation Envoyé par Médinoc Voir le message
    Aussi, toute ta GUI est bien dans le même thread, hein?
    Oui dans l'exemple ci-dessus, c'est le cas... non??

    Donc je comprends pas très bien ce qui se passe.

    Avec une autre classe que j'ai écrite (un panneau de propriété) je crée une fenêtre avec un tableau de propriétés et quelques boutons. Le tableau doit se redimensionner à chaque fois que WM_SIZE est intercepté. J'avais constaté que des fois à l'initialisation, la taille récupérée est 0,0 et donc mon tableau de propriétés et mes boutons n'apparaissent pas car ils ont une taille de 0 (puisque tout est calculé et redimensionné à partir de la taille de la fenêtre hôte). Il suffit que je redimensionne à la main ma fenêtre pour résoudre le problème... mais évidemment je voudrais que ce soit correct au démarrage!

    Je suis un peu paumé et il y a quelque chose que je ne comprends plus (sous VC6 j'avais pas ce genre de problème.. mais c'est vrai aussi que je m'amusais moins à créer des contrôles custom).

    Bon, je sais pas si c'est beaucoup plus clair...

    GT

  4. #4
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 380
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 380
    Points : 41 576
    Points
    41 576
    Par défaut
    Je ne vois pas vraiment le problème, mais le coup du HDC_Screen me paraît suspect. On n'est pas censer "mémoriser" un HDC vers l'écran, on est supposer faire un ReleaseDC() avant la fin du traitement du message...
    Surtout s'il ne te sert qu'à créer un Compatible DC.

    Si tel est le cas, tu devrais plutôt faire ceci:
    Code C++ : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    	{
    		CDC *pdc_Screen = GetDC();
    		Hdc_Memory = CreateCompatibleDC(pdc_Screen->GetSafeHdc());
     		hBitmapFullPlot = CreateCompatibleBitmap(pdc_Screen->GetSafeHdc(), m_ClientRect.Width(), m_ClientRect.Height());
    		ReleaseDC(pdc_Screen);
    	}

  5. #5
    Membre régulier
    Profil pro
    Inscrit en
    Octobre 2007
    Messages
    157
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2007
    Messages : 157
    Points : 78
    Points
    78
    Par défaut
    Oui merci, je vais changer ça et voir si ça vient de là. Le Hdc_Screen je m'en sers plus tard pour effectivement copier le bitmap complet vers l'affichage.

    Il faut que je récupère le Hdc_Screen à chaque fois que je l'utilise et que je le libère juste après, c'est bien ça...?

    Par contre est-ce que c'est pas la même logique pour le Hdc_Memory? (quelle différence avec le Hdc_Screen sur le principe?)

    G13.

    [EDIT] je viens de commencer à changer effectivement les "screen HDC" permanents vers des HDC "locaux". Le problème est que mes "memory DC" doivent aussi être créés à chaque fois? (j'imagine que oui parce que là les graphes sont partiellement affichés, et d'autres de mes contrôles n'affichent plus rien, etc...)

  6. #6
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 380
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 380
    Points : 41 576
    Points
    41 576
    Par défaut
    Citation Envoyé par Gorgo13 Voir le message
    Il faut que je récupère le Hdc_Screen à chaque fois que je l'utilise et que je le libère juste après, c'est bien ça...?
    Oui.

    Par contre est-ce que c'est pas la même logique pour le Hdc_Memory? (quelle différence avec le Hdc_Screen sur le principe?)
    Les Memory DC sont créés et détruits plus où moins à volonté, alors que les Screen DC sont un pool limité. Un Screen DC "acquis" doit donc être "rendu" le plus tôt possible.

    Edit: Puisque tu utilises MFC, pense à utiliser la classe CClientDC: Une variable locale de ce type fera le GetDC() et le ReleaseDC() pour toi.

  7. #7
    Membre régulier
    Profil pro
    Inscrit en
    Octobre 2007
    Messages
    157
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2007
    Messages : 157
    Points : 78
    Points
    78
    Par défaut
    Voilà, j'ai viré toutes les variables "permanentes" de ma classe pour les HBITMAP et les HDC "Screen". Par contre ça ne fonctionne pas correctement: voici le code de ma fonction OnPaint():

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    void MCODE_Graph::OnPaint()
    {
    	if (this->m_hWnd == NULL) return;
     
    	CPaintDC dc(this); // device context for painting
     
    	HDC hdcScreen = dc.GetSafeHdc();
    	HDC hdcMemory = CreateCompatibleDC(hdcScreen);
    	this->GetClientRect(&m_ClientRect);
    	HBITMAP hBitmapFullPlot = CreateCompatibleBitmap(hdcScreen,m_ClientRect.Width(),m_ClientRect.Height());
     
    	HBITMAP OldBitmap = (HBITMAP)SelectObject(hdcMemory,hBitmapFullPlot);
    	::FillRect(hdcMemory,&m_ClientRect,BkgBrush);
     
    	for (int k=0;k<(int)Graph.size();++k)
    	{
    		this->UpdatePlot(k,hdcMemory,hBitmapFullPlot);
    	}
     
    	BOOL bVal = BitBlt(hdcScreen,0,0,m_ClientRect.Width(),m_ClientRect.Height(),hdcMemory,0,0,SRCCOPY);
    	SelectObject(hdcMemory,OldBitmap);
    }
    Le problème est que mes graphes ne se rafraichissent plus du tout dans la zone de la fenêtre courante. Ci-joint un exemple. Image de gauche au lancement du projet, et à droite lorsque je scroll horizontalement (on voit que la partie du plot était plotée correctement, mais dans la partie non visible de la fenêtre). Dans ce projet test, je rafraichis les plots dans le OnMouseMove (en envoyant un WM_PAINT au graph), mais rien ne se passe...

    [EDIT] Je croyais que ça pouvait venir d'un appel à ClipRegion, mais j'ai enlevé ça et le comportement ne change pas...

    Nom : Image01.png
Affichages : 124
Taille : 84,9 Ko
    Nom : Image02.png
Affichages : 126
Taille : 60,4 Ko

  8. #8
    Membre régulier
    Profil pro
    Inscrit en
    Octobre 2007
    Messages
    157
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2007
    Messages : 157
    Points : 78
    Points
    78
    Par défaut
    Bon, j'ai partiellement résolu le problème en appelant une fonction RefreshScreenPlot() qui elle-même appelle InvalidateRect sur toute la zone du plot...

    Maintenant je vais avouer que je n'ai jamais rien compris à cette fonction InvalidateRect... elle est bien censé appeler OnPaint, non? Donc quelle différence avec le fait d'appeler directement OnPaint??

    G13

  9. #9
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 380
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 380
    Points : 41 576
    Points
    41 576
    Par défaut
    Ton OnPaint() me parait presque correct, sauf que tu oublies le DeleteDC() à la fin sur ton DC mémoire.
    Tu devrais utiliser la classe CMemoryDC pour éviter ce genre de désagrément.

    Citation Envoyé par Gorgo13 Voir le message
    Bon, j'ai partiellement résolu le problème en appelant une fonction RefreshScreenPlot() qui elle-même appelle InvalidateRect sur toute la zone du plot...

    Maintenant je vais avouer que je n'ai jamais rien compris à cette fonction InvalidateRect... elle est bien censé appeler OnPaint, non? Donc quelle différence avec le fait d'appeler directement OnPaint??
    InvalidateRect() est asynchrone, et fait deux choses:
    • Invalider le rectangle spécifié (ou bien toute la zone client de la fenêtre), ce qui l'inclut dans le clipping (sinon, le CPaintDC() refusera de gribouiller dessus), il me semble.
    • Faire l'équivalent de poster (PostMessage) un message WM_PAINT à priorité basse (il est reçu seulement quand il n'y a pas d'autres messages dans la file, sauf WM_TIMER qui a une priorité encore plus basse).

    OnPaint() sera donc appelé lorsque le message sera reçu. Mais la fonction UpdateWindow() peut forcer un paint immédiat sur la zone invalidée (elle fait l'équivalent d'un SendMessage(WM_PAINT)).

    Si tu appelles directement OnPaint(), vu que la zone client de la fenêtre n'aura pas été invalidée, il est fort possible que le OnPaint() ne fasse rien du tout, car le CPaintDC contiendra une zone de clipping vide.

  10. #10
    Membre régulier
    Profil pro
    Inscrit en
    Octobre 2007
    Messages
    157
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2007
    Messages : 157
    Points : 78
    Points
    78
    Par défaut
    Alléluia!!

    Merci Médinoc pour cet éclaircissement... bizarre que je n'ai jamais trouvé cette explication nulle part (en fait personnellement je bloque toujours quand je ne comprends pas le sens des mots.. et là "invalider", j'ai mis longtemps avant de capter la signification).

    Bon, sinon je pense avoir complètement nettoyé mon code avec tes recommandations et ça tourne maintenant comme avant (j'ai eu quelques gouttes de sueur qui ont commencé à perler quand j'ai commencer à tout changer et que ça ne marchait plus du tout ).

    Je dois vérifier si le comportement décrit au tout début de mon post est toujours là ou pas, mais au moins maintenant mon code est clean!

    Merci encore de ton aide toujours précieuse! (je vérifie donc avant de marquer résolu...)

    Gorgo Treize.

  11. #11
    Membre régulier
    Profil pro
    Inscrit en
    Octobre 2007
    Messages
    157
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Octobre 2007
    Messages : 157
    Points : 78
    Points
    78
    Par défaut
    Bon, je ne suis pas sûr à 100% que ça soit résolu mais je n'ai pas vu de comportement anormal depuis mes modifs. Donc je mets "Résolu".

    Merci encore.

    G13

  12. #12
    Expert éminent sénior
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Février 2005
    Messages
    5 160
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 52
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Conseil

    Informations forums :
    Inscription : Février 2005
    Messages : 5 160
    Points : 12 275
    Points
    12 275
    Par défaut
    Selon mes souvenirs InvalidateRect ne poste pas de message WM_PAINT.

    Il ne fait que modifier la zone de clipping de rafraichissement.

    C'est la pompe à message des MFC qui génère le WM_PAINT quand elle ne trouve plus de message dans la file et que la zone de clipping de rafraichissement n'est pas vide.

    C'est comme ça que ce message semble avoir une priorité "faible" comme WM_TIMER, qui repose sur le même principe de génération lors d'une file de message vide.

  13. #13
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 380
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 380
    Points : 41 576
    Points
    41 576
    Par défaut
    C'est pourquoi j'ai dit "l'équivalent de poster un message WM_PAINT à basse priorité".
    Plus d'infos.

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. liste sharepoint : disponibilité dans le temps des ressources
    Par ITParty dans le forum Développement Sharepoint
    Réponses: 5
    Dernier message: 28/05/2013, 14h43
  2. Réponses: 5
    Dernier message: 10/04/2010, 10h36
  3. [EJB3] Exception dans la création des beans de l'application
    Par aymen007 dans le forum Java EE
    Réponses: 2
    Dernier message: 19/01/2009, 20h47
  4. Réponses: 6
    Dernier message: 29/03/2008, 19h01

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo