[{"data":1,"prerenderedAt":1236},["ShallowReactive",2],{"doc:\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fuse-qgis-custom-widgets-in-qt-designer":3},{"id":4,"title":5,"body":6,"description":1226,"extension":1227,"meta":1228,"navigation":394,"path":1232,"seo":1233,"stem":1234,"__hash__":1235},"docs\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fuse-qgis-custom-widgets-in-qt-designer\u002Findex.md","Use QGIS Custom Widgets in Qt Designer",{"type":7,"value":8,"toc":1213},"minimark",[9,13,17,31,164,169,191,195,272,275,279,282,314,335,339,342,530,555,559,562,591,599,696,700,833,853,946,950,1015,1021,1025,1080,1084,1090,1094,1100,1106,1116,1133,1143,1149,1162,1176,1180,1209],[10,11,5],"h1",{"id":12},"use-qgis-custom-widgets-in-qt-designer",[14,15,16],"p",{},"Every plugin dialog needs the same handful of controls: pick a layer, pick a field from that layer, choose an output file, choose a colour, choose a coordinate system. Writing them from plain Qt widgets means populating combo boxes from the project, keeping them in sync when layers are added, filtering by geometry type, and validating paths — perhaps two hundred lines that QGIS has already written and that behave exactly like the rest of the application.",[14,18,19,20,25,26,30],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002F","Qt Designer for GIS Interfaces",". It covers the widgets worth knowing, how to place them in a ",[27,28,29],"code",{},".ui"," file through promotion, how to use them directly in code, and how to wire the dependent ones together.",[14,32,33],{},[34,35,40,44,48,55,64,74,80,89,96,99,103,106,110,113,117,122,127,132,136,140,144,146,149,151,154,156,159,161],"svg",{"viewBox":36,"role":37,"ariaLabel":38,"xmlns":39},"0 0 760 286","img","Comparison of a dialog built from plain Qt widgets requiring manual population and synchronisation against the same dialog built from QGIS widgets that handle it automatically","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[41,42,43],"title",{},"The same dialog, built two ways",[45,46,47],"desc",{},"Built from plain Qt widgets, a layer combo box must be filled from the project, filtered by geometry type, and refreshed whenever a layer is added or removed, and the field combo must be repopulated whenever the layer changes. Built from QGIS widgets, all of that behaviour is already present and the plugin code only reads the selected values.",[49,50],"rect",{"x":51,"y":51,"width":52,"height":53,"fill":54},"0","760","286","#f6f3ea",[56,57,63],"text",{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"380","28","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Two hundred lines you do not have to write or maintain",[49,65],{"x":66,"y":67,"width":68,"height":69,"rx":70,"fill":71,"stroke":72,"style":73},"20","48","348","212","10","#fdf2e2","#b45309","stroke-width:2.5",[56,75,79],{"x":76,"y":77,"style":78,"fill":72,"textAnchor":62},"194","74","text-anchor:middle;font-size:12px;font-weight:bold;font-family:sans-serif","plain QComboBox",[49,81],{"x":82,"y":83,"width":84,"height":59,"rx":85,"fill":86,"stroke":87,"style":88},"44","90","300","5","#fffdf7","#59645f","stroke-width:1.2",[56,90,95],{"x":91,"y":92,"style":93,"fill":94},"60","109","font-size:11px;font-family:sans-serif","#2f3b35","fill it from the project",[49,97],{"x":82,"y":98,"width":84,"height":59,"rx":85,"fill":86,"stroke":87,"style":88},"124",[56,100,102],{"x":91,"y":101,"style":93,"fill":94},"143","filter to polygon layers",[49,104],{"x":82,"y":105,"width":84,"height":59,"rx":85,"fill":86,"stroke":87,"style":88},"158",[56,107,109],{"x":91,"y":108,"style":93,"fill":94},"177","refresh on layersAdded and removed",[49,111],{"x":82,"y":112,"width":84,"height":59,"rx":85,"fill":86,"stroke":87,"style":88},"192",[56,114,116],{"x":91,"y":115,"style":93,"fill":94},"211","repopulate fields on change",[49,118],{"x":82,"y":119,"width":84,"height":120,"rx":85,"fill":54,"stroke":72,"style":121},"226","26","stroke-width:1.5",[56,123,126],{"x":91,"y":124,"style":125,"fill":72},"244","font-size:11px;font-weight:bold;font-family:sans-serif","and keep it all working",[49,128],{"x":129,"y":67,"width":68,"height":69,"rx":70,"fill":130,"stroke":131,"style":73},"392","#edf8e9","#15803d",[56,133,135],{"x":134,"y":77,"style":78,"fill":131,"textAnchor":62},"566","QgsMapLayerComboBox",[49,137],{"x":138,"y":83,"width":84,"height":59,"rx":85,"fill":86,"stroke":139,"style":121},"416","#0f766e",[56,141,143],{"x":142,"y":92,"style":93,"fill":94},"432","already knows the project",[49,145],{"x":138,"y":98,"width":84,"height":59,"rx":85,"fill":86,"stroke":139,"style":121},[56,147,148],{"x":142,"y":101,"style":93,"fill":94},"setFilters — one line",[49,150],{"x":138,"y":105,"width":84,"height":59,"rx":85,"fill":86,"stroke":139,"style":121},[56,152,153],{"x":142,"y":108,"style":93,"fill":94},"stays in sync automatically",[49,155],{"x":138,"y":112,"width":84,"height":59,"rx":85,"fill":86,"stroke":139,"style":121},[56,157,158],{"x":142,"y":115,"style":93,"fill":94},"field box follows it by signal",[49,160],{"x":138,"y":119,"width":84,"height":120,"rx":85,"fill":86,"stroke":131,"style":121},[56,162,163],{"x":142,"y":124,"style":125,"fill":131},"and looks like QGIS",[165,166,168],"h2",{"id":167},"prerequisites","Prerequisites",[170,171,172,180,183],"ul",{},[173,174,175,179],"li",{},[176,177,178],"strong",{},"QGIS 3.34 LTR"," (bundled Python 3.12) or newer.",[173,181,182],{},"Qt Designer, from your QGIS installation or your package manager.",[173,184,185,186,190],{},"A plugin dialog to work on — see ",[21,187,189],{"href":188},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fload-ui-file-at-runtime-pyqgis\u002F","Load a .ui File at Runtime in PyQGIS",".",[165,192,194],{"id":193},"the-widgets-worth-knowing","The widgets worth knowing",[170,196,197,204,212,220,228,236,244,252,264],{},[173,198,199,203],{},[176,200,201],{},[27,202,135],{}," — every layer in the project, filterable by type and geometry, kept in sync automatically.",[173,205,206,211],{},[176,207,208],{},[27,209,210],{},"QgsFieldComboBox"," — the fields of a layer, filterable to numeric, string or date fields.",[173,213,214,219],{},[176,215,216],{},[27,217,218],{},"QgsFieldExpressionWidget"," — a field picker that also accepts an expression, with the expression builder attached.",[173,221,222,227],{},[176,223,224],{},[27,225,226],{},"QgsFileWidget"," — a path field with a browse button, in file, save, directory or multi-file mode.",[173,229,230,235],{},[176,231,232],{},[27,233,234],{},"QgsColorButton"," — a colour swatch opening QGIS's colour dialog, with the project colour scheme.",[173,237,238,243],{},[176,239,240],{},[27,241,242],{},"QgsExtentGroupBox"," — an extent, from a layer, the canvas, or typed coordinates.",[173,245,246,251],{},[176,247,248],{},[27,249,250],{},"QgsProjectionSelectionWidget"," — a coordinate system picker with recent and project options.",[173,253,254,263],{},[176,255,256,259,260],{},[27,257,258],{},"QgsSpinBox"," and ",[27,261,262],{},"QgsDoubleSpinBox"," — spin boxes that support a clearable \"not set\" state.",[173,265,266,271],{},[176,267,268],{},[27,269,270],{},"QgsCollapsibleGroupBox"," — a group box that remembers whether the user collapsed it.",[14,273,274],{},"Each behaves the way its counterpart in QGIS's own dialogs behaves, which means users already know how to use them.",[165,276,278],{"id":277},"promote-a-widget-in-qt-designer","Promote a widget in Qt Designer",[14,280,281],{},"QGIS's widgets are not in Designer's palette unless the custom widget plugin is installed, and the portable way to place them is promotion:",[283,284,285,296,302,311],"ol",{},[173,286,287,288,291,292,295],{},"Drop the closest standard widget onto the form — a ",[27,289,290],{},"QComboBox"," for a layer picker, a ",[27,293,294],{},"QWidget"," for anything without an obvious base.",[173,297,298,299,190],{},"Right-click it and choose ",[176,300,301],{},"Promote to…",[173,303,304,305,307,308,190],{},"Set the promoted class name to ",[27,306,135],{}," and the header file to ",[27,309,310],{},"qgis.gui",[173,312,313],{},"Press Add, then Promote.",[14,315,316,319,320,322,323,325,326,328,329,331,332,334],{},[176,317,318],{},"Breakdown:"," Promotion records \"treat this widget as that class\" in the ",[27,321,29],{}," file, and the header value ",[27,324,310],{}," is what tells the Python loader which module to import — it is a Python module path here rather than a C++ header, which is the detail that trips people up. Once promoted, the widget is a plain ",[27,327,290],{}," in Designer's preview and the real QGIS widget at run time. The base class matters: promote a ",[27,330,290],{}," for the combo widgets and a ",[27,333,294],{}," for the file, colour, extent and projection widgets, or the loader raises about incompatible types.",[165,336,338],{"id":337},"or-build-them-in-code","Or build them in code",[14,340,341],{},"For a dialog assembled in Python, skip Designer entirely:",[343,344,349],"pre",{"className":345,"code":346,"language":347,"meta":348,"style":348},"language-python shiki shiki-themes github-dark","from qgis.core import QgsMapLayerProxyModel, QgsFieldProxyModel\nfrom qgis.gui import (QgsMapLayerComboBox, QgsFieldComboBox,\n                      QgsFileWidget, QgsProjectionSelectionWidget)\n\nself.layer_box = QgsMapLayerComboBox()\nself.layer_box.setFilters(QgsMapLayerProxyModel.PolygonLayer)\nself.layer_box.setAllowEmptyLayer(True)\n\nself.field_box = QgsFieldComboBox()\nself.field_box.setFilters(QgsFieldProxyModel.Numeric)\n\nself.output = QgsFileWidget()\nself.output.setStorageMode(QgsFileWidget.SaveFile)\nself.output.setFilter(\"GeoPackage (*.gpkg)\")\n\nself.crs_box = QgsProjectionSelectionWidget()\nself.crs_box.setOptionVisible(QgsProjectionSelectionWidget.CurrentCrs, True)\n","python","",[27,350,351,370,383,389,396,412,420,434,439,452,460,465,478,486,500,505,518],{"__ignoreMap":348},[352,353,356,360,364,367],"span",{"class":354,"line":355},"line",1,[352,357,359],{"class":358},"snl16","from",[352,361,363],{"class":362},"s95oV"," qgis.core ",[352,365,366],{"class":358},"import",[352,368,369],{"class":362}," QgsMapLayerProxyModel, QgsFieldProxyModel\n",[352,371,373,375,378,380],{"class":354,"line":372},2,[352,374,359],{"class":358},[352,376,377],{"class":362}," qgis.gui ",[352,379,366],{"class":358},[352,381,382],{"class":362}," (QgsMapLayerComboBox, QgsFieldComboBox,\n",[352,384,386],{"class":354,"line":385},3,[352,387,388],{"class":362},"                      QgsFileWidget, QgsProjectionSelectionWidget)\n",[352,390,392],{"class":354,"line":391},4,[352,393,395],{"emptyLinePlaceholder":394},true,"\n",[352,397,399,403,406,409],{"class":354,"line":398},5,[352,400,402],{"class":401},"sDLfK","self",[352,404,405],{"class":362},".layer_box ",[352,407,408],{"class":358},"=",[352,410,411],{"class":362}," QgsMapLayerComboBox()\n",[352,413,415,417],{"class":354,"line":414},6,[352,416,402],{"class":401},[352,418,419],{"class":362},".layer_box.setFilters(QgsMapLayerProxyModel.PolygonLayer)\n",[352,421,423,425,428,431],{"class":354,"line":422},7,[352,424,402],{"class":401},[352,426,427],{"class":362},".layer_box.setAllowEmptyLayer(",[352,429,430],{"class":401},"True",[352,432,433],{"class":362},")\n",[352,435,437],{"class":354,"line":436},8,[352,438,395],{"emptyLinePlaceholder":394},[352,440,442,444,447,449],{"class":354,"line":441},9,[352,443,402],{"class":401},[352,445,446],{"class":362},".field_box ",[352,448,408],{"class":358},[352,450,451],{"class":362}," QgsFieldComboBox()\n",[352,453,455,457],{"class":354,"line":454},10,[352,456,402],{"class":401},[352,458,459],{"class":362},".field_box.setFilters(QgsFieldProxyModel.Numeric)\n",[352,461,463],{"class":354,"line":462},11,[352,464,395],{"emptyLinePlaceholder":394},[352,466,468,470,473,475],{"class":354,"line":467},12,[352,469,402],{"class":401},[352,471,472],{"class":362},".output ",[352,474,408],{"class":358},[352,476,477],{"class":362}," QgsFileWidget()\n",[352,479,481,483],{"class":354,"line":480},13,[352,482,402],{"class":401},[352,484,485],{"class":362},".output.setStorageMode(QgsFileWidget.SaveFile)\n",[352,487,489,491,494,498],{"class":354,"line":488},14,[352,490,402],{"class":401},[352,492,493],{"class":362},".output.setFilter(",[352,495,497],{"class":496},"sU2Wk","\"GeoPackage (*.gpkg)\"",[352,499,433],{"class":362},[352,501,503],{"class":354,"line":502},15,[352,504,395],{"emptyLinePlaceholder":394},[352,506,508,510,513,515],{"class":354,"line":507},16,[352,509,402],{"class":401},[352,511,512],{"class":362},".crs_box ",[352,514,408],{"class":358},[352,516,517],{"class":362}," QgsProjectionSelectionWidget()\n",[352,519,521,523,526,528],{"class":354,"line":520},17,[352,522,402],{"class":401},[352,524,525],{"class":362},".crs_box.setOptionVisible(QgsProjectionSelectionWidget.CurrentCrs, ",[352,527,430],{"class":401},[352,529,433],{"class":362},[14,531,532,534,535,538,539,542,543,546,547,550,551,554],{},[176,533,318],{}," ",[27,536,537],{},"setFilters()"," on the layer box takes proxy-model flags that can be combined — ",[27,540,541],{},"PolygonLayer | LineLayer"," for a dialog accepting either — and it filters live, so a raster added later never appears. ",[27,544,545],{},"setAllowEmptyLayer(True)"," adds a blank entry, which is the correct way to express an optional input rather than a separate checkbox. The field box's filters are a different enumeration with the same shape, which is easy to mix up: ",[27,548,549],{},"QgsFieldProxyModel"," for fields, ",[27,552,553],{},"QgsMapLayerProxyModel"," for layers. The projection widget's options control which shortcuts appear — project CRS, layer CRS, recently used — and showing the relevant ones saves the user a trip through the full selector.",[165,556,558],{"id":557},"wire-the-dependent-ones-together","Wire the dependent ones together",[14,560,561],{},"A field picker is only useful when it follows the layer picker.",[343,563,565],{"className":345,"code":564,"language":347,"meta":348,"style":348},"self.layer_box.layerChanged.connect(self.field_box.setLayer)\nself.field_box.setLayer(self.layer_box.currentLayer())\n",[27,566,567,579],{"__ignoreMap":348},[352,568,569,571,574,576],{"class":354,"line":355},[352,570,402],{"class":401},[352,572,573],{"class":362},".layer_box.layerChanged.connect(",[352,575,402],{"class":401},[352,577,578],{"class":362},".field_box.setLayer)\n",[352,580,581,583,586,588],{"class":354,"line":372},[352,582,402],{"class":401},[352,584,585],{"class":362},".field_box.setLayer(",[352,587,402],{"class":401},[352,589,590],{"class":362},".layer_box.currentLayer())\n",[14,592,593,595,596,598],{},[176,594,318],{}," Two lines: connect the signal so future changes propagate, and call it once so the initial state is correct — omitting the second is why a dialog opens with an empty field list until the user touches the layer box. ",[27,597,218],{}," uses the same pattern with the same method name, so swapping one for the other is a one-word change. For a dialog with several dependent widgets, connect them all in one place immediately after construction, which keeps the wiring visible rather than scattered through the class.",[14,600,601],{},[34,602,605,608,611,614,629,632,639,642,647,654,659,663,665,667,669,672,675,678,685,687,691],{"viewBox":603,"role":37,"ariaLabel":604,"xmlns":39},"0 0 760 258","Diagram of signal wiring between a layer combo box and the widgets that depend on it, showing the field picker, expression widget and extent box all following the selected layer",[41,606,607],{},"One signal keeps the dialog consistent",[45,609,610],{},"The layer combo box emits a layer changed signal. The field combo box, the field expression widget and the extent group box each connect to it so their contents follow the selected layer. Each connection must also be invoked once at construction so the dialog opens in a consistent state rather than with empty dependent widgets.",[49,612],{"x":51,"y":51,"width":52,"height":613,"fill":54},"258",[615,616,617],"defs",{},[618,619,625],"marker",{"id":620,"viewBox":621,"refX":622,"refY":85,"markerWidth":623,"markerHeight":623,"orient":624},"wireArrow","0 0 10 10","8","7","auto-start-reverse",[626,627],"path",{"d":628,"fill":139},"M0 0 L10 5 L0 10 z",[56,630,631],{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"Connect it, then call it once",[49,633],{"x":634,"y":635,"width":636,"height":637,"rx":70,"fill":638,"stroke":139,"style":73},"272","52","216","64","#eef7f4",[56,640,135],{"x":58,"y":641,"style":78,"fill":139,"textAnchor":62},"78",[56,643,646],{"x":58,"y":644,"style":645,"fill":94,"textAnchor":62},"100","text-anchor:middle;font-size:11px;font-family:sans-serif","emits layerChanged",[49,648],{"x":649,"y":650,"width":636,"height":651,"rx":622,"fill":86,"stroke":652,"style":653},"24","160","72","#2563eb","stroke-width:2",[56,655,210],{"x":656,"y":657,"style":658,"fill":652,"textAnchor":62},"132","188","text-anchor:middle;font-size:11px;font-weight:bold;font-family:sans-serif",[56,660,662],{"x":656,"y":661,"style":645,"fill":94,"textAnchor":62},"210","setLayer",[49,664],{"x":634,"y":650,"width":636,"height":651,"rx":622,"fill":86,"stroke":652,"style":653},[56,666,218],{"x":58,"y":657,"style":658,"fill":652,"textAnchor":62},[56,668,662],{"x":58,"y":661,"style":645,"fill":94,"textAnchor":62},[49,670],{"x":671,"y":650,"width":636,"height":651,"rx":622,"fill":86,"stroke":652,"style":653},"520",[56,673,242],{"x":674,"y":657,"style":658,"fill":652,"textAnchor":62},"628",[56,676,677],{"x":674,"y":661,"style":645,"fill":94,"textAnchor":62},"setOutputExtentFromLayer",[354,679],{"x1":680,"y1":681,"x2":682,"y2":683,"stroke":139,"style":684},"330","116","150","154","stroke-width:2;marker-end:url(#wireArrow)",[354,686],{"x1":58,"y1":681,"x2":58,"y2":683,"stroke":139,"style":684},[354,688],{"x1":689,"y1":681,"x2":690,"y2":683,"stroke":139,"style":684},"430","610",[56,692,695],{"x":58,"y":693,"style":694,"fill":87,"textAnchor":62},"146","text-anchor:middle;font-size:10px;font-family:sans-serif","connect once, and prime the initial state",[165,697,699],{"id":698},"read-the-values-back","Read the values back",[343,701,703],{"className":345,"code":702,"language":347,"meta":348,"style":348},"layer = self.layer_box.currentLayer()          # a QgsMapLayer or None\nfield = self.field_box.currentField()          # a field name, or an empty string\npath = self.output.filePath()                  # a path, or an empty string\ncrs = self.crs_box.crs()                       # a QgsCoordinateReferenceSystem\nexpression = self.expression_widget.currentField()   # name or expression text\n\nif layer is None:\n    self.iface.messageBar().pushWarning(\"Parcel Tools\", self.tr(\"Select a layer\"))\n    return\n",[27,704,705,722,737,752,767,782,786,803,828],{"__ignoreMap":348},[352,706,707,710,712,715,718],{"class":354,"line":355},[352,708,709],{"class":362},"layer ",[352,711,408],{"class":358},[352,713,714],{"class":401}," self",[352,716,717],{"class":362},".layer_box.currentLayer()          ",[352,719,721],{"class":720},"sjoCn","# a QgsMapLayer or None\n",[352,723,724,727,729,731,734],{"class":354,"line":372},[352,725,726],{"class":362},"field ",[352,728,408],{"class":358},[352,730,714],{"class":401},[352,732,733],{"class":362},".field_box.currentField()          ",[352,735,736],{"class":720},"# a field name, or an empty string\n",[352,738,739,742,744,746,749],{"class":354,"line":385},[352,740,741],{"class":362},"path ",[352,743,408],{"class":358},[352,745,714],{"class":401},[352,747,748],{"class":362},".output.filePath()                  ",[352,750,751],{"class":720},"# a path, or an empty string\n",[352,753,754,757,759,761,764],{"class":354,"line":391},[352,755,756],{"class":362},"crs ",[352,758,408],{"class":358},[352,760,714],{"class":401},[352,762,763],{"class":362},".crs_box.crs()                       ",[352,765,766],{"class":720},"# a QgsCoordinateReferenceSystem\n",[352,768,769,772,774,776,779],{"class":354,"line":398},[352,770,771],{"class":362},"expression ",[352,773,408],{"class":358},[352,775,714],{"class":401},[352,777,778],{"class":362},".expression_widget.currentField()   ",[352,780,781],{"class":720},"# name or expression text\n",[352,783,784],{"class":354,"line":414},[352,785,395],{"emptyLinePlaceholder":394},[352,787,788,791,794,797,800],{"class":354,"line":422},[352,789,790],{"class":358},"if",[352,792,793],{"class":362}," layer ",[352,795,796],{"class":358},"is",[352,798,799],{"class":401}," None",[352,801,802],{"class":362},":\n",[352,804,805,808,811,814,817,819,822,825],{"class":354,"line":436},[352,806,807],{"class":401},"    self",[352,809,810],{"class":362},".iface.messageBar().pushWarning(",[352,812,813],{"class":496},"\"Parcel Tools\"",[352,815,816],{"class":362},", ",[352,818,402],{"class":401},[352,820,821],{"class":362},".tr(",[352,823,824],{"class":496},"\"Select a layer\"",[352,826,827],{"class":362},"))\n",[352,829,830],{"class":354,"line":441},[352,831,832],{"class":358},"    return\n",[14,834,835,837,838,841,842,845,846,848,849,852],{},[176,836,318],{}," Each widget returns a real object rather than a string you have to look up — ",[27,839,840],{},"currentLayer()"," gives the layer itself, which removes the lookup-by-name step and with it the ambiguity when two layers share a name. Empty values are empty strings and ",[27,843,844],{},"None",", never exceptions, so validation is a plain check. ",[27,847,218],{}," returns either a field name or an expression, and its companion ",[27,850,851],{},"isValidExpression()"," tells you which — worth checking before passing the value somewhere that expects one or the other.",[14,854,855],{},[34,856,859,862,865,868,875,878,882,887,893,896,899,903,906,910,912,914,917,920,924,928,931,934,937,942],{"viewBox":857,"role":37,"ariaLabel":858,"xmlns":39},"0 0 760 246","Diagram of promotion in Qt Designer, showing a standard base widget on the form being promoted to a QGIS class with a Python module as its header",[41,860,861],{},"What promotion records in the .ui file",[45,863,864],{},"A standard widget dropped on the form is promoted by recording two values: the QGIS class it should become, and the module to import it from. Designer still shows the plain base widget in its preview, while the loader constructs the real QGIS widget at run time. The base widget must be compatible: a combo box for combo widgets and a plain widget for the rest.",[49,866],{"x":51,"y":51,"width":52,"height":867,"fill":54},"246",[615,869,870],{},[618,871,873],{"id":872,"viewBox":621,"refX":622,"refY":85,"markerWidth":623,"markerHeight":623,"orient":624},"promArrow",[626,874],{"d":628,"fill":139},[56,876,877],{"x":58,"y":59,"style":60,"fill":61,"textAnchor":62},"Two values, recorded once, resolved at run time",[49,879],{"x":66,"y":91,"width":880,"height":881,"rx":70,"fill":86,"stroke":87,"style":653},"200","140",[56,883,886],{"x":884,"y":885,"style":658,"fill":61,"textAnchor":62},"120","86","in Designer",[49,888],{"x":889,"y":890,"width":891,"height":892,"rx":85,"fill":54,"stroke":87,"style":121},"42","104","156","30",[56,894,895],{"x":884,"y":98,"style":645,"fill":94,"textAnchor":62},"a plain QComboBox",[56,897,898],{"x":884,"y":650,"style":694,"fill":87,"textAnchor":62},"the preview never changes",[49,900],{"x":901,"y":91,"width":902,"height":881,"rx":70,"fill":638,"stroke":139,"style":73},"268","224",[56,904,905],{"x":58,"y":885,"style":658,"fill":139,"textAnchor":62},"what promotion records",[49,907],{"x":908,"y":890,"width":909,"height":892,"rx":85,"fill":86,"stroke":87,"style":121},"290","180",[56,911,135],{"x":58,"y":98,"style":645,"fill":94,"textAnchor":62},[49,913],{"x":908,"y":881,"width":909,"height":892,"rx":85,"fill":86,"stroke":87,"style":121},[56,915,916],{"x":58,"y":650,"style":645,"fill":94,"textAnchor":62},"header: qgis.gui",[56,918,919],{"x":58,"y":657,"style":694,"fill":139,"textAnchor":62},"a Python module, not a C++ header",[49,921],{"x":922,"y":91,"width":880,"height":881,"rx":70,"fill":923,"stroke":652,"style":73},"540","#eff3ff",[56,925,927],{"x":926,"y":885,"style":658,"fill":652,"textAnchor":62},"640","at run time",[49,929],{"x":930,"y":890,"width":891,"height":892,"rx":85,"fill":86,"stroke":652,"style":653},"562",[56,932,933],{"x":926,"y":98,"style":645,"fill":94,"textAnchor":62},"the real QGIS widget",[56,935,936],{"x":926,"y":650,"style":694,"fill":87,"textAnchor":62},"filters, syncs, knows the project",[354,938],{"x1":939,"y1":884,"x2":940,"y2":884,"stroke":139,"style":941},"220","262","stroke-width:2;marker-end:url(#promArrow)",[354,943],{"x1":944,"y1":884,"x2":945,"y2":884,"stroke":139,"style":941},"492","534",[165,947,949],{"id":948},"qgis-version-compatibility","QGIS version compatibility",[951,952,953,969],"table",{},[954,955,956],"thead",{},[957,958,959,963,966],"tr",{},[960,961,962],"th",{},"QGIS version",[960,964,965],{},"Python",[960,967,968],{},"Notes",[970,971,972,984,994,1005],"tbody",{},[957,973,974,978,981],{},[975,976,977],"td",{},"3.22 LTR",[975,979,980],{},"3.9",[975,982,983],{},"All widgets listed are present with these methods.",[957,985,986,989,991],{},[975,987,988],{},"3.28 LTR",[975,990,980],{},[975,992,993],{},"Identical.",[957,995,996,999,1002],{},[975,997,998],{},"3.34 LTR",[975,1000,1001],{},"3.12",[975,1003,1004],{},"Baseline for this page.",[957,1006,1007,1010,1012],{},[975,1008,1009],{},"3.40 \u002F 3.44",[975,1011,1001],{},[975,1013,1014],{},"Adds further widgets, including point cloud pickers; the filter enumerations gained scoped names alongside the legacy ones.",[14,1016,1017,1018,1020],{},"Promotion in ",[27,1019,29],{}," files is stable across all of these, since it records only a class name and a module path.",[165,1022,1024],{"id":1023},"troubleshooting","Troubleshooting",[170,1026,1027,1042,1050,1059,1068,1074],{},[173,1028,1029,1035,1036,1038,1039,1041],{},[176,1030,1031,1034],{},[27,1032,1033],{},"TypeError"," when loading the .ui file."," The promoted class does not match the base widget — promote a ",[27,1037,290],{}," for combo widgets and a ",[27,1040,294],{}," for the rest.",[173,1043,1044,1047,1048,190],{},[176,1045,1046],{},"The widget appears as a plain combo box at run time."," The header was left as a C++ header instead of ",[27,1049,310],{},[173,1051,1052,1055,1056,1058],{},[176,1053,1054],{},"The layer list is empty."," Filters exclude everything, or the dialog was built before the project loaded. Check ",[27,1057,537],{}," first.",[173,1060,1061,534,1064,1067],{},[176,1062,1063],{},"The field list stays empty.",[27,1065,1066],{},"setLayer()"," was connected but never called once at construction.",[173,1069,1070,1073],{},[176,1071,1072],{},"A colour button shows the wrong colour."," It was set before the dialog was shown; set values after construction, not during it.",[173,1075,1076,1079],{},[176,1077,1078],{},"The file widget accepts a folder for a file."," Storage mode was left at its default. Set it explicitly.",[165,1081,1083],{"id":1082},"conclusion","Conclusion",[14,1085,1086,1087,1089],{},"Use the widgets QGIS ships: they know about the project, filter themselves, stay in sync, and look like the rest of the application. Place them in Designer by promoting a compatible base widget with ",[27,1088,310],{}," as the header, or construct them directly in Python. Wire the dependent ones with one signal connection plus one priming call, and read values back as real objects rather than names.",[165,1091,1093],{"id":1092},"frequently-asked-questions","Frequently Asked Questions",[14,1095,1096,1099],{},[176,1097,1098],{},"Why are the QGIS widgets missing from Designer's palette?","\nThe custom widgets plugin is not installed or not found. Promotion works without it and is more portable, which is why it is the recommended route.",[14,1101,1102,1105],{},[176,1103,1104],{},"Can I use these widgets outside a plugin?","\nYes, in any Qt application that has the QGIS libraries available — they are ordinary widgets. Most need a QGIS application initialised to be useful.",[14,1107,1108,1111,1112,1115],{},[176,1109,1110],{},"How do I restrict the layer box to one specific layer type and geometry?","\nCombine proxy-model flags with the bitwise or: ",[27,1113,1114],{},"PolygonLayer | NoGeometry"," for polygons and attribute-only tables, for example.",[14,1117,1118,1121,1122,259,1125,1128,1129,190],{},[176,1119,1120],{},"Is there a widget for choosing a symbol or a colour ramp?","\nYes — ",[27,1123,1124],{},"QgsSymbolButton",[27,1126,1127],{},"QgsColorRampButton",", both opening QGIS's own editors, which pairs well with ",[21,1130,1132],{"href":1131},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002F","Programmatic Layer Styling in PyQGIS",[14,1134,1135,1138,1139,1142],{},[176,1136,1137],{},"Do these widgets handle translation?","\nTheir own labels and dialogs follow the QGIS interface language automatically. Your labels around them need ",[27,1140,1141],{},"tr()"," as usual.",[14,1144,1145,1148],{},[176,1146,1147],{},"Should a Processing algorithm use these?","\nNo — algorithms declare parameters and the framework builds the interface. These widgets are for a plugin's own dialogs.",[14,1150,1151,1154,1155,1158,1159,1161],{},[176,1152,1153],{},"What happens to a layer combo box when the user removes the selected layer?","\nIt drops the entry and emits ",[27,1156,1157],{},"layerChanged"," with the new selection, which may be ",[27,1160,844],{},". Handle that case in the connected slot rather than assuming the value is always a layer — this is the most common crash in dialogs that stay open while the user works.",[14,1163,1164,1167,1168,1171,1172,1175],{},[176,1165,1166],{},"Can I preselect a layer when the dialog opens?","\nYes: ",[27,1169,1170],{},"setLayer(layer)"," on the combo box, typically with ",[27,1173,1174],{},"iface.activeLayer()"," if it passes your filters. Preselecting the layer the user already has highlighted removes one interaction from every run, and falls back gracefully when the active layer is the wrong type.",[165,1177,1179],{"id":1178},"related","Related",[170,1181,1182,1187,1191,1197,1203],{},[173,1183,1184,1186],{},[21,1185,24],{"href":23}," — the guide this recipe belongs to",[173,1188,1189],{},[21,1190,189],{"href":188},[173,1192,1193],{},[21,1194,1196],{"href":1195},"\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fadd-custom-dock-widget-pyqgis\u002F","Add a Custom Dock Widget in PyQGIS",[173,1198,1199],{},[21,1200,1202],{"href":1201},"\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fremember-last-used-folder-in-plugin-pyqgis\u002F","Remember the Last Used Folder in a Plugin",[173,1204,1205],{},[21,1206,1208],{"href":1207},"\u002Fqgis-plugin-development\u002Fplugin-settings-and-localization\u002Fadd-plugin-options-page-to-qgis-settings\u002F","Add a Plugin Options Page to QGIS Settings",[1210,1211,1212],"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 .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 .sjoCn, html code.shiki .sjoCn{--shiki-default:#9AA79F}",{"title":348,"searchDepth":372,"depth":372,"links":1214},[1215,1216,1217,1218,1219,1220,1221,1222,1223,1224,1225],{"id":167,"depth":372,"text":168},{"id":193,"depth":372,"text":194},{"id":277,"depth":372,"text":278},{"id":337,"depth":372,"text":338},{"id":557,"depth":372,"text":558},{"id":698,"depth":372,"text":699},{"id":948,"depth":372,"text":949},{"id":1023,"depth":372,"text":1024},{"id":1082,"depth":372,"text":1083},{"id":1092,"depth":372,"text":1093},{"id":1178,"depth":372,"text":1179},"Build plugin dialogs from the widgets QGIS already ships — layer and field combo boxes, file and colour pickers, extent and CRS selectors — through promotion in Qt Designer or directly in code.","md",{"slug":12,"type":1229,"breadcrumb":1230,"datePublished":1231,"dateModified":1231},"article","QGIS Custom Widgets","2026-08-15","\u002Fqgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fuse-qgis-custom-widgets-in-qt-designer",{"title":5,"description":1226},"qgis-plugin-development\u002Fqt-designer-for-gis-interfaces\u002Fuse-qgis-custom-widgets-in-qt-designer\u002Findex","WqfJVmuK_H7Tt2D7bcN5takquw5bBci2rN71lrsgvqU",1786789584662]