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

Python Discussion :

Glisser-déposer entre deux treeview tkinter


Sujet :

Python

  1. #1
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Février 2013
    Messages
    55
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Février 2013
    Messages : 55
    Par défaut Glisser-déposer entre deux treeview tkinter
    Bonjour à tous,

    Je recherche un moyen d'effectuer un drag and drop entre 2 treeview
    Je trouve des tutos pour le faire à l’intérieur d’un treeview mais pas entre deux diffèrent





    Merci d’avance


    J'utilise ce code pour mes tests.

    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
     
    from tkinter import *
    from tkinter import ttk
     
    def bDown_Shift(event):
        tv = event.widget
        select = [tv.index(s) for s in tv.selection()]
        select.append(tv.index(tv.identify_row(event.y)))
        select.sort()
        for i in range(select[0],select[-1]+1,1):
            tv.selection_add(tv.get_children()[i])
     
    def bDown(event):
        tv = event.widget
     
        if tv.identify_row(event.y) not in tv.selection():
            tv.selection_set(tv.identify_row(event.y))
     
    def bUp(event):
        tv = event.widget
        if tv.identify_row(event.y) in tv.selection():
            tv.selection_set(tv.identify_row(event.y))
     
    def bUp_Shift(event):
        pass
     
    def bMove(event):
        tv = event.widget
        print(tv)
        moveto = tv.index(tv.identify_row(event.y))
        for s in tv.selection():
            tv.move(s, '', moveto)
     
    root = Tk()
     
    tree = ttk.Treeview(columns=("col1","col2"),
                        displaycolumns="col2",
                        selectmode='none')
     
    # insert some items into the tree
    for i in range(10):
        tree.insert('', 'end',iid='line%i' % i, text='line:%s' % i, values=('', i))
     
    tree2 = ttk.Treeview (columns=("col1", "col2"),
                         displaycolumns="col2",
                         selectmode='none')
     
    # insert some items into the tree
    for i in range (10) :
        tree2.insert ('', 'end', iid='line%i' % i, text='line:%s' % i, values=('', i))
     
     
     
     
     
    tree.pack()
    tree2.pack()
     
    tree.bind("<ButtonPress-1>",bDown)
    tree.bind("<ButtonRelease-1>",bUp, add='+')
    tree.bind("<B1-Motion>",bMove, add='+')
     
    tree.bind("<Shift-ButtonPress-1>",bDown_Shift, add='+')
    tree.bind("<Shift-ButtonRelease-1>",bUp_Shift, add='+')
     
     
    tree2.bind("<ButtonPress-1>",bDown)
    tree2.bind("<ButtonRelease-1>",bUp, add='+')
    tree2.bind("<B1-Motion>",bMove, add='+')
     
     
    tree2.bind("<Shift-ButtonPress-1>",bDown_Shift, add='+')
    tree2.bind("<Shift-ButtonRelease-1>",bUp_Shift, add='+')
     
     
     
    root.mainloop()

  2. #2
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 717
    Par défaut
    Salut,

    Vous avez un module de tkinter qui fournit une base "adaptable" modulo avoir passé du temps à comprendre comment ça fonctionne et avoir appris à l'utiliser.

    Après si vous voulez coder une solution originale, il y a un gros travail de conception à faire car il faut faire "avec" tkinter (et bien le maîtriser).

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  3. #3
    Expert confirmé
    Avatar de jurassic pork
    Homme Profil pro
    Bidouilleur
    Inscrit en
    Décembre 2008
    Messages
    4 203
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Bidouilleur
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2008
    Messages : 4 203
    Par défaut
    hello,
    vad92, dans le code que tu as mis c'est un "faux" drag n drop qui n'utilise pas le mécanisme de drag n drop. Comme l'indique Wiztrick dans tkinter pour faire un "vrai" drag and drop il faut installer un module de tkinter tkdnd. Voici le genre de chose que l'on pourrait faire :

    Nom : pytkdnd2.gif
Affichages : 1040
Taille : 32,1 Ko

    Mais ce n'est pas si simple que cela à gérer surtout pour un treeview.

    Ami calmant, J.P

  4. #4
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 717
    Par défaut
    Citation Envoyé par jurassic pork Voir le message
    il faut installer un module de tkinter tkdnd.
    Il n'y a rien à installer: c'est en standard depuis la version 3.9.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  5. #5
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Février 2013
    Messages
    55
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Février 2013
    Messages : 55
    Par défaut
    Bonjour,
    Merci pour vos réponses, l’exemple de jurassic pork est exactement ce que je recherche, si tu un code exemple qui va avec ça serai top,
    dans tous les cas je me lance dans l'apprentissage dans ce module tkdnd

    Merci Encore

  6. #6
    Expert confirmé
    Avatar de jurassic pork
    Homme Profil pro
    Bidouilleur
    Inscrit en
    Décembre 2008
    Messages
    4 203
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Bidouilleur
    Secteur : Industrie

    Informations forums :
    Inscription : Décembre 2008
    Messages : 4 203
    Par défaut
    hello,
    Citation Envoyé par wiztricks Voir le message
    Il n'y a rien à installer: c'est en standard depuis la version 3.9.

    - W
    Il me semble que ce dnd n'est toujours pas un Dnd natif à l'O.S (pas possible de l'utiliser avec une autre application que tkinter). Wiztricks peux-tu nous montrer comment tu transfères un élément d'un treeview vers un autre treeview avec le dnd de tkinter présent depuis la 3.9 ?


    Vader, En attendant la réponse de wiztricks voici mon code qui utilise le module tkinterdnd2 (présent dans Pypi). Attention le paquet ne contient que les binaires pour les versions python 64 bits de linux, windows, macOSX. Pour les versions python 32 bits, il y a quelques manipulations à faire et fichiers à récupérer.
    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    # Démo DnD entre 2 Treeviews Tkinter J.P Février 2022
    from tkinter import ttk
    from tkinterdnd2 import *
    import json
     
     
    def drop_enter(event):
        event.widget.focus_force()
        #print('On entre dans la zone :  %s' % event.widget)
        return event.action
     
     
    def drop_position(event):
        #print('Position: x %d, y %d' %(event.x_root, event.y_root))
        return event.action
     
     
    def drop_leave(event):
        #print('On quitte %s' % event.widget)
        return event.action
     
     
    def drop(event):
        if event.data:
            #print(event.data)
            # conversion des data en format json
            json_string = event.data.replace("'", "\"")
            # conversion json en dictionnaire
            dict = json.loads(json_string)
            print(dict["text"])
        tv = event.widget
        tv.insert('', 'end', iid=dict['text'], text=dict['text'], values=dict['values'])
        return event.action
     
     
    def drag_init(event):
        tv = event.widget
        curItem = tv.focus()
        print(tv.item(curItem))
        data = tv.item(curItem)
        return (COPY, DND_TEXT, data)
     
     
    #Créer une liste de fruits
    f_name=['Pommes','Poires','Bananes','Prunes','Oranges','Pêches','Abricots','Kiwis']
    #Créer une liste de prix de fruits
    f_price_num=[2.24,3.31,1.69,3.78,2.05,3.69,4.20,3.53]
    #Créer une liste de numéros de séquence
    f_list=[1,2,3,4,5,6,7,8]
     
     
    root = TkinterDnD.Tk()
    style = ttk.Style(root)
    # utilisation du thème "clam" qui supporte l'option fieldbackground
    style.theme_use("clam")
    style.configure('Treeview.Heading', background="white")
    style.configure("Treeview",
                    background="#E8FFFF",
                    foreground="black",
                    rowheight=25,
                    fieldbackground="silver",
                    )
    style.map('Treeview', background=[('selected', 'blue')])
    #Créer un tableau
    columns=("Fruit","Prix","Numéro")
    tree=ttk.Treeview(height=10,show="headings",columns=columns)
    tree.heading("Fruit",text="Fruit") # Afficher l'en - tête
    tree.heading("Prix",text="Prix")
    tree.heading("Numéro",text="Numéro")
    #Définir la largeur
    tree.column("Fruit", width=130, anchor='center')
    tree.column("Prix", width=100, anchor='center')
    tree.column("Numéro", width=100, anchor='center')
    # Insérer des données de fonction avec couleur alternée par ligne
    tree.tag_configure('paire', background="#FFFFFF")
    tree.tag_configure('impaire', background="#E8FFFF")
    for i in range(min(len(f_name),len(f_price_num),len(f_list))):
        motag = 'paire' if i%2 else  'impaire'
        tree.insert('',i,text=f_name[i],values=(f_name[i],f_price_num[i],f_list[i]),tags = (motag,))
     
     
    # deuxième treeview vide
    tree2=ttk.Treeview(height=10,show="headings",columns=columns)
    tree2.heading("Fruit",text="Fruit") # Afficher l'en - tête
    tree2.heading("Prix",text="Prix")
    tree2.heading("Numéro",text="Numéro")
    #Définir la largeur
    tree2.column("Fruit", width=130, anchor='center')
    tree2.column("Prix", width=100, anchor='center')
    tree2.column("Numéro", width=100, anchor='center')
     
     
    tree.pack()
    tree2.pack()
     
     
    # gestion des événements
    tree.drag_source_register(DND_TEXT)
    tree.dnd_bind('<<DragInitCmd>>', drag_init)
     
     
    tree2.drop_target_register(DND_TEXT)
    tree2.dnd_bind('<<DropEnter>>', drop_enter)
    tree2.dnd_bind('<<DropPosition>>', drop_position)
    tree2.dnd_bind('<<DropLeave>>', drop_leave)
    tree2.dnd_bind('<<Drop>>', drop)
     
     
    root.mainloop()
    Nom : tkdnd2.gif
Affichages : 985
Taille : 42,9 Ko



    Comme on ne peut apparemment pas transférer d'objets python par ce drag and drop mais une chaine, j'ai utilisé la représentation chaine des éléments du treeview pour faire le transfert. A la réception, je convertis cette chaine en objet json et je convertis le json en dictionnaire. Si quelqu'un a une idée plus simple pour faire le transfert ?


    Sinon, il y a aussi possibilité d'utiliser comme interface graphique pyqt au lieu de tkinter car le drag and drop fonctionne complétement sous qt.


    Ami calmant, J.P

  7. #7
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 717
    Par défaut
    Citation Envoyé par jurassic pork Voir le message
    Il me semble que ce dnd n'est toujours pas un Dnd natif à l'O.S (pas possible de l'utiliser avec une autre application que tkinter).
    Certes mais le sujet était entre Treeview.

    Citation Envoyé par jurassic pork Voir le message
    Wiztricks peux-tu nous montrer comment tu transfères un élément d'un treeview vers un autre treeview avec le dnd de tkinter présent depuis la 3.9 ?
    Si j'ai du temps, je dois pouvoir repartir de codes que j'ai, mais ce ne sera pas avant la fin de la semaine prochaine, désolé.


    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  8. #8
    Membre confirmé
    Homme Profil pro
    Inscrit en
    Février 2013
    Messages
    55
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations forums :
    Inscription : Février 2013
    Messages : 55
    Par défaut
    Bonjour,
    J'ai enfin pu tester le code de jurassic pork et ça fonctionne super bien et très pratique.
    je laisse encore le sujet en non résolu encore quelques temps si wiztricks a le temps de partager son code pour comparer.
    merci encore à vous deux

  9. #9
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 717
    Par défaut exmple simple.
    Salut,

    Je vais faire ça en plusieurs "parties"... pour compliquer au fur et à mesure histoire d'essayer de vous expliquer comment ça marche.

    Pour démarrer doucement on peut reposer le problème car recopier la sélection d'une Treeview dans une autre demande un certain appariement des 2 (une ligne est composée de colonnes qui sont associées a certains type d'objets => on ne peut pas déplacer n'importe quoi).

    Et cet appariement rend une solution "sans" drag&drop tout a fait acceptable: on affiche les Treeview cote à cote avec au milieu des Button qui permettent de "pousser" la sélection dans la direction (et le Treeview) souhaité.

    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    import tkinter.ttk as ttk
    import tkinter as tk
     
    class Treeview(ttk.Treeview):
        def copy_selected(self, source):
            if selected := source.selection():
                for item in selected:
                    data = source.item(item)
                    row = { k: v for k, v in data.items() if k in ('text values') }
                    self.insert('', 'end', **row)
                source.delete(*selected)
     
    if __name__ == '__main__':    
     
        from collections import namedtuple
     
        fruit = namedtuple('Fruit', 'name, cost, range')
        fruits = [
            fruit('Pommes', 2.24, 1),
            fruit('Poires', 3.31, 2),
            fruit('Bananes', 1.69, 3),
            fruit('Prunes', 3.78, 4),
            fruit('Oranges', 2.05, 5),
            fruit('Pêches', 3.69, 6),
            fruit('Abricots', 4.20, 7),
            fruit('Kiwis', 3.53, 8),
            ]
     
        root = tk.Tk()
     
        #Créer un tableau
        columns=("Fruit","Prix","Numéro")
        widths = 130, 100, 100
        trees = []
        for i in range(2):
            tv = Treeview(root, height=10, show="headings", columns=columns,
                             name=f'tv_{i}')
            for label in columns:
                tv.heading(label,text=label)
            for label, width in zip(columns, widths):
                tv.column(label, width=width, anchor='center')
            trees.append(tv)
            tv.grid(row=0, column=(2*i), rowspan=2)
     
        # Insérer des données de fonction avec couleur alternée par ligne
        tv = trees[0]
        for i, fruit in enumerate(fruits):
            tv.insert('', i,text=fruit.name,values=fruit)
     
        #+   
        A, B = trees
        btn = tk.Button(root, text='  >>', command=lambda: B.copy_selected(A),
                        font=('Courier Bold', 18))
        btn.grid(row=0, column=1, sticky='s')
        btn = tk.Button(root, text='<<  ', command=lambda: A.copy_selected(B),
                        font=('Courier Bold', 18))
        btn.grid(row=1, column=1, sticky='n')
        #-
        root.mainloop()
    Et c'est une option à ne pas négliger pour éviter les complications de la suite.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  10. #10
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 717
    Par défaut Le protocole drag and drop.
    Re Salut,

    Le code mentionné dans la documentation n'est qu'une ébauche qui décompose le problème en distribuant rôle et responsabilités à plusieurs objets source, target, DndHandler et Icon.

    La logique est: on clique sur un objet "capable" de "drag & drop" pour le déplacer au dessus d'une cible qui l'accepte. Pour que ce soit "visuel", l'auteur a choisi de "bouger" un widget de type Label d'un Canvas dans un autre.

    Ce qui rend le boulot à faire dans Icon assez "compliqué" et obscurcit un peu ce qu'il faut comprendre.
    Pour simplifier, je suis reparti d'une Icon minimaliste (le curseur qui change comme dans l'exemple de Jurassik Porc), simplifié et revu le code du DndHandler. J'ai aussi ajouté une pseudo-classe abstraite DnD_able qui résume les méthodes à réaliser côté source et cible.

    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    import tkinter.ttk as ttk
    import tkinter as tk
     
    class DnDHandler():
     
        def __init__(self, draggable):
            self.draggable = draggable
            self.source = draggable.source
            self.target = None
            root = self.source._root()
            root.bind('<Motion>', self.on_motion)
            root.bind('<ButtonRelease-1>', self.on_button_release)
     
        def on_motion(self, event):
            draggable = self.draggable
            if draggable.withdrawn:
                self.draggable.show()
     
            x, y = event.x_root, event.y_root
     
            source = self.source
            target = source.winfo_containing(x, y)
            new_target = None
     
            while target is not None:
                if dnd_accept := getattr(target, 'dnd_accept', None):
                    new_target = dnd_accept(source, event)
                    if new_target is not None:
                        break
                target = target.master
     
            old_target = self.target
            if old_target is not new_target:
                if old_target is not None:
                    self.target = None
                if new_target is not None:
                    self.target = new_target
     
        def on_button_release(self, event):
            target = self.target
            source = self.source
            root = source._root()
            root.unbind('<Motion>', )
            root.unbind('<ButtonRelease-1>', )
            commit = target is not None and target != source
            if commit:
                target.dnd_commit(source)
            self.draggable.destroy(commit)
     
    class Draggable(tk.Variable):
        withdrawn = True
        commit = False
     
        def __init__(self, source):
            super().__init__()
            self.source = source
            self.save_cursor = source['cursor'] or ""
            self.dnd_handler = DnDHandler(self)
            source.wait_variable(self)
     
        def show(self):
            source = self.source
            self.saved_cursor = source['cursor'] or ""
            source['cursor'] = 'hand2'
            self.withdrawn = False
     
        def destroy(self, commit=False):
            if not self.withdrawn:
                self.source['cursor'] = self.saved_cursor
            self.set('done')
            self.commit = commit
     
    class DnD_able:
     
        def dnd_accept(self, source, event):
            '''returns self if it accepts to be a drop target or None'''
            return None
     
        def dnd_commit(self, source):
            return self
     
     
    class Treeview(ttk.Treeview, DnD_able):
     
        def __init__(self, master=None, **kwargs):
            super().__init__(master, **kwargs)
            self.selected = []
            self.bind('<<TreeviewSelect>>', self.on_selection_change)
     
        def on_selection_change(self, event):
            self.selected = self.selection()
            if self.selected:
                w = Draggable(self)
                if w.commit:
                    self.delete(*self.selected)
                    self.selected = []
     
        def dnd_accept(self, source, event):
            return self if isinstance(source, type(self)) else None
     
        def dnd_commit(self, source):
            for item in source.selected:
                data = source.item(item)
                row = { k: v for k, v in data.items() if k in ('text values') }
                self.insert('', 'end', **row)
     
    if __name__ == '__main__':    
     
        from collections import namedtuple
     
        fruit = namedtuple('Fruit', 'name, cost, range')
        fruits = [
            fruit('Pommes', 2.24, 1),
            fruit('Poires', 3.31, 2),
            fruit('Bananes', 1.69, 3),
            fruit('Prunes', 3.78, 4),
            fruit('Oranges', 2.05, 5),
            fruit('Pêches', 3.69, 6),
            fruit('Abricots', 4.20, 7),
            fruit('Kiwis', 3.53, 8),
            ]
     
        root = tk.Tk()
     
        #Créer un tableau
        columns=("Fruit","Prix","Numéro")
        widths = 130, 100, 100
        trees = []
        for i in range(2):
            tv = Treeview(root, height=10, show="headings", columns=columns,
                             name=f'tv_{i}')
            for label in columns:
                tv.heading(label,text=label)
            for label, width in zip(columns, widths):
                tv.column(label, width=width, anchor='center')
            trees.append(tv)
            tv.grid(row=0, column=i)
     
        # Insérer des données de fonction avec couleur alternée par ligne
        tv = trees[0]
        for i, fruit in enumerate(fruits):
            tv.insert('', i,text=fruit.name,values=fruit)
     
        root.mainloop()
    note: mon Draggable est "variable Tk" pour utiliser le .wait_variable et remonter un status à la source.

    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

  11. #11
    Expert éminent
    Homme Profil pro
    Architecte technique retraité
    Inscrit en
    Juin 2008
    Messages
    21 717
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Manche (Basse Normandie)

    Informations professionnelles :
    Activité : Architecte technique retraité
    Secteur : Industrie

    Informations forums :
    Inscription : Juin 2008
    Messages : 21 717
    Par défaut Un peu de décoration.
    Re-Salut,

    Le principal défaut est qu'on ne voit pas si on atteint la cible ou pas. Dans la version originale, le repérage de la cible est fait avec .focus_set qui active les "highlight".
    Je n'ai pas trouvé comment faire cela côté Treeview alors je l'ai habillé pour.

    L'autre intérêt de l'exercice est d'ajouter des primitives à l'interface entre DndHandler et les widgets source et target pour "visibiliser" l'entrée et la sortie de...

    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
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    import tkinter.ttk as ttk
    import tkinter as tk
     
    class DnDHandler():
     
        def __init__(self, draggable):
            self.draggable = draggable
            self.source = draggable.source
            self.target = None
            root = self.source._root()
            root.bind('<Motion>', self.on_motion)
            root.bind('<ButtonRelease-1>', self.on_button_release)
     
        def on_motion(self, event):
            draggable = self.draggable
            if draggable.withdrawn:
                self.draggable.show()
                self.source.dnd_enter()
     
            x, y = event.x_root, event.y_root
     
            source = self.source
            target = source.winfo_containing(x, y)
            new_target = None
     
            while target is not None:
                if dnd_accept := getattr(target, 'dnd_accept', None):
                    new_target = dnd_accept(source, event)
                    if new_target is not None:
                        break
                target = target.master
     
            old_target = self.target
            if old_target is not new_target:
                if old_target is not None:
                    self.target = None
                    old_target.dnd_leave()
                if new_target is not None:
                    self.target = new_target
                    new_target.dnd_enter()
     
        def on_button_release(self, event):
     
            target = self.target
            source = self.source
            root = source._root()
            root.unbind('<Motion>', )
            root.unbind('<ButtonRelease-1>', )
            commit = target is not None and target != source
            if commit:
                target.dnd_commit(source)
            self.draggable.destroy(commit)
            source.dnd_end()
     
    class Draggable(tk.Variable):
        withdrawn = True
        commit = False
     
        def __init__(self, source):
            super().__init__()
            self.source = source
            self.save_cursor = source['cursor'] or ""
            self.dnd_handler = DnDHandler(self)
            source.wait_variable(self)
     
        def show(self):
            source = self.source
            self.saved_cursor = source['cursor'] or ""
            source['cursor'] = 'hand2'
            self.withdrawn = False
     
        def destroy(self, commit=False):
            if not self.withdrawn:
                self.source['cursor'] = self.saved_cursor
            self.set('done')
            self.commit = commit
     
    class DnD_able:
     
        def dnd_accept(self, source, event):
            '''returns self if it accepts to be a drop target or None'''
            return None
     
        def dnd_commit(self, source):
            return self
     
     
        def dnd_enter(self):
            pass
     
        def dnd_leave(self):
            pass
     
        def dnd_end(self):
            pass
     
    class TreeviewBorder(ttk.Treeview):
     
        def __init__(self, master=None, **kw):
            frame = self._frame = tk.Frame(master, bd=2)
            self.saved_bg = frame.cget('bg')
            super().__init__(frame, **kw)
     
        def grid(self, **kw):
            self._frame.grid(**kw)
            super().pack()
     
        def grid_forget(self):
            self._frame.grid_forget()
            super().pack_forget()
     
        def toggle_highlight(self, highlight):
            if highlight:
                self._frame.configure(bg='blue')
            else:
                self._frame.configure(bg=self.saved_bg)            
     
     
    class Treeview(TreeviewBorder, DnD_able):
     
        def __init__(self, master=None, **kwargs):
            super().__init__(master, **kwargs)
            self.selected = []
            self.bind('<<TreeviewSelect>>', self.on_selection_change)
     
        def on_selection_change(self, event):
            self.selected = self.selection()
            if self.selected:
                w = Draggable(self)
                if w.commit:
                    self.delete(*self.selected)
                    self.selected = []
     
        def dnd_accept(self, source, event):
            return self if isinstance(source, type(self)) else None
     
        def dnd_commit(self, source):
            for item in source.selected:
                data = source.item(item)
                row = { k: v for k, v in data.items() if k in ('text values') }
                self.insert('', 'end', **row)
     
     
        dnd_enter = lambda self: super().toggle_highlight(True)
        dnd_end = dnd_leave = lambda self: super().toggle_highlight(False)
     
     
     
     
    if __name__ == '__main__':    
     
        from collections import namedtuple
     
        fruit = namedtuple('Fruit', 'name, cost, range')
        fruits = [
            fruit('Pommes', 2.24, 1),
            fruit('Poires', 3.31, 2),
            fruit('Bananes', 1.69, 3),
            fruit('Prunes', 3.78, 4),
            fruit('Oranges', 2.05, 5),
            fruit('Pêches', 3.69, 6),
            fruit('Abricots', 4.20, 7),
            fruit('Kiwis', 3.53, 8),
            ]
     
        root = tk.Tk()
     
        #Créer un tableau
        columns=("Fruit","Prix","Numéro")
        widths = 130, 100, 100
        trees = []
        for i in range(2):
            tv = Treeview(root, height=10, show="headings", columns=columns,
                             name=f'tv_{i}')
            for label in columns:
                tv.heading(label,text=label)
            for label, width in zip(columns, widths):
                tv.column(label, width=width, anchor='center')
            trees.append(tv)
            tv.grid(row=0, column=i)
     
        # Insérer des données de fonction avec couleur alternée par ligne
        tv = trees[0]
        for i, fruit in enumerate(fruits):
            tv.insert('', i,text=fruit.name,values=fruit)
     
        root.mainloop()
    Il me reste à rendre le Draggable un peu plus "riche" qu'un simple changement de curseur. Ce qui va encore ajouter quelques primitives entre le DndHandler et Draggable. Mais je ne suis pas encore satisfait de ma copie (et je n'ai pas que çà sur le gaz).

    - W


    - W
    Architectures post-modernes.
    Python sur DVP c'est aussi des FAQs, des cours et tutoriels

Discussions similaires

  1. Glisser-déposer entre deux QTableWidget
    Par ApproxDev dans le forum Qt
    Réponses: 2
    Dernier message: 12/10/2015, 09h41
  2. Glisser-déposer entre deux QTreeView
    Par sfarc dans le forum Débuter
    Réponses: 21
    Dernier message: 16/05/2012, 10h59
  3. Glisser-déposer entre deux zones de listes
    Par Arkham46 dans le forum Contribuez
    Réponses: 2
    Dernier message: 13/04/2012, 12h20
  4. Réponses: 1
    Dernier message: 16/09/2011, 17h28
  5. Transfert de données entre deux treeview
    Par stefsas dans le forum ASP.NET
    Réponses: 0
    Dernier message: 05/07/2010, 16h21

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