[{"data":1,"prerenderedAt":1393},["ShallowReactive",2],{"doc:\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Fuse-iface-to-control-qgis-interface-pyqgis":3},{"id":4,"title":5,"body":6,"description":1382,"extension":1383,"meta":1384,"navigation":252,"path":1389,"seo":1390,"stem":1391,"__hash__":1392},"docs\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Fuse-iface-to-control-qgis-interface-pyqgis\u002Findex.md","Use iface to Control the QGIS Interface in PyQGIS",{"type":7,"value":8,"toc":1369},"minimark",[9,13,24,43,164,169,187,191,197,312,337,340,443,466,470,483,514,530,543,586,594,600,660,685,689,782,950,973,977,980,1054,1069,1074,1078,1081,1160,1178,1182,1199,1203,1265,1269,1286,1290,1296,1305,1314,1331,1335,1365],[10,11,5],"h1",{"id":12},"use-iface-to-control-the-qgis-interface-in-pyqgis",[14,15,16,20,21,23],"p",{},[17,18,19],"code",{},"iface"," is the one name in PyQGIS that exists only inside QGIS Desktop. It is the handle on the running application's interface — the canvas, the layer tree, the menus, the message bar, the dialogs — and it is what every example that starts \"open the Python console and type\" depends on. Understanding what belongs to ",[17,22,19],{}," and what does not is the difference between a script that runs everywhere and one that runs only when a window is open.",[14,25,26,27,32,33,35,36,38,39,42],{},"This recipe belongs to ",[28,29,31],"a",{"href":30},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002F","QGIS Python Console Basics",". It covers what ",[17,34,19],{}," exposes, how to get it inside a plugin, the difference between ",[17,37,19],{}," and ",[17,40,41],{},"QgsProject",", and how to write code that works both with and without it.",[14,44,45],{},[46,47,52,56,60,67,76,86,92,98,102,106,110,114,120,125,129,133,136,139,142,145,149,159],"svg",{"viewBox":48,"role":49,"ariaLabel":50,"xmlns":51},"0 0 760 330","img","What belongs to iface and what belongs to the project: interface objects such as the canvas and menus on one side, and data objects such as layers and the project on the other","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[53,54,55],"title",{},"The interface and the project are different things",[57,58,59],"desc",{},"The iface object owns everything about the running window: the map canvas, the layer tree view, menus, toolbars, the message bar and the dialogs. The project object owns the data: the layers, their styling, the coordinate system and the project settings. A headless script has the project and no interface.",[61,62],"rect",{"x":63,"y":63,"width":64,"height":65,"fill":66},"0","760","330","#f6f3ea",[68,69,75],"text",{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Only one of these survives without a window",[61,77],{"x":78,"y":79,"width":80,"height":81,"rx":82,"fill":83,"stroke":84,"style":85},"24","46","344","212","10","#eff3ff","#2563eb","stroke-width:2.5",[68,87,91],{"x":88,"y":89,"style":90,"fill":84,"textAnchor":74},"196","72","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","iface — the window",[68,93,97],{"x":79,"y":94,"style":95,"fill":96},"102","font-size:10.5px;font-family:monospace","#2f3b35","mapCanvas()",[68,99,101],{"x":79,"y":100,"style":95,"fill":96},"126","layerTreeView()",[68,103,105],{"x":79,"y":104,"style":95,"fill":96},"150","messageBar()",[68,107,109],{"x":79,"y":108,"style":95,"fill":96},"174","addToolBarIcon()",[68,111,113],{"x":79,"y":112,"style":95,"fill":96},"198","activeLayer()",[68,115,119],{"x":79,"y":116,"style":117,"fill":118},"230","font-size:10px;font-family:sans-serif","#b91c1c","None in a standalone script",[61,121],{"x":122,"y":79,"width":80,"height":81,"rx":82,"fill":123,"stroke":124,"style":85},"392","#eef7f4","#0f766e",[68,126,128],{"x":127,"y":89,"style":90,"fill":124,"textAnchor":74},"564","QgsProject — the data",[68,130,132],{"x":131,"y":94,"style":95,"fill":96},"414","mapLayers()",[68,134,135],{"x":131,"y":100,"style":95,"fill":96},"mapLayersByName()",[68,137,138],{"x":131,"y":104,"style":95,"fill":96},"addMapLayer()",[68,140,141],{"x":131,"y":108,"style":95,"fill":96},"crs() · setCrs()",[68,143,144],{"x":131,"y":112,"style":95,"fill":96},"layerTreeRoot()",[68,146,148],{"x":131,"y":116,"style":117,"fill":147},"#15803d","available everywhere",[61,150],{"x":151,"y":152,"width":153,"height":154,"rx":155,"fill":156,"stroke":157,"style":158},"80","280","600","40","8","#fdf2e2","#b45309","stroke-width:2",[68,160,163],{"x":70,"y":161,"style":162,"fill":96,"textAnchor":74},"305","text-anchor:middle;font-size:10.5px;font-family:sans-serif","write against the right-hand column wherever you can — it is the portable half",[165,166,168],"h2",{"id":167},"prerequisites","Prerequisites",[170,171,172,180],"ul",{},[173,174,175,179],"li",{},[176,177,178],"strong",{},"QGIS 3.34 LTR"," or newer, running with a GUI for anything on this page.",[173,181,182,183,186],{},"The Python console open (",[176,184,185],{},"Plugins → Python Console","), or a plugin under development.",[165,188,190],{"id":189},"where-iface-comes-from","Where iface comes from",[14,192,193,194,196],{},"In the Python console, ",[17,195,19],{}," is pre-defined and needs no import. In a plugin, it is handed to you:",[198,199,204],"pre",{"className":200,"code":201,"language":202,"meta":203,"style":203},"language-python shiki shiki-themes github-dark","def classFactory(iface):\n    from .my_plugin import MyPlugin\n    return MyPlugin(iface)\n\n\nclass MyPlugin:\n    def __init__(self, iface):\n        self.iface = iface\n        self.canvas = iface.mapCanvas()\n","python","",[17,205,206,223,238,247,254,259,271,284,299],{"__ignoreMap":203},[207,208,211,215,219],"span",{"class":209,"line":210},"line",1,[207,212,214],{"class":213},"snl16","def",[207,216,218],{"class":217},"svObZ"," classFactory",[207,220,222],{"class":221},"s95oV","(iface):\n",[207,224,226,229,232,235],{"class":209,"line":225},2,[207,227,228],{"class":213},"    from",[207,230,231],{"class":221}," .my_plugin ",[207,233,234],{"class":213},"import",[207,236,237],{"class":221}," MyPlugin\n",[207,239,241,244],{"class":209,"line":240},3,[207,242,243],{"class":213},"    return",[207,245,246],{"class":221}," MyPlugin(iface)\n",[207,248,250],{"class":209,"line":249},4,[207,251,253],{"emptyLinePlaceholder":252},true,"\n",[207,255,257],{"class":209,"line":256},5,[207,258,253],{"emptyLinePlaceholder":252},[207,260,262,265,268],{"class":209,"line":261},6,[207,263,264],{"class":213},"class",[207,266,267],{"class":217}," MyPlugin",[207,269,270],{"class":221},":\n",[207,272,274,277,281],{"class":209,"line":273},7,[207,275,276],{"class":213},"    def",[207,278,280],{"class":279},"sDLfK"," __init__",[207,282,283],{"class":221},"(self, iface):\n",[207,285,287,290,293,296],{"class":209,"line":286},8,[207,288,289],{"class":279},"        self",[207,291,292],{"class":221},".iface ",[207,294,295],{"class":213},"=",[207,297,298],{"class":221}," iface\n",[207,300,302,304,307,309],{"class":209,"line":301},9,[207,303,289],{"class":279},[207,305,306],{"class":221},".canvas ",[207,308,295],{"class":213},[207,310,311],{"class":221}," iface.mapCanvas()\n",[14,313,314,317,318,321,322,324,325,328,329,333,334,336],{},[176,315,316],{},"Breakdown:"," ",[17,319,320],{},"classFactory"," is the entry point QGIS calls when loading a plugin, and the ",[17,323,19],{}," it passes is a ",[17,326,327],{},"QgisInterface"," — the same object the console exposes. Storing it on the instance rather than reaching for a global is what keeps a plugin testable, because a test can pass a mock in its place, as covered in ",[28,330,332],{"href":331},"\u002Fqgis-plugin-development\u002Ftesting-and-ci-for-plugins\u002Fmock-qgis-interface-in-plugin-tests\u002F","mocking the QGIS interface in plugin tests",". Outside a plugin and outside the console, there is no ",[17,335,19],{}," and no way to make one: it is the running application's own interface object.",[14,338,339],{},"Inside a Processing script or an expression function it is sometimes available and sometimes not, so code that might run in either place should check rather than assume:",[198,341,343],{"className":200,"code":342,"language":202,"meta":203,"style":203},"try:\n    from qgis.utils import iface\nexcept ImportError:\n    iface = None\n\nif iface is not None:\n    iface.messageBar().pushInfo(\"Ready\", \"Layers loaded\")\nelse:\n    print(\"Layers loaded\")\n",[17,344,345,352,363,373,383,387,406,424,431],{"__ignoreMap":203},[207,346,347,350],{"class":209,"line":210},[207,348,349],{"class":213},"try",[207,351,270],{"class":221},[207,353,354,356,359,361],{"class":209,"line":225},[207,355,228],{"class":213},[207,357,358],{"class":221}," qgis.utils ",[207,360,234],{"class":213},[207,362,298],{"class":221},[207,364,365,368,371],{"class":209,"line":240},[207,366,367],{"class":213},"except",[207,369,370],{"class":279}," ImportError",[207,372,270],{"class":221},[207,374,375,378,380],{"class":209,"line":249},[207,376,377],{"class":221},"    iface ",[207,379,295],{"class":213},[207,381,382],{"class":279}," None\n",[207,384,385],{"class":209,"line":256},[207,386,253],{"emptyLinePlaceholder":252},[207,388,389,392,395,398,401,404],{"class":209,"line":261},[207,390,391],{"class":213},"if",[207,393,394],{"class":221}," iface ",[207,396,397],{"class":213},"is",[207,399,400],{"class":213}," not",[207,402,403],{"class":279}," None",[207,405,270],{"class":221},[207,407,408,411,415,418,421],{"class":209,"line":273},[207,409,410],{"class":221},"    iface.messageBar().pushInfo(",[207,412,414],{"class":413},"sU2Wk","\"Ready\"",[207,416,417],{"class":221},", ",[207,419,420],{"class":413},"\"Layers loaded\"",[207,422,423],{"class":221},")\n",[207,425,426,429],{"class":209,"line":286},[207,427,428],{"class":213},"else",[207,430,270],{"class":221},[207,432,433,436,439,441],{"class":209,"line":301},[207,434,435],{"class":279},"    print",[207,437,438],{"class":221},"(",[207,440,420],{"class":413},[207,442,423],{"class":221},[14,444,445,317,447,450,451,454,455,458,459,461,462,465],{},[176,446,316],{},[17,448,449],{},"qgis.utils.iface"," is the importable form and is ",[17,452,453],{},"None"," in a headless application rather than absent, so both the ",[17,456,457],{},"ImportError"," and the ",[17,460,453],{}," check earn their place across environments. Structuring reporting this way — message bar when there is a window, ",[17,463,464],{},"print"," otherwise — is what lets one script serve an interactive user and a scheduled job without branching everywhere.",[165,467,469],{"id":468},"the-parts-you-will-actually-use","The parts you will actually use",[14,471,472,317,475,478,479,482],{},[176,473,474],{},"The canvas.",[17,476,477],{},"iface.mapCanvas()"," gives the ",[17,480,481],{},"QgsMapCanvas",", which is where extents, refreshes and map tools live.",[198,484,486],{"className":200,"code":485,"language":202,"meta":203,"style":203},"canvas = iface.mapCanvas()\ncanvas.setExtent(layer.extent())\ncanvas.refresh()\nprint(canvas.scale(), canvas.mapUnitsPerPixel())\n",[17,487,488,497,502,507],{"__ignoreMap":203},[207,489,490,493,495],{"class":209,"line":210},[207,491,492],{"class":221},"canvas ",[207,494,295],{"class":213},[207,496,311],{"class":221},[207,498,499],{"class":209,"line":225},[207,500,501],{"class":221},"canvas.setExtent(layer.extent())\n",[207,503,504],{"class":209,"line":240},[207,505,506],{"class":221},"canvas.refresh()\n",[207,508,509,511],{"class":209,"line":249},[207,510,464],{"class":279},[207,512,513],{"class":221},"(canvas.scale(), canvas.mapUnitsPerPixel())\n",[14,515,516,317,518,521,522,525,526,529],{},[176,517,316],{},[17,519,520],{},"setExtent"," changes what the canvas shows but does not redraw on its own — ",[17,523,524],{},"refresh()"," is what triggers the render, and forgetting it produces the very common \"my zoom did nothing\" report. ",[17,527,528],{},"scale()"," is the denominator of the current map scale, which is what scale-dependent logic compares against. Note that the canvas has its own CRS and extent independent of any layer, so zooming to a layer in a different projection requires transforming the extent first.",[14,531,532,317,535,538,539,542],{},[176,533,534],{},"The active layer.",[17,536,537],{},"iface.activeLayer()"," is whatever is highlighted in the layer tree, and ",[17,540,541],{},"setActiveLayer()"," changes it.",[198,544,546],{"className":200,"code":545,"language":202,"meta":203,"style":203},"layer = iface.activeLayer()\nif layer is None:\n    iface.messageBar().pushWarning(\"Nothing selected\", \"Select a layer first\")\n",[17,547,548,558,571],{"__ignoreMap":203},[207,549,550,553,555],{"class":209,"line":210},[207,551,552],{"class":221},"layer ",[207,554,295],{"class":213},[207,556,557],{"class":221}," iface.activeLayer()\n",[207,559,560,562,565,567,569],{"class":209,"line":225},[207,561,391],{"class":213},[207,563,564],{"class":221}," layer ",[207,566,397],{"class":213},[207,568,403],{"class":279},[207,570,270],{"class":221},[207,572,573,576,579,581,584],{"class":209,"line":240},[207,574,575],{"class":221},"    iface.messageBar().pushWarning(",[207,577,578],{"class":413},"\"Nothing selected\"",[207,580,417],{"class":221},[207,582,583],{"class":413},"\"Select a layer first\"",[207,585,423],{"class":221},[14,587,588,590,591,593],{},[176,589,316],{}," This returns ",[17,592,453],{}," when nothing is selected or when the selection is a group, so the check is not optional in anything a user will run. \"Active layer\" is a purely interface concept — a headless script has no such thing, and code that reaches for it is code that will not run in a batch job. Where a script genuinely operates on one nominated layer, taking it by name from the project is the portable alternative.",[14,595,596,599],{},[176,597,598],{},"The message bar."," The right place for feedback that is not an error.",[198,601,603],{"className":200,"code":602,"language":202,"meta":203,"style":203},"from qgis.core import Qgis\n\niface.messageBar().pushMessage(\n    \"Export complete\", \"Wrote 412 features\", level=Qgis.MessageLevel.Success, duration=6\n)\n",[17,604,605,618,622,627,656],{"__ignoreMap":203},[207,606,607,610,613,615],{"class":209,"line":210},[207,608,609],{"class":213},"from",[207,611,612],{"class":221}," qgis.core ",[207,614,234],{"class":213},[207,616,617],{"class":221}," Qgis\n",[207,619,620],{"class":209,"line":225},[207,621,253],{"emptyLinePlaceholder":252},[207,623,624],{"class":209,"line":240},[207,625,626],{"class":221},"iface.messageBar().pushMessage(\n",[207,628,629,632,634,637,639,643,645,648,651,653],{"class":209,"line":249},[207,630,631],{"class":413},"    \"Export complete\"",[207,633,417],{"class":221},[207,635,636],{"class":413},"\"Wrote 412 features\"",[207,638,417],{"class":221},[207,640,642],{"class":641},"s9osk","level",[207,644,295],{"class":213},[207,646,647],{"class":221},"Qgis.MessageLevel.Success, ",[207,649,650],{"class":641},"duration",[207,652,295],{"class":213},[207,654,655],{"class":279},"6\n",[207,657,658],{"class":209,"line":256},[207,659,423],{"class":221},[14,661,662,317,664,666,667,417,670,417,673,38,676,679,680,684],{},[176,663,316],{},[17,665,650],{}," is in seconds, and zero means the message stays until dismissed — reserve that for things the user must acknowledge. The convenience methods ",[17,668,669],{},"pushInfo",[17,671,672],{},"pushWarning",[17,674,675],{},"pushCritical",[17,677,678],{},"pushSuccess"," cover the common levels with less typing. A message bar notification is far better than a modal dialog for anything the user does not have to answer, and it is covered in more depth in ",[28,681,683],{"href":682},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fshow-messages-with-qgsmessagebar-pyqgis\u002F","showing messages with QgsMessageBar",".",[165,686,688],{"id":687},"menus-toolbars-and-actions","Menus, toolbars and actions",[14,690,691],{},[46,692,695,698,701,704,719,722,724,728,732,736,739,743,746,750,754,757,760,763,768,770,772,779],{"viewBox":693,"role":49,"ariaLabel":694,"xmlns":51},"0 0 760 300","The symmetry between adding a plugin action to the interface and removing it on unload, with each add call paired to a remove call",[53,696,697],{},"Every add needs its remove",[57,699,700],{},"Adding a toolbar icon, a plugin menu entry and a dock widget through iface must be matched by the corresponding removal calls when the plugin unloads. Skipping the removals leaves duplicate icons and menu entries after every plugin reload, and eventually a crash.",[61,702],{"x":63,"y":63,"width":64,"height":703,"fill":66},"300",[705,706,707],"defs",{},[708,709,715],"marker",{"id":710,"viewBox":711,"refX":155,"refY":712,"markerWidth":713,"markerHeight":713,"orient":714},"ifcPairArrow","0 0 10 10","5","7","auto-start-reverse",[716,717],"path",{"d":718,"fill":96},"M0 0 L10 5 L0 10 z",[68,720,721],{"x":70,"y":71,"style":72,"fill":73,"textAnchor":74},"initGui adds; unload removes",[61,723],{"x":78,"y":79,"width":65,"height":88,"rx":82,"fill":123,"stroke":124,"style":85},[68,725,727],{"x":726,"y":89,"style":90,"fill":124,"textAnchor":74},"189","initGui()",[68,729,731],{"x":79,"y":730,"style":95,"fill":96},"106","addToolBarIcon(action)",[68,733,735],{"x":79,"y":734,"style":95,"fill":96},"140","addPluginToMenu(name, action)",[68,737,738],{"x":79,"y":108,"style":95,"fill":96},"addDockWidget(area, dock)",[68,740,742],{"x":79,"y":81,"style":117,"fill":741},"#59645f","called once when the plugin loads",[61,744],{"x":745,"y":79,"width":65,"height":88,"rx":82,"fill":156,"stroke":157,"style":85},"406",[68,747,749],{"x":748,"y":89,"style":90,"fill":157,"textAnchor":74},"571","unload()",[68,751,753],{"x":752,"y":730,"style":95,"fill":96},"428","removeToolBarIcon(action)",[68,755,756],{"x":752,"y":734,"style":95,"fill":96},"removePluginMenu(name, action)",[68,758,759],{"x":752,"y":108,"style":95,"fill":96},"removeDockWidget(dock)",[68,761,762],{"x":752,"y":81,"style":117,"fill":741},"called on reload and on disable",[209,764],{"x1":765,"y1":730,"x2":766,"y2":730,"stroke":96,"style":767},"354","400","stroke-width:1.8;marker-end:url(#ifcPairArrow)",[209,769],{"x1":765,"y1":734,"x2":766,"y2":734,"stroke":96,"style":767},[209,771],{"x1":765,"y1":108,"x2":766,"y2":108,"stroke":96,"style":767},[61,773],{"x":774,"y":775,"width":776,"height":777,"rx":155,"fill":778,"stroke":118,"style":158},"120","258","520","34","#fffdf7",[68,780,781],{"x":70,"y":152,"style":162,"fill":118,"textAnchor":74},"an unpaired add is why reloading a plugin duplicates its toolbar icon",[198,783,785],{"className":200,"code":784,"language":202,"meta":203,"style":203},"from qgis.PyQt.QtWidgets import QAction\nfrom qgis.PyQt.QtGui import QIcon\n\n\nclass MyPlugin:\n    def initGui(self):\n        self.action = QAction(QIcon(\":\u002Fplugins\u002Fmy\u002Ficon.png\"), \"Run export\", self.iface.mainWindow())\n        self.action.triggered.connect(self.run)\n        self.iface.addToolBarIcon(self.action)\n        self.iface.addPluginToMenu(\"&My Plugin\", self.action)\n\n    def unload(self):\n        self.iface.removePluginMenu(\"&My Plugin\", self.action)\n        self.iface.removeToolBarIcon(self.action)\n",[17,786,787,799,811,815,819,827,837,866,878,890,907,912,922,938],{"__ignoreMap":203},[207,788,789,791,794,796],{"class":209,"line":210},[207,790,609],{"class":213},[207,792,793],{"class":221}," qgis.PyQt.QtWidgets ",[207,795,234],{"class":213},[207,797,798],{"class":221}," QAction\n",[207,800,801,803,806,808],{"class":209,"line":225},[207,802,609],{"class":213},[207,804,805],{"class":221}," qgis.PyQt.QtGui ",[207,807,234],{"class":213},[207,809,810],{"class":221}," QIcon\n",[207,812,813],{"class":209,"line":240},[207,814,253],{"emptyLinePlaceholder":252},[207,816,817],{"class":209,"line":249},[207,818,253],{"emptyLinePlaceholder":252},[207,820,821,823,825],{"class":209,"line":256},[207,822,264],{"class":213},[207,824,267],{"class":217},[207,826,270],{"class":221},[207,828,829,831,834],{"class":209,"line":261},[207,830,276],{"class":213},[207,832,833],{"class":217}," initGui",[207,835,836],{"class":221},"(self):\n",[207,838,839,841,844,846,849,852,855,858,860,863],{"class":209,"line":273},[207,840,289],{"class":279},[207,842,843],{"class":221},".action ",[207,845,295],{"class":213},[207,847,848],{"class":221}," QAction(QIcon(",[207,850,851],{"class":413},"\":\u002Fplugins\u002Fmy\u002Ficon.png\"",[207,853,854],{"class":221},"), ",[207,856,857],{"class":413},"\"Run export\"",[207,859,417],{"class":221},[207,861,862],{"class":279},"self",[207,864,865],{"class":221},".iface.mainWindow())\n",[207,867,868,870,873,875],{"class":209,"line":286},[207,869,289],{"class":279},[207,871,872],{"class":221},".action.triggered.connect(",[207,874,862],{"class":279},[207,876,877],{"class":221},".run)\n",[207,879,880,882,885,887],{"class":209,"line":301},[207,881,289],{"class":279},[207,883,884],{"class":221},".iface.addToolBarIcon(",[207,886,862],{"class":279},[207,888,889],{"class":221},".action)\n",[207,891,893,895,898,901,903,905],{"class":209,"line":892},10,[207,894,289],{"class":279},[207,896,897],{"class":221},".iface.addPluginToMenu(",[207,899,900],{"class":413},"\"&My Plugin\"",[207,902,417],{"class":221},[207,904,862],{"class":279},[207,906,889],{"class":221},[207,908,910],{"class":209,"line":909},11,[207,911,253],{"emptyLinePlaceholder":252},[207,913,915,917,920],{"class":209,"line":914},12,[207,916,276],{"class":213},[207,918,919],{"class":217}," unload",[207,921,836],{"class":221},[207,923,925,927,930,932,934,936],{"class":209,"line":924},13,[207,926,289],{"class":279},[207,928,929],{"class":221},".iface.removePluginMenu(",[207,931,900],{"class":413},[207,933,417],{"class":221},[207,935,862],{"class":279},[207,937,889],{"class":221},[207,939,941,943,946,948],{"class":209,"line":940},14,[207,942,289],{"class":279},[207,944,945],{"class":221},".iface.removeToolBarIcon(",[207,947,862],{"class":279},[207,949,889],{"class":221},[14,951,952,954,955,958,959,38,962,965,966,968,969,972],{},[176,953,316],{}," Parenting the action to ",[17,956,957],{},"iface.mainWindow()"," keeps Qt's ownership sane and stops the action being garbage-collected while the toolbar still references it — a classic cause of a menu entry that does nothing. The menu name must match exactly between ",[17,960,961],{},"addPluginToMenu",[17,963,964],{},"removePluginMenu",", ampersand included, or the removal silently fails and the entry persists. Keeping the action on ",[17,967,862],{}," is required so ",[17,970,971],{},"unload"," can reach it.",[165,974,976],{"id":975},"dock-widgets-and-the-main-window","Dock widgets and the main window",[14,978,979],{},"Anything that should live alongside the canvas rather than in front of it belongs in a dock.",[198,981,983],{"className":200,"code":982,"language":202,"meta":203,"style":203},"from qgis.PyQt.QtWidgets import QDockWidget, QLabel\nfrom qgis.PyQt.QtCore import Qt\n\ndock = QDockWidget(\"Export status\", iface.mainWindow())\ndock.setObjectName(\"MyPluginDock\")\ndock.setWidget(QLabel(\"Idle\"))\niface.addDockWidget(Qt.RightDockWidgetArea, dock)\n",[17,984,985,996,1008,1012,1028,1038,1049],{"__ignoreMap":203},[207,986,987,989,991,993],{"class":209,"line":210},[207,988,609],{"class":213},[207,990,793],{"class":221},[207,992,234],{"class":213},[207,994,995],{"class":221}," QDockWidget, QLabel\n",[207,997,998,1000,1003,1005],{"class":209,"line":225},[207,999,609],{"class":213},[207,1001,1002],{"class":221}," qgis.PyQt.QtCore ",[207,1004,234],{"class":213},[207,1006,1007],{"class":221}," Qt\n",[207,1009,1010],{"class":209,"line":240},[207,1011,253],{"emptyLinePlaceholder":252},[207,1013,1014,1017,1019,1022,1025],{"class":209,"line":249},[207,1015,1016],{"class":221},"dock ",[207,1018,295],{"class":213},[207,1020,1021],{"class":221}," QDockWidget(",[207,1023,1024],{"class":413},"\"Export status\"",[207,1026,1027],{"class":221},", iface.mainWindow())\n",[207,1029,1030,1033,1036],{"class":209,"line":256},[207,1031,1032],{"class":221},"dock.setObjectName(",[207,1034,1035],{"class":413},"\"MyPluginDock\"",[207,1037,423],{"class":221},[207,1039,1040,1043,1046],{"class":209,"line":261},[207,1041,1042],{"class":221},"dock.setWidget(QLabel(",[207,1044,1045],{"class":413},"\"Idle\"",[207,1047,1048],{"class":221},"))\n",[207,1050,1051],{"class":209,"line":273},[207,1052,1053],{"class":221},"iface.addDockWidget(Qt.RightDockWidgetArea, dock)\n",[14,1055,1056,317,1058,1061,1062,1064,1065,1068],{},[176,1057,316],{},[17,1059,1060],{},"setObjectName"," is what lets QGIS remember the dock's position and visibility between sessions — a dock without one reappears in the default place every time and is a small, permanent irritation for users. Parenting to ",[17,1063,957],{}," again keeps Qt's ownership correct. ",[17,1066,1067],{},"addDockWidget"," takes the Qt dock area constant, and QGIS handles tabbing it with any dock already in that area, which is why a plugin should generally not fight for a specific corner.",[14,1070,1071,1073],{},[17,1072,957],{}," itself is useful beyond parenting: it is the right parent for any modal dialog, so that the dialog centres on QGIS and blocks the correct window. A dialog created with no parent can appear behind the main window, which reads to the user as the application having frozen.",[165,1075,1077],{"id":1076},"what-to-use-instead-where-you-can","What to use instead, where you can",[14,1079,1080],{},"Anything that touches data has a non-interface equivalent, and preferring it makes scripts portable.",[1082,1083,1084,1097],"table",{},[1085,1086,1087],"thead",{},[1088,1089,1090,1094],"tr",{},[1091,1092,1093],"th",{},"Interface way",[1091,1095,1096],{},"Portable way",[1098,1099,1100,1112,1124,1138,1150],"tbody",{},[1088,1101,1102,1107],{},[1103,1104,1105],"td",{},[17,1106,537],{},[1103,1108,1109],{},[17,1110,1111],{},"QgsProject.instance().mapLayersByName(\"roads\")[0]",[1088,1113,1114,1119],{},[1103,1115,1116],{},[17,1117,1118],{},"iface.addVectorLayer(path, name, \"ogr\")",[1103,1120,1121],{},[17,1122,1123],{},"QgsProject.instance().addMapLayer(QgsVectorLayer(path, name, \"ogr\"))",[1088,1125,1126,1131],{},[1103,1127,1128],{},[17,1129,1130],{},"iface.mapCanvas().setExtent(...)",[1103,1132,1133,1134,1137],{},"set the extent on a ",[17,1135,1136],{},"QgsMapSettings"," for rendering",[1088,1139,1140,1145],{},[1103,1141,1142],{},[17,1143,1144],{},"iface.messageBar().pushInfo(...)",[1103,1146,1147],{},[17,1148,1149],{},"QgsMessageLog.logMessage(...)",[1088,1151,1152,1157],{},[1103,1153,1154],{},[17,1155,1156],{},"iface.zoomToActiveLayer()",[1103,1158,1159],{},"compute the extent and use it explicitly",[14,1161,1162,1163,1165,1166,1168,1169,1172,1173,1177],{},"The general rule is that ",[17,1164,19],{}," is for talking to a person and ",[17,1167,41],{}," is for talking to data. A script that only uses the second runs in the console, in a plugin, in ",[17,1170,1171],{},"qgis_process"," and in a cron job without modification, which is the property that makes ",[28,1174,1176],{"href":1175},"\u002Fpyqgis-fundamentals-environment-setup\u002Fheadless-qgis-and-server-automation\u002F","headless automation"," possible at all.",[165,1179,1181],{"id":1180},"qgis-version-compatibility","QGIS version compatibility",[14,1183,1184,1186,1187,1190,1191,1194,1195,1198],{},[17,1185,327],{}," has been remarkably stable across QGIS 3. Methods have been added rather than removed, so code written for 3.4 generally still runs on 3.44. The ",[17,1188,1189],{},"Qgis.MessageLevel"," enum moved into the scoped namespace in 3.30 with the old ",[17,1192,1193],{},"Qgis.Success"," names retained. ",[17,1196,1197],{},"iface.addDockWidget"," and its removal counterpart are unchanged throughout.",[165,1200,1202],{"id":1201},"troubleshooting","Troubleshooting",[170,1204,1205,1217,1228,1240,1246,1257],{},[173,1206,1207,1212,1213,1216],{},[176,1208,1209,684],{},[17,1210,1211],{},"NameError: name 'iface' is not defined"," Not in the console — import it from ",[17,1214,1215],{},"qgis.utils",", or accept that there is none.",[173,1218,1219,1227],{},[176,1220,1221,1223,1224,1226],{},[17,1222,19],{}," is ",[17,1225,453],{}," in a standalone script."," Expected. There is no interface without a window.",[173,1229,1230,317,1233,1235,1236,1239],{},[176,1231,1232],{},"The toolbar icon appears twice after a reload.",[17,1234,971],{}," does not remove what ",[17,1237,1238],{},"initGui"," added.",[173,1241,1242,1245],{},[176,1243,1244],{},"A menu entry survives unloading."," The menu name string does not match between add and remove.",[173,1247,1248,1256],{},[176,1249,1250,1252,1253,1255],{},[17,1251,113],{}," returns ",[17,1254,453],{}," unexpectedly."," A group is selected, or focus is in a different panel.",[173,1258,1259,317,1262,1264],{},[176,1260,1261],{},"A canvas change does not appear.",[17,1263,524],{}," was not called after changing the extent.",[165,1266,1268],{"id":1267},"conclusion","Conclusion",[14,1270,1271,1272,1274,1275,1277,1278,1281,1282,1285],{},"Treat ",[17,1273,19],{}," as the interface layer and nothing more: use it for the canvas, the message bar, menus and dialogs, and use ",[17,1276,41],{}," for everything about the data. Pair every ",[17,1279,1280],{},"add"," with a ",[17,1283,1284],{},"remove"," in a plugin, and guard the import wherever the code might run headless. That split is what turns console experiments into scripts that survive being scheduled.",[165,1287,1289],{"id":1288},"frequently-asked-questions","Frequently Asked Questions",[14,1291,1292,1295],{},[176,1293,1294],{},"Can I create an iface object myself?","\nNo. It is provided by the running application. For tests, a mock implementing the methods you use is the standard approach.",[14,1297,1298,1301,1304],{},[176,1299,1300],{},"How do I open a layer's attribute table from code?",[17,1302,1303],{},"iface.showAttributeTable(layer)",", which returns the dialog so you can position or filter it. It is one of the several dialog helpers on the interface.",[14,1306,1307,1313],{},[176,1308,1309,1310,1312],{},"Is ",[17,1311,477],{}," the same canvas as in a layout?","\nNo. A layout map item renders independently, which is why layout output is unaffected by the canvas extent.",[14,1315,1316,1319,1322,1323,1326,1327,684],{},[176,1317,1318],{},"Where do I find the full list of methods?",[17,1320,1321],{},"dir(iface)"," in the console is the fastest route, and ",[17,1324,1325],{},"help(iface.addDockWidget)"," gives the signature — the approach described in ",[28,1328,1330],{"href":1329},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-api-architecture\u002Fexplore-pyqgis-api-with-dir-and-help\u002F","exploring the PyQGIS API with dir and help",[165,1332,1334],{"id":1333},"related","Related",[170,1336,1337,1342,1348,1353,1359],{},[173,1338,1339,1341],{},[28,1340,31],{"href":30}," — the guide this recipe belongs to",[173,1343,1344],{},[28,1345,1347],{"href":1346},"\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Faccess-active-layer-and-selection-pyqgis\u002F","Access the Active Layer and Selection in PyQGIS",[173,1349,1350],{},[28,1351,1352],{"href":1329},"Explore the PyQGIS API with dir and help",[173,1354,1355],{},[28,1356,1358],{"href":1357},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin\u002F","Add a Toolbar Button to a QGIS Plugin",[173,1360,1361],{},[28,1362,1364],{"href":1363},"\u002Fpyqgis-fundamentals-environment-setup\u002Fvirtual-environments-for-gis\u002Frunning-python-scripts-outside-qgis-desktop\u002F","Running Python Scripts Outside QGIS Desktop",[1366,1367,1368],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":203,"searchDepth":225,"depth":225,"links":1370},[1371,1372,1373,1374,1375,1376,1377,1378,1379,1380,1381],{"id":167,"depth":225,"text":168},{"id":189,"depth":225,"text":190},{"id":468,"depth":225,"text":469},{"id":687,"depth":225,"text":688},{"id":975,"depth":225,"text":976},{"id":1076,"depth":225,"text":1077},{"id":1180,"depth":225,"text":1181},{"id":1201,"depth":225,"text":1202},{"id":1267,"depth":225,"text":1268},{"id":1288,"depth":225,"text":1289},{"id":1333,"depth":225,"text":1334},"What the iface object gives you — canvas, layer tree, menus, toolbars, message bar and dialogs — how to reach it from a plugin, and why it does not exist in a headless script.","md",{"slug":1385,"type":1386,"breadcrumb":1387,"datePublished":1388,"dateModified":1388},"use-iface-to-control-qgis-interface-pyqgis","article","Use iface","2026-09-05","\u002Fpyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Fuse-iface-to-control-qgis-interface-pyqgis",{"title":5,"description":1382},"pyqgis-fundamentals-environment-setup\u002Fqgis-python-console-basics\u002Fuse-iface-to-control-qgis-interface-pyqgis\u002Findex","cewvNeW1CdQuW18A827vTxBT_CQNqZdqZxEaiKG27F0",1788563847745]