Tranparence d'un contrôle durant son déplacement

Résolu
gregory__forel Messages postés 36 Date d'inscription lundi 18 juillet 2005 Statut Membre Dernière intervention 18 juillet 2008 - 26 déc. 2007 à 18:27
Kevin.Ory Messages postés 840 Date d'inscription mercredi 22 octobre 2003 Statut Membre Dernière intervention 7 janvier 2009 - 30 déc. 2007 à 16:10
Bonjour à tous,

J'essaye d'écrire une classe pour créer un composant dont le fond sera transparent. Cela fonctionne lorsque celui ci est ajouté a la forme. L'idée finale est de créer un programme du type Visio, mais j'ai de bonnes raisons d'utiliser des contrôles plutôt que du dessin pur.
Par contre, lorsque je le déplace durant l'exécution du programme, je n'ai pas trouvé d'autre manière que de faire un refresh de la form entière, ce qui fait évidemment scintiller le tout. Quelqu'un aurait-il une idée ? Si je trouve la solution, je la mettrai dans la section code, car j'ai vu que bcp de personnes avaient ce problème.

Merci beaucoup,

Greg

Créer une form, ajouter un bouton, et écrire dans l'évènement click du bouton:
dim rect as new TransparentComponent
rect.AddToForm(Me)

Créer un composant avec le nom "TransparentComponent" par exemple et y coller le code suivant:

Public Class TranparentComponent

    Inherits System.Windows.Forms.UserControl

    Private Const WS_EX_TRANSPARENT As Int16 = &H20

    ' This part of the code allows to manage transparency
    Protected Overrides ReadOnly Property CreateParams() As System.Windows.Forms.CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams
            cp.ExStyle() = cp.ExStyle() Or WS_EX_TRANSPARENT
            Return cp
        End Get
    End Property

    ' Set the form of the control (a rectangle in this case)
    Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
        ' TODO: add any drawing code, if needed.
        MyBase.OnPaint(e)
        Dim p As New Pen(Color.Black, 5)
        Dim rect As New Rectangle(10, 10, 100, 100)

        e.Graphics.DrawRectangle(p, rect)
    End Sub

#Region "Add Control To Form"
    ' Add control to specified form
    Public Sub AddToForm(ByVal form As Form)
        Dim Control As New TranparentComponent

        Control.Size = New Size(Me.Width, Me.Height)
        form.Controls.Add(Control)
    End Sub
#End Region

#Region "Control Movements"
    ' Manage the movements.

    Private blnMouseDown As Boolean
    Private xControl As Integer
    Private yControl As Integer
    Private xCoord As Integer
    Private yCoord As Integer

    Private Sub Handler_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseDown
        Windows.Forms.Cursor.Current = Cursors.SizeAll

        blnMouseDown = True
        xControl = e.X
        yControl = e.Y
    End Sub

    Private Sub Handler_MouseMove(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseMove
        Windows.Forms.Cursor.Current = Cursors.SizeAll

        xCoord = e.X
        yCoord = e.Y

        xCoord = Me.Left + e.X
        yCoord = Me.Top + e.Y

        ' If we click on control
        If blnMouseDown Then
            ' We move control to mouse place minus first click position
            Me.Top = yCoord - yControl
            Me.Left = xCoord - xControl

             ' Update the entire form.
            Me.FindForm.Refresh ' SCINTILLEMENT !!! COMMENT FAIRE ?
        End If
    End Sub

    Private Sub Handler_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Me.MouseUp
        blnMouseDown = False
    End Sub
#End Region

End Class

10 réponses

Kevin.Ory Messages postés 840 Date d'inscription mercredi 22 octobre 2003 Statut Membre Dernière intervention 7 janvier 2009 11
27 déc. 2007 à 17:08
"Cette fonction te permet en deux secondes de gérer la transparence + un double buffer pour éviter le scintillement..."

J'ai jamais utilisé la transparence par le SetStyle, mais je pense qu'il s'agit de la même chose que d'appliquer la couleur transparente comme couleur de fond (BackColor = Color.Transparent). Et donc, si tu as des mouvements, c'est absolument pas performant. Cette technique, comme je l'ai déjà dis, n'est pas de la vraie transparence. Il s'agit d'une copie du fond du conteneur (qui contient ton contrôle transparent) sur la surface du contrôle "transparent" en question. Et donc:

- Seul le fond du conteneur est copié sur la surface du contrôl, mais rien d'autre. Donc par exemple si tu as 2 contrôle qui se chevauchent, le contrôle du dessous ne sera pas visible à travers les parties transparentes du contrôle du dessus. Je pense que c'est justement cela que tu voudrais; pouvoir superposer des contrôles tout en voyant ceux de dessous, pas de mettre une image de fond à ton plan de travail. Et donc cette technique ne s'applique absolument pas à ton cas.
- A chaque mouvement du contrôle, il faut redessiner sa surface d'après le fond du conteneur. Ceci prend du temps, et donc lorsque ton contrôle bougeras, tu aura l'impression que l'image de fond suivra les mouvements du contrôle (dans sa région de dessin seulement, c'est a dire dans le carré du contrôle définit par sa position et sa taille). C'est très moche. Et encore une fois, seul le fond du conteneur (une image?) sera appliqué comme fond au contrôle.

Avec la région, tu redéfinit justement la zone de dessin du contrôle, qui ne sera plus définit par sa position et sa taille, mais par sa région (en fait la région correspond au rectangle du contrôle par défaut). Mais:

- Avec les régions, c'est du tout ou rien. Impossible de faire des régions semi-transparente à ton contrôle (tu devra utiliser la méthode précédement expliqué pour le faire, et donc tu héritera de son problème). De plus il ne sera pas question d'appliquer un quelconque lissage à tes formes mais surtout à ton texte, ca ne sera donc pas joli (par défaut, tout est lissé)
- Il peut être difficile de définir la région lorsque ce que tu dois afficher est une forme complexe (un carré ou un cercle = facile, mais si tu commence à afficher des graphiques complexe ou du texte, va faloir faire un algo qui te créé ces régions, au pixel près). (c'est dans ce processus qu'il est impossible de travailer avec des pixels semi-transprent (lissage))
- Hors d'une région, le MouseDown, MouseClick ou MouseMove par exemple, ne seront pas détecté. Impossible donc de sélectionner un rectangle à fond transparent en cliquant dans sa zone transparente.
- Va falloir redéfinir la région en temps réel si ta forme change (par exemple si tu change sa taille ou si tu modifie du texte sur fond transparent), ca peut vite devenir impossible.

Je ne sais pas vraiment ce que tu veux faire. Si tu veux faire un truc vite fait mais qui fonctionne, utlise les régions, mais ne compte pas sur les fonds transparents ou ce genre de chose. Si tu veux un truc qui rivalise avec les prgrammes d'édition graphique (genre visio), tu n'a pas le choix: fais ça en dessin pur. Aucun programme de dessin ou de création graphique existant n'utlise une superposition de contrôle, c'est impossible de faire qqch de potable de cette facon.
Il faut plutôt utilise un système de couche graphique (chaque forme est une couche, ça permet de travailler séparément sur chaque forme) et les superposer au moment du dessin. Il n'y a plus de limitation avec cette technique, aussi bien au niveau modifications qu'au niveau semi-transparence et lissage.
En parallèle aux couches graphique, tu peux faire des couches de régions. Ca te permetra de facilement gérer les événements souris sur ta surface de travail.
Et enfin, en couche supérieur, tu affiche tous les éléments de sélection (genre les petits carrée aux coins d'une forme sélectionnée)

Le problème, c'est qu'il faut tout faire sois-même, par exemple l'édition du texte. (il y a des programmes ou le texte n'est pas modifié directement dans la forme, mais dans un contrôle séparé, ce n'est pas pour rien)

Au niveau des performances, ça va si t'as pas une multitude de forme et surtout pas de grandes images à afficher. Si vraiment ce n'est pas assez performant, va falloir créer un système de couche graphique avec des CachedBitmap (des bitmaps en mémoire bcp plus rapides que les images normales de .NET), malheureusement celle-ci ne sont pas implémenté avant le framework 3.5, il faudra donc sois même accéder à GDI+ (la DLL qui permet de dessiner dans windows, tout ce qui est graphique dans .NET y fait référence) si tu utilise une version antérieur du framework .NET.

Ben voilà ce que j'avais à dire :) si t'as d'autre questions, n'hésite pas. Mais c'est clair que tout ceci n'est pas tout facile, y'a du boulot.

J'ajoute juste encore une remarque au sujet du commentaire de divadav:
Si tu fait du dessin double-bufferé de cette facon, ca reste quand même du dessin direct, c'est a dire que tu dois redessiner à chaque événement paint du contrôle. Avec un DoubleBuffer, tu redessine seulement lorsque qqch change, le reste du temps suffit de faire un render (super-rapide). Les 2 techniques ne sont donc pas vraiment comparables.

Bonne soirée,
Kevin
3
cs_yvesyves Messages postés 561 Date d'inscription samedi 10 janvier 2004 Statut Membre Dernière intervention 11 octobre 2010
26 déc. 2007 à 21:34
Tu peux peut être essayer de rendre les zones graphiques de ton contrôle transparent par un calcul de moyenne entre le pixel du fond et celui du contrôle. En éque cet envoi a marché et que ça t'aidera..
0
gregory__forel Messages postés 36 Date d'inscription lundi 18 juillet 2005 Statut Membre Dernière intervention 18 juillet 2008
26 déc. 2007 à 21:53
Je ne voyais pas ca si compliqué... Je vais attendre de voir si j'ai d'autres réponses, et si c'est vraiment la seule solution, j'essaierai.
D'ailleurs, je me suis mal exprimé, parceque ce n'est pas transparent que je veux, mais complètement invisible sauf pour les parties dessinées (côtés d'un carré par exemple)

Merci !
0
Kevin.Ory Messages postés 840 Date d'inscription mercredi 22 octobre 2003 Statut Membre Dernière intervention 7 janvier 2009 11
27 déc. 2007 à 01:57
Salut,

Un simple "Control.BackColor = Color.Transparent" ne pourrait-il pas remplacer tous ce code que tu nous a mis?

Mais ne compte pas sur des mouvements fluides avec cette méthode, surtout si tu mets une image de fond au conteneur...

Sinon, tu peux redéfinir la région du control avec sa propriété "Region". Les parties exclues de la région ne seront pas du tout dessiné (contrairement à la méthode "backcolor=transparent" qui ne fait que copier le fond du conteneur sur sa surface) et donc les mouvements seront fluides.

Et je te conseil fortement de tout faire avec des BufferedGraphics, sinon tout sera lent et moche. Mais par contre je te déconseil de faire ca avec des controls séparé plutot qu'en "dessin pur", ca va t'apporter plus de problèmes que d'avantages au final
0

Vous n’avez pas trouvé la réponse que vous recherchez ?

Posez votre question
gregory__forel Messages postés 36 Date d'inscription lundi 18 juillet 2005 Statut Membre Dernière intervention 18 juillet 2008
27 déc. 2007 à 03:37
Salut,

Je vais essayer avec les régions, c'est peut-être une bonne option, merci !
0
divadav Messages postés 94 Date d'inscription lundi 13 janvier 2003 Statut Membre Dernière intervention 22 janvier 2009 2
27 déc. 2007 à 08:44
Salut !

Dans ton constructeur, il faut que tu joues avec la fonction SetStyle
Cette fonction te permet en deux secondes de gérer la transparence + un double buffer pour éviter le scintillement...

<hr size="2" width="100%" />Divad
Techniques de Web Design, Traductions d'articles sur le Web Design, Astuces .Net
0
gregory__forel Messages postés 36 Date d'inscription lundi 18 juillet 2005 Statut Membre Dernière intervention 18 juillet 2008
27 déc. 2007 à 19:24
Ca c'est de la réponse Kevin !!! J'ai suivi ton premier conseil et utilisé les régions, ca marche, et évidemment, ce n'est pas absolument propre, lisse, etc. mais c'est un bon début.

En revanche, ton analyse est exacte, donc je vais prendre plus de temps et essayer de développer les classes nécessaires pour faire du "tout dessiné". Probablement une gestion de layouts, de regions, etc. Je n'ai d'ailleurs pas trouvé de classes gratuites ou open source pour faire tout ca, à moins d'extraire des parties de code de Paint.NET. Si quelqu'un en connaît une toute prête, je m'économiserais bien ces dizaines (centaines ?) d'heures de travail.

Encore merci pour ton analyse plus que complète !

Greg
0
gregory__forel Messages postés 36 Date d'inscription lundi 18 juillet 2005 Statut Membre Dernière intervention 18 juillet 2008
27 déc. 2007 à 19:26
Je précise par ailleurs que SetStyle "doubble buffer" et "transparent" ne sont pas compatibles, cela affiche un gros carré noir en lieu et place du contrôle.
0
divadav Messages postés 94 Date d'inscription lundi 13 janvier 2003 Statut Membre Dernière intervention 22 janvier 2009 2
28 déc. 2007 à 07:47
Si si, c'est compatible... Il faut faire :

        Me.SetStyle(ControlStyles.AllPaintingInWmPaint Or _
                    ControlStyles.UserPaint Or _
                    ControlStyles.SupportsTransparentBackColor Or _
                    ControlStyles.OptimizedDoubleBuffer, True)
        Me.DoubleBuffered = True

Puis redéfinir la méthode OnPaint de ton contrôle... Mais c'est vrai que ce n'est pas la technique la plus optimisée ! C'est bien pour redéfinir un petit contrôle, un bouton ou quelque chose du style... Pas pour ce que tu veux faire !

<hr size="2" width="100%" />Divad
Techniques de Web Design, Traductions d'articles sur le Web Design, Astuces .Net
0
Kevin.Ory Messages postés 840 Date d'inscription mercredi 22 octobre 2003 Statut Membre Dernière intervention 7 janvier 2009 11
30 déc. 2007 à 16:10
Le double buffer "direct", c'est à dire en définissant Control.DoubleBuffered = True ou avec l'utlisation de SetStyle, n'est utile que pour éviter le scintillement lorsque on doit peindre plusieurs "choses" (couche). Au lieu d'afficher caque élément que lorsque on le peind, il va les afficher tous en même temps une fois que la fonction Paint est terminé, c'est tout. Ca n'améliore donc pas la vitesse je pense.

Je crois que ce que Gregory veut dire, c'est qu'on ne peut pas appliquer un fond transparent dans un graphics "bufferé" Effectivement, un Graphics.Clear(Color.Transparent) par exemple, fait un fond noir dans ton BufferedGraphics

Et ça, je suis bien obligé de le dire, c'est de la m.......
Impossible de superposer les BufferedGraphics, car chacun peind sur la zone complète sans gestion de la transparence et donc efface le dessin du précédent Render. Donc... impossible de développer un système de couches graphiques avec des BufferedGraphics...

Le Render du BufferedGraphics fait une copie du genre "SRCCOPY" dans BitBlt de GDI apparement, et je crois qu'il faudrait faire une copie MERGECOPY pour que la transparence soit géré... dans GDI+ le dessin est différent, mais c'est surement un problème du même genre...

Donc, ce qu'il faut je pense, c'est maitriser l'utlisation de GDI+ avant tout. J'avais essayé il y a qq temps, histoire de faire un système de couches graphiques pouvant être réutlisé pour la création d'animations graphiques (plusieurs images qui bougent, comme dans un jeu 2D par exemple, genre Mario) mais sans succès.

Il y a cette fameuse classe CachedBitmaps dont j'ai déjà parlé, qui est une image mémoire déjà optimisé pour l'affichage et qui rend donc son affichage bcp plus rapide (une image normale (classe Image dans .NET) doit être traité avant de pouvoir être affiché, et c'est ce qui prend du temps). Elle est utile seulement si l'image en question ne doit pas être modifié chaque fois qu'on veut la dessiner, car dans ce cas l'optimisation doit de toute façon être refaite, mais c'est l'idéal pour déveloper un système de couche.
Malheureusement (comme je l'ai déjà dis aussi) elle n'est pas implémenté dans .NET 2.0. J'y ai vu une référence dans .NET 3.5, mais je ne m'y suis pas intéressé puisque je code encore en .NET 2.0 (Win XP).

Peut-être qu'il suffit de créer une classe managé de CachedBitmaps et qu'ensuite on pourra l'utliser avec un Graphics déjà présent dans le framework, ou peut être qu'il faudra utliser directement la classe Graphics non managé de GDI+, je ne sais pas.

Mais j'ajoute quand même que dans ton cas, une simple copie d'image dans la classe Graphics managé de .NET te suffit je pense. (Il y a la fonction DrawImageUnscaled qui est la plus rapide)

Dans un jeu 2D, c'est une autre histoire.. il faut quand même maintenir les 30 FPS avec des grandes images (le paysage de fond), et la ca ne suffit plus, du moins sur mon vétérant de PC (Athlon XP 1900+). Je parle de jeu, c'est claire que pour un jeu on aura plutot tendance à se tourner vers DirectX, mais mon cas était pour faire un soft d'appercu des images style Vista (une mosaique plein-écran de miniatures qui défillent, avec une image de fond et des effets de survol, de zoom....)

Dans tout les cas, si tu travail sur cette gdiplus.dll et que t'arrive à un résultat, n'hésite surtout pas à m'en faire part Ca me serait grandement utile.


Ca compte pour toute autre personne aussi bien évidement, si qq'un à
utlisé gdiplus.dll depuis un code managé, qu'il nous en fasse part au
plus vite

Salutations
0
Rejoignez-nous