Posts: 6
Threads: 1
Joined: Aug 2019
I am trying to display streamed video in a GUI I created using PAGE. I can’t get the references to the Class to work. In line 82 I have: self.Frame1.CanvasZ.after(10, video_stream). Python says: AttributeError: 'Toplevel1' object has no attribute 'CanvasZ'. I’ve tried putting self.Frame1.CanvasZ, Frame1.CanvasZ, etc. but nothing works. The video stream code works fine in a standalone app, but doesn't in the class. I've also tried variations with Canvas.create_image (line 127).
Can someone telling me the secret of getting this to work?
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# GUI module generated by PAGE version 4.24.1
# in conjunction with Tcl version 8.6
# Aug 13, 2019 01:18:58 PM MDT platform: Windows NT
import sys
import tkinter as tk
import tkinter.ttk as ttk
import carCtrl_support
import socket
import time
import cv2
from PIL import ImageTk, Image
# Capture from camera
cap = cv2.VideoCapture('http://192.168.1.1:8080/?action=stream')
def vp_start_gui():
'''Starting point when module is the main routine.'''
global val, w, root
root = tk.Tk()
carCtrl_support.set_Tk_var()
top = Toplevel1 (root)
carCtrl_support.init(root, top)
root.mainloop()
w = None
def create_Toplevel1(root, *args, **kwargs):
'''Starting point when module is imported by another program.'''
global w, w_win, rt
rt = root
w = tk.Toplevel (root)
carCtrl_support.set_Tk_var()
top = Toplevel1 (w)
carCtrl_support.init(w, top, *args, **kwargs)
return (w, top)
def destroy_Toplevel1():
global w
w.destroy()
w = None
class Toplevel1:
def connect (self):
""" Make a socket connection to the car and set the data
timeout (timeout also forms the step duration)
"""
global s
if self.sockConnected == True:
print("sockConnected True")
s.shutdown(socket.SHUT_RDWR)
s.close()
self.connectBtn.configure(text='''Connect''')
self.sockConnected = False
print("sockConnected " + str(self.sockConnected))
else:
s = socket.socket()
host = '192.168.1.1'
port = 2001
s.connect((host, port))
data = s.recv(32)
stringdata = data.decode('utf-8')
print("Data: " + stringdata)
s.settimeout(0.1)
self.sockConnected = True
self.connectBtn.configure(text='''Disconnect''')
# function for video streaming
def video_stream(self):
_, frame = cap.read()
cv2image = cv2.cvtColor(frame, cv2.COLOR_BGR2RGBA)
img = Image.fromarray(cv2image)
imgtk = ImageTk.PhotoImage(image=img)
self.Frame1.CanvasZ.after(10, video_stream)
def __init__(self, top=None):
'''This class configures and populates the toplevel window.
top is the toplevel containing window.'''
_bgcolor = '#d9d9d9' # X11 color: 'gray85'
_fgcolor = '#000000' # X11 color: 'black'
_compcolor = '#d9d9d9' # X11 color: 'gray85'
_ana1color = '#d9d9d9' # X11 color: 'gray85'
_ana2color = '#ececec' # Closest X11 color: 'gray92'
font9 = "-family {Segoe UI} -size 9 -weight normal -slant " \
"roman -underline 0 -overstrike 0"
swVer = "0.1"
self.sockConnected = False
top.geometry("583x496+2112+204")
top.title("Video" + swVer)
top.configure(background="#d9d9d9")
top.configure(highlightbackground="#d9d9d9")
top.configure(highlightcolor="black")
self.Frame1 = tk.Frame(top)
self.Frame1.place(relx=0.189, rely=0.081, height=280, width=360)
self.Frame1.configure(relief='groove')
self.Frame1.configure(borderwidth="2")
self.Frame1.configure(relief="groove")
self.Frame1.configure(background="white")
self.Frame1.configure(width=385)
self.Frame1.configure(command = self.video_stream())
#self.LMain = tk.Label(top)
#self.LMain.place(relx=0.2, rely=0.2)
#self.LMain.pack(padx=5, pady=10, side="left")
self.CanvasZ = tk.Canvas(self.Frame1)
self.CanvasZ.place(relx=0.05, rely=0.05, height=240, width=320)
self.CanvasZ.configure(background="#d9d9d9")
self.CanvasZ.configure(borderwidth="0")
self.CanvasZ.configure(insertbackground="black")
self.CanvasZ.configure(relief="ridge")
self.CanvasZ.configure(selectbackground="#c4c4c4")
self.CanvasZ.configure(selectforeground="black")
self.CanvasZ.configure(width=120)
self.CanvasZ.create_image(10, 10)
self.pix = self.CanvasZ.create_image(10,10)
if __name__ == '__main__':
vp_start_gui()
Posts: 12,029
Threads: 485
Joined: Sep 2016
Aug-27-2019, 04:37 AM
(This post was last modified: Aug-27-2019, 07:28 PM by Larz60+.)
You certainly can use tkinter for this, but you might want to take a look at wxpython phoenix (The new wxpython for python 3)
It has built in video controls,
see: https://wxpython.org/Phoenix/docs/html/w...aCtrl.html
and quite a few others:
Qt almost certainly has them as well.
I'm partial to wxpython because it's royality free for both commercial and non commercial applications.
Posts: 211
Threads: 4
Joined: May 2019
There is also a module for pyqt that I have a working bit of code for that was created by someone else. The module is called CV2
Posts: 5,151
Threads: 396
Joined: Sep 2016
Aug-27-2019, 01:38 PM
(This post was last modified: Aug-27-2019, 01:38 PM by metulburr.)
This is WxPython Phenoix demo code for the video player
#!/usr/bin/env python
import wx
import wx.media
import os
#----------------------------------------------------------------------
class StaticText(wx.StaticText):
"""
A StaticText that only updates the label if it has changed, to
help reduce potential flicker since these controls would be
updated very frequently otherwise.
"""
def SetLabel(self, label):
if label != self.GetLabel():
wx.StaticText.SetLabel(self, label)
#----------------------------------------------------------------------
class TestPanel(wx.Panel):
def __init__(self, parent, log):
self.log = log
wx.Panel.__init__(self, parent, -1,
style=wx.TAB_TRAVERSAL|wx.CLIP_CHILDREN)
# Create some controls
try:
backend = "" # let MediaCtrl choose default backend
#backend=wx.media.MEDIABACKEND_DIRECTSHOW
#backend=wx.media.MEDIABACKEND_WMP10
self.mc = wx.media.MediaCtrl()
ok = self.mc.Create(self, style=wx.SIMPLE_BORDER,
szBackend=backend)
if not ok:
raise NotImplementedError
except NotImplementedError:
self.Destroy()
raise
# the following event is not sent with the Windows default backend
# MEDIABACKEND_DIRECTSHOW
# choose above e.g. MEDIABACKEND_WMP10 if this is a problem for you
self.Bind(wx.media.EVT_MEDIA_LOADED, self.OnMediaLoaded)
btn1 = wx.Button(self, -1, "Load File")
self.Bind(wx.EVT_BUTTON, self.OnLoadFile, btn1)
btn2 = wx.Button(self, -1, "Play")
self.Bind(wx.EVT_BUTTON, self.OnPlay, btn2)
self.playBtn = btn2
btn3 = wx.Button(self, -1, "Pause")
self.Bind(wx.EVT_BUTTON, self.OnPause, btn3)
btn4 = wx.Button(self, -1, "Stop")
self.Bind(wx.EVT_BUTTON, self.OnStop, btn4)
slider = wx.Slider(self, -1, 0, 0, 10)
self.slider = slider
slider.SetMinSize((150, -1))
self.Bind(wx.EVT_SLIDER, self.OnSeek, slider)
self.st_size = StaticText(self, -1, size=(100,-1))
self.st_len = StaticText(self, -1, size=(100,-1))
self.st_pos = StaticText(self, -1, size=(100,-1))
# setup the layout
sizer = wx.GridBagSizer(5,5)
sizer.Add(self.mc, (1,1), span=(5,1))#, flag=wx.EXPAND)
sizer.Add(btn1, (1,3))
sizer.Add(btn2, (2,3))
sizer.Add(btn3, (3,3))
sizer.Add(btn4, (4,3))
sizer.Add(slider, (6,1), flag=wx.EXPAND)
sizer.Add(self.st_size, (1, 5))
sizer.Add(self.st_len, (2, 5))
sizer.Add(self.st_pos, (3, 5))
self.SetSizer(sizer)
#self.DoLoadFile(os.path.abspath("data/testmovie.mpg"))
wx.CallAfter(self.DoLoadFile, os.path.abspath("data/testmovie.mpg"))
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer)
self.timer.Start(100)
def OnLoadFile(self, evt):
dlg = wx.FileDialog(self, message="Choose a media file",
defaultDir=os.getcwd(), defaultFile="",
style=wx.FD_OPEN | wx.FD_CHANGE_DIR )
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
self.DoLoadFile(path)
dlg.Destroy()
def DoLoadFile(self, path):
if not self.mc.Load(path):
wx.MessageBox("Unable to load %s: Unsupported format?" % path,
"ERROR",
wx.ICON_ERROR | wx.OK)
self.playBtn.Disable()
else:
self.mc.SetInitialSize()
self.GetSizer().Layout()
self.slider.SetRange(0, self.mc.Length())
self.playBtn.Enable()
def OnMediaLoaded(self, evt):
self.playBtn.Enable()
def OnPlay(self, evt):
if not self.mc.Play():
wx.MessageBox("Unable to Play media : Unsupported format?",
"ERROR",
wx.ICON_ERROR | wx.OK)
else:
self.mc.SetInitialSize()
self.GetSizer().Layout()
self.slider.SetRange(0, self.mc.Length())
def OnPause(self, evt):
self.mc.Pause()
def OnStop(self, evt):
self.mc.Stop()
def OnSeek(self, evt):
offset = self.slider.GetValue()
self.mc.Seek(offset)
def OnTimer(self, evt):
offset = self.mc.Tell()
self.slider.SetValue(offset)
self.st_size.SetLabel('size: %s' % self.mc.GetBestSize())
self.st_len.SetLabel('length: %d seconds' % (self.mc.Length()/1000))
self.st_pos.SetLabel('position: %d' % offset)
def ShutdownDemo(self):
self.timer.Stop()
del self.timer
#----------------------------------------------------------------------
def runTest(frame, nb, log):
try:
win = TestPanel(nb, log)
return win
except NotImplementedError:
from wx.lib.msgpanel import MessagePanel
win = MessagePanel(nb, 'wx.MediaCtrl is not available on this platform.',
'Sorry', wx.ICON_WARNING)
return win
#----------------------------------------------------------------------
overview = """<html><body>
<h2><center>wx.MediaCtrl</center></h2>
wx.MediaCtrl is a class that allows a way to convieniently display
various types of media, such as videos, audio files, natively through
native codecs. Several different formats of audio and video files are
supported, but some formats may not be playable on all platforms or
may require specific codecs to be installed.
<p>
wx.MediaCtrl uses native backends to render media, for example on Windows
there is a ActiveMovie/DirectShow backend, and on Macintosh there is a
QuickTime backend.
<p>
wx.MediaCtrl is not currently available on unix systems.
</body></html>
"""
if __name__ == '__main__':
import sys,os
import run
run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) in which you can run a multitude of demos via command to the path (linux)
$python3.6 ~/.wxPython-4.0.3/demo/demo.py
Attached Files
Thumbnail(s)
Recommended Tutorials:
Posts: 6
Threads: 1
Joined: Aug 2019
I tried running metulburr's example and don't understand. What is the purpose of the section starting at 162? Is this supposed to be in a different file? If I run it as is, I get AttributeError: module 'run' has no attribute 'main'. If I save above 162 to run.py and the rest to foo.py and run foo, it says AttributeError: module 'run' has no attribute 'main'. I am new enough at python to know I'm missing something important.
Posts: 5,151
Threads: 396
Joined: Sep 2016
Aug-27-2019, 05:24 PM
(This post was last modified: Aug-27-2019, 05:24 PM by metulburr.)
Sorry, my fault. That is the example code that works with the embedded demo. There is code in there that expects it to be ran in their demo (the run module, the default video path, etc.)
To split it out on its own you have to not use their run module. That is only for running inside their demo program. Which i modified at the end there for you. The HTML part is just for the demo to show text. You dont need that at all.
The following is that example modified to run by itself.
#!/usr/bin/env python
import wx
import wx.media
import os
#----------------------------------------------------------------------
class StaticText(wx.StaticText):
"""
A StaticText that only updates the label if it has changed, to
help reduce potential flicker since these controls would be
updated very frequently otherwise.
"""
def SetLabel(self, label):
if label != self.GetLabel():
wx.StaticText.SetLabel(self, label)
#----------------------------------------------------------------------
class TestPanel(wx.Panel):
def __init__(self, parent, log):
self.log = log
wx.Panel.__init__(self, parent, -1,
style=wx.TAB_TRAVERSAL|wx.CLIP_CHILDREN)
# Create some controls
try:
backend = "" # let MediaCtrl choose default backend
#backend=wx.media.MEDIABACKEND_DIRECTSHOW
#backend=wx.media.MEDIABACKEND_WMP10
self.mc = wx.media.MediaCtrl()
ok = self.mc.Create(self, style=wx.SIMPLE_BORDER,
szBackend=backend)
if not ok:
raise NotImplementedError
except NotImplementedError:
self.Destroy()
raise
# the following event is not sent with the Windows default backend
# MEDIABACKEND_DIRECTSHOW
# choose above e.g. MEDIABACKEND_WMP10 if this is a problem for you
self.Bind(wx.media.EVT_MEDIA_LOADED, self.OnMediaLoaded)
btn1 = wx.Button(self, -1, "Load File")
self.Bind(wx.EVT_BUTTON, self.OnLoadFile, btn1)
btn2 = wx.Button(self, -1, "Play")
self.Bind(wx.EVT_BUTTON, self.OnPlay, btn2)
self.playBtn = btn2
btn3 = wx.Button(self, -1, "Pause")
self.Bind(wx.EVT_BUTTON, self.OnPause, btn3)
btn4 = wx.Button(self, -1, "Stop")
self.Bind(wx.EVT_BUTTON, self.OnStop, btn4)
slider = wx.Slider(self, -1, 0, 0, 10)
self.slider = slider
slider.SetMinSize((150, -1))
self.Bind(wx.EVT_SLIDER, self.OnSeek, slider)
self.st_size = StaticText(self, -1, size=(100,-1))
self.st_len = StaticText(self, -1, size=(100,-1))
self.st_pos = StaticText(self, -1, size=(100,-1))
# setup the layout
sizer = wx.GridBagSizer(5,5)
sizer.Add(self.mc, (1,1), span=(5,1))#, flag=wx.EXPAND)
sizer.Add(btn1, (1,3))
sizer.Add(btn2, (2,3))
sizer.Add(btn3, (3,3))
sizer.Add(btn4, (4,3))
sizer.Add(slider, (6,1), flag=wx.EXPAND)
sizer.Add(self.st_size, (1, 5))
sizer.Add(self.st_len, (2, 5))
sizer.Add(self.st_pos, (3, 5))
self.SetSizer(sizer)
#self.DoLoadFile(os.path.abspath("data/testmovie.mpg"))
#wx.CallAfter(self.DoLoadFile, os.path.abspath("data/testmovie.mpg"))
self.timer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.OnTimer)
self.timer.Start(100)
def OnLoadFile(self, evt):
dlg = wx.FileDialog(self, message="Choose a media file",
defaultDir=os.getcwd(), defaultFile="",
style=wx.FD_OPEN | wx.FD_CHANGE_DIR )
if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath()
self.DoLoadFile(path)
dlg.Destroy()
def DoLoadFile(self, path):
if not self.mc.Load(path):
wx.MessageBox("Unable to load %s: Unsupported format?" % path,
"ERROR",
wx.ICON_ERROR | wx.OK)
self.playBtn.Disable()
else:
self.mc.SetInitialSize()
self.GetSizer().Layout()
self.slider.SetRange(0, self.mc.Length())
self.playBtn.Enable()
def OnMediaLoaded(self, evt):
self.playBtn.Enable()
def OnPlay(self, evt):
if not self.mc.Play():
wx.MessageBox("Unable to Play media : Unsupported format?",
"ERROR",
wx.ICON_ERROR | wx.OK)
else:
self.mc.SetInitialSize()
self.GetSizer().Layout()
self.slider.SetRange(0, self.mc.Length())
def OnPause(self, evt):
self.mc.Pause()
def OnStop(self, evt):
self.mc.Stop()
def OnSeek(self, evt):
offset = self.slider.GetValue()
self.mc.Seek(offset)
def OnTimer(self, evt):
offset = self.mc.Tell()
self.slider.SetValue(offset)
self.st_size.SetLabel('size: %s' % self.mc.GetBestSize())
self.st_len.SetLabel('length: %d seconds' % (self.mc.Length()/1000))
self.st_pos.SetLabel('position: %d' % offset)
def ShutdownDemo(self):
self.timer.Stop()
del self.timer
#----------------------------------------------------------------------
def runTest(frame, nb, log):
try:
win = TestPanel(nb, log)
return win
except NotImplementedError:
from wx.lib.msgpanel import MessagePanel
win = MessagePanel(nb, 'wx.MediaCtrl is not available on this platform.',
'Sorry', wx.ICON_WARNING)
return win
#---------------------------------------------------------------------
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,parent=None, title="Video Player")
self._my_sizer = wx.BoxSizer(wx.VERTICAL)
panel1 = TestPanel(self, None)
self._my_sizer.Add(panel1, 1, wx.EXPAND)
self.SetSizer(self._my_sizer)
self.Fit()
self.Show()
if __name__ == '__main__':
import sys,os
#import run
#run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
app = wx.App()
top = MyFrame()
top.Show()
app.MainLoop() The modifications are as following:
from this
if __name__ == '__main__':
import sys,os
import run
run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) to this:
if __name__ == '__main__':
import sys,os
#import run
#run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
app = wx.App()
top = MyFrame()
top.Show()
app.MainLoop() removed the run module and setup an app main loop for it to run on its own. As well as created a frame class. This was required by the way the TestPanel was written in the demo code. Mainly for sending the frame to it.
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,parent=None, title="Video Player")
self._my_sizer = wx.BoxSizer(wx.VERTICAL)
panel1 = TestPanel(self, None)
self._my_sizer.Add(panel1, 1, wx.EXPAND)
self.SetSizer(self._my_sizer)
self.Fit()
self.Show() and changed this
#self.DoLoadFile(os.path.abspath("data/testmovie.mpg"))
wx.CallAfter(self.DoLoadFile, os.path.abspath("data/testmovie.mpg")) to this
#self.DoLoadFile(os.path.abspath("data/testmovie.mpg"))
#wx.CallAfter(self.DoLoadFile, os.path.abspath("data/testmovie.mpg")) to stop it from loading a video in the demo that does not exist if you dont have the demo ir are running it directly.
Recommended Tutorials:
Posts: 6
Threads: 1
Joined: Aug 2019
Excellent. The demo does work. I'll try adapting my GUI. Thanks.
Posts: 6
Threads: 1
Joined: Aug 2019
I've adapted my project to PyQt5 but have failed on getting the video to work.
This version fails to "connect() failed between Thread.changePixmap[QImage] and setImage()" in line 76. I've done some other tests where I managed to get it to connect, but it failed with "Thread destroyed before being terminated" (or maybe the opposite of that).
from PyQt5 import QtCore, QtGui, QtWidgets
from pyqt_led import Led
from PyQt5.QtCore import pyqtSlot
import cv2
import socket
import time
class Thread(QtCore.QThread):
changePixmap = QtCore.pyqtSignal(QtGui.QImage)
def run(self):
cap = cv2.VideoCapture('http://192.168.1.1:8080/?action=stream')
while True:
ret, frame = cap.read()
if ret:
# https://stackoverflow.com/a/55468544/6622587
rgbImage = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
h, w, ch = rgbImage.shape
bytesPerLine = ch * w
convertToQtFormat = QtGui.QImage(rgbImage.data, w, h, bytesPerLine, QtGui.QImage.Format_RGB888)
p = convertToQtFormat.scaled(640, 480)
self.changePixmap.emit(p)
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
super().__init__()
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 754)
font = QtGui.QFont()
font.setPointSize(10)
MainWindow.setFont(font)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.vlabel = QtWidgets.QLabel(self.centralwidget)
self.vlabel.setGeometry(QtCore.QRect(80, 40, 640, 480))
self.vlabel.setObjectName("vlabel")
self.vlabel.move(80, 40)
self.vlabel.resize(640, 480)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 23))
self.menubar.setObjectName("menubar")
self.menuCar_Control_v = QtWidgets.QMenu(self.menubar)
self.menuCar_Control_v.setObjectName("menuCar_Control_v")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.menubar.addAction(self.menuCar_Control_v.menuAction())
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.initUI()
@pyqtSlot(QtGui.QImage)
def setImage(self, image):
self.vlabel.setPixmap(QPixmap.fromImage(image))
def initUI(self):
th = Thread()
th.changePixmap.connect(self.setImage)
th.start()
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.vlabel.setText(_translate("MainWindow", "VLable"))
self.menuCar_Control_v.setTitle(_translate("MainWindow", "Car Control v"))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Posts: 211
Threads: 4
Joined: May 2019
Sep-04-2019, 02:34 PM
(This post was last modified: Sep-04-2019, 02:34 PM by Denni.)
Okay first off you are improperly using QThread probably because the documentation you used is in error (a known issue that has not been resolved yet) as a QThread should not be sub-classed due to its the nature of how it functions - sub-classing it as you did and/or the documentation describes creates a situation that creates unexpected results.
Still with that said - I cannot help you with the QThread issue here but I will state that I know that CV2 does not need to use QThread if you want more help with CV2 and have Discord I can invite you to the Python server (just PM me) as one of my students is using the CV2 module and has already implemented that functionality via a Class
That being said though I will share with you more properly set up program that can handle adding in these features.
In your program you do several unnecessary things:
1) Reference sys.argv but never use it
2) Implement the complexity of the RetranslateUI functionality but not really need it
Which includes: Using retranslateUi function, setting ObjectNames and using QMetaObject
Did a few things improperly and/or nonfunctionally
1) Defined a Menu Action that is never defined
2) Used the Coordinate System instead of PyQt Layout system
3) Used QRect this is rarely if ever needed when using the Layout system
4) Import the entirety of QtCore, QtGui, and QtWidgets only import what you need
-- you then also imported an aspect of QtCore which would have been redundant
5) Import socket and time but I did not see them being used anywhere
So to help here is a redesign of the code that implements everything but QThread and CV2 but gives you a solid template to go forward from should you want to use this to implement your CV2 and/or QThread(if determined you actually need it) from as well as implement additional changes to your Center Panel -- note some of what I have done is pure style but most of my style is meant to make things more easily readable later on since I might have to come back to in a month or two after I have completely forgotten what I was doing with it and believe me that does happen.
from sys import exit as sysExit
#from PyQt5.QtCore import ??
from PyQt5.QtGui import QFont
# QtWidgets Container Objects
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QDockWidget
from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QStatusBar, QMenuBar
# QtWidgets Action Objects
from PyQt5.QtWidgets import QAction
# Self Contained Encapsulated MenuToolBar Class
class MenuToolBar(QDockWidget):
def __init__(self, MainWin):
QDockWidget.__init__(self)
self.MainWin = MainWin
self.MainMenu = MainWin.menuBar()
# Create Menu Action Reference Array in case you use a Tool Bar later
self.MenuActRef = {'BgnCarVAct':0,
'EndCarVAct':0}
# ******* Create the Car V Menu *******
self.CarVMenu = self.MainMenu.addMenu('Car Control V')
# ******* Create Car V Menu Items *******
self.BgnCarVAct = QAction('&Start', self)
self.BgnCarVAct.setShortcut("Ctrl+S")
self.BgnCarVAct.setStatusTip('Start Car V')
self.BgnCarVAct.triggered.connect(self.StartCarV)
self.MenuActRef['BgnCarVAct'] = self.BgnCarVAct
self.EndCarVAct = QAction('&End', self)
self.EndCarVAct.setShortcut("Ctrl+E")
self.EndCarVAct.setStatusTip('End Car V')
self.EndCarVAct.triggered.connect(self.StopCarV)
self.MenuActRef['EndCarVAct'] = self.EndCarVAct
# ******* Setup the File Menu *******
self.CarVMenu.addAction(self.BgnCarVAct)
self.CarVMenu.addSeparator()
self.CarVMenu.addAction(self.EndCarVAct)
# These call back to the Main Window because this class is only about
# controlling the functionality of the Menu and Tool Bar and could care
# less about what it actions actual do or do not do
def StartCarV(self):
self.MainWin.StartCarV()
def StopCarV(self):
self.MainWin.StopCarV()
class CenterPanel(QWidget):
def __init__(self, parent):
QWidget.__init__(self)
self.MyParent = parent
# Declare Font Aspects just to spice it up a bit
font = QFont()
font.setPointSize(14)
font.setBold(True)
self.lblCarV = QLabel()
self.lblCarV.setText('Car V')
self.lblCarV.setFont(font)
self.lblActions = QLabel()
# Centering this within the Center Pane
HBox1 = QHBoxLayout()
HBox1.addStretch(1)
HBox1.addWidget(self.lblCarV)
HBox1.addStretch(1)
HBox2 = QHBoxLayout()
HBox2.addStretch(1)
HBox2.addWidget(self.lblActions)
HBox2.addStretch(1)
VBox = QVBoxLayout()
VBox.addStretch(1)
VBox.addLayout(HBox1)
VBox.addWidget(QLabel(' ')) #An Invisible Spacer
VBox.addLayout(HBox2)
VBox.addStretch(1)
self.setLayout(VBox)
class UI_MainWindow(QMainWindow):
def __init__(self):
super(UI_MainWindow, self).__init__()
# Declare Font Aspects
font = QFont()
font.setPointSize(10)
# Declare Main Window Aspects
self.resize(800, 754)
self.setWindowTitle("Main Window")
self.setFont(font)
# Define your Center Pane via a Class
self.CenterPane = CenterPanel(self)
self.setCentralWidget(self.CenterPane)
# Define your Menu/Tool Bar via a Class
self.MainMenu = MenuToolBar(self)
# Define your Status Bar class not needed for this one
self.StatusBar = QStatusBar(self)
self.setStatusBar(self.StatusBar)
def StartCarV(self):
self.CenterPane.lblActions.setText('Started Car V')
# Use this to Launch your, I presume, continuous Thread
# which btw can be a self-contained encapsulated Class without
# needing to subclass QThread which one should not do
def StopCarV(self):
self.CenterPane.lblActions.setText('Stopped Car V')
# Use this to Terminate your, again I presume, continuous Thread
if __name__ == "__main__":
MainThred = QApplication([])
MainGui = UI_MainWindow()
MainGui.show()
sysExit(MainThred.exec_())
Posts: 6
Threads: 1
Joined: Aug 2019
Thanks very much Denni, I will look this over and try to figure out the differences. FWIW, I used the Qt Designer for the code and then stripped all of the buttons and boxes that weren't relevant to the video. Some of the things like the menu seem to be from the designer.
I added the thread as "the solution recommended on other posts." They could be outdated. As a standalone app, the sample I took the thread from works fine. I could never get it integrated into my code. I'm all for the simplest solution so lowing the thread isn't an issue.
I have never used "Discord".
|