PBToolboxAI v3 ← Site

crosstab — u_pbt_crosstab #

← Référence des composants · Sommaire du guide

Tableau croisé dynamique complet : zones lignes / colonnes / valeurs alimentées depuis un DataStore, agrégations, filtres, mise en forme conditionnelle, regroupement de dates, mesures calculées et exports CSV / Excel.

▶ Le voir en vrai — Application de démonstration, tuile Crosstab : l'aperçu, le code qui le produit et cette page, côte à côte.


En bref #

Userobjectu_pbt_crosstab
Classe d'items— (les champs se placent par méthodes)
Sert àDonner à vos utilisateurs une analyse croisée de vos données, qu'ils réarrangent eux-mêmes, sans écrire de SQL ni sortir vers Excel
Limite en mode démo500 lignes sources exploitées ; exports CSV et Excel désactivés — voir le mode démo

Le principe #

Vous fournissez au composant un jeu de données à plat — un DataStore, donc n'importe quelle requête déjà écrite dans votre application. Le crosstab s'occupe du reste : il en déduit la liste des champs, et vous les répartissez dans quatre zones.

ZoneCe qu'elle contientEffet
LignesDes champs de regroupementUn niveau de lignes par champ, repliable
ColonnesDes champs de regroupementUn niveau d'en-têtes de colonnes par champ
ValeursDes champs numériques et leur agrégationCe qui est calculé dans les cellules
FiltresDes champs de sélectionUn filtre au-dessus du tableau, appliqué à tout

Tout le calcul se fait dans le composant : une fois les données transmises, réarranger le tableau ne déclenche aucun aller-retour vers la base ni vers PowerBuilder.


Démarrage rapide #

// event open de la fenetre
datastore lds
lds = create datastore
lds.dataobject = "d_ventes"
lds.SetTransObject(SQLCA)
lds.Retrieve()

// 1. Transmettre les donnees : les champs sont deduits des colonnes
uo_croise.of_set_data(lds)

// 2. Repartir les champs dans les zones
uo_croise.of_add_row_field(/*champ*/ "region")
uo_croise.of_add_col_field(/*champ*/ "annee")
uo_croise.of_add_value_field(/*champ*/ "montant", /*agregat*/ uo_croise.AGG_SUM)

// 3. Presenter : format des montants et totaux generaux
uo_croise.of_set_value_format(/*champ*/ "montant", /*decimales*/ 0, /*milliers*/ "locale", /*symbole*/ "$", /*symbole_avant*/ true)
uo_croise.ib_row_grand_total = true
uo_croise.ib_col_grand_total = true
// event ue_cell_double_clicked de uo_croise : (string as_row_tuple_json, string as_col_tuple_json, double ad_value)
// L'utilisateur veut le detail derriere un chiffre : ouvrir la liste correspondante.
of_ouvrir_detail(as_row_tuple_json, as_col_tuple_json)

Constantes #

ConstanteValeurPour
TOTALS_BOTTOM · TOTALS_TOP"bottom" "top"is_totals_position
VALUES_COLS · VALUES_ROWS"cols" "rows"is_values_axis
AGG_SUM · AGG_COUNT · AGG_DISTINCT_COUNT"sum" "count" "dcount"of_add_value_field
AGG_AVG · AGG_MIN · AGG_MAX"avg" "min" "max"of_add_value_field

Les constantes d'agrégation se lisent sur le composant : uo_croise.AGG_SUM. Celles d'un champ (SHOW_*, CF_*) se lisent sur le handle du champ.


Propriétés #

PropriétéTypeDéfautRôle
is_totals_positionstring"bottom"Où se place la ligne de total général : TOTALS_BOTTOM (en pied, défaut) ou TOTALS_TOP (en tête, juste sous les en-têtes)
is_values_axisstring"cols"Orientation des mesures quand il y en a plusieurs : VALUES_COLS (côte à côte en colonnes, défaut) ou VALUES_ROWS (empilées en lignes)
is_currency_symbolstring""La devise que le menu Format du nombre d'un chip de valeur propose, à côté de « Aucun symbole » et « % ». Vide = celle de la langue d'affichage ($ en anglais, € ailleurs)
ib_field_listbooleantrueAffiche le panneau de champs, où l'utilisateur réarrange le tableau à la souris
ib_row_subtotalsbooleantrueAffiche un sous-total par groupe de lignes
ib_col_subtotalsbooleantrueAffiche un sous-total par groupe de colonnes
ib_row_grand_totalbooleantrueAffiche la ligne de total général sous le tableau (jumelle de ib_col_grand_total)
ib_col_grand_totalbooleantrueAffiche la colonne de total général après le tableau (jumelle de ib_row_grand_total)
ib_enabledbooleantrueGrisé : la grille montre toujours ses chiffres — un croisé vide n'est pas la même chose qu'un croisé que l'application a éteint — mais elle cesse de répondre au pointeur
is_theme_stylestringfluentStyle visuel du composant (constantes THEME_STYLE_*)
is_theme_modestringlightVariante claire ou sombre (constantes THEME_MODE_*)
il_theme_accentlong-1Couleur d'accent de ce composant (-1 = accent du thème)
is_tooltipstring""Info-bulle simple affichée au survol du composant
is_super_tooltip_titlestring""Titre de l'info-bulle enrichie (prend le pas sur is_tooltip)
is_super_tooltip_textstring""Texte de l'info-bulle enrichie (balisage riche accepté)
is_super_tooltip_imagestring""Image de l'info-bulle enrichie

Propriétés d'un champ #

of_field (string as_field) renvoie le handle d'un champ : vous le récupérez une fois, puis vous pilotez le champ par ses propriétés. Le handle est créé au premier appel et réutilisé ensuite.

PropriétéTypeDéfautRôle
is_labelstring""Libellé lisible du champ ("montant" → "Chiffre d'affaires")
is_showstring"normal"Ce que la cellule affiche : "normal", "pctGrand" (% du total général), "pctRow" (% de la ligne), "pctCol" (% de la colonne), "running" (cumul), "diff" (écart au précédent)
is_conditional_formattingstring"none"Mise en forme conditionnelle : CF_NONE, CF_SCALE (échelle de couleurs) ou CF_BARS (barres dans la cellule)

Les valeurs de is_show et is_conditional_formatting sont aussi disponibles en constantes sur le handle (SHOW_PCT_COL, CF_SCALE…).

n_pbt_crosstab_field lnv_champ

lnv_champ = uo_croise.of_field(/*champ*/ "montant")
lnv_champ.is_label = "Chiffre d'affaires"
lnv_champ.is_conditional_formatting = lnv_champ.CF_SCALE

⚠️ Changement incompatible. Cette propriété s'appelait is_cf : l'abréviation ne disait rien au point d'appel. L'ancien nom n'existe plus — un code qui l'utilise ne compile pas. Le remplacement est mécanique : is_cf → is_conditional_formatting, sans changement de valeurs ni de comportement.


Méthodes #

Alimenter et nommer #

MéthodeRôle
of_set_data (datastore ads_data)Transmet le jeu de données : les champs sont déduits des colonnes du DataStore, leur libellé du texte d'en-tête. Renvoie 0 une fois les données chargées, -5 si le DataStore n'est pas valide ou n'a aucune colonne, -2 si le composant n'est pas créé
of_field (string as_field)Renvoie le handle d'un champ, pour le libeller ou le mettre en forme (voir Propriétés d'un champ)

Construire le tableau #

MéthodeRôle
of_clear_layout ( )Vide les quatre zones : le tableau redevient vierge, les données restent chargées. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_add_row_field (string as_field)Ajoute un champ en zone Lignes (l'ordre des appels donne l'ordre des niveaux). Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_add_col_field (string as_field)Ajoute un champ en zone Colonnes. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_add_value_field (string as_field, string as_agg)Ajoute une mesure en zone Valeurs, avec son agrégation. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_add_filter_field (string as_field)Ajoute un champ en zone Filtres, au-dessus du tableau. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé

Les agrégations acceptées par of_add_value_field sont portées par le composant en constantes : AGG_SUM (défaut), AGG_COUNT, AGG_DISTINCT_COUNT (comptage de valeurs distinctes), AGG_AVG, AGG_MIN, AGG_MAX.

Totaux et sous-totaux #

MéthodeRôle

Filtrer #

MéthodeRôle
of_set_member_filter (string as_field, string as_values_tab)Ne garde que les valeurs listées d'un champ. Les valeurs sont séparées par des tabulations (~t). Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé
of_clear_member_filter (string as_field)Retire ce filtre. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_set_value_filter (string as_field, string as_type, double ad_a, double ad_b, integer ai_measure_index)Filtre sur le total : "top" (les ad_a premiers), "gt", "lt", "between". ai_measure_index désigne la mesure concernée (la première = 1). Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé
of_clear_value_filter (string as_field)Retire ce filtre. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_set_label_filter (string as_field, string as_type, double ad_a, double ad_b)Filtre sur la valeur du champ lui-même : "gt", "lt", "between". Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé
of_clear_label_filter (string as_field)Retire ce filtre. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé

Mettre en forme #

MéthodeRôle
of_set_value_format (string as_field, integer ai_decimals, string as_thousands, string as_suffix { , boolean ab_symbol_before })Format d'une mesure : nombre de décimales (0 à 6), séparateur de milliers ("locale", "space", "none"), symbole — après le nombre par défaut (1 234 EUR), AVANT quand ab_symbol_before vaut true ($1,234). Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé
of_clear_value_format (string as_field)Retour au format par défaut. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_set_member_order (string as_field, string as_values_tab)Ordre d'affichage imposé aux valeurs d'un champ (séparées par ~t) ; une chaîne vide rend l'ordre naturel. Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé

Dates et champs calculés #

MéthodeRôle
of_group_date_field (string as_field, string as_part)Crée un champ dérivé d'une colonne date : "year", "quarter" ou "month". Il rejoint la liste des champs et s'emploie comme les autres. Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé
of_add_calc_field (string as_name, string as_label, string as_formula)Champ calculé ligne à ligne ("[montant] * 0.8" = le net de chaque vente, sommé ensuite comme toute colonne), utilisable dans n'importe quelle zone. Pas pour un ratio de totaux (prix moyen) : c'est of_add_calc_measure. Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé
of_remove_calc_field (string as_name)Retire un champ calculé. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_add_calc_measure (string as_name, string as_label, string as_formula)Mesure calculée cellule à cellule, sur les totaux ("[marge] / [ca]" = taux de marge global). Se place uniquement en zone Valeurs. Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé
of_remove_calc_measure (string as_name)Retire une mesure calculée. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé

Une formule accepte les opérateurs + - * / ( ), des nombres et des champs entre crochets. Une formule invalide déclenche ue_calc_field_error — rien ne plante.

Déplier, mémoriser, exporter #

MéthodeRôle
of_expand_all ( ) · of_collapse_all ( )Déplie ou replie tous les groupes de lignes. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_expand_to_level (integer ai_level)Déplie jusqu'à un niveau donné (1 = premier niveau seulement). Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_get_layout ( ) → stringRend l'état complet du tableau — à conserver tel quel, puis à rejouer par of_set_layout
of_set_layout (string as_state_json)Restaure un état obtenu précédemment. Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé
of_export_csv (string as_path)Écrit un fichier CSV de la vue courante (UTF-8 avec BOM, point-virgule) ; ue_csv_saved confirme. Le jumeau de of_export_xlsx. Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé
of_export_xlsx (string as_path)Écrit un fichier Excel du tableau, mise en forme comprise ; ue_xlsx_saved confirme. Renvoie 0 une fois appliqué, -5 sur un argument invalide, -2 si le composant n'est pas créé

Communes #

MéthodeRôle
of_reset ( )Remet le composant à son état neuf. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_set_redraw (boolean)Regroupe une rafale de modifications en un seul rendu. Renvoie 0 une fois appliqué, -2 si le composant n'est pas créé
of_save_as_png (string) · of_save_as_jpg (string)Exporte le rendu en image. Renvoie 0, -2 si le composant n'est pas créé, -4 si la capture échoue, -5 sur un chemin vide

Événements #

ÉvénementDéclenché quand
ue_layout_changed (string as_layout_json)L'utilisateur a réarrangé le tableau (déplacé un champ, changé une agrégation, replié un groupe…)
ue_cell_double_clicked (string as_row_tuple_json, string as_col_tuple_json, double ad_value)Double-clic sur une cellule : les deux premiers arguments décrivent le croisement, le troisième la valeur affichée. C'est le point d'entrée d'un détail
ue_csv_saved (string as_path, boolean ab_ok, string as_error)Le fichier CSV a été écrit — ou non, et as_error dit pourquoi
ue_xlsx_saved (string as_path, boolean ab_ok, string as_error)Le fichier Excel a été écrit — ou non, et as_error dit pourquoi
ue_calc_field_error (string as_field, string as_message)Une formule de champ ou de mesure calculée est invalide
ue_copy (string as_tsv)L'utilisateur a copié une sélection de cellules (Ctrl+C) : à vous de la poser dans le presse-papiers
ue_ready ( )Le composant a fini de charger ; tout ce qui a été envoyé avant a été rejoué
ue_runtime_missing ( )Le runtime WebView2 est absent : le composant reste vide
ue_bg_color (long al_color)Le composant a calculé sa couleur de fond de thème ; l'userobject l'a déjà adoptée (backcolor)

Ce que l'utilisateur peut faire sans une ligne de code #

Le tableau est vivant : c'est tout l'intérêt du composant. Avec le panneau de champs affiché (ib_field_list = true), l'utilisateur peut :

Chacune de ces manipulations remonte dans ue_layout_changed : couplé à of_get_layout / of_set_layout, cela permet d'offrir des « vues enregistrées » à vos utilisateurs.

Au clavier. Chacun de ces gestes est atteignable sans souris : Tab amène sur un champ, un triangle de repli ou un en-tête triable, Entrée ou Espace le déclenche. Sur un champ, cela ouvre son menu — celui qui porte Ajouter aux lignes / aux colonnes / aux valeurs / aux filtres et Retirer : toute la construction du tableau passe par là.


Exemples #

Un rapport de ventes complet #

// Lignes : region, puis ville a l'interieur de chaque region
uo_croise.of_clear_layout()
uo_croise.of_add_row_field(/*champ*/ "region")
uo_croise.of_add_row_field(/*champ*/ "ville")

// Colonnes : une par annee
uo_croise.of_add_col_field(/*champ*/ "annee")

// Cellules : le montant total
uo_croise.of_add_value_field(/*champ*/ "montant", /*agregat*/ uo_croise.AGG_SUM)

// Presentation : montants lisibles, sous-totaux et totaux generaux
uo_croise.of_set_value_format(/*champ*/ "montant", /*decimales*/ 0, /*milliers*/ "locale", /*symbole*/ "$", /*symbole_avant*/ true)
uo_croise.ib_row_subtotals = true
uo_croise.ib_row_grand_total = true
uo_croise.ib_col_grand_total = true

// Colorer les cellules pour reperer les gros montants d'un coup d'oeil
uo_croise.of_field(/*champ*/ "montant").is_conditional_formatting = n_pbt_crosstab_field.CF_SCALE

Des libellés lisibles #

Vos colonnes s'appellent souvent mt_ht ou cd_reg. Renommez-les une fois pour toutes, juste après of_set_data.

uo_croise.of_set_data(lds)

uo_croise.of_field("region").is_label    = "Region"
uo_croise.of_field("ville").is_label     = "Ville"
uo_croise.of_field("categorie").is_label = "Categorie"
uo_croise.of_field("annee").is_label     = "Annee"
uo_croise.of_field("montant").is_label   = "Chiffre d'affaires"
uo_croise.of_field("quantite").is_label  = "Quantite"

Analyser des parts plutôt que des montants #

n_pbt_crosstab_field lnv_montant

uo_croise.of_clear_layout()
uo_croise.of_add_row_field(/*champ*/ "categorie")
uo_croise.of_add_col_field(/*champ*/ "annee")
uo_croise.of_add_value_field(/*champ*/ "montant", /*agregat*/ uo_croise.AGG_SUM)

// Ne garder que deux categories a l'ecran (valeurs separees par une tabulation)
uo_croise.of_set_member_filter(/*champ*/ "categorie", /*valeurs*/ "Informatique~tMobilier")

lnv_montant = uo_croise.of_field(/*champ*/ "montant")

// Afficher la part de chaque cellule dans le total de sa colonne
lnv_montant.is_show = lnv_montant.SHOW_PCT_COL

// Une petite barre dans chaque cellule pour comparer les parts d'un regard
lnv_montant.is_conditional_formatting = lnv_montant.CF_BARS

Une mesure à vous : le prix moyen #

Une mesure calculée s'évalue sur les totaux de chaque cellule, pas ligne à ligne : c'est ce qui rend un ratio juste.

uo_croise.of_clear_layout()
uo_croise.of_add_row_field(/*champ*/ "region")

// Les deux totaux qui vont servir de base au calcul
uo_croise.of_add_value_field(/*champ*/ "montant",  /*agregat*/ uo_croise.AGG_SUM)
uo_croise.of_add_value_field(/*champ*/ "quantite", /*agregat*/ uo_croise.AGG_SUM)

// Prix moyen = montant total divise par quantite totale
uo_croise.of_add_calc_measure(/*nom*/ "prix_moyen", /*libelle*/ "Prix moyen", &
                              /*formule*/ "[montant] / [quantite]")
uo_croise.of_set_value_format(/*champ*/ "prix_moyen", /*decimales*/ 2, /*milliers*/ "locale", /*symbole*/ "$", /*symbole_avant*/ true)

// Puis la placer parmi les valeurs comme n'importe quel champ (l'agregat ne compte pas : une mesure se calcule)
uo_croise.of_add_value_field(/*champ*/ "prix_moyen", /*agregat*/ uo_croise.AGG_SUM)
// event ue_calc_field_error de uo_croise : (string as_field, string as_message)
// Formule invalide : prevenir sans rien casser, le tableau reste affiche.
uo_statut.of_item("main").is_text = "Formule " + as_field + " : " + as_message

Analyser par mois, trimestre ou année #

Une colonne de date ne se croise pas telle quelle — chaque jour ferait sa propre ligne. Dérivez d'abord le niveau voulu.

// Creer trois champs derives de la colonne date_vente
uo_croise.of_group_date_field(/*champ*/ "date_vente", /*niveau*/ "year")
uo_croise.of_group_date_field(/*champ*/ "date_vente", /*niveau*/ "quarter")
uo_croise.of_group_date_field(/*champ*/ "date_vente", /*niveau*/ "month")

// Puis les croiser comme n'importe quel champ : annee en colonnes, trimestre en dessous
uo_croise.of_add_col_field("date_vente__year")
uo_croise.of_add_col_field("date_vente__quarter")

Le palmarès des dix meilleures régions #

// Ne garder que les 10 regions au plus fort total sur la premiere mesure
uo_croise.of_set_value_filter(/*champ*/ "region", /*type*/ "top", &
                              /*a*/ 10, /*b*/ 0, /*mesure*/ 1)

Exporter #

L'export reprend exactement la vue en cours : mêmes filtres, mêmes totaux, même mise en forme.

// Vers Excel : le fichier est ecrit directement au chemin indique
uo_croise.of_export_xlsx("C:\temp\ventes.xlsx")
// event ue_xlsx_saved de uo_croise : (string as_path, boolean ab_ok, string as_error)
// inv_notif = un n_pbt_toaster declare en variable d'instance de la fenetre
if ab_ok then
    inv_notif.is_title = "Export termine"
    inv_notif.is_text  = as_path
    inv_notif.is_kind  = inv_notif.KIND_SUCCESS
else
    inv_notif.is_title = "Export impossible"
    inv_notif.is_text  = as_error
    inv_notif.is_kind  = inv_notif.KIND_ERROR
end if
inv_notif.of_show()
// Vers CSV : un fichier, comme pour Excel ; ue_csv_saved confirme
uo_croise.of_export_csv(/*chemin*/ "C:\exports\ventes.csv")

Offrir des vues enregistrées #

string ls_vue

// Enregistrer la vue courante : of_get_layout la rend tout de suite
ls_vue = uo_croise.of_get_layout()

// Conserver le contenu TEL QUEL : il se rejoue sans transformation.
of_enregistrer_vue(is_vue_courante, ls_vue)
// Plus tard : rejouer une vue enregistree
uo_croise.of_set_layout(of_lire_vue("Ventes par region"))

Descendre au détail derrière un chiffre #

// event ue_cell_double_clicked de uo_croise : (string as_row_tuple_json, string as_col_tuple_json, double ad_value)
// Les deux premiers arguments decrivent le croisement (quelles valeurs de lignes,
// quelles valeurs de colonnes) : de quoi reconstruire une requete de detail.
w_detail_ventes lw_detail

OpenWithParm(lw_detail, as_row_tuple_json + "|" + as_col_tuple_json)

Bonnes pratiques #

Hérité du socle commun #

Ces membres existent sur tous les composants visuels — ils ne sont pas propres à celui-ci. Ils sont détaillés une seule fois, dans les chapitres transverses ; cette table dit seulement où les lire.

MembresRôleDétaillé dans
of_count · of_keys_at · of_hasParcourir ce que le composant contient3.2 Les items
of_resetRemettre le composant à zéro3.6 Remettre un composant à zéro : of_reset()
of_set_property · of_get_property · of_component_namePiloter une propriété par son nom3.1 Le moteur de propriétés
of_register_shortcut · of_clear_shortcutsRaccourcis clavier du composant3.5 Les raccourcis clavier
of_is_created · of_is_ready · of_get_last_errorS'il est né, s'il est prêt, ce qui a échoué3.7 Diagnostic
of_save_as_png · of_save_as_jpgExporter le rendu en image3.8 Exporter le rendu en image
of_set_redrawGrouper les modifications en un seul repaint3.10 Bonnes pratiques
of_preload_iconsIcônes affichées sans délaiAffichage instantané : of_icon
of_set_translationTraduire un libellé du composant5.2 Adapter un libellé : of_set_translation
of_focus_webviewDonner le focus au composant6.4 Clavier et focus
of_print · of_print_to_pdfImprimer, ou écrire un PDF6.9 Imprimer

Deux aides ne sont pas héritées : of_icon et of_escape_markup vivent sur n_pbt_utils. Déclarez-en une — n_pbt_utils lnv_utils, rien à créer — et appelez-les dessus.


← Référence des composants · Sommaire du guide