Adding an Excel Textbox with win32com - python

I am trying to add an Excel Textbox to a worksheet... the typical shortcut I use in the Excel GUI is Alt+N X and then click where I want the Textbox; however, I don't have access to the COM browser, which leaves me guessing where Microsoft hid the Textbox API under Python's win32com...
from win32com import client
excel=client.Dispatch("Excel.Application")
excel.Visible=True
book=excel.Workbooks.Open("c:/Users/dpennington/Desktop/Blank.xls", False,
True)
sheet=book.Worksheets(2)
How would I add a textbox (i.e. in the Excel GUI: Alt+N X), using Python's win32com api? (Specific positioning in the worksheet is up to you...)

Use the AddTextbox method of the Shapes object:
import win32com.client as client
xl = client.Dispatch("Excel.Application")
xl.Visible = True
wb = xl.Workbooks.Open("c:/1temp/badacres.xls")
ws = wb.Sheets(1)
tb = ws.Shapes.AddTextbox(1, 570, 45, 171, 80)
tb.TextFrame2.TextRange.Characters.Text = 'This is a great big test.'
You can find more on the AddTextbox method here.

Related

Update Links of Powerpoint using win32com (Python)

I have a PowerPoint slide which has a linked image (table) and the data for that image is in excel.
I am trying to open the PowerPoint but even after PPTApp.DisplayAlerts = False my script gets stuck due to Security pop up which asks me to update the external links.
Here is my code:
import win32com.client
PPTApp = win32com.client.Dispatch("PowerPoint.Application")
if PPTApp.Visible == False:
PPTApp.Visible = True
PPTApp.DisplayAlerts = False
#PPTApp.AskToUpdateLinks = False (this does not work)
PPTPresentation = PPTApp.Presentations.Open(r"C:\Daily_Data_Slide.pptx")
PPTPresentation.UpdateLinks = True
Just call UpdateLinks() instead of setting the attribute UpdateLinks to True, and remove the line where you are invoking alerts PPTApp.DisplayAlerts = False :
import win32com.client
PPTApp = win32com.client.Dispatch("PowerPoint.Application")
if not PPTApp.Visible:
PPTApp.Visible = True
PPTPresentation = PPTApp.Presentations.Open(r"C:\Daily_Data_Slide.pptx")
PPTPresentation.UpdateLinks()
This is what I was able to work so far related to your question on my own projects.
I will first disable the Automatic Update for liks (File->Info->Edit Liks to file) to avoid getting stuck at the Security pop up which asks to update the external links. We will be update by looping through the slides and refreshing each shape if applicable.
Code:
import win32com.client as win32
from datetime import datetime
import time
path="\\\\Servername\\share$\\folder\\"
pptfile = "filename.pptx"
pptfilename=pptfile[:pptfile.find('.')] #filename without extension
date= datetime.now().strftime('%#d-%b-%Y')
pptApp = win32.Dispatch("PowerPoint.Application")
pptApp.DisplayAlerts = 0
Presentation = pptApp.Presentations.Open(path+pptfile,0,0,0) # This hide the power point
# Then Search in all slides and if it is a Linked object (Type 10) then Refresh
for slide in Presentation.Slides:
for shape in slide.Shapes:
if shape.Type == 10:
shape.LinkFormat.Update()
Presentation.SaveAs(FileName=path+pptfilename+"("+date+").pptx") # Save a copy with the date
Presentation.Close()
pptApp.Quit()

Refresh Excel Chart External Data Link with Python

I am trying to update the external data link for a chart in Excel using python. The chart sits in workbook1.xlsm and the data it references to update itself sits in external_workbook.xlsx. The reason for the separation is the data has to be updated in workbook1.xlsm periodically using python, which erases the chart if it's in workbook1.xlsm.
I've looked at various solutions but none are working for me so far. The two solutions I've tried so far include (1) refreshing the workbook programmatically and (2) running a macro in the workbook to refresh it programmatically.
Code for (1):
import win32com.client as w3c
xlapp = w3c.gencache.EnsureDispatch('Excel.Application')
xlapp.Visible = 0
xlwb = xlapp.Workbooks.Open(r'{}\{}'.format(path, fname), False, True, None)
xlwb.RefreshAll() # Runs with no errors, but doesn't refresh
time.sleep(5)
xlwb.Save()
xlapp.Quit()
Code for (2):
# ***************** #
# Excel macro - I've verified the macro works when I have the worksheet open.
Sub Update_Links()
ActiveWorkbook.UpdateLink Name:=ActiveWorkbook.LinkSources
End Sub
# ***************** #
import win32com.client as w3c
xlapp = w3c.gencache.EnsureDispatch('Excel.Application')
xlapp.Visible = 0
xlwb = xlapp.Workbooks.Open(r'{}\{}'.format(path, fname), False, True, None)
xlwb.Application.Run("{}!Module1.Update_Links".format(fname)) # Runs with no errors, but doesn't refresh
xlwb.Save()
xlapp.Quit()
The series for my chart in Excel is
# External data link for Excel chart #
=SERIES(,'...path_to_external_file...[external_workbook.xlsx]Sheet1'!$A$2:$A$2000,
'...path_to_external_file...[external_workbook.xlsx]Sheet1'!$F$2:$F$2000,1)
Could anyone provide me with an alternative solution of how to make this work?
EDIT
So I tried something simpler to test this. I created a new sheet called temp in workbook1.xlsm and tried to write a random value to cell A1 using the code below. The temp sheet is still blank after running the code.
import win32com.client as w3c
import random
xlapp = w3c.gencache.EnsureDispatch('Excel.Application')
xlapp.Visible = 0
xlwb = xlapp.Workbooks.Open(r'{}\{}'.format(path, fname), False, True, None)
books = w3c.Dispatch(xlwb)
sheet_temp = books.Sheets('temp')
sheet_temp.Cells(1,1).Value = random.random()
xlwb.RefreshAll() # Runs with no errors, but doesn't refresh
time.sleep(5)
xlwb.Save()
xlapp.Quit()
I get no errors with the code and am following examples other people have posted online. Could someone point me to where I'm going wrong with this?
The answer is I needed to open the workbook the external_workbook.xlsx prior to updating the workbook1.xlsm, so the data could be refreshed.
The working code is as follows:
import win32com.client as w3c
import random
xlapp = w3c.gencache.EnsureDispatch('Excel.Application')
xlapp.Visible = 0
# ********************************* #
# New line that fixes it #
xlwb_data = xlapp.Workbooks.Open(r'{}\{}'.format(path, 'external_workbook.xlsx'), False, True, None)
# ********************************* #
xlwb = xlapp.Workbooks.Open(r'{}\{}'.format(path, 'workbook1.xlsm'), False, True, None)
books = w3c.Dispatch(xlwb)
sheet_temp = books.Sheets('temp')
sheet_temp.Cells(1,1).Value = random.random()
xlwb.RefreshAll() # Runs with no errors, but doesn't refresh
time.sleep(5)
xlwb.Save()
xlapp.Quit()

win32com Excel PasteSpecial

I'm having some trouble with PasteSpecial in python. Here's the sample code:
import win32com.client as win32com
from win32com.client import constants
xl = win32com.gencache.EnsureDispatch('Excel.Application')
xl.Visible = True
wb = xl.Workbooks.Add ()
Sheet1 = wb.Sheets("Sheet1")
# Fill in some summy formulas
for i in range(10):
Sheet1.Cells(i+1,1).Value = "=10*"+str(i+1)
Sheet1.Range("A1:A16").Copy()
Sheet1.Range("C1").Select()
Sheet1.PasteSpecial(Paste=constants.xlPasteValues)
I'm getting the following error:
TypeError: Paste() got an unexpected keyword argument 'Paste'
I know that paste is a keyword argument because of the MSDN here:
http://msdn.microsoft.com/en-us/library/office/ff839476(v=office.15).aspx
Any idea why it won't let me do this? Can't really find much on the web.
Edit for solution(s):
import win32com.client as win32com
from win32com.client import constants
xl = win32com.gencache.EnsureDispatch('Excel.Application')
xl.Visible = True
wb = xl.Workbooks.Add ()
Sheet1 = wb.Sheets("Sheet1")
# Fill in some summy formulas
for i in range(10):
Sheet1.Cells(i+1,1).Value = "=10*"+str(i+1)
Sheet1.Range("A1:A16").Copy()
Sheet1.Range("C1").PasteSpecial(Paste=constants.xlPasteValues)
# OR this I just found right after I posted this works as well:
xl.Selection.PasteSpecial(Paste=constants.xlPasteValues)
You can get value for xlPasteFormats by execute macro in Excel vb:
Sub Macro2()
Range("A7").Select
ActiveCell.FormulaR1C1 = xlPasteFormats
End Sub
The value for xlPasteFormats is -4122
In Python script you can use
xlSheet.Range("A7:H7").Copy()
xlSheet.Range("A%s:H%s"%(r,r)).PasteSpecial(Paste=-4122)
I don't work with python but to do a PasteSpecial in Excel-VBA, you have to mention the cell where you want to perform the pastespecial, so try like
Sheet1.Range("C1").PasteSpecial(Paste=constants.xlPasteValues)
If you want a simple paste then I guess this should work
Sheet1.Paste

AutoFilter method of Range class failed (Dispatch vs EnsureDispatch)

This code fails with error: "AutoFilter method of Range class failed"
from win32com.client.gencache import EnsureDispatch
excel = EnsureDispatch('Excel.Application')
excel.Visible = 1
workbook = excel.Workbooks.Add()
sheet = workbook.ActiveSheet
sheet.Cells(1, 1).Value = 'Hello world'
sheet.Columns.AutoFilter()
This code also fails although it used to work:
from win32com.client import Dispatch
excel = Dispatch('Excel.Application')
excel.Visible = 1
workbook = excel.Workbooks.Add()
sheet = excel.ActiveSheet
sheet.Cells(1, 1).Value = 'Hello world'
sheet.Columns.AutoFilter()
Python uses win32com to communicate directly with Windows applications, and can work with (via EnsureDispatch) or without (via Dispatch) prior knowledge of the application's API. When you call EnsureDispatch, the API is fetched and written into win32com.gen_py., thereby permanently adding the application's API into your Python library.
Once you've initialised an application with EnsureDispatch, any time that a script uses Dispatch for that application, it will be given the pre-fetched API. This is good, because you can then make use of the predefined application constants (from win32com.client import constants).
However, sometimes previously working code will break. For example, in the following code, AutoFilter() will work without an argument as long as the Excel API has never previously been cached in the library...
# ExcelAutoFilterTest1
# Works unless you ever previously called EnsureDispatch('Excel.Application')
from win32com.client import Dispatch
excel = Dispatch('Excel.Application')
excel.Visible = 1
workbook = excel.Workbooks.Add()
sheet = workbook.ActiveSheet
sheet.Cells(1, 1).Value = 'Hello world'
sheet.Columns.AutoFilter()
The following code will always fail because now the Excel API has been fetched and written to win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x7 in your Python library, it will no longer accept AutoFilter() without an argument.
# ExcelAutoFilterTest2
# Always fails with error: AutoFilter method of Range class failed
from win32com.client.gencache import EnsureDispatch
excel = EnsureDispatch('Excel.Application')
excel.Visible = 1
workbook = excel.Workbooks.Add()
sheet = workbook.ActiveSheet
sheet.Cells(1, 1).Value = 'Hello world'
sheet.Columns.AutoFilter()
The following code always works because we're now providing the VisibleDropDown argument (1=on, 0=off).
# ExcelAutoFilterTest3
# Always succeeds
from win32com.client.gencache import EnsureDispatch
excel = EnsureDispatch('Excel.Application')
excel.Visible = 1
workbook = excel.Workbooks.Add()
sheet = workbook.ActiveSheet
sheet.Cells(1, 1).Value = 'Hello world'
sheet.Columns.AutoFilter(1)
This seems to be a bug, because the Excel API documentation claims that all arguments to AutoFilter are optional:
"If you omit all the arguments, this method simply toggles the display
of the AutoFilter drop-down arrows in the specified range."

Problem using Python comtypes library to add a querytable to Excel

I'm trying to create a QueryTable in an excel spreadsheet using the Python comtypes library, but getting a rather uninformative error...
In vba (in a module within the workbook), the following code works fine:
Sub CreateQuery()
Dim con As ADODB.Connection
Dim rs As ADODB.Recordset
Dim ws As Worksheet
Dim qt As QueryTable
Set ws = ActiveWorkbook.Sheets(1)
Set con = New ADODB.Connection
con.Open ("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Path\to\Db.mdb;")
Set rs = New ADODB.Recordset
rs.Open "Select * from [tbl Base Data];", con
Set qt = ws.QueryTables.Add(rs, ws.Range("A1"))
qt.Refresh
End Sub
But the following Python code:
import sys
import comtypes.client as client
def create_querytable():
constring = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\Path\\to\\Db.mdb"
conn = client.CreateObject("ADODB.Connection", dynamic = True)
rs = client.CreateObject("ADODB.Recordset", dynamic = True)
SQL = "Select * from [tbl Base Data];"
conn.Open(constring)
rs.Open(SQL, conn)
excel = client.CreateObject("Excel.Application", dynamic = True)
excel.Visible = True
ws = excel.Workbooks.Add().Sheets(1)
qt = ws.QueryTables.Add(rs, ws.Range["A1"])
qt.Refresh()
rs.Close()
conn.Close()
Throws the unhelpful error message:
Traceback (most recent call last):
File "<pyshell#34>", line 1, in <module>
create_querytable()
File "C:/Documents and Settings/cvmne250/Desktop/temp.py", line 17, in create_querytable
qt = ws.QueryTables.Add(rs, ws.Range["A1"])
File "G:\ISA\SPSS\comtypes\lib\comtypes\client\lazybind.py", line 160, in caller
File "G:\ISA\SPSS\comtypes\lib\comtypes\automation.py", line 628, in _invoke
COMError: (-2147352567, 'Exception occurred.', (None, None, None, 0, None))
Any ideas on what's happening here?
Thanks!
I simplified your code and this should work fine (I'll explain the changes below):
def create_querytable2():
constring = "OLEDB;Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\path\to\db.mdb;"
SQL = "Select * from tblName;"
excel = client.CreateObject("Excel.Application", dynamic=True)
excel.Visible = True
ws = excel.Workbooks.Add().Worksheets(1)
ws.QueryTables.Add(constring, ws.Range["A1"], SQL).Refresh()
The QueryTables.Add() function can create the Connection and Recordset objects for you, so that simplifies a lot of things... you just need to add what type of connection it is in the conneciton string (the "OLEDB" part).
Letting Excel do most of the work seems to solve your problem :)
It looks like your error is on this line:
qt = ws.QueryTables.Add(rs, ws.Range["A1"])
I think your problem is that you are using python syntax to look up a value in a VBA Collection. Try changing your square brackets to parentheses.
i.e.
qt = ws.QueryTables.Add(rs, ws.Range("A1"))
The reason being that in VBA when you invoke a Collection like this, Range("A1"), you are actually calling it's default method, Range.Item("A1"). Basically, VBA Collections do not translate to python dictionaries.
I'm getting this from this forum thread, and my experience with VBA.
Edit due to comment:
Unfortunately, I've tried both: as
noted in your link, they sometimes
don't do the same thing, but my gut
feeling here is that the '[' is more
likely to be what I want. – mavnn
Do you know if comtypes.client.CreateObject works the same as win32com.client.Dispatch? You might try creating your com object with the win32com package and see if that makes a difference.

Categories

Resources