PBToolboxAI v5 ← 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'itemsn_pbt_crosstab_field (un champ, par of_field) · n_pbt_crosstab_value (une valeur de la zone Valeurs, par of_value)
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 — of_get_cell_value et of_get_cell_rows lisent ces 500 lignes, les mêmes chiffres qu'à l'écran ; exports CSV et Excel et copie (Ctrl+C) 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
// Local variables
datastore lds
lds = create datastore
lds.dataobject = "d_sales"
lds.SetTransObject(SQLCA)
lds.Retrieve()

// 1. Transmettre les donnees : les champs sont deduits des colonnes
uo_crosstab.of_from_datastore(/*ads*/ lds)

// 2. Repartir les champs dans les zones
uo_crosstab.of_add_row_field(/*field*/ "region")
uo_crosstab.of_add_col_field(/*field*/ "year")
uo_crosstab.of_add_value_field(/*field*/ "amount", /*agg*/ uo_crosstab.AGG_SUM)

// 3. Presenter : format des montants et totaux generaux
uo_crosstab.of_field(/*field*/ "amount").is_format = "$#,##0"
uo_crosstab.ib_row_grand_total = true
uo_crosstab.ib_col_grand_total = true
// event ue_cell_double_clicked de uo_crosstab : (string as_row_tuple_json, string as_col_tuple_json, integer ai_measure, double ad_value)
// L'utilisateur veut le detail derriere un chiffre : ouvrir la liste correspondante.
of_open_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
THOUSANDS_LOCALE · THOUSANDS_SPACE · THOUSANDS_NONE"locale" "space" "none"is_thousands
AGG_SUM · AGG_COUNT · AGG_COUNT_ROWS · AGG_DISTINCT_COUNT"sum" "count" "countrows" "dcount"of_add_value_field · is_agg
AGG_AVG · AGG_MIN · AGG_MAX"avg" "min" "max"of_add_value_field · is_agg
VALUEFILTER_TOP · VALUEFILTER_BOTTOM"top" "bottom"of_set_value_filter
VALUEFILTER_GT · VALUEFILTER_LT · VALUEFILTER_BETWEEN"gt" "lt" "between"of_set_value_filter
LABEL_GT · LABEL_LT · LABEL_BETWEEN"gt" "lt" "between"of_set_label_filter
LABEL_CONTAINS · LABEL_BEGINS · LABEL_ENDS"contains" "begins" "ends"of_set_label_filter
LABEL_BEFORE · LABEL_AFTER"before" "after"of_set_label_filter
DATE_YEAR · DATE_QUARTER · DATE_MONTH"year" "quarter" "month"of_group_date_field

Les constantes d'agrégation, de filtre, de date et de séparateur se lisent sur le composant : uo_crosstab.AGG_SUM. Celles d'un champ ou d'une valeur (SHOW_*, CF_*) se lisent sur la classe de sa poignée : n_pbt_crosstab_field.CF_SCALE, n_pbt_crosstab_value.SHOW_PCT_GRAND. AGG_COUNT compte les valeurs non vides, comme le Nombre d'Excel ; AGG_COUNT_ROWS compte les lignes, vides comprises.


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). Seul le total général se déplace : le sous-total d'un groupe reste sur la ligne du groupe
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)
is_thousandsstring"locale"Séparateur de milliers des valeurs qui n'ont pas de format à elles (is_format vide et aucun Format sur la colonne du DataWindow) — le même réglage que le menu Options de la grille : THOUSANDS_LOCALE (celui de la langue d'affichage, défaut), THOUSANDS_SPACE, THOUSANDS_NONE, ou le séparateur lui-même (",", ".", " ")
ib_field_listbooleantrueAffiche le panneau de champs, où l'utilisateur réarrange le tableau à la souris
ib_row_subtotalsbooleantrueAffiche le sous-total de chaque groupe de lignes, écrit sur la ligne du groupe lui-même, au-dessus de ses membres ; désactivé, cette ligne garde son libellé sans chiffre
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). Sans champ en Colonnes, il n'y a pas de colonne de total — la seule colonne de valeurs EST le total — et la propriété garde sa valeur pour le jour où un champ y entre. Avec plusieurs mesures, la colonne Total a un en-tête « Total » puis un en-tête par mesure, triable
ib_copy_headersbooleantrueCtrl+C copie les en-têtes avec les cellules sélectionnées : les noms des colonnes sur une première ligne (la mesure n'y est nommée que si le tableau la montre : plusieurs valeurs, ou aucun champ en colonnes) et le membre de chaque ligne — son chemin entier, « Nord / Lille » — dans une première colonne, dont le coin porte les noms des champs en lignes (« region / ville »). Un collage dans un tableur dit ainsi ce que sont les chiffres. false copie les chiffres seuls. Lue en direct, remise à true par of_reset
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_stylestring""Style visuel du composant (constantes THEME_STYLE_*) ; vide = celui de l'application, suivi à chaque changement
is_theme_modestring""Variante claire ou sombre (constantes THEME_MODE_*) ; vide = celle de l'application, suivie à chaque changement
il_theme_accentlong-1Couleur d'accent de ce composant (-1 = l'accent de l'application, ou celui 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_labelstringen-tête du DataWindowLibellé lisible du champ ("amount" → "Chiffre d'affaires"). Par défaut le texte d'en-tête de la colonne dans le DataWindow, sinon son nom ; une chaîne vide le rend. Posé par votre code, il survit à of_from_datastore
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), CF_BARS (barres dans la cellule) ou CF_ICONS (une flèche par tiers : haut, stable, bas)
is_formatstringFormat de la colonneFormat d'affichage des valeurs du champ, au format PowerBuilder ("#,##0.00", "$#,##0;($#,##0)", "0.0%") : séparateurs de la langue d'affichage. Vide = le Format de la colonne du DataWindow, lu par of_from_datastore ; sans l'un ni l'autre, is_thousands et deux décimales au plus. Relue : le format en vigueur, celui posé sinon celui de la colonne

Les valeurs de is_show et is_conditional_formatting sont aussi disponibles en constantes sur le handle (SHOW_PCT_COL, CF_SCALE…). Posées sur le champ, is_show, is_conditional_formatting et is_format valent pour toutes ses valeurs de la zone Valeurs et pour celles qui y entrent ensuite — posées avant que le champ soit en Valeurs, elles sont gardées, se relisent telles quelles et s'appliquent à son arrivée. Pour une valeur seule (le même champ deux fois : le montant, et sa part du total), c'est la poignée de valeur, ci-dessous.

// Local variables
n_pbt_crosstab_field lnv_field

// Le champ Montant : son libelle et une echelle de couleurs
lnv_field = uo_crosstab.of_field(/*field*/ "amount")
lnv_field.is_label = "Chiffre d'affaires"
lnv_field.is_conditional_formatting = lnv_field.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.

Propriétés d'une valeur #

of_value (integer ai_position) renvoie le handle d'une valeur de la zone Valeurs, par sa position (1 = la première). Un champ peut y être deux fois — le montant, et sa part du total général — et chaque valeur a ses réglages, qui passent devant ceux de son champ. Le handle désigne une position : après of_remove_value, of_move_value, of_remove_field, of_remove_calc_measure, of_clear_layout ou of_set_layout, il est libéré — redemandez of_value. Ses constantes SHOW_* et CF_* sont celles de n_pbt_crosstab_field.

PropriétéTypeDéfautRôle
is_aggstringcelle d'of_add_value_fieldAgrégation de cette valeur : une constante AGG_* du composant (uo_crosstab.AGG_AVG). Une mesure calculée n'en a pas à choisir : l'affectation est ignorée
is_showstring"normal"Ce que les cellules de cette valeur affichent : SHOW_NORMAL, SHOW_PCT_GRAND, SHOW_PCT_ROW, SHOW_PCT_COL, SHOW_RUNNING, SHOW_DIFF (comme sur le champ)
is_conditional_formattingstring"none"Mise en forme conditionnelle de cette valeur : CF_NONE, CF_SCALE, CF_BARS, CF_ICONS
is_formatstringFormat de la colonneFormat d'affichage de cette valeur seule, au format PowerBuilder ("#,##0.00", "$#,##0;($#,##0)"). Vide = le Format de la colonne du DataWindow. Relue : le format en vigueur
ib_negative_redbooleanfalseLes nombres négatifs de cette valeur en rouge — à l'écran et dans l'export Excel. Le même réglage que l'entrée « Négatifs en rouge » du menu de la puce
is_labelstring""Libellé de cette valeur dans les en-têtes (« Part des ventes ») ; vide rend celui d'origine, l'agrégation et le libellé du champ (« Somme de Montant »)
MéthodeRôle
of_field ( ) → stringLe champ que cette valeur agrège ("amount") ; "" quand la position ne porte aucune valeur
// Local variables
n_pbt_crosstab_value lnv_share, lnv_amount

// Le montant deux fois en Valeurs : la somme, puis sa part du total general
uo_crosstab.of_add_value_field(/*field*/ "amount", /*agg*/ uo_crosstab.AGG_SUM)
uo_crosstab.of_add_value_field(/*field*/ "amount", /*agg*/ uo_crosstab.AGG_SUM)

// La deuxieme valeur seule : sa part du total general, sous son propre libelle
lnv_share = uo_crosstab.of_value(/*position*/ 2)
lnv_share.is_show = n_pbt_crosstab_value.SHOW_PCT_GRAND
lnv_share.is_label = "Part des ventes"

// La premiere valeur : deux decimales, les negatifs en rouge
lnv_amount = uo_crosstab.of_value(/*position*/ 1)
lnv_amount.is_format = "#,##0.00"
lnv_amount.ib_negative_red = true

Méthodes #

Alimenter et nommer #

MéthodeRôle
of_from_datastore (datastore ads)Transmet le jeu de données : les champs sont déduits des colonnes du DataStore, leur libellé du texte d'en-tête, le format par défaut de leurs valeurs du Format de la colonne. Un texte qui contient une tabulation ou un retour à la ligne reste dans sa ligne ; une valeur CHAR perd ses blancs de remplissage ; un décimal se lit avec le séparateur du poste ; une valeur vide (NULL, texte vide) est un seul membre (vide), rangé en dernier ; les libellés posés par is_label survivent au rechargement. 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_from_datastore (datawindow adw)Le même chargement depuis un DataWindow : le croisé lit ses lignes par un DataStore de travail qui les partage (ShareData, vérifié) — ce que le DataWindow montre, le tableau le croise. Rend 0, -5 quand adw n'est pas valide, n'a pas de colonne ou ne peut pas partager ses lignes (of_get_last_error dit lequel), -2 quand le composant n'est pas créé
of_from_datastore (datawindowchild adwc)Le même chargement depuis un DataWindowChild (un DataWindow déroulant). Mêmes retours
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)
of_value (integer ai_position) → n_pbt_crosstab_valueRenvoie le handle d'une valeur de la zone Valeurs, par sa position (1 = la première), pour régler son agrégation, son affichage, son format ou son libellé (voir Propriétés d'une valeur)
of_value_count ( ) → longLe nombre de valeurs de la zone Valeurs (un champ qui y est deux fois compte deux fois) ; 0 quand la zone est vide ou que le composant n'est pas créé

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). Un champ déjà en Valeurs y reste : il peut être aux deux (un comptage distinct par membre). Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas ou pour une mesure calculée (elle va en Valeurs seulement), -2 si le composant n'est pas créé
of_add_col_field (string as_field)Ajoute un champ en zone Colonnes. Un champ déjà en Valeurs y reste : il peut être aux deux (un comptage distinct par membre). Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas ou pour une mesure calculée (elle va en Valeurs seulement), -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 (AGG_*, vide = AGG_SUM). Le champ garde sa place en Lignes, Colonnes ou Filtres. Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas ou une autre agrégation, -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é, -5 pour un champ que les données n'ont pas ou pour une mesure calculée (elle va en Valeurs seulement), -2 si le composant n'est pas créé
of_remove_field (string as_field)Retire un champ du tableau : de toutes les zones où il est (Lignes, Colonnes, Filtres, et chacune de ses valeurs), avec ses filtres — comme « Retirer » depuis la liste des champs. Le reste de la disposition reste tel quel, et ue_layout_changed dit la nouvelle. Renvoie 0 une fois appliqué, -4 quand le champ n'est dans aucune zone, -5 pour un champ que les données n'ont pas
of_remove_value (integer ai_position)Retire une valeur de la zone Valeurs, par sa position (la 2ᵉ « Somme de Montant » pendant que la 1ʳᵉ reste) ; avec la dernière valeur d'un champ placé nulle part ailleurs partent ses filtres. Renvoie 0 une fois appliqué, -5 pour une position sans valeur, -2 si le composant n'est pas créé
of_move_value (integer ai_from, integer ai_to)Déplace une valeur de la zone Valeurs d'une position à une autre (1 = la première) : ses réglages, les filtres et le tri qui la nomment la suivent. Renvoie 0 une fois appliqué, -5 pour une position sans valeur, -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 (le nombre de valeurs non vides, comme le Nombre d'Excel), AGG_COUNT_ROWS (le nombre de lignes, vides comprises), AGG_DISTINCT_COUNT (comptage de valeurs distinctes, les cellules vides ignorées comme dans Excel), AGG_AVG, AGG_MIN, AGG_MAX. Une somme sans aucune valeur est une cellule vide, pas 0 ; une « différence avec la précédente » dont l'une des deux cellules est vide est vide aussi. Les pourcentages s'écrivent selon la langue d'affichage (57.9% en anglais, 57,9 % en français).

Totaux et sous-totaux #

Les totaux se règlent par propriétés, décrites plus haut : ib_row_grand_total et ib_col_grand_total pour les totaux généraux, ib_row_subtotals et ib_col_subtotals pour les sous-totaux, is_totals_position pour la place de la ligne de total général. Le menu Options de la grille (la roue dentée) offre les mêmes quatre interrupteurs, une entrée par axe : Ligne de total général, Colonne de total général, Sous-totaux des lignes, Sous-totaux des colonnes.

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) et s'écrivent comme la grille les garde (voir sous le tableau) ; une liste vide retire le filtre, comme of_set_member_order. Un champ placé dans aucune zone rejoint la zone Filtres, pour que le filtre se voie. Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas, -2 si le composant n'est pas créé
of_clear_member_filter (string as_field)Retire ce filtre. Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas, -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 de la mesure n° ai_measure_index (la première = 1) : VALUEFILTER_TOP ou VALUEFILTER_BOTTOM (les ad_a premiers ou derniers), VALUEFILTER_GT, VALUEFILTER_LT (au-dessus, en dessous de ad_a), VALUEFILTER_BETWEEN (entre ad_a et ad_b). Sur un champ placé sous un autre de sa zone, le classement se fait à l'intérieur de chaque parent (la meilleure ville de chaque région) ; au premier niveau, sur tout le tableau. Les ex-aequo du N-ième restent tous. Le champ doit être en Lignes ou en Colonnes. Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas, un autre type, un Top ou un Bottom de moins de 1 membre ou une position sans mesure en Valeurs, -2 si le composant n'est pas créé
of_clear_value_filter (string as_field)Retire ce filtre. Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas, -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 numérique sur la valeur du champ lui-même : LABEL_GT, LABEL_LT, LABEL_BETWEEN. Un champ date prend des jours, par la surcharge texte. Le champ doit être en Lignes ou en Colonnes. Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas, un autre type ou un champ date, -2 si le composant n'est pas créé
of_set_label_filter (string as_field, string as_type, string as_a, string as_b)Filtre texte sur la valeur du champ : LABEL_CONTAINS, LABEL_BEGINS, LABEL_ENDS (as_a, sans tenir compte de la casse ; as_b ne sert pas). Sur un champ date : LABEL_BEFORE, LABEL_AFTER (as_a, un jour écrit yyyy-mm-dd) ou LABEL_BETWEEN (as_a et as_b, jours compris). Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas, un type qui ne convient pas au champ (LABEL_CONTAINS sur une date, LABEL_BEFORE sur un texte), un texte vide ou un jour qui n'en est pas un, -2 si le composant n'est pas créé
of_clear_label_filter (string as_field)Retire ce filtre. Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas, -2 si le composant n'est pas créé

Les valeurs que of_set_member_filter et of_set_member_order attendent sont celles que la grille garde, pas toujours celles qu'elle affiche : sur un champ dérivé d'une date, un mois s'écrit "01"…"12", un trimestre "1"…"4", une année "2024" ; un membre date non groupé s'écrit yyyy-mm-dd. L'utilisateur, lui, trouve le filtre de libellé dans le menu de la puce de tout champ — texte, nombre ou date.

Mettre en forme #

Le format d'une valeur se pose par propriété : of_field("amount").is_format = "$#,##0.00" pour toutes les valeurs d'un champ, of_value(2).is_format pour une seule (voir Propriétés d'un champ et Propriétés d'une valeur). C'est un format d'affichage PowerBuilder, lu par le moteur de masques de la bibliothèque ; vide, la valeur prend le Format de la colonne du DataWindow. Les négatifs en rouge : of_value(n).ib_negative_red = true. Le séparateur de milliers des valeurs sans format reste is_thousands, la devise du menu de la puce is_currency_symbol.

MéthodeRôle
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 pour un champ que les données n'ont pas, -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, datetime ou timestamp (une heure seule n'a pas de date à regrouper) : DATE_YEAR, DATE_QUARTER ou DATE_MONTH, nommé <colonne>__<partie>. Il rejoint la liste des champs et s'emploie comme les autres. Renvoie 0 une fois appliqué, -5 pour un champ que les données n'ont pas, une autre partie ou un champ qui ne contient pas de dates (un texte, une heure), -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 ("[amount] * 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 créé, -5 pour un nom ou une formule vide, un nom qui contient / ou `, un nom déjà pris par une colonne ou une formule qui ne se compile pas — la raison est alors dans of_get_last_error, et ue_calc_field_error est levé —, -2` si le composant n'est pas créé
of_remove_calc_field (string as_name)Retire un champ calculé. Renvoie 0 une fois appliqué, -5 si aucun champ calculé ne porte ce nom, -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. Sa formule ne cite que des champs de données : une autre mesure calculée est refusée. Mêmes codes que of_add_calc_field : 0 une fois créée, -5 (la raison dans of_get_last_error, ue_calc_field_error levé), -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é, -5 si aucune mesure calculée ne porte ce nom, -2 si le composant n'est pas créé

Une formule accepte les opérateurs + - * / ( ), des nombres (12, 0.5, .5, 1e3, toujours avec un point) et des champs entre crochets ; ^ n'est pas un opérateur. 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 ET de colonnes. 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é, -5 sous 1, -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 (le JSON d'of_get_layout ou d'ue_layout_changed, tel quel). ue_layout_changed dit la nouvelle disposition une fois, rien si c'est celle déjà affichée ; les poignées de valeur sont libérées. Renvoie 0 une fois appliqué, -5 pour un texte vide ou qui n'est pas du JSON, -2 si le composant n'est pas créé
of_get_cell_value (string as_row_tuple_json, string as_col_tuple_json, integer ai_measure, ref double ad_value)Lit la valeur d'une cellule, celle que le tableau affiche (0,8 sous 80,0 %, le cumul sous un cumul). Sa ligne et sa colonne sont les tuples que donne ue_cell_double_clicked — {"region":"Nord","city":"Lille"}, un membre vide null, {} (ou une chaîne vide) pour le total — et ai_measure est la position de la valeur dans la zone Valeurs, à partir de 1. Un tuple nomme les champs de sa zone depuis le premier : {"region":"Nord"} est le sous-total de Nord. La réponse ne dépend pas de l'affichage : une branche repliée ou des sous-totaux masqués se lisent quand même. Renvoie 0 avec la valeur dans ad_value (NULL pour une cellule sans valeur), -4 quand le tableau n'a pas cette cellule (un membre ou un champ qu'il ne montre pas là) ou ne peut pas être lu maintenant — appelé depuis un événement que la grille a levé pendant qu'un ordre de ce script attendait : of_get_last_error le dit, lisez après l'événement —, -5 pour un tuple mal formé ou une position sans mesure, -2 si le composant n'est pas créé
of_get_cell_rows (string as_row_tuple_json, string as_col_tuple_json, ref long al_rows[]) → longLes lignes du DataStore qu'une cellule agrège (drill-through) : al_rows[] reçoit leurs numéros de ligne et le nombre est rendu. La cellule s'adresse comme pour of_get_cell_value, sans mesure — les mêmes lignes nourrissent toutes les valeurs de la cellule. Les numéros sont ceux que les lignes ont maintenant dans le DataStore donné à of_from_datastore, même après un tri ou un filtre de ce DataStore ; seules comptent les lignes qui passent tous les filtres du tableau ; en mode démo, les lignes au-delà du plafond ne servent pas, donc ne sont pas rendues ; une ligne supprimée depuis le chargement est omise. Rechargez la grille après un Retrieve. Renvoie le nombre (0 ou plus), -4 quand le tableau n'a pas cette cellule ou ne peut pas être lu maintenant (voir of_get_cell_value), -5 pour un tuple mal formé, -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. Les nombres prennent le séparateur décimal de la langue d'affichage (virgule en français, allemand, italien, espagnol, portugais) ; un libellé qui se lirait comme une formule (= + - @) est écrit précédé d'une apostrophe. 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 tel qu'il s'affiche, mise en forme comprise (négatifs en rouge compris) ; ue_xlsx_saved confirme — ou refuse avec sa raison au-delà de 16 384 colonnes ou 1 048 576 lignes, les limites d'une feuille Excel. 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)La disposition a changé — par l'utilisateur (déplacer un champ, changer une agrégation, trier une colonne, replier un groupe…) ou par le code : tout ordre qui change ce que rend of_get_layout (of_set_layout compris) le lève une fois, et une seule fois à la fin d'une construction encadrée par of_set_redraw(false) … of_set_redraw(true) ; jamais quand rien ne change, ni pour of_reset. as_layout_json est tout ce qu'of_get_layout sauvegarde
ue_cell_double_clicked (string as_row_tuple_json, string as_col_tuple_json, integer ai_measure, double ad_value)Double-clic sur une cellule : les deux premiers arguments décrivent le croisement ({"region":"Nord","city":"Lille"}, un membre vide y vaut null, {} pour le total), ai_measure la position de la valeur double-cliquée dans la zone Valeurs, à partir de 1, et ad_value la valeur que la cellule affiche — 0.8 sous « 80,0 % », le cumul sous un cumul — ou NULL pour une cellule vide. Les deux tuples et ai_measure forment l'adresse que of_get_cell_value relit. 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) : la grille a déjà mis le texte dans le presse-papiers, l'événement vous remet le même texte — pour le journaliser ou le reprendre ailleurs. as_tsv est un texte séparé par des tabulations qui porte par défaut les en-têtes — les noms des colonnes sur une première ligne, le membre de chaque ligne (son chemin entier, « Nord / Lille ») dans une première colonne — ou les chiffres seuls avec ib_copy_headers = false. En mode démo la copie est un export : refusée, et la grille le dit
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, la copie mise à part (elle lève ue_copy), remonte dans ue_layout_changed : couplé à of_get_layout / of_set_layout, cela permet d'offrir des « vues enregistrées » à vos utilisateurs.

Au clavier. La construction du tableau se fait sans souris — puces, menus, replis et tris : 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à. Maj+F10 ou la touche Menu ouvrent aussi ce menu, et le focus suit le champ qu'on vient de déplacer, même dans une autre zone. Sélectionner des cellules et les copier (Ctrl+C) demandent la souris aujourd'hui.


Exemples #

Un rapport de ventes complet #

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

// Colonnes : une par annee
uo_crosstab.of_add_col_field(/*field*/ "year")

// Cellules : le montant total
uo_crosstab.of_add_value_field(/*field*/ "amount", /*agg*/ uo_crosstab.AGG_SUM)

// Presentation : montants lisibles, sous-totaux et totaux generaux
uo_crosstab.of_field(/*field*/ "amount").is_format = "$#,##0"
uo_crosstab.ib_row_subtotals = true
uo_crosstab.ib_row_grand_total = true
uo_crosstab.ib_col_grand_total = true

// Colorer les cellules pour reperer les gros montants d'un coup d'oeil
uo_crosstab.of_field(/*field*/ "amount").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_from_datastore.

// Les donnees d'abord : les champs prennent le nom des colonnes
uo_crosstab.of_from_datastore(/*ads*/ lds)

// Puis un libelle lisible pour chaque champ
uo_crosstab.of_field(/*field*/ "region").is_label    = "Region"
uo_crosstab.of_field(/*field*/ "city").is_label     = "Ville"
uo_crosstab.of_field(/*field*/ "category").is_label = "Categorie"
uo_crosstab.of_field(/*field*/ "year").is_label     = "Annee"
uo_crosstab.of_field(/*field*/ "amount").is_label   = "Chiffre d'affaires"
uo_crosstab.of_field(/*field*/ "quantity").is_label  = "Quantite"

Analyser des parts plutôt que des montants #

// Local variables
n_pbt_crosstab_field lnv_amount

// Repartir de zones vides, puis placer les champs
uo_crosstab.of_clear_layout()
uo_crosstab.of_add_row_field(/*field*/ "category")
uo_crosstab.of_add_col_field(/*field*/ "year")
uo_crosstab.of_add_value_field(/*field*/ "amount", /*agg*/ uo_crosstab.AGG_SUM)

// Ne garder que deux categories a l'ecran (valeurs separees par une tabulation)
uo_crosstab.of_set_member_filter(/*field*/ "category", /*values_tab*/ "Informatique~tMobilier")

// Le champ Montant, pour regler son affichage
lnv_amount = uo_crosstab.of_field(/*field*/ "amount")

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

// Une petite barre dans chaque cellule pour comparer les parts d'un regard
lnv_amount.is_conditional_formatting = lnv_amount.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.

// Repartir de zones vides, puis placer les champs
uo_crosstab.of_clear_layout()
uo_crosstab.of_add_row_field(/*field*/ "region")

// Les deux totaux qui vont servir de base au calcul
uo_crosstab.of_add_value_field(/*field*/ "amount",  /*agg*/ uo_crosstab.AGG_SUM)
uo_crosstab.of_add_value_field(/*field*/ "quantity", /*agg*/ uo_crosstab.AGG_SUM)

// Prix moyen = montant total divise par quantite totale
uo_crosstab.of_add_calc_measure(/*name*/ "avg_price", /*label*/ "Prix moyen", /*formula*/ "[amount] / [quantity]")
uo_crosstab.of_field(/*field*/ "avg_price").is_format = "$#,##0.00"

// Puis la placer parmi les valeurs comme n'importe quel champ (l'agregat ne compte pas : une mesure se calcule)
uo_crosstab.of_add_value_field(/*field*/ "avg_price", /*agg*/ uo_crosstab.AGG_SUM)
// event ue_calc_field_error de uo_crosstab : (string as_field, string as_message)
// Formule invalide : prevenir sans rien casser, le tableau reste affiche.
uo_status.of_panel(/*key*/ "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 sale_date
uo_crosstab.of_group_date_field(/*field*/ "sale_date", /*part*/ uo_crosstab.DATE_YEAR)
uo_crosstab.of_group_date_field(/*field*/ "sale_date", /*part*/ uo_crosstab.DATE_QUARTER)
uo_crosstab.of_group_date_field(/*field*/ "sale_date", /*part*/ uo_crosstab.DATE_MONTH)

// Puis les croiser comme n'importe quel champ : annee en colonnes, trimestre en dessous
uo_crosstab.of_add_col_field(/*field*/ "sale_date__year")
uo_crosstab.of_add_col_field(/*field*/ "sale_date__quarter")

Le palmarès des dix meilleures régions #

// Ne garder que les 10 regions au plus fort total sur la premiere mesure
uo_crosstab.of_set_value_filter(/*field*/ "region", /*type*/ uo_crosstab.VALUEFILTER_TOP, /*a*/ 10, /*b*/ 0, /*measure_index*/ 1)

Sur un champ placé sous un autre, le classement se fait à l'intérieur de chaque parent : la meilleure ville de chaque région, pas la meilleure ville du pays. Les ex-aequo restent tous.

// Les villes sous les regions
uo_crosstab.of_add_row_field(/*field*/ "region")
uo_crosstab.of_add_row_field(/*field*/ "city")

// La meilleure ville de chaque region, sur la premiere mesure
uo_crosstab.of_set_value_filter(/*field*/ "city", /*type*/ uo_crosstab.VALUEFILTER_TOP, /*a*/ 1, /*b*/ 0, /*measure_index*/ 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_crosstab.of_export_xlsx(/*path*/ "C:\temp\ventes.xlsx")
// event ue_xlsx_saved de uo_crosstab : (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_crosstab.of_export_csv(/*path*/ "C:\exports\ventes.csv")

Offrir des vues enregistrées #

// Local variables
string ls_view

// Enregistrer la vue courante : of_get_layout la rend tout de suite
ls_view = uo_crosstab.of_get_layout()

// Conserver le contenu TEL QUEL : il se rejoue sans transformation.
of_save_view(is_current_view, ls_view)
// Plus tard : rejouer une vue enregistree
uo_crosstab.of_set_layout(/*state_json*/ of_read_view("Ventes par region"))

Descendre au détail derrière un chiffre #

// event ue_cell_double_clicked de uo_crosstab : (string as_row_tuple_json, string as_col_tuple_json, integer ai_measure, double ad_value)
// Local variables
long ll_rows[], ll_count, ll_i

// Les lignes du DataStore derriere le chiffre double-clique
ll_count = uo_crosstab.of_get_cell_rows(/*row_tuple_json*/ as_row_tuple_json, /*col_tuple_json*/ as_col_tuple_json, /*rows*/ ll_rows)

// Rien a montrer : aucune ligne, ou une cellule illisible maintenant
if ll_count <= 0 then return

// Les copier dans la liste de detail : ids_sales est le DataStore donne a of_from_datastore
dw_detail.Reset()
for ll_i = 1 to ll_count
	ids_sales.RowsCopy(ll_rows[ll_i], ll_rows[ll_i], Primary!, dw_detail, dw_detail.RowCount() + 1, Primary!)
next

Les deux tuples et ai_measure forment l'adresse de la cellule : of_get_cell_value la relit plus tard, telle que le tableau l'affiche à ce moment-là.

// ue_cell_double_clicked event of uo_crosstab : the address of the cell, read again later
// Local variables
double ld_value

// Read the cell again from its address : 0 when it exists
if uo_crosstab.of_get_cell_value(/*row_tuple_json*/ as_row_tuple_json, /*col_tuple_json*/ as_col_tuple_json, /*measure*/ ai_measure, /*value*/ ld_value) = 0 then

	// ld_value is what the cell shows ; NULL for an empty cell
end if

Une cellule se lit aussi sans clic, par ses membres : un tuple partiel désigne un sous-total. Et of_remove_field retire un champ de toutes ses zones d'un coup, filtres compris.

// The sales of Nord in 2025, first measure of Values
// Local variables
double ld_north

// A partial address : the region and the year, no other member
uo_crosstab.of_get_cell_value(/*row_tuple_json*/ '{"region":"Nord"}', /*col_tuple_json*/ '{"year":"2025"}', /*measure*/ 1, /*value*/ ld_north)

// Take the year out of the table, filters included
uo_crosstab.of_remove_field(/*field*/ "year")

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
is_accessible_name · is_accessible_descriptionLe nom et la description que dit un lecteur d'écran3.4 Propriétés et events optionnels
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