如何访问 Access 中的选定行?

Posted

技术标签:

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

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

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

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

谢谢!

【问题讨论】:

【参考方案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

【讨论】:

忘了提一下:我的场景涉及选择源查询不可更新的表单中的记录,用户需要删除记录;不是子表单。

以上是关于如何访问 Access 中的选定行?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Access 的子窗体上不显示选定的记录?

如何限制对 Access 中某些列的访问

从 Access 中的列表框中提取选定的行

如何仅使用选定的列从Access数据库填充我的DataGridView?

Access 中的简单导入表单

如何在不使用 SQL Server/MS Access 的所有选定列的情况下进行分组和求和?