I'm attempting to programmatically write a Word file and am struggling to alternate styles on a selection object. Here is the relevant bit of code:
objWord = win32com.client.Dispatch("Word.Application")
objSel = objWord.Selection
writestuff = "\r\nSome Heading"
objSel.Style = objWord.ActiveDocument.Styles("Heading 3")
objSel.TypeText(writestuff)
#This works so far, we have a heading and some text, now we want to write data below the heading
objSel.Style = objWord.ActiveDocument.Styles("Normal") #Setting style back to 'Normal' for next section
writestuff = "\r\nSome data about the heading we just wrote")
objSel.TypeText(writestuff)
#At this point the heading and new text both go to the 'Normal' style.
It appears that my 'selection' is affecting all of the document, however, when I make my initial objSel.Style assignment to 'Heading 3' previous lines aren't affected. When I switch back to normal everything else is.
It appears that what I needed to do (or at least one of the things which will solve this problem) is to add a TypeParagraph() method
Corrected code looks like this:
objWord = win32com.client.Dispatch("Word.Application")
objSel = objWord.Selection
writestuff = "\r\nSome Heading"
objSel.Style = objWord.ActiveDocument.Styles("Heading 3")
objSel.TypeText(writestuff)
objSel.TypeParagraph ##THIS IS WHAT I ADDED, NO NEED TO READJUST STYLE##
writestuff = "\r\nSome data about the heading we just wrote")
objSel.TypeText(writestuff)
This would present data with the correct heading and then normal-type text on the following line.
Related
I want to write a LibreOffice Calc document from within a Python3 program. Using pyoo I can do almost everything I want, including formatting and merging cells. But I cannot adjust row heights and column widths.
I found Change the column width and row height very helpful, and have been experimenting with it, but I can't seem to get quite the result I want. My present test file, based on the answer mentioned above, looks like this:
#! /usr/bin/python3
import os, pyoo, time, uno
s = '-'
while s != 'Y':
s = input("Have you remembered to start Calc? ").upper()
os.popen("soffice --accept=\"socket,host=localhost,port=2002;urp;\" --norestore --nologo --nodefault")
time.sleep(2)
desktop = pyoo.Desktop('localhost', 2002)
doc = desktop.create_spreadsheet()
class ofic:
sheet_idx = 0
row_num = 0
sheet = None
o = ofic()
uno_localContext = uno.getComponentContext()
uno_resolver = uno_localContext.ServiceManager.createInstanceWithContext("com.sun.star.bridge.UnoUrlResolver", uno_localContext )
uno_ctx = uno_resolver.resolve( "uno:socket,host=localhost,port=2002;urp;StarOffice.ComponentContext" )
uno_smgr = uno_ctx.ServiceManager
uno_desktop = uno_smgr.createInstanceWithContext( "com.sun.star.frame.Desktop", uno_ctx)
uno_model = uno_desktop.getCurrentComponent()
uno_controller = uno_model.getCurrentController()
uno_sheet_count = 0
doc.sheets.create("Page {}".format(1), index=o.sheet_idx)
o.sheet = doc.sheets[o.sheet_idx]
o.sheet[0, 0].value = "The quick brown fox jumps over the lazy dog"
o.sheet[1, 1].value = o.sheet_idx
uno_controller.setActiveSheet(uno_model.Sheets.getByIndex(uno_sheet_count))
uno_sheet_count += 1
uno_active_sheet = uno_model.CurrentController.ActiveSheet
uno_columns = uno_active_sheet.getColumns()
uno_column = uno_columns.getByName("B")
uno_column.Width = 1000
The main problem with the above is that I have 2 Calc documents on the screen, one of which is created before the Python program gets going; the other is created from Python with a pyoo function. The first document gets the column width change, and the second receives the text input etc. I want just the second document, and of course I want the column width change applied to it.
I am sure the answer must be fairly straightforward, but after hours of experimentation I still can't find it. Could someone point me in the right direction, please?
Your code alternates between pyoo and straight Python-UNO, so it's no wonder that it's giving messy results. Pick one or the other. Personally, I use straight Python-UNO and don't see the benefit of adding the extra pyoo library.
the other is created from Python with a pyoo function
Do you mean this line of code from your question, and is this the "second document" that you want the column change applied to?
doc = desktop.create_spreadsheet()
If so, then get objects from that document instead of whichever window the desktop happens to have selected.
controller = doc.getCurrentController()
sheets = doc.getSheets()
Or perhaps you want the other document, the one that didn't get created from Python. In that case, grab a reference to that document before creating the second one.
first_doc = uno_desktop.getCurrentComponent()
second_doc = desktop.create_spreadsheet()
controller = first_doc.getCurrentController()
sheets = first_doc.getSheets()
If you don't have a reference to the document, you can find it by iterating through the open windows.
oComponents = desktop.getComponents()
oDocs = oComponents.createEnumeration()
Finally, how to resize a column. The link in your question is for Excel and VBA (both from Microsoft), so I'm not sure why you think that would be relevant. Here is a Python-UNO example of resizing columns.
oColumns = oSheet.getColumns()
oColumn = oColumns.getByName("A")
oColumn.Width = 7000
oColumn = oColumns.getByName("B")
oColumn.OptimalWidth = True
self.fm10 = tk.Frame(self.fm5)
self.fm10.grid(row=3,rowspan=3,columnspan=3,padx=5,pady=5)
self.treeview_box2 = Treeview(self.fm10,columns=('Assignment'))
self.style2 = Style()
self.style2.configure('Treeview',rowheight=70)
self.treeview_box2.heading('#0',text='Date')
self.treeview_box2.heading('Assignment',text='Assignment')
self.treeview_box2.column('#0',width=60,stretch=False)
self.treeview_box2.column('Assignment',width=175,stretch=False)
self.treeview_box2.pack(expand=True,pady=5)
my_cursor.execute('SELECT * FROM schedule')
self.schd = my_cursor.fetchall()
print(self.schd) ##A138MS Excel Test
self.count2 = 1
for s in self.schd:
self.treeview_box2.insert('',tk.END,text=s[0],values=(s[1]))
self.count2 += 1
In this code, I am trying to show the value s[1] on the treeview_box2 second column. The program is run successfully and stored into the database table. However the right side of value s[1] cannot be shown and only the left part is shown on the treeview_box2 table. May I ask is there any problems causing the value can be printed out but only the right part cannot be shown on the table?
I'm trying to append a column to a table in PowerPoint using python-pptx. A number of threads mention the solution:
def append_col(prs_obj, sl_i, sh_i):
# prs_obj is a pptx.Presentation('path') object.
# sli_i and sh_i are int indexs to locate a particular table object.
tab = prs_obj.slides[sl_i].shapes[sh_i].table
new_col = copy.deepcopy(tab._tbl.tblGrid.gridCol_lst[-1])
tab._tbl.tblGrid.append(new_col) # copies last grid element
for tr in tab._tbl.tr_lst:
# duplicate last cell of each row
new_tc = copy.deepcopy(tr.tc_lst[-1])
tr.append(new_tc)
cell = _Cell(new_tc, tr.tc_lst)
cell.text = '--'
return tab
After running this, when you open PowerPoint the new column will be there, but it won't contain the cell.text. If you click in the cell and type, the letters will appear in the cell of the previous column. Saving powerpoint enables you to edit the column as normal, but obviously you've lost the cell.text (and formatting).
QUESTION UPDATE 1- FOLLOWING COMMENT FROM #scanny
For the simplest possible case, a (1x3) table, like so: |xx|--|xx| the tab._tbl.xml prints before and after appending the column are:
xml diff 1
xml diff 2
xml diff 3
xml diff 4
QUESTION UPDATE 2- FOLLOWING COMMENT FROM #scanny
I modified the above append_col function to forcibly remove the extLst element from the copied gridCol. This stopped the problem of typing in one cell and text appearing in another cell.
def append_col(prs_obj, sl_i, sh_i):
# existing lines removed for brevity
# New Code
tblchildren = tab._tbl.getchildren()
for child in tblchildren:
if isinstance(child, oxml.table.CT_TableGrid):
ws = set()
for j in child:
if j.w not in ws:
ws.add(j.w)
else:
for elem in j:
j.remove(elem)
return tab
However cell.text(and formatting)are still missing. Moreover, manually saving the presentation changes the tab.xml object back. The screenshots before and after manually opening the PowerPoint presentation are:
AFTER removing extLst, before manual save - xml diff 1
AFTER removing extLst, AFTER manual save - xml diff 2
If you're serious about solving this sort of problem, you'll need to reverse-engineer the Word XML for this aspect of tables.
The place to start is with before and after (adding a column) XML dumps of the table, identifying the changes made by Word, then duplicating those that matter (things like revision-numbers probably don't matter).
This process is simplified by having a small example, say a 2 x 2 table to a 2 x 3 table.
You can get the XML for a python-docx XML element using its .xml attribute, like:
print(tab._tbl.xml)
You could compare the deepcopy results and then have concrete differences to start to explain the results not working. I expect you'll find that table items have unique ids and when you duplicate those, funky things happen.
With help from Scanny, I've come up with the following workaround which works:
def append_col(prs_obj, sl_i, sh_i):
tab = prs_obj.slides[sl_i].shapes[sh_i].table
new_col = copy.deepcopy(tab._tbl.tblGrid.gridCol_lst[-1])
tab._tbl.tblGrid.append(new_col) # copies last grid element
for tr in tab._tbl.tr_lst:
new_tc = copy.deepcopy(tr.tc_lst[-1])
tr.tc_lst[-1].addnext(new_tc)
cell = _Cell(new_tc, tr.tc_lst)
for paragraph in cell.text_frame.paragraphs:
for run in paragraph.runs:
run.text = '--'
tblchildren = tab._tbl.getchildren()
for child in tblchildren:
if isinstance(child, oxml.table.CT_TableGrid):
ws = set()
for j in child:
if j.w not in ws:
ws.add(j.w)
else:
# print('j:\n', j.xml)
for elem in j:
j.remove(elem)
return tab
As I needed to add custom text to a chart data labels in python-pptx I used
for point in plot.series[1].points:
frame = point.data_label.text_frame
frame.text = "Test "
for run in frame.paragraphs[0].runs:
run.font.size = Pt(10)
run.font.color.rgb = RGBColor(0x0A, 0x42, 0x80)
This allowed me to change the font to the labels but I would need to rotate them.
I saw the solution from This other thread but it does not work. Any ideas?
I believe you need to change the txPr (as in the thread you mention) but on the respective element child:
for all_series in range(0, len(plot.series)):
for i in range(0, len(plot.series[all_series]._element)):
if re.sub("{.*}", '', plot.series[all_series]._element[i].tag) == >"dLbls":
txPr = plot.series[all_series]._element[i].get_or_add_txPr()
txPr.bodyPr.set('rot', '-5400000')
So you need to access the "dLbls" child within the correct element index, the "[i]" is the difference to the mentioned thread. I use regular expressions to derive the name from the ".tag"-method. There is probably a better way to find the correct index, but this is at least one ;)
This solution iterates over all series, if you do not need this you can skip the first loop (assuming you want only the 1-series):
for i in range(0, len(plot.series[1]._element)):
if re.sub("{.*}", '', plot.series[1]._element[i].tag) == "dLbls":
txPr = plot.series[1]._element[i].get_or_add_txPr()
txPr.bodyPr.set('rot', '-5400000')
Note, that this only applies to data labels which were set on the single point, so e.g. through
plot.series[1].points[0].data_label.text_frame.text = "Foo"
Hopefully this helps :)
You can try this custom function, feed in your plot variable, and other style properties as you needed. If we go at point level for data label then this wont work as we think, and below can help to achieve.
def apply_data_labels(plot,font,fontsize,numformat,rotate = False):
plot.has_data_labels = True
data_labels = plot.data_labels
data_labels.font.size = Pt(fontsize)
data_labels.font.name = font
if rotate == True:
data_labels._element.get_or_add_txPr().bodyPr.set('rot','-5400000')
data_labels.number_format = numformat
and call this function with your plot variable for example.
apply_data_labels(chart.plots[0],'Invention',10,'#,##0;[Red]#,##0',rotate = True)
I am trying to create a list like in outlook. With list items with an layout like this:
Don't get me wrong this isn't a "give me the full answer" question. I just have the problem of the right naming. I would appreciate it a lot if some could throw in the right words and I will look for them by my own.
I used tkinter at the moment but in that it seems like there isn't a solution for that.
Kind regards.
I think Tkinter can do this by using a bit of object oriented programming you could define how one list element should look like and then with static variables you could define a position of a new line relative to the position of the previous line. Something like:
from tkinter import *
class Line:
y = 0 # static variable for the y position
def __init__(self):
self.y_local = Line.y
Line.y = Line.y + 40 # increase the static variable for the next line
print(self.y_local)
def draw(self, tk, h_line="Headline", a_info="Addtional Information", date="01/01/1970"):
self.label_head = Label(text=h_line)
self.label_head.place(x=20, y=self.y)
self.label_info = Label(text=a_info)
self.label_info.place(x=20, y=self.y+20)
self.label_date = Label(text='Date')
self.label_date.place(x=200, y=self.y)
self.label_ndate = Label(text=date)
self.label_ndate.place(x=200, y=self.y+20)
self.chkbtn = Checkbutton(tk, text="Checkbox")
self.chkbtn.place(x=300, y=self.y+20)
tk = Tk()
data = [
["News", "WWIII has been avoided", "02/04/2018"],
["Python", "Python solves 42 riddles", "02/04/2018"]
]
for line in data:
temp = Line()
temp.draw(tk, line[0], line[1], line[2])
mainloop()
Hope I understood your question well. That you have a list with information and want to display that in an easy and scalable way. I have not looked to add the lines around the information as I've never done that before I know there are separators I've used vertical once before but I wouldn't be surprised if you can draw a box around each line either.