【问题标题】:VBA Drag Drop From TreeView to ListView & ListView to TreeView (ActiveX Controls)VBA 拖放从 TreeView 到 ListView & ListView 到 TreeView(ActiveX 控件)
【发布时间】:2016-04-06 05:48:04
【问题描述】:

试图仅将子节点从 ActiveX TreeView 控件拖动到 VBA for Excel 中的 ActiveX ListView 控件。它偶尔会起作用,但有问题。我无法始终触发拖动事件(有时有效,有时无效),或者在触发时确定选择添加到列表中的内容。

我的 TreeView 有以下节点

-US (tag='parent')
   -West (tag='parent')
       -CA (tag='child')
       -WA (tag='child')
   -East (tag='parent')
       -NY (tag='child')
       -FL (tag='child')

在上面,我只希望拖动在标记为“子”的节点上工作。我尝试的代码如下:

Dim MyTreeNode As Node
Dim MyText As String

Private Sub TreeView1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As stdole.OLE_XPOS_PIXELS, ByVal Y As stdole.OLE_YPOS_PIXELS)
    Dim MyDataObject As DataObject
    Dim Effect As Integer

    If Button = 1 Then
        'For some reason this executes multple times even though I'm only picking one node.
        Debug.Print TreeView1.SelectedItem.Text

        If InStr(1, TreeView1.SelectedItem.Tag, "Child") > 0 Then
            Set MyTreeNode = TreeView1.SelectedItem
            Set MyDataObject = New DataObject

            MyText = TreeView1.SelectedItem.Text
            MyDataObject.SetText MyText
            Effect = MyDataObject.StartDrag
        End If
    End If
End Sub

Private Sub ListView1_OLEDragDrop(Data As MSComctlLib.DataObject, Effect As Long, Button As Integer, Shift As Integer, X As Single, Y As Single)
    Dim MyListViewItem As ListItem
    Set MyListViewItem = ListView1.ListItems.Add(1, "M" & MyTreeNode.Key, MyTreeNode.Text)
End Sub

也尝试反向执行此操作,但从 TreeView 开始到 ListView

【问题讨论】:

    标签: vba listview drag-and-drop treeview activex


    【解决方案1】:

    哇!经过几天的玩耍和研究,我自己找到了答案。这里是为其他可能有同样问题的人准备的。

    首先,几个重要的注意事项:

    1)。您必须为 TreeView 和 ListView 设置以下 OLE 属性。

    TreeView1.OLEDragMode = 1  'Automatic 
    ListView1.OLEDropMode = 1  'Manual
    

    2)。为了从 TreeView 中确定选中的节点,必须在 MouseDown 事件期间使用 HitTest 方法。

    这导致了我的大部分问题,因为我无法让它为我提供正确的选定节点,然后知道哪些数据要添加到我的 ListView。

    要确定选定的节点,请使用 TreeView.SelectedItem 属性。奇怪的是,除非您在 MouseDown 事件期间设置它,否则 VB 将始终认为您先前选择的项目是当前选择的项目,并将错误的数据添加到 ListView。为什么?

    TreeView.SelectedItem 由 MouseUp 事件决定。例如,如果您在“节点 1”上完全单击并释放鼠标,则 MouseDown 和 MouseUp 事件都会触发,并且 MouseUp 事件会将 TreeView.SelectedItem 设置为“节点 1”。然后,如果您在“节点 2”上单击并按住鼠标按钮,然后立即开始拖动(不释放鼠标按钮),则只会触发 MouseDown 事件。由于 MouseUp 事件永远不会触发,因此即使您拖动“节点 2”,TreeView.SelectedItem 属性也会保持为“节点 1”。因此,当您稍后尝试使用 SelectedItem 属性来确定要添加到目标 ListView(在我的情况下)的内容时,它会得到错误的数据。

    3)。在 MouseDown 事件期间使用 HitTest 方法时,必须将像素转换为 TWIPS。

    MouseDown 方法返回以像素为单位的 x-y 坐标,但是,在 VBA 中,HitTest 方法使用 TWIPS(显然 .NET 现在使用像素,因此在那里不需要转换)。因此,为了确定正确的节点,您必须对其进行转换。我读到的几乎所有 Windows 计算机都有 15 比 1 的比例,因此您可以简单地使用以下内容:

    Set TreeView1.SelectedItem = TreeView1.HitTest(x * 15, y * 15)
    

    但是,如果您不想冒险让 15 比 1 的比率适用于所有 Windows 计算机,您可以使用我在下面演示的 Windows API 调用来计算它。

    这是代码的精简版。

    注意,我通过使用“自动”拖动属性和设置来保持简单,因此我不必使用“数据对象”方法来设置光标、确定拖动效果等...我是只使用默认值并保持简单。

    Private Sub TreeView1_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As stdole.OLE_XPOS_PIXELS, ByVal y As stdole.OLE_YPOS_PIXELS)
        Set TreeView1.SelectedItem = Nothing
        If TreeView1.SelectedItem Is Nothing Then
            Set TreeView1.SelectedItem = TreeView1.HitTest(x * 15, y * 15)
        End If
    End Sub
    
    Private Sub TreeView1_OLEStartDrag(Data As MSComctlLib.DataObject, AllowedEffects As Long)
        Data.SetData TreeView1.SelectedItem.Text, 1
    End Sub
    
    Private Sub ListView1_OLEDragDrop(Data As MSComctlLib.DataObject, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)
        ListView1.ListItems.Add ListView1.ListItems.Count + 1, , Data.GetData(1)
    End Sub
    

    就是这样!

    您应该能够从那里获取它以添加您想要的任何其他功能。下面,我给出了更多的选择。

    备选方案 1 - 赋予高光效果

    可以使用另一种方法为用户提供在选择之前突出显示树节点的视觉效果。 (注意:您也可以在 TreeView OLEDragOver 事件期间执行此操作,但我使用的是 MouseMove 事件)

    Private Sub TreeView1_MouseMove(ByVal Button As Integer, ByVal Shift As Integer, ByVal x As stdole.OLE_XPOS_PIXELS, ByVal y As stdole.OLE_YPOS_PIXELS)
        If Not (TreeView1.HitTest(x * TwipsPerPixelX, y * TwipsPerPixelY) Is Nothing) Then
            Dim MyNode As Node
            Set MyNode = TreeView1.HitTest(x * 15, y * 15)
            MyNode.Selected = True
            Set MyNode = Nothing
        End If
    End Sub
    
    Private Sub TreeView1_OLEStartDrag(Data As MSComctlLib.DataObject, AllowedEffects As Long)
        Data.SetData TreeView1.SelectedItem.Text, 1
    End Sub
    
    Private Sub ListView1_OLEDragDrop(Data As MSComctlLib.DataObject, Effect As Long, Button As Integer, Shift As Integer, x As Single, y As Single)
        ListView1.ListItems.Add ListView1.ListItems.Count + 1, , Data.GetData(1)
    End Sub
    

    备选方案 2 - 计算像素到 TWIPS 的转换

    请记住,这仅在 VBA 中需要。您不需要在 .NET 中执行此操作,因为我相信它在 Event 和 HitTest 方法中都使用像素。

    而不是像上面那样明确地将转换声明为 15:

    Set MyNode = TreeView1.HitTest(x * 15, y * 15)
    

    您可以结合使用 Windows API 调用和您自己的函数来计算它。方法如下。

    首先,将Windows API调用和用户定义函数放在Module1中:

    Public Declare Function GetDesktopWindow Lib "user32" () As Long
    Public Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
    Public Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As Long
    Public Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
    
    Const LOGPIXELSX = 88
    Const LOGPIXELSY = 90
    
    Public Function TwipsPerPixelX() As Integer
        Dim MyDesktopWindowHandle As Long, MyDesktopWindowDeviceContext As Long
        Dim MyWidthOfScreen As Long, MyUsedToReleaseDeviceContext As Long
       'Get the handle of the desktop window
        MyDesktopWindowHandle = GetDesktopWindow()
        'Get the desktop window's device context
        MyDesktopWindowDeviceContext = GetDC(MyDesktopWindowHandle)
        'Get the width of the screen
        MyWidthOfScreen = GetDeviceCaps(MyDesktopWindowDeviceContext, LOGPIXELSX)
        'Release the device context
        MyUsedToReleaseDeviceContext = ReleaseDC(MyDesktopWindowHandle, MyDesktopWindowDeviceContext)
    
        TwipsPerPixelX = 1440 / MyWidthOfScreen '1 inch is always 1440 twips
    End Function
    
    Public Function TwipsPerPixelY() As Integer
        Dim MyDesktopWindowHandle As Long, MyDesktopWindowDeviceContext As Long
        Dim MyHeightOfScreen As Long, MyUsedToReleaseDeviceContext As Long
    
        'Get the handle of the desktop window
        MyDesktopWindowHandle = GetDesktopWindow()
        'Get the desktop window's device context
        MyDesktopWindowDeviceContext = GetDC(MyDesktopWindowHandle)
        'Get the width of the screen
        MyHeightOfScreen = GetDeviceCaps(MyDesktopWindowDeviceContext, LOGPIXELSY)
        'Release the device context
        MyUsedToReleaseDeviceContext = ReleaseDC(MyDesktopWindowHandle, MyDesktopWindowDeviceContext)
    
        TwipsPerPixelY = 1440 / MyHeightOfScreen '1 inch is always 1440 twips
    End Function
    

    然后将代码的HitTest部分改成如下:

    Set TreeView1.SelectedItem = TreeView1.HitTest(x * TwipsPerPixelX, y * TwipsPerPixelY)
    

    希望有帮助!

    参考资料:

    以下是帮助将其拼凑起来的参考资料,我必须在应得的地方给予表扬。

    Creating a 'mouse over' effect on a VB TreeView node

    http://forums.ni.com/t5/facebookforums/facebooksingletopicpage/facebook-app/417075545007603/message-uid/78682/tab/board/page/4806

    http://vbcity.com/forums/t/49091.aspx

    http://www.experts-exchange.com/questions/20497792/TwipsPerPixelX-Y-via-the-API-for-VBA.html

    【讨论】:

    • 接受这个作为答案,你付出了很多努力。
    • 不仅是努力,而且是唯一有效的解决方案 -> 如果用户有显示缩放系数,“替代 2”中的用户定义函数对于 VBA 中的这个“错误”至关重要大于 100%。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-26
    • 2013-05-25
    • 1970-01-01
    • 2010-09-22
    • 2018-07-05
    • 1970-01-01
    • 2017-07-30
    相关资源
    最近更新 更多