Cập nhật: Đối với một số lý do, mã trước đây tôi đã đăng đã chỉ làm việc khi các định dạng tập tin truy cập là năm 2000. Tôi đã xác nhận rằng mã mới này cũng làm việc với Access 2002 và 2010 tập tin.
Mã:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using VBA = Microsoft.Vbe.Interop;
namespace CaptureVBAErrorsTest
{
class CaptureVBAErrors
{
public void runApp(string databaseName, string function)
{
VBA.VBComponent f = null;
VBA.VBComponent f2 = null;
Microsoft.Office.Interop.Access.Application app = null;
object Missing = System.Reflection.Missing.Value;
Object tempObject = null;
try
{
app = new Microsoft.Office.Interop.Access.Application();
app.Visible = true;
app.OpenCurrentDatabase(databaseName, false, "");
//Step 1: Programatically create a new temporary class module in the target Access file, with which to call the target function in the Access database
//Create a Guid to append to the object name, so that in case the temporary class and module somehow get "stuck",
//the temp objects won't interfere with other objects each other (if there are multiples).
string tempGuid = Guid.NewGuid().ToString("N");
f = app.VBE.ActiveVBProject.VBComponents.Add(VBA.vbext_ComponentType.vbext_ct_ClassModule);
//We must set the Instancing to 2-PublicNotCreatable
f.Properties.Item("Instancing").Value = 2;
f.Name = "TEMP_CLASS_" + tempGuid;
f.CodeModule.AddFromString(
"Public Sub TempClassCall()\r\n" +
" Call " + function + "\r\n" +
"End Sub\r\n");
//Step 2: Append a new standard module to the target Access file, and create a public function to instantiate the class and return it.
f2 = app.VBE.ActiveVBProject.VBComponents.Add(VBA.vbext_ComponentType.vbext_ct_StdModule);
f2.Name = "TEMP_MODULE_" + tempGuid
f2.CodeModule.AddFromString(string.Format(
"Public Function instantiateTempClass_{0}() As Object\r\n" +
" Set instantiateTempClass_{0} = New TEMP_CLASS_{0}\r\n" +
"End Function"
,tempGuid));
//Step 3: Get a reference to a new TEMP_CLASS_* object
tempObject = app.Run("instantiateTempClass_" + tempGuid, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing, ref Missing);
//Step 4: Call the method on the TEMP_CLASS_* object.
Microsoft.VisualBasic.Interaction.CallByName(tempObject, "TempClassCall", Microsoft.VisualBasic.CallType.Method);
}
catch (COMException e)
{
MessageBox.Show("A VBA Exception occurred in file:" + e.Message);
}
catch (Exception e)
{
MessageBox.Show("A general exception has occurred: " + e.StackTrace.ToString());
}
finally
{
//Clean up
if (f != null)
{
app.VBE.ActiveVBProject.VBComponents.Remove(f);
Marshal.FinalReleaseComObject(f);
}
if (f2 != null)
{
app.VBE.ActiveVBProject.VBComponents.Remove(f2);
Marshal.FinalReleaseComObject(f2);
}
if (tempObject != null) Marshal.FinalReleaseComObject(tempObject);
if (app != null)
{
//Step 5: When you close the database, you call Application.Quit() with acQuitSaveNone, so none of the VBA code you just created gets saved.
app.Quit(Microsoft.Office.Interop.Access.AcQuitOption.acQuitSaveNone);
Marshal.FinalReleaseComObject(app);
}
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
}
}
Các chi tiết:
Theo the thread I had linked to bởi Mike Rosenblum, CallByName() có thể thực thi mã văn phòng trong C#, và có thể ngoại lệ VBA bẫy (Application.Run()
và Application.Eval()
dường như chỉ bị bắt sau khi người dùng tương tác với cửa sổ gỡ lỗi). Vấn đề là CallByName() yêu cầu một đối tượng [instantiated] để gọi một phương thức. Theo mặc định, Excel có đối tượng ThisWorkbook
, được khởi tạo khi mở sổ làm việc. Truy cập không có một đối tượng tương tự có thể truy cập, theo như tôi biết.
A subsequent post on the same thread đề xuất thêm mã động vào sổ làm việc Excel để cho phép gọi các phương thức trong mô-đun chuẩn. Làm như vậy trên ThisWorkbook
là tương đối tầm thường, bởi vì ThisWorkbook có một mã phía sau và được tự động khởi tạo. Nhưng làm thế nào chúng ta có thể làm điều này trong Access?
Các giải pháp kết hợp hai kỹ thuật trên theo cách sau:
- Programatically tạo ra một mô-đun lớp tạm thời mới trong file truy cập mục tiêu, mà để gọi hàm mục tiêu trong cơ sở dữ liệu Access. Lưu ý rằng thuộc tính
Instancing
của lớp phải được đặt thành 2 - PublicNotCreatable
. Điều này có nghĩa là lớp học không thể tạo ra bên ngoài dự án đó, nhưng nó có thể truy cập công khai.
- Nối một mô-đun chuẩn mới vào tệp Access đích và tạo một hàm công khai để khởi tạo lớp và trả về nó.
- Nhận tham chiếu đến đối tượng trong mã C# của bạn, bằng cách gọi mã VBA ở bước (2). Điều này có thể được thực hiện bằng cách sử dụng Application.Run của Access interop().
- Gọi phương thức trên đối tượng từ (3) bằng cách sử dụng CallByName-- gọi phương thức này trong mô-đun chuẩn và có thể được chuyển nhượng.
- Khi bạn đóng cơ sở dữ liệu, bạn gọi
Application.Quit()
với acQuitSaveNone
, vì vậy không có mã VBA nào bạn vừa tạo được lưu.
Để lấy mô tả lỗi VBA, sử dụng "e.Message", trong đó "e" là đối tượng COMException.
Hãy chắc chắn rằng bạn thêm các tài liệu tham khảo NET sau vào dự án # C:
Microsoft.Office.Interop.Access
Microsoft.Vbe.Interop
Microsoft.VisualBasic
Tôi đã thử một try/catch xung quanh phương pháp Eval, và nó dường như hoàn toàn bỏ qua nó. Bạn đang sử dụng Eval để gọi các hàm Excel của bạn? – transistor1
Không, tôi không. Tôi đã nghĩ rằng interop sẽ nắm bắt nó và bong bóng nó lên đến người tiêu dùng của nó. – squillman
Vì vậy, những gì bạn sử dụng bên trong phương pháp 'DoSomething()' của bạn để gọi các hàm VBA từ bên trong sổ làm việc Excel của bạn? Tôi hy vọng tôi đang thiếu một cái gì đó hiển nhiên! – transistor1