إعادة توجيه نتائج سطر الأوامر إلى واجهة المستخدم الرسومية tkinter
-
21-08-2019 - |
سؤال
لقد قمت بإنشاء برنامج يقوم بطباعة النتائج على سطر الأوامر.(إنه خادم ويقوم بطباعة السجل على سطر الأوامر.)
الآن، أريد أن أرى نفس النتيجة لواجهة المستخدم الرسومية.
كيف يمكنني إعادة توجيه نتائج سطر الأوامر إلى واجهة المستخدم الرسومية؟
من فضلك، اقترح خدعة لتحويل تطبيق وحدة التحكم بسهولة إلى واجهة مستخدم رسومية بسيطة.
لاحظ أنه يجب أن يعمل على Linux وWindows.
المحلول
يمكنك إنشاء غلاف برنامج نصي يقوم بتشغيل برنامج سطر الأوامر الخاص بك كعملية فرعية، ثم إضافة الإخراج إلى شيء مثل عنصر واجهة المستخدم النصي.
from tkinter import *
import subprocess as sub
p = sub.Popen('./script',stdout=sub.PIPE,stderr=sub.PIPE)
output, errors = p.communicate()
root = Tk()
text = Text(root)
text.pack()
text.insert(END, output)
root.mainloop()
حيث البرنامج النصي هو البرنامج الخاص بك.من الواضح أنه يمكنك طباعة الأخطاء بلون مختلف، أو شيء من هذا القبيل.
نصائح أخرى
لعرض مخرجات العملية الفرعية في واجهة المستخدم الرسومية بينما لا يزال قيد التشغيل, ، يجب أن يستخدم الحل المحمول stdlib فقط الذي يعمل على كل من Python 2 و 3 مؤشر ترابط في الخلفية:
#!/usr/bin/python
"""
- read output from a subprocess in a background thread
- show the output in the GUI
"""
import sys
from itertools import islice
from subprocess import Popen, PIPE
from textwrap import dedent
from threading import Thread
try:
import Tkinter as tk
from Queue import Queue, Empty
except ImportError:
import tkinter as tk # Python 3
from queue import Queue, Empty # Python 3
def iter_except(function, exception):
"""Works like builtin 2-argument `iter()`, but stops on `exception`."""
try:
while True:
yield function()
except exception:
return
class DisplaySubprocessOutputDemo:
def __init__(self, root):
self.root = root
# start dummy subprocess to generate some output
self.process = Popen([sys.executable, "-u", "-c", dedent("""
import itertools, time
for i in itertools.count():
print("%d.%d" % divmod(i, 10))
time.sleep(0.1)
""")], stdout=PIPE)
# launch thread to read the subprocess output
# (put the subprocess output into the queue in a background thread,
# get output from the queue in the GUI thread.
# Output chain: process.readline -> queue -> label)
q = Queue(maxsize=1024) # limit output buffering (may stall subprocess)
t = Thread(target=self.reader_thread, args=[q])
t.daemon = True # close pipe if GUI process exits
t.start()
# show subprocess' stdout in GUI
self.label = tk.Label(root, text=" ", font=(None, 200))
self.label.pack(ipadx=4, padx=4, ipady=4, pady=4, fill='both')
self.update(q) # start update loop
def reader_thread(self, q):
"""Read subprocess output and put it into the queue."""
try:
with self.process.stdout as pipe:
for line in iter(pipe.readline, b''):
q.put(line)
finally:
q.put(None)
def update(self, q):
"""Update GUI with items from the queue."""
for line in iter_except(q.get_nowait, Empty): # display all content
if line is None:
self.quit()
return
else:
self.label['text'] = line # update GUI
break # display no more than one line per 40 milliseconds
self.root.after(40, self.update, q) # schedule next update
def quit(self):
self.process.kill() # exit subprocess if GUI is closed (zombie!)
self.root.destroy()
root = tk.Tk()
app = DisplaySubprocessOutputDemo(root)
root.protocol("WM_DELETE_WINDOW", app.quit)
# center window
root.eval('tk::PlaceWindow %s center' % root.winfo_pathname(root.winfo_id()))
root.mainloop()
جوهر الحل هو:
- ضع مخرجات العملية الفرعية في قائمة الانتظار في سلسلة رسائل الخلفية
- الحصول على الإخراج من قائمة الانتظار في مؤشر ترابط واجهة المستخدم الرسومية.
أي اتصل process.readline()
في موضوع الخلفية -> قائمة الانتظار -> تحديث تسمية واجهة المستخدم الرسومية في الموضوع الرئيسي.متعلق ب kill-process.py
(لا يوجد اقتراع - حل أقل قابلية للنقل يستخدم event_generate
في موضوع الخلفية).
تعد إعادة توجيه stdout إلى طريقة write() التي تعمل على تحديث واجهة المستخدم الرسومية إحدى الطرق التي يمكنك اتباعها، وربما هي الأسرع - على الرغم من أن تشغيل عملية فرعية ربما يكون حلاً أكثر أناقة.
لا تقم بإعادة توجيه stderr إلا عندما تكون واثقًا من أنه جاهز للعمل، رغم ذلك!
مثال على التنفيذ (ملف واجهة المستخدم الرسومية والبرنامج النصي للاختبار):
test_gui.py:
from Tkinter import *
import sys
sys.path.append("/path/to/script/file/directory/")
class App(Frame):
def run_script(self):
sys.stdout = self
## sys.stderr = self
try:
del(sys.modules["test_script"])
except:
## Yeah, it's a real ugly solution...
pass
import test_script
test_script.HelloWorld()
sys.stdout = sys.__stdout__
## sys.stderr = __stderr__
def build_widgets(self):
self.text1 = Text(self)
self.text1.pack(side=TOP)
self.button = Button(self)
self.button["text"] = "Trigger script"
self.button["command"] = self.run_script
self.button.pack(side=TOP)
def write(self, txt):
self.text1.insert(INSERT, txt)
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.build_widgets()
root = Tk()
app = App(master = root)
app.mainloop()
test_script.py:
print "Hello world!"
def HelloWorld():
print "HelloWorldFromDef!"
اسف على سوء لغتي الانجليزية.لقد استخدمت بالفعل طريقة مختلفة لطباعة إخراج موجه الأوامر في أداة الأتمتة الجديدة الخاصة بي.يرجى العثور على هذه الخطوات أدناه.
1> قم بإنشاء ملف Bat وأعد توجيه مخرجاته إلى ملف LOG.أمر موجه الأوامر: tasklist /svc
2> قم بقراءة هذا الملف باستخدام Python 3.x.`processedFile = open('D:\LOG askLog.txt', 'r')
3> الخطوة النهائية.ttk.Label(Tab4, text=[ProcessFile.read()]).place(x=0, y=27)
** ومن ثم يرجى العلم أنني لم أقم بتضمين شريط التمرير في هذا الرمز حتى الآن.
نشر لقطة الشاشة: