[{"data":1,"prerenderedAt":2473},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis":3},{"id":4,"title":5,"body":6,"description":2461,"extension":2462,"meta":2463,"navigation":211,"path":2469,"seo":2470,"stem":2471,"__hash__":2472},"docs\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis\u002Findex.md","Add a Custom Dock Widget in PyQGIS",{"type":7,"value":8,"toc":2447},"minimark",[9,13,41,57,62,105,109,127,752,785,789,810,1047,1091,1095,1108,1310,1697,1725,1729,1746,1923,1949,1953,1956,2037,2040,2044,2047,2136,2140,2143,2233,2243,2247,2269,2281,2300,2315,2328,2332,2364,2368,2377,2391,2411,2425,2429,2443],[10,11,5],"h1",{"id":12},"add-a-custom-dock-widget-in-pyqgis",[14,15,16,17,21,22,24,25,28,29,32,33,36,37,40],"p",{},"A dock widget is the right container for any plugin tool that should stay visible while the user works — a layer inspector, a live query panel, a styling controller. Unlike a modal dialog that interrupts the workflow, a ",[18,19,20],"code",{},"QDockWidget"," snaps into the QGIS window alongside the Layers and Browser panels, can be moved, floated, or hidden, and persists across the session. This page builds one two ways — by subclassing ",[18,23,20],{}," directly and by loading a Qt Designer ",[18,26,27],{},".ui"," with ",[18,30,31],{},"uic.loadUiType"," — embeds GIS-aware widgets inside it, docks it with ",[18,34,35],{},"addDockWidget",", remembers its visibility between sessions, and removes it cleanly in ",[18,38,39],{},"unload()",".",[14,42,43,44,49,50,52,53,40],{},"This task belongs to ",[45,46,48],"a",{"href":47},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002F","Qt Designer for GIS Interfaces",", which covers the broader ",[18,51,27],{}," workflow. A dock widget is typically opened by a toolbar control, so it pairs naturally with ",[45,54,56],{"href":55},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fadd-toolbar-button-to-qgis-plugin\u002F","Add a Toolbar Button to a QGIS Plugin",[58,59,61],"h2",{"id":60},"prerequisites","Prerequisites",[63,64,65,73,92,99],"ul",{},[66,67,68,72],"li",{},[69,70,71],"strong",{},"QGIS 3.34 LTR"," (Python 3.12) with the Python console available",[66,74,75,76,79,80,83,84,86,87,91],{},"A plugin skeleton with a main class that receives ",[18,77,78],{},"iface"," and implements ",[18,81,82],{},"initGui()"," \u002F ",[18,85,39],{}," — generate one with ",[45,88,90],{"href":89},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Fcreate-qgis-plugin-with-plugin-builder\u002F","Create a QGIS Plugin with Plugin Builder 3"," if needed",[66,93,94,95,98],{},"Basic Qt layout knowledge (",[18,96,97],{},"QVBoxLayout",", signal\u002Fslot connections)",[66,100,101,102,104],{},"For the ",[18,103,27],{}," approach, Qt Designer installed and widget promotion understood",[58,106,108],{"id":107},"approach-a-subclass-qdockwidget-directly","Approach A: Subclass QDockWidget Directly",[14,110,111,112,114,115,118,119,122,123,126],{},"For a panel with a handful of controls, writing the widget in Python avoids a ",[18,113,27],{}," round-trip. Build a ",[18,116,117],{},"QWidget"," content area, lay it out, and set it as the dock's content. Embedding ",[18,120,121],{},"QgsMapLayerComboBox"," and ",[18,124,125],{},"QgsFieldComboBox"," gives users the same layer and field pickers QGIS uses natively.",[128,129,134],"pre",{"className":130,"code":131,"language":132,"meta":133,"style":133},"language-python shiki shiki-themes github-dark","from qgis.PyQt.QtCore import Qt, pyqtSignal\nfrom qgis.PyQt.QtWidgets import (\n    QDockWidget, QWidget, QVBoxLayout, QLabel, QPushButton,\n)\nfrom qgis.gui import QgsMapLayerComboBox, QgsFieldComboBox\nfrom qgis.core import QgsMapLayerProxyModel\n\n\nclass LayerInspectorDock(QDockWidget):\n    \"\"\"A dockable panel that reports feature counts for a chosen field.\"\"\"\n\n    closingPlugin = pyqtSignal()\n\n    def __init__(self, iface, parent=None):\n        super().__init__(\"Layer Inspector\", parent)\n        self.iface = iface\n        self.setObjectName(\"LayerInspectorDock\")  # needed to persist state\n\n        content = QWidget(self)\n        layout = QVBoxLayout(content)\n\n        layout.addWidget(QLabel(\"Layer\"))\n        self.layer_combo = QgsMapLayerComboBox()\n        self.layer_combo.setFilters(QgsMapLayerProxyModel.VectorLayer)\n        layout.addWidget(self.layer_combo)\n\n        layout.addWidget(QLabel(\"Field\"))\n        self.field_combo = QgsFieldComboBox()\n        layout.addWidget(self.field_combo)\n\n        self.count_button = QPushButton(\"Count distinct values\")\n        layout.addWidget(self.count_button)\n        self.result = QLabel(\"--\")\n        layout.addWidget(self.result)\n        layout.addStretch()\n\n        self.setWidget(content)\n\n        # Keep the field combo in sync with the selected layer\n        self.layer_combo.layerChanged.connect(self.field_combo.setLayer)\n        self.field_combo.setLayer(self.layer_combo.currentLayer())\n        self.count_button.clicked.connect(self._count)\n\n    def _count(self):\n        layer = self.layer_combo.currentLayer()\n        field = self.field_combo.currentField()\n        if layer is None or not field:\n            self.result.setText(\"Pick a layer and field\")\n            return\n        idx = layer.fields().indexOf(field)\n        distinct = layer.uniqueValues(idx)\n        self.result.setText(f\"{len(distinct)} distinct values\")\n\n    def closeEvent(self, event):\n        self.closingPlugin.emit()\n        event.accept()\n","python","",[18,135,136,155,168,174,180,193,206,213,218,236,243,248,260,265,285,305,319,337,342,358,369,374,386,399,407,418,423,433,446,456,461,479,489,507,517,523,528,536,541,547,560,573,586,591,602,616,629,653,667,673,684,695,722,727,738,746],{"__ignoreMap":133},[137,138,141,145,149,152],"span",{"class":139,"line":140},"line",1,[137,142,144],{"class":143},"snl16","from",[137,146,148],{"class":147},"s95oV"," qgis.PyQt.QtCore ",[137,150,151],{"class":143},"import",[137,153,154],{"class":147}," Qt, pyqtSignal\n",[137,156,158,160,163,165],{"class":139,"line":157},2,[137,159,144],{"class":143},[137,161,162],{"class":147}," qgis.PyQt.QtWidgets ",[137,164,151],{"class":143},[137,166,167],{"class":147}," (\n",[137,169,171],{"class":139,"line":170},3,[137,172,173],{"class":147},"    QDockWidget, QWidget, QVBoxLayout, QLabel, QPushButton,\n",[137,175,177],{"class":139,"line":176},4,[137,178,179],{"class":147},")\n",[137,181,183,185,188,190],{"class":139,"line":182},5,[137,184,144],{"class":143},[137,186,187],{"class":147}," qgis.gui ",[137,189,151],{"class":143},[137,191,192],{"class":147}," QgsMapLayerComboBox, QgsFieldComboBox\n",[137,194,196,198,201,203],{"class":139,"line":195},6,[137,197,144],{"class":143},[137,199,200],{"class":147}," qgis.core ",[137,202,151],{"class":143},[137,204,205],{"class":147}," QgsMapLayerProxyModel\n",[137,207,209],{"class":139,"line":208},7,[137,210,212],{"emptyLinePlaceholder":211},true,"\n",[137,214,216],{"class":139,"line":215},8,[137,217,212],{"emptyLinePlaceholder":211},[137,219,221,224,228,231,233],{"class":139,"line":220},9,[137,222,223],{"class":143},"class",[137,225,227],{"class":226},"svObZ"," LayerInspectorDock",[137,229,230],{"class":147},"(",[137,232,20],{"class":226},[137,234,235],{"class":147},"):\n",[137,237,239],{"class":139,"line":238},10,[137,240,242],{"class":241},"sU2Wk","    \"\"\"A dockable panel that reports feature counts for a chosen field.\"\"\"\n",[137,244,246],{"class":139,"line":245},11,[137,247,212],{"emptyLinePlaceholder":211},[137,249,251,254,257],{"class":139,"line":250},12,[137,252,253],{"class":147},"    closingPlugin ",[137,255,256],{"class":143},"=",[137,258,259],{"class":147}," pyqtSignal()\n",[137,261,263],{"class":139,"line":262},13,[137,264,212],{"emptyLinePlaceholder":211},[137,266,268,271,275,278,280,283],{"class":139,"line":267},14,[137,269,270],{"class":143},"    def",[137,272,274],{"class":273},"sDLfK"," __init__",[137,276,277],{"class":147},"(self, iface, parent",[137,279,256],{"class":143},[137,281,282],{"class":273},"None",[137,284,235],{"class":147},[137,286,288,291,294,297,299,302],{"class":139,"line":287},15,[137,289,290],{"class":273},"        super",[137,292,293],{"class":147},"().",[137,295,296],{"class":273},"__init__",[137,298,230],{"class":147},[137,300,301],{"class":241},"\"Layer Inspector\"",[137,303,304],{"class":147},", parent)\n",[137,306,308,311,314,316],{"class":139,"line":307},16,[137,309,310],{"class":273},"        self",[137,312,313],{"class":147},".iface ",[137,315,256],{"class":143},[137,317,318],{"class":147}," iface\n",[137,320,322,324,327,330,333],{"class":139,"line":321},17,[137,323,310],{"class":273},[137,325,326],{"class":147},".setObjectName(",[137,328,329],{"class":241},"\"LayerInspectorDock\"",[137,331,332],{"class":147},")  ",[137,334,336],{"class":335},"sjoCn","# needed to persist state\n",[137,338,340],{"class":139,"line":339},18,[137,341,212],{"emptyLinePlaceholder":211},[137,343,345,348,350,353,356],{"class":139,"line":344},19,[137,346,347],{"class":147},"        content ",[137,349,256],{"class":143},[137,351,352],{"class":147}," QWidget(",[137,354,355],{"class":273},"self",[137,357,179],{"class":147},[137,359,361,364,366],{"class":139,"line":360},20,[137,362,363],{"class":147},"        layout ",[137,365,256],{"class":143},[137,367,368],{"class":147}," QVBoxLayout(content)\n",[137,370,372],{"class":139,"line":371},21,[137,373,212],{"emptyLinePlaceholder":211},[137,375,377,380,383],{"class":139,"line":376},22,[137,378,379],{"class":147},"        layout.addWidget(QLabel(",[137,381,382],{"class":241},"\"Layer\"",[137,384,385],{"class":147},"))\n",[137,387,389,391,394,396],{"class":139,"line":388},23,[137,390,310],{"class":273},[137,392,393],{"class":147},".layer_combo ",[137,395,256],{"class":143},[137,397,398],{"class":147}," QgsMapLayerComboBox()\n",[137,400,402,404],{"class":139,"line":401},24,[137,403,310],{"class":273},[137,405,406],{"class":147},".layer_combo.setFilters(QgsMapLayerProxyModel.VectorLayer)\n",[137,408,410,413,415],{"class":139,"line":409},25,[137,411,412],{"class":147},"        layout.addWidget(",[137,414,355],{"class":273},[137,416,417],{"class":147},".layer_combo)\n",[137,419,421],{"class":139,"line":420},26,[137,422,212],{"emptyLinePlaceholder":211},[137,424,426,428,431],{"class":139,"line":425},27,[137,427,379],{"class":147},[137,429,430],{"class":241},"\"Field\"",[137,432,385],{"class":147},[137,434,436,438,441,443],{"class":139,"line":435},28,[137,437,310],{"class":273},[137,439,440],{"class":147},".field_combo ",[137,442,256],{"class":143},[137,444,445],{"class":147}," QgsFieldComboBox()\n",[137,447,449,451,453],{"class":139,"line":448},29,[137,450,412],{"class":147},[137,452,355],{"class":273},[137,454,455],{"class":147},".field_combo)\n",[137,457,459],{"class":139,"line":458},30,[137,460,212],{"emptyLinePlaceholder":211},[137,462,464,466,469,471,474,477],{"class":139,"line":463},31,[137,465,310],{"class":273},[137,467,468],{"class":147},".count_button ",[137,470,256],{"class":143},[137,472,473],{"class":147}," QPushButton(",[137,475,476],{"class":241},"\"Count distinct values\"",[137,478,179],{"class":147},[137,480,482,484,486],{"class":139,"line":481},32,[137,483,412],{"class":147},[137,485,355],{"class":273},[137,487,488],{"class":147},".count_button)\n",[137,490,492,494,497,499,502,505],{"class":139,"line":491},33,[137,493,310],{"class":273},[137,495,496],{"class":147},".result ",[137,498,256],{"class":143},[137,500,501],{"class":147}," QLabel(",[137,503,504],{"class":241},"\"--\"",[137,506,179],{"class":147},[137,508,510,512,514],{"class":139,"line":509},34,[137,511,412],{"class":147},[137,513,355],{"class":273},[137,515,516],{"class":147},".result)\n",[137,518,520],{"class":139,"line":519},35,[137,521,522],{"class":147},"        layout.addStretch()\n",[137,524,526],{"class":139,"line":525},36,[137,527,212],{"emptyLinePlaceholder":211},[137,529,531,533],{"class":139,"line":530},37,[137,532,310],{"class":273},[137,534,535],{"class":147},".setWidget(content)\n",[137,537,539],{"class":139,"line":538},38,[137,540,212],{"emptyLinePlaceholder":211},[137,542,544],{"class":139,"line":543},39,[137,545,546],{"class":335},"        # Keep the field combo in sync with the selected layer\n",[137,548,550,552,555,557],{"class":139,"line":549},40,[137,551,310],{"class":273},[137,553,554],{"class":147},".layer_combo.layerChanged.connect(",[137,556,355],{"class":273},[137,558,559],{"class":147},".field_combo.setLayer)\n",[137,561,563,565,568,570],{"class":139,"line":562},41,[137,564,310],{"class":273},[137,566,567],{"class":147},".field_combo.setLayer(",[137,569,355],{"class":273},[137,571,572],{"class":147},".layer_combo.currentLayer())\n",[137,574,576,578,581,583],{"class":139,"line":575},42,[137,577,310],{"class":273},[137,579,580],{"class":147},".count_button.clicked.connect(",[137,582,355],{"class":273},[137,584,585],{"class":147},"._count)\n",[137,587,589],{"class":139,"line":588},43,[137,590,212],{"emptyLinePlaceholder":211},[137,592,594,596,599],{"class":139,"line":593},44,[137,595,270],{"class":143},[137,597,598],{"class":226}," _count",[137,600,601],{"class":147},"(self):\n",[137,603,605,608,610,613],{"class":139,"line":604},45,[137,606,607],{"class":147},"        layer ",[137,609,256],{"class":143},[137,611,612],{"class":273}," self",[137,614,615],{"class":147},".layer_combo.currentLayer()\n",[137,617,619,622,624,626],{"class":139,"line":618},46,[137,620,621],{"class":147},"        field ",[137,623,256],{"class":143},[137,625,612],{"class":273},[137,627,628],{"class":147},".field_combo.currentField()\n",[137,630,632,635,638,641,644,647,650],{"class":139,"line":631},47,[137,633,634],{"class":143},"        if",[137,636,637],{"class":147}," layer ",[137,639,640],{"class":143},"is",[137,642,643],{"class":273}," None",[137,645,646],{"class":143}," or",[137,648,649],{"class":143}," not",[137,651,652],{"class":147}," field:\n",[137,654,656,659,662,665],{"class":139,"line":655},48,[137,657,658],{"class":273},"            self",[137,660,661],{"class":147},".result.setText(",[137,663,664],{"class":241},"\"Pick a layer and field\"",[137,666,179],{"class":147},[137,668,670],{"class":139,"line":669},49,[137,671,672],{"class":143},"            return\n",[137,674,676,679,681],{"class":139,"line":675},50,[137,677,678],{"class":147},"        idx ",[137,680,256],{"class":143},[137,682,683],{"class":147}," layer.fields().indexOf(field)\n",[137,685,687,690,692],{"class":139,"line":686},51,[137,688,689],{"class":147},"        distinct ",[137,691,256],{"class":143},[137,693,694],{"class":147}," layer.uniqueValues(idx)\n",[137,696,698,700,702,705,708,711,714,717,720],{"class":139,"line":697},52,[137,699,310],{"class":273},[137,701,661],{"class":147},[137,703,704],{"class":143},"f",[137,706,707],{"class":241},"\"",[137,709,710],{"class":273},"{len",[137,712,713],{"class":147},"(distinct)",[137,715,716],{"class":273},"}",[137,718,719],{"class":241}," distinct values\"",[137,721,179],{"class":147},[137,723,725],{"class":139,"line":724},53,[137,726,212],{"emptyLinePlaceholder":211},[137,728,730,732,735],{"class":139,"line":729},54,[137,731,270],{"class":143},[137,733,734],{"class":226}," closeEvent",[137,736,737],{"class":147},"(self, event):\n",[137,739,741,743],{"class":139,"line":740},55,[137,742,310],{"class":273},[137,744,745],{"class":147},".closingPlugin.emit()\n",[137,747,749],{"class":139,"line":748},56,[137,750,751],{"class":147},"        event.accept()\n",[14,753,754,757,758,760,761,764,765,768,769,772,773,776,777,780,781,784],{},[69,755,756],{},"Breakdown:"," The dock title (",[18,759,301],{},") appears on the panel's title bar. ",[18,762,763],{},"setObjectName"," is mandatory for QGIS to save and restore the dock's position. ",[18,766,767],{},"QgsMapLayerComboBox.setFilters(QgsMapLayerProxyModel.VectorLayer)"," limits the picker to vector layers; its ",[18,770,771],{},"layerChanged"," signal feeds ",[18,774,775],{},"QgsFieldComboBox.setLayer",", so the field list always reflects the chosen layer's schema. ",[18,778,779],{},"layer.uniqueValues(index)"," returns the set of distinct values for the field. The custom ",[18,782,783],{},"closingPlugin"," signal lets the host plugin react when the user clicks the dock's close button, which is how we keep the toolbar toggle in sync below.",[58,786,788],{"id":787},"approach-b-load-a-ui-file-with-uicloaduitype","Approach B: Load a .ui File with uic.loadUiType",[14,790,791,792,795,796,798,799,802,803,805,806,809],{},"When the panel grows or a designer maintains the layout, build it in Qt Designer from the ",[69,793,794],{},"Dock Widget"," template and load it at runtime. Promote the layer and field pickers in Designer (",[18,797,121],{}," → header ",[18,800,801],{},"qgsmaplayercombobox.h","; ",[18,804,125],{}," → ",[18,807,808],{},"qgsfieldcombobox.h",") so the same widgets appear without manual instantiation.",[128,811,813],{"className":130,"code":812,"language":132,"meta":133,"style":133},"import os\nfrom qgis.PyQt import uic\nfrom qgis.PyQt.QtCore import pyqtSignal\nfrom qgis.core import QgsMapLayerProxyModel\n\nFORM_CLASS, _ = uic.loadUiType(\n    os.path.join(os.path.dirname(__file__), \"ui\", \"inspector_dock_base.ui\")\n)\n\n\nclass LayerInspectorDock(FORM_CLASS):\n    \"\"\"Dock widget whose layout comes from a Qt Designer .ui file.\"\"\"\n\n    closingPlugin = pyqtSignal()\n\n    def __init__(self, iface, parent=None):\n        super().__init__(parent)\n        self.setupUi(self)\n        self.iface = iface\n\n        # Widgets below are defined and promoted in the .ui file\n        self.mLayerCombo.setFilters(QgsMapLayerProxyModel.VectorLayer)\n        self.mLayerCombo.layerChanged.connect(self.mFieldCombo.setLayer)\n        self.mFieldCombo.setLayer(self.mLayerCombo.currentLayer())\n\n    def closeEvent(self, event):\n        self.closingPlugin.emit()\n        event.accept()\n",[18,814,815,822,834,845,855,859,872,894,898,902,906,918,923,927,935,939,953,964,975,985,989,994,1001,1013,1025,1029,1037,1043],{"__ignoreMap":133},[137,816,817,819],{"class":139,"line":140},[137,818,151],{"class":143},[137,820,821],{"class":147}," os\n",[137,823,824,826,829,831],{"class":139,"line":157},[137,825,144],{"class":143},[137,827,828],{"class":147}," qgis.PyQt ",[137,830,151],{"class":143},[137,832,833],{"class":147}," uic\n",[137,835,836,838,840,842],{"class":139,"line":170},[137,837,144],{"class":143},[137,839,148],{"class":147},[137,841,151],{"class":143},[137,843,844],{"class":147}," pyqtSignal\n",[137,846,847,849,851,853],{"class":139,"line":176},[137,848,144],{"class":143},[137,850,200],{"class":147},[137,852,151],{"class":143},[137,854,205],{"class":147},[137,856,857],{"class":139,"line":182},[137,858,212],{"emptyLinePlaceholder":211},[137,860,861,864,867,869],{"class":139,"line":195},[137,862,863],{"class":273},"FORM_CLASS",[137,865,866],{"class":147},", _ ",[137,868,256],{"class":143},[137,870,871],{"class":147}," uic.loadUiType(\n",[137,873,874,877,880,883,886,889,892],{"class":139,"line":208},[137,875,876],{"class":147},"    os.path.join(os.path.dirname(",[137,878,879],{"class":273},"__file__",[137,881,882],{"class":147},"), ",[137,884,885],{"class":241},"\"ui\"",[137,887,888],{"class":147},", ",[137,890,891],{"class":241},"\"inspector_dock_base.ui\"",[137,893,179],{"class":147},[137,895,896],{"class":139,"line":215},[137,897,179],{"class":147},[137,899,900],{"class":139,"line":220},[137,901,212],{"emptyLinePlaceholder":211},[137,903,904],{"class":139,"line":238},[137,905,212],{"emptyLinePlaceholder":211},[137,907,908,910,912,914,916],{"class":139,"line":245},[137,909,223],{"class":143},[137,911,227],{"class":226},[137,913,230],{"class":147},[137,915,863],{"class":273},[137,917,235],{"class":147},[137,919,920],{"class":139,"line":250},[137,921,922],{"class":241},"    \"\"\"Dock widget whose layout comes from a Qt Designer .ui file.\"\"\"\n",[137,924,925],{"class":139,"line":262},[137,926,212],{"emptyLinePlaceholder":211},[137,928,929,931,933],{"class":139,"line":267},[137,930,253],{"class":147},[137,932,256],{"class":143},[137,934,259],{"class":147},[137,936,937],{"class":139,"line":287},[137,938,212],{"emptyLinePlaceholder":211},[137,940,941,943,945,947,949,951],{"class":139,"line":307},[137,942,270],{"class":143},[137,944,274],{"class":273},[137,946,277],{"class":147},[137,948,256],{"class":143},[137,950,282],{"class":273},[137,952,235],{"class":147},[137,954,955,957,959,961],{"class":139,"line":321},[137,956,290],{"class":273},[137,958,293],{"class":147},[137,960,296],{"class":273},[137,962,963],{"class":147},"(parent)\n",[137,965,966,968,971,973],{"class":139,"line":339},[137,967,310],{"class":273},[137,969,970],{"class":147},".setupUi(",[137,972,355],{"class":273},[137,974,179],{"class":147},[137,976,977,979,981,983],{"class":139,"line":344},[137,978,310],{"class":273},[137,980,313],{"class":147},[137,982,256],{"class":143},[137,984,318],{"class":147},[137,986,987],{"class":139,"line":360},[137,988,212],{"emptyLinePlaceholder":211},[137,990,991],{"class":139,"line":371},[137,992,993],{"class":335},"        # Widgets below are defined and promoted in the .ui file\n",[137,995,996,998],{"class":139,"line":376},[137,997,310],{"class":273},[137,999,1000],{"class":147},".mLayerCombo.setFilters(QgsMapLayerProxyModel.VectorLayer)\n",[137,1002,1003,1005,1008,1010],{"class":139,"line":388},[137,1004,310],{"class":273},[137,1006,1007],{"class":147},".mLayerCombo.layerChanged.connect(",[137,1009,355],{"class":273},[137,1011,1012],{"class":147},".mFieldCombo.setLayer)\n",[137,1014,1015,1017,1020,1022],{"class":139,"line":401},[137,1016,310],{"class":273},[137,1018,1019],{"class":147},".mFieldCombo.setLayer(",[137,1021,355],{"class":273},[137,1023,1024],{"class":147},".mLayerCombo.currentLayer())\n",[137,1026,1027],{"class":139,"line":409},[137,1028,212],{"emptyLinePlaceholder":211},[137,1030,1031,1033,1035],{"class":139,"line":420},[137,1032,270],{"class":143},[137,1034,734],{"class":226},[137,1036,737],{"class":147},[137,1038,1039,1041],{"class":139,"line":425},[137,1040,310],{"class":273},[137,1042,745],{"class":147},[137,1044,1045],{"class":139,"line":435},[137,1046,751],{"class":147},[14,1048,1049,1051,1052,1055,1056,1058,1059,1061,1062,1065,1066,1069,1070,1073,1074,888,1077,1080,1081,1084,1085,1087,1088,1090],{},[69,1050,756],{}," ",[18,1053,1054],{},"uic.loadUiType()"," parses the ",[18,1057,27],{}," XML once at import time and returns a base class — for a Dock Widget template that base is already a ",[18,1060,20],{},", so the subclass ",[1063,1064,640],"em",{}," a dock. ",[18,1067,1068],{},"setupUi(self)"," instantiates every widget defined in Designer and attaches it as an attribute named after its ",[18,1071,1072],{},"objectName"," (",[18,1075,1076],{},"mLayerCombo",[18,1078,1079],{},"mFieldCombo","). Runtime loading sidesteps ",[18,1082,1083],{},"pyuic5"," version mismatches, which is why it is preferred over static compilation; the trade-off is that the ",[18,1086,27],{}," file must ship inside the plugin. Notice there is no ",[18,1089,763],{}," call here — set it on the dock in Designer's property editor instead.",[58,1092,1094],{"id":1093},"docking-toggling-and-removing-on-unload","Docking, Toggling, and Removing on Unload",[14,1096,1097,1098,1100,1101,1103,1104,1107],{},"The main plugin class owns the dock's lifecycle. Add it with ",[18,1099,35],{},", expose a checkable toolbar action to show or hide it, and remove it in ",[18,1102,39],{},". The dock's ",[18,1105,1106],{},"visibilityChanged"," signal keeps the toolbar toggle accurate when the user closes the panel manually.",[1109,1110,1115,1119,1123,1152,1159,1167,1172,1182,1188,1192,1196,1200,1206,1210,1215,1220,1223,1226,1234,1241,1246,1249,1255,1261,1265,1269,1273,1277,1282,1286,1290,1294,1298,1302,1305],"svg",{"viewBox":1111,"role":1112,"ariaLabel":1113,"xmlns":1114},"0 0 780 430","img","Lifecycle of a custom dock widget from initGui through the runtime sync loop to unload","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[1116,1117,1118],"title",{},"Dock widget lifecycle: setup, two-way sync, and teardown",[1120,1121,1122],"desc",{},"initGui() creates a checkable QAction and adds it to the toolbar. The action's toggled signal calls toggle_dock, which lazily builds the LayerInspectorDock and calls addDockWidget on first activation. The dock's visibilityChanged and closingPlugin signals flow back to action.setChecked, keeping the toolbar button and the panel in sync. unload() reverses the setup with removeToolBarIcon on the action and removeDockWidget plus deleteLater on the dock.",[1124,1125,1126,1140,1146],"defs",{},[1127,1128,1135],"marker",{"id":1129,"viewBox":1130,"refX":1131,"refY":1132,"markerWidth":1133,"markerHeight":1133,"orient":1134},"dwArrowD","0 0 10 10","9","5","7","auto-start-reverse",[1136,1137],"path",{"d":1138,"fill":1139},"M0,0 L10,5 L0,10 z","#2f3b35",[1127,1141,1143],{"id":1142,"viewBox":1130,"refX":1131,"refY":1132,"markerWidth":1133,"markerHeight":1133,"orient":1134},"dwArrowG",[1136,1144],{"d":1138,"fill":1145},"#15803d",[1127,1147,1149],{"id":1148,"viewBox":1130,"refX":1131,"refY":1132,"markerWidth":1133,"markerHeight":1133,"orient":1134},"dwArrowA",[1136,1150],{"d":1138,"fill":1151},"#b45309",[1153,1154],"rect",{"x":1155,"y":1155,"width":1156,"height":1157,"fill":1158},"0","780","430","#f6f3ea",[1160,1161,1166],"text",{"x":1162,"y":1163,"style":1164,"fill":1145,"textAnchor":1165},"390","26","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","middle","visibilityChanged \u002F closingPlugin  →  action.setChecked  (keeps toolbar and panel in sync)",[1136,1168],{"d":1169,"fill":1170,"stroke":1145,"style":1171},"M692,108 C692,46 305,46 305,116","none","stroke-width:2;stroke-dasharray:6 4;marker-end:url(#dwArrowG)",[1153,1173],{"x":1174,"y":1175,"width":1176,"height":1177,"rx":1178,"fill":1179,"stroke":1180,"style":1181},"16","118","160","66","8","#fffdf7","#2563eb","stroke-width:2.5",[1160,1183,82],{"x":1184,"y":1185,"style":1186,"fill":1187,"textAnchor":1165},"96","142","text-anchor:middle;font-size:13px;font-weight:bold;font-family:monospace","#17211d",[1160,1189,1191],{"x":1184,"y":1176,"style":1190,"fill":1139,"textAnchor":1165},"text-anchor:middle;font-size:9.5px;font-family:sans-serif","create checkable QAction",[1160,1193,1195],{"x":1184,"y":1194,"style":1190,"fill":1139,"textAnchor":1165},"174","+ addToolBarIcon",[1153,1197],{"x":1198,"y":1175,"width":1199,"height":1177,"rx":1178,"fill":1179,"stroke":1151,"style":1181},"220","150",[1160,1201,1205],{"x":1202,"y":1203,"style":1204,"fill":1187,"textAnchor":1165},"295","145","text-anchor:middle;font-size:13px;font-weight:bold;font-family:sans-serif","QAction",[1160,1207,1209],{"x":1202,"y":1208,"style":1190,"fill":1139,"textAnchor":1165},"164","checkable toolbar toggle",[1153,1211],{"x":1212,"y":1175,"width":1213,"height":1177,"rx":1178,"fill":1179,"stroke":1214,"style":1181},"414","166","#0f766e",[1160,1216,1219],{"x":1217,"y":1185,"style":1218,"fill":1187,"textAnchor":1165},"497","text-anchor:middle;font-size:12px;font-weight:bold;font-family:monospace","toggle_dock(checked)",[1160,1221,1222],{"x":1217,"y":1176,"style":1190,"fill":1139,"textAnchor":1165},"lazily builds the dock",[1160,1224,1225],{"x":1217,"y":1194,"style":1190,"fill":1139,"textAnchor":1165},"on first activation",[1153,1227],{"x":1228,"y":1229,"width":1230,"height":1231,"rx":1178,"fill":1232,"stroke":1233,"style":1181},"620","110","146","82","#26322d","#22c55e",[1160,1235,1240],{"x":1236,"y":1237,"style":1238,"fill":1239,"textAnchor":1165},"693","136","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","#d9f99d","LayerInspectorDock",[1160,1242,1245],{"x":1236,"y":1243,"style":1244,"fill":1239,"textAnchor":1165},"156","text-anchor:middle;font-size:9.5px;font-family:monospace","addDockWidget(Right)",[1160,1247,1248],{"x":1236,"y":1194,"style":1190,"fill":1239,"textAnchor":1165},"the docked panel",[139,1250],{"x1":1251,"y1":1252,"x2":1253,"y2":1252,"stroke":1139,"style":1254},"176","151","216","stroke-width:2.5;marker-end:url(#dwArrowD)",[1160,1256,1260],{"x":1257,"y":1258,"style":1259,"fill":1139,"textAnchor":1165},"196","143","text-anchor:middle;font-size:9px;font-family:sans-serif","creates",[139,1262],{"x1":1263,"y1":1252,"x2":1264,"y2":1252,"stroke":1139,"style":1254},"370","410",[1160,1266,1268],{"x":1162,"y":1258,"style":1267,"fill":1139,"textAnchor":1165},"text-anchor:middle;font-size:9px;font-family:monospace","toggled",[139,1270],{"x1":1271,"y1":1252,"x2":1272,"y2":1252,"stroke":1139,"style":1254},"580","616",[1160,1274,1276],{"x":1275,"y":1258,"style":1259,"fill":1139,"textAnchor":1165},"598","docks",[1153,1278],{"x":1174,"y":1279,"width":1280,"height":1281,"rx":1178,"fill":1179,"stroke":1151,"style":1181},"330","210","72",[1160,1283,39],{"x":1284,"y":1285,"style":1186,"fill":1187,"textAnchor":1165},"121","356",[1160,1287,1289],{"x":1284,"y":1288,"style":1190,"fill":1139,"textAnchor":1165},"375","removeDockWidget · deleteLater",[1160,1291,1293],{"x":1284,"y":1292,"style":1190,"fill":1139,"textAnchor":1165},"389","removeToolBarIcon",[1136,1295],{"d":1296,"fill":1170,"stroke":1151,"style":1297},"M170,330 C185,272 248,214 297,186","stroke-width:2;stroke-dasharray:6 4;marker-end:url(#dwArrowA)",[1160,1299,1293],{"x":1300,"y":1301,"style":1267,"fill":1151,"textAnchor":1165},"246","250",[1136,1303],{"d":1304,"fill":1170,"stroke":1151,"style":1297},"M226,354 C420,344 570,252 658,196",[1160,1306,1309],{"x":1307,"y":1308,"style":1267,"fill":1151,"textAnchor":1165},"470","300","removeDockWidget + deleteLater",[128,1311,1313],{"className":130,"code":1312,"language":132,"meta":133,"style":133},"from qgis.PyQt.QtCore import Qt\nfrom qgis.PyQt.QtGui import QIcon\nfrom qgis.PyQt.QtWidgets import QAction\n\n\nclass InspectorPlugin:\n    def __init__(self, iface):\n        self.iface = iface\n        self.dock = None\n        self.action = None\n\n    def initGui(self):\n        self.action = QAction(QIcon(), \"Layer Inspector\", self.iface.mainWindow())\n        self.action.setCheckable(True)\n        self.action.toggled.connect(self.toggle_dock)\n        self.iface.addToolBarIcon(self.action)\n\n    def toggle_dock(self, checked):\n        if checked and self.dock is None:\n            self.dock = LayerInspectorDock(self.iface, self.iface.mainWindow())\n            self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dock)\n            self.dock.closingPlugin.connect(lambda: self.action.setChecked(False))\n            self.dock.visibilityChanged.connect(self.action.setChecked)\n        elif self.dock is not None:\n            self.dock.setVisible(checked)\n\n    def unload(self):\n        if self.dock is not None:\n            self.iface.removeDockWidget(self.dock)\n            self.dock.deleteLater()\n            self.dock = None\n        if self.action is not None:\n            self.iface.removeToolBarIcon(self.action)\n            self.action = None\n",[18,1314,1315,1326,1338,1349,1353,1357,1367,1376,1386,1398,1409,1413,1422,1442,1454,1466,1478,1482,1492,1512,1532,1544,1567,1579,1596,1603,1607,1616,1632,1643,1650,1660,1676,1687],{"__ignoreMap":133},[137,1316,1317,1319,1321,1323],{"class":139,"line":140},[137,1318,144],{"class":143},[137,1320,148],{"class":147},[137,1322,151],{"class":143},[137,1324,1325],{"class":147}," Qt\n",[137,1327,1328,1330,1333,1335],{"class":139,"line":157},[137,1329,144],{"class":143},[137,1331,1332],{"class":147}," qgis.PyQt.QtGui ",[137,1334,151],{"class":143},[137,1336,1337],{"class":147}," QIcon\n",[137,1339,1340,1342,1344,1346],{"class":139,"line":170},[137,1341,144],{"class":143},[137,1343,162],{"class":147},[137,1345,151],{"class":143},[137,1347,1348],{"class":147}," QAction\n",[137,1350,1351],{"class":139,"line":176},[137,1352,212],{"emptyLinePlaceholder":211},[137,1354,1355],{"class":139,"line":182},[137,1356,212],{"emptyLinePlaceholder":211},[137,1358,1359,1361,1364],{"class":139,"line":195},[137,1360,223],{"class":143},[137,1362,1363],{"class":226}," InspectorPlugin",[137,1365,1366],{"class":147},":\n",[137,1368,1369,1371,1373],{"class":139,"line":208},[137,1370,270],{"class":143},[137,1372,274],{"class":273},[137,1374,1375],{"class":147},"(self, iface):\n",[137,1377,1378,1380,1382,1384],{"class":139,"line":215},[137,1379,310],{"class":273},[137,1381,313],{"class":147},[137,1383,256],{"class":143},[137,1385,318],{"class":147},[137,1387,1388,1390,1393,1395],{"class":139,"line":220},[137,1389,310],{"class":273},[137,1391,1392],{"class":147},".dock ",[137,1394,256],{"class":143},[137,1396,1397],{"class":273}," None\n",[137,1399,1400,1402,1405,1407],{"class":139,"line":238},[137,1401,310],{"class":273},[137,1403,1404],{"class":147},".action ",[137,1406,256],{"class":143},[137,1408,1397],{"class":273},[137,1410,1411],{"class":139,"line":245},[137,1412,212],{"emptyLinePlaceholder":211},[137,1414,1415,1417,1420],{"class":139,"line":250},[137,1416,270],{"class":143},[137,1418,1419],{"class":226}," initGui",[137,1421,601],{"class":147},[137,1423,1424,1426,1428,1430,1433,1435,1437,1439],{"class":139,"line":262},[137,1425,310],{"class":273},[137,1427,1404],{"class":147},[137,1429,256],{"class":143},[137,1431,1432],{"class":147}," QAction(QIcon(), ",[137,1434,301],{"class":241},[137,1436,888],{"class":147},[137,1438,355],{"class":273},[137,1440,1441],{"class":147},".iface.mainWindow())\n",[137,1443,1444,1446,1449,1452],{"class":139,"line":267},[137,1445,310],{"class":273},[137,1447,1448],{"class":147},".action.setCheckable(",[137,1450,1451],{"class":273},"True",[137,1453,179],{"class":147},[137,1455,1456,1458,1461,1463],{"class":139,"line":287},[137,1457,310],{"class":273},[137,1459,1460],{"class":147},".action.toggled.connect(",[137,1462,355],{"class":273},[137,1464,1465],{"class":147},".toggle_dock)\n",[137,1467,1468,1470,1473,1475],{"class":139,"line":307},[137,1469,310],{"class":273},[137,1471,1472],{"class":147},".iface.addToolBarIcon(",[137,1474,355],{"class":273},[137,1476,1477],{"class":147},".action)\n",[137,1479,1480],{"class":139,"line":321},[137,1481,212],{"emptyLinePlaceholder":211},[137,1483,1484,1486,1489],{"class":139,"line":339},[137,1485,270],{"class":143},[137,1487,1488],{"class":226}," toggle_dock",[137,1490,1491],{"class":147},"(self, checked):\n",[137,1493,1494,1496,1499,1502,1504,1506,1508,1510],{"class":139,"line":344},[137,1495,634],{"class":143},[137,1497,1498],{"class":147}," checked ",[137,1500,1501],{"class":143},"and",[137,1503,612],{"class":273},[137,1505,1392],{"class":147},[137,1507,640],{"class":143},[137,1509,643],{"class":273},[137,1511,1366],{"class":147},[137,1513,1514,1516,1518,1520,1523,1525,1528,1530],{"class":139,"line":360},[137,1515,658],{"class":273},[137,1517,1392],{"class":147},[137,1519,256],{"class":143},[137,1521,1522],{"class":147}," LayerInspectorDock(",[137,1524,355],{"class":273},[137,1526,1527],{"class":147},".iface, ",[137,1529,355],{"class":273},[137,1531,1441],{"class":147},[137,1533,1534,1536,1539,1541],{"class":139,"line":371},[137,1535,658],{"class":273},[137,1537,1538],{"class":147},".iface.addDockWidget(Qt.RightDockWidgetArea, ",[137,1540,355],{"class":273},[137,1542,1543],{"class":147},".dock)\n",[137,1545,1546,1548,1551,1554,1557,1559,1562,1565],{"class":139,"line":376},[137,1547,658],{"class":273},[137,1549,1550],{"class":147},".dock.closingPlugin.connect(",[137,1552,1553],{"class":143},"lambda",[137,1555,1556],{"class":147},": ",[137,1558,355],{"class":273},[137,1560,1561],{"class":147},".action.setChecked(",[137,1563,1564],{"class":273},"False",[137,1566,385],{"class":147},[137,1568,1569,1571,1574,1576],{"class":139,"line":388},[137,1570,658],{"class":273},[137,1572,1573],{"class":147},".dock.visibilityChanged.connect(",[137,1575,355],{"class":273},[137,1577,1578],{"class":147},".action.setChecked)\n",[137,1580,1581,1584,1586,1588,1590,1592,1594],{"class":139,"line":401},[137,1582,1583],{"class":143},"        elif",[137,1585,612],{"class":273},[137,1587,1392],{"class":147},[137,1589,640],{"class":143},[137,1591,649],{"class":143},[137,1593,643],{"class":273},[137,1595,1366],{"class":147},[137,1597,1598,1600],{"class":139,"line":409},[137,1599,658],{"class":273},[137,1601,1602],{"class":147},".dock.setVisible(checked)\n",[137,1604,1605],{"class":139,"line":420},[137,1606,212],{"emptyLinePlaceholder":211},[137,1608,1609,1611,1614],{"class":139,"line":425},[137,1610,270],{"class":143},[137,1612,1613],{"class":226}," unload",[137,1615,601],{"class":147},[137,1617,1618,1620,1622,1624,1626,1628,1630],{"class":139,"line":435},[137,1619,634],{"class":143},[137,1621,612],{"class":273},[137,1623,1392],{"class":147},[137,1625,640],{"class":143},[137,1627,649],{"class":143},[137,1629,643],{"class":273},[137,1631,1366],{"class":147},[137,1633,1634,1636,1639,1641],{"class":139,"line":448},[137,1635,658],{"class":273},[137,1637,1638],{"class":147},".iface.removeDockWidget(",[137,1640,355],{"class":273},[137,1642,1543],{"class":147},[137,1644,1645,1647],{"class":139,"line":458},[137,1646,658],{"class":273},[137,1648,1649],{"class":147},".dock.deleteLater()\n",[137,1651,1652,1654,1656,1658],{"class":139,"line":463},[137,1653,658],{"class":273},[137,1655,1392],{"class":147},[137,1657,256],{"class":143},[137,1659,1397],{"class":273},[137,1661,1662,1664,1666,1668,1670,1672,1674],{"class":139,"line":481},[137,1663,634],{"class":143},[137,1665,612],{"class":273},[137,1667,1404],{"class":147},[137,1669,640],{"class":143},[137,1671,649],{"class":143},[137,1673,643],{"class":273},[137,1675,1366],{"class":147},[137,1677,1678,1680,1683,1685],{"class":139,"line":491},[137,1679,658],{"class":273},[137,1681,1682],{"class":147},".iface.removeToolBarIcon(",[137,1684,355],{"class":273},[137,1686,1477],{"class":147},[137,1688,1689,1691,1693,1695],{"class":139,"line":509},[137,1690,658],{"class":273},[137,1692,1404],{"class":147},[137,1694,256],{"class":143},[137,1696,1397],{"class":273},[14,1698,1699,1051,1701,1704,1705,1707,1708,1711,1712,1714,1715,888,1717,1720,1721,1724],{},[69,1700,756],{},[18,1702,1703],{},"addDockWidget(Qt.RightDockWidgetArea, dock)"," snaps the panel into the right-hand dock area, beside Layers and Browser; users can drag it anywhere afterward. The dock is created lazily on first activation so a disabled feature costs nothing. Connecting ",[18,1706,1106],{}," to ",[18,1709,1710],{},"action.setChecked"," keeps the toolbar button's pressed state in lockstep with the panel — including when the user clicks the panel's close X, which also fires our ",[18,1713,783],{}," signal. In ",[18,1716,39],{},[18,1718,1719],{},"removeDockWidget"," detaches the panel from the main window and ",[18,1722,1723],{},"deleteLater()"," schedules its Qt-side destruction; skipping either leaves a stranded panel after the plugin is disabled.",[58,1726,1728],{"id":1727},"persisting-visibility-across-sessions","Persisting Visibility Across Sessions",[14,1730,1731,1732,1735,1736,1738,1739,1742,1743,1745],{},"QGIS restores a dock's ",[1063,1733,1734],{},"position"," automatically once ",[18,1737,763],{}," is set, but not whether your plugin should recreate it on the next launch. Use ",[18,1740,1741],{},"QSettings"," to remember the user's preference and reopen the dock during ",[18,1744,82],{}," if it was open last time.",[128,1747,1749],{"className":130,"code":1748,"language":132,"meta":133,"style":133},"from qgis.PyQt.QtCore import QSettings\n\nSETTINGS_KEY = \"InspectorPlugin\u002FdockVisible\"\n\n\ndef initGui(self):\n    self.action = QAction(QIcon(), \"Layer Inspector\", self.iface.mainWindow())\n    self.action.setCheckable(True)\n    self.action.toggled.connect(self.toggle_dock)\n    self.iface.addToolBarIcon(self.action)\n\n    # Restore last session's choice\n    if QSettings().value(SETTINGS_KEY, False, type=bool):\n        self.action.setChecked(True)  # triggers toggle_dock -> creates the dock\n\n\ndef toggle_dock(self, checked):\n    QSettings().setValue(SETTINGS_KEY, checked)\n    # ... creation \u002F visibility logic from the previous section ...\n",[18,1750,1751,1762,1766,1777,1781,1785,1794,1813,1823,1833,1843,1847,1852,1879,1892,1896,1900,1908,1918],{"__ignoreMap":133},[137,1752,1753,1755,1757,1759],{"class":139,"line":140},[137,1754,144],{"class":143},[137,1756,148],{"class":147},[137,1758,151],{"class":143},[137,1760,1761],{"class":147}," QSettings\n",[137,1763,1764],{"class":139,"line":157},[137,1765,212],{"emptyLinePlaceholder":211},[137,1767,1768,1771,1774],{"class":139,"line":170},[137,1769,1770],{"class":273},"SETTINGS_KEY",[137,1772,1773],{"class":143}," =",[137,1775,1776],{"class":241}," \"InspectorPlugin\u002FdockVisible\"\n",[137,1778,1779],{"class":139,"line":176},[137,1780,212],{"emptyLinePlaceholder":211},[137,1782,1783],{"class":139,"line":182},[137,1784,212],{"emptyLinePlaceholder":211},[137,1786,1787,1790,1792],{"class":139,"line":195},[137,1788,1789],{"class":143},"def",[137,1791,1419],{"class":226},[137,1793,601],{"class":147},[137,1795,1796,1799,1801,1803,1805,1807,1809,1811],{"class":139,"line":208},[137,1797,1798],{"class":273},"    self",[137,1800,1404],{"class":147},[137,1802,256],{"class":143},[137,1804,1432],{"class":147},[137,1806,301],{"class":241},[137,1808,888],{"class":147},[137,1810,355],{"class":273},[137,1812,1441],{"class":147},[137,1814,1815,1817,1819,1821],{"class":139,"line":215},[137,1816,1798],{"class":273},[137,1818,1448],{"class":147},[137,1820,1451],{"class":273},[137,1822,179],{"class":147},[137,1824,1825,1827,1829,1831],{"class":139,"line":220},[137,1826,1798],{"class":273},[137,1828,1460],{"class":147},[137,1830,355],{"class":273},[137,1832,1465],{"class":147},[137,1834,1835,1837,1839,1841],{"class":139,"line":238},[137,1836,1798],{"class":273},[137,1838,1472],{"class":147},[137,1840,355],{"class":273},[137,1842,1477],{"class":147},[137,1844,1845],{"class":139,"line":245},[137,1846,212],{"emptyLinePlaceholder":211},[137,1848,1849],{"class":139,"line":250},[137,1850,1851],{"class":335},"    # Restore last session's choice\n",[137,1853,1854,1857,1860,1862,1864,1866,1868,1872,1874,1877],{"class":139,"line":262},[137,1855,1856],{"class":143},"    if",[137,1858,1859],{"class":147}," QSettings().value(",[137,1861,1770],{"class":273},[137,1863,888],{"class":147},[137,1865,1564],{"class":273},[137,1867,888],{"class":147},[137,1869,1871],{"class":1870},"s9osk","type",[137,1873,256],{"class":143},[137,1875,1876],{"class":273},"bool",[137,1878,235],{"class":147},[137,1880,1881,1883,1885,1887,1889],{"class":139,"line":267},[137,1882,310],{"class":273},[137,1884,1561],{"class":147},[137,1886,1451],{"class":273},[137,1888,332],{"class":147},[137,1890,1891],{"class":335},"# triggers toggle_dock -> creates the dock\n",[137,1893,1894],{"class":139,"line":287},[137,1895,212],{"emptyLinePlaceholder":211},[137,1897,1898],{"class":139,"line":307},[137,1899,212],{"emptyLinePlaceholder":211},[137,1901,1902,1904,1906],{"class":139,"line":321},[137,1903,1789],{"class":143},[137,1905,1488],{"class":226},[137,1907,1491],{"class":147},[137,1909,1910,1913,1915],{"class":139,"line":339},[137,1911,1912],{"class":147},"    QSettings().setValue(",[137,1914,1770],{"class":273},[137,1916,1917],{"class":147},", checked)\n",[137,1919,1920],{"class":139,"line":344},[137,1921,1922],{"class":335},"    # ... creation \u002F visibility logic from the previous section ...\n",[14,1924,1925,1051,1927,1929,1930,1933,1934,1937,1938,1941,1942,1944,1945,1948],{},[69,1926,756],{},[18,1928,1741],{}," writes to the platform-native store (registry on Windows, plist on macOS, ",[18,1931,1932],{},".conf"," on Linux) under the QGIS organization, so the value survives restarts. Reading it with ",[18,1935,1936],{},"type=bool"," guarantees a real boolean rather than the string ",[18,1939,1940],{},"\"false\"",", which is truthy and a common bug. Setting the checkable action to checked during ",[18,1943,82],{}," re-runs ",[18,1946,1947],{},"toggle_dock",", recreating the panel exactly as the user left it.",[58,1950,1952],{"id":1951},"dock-areas-and-what-each-implies","Dock areas and what each implies",[14,1954,1955],{},"Where a dock opens tells the user what kind of tool it is before they read a word of it.",[14,1957,1958],{},[1109,1959,1962,1965,1968,1971,1976,1985,1990,1995,2001,2006,2010,2015,2019,2022,2026,2029,2033],{"viewBox":1960,"role":1112,"ariaLabel":1961,"xmlns":1114},"0 0 760 246","The QGIS main window with the four dock areas marked and the kind of panel each conventionally holds",[1116,1963,1964],{},"The four dock areas and their conventions",[1120,1966,1967],{},"The QGIS main window is shown with the map canvas in the centre. The left dock area conventionally holds the layer panel and browser. The right holds property and processing panels. The bottom holds the Python console and log messages. The top is rarely used and generally avoided.",[1153,1969],{"x":1155,"y":1155,"width":1970,"height":1300,"fill":1158},"760",[1160,1972,1975],{"x":1973,"y":1163,"style":1974,"fill":1187,"textAnchor":1165},"380","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","Convention tells the user what your panel is for",[1153,1977],{"x":1978,"y":1979,"width":1980,"height":1981,"rx":1982,"fill":1179,"stroke":1983,"style":1984},"140","46","480","182","6","#59645f","stroke-width:2",[1153,1986],{"x":1978,"y":1979,"width":1980,"height":1987,"fill":1988,"stroke":1983,"style":1989},"20","#e7e2d4","stroke-width:1",[1160,1991,1994],{"x":1973,"y":1992,"style":1993,"fill":1983,"textAnchor":1165},"60","text-anchor:middle;font-size:10px;font-family:sans-serif","top — rarely used, avoid",[1153,1996],{"x":1230,"y":1281,"width":1997,"height":1998,"fill":1214,"fillOpacity":1999,"stroke":1214,"style":2000},"104","120",0.16,"stroke-width:1.6",[1160,2002,2005],{"x":2003,"y":2004,"style":1993,"fill":1139,"textAnchor":1165},"198","126","left",[1160,2007,2009],{"x":2003,"y":2008,"style":1190,"fill":1983,"textAnchor":1165},"144","layers, browser",[1153,2011],{"x":2012,"y":1281,"width":2013,"height":1998,"fill":1988,"stroke":1983,"style":2014},"256","248","stroke-width:1.4",[1160,2016,2018],{"x":1973,"y":1237,"style":2017,"fill":1139,"textAnchor":1165},"text-anchor:middle;font-size:11px;font-family:sans-serif","map canvas",[1153,2020],{"x":2021,"y":1281,"width":1997,"height":1998,"fill":1180,"fillOpacity":1999,"stroke":1180,"style":2000},"510",[1160,2023,2025],{"x":2024,"y":2004,"style":1993,"fill":1139,"textAnchor":1165},"562","right",[1160,2027,2028],{"x":2024,"y":2008,"style":1190,"fill":1983,"textAnchor":1165},"properties, tools",[1153,2030],{"x":1230,"y":2003,"width":2031,"height":2032,"fill":1151,"fillOpacity":1999,"stroke":1151,"style":2000},"468","24",[1160,2034,2036],{"x":1973,"y":2035,"style":1993,"fill":1139,"textAnchor":1165},"214","bottom — console, log messages",[14,2038,2039],{},"Right is the safe default for a plugin panel: it is where users expect tool-specific controls, and it does not compete with the layer panel for the space people rely on most.",[58,2041,2043],{"id":2042},"restoring-state-between-sessions","Restoring state between sessions",[14,2045,2046],{},"A dock that reopens where the user left it feels finished. The state Qt can save for you covers most of it; the rest is one saved setting.",[14,2048,2049],{},[1109,2050,2053,2056,2059,2062,2070,2073,2078,2083,2088,2091,2095,2099,2102,2106,2108,2110,2113,2116,2119,2123,2126,2129,2133],{"viewBox":2051,"role":1112,"ariaLabel":2052,"xmlns":1114},"0 0 760 236","Three pieces of dock state — visibility, area and size — with which mechanism restores each between sessions",[1116,2054,2055],{},"What restores the dock, and what you must save yourself",[1120,2057,2058],{},"Qt's main window state restores the dock area and its size automatically as long as the widget has a stable object name. Whether the dock was open is not restored by that mechanism and must be written to QSettings by the plugin and read back on the next start.",[1153,2060],{"x":1155,"y":1155,"width":1970,"height":2061,"fill":1158},"236",[1124,2063,2064],{},[1127,2065,2067],{"id":2066,"viewBox":1130,"refX":1178,"refY":1132,"markerWidth":1133,"markerHeight":1133,"orient":1134},"dockStateArrow",[1136,2068],{"d":2069,"fill":1214},"M0 0 L10 5 L0 10 z",[1160,2071,2072],{"x":1973,"y":1163,"style":1974,"fill":1187,"textAnchor":1165},"Two of these are free; one is yours",[1153,2074],{"x":1174,"y":2075,"width":2076,"height":1208,"rx":2077,"fill":1179,"stroke":1145,"style":1181},"52","228","10",[1160,2079,2082],{"x":2080,"y":2081,"style":1238,"fill":1145,"textAnchor":1165},"130","78","which area",[1160,2084,2087],{"x":2080,"y":2085,"style":2086,"fill":1145,"textAnchor":1165},"112","text-anchor:middle;font-size:22px;font-weight:bold;font-family:sans-serif","✓",[1160,2089,2090],{"x":2080,"y":1230,"style":2017,"fill":1139,"textAnchor":1165},"restored by Qt",[1160,2092,2094],{"x":2080,"y":1251,"style":2093,"fill":1983,"textAnchor":1165},"text-anchor:middle;font-size:10.5px;font-family:sans-serif","needs a stable",[1160,2096,2098],{"x":2080,"y":2097,"style":2093,"fill":1983,"textAnchor":1165},"194","setObjectName()",[1153,2100],{"x":2101,"y":2075,"width":2076,"height":1208,"rx":2077,"fill":1179,"stroke":1145,"style":1181},"262",[1160,2103,2105],{"x":2104,"y":2081,"style":1238,"fill":1145,"textAnchor":1165},"376","size and position",[1160,2107,2087],{"x":2104,"y":2085,"style":2086,"fill":1145,"textAnchor":1165},[1160,2109,2090],{"x":2104,"y":1230,"style":2017,"fill":1139,"textAnchor":1165},[1160,2111,2112],{"x":2104,"y":1251,"style":2093,"fill":1983,"textAnchor":1165},"part of the main",[1160,2114,2115],{"x":2104,"y":2097,"style":2093,"fill":1983,"textAnchor":1165},"window state",[1153,2117],{"x":2118,"y":2075,"width":2061,"height":1208,"rx":2077,"fill":1179,"stroke":1151,"style":1181},"508",[1160,2120,2122],{"x":2121,"y":2081,"style":1238,"fill":1151,"textAnchor":1165},"626","was it open?",[1160,2124,2125],{"x":2121,"y":2085,"style":2086,"fill":1151,"textAnchor":1165},"✗",[1160,2127,2128],{"x":2121,"y":1230,"style":2017,"fill":1139,"textAnchor":1165},"save it yourself",[1160,2130,2132],{"x":2121,"y":1251,"style":2131,"fill":1983,"textAnchor":1165},"text-anchor:middle;font-size:10.5px;font-family:monospace","QgsSettings().setValue(",[1160,2134,2135],{"x":2121,"y":2097,"style":2131,"fill":1983,"textAnchor":1165},"\"myplugin\u002Fdock\", True)",[58,2137,2139],{"id":2138},"qgis-version-compatibility","QGIS Version Compatibility",[14,2141,2142],{},"The dock widget APIs are stable across the 3.x line; the only moving parts are enum spellings, which affect both approaches identically.",[2144,2145,2146,2165],"table",{},[2147,2148,2149],"thead",{},[2150,2151,2152,2156,2159,2162],"tr",{},[2153,2154,2155],"th",{},"API",[2153,2157,2158],{},"QGIS 3.28 LTR (Py 3.9)",[2153,2160,2161],{},"QGIS 3.34 LTR (Py 3.12)",[2153,2163,2164],{},"QGIS 3.40 \u002F 3.44 (Py 3.12)",[2166,2167,2168,2185,2200,2219],"tbody",{},[2150,2169,2170,2178,2181,2183],{},[2171,2172,2173,83,2176],"td",{},[18,2174,2175],{},"iface.addDockWidget",[18,2177,1719],{},[2171,2179,2180],{},"stable",[2171,2182,2180],{},[2171,2184,2180],{},[2150,2186,2187,2193,2196,2198],{},[2171,2188,2189,83,2191],{},[18,2190,121],{},[18,2192,125],{},[2171,2194,2195],{},"available",[2171,2197,2195],{},[2171,2199,2195],{},[2150,2201,2202,2205,2210,2213],{},[2171,2203,2204],{},"Filter enum",[2171,2206,2207],{},[18,2208,2209],{},"QgsMapLayerProxyModel.VectorLayer",[2171,2211,2212],{},"same",[2171,2214,2215,2218],{},[18,2216,2217],{},"Qgis.LayerFilter.VectorLayer"," also available",[2150,2220,2221,2224,2229,2231],{},[2171,2222,2223],{},"Dock area enum",[2171,2225,2226],{},[18,2227,2228],{},"Qt.RightDockWidgetArea",[2171,2230,2212],{},[2171,2232,2212],{},[14,2234,2235,2236,2238,2239,2242],{},"On QGIS 3.34 the classic ",[18,2237,2209],{}," filter remains the documented form. Newer releases add ",[18,2240,2241],{},"Qgis.LayerFilter"," aliases, but the old enum still works, so the code above is portable.",[58,2244,2246],{"id":2245},"troubleshooting","Troubleshooting",[14,2248,2249,2254,2255,2257,2258,2260,2261,2263,2264,83,2266,2268],{},[69,2250,2251,40],{},[18,2252,2253],{},"AttributeError: 'NoneType' object has no attribute 'setLayer'"," The promoted widget name in your code does not match the ",[18,2256,1072],{}," in the ",[18,2259,27],{}," file. Open the ",[18,2262,27],{}," in Qt Designer and confirm ",[18,2265,1076],{},[18,2267,1079],{}," exactly match your attribute names.",[14,2270,2271,2274,2275,2277,2278,2280],{},[69,2272,2273],{},"Dock position is not remembered between sessions."," You did not set an object name. Call ",[18,2276,763],{}," (subclass approach) or set it in Designer (",[18,2279,27],{}," approach) — QGIS keys saved layout state on it.",[14,2282,2283,2286,2287,2289,2290,2292,2293,2295,2296,2299],{},[69,2284,2285],{},"The toolbar toggle and the panel get out of sync."," Connect the dock's ",[18,2288,1106],{}," signal to ",[18,2291,1710],{},", and emit a ",[18,2294,783],{}," signal from ",[18,2297,2298],{},"closeEvent"," so the close button also updates the toggle.",[14,2301,2302,1051,2305,2307,2308,2311,2312,2314],{},[69,2303,2304],{},"Field combo is empty.",[18,2306,125],{}," needs a layer. Call ",[18,2309,2310],{},"setLayer"," once after creation and connect it to the layer combo's ",[18,2313,771],{}," signal so it updates on every change.",[14,2316,2317,1051,2320,2322,2323,122,2325,2327],{},[69,2318,2319],{},"Panel remains after disabling the plugin.",[18,2321,39],{}," must call ",[18,2324,1719],{},[18,2326,1723],{}," on the dock; removing only the toolbar icon leaves the panel behind.",[58,2329,2331],{"id":2330},"conclusion","Conclusion",[14,2333,2334,2335,2337,2338,28,2340,2342,2343,122,2345,2347,2348,2350,2351,2353,2354,2356,2357,28,2359,122,2361,2363],{},"A custom dock widget turns a one-shot dialog into a persistent, native-feeling part of the QGIS workspace. Whether you subclass ",[18,2336,20],{}," in Python or load a Qt Designer ",[18,2339,27],{},[18,2341,31],{},", the essentials are the same: embed GIS-aware widgets like ",[18,2344,121],{},[18,2346,125],{},", dock it with ",[18,2349,35],{},", keep a checkable toolbar action in sync via ",[18,2352,1106],{},", persist the user's choice with ",[18,2355,1741],{},", and tear it all down in ",[18,2358,39],{},[18,2360,1719],{},[18,2362,1723],{},". Get that lifecycle right and the panel behaves exactly like a built-in QGIS dock.",[58,2365,2367],{"id":2366},"frequently-asked-questions","Frequently Asked Questions",[14,2369,2370,2373,2374,2376],{},[69,2371,2372],{},"Should I subclass QDockWidget or load a .ui file?"," Subclass for small, code-driven panels; use a ",[18,2375,27],{}," when the layout is complex or maintained by a designer. Both dock identically — the choice is about how you author the layout.",[14,2378,2379,2382,2383,2385,2386,2388,2389,40],{},[69,2380,2381],{},"Why does the panel not reappear after a QGIS restart?"," QGIS restores dock ",[1063,2384,1734],{}," once you set an object name, but your plugin must decide whether to recreate the widget. Persist that intent with ",[18,2387,1741],{}," and recreate the dock in ",[18,2390,82],{},[14,2392,2393,2396,2397,2399,2400,888,2403,2406,2407,2410],{},[69,2394,2395],{},"Can I dock the panel on the left or bottom?"," Yes. Pass a different area to ",[18,2398,35],{}," — ",[18,2401,2402],{},"Qt.LeftDockWidgetArea",[18,2404,2405],{},"Qt.BottomDockWidgetArea",", or ",[18,2408,2409],{},"Qt.TopDockWidgetArea",". The user can still move it afterward.",[14,2412,2413,2416,2417,2419,2420,2422,2423,40],{},[69,2414,2415],{},"How do I open the dock from a toolbar button?"," Use a checkable ",[18,2418,1205],{}," whose ",[18,2421,1268],{}," signal shows or hides the dock, as shown above. The toolbar mechanics are covered in ",[45,2424,56],{"href":55},[58,2426,2428],{"id":2427},"related","Related",[63,2430,2431,2435,2439],{},[66,2432,2433],{},[45,2434,48],{"href":47},[66,2436,2437],{},[45,2438,56],{"href":55},[66,2440,2441],{},[45,2442,90],{"href":89},[2444,2445,2446],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}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 .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":133,"searchDepth":157,"depth":157,"links":2448},[2449,2450,2451,2452,2453,2454,2455,2456,2457,2458,2459,2460],{"id":60,"depth":157,"text":61},{"id":107,"depth":157,"text":108},{"id":787,"depth":157,"text":788},{"id":1093,"depth":157,"text":1094},{"id":1727,"depth":157,"text":1728},{"id":1951,"depth":157,"text":1952},{"id":2042,"depth":157,"text":2043},{"id":2138,"depth":157,"text":2139},{"id":2245,"depth":157,"text":2246},{"id":2330,"depth":157,"text":2331},{"id":2366,"depth":157,"text":2367},{"id":2427,"depth":157,"text":2428},"Build a custom QDockWidget in a QGIS plugin with PyQGIS. Embed QgsMapLayerComboBox and QgsFieldComboBox, dock it, persist visibility, and remove it on unload.","md",{"slug":2464,"type":2465,"breadcrumb":2466,"datePublished":2467,"dateModified":2468},"add-custom-dock-widget-pyqgis","article","Add a Custom Dock Widget","2025-05-06","2026-07-18","\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis",{"title":5,"description":2461},"qgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis\u002Findex","xkaoZ175CZmrdliwO5LGtCERWdIKNEptZlW2FcVnbQA",1787823363134]