在Excel VBA中取消外部查询

时间:2009-11-10 22:05:32

标签: excel vba excel-vba

我创建了一个Excel电子表格,可帮助从Oracle数据库进行数据分析。

用户输入然后单击“刷新查询”按钮,该按钮生成Oracle要执行的查询。查询需要一分钟左右才能完成。虽然VBA代码不会挂起“.Refresh”,但所有Excel窗口都会保持冻结状态,直到查询完成。

Sub refreshQuery_click()
    Dim queryStr as String

    ' Validate parameters and generate query   
    ' ** Code not included **
    '

    ' Refresh Query
    With ActiveWorkbook.Connections("Connection").OLEDBConnection
        .CommandText = queryStr
        .Refresh
    End With
End Sub

用户在冻结Excel用户界面时是否有办法手动取消查询(调用.CancelRefresh)?

编辑我不知道以下是值得注意还是常规行为。查询正在执行时,所有打开的Excel窗口(包括VBA编辑器)在任务管理器中变为“无响应”。按Esc和Ctrl + Break都不会取消脚本。此外,调用DoEvents(在.Refresh之前或之后)不会改变此行为。

4 个答案:

答案 0 :(得分:4)

这是我知道可行的方法。但是,有一些并发症。

以下是它的完成方式:

  • 将包含数据的电子表格放在单独的工作簿中。此工作表应在打开时执行刷新查询,然后在数据更新后关闭。
  • 创建批处理文件以调用“数据”Excel文件。
  • 在不同的工作簿中,为用户创建一个过程(宏)以进行调用。此过程将调用批处理文件,该文件随后调用Excel文件。由于您正在调用批处理文件而不是直接调用Excel,因此Excel过程将继续,因为命令shell的释放速度非常快,并在另一个线程中打开另一个Excel文件。这允许您继续在主Excel文件中工作。

以下是一些并发症:

  • 我提供了一种方法来提醒用户数据已被更新。有时间问题,当工作簿无法访问时,可以尝试检查数据是否已更新,这会强制用户尝试更新值。我包含了一个名为my time的方法,它暂停代码的执行,因此它只检查每隔几秒钟。
  • 更新的工作表将在新窗口中弹出,因此用户需要单击其原始工作表并继续工作。如果你对Windows脚本感到满意,你可以学会隐藏它(我还没有学到这一点)。

以下是一些文件和代码。请务必阅读代码中的注释,了解为什么有些内容。

文件:C:\ DataUpdate.xls

我们将创建一个名为“DataUpdate.xls”的工作簿并将其放在我们的C:\文件夹中。在Sheet1的单元格A1中,我们将添加我们的QueryTable,它可以获取外部数据。

Option Explicit

Sub UpdateTable()

Dim ws As Worksheet
Dim qt As QueryTable

Set ws = Worksheets("Sheet1")
Set qt = ws.Range("A1").QueryTable

qt.Refresh BackgroundQuery:=False

End Sub

Sub OnWorkbookOpen()
Dim wb As Workbook
Set wb = ActiveWorkbook

'I put this If statement in so I can change the file's
'name and then edit the file without code
'running.  You may find a better way to do this.
If ActiveWorkbook.Name = "DataUpdate.xls" Then


    UpdateTable
    'I update a cell in a different sheet once the update is completed.
    'I'll check this cell from the "user workbook" to see when the data's been updated.
    Sheets("Sheet2").Range("A1").Value = "Update Table Completed    " & Now()
    wb.Save

    Application.Quit

End If


End Sub

在Excel中的ThisWorkbook对象中,有一个名为Workbook_Open()的过程。它应该如下所示,因此它在打开时执行更新代码。

Private Sub Workbook_Open()
OnWorkbookOpen

End Sub

注意:如果1)您从命令行或shell访问该文件并且2)您安装了Office Live Add-in,则在此文件关闭时发现了一个错误。如果安装了Office Live Add-in,则会在退出时抛出异常。

文件:C:\ RunExcel.bat

接下来,我们将创建一个批处理文件,打开我们刚刚制作的Excel文件。之所以从批处理文件中调用Excel文件而不是直接使用Shell调用其他Excel文件,是因为在其他应用程序关闭之前Shell不会继续(至少在使用Excel.exe "c:\File.xls"时)。但是,批处理文件运行其代码然后立即关闭,从而允许调用它的原始代码继续。这将使您的用途继续在Excel中工作。

所有这些文件需要的是:

cd "C:\Program Files\Microsoft Office\Office10\" 
Excel.exe "C:\DataUpdate.xls"

如果您使用Windows Scripting非常方便,那么您可以使用隐藏模式打开窗口或传递文件名或Excel位置的参数。我用批处理文件保持简单。

文件:C:\ UserWorkbook.xls

这是用户将打开以“完成工作”的文件。他们将调用代码来更新此工作簿中的其他工作簿,并且当这个工作簿正在更新时,他们仍然可以在此工作簿中工作。

您需要在此工作簿中使用一个单元格,您将从DataUpdate工作簿中检查“更新表已完成”单元格。我在Sheet1中选择了单元格G1作为我的例子。

将以下代码添加到此工作簿中的VBA模块:

Option Explicit

Sub UpdateOtherWorkbook()
Dim strFilePath As String
Dim intOpenMode As Integer
Dim strCallPath As String
Dim strCellValue As String
Dim strCellFormula As String
Dim ws As Worksheet
Dim rng As Range

Set ws = Worksheets("Sheet1")
Set rng = ws.Range("G1")
strCellFormula = "='C:\[DataUpdate.xls]Sheet2'!A1"
'This makes sure the formula has the most recent "Updated" value
'from the data file.
rng.Formula = strCellFormula

strFilePath = "C:\RunExcel.bat"
intOpenMode = vbHide

'This will call the batch file that calls the Excel file.
'Since the batch file executes it's code and then closes,
'the Excel file will be able to keep running.
Shell strFilePath, intOpenMode

'This method, defined below, will alert the user with a
'message box once the update is complete. We know that
'the update is complete because the "Updated" value will
'have changed in the Data workbook.
AlertWhenChanged

End Sub

Sub AlertWhenChanged()

Dim strCellValue As String
Dim strUpdatedCellValue As String
Dim strCellFormula As String
Dim ws As Worksheet
Dim rng As Range

Set ws = Worksheets("Sheet1")
Set rng = ws.Range("G1")
strCellFormula = "='C:\[DataUpdate.xls]Sheet2'!A1"

strCellValue = rng.Value
strUpdatedCellValue = strCellValue

'This will check every 4 seconds to see if the Update value of the
'Data workbook has been changed.  MyWait is included to make sure
'we don't try to access the Data file while it is inaccessible.
'During this entire process, the user is still able to work.
Do While strCellValue = strUpdatedCellValue
    MyWait 2
        rng.Formula = strCellFormula
    MyWait 2
        strUpdatedCellValue = rng.Value
    DoEvents
Loop

MsgBox "Data Has Been Updated!"

End Sub

Sub MyWait(lngSeconds As Long)
Dim dtmNewTime As Date

dtmNewTime = DateAdd("s", lngSeconds, Now)

Do While Now < dtmNewTime
    DoEvents
Loop


End Sub

正如您所看到的,我不断更新“Listening Cell”中的公式,以查看其他单元格何时更新。一旦更新了数据工作簿,我不确定如何在不重写所有单元格的情况下强制更新代码。关闭工作簿并重新打开它应该刷新值,但我不确定在代码中执行它的最佳方法。

整个过程有效,因为您使用批处理文件将Excel调用到与原始文件不同的线程中。这允许您在原始文件中工作,并在更新其他文件时仍然收到警报。

祝你好运!

答案 1 :(得分:1)

编辑:我没有在同一个答案中包含更完整的答案,而是创建了一个完全专注于该解决方案的单独答案。请在下方查看(如果投票,则在上面查看)

您的用户可以通过按键盘上的Ctrl + Break来中断VBA功能。但是,我发现这可能会导致函数随机中断,直到每次运行任何函数。重新启动计算机时它会消失。

如果你在一个新的Excel实例中打开这个文件(意思是,转到开始&gt;程序并从那里打开Excel),我认为唯一将被冻结的工作簿将是执行代码的工作簿。 Excel的其他内容不应受到影响。

最后,您可能会研究DoEvents函数,这会将执行返回到操作系统,以便它可以处理其他事件。我不确定它是否适合你的情况,但你可以调查一下。这样你就可以在流程完成时做其他事情(这有点危险,因为用户可以在流程运行时更改应用程序的状态)。


我相信我知道一种实际可行的方法,但它很复杂,我没有在我面前的代码。它涉及在代码中创建Excel应用程序的单独实例,并将处理程序附加到该实例的执行。您将代码的DoEvents部分包含在应用程序关闭后释放的循环中。另一个实例化的Excel应用程序的唯一目的是打开文件以执行脚本然后关闭它自己。我以前做过这样的事情所以我知道它有效。我明天是否可以找到代码并添加它。

答案 2 :(得分:0)

好吧,您可以考虑采用旧式方式 - 将查询拆分为较小的批次,并在批次之间使用Do Events

答案 3 :(得分:0)

您可以尝试XLLoop。这使您可以在外部服务器上编写Excel函数(UDfs)。它包括许多语言的服务器实现(例如Java,Ruby,Python,PHP)。

然后,您可以连接到您的oracle数据库(并可能添加一个缓存层)并以这种方式将数据提供给您的电子表格。

XLL还具有弹出“忙”GUI的功能,该GUI允许用户取消功能调用(与服务器断开连接)。

顺便说一句,我参与了这个项目,如果您有任何问题,请告诉我。