Slepen en Pleuren (Drag en Drop) in C++ Builder.

© Harry Broeders.

Deze pagina is bestemd voor studenten van de Haagse Hogeschool - Academie voor Technology, Innovation & Society Delft.

Als je nog niet weet hoe je een eenvoudige GUI Windows applicatie met C++ Builder kunt maken lees dan eerst deze pagina.

Je kunt een voorwerp op het scherm op allerlei manieren laten bewegen. Een van de mogelijkheden is gebruik te maken van drag en drop. Het is ook mogelijk om de objecten zelf te verplaatsen door mouse events af te vangen.

Drag en Drop.

Een van de mogelijkheden om objecten te verplaatsen is het gebruik van drag en drop. Alle van TControl afgeleide classes in VCL ondersteunen drag en drop. In het onderstaande voorbeeld zijn TPanel's gebruikt maar dat is maar een voorbeeld. De code voor het onderstaande voorbeeld kun je vinden in dragdrop.zip .

De vier gekleurde vlakken zijn allemaal van het type TPanel. De layout van de verschillende objecten op de TForm is hieronder weergegeven.

Het lichtgroene vlak is Panel1 en rode vlak is Panel2. Beide kunnen versleept worden als de applicatie runt. Om dit mogelijk te maken moet de property DragMode op dmAutomatic worden gezet.

De reset knoppen op deze panels zetten de Top en Left properties weer op hun oorspronkelijke waarden zodat de panels weer teruggaan naar hun oorspronkelijke posities.

void __fastcall TForm1::Button1Click(TObject *Sender) {
        Panel1->Top=8;
        Panel1->Left=8;
}

void __fastcall TForm1::Button2Click(TObject *Sender) {
        Panel2->Top=120;
        Panel2->Left=8;
}

Het donkergroene vlak is Panel3 en het gele vlak is Panel4. Op deze vakken kun je Panel1 en Panel2 laten vallen. Om dit mogelijk te maken moeten de events OnDragDrop en OnDragOver gedefinieerd worden.

De functie Panel3DragOver wordt aangeroepen als een gesleept object boven Panel3 "zweeft". Met de parameter Accept moet aangegeven worden of het gesleepte object (aangewezen door de pointer Source) wordt geaccepteerd. In dit geval accepteren we Panel1 en Panel2.

void __fastcall TForm1::Panel3DragOver(TObject *Sender, TObject *Source, int X, int Y, TDragState State, bool &Accept) {
        Accept = Source==Panel1 || Source==Panel2 ;
}

Als het gesleepte object op Panel3 wordt "gedropt" dan wordt de functie Panel3DragDrop aangeroepen. In deze functie wordt het gesleepte object (aangewezen door de pointer Source) verplaatst naar het object (aangewezen door de pointer Sender) waar het gedropt is.

void __fastcall TForm1::Panel3DragDrop(TObject *Sender, TObject *Source, int X, int Y) {
        TControl* bestemming(static_cast<TControl*>(Sender));
        TControl* bron(static_cast<TControl*>(Source));
        // Bepaal nieuwe lokatie binnen TForm
        bron->Left=bestemming->Left;
        bron->Top=bestemming->Top;
        // Zet op voorgrond
        bron->BringToFront();
}

Panel4 is groter dan Panel1 en Panel2. Hierdoor wordt de implementatie van de functie Panel4DragDrop iets ingewikkelder. Een object kan ook binnen Panel4 verplaatst worden. Omdat er op Panel4 meer ruimte is kunnen ook Panel1 en Panel2 beide op Panel4 geplaatst worden. Panel4 is voorzien van een TCheckBox. De toestand van deze CheckBox1 wordt bijgehouden in de private variabele lock. Panel4 accepteerd alleen objecten als de variabel lock false is.

void __fastcall TForm1::CheckBox1Click(TObject *Sender) {
        TCheckBox* box(static_cast<TCheckBox*>(Sender));
        lock=box->Checked;
}

void __fastcall TForm1::Panel4DragOver(TObject *Sender, TObject *Source, int X, int Y, TDragState State, bool &Accept) {
        // kijk of je het object wil accepteren
        // accepteert Panel1 en Panel2 als lock niet aanstaat
        Accept = (Source==Panel1 || Source==Panel2) && !lock;
}

void __fastcall TForm1::Panel4DragDrop(TObject *Sender, TObject *Source, int X, int Y) {
        TControl* bestemming(static_cast<TControl*>(Sender));
        TControl* bron(static_cast<TControl*>(Source));
        int Left, Top;
        // Bepaal nieuwe lokatie binnen bestemming
        Left=X-bron->Width/2;
        Top=Y-bron->Height/2;
        // Zorg dat bron binnen bestemming blijft.
        if (Left<0)
                Left=0;
        if (Left+bron->Width>bestemming->Width)
                Left=bestemming->Width-bron->Width;
        if (Top<0)
                Top=0;
        if (Top+bron->Height>bestemming->Height)
                Top=bestemming->Height-bron->Height;
        // Bepaal nieuwe lokatie binnen TForm
        bron->Left=bestemming->Left+Left;
        bron->Top=bestemming->Top+Top;
        bron->BringToFront();
}

Slepen.

Objecten kunnen ook verplaatst worden als reactie op mouse events. Op deze manier kun je objecten "verslepen".

Eind 2005 stelde ik aan student Chris Werkmeester de vraag:

Ik heb geprobeerd om het hele panel te verslepen door bij een MouseMove de Top en Left attributen aan te passen maar dat werkt niet goed. Je ziet het panel dan 2x getekend (op een behoorlijke afstand van elkaar) en het panel volgt de muis ook niet goed. Heb jij al een manier gevonden om het panel in zijn geheel met de muis te verslepen? Dus zonder drag en drop maar gewoon door zelf op de muisbewegingen te reageren.

Chris schreef:

Ja dat lukte me makkelijk.

Idd met behulp van Top & Left. En de MouseMove & MouseDown functie.

Maar daarbinnen gebruik je dan nl twee globale/static variabelen. en je moet niet vergeten dat de X & Y argumenten afhankelijk zijn van Top & Left, je moet daarvoor compenseren anders krijg je idd een schokkend effect.

Een simpel voorbeeld is MoveMe.zip. Niet helemaal 'mooi' maar dat hoeft ook niet als voorbeeld ;)

Ps: als de automatische refresh je hindert kan je eventueel een timer plaatsen mbv een constructie als if( currentx != 0xFFFFFFFF && (timed) ) om de verplaating te forceren naar bv. elke 100ms max ipv. direct bij een event. Maar dan moet je bij mouseup ook nog de verplaats controle uitvoeren, anders kan hij natuurlijk blijven haken.

Met dit programma kun je twee "blokjes" binnen een window verslepen.