[{"data":1,"prerenderedAt":1640},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fload-ui-file-at-runtime-pyqgis":3},{"id":4,"title":5,"body":6,"description":1629,"extension":1630,"meta":1631,"navigation":286,"path":1636,"seo":1637,"stem":1638,"__hash__":1639},"docs\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fload-ui-file-at-runtime-pyqgis\u002Findex.md","Load a .ui File at Runtime in PyQGIS",{"type":7,"value":8,"toc":1615},"minimark",[9,13,30,43,197,202,229,233,405,434,448,452,647,675,679,682,734,754,843,847,925,942,946,956,969,973,980,1214,1248,1264,1357,1361,1367,1444,1448,1499,1503,1515,1519,1525,1531,1544,1558,1567,1580,1584,1611],[10,11,5],"h1",{"id":12},"load-a-ui-file-at-runtime-in-pyqgis",[14,15,16,17,21,22,25,26,29],"p",{},"There are two ways to get a Qt Designer dialog into a plugin: compile the ",[18,19,20],"code",{},".ui"," into a Python module with ",[18,23,24],{},"pyuic5",", or load it at runtime with ",[18,27,28],{},"uic",". The second is what the QGIS Plugin Builder template does, and it is the better default — the designer file stays the single source of truth, no build step can go stale, and a colleague editing the dialog does not have to remember to regenerate anything.",[14,31,32,33,38,39,42],{},"This recipe belongs to ",[34,35,37],"a",{"href":36},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002F","Qt Designer for GIS Interfaces",". It covers ",[18,40,41],{},"loadUiType",", resolving the path so it works from any working directory, wiring signals, using the QGIS custom widgets, and the reload behaviour that makes runtime loading pleasant during development.",[14,44,45],{},[46,47,52,56,60,67,84,93,100,110,116,121,125,130,133,137,142,146,153,157,161,165,170,173,176,180,184,186,188,191,193],"svg",{"viewBox":48,"role":49,"ariaLabel":50,"xmlns":51},"0 0 760 268","img","Two routes from a designer file to a dialog class: compiling to Python with pyuic and loading at runtime with uic, showing the extra build step in the compiled route","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[53,54,55],"title",{},"Compile the dialog, or load it at runtime",[57,58,59],"desc",{},"The compiled route runs pyuic over the designer file to produce a generated Python module which the plugin imports, adding a build step that can be forgotten. The runtime route reads the designer file directly with uic loadUiType each time the plugin starts, so the designer file is always the source of truth.",[61,62],"rect",{"x":63,"y":63,"width":64,"height":65,"fill":66},"0","760","268","#f6f3ea",[68,69,70],"defs",{},[71,72,79],"marker",{"id":73,"viewBox":74,"refX":75,"refY":76,"markerWidth":77,"markerHeight":77,"orient":78},"uicArrow","0 0 10 10","8","5","7","auto-start-reverse",[80,81],"path",{"d":82,"fill":83},"M0 0 L10 5 L0 10 z","#2f3b35",[85,86,92],"text",{"x":87,"y":88,"style":89,"fill":90,"textAnchor":91},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","One of these has a step you can forget to run",[85,94,99],{"x":95,"y":96,"style":97,"fill":98},"24","86","font-size:12px;font-weight:bold;font-family:sans-serif","#b45309","compiled",[61,101],{"x":102,"y":103,"width":104,"height":105,"rx":106,"fill":107,"stroke":108,"style":109},"140","62","132","46","6","#fffdf7","#59645f","stroke-width:1.5",[85,111,115],{"x":112,"y":113,"style":114,"fill":83,"textAnchor":91},"206","90","text-anchor:middle;font-size:11px;font-family:sans-serif","dialog.ui",[61,117],{"x":118,"y":103,"width":104,"height":105,"rx":106,"fill":119,"stroke":98,"style":120},"308","#fdf2e2","stroke-width:2.5",[85,122,24],{"x":123,"y":124,"style":114,"fill":98,"textAnchor":91},"374","84",[85,126,129],{"x":123,"y":127,"style":128,"fill":83,"textAnchor":91},"100","text-anchor:middle;font-size:10px;font-family:sans-serif","build step",[61,131],{"x":132,"y":103,"width":104,"height":105,"rx":106,"fill":107,"stroke":108,"style":109},"476",[85,134,136],{"x":135,"y":113,"style":114,"fill":83,"textAnchor":91},"542","ui_dialog.py",[61,138],{"x":139,"y":103,"width":127,"height":105,"rx":106,"fill":140,"stroke":141,"style":109},"644","#eef7f4","#0f766e",[85,143,145],{"x":144,"y":113,"style":114,"fill":83,"textAnchor":91},"694","dialog",[147,148],"line",{"x1":149,"y1":150,"x2":151,"y2":150,"stroke":83,"style":152},"272","85","302","stroke-width:2;marker-end:url(#uicArrow)",[147,154],{"x1":155,"y1":150,"x2":156,"y2":150,"stroke":83,"style":152},"440","470",[147,158],{"x1":159,"y1":150,"x2":160,"y2":150,"stroke":83,"style":152},"608","638",[85,162,164],{"x":123,"y":163,"style":114,"fill":98,"textAnchor":91},"130","edit the .ui, forget the build, ship yesterday's dialog",[85,166,169],{"x":95,"y":167,"style":97,"fill":168},"190","#15803d","runtime",[61,171],{"x":102,"y":172,"width":104,"height":105,"rx":106,"fill":107,"stroke":108,"style":109},"166",[85,174,115],{"x":112,"y":175,"style":114,"fill":83,"textAnchor":91},"194",[61,177],{"x":118,"y":172,"width":178,"height":105,"rx":106,"fill":179,"stroke":168,"style":120},"300","#edf8e9",[85,181,183],{"x":182,"y":175,"style":114,"fill":83,"textAnchor":91},"458","uic.loadUiType(path) at import time",[61,185],{"x":139,"y":172,"width":127,"height":105,"rx":106,"fill":140,"stroke":141,"style":109},[85,187,145],{"x":144,"y":175,"style":114,"fill":83,"textAnchor":91},[147,189],{"x1":149,"y1":190,"x2":151,"y2":190,"stroke":83,"style":152},"189",[147,192],{"x1":159,"y1":190,"x2":160,"y2":190,"stroke":83,"style":152},[85,194,196],{"x":182,"y":195,"style":114,"fill":168,"textAnchor":91},"234","the designer file is always what the user sees",[198,199,201],"h2",{"id":200},"prerequisites","Prerequisites",[203,204,205,213,222],"ul",{},[206,207,208,212],"li",{},[209,210,211],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[206,214,215,216,218,219,221],{},"A ",[18,217,20],{}," file produced by Qt Designer — see ",[34,220,37],{"href":36},".",[206,223,224,225,221],{},"A plugin skeleton with the usual structure — see ",[34,226,228],{"href":227},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002F","Plugin Boilerplate and Structure",[198,230,232],{"id":231},"load-the-form","Load the form",[234,235,240],"pre",{"className":236,"code":237,"language":238,"meta":239,"style":239},"language-python shiki shiki-themes github-dark","import os\nfrom qgis.PyQt import uic\nfrom qgis.PyQt.QtWidgets import QDialog\n\nFORM_CLASS, _ = uic.loadUiType(\n    os.path.join(os.path.dirname(__file__), \"parcel_tools_dialog.ui\")\n)\n\nclass ParcelToolsDialog(QDialog, FORM_CLASS):\n    def __init__(self, parent=None):\n        super().__init__(parent)\n        self.setupUi(self)\n","python","",[18,241,242,254,268,281,288,304,323,328,333,357,376,391],{"__ignoreMap":239},[243,244,246,250],"span",{"class":147,"line":245},1,[243,247,249],{"class":248},"snl16","import",[243,251,253],{"class":252},"s95oV"," os\n",[243,255,257,260,263,265],{"class":147,"line":256},2,[243,258,259],{"class":248},"from",[243,261,262],{"class":252}," qgis.PyQt ",[243,264,249],{"class":248},[243,266,267],{"class":252}," uic\n",[243,269,271,273,276,278],{"class":147,"line":270},3,[243,272,259],{"class":248},[243,274,275],{"class":252}," qgis.PyQt.QtWidgets ",[243,277,249],{"class":248},[243,279,280],{"class":252}," QDialog\n",[243,282,284],{"class":147,"line":283},4,[243,285,287],{"emptyLinePlaceholder":286},true,"\n",[243,289,291,295,298,301],{"class":147,"line":290},5,[243,292,294],{"class":293},"sDLfK","FORM_CLASS",[243,296,297],{"class":252},", _ ",[243,299,300],{"class":248},"=",[243,302,303],{"class":252}," uic.loadUiType(\n",[243,305,307,310,313,316,320],{"class":147,"line":306},6,[243,308,309],{"class":252},"    os.path.join(os.path.dirname(",[243,311,312],{"class":293},"__file__",[243,314,315],{"class":252},"), ",[243,317,319],{"class":318},"sU2Wk","\"parcel_tools_dialog.ui\"",[243,321,322],{"class":252},")\n",[243,324,326],{"class":147,"line":325},7,[243,327,322],{"class":252},[243,329,331],{"class":147,"line":330},8,[243,332,287],{"emptyLinePlaceholder":286},[243,334,336,339,343,346,349,352,354],{"class":147,"line":335},9,[243,337,338],{"class":248},"class",[243,340,342],{"class":341},"svObZ"," ParcelToolsDialog",[243,344,345],{"class":252},"(",[243,347,348],{"class":341},"QDialog",[243,350,351],{"class":252},", ",[243,353,294],{"class":293},[243,355,356],{"class":252},"):\n",[243,358,360,363,366,369,371,374],{"class":147,"line":359},10,[243,361,362],{"class":248},"    def",[243,364,365],{"class":293}," __init__",[243,367,368],{"class":252},"(self, parent",[243,370,300],{"class":248},[243,372,373],{"class":293},"None",[243,375,356],{"class":252},[243,377,379,382,385,388],{"class":147,"line":378},11,[243,380,381],{"class":293},"        super",[243,383,384],{"class":252},"().",[243,386,387],{"class":293},"__init__",[243,389,390],{"class":252},"(parent)\n",[243,392,394,397,400,403],{"class":147,"line":393},12,[243,395,396],{"class":293},"        self",[243,398,399],{"class":252},".setupUi(",[243,401,402],{"class":293},"self",[243,404,322],{"class":252},[14,406,407,410,411,414,415,417,418,421,422,425,426,429,430,433],{},[209,408,409],{},"Breakdown:"," ",[18,412,413],{},"loadUiType()"," returns a tuple of the generated form class and the base class the designer file declared; taking only the first and inheriting from ",[18,416,348],{}," explicitly is the conventional pattern. The path is built from ",[18,419,420],{},"os.path.dirname(__file__)"," rather than a relative string, and that is the important line — a relative path resolves against the process's working directory, which for QGIS is wherever it was launched from, not the plugin folder. Calling ",[18,423,424],{},"setupUi(self)"," builds the widgets and, crucially, assigns each one to an attribute named after its ",[18,427,428],{},"objectName"," in Designer, which is how ",[18,431,432],{},"self.wardCombo"," comes to exist.",[14,435,436,437,439,440,443,444,447],{},"Import ",[18,438,28],{}," from ",[18,441,442],{},"qgis.PyQt"," rather than ",[18,445,446],{},"PyQt5"," directly. The shim picks the right Qt binding for the QGIS build, so the same plugin works on a Qt 6 build without changes.",[198,449,451],{"id":450},"wire-the-signals","Wire the signals",[234,453,455],{"className":236,"code":454,"language":238,"meta":239,"style":239},"class ParcelToolsDialog(QDialog, FORM_CLASS):\n    def __init__(self, iface, parent=None):\n        super().__init__(parent)\n        self.setupUi(self)\n        self.iface = iface\n\n        self.browseButton.clicked.connect(self.choose_output)\n        self.wardCombo.currentIndexChanged.connect(self.on_ward_changed)\n        self.buttonBox.accepted.connect(self.run)\n        self.buttonBox.rejected.connect(self.reject)\n\n    def choose_output(self):\n        from qgis.PyQt.QtWidgets import QFileDialog\n        path, _ = QFileDialog.getSaveFileName(self, \"Output\", \"\", \"GeoPackage (*.gpkg)\")\n        if path:\n            self.outputEdit.setText(path)\n",[18,456,457,473,488,498,508,520,524,536,548,560,572,576,586,599,629,638],{"__ignoreMap":239},[243,458,459,461,463,465,467,469,471],{"class":147,"line":245},[243,460,338],{"class":248},[243,462,342],{"class":341},[243,464,345],{"class":252},[243,466,348],{"class":341},[243,468,351],{"class":252},[243,470,294],{"class":293},[243,472,356],{"class":252},[243,474,475,477,479,482,484,486],{"class":147,"line":256},[243,476,362],{"class":248},[243,478,365],{"class":293},[243,480,481],{"class":252},"(self, iface, parent",[243,483,300],{"class":248},[243,485,373],{"class":293},[243,487,356],{"class":252},[243,489,490,492,494,496],{"class":147,"line":270},[243,491,381],{"class":293},[243,493,384],{"class":252},[243,495,387],{"class":293},[243,497,390],{"class":252},[243,499,500,502,504,506],{"class":147,"line":283},[243,501,396],{"class":293},[243,503,399],{"class":252},[243,505,402],{"class":293},[243,507,322],{"class":252},[243,509,510,512,515,517],{"class":147,"line":290},[243,511,396],{"class":293},[243,513,514],{"class":252},".iface ",[243,516,300],{"class":248},[243,518,519],{"class":252}," iface\n",[243,521,522],{"class":147,"line":306},[243,523,287],{"emptyLinePlaceholder":286},[243,525,526,528,531,533],{"class":147,"line":325},[243,527,396],{"class":293},[243,529,530],{"class":252},".browseButton.clicked.connect(",[243,532,402],{"class":293},[243,534,535],{"class":252},".choose_output)\n",[243,537,538,540,543,545],{"class":147,"line":330},[243,539,396],{"class":293},[243,541,542],{"class":252},".wardCombo.currentIndexChanged.connect(",[243,544,402],{"class":293},[243,546,547],{"class":252},".on_ward_changed)\n",[243,549,550,552,555,557],{"class":147,"line":335},[243,551,396],{"class":293},[243,553,554],{"class":252},".buttonBox.accepted.connect(",[243,556,402],{"class":293},[243,558,559],{"class":252},".run)\n",[243,561,562,564,567,569],{"class":147,"line":359},[243,563,396],{"class":293},[243,565,566],{"class":252},".buttonBox.rejected.connect(",[243,568,402],{"class":293},[243,570,571],{"class":252},".reject)\n",[243,573,574],{"class":147,"line":378},[243,575,287],{"emptyLinePlaceholder":286},[243,577,578,580,583],{"class":147,"line":393},[243,579,362],{"class":248},[243,581,582],{"class":341}," choose_output",[243,584,585],{"class":252},"(self):\n",[243,587,589,592,594,596],{"class":147,"line":588},13,[243,590,591],{"class":248},"        from",[243,593,275],{"class":252},[243,595,249],{"class":248},[243,597,598],{"class":252}," QFileDialog\n",[243,600,602,605,607,610,612,614,617,619,622,624,627],{"class":147,"line":601},14,[243,603,604],{"class":252},"        path, _ ",[243,606,300],{"class":248},[243,608,609],{"class":252}," QFileDialog.getSaveFileName(",[243,611,402],{"class":293},[243,613,351],{"class":252},[243,615,616],{"class":318},"\"Output\"",[243,618,351],{"class":252},[243,620,621],{"class":318},"\"\"",[243,623,351],{"class":252},[243,625,626],{"class":318},"\"GeoPackage (*.gpkg)\"",[243,628,322],{"class":252},[243,630,632,635],{"class":147,"line":631},15,[243,633,634],{"class":248},"        if",[243,636,637],{"class":252}," path:\n",[243,639,641,644],{"class":147,"line":640},16,[243,642,643],{"class":293},"            self",[243,645,646],{"class":252},".outputEdit.setText(path)\n",[14,648,649,651,652,654,655,658,659,662,663,666,667,670,671,674],{},[209,650,409],{}," Connections belong in ",[18,653,387],{}," after ",[18,656,657],{},"setupUi()",", because the widgets do not exist before it. Widget names come straight from Designer's object names, so keeping those meaningful — ",[18,660,661],{},"wardCombo",", not ",[18,664,665],{},"comboBox_3"," — is what makes this code readable a year later. Avoid Qt's auto-connection convention (",[18,668,669],{},"on_wardCombo_currentIndexChanged","): it works, but a renamed widget silently disconnects the handler with no error, whereas an explicit connection raises ",[18,672,673],{},"AttributeError"," immediately.",[198,676,678],{"id":677},"use-the-qgis-custom-widgets","Use the QGIS custom widgets",[14,680,681],{},"Designer, launched with the QGIS environment, offers QGIS's own widgets — layer combo boxes, field selectors, colour buttons, extent pickers. They load at runtime with no extra work and remove a great deal of boilerplate.",[234,683,685],{"className":236,"code":684,"language":238,"meta":239,"style":239},"from qgis.core import QgsMapLayerProxyModel\n\nself.layerCombo.setFilters(QgsMapLayerProxyModel.PolygonLayer)\nself.fieldCombo.setLayer(self.layerCombo.currentLayer())\nself.layerCombo.layerChanged.connect(self.fieldCombo.setLayer)\n",[18,686,687,699,703,710,722],{"__ignoreMap":239},[243,688,689,691,694,696],{"class":147,"line":245},[243,690,259],{"class":248},[243,692,693],{"class":252}," qgis.core ",[243,695,249],{"class":248},[243,697,698],{"class":252}," QgsMapLayerProxyModel\n",[243,700,701],{"class":147,"line":256},[243,702,287],{"emptyLinePlaceholder":286},[243,704,705,707],{"class":147,"line":270},[243,706,402],{"class":293},[243,708,709],{"class":252},".layerCombo.setFilters(QgsMapLayerProxyModel.PolygonLayer)\n",[243,711,712,714,717,719],{"class":147,"line":283},[243,713,402],{"class":293},[243,715,716],{"class":252},".fieldCombo.setLayer(",[243,718,402],{"class":293},[243,720,721],{"class":252},".layerCombo.currentLayer())\n",[243,723,724,726,729,731],{"class":147,"line":290},[243,725,402],{"class":293},[243,727,728],{"class":252},".layerCombo.layerChanged.connect(",[243,730,402],{"class":293},[243,732,733],{"class":252},".fieldCombo.setLayer)\n",[14,735,736,410,738,741,742,745,746,749,750,753],{},[209,737,409],{},[18,739,740],{},"QgsMapLayerComboBox"," populates itself from the project and keeps itself current as layers are added or removed — replacing the manual population loop that every plugin used to carry, and the stale-list bug that came with it. Filtering to polygon layers stops the user selecting something the tool cannot process. Connecting ",[18,743,744],{},"layerChanged"," straight to ",[18,747,748],{},"setLayer"," on the field combo makes the field list follow the chosen layer with no handler of your own. If the widgets appear as plain Qt ones in Designer, launch Designer through the QGIS environment — on Linux ",[18,751,752],{},"qgis --designer",", on Windows the Designer shortcut inside the OSGeo4W menu.",[14,755,756],{},[46,757,760,763,766,769,772,780,786,791,797,800,804,807,811,815,821,827,831,835,839],{"viewBox":758,"role":49,"ariaLabel":759,"xmlns":51},"0 0 760 260","A plugin folder showing the ui file, the python dialog module and the path expression that resolves between them, with a relative path marked as failing",[53,761,762],{},"Why the path must be built from __file__",[57,764,765],{},"A plugin folder contains the dialog python module and the designer file side by side. Building the path from the module's own directory resolves correctly wherever QGIS was launched from. A bare relative path resolves against the process working directory, which is usually the user's home or the QGIS installation folder, and fails.",[61,767],{"x":63,"y":63,"width":64,"height":768,"fill":66},"260",[85,770,771],{"x":87,"y":88,"style":89,"fill":90,"textAnchor":91},"The plugin folder is not the working directory",[61,773],{"x":774,"y":775,"width":776,"height":777,"rx":778,"fill":107,"stroke":108,"style":779},"16","48","316","180","10","stroke-width:2",[85,781,785],{"x":782,"y":783,"style":784,"fill":90,"textAnchor":91},"174","74","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","~\u002F.local\u002Fshare\u002FQGIS\u002F…\u002Fparcel_tools\u002F",[61,787],{"x":788,"y":789,"width":65,"height":88,"rx":790,"fill":140,"stroke":141,"style":109},"40","92","4",[85,792,796],{"x":793,"y":794,"style":795,"fill":83},"54","111","font-size:11px;font-family:sans-serif","parcel_tools_dialog.py",[61,798],{"x":788,"y":799,"width":65,"height":88,"rx":790,"fill":140,"stroke":141,"style":109},"126",[85,801,803],{"x":793,"y":802,"style":795,"fill":83},"145","parcel_tools_dialog.ui",[61,805],{"x":788,"y":806,"width":65,"height":88,"rx":790,"fill":107,"stroke":108,"style":109},"160",[85,808,810],{"x":793,"y":809,"style":795,"fill":83},"179","metadata.txt",[85,812,814],{"x":782,"y":813,"style":114,"fill":108,"textAnchor":91},"212","both files live together",[61,816],{"x":817,"y":818,"width":819,"height":820,"rx":75,"fill":179,"stroke":168,"style":120},"360","70","384","66",[85,822,826],{"x":823,"y":824,"style":825,"fill":168,"textAnchor":91},"552","96","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif","os.path.join(os.path.dirname(__file__), \"…ui\")",[85,828,830],{"x":823,"y":829,"style":114,"fill":83,"textAnchor":91},"118","resolves next to the module, always",[61,832],{"x":817,"y":833,"width":819,"height":820,"rx":75,"fill":119,"stroke":834,"style":120},"152","#b91c1c",[85,836,838],{"x":823,"y":837,"style":825,"fill":834,"textAnchor":91},"178","uic.loadUiType(\"parcel_tools_dialog.ui\")",[85,840,842],{"x":823,"y":841,"style":114,"fill":83,"textAnchor":91},"200","resolves against wherever QGIS was started",[198,844,846],{"id":845},"show-it-without-blocking","Show it without blocking",[234,848,850],{"className":236,"code":849,"language":238,"meta":239,"style":239},"def run(self):\n    if self.dialog is None:\n        self.dialog = ParcelToolsDialog(self.iface, self.iface.mainWindow())\n    self.dialog.show()\n    self.dialog.raise_()\n    self.dialog.activateWindow()\n",[18,851,852,862,882,903,911,918],{"__ignoreMap":239},[243,853,854,857,860],{"class":147,"line":245},[243,855,856],{"class":248},"def",[243,858,859],{"class":341}," run",[243,861,585],{"class":252},[243,863,864,867,870,873,876,879],{"class":147,"line":256},[243,865,866],{"class":248},"    if",[243,868,869],{"class":293}," self",[243,871,872],{"class":252},".dialog ",[243,874,875],{"class":248},"is",[243,877,878],{"class":293}," None",[243,880,881],{"class":252},":\n",[243,883,884,886,888,890,893,895,898,900],{"class":147,"line":270},[243,885,396],{"class":293},[243,887,872],{"class":252},[243,889,300],{"class":248},[243,891,892],{"class":252}," ParcelToolsDialog(",[243,894,402],{"class":293},[243,896,897],{"class":252},".iface, ",[243,899,402],{"class":293},[243,901,902],{"class":252},".iface.mainWindow())\n",[243,904,905,908],{"class":147,"line":283},[243,906,907],{"class":293},"    self",[243,909,910],{"class":252},".dialog.show()\n",[243,912,913,915],{"class":147,"line":290},[243,914,907],{"class":293},[243,916,917],{"class":252},".dialog.raise_()\n",[243,919,920,922],{"class":147,"line":306},[243,921,907],{"class":293},[243,923,924],{"class":252},".dialog.activateWindow()\n",[14,926,927,929,930,933,934,937,938,941],{},[209,928,409],{}," Passing ",[18,931,932],{},"iface.mainWindow()"," as the parent makes the dialog behave properly with the main window — it stays on top of QGIS, minimises with it, and is destroyed with it. Creating it once and reusing it keeps the user's entries between invocations, which is almost always what they expect; recreating it every time silently discards their last settings. ",[18,935,936],{},"show()"," is modeless, so QGIS stays usable behind it; ",[18,939,940],{},"exec_()"," would block everything, which is only right for a question that must be answered before anything else can happen.",[198,943,945],{"id":944},"reload-during-development","Reload during development",[14,947,948,949,951,952,221],{},"Runtime loading pairs well with the Plugin Reloader: edit the ",[18,950,20],{}," in Designer, save, reload the plugin, and the new dialog appears — no build, no restart. The reload mechanics are covered in ",[34,953,955],{"href":954},"\u002Fqgis-plugin-development\u002Fplugin-boilerplate-structure\u002Freload-qgis-plugin-without-restart\u002F","Reload a QGIS Plugin Without Restarting",[14,957,958,959,961,962,964,965,968],{},"One caveat: ",[18,960,413],{}," runs at ",[209,963,249],{}," time, so the form class is captured when the module is first imported. A plugin reload re-imports the module and therefore re-reads the file; simply calling ",[18,966,967],{},"run()"," again does not. If a dialog stubbornly shows an old layout, the module was not re-imported.",[198,970,972],{"id":971},"remember-what-the-user-typed","Remember what the user typed",[14,974,975,976,979],{},"A dialog that forgets its settings between sessions makes a user re-enter the same output folder every morning. ",[18,977,978],{},"QgsSettings"," persists them into the QGIS profile with no file handling of your own.",[234,981,983],{"className":236,"code":982,"language":238,"meta":239,"style":239},"from qgis.core import QgsSettings\n\nGROUP = \"plugins\u002Fparcel_tools\"\n\ndef restore_state(self):\n    settings = QgsSettings()\n    self.outputEdit.setText(settings.value(f\"{GROUP}\u002Foutput_dir\", \"\", type=str))\n    self.bufferSpin.setValue(settings.value(f\"{GROUP}\u002Fbuffer\", 25, type=int))\n    self.overwriteCheck.setChecked(settings.value(f\"{GROUP}\u002Foverwrite\", False, type=bool))\n\ndef save_state(self):\n    settings = QgsSettings()\n    settings.setValue(f\"{GROUP}\u002Foutput_dir\", self.outputEdit.text())\n    settings.setValue(f\"{GROUP}\u002Fbuffer\", self.bufferSpin.value())\n    settings.setValue(f\"{GROUP}\u002Foverwrite\", self.overwriteCheck.isChecked())\n",[18,984,985,996,1000,1011,1015,1024,1034,1071,1103,1135,1139,1148,1156,1176,1195],{"__ignoreMap":239},[243,986,987,989,991,993],{"class":147,"line":245},[243,988,259],{"class":248},[243,990,693],{"class":252},[243,992,249],{"class":248},[243,994,995],{"class":252}," QgsSettings\n",[243,997,998],{"class":147,"line":256},[243,999,287],{"emptyLinePlaceholder":286},[243,1001,1002,1005,1008],{"class":147,"line":270},[243,1003,1004],{"class":293},"GROUP",[243,1006,1007],{"class":248}," =",[243,1009,1010],{"class":318}," \"plugins\u002Fparcel_tools\"\n",[243,1012,1013],{"class":147,"line":283},[243,1014,287],{"emptyLinePlaceholder":286},[243,1016,1017,1019,1022],{"class":147,"line":290},[243,1018,856],{"class":248},[243,1020,1021],{"class":341}," restore_state",[243,1023,585],{"class":252},[243,1025,1026,1029,1031],{"class":147,"line":306},[243,1027,1028],{"class":252},"    settings ",[243,1030,300],{"class":248},[243,1032,1033],{"class":252}," QgsSettings()\n",[243,1035,1036,1038,1041,1044,1047,1050,1053,1055,1057,1059,1063,1065,1068],{"class":147,"line":325},[243,1037,907],{"class":293},[243,1039,1040],{"class":252},".outputEdit.setText(settings.value(",[243,1042,1043],{"class":248},"f",[243,1045,1046],{"class":318},"\"",[243,1048,1049],{"class":293},"{GROUP}",[243,1051,1052],{"class":318},"\u002Foutput_dir\"",[243,1054,351],{"class":252},[243,1056,621],{"class":318},[243,1058,351],{"class":252},[243,1060,1062],{"class":1061},"s9osk","type",[243,1064,300],{"class":248},[243,1066,1067],{"class":293},"str",[243,1069,1070],{"class":252},"))\n",[243,1072,1073,1075,1078,1080,1082,1084,1087,1089,1092,1094,1096,1098,1101],{"class":147,"line":330},[243,1074,907],{"class":293},[243,1076,1077],{"class":252},".bufferSpin.setValue(settings.value(",[243,1079,1043],{"class":248},[243,1081,1046],{"class":318},[243,1083,1049],{"class":293},[243,1085,1086],{"class":318},"\u002Fbuffer\"",[243,1088,351],{"class":252},[243,1090,1091],{"class":293},"25",[243,1093,351],{"class":252},[243,1095,1062],{"class":1061},[243,1097,300],{"class":248},[243,1099,1100],{"class":293},"int",[243,1102,1070],{"class":252},[243,1104,1105,1107,1110,1112,1114,1116,1119,1121,1124,1126,1128,1130,1133],{"class":147,"line":335},[243,1106,907],{"class":293},[243,1108,1109],{"class":252},".overwriteCheck.setChecked(settings.value(",[243,1111,1043],{"class":248},[243,1113,1046],{"class":318},[243,1115,1049],{"class":293},[243,1117,1118],{"class":318},"\u002Foverwrite\"",[243,1120,351],{"class":252},[243,1122,1123],{"class":293},"False",[243,1125,351],{"class":252},[243,1127,1062],{"class":1061},[243,1129,300],{"class":248},[243,1131,1132],{"class":293},"bool",[243,1134,1070],{"class":252},[243,1136,1137],{"class":147,"line":359},[243,1138,287],{"emptyLinePlaceholder":286},[243,1140,1141,1143,1146],{"class":147,"line":378},[243,1142,856],{"class":248},[243,1144,1145],{"class":341}," save_state",[243,1147,585],{"class":252},[243,1149,1150,1152,1154],{"class":147,"line":393},[243,1151,1028],{"class":252},[243,1153,300],{"class":248},[243,1155,1033],{"class":252},[243,1157,1158,1161,1163,1165,1167,1169,1171,1173],{"class":147,"line":588},[243,1159,1160],{"class":252},"    settings.setValue(",[243,1162,1043],{"class":248},[243,1164,1046],{"class":318},[243,1166,1049],{"class":293},[243,1168,1052],{"class":318},[243,1170,351],{"class":252},[243,1172,402],{"class":293},[243,1174,1175],{"class":252},".outputEdit.text())\n",[243,1177,1178,1180,1182,1184,1186,1188,1190,1192],{"class":147,"line":601},[243,1179,1160],{"class":252},[243,1181,1043],{"class":248},[243,1183,1046],{"class":318},[243,1185,1049],{"class":293},[243,1187,1086],{"class":318},[243,1189,351],{"class":252},[243,1191,402],{"class":293},[243,1193,1194],{"class":252},".bufferSpin.value())\n",[243,1196,1197,1199,1201,1203,1205,1207,1209,1211],{"class":147,"line":631},[243,1198,1160],{"class":252},[243,1200,1043],{"class":248},[243,1202,1046],{"class":318},[243,1204,1049],{"class":293},[243,1206,1118],{"class":318},[243,1208,351],{"class":252},[243,1210,402],{"class":293},[243,1212,1213],{"class":252},".overwriteCheck.isChecked())\n",[14,1215,1216,929,1218,1221,1222,1225,1226,1229,1230,1233,1234,1237,1238,654,1241,1243,1244,1247],{},[209,1217,409],{},[18,1219,1220],{},"type="," to ",[18,1223,1224],{},"value()"," is what makes the round trip reliable — settings are stored as strings on some platforms, and a checkbox restored without ",[18,1227,1228],{},"type=bool"," receives the string ",[18,1231,1232],{},"\"false\"",", which Python considers true. Namespacing every key under ",[18,1235,1236],{},"plugins\u002F\u003Cyour plugin>"," keeps your settings out of everyone else's and makes them easy to find, or clear, in the profile's settings file. Call ",[18,1239,1240],{},"restore_state()",[18,1242,657],{}," and ",[18,1245,1246],{},"save_state()"," when the dialog is accepted, not on every keystroke.",[14,1249,1250,1251,1253,1254,1258,1259,1263],{},"What not to persist is equally important: never store a password here, since ",[18,1252,978],{}," is plain text — that is what the authentication database is for, as described in ",[34,1255,1257],{"href":1256},"\u002Fspatial-data-processing-automation\u002Fpostgis-and-database-workflows\u002Fconnect-to-postgis-database-pyqgis\u002F","Connect to a PostGIS Database in PyQGIS",". Storing a layer ",[1260,1261,1262],"em",{},"name"," rather than a layer id is similarly fragile across projects; store the id and fall back gracefully when it no longer resolves.",[14,1265,1266],{},[46,1267,1270,1273,1276,1279,1286,1289,1295,1298,1302,1306,1310,1312,1315,1318,1321,1324,1327,1331,1334,1337,1341,1347,1349,1354],{"viewBox":1268,"role":49,"ariaLabel":1269,"xmlns":51},"0 0 760 232","Dialog values written into the QGIS profile settings on accept and read back on the next open, with a password marked as belonging in the authentication database instead",[53,1271,1272],{},"What belongs in settings, and what does not",[57,1274,1275],{},"The output folder, buffer distance and overwrite flag are written to the QGIS settings under a plugin namespace when the dialog is accepted, and read back the next time it opens. A password is marked as excluded, belonging in the encrypted authentication database rather than in plain-text settings.",[61,1277],{"x":63,"y":63,"width":64,"height":1278,"fill":66},"232",[68,1280,1281],{},[71,1282,1284],{"id":1283,"viewBox":74,"refX":75,"refY":76,"markerWidth":77,"markerHeight":77,"orient":78},"setArrow",[80,1285],{"d":82,"fill":83},[85,1287,1288],{"x":87,"y":88,"style":89,"fill":90,"textAnchor":91},"Persist the preferences, never the secret",[61,1290],{"x":1291,"y":1292,"width":1293,"height":1294,"rx":75,"fill":140,"stroke":141,"style":120},"20","60","220","120",[85,1296,1297],{"x":163,"y":96,"style":784,"fill":141,"textAnchor":91},"the dialog",[85,1299,1301],{"x":163,"y":1300,"style":114,"fill":83,"textAnchor":91},"112","output folder",[85,1303,1305],{"x":163,"y":1304,"style":114,"fill":83,"textAnchor":91},"134","buffer distance",[85,1307,1309],{"x":163,"y":1308,"style":114,"fill":83,"textAnchor":91},"156","overwrite flag",[61,1311],{"x":178,"y":1292,"width":1293,"height":1294,"rx":75,"fill":179,"stroke":168,"style":120},[85,1313,978],{"x":1314,"y":96,"style":784,"fill":168,"textAnchor":91},"410",[85,1316,1317],{"x":1314,"y":1300,"style":114,"fill":83,"textAnchor":91},"plugins\u002Fparcel_tools\u002F…",[85,1319,1320],{"x":1314,"y":1304,"style":114,"fill":83,"textAnchor":91},"in the QGIS profile",[85,1322,1323],{"x":1314,"y":1308,"style":114,"fill":108,"textAnchor":91},"plain text on disk",[61,1325],{"x":1326,"y":1292,"width":806,"height":1294,"rx":75,"fill":119,"stroke":834,"style":120},"580",[85,1328,1330],{"x":1329,"y":96,"style":784,"fill":834,"textAnchor":91},"660","a password",[85,1332,1333],{"x":1329,"y":1300,"style":114,"fill":83,"textAnchor":91},"not here",[85,1335,1336],{"x":1329,"y":102,"style":114,"fill":83,"textAnchor":91},"authentication",[85,1338,1340],{"x":1329,"y":1339,"style":114,"fill":83,"textAnchor":91},"158","database instead",[147,1342],{"x1":1343,"y1":1344,"x2":1345,"y2":1344,"stroke":83,"style":1346},"240","104","294","stroke-width:2;marker-end:url(#setArrow)",[147,1348],{"x1":1345,"y1":102,"x2":1343,"y2":102,"stroke":83,"style":1346},[147,1350],{"x1":1351,"y1":1294,"x2":1352,"y2":1294,"stroke":834,"style":1353},"574","526","stroke-width:2;stroke-dasharray:5 4;marker-end:url(#setArrow)",[85,1355,1356],{"x":87,"y":813,"style":114,"fill":108,"textAnchor":91},"Always pass type= when reading back, or a stored \"false\" comes back as a truthy string",[198,1358,1360],{"id":1359},"qgis-version-compatibility","QGIS version compatibility",[14,1362,1363,1364,1366],{},"The examples target ",[209,1365,211],{}," (Python 3.12).",[1368,1369,1370,1386],"table",{},[1371,1372,1373],"thead",{},[1374,1375,1376,1380,1383],"tr",{},[1377,1378,1379],"th",{},"QGIS version",[1377,1381,1382],{},"Python",[1377,1384,1385],{},"Notes",[1387,1388,1389,1408,1418,1429],"tbody",{},[1374,1390,1391,1395,1398],{},[1392,1393,1394],"td",{},"3.22 LTR",[1392,1396,1397],{},"3.9",[1392,1399,1400,1403,1404,1407],{},[18,1401,1402],{},"qgis.PyQt.uic"," present; ",[18,1405,1406],{},"QgsMapLayerProxyModel"," filters identical.",[1374,1409,1410,1413,1415],{},[1392,1411,1412],{},"3.28 LTR",[1392,1414,1397],{},[1392,1416,1417],{},"Behaviour matches this page.",[1374,1419,1420,1423,1426],{},[1392,1421,1422],{},"3.34 LTR",[1392,1424,1425],{},"3.12",[1392,1427,1428],{},"Baseline for this page.",[1374,1430,1431,1434,1436],{},[1392,1432,1433],{},"3.40 \u002F 3.44",[1392,1435,1425],{},[1392,1437,1438,1439,443,1441,1443],{},"Qt 6 builds appear; importing from ",[18,1440,442],{},[18,1442,446],{}," is what keeps the plugin working on both.",[198,1445,1447],{"id":1446},"troubleshooting","Troubleshooting",[203,1449,1450,1458,1469,1475,1483,1489],{},[206,1451,1452,1455,1456,221],{},[209,1453,1454],{},"\"No such file or directory\" for the .ui."," A relative path was used. Build it from ",[18,1457,420],{},[206,1459,1460,1465,1466,1468],{},[209,1461,1462,1464],{},[18,1463,673],{}," on a widget name."," The object name in Designer differs from the attribute used, or ",[18,1467,657],{}," was never called.",[206,1470,1471,1474],{},[209,1472,1473],{},"QGIS custom widgets fail to load."," Designer was not launched with the QGIS environment, so the widget plugin was unavailable and the promoted class is unknown at load time.",[206,1476,1477,1480,1481,221],{},[209,1478,1479],{},"The dialog appears behind the main window."," No parent was passed. Use ",[18,1482,932],{},[206,1484,1485,1488],{},[209,1486,1487],{},"Edits to the .ui do not show."," The module was not re-imported. Reload the plugin rather than reopening the dialog.",[206,1490,1491,1494,1495,1498],{},[209,1492,1493],{},"Icons are missing."," A compiled ",[18,1496,1497],{},"resources.py"," is stale or missing. Regenerate it, or reference icons by path from the plugin directory.",[198,1500,1502],{"id":1501},"conclusion","Conclusion",[14,1504,1505,1508,1509,1511,1512,1514],{},[18,1506,1507],{},"uic.loadUiType()"," with a path built from ",[18,1510,312],{}," keeps the designer file as the single source of truth and removes a build step that is easy to forget. Connect signals explicitly after ",[18,1513,657],{},", use the QGIS custom widgets rather than repopulating combo boxes by hand, and parent the dialog to the main window so it behaves like the rest of QGIS.",[198,1516,1518],{"id":1517},"frequently-asked-questions","Frequently Asked Questions",[14,1520,1521,1524],{},[209,1522,1523],{},"Is runtime loading slower than a compiled form?","\nMarginally — parsing the XML takes a few milliseconds, once per import. It is not measurable next to plugin start-up.",[14,1526,1527,1530],{},[209,1528,1529],{},"When should I compile instead?","\nWhen you want the form under static analysis, when a packaging step strips non-Python files, or when a very large form is created repeatedly in a loop.",[14,1532,1533,1536,1537,1540,1541,1543],{},[209,1534,1535],{},"Can I load a .ui into an existing widget instead of subclassing?","\nYes — ",[18,1538,1539],{},"uic.loadUi(path, self)"," populates an already-constructed widget. ",[18,1542,41],{}," is preferred because the resulting class is explicit and inspectable.",[14,1545,1546,1549,1550,1553,1554,1557],{},[209,1547,1548],{},"How do I add a QGIS widget to a form Designer does not offer?","\nPromote a base widget: place a ",[18,1551,1552],{},"QWidget"," or ",[18,1555,1556],{},"QComboBox",", right-click, Promote to, and give the QGIS class name and header. Runtime loading resolves the promotion.",[14,1559,1560,1563,1564,1566],{},[209,1561,1562],{},"Can two dialogs share one .ui file?","\nYes — call ",[18,1565,413],{}," once at module level and let two classes inherit the same form class. It is occasionally useful for a dialog and a dock widget that present the same controls, though usually a shared widget promoted into both is cleaner.",[14,1568,1569,1572,1573,1576,1577,1579],{},[209,1570,1571],{},"Where should the dialog live in the plugin?","\nBeside the module that loads it, in the plugin's top folder or a ",[18,1574,1575],{},"ui\u002F"," subfolder. Whichever you pick, build the path from ",[18,1578,312],{}," so the choice does not matter to the code.",[198,1581,1583],{"id":1582},"related","Related",[203,1585,1586,1591,1597,1601,1607],{},[206,1587,1588,1590],{},[34,1589,37],{"href":36}," — the guide this recipe belongs to",[206,1592,1593],{},[34,1594,1596],{"href":1595},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis\u002F","Add a Custom Dock Widget in PyQGIS",[206,1598,1599],{},[34,1600,955],{"href":954},[206,1602,1603],{},[34,1604,1606],{"href":1605},"\u002Fqgis-plugin-development\u002Fbackground-tasks-and-plugin-performance\u002Fshow-plugin-progress-and-cancellation-pyqgis\u002F","Show Progress and Support Cancellation in a QGIS Plugin",[206,1608,1609],{},[34,1610,228],{"href":227},[1612,1613,1614],"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 .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);}html pre.shiki code .s9osk, html code.shiki .s9osk{--shiki-default:#FFAB70}",{"title":239,"searchDepth":256,"depth":256,"links":1616},[1617,1618,1619,1620,1621,1622,1623,1624,1625,1626,1627,1628],{"id":200,"depth":256,"text":201},{"id":231,"depth":256,"text":232},{"id":450,"depth":256,"text":451},{"id":677,"depth":256,"text":678},{"id":845,"depth":256,"text":846},{"id":944,"depth":256,"text":945},{"id":971,"depth":256,"text":972},{"id":1359,"depth":256,"text":1360},{"id":1446,"depth":256,"text":1447},{"id":1501,"depth":256,"text":1502},{"id":1517,"depth":256,"text":1518},{"id":1582,"depth":256,"text":1583},"Load Qt Designer dialogs with uic.loadUiType instead of compiling them — resolve paths correctly, wire signals, embed QGIS custom widgets, and avoid the plugin-relative path bug.","md",{"slug":1632,"type":1633,"breadcrumb":1634,"datePublished":1635,"dateModified":1635},"load-ui-file-at-runtime-pyqgis","article","Load .ui at Runtime","2026-08-10","\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fload-ui-file-at-runtime-pyqgis",{"title":5,"description":1629},"qgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fload-ui-file-at-runtime-pyqgis\u002Findex","2jDxlUjZNkf1e2bfSpDTcVn2Jse8J-x9oL6fxw--BWQ",1786401337554]