質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.06%

[PyQt-pyhton]同じwindow上でレイヤーを分けて表示したい

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 207

Kenza

score 18

PyQt5-pythonを用いて、簡単なペイントツールを作成しています。

現状では描画領域(canvas)上にボタンが配置されているのですが、描画領域とボタンを分けて表示したいです。
イメージでいうと下図のようなレイアウトになるのが理想です。同じwindow内に描画領域とツール領域を分別して表示するにはどうすればよいでしょうか?よろしくお願いいたします。

環境:windows10

イメージ説明

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

class MainWindow(QMainWindow):
  def __init__(self):
    super(MainWindow, self).__init__()
    # Canvasクラスを呼び出すよ。
    self.canvas = Canvas()
    # 呼び出したら箱に入れてあげようね。そうしないと動いてくれないよ。
    self.setCentralWidget(self.canvas)

    self.initUI()

  def initUI(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)

    resetAct = QAction('&Reset', self)
    resetAct.triggered.connect(self.canvas.resetImage)


    fileMenu = menubar.addMenu('&File')
    fileMenu.addAction(resetAct)
    fileMenu.addAction(openAct)
    fileMenu.addAction(saveAct)
    fileMenu.addAction(exitAct)

    selectColorAct = QAction('&Pen Color', self)
    selectColorAct.triggered.connect(self.selectColor)

    selectWidthAct = QAction('&Pen Width', self)
    selectWidthAct.triggered.connect(self.selectWidth)

    #backAct = QAction('&Back', self)
    #backAct.setShortcut('Ctrl+Z')
    #backAct.triggered.connect(self.canvas.backImage)

    #nextAct = QAction('&Next', self)
    #nextAct.setShortcut('Ctrl+Y')
    #nextAct.triggered.connect(self.canvas.nextImage)

    penColorMenu = menubar.addMenu('&Pen Color')
    penColorMenu.addAction(selectColorAct)
    penWidthMenu = menubar.addMenu('&Pen Width')
    penWidthMenu.addAction(selectWidthAct)
    backMenu = menubar.addMenu('&Back')
    #backMenu.addAction(backAct)
    #nextMenu = menubar.addMenu('&Next')
    #nextMenu.addAction(nextAct)

    #back buttonの定義
    button_back=QPushButton('Back',self)
    button_back.clicked.connect(self.on_back)


    #rest buttonの定義
    button_reset=QPushButton('Reset',self)
    button_reset.clicked.connect(self.on_reset)


    hbox=QHBoxLayout()
    hbox.addStretch(1)
    hbox.addWidget(button_back)
    hbox.addWidget(button_reset)
    # 垂直なボックスを作成
    vbox = QVBoxLayout()
    # 垂直方向に伸縮可能なスペースを作る
    vbox.addStretch(1)
    # 右下にボタンが移る
    vbox.addLayout(hbox)
    # 画面に上で設定したレイアウトを加える
    self.setLayout(vbox) 


    self.setGeometry(300, 300, 1000, 500)
    self.setWindowTitle("MainWindow")
    self.show()

  @pyqtSlot()
  def on_back(self):
    print('PyQt5 button click')
    self.canvas.backImage()

  @pyqtSlot()
  def on_reset(self):
    self.canvas.resetImage()

  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.canvas.openImage(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 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.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()

  #def nextImage(self):
    #if self.next:
      #next_ = self.next.pop()
      #self.back.append(next_)
      #self.image = QImage(next_)
      #self.update()

if __name__ == '__main__':
  # 動け~
  app = QApplication(sys.argv)
  # ここがCanvasのままだと何も表示されないよ。気を付けようね
  ex = MainWindow()
  sys.exit(app.exec_())
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

問題の原因: レイアウトの競合

  • setCentralWidget に Canvas を設定
  • setLayout でボタン類を設置

という二通りの方法でウィジェットを配置している為、このような警告メッセージが出てると思います。

QWidget::stLayout: Attempting to set QLayout "" on QMainWIndow "", 
which already has a layout


解決策: 

setCentralWidget(canvas)を辞めて、
ボタン類とキャンバスの親となるウィジェットを作り、
キャンバスとボタン類を共通のレイアウトに配置するようにします。

※ コードは概要のみ

MainWindow
   - QWidget <-- 共通の親を作る
       - Canvas
       - QPushButton 等
# "self" は、class MainWindow 内を想定

frame = QWidget()
canvas = Canvas()
button = QPushButton("TEST")

hbox = QHBoxLayout(frame) # <- 親を指定すると、frame.setLayout(hbox) を省略できます
hbox.addWidget(canvas)
hbox.addWidget(button)

self.setCentralWidget(frame)

他の方法: 共通の親ウィジェットを作らない場合は、setCentralWidget を使いませんが、
共通のレイアウトに Canvas と ボタン類を入れる点は同じです。

canvas = Canvas()
button = QPushButton("TEST")

hbox = QHBoxLayout(self)
hbox.addWidget(canvas)
hbox.addWidget(button)

self.setLayout(hbox)

遅れましたが一応投稿。レイアウト表示に焦点を絞るために、
キャンバスの実装やイベント類のコードは省いてます。ブラウザ内で実行確認可能です。

source on repl.it

イメージ説明

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2020/05/22 20:59

    ソース見れました。ほぼ修正はできてそうな感じですね、
    表示されない原因は show() が何処かに行ってしまったためです。

    ex = MainWindow()の後に ex.show() を呼び出してみてください。

    キャンセル

  • 2020/05/22 21:00

    >show() が何処かに行って

    savefile() メソッドの中に一連のコードがありました。
    これを適切な位置に移動すると直るはずです。

    キャンセル

  • 2020/05/22 21:42

    できました!!!!
    teamiklさんのおかげで、解決することができました!
    ありがとうございました!

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.06%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る