【问题标题】:How do I access the selected rows in Access?如何访问 Access 中的选定行?
【发布时间】:2009-11-03 20:27:57
【问题描述】:

我有一个包含数据表的表格。我想让用户可以选择多行,单击一个按钮并运行一些 sql 查询并对这些行执行一些工作。

查看我的 VBA 代码,我了解如何使用 CurrentRecord 属性访问最后选择的记录。但是我不知道如何知道在多项选择中选择了哪些行。 (我希望我很清楚......)

这样做的标准方法是什么?访问 VBA 文档在网上有些晦涩...

谢谢!

【问题讨论】:

    标签: ms-access vba selection


    【解决方案1】:

    这是执行此操作的代码,但有一个问题。

    Private Sub Command1_Click()
         Dim i As Long
         Dim RS As Recordset
         Dim F As Form
    
         Set F = Me.sf.Form
         Set RS = F.RecordsetClone
    
         If F.SelHeight = 0 Then Exit Sub
    
         ' Move to the first selected record.
         RS.Move F.SelTop - 1
    
         For i = 1 To F.SelHeight
           MsgBox RS![myfield]
           RS.MoveNext
         Next i
    
    End Sub
    

    关键是: 如果将代码添加到按钮,一旦用户单击该按钮,网格中的选择就会丢失(selheight 将为零)。因此,您需要捕获该信息并将其与计时器或表单上的其他事件一起保存到模块级变量中。

    这里有一篇文章详细描述了如何解决问题。
    http://www.mvps.org/access/forms/frm0033.htm

    Catch 2:这仅适用于连续选择。他们不能在网格中选择多个不连续的行。

    更新:
    可能有一个更好的事件来捕获它,但这是一个使用我测试过的 form.timerinterval 属性的工作实现(至少在 Access 2k3 中,但 2k7 应该可以正常工作)

    这段代码在SUBFORM中,使用属性获取主表单中的selheight值。

    Public m_save_selheight As Integer
    
    Public Property Get save_selheight() As Integer
        save_selheight = m_save_selheight
    End Property
    
    Private Sub Form_Open(Cancel As Integer)
        Me.TimerInterval = 500
    End Sub
    
    Private Sub Form_Timer()
        m_save_selheight = Me.selheight
    End Sub
    

    【讨论】:

    • 太棒了...这就是为什么我的 selheight 一直在 0... 非常感谢,我会立即尝试并接受您的回答,如果成功的话!
    • 诀窍是在失去焦点之前抓住价值。在什么情况下,您要获取 selheight 值?您是否在子表单中的计时器中尝试过?尝试检查即时窗口中的值(ctrl-g),看看问题是否是失去焦点后选择重置。
    • 每次用户点击要选择的项目时是否注册一个事件?如果是这样,您可以使用该事件来捕获用户每次单击时选择的所有内容。当然,这是重复的,但它似乎可以保证当焦点更改为按钮控件时,您始终拥有最新的选定项目列表。
    • 我在答案中添加了一些代码,显示如何使用 TimerInterval 事件执行此操作。可能会有更好的事件与更改选择更密切相关,但我不确定它是什么。我尝试了 SelectionChange(这似乎是最合适的,但它也对我不起作用)。您可能只想尝试将代码移动到看起来很有希望的事件中,直到获得好的结果。在最坏的情况下,timerinterval 方法确实有效。
    • 可以使用标签作为按钮,这样选中的记录在点击标签时不会失去焦点,返回正确的SelHeight数字
    【解决方案2】:

    我使用了类似于 JohnFx 的技术

    为了在选择高度消失之前捕获它,我在主窗体中使用了子窗体控件的 Exit 事件。

    所以在主窗体中:

    Private Sub MySubForm_Exit(Cancel As Integer)
    
      With MySubForm.Form
        m_SelNumRecs = .SelHeight
        m_SelTopRec = .SelTop
        m_CurrentRec = .CurrentRecord
      End With
    
    End Sub
    

    【讨论】:

    • 这比表单Timer事件优雅高效多了!如果有必要的代码在 SubForm 上运行,则可以在同一 Exit 事件处理程序中从父窗体调用 SubForm 的公共方法:MySubForm.Form.PublicMethod。 SelHeight 和 SelTop 值在该方法执行期间仍然有效。这种模式的好处是因为工作代码在 SubForm 模块中,如果 SubForm 位于多个父窗体上,它将是可重用的......因此父窗体上的重复代码最少。
    • .CurrentRecord 只是一个数字。您如何获得该记录的实际字段值?
    【解决方案3】:

    我之前尝试过这样做,但我从未成功使用需要用户选择与 Windows 文件对话框相同样式的多行的方法(按 Ctrl、Shift 等) .

    我使用的一种方法是使用两个列表框。用户可以双击左侧列表框中的某个项目,或者当一个项目被选中时单击一个按钮,它将移动到右侧列表框中。

    另一种选择是使用一个本地表,该表填充了您的源数据以及在子表单中表示为复选框的布尔值。在用户通过单击复选框选择他们想要的数据后,用户按下一个按钮(或其他一些事件),此时您直接转到基础数据表并仅查询那些被选中的行。我认为这个选项是最好的,虽然它需要一点代码才能正常工作。

    即使在 Access 中,我发现有时直接使用表和查询比尝试使用 Access 表单中的内置工具更容易。有时内置工具并不能完全满足您的需求。

    【讨论】:

    • +1 作为您的第二个选项,但如果可能的话,我宁愿不这样做......希望微软想出了一种晦涩难懂的方式来访问那些选定的行......?
    • 您使用的是哪个版本的 Access?我在工作中使用 Access 2002。有趣的是,我刚刚翻阅了“Access 2002 Developer's Handbook”(考虑为 Access Developer's Bible),他们创建了一个名为 MultiPik 的类。这个类确实允许一次选择多个项目,但是当你有大量项目时他们说它很慢(他们说 300 个项目很慢)。如果您有兴趣,我可以查看在 Stack Overflow 上发布此类代码的合法性。告诉我。
    • 好吧,我使用的是 2007 年,我的表有超过 10k 的条目,所以可能无法正常工作..
    • 看到 JohnFx 的回答中的 Catch 2 可能会阻止您的解决方案工作,您是否希望我向您展示如何从上面的解决方案中选择 2?输入代码需要我一点时间,所以如果你想看的话,我很乐意这样做。
    • 鉴于子表单控件中的选择不能是非连续的,我认为临时表中的多选列表框或布尔复选框更可取。我自己总是使用后者。它真的很容易实现并且用户理解它是如何工作的(他们对多选列表框不太满意,即使是“简单”版本,这对于选择多个项目来说是一种痛苦(即,SHIFT 键不起作用) .
    【解决方案4】:

    子表单失去焦点时选择丢失的解决方法是将选择保存在退出事件中(正如其他人已经提到的)。

    一个很好的补充是立即恢复它,使用计时器,这样用户仍然能够看到他所做的选择。

    注意:如果您想在按钮处理程序中使用选择,则选择可能在执行时尚未恢复。确保使用变量中保存的值或在按钮处理程序的开头添加一个 DoEvents 以让计时器处理程序首先执行。

    Dim m_iOperSelLeft As Integer
    Dim m_iSelTop As Integer
    Dim m_iSelWidth As Integer
    Dim m_iSelHeight As Integer
    
    Private Sub MySubForm_Exit(Cancel As Integer)
    
        m_iSelLeft = MySubForm.Form.SelLeft
        m_iSelTop = MySubForm.Form.SelTop
        m_iSelWidth = MySubForm.Form.SelWidth
        m_iSelHeight = MySubForm.Form.SelHeight
    
        TimerInterval = 1
    
    End Sub
    
    Private Sub Form_Timer()
    
        TimerInterval = 0
    
        MySubForm.Form.SelLeft = m_iSelLeft - 1
        MySubForm.Form.SelTop = m_iSelTop
        MySubForm.Form.SelWidth = m_iSelWidth
        MySubForm.Form.SelHeight = m_iSelHeight
    
    End Sub
    

    【讨论】:

      【解决方案5】:

      还有另一种解决方案。

      只要您松开鼠标按钮,下面的代码就会显示选定的行数。 保存这个值就可以了。

      Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
      
              MsgBox Me.SelHeight
      
      End Sub
      

      【讨论】:

      • 嘿,你可能想修正你的代码格式,开头和结尾没有缩进。
      • 这很好用,但仅限于鼠标操作。键盘也可用于选择多个列和行,但这不会捕获这些。遗憾的是,KeyPress 和 KeyUp 事件在数据表中导航时似乎没有捕获控制键和箭头键。其他发布的涉及父退出事件的解决方案更通用,不依赖于选择方法。
      【解决方案6】:

      在表单中使用全局变量,然后在按钮代码中引用。

      Dim g_numSelectedRecords as long
      
      Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
         g_numSelectedRecords = Me.SelHeight
      End Sub
      
      
      Dim formRecords As DAO.Recordset
      Dim i As Long
      
      Set formRecords = Me.RecordsetClone
      
      ' Move to the first record in the recordset.
      formRecords.MoveFirst
      
      ' Move to the first selected record.
      formRecords.Move Me.SelTop - 1
      
      For i = 1 To numSelectedRecords
          formRecords.Edit
          formRecords.Fields("Archived") = True
          formRecords.Update
          formRecords.MoveNext
      Next i
      

      【讨论】:

        【解决方案7】:

        为什么不使用数组或记录集,然后每次用户单击一行时(无论是否连续,将该行或某个标识符保存到记录集中。然后当他们单击父窗体上的按钮时,只需迭代保存的记录集以执行您想要的操作。只是不要忘记在单击按钮后清除数组或记录集。?

        【讨论】:

          【解决方案8】:

          在尝试执行过程时保持选择的另一种解决方法 - 无需离开数据表来激活按钮,只需使用 OnKeyDown 事件并定义特定的键码和 shift 组合来执行您的代码。

          【讨论】:

            【解决方案9】:

            JohnFx 提供的代码运行良好。我以这种方式在没有计时器的情况下实现了它(MS-Access 2003):
            1- 将表单的键预览设置为是
            2-将代码放入函数中
            3-设置事件 OnKeyUp 和 OnMouseUp 来调用函数。

            Option Compare Database
            Option Explicit
            
            Dim rowSelected() As String
            
            Private Sub Form_Load()
            'initialize array
            ReDim rowSelected(0, 2)
            End Sub
            
            Private Sub Form_Current()
            ' if cursor place on a different record after a selection was made 
            ' the selection is no longer valid
            If "" <> rowSelected(0, 2) Then
              If Me.Recordset.AbsolutePosition <> rowSelected(0, 2) Then
                rowSelected(0, 0) = ""
                rowSelected(0, 1) = ""
                rowSelected(0, 2) = ""
              End If
            End If
            End Sub
            
            Private Sub Form_KeyUp(KeyCode As Integer, Shift As Integer)
            rowsSelected
            If KeyCode = vbKeyDelete And Me.SelHeight > 0 Then
                removeRows
            End If
            End Sub
            
            Private Sub Form_MouseUp(Button As Integer, Shift As Integer, X As Single, Y As Single)
            rowsSelected
            End Sub
            
            Sub rowsSelected()
            Dim i As Long, rs As DAO.Recordset, selH As Long, selT As Long
            selH = Me.SelHeight
            selT = Me.SelTop - 1
            If selH = 0 Then
                ReDim rowSelected(0, 2)
                Exit Sub
            Else
                ReDim rowSelected(selH, 2)
                rowSelected(0, 0) = selT
                rowSelected(0, 1) = selH
                rowSelected(0, 2) = Me.Recordset.AbsolutePosition ' for repositioning 
                Set rs = Me.RecordsetClone
                rs.MoveFirst ' other key touched caused the pointer to shift
                rs.Move selT
                For i = 1 To selH
                    rowSelected(i, 0) = rs!PositionNumber
                    rowSelected(i, 1) = Nz(rs!CurrentMbr)
                    rowSelected(i, 2) = Nz(rs!FutureMbr)
                    rs.MoveNext
                Next
                Set rs = Nothing
                Debug.Print selH & " rows selected starting at " & selT 
            End If
            End Sub
            
            Sub removeRows()
            ' remove rows in underlying table using collected criteria in rowSelected()
                Me.Requery
            ' reposition cursor
            End Sub
            
            Private Sub cmdRemRows_Click()
            If Val(rowSelected(0, 1)) > 0 Then
                removeRows
            Else
                MsgBox "To remove row(s) select one or more sequential records using the record selector on the left side."
            End If
            End Sub
            

            【讨论】:

            • 忘了提一下:我的场景涉及选择源查询不可更新的表单中的记录,用户需要删除记录;不是子表单。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2015-05-05
            • 1970-01-01
            • 2017-08-02
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多