Python code for running ms-Access Module Subroutine

后端 未结 2 1242
Happy的楠姐
Happy的楠姐 2020-12-10 19:58

I am very new to programming and this is my first question on stackoverflow. I am trying to make python open an .accdb file and run a subroutine which is already defined in

相关标签:
2条回答
  • 2020-12-10 20:04

    I am by no means an expert in Python but have reasonable familiarity with Access and Excel VBA. If I knew Python better and earlier in my amateur programming career I would never have attempted to do what I set out to do below using any VBA code at all. Given the investment in time in VBA code I had to find a way....

    I have spent the last several days/week trying to find a way to talk Python->Access VBA with the following requirements:

    • call Access VBA function from Python
    • send parameter to Access VBA function from Python
    • return value from Access VBA function to Python

    I made attempts with clr using pythonnet and IronPython and found more and more confusing and unclear error messages and exceptions. I tried the above suggested DoCmd.RunMacro method but Access macros do not return values. Tried a new(ish) version of Access macros (2010) called data macros which do have an action called SetReturnVar but they do not talk to VBA unless through the more traditional macros and as I mentioned above, traditional macros do not return values. Today I read some Microsoft documentation (Access Application.Run method) more carefully:

    Access Application.Run method

    I am not completely aware of the implications of that statement "you can't set a reference to an individual Microsoft Access database from any application other than Microsoft Access" but it occurred to me that a lot of the Python<->Office Application articles seemed more successful when talking Python<->Excel VBA. I reasoned that because I had been able to run Excel VBA <-> Access VBA in the past and if Python <-> Excel VBA worked as well as I had read, then a solution (albeit convoluted) seemed possible (I think the proper programmers call it a hack).

    After about 1 1/2 hours of cutting/pasting code snippets and debugging:

    Python

    from win32com.client import Dispatch
    
    FILELOC = r'C:\Users\Desktop\PyExcel.xlsm'
    PROGNAME='Excel.Application'
    num = 4
    
    #open excel workbook containing VBA code
    #...could do more to ensure excel isn't already running
    xl = Dispatch(PROGNAME)
    xl.Visible = True
    
    #open excel file containing the VBA code
    #...could do more to check if file is already open, etc 
    xl.Workbooks.Open(Filename=FILELOC)
    #call to VBA code within excel
    rtrn_int = xl.Run("RunCOMObject", num)
    
    #print return value
    print(rtrn_int)
    
    #Quit excel-this doesn't work very well and there are articles about
    #Python or the COM object not being able to actually remove Excel
    #from the task manager
    xl.Quit()
    

    Excel VBA

    Option Explicit
    
    Private Const ACCESS_FILELOC As String = "C:\Users\Desktop\Test.accdb"
    Private Const TEMP_FILELOC As String = "C:\Users\Desktop\TestTemp.accdb"
    
    Function RunCOMObject(intNum As Integer) As Integer
    
        Dim objAcc As Object, objProject As Object
        Dim accAppl As Access.Application
        Dim MyAppl As String
    
        MyAppl = "Access.Application"
    
        If Not IsRunning(MyAppl) Then  'Access not running, simply start 
              'up Access and open file
            Set accAppl = CreateObject(MyAppl) 'start Access
            accAppl.Visible = True
            accAppl.OpenCurrentDatabase (ACCESS_FILELOC) 'open file
        Else:  'Access is running
            On Error Resume Next
            Set accAppl = GetObject(, MyAppl)  'assign the running application 
                'to a variable
            On Error GoTo Err_File_Open 'use an error in attempting to rename 
                'the database of interest to determine if the open file is the 
                'desired file
            Name ACCESS_FILELOC As TEMP_FILELOC 'rename the file of interest
            Name TEMP_FILELOC As ACCESS_FILELOC 'file was successfully renamed 
                'therefore not open
            Call NoFileOrOther(accAppl, MyAppl)
        End If
    
    Err_File_Open:
        'Required Access file is open
        RunCOMObject = accAppl.Run("TestLink", intNum) 'run the VBA function in 
           'Access
        accAppl.CloseCurrentDatabase  'close database
        accAppl.Quit 'quit Access
        Set accAppl = Nothing
    
    End Function
    
    
    Function IsRunning(ByVal MyAppl As String) As Boolean
    
        Dim applRef As Object
    
        On Error Resume Next 'error occurs if GetObject is unable to find a 
           'running version of the application
    
        Set applRef = GetObject(, MyAppl) 'attempt to obtain the required 
           'application object
        If Not applRef Is Nothing Then  'if application is already running
            Set applRef = Nothing
            IsRunning = True
        Else  'application is not running
            IsRunning = False
        End If
        Set applRef = Nothing
    End Function
    
    Sub NoFileOrOther(accAppl As Access.Application, MyAppl As String)
    
        On Error GoTo Err_No_FileOpen
        If accAppl.CurrentProject.Name <> "" Then  'Access active with another a 
           'different file open
            Set accAppl = CreateObject(MyAppl) 'start a new instance of Access
            accAppl.Visible = True
            accAppl.OpenCurrentDatabase (ACCESS_FILELOC) 'open file
        End If
    
        Exit Sub
    
    Err_No_FileOpen:
        accAppl.OpenCurrentDatabase (ACCESS_FILELOC)  'in the event of Access 
           'being active without a database open
    
    End Sub
    

    Access VBA

    This is a completely trivial example and belies the amount of code I had already written in Access VBA to warrant this workaround but it serves to demonstrate the methodology

    Option Compare Database
    Option Explicit
    
    Function TestLink(intNum As Integer) As Integer
        TestLink = intNum + 10
    End Function
    

    Python Output:

    14

    Success!!!! The number was initially 4 in Python and was sent as a parameter to Excel and Access where it had 10 added to it before being returned via Excel to Python print(rtrn_int) = 14.

    If anyone knows how to definitively (ie through similar rigour demonstrated above) send arguments from Python -> Access VBA and return a value to Python without using Excel VBA as an intermediary I would be very happy to hear from you. Alternatively methods using pythonnet referencing clr would be similarly appreciated.

    0 讨论(0)
  • 2020-12-10 20:11

    Consider creating a new Access macro object with a RunCode action that calls the function in the module. Then, call the macro in Python's Windows COM API using use the DoCmd.RunMacro method.

    MACRO

    Macro
    RunCode: TestMe()
    

    NOTE: Only functions can be referenced with RunCode not subroutines unless you create a VBA module function that calls the subroutine: Call SubroutineName:

    Python

    import win32com.client
    ac = win32com.client.Dispatch("Access.Application")
    ac.Visible=True
    ac.OpenCurrentDatabase("<mydirectory>\\testdb.accdb")
    ac.DoCmd.RunMacro('MacroName')
    
    ac.DoCmd.CloseDatabase
    ac = None
    
    0 讨论(0)
提交回复
热议问题