Hello World / plɹoM ollǝH

Programmers Live in Vain

PySide 編集可能なQTreeWidgetを作る

f:id:dungeonneko:20170622151448g:plain

import sys
from PySide import QtCore, QtGui


class MyTreeWidget(QtGui.QTreeWidget):
    def __init__(self):
        super().__init__()
        self.setHeaderHidden(True)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.contextMenuRequested)

        # アイテムの移動を有効にする
        self.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
        self.setDragEnabled(True)
        self.viewport().setAcceptDrops(True)

        # 挿入位置カーソルを表示する
        self.setDropIndicatorShown(True)

    def contextMenuRequested(self, pos):
        """ コンテキストメニュー
        """
        menu = QtGui.QMenu()
        item = self.itemAt(pos)
        if item:
            action = QtGui.QAction('Delete Item', self)
            action.triggered.connect(lambda: self.removeItem(item))
            menu.addAction(action)
        else:
            action = QtGui.QAction('New Item', self)
            action.triggered.connect(self.createNewItem)
            menu.addAction(action)
        menu.exec_(self.mapToGlobal(pos))

    def createNewItem(self):
        """ 新規項目
        """
        item = QtGui.QTreeWidgetItem()
        item.setText(0, 'Item')

        # アイテムを編集可能にする
        item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)

        self.addTopLevelItem(item)

    def removeItem(self, item):
        """ 項目削除
        """
        parent = item.parent()
        if parent:
            parent.takeChild(parent.indexOfChild(item))
        else:
            self.takeTopLevelItem(self.indexOfTopLevelItem(item))


app = QtGui.QApplication(sys.argv)
tr = MyTreeWidget()
tr.show()
sys.exit(app.exec_())

PySide movable and resizable widget without frame

f:id:dungeonneko:20170619180539g:plain

import sys, enum
from PySide import QtCore, QtGui

class Item(QtGui.QLabel):
    Manipilate = enum.Enum('Manipilate', 'none move resize_l resize_r')
    
    def __init__(self, text):
        super().__init__(text)
        self.setStyleSheet('background-color: white; border: 1px solid black; padding: 4px;')
        self.resize(128, 64)
        self._mani = Item.Manipilate.none
        self._offset = QtCore.QPoint(0, 0)
        self._rect = self.rect()
        self.setMouseTracking(True)

    def mousePressEvent(self, event):
        pos = event.pos()
        if self._mani == Item.Manipilate.none:
            self._mani = self.get_manipulation(pos)
        self._offset = event.pos()
        self._rect = self.geometry()

    def mouseReleaseEvent(self, event):
        self._mani = Item.Manipilate.none

    def mouseMoveEvent(self, event):
        pos = event.pos()
        if self._mani == Item.Manipilate.none:
            self.setCursor({
                Item.Manipilate.none: QtCore.Qt.ArrowCursor,
                Item.Manipilate.move: QtCore.Qt.ArrowCursor,
                Item.Manipilate.resize_l: QtCore.Qt.SizeHorCursor,
                Item.Manipilate.resize_r: QtCore.Qt.SizeHorCursor
            }[self.get_manipulation(pos)])
        elif self._mani == Item.Manipilate.move:
            self.move(self.mapToParent(pos - self._offset))
        elif self._mani == Item.Manipilate.resize_l:
            sub = pos - self._offset
            self.setGeometry(self._rect.x() + sub.x(), self._rect.y(), self._rect.width() - sub.x(), self._rect.height())
            self._rect = self.geometry()
        elif self._mani == Item.Manipilate.resize_r:
            sub = pos - self._offset
            self.setGeometry(self._rect.x(), self._rect.y(), self._rect.width() + sub.x(), self._rect.height())
        self.update()

    def get_manipulation(self, pos):
        if pos.x() < 8:
            return Item.Manipilate.resize_l
        if pos.x() > (self.width() - 8):
            return Item.Manipilate.resize_r
        else:
            return Item.Manipilate.move

app = QtGui.QApplication(sys.argv)
widget = QtGui.QWidget()
widget.setFixedSize(640, 480)
item = Item('item')
item.setParent(widget)
item.move(0, 0)
widget.show()
sys.exit(app.exec_())

PySide Progressbarを表示してみる

QProgressDialogを使うと一番簡単であります

f:id:dungeonneko:20170612143019g:plain

import sys
from PySide import QtGui

app = QtGui.QApplication(sys.argv)
prog = QtGui.QProgressDialog('何か処理しています...', 'キャンセル', 0, 100, None, 0)
prog.show()
prog.setValue(50)
sys.exit(app.exec_())
キャンセルボタンなくしたい

コンストラクタのキャンセルボタンのテキストにNoneを指定

QtGui.QProgressDialog('何か処理しています...', None, 0, 100, None, 0)
ウインドウのボタンを消したい

コンストラクタの最後の引数でウインドウフラグを指定

QtGui.QProgressDialog('何か処理しています...', 'キャンセル', 0, 100, None,
    QtCore.Qt.Window | QtCore.Qt.WindowTitleHint | QtCore.Qt.CustomizeWindowHint)
リサイズなくしたい

setFixedSizeを呼ぶ

prog.setFixedSize(prog.sizeHint())
Marqueeスタイル(進捗表示しないで処理の終了だけ待つやつ)にしたい

f:id:dungeonneko:20170612153620g:plain

プログレスバーの範囲を 0 ~ 0 にする

QtGui.QProgressDialog('何か処理しています...', 'キャンセル', 0, 0, None, 0)

バー横の空白が残る場合は進捗表示テキストをセンター寄せすればいい

prog.setStyleSheet('QProgressBar {text-align: center;}')

QScrollAreaに固定されたヘッダーを表示する

f:id:dungeonneko:20170605143912g:plain

import sys
from PySide import QtGui


class MyTable(QtGui.QWidget):
    def __init__(self):
        super().__init__()
        lo = QtGui.QHBoxLayout()
        lo.setContentsMargins(0, 0, 0, 0)
        lo.setSpacing(0)
        self.setLayout(lo)

        self._header = QtGui.QLabel('header')
        self._header.setStyleSheet('border-right: 1px solid black;\
            border-bottom: 1px solid black;\
            background-color: red;')
        self._header.setFixedSize(128, 32)
        lo.addWidget(self._header)

        for i in range(0, 8):
            item = QtGui.QLabel('cell {0}'.format(i))
            item.setStyleSheet('border-right: 1px solid black;\
                border-bottom: 1px solid black;\
                background-color: white;')
            item.setFixedSize(128, 32)
            lo.addWidget(item)

    def setHeaderPos(self, x, y):
        self._header.move(x, y)
        self._header.raise_()


app = QtGui.QApplication(sys.argv)
table = MyTable()
scroll = QtGui.QScrollArea()
scroll.setWidget(table)

def moveHeader():
    x = scroll.horizontalScrollBar().value()
    table.setHeaderPos(x, 0)

scroll.horizontalScrollBar().valueChanged.connect(moveHeader)
scroll.show()
sys.exit(app.exec_())

スライダーの移動量を固定する

f:id:dungeonneko:20170605163459g:plain

下記コードを追加する

def scrollControll(x):
    x = int(x / 128) * 128
    scroll.horizontalScrollBar().setValue(x)

scroll.horizontalScrollBar().setSingleStep(128)  # ボタン押された時の移動量
scroll.horizontalScrollBar().sliderMoved.connect(scrollControll)

PySide ドラッグでデータ受け渡し

f:id:dungeonneko:20170525143550g:plain

import sys
from PySide import QtCore, QtGui


# ドラッグ&ドロップで色を受け渡す
class WidgetItem(QtGui.QLabel):
    def __init__(self, color):
        super().__init__('')
        self._color = color
        self.setStyleSheet('border: 1px solid black;')
        self.setFixedSize(32, 32)
        self.setAcceptDrops(True)

    def paintEvent(self, event):
        self.changeColor()
        super().paintEvent(event)

    def changeColor(self):
        pal = QtGui.QPalette(self.palette())
        pal.setColor(QtGui.QPalette.Window, QtGui.QColor(self._color))
        self.setAutoFillBackground(True)
        self.setPalette(pal)

    def mousePressEvent(self, event):
        # ドラッグオブジェクトに色データ(テキスト)を埋め込んで実行
        drag = QtGui.QDrag(self)
        mime = QtCore.QMimeData()
        mime.setText(self._color)
        drag.setMimeData(mime)
        drag.exec_()

    def dragEnterEvent(self, event):
        event.accept()

    def dragMoveEvent(self, event):
        event.accept()

    def dropEvent(self, event):
        if event.mimeData().hasText():
            self._color = event.mimeData().text()
            self.changeColor()


app = QtGui.QApplication(sys.argv)

widget = QtGui.QWidget()
widget.setFixedSize(640, 240)
widget.setLayout(QtGui.QHBoxLayout())
layout = widget.layout()
layout.addWidget(WidgetItem('red'))
layout.addWidget(WidgetItem('blue'))
layout.addWidget(WidgetItem('green'))
layout.addWidget(WidgetItem('white'))
widget.show()
sys.exit(app.exec_())

PySide ドラッグでWidgetを移動させる

f:id:dungeonneko:20170525134629g:plain

import sys
from PySide import QtCore, QtGui

# ドラッグで動くWidget
class WidgetItem(QtGui.QLabel):
    def __init__(self):
        super().__init__('')
        self.setStyleSheet('border: 1px solid black; background-color: red;')
        self.setFixedSize(32, 32)
        self._drag = False
        self._offset = QtCore.QPoint(0, 0)

    def mousePressEvent(self, event):
        self._drag = True
        self._offset = event.pos()

    def mouseReleaseEvent(self, event):
        self._drag = False

    def mouseMoveEvent(self, event):
        if self._drag:
            self.move(self.mapToParent(event.pos() - self._offset))

app = QtGui.QApplication(sys.argv)

widget = QtGui.QWidget()
widget.setFixedSize(640, 480)
widget.setLayout(QtGui.QVBoxLayout())
layout = widget.layout()
item = WidgetItem()
layout.addWidget(item)
widget.show()
sys.exit(app.exec_())

Pythonゲームプログラミング #9 スクロール

gist消してしまったので
代わりにこのリポジトリを参考にしてください

#0 環境構築
#1 メインループ
#2 画像表示
#3 アニメーション
#4 キー入力
#5 サウンド
#6 衝突判定
#7 ステートマシン
#8 マップチップ
#9 スクロール

スクロール

pygameを使ったゲームプログラミングについて説明していきます。

  • Windows10
  • Python3.6.1
  • pygame1.9.3

今回はスクロールについて紹介します。

スクロールとは?

画面に収まりきらない空間をカメラを移動させることで表示する技法です。 実際は描画位置を「カメラからの相対位置」に変換することで実現しています。

f:id:dungeonneko:20170519211651p:plain

サンプルコード

前回のマップを拡げて画面に収まらないようにします。 ついでに上下左右キーで移動できるカメラを追加します。

実行結果

f:id:dungeonneko:20170519210628g:plain