Tech Articles


PowerBuilder 2025 R2: Runtime DataWindow Inspection with GetDWObject


PowerBuilder 2025 R2: Runtime DataWindow Inspection with GetDWObject

How to build a runtime metadata inspector by combining DescribeGetDWObjectDWObjectModify, a shared DataStore, and UTF-8 JSON export.

 

Introduction

A DataWindow is not merely a row container. Its definition contains a tree of visual and functional objects: columns, text labels, computed fields, buttons, and other controls. Each element has an identity, type, band, geometry, visibility, and specialized properties. That information is usually implicit in the DataWindow Painter, but PowerBuilder can also inspect and modify it at runtime.

The Demo_GetDWObject project turns this capability into a visible tool. Users can enumerate the objects in a DataWindow, filter them by type, select one by name, retrieve its properties, locate it through temporary highlighting, export the result to JSON, and run basic design validation. The entire demo uses local data and requires no database connection.

The technical value of the demo is not a single method call. It presents a complete pipeline: Describe("DataWindow.Objects") discovers names, GetDWObject obtains a typed object, the NVO normalizes its properties, Modify applies a reversible visual change, and FileWriteEx produces a UTF-8 JSON artifact.

The Problem: Inspecting a Definition Known Only at Runtime

A direct reference such as dw_origen.Object.saldo works when the developer knows the object name at compile time. It does not solve generic support, documentation, or auditing scenarios in which the name arrives as text, changes between DataWindows, or is selected by the user.

The demo divides the problem into three complementary operations:

Requirement Mechanism Demo result
Discover which objects exist Describe("DataWindow.Objects") A tab-delimited list of names that can be traversed and filtered.
Read properties for the actual type GetDWObject(name) DWObject reference used to inspect Type, geometry, format, expression, text, or action.
Change a property using a dynamic name Describe + Modify The previous visual state is read, temporary highlighting is applied, and the state is later restored.

Why the Dynamic Approach Matters

1. Coupled approach: hard-coded references in every window

A conventional implementation might write separate conditions for saldonombre_cliente, or total_saldos. This works for one DataWindow, but it requires source changes when the design evolves, duplicates logic in visual events, and makes cross-cutting development tools difficult to build.

2. Reusable approach: runtime name, type, and properties

The nvo_inspector_dw NVO accepts a DataWindow control and a name. The window does not need to know how inspecting a column differs from inspecting a computed field. The NVO determines the type and returns normalized rows with three fields: property, value, and category.

3. Architecture comparison

Concern Coupled UI code Demo architecture
Discovery Hard-coded list of names Inventory read from the active definition.
Access A different expression for every object GetDWObject accepts the dynamic name.
Specialization Conditions spread across the window A type-based Choose Case centralized in the NVO.
Presentation Controls filled individually Properties normalized in a DataStore.
Reuse Limited to the original DataWindow A service that can inspect other compatible DataWindows.
Output Visible only on screen Grid, validation report, and UTF-8 JSON document.

Demo Architecture

The project keeps responsibilities clearly separated:

Object Technical responsibility
demo_getdwobject1.sra Opens w_inspector_dw_ejecucion as the main window.
w_inspector_dw_ejecucion.srw Builds the UI, creates the service and DataStore, loads sample data, wires events, and presents status information.
nvo_inspector_dw.sru Centralizes discovery, GetDWObject access, classification, property extraction, JSON creation, highlighting, and validation.
d_origen_inspeccion.srd Sample DataWindow with five columns, headers, a sum expression, a label, a button, and a hidden column.
d_propiedades_objeto.srd Database-independent external DataWindow with propiedadvalor, and categoria columns.

The window holds two instance references: inv_inspector for the service and ids_propiedades for the DataStore. It also stores the highlighted object name, its previous color, and its previous background mode so the visual change can be safely undone.

Complete Runtime Flow

  1. The open event creates nvo_inspector_dw and a DataStore.
  2. It assigns d_origen_inspeccion to the source control and d_propiedades_objeto to both the result grid and DataStore.
  3. ShareData connects the nonvisual DataStore to dw_propiedades.
  4. The window inserts three customer rows, including names with accented characters.
  5. The NVO enumerates objects and applies the selected filter.
  6. A list selection or a click inside the DataWindow sends a name to of_inspeccionar_objeto.
  7. The NVO loads properties; the window updates the table, JSON, and status line.
  8. The user may highlight the object, export JSON, or validate the design.
  9. The close event restores pending highlighting and destroys created objects.

Step 1: Initializing a Database-Independent Demo

The window builds its infrastructure in memory. The source DataWindow defines columns, but rows are inserted locally. The result DataStore uses an external DataWindow as its schema.

event open;
inv_inspector = Create nvo_inspector_dw
ids_propiedades = Create DataStore

dw_origen.DataObject = "d_origen_inspeccion"
dw_propiedades.DataObject = "d_propiedades_objeto"
ids_propiedades.DataObject = "d_propiedades_objeto"
ids_propiedades.ShareData(dw_propiedades)

ddlb_tipo_objeto.SelectItem(1)
of_cargar_datos_demostracion()
of_actualizar_lista_objetos()
of_inspeccionar_objeto()
end event
Design decision: ShareData lets the NVO work against a nonvisual DataStore while the grid reflects the same rows. The service therefore does not depend on the particular UI control used to display its result.

Step 2: Discovering Objects in the Active Definition

DataWindow.Objects returns tab-delimited names. of_obtener_nombres walks the string without assuming how many objects it contains, retrieves each DWObject, reads its type, and adds only those that match the active filter.

ls_objetos = adw_origen.Describe("DataWindow.Objects")

Do While Len(ls_objetos) > 0
   ll_posicion = Pos(ls_objetos, "~t")
   If ll_posicion > 0 Then
      ls_nombre = Left(ls_objetos, ll_posicion - 1)
      ls_objetos = Mid(ls_objetos, ll_posicion + 1)
   Else
      ls_nombre = ls_objetos
      ls_objetos = ""
   End If

   ldwo_objeto = of_obtener_objeto(adw_origen, ls_nombre)
   If Not IsNull(ldwo_objeto) Then
      If of_coincide_tipo(of_obtener_tipo(ldwo_objeto), as_filtro) Then
         ll_total++
         as_nombres[ll_total] = ls_nombre
      End If
      Destroy ldwo_objeto
   End If
Loop

The Spanish UI filters map to technical object types: columns to column, computed fields to compute, and labels to text. The controls option accepts types outside these three groups; in the sample DataWindow, this includes boton_actualizar.

Step 3: Obtaining and Validating the DWObject

Access is centralized in of_obtener_objeto. Before invoking GetDWObject, it validates the control, trims the name, and rejects empty strings. Callers always check IsNull before reading properties.

public function dwobject of_obtener_objeto (datawindow adw_origen, string as_nombre);
DWObject ldwo_objeto
String ls_nombre

If Not IsValid(adw_origen) Then Return ldwo_objeto
ls_nombre = Trim(as_nombre)
If Len(ls_nombre) = 0 Then Return ldwo_objeto

ldwo_objeto = adw_origen.GetDWObject(ls_nombre)
Return ldwo_objeto
end function
Null contract: an unknown name must not continue into adwo_objeto.Type or any other property. In the demo, of_cargar_propiedades returns -1, and the window reports which name could not be found.

Step 4: Normalizing Common and Type-Specific Properties

Every inspected object contributes a common core: NameTypeBandXYWidthHeight, and Visible. The actual type then determines which additional properties are valid.

DWObject type Additional properties Result categories
column ColTypeFormatTabSequence Data and interaction
compute ExpressionFormat Expression and data
text TextAlignment Content and visual
button TextAction Content and interaction
ls_tipo = of_obtener_tipo(ldwo_objeto)
ll_total = of_agregar_propiedad(ads_propiedades, "Name", String(ldwo_objeto.Name), "Identidad")
ll_total = of_agregar_propiedad(ads_propiedades, "Type", String(ldwo_objeto.Type), "Identidad")
ll_total = of_agregar_propiedad(ads_propiedades, "X", String(ldwo_objeto.X), "Geometría")
ll_total = of_agregar_propiedad(ads_propiedades, "Visible", String(ldwo_objeto.Visible), "Visual")

Choose Case ls_tipo
   Case "column"
      ll_total = of_agregar_propiedad(ads_propiedades, "ColType", String(ldwo_objeto.ColType), "Datos")
      ll_total = of_agregar_propiedad(ads_propiedades, "Format", String(ldwo_objeto.Format), "Datos")
   Case "compute"
      ll_total = of_agregar_propiedad(ads_propiedades, "Expression", String(ldwo_objeto.Expression), "Expresión")
   Case "text"
      ll_total = of_agregar_propiedad(ads_propiedades, "Text", String(ldwo_objeto.Text), "Contenido")
End Choose

The source DataWindow provides verifiable cases: saldo exposes a currency format, total_saldos contains the sum(saldo for all) expression, etiqueta_total provides text and alignment, and boton_actualizar exposes its caption and action.

Step 5: Selecting Objects Directly in the DataWindow

In addition to the object list, users can click a visible element. The clicked event receives dwo, checks that it is not null, copies dwo.Name into the search field, and runs the same inspection path.

event clicked;
If Not IsNull(dwo) Then
   sle_nombre_objeto.Text = String(dwo.Name)
   of_inspeccionar_objeto()
End If
end event

This pattern preserves a single functional implementation: typing a name, selecting it from the list, double-clicking it, or clicking inside the DataWindow all end at of_inspeccionar_objeto.

Step 6: Applying and Reverting Dynamic Highlighting

Before changing the background, the NVO retrieves the current color and mode with Describe. It then uses Modify with the dynamic object name. An empty return string from Modify is treated as success; returned text is treated as an error.

as_color_anterior = adw_origen.Describe(as_nombre + ".Background.Color")
as_modo_anterior = adw_origen.Describe(as_nombre + ".Background.Mode")

ls_resultado = adw_origen.Modify( &
   as_nombre + ".Background.Mode='2' " + &
   as_nombre + ".Background.Color='16022560'")

If Len(ls_resultado) > 0 Then Return -1
Return 1

The window restores the object before highlighting another one and also during close. This symmetry matters: a diagnostic tool should leave the DataWindow in the same visual state in which it found it.

Step 7: Producing Valid JSON and Writing UTF-8

of_exportar_json traverses the DataStore and builds a properties array. The implementation escapes backslashes, quotation marks, carriage returns, line feeds, and tabs before inserting values into the document.

{
  "properties": [
    {"name": "Name", "value": "total_saldos", "category": "Identidad"},
    {"name": "Type", "value": "compute", "category": "Identidad"},
    {"name": "Expression", "value": "sum(saldo for all)", "category": "Expresión"}
  ]
}

When saving, the string is explicitly converted with EncodingUTF8!. This preserves values such as “Comercial Águila,” “Distribuidora del Pacífico,” and “Tecnología y Diseño México.”

lblb_contenido = Blob(as_contenido, EncodingUTF8!)
ll_archivo = FileOpen(as_ruta, StreamMode!, Write!, LockWrite!, Replace!)
If ll_archivo < 1 Then Return -1

ll_escritos = FileWriteEx(ll_archivo, lblb_contenido)
FileClose(ll_archivo)
Separation of concerns: the window obtains a path through GetFileSaveName; the NVO generates the document and performs the UTF-8 write. The dialog belongs to user interaction, while serialization and persistence remain centralized.

Step 8: Validating the Design with the Same Inventory

The Validate design action demonstrates how inspection can become an automated rule. For every name, the NVO reads VisibleXYWidth, and Height, and reports:

  • hidden objects;
  • positions with negative coordinates;
  • widths or heights less than or equal to zero.

d_origen_inspeccion deliberately contains columna_oculta with visible="0". The validator therefore has at least one controlled condition it can detect without a database or extra preparation.

If String(ldwo_objeto.Visible) = "0" Then
   ll_hallazgos++
   ls_informe += "[Oculto] " + ls_nombre + "~r~n"
End If
If ll_x < 0 Or ll_y < 0 Then
   ll_hallazgos++
   ls_informe += "[Posición] " + ls_nombre + " está fuera del origen visible.~r~n"
End If
If ll_ancho <= 0 Or ll_alto <= 0 Then
   ll_hallazgos++
   ls_informe += "[Dimensión] " + ls_nombre + " tiene ancho o alto no válido.~r~n"
End If

Lifecycle Management

Each temporary DWObject reference is destroyed after inspection. The window also destroys its DataStore and NVO during shutdown, after first restoring any pending highlight.

event close;
of_restaurar_resaltado_actual()
If IsValid(ids_propiedades) Then Destroy ids_propiedades
If IsValid(inv_inspector) Then Destroy inv_inspector
end event

This order prevents a temporary visual modification from being abandoned and makes ownership of objects created with Create explicit.

Practical Applications

  • Support: internal inspectors that expose name, type, band, and geometry without opening the Painter.
  • Documentation: JSON inventories for technical catalogs or automated documentation.
  • Quality control: rules for hidden objects, inconsistent formats, invalid sizes, or tab sequences.
  • Migration: comparing properties before and after converting an application.
  • Testing: locating objects by type or name without coupling tests to static references.
  • Developer tooling: runtime highlighting, navigation, and diagnosis of complex DataWindows.

Recommended Production Enhancements

  • Add explicit Describe error handling and validate responses that begin with !.
  • Expand the property matrix for graphs, lines, ovals, nested reports, and other types used by the application.
  • Move JSON serialization into a dedicated component or use a JSON generator as the contract grows.
  • Include the DataObject name, version, timestamp, and runtime context in exported metadata.
  • Use configurable rules instead of hard-coding every validation inside the NVO.
  • Process large inventories without freezing the UI and add a technical error log.
  • Do not export expressions or sensitive metadata without access controls and diagnostic-data policies.
  • Add tests for unknown names, special characters, and object types that are not yet mapped.

Conclusion

GetDWObject becomes most useful as part of a complete introspection architecture. Describe discovers the active definition, GetDWObject interprets each element according to its runtime type, an NVO transforms heterogeneous properties into uniform rows, and Modify adds reversible visual interaction.

Demo_GetDWObject carries this pattern from initialization to a reusable result: it runs without a database, supports several selection paths, preserves international characters in UTF-8, exports JSON, and shows how the same properties can feed automated validation. It is compact, but technically sound, and provides a practical foundation for DataWindow inspectors, documentation tools, and auditing utilities in PowerBuilder 2025 R2.

View the demo on CodeXchange.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Comments (0)
There are no comments posted here yet

Find Articles by Tag

Windows OS Variable Windows 10 Icons PFC Debug Stored Procedure HTTPClient DataWindow JSON Performance PBNI Event Handling iOS PBDOM Source Control .NET DataStore Trial PowerBuilder Compiler RibbonBar Builder Encoding Mobile TFS Syntax InfoMaker Source Code JSON PostgreSQL ODBC driver CI/CD Database Table Schema Automated Testing Outlook Debugging TortoiseGit Messagging Bug Installation Web API Script Model Oracle PDF Database Table Data File BLOB Platform JSONGenerator SqlModelMapper Encryption Debugger XML SVN Testing Branch & Merge TreeView DataType Git IDE Import UI Error ODBC Linux OS Authentication License C# PostgreSQL Repository SnapObjects External Functions Elevate Conference 64-bit Open Source COM DragDrop Filter MessageBox TLS/SSL Text Visual Studio Service Event Handler UI Modernization SOAP OAuth DataWindow PowerServer Mobile UI Themes Android API SQL Server Database Painter CrypterObject REST Database Profile Excel Expression PowerScript (PS) Database Object Charts NativePDF SQL Import JSON .NET Assembly DLL RibbonBar Sort PDFlib Migration Data PowerBuilder Database SnapDevelop Database Table Authorization OLE RichTextEdit Control RESTClient Deployment Database Connection Application Menu Icon Validation CoderObject Interface Azure Window Resize Event 32-bit Graph OrcaScript PowerBuilder (Appeon) Web Service Proxy Transaction Configuration .NET Std Framework WebBrowser SqlExecutor ActiveX OAuth 2.0 PBVM SDK Export GhostScript Class DevOps Export JSON WinAPI Array PowerServer Web Design Jenkins JSONParser