Edit xlsm file containing macro and activex objects using python - python

The final goal is to append data into an xlsm file which is containing vba script and a activex listbox. Using python/pandas is preferred. For starters i would like to just open the xlsm file, add an empty sheet and close it again without the VBA code giving me errors due to not having a listbox.
The activex listbox gives me troubles, since it is not "transfered" and the VBA script yields an error when trying to use the listbox. The VBA code looks just fine, after running the python script, but it crashes when setting the 'xLstBox' variable.
Possible fix i can see but not able to implement are:
having python edit the file without messing with the activex objects
if the VBA script could generate the listbox.
My python code:
from openpyxl import load_workbook
xlsx_path = 'excel_file.xlsm'
wb = load_workbook(filename=xlsx_path, keep_vba=True)
wb.create_sheet('sheetname')
wb.save(xlsx_path)
My VBA code in the prefabricated xlsm file:
Public PreviousActiveCell As Range
Private Sub Worksheet_SelectionChange(ByVal Target As Range)
Dim xSelLst As Variant, I As Integer
Set xLstBox = ActiveSheet.ListBox1
Static pPrevious As Range
Set PreviousActiveCell = pPrevious
Set pPrevious = ActiveCell
If Not Intersect(Target, Range("A2:A999999")) Is Nothing Then
If xLstBox.Visible = False Then
xLstBox.Visible = True
xLstBox.Top = ActiveCell.Row * 15
xLstBox.Left = 0
End If
Else
If xLstBox.Visible = True Then
xLstBox.Visible = False
For I = xLstBox.ListCount - 1 To 0 Step -1
If xLstBox.Selected(I) = True Then
xSelLst = xLstBox.List(I) & "," & xSelLst
End If
Next I
If xSelLst <> "" Then
PreviousActiveCell = Mid(xSelLst, 1, Len(xSelLst) - 1)
End If
For I = xLstBox.ListCount - 1 To 0 Step -1
ListBox1.Selected(I) = False
Next I
End If
End If
End Sub

Openpyxl isn't guaranteed to manage vba components so you may have issues with xlsm sheets that contain these objects.
If you are running on Windows (or perhaps Mac) and can use Xlwings this may help. Xlwings needs Excel installed locally.
Your first test for example, adding a sheet should not impact your listbox.
import xlwings as xw
xlsx_path = 'excel_file.xlsm'
with xw.App() as app:
wb = xw.Book(xlsx_path)
wb.sheets.add('sheetname', after='Sheet1')
wb.save(xlsx_path)
wb.close()

Related

How to run Excel Macro on MacOS using XLWINGS?

I have an API file that takes in dates, and then when the macro is run pulls in data for said date range.
I can run this on a Windows computer without use of Python, however I am automating a data clean up process and would like to be able to run this macro from MacOS.
I am using XLWINGS.
My logic is :
Open the workbook.
Update the date values.
Execute the macro.
Save workbook as a new file.
Close workbook.
Eventually set it on a loop to update itself
Current Code :
import xlwings as xw
start = dt.datetime(2022,2,1)
end = dt.datetime(2022,3,31)
excel_app = xw.App(visible=False)
wb = excel_app.books.open('sheet.xlsm')
sheet = wb.sheets['Start']
sheet.range('E5').value = start
sheet.range('E7').value = end
update = wb.macro("'sheet.xlsm'!Code.update_report")
update()
wb.save('PATH')
wb.close()
I get prompted with no errors, the script opens the workbook, updates the values, then saves it in the new location I defined with the new name.
However when I open the .xlsm file after its run its course - it does not have the update data for the inputted date range. Essentially, the macro does not run.
Other ways I've tried to run the macro (instead of running the macro on the original file, I save the original file once the date range has been updated and then try to execute the macro):
import xlwings as xw
if os.path.exists('sheet.xlsm'):
excel_app = xlwings.App(visible=False)
wb = xw.Book('sheet.xlsm')
app = wb.app
update = app.macro("!Code.update_report")
update()
wb.save()
Still - runs fine, no errors, except it does not execute the macro.
Any help welcomed! Thank you.

Can I use Python to run a piece of VBA code without my macro being saved in Excel?

Im working on automating some spreadsheets and when using OpenPyXl to comment in a spreadsheet it does not size the comments correctly.
I added a macro to the spreadsheet that autosizes the comments, however I have been told the spreadsheet has to be kept as I was given it and the macro cannot be added.
Can I add this macro by implementing it in my python code somehow? or calling a VBA script?
This is my VBA code:
Sub AutoFitCommentBox()
Set myRange = Application.Selection
Set myRange = Application.InputBox("Select one Range that contain comment boxes:", "AutoFitCommentBox", myRange.Address, Type:=8)
For Each oneCell In myRange
If Not oneCell.Comment Is Nothing Then
oneCell.Comment.Shape.TextFrame.AutoSize = True
End If
Next
End Sub
I've tested the code compiles and runs OK - If you are interested in improving the UserForm code you might find this article about UserForm1.Show useful.
This solution will make use of a UserForm with a ComboBox and CommandButton on it - all of which have their default names.
You will need to open the Excel workbook you save this code to (and remember to save in macro enabled format). You can open the workbook with additional code which you can add if you desire.
When the form loads, the ComboBox will populate a list of all open Excel Workbook names. Note: I haven't tested with this code, but if memory serves me correctly, it won't find workbooks that are open in a separate instance of Excel - this means your workbook may not appear in the list.
When the user makes their selection, the form hides and then calls your code you wrote with the addition of passing on the Workbook name selected in the userform. With this your code will specifically target the named workbook without the code being written in it.
In the ThisWorkbook code module:
Private Sub Workbook_Open()
UserForm1.Show
End Sub
In the UserForm1 code module:
Option Explicit
___________________________________________________________________________________
Private Sub CommandButton1_Click()
Dim ComboBoxSelection As String
ComboBoxSelection = Me.ComboBox1.Value
Me.Hide
AutoFitCommentBox (ComboBoxSelection)
End Sub
___________________________________________________________________________________
Private Sub UserForm_Initialize()
Dim WorkbookNameArray As Variant
Dim ArrayElementCounter As Long
WorkbookNameArray = FindOpenWorkbooks
For ArrayElementCounter = LBound(WorkbookNameArray) To UBound(WorkbookNameArray)
Me.ComboBox1.AddItem WorkbookNameArray(ArrayElementCounter)
Next ArrayElementCounter
End Sub
In the Module1 code module:
Public Sub AutoFitCommentBox(ByVal TargetWorkbookName As String)
Dim TargetWorkbook As Workbook
Dim myRange As Range
Set TargetWorkbook = Workbooks(TargetWorkbookName)
With TargetWorkbook
Set myRange = Application.Selection
Set myRange = Application.InputBox("Select one Range that contain comment boxes:", "AutoFitCommentBox", myRange.Address, Type:=8)
Dim oneCell As Variant
For Each oneCell In myRange
If Not oneCell.Comment Is Nothing Then
oneCell.Comment.Shape.TextFrame.AutoSize = True
End If
Next
End With
End Sub
_________________________________________________________________________________
Public Function FindOpenWorkbooks() As Variant
Dim myWorkbook As Workbook
Dim WorkbookArray As Variant
Dim ArrayElementCounter As Long
ReDim WorkbookArray(1 To Application.Workbooks.Count)
i = LBound(WorkbookArray)
For Each myWorkbook In Application.Workbooks
WorkbookArray(i) = myWorkbook.Name
i = i + 1
Next myWorkbook
FindOpenWorkbooks = WorkbookArray
End Function
For reference, here is a screenshot of the UserForm I created:

How to filter in Excel using Python?

I am trying to automate a macro-enabled Excel workbook process using Python. I would like to use win32com if possible but am open to switching to other libraries if needed.
Once I get the workbook open and on the sheet I need, there is data already there with auto-filters applied. I just need to filter on a column to make the data available to the macro when I run it.
I use wb.RefreshAll() to import the data from existing connections. Eventually I will need to pass a value entered by the user to the filter as it will be different each time the automation runs.
Most solutions involve copying select data to a Pandas DataFrame etc. but I need the filtered data to remain in the sheet so it can be used by the macro.
I recently wrote a Python script to automate Macros. Basically, the idea was to batch-edit a collection of .doc files and have a macro run for all of them, without having to open them one by one.
First:
What is this macro you want to run?
Second:
What is the data that you need to make visible, and what do you mean by that?
To get you started, try this:
data = [{"test1": 1, "test2": 2}, {"test1": 3, "test2": 4}]
import win32com.client as win32
def openExcel():
xl = win32.gencache.EnsureDispatch('Excel.Application')
wb = xl.Workbooks.Add()
#wb = xl.Workbooks.Open(filepath)
ws = wb.Sheets(1) #The worksheet you want to edit later
xl.Visible = True
return ws
def print2Excel(datapoint:dict, ws):
print(datapoint)
const = win32.constants #.Insert()-Methods keywargs are packaged into const.
ws.Range("A1:B1").Insert(const.xlShiftDown, const.xlFormatFromRightOrBelow)
ws.Cells(1,1).Value = datapoint["test1"]
ws.Cells(1,2).Value = datapoint["test2"]
ws = openExcel() #<- When using Open(filepath), pass the whole filepath, starting at C:\ or whatever drive.
for datapoint in data:
print2Excel(datapoint, ws)
This showcases some of the basics on how to work with Excel objects in win32com

How can I Bold only part of a string in an Excel cell with Python?

I'm using win32com to fill out an Excel spreadsheet with some analytic information. I have a cell that I want to be in this form:
This is the problem description: this is the command you run to fix it
I can't seem to figure out how to get the text into the cell with Python with mixed formatting.
import win32com.client as win32
excel = win32.gencache.EnsureDispatch('Excel.Application')
wb = excel.Workbooks.Add()
ws = wb.Worksheets("Sheet1")
excel.Visible=True
sel = excel.Selection
sel.Value = "this is the command you run to fix it"
sel.Font.Bold = True
ws.Cells(1,1).Value = 'This is the problem description' + sel.Value #makes the whole cell bold
ws.Cells(1,1).Value = "{} {}".format("this is the problem desc",sel) #makes the whole cell bold
The selection object has a Characters attribute, but I can't find any documentation on what it does.
Per #ron-rosenfield, the VBA code to pull this off looks like:
Range("A1").Characters(2,5).Font.Bold = true
And similarly, there is a Characters object attribute of the excel WorkSheet's Range
>>> ws.Range("A1").Characters
<win32com.gen_py.Microsoft Excel 14.0 Object Library.Characters 967192>
HOWEVER, the method you need to access a range with that object is GetCharacters(start, length) (index starts at 1 as per usual with the excel com methods)
ws.Range("A1").GetCharacters(2,4).Font.Bold = True
You can use this command to update the boldness of characters in a cell after the fact.
Although you are looking for a win32com solution in the body of your question I'll point out for anyone who gets here via the title that this is also possible in Python with XlsxWriter.
Example:

How to paste special in excel with python

So I am trying to copy alot of data from one woorkbook to another. The thing is that the data in the source workbook has some weird formating so I want to just get the values. The code I have so far is this.
excel=win32com.client.Dispatch("Excel.Application");
excel.Visible = 1;
source = excel.Workbooks.Open(Cali.xlsm');
copy = excel.Workbooks.Open(temp.xlsx');
sdata = source.ActiveSheet;cdata = copy.ActiveSheet;
data=sdata.Range("89:89")
sdata.Range("89:89",data.End(4)).Copy()
now I can use
cdata.Paste()
but it pastes the formating as well
I found
cdata.PasteSpecial()
but it also pastes the formating.
Anyone who know how to use PasteSpecial() so it copies just the values or someone knows a better way I would be very apprecitave.
You could try the following which copies values from cells A1:A3 from one workbook to another.
from win32com.client import Dispatch
wkbk1 = "...\workbook1.xlsx"
wkbk2 = "...\workbook2.xlsx"
excel = Dispatch("Excel.Application")
excel.Visible = 1
source = excel.Workbooks.Open(wkbk1)
excel.Range("A1:A3").Select()
excel.Selection.Copy()
copy = excel.Workbooks.Open(wkbk2)
excel.Range("A1:A3").Select()
excel.Selection.PasteSpecial(Paste=-4163)
wkbk1 and wkbk2 are file paths to two workbooks.

Categories