回答編集履歴

2 teamiklの指摘あり修正

taizan205

taizan205 score 471

2020/05/23 15:45  投稿

※投稿後追記:上のteamiklさんの回答の方が正確なため、teamiklさんの回答&コードを参考にされた方がよいと思います。
修正してみました。
⓵ canvasの追加について、`frame = QWidget(self)` で定義されたframeが最終的にwidgetに配置されていません。
この部分は
```
 # 説明に不要な部分は省略しています
 def initUI(self):
       frame = QWidget(self)
       self.canvas = Canvas(frame)
       
       self.predict_canvas = PredictCanvas(frame)
       hbox = QHBoxLayout(frame)
       hbox.addWidget(self.canvas)
       hbox.addWidget(self.predict_canvas)
       hbox.addWidget(tools)
       frame.setLayout(hbox)     # ここが必要
       return frame
```
というように、boxレイアウトを登録したwidgetを返してあげる必要があります。
※投稿後追記:
動くコードにはなっていますが、当方の知識不足の点が多々あると思います。上のteamiklさんの回答の方が正確なため、teamiklさんの回答&コードを参考にしてください。
① 削除
② PredictCanvasで下記のようにpaintEventを実装してあげないと、描画更新されません。
```
 def paintEvent(self, event):
   painter = QPainter(self)
   rect = event.rect()
   painter.drawImage(rect, self.image_predict, rect)
 
```
③ 元コードでは、ToolFrame(QWidget)で新たにPredictCanvasが生成されてしまっており、メインウィジェットのpredict(predict_canvas)が共有されていません。
下記のように、メインウィジェットのpredict_canvasをToolFrame(QWidget)が受け取れるように修正する必要があります。
呼び出し元
```
 def initUI(self):
       frame = QWidget(self)
       self.canvas = Canvas(frame)
       
       self.predict_canvas = PredictCanvas(frame)
       tools = ToolFrame(frame, canvas=self.predict_canvas) #修正部分 メインウィジェットのpredict_canvsを引数として呼び出す。
```
呼び出し先
```
class ToolFrame(QWidget):
   def __init__(self, parent=None, canvas = None): 修正
       super().__init__(parent)
       self.predict = canvas # 修正 メインウィジェットのpredict_canvasを受け取る
       self.initUI()
```
--------------------------
以上をまとめた全体コードが、下記のコードです。
```
import sys
from PyQt5.QtWidgets import (
 QWidget, QApplication, QMainWindow, QAction,
 QFileDialog, QColorDialog, QInputDialog, QPushButton,QHBoxLayout, QVBoxLayout, QApplication)
from PyQt5.QtGui import QPainter, QImage, QPen, qRgb
from PyQt5.QtCore import Qt, QPoint, QRect, QSize, QDir
from collections import deque
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QIcon
class MainWindow(QMainWindow):
 def __init__(self):
   super(MainWindow, self).__init__()
   self.setupUI()
   self.setCentralWidget(self.initUI())
   #クラスを呼び出す。
 def initUI(self):
       frame = QWidget(self)
       self.canvas = Canvas(frame)
       
       self.predict_canvas = PredictCanvas(frame)
       tools = ToolFrame(frame, canvas=self.predict_canvas)
       hbox = QHBoxLayout(frame)
       #vbox=QVBoxLayout()
       #vbox.addStretch(0)
       #vbox.addWidget(tools)
       #vbox.addWidget(predict)
       hbox.addWidget(self.canvas)
       hbox.addWidget(self.predict_canvas)
       hbox.addWidget(tools)
       frame.setLayout(hbox)
       # frame.setLayout(hbox) # 9行目前でQHBoxLayout(frame)というように親を指定しているため不要
       return frame
 def setupUI(self):
   menubar = self.menuBar()
   openAct = QAction('&Open', self)
   openAct.setShortcut('Ctrl+O')
   openAct.triggered.connect(self.openFile)
   exitAct = QAction('&Exit', self)
   exitAct.setShortcut('Ctrl+Q')
   exitAct.triggered.connect(self.close)
   saveAct = QAction('&Save', self)
   saveAct.setShortcut('Ctrl+S')
   saveAct.triggered.connect(self.saveFile)
   fileMenu = menubar.addMenu('&File')
   #fileMenu.addAction(resetAct)
   fileMenu.addAction(openAct)
   fileMenu.addAction(saveAct)
   fileMenu.addAction(exitAct)
   # 画像付き Pencolor selectするアクションオブジェクト作成
   selectColorAction = QAction( QIcon('sample/white-bear.png'),'Pen Color', self)
   selectColorAction.setShortcut('Ctrl+A')
   selectColorAction.triggered.connect(self.selectColor)
   self.toolbar = self.addToolBar('Pen Color')
   self.toolbar.addAction(selectColorAction)
   # ツールバー作成
   selectwidthAction = QAction( QIcon('sample/white-bear.png'),'Selecwidth', self)
   selectwidthAction.triggered.connect(self.selectWidth)
   self.toolbar = self.addToolBar('Width')
   self.toolbar.addAction(selectwidthAction)
   # ツールバー作成
   backAction = QAction( QIcon('sample/white-bear.png'),'Back', self)
   backAction.setShortcut('Ctrl+Z')
   backAction.triggered.connect(self.on_back)
   self.toolbar = self.addToolBar('Undo')
   self.toolbar.addAction(backAction)
   # ツールバー作成
   clearAction = QAction( QIcon('sample/white-bear.png'),'Clear', self)
   clearAction.setShortcut('Ctrl+B')
   clearAction.triggered.connect(self.on_reset)
   self.toolbar = self.addToolBar('Undo')
   self.toolbar.addAction(clearAction)
 @pyqtSlot()
 def on_back(self):
   print('PyQt5 button click')
   self.canvas.backImage()
 @pyqtSlot()
 def on_reset(self):
   self.canvas.resetImage()
 @pyqtSlot()
 def on_run(self):
   fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath())
   if fileName:
     self.predict_canvas.PredictImage(fileName)
     #self.canvas.openImage(fileName)
 def selectColor(self):
   newColor = QColorDialog.getColor(self.canvas.penColor())
   self.canvas.setPenColor(newColor)
 def selectWidth(self):
   newWidth, ok = QInputDialog.getInt(
     self, "select",
     "select pen width: ", self.canvas.penWidth(), 1, 100, 1)
   if ok:
     self.canvas.setPenWidth(newWidth)
 def openFile(self):
   fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath())
   if fileName:
     self.predict_canvas.PredictImage(fileName)
 def saveFile(self):
   path = QDir.currentPath()
   print(path)
   fileName, _ = QFileDialog.getSaveFileName(self, "Save as",path)
   fileName=fileName+'.png'
   if fileName:
     print(fileName)
     return self.canvas.saveImage(fileName)
   else:
     print("you couldnt save the file")
   return False
class ToolFrame(QWidget):
   def __init__(self, parent=None, canvas = None):
       super().__init__(parent)
       self.predict = canvas # PredictCanvas()
       self.initUI()
   def initUI(self):
       button_predict = QPushButton('predict', self)
       button_predict.clicked.connect(self.on_run)
       button_reset = QPushButton('Reset', self)
       vbox = QVBoxLayout(self)
       vbox.addStretch(1)
       vbox.addWidget(button_predict)
       vbox.addWidget(button_reset)
   @pyqtSlot()
   def on_run(self):
     fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath())
     if fileName:
       self.predict.PredictImage(fileName)
       #self.canvas.openImage(fileName)
class Canvas(QWidget):
 def __init__(self, parent = None):
   super(Canvas, self).__init__(parent)
   self.myPenWidth = 2
   self.myPenColor = Qt.black
   self.image = QImage()
   self.check = False
   self.back = deque(maxlen = 10)
   self.image = QImage(300, 300, QImage.Format_RGB32)
   self.image.fill(qRgb(255, 255, 255))
   #self.next = deque(maxlen = 10)
   # initUIはもう必要ないから消しておこうね
 def mousePressEvent(self, event):
   if event.button() == Qt.LeftButton:
     self.back.append(self.resizeImage(self.image, self.image.size()))
     self.lastPos = event.pos()
     self.check = True
 def mouseMoveEvent(self, event):
   if event.buttons() and Qt.LeftButton and self.check:
     self.drawLine(event.pos())
 def mouseReleaseEvent(self, event):
   if event.button() == Qt.LeftButton and self.check:
     self.drawLine(event.pos())
     self.check = False
 def drawLine(self, endPos):
   painter = QPainter(self.image)
   painter.setPen(
     QPen(self.myPenColor, self.myPenWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
   )
   painter.drawLine(self.lastPos, endPos)
   self.update()
   self.lastPos = QPoint(endPos)
 def paintEvent(self, event):
   painter = QPainter(self)
   rect = event.rect()
   painter.drawImage(rect, self.image, rect)
 def resizeEvent(self, event):
   if self.image.width() < self.width() or self.image.height() < self.height():
     changeWidth = max(self.width(), self.image.width())
     changeHeight = max(self.height(), self.image.height())
     self.image = self.resizeImage(self.image, QSize(changeWidth, changeHeight))
     self.update()
 def resizeImage(self, image, newSize):
   changeImage = QImage(newSize, QImage.Format_RGB32)
   changeImage.fill(qRgb(255, 255, 255))
   painter = QPainter(changeImage)
   painter.drawImage(QPoint(0, 0), image)
   return changeImage
 def saveImage(self, filename):
   if self.image.save(filename):
     return True
   else:
     return False
 def openImage(self, filename):
   image = QImage()
   if not image.load(filename):
     return False
   self.image = image
   self.update()
   return True
 def penColor(self):
   return self.myPenColor
 def penWidth(self):
   return self.myPenWidth
 def setPenColor(self, newColor):
   self.myPenColor = newColor
 def setPenWidth(self, newWidth):
   self.myPenWidth = newWidth
 def resetImage(self):
   self.image.fill(qRgb(255, 255, 255))
   self.update()
 def backImage(self):
   if self.back:
     back_ = self.back.pop()
     #self.next.append(back_)
     self.image = QImage(back_)
     self.update()
# teratail 264079 modified by taizan-hokuto
class PredictCanvas(QWidget):
 def __init__(self, parent = None):
   super(PredictCanvas, self).__init__(parent)
   self.image_predict = QImage()
   self.image_predict.fill(qRgb(255, 0, 255))
 def PredictImage(self, filename):
   image = QImage()
   if not image.load(filename):
     return False
   self.image_predict = image
   self.update()
   return True
 def paintEvent(self, event):
   painter = QPainter(self)
   rect = event.rect()
   painter.drawImage(rect, self.image_predict, rect)
 
def main():
 app = QApplication(sys.argv)
 ex = MainWindow()
 ex.setWindowTitle('Paint Tools')
 ex.setGeometry(50, 50, 1400, 800)
 ex.show()
 sys.exit(app.exec_())
if __name__ == '__main__':
 main()
```
1 追記

taizan205

taizan205 score 471

2020/05/23 15:26  投稿

いくつか修正すべき点があります。
## ⓵ Boxレイアウトのウィジェットに対する登録の欠如
canvasの追加について、`frame = QWidget(self)` で定義されたframeが最終的にwidgetに配置されていません。
※投稿後追記:上のteamiklさんの回答の方が正確なため、teamiklさんの回答&コードを参考にされた方がよいと思います。
修正してみました。
⓵ canvasの追加について、`frame = QWidget(self)` で定義されたframeが最終的にwidgetに配置されていません。
この部分は
```
 # 説明に不要な部分は省略しています
 def initUI(self):
       frame = QWidget(self)
       self.canvas = Canvas(frame)
       
       self.predict_canvas = PredictCanvas(frame)
       hbox = QHBoxLayout(frame)
       hbox.addWidget(self.canvas)
       hbox.addWidget(self.predict_canvas)
       hbox.addWidget(tools)
       frame.setLayout(hbox)     # ここが必要
       return frame
```
というように、boxレイアウトを登録したwidgetを返してあげる必要があります。
## ② PredictCanvasオブジェクトにおける再描画の欠如
PredictCanvasで下記のようにpaintEventを実装してあげないと、描画更新されません。
② PredictCanvasで下記のようにpaintEventを実装してあげないと、描画更新されません。
```
 def paintEvent(self, event):
   painter = QPainter(self)
   rect = event.rect()
   painter.drawImage(rect, self.image_predict, rect)
 
```
## ③ predict_canvasのコンテキストがオブジェクト間で共有されていない
元コードでは、ToolFrame(QWidget)で新たにPredictCanvasが生成されてしまっており、メインウィジェットのpredict(predict_canvas)が共有されていません。
③ 元コードでは、ToolFrame(QWidget)で新たにPredictCanvasが生成されてしまっており、メインウィジェットのpredict(predict_canvas)が共有されていません。
下記のように、メインウィジェットのpredict_canvasをToolFrame(QWidget)が受け取れるように修正する必要があります。
呼び出し元
```
 def initUI(self):
       frame = QWidget(self)
       self.canvas = Canvas(frame)
       
       self.predict_canvas = PredictCanvas(frame)
       tools = ToolFrame(frame, canvas=self.predict_canvas) #修正部分 メインウィジェットのpredict_canvsを引数として呼び出す。
```
呼び出し先
```
class ToolFrame(QWidget):
   def __init__(self, parent=None, canvas = None): 修正
       super().__init__(parent)
       self.predict = canvas # 修正 メインウィジェットのpredict_canvasを受け取る
       self.initUI()
```
 
 
--------------------------
--------------------------  
 
 
以上をまとめた全体コードが、下記のコードです。
```
import sys
from PyQt5.QtWidgets import (
 QWidget, QApplication, QMainWindow, QAction,
 QFileDialog, QColorDialog, QInputDialog, QPushButton,QHBoxLayout, QVBoxLayout, QApplication)
from PyQt5.QtGui import QPainter, QImage, QPen, qRgb
from PyQt5.QtCore import Qt, QPoint, QRect, QSize, QDir
from collections import deque
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtGui import QIcon
class MainWindow(QMainWindow):
 def __init__(self):
   super(MainWindow, self).__init__()
   self.setupUI()
   self.setCentralWidget(self.initUI())
   #クラスを呼び出す。
 def initUI(self):
       frame = QWidget(self)
       self.canvas = Canvas(frame)
       
       self.predict_canvas = PredictCanvas(frame)
       tools = ToolFrame(frame, canvas=self.predict_canvas)
       hbox = QHBoxLayout(frame)
       #vbox=QVBoxLayout()
       #vbox.addStretch(0)
       #vbox.addWidget(tools)
       #vbox.addWidget(predict)
       hbox.addWidget(self.canvas)
       hbox.addWidget(self.predict_canvas)
       hbox.addWidget(tools)
       frame.setLayout(hbox)
       return frame
 def setupUI(self):
   menubar = self.menuBar()
   openAct = QAction('&Open', self)
   openAct.setShortcut('Ctrl+O')
   openAct.triggered.connect(self.openFile)
   exitAct = QAction('&Exit', self)
   exitAct.setShortcut('Ctrl+Q')
   exitAct.triggered.connect(self.close)
   saveAct = QAction('&Save', self)
   saveAct.setShortcut('Ctrl+S')
   saveAct.triggered.connect(self.saveFile)
   fileMenu = menubar.addMenu('&File')
   #fileMenu.addAction(resetAct)
   fileMenu.addAction(openAct)
   fileMenu.addAction(saveAct)
   fileMenu.addAction(exitAct)
   # 画像付き Pencolor selectするアクションオブジェクト作成
   selectColorAction = QAction( QIcon('sample/white-bear.png'),'Pen Color', self)
   selectColorAction.setShortcut('Ctrl+A')
   selectColorAction.triggered.connect(self.selectColor)
   self.toolbar = self.addToolBar('Pen Color')
   self.toolbar.addAction(selectColorAction)
   # ツールバー作成
   selectwidthAction = QAction( QIcon('sample/white-bear.png'),'Selecwidth', self)
   selectwidthAction.triggered.connect(self.selectWidth)
   self.toolbar = self.addToolBar('Width')
   self.toolbar.addAction(selectwidthAction)
   # ツールバー作成
   backAction = QAction( QIcon('sample/white-bear.png'),'Back', self)
   backAction.setShortcut('Ctrl+Z')
   backAction.triggered.connect(self.on_back)
   self.toolbar = self.addToolBar('Undo')
   self.toolbar.addAction(backAction)
   # ツールバー作成
   clearAction = QAction( QIcon('sample/white-bear.png'),'Clear', self)
   clearAction.setShortcut('Ctrl+B')
   clearAction.triggered.connect(self.on_reset)
   self.toolbar = self.addToolBar('Undo')
   self.toolbar.addAction(clearAction)
 @pyqtSlot()
 def on_back(self):
   print('PyQt5 button click')
   self.canvas.backImage()
 @pyqtSlot()
 def on_reset(self):
   self.canvas.resetImage()
 @pyqtSlot()
 def on_run(self):
   fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath())
   if fileName:
     self.predict_canvas.PredictImage(fileName)
     #self.canvas.openImage(fileName)
 def selectColor(self):
   newColor = QColorDialog.getColor(self.canvas.penColor())
   self.canvas.setPenColor(newColor)
 def selectWidth(self):
   newWidth, ok = QInputDialog.getInt(
     self, "select",
     "select pen width: ", self.canvas.penWidth(), 1, 100, 1)
   if ok:
     self.canvas.setPenWidth(newWidth)
 def openFile(self):
   fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath())
   if fileName:
     self.predict_canvas.PredictImage(fileName)
 def saveFile(self):
   path = QDir.currentPath()
   print(path)
   fileName, _ = QFileDialog.getSaveFileName(self, "Save as",path)
   fileName=fileName+'.png'
   if fileName:
     print(fileName)
     return self.canvas.saveImage(fileName)
   else:
     print("you couldnt save the file")
   return False
class ToolFrame(QWidget):
   def __init__(self, parent=None, canvas = None):
       super().__init__(parent)
       self.predict = canvas # PredictCanvas()
       self.initUI()
   def initUI(self):
       button_predict = QPushButton('predict', self)
       button_predict.clicked.connect(self.on_run)
       button_reset = QPushButton('Reset', self)
       vbox = QVBoxLayout(self)
       vbox.addStretch(1)
       vbox.addWidget(button_predict)
       vbox.addWidget(button_reset)
   @pyqtSlot()
   def on_run(self):
     fileName, _ = QFileDialog.getOpenFileName(self, "Open File", QDir.currentPath())
     if fileName:
       self.predict.PredictImage(fileName)
       #self.canvas.openImage(fileName)
class Canvas(QWidget):
 def __init__(self, parent = None):
   super(Canvas, self).__init__(parent)
   self.myPenWidth = 2
   self.myPenColor = Qt.black
   self.image = QImage()
   self.check = False
   self.back = deque(maxlen = 10)
   self.image = QImage(300, 300, QImage.Format_RGB32)
   self.image.fill(qRgb(255, 255, 255))
   #self.next = deque(maxlen = 10)
   # initUIはもう必要ないから消しておこうね
 def mousePressEvent(self, event):
   if event.button() == Qt.LeftButton:
     self.back.append(self.resizeImage(self.image, self.image.size()))
     self.lastPos = event.pos()
     self.check = True
 def mouseMoveEvent(self, event):
   if event.buttons() and Qt.LeftButton and self.check:
     self.drawLine(event.pos())
 def mouseReleaseEvent(self, event):
   if event.button() == Qt.LeftButton and self.check:
     self.drawLine(event.pos())
     self.check = False
 def drawLine(self, endPos):
   painter = QPainter(self.image)
   painter.setPen(
     QPen(self.myPenColor, self.myPenWidth, Qt.SolidLine, Qt.RoundCap, Qt.RoundJoin)
   )
   painter.drawLine(self.lastPos, endPos)
   self.update()
   self.lastPos = QPoint(endPos)
 def paintEvent(self, event):
   painter = QPainter(self)
   rect = event.rect()
   painter.drawImage(rect, self.image, rect)
 def resizeEvent(self, event):
   if self.image.width() < self.width() or self.image.height() < self.height():
     changeWidth = max(self.width(), self.image.width())
     changeHeight = max(self.height(), self.image.height())
     self.image = self.resizeImage(self.image, QSize(changeWidth, changeHeight))
     self.update()
 def resizeImage(self, image, newSize):
   changeImage = QImage(newSize, QImage.Format_RGB32)
   changeImage.fill(qRgb(255, 255, 255))
   painter = QPainter(changeImage)
   painter.drawImage(QPoint(0, 0), image)
   return changeImage
 def saveImage(self, filename):
   if self.image.save(filename):
     return True
   else:
     return False
 def openImage(self, filename):
   image = QImage()
   if not image.load(filename):
     return False
   self.image = image
   self.update()
   return True
 def penColor(self):
   return self.myPenColor
 def penWidth(self):
   return self.myPenWidth
 def setPenColor(self, newColor):
   self.myPenColor = newColor
 def setPenWidth(self, newWidth):
   self.myPenWidth = newWidth
 def resetImage(self):
   self.image.fill(qRgb(255, 255, 255))
   self.update()
 def backImage(self):
   if self.back:
     back_ = self.back.pop()
     #self.next.append(back_)
     self.image = QImage(back_)
     self.update()
# teratail 264079 modified by taizan-hokuto
class PredictCanvas(QWidget):
 def __init__(self, parent = None):
   super(PredictCanvas, self).__init__(parent)
   self.image_predict = QImage()
   self.image_predict.fill(qRgb(255, 0, 255))
 def PredictImage(self, filename):
   image = QImage()
   if not image.load(filename):
     return False
   self.image_predict = image
   self.update()
   return True
 def paintEvent(self, event):
   painter = QPainter(self)
   rect = event.rect()
   painter.drawImage(rect, self.image_predict, rect)
 
def main():
 app = QApplication(sys.argv)
 ex = MainWindow()
 ex.setWindowTitle('Paint Tools')
 ex.setGeometry(50, 50, 1400, 800)
 ex.show()
 sys.exit(app.exec_())
if __name__ == '__main__':
 main()
```

思考するエンジニアのためのQ&Aサイト「teratail」について詳しく知る