Форум программистов, компьютерный форум, киберфорум
Наши страницы

C++ Builder

Войти
Регистрация
Восстановить пароль
 
Рейтинг: Рейтинг темы: голосов - 20, средняя оценка - 4.95
volvo
Супер-модератор
Эксперт Pascal/DelphiАвтор FAQ
24036 / 16017 / 4851
Регистрация: 22.10.2011
Сообщений: 28,355
Записей в блоге: 5
#1

Добавление в приложение функционала Drag-n-Drop/Drag-n-Dock - C++ Builder

14.12.2015, 01:30. Просмотров 4870. Ответов 0
Метки нет (Все метки)

Добавляем функционал Drag-n-Drop в приложение.

Давайте рассмотрим несколько разновидностей D&D:
  1. Самый простой вариант: перетаскивание данных из одного компонента в другой в пределах формы (переупорядочивание дерева TTreeView, перенос информации из дерева в TMemo, в TListView, перемещение вкладок TPageControl; вариантов очень много, все зависит только от вашей фантазии.
  2. Чуть сложнее: перетаскивание файлов/текста извне в наше приложение
  3. Еще чуть сложнее: перетаскивание файлов из нашего приложения наружу

Рассмотрим все три варианта:

1. Перетаскивание данных между компонентами, лежащими на форме, делается очень просто. Для примера - вот перенос из одного ListBox-а в другой:

выставляем для обоих ListBox-ов свойство DragMode в dmAutomatic, и назначаем события приемника (того компонента, куда будем бросать информацию) OnDragOver (для определения, стоит ли принимать то, что сейчас находится над компонентом, и может быть на него брошено. Если тут выставить параметр Accept в false - то курсор мыши покажет, что бросать то, что принесли, бесполезно) и OnDragDrop (тут собственно предпринимаются действия по переносу данных из одного компонента в другой, то есть, из Source в Sender)

Добавление в приложение функционала Drag-n-Drop/Drag-n-Dock

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void __fastcall TForm1::ListBox2DragOver(TObject *Sender, TObject *Source, int X,
          int Y, TDragState State, bool &Accept)
{
    // принимать D&D только если перетягивается что-то из ListBox-а
    Accept = (Source->ClassNameIs("TListBox") && static_cast<TListBox*>(Source)->ItemIndex > -1);
}
//---------------------------------------------------------------------------
 
void __fastcall TForm1::ListBox2DragDrop(TObject *Sender, TObject *Source, int X,
          int Y)
{
    TListBox *src = static_cast<TListBox*>(Source);
    TListBox *targ = static_cast<TListBox*>(Sender);
    // Все выделенные элементы в источнике, которых в приемнике
    // еще нет, добавляем в приемник (дубликаты отсекаем)
    for(int i = 0; i < src->Count; i++)
    {
        if(src->Selected[i] && targ->Items->IndexOf(src->Items->Strings[i]) < 0)
        {
            targ->Items->Add(src->Items->Strings[i]);
        }
    }
}
По той же схеме делаются и другие переносы между компонентами на форме.

Тестовые проектыBCB6:
bcb6_dnd_inner.7z

XE2:
dnd_inner.7z



2. Перетаскивание данных (файлов или текста) в приложение из другого приложения:

Перетаскивание файлов и текста делается разными способами. Для того, чтобы форма умела принимать файлы, брошенные на нее извне (например, из Windows Explorer-а) достаточно добавить обработчик сообщения WM_DROPFILES:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// заголовочный файл
class TForm1 : public TForm
{
__published:    // IDE-managed Components
    TListBox *ListBox1;
    void __fastcall FormCreate(TObject *Sender);
    void __fastcall FormDestroy(TObject *Sender);
private:    // User declarations
public:     // User declarations
    __fastcall TForm1(TComponent* Owner);
 
protected:
    void __fastcall WMDropFiles(TWMDropFiles &Message);
    BEGIN_MESSAGE_MAP
        MESSAGE_HANDLER(WM_DROPFILES, TWMDropFiles, WMDropFiles);
    END_MESSAGE_MAP(TForm);
};
C++
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
// реализация
void __fastcall TForm1::WMDropFiles(TWMDropFiles &Message)
{
    HDROP drop_handle = (HDROP)Message.Drop;
    TCHAR fName[256];
 
    // количество перетягиваемых файлов
    int count = DragQueryFile(drop_handle, -1, NULL, NULL);
    for (int i=0; i<count; i++)
    {
        // Получаем имя каждого файла
        DragQueryFile(drop_handle, i, fName, 256);
        // и обрабатываем его нужным образом (для примера - просто добавляем в ListBox)
        ListBox1->Items->Add(fName);
    };
    DragFinish(drop_handle);
}
void __fastcall TForm1::FormCreate(TObject *Sender)
{
    // уведомляем систему о том, что наша форма умеет обрабатывать WM_DROPFILES
    DragAcceptFiles(this->Handle, true);
}
//---------------------------------------------------------------------------
 
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
    // ну, и по привычке, чистим за собой перед выходом
    DragAcceptFiles(this->Handle, false);
}
Чтобы иметь возможность перетянуть на форму текст нужно действовать по-другому. Для этого форма должна реализовать интерфейс IDropTarget. Добавим в вышеприведенный код еще и такую возможность:

C++
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
// Заголовочный файл
 
class TForm1 : public TForm, public IDropTarget
{
__published: // IDE-managed Components
 
    TListBox *ListBox1;
    TMemo *Memo1;
 
    void __fastcall FormCreate(TObject *Sender);
    void __fastcall FormDestroy(TObject *Sender);
 
private: // User declarations
public : // User declarations
    __fastcall TForm1(TComponent* Owner);
 
protected:
    void __fastcall WMDropFiles(TWMDropFiles &Message);
        BEGIN_MESSAGE_MAP MESSAGE_HANDLER(WM_DROPFILES, TWMDropFiles, WMDropFiles);
    END_MESSAGE_MAP(TForm);
 
    // IUnknown
    STDMETHOD_(ULONG, AddRef)()
    {
        return 1;
    }
 
    STDMETHOD_(ULONG, Release)()
    {
        return 1;
    }
 
    // IDropTarget
    STDMETHOD(DragEnter)(LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect);
    STDMETHOD(DragOver)(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect);
    STDMETHOD(DragLeave)();
    STDMETHOD(Drop)(LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect);
};
C++
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
// Реализация TForm1::WMDropFiles приведена выше, она не изменилась 
 
void __fastcall TForm1::FormCreate(TObject *Sender)
{
    OleInitialize(0);
    // Регистрируем окно, как приемник Drag-n-Drop операций
    OleCheck(RegisterDragDrop(this->Handle, this));
 
    // уведомляем систему о том, что наша форма умеет обрабатывать еще и WM_DROPFILES
    DragAcceptFiles(this->Handle, true);
}
// ---------------------------------------------------------------------------
 
void __fastcall TForm1::FormDestroy(TObject *Sender)
{
    // ну, и по привычке, чистим за собой перед выходом
    DragAcceptFiles(this->Handle, false);
 
    // Заодно прекращаем регистрацию приемника
    RevokeDragDrop(this->Handle);
    OleUninitialize();
 
}
// ---------------------------------------------------------------------------
 
// Реализация методов интерфейса IDropTarget
STDMETHODIMP TForm1::DragEnter(LPDATAOBJECT pDataObj, DWORD grfKeyState,
    POINTL pt, LPDWORD pdwEffect)
{
    *pdwEffect = DROPEFFECT_COPY;
    return S_OK;
}
// ---------------------------------------------------------------------------
 
STDMETHODIMP TForm1::DragOver(DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
{
    *pdwEffect = DROPEFFECT_COPY;
    return S_OK;
}
// ---------------------------------------------------------------------------
 
STDMETHODIMP TForm1::DragLeave()
{
    return S_OK;
}
// ---------------------------------------------------------------------------
 
// Основной метод - при отпускании объекта на форме вызывается именно он
STDMETHODIMP TForm1::Drop(LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt,
    LPDWORD pdwEffect)
{
    FORMATETC fmtetc;
    fmtetc.cfFormat = CF_UNICODETEXT; // или просто CF_TEXT, если собирается неюникодный проект
    fmtetc.ptd = NULL;
    fmtetc.dwAspect = DVASPECT_CONTENT;
    fmtetc.lindex = -1;
    fmtetc.tymed = TYMED_HGLOBAL;
 
    STGMEDIUM medium;
    // получаем данные из перетягиваемого объекта
    HRESULT hr = pDataObj->GetData(&fmtetc, &medium);
 
    // если при этом не было ошибки
    if (!FAILED(hr))
    {
        HGLOBAL hTxt = medium.hGlobal;
        // то вытягиваем текст, который перетянули на форму
        LPCTSTR pData = (LPCTSTR)GlobalLock(hTxt);
 
        // и заносим его в Memo
        Memo1->Text = pData;
        
        // после чего разблокируем память
        GlobalUnlock(hTxt);
        // и освобождаем Storage medium
        ReleaseStgMedium(&medium);
    }
    else // если была ошибка при получении данных из IDataObject
    {
        *pdwEffect = DROPEFFECT_NONE;
        // то ничего не делаем и возвращаем код этой ошибки
        return hr;
    }
    return S_OK;
}
Таким образом, наша форма сейчас умеет принимать и файлы и текст, если на нее бросить текст из Notepad++ или из Firefox-а (т.е., из тех приложений, которые умеют быть источниками при перетаскивании текста, обычный виндовый Блокнот, к примеру, не умеет этого, также как и браузер Opera), то этот текст появится в Memo, а если бросить группу файлов из Проводника - то их имена добавятся в ListBox.

Тестовые проектыBCB6:
bcb6_dnd_target.7z

XE2:
dnd_target.7z



3. Вот мы и добрались до пункта, когда нужно из нашего приложения отослать текст или файл наружу.

Для этого нужно реализовать интерфейс IDropSource, перенести требуемый текст в IDataObject, и вызвать функцию DoDragDrop

Основной код в этом случае будет выглядеть так (обработка сообщений через WinAPI понадобилась исключительно для того, чтобы при перемещении выделенного участка текста, выделение с него не снималось) :
C++
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
            case WM_MOUSEMOVE: // в случае, если пользователь перемещает мышь над Memo
                // и кнопка мыши нажата
                if (fMouseDown)
                {
                    IDataObject *pDataObject;
                    IDropSource *pDropSource;
                    DWORD dwEffect;
                    DWORD dwResult;
 
                    FORMATETC fmtetc =
                        {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; // CF_TEXT для BCB 6 - 2007
                    STGMEDIUM stgmed =
                        {TYMED_HGLOBAL, {0}, 0};
 
                    // Перемещаем выделенный в Memo текст в IDataObject
                    stgmed.hGlobal = CopySelection();
 
                    // Создаем экземпляры COM-объектов IDataObject и IDropSource
                    CreateDropSource(&pDropSource);
                    CreateDataObject(&fmtetc, &stgmed, 1, &pDataObject);
 
                    // Начинаем операцию Drag-n-Drop
                    dwResult = DoDragDrop(pDataObject, pDropSource,
                        DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);
                    // В случае успеха
                    if (dwResult == DRAGDROP_S_DROP)
                    {
                        if (dwEffect & DROPEFFECT_MOVE)
                        {
                            // Если текст был именно перенесен, а не скопирован
                            // (при выполнении Drag-n-Drop была зажата кнопка Alt, 
                            // а не Ctrl) - удалить выделенный текст из Memo
                            this->SelText = "";
                        }
                    }
                    // операция переноса отменена
                    else if (dwResult == DRAGDROP_S_CANCEL)
                    {
                    }
                    pDataObject->Release(); // и удаляем все объекты
                    pDropSource->Release();
 
                    ReleaseCapture();
                    fMouseDown = FALSE;
                    fDidDragDrop = TRUE;
                }
                break;
Полностью работоспособный пример, реализующий перетаскивание с формы файлов и текстовой информации присоединен в архиве:

Тестовые проектыBCB6:
bcb6_dnd_source.7z

XE2:
dnd_source.7z



Я привел проекты, показывающие реализацию всех трех разновидностей Drag-n-Drop для разных версий Билдера (для BCB6 и для RAD XE2). В принципе они одинаковые, но есть несколько отличий:
  1. При работе с Юникодными версиями Билдера поле cfFormat структуры FORMATETC должно содержать значение CF_UNICODETEXT, в то время как при использовании неюникодных Билдеров туда пишется CF_TEXT
  2. Начиная с версии RAD 2011 нет необходимости переопределять методы IUnknown (_AddRef и _Release) вручную, достаточно унаследоваться от TInterfacedObject и воспользоваться реализациями TInterfacedObject::_AddRef() и TInterfacedObject::_Release().
    (хотя у меня в BCB6 Portable прекрасно поддерживаются TInterfacedObject-ы, поэтому переделывать пока не стал, оставил как есть)


P.S. Замечания/пожелания/вопросы приветствуются, обращайтесь в ЛС.

P.P.S. Продолжение следует (реализация Drag-n-Dock в приложениях)
24
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
14.12.2015, 01:30
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Добавление в приложение функционала Drag-n-Drop/Drag-n-Dock (C++ Builder):

Drag Drop Dock - C++ Builder
есть ли толковая литература или интересные статьи по этому методу мне нужно просто реализовать перетаскивание картинки по форме только...

Drag'n'Dock Мерцание и тормоза при перерисовки рамки окна - C++ Builder
Подскажите, пожалуйста, как избавиться от мерцания и &quot;тормозов&quot; при рисовании рамки окна, когда оно перемещается пользователем (DragKind =...

Drag and drop! - C++ Builder
Парни может у кого есть пример или что кодик какой как реализовать drag and drop с заменой. Т.е. допустим я перетаскиваю объект одного типа...

Drag&Drop - C++ Builder
Здравствуйте:) Уже месяц работаю над текстовым редактором. Встал вопрос: например на рабочем столе файл. Как реализовать Drag&amp;Drop?...

Drag-and-Drop по сетке - C++ Builder
Подскажите, как перемещать компоненты по форме с привязкой к сетке?

не работает Drag and Drop - C++ Builder
Мне для программы нужно использовать Drag and Drop я нашёл http://www.cyberforum.ru/cpp-builder/thread81001.html, и сделал всё так как...

0
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
14.12.2015, 01:30
Привет! Вот еще темы с ответами:

Drag&Drop - C++ Builder
Как перенести из TreeView1 текст в Edit1 !?!?!?

Drag Drop файлов - C++ Builder
Делаю вот так: : class TForm1 : public TForm { private: void __fastcall WMDropFiles(TWMDropFiles message); public: ...

TFrame: Drag & Drop - C++ Builder
Доброго дня! На форме по-умолчанию находится 2 экземпляра TFrame. На каждом TFrame находится TPanel c свойством Align = alClient. ...

ListBox с мультивыбором, Drag and Drop - C++ Builder
Написал код для перетаскивания строк из одного Listbox в другой...не пойму почему выбивает ошибку в этой строчке for ( int i=0;...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
1
Закрытая тема Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru