I am trying to create a plugin for QGIS, when I initialize the start of the plugin build and create the GUI without coding any of the buttons associated with the GUI the plugin works, but as soon as I start to edit the code of the plugin I get the following error:
AttributeError: 'object_Finder' object has no attribute 'dlg'
Here is the plugin code for some background:
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction
# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .identifier_dialog import object_FinderDialog
import os.path
class object_Finder:
"""QGIS Plugin Implementation."""
def __init__(self, iface):
"""Constructor.
:param iface: An interface instance that will be passed to this class
which provides the hook by which you can manipulate the QGIS
application at run time.
:type iface: QgsInterface
"""
# Save reference to the QGIS interface
self.iface = iface
# initialize plugin directory
self.plugin_dir = os.path.dirname(__file__)
# initialize locale
locale = QSettings().value('locale/userLocale')[0:2]
locale_path = os.path.join(
self.plugin_dir,
'i18n',
'object_Finder_{}.qm'.format(locale))
if os.path.exists(locale_path):
self.translator = QTranslator()
self.translator.load(locale_path)
QCoreApplication.installTranslator(self.translator)
# Declare instance attributes
self.actions = []
self.menu = self.tr(u'&object_finder')
# Check if plugin was started the first time in current QGIS session
# Must be set in initGui() to survive plugin reloads
self.first_start = None
# noinspection PyMethodMayBeStatic
def tr(self, message):
"""Get the translation for a string using Qt translation API.
We implement this ourselves since we do not inherit QObject.
:param message: String for translation.
:type message: str, QString
:returns: Translated version of message.
:rtype: QString
"""
# noinspection PyTypeChecker,PyArgumentList,PyCallByClass
return QCoreApplication.translate('object_Finder', message)
def add_action(
self,
icon_path,
text,
callback,
enabled_flag=True,
add_to_menu=True,
add_to_toolbar=True,
status_tip=None,
whats_this=None,
parent=None):
"""Add a toolbar icon to the toolbar.
:param icon_path: Path to the icon for this action. Can be a resource
path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
:type icon_path: str
:param text: Text that should be shown in menu items for this action.
:type text: str
:param callback: Function to be called when the action is triggered.
:type callback: function
:param enabled_flag: A flag indicating if the action should be enabled
by default. Defaults to True.
:type enabled_flag: bool
:param add_to_menu: Flag indicating whether the action should also
be added to the menu. Defaults to True.
:type add_to_menu: bool
:param add_to_toolbar: Flag indicating whether the action should also
be added to the toolbar. Defaults to True.
:type add_to_toolbar: bool
:param status_tip: Optional text to show in a popup when mouse pointer
hovers over the action.
:type status_tip: str
:param parent: Parent widget for the new action. Defaults None.
:type parent: QWidget
:param whats_this: Optional text to show in the status bar when the
mouse pointer hovers over the action.
:returns: The action that was created. Note that the action is also
added to self.actions list.
:rtype: QAction
"""
icon = QIcon(icon_path)
action = QAction(icon, text, parent)
action.triggered.connect(callback)
action.setEnabled(enabled_flag)
if status_tip is not None:
action.setStatusTip(status_tip)
if whats_this is not None:
action.setWhatsThis(whats_this)
if add_to_toolbar:
# Adds plugin icon to Plugins toolbar
self.iface.addToolBarIcon(action)
if add_to_menu:
self.iface.addPluginToMenu(
self.menu,
action)
self.actions.append(action)
return action
def initGui(self):
"""Create the menu entries and toolbar icons inside the QGIS GUI."""
icon_path = ':/plugins/identifier/icon.png'
self.add_action(
icon_path,
text=self.tr(u'object_finder'),
callback=self.run,
parent=self.iface.mainWindow())
# will be set False in run()
def unload(self):
"""Removes the plugin menu item and icon from QGIS GUI."""
for action in self.actions:
self.iface.removePluginMenu(
self.tr(u'&object_finder'),
action)
self.iface.removeToolBarIcon(action)
def run(self):
"""Run method that performs all the real work"""
# Create the dialog with elements (after translation) and keep reference
# Only create GUI ONCE in callback, so that it will only load when the plugin is started
if self.first_start == True:
self.first_start = False
self.dlg = object_FinderDialog()
# show the dialog
self.dlg.show()
# Run the dialog event loop
result = self.dlg.exec_()
# See if OK was pressed
#self.first_start = True
#self.viewRasters()
#self.dlg.toolButton.clicked.connect(self.openRasters)
if result:
# Do something useful here - delete the line containing pass and
# substitute with your code.
pass
My functions created are the openRasters
and loadRasters
.
1 Answer 1
Spoiler: It looks like you have (perhaps inadvertently?) deleted a line from the initGui()
method which which sets the self.first_start
variable to True
. If you look in your initGui()
method, you will see that you actually have a commented line: # will be set False in run()
. The line: self.first_start = True
should normally be created in the boiler-plate code generated by Plugin Builder immediately after that commented line.
First let's look at the reason for the attribute error. Let's track a couple of variables in the code you have posted. The attribute which is causing the problem, self.dlg
, is only created in the run()
method if self.first_start
is True
. Ok, let's track the self.first_start
variable. It is created in the __init__()
method and initialized to None
and its value is never changed. So, in the run()
method, when you check if self.first_start
is True
, the condition is not passed, because self.first_start
is still None
- so the line: self.dlg = object_FinderDialog()
is not executed and the dlg
attribute of the 'object_Finder'
class instance is never created. To fix this, you should add the line: self.first_start = True
inside the initGui()
method.
Another thing to be aware of is how and where you set up your signal/slot connections. If you prefer to stick with Plugin Builder and the generated boiler-plate code, you should also connect the signals and slots dependent on the self.first_start == True:
condition. This will ensure they are only connected once, the first time that your plugin is launched. If you connect them in the run()
method outside of the conditional statement, a new additional connection will be created every time you launch your plugin. This means that when you run your plugin and click 'Ok' on your plugin dialog, the slot function will be called once the first time, then twice, three times and so on until you either restart QGIS or re-load your plugin.
Another approach (and this would be my personal preference) is to remove the parts of the code referring to the self.first_start
flag, simply create the instance of your dialog class inside the __init__()
method of your main plugin class e.g. self.dlg = object_FinderDialog()
and set up your signal slot connections inside the initGui()
method e.g. self.dlg.toolButton.clicked.connect(self.openRasters)
. Then in the run()
method, just call self.dlg.show()
.
Example below:
from qgis.PyQt.QtCore import QSettings, QTranslator, QCoreApplication
from qgis.PyQt.QtGui import QIcon
from qgis.PyQt.QtWidgets import QAction
# Initialize Qt resources from file resources.py
from .resources import *
# Import the code for the dialog
from .identifier_dialog import object_FinderDialog
import os.path
class object_Finder:
"""QGIS Plugin Implementation."""
def __init__(self, iface):
# Save reference to the QGIS interface
self.iface = iface
# initialize plugin directory
self.plugin_dir = os.path.dirname(__file__)
# initialize locale
locale = QSettings().value('locale/userLocale')[0:2]
locale_path = os.path.join(
self.plugin_dir,
'i18n',
'object_Finder_{}.qm'.format(locale))
if os.path.exists(locale_path):
self.translator = QTranslator()
self.translator.load(locale_path)
QCoreApplication.installTranslator(self.translator)
# Declare instance attributes
self.actions = []
self.menu = self.tr(u'&object_finder')
# create instance of dialog class
self.dlg = object_FinderDialog()
# noinspection PyMethodMayBeStatic
def tr(self, message):
# noinspection PyTypeChecker,PyArgumentList,PyCallByClass
return QCoreApplication.translate('object_Finder', message)
def add_action(
self,
icon_path,
text,
callback,
enabled_flag=True,
add_to_menu=True,
add_to_toolbar=True,
status_tip=None,
whats_this=None,
parent=None):
icon = QIcon(icon_path)
action = QAction(icon, text, parent)
action.triggered.connect(callback)
action.setEnabled(enabled_flag)
if status_tip is not None:
action.setStatusTip(status_tip)
if whats_this is not None:
action.setWhatsThis(whats_this)
if add_to_toolbar:
# Adds plugin icon to Plugins toolbar
self.iface.addToolBarIcon(action)
if add_to_menu:
self.iface.addPluginToMenu(
self.menu,
action)
self.actions.append(action)
return action
def initGui(self):
"""Create the menu entries and toolbar icons inside the QGIS GUI."""
icon_path = ':/plugins/identifier/icon.png'
self.add_action(
icon_path,
text=self.tr(u'object_finder'),
callback=self.run,
parent=self.iface.mainWindow())
# connect dialog signals and slots here
self.dlg.toolButton.clicked.connect(self.openRasters)
def unload(self):
"""Removes the plugin menu item and icon from QGIS GUI."""
for action in self.actions:
self.iface.removePluginMenu(
self.tr(u'&object_finder'),
action)
self.iface.removeToolBarIcon(action)
def run(self):
"""Run method that performs all the real work"""
# show the dialog
self.dlg.show()
# Run the dialog event loop
result = self.dlg.exec_()
# See if OK was pressed
if result:
# Do something useful here - delete the line containing pass and
# substitute with your code.
pass
def openRasters(self):
"""Slot method called when self.dlg.toolButton is clicked"""
pass
-
hello ben, thank you for the help. I have a follow up question in the init function should I remove the self.first_start = none ? and just for clarification i should remove the self.first_start == true in the run method?prashant– prashant2021年08月16日 15:48:57 +00:00Commented Aug 16, 2021 at 15:48
-
@prashant, I added an example.Ben W– Ben W2021年08月17日 03:55:04 +00:00Commented Aug 17, 2021 at 3:55