-1

I am having a problem possibly due to my lack of knowledge. I want to open the windows dialog to choose printer and send to print (to a printer) using Tkinter.

Currently, I use a code to relate Tkinter to Wxpython and make it asynchronous creating a separate process.

This is the code mentioned above:

from tkinter import *
from threading import Thread
import wx

def f_imprimir(ventana, entry):
    class TextDocPrintout(wx.Printout):
        def __init__(self):
            wx.Printout.__init__(self)

        def OnPrintPage(self, page):
            dc = self.GetDC()

            ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()     
            ppiScreenX, ppiScreenY = self.GetPPIScreen()     
            logScale = float(ppiPrinterX)/float(ppiScreenX)

            pw, ph = self.GetPageSizePixels()
            dw, dh = dc.GetSize()     
            scale = logScale * float(dw)/float(pw)
            dc.SetUserScale(scale, scale)

            logUnitsMM = float(ppiPrinterX)/(logScale*25.4)

            ### Print code ###

            return True

    class PrintFrameworkSample(wx.Frame):        
        def OnPrint(self):
            pdata = wx.PrintData()
            pdata.SetPaperId(wx.PAPER_A4)
            pdata.SetOrientation(wx.LANDSCAPE)

            data = wx.PrintDialogData(pdata)
            printer = wx.Printer(data)

            printout = TextDocPrintout()

            useSetupDialog = True

            if not printer.Print(self, printout, useSetupDialog) and printer.GetLastError() == 
wx.PRINTER_ERROR:

                wx.MessageBox(

                    "There was a problem printing.\n"

                    "Perhaps your current printer is not set correctly?",

                    "Printing Error", wx.OK)

            else:
                data = printer.GetPrintDialogData() 
                pdata = wx.PrintData(data.GetPrintData()) # force a copy

            printout.Destroy()
            self.Destroy()

    app=wx.App(False)
    PrintFrameworkSample().OnPrint()
    entry.config(state="normal")


def process(ventana, entry):
    entry.config(state="disable")
    t = Thread(target=f_imprimir, args=(ventana,entry))
    t.start()

v = Tk()
entry = Entry(v)
entry.pack()
v.bind("a", lambda a:process(v,entry))

when wx.app finishes, which can happen when the Printer Selector closes, I plan to change the status of the entry to "normal". But it throws an error when changing the state of the entry to "normal", which I suppose is because the window and the order I send are in separate processes. The error would be:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "C:\Python38-32\lib\threading.py", line 932, in _bootstrap_inner
    self.run()
  File "C:\Python38-32\lib\threading.py", line 870, in run
    self._target(*self._args, **self._kwargs)
  File "C:\Users\DANTE\Google Drive\JNAAB\DESARROLLO\pruebas\pedasito.py", line 65, in f_imprimir
    entry.config(state="normal")
  File "C:\Python38-32\lib\tkinter\__init__.py", line 1637, in configure
    return self._configure('configure', cnf, kw)
  File "C:\Python38-32\lib\tkinter\__init__.py", line 1627, in _configure
    self.tk.call(_flatten((self._w, cmd)) + self._options(cnf))
RuntimeError: main thread is not in main loop

Does anyone have a solution for this problem or an alternative way to create the print window without blocking the TCL window and being able to block the entry? If there is a way to do it or send to print using Tkinter and avoid this mess it would be even better. Thank you.

Dante S.
  • 222
  • 3
  • 16
  • ***"RuntimeError:"***: Does it work when you disable this line: `entry.config(state="normal")` – stovfl Mar 04 '20 at 21:09
  • yes, but the entry is not unlocked – Dante S. Mar 04 '20 at 21:14
  • 1
    ***"yes, but the entry is not unlocked"***: You violate the `tkinter` paradigm, **not** to call any widgets methode from another `Thread`, which results in `... not in main loop`. You can use a virtual event instead. Read up on [Widget.event_generate-method](http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.event_generate-method) and [Widget.bind-method](http://effbot.org/tkinterbook/widget.htm#Tkinter.Widget.bind-method). Read also [multiprocessing-vs-threading-python](https://stackoverflow.com/questions/3044580) – stovfl Mar 04 '20 at 21:20
  • It gave me a very slight idea that it had to do with that rule. I did not know about the events, the truth seems very useful. Thank you!!! And thanks anyway for trying to help me with my previous question, you see that you frequent the page! And actually I would have used multiprocessing, but I failed to try to pickling the Tk class. – Dante S. Mar 04 '20 at 21:31
  • ***"but I failed to try to pickling the Tk class"***: That's another **no go**. As it stands, you don't use the passed arg `ventana`. What do you want to accomplish here? Using `multiprocessing` the usage of `tkinter` events is not possible, then you have to use `multiprocessing.Event` or `Queue`. – stovfl Mar 04 '20 at 21:37
  • I don't know as much as you do, I tried different ways to make it asynchronous and that was what worked best. I recently delved beyond basic Tkinter. I guess that's why you gave -1 to my question. – Dante S. Mar 04 '20 at 21:44
  • @DanteS. the downvote could have been anyone don't assume it is the person who commented. That said a downvote simple reflects that the person who voted does not think your question is a good question for whatever reason. To help prevent this in the future you can follow the guidlines here: [minimal-reproducible-example](https://stackoverflow.com/help/minimal-reproducible-example) and [how-to-ask](https://stackoverflow.com/help/how-to-ask). Stack Overflow is not your typical Q/A site so do not take offense from the downvotes. We all had to deal with them when we first started asking question. – Mike - SMT Mar 04 '20 at 21:51
  • That is not what offends me, I clarified it in the "false answer". Sorry, it was a misunderstanding, I thought it said in Spanish "What do you want to achieve here?", "¿Que quieres lograr aqui?" – Dante S. Mar 04 '20 at 21:55
  • "For example are you trying to pass the value of the entry field to the print function? If so you can use get() and just send a string to your threaded function" Mike - SMT ------------------ I am aware of that. In fact, the plan is to create a function that you receive from a DC argument and work on that function. Thanks the same for the advice. – Dante S. Mar 04 '20 at 22:01
  • I regret so much confusion, I will be less rushed. Events can be executed immediately as if they were a function. – Dante S. Mar 05 '20 at 11:58

1 Answers1

1

I dont use wxPython however your error is due to a threading issue related to tkinter. Tkinter likes to be in the main thread and trying to pass widgets to a seperate thread can cause problems. However your entry field is in the global namespace already so you do not need to pass it.

Simply updated from your thread once it needs to be.

I would do this in your if/else condition so it only happens at the correct time.

Something like this would work: Note you will need to actually do something with the value passed. As it is now none of your code actually prints anything other than a blank page.

import tkinter as tk
from threading import Thread
import wx


def f_imprimir(value):
    # here you can see the value of entry was passed as a string so we can avoid any issues with the widget
    print(value)
    class TextDocPrintout(wx.Printout):
        def __init__(self):
            wx.Printout.__init__(self)

        def OnPrintPage(self, page):
            dc = self.GetDC()
            ppiPrinterX, ppiPrinterY = self.GetPPIPrinter()     
            ppiScreenX, ppiScreenY = self.GetPPIScreen()     
            logScale = float(ppiPrinterX)/float(ppiScreenX)

            pw, ph = self.GetPageSizePixels()
            dw, dh = dc.GetSize()     
            scale = logScale * float(dw)/float(pw)
            dc.SetUserScale(scale, scale)
            logUnitsMM = float(ppiPrinterX)/(logScale*25.4)
            return True

    class PrintFrameworkSample(wx.Frame):        
        def OnPrint(self):
            pdata = wx.PrintData()
            pdata.SetPaperId(wx.PAPER_A4)
            pdata.SetOrientation(wx.LANDSCAPE)
            data = wx.PrintDialogData(pdata)
            printer = wx.Printer(data)
            printout = TextDocPrintout()
            useSetupDialog = True

            if not printer.Print(self, printout, useSetupDialog) and printer.GetLastError() == wx.PRINTER_ERROR:

                wx.MessageBox("There was a problem printing.\n\n"
                              "Perhaps your current printer is not set correctly?\n\n"
                              "Printing Error", wx.OK)
                entry.config(state="normal")
            else:
                data = printer.GetPrintDialogData() 
                pdata = wx.PrintData(data.GetPrintData())  # force a copy
                entry.config(state="normal")

            printout.Destroy()
            self.Destroy()

    app = wx.App(False)
    PrintFrameworkSample().OnPrint()



def process(_=None):
    entry.config(state="disable")
    t = Thread(target=f_imprimir, args=(entry.get(),))
    t.start()


v = tk.Tk()
entry = tk.Entry(v)
entry.pack()
v.bind("<Return>", process)
v.mainloop()
Mike - SMT
  • 14,784
  • 4
  • 35
  • 79
  • I understand your idea, but what you saw is actually a test code. I'm going to have N entrys (I'm looking to make a module). In addition, for certain problems I cannot perform the test, but I think it would not work because, as Stovfl says, you call widget methods in another process, thus removing it from the mainloop. Just read the links that happened to me stovfl, I tried `v.bind ("a", function, add = "+") ` and it worked asynchronously and, being within the same process, also reactivated the entry. I have to read more about the events. Anything I ask. Thanks again the same. – Dante S. Mar 04 '20 at 22:16
  • @DanteS. you can store your many entry fields in a list then generate a list of values to pass to your threaded function. The concept is still the same. Maybe if you can produce an example input and an example desired output I can be more clear. – Mike - SMT Mar 04 '20 at 23:39
  • Unfortunately that was what didn't work. But don't worry, I read the stovfl links a bit and tried the events. It seems not to "lock" the window and being in the same process also control the entrys. If I have a problem with that, I will ask them. Thanks also for the help. Sorry for before you and stovfl. – Dante S. Mar 05 '20 at 00:29
  • @DanteS. trust me it works. You just are missing something. I have done it many times myself. There may be other ways but you can send a list of values to the thread not the entry fields themselves. You can even update the entry fields from in the thread without issues as long as you do it in a safe way. – Mike - SMT Mar 05 '20 at 00:34
  • I could already try it. You're right, it worked. Then I must call entrys created in the mainloop instead of passing them as arguments. I can use it as a possible solution. But it may be more neat to learn how to use tkinter events. If it does not work I will not hesitate to resort to your solution. For those who have enough with this, I will mark it as a solution as soon as StackOverflow allows me. – Dante S. Mar 05 '20 at 11:07
  • @DanteS. sure go ahead and explore the events. They are useful. There is normally more than one way to a solution. – Mike - SMT Mar 05 '20 at 12:18
  • I don't know what `v.mainloop` does internally in tkinter, but it seems to solve the problem if it is placed at the end of the code. – Dante S. Mar 05 '20 at 13:12
  • @DanteS. the `mainloop()` in what manages the tkinter loop. `v` is what you named your root instance of `Tk()`. So `v.mainloop()` is running the mainloop method within the `Tk()` class. All tkinter instances require a mainloop. If you are using the Python IDLE it is not normally required because that IDLE assumes it is needed and runs it for you. – Mike - SMT Mar 05 '20 at 13:41
  • The same happens if it is within functions. I get it. That thing, I have a problem and the solution ends up being simple. Thank you. – Dante S. Mar 05 '20 at 13:45