wxDev.fr, le portail francophone consacré à wxWidgets ! ( The french portal for wxWidgets )  
Esp. membre
Recheche rapide



Recherche avancée
Statistiques
Membres inscrits :2359

Membres en ligne : 0
Invités en ligne : 3
Pub hébergeur
Pourquoi cette pub ?

Valid XHTML 1.0 Transitional

Valid CSS2

Menu Snippets (navigation):
Pages: 1    Accueil » Snippets » Système
» [D&D] transfère des données complexes
Détails du snippet : [D&D] transfère des données complexes
Informations sur l'auteur de ce snippet :


Hors ligne
gbdivers (Membre)
Inscrit le : 05-03-2008
Messages: 92
Snippets: 2
Tutoriels: 0
Introduction / Description :
Suite à une question sur le forum, voici un snippet sur d&d d'objets complexes.

Pour commencer, c'est quoi un objet complexe dans ce cadre précis ?
Pour simplifier la vie des petits codeurs que nous sommes, wxWidgets intègre des objets permettant de faire des d&d des formats les plus courants : un chaine de caractère (wxTextDataObject), une liste de fichier (wxFileDataObject) ou une image (wxBitmapDataObject). Tous les autres formats sont des objets complexes !

Voici comment se présente le snippet :
1. Explication du d&d avec wxWidgets
2. La classe contenant les données à transférer
3. La classe créant les données à transférer
4. La classe recevant les données à transférer
5. Remarques divers

Pour illustrer ce snippet, je crée un d&d qui transfère plusieurs variables différentes : un int, un double, un pointeur, une chaine de caractère (je verrais plus tard pour ajouter le transfère d'image et de liste de string). Il est également possible d'exporter (application vers extérieur) des strings.
Contenu du snippet :
1. Comment fonctionne les d&d avec wxWidgets
Rien de particulier dans le fonctionnement général : une source (wxDropSource) crée un objet contenant les données à transférer (wxDataObject), une cible (wxDropTarget) reçoit cet objet et traite les données reçues.

Une particularité à signaler : lors de la création de la cible (wxDropTarget), un objet vide est crée (wxDataObject). Lors du d&d, un tampon en mémoire est créer, contenant une copie des données à transférer. Ce tampon est utilisé pour transférer les données entre l'objet crée lors du d&d et contenant les données et l'objet crée lors de la création de la cible. A charge au codeur d'écrire les fonctions qui lisent et écrivent les données sous forme de tampon.
Pour simplifier le code, je passe simplement un pointeur sur l'objet pour les d&d internes.

2. L'objet à transférer
L'objet à déplacer dérive de wxDataObjet. Cet objet contient les informations à transférer entre la source et la cible.

le format (wxDataFormat) permet de définir le type de données qu'il est possible de lire (Get) ou d'écrire (Set). Par exemple, dans le cas présenté, il est possible de définir 2 formats : le format "MyDataObject" utilisé en interne pour copier les données et le format par défaut "wxDF_TEXT" permettant le d&d du nom de fichier seul (wxString) versl'extérieur de l'application.

Code wxWidgets:

#include <wx/wx.h>
#include <wx/dnd.h>
 
class MyDataObject : public wxDataObject
{
    public:
        // default constructor
        MyDataObject();
 
        // class accessors
        wxString GetStr() { return m_Str; }
        void SetStr(wxString str) { m_Str = str; }
        int GetInt() { return m_Int; }
        void SetInt(int i) { m_Int = i; }
        double GetDouble() { return m_Double; }
        void SetDouble(double d) { m_Double = d; }
        void* GetPtr() { return m_Ptr; }
        void SetPtr(void* ptr) { m_Ptr = ptr; }
 
        // virtual methods
        wxDataFormat GetPreferredFormat(Direction WXUNUSED(dir)) const;
        size_t GetFormatCount(Direction dir) const;
        void GetAllFormats(wxDataFormat *formats, Direction dir) const;
        size_t GetDataSize(const wxDataFormat& format) const;
        bool GetDataHere(const wxDataFormat& format, void *pBuf) const;
        bool SetData(const wxDataFormat& format, size_t WXUNUSED(len), const void *buf);
 
    private:
        // data
        wxString m_Str;
        int m_Int;
        double m_Double;
        void* m_Ptr;
 
        / format
        wxDataFormat m_Format;
};
 
 
MyDataObject::MyDataObject()
: wxDataObject()
{
    m_Str = wxT("");
    m_Int = 0;
    m_Double = 0.0;
    m_Ptr = NULL;
 
    m_Format.SetId(wxT("MyDataObject"));
}
 
wxDataFormat MyDataObject::GetPreferredFormat(Direction WXUNUSED(dir)) const
{
    return m_Format;
}
 
size_t MyDataObject::GetFormatCount(Direction dir) const
{
    if(dir == Set)
    // 2 formats pour les cibles (formats interne+texte)
        return 2;
    else
    // 1 format pour les sources (format interne)
        return 1;
}
 
void MyDataObject::GetAllFormats(wxDataFormat *formats, Direction dir) const
{
    // format interne utilisable comme source ou cible
    formats[0] = m_Format;
 
    // format string utilisable uniquement comme cible
    if(dir == Set)
        formats[1] = wxDataFormat(wxDF_TEXT);
}
 
size_t MyDataObject::GetDataSize(const wxDataFormat& format) const
{
    if (format == m_Format)
    // si format interne, taille = pointeur
        return sizeof(void*);
    else if (format == wxDataFormat(wxDF_TEXT))
    // si format texte, taille = la chaine de caractères
        return ( sizeof(int) + (m_Str.Len()+1) * sizeof(wxChar) );
    else
        return 0;
}
 
bool MyDataObject::GetDataHere(const wxDataFormat& format, void *pBuf) const
{
    if ( format == m_Format )
    {
        // pour le format interne, on revoit un pointeur sur l'objet
        *pBuf = (void*) this;
        return true;
    }
    else if (format == wxDataFormat(wxDF_TEXT))
    {
        // pour le format text, on copie la chaine dans le tampon
        wxStrcpy( (wxChar*)pBuf,  m_Str.c_str() );
        return true;
    }
    else
        return false;
}
 
bool MyDataObject::SetData(const wxDataFormat& format, size_t WXUNUSED(len), const void *buf)
{
    if ( format == m_Format )
    {
        // pour le format interne, on récupère un pointeur sur l'objet
        (MyDataObject*) ptr = *buf;
 
        // puis on récupère les données
        SetStr( ptr->GetStr() );
        SetInt( ptr->GetInt() );
        SetDouble( ptr->GetDouble() );
        SetPtr( ptr->GetPtr() );
 
        return true;
    }
    else if (format == wxDataFormat(wxDF_TEXT))
    {
        // pour le format texte, on copie le tampon dans une chaine
        m_Str = wxString( (const wxChar*) buf );
        return true;
    }
    else
        return false;
}


3. La source
La source peut être n'importe quel objet qui réagisse à un clic souris. En général, ca sera un objet dérivé d'un wxWindow. Lors d'un clic souris (par exemple un EVT_LEFT_DOWN), On crée un objet de type décrit en 1. Certaine classe possède des events spécifique pour les BeginDrag (par exemple "EVT_LIST_BEGIN_DRAG" pour wxListCtrl).

Code wxWidgets:

class MyDlg : public wxDialog
{
    ...
    wxListCtrl* m_ListSource;
    void OnBeginDrag(wxListEvent& event);
    DECLARE_EVENT_TABLE()
};
 
BEGIN_EVENT_TABLE(MyDlg, wxTextCtrl)
    EVT_LIST_BEGIN_DRAG(ID_SOURCE, MyDlg::OnBeginDrag)
    // EVT_LEFT_DOWN(ID_SOURCE, MyDlg::OnBeginDrag)
END_EVENT_TABLE()
 
void MyDlg::OnBeginDrag(wxListEvent& event)
{
    wxListItem item;
    item.SetId(event.GetIndex());
    item.SetColumn(0);                // récupère le text de la 1ère colonne
    item.SetMask(wxLIST_MASK_TEXT);
    m_ListSource->GetItem(item);
    wxString text = item.GetText();
 
    // on crée l'objet contenant les données à transférer
    MyDataObject data();
    data.SetStr(text);
    data.SetInt(event.GetIndex());
    data.SetPtr( (void*) m_ListSource );
    // data.SetDouble(...); non utilisé
 
    // on crée la source
    wxDropSource source(this, wxDROP_ICON(dnd_copy),
        wxDROP_ICON(dnd_move), wxDROP_ICON(dnd_none));
    source.SetData(data);
 
    // on rend la main au système pour qu'il gère le d&d
    source.DoDragDrop(true);
}


4. La cible
La cible dérive de wxWindow. On crée une classe dérivée de wxDropTarget qui se charge de traiter les données transmises. Puis on "active" la cible avec la fonction SetDropTarget.

Code wxWidgets:

#include <wx/dnd.h>
 
class MyDropTarget : public wxDropTarget
{
    public:
        MyDroptarget(MyDlg* dlg);
        wxDragResult OnData(wxCoord x, wxCoord y, wxDragResult def);
        bool OnDrop(wxCoord x, wxCoord y);
 
    private:
        // on garde un pointeur vers le dialog contenant la cible du d&d
        MyDlg* m_Dlg;
};
 
 
PT_MyDropTarget::MyDropTarget(MyDlg* dlg)
: wxDropTarget(new MyDataObject)
{
    m_Dlg = dlg;
}
 
bool MyDropTarget::OnDrop(wxCoord x, wxCoord y)
{
    return true;
}
 
wxDragResult MyDropTarget::OnData(wxCoord x, wxCoord y, wxDragResult def)
{
    if ( !GetData() )
    {
        wxMessageBox(wxT("Failed to get drag and drop data"));
        return wxDragNone;
    }
    else
        wxMessageBox(_("reussit"), _("reussit"));
 
    MyDataObject* data = (MyDataObject*) GetDataObject();
    // non utilisé
    // data->GetInt()
    // data->GetDouble()
    // data->GetPtr()
    m_Dlg->OnDrop(data->GetStr());
 
    return def;
}
 


et dans la classe cible :

Code wxWidgets:

class MyDlg : public wxDialog
{
    ...
    wxListCtrl* m_ListTarget;
    void OnDrop(wxString text);
};
 
 
MyDlg::MyDlg()
{
    ...
    m_ListTarget= new wxListCtrl(this, ID_TARGET, wxDefaultPosition,
                wxDefaultSize, wxLC_REPORT|wxLC_VRULES|wxLC_HRULES|wxBORDER_RAISED);
    m_ListTarget->SetDropTarget(new MyDropTarget(this));
}
 
void MyDlg::OnDrop(wxString text)
{
    long item = m_ListTarget->InsertItem(m_ListTarget->GetCount(), text);
}


5. Remarques divers
Voici ce qui se passe en pratique :
1. A l'initialisation :
- création du dialog (MyDlg::MyDlg...)
- création du control source (new wxListCtrl...)
- création du control cible (new wxListCtrl...)
- activation de la cible (SetDroptarget...) Un objet MyDropTarget contenant un object MyDataObject est créé.

2. Lors du "begin drag" (EVT_LEFT_DOWN) :
- appel de la fonction OnBeginDrag (lié au EVT_LEFT_DOWN)
- création d'un wxDataObject contenant les informations de la source à transmettre (MyDataObject data...)
- création d'un wxDropSource (wxDropSource source...)
- on laisse la main au système pour gérer le d&d (source.DoDragDrop(true);)

3. Lors du "end drag" (aucun event associé) :
- appel de MyDropTarget::OnData() de l'objet MyDropTarget créé lors de l'initialisation (lors du SetDropTarget). On a donc 2 objets MyDataOject : celui créer lors de l'initialisation (vide, lié à l'objet MyDropTarget) et celui créé lors du "begin drag" (contenant les informations). Les fonctions GetDataHere et SetData sont appelée pour transférer les données entre les 2 objets MyDataObject (sous forme d'un tampon mémoire de taille "MyDataObject::GetDataSize")
- appel de MyDropTarget::OnDrop(). Les données transférées sont récuérable grace a la fonction MyDropTarget::GetDataObject(). Cette fonction appelle (ici) la fonction MyDlg::OnDrop() pour créer le nouvel item.


- Le transfert d'une wxString par buffer (dans les fonctions GetDataSize(), GetDataHere() et SetData()) est plus complexe que ce que j'ai écris. En particulier, il peut être nécessaire d'utiliser différentes implémentation en fonction des constantes de compilation wxNEEDS_UTF8_FOR_TEXT_DATAOBJ, wxUSE_UNICODE_WCHAR, wxUSE_UNICODE_UTF8, wxNEEDS_UTF16_FOR_TEXT_DATAOBJ... (voir le fichier common/dobjcmn.cpp pour les différentes implémentations : http://trac.wxwidgets.org/browser/wxWid … objcmn.cpp)
Explications finales :
J'espère que cet exemple aidera ceux qui utilise le d&d.
Commentaires
PaowZ (Membre)
Inscrit le : 04-02-2008
Messages: 16
Snippets: 0
Tutoriels: 0
Hors ligne
Je viens de tomber sur ce bout de code très intéressant pour la compréhension du DnD par wxWidget. Mais j'aurais une question concernant cette ligne:

Code:

*pBuf = (void*) this;

(méthode GetDataHere() de MyDataObject)

pBuf est un pointeur de type void, pourquoi affecte-t-on un pointeur vers this à l'adresse pointée par pBuf qui, a priori, est inconnue ?
Ne devrait-ce pas être :

Code:

pBuf = (void*) this;


??

Dernière modification par PaowZ (09-03-2010 19:51:30)

Xaviou (Administrateur)
Lieu: Annecy (74)
Inscrit le : 27-08-2007
Messages: 1367
Snippets: 25
Tutoriels: 6
Site web
Hors ligne
Salut.

Je t'ai répondu ici.

@+
Xav'

Le nouveau portail wxWidgets francophone : www.wxdev.fr
Ben en fait, vous y êtes déjà...
Menu Snippets (navigation):
Pages: 1    Accueil » Snippets » Système
» [D&D] transfère des données complexes