Juri Shutenko Personal Homepage. Visual FoxPro.

ActiveX TreeView. Заметки.
Drag-and-Drop. Прокрутка (scrolling) окна TreeView
Первая неприятность, ожидающая вас при реализации возможности использования операций Drag-and-Drop на компоненте TreeView, заключается в отсутствии прокрутки его окна при перемещении перетаскиваемого объекта. Microsoft официально признает существование такой проблемы и предлагает решение, с текстом которого можно ознакомиться здесь. Код, разумеется, приводится для Visual Basic, однако его нетрудно адаптировать к VFP.
 
Суть решения заключается в использовании таймера, который будет посылать сообщения окну TreeView c помощью функции API SendMessage - прокрутить на одну линию вверх, или прокрутить на одну лннию вниз, в зависимости от позиции курсора относительно координат окна TreeView.
 
Вторая неприятность заключается в том, что используемый в операциях Drag-and-Drop метод HitTest, возвращающий ссылку на объект, принимает в качестве параметров координаты x,y, которые должны быть оразмерены в твипах, VFP же возвращает в событии DragOver, DragDrop координаты указателя мыши, оразмеренные в пикселях.
 
Для начала разберемся со второй неприятностью - с твипами, применительно к графической среде MS Windows. Твип представляет собой экранонезависимую (screen-independent) едниницу, используемую для гарантии того, что размещение и пропорции экранных элеметов в ваших экранных приложениях были бы одинаковыми для всех отображающих систем. Твип представляет собой единицу экранного измерения равную 1/20 печатной точки. На один логический дюйм приходится примерно 1440 твипов или 567 твипов на логический сантиметр (длина элемента экрана, измеряемая одним дюймом или одним сантиметром при печати). В свою очеред пиксель (picture element), представляет собой точку, которая отображает наименьший графический элемент измерения на экране, то есть является экранозависимым(screen-dependent); то есть, размеры экранного элемента могут варьироваться для систем отображения и разрешения. (Более подробно можно почитать в статье Ron Gery "Coordinate Mapping")
 
Для работы с методом HitTest объекта TreeView необходимо получить коэффициенты соответствия твип/пиксель для обоих координат. Это можно сделать с помошью функции WinAPI GetDeviceCaps(), которая возвращает информацию о возможностях устройств. Нам нужно получить значения: LOGPIXELSX - число пикселей на логический дюйм относительно ширины экрана, и LOGPIXELSY - число пикселей на логический дюйм, относительно высоты экрана. Функция требует в качестве параметра указатель на контекст устройства - hdc, который можно получить другой функцией WinAPI GetDC(). (Контекст устройства - это просто структура, которая определяет набор графических объектов и связанных с ними аттрибутов). В нашем случае это контекст экрана или окна. В свою очередь GetDC() требует в качестве параметра указатель на графический объект. Его можно получить еще одной функцией WinAPI - GetActiveWindow(), которая уже не требует никаких параметров. Если в качестве параметра функции GetDC передать 0, то функция возвратит указатель для всего экрана. После вызова функции GetDC необходимо освободить контекс устройства с помощью функции ReleaseDC, которая принимает в качестве параметров указатель на окно и указатель на контекст
 
Нам потребуеются константы WinAPI. Для удобства работы мы определим их в файле заголовка, например, commonapi.h. для того, чтобы использовать и с другими формами, а затем подключим его к нашей тестовой форме.
#DEFINE WM_NULL           0
#DEFINE WM_SYSCOMMAND   274  && 0x112 for SendMassage
#DEFINE MOUSE_MOVE    61458  && 0xF012
#DEFINE SC_VSCROLL    61552  && 0xF070 for SendMassage
#DEFINE LOGPIXELSX       88  && Logical pixels/inch in X
#DEFINE LOGPIXELSY       90  && Logical pixels/inch in Y

#DEFINE WM_VSCROLL       277  && 0x0115 for SendMassage
#DEFINE SB_LINEUP          0
#DEFINE SB_LINELEFT        0
#DEFINE SB_LINEDOWN        1
#DEFINE SB_LINERIGHT       1
#DEFINE SB_PAGEUP          2
#DEFINE SB_PAGELEFT        2
#DEFINE SB_PAGEDOWN        3
#DEFINE SB_PAGERIGHT       3
#DEFINE SB_THUMBPOSITION   4
#DEFINE SB_THUMBTRACK      5
#DEFINE SB_TOP             6
#DEFINE SB_LEFT            6
#DEFINE SB_BOTTOM          7
#DEFINE SB_RIGHT           7
#DEFINE SB_ENDSCROLL       8
В тестовой форме используются несколько пользовательских свойств, среди них:
fvn_PTRatioX - для хранения отношения твипов на пиксель для координат X
fvn_PTRatioY - для хранения отношения твипов на пиксель для координат Y

 
В форму введен пользовательский метод udm_DLLDeclarations, который вызывается из кода метода, ассоциированного с событием Load формы. Текст кода для udm_DLLDeclarations приведен ниже:
Declare INTEGER SendMessage IN user32;
   INTEGER ihWnd,;
   INTEGER iMsg,;
   INTEGER iwParam,;
   INTEGER ilParam
Declare INTEGER ReleaseCapture IN user32
Declare INTEGER GetActiveWindow IN WIN32API
Declare INTEGER GetDC IN WIN32API INTEGER iHDC
Declare INTEGER ReleaseDC IN WIN32API INTEGER iHWnd, INTEGER iHDC
Declare INTEGER DeleteDC IN WIN32API INTEGER iHDC
Declare INTEGER GetDeviceCaps IN WIN32API INTEGER iHDC, INTEGER iIndex
В форму введен также пользовательский метод udm_GetPixelTwipRatio, который вызывается из кода метода, ассоциированного с событием Init формы. Текст кода для udm_GetPixelTwipRatio приведен ниже:
LOCAL lnHDC, lnHWnd
lnHWnd = GetActiveWindow()
lnHDC  = GetDC(lnHWnd)
This.fvn_PTRatioX=1440/GetDeviceCaps(lnHDC,LOGPIXELSX)
This.fvn_PTRatioY=1440/GetDeviceCaps(lnHDC,LOGPIXELSY)
=ReleaseDC(lnHWnd, lnHDC)
Думаю, что нет смысла подробно разбирать приведенный код, здесь должно быть все понятно.
 
Поскольку мы готовы "правильно" работать с методом HitTest TreeView, перейдем к первой неприятности и реальзуем прокрутку на основе рекоментации MicroSoft.
 
Сделать это можно благодаря тому, что TreeView "поселяется" на форме в своем собственном окне, указатель на который можно получить, обратившись к свойству hwnd компонента.
 
Итак:
в тестовой форме используются несколько пользовательских свойств, среди них:
fvn_MouseX - для временного хранения координаты X
fvn_MouseY - для временного хранения координаты Y
fvn_ScrollDirection = для временного хранения величины, определяющей направление прокрутки.
 
В тестовую форму введен таймер, для которого в методе, ассоциированным с событием Init определим значения свойств Enabled и Interval:
This.Enabled  =.F.
This.Interval =100
Далее, в методе, ассоциированным с событием Timer пропишем следующий код:
With This.Parent
   .ole_TreeView.DropHighlight = ;
      .ole_TreeView.HitTest(;
      .fvn_MouseX*.fvn_PTRatioX,;
      .fvn_MouseY*.fvn_PTRatioX;
      )
   Do CASE
      Case .fvn_ScrollDirection=-1
         =SendMessage(;
            .ole_TreeView.hwnd,WM_VSCROLL,SB_LINEUP,WM_NULL;
            )
      Case .fvn_ScrollDirection=1
         =SendMessage(;
            .ole_TreeView.hwnd,WM_VSCROLL,SB_LINEDOWN,WM_NULL;
            )
   Endcase
Endwith
Разберем немного приведенный код. Для того, чтобы при прокрутке окна TreeView узел, поверх которого перетаскивается узел-источник, мог выступать потенциальной целью операции Drag-and-Drop нужно передать ссылку на него свойству TreeView DropHighlight, для чего вызывается метод HitTest и ссылка на объект, находящийся под указателем мыши присваивается указанному свойству. Далее, в зависимости от значения переменной - пользовательского свойства формы fvn_ScrollDirection, окну TreeView посылается сообщение о прокрутке либо на одну линию вверх, либо на одну линию вниз. Направление прокрутки определяется в методе, ассоциированным с событием DragOver объекта TreeView, размещенного на форме. Код приведен ниже:
With THIS
   .DropHighlight = ;
      .HitTest(;
      nXCoord*.Parent.fvn_PTRatioX,;
      nYCoord*.Parent.fvn_PTRatioY;
      )
   .Parent.fvn_MouseX = nXCoord
   .Parent.fvn_MouseY = nYCoord

   Do CASE
      Case nYCoord > 0 .AND. nYCoord < (.Top+20)
          * Then scroll up
         .Parent.fvn_ScrollDirection = -1
         .Parent.Timer1.Enabled=.T.
      Case nYCoord > (.Top+.Height-20) AND nYCoord < .Height
          * Then scroll down
         .Parent.fvn_ScrollDirection = 1
         .Parent.Timer1.Enabled=.T.
      Otherwise
         .Parent.fvn_ScrollDirection = 0
         .Parent.Timer1.Enabled=.F.
   Endcase
Endwith
Аналогично действию, производимому в событии Timer для объекта Timer1, первой командой метода мы связываем свойство DropHighlight с объектом, над которым находится указатель мыши, путем вызова метода HitTest объекта TreeView. Далее, сохраняем в переменных-пользовательских свойствах формы fvn_MouseX и fvn_MouseY координаты указателя мыши, они нам понадобятся в другом методе.
 
Далее проверяем условие вхождения указателя мыши в регионы окна, определяемые как пространства шириной в 20 пикселей, считая от верхней и нижней кромок окна. Если указатель мыши вошел в "верхний" регион окна, устанавливаем значение переменной направления прокрутки равным минус 1 и разрешаем работу таймера - "включаем прокрутку вверх". Аналогично действия производятся и для "нижнего" региона, с той лишь разницей, что переменной направления прокрутки присваивается значение плюс 1. В случае, если указатель мыши не находится ни в одном из регионов, сбрасываем значение переменной направления в ноль и запрещаем работу таймера.
 
Вот собственно и все действия, необходимые для обеспечения прокрутки при операциях Drag-and-Drop.
 
Получение информации о всех дочерних узлов выбранного узла TreeView
Приведенный ниже пример возвращает перечень дочерних объектов одного уровня для выбранного узла в виде строки текстов меток, разделенных запятой. Этот код можно поместить во введенный пользовательский метод формы.
Local lcChildList,lnChildren, loChild
lcChildList=""
lnChildren=This.Parent.ole_TreeView.SelectedItem.Children
If lnChildren=0
   Return ""
Endif

loChild=This.Parent.ole_TreeView.SelectedItem.Child

*!*   Local lni
*!*   FOR lni=1 TO lnChildren
*!*      lcChildList=lcChildList+loChild.Text+","
*!*      loChild=loChild.Next
*!*   ENDFOR

Do WHILE !ISNULL(loChild)
   lcChildList=lcChildList+loChild.Text+","
   loChild=loChild.Next
Enddo

lcChildList=left(lcChildList,len(lcChildList)-1)
RETURN lcChildList
Пояснение к тексту кода.
Через обращение к свойству Children выбранного узла получаем информацию о количестве дочерних элементов одного уровня иерархии. Если выбранный узел не имеет дочерних элементов, обращение к свойству Children возвращает 0 и метод возвращает пустую строку. Если же дочерние элементы имеются, то локальному объекту loChild присваивается ссылка на объект узла первого дочернего элемента, который определяется как ole_TreeView.SelectedItem.Child. Для того, чтобы получить информацию о всех остальных дочерних элементов, используется свойство узла Next, которое возвращает ссылку на объект, являющийся следующим в иерархии объектов одного уровня. Последовательно присваивая локальному объекту узлы в иерархии добираемся до момента, когда следующий узел отсутствует. Если вам не нравится цикл Do WHILE, можно использовать цикл FOR, где в качестве верхней границы счетчика используется значение числа дочерних элементов, полученное обращением к свойству Children выбранного узла. Указанный цикл закоментирован в коде.
 
Если необходимо получить полную информацию о всех дочерних элементах выбранного узла, а именно, ключи, тексты меток и индексы, то можно переслать в метод массив (по ссылке) и присвоить элементам массива все требуемые параметры дочерних узлов.
 
Изменение BackColor для объекта TreeView
Для того, чтобы изменить цвет окна TreeView можно воспользоваться следующей функцией API:
 
Декларируем:
Declare INTEGER SendMessage IN user32;
   INTEGER ihWnd,;
   INTEGER iMsg,;
   INTEGER iwParam,;
   INTEGER ilParam
и используем:
SendMessage(ThisForm.Olecontrol1.hWnd, 4381, 0, RGB(255,255,0))
Cелектор для быстрого перехода на сайты, связанные с Visual FoxPro.