PyQt程序调整大小QGraphicsView问题

时间:2018-11-09 03:48:17

标签: python pyqt pyqt5

Qt新手在这里。我正在使用PyQt在QGraphicsView中创建可调整大小的QGraphicsScene。这与我提供的代码一起正常工作。我的解决方案的问题在于,当我使用resize函数以编程方式更新QMainWindow的大小时,图形场景的元素无法缩放。用鼠标手动调整大小确实会调用正确的行为。在ForestViewer类的resize调用中,使用提供的代码可以看到不正确的行为。通过手动调整窗口大小可以看到正确的行为。尽管所有UI工作都是在forestviewer.py中完成的,但该项目要构建四个模块。任何帮助将不胜感激!

OS- Windows ,Python版本- 2.7 ,PyQt版本- 5.9.2

forestviewer.py

from forest import Forest
from PyQt5.QtCore import QRectF, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateTimeEdit,
        QDial, QDialog, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit,
        QProgressBar, QPushButton, QRadioButton, QScrollBar, QSizePolicy,
        QSlider, QSpinBox, QStyleFactory, QTableWidget, QTabWidget, QTextEdit,
        QVBoxLayout, QWidget, QGraphicsScene, QGraphicsView, QMainWindow,
        QFormLayout)
from PyQt5.QtGui import QPen, QBrush, QColor
import config

class ForestViewer(QMainWindow):
    resized = pyqtSignal()
    def  __init__(self, forest, parent=None):
        super(ForestViewer, self).__init__(parent=parent)
        self.forest = forest
        self.ForestDialog = ForestDialog( self.forest )
        self.resized.connect( self.handleResize )
        self.setCentralWidget( self.ForestDialog)
        self.setWindowTitle("Chestnut Blight Forest Simulator")
        QApplication.setStyle(QStyleFactory.create('Fusion'))                        
        self.resize(1200,900) #<-- Does not resize grid
        self.ForestDialog.repaint()


    def resizeEvent(self, event):
        self.resized.emit()
        return super(ForestViewer, self).resizeEvent(event)

    def handleResize(self):
        self.ForestDialog.repaint()

class ForestDialog(QDialog):
    def __init__(self, forest=None, parent=None):
        super(ForestDialog, self).__init__(parent)
        self.forest = forest
        self.setContentsMargins(1,1,1,1)

        self.createForestView()
        self.createForestControl()

        self.mainLayout = QGridLayout()
        self.mainLayout.addWidget(self.forest_view_box, 0, 0)
        self.mainLayout.addWidget(self.ForestControlBox, 0, 1)
        self.setLayout(self.mainLayout)

    def createForestView(self):

        self.forest_view_box = QGroupBox("Forest View")
        forest_scene = QGraphicsScene() #init size?
        forest_scene.setBackgroundBrush(QColor("orange"))
        self.forest_view = QGraphicsView(forest_scene)
        self.forest_view_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        layout = QHBoxLayout(self)
        layout.addWidget(self.forest_view)
        self.forest_view_box.setLayout(layout)
        if self.forest != None:
            self.paintGrid()


    def createForestControl(self):

        self.ForestControlBox = QGroupBox("Forest Control")

        years_to_sim = QSpinBox(self.ForestControlBox)
        years_to_sim.setValue(50)

        update_delay = QSpinBox(self.ForestControlBox)
        update_delay.setValue(0)        

        layout = QFormLayout()
        layout.addRow(QLabel("Forest Size: "))
        layout.addRow(QLabel("Years to Simulate:"), years_to_sim)
        layout.addRow(QLabel("Update Delay (seconds):"), update_delay)

        self.ForestControlBox.setLayout(layout)

    def paintGrid(self):
        forest = self.forest
        if forest == None:
            print("No Forest")
            return
        view = self.forest_view # group = self.forest_view_box
        scene = view.scene()
        scene.setBackgroundBrush(QColor("red"))
        rect = scene.itemsBoundingRect()
        contents = view.contentsRect()
        w = float(contents.width())
        h = float(contents.height()) #view.childrenRect()
        scene.clear()
        view.setSceneRect(0, 0, w, h)

        cell_w = w / forest.cols
        cell_h = h / forest.rows

        rad = min(cell_w, cell_h)
        grid = forest.grid
        for r in range(forest.rows):
            for c in range(forest.cols):
                x = c * cell_w
                y = r * cell_h
                rect = QRectF(x, y, cell_w, cell_h)
                bg_col = QColor("#EDEDED")
                off_white = QColor("#E8E8E8")
                trans = QColor("transparent")
                scene.addRect(rect, QPen(bg_col), QBrush(off_white))
                tree = grid[r][c]
                if tree != None and tree.stage != config.DEAD:
                    qcol = self.colorFromTree(tree)
                    mrad = tree.stage * 0.24 * rad # define circle radius based on tree size
                    xe = x + ((cell_w - mrad) / 2)
                    ye = y + ((cell_h - mrad) / 2)
                    scene.addEllipse(xe, ye, mrad, mrad, QPen(trans), QBrush(qcol))

    def colorFromTree(self, tree):
        col = "transparent"
        if tree != None:
            col_switch = {
                config.V: "red",
                config.HV: "turquoise",
                config.HEALTHY: "green"
            }
            col = col_switch.get(tree.rating)
        return QColor(col)

    def repaint(self):
        if(self.forest_view != None and self.forest != None):
            self.paintGrid()  

forest.py

from tree import Tree
import random
import math
import config
from copy import copy, deepcopy

class Forest:

    num_deaths, num_births = 0, 0

    # TODO: Add getters, setters?
    def __init__(self, rows, cols):
        self.rows = rows
        self.cols = cols
        self.grid = [[None] * cols for i in range(rows)]

    # Generates a random Tree grid based on 2002 CDF data
    def generate_grid(self):
        new_grid = [[None] * self.cols for i in range(self.rows)]
        for row in range(self.rows):
            for col in range(self.cols):
                rating = 0 # TODO enum?
                stage = 0
                rand = random.random()
                if rand < config.TREE_DENSITY:
                    tree_type = random.random()
                    i = 0
                    while i <= len(config.POP_2002_CDF):
                        if tree_type < config.POP_2002_CDF[i]:
                            rating = i / config.DBH_STAGE4 + 1
                            stage = i % config.DBH_STAGE4 + 1
                            break
                        i += 1
                new_tree = Tree(row, col, rating, stage, config.UNTREATED)
                new_grid[row][col] = new_tree
        return new_grid

    def print_forest(self):
        grid = self.grid
        for row in grid:
            print(' '.join([str(tree.stage) for tree in row]))
            # above only prints stage, uncomment below for full tree details
            # for tree in row
                #tree.print_tree()

    def init_random(self):
        self.grid = self.generate_grid()

    # Returns a 2D list of lists of trees, i.e., the new grid 
    # TODO: Add infect function
    def get_next_year(self):
        prev_year = self.grid
        next_year = deepcopy(self.grid)

        coords = [(r,c) for r in range(self.rows) for c in range(self.cols)]
        random.shuffle(coords)

        for coord in coords:
            r = coord[0]
            c = coord[1]
            tree = prev_year[coord[0]][coord[1]] # original tree
            t_tree = next_year[coord[0]][coord[1]] # transformed tree

            if tree.stage != config.DEAD:
                rand = random.random()
                next_stage_row = ((tree.rating - 1) * config.DBH_STAGE4) + \
                    (tree.stage - 1)


                i = 0
                while rand >=  config.NEW_STAGE_CDF[next_stage_row][i] and \
                    i < config.DBH_STAGE4 + 1:
                        next_year[r][c].stage = i
                        i += 1

                # block not required?
                if tree.stage == config.DEAD:
                    t_tree.stage = config.DEAD
                    t_tree.treatment = config.UNTREATED
                    # TODO: decide if reset tree treatment here
                else:
                    rand = random.random()
                    next_rating_row = tree.treatment * (config.HEALTHY - 1) \
                        + (tree.rating - 1)
                    i = 0
                    while i < config.HEALTHY - 1 and rand >= \
                        config.NEW_RATING_CDF[next_rating_row][i]:
                            next_year[r][c].rating = i + 1
                            i += 1

                    #if tree.rating == config.V:
                        #infect(config.V, r, c, prev_year, next_year)
                    #else if tree.rating == config.HV
                        #infect(config.HV, ...)

                    rep = config.REPRODUCTION[tree.rating - 1][tree.stage - 1]
                    l = math.exp(-rep)
                    p = random.random()
                    rand_poisson = 1

                    while p > l :
                        p = p * random.random()
                        rand_poisson += 1
                    rand_poisson -= 1

                    sites = copy(coords)
                    random.shuffle(sites)
                    while rand_poisson > 0 and len(sites) > 0:
                        site = sites.pop()
                        s_r = site[0]
                        s_c = site[1]
                        if prev_year[s_r][s_c].stage == config.DEAD:
                            print("add new tree")
                            next_year[s_r][s_c].stage = config.DBH_STAGE1
                            next_year[s_r][s_c].rating = config.HEALTHY
                            self.num_births += 1
                            rand_poisson -= 1

        return next_year

    # Generates a new grid for the Forest and sets the active grid to
    # the grid of the next year
    def set_next_year(self):
        next_year = self.get_next_year()
        self.grid = next_year

tree.py

class Tree:

    def __init__(self, r = 0, c = 0, rating = 0, stage = 0, treatment = 0):
        self._r = r # row
        self._c = c
        self._rating = rating # health
        self._stage = stage # size
        self._treatment = treatment # treatment

    #def __eq__(self, other):
        # May need to define custom '==' operator or .equals function

    def print_tree(self):
        print( "Tree (" + str(self.x) + ", " + str(self.y) + "):\n  Rating: " +
              str(self.rating) + "\n  Stage: " + str(self.stage) +
              "\n  Treatment: " + str(self.treatment) )

    # r: row of tree
    @property
    def r(self):
        return self._r

    @r.setter
    def r(self, val):
        self._r = val

    # c: column of tree
    @property
    def c(self):
        return self._c

    @c.setter
    def c(self, val):
        self._c = val

    # rating: health of tree ( V, HV, Healthy )
    @property
    def rating(self):
        return self._rating

    @rating.setter
    def rating(self, val):
        self._rating = val

    # rating: health of tree ( V, HV, Healthy )
    @property
    def stage(self):
        return self._stage

    # stage: Size of tree, 0 = dead, 1-4 see DBH_STAGEs
    @stage.setter
    def stage(self, val):
        self._stage = val

    # treatment: 0 = untreated, 1 = treated
    @property
    def treatment(self):
        return self._treatment

    @treatment.setter
    def treatment(self, val):
        self._treatment = val

config.py

import math

"""
-------------------------------------------------------
                 SIMULATION SETTINGS 
-------------------------------------------------------
""" 
# Number of years for simulating, classifier training
YEARS = 200

# Size of a square within the world (2x2 meters)
SITE_SIZE = 2

# Scalar value used to determine infection range distance of a tree (meters)
DIST_CLASS = 8

# Used to calculate the number of infection "attempts" when determining which 
# trees will get infected. This new value was introduced when modifying the 
# infection algorithm to change some unintended behavior. The old method would 
# calculate the number of trees to be infected within a certain distance and then 
# infect them at random. This method did not take tree spacial locality into 
# consideration when selecting a new tree to infect. Instead, the infected tree 
# now selectes tiles at random, favoring those tiles closer to the source tree, 
# given a number of sporing events. The current infection still mimics the 
# anticipated West Salem data in the # of infections, but now takes more 
# consideration to tree locality. The downside to this is that the simulation 
# time increases.
SPORE_SCALAR = 400

# Forest height (SITESIZE tiles)
HEIGHT = 50

# Forest width (SITESIZE tiles)
WIDTH = 50

"""
-------------------------------------------------------
                 TREE STATE DEFINITIONS 
-------------------------------------------------------
""" 
# Titled a Simulation "Action", but really is a treatment STATE
UNTREATED = 0

# Titled a Simulation "Action", but really is a treatment STATE
TREATED = 1

# Virulent - A tree health rating. All virulent cankers 
V = 1

# Hypovirulent - A tree health rating. Mix of virulent and hypovirulent cankers
HV = 2

# Healthy - A tree health rating.
HEALTHY = 3

# DBH Stage - Indicates a dead tree. (Diameter at Breast Height)
DEAD = 0

# DBH Stage - DBH <= 1 cm
DBH_STAGE1 = 1

# DBH Stage -  1 < DBH <= 10 cm
DBH_STAGE2 = 2

# DBH Stage -  10 < DBH <= 20 cm
DBH_STAGE3 = 3

# DBH Stage -  DBH > 20 cm
DBH_STAGE4 = 4


"""
-------------------------------------------------------
              GROWTH / INFECTION MATH 
-------------------------------------------------------
""" 
# From rough density analysis on West Salem plot, likelihood of a tree being 
# in a site
TREE_DENSITY = 0.01071632 * SITE_SIZE * SITE_SIZE

# CDF of percentages from tree ratings in 2002 
# (original values {0.2356828, 0.2048458, 0.5594714})
BEGIN_RATING = [0.2356828, 0.4405286, 1]

# CDF of percentages from tree stages in 2002 
# (orig. values {0.1059603, 0.4282561, 0.2030905, 0.2626932})
BEGIN_STAGE = [0.1059603, 0.5342164, 0.7373069, 1]

# Used to determine tree reproduction, i.e. the creation of Stage 1 trees 
REPRODUCTION = [
    [0, 0.02, 0.32, 3.69],
    [0, 0.016, 0.78, 2.43],
    [0, 0.001, 0.03, 7.74]
]

# Probability of a virulent tree sporing event
PROB_OF_SPORE_VIRU = 1 - math.exp(-1.5)

# Probability of a hypovirulent tree sporing event
PROB_OF_SPORE_HYPO = 1 - math.exp(-.75)

# Previously used to calculate the number of infections. 
# Usage:   
# maxInfections = (int) math.round(math.exp(config.NUM_INF_CDF[0]*math.random()- config.NUM_INF_CDF[1]))
# Now is used to calculate the MAX number of infections.
NUM_INF_CDF = [5.6117, 3.6341]

# Percentage of infections from HV trees that result in HV infections 
# (otherwise virulent infection)
PER_HV_TO_HV = 0.65

# NEW Distance Coefficients for Infect (4/22/18).
# Each instance represents a probability for an infection on 8m intervals
# e.g. 0.3915 probability spread 8m, 0.5195 probaility 16m, ...
V_INFECT_RANGE_PROB_8M_INT = [
    0.3915, 0.5195, 0.6081, 0.6706, 0.7188, 0.7580, 
    0.7912, 0.8199, 0.8451, 0.8677, 0.8882, 0.9068,
    0.9239, 0.9398, 0.9545, 0.9683, 0.9813, 0.9935, 
    1.0000
]

# NEW Distance Coefficients for Infect (4/22/18).
# Each instance represents a probability for an infection on 8m intervals
HV_INFECT_RANGE_PROB_8M_INT = [
    0.5746, 0.6587, 0.7169, 0.7578, 0.7894, 0.8152, 0.8370, 0.8558, 0.8724, 
    0.8872, 0.9006, 0.9129, 0.9241, 0.9345, 0.9442, 0.9533, 0.9618, 0.9698, 
    0.9774, 0.9846, 0.9914, 0.9979, 1.0000
]

# Population dynamics of West Salem in 2002. Rows are virulent, hypovirulent 
# and healthy. Each entry corresponds to a stage (size).
POP_2002_CDF = [
    0.03311258, 0.09050773, 0.15011038, 0.23399558, 0.23399558, 0.27373068, 
    0.31567329, 0.43929360, 0.51214128, 0.84326711, 0.94481236, 1.00000000
]

# Used to calculate new ratings of trees in getNextYear(). Implementation is 
# not exactly clear as to what is happening.
NEW_RATING_CDF = [
    [0.69714286, 1],
    [0.20588235, 1],
    [0.52651515, 1],
    [0.09884467, 1]
]

# Used to calculate new stages of trees in getNextYear().  
# Implementation is not exactly clear as to what is happening.
NEW_STAGE_CDF = [
    [0.2, 0.962, 0.999, 0.999, 1],
    [0.006, 0.09, 0.99, 1, 1],
    [0.05, 0.05,0.3, 0.96,1], 
    [0.021, 0.021, 0.101, 0.111, 1], 
    [0.18, 0.996, 1, 1, 1], 
    [0, 0.09, 0.98, 1, 1], 
    [0.01, 0.01, 0.05, 0.96, 1], 
    [0.001, 0.001, 0.006, 0.056, 1], 
    [0.16, 0.994, 1, 1, 1],
    [0, 0.07, 0.99, 1, 1],
    [0, 0, 0, 0.95, 1],
    [0.013, 0.013, 0.013, 0.013, 1]
]

"""
-------------------------------------------------------
              NEURAL NETWORK SETTINGS
-------------------------------------------------------
""" 
# # of input nodes for neural network classifier 
# (rating, stage, virulent neighbors, hypo-virulent neighbors, healthy neighbors)
INPUT_LAYER_NODE_COUNT = 5

# # of hidden nodes for neural network classifier
HIDDEN_LAYER_NODE_COUNT = 10
# # of output nodes for neural network classifier (good, bad)
OUTPUT_LAYER_NODE_COUNT = 2
# Defines the square range (NEIGHBOR_RADIUSxNEIGHBOR_RADIUS) of SITESIZE tiles 
# to look at when determining the neighbors of a tree. The neighbors are used 
# for Q-Learning. A higher neighbor_radius means a larger state space.
NEIGHBOR_RADIUS = 4

0 个答案:

没有答案