未绑定文本框撤销的SendKeys "{Esc}"替代方法

3
我有一个未绑定的文本框控件。用户输入一些有效的文本并离开该控件。然后,用户返回到该控件并输入一些无效的文本。我想向用户显示一条消息,然后将控件的值回滚到其先前的状态,并保持焦点在该控件中。
我尝试了以下方法,但没有一个能完全符合我的要求:
选项A:SendKeys
Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
    Cancel = DataIsInvalid(Me.MyTextBox.Value)
    If Cancel Then SendKeys "{Esc}"
End Sub    

这正是我想要的,但我真的想避免使用SendKeys。使用SendKeys会带来许多问题:Vista+兼容性,键被发送到不同的应用程序等。


选项B:控件的撤销方法

Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
    Cancel = DataIsInvalid(Me.MyTextBox.Value)
    If Cancel Then Me.MyTextBox.Undo
End Sub    

对于未绑定控件来说,这种方法根本不起作用(至少在MS Access 2002/XP上是如此)。该方法无法将MyTextBox的值恢复为有效输入。但是,它允许用户将焦点更改到新控件,同时保留Me.MyTextBox中的无效输入!难以置信!!!


选项C:表单的撤消方法

Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
    Cancel = DataIsInvalid(Me.MyTextBox.Value)
    If Cancel Then Me.Form.Undo
End Sub    

这里的“撤销”操作实际上没有任何作用,但至少它不会破坏BeforeUpdate事件中的Cancel=True代码并允许无效数据存在。
选项D:在BeforeUpdate事件中显式还原旧值。
Private mPrevMyTextBoxValue As Variant

Private Sub MyTextBox_AfterUpdate()
    mPrevMyTextBoxValue = Me.MyTextBox.Value
End Sub
Private Sub MyTextBox_BeforeUpdate(Cancel As Integer)
    Cancel = DataIsInvalid(Me.MyTextBox.Value)
    If Cancel Then Me.MyTextBox.Value = mPrevMyTextBoxValue
    'If Cancel Then Me.MyTextBox.Text = mPrevMyTextBoxValue
End Sub    

尝试将以前的值分配给文本框的.Value.Text属性均会导致相同的错误消息:

为该字段设置的BeforeUpdate或ValidationRule属性的宏或函数正在阻止{Program Name}保存字段中的数据。


选项E:在AfterUpdate事件中显式恢复旧值。
Private mPrevMyTextBoxValue As Variant

Private Sub MyTextBox_AfterUpdate()
    If DataIsInvalid(Me.MyTextBox.Value) Then
        Me.MyTextBox.Value = mPrevMyTextBoxValue
        Me.MyTextBox.SetFocus
    Else
        mPrevMyTextBoxValue = Me.MyTextBox.Value
    End If
End Sub

这非常接近期望的行为。 不幸的是,由于AfterUpdate事件在处理Tab / Enter键按下或鼠标单击事件之前运行,因此无法保持对控件的焦点。 因此,即使我们尝试通过上面的.SetFocus语句强制将焦点放在正确的控件上,程序也会立即将焦点转移到用户选择的控件。
在我看来,做到这一点的“正确”方法是使用控件的.Undo方法。 但是,对于未绑定的控件,这种方法不起作用。 这是一个难以理解的缺陷,特别是考虑到Escape键按下可以为未绑定字段执行此功能。
是否有更好的方法来做到这一点,还是我应该坚持使用SendKeys?

自动回滚到先前的值是个难题。你能否只通知用户输入的值无效,保持文本框的焦点,并允许用户选择输入新的有效值或按ESC键回滚到先前的值? - HansUp
选项D肯定是我的选择,但不幸的是,微软的解决方案是使用Sendkeys,如下所示:http://support.microsoft.com/kb/128195 - Newd
@HansUp: 这通常是我采用的方法。但在这种情况下,我真的很想在没有用户干预的情况下强制使用先前的值。 - mwolfe02
@HansUp:当然,用户即使看到简短明了的消息框也不会阅读。我曾经在用户身边观察,他们一遍又一遍地出现同样的错误。每次弹出一个消息框告诉他们如何解决错误,但他们每次都会关闭它而不去阅读,以便继续操作。最后,他们总是会转向我并问为什么不起作用。然后我会礼貌地告诉他们要放慢速度并阅读使用手册。 - mwolfe02
@Newd:感谢你提供的链接。它为只使用SendKeys提供了一些官方支持。 - mwolfe02
如果您想看到一些有趣的东西,请尝试选项E,但将焦点设置为另一个控件,然后再返回到您的文本框。我会在下面发布我的意思。 - Newd
2个回答

1
在谷歌搜索到第三页后,我决定随便试试选项E会发生什么样的排序。以下是我能够使用的最佳解决方法,以使焦点“停留”在相关的文本框上。
Private mPrevMyTextBoxValue As Variant

Private Sub Text0_AfterUpdate()
    If Me.Text0.Value = 0 Then
        Me.Text0.Value = mPrevMyTextBoxValue
        Me.Text12.SetFocus
        Me.Text0.SetFocus
    Else
        mPrevMyTextBoxValue = Me.Text0.Value
    End If
End Sub

0代表未通过验证。如果在将焦点重新设置到正在使用的文本框之前将其设置为其他内容,它似乎会一直停留在那里。很遗憾,我不知道原因。

编辑:我已经决定尝试按照自己的理论编程来解决这个问题。

Private blnGoToNextControl as Boolean

Private Function SetFocus(ctrl As control)

   blnGoToNextControl = True
   If ctrl.HasFocus = True Then 
    'Do nothing
   Else 
      ctrl.HasFocus = True 
      blnGoToNextControl = False
   End If
End Function 

这是我对SetFocus函数的工作原理的想法。因此,在AfterUpdate事件运行后,它将检查是否要前往下一个控件的标志,并且如果发现该标志被设置为false,则不会前往下一个控件。

显然这只是粗略的编码,但希望能够清楚地表达我的理论?


哇,这完全不是我预期会发生的。 - mwolfe02
我的理论是,当你设置焦点时,它会检查该控件是否已经拥有焦点。如果是,则简单地跳过请求。一旦跳过请求,"转到下一个选项卡顺序"的标志就保持为true。在函数执行完毕后,它会转到下一个选项卡。因此,当你离开并回来时,它会将标志设置为false,并将其保持在你刚刚设置焦点的控件上。我意识到这基本上只是一堆词,但希望我的解释有意义。 - Newd
是的,我的数字锁定键至少有一次关闭了。你为什么问? - mwolfe02
SendKeys... 当我第一次开始在Access编程时,花了我一个月的时间才发现我的数字键盘被SendKeys关闭了。我以为我疯了。 - Newd
1
Karl Peterson的SendKeys替代方案似乎没有NumLock问题。它还与Windows Vista及更高版本的操作系统兼容。我强烈推荐在你被迫使用SendKeys时使用它。 - mwolfe02
显示剩余5条评论

0
如果它是未绑定的,为什么不使用AfterUpdate事件呢?
Private Sub MyTextBox_AfterUpdate()
    If DataIsInvalid(Me!MyTextBox.Value) Then
        Me!MyTextBox.Value = Null
        Me!MyTextBox.SetFocus
    End If
End Sub   

或修改您的其他解决方案:

Private Sub MyTextBox_AfterUpdate()

    Static mPrevMyTextBoxValue As Variant

    If IsEmpty(mPrevMyTextBoxValue) Then
        mPrevMyTextBoxValue = Null
    End If

    If DataIsInvalid(Me!MyTextBox.Value) Then
        Me!MyTextBox.Value = mPrevMyTextBoxValue 
        Me!MyTextBox.SetFocus
    Else
        mPrevMyTextBoxValue = Me!MyTextBox.Value
    End If
End Sub

尝试将焦点从文本框移开,然后再移回:

        Me!MyTextBox.Value = mPrevMyTextBoxValue 
        Me!SomeOtherControl.SetFocus
        Me!MyTextBox.SetFocus

那个 someothercontrol 可以是一个微小几乎隐藏的文本框。


这是我问题中的“选项E”。它行不通,因为我们无法强制将焦点返回到MyTextBox控件。这是因为MS Access按照以下顺序处理相关事件和代码:用户按Tab / Enter键-> BeforeUpdate运行-> AfterUpdate运行-> 焦点转移到下一个控件。问题在于Access会在AfterUpdate过程运行之后将焦点设置为下一个控件。因此,虽然Access将遵守将焦点设置为MyTextBox控件的请求,但它随后会立即将焦点转移到用户在AfterUpdate之前请求的其他控件。明白了吗? - mwolfe02

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接