[{"data":1,"prerenderedAt":1886},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces":3},{"id":4,"title":5,"body":6,"description":1875,"extension":1876,"meta":1877,"navigation":450,"path":1882,"seo":1883,"stem":1884,"__hash__":1885},"docs\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Findex.md","Qt Designer for QGIS Plugin Interfaces",{"type":7,"value":8,"toc":1861},"minimark",[9,14,38,215,220,234,238,241,361,365,371,907,917,1033,1037,1049,1063,1078,1082,1118,1122,1125,1154,1171,1195,1222,1245,1249,1280,1284,1287,1389,1395,1449,1463,1467,1470,1483,1494,1498,1571,1584,1588,1615,1646,1673,1686,1707,1716,1728,1738,1748,1754,1775,1781,1785,1857],[10,11,13],"h1",{"id":12},"qt-designer-for-gis-interfaces","Qt Designer for GIS Interfaces",[15,16,17,18,22,23,28,29,33,34,37],"p",{},"Building professional geospatial tools requires more than functional code; it demands an intuitive, responsive user interface. Qt Designer gives you a visual workflow that bridges complex spatial operations and end-user accessibility, and within the PyQGIS ecosystem its ",[19,20,21],"code",{},".ui"," files keep interface layout cleanly separated from your business logic. This guide sits inside the broader ",[24,25,27],"a",{"href":26},"\u002Fqgis-plugin-development\u002F","QGIS Plugin Development"," track: once you have a working skeleton from ",[24,30,32],{"href":31},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002F","Plugin Boilerplate & Structure",", Qt Designer is how you build the dialog its ",[19,35,36],{},"run()"," method opens. Everything here targets QGIS 3.34 LTR on Python 3, with API differences flagged where they matter, and walks a production-ready path for designing, loading, and integrating Qt Designer assets into a plugin.",[39,40,45,49,53,60,70,79,85,90,97,103,108,112,115,119,124,129,134,138,143,148,152,154,157,160,176,183,186,190,192,197,204,207,211],"svg",{"viewBox":41,"role":42,"ariaLabel":43,"xmlns":44},"0 0 760 380","img","Qt Designer workflow from .ui file to a PyQGIS-connected dialog","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[46,47,48],"title",{},"Qt Designer to PyQGIS integration workflow",[50,51,52],"desc",{},"A .ui file produced in Qt Designer is loaded via uic.loadUiType() or compiled with pyuic5 into a form class, which becomes the dialog or widget class; its signals connect to PyQGIS slots, and promoted widgets such as QgsMapLayerComboBox are embedded directly.",[54,55],"rect",{"x":56,"y":56,"width":57,"height":58,"fill":59},"0","760","380","#f6f3ea",[54,61],{"x":62,"y":63,"width":64,"height":65,"rx":66,"fill":67,"stroke":68,"style":69},"24","40","180","72","8","#fffdf7","#b45309","stroke-width:2.5",[71,72,78],"text",{"x":73,"y":74,"style":75,"fill":76,"textAnchor":77},"114","68","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Qt Designer",[71,80,84],{"x":73,"y":81,"style":82,"fill":83,"textAnchor":77},"88","text-anchor:middle;font-size:12px;font-family:monospace","#2f3b35","my_dialog.ui",[71,86,89],{"x":73,"y":87,"style":88,"fill":83,"textAnchor":77},"104","text-anchor:middle;font-size:11px;font-family:sans-serif","XML layout",[54,91],{"x":92,"y":93,"width":64,"height":94,"rx":66,"fill":95,"stroke":96,"style":69},"262","20","48","#26322d","#22c55e",[71,98,102],{"x":99,"y":63,"style":100,"fill":101,"textAnchor":77},"352","text-anchor:middle;font-size:12px;font-weight:bold;font-family:monospace","#d9f99d","uic.loadUiType()",[71,104,107],{"x":99,"y":105,"style":106,"fill":101,"textAnchor":77},"57","text-anchor:middle;font-size:10px;font-family:sans-serif","runtime (recommended)",[54,109],{"x":92,"y":110,"width":64,"height":94,"rx":66,"fill":67,"stroke":111,"style":69},"84","#0f766e",[71,113,114],{"x":99,"y":87,"style":100,"fill":76,"textAnchor":77},"pyuic5",[71,116,118],{"x":99,"y":117,"style":106,"fill":83,"textAnchor":77},"121","static compile",[54,120],{"x":121,"y":63,"width":122,"height":65,"rx":66,"fill":67,"stroke":123,"style":69},"500","236","#2563eb",[71,125,128],{"x":126,"y":127,"style":75,"fill":76,"textAnchor":77},"618","66","Dialog \u002F Widget class",[71,130,133],{"x":126,"y":131,"style":132,"fill":83,"textAnchor":77},"86","text-anchor:middle;font-size:11px;font-family:monospace","QDialog, FORM_CLASS",[71,135,137],{"x":126,"y":136,"style":132,"fill":83,"textAnchor":77},"102","setupUi(self)",[54,139],{"x":92,"y":140,"width":122,"height":141,"rx":66,"fill":67,"stroke":142,"style":69},"208","64","#15803d",[71,144,147],{"x":58,"y":145,"style":146,"fill":76,"textAnchor":77},"234","text-anchor:middle;font-size:13px;font-weight:bold;font-family:sans-serif","PyQGIS slots",[71,149,151],{"x":58,"y":150,"style":88,"fill":83,"textAnchor":77},"254","geoprocessing & map logic",[54,153],{"x":121,"y":140,"width":122,"height":141,"rx":66,"fill":67,"stroke":111,"style":69},[71,155,156],{"x":126,"y":145,"style":146,"fill":76,"textAnchor":77},"Promoted widgets",[71,158,159],{"x":126,"y":150,"style":132,"fill":83,"textAnchor":77},"QgsMapLayerComboBox",[161,162,163],"defs",{},[164,165,172],"marker",{"id":166,"viewBox":167,"refX":168,"refY":169,"markerWidth":170,"markerHeight":170,"orient":171},"arrowq","0 0 10 10","9","5","7","auto-start-reverse",[173,174],"path",{"d":175,"fill":83},"M0,0 L10,5 L0,10 z",[177,178],"line",{"x1":179,"y1":141,"x2":180,"y2":181,"stroke":83,"style":182},"204","258","44","stroke-width:2.5;marker-end:url(#arrowq)",[177,184],{"x1":179,"y1":81,"x2":180,"y2":185,"stroke":83,"style":182},"108",[177,187],{"x1":188,"y1":181,"x2":189,"y2":141,"stroke":83,"style":182},"442","496",[177,191],{"x1":188,"y1":185,"x2":189,"y2":81,"stroke":83,"style":182},[177,193],{"x1":194,"y1":195,"x2":196,"y2":179,"stroke":83,"style":182},"560","112","430",[71,198,203],{"x":199,"y":200,"style":201,"fill":83,"textAnchor":202},"455","160","text-anchor:start;font-size:10px;font-family:sans-serif","start","connect() signals",[177,205],{"x1":126,"y1":195,"x2":126,"y2":179,"stroke":83,"style":206},"stroke-width:2;stroke-dasharray:5 4;marker-end:url(#arrowq)",[71,208,210],{"x":209,"y":200,"style":201,"fill":83,"textAnchor":202},"628","embedded",[71,212,214],{"x":58,"y":213,"style":88,"fill":68,"textAnchor":77},"316","Layout stays in the .ui file; behavior lives in Python.",[216,217,219],"h2",{"id":218},"prerequisites","Prerequisites",[15,221,222,223,226,227,229,230,233],{},"Before designing spatial interfaces, developers must establish a stable development environment. QGIS ships with PyQt bindings and the built-in ",[19,224,225],{},"uic"," module, but standalone Qt Designer must be installed separately via your OS package manager or the Qt Online Installer. Familiarity with Python, object-oriented programming, and the foundational concepts of ",[24,228,27],{"href":26}," is essential. Ensure your IDE recognizes QGIS Python paths, and verify that the ",[19,231,232],{},"qgis.PyQt.uic"," module is accessible. A working knowledge of Qt's layout system and signal-slot architecture will significantly reduce debugging time during interface integration.",[216,235,237],{"id":236},"step-by-step-workflow","Step-by-Step Workflow",[15,239,240],{},"The visual design process follows a predictable pipeline that aligns with standard PyQGIS architecture:",[242,243,244,264,288,312,329,346],"ol",{},[245,246,247,251,252,255,256,259,260,263],"li",{},[248,249,250],"strong",{},"Initialize the Layout Template:"," Open Qt Designer and select a template matching your plugin architecture. For standalone dialogs, ",[19,253,254],{},"Dialog with Buttons Bottom"," is standard. For persistent tools, ",[19,257,258],{},"Widget"," or ",[19,261,262],{},"Dock Widget"," templates align better with QGIS workspace paradigms.",[245,265,266,269,270,273,274,273,277,273,280,283,284,287],{},[248,267,268],{},"Place Standard Controls:"," Drag Qt widgets (",[19,271,272],{},"QComboBox",", ",[19,275,276],{},"QSpinBox",[19,278,279],{},"QTableWidget",[19,281,282],{},"QLineEdit",") onto the canvas. Assign descriptive ",[19,285,286],{},"objectName"," properties to every interactive element. These names become direct Python attributes during runtime.",[245,289,290,293,294,273,297,300,301,304,305,259,308,311],{},[248,291,292],{},"Apply Layout Managers:"," Never rely on absolute positioning. Apply ",[19,295,296],{},"QVBoxLayout",[19,298,299],{},"QHBoxLayout",", or ",[19,302,303],{},"QGridLayout"," to top-level containers. Set size policies to ",[19,306,307],{},"Expanding",[19,309,310],{},"MinimumExpanding"," to ensure responsive scaling across different DPI settings and QGIS window states.",[245,313,314,317,318,321,322,324,325,328],{},[248,315,316],{},"Promote GIS-Specific Widgets:"," Select a standard widget, right-click, and choose ",[19,319,320],{},"Promote to...",". Enter the QGIS class name (e.g., ",[19,323,159],{},") and header file (e.g., ",[19,326,327],{},"qgsmaplayercombobox.h","). This instructs Qt Designer to generate placeholder code that QGIS will resolve at runtime through its Python bindings.",[245,330,331,337,338,341,342,345],{},[248,332,333,334,336],{},"Export the ",[19,335,21],{}," File:"," Save the design as ",[19,339,340],{},"my_plugin_dialog.ui"," in your plugin's ",[19,343,344],{},"ui\u002F"," directory. This XML-based format decouples visual structure from Python execution.",[245,347,348,351,352,354,355,357,358,360],{},[248,349,350],{},"Load Dynamically (Recommended):"," Modern PyQGIS workflows bypass static compilation by loading ",[19,353,21],{}," files dynamically at runtime using ",[19,356,102],{},". This simplifies iteration, avoids ",[19,359,114],{}," version mismatches, and reduces boilerplate.",[216,362,364],{"id":363},"code-breakdown-dynamic-ui-integration","Code Breakdown: Dynamic UI Integration",[15,366,367,368,370],{},"Once the interface is prepared, integration with PyQGIS requires careful initialization. The following pattern demonstrates runtime loading, which pairs seamlessly with the structural conventions outlined in ",[24,369,32],{"href":31},".",[372,373,378],"pre",{"className":374,"code":375,"language":376,"meta":377,"style":377},"language-python shiki shiki-themes github-dark","import os\nfrom qgis.PyQt import uic\nfrom qgis.PyQt.QtCore import Qt\nfrom qgis.PyQt.QtWidgets import QDialog, QMessageBox\nfrom qgis.core import QgsMapLayerProxyModel\n\n# Dynamically parse the .ui XML file at runtime\nFORM_CLASS, _ = uic.loadUiType(os.path.join(\n    os.path.dirname(__file__), 'ui', 'my_plugin_dialog.ui'))\n\n\nclass MyPluginDialog(QDialog, FORM_CLASS):\n    def __init__(self, parent=None):\n        super().__init__(parent)\n        self.setupUi(self)\n        # Ensure automatic memory cleanup on close\n        self.setAttribute(Qt.WA_DeleteOnClose)\n        self._configure_gis_widgets()\n        self._connect_signals()\n\n    def _configure_gis_widgets(self):\n        # Filter layers to only show vector types\n        self.layer_combo.setFilters(\n            QgsMapLayerProxyModel.PointLayer\n            | QgsMapLayerProxyModel.PolygonLayer\n            | QgsMapLayerProxyModel.LineLayer\n        )\n        self.layer_combo.setAllowEmptyLayer(True)\n\n    def _connect_signals(self):\n        self.run_button.clicked.connect(self._execute_analysis)\n        self.cancel_button.clicked.connect(self.reject)\n        self.clear_button.clicked.connect(self._reset_inputs)\n\n    def _execute_analysis(self):\n        selected_layer = self.layer_combo.currentLayer()\n        if not selected_layer:\n            QMessageBox.warning(self, \"Missing Input\", \"Please select a valid vector layer.\")\n            return\n\n        # GIS processing logic goes here\n        QMessageBox.information(self, \"Success\", \"Analysis triggered successfully.\")\n        self.accept()\n\n    def _reset_inputs(self):\n        self.layer_combo.setLayer(None)\n        if hasattr(self, \"threshold_spin\"):\n            self.threshold_spin.setValue(0)\n","python","",[19,379,380,392,406,419,432,445,452,459,475,499,504,509,532,551,566,581,587,600,608,616,621,632,638,646,652,661,669,675,688,693,703,716,729,742,747,757,771,783,803,809,814,820,840,848,853,863,875,894],{"__ignoreMap":377},[381,382,384,388],"span",{"class":177,"line":383},1,[381,385,387],{"class":386},"snl16","import",[381,389,391],{"class":390},"s95oV"," os\n",[381,393,395,398,401,403],{"class":177,"line":394},2,[381,396,397],{"class":386},"from",[381,399,400],{"class":390}," qgis.PyQt ",[381,402,387],{"class":386},[381,404,405],{"class":390}," uic\n",[381,407,409,411,414,416],{"class":177,"line":408},3,[381,410,397],{"class":386},[381,412,413],{"class":390}," qgis.PyQt.QtCore ",[381,415,387],{"class":386},[381,417,418],{"class":390}," Qt\n",[381,420,422,424,427,429],{"class":177,"line":421},4,[381,423,397],{"class":386},[381,425,426],{"class":390}," qgis.PyQt.QtWidgets ",[381,428,387],{"class":386},[381,430,431],{"class":390}," QDialog, QMessageBox\n",[381,433,435,437,440,442],{"class":177,"line":434},5,[381,436,397],{"class":386},[381,438,439],{"class":390}," qgis.core ",[381,441,387],{"class":386},[381,443,444],{"class":390}," QgsMapLayerProxyModel\n",[381,446,448],{"class":177,"line":447},6,[381,449,451],{"emptyLinePlaceholder":450},true,"\n",[381,453,455],{"class":177,"line":454},7,[381,456,458],{"class":457},"sjoCn","# Dynamically parse the .ui XML file at runtime\n",[381,460,462,466,469,472],{"class":177,"line":461},8,[381,463,465],{"class":464},"sDLfK","FORM_CLASS",[381,467,468],{"class":390},", _ ",[381,470,471],{"class":386},"=",[381,473,474],{"class":390}," uic.loadUiType(os.path.join(\n",[381,476,478,481,484,487,491,493,496],{"class":177,"line":477},9,[381,479,480],{"class":390},"    os.path.dirname(",[381,482,483],{"class":464},"__file__",[381,485,486],{"class":390},"), ",[381,488,490],{"class":489},"sU2Wk","'ui'",[381,492,273],{"class":390},[381,494,495],{"class":489},"'my_plugin_dialog.ui'",[381,497,498],{"class":390},"))\n",[381,500,502],{"class":177,"line":501},10,[381,503,451],{"emptyLinePlaceholder":450},[381,505,507],{"class":177,"line":506},11,[381,508,451],{"emptyLinePlaceholder":450},[381,510,512,515,519,522,525,527,529],{"class":177,"line":511},12,[381,513,514],{"class":386},"class",[381,516,518],{"class":517},"svObZ"," MyPluginDialog",[381,520,521],{"class":390},"(",[381,523,524],{"class":517},"QDialog",[381,526,273],{"class":390},[381,528,465],{"class":464},[381,530,531],{"class":390},"):\n",[381,533,535,538,541,544,546,549],{"class":177,"line":534},13,[381,536,537],{"class":386},"    def",[381,539,540],{"class":464}," __init__",[381,542,543],{"class":390},"(self, parent",[381,545,471],{"class":386},[381,547,548],{"class":464},"None",[381,550,531],{"class":390},[381,552,554,557,560,563],{"class":177,"line":553},14,[381,555,556],{"class":464},"        super",[381,558,559],{"class":390},"().",[381,561,562],{"class":464},"__init__",[381,564,565],{"class":390},"(parent)\n",[381,567,569,572,575,578],{"class":177,"line":568},15,[381,570,571],{"class":464},"        self",[381,573,574],{"class":390},".setupUi(",[381,576,577],{"class":464},"self",[381,579,580],{"class":390},")\n",[381,582,584],{"class":177,"line":583},16,[381,585,586],{"class":457},"        # Ensure automatic memory cleanup on close\n",[381,588,590,592,595,598],{"class":177,"line":589},17,[381,591,571],{"class":464},[381,593,594],{"class":390},".setAttribute(Qt.",[381,596,597],{"class":464},"WA_DeleteOnClose",[381,599,580],{"class":390},[381,601,603,605],{"class":177,"line":602},18,[381,604,571],{"class":464},[381,606,607],{"class":390},"._configure_gis_widgets()\n",[381,609,611,613],{"class":177,"line":610},19,[381,612,571],{"class":464},[381,614,615],{"class":390},"._connect_signals()\n",[381,617,619],{"class":177,"line":618},20,[381,620,451],{"emptyLinePlaceholder":450},[381,622,624,626,629],{"class":177,"line":623},21,[381,625,537],{"class":386},[381,627,628],{"class":517}," _configure_gis_widgets",[381,630,631],{"class":390},"(self):\n",[381,633,635],{"class":177,"line":634},22,[381,636,637],{"class":457},"        # Filter layers to only show vector types\n",[381,639,641,643],{"class":177,"line":640},23,[381,642,571],{"class":464},[381,644,645],{"class":390},".layer_combo.setFilters(\n",[381,647,649],{"class":177,"line":648},24,[381,650,651],{"class":390},"            QgsMapLayerProxyModel.PointLayer\n",[381,653,655,658],{"class":177,"line":654},25,[381,656,657],{"class":386},"            |",[381,659,660],{"class":390}," QgsMapLayerProxyModel.PolygonLayer\n",[381,662,664,666],{"class":177,"line":663},26,[381,665,657],{"class":386},[381,667,668],{"class":390}," QgsMapLayerProxyModel.LineLayer\n",[381,670,672],{"class":177,"line":671},27,[381,673,674],{"class":390},"        )\n",[381,676,678,680,683,686],{"class":177,"line":677},28,[381,679,571],{"class":464},[381,681,682],{"class":390},".layer_combo.setAllowEmptyLayer(",[381,684,685],{"class":464},"True",[381,687,580],{"class":390},[381,689,691],{"class":177,"line":690},29,[381,692,451],{"emptyLinePlaceholder":450},[381,694,696,698,701],{"class":177,"line":695},30,[381,697,537],{"class":386},[381,699,700],{"class":517}," _connect_signals",[381,702,631],{"class":390},[381,704,706,708,711,713],{"class":177,"line":705},31,[381,707,571],{"class":464},[381,709,710],{"class":390},".run_button.clicked.connect(",[381,712,577],{"class":464},[381,714,715],{"class":390},"._execute_analysis)\n",[381,717,719,721,724,726],{"class":177,"line":718},32,[381,720,571],{"class":464},[381,722,723],{"class":390},".cancel_button.clicked.connect(",[381,725,577],{"class":464},[381,727,728],{"class":390},".reject)\n",[381,730,732,734,737,739],{"class":177,"line":731},33,[381,733,571],{"class":464},[381,735,736],{"class":390},".clear_button.clicked.connect(",[381,738,577],{"class":464},[381,740,741],{"class":390},"._reset_inputs)\n",[381,743,745],{"class":177,"line":744},34,[381,746,451],{"emptyLinePlaceholder":450},[381,748,750,752,755],{"class":177,"line":749},35,[381,751,537],{"class":386},[381,753,754],{"class":517}," _execute_analysis",[381,756,631],{"class":390},[381,758,760,763,765,768],{"class":177,"line":759},36,[381,761,762],{"class":390},"        selected_layer ",[381,764,471],{"class":386},[381,766,767],{"class":464}," self",[381,769,770],{"class":390},".layer_combo.currentLayer()\n",[381,772,774,777,780],{"class":177,"line":773},37,[381,775,776],{"class":386},"        if",[381,778,779],{"class":386}," not",[381,781,782],{"class":390}," selected_layer:\n",[381,784,786,789,791,793,796,798,801],{"class":177,"line":785},38,[381,787,788],{"class":390},"            QMessageBox.warning(",[381,790,577],{"class":464},[381,792,273],{"class":390},[381,794,795],{"class":489},"\"Missing Input\"",[381,797,273],{"class":390},[381,799,800],{"class":489},"\"Please select a valid vector layer.\"",[381,802,580],{"class":390},[381,804,806],{"class":177,"line":805},39,[381,807,808],{"class":386},"            return\n",[381,810,812],{"class":177,"line":811},40,[381,813,451],{"emptyLinePlaceholder":450},[381,815,817],{"class":177,"line":816},41,[381,818,819],{"class":457},"        # GIS processing logic goes here\n",[381,821,823,826,828,830,833,835,838],{"class":177,"line":822},42,[381,824,825],{"class":390},"        QMessageBox.information(",[381,827,577],{"class":464},[381,829,273],{"class":390},[381,831,832],{"class":489},"\"Success\"",[381,834,273],{"class":390},[381,836,837],{"class":489},"\"Analysis triggered successfully.\"",[381,839,580],{"class":390},[381,841,843,845],{"class":177,"line":842},43,[381,844,571],{"class":464},[381,846,847],{"class":390},".accept()\n",[381,849,851],{"class":177,"line":850},44,[381,852,451],{"emptyLinePlaceholder":450},[381,854,856,858,861],{"class":177,"line":855},45,[381,857,537],{"class":386},[381,859,860],{"class":517}," _reset_inputs",[381,862,631],{"class":390},[381,864,866,868,871,873],{"class":177,"line":865},46,[381,867,571],{"class":464},[381,869,870],{"class":390},".layer_combo.setLayer(",[381,872,548],{"class":464},[381,874,580],{"class":390},[381,876,878,880,883,885,887,889,892],{"class":177,"line":877},47,[381,879,776],{"class":386},[381,881,882],{"class":464}," hasattr",[381,884,521],{"class":390},[381,886,577],{"class":464},[381,888,273],{"class":390},[381,890,891],{"class":489},"\"threshold_spin\"",[381,893,531],{"class":390},[381,895,897,900,903,905],{"class":177,"line":896},48,[381,898,899],{"class":464},"            self",[381,901,902],{"class":390},".threshold_spin.setValue(",[381,904,56],{"class":464},[381,906,580],{"class":390},[15,908,909,910,912,913,916],{},"The ",[19,911,102],{}," function parses the XML interface and returns a dynamic class that inherits from the base Qt widget. Calling ",[19,914,915],{},"self.setupUi(self)"," injects all defined controls into the dialog. Promoting widgets within Qt Designer requires specifying the exact header file and class name. When the dialog initializes, QGIS automatically resolves these headers through its Python bindings, eliminating manual import statements.",[39,918,921,924,927,930,937,940,945,948,952,957,962,966,971,974,977,981,984,988,992,998,1002,1007,1011,1016,1020,1024,1029],{"viewBox":919,"role":42,"ariaLabel":920,"xmlns":44},"0 0 760 260","How a plain Qt widget is promoted to a QGIS widget and resolved at runtime",[46,922,923],{},"Widget promotion: from a plain QComboBox to a runtime QgsMapLayerComboBox",[50,925,926],{},"A plain QComboBox dropped on the Qt Designer canvas is right-clicked and promoted by entering the class name QgsMapLayerComboBox and header qgsmaplayercombobox.h. Qt Designer saves only a placeholder in the .ui XML. When setupUi() runs inside QGIS, its Python bindings resolve that placeholder into a real QgsMapLayerComboBox that gains layer filtering, CRS awareness, and currentLayer().",[54,928],{"x":56,"y":56,"width":57,"height":929,"fill":59},"260",[161,931,932],{},[164,933,935],{"id":934,"viewBox":167,"refX":168,"refY":169,"markerWidth":170,"markerHeight":170,"orient":171},"arrowp",[173,936],{"d":175,"fill":83},[54,938],{"x":93,"y":131,"width":939,"height":81,"rx":66,"fill":67,"stroke":123,"style":69},"200",[71,941,944],{"x":942,"y":943,"style":146,"fill":76,"textAnchor":77},"120","116","Plain Qt widget",[71,946,272],{"x":942,"y":947,"style":82,"fill":83,"textAnchor":77},"139",[71,949,951],{"x":942,"y":950,"style":88,"fill":83,"textAnchor":77},"157","dropped on canvas",[54,953],{"x":954,"y":955,"width":939,"height":956,"rx":66,"fill":67,"stroke":68,"style":69},"288","52","156",[71,958,961],{"x":959,"y":960,"style":146,"fill":76,"textAnchor":77},"388","78","Promote to…",[71,963,965],{"x":959,"y":964,"style":88,"fill":83,"textAnchor":77},"96","right-click the widget",[177,967],{"x1":968,"y1":185,"x2":969,"y2":185,"stroke":68,"style":970},"306","470","stroke-width:1;stroke-dasharray:3 3",[71,972,159],{"x":959,"y":973,"style":82,"fill":76,"textAnchor":77},"130",[71,975,327],{"x":959,"y":976,"style":82,"fill":76,"textAnchor":77},"150",[71,978,980],{"x":959,"y":979,"style":88,"fill":68,"textAnchor":77},"188","placeholder saved in .ui XML",[54,982],{"x":983,"y":955,"width":939,"height":956,"rx":66,"fill":95,"stroke":96,"style":69},"540",[71,985,987],{"x":986,"y":960,"style":146,"fill":101,"textAnchor":77},"640","Resolved at runtime",[71,989,159],{"x":986,"y":990,"style":991,"fill":101,"textAnchor":77},"99","text-anchor:middle;font-size:11.5px;font-family:monospace",[71,993,997],{"x":994,"y":995,"style":996,"fill":101,"textAnchor":202},"562","128","text-anchor:start;font-size:11px;font-family:sans-serif","• layer filtering",[71,999,1001],{"x":994,"y":1000,"style":996,"fill":101,"textAnchor":202},"148","• CRS awareness",[71,1003,1006],{"x":994,"y":1004,"style":1005,"fill":101,"textAnchor":202},"168","text-anchor:start;font-size:11px;font-family:monospace","• currentLayer()",[71,1008,1010],{"x":986,"y":1009,"style":106,"fill":101,"textAnchor":77},"194","via QGIS Python bindings",[177,1012],{"x1":1013,"y1":973,"x2":1014,"y2":973,"stroke":83,"style":1015},"222","284","stroke-width:2.5;marker-end:url(#arrowp)",[71,1017,1019],{"x":1018,"y":942,"style":106,"fill":83,"textAnchor":77},"253","promote",[177,1021],{"x1":1022,"y1":973,"x2":1023,"y2":973,"stroke":83,"style":1015},"490","536",[71,1025,1028],{"x":1026,"y":942,"style":1027,"fill":83,"textAnchor":77},"513","text-anchor:middle;font-size:10px;font-family:monospace","setupUi()",[71,1030,1032],{"x":93,"y":1031,"style":996,"fill":83,"textAnchor":202},"238","The .ui file never imports the QGIS class — it stores a name; QGIS supplies the real widget on load.",[216,1034,1036],{"id":1035},"signal-handling-map-interaction","Signal Handling & Map Interaction",[15,1038,1039,1040,1043,1044,1048],{},"Qt's signal-slot architecture drives interactive behavior. Map tools require careful state management to avoid blocking the main thread. When connecting UI controls to spatial operations, always validate inputs before triggering heavy geoprocessing. The ",[19,1041,1042],{},"QgsTaskManager"," framework should handle long-running operations, keeping the interface responsive — and for algorithms you expect to reuse, route the work through a ",[24,1045,1047],{"href":1046},"\u002Fqgis-plugin-development\u002Fprocessing-provider-plugins\u002F","processing provider plugin"," rather than embedding it in a slot, which gives you batch execution and Model Builder support for free.",[15,1050,1051,1052,1056,1057,1059,1060,370],{},"Not every interface is a modal dialog. A tool the user keeps open while working — a layer inspector or live query panel — belongs in a dockable panel instead; ",[24,1053,1055],{"href":1054},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis\u002F","add a custom dock widget in PyQGIS"," applies the same ",[19,1058,21],{}," loading and widget-promotion patterns to a ",[19,1061,1062],{},"QDockWidget",[15,1064,1065,1066,1069,1070,1073,1074,1077],{},"To prevent memory leaks or dangling references, avoid manual signal disconnection unless absolutely necessary. Qt's parent-child hierarchy and ",[19,1067,1068],{},"Qt.WA_DeleteOnClose"," handle cleanup automatically. If you must disconnect, wrap the call in a ",[19,1071,1072],{},"try\u002Fexcept"," block to prevent ",[19,1075,1076],{},"RuntimeError"," when the slot is already disconnected.",[216,1079,1081],{"id":1080},"internationalization","Internationalization",[15,1083,1084,1085,1087,1088,1091,1092,1095,1096,1099,1100,1103,1104,1107,1108,1111,1112,1114,1115,1117],{},"Production plugins must support multilingual workflows. Qt Designer stores translatable strings in the ",[19,1086,21],{}," file using standard ",[19,1089,1090],{},"tr()"," wrappers. After generating a ",[19,1093,1094],{},".ts"," translation file with ",[19,1097,1098],{},"pylupdate5",", translators populate localized strings, which are compiled into ",[19,1101,1102],{},".qm"," binary files using ",[19,1105,1106],{},"lrelease",". Loading these at runtime requires initializing ",[19,1109,1110],{},"QTranslator"," before the plugin UI instantiates — see the boilerplate ",[19,1113,562],{}," pattern in ",[24,1116,32],{"href":31}," for the standard loading sequence.",[216,1119,1121],{"id":1120},"common-errors-fixes","Common Errors & Fixes",[15,1123,1124],{},"Visual interface development introduces specific failure modes. Understanding these prevents deployment bottlenecks.",[15,1126,1127,1133,1137,1138,1141,1142,1145,1146,1149,1150,1153],{},[248,1128,1129,1130],{},"1. ",[19,1131,1132],{},"ImportError: No module named 'qgis.PyQt.uic'",[1134,1135,1136],"em",{},"Cause:"," Running the script outside the QGIS Python environment or using a mismatched PyQt version.\n",[1134,1139,1140],{},"Fix:"," Always execute PyQGIS code within the QGIS Python console or a virtual environment configured with ",[19,1143,1144],{},"qgis.core"," and ",[19,1147,1148],{},"qgis.PyQt"," paths. Verify ",[19,1151,1152],{},"sys.executable"," points to the QGIS Python interpreter.",[15,1155,1156,1159,1161,1162,1164,1165,1167,1168,1170],{},[248,1157,1158],{},"2. Widget Promotion Fails at Runtime",[1134,1160,1136],{}," Qt Designer cannot locate the promoted header, or the header path is incorrect for the current QGIS version.\n",[1134,1163,1140],{}," Use the exact class name and header as documented in the QGIS API reference. For example, ",[19,1166,327],{}," resolves correctly in QGIS 3.x. If promotion fails, instantiate the widget programmatically after ",[19,1169,1028],{}," and replace the placeholder using layout management.",[15,1172,1173,1176,1178,1179,1181,1182,259,1184,304,1186,259,1188,1190,1191,1194],{},[248,1174,1175],{},"3. UI Layout Breaks on High-DPI Displays",[1134,1177,1136],{}," Hardcoded pixel dimensions or missing layout containers.\n",[1134,1180,1140],{}," Apply ",[19,1183,296],{},[19,1185,303],{},[19,1187,307],{},[19,1189,310],{},". Test interfaces with ",[19,1192,1193],{},"QT_SCALE_FACTOR=2"," to simulate high-DPI environments.",[15,1196,1197,1200,1202,1203,1205,1206,1209,1210,1213,1214,1217,1218,1221],{},[248,1198,1199],{},"4. Memory Leaks from Unclosed Dialogs",[1134,1201,1136],{}," Creating new dialog instances without proper parent assignment or garbage collection.\n",[1134,1204,1140],{}," Pass ",[19,1207,1208],{},"iface.mainWindow()"," as the parent during initialization, or use ",[19,1211,1212],{},"self.setAttribute(Qt.WA_DeleteOnClose)",". For modal dialogs, call ",[19,1215,1216],{},"exec()"," instead of ",[19,1219,1220],{},"show()"," to block execution until closure.",[15,1223,1224,1230,1232,1233,1235,1236,1238,1239,1241,1242,1244],{},[248,1225,1226,1227,1229],{},"5. ",[19,1228,114],{}," Compilation Errors",[1134,1231,1136],{}," Malformed XML in the ",[19,1234,21],{}," file or unsupported custom widgets.\n",[1134,1237,1140],{}," Validate the ",[19,1240,21],{}," file by reopening it in Qt Designer. Remove unsupported third-party widgets before compilation. Alternatively, switch to runtime ",[19,1243,102],{}," to bypass compilation entirely.",[216,1246,1248],{"id":1247},"packaging-considerations","Packaging Considerations",[15,1250,1251,1252,1254,1255,1257,1258,1260,1261,1265,1266,1268,1269,1271,1272,1274,1275,1279],{},"Once the interface is stable, asset distribution requires careful planning. The ",[19,1253,21],{}," files must be included in the plugin's directory structure and referenced correctly in the initialization script — the same ",[19,1256,344],{}," folder that ",[24,1259,32],{"href":31}," reserves for exactly this purpose. When preparing releases, ensure all UI resources are bundled alongside Python modules. For plugins distributed through ",[24,1262,1264],{"href":1263},"\u002Fqgis-plugin-development\u002Fpublishing-to-the-qgis-plugin-repository\u002F","publishing to the QGIS plugin repository",", the zip structure must include the ",[19,1267,344],{}," folder with every ",[19,1270,21],{}," file intact so that ",[19,1273,102],{}," resolves correctly on end-user machines. Because dynamic loading depends on the raw XML shipping with the package, add a check for it to your build — the headless harness in ",[24,1276,1278],{"href":1277},"\u002Fqgis-plugin-development\u002Ftesting-and-ci-for-plugins\u002F","testing and CI for plugins"," can assert that each dialog constructs without a running desktop.",[216,1281,1283],{"id":1282},"two-ways-to-turn-a-ui-file-into-a-class","Two ways to turn a .ui file into a class",[15,1285,1286],{},"Qt Designer produces XML. Getting from that XML to a usable Python class can happen at build time or at run time, and the choice affects packaging, debugging and how quickly a design change appears.",[15,1288,1289],{},[39,1290,1293,1296,1299,1302,1310,1314,1321,1326,1331,1337,1342,1346,1350,1352,1356,1360,1364,1369,1373,1376,1380,1383,1387],{"viewBox":1291,"role":42,"ariaLabel":1292,"xmlns":44},"0 0 760 252","Two paths from a ui file to a Python dialog: compiling with pyuic5 into a generated module, or loading it at runtime with uic.loadUiType",[46,1294,1295],{},"Compile ahead of time, or load at run time",[50,1297,1298],{},"The compile path runs pyuic5 over the ui file to produce a Python module that is imported like any other, which means the ui file is not needed at run time but must be recompiled after every design change. The runtime path calls uic.loadUiType on the ui file when the plugin starts, so design changes appear immediately but the ui file must ship with the plugin and be findable.",[54,1300],{"x":56,"y":56,"width":57,"height":1301,"fill":59},"252",[161,1303,1304],{},[164,1305,1307],{"id":1306,"viewBox":167,"refX":66,"refY":169,"markerWidth":170,"markerHeight":170,"orient":171},"uiPathArrow",[173,1308],{"d":1309,"fill":111},"M0 0 L10 5 L0 10 z",[71,1311,1313],{"x":58,"y":1312,"style":75,"fill":76,"textAnchor":77},"26","The same XML, two ways to reach a class",[54,1315],{"x":1316,"y":1317,"width":1318,"height":1319,"rx":1320,"fill":95,"stroke":111,"style":69},"16","106","140","60","10",[71,1322,1325],{"x":131,"y":1323,"style":1324,"fill":101,"textAnchor":77},"132","text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","dialog.ui",[71,1327,1330],{"x":131,"y":1328,"style":1329,"fill":101,"textAnchor":77},"152","text-anchor:middle;font-size:10.5px;font-family:sans-serif","Qt Designer XML",[54,1332],{"x":1333,"y":1334,"width":1335,"height":1336,"rx":1320,"fill":67,"stroke":123,"style":69},"216","46","304","76",[71,1338,1341],{"x":1339,"y":1340,"style":1324,"fill":123,"textAnchor":77},"368","70","pyuic5 → ui_dialog.py",[71,1343,1345],{"x":1339,"y":1344,"style":1329,"fill":83,"textAnchor":77},"92","a normal import · steps into cleanly",[71,1347,1349],{"x":1339,"y":1348,"style":1329,"fill":68,"textAnchor":77},"110","must recompile after every edit",[54,1351],{"x":1333,"y":976,"width":1335,"height":1336,"rx":1320,"fill":67,"stroke":142,"style":69},[71,1353,1355],{"x":1339,"y":1354,"style":1324,"fill":142,"textAnchor":77},"174","uic.loadUiType(path)",[71,1357,1359],{"x":1339,"y":1358,"style":1329,"fill":83,"textAnchor":77},"196","edits appear on the next reload",[71,1361,1363],{"x":1339,"y":1362,"style":1329,"fill":68,"textAnchor":77},"214","the .ui must ship and be findable",[54,1365],{"x":1366,"y":1317,"width":1004,"height":1319,"rx":1320,"fill":67,"stroke":1367,"style":1368},"576","#59645f","stroke-width:2",[71,1370,1372],{"x":1371,"y":1323,"style":1324,"fill":76,"textAnchor":77},"660","your dialog class",[71,1374,1375],{"x":1371,"y":1328,"style":1329,"fill":83,"textAnchor":77},"identical either way",[177,1377],{"x1":956,"y1":942,"x2":1378,"y2":81,"stroke":111,"style":1379},"212","stroke-width:2.5;marker-end:url(#uiPathArrow)",[177,1381],{"x1":956,"y1":976,"x2":1378,"y2":1382,"stroke":111,"style":1379},"184",[177,1384],{"x1":1385,"y1":81,"x2":1386,"y2":942,"stroke":111,"style":1379},"520","572",[177,1388],{"x1":1385,"y1":1382,"x2":1386,"y2":1328,"stroke":111,"style":1379},[15,1390,1391,1392,1394],{},"The runtime form is the better default during development, because a change in Designer is visible after a plugin reload with no build step in between. Build the path from ",[19,1393,483],{}," rather than hard-coding it, or the dialog will load on your machine and fail on everyone else's:",[372,1396,1398],{"className":374,"code":1397,"language":376,"meta":377,"style":377},"import os\nfrom qgis.PyQt import uic\n\nFORM_CLASS, _ = uic.loadUiType(\n    os.path.join(os.path.dirname(__file__), \"dialog.ui\")\n)\n",[19,1399,1400,1406,1416,1420,1431,1445],{"__ignoreMap":377},[381,1401,1402,1404],{"class":177,"line":383},[381,1403,387],{"class":386},[381,1405,391],{"class":390},[381,1407,1408,1410,1412,1414],{"class":177,"line":394},[381,1409,397],{"class":386},[381,1411,400],{"class":390},[381,1413,387],{"class":386},[381,1415,405],{"class":390},[381,1417,1418],{"class":177,"line":408},[381,1419,451],{"emptyLinePlaceholder":450},[381,1421,1422,1424,1426,1428],{"class":177,"line":421},[381,1423,465],{"class":464},[381,1425,468],{"class":390},[381,1427,471],{"class":386},[381,1429,1430],{"class":390}," uic.loadUiType(\n",[381,1432,1433,1436,1438,1440,1443],{"class":177,"line":434},[381,1434,1435],{"class":390},"    os.path.join(os.path.dirname(",[381,1437,483],{"class":464},[381,1439,486],{"class":390},[381,1441,1442],{"class":489},"\"dialog.ui\"",[381,1444,580],{"class":390},[381,1446,1447],{"class":177,"line":447},[381,1448,580],{"class":390},[15,1450,1451,1454,1455,1458,1459,1462],{},[248,1452,1453],{},"Breakdown:"," ",[19,1456,1457],{},"loadUiType()"," returns a tuple of the generated form class and the base widget class, which is why the second value is discarded. Resolving the path from ",[19,1460,1461],{},"os.path.dirname(__file__)"," makes it correct wherever the plugin is installed — an absolute path from your development tree is the single most common reason a dialog fails for other users. Doing this at module level means the parsing cost is paid once at import rather than on every dialog construction.",[216,1464,1466],{"id":1465},"naming-is-the-contract-between-designer-and-python","Naming is the contract between Designer and Python",[15,1468,1469],{},"Everything you place in Designer becomes an attribute on the dialog, named by its object name. That makes the object name a genuine API rather than a label, and renaming a widget in Designer silently breaks every line of Python that referenced it.",[15,1471,1472,1473,273,1476,273,1479,1482],{},"The practical discipline is short: give every widget you will touch from code a deliberate, prefixed name — ",[19,1474,1475],{},"btn_run",[19,1477,1478],{},"cmb_layer",[19,1480,1481],{},"spn_distance"," — and leave the rest at their defaults. A dialog where only the interactive widgets are named tells the next reader exactly which ones the code cares about.",[15,1484,1485,1486,1489,1490,1493],{},"Signal connections are the other half of that contract. Qt supports automatic connection by naming convention, where a method called ",[19,1487,1488],{},"on_btn_run_clicked"," is wired up implicitly — and it is worth avoiding. The connection is invisible, a typo produces silence rather than an error, and renaming either side breaks it without warning. Explicit ",[19,1491,1492],{},"self.btn_run.clicked.connect(self.run)"," is one more line and fails loudly when the widget name is wrong.",[216,1495,1497],{"id":1496},"key-takeaways","Key Takeaways",[1499,1500,1501,1513,1527,1539,1550,1560],"ul",{},[245,1502,1503,1509,1510,1512],{},[248,1504,1505,1506,1508],{},"Design in the ",[19,1507,21],{}," file, behave in Python."," Keep every layout, size policy, and ",[19,1511,286],{}," in Qt Designer, and put all logic — validation, geoprocessing, signal handling — in the dialog class. That separation is what lets the interface and the code evolve independently.",[245,1514,1515,1523,1524,1526],{},[248,1516,1517,1518,1520,1521,370],{},"Prefer ",[19,1519,102],{}," over ",[19,1522,114],{}," Runtime loading parses the XML on the user's machine, sidestepping ",[19,1525,114],{}," version mismatches between your build box and their install, and removes a compile step from every iteration.",[245,1528,1529,1532,1533,273,1535,1538],{},[248,1530,1531],{},"Promote QGIS widgets, don't rebuild them."," Right-click a plain widget and promote it to ",[19,1534,159],{},[19,1536,1537],{},"QgsFieldComboBox",", or similar; QGIS resolves the header through its Python bindings at runtime, so you inherit layer filtering and CRS awareness for free.",[245,1540,1541,1454,1544,1546,1547,1549],{},[248,1542,1543],{},"Name every interactive widget.",[19,1545,137],{}," only binds controls that carry an ",[19,1548,286],{},"; an unnamed widget exists in the layout but is unreachable from Python.",[245,1551,1552,1555,1556,1559],{},[248,1553,1554],{},"Keep the main thread free."," Validate in the slot, then hand heavy work to ",[19,1557,1558],{},"QgsTask"," or a Processing algorithm so the dialog — and the whole QGIS window — never freezes.",[245,1561,1562,1205,1565,1567,1568,1570],{},[248,1563,1564],{},"Let Qt own cleanup.",[19,1566,1208],{}," as the parent and set ",[19,1569,1068],{},"; avoid manual signal disconnection unless you truly need it.",[15,1572,1573,1574,1576,1577,1580,1581,370],{},"With the dialog in place, the natural next steps are exposing its logic as a reusable ",[24,1575,1047],{"href":1046},", covering it with ",[24,1578,1579],{"href":1277},"automated tests and CI",", and shipping it through the ",[24,1582,1583],{"href":1263},"QGIS plugin repository",[216,1585,1587],{"id":1586},"frequently-asked-questions","Frequently Asked Questions",[15,1589,1590,1602,1603,1605,1606,1608,1609,1611,1612,1614],{},[248,1591,1592,1593,1595,1596,1598,1599,1601],{},"Should I use ",[19,1594,102],{}," or compile the ",[19,1597,21],{}," file with ",[19,1600,114],{},"?","\nDynamic loading with ",[19,1604,102],{}," is recommended for QGIS plugins because it parses the ",[19,1607,21],{}," file at runtime, avoiding ",[19,1610,114],{}," version mismatches between your build machine and end-user installs. Static compilation is only worth it when you need to ship without the raw ",[19,1613,21],{}," or want a marginal startup gain. For QGIS 3.34 LTR, runtime loading is the simplest reliable path.",[15,1616,1617,1623,1624,1626,1627,1629,1630,1632,1633,1635,1636,1638,1639,1145,1642,1645],{},[248,1618,1619,1620,1622],{},"How do I embed a QGIS widget like ",[19,1621,159],{}," in Qt Designer?","\nDrop a plain ",[19,1625,272],{}," onto the canvas, right-click it, and choose ",[19,1628,320],{},", then enter the class name ",[19,1631,159],{}," and header ",[19,1634,327],{},". Qt Designer stores a placeholder that QGIS resolves through its Python bindings at runtime, so no manual import is needed. After ",[19,1637,1028],{}," you can call methods like ",[19,1640,1641],{},"setFilters()",[19,1643,1644],{},"currentLayer()"," directly.",[15,1647,1648,1654,1656,1657,1659,1660,1662,1663,259,1666,1669,1670,1672],{},[248,1649,1650,1651,1653],{},"Why are my widget ",[19,1652,286],{}," values not becoming Python attributes?",[19,1655,137],{}," only binds widgets that have an ",[19,1658,286],{}," assigned in Qt Designer. If you skipped naming a control, it exists in the layout but is unreachable from Python. Give every interactive widget a descriptive ",[19,1661,286],{}," such as ",[19,1664,1665],{},"layer_combo",[19,1667,1668],{},"run_button"," before saving the ",[19,1671,21],{}," file.",[15,1674,1675,1678,1679,1681,1682,1685],{},[248,1676,1677],{},"How do I keep the interface responsive during heavy geoprocessing?","\nValidate inputs in the slot, then hand long-running work to ",[19,1680,1558],{}," or the Processing Framework rather than running it inline on the main thread. Connecting a button's ",[19,1683,1684],{},"clicked"," signal directly to a blocking function freezes the dialog and the whole QGIS window. Push status updates back to the UI from the task's completion signal.",[15,1687,1688,1691,1692,1567,1694,1696,1697,1699,1700,1702,1703,1217,1705,370],{},[248,1689,1690],{},"How should I handle dialog cleanup to avoid memory leaks?","\nPass ",[19,1693,1208],{},[19,1695,1212],{}," so Qt's parent-child hierarchy disposes of the dialog automatically. Avoid manual signal disconnection unless necessary; if you must disconnect, wrap it in ",[19,1698,1072],{}," to swallow the ",[19,1701,1076],{}," raised when a slot is already gone. For modal dialogs, use ",[19,1704,1216],{},[19,1706,1220],{},[15,1708,1709,1712,1713,1715],{},[248,1710,1711],{},"Should I compile the .ui file or load it at run time?","\nLoad it at run time during development, because a change in Designer then appears after a plugin reload with no build step. Compiling ahead of time is worth it only when you want to ship without the ",[19,1714,21],{}," file or need the generated source in version control.",[15,1717,1718,1721,1722,1724,1725,1727],{},[248,1719,1720],{},"Why does my dialog fail to open on someone else's machine?","\nAlmost always an absolute path to the ",[19,1723,21],{}," file left over from development. Build the path from ",[19,1726,1461],{}," so it resolves wherever the plugin is installed.",[15,1729,1730,1733,1734,1737],{},[248,1731,1732],{},"Should I use Qt's automatic signal connections?","\nNo. Connections made by naming convention are invisible in the source, fail silently on a typo and break when either side is renamed. An explicit ",[19,1735,1736],{},"connect()"," call is one extra line and fails loudly when something is wrong.",[15,1739,1740,1743,1744,1747],{},[248,1741,1742],{},"Can I use the QGIS-specific widgets in Designer?","\nYes. QGIS ships a Designer plugin providing widgets such as the layer combo box and the file picker, and using them saves reimplementing behaviour users already know. They import from ",[19,1745,1746],{},"qgis.gui",", so a dialog using them cannot be tested headless.",[15,1749,1750,1753],{},[248,1751,1752],{},"Should the dialog contain any logic?","\nAs little as possible. A dialog that collects values and emits them keeps the logic testable without a display, which is the difference between a suite that runs in CI and one that does not.",[15,1755,1756,1759,1760,1763,1764,1767,1768,1145,1771,1774],{},[248,1757,1758],{},"How do I make a dialog remember its size?","\nSave the geometry in ",[19,1761,1762],{},"closeEvent"," and restore it when the dialog is constructed, using ",[19,1765,1766],{},"QgsSettings"," keyed under your plugin's name. Qt provides ",[19,1769,1770],{},"saveGeometry()",[19,1772,1773],{},"restoreGeometry()"," for exactly this, and the stored value is opaque, so no parsing is needed.",[15,1776,1777,1780],{},[248,1778,1779],{},"Should dialogs be modal?","\nRarely. A modal dialog blocks the whole application, which prevents the user consulting the map they are trying to describe. A modeless dialog or a dock widget is almost always the better choice for GIS work.",[216,1782,1784],{"id":1783},"related-guides","Related Guides",[1499,1786,1787,1795,1804,1815,1822,1829,1835,1841,1850],{},[245,1788,1789,1454,1792,1794],{},[248,1790,1791],{},"Up:",[24,1793,27],{"href":26}," — the parent guide covering the full plugin journey.",[245,1796,1797,1800,1801,1803],{},[24,1798,1799],{"href":31},"QGIS Plugin Boilerplate & Structure"," — build the skeleton whose ",[19,1802,36],{}," method opens the dialog you design here.",[245,1805,1806,1809,1810,1812,1813,370],{},[24,1807,1808],{"href":1054},"Add a Custom Dock Widget in PyQGIS"," — apply the same ",[19,1811,21],{}," and promotion patterns to a persistent ",[19,1814,1062],{},[245,1816,1817,1821],{},[24,1818,1820],{"href":1819},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fload-ui-file-at-runtime-pyqgis\u002F","Load a .ui File at Runtime in PyQGIS"," — skip the compile step and keep the designer file as the source of truth.",[245,1823,1824,1828],{},[24,1825,1827],{"href":1826},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002F","Background Tasks and Plugin Performance"," — keep the dialog responsive once the work behind it grows.",[245,1830,1831,1834],{},[24,1832,1833],{"href":1046},"Processing Provider Plugins for QGIS"," — move heavy geoprocessing out of your slots and into reusable algorithms.",[245,1836,1837,1840],{},[24,1838,1839],{"href":1277},"Testing & CI for QGIS Plugins"," — assert that each dialog constructs headlessly.",[245,1842,1843,1846,1847,1849],{},[24,1844,1845],{"href":1263},"Publishing to the QGIS Plugin Repository"," — bundle the ",[19,1848,344],{}," folder correctly for end-user installs.",[245,1851,1852,1856],{},[24,1853,1855],{"href":1854},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fuse-qgis-custom-widgets-in-qt-designer\u002F","Use QGIS Custom Widgets in Qt Designer"," — layer, field, file and CRS pickers that already know about the project.",[1858,1859,1860],"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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .svObZ, html code.shiki .svObZ{--shiki-default:#B392F0}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);}",{"title":377,"searchDepth":394,"depth":394,"links":1862},[1863,1864,1865,1866,1867,1868,1869,1870,1871,1872,1873,1874],{"id":218,"depth":394,"text":219},{"id":236,"depth":394,"text":237},{"id":363,"depth":394,"text":364},{"id":1035,"depth":394,"text":1036},{"id":1080,"depth":394,"text":1081},{"id":1120,"depth":394,"text":1121},{"id":1247,"depth":394,"text":1248},{"id":1282,"depth":394,"text":1283},{"id":1465,"depth":394,"text":1466},{"id":1496,"depth":394,"text":1497},{"id":1586,"depth":394,"text":1587},{"id":1783,"depth":394,"text":1784},"Build QGIS plugin UIs with Qt Designer. Create .ui files, load them at runtime with uic.loadUiType, promote QGIS widgets, and wire signals to PyQGIS logic.","md",{"slug":12,"type":1878,"breadcrumb":1879,"datePublished":1880,"dateModified":1881},"guide","Qt Designer for Interfaces","2025-09-18","2026-07-18","\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces",{"title":5,"description":1875},"qgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Findex","QovMxsbvDN1IeLDL2fYs3u9lH1xID40rwH6SL6NXxe8",1787823363125]