I am using the win32com (Python 2.7 (Anaconda) in LiClipse) to start separate instances of excel...
class ExcelDocument(object):
"""Excel class
"""
def __init__(self, xlDocIn, make_visible=False):
"""Open spreadsheet"""
self.excelApp = DispatchEx('Excel.Application')
I then do bits and bobs to the excel document (using MS Office 2013), including opening another document with DispatchEx...
objExcel1 = ExcelDocument(PATH_TABLE,False)
objExcel1.update_sheets()
...
objExcel2 = ExcelDocument(PATH_BACKG, False)
Trying to assign a second ExceLDocument class will crash the script.
If I change init to
def __init__(self, xlDocIn, make_visible=False):
"""Open spreadsheet"""
try:
self.excelApp = GetActiveObject('Excel.Application')
except:
self.excelApp = DispatchEx('Excel.Application')
the script runs fine.
If i have an excel file open when I run the script either
a. the script will crash upon editing the open file.
b. the open excel file will close when these lines of code are executed...
def close(self):
"""Close spreadsheet resources"""
self.excelApp.DisplayAlerts = True
self.workbook.Saved = 0
self.workbook.Close(SaveChanges=0)
self.excelApp.Visible = 0
Is there a reason I cannot call DispatchEx the second time, as my script should run in the background and not interrupt the any open excel files?
You should design your script assuming there can only be one instance of Excel running, although it can contain multiple workbooks. If you use GetObject (instead of GetActiveObject) then win32com will return a handle to the existing app if one exists, or start the app if it doesn't. So you won't need the if/else.
Also what this means in terms of design is that you should have a way of tracking which workbooks get opened, and close only those, so that the final state of the Excel application is the same as when you started script. So you would have one instance of ExcelDocument per workbook, each using GetObject, and each one closing the workbook that it represents. Before you create the first ExcelDocument, save GetActiveObject so your script knows if it should close the app on exit.
Basically:
activeXlApp = win32com.client.GetActiveObject('Excel.Application')
objExcel1 = ExcelDocument(PATH_TABLE,False) # uses GetObject()
objExcel1.update_sheets()
...
objExcel2 = ExcelDocument(PATH_BACKG, False)
if activeXlApp is not None:
activeXlApp.Close()
Related
I manage to open Outlook Elements like mails or task with this code:
import win32com.client as win32
outlook = win32.gencache.EnsureDispatch('Outlook.Application')
new_mail = outlook.CreateItem(4)
new_mail.Display(True)
And its possible to open Excel with this code:
excel = win32.gencache.EnsureDispatch('Excel.Application')
excel.Visible = True
But how can I just open the main window of outlook? Can't find any functions or numbers for the "createItem" to just open the normal outlook window.
Someone got ideas?
I tried to search throu the MS VBA Api but couldnt find any solution
Inspired by this, one option might be:
outlook.Session.GetDefaultFolder(6).Display()
where 6 corresponds to the inbox as documented here.
You need to display an Explorer window of Outlook. The Explorers.Add method creates a new instance of the explorer window, then you just need to call the Explorer.Display method which displays a new Explorer object for the folder. The Display method is supported for explorer and inspector windows for the sake of backward compatibility. To activate an explorer or inspector window, use the Activate method instead. Here is the sample VBA code which opens a new explorer window for the Drafts folder in Outlook:
Sub DisplayDrafts()
Dim myExplorers As Outlook.Explorers
Dim myOlExpl As Outlook.Explorer
Dim myFolder As Outlook.Folder
Set myExplorers = Application.Explorers
Set myFolder = Application.GetNamespace("MAPI").GetDefaultFolder(olFolderDrafts)
Set myOlExpl = myExplorers.Add(myFolder, olFolderDisplayNoNavigation)
myOlExpl.Display
End Sub
The Outlook object model is common for all programming languages, so I think you will find the sequence of property and method calls that should be used to get the job done.
To start Outlook application, try os.startfile("outlook") or see example
import psutil
import os
class Outlook:
#staticmethod
def is_outlook_running():
for p in psutil.process_iter(attrs=['pid', 'name']):
if p.info['name'] == "OUTLOOK.EXE":
print("Yes", p.info['name'], "is running")
break
else:
print("No, Outlook is not running")
os.startfile("outlook")
print("Outlook is starting now...")
if __name__ == "__main__":
outlook = Outlook()
outlook.is_outlook_running()
I have searched and cannot seem to find the answer to my issue. I’m hoping someone can help.
Here is a slimmed down version of my code. I have it within a unit test TestCase class. I open three books, one of which is an xlsm file (wb2), do some processing, and then save the wb2 file with another name and close all three workbooks.
What's happening is that all three workbooks are closing, but there still remains an instance of Excel open. Like, an empty shell with nothing in it. I searched and found the kill() method, but that is not killing it (whether I use self.xw.App().kill() or just xw.App().kill()). Also, I print out all of the open apps and it returns an empty list. So, I don't know what this instance remaining is all about and why it's not getting killed. BTW, I am using Excel 365. Thanks in advance.
import xlwings as xw
class TestClass(unittest.TestCase):
def test_xltest(self):
#Open 3 separate workbooks
self.wb1 = xw.Book(workbook1)
self.wb2 = xw.Book(workbook2)
self.wb3 = xw.Book(workbook3)
#Do some processing here
def tearDown(self):
self.wb1.close()
self.wb2.save(self.newDirectoryPath)
self.wb2.close()
self.wb3.close()
print (xw.apps)
self.xw.App().kill()
if __name__ == "__main__":
unittest.main()
I have a program that runs a method every five seconds. In this method, I require writing to the configuration .ini file and, because this is embedded software, it must be able to handle the system shutting down at unknown times. However, every time the system shuts down while the program is running, the .ini file is empty when the system starts up again.
Here is the code for the method being run:
def HandleBatteryMonitoring():
#some code before this...
systemConfig = ConfigObj('settings.ini')
systemConfig.filename = 'settings.ini'
systemConfig['systemsettings']['batterychargebuslsb'] = str(chargingBatteryMSB)
systemConfig['systemsettings']['batterychargebuslsb'] = str(chargingBatteryLSB)
systemConfig['systemsettings']['batterydischargebusmsb'] = str(dischargeBatteryMSB)
systemConfig['systemsettings']['batterydischargebuslsb'] = str(dischargeBatteryLSB)
systemConfig['systemsettings']['batterypercentage'] = str(batteryPercentage)
systemConfig.write()
Currently it is using ConfigObj, but only because the same problem happened with ConfigParser, and I was hoping a different library would help the problem. Here's the same code when it used ConfigParser:
def HandleBatteryMonitoring():
#some code before this...
systemConfig = configparser.ConfigParser('settings.ini')
systemConfig['systemsettings']['batterychargebuslsb'] = str(chargingBatteryMSB)
systemConfig['systemsettings']['batterychargebuslsb'] = str(chargingBatteryLSB)
systemConfig['systemsettings']['batterydischargebusmsb'] = str(dischargeBatteryMSB)
systemConfig['systemsettings']['batterydischargebuslsb'] = str(dischargeBatteryLSB)
systemConfig['systemsettings']['batterypercentage'] = str(batteryPercentage)
with open('settings.ini', 'w') as file:
systemConfig.write(file)
That method is called from here:
def OnHandleCharging():
while True:
HandleBatteryCharging()
time.sleep(5)
It should be noted that, while the program is running, the file is being written to correctly, and I can watch the values changing as the file changes. This only happens when the system is restarted during operation.
I need this config file to not be emptied upon restarting. Any solutions or workarounds for this would be greatly appreciated.
Try to open the file as 'r+' and just change the vaules, it is probably overwriting the file if you open it with 'w'.
I am running simulations in Python that generate output that need to be directly consumed by a modeler in their excel workbooks. I have generated code that will directly output my data into their excel spreadsheet template. The code I have generated to output the data directly to their template is fine, but the problem I am running into is that the modeler has a series of workbooks that are "linked" together. If I insert my data into their spreadsheet, the links to that workbook do no update unless the user physically opens the workbook to "Edit Links" -> "Update Values". If there was one workbook, then the user can simply open the workbook with no problem. In reality, there will be over 100 workbooks that need the links updated. Unfortunately, there is nothing I can do to change the modeler's approach in linking workbooks -- the only thing I can do is accommodate their approach.
My goal is to create a Python solution that will allow me to 1) Generate the simulated Data, 2) Insert my generated data into the modeler's workbook, and 3) Update all of the links between workbooks. Ultimately, in order to be streamlined, I want to be able to do all three in one end-to-end python program. I have solved (1) and (2), and I have a solution for (3) that almost works. I have generated the following functional script:
from win32com.client import Dispatch
import pandas as pd
from openpyxl import load_workbook
import os
import time
def run_macro(workbook_name, vba_sub, com_instance):
wb = com_instance.workbooks.open(workbook_name)
wb.RefreshAll()
xl_module = wb.VBProject.VBComponents.Add(1)
xl_module.CodeModule.AddFromString(vba_sub.strip())
com_instance.Application.Run('UpdateLinkValues')
wb.Save()
wb.Close()
return True
def main():
dir_root = ("C:\\Model_Spreadsheets")
vba_sub = \
'''
sub UpdateLinkValues()
Application.AskToUpdateLinks = False
ActiveWorkbook.UpdateLink Name:=ActiveWorkbook.LinkSources
end sub
'''
xl_app = Dispatch("Excel.Application")
xl_app.Visible = False
xl_app.DisplayAlerts = False
for root, dirs, files in os.walk(dir_root):
for fn in files:
if fn.endswith(".xlsx") and fn[0] is not "~":
run_macro(os.path.join(root, fn), vba_sub, xl_app)
xl_app.Quit()
if __name__ == "__main__":
main()
This script is really close to the correct solution I am looking for, but I run into a VBA error seemingly 'randomly':
run-time error '1004' method 'updatelink' method of object '_workbook' failed
This error does appear each time I try to run this script, but it does not occur for the same workbook each time -- sometimes, it occurs on the first workbook, sometimes on the 15th, etc...
I have an option to debug in VBA, and the only way that I can continue on to the next workbook is if I change the macro to
sub UpdateLinkValues()
Application.AskToUpdateLinks = False
end sub
if I run this macro and exit debug, the program will continue to run until it encounters the same error again. My first thought was that maybe there is a timing issue between me opening the workbook and trying to run the macro. A workaround that I have found is that I can change the macro and the app visibility:
vba_sub = \
'''
sub UpdateLinkValues()
Application.AskToUpdateLinks = False
end sub
'''
and
xl_app.Visible = True
This works fine, but I am not a fan of having each of the workbooks open and close because it takes a long time. My question is, does anyone know why this run-time error is coming up -- with a solution? Or perhaps, does anyone know how to intercept this run-time error in Python as an exception? If I can intercept this error as an exception in python, then I could use my alternative solution for those particulars workbooks.
Thanks in advance!
Consider having Python directly run the method UpdateLink with the COM objects you initialize, namely the xl_app and wb objects. No need to build a macro in each workbook and then call it.
Below UpdateLink() is wrapped in a try/except/finally block in case workbook has no links as LinkSources will return an Empty value, raising a COM exception, the very error you receive:
run-time error '1004' method 'updatelink' method of object '_workbook'
failed
Also be sure to uninitialize objects (a good best practice in VBA too: Set wb = Nothing) after use to free CPU resources else they remain as background processes until garbage collection.
def run_macro(workbook_name, com_instance):
wb = com_instance.workbooks.open(workbook_name)
com_instance.AskToUpdateLinks = False
try:
wb.UpdateLink(Name=wb.LinkSources())
except Exception as e:
print(e)
finally:
wb.Close(True)
wb = None
return True
def main():
dir_root = ("C:\\Model_Spreadsheets")
xl_app = Dispatch("Excel.Application")
xl_app.Visible = False
xl_app.DisplayAlerts = False
for root, dirs, files in os.walk(dir_root):
for fn in files:
if fn.endswith(".xlsx") and fn[0] is not "~":
run_macro(os.path.join(root, fn), xl_app)
xl_app.Quit()
xl = None
Aside - though VBA ships by default with Excel and MS Office applications, it is actually a separate component. To check, under Tools \ References in VBA IDE, you will see VBA is the first checked item, nothing built-in. In fact, VBA does exactly what you are doing in Python: making a COM interface to the Excel Object Library. So in a sense VBA is just as related to Excel and Python is!
I've a macro in LibreOffice BASIC and I want to run it from my python program. I've found some threads in which they use this code:
import os
import win32com.client
if os.path.exists("excelsheet.xlsm"):
xl=win32com.client.Dispatch("Excel.Application")
xl.Workbooks.Open(Filename="C:\Full Location\To\excelsheet.xlsm", ReadOnly=1)
xl.Application.Run("excelsheet.xlsm!modulename.macroname")
## xl.Application.Save() # if you want to save then uncomment this line and change delete the ", ReadOnly=1" part from the open function.
xl.Application.Quit() # Comment this out if your excel script closes
del xl
But this is for windows Excell program and I want for the LibreOffice program. Is it possible to do this?
Thanks :)
My preferred way is to put the python script in the Scripts/python subfolder of your LibreOffice user directory. Then add this function at the bottom:
def call_basic_macro():
document = XSCRIPTCONTEXT.getDocument()
frame = document.getCurrentController().getFrame()
ctx = XSCRIPTCONTEXT.getComponentContext()
dispatcher = ctx.ServiceManager.createInstanceWithContext(
'com.sun.star.frame.DispatchHelper', ctx)
url = document.getURL()
macro_call = ('macro:///Standard.Module1.Macro1("%s")' % url)
dispatcher.executeDispatch(frame, macro_call, "", 0, ())
g_exported_scripts=call_basic_macro,
Now run the python script from Writer by going to Tools -> Macros -> Run Macro. Expand My Macros and select the name of the script.
Another way which seems closer to your Excel example is to start a listening instance of LibreOffice with a system call:
start soffice -accept=socket,host=0,port=2002;urp;
I typically do that part in a shell script (batch file on Windows) rather than python. Then in python, get the document context from the instance:
import uno
localContext = uno.getComponentContext()
After that, the code would look similar to what is above. Note that with this approach on Windows, the python.exe included with LibreOffice must be used in order to load the uno module.
A third way is to simply do a system call:
soffice "macro:///Standard.Module1.Macro1()"
For more on this third approach, see https://forum.openoffice.org/en/forum/viewtopic.php?f=20&t=8232.