MercatorPenguin et CustomPages

0000003242     -      02/11/2024

MercatorPenguin 3.0 ou ultérieur permet l'intégration d'écrans totalement personnalisés qui ne concernent aucun des types d'écrans que MercatorPenguin permet déjà de configurer très largement (signalétiques, actions, ventes, achats, …). Les CustomPages sont des pages écrites avec l'environnement de développement MAUI et que MercatorPenguin est capable d'intégrer. Il s'agit donc bien de programmations 100% sur mesure, effectuées dans un projet autonome en Visual Studio.

Les explications données ci-dessous prévoient un développement effectué à la fois pour Android et pour iOS. Il est toutefois possible de n'effectuer ce développement que pour une seule de ces deux plateformes. Il suffit pour cela d'ignorer toutes les parties qui sont relatives à la plateforme non implémentée.

Les CustomPages requièrent la version Core de MercatorPenguinServer. Le cas échéant, il est nécessaire de remplacer la version classique de MercatorPenguinServer par la version Core et d'upgrader le Mercator qui y est lié en version Core.

1. Préparation du projet

Un projet de CustomPage doit être initialisé dans VisualStudio sous la forme d'une bibliothèque de classes .net MAUI (.net MAUI class library). Ce projet doit avoir comme TargetFramework Android et/ou iOS. Les autres plateformes n'étant pas prises en charge par MercatorPenguin peuvent être retirées du projet. De même, dans le répertoire Platforms, on pourra supprimer les plateformes non utilisées.

Dans le projet, on trouvera donc, par exemple, ceci :

<PropertyGroup>
<TargetFrameworks>net8.0-android;net8.0-ios</TargetFrameworks>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">11.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
</PropertyGroup>

 Le projet doit au minimum référencer ces nugets :

<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="9.9.9" />
<PackageReference Include="BarcodeScanning.Native.Maui" Version="9.9.9" />
</ItemGroup>

Pour ces deux nugets, il est nécessaire de remplacer 9.9.9 par la version visible dans la fenêtre d'information de MercatorPenguin (lien "Nugets" dans le bas).

Il doit aussi référencer la version de MercatorTunnel.dll correspondant à chacune des plateformes :

  <ItemGroup Condition="'$(TargetFramework)' == 'net8.0-android'">
    <Reference Include="MercatorTunnel">
      <HintPath>C:\...\ReleaseMaui\net8.0-android\MercatorTunnel.dll</HintPath>
    </Reference>
  </ItemGroup>

  <ItemGroup Condition="'$(TargetFramework)' == 'net8.0-ios'">
    <Reference Include="MercatorTunnel">
      <HintPath>C:\...\ReleaseMaui\net8.0-ios\MercatorTunnel.dll</HintPath>
    </Reference>
  </ItemGroup>

Ces différentes versions sont téléchargeables depuis cette page.

Voir aussi : Paramétrage d'une CustomPage avec une ou plusieurs assemblies en référence


2. Creation de la CustomPage

Dans le projet, il faut ajouter une nouvelle page de contenu .net MAUI, au format XAML. Cette page doit toutefois hériter de MercatorPenguin.BaseCustomPage, existant dans MercatorTunnel.dll

Pour cela, il faut effectuer ces modifications :

Dans le code XAML, dans l'entête reprenant les namespaces (xmlns), ajouter cette ligne :

xmlns:t="clr-namespace:MercatorPenguin;assembly=MercatorTunnel"

et ensuite remplacer 

<ContentPage 

par 

<t:BaseCustomPage

Dans le code behind, il faut remplacer dans la définition partielle de classe

: ContentPage

par 

: MercatorPenguin.BaseCustomPage

Le constructeur de la classe doit aussi être modifié

Zoom
public CustomPage1(DataTable dataTable, string xaml)
    :base(dataTable, xaml)

 

Note : seule la page qui sera instanciée par MercatorPenguin doit être une BaseCustomPage. Si le développement sur mesure fait appel à d'autres fenêtres, celles-ci peuvent être d'un autre type.

  Une page XAML ne peut faire référence à une vue définie sous la forme XAML. Un composant enfant inclus dans une page écrite en XAML doit donc être construit par code et non par une représentation XAML. (Voir par exemple SearchView.cs dans le projet TestPenguinCustom).


3. Mise à disposition des assemblies pour MercatorPenguin

Les assemblies doivent être placées dans la base de données SQL afin de pouvoir être téléchargées par MercatorPenguin. Elles doivent être placées dans les fichiers SQL > Autres (et non Assemblies, car il ne s'agit pas d'assemblies qui doivent être distribuées à Mercator ERP).

Par convention, on créera dans les fichiers SQL > Autres, un répertoire portant le nom du projet. Ce répertoire contiendra deux sous-répertoires : 

  • Android
  • iOS

Lors de la compilation du projet, Mercator produit deux répertoires bin distincts, l'un pour Android, l'autre pour iOS. Il faut localiser l'assembly correspondant à ce projet pour chaque plateforme et la sauvegarder dans le répertoire correspondant créé ci-dessus. Ce répertoire bin contient un nombre important de DLL. Seule la DLL correspondant au projet doit être placée dans la base de données de Mercator. Les autres DLL font déjà partie de MercatorPenguin.

En résumé : il faut uniquement placer dans la base de données de Mercator, MonProjet.dll pour Android et MonProjet.dll pour iOS. Le fichier PDB (information de debug) suit automatiquement.

Exemple pour un projet nommé TestPenguinCustom.csproj :


4. Paramétrage d'un bundle qui va montrer la CustomPage

L'accès à une CustomPage se fait toujours au départ d'un bundle de type liste et à n'importe quelle étape de ce bundle. Cette étape doit contenir dans le code XAML ListViewCell uniquement le composant <CustomPage /> sous la forme :

<CustomPage>
<Android>
<MainAssembly><Other\TestPenguinCustom\Android\TestPenguinCustom.dll</MainAssembly>
</Android>
<iOS>
<MainAssembly><Other\TestPenguinCustom\iOS\TestPenguinCustom.dll</MainAssembly>
</iOS>
<ClassName>TestPenguinCustom.CustomPage1</ClassName>
</CustomPage>

On reconnait dans ce composant :

  • le chemin d'accès vers l'assembly sous Android,
  • le chemin d'accès vers l'assembly sous iOS,
  • le nom de la classe correspondant à la CustomPage que MercatorPenguin va instancier. Il s'agit du nom complet de cette classe : espace de nom, suivi d'un point, suivi du nom de la classe.

Un bundle ne peut contenir qu'un seul composant CustomPage, mais à n'importe quelle étape.

Tout comme pour un bundle de type liste classique, le passage à l'étape suivante va exécuter la requête SQL de l'étape en cours et produire une DataTable. Habituellement, cette DataTable est présentée sous la forme d'une ListView dans l'étape suivante. Dans le cas d'une CustomPage, cette dernière sera affichée et recevra, via son constructeur, cette DataTable. La CustomPage peut ainsi tenir compte de données de l'étape précédente.

Le second paramètre passé au constructeur xaml contient l'entièreté du code de la ListViewCell. Il est ainsi possible de passer des informations supplémentaires directement depuis le paramétrage du bundle vers la CustomPage.

Conseils : chaque assembly contenant une CustomPage est associée à son bundle et transportée avec le paramétrage du bundle. En conséquence :

  1. Si l'assembly a été recompilée et sa version mise à jour dans la base de données, il faut actualiser la liste des bundles (en la tirant vers le bas). La version de l'assembly (par exemple sous la forme 1.0.10.1) n'est jamais prise en compte.
  2. Il est recommandé de créer une assembly séparée pour chaque CustomPage (donc un projet séparé). Si un assembly contient un grand nombre de CustomPages, elle sera attachée à chacun des bundles faisant appel à une CustomPage.

Si la CustomPage n'utilise pas les données provenant de la requête SQL du bundle, alors on peut indiquer ceci. Cela permet d'économiser une connexion au serveur pour obtenir des données inutiles.

<CustomPage>
<ClassName>...</ClassName>
<NoData>True</NoData>
</CustomPage>

5. Outils mis à disposition des développeurs

Les développeurs de CustomPages ont accès à toutes les fonctionnalités de MercatorTunnel qui ont été portées vers les environnements Android et iOS. Ainsi, les méthodes de MercatorApi ou les extensions de MercatorExtensions sont pour la plupart disponibles.

Mercator ne disposant pas d'accès au serveur SQL, les fonctions utilisant une connexion au serveur SQL sont toutefois indisponibles (Zselect, xLookup, ...). Voir GetRunSqlDataSet et GetRunSqlData ci-dessous.

Afficher une simple boîte de dialogue 

Zoom
MercatorPenguin.Dialogs.Stop(this, "Ceci est un message !");

this correspond à la page en cours.

 

Obtenir des données sous la forme d'un DataSet depuis la base de données SQL

Zoom
var r = await GetRunSqlDataSet("select isnull(sum(tot_ttc_fb),0) as tot_ca from PIEDS_V where id_cli=@c_id", [new MercatorPenguin.RunSqlDescriptor.Parameter("@c_id", DataTable.Rows[0]["c_id"])]);

 

Obtenir des données typées depuis la base de données SQL (List<T>)

Zoom
var r = await GetRunSqlData<HistoDescriptor>("select top 20 type,id,journal,cast(piece as bigint) as piece,date,tot_ttc_dv as tot from PIEDS_V where id_cli=@c_id order by date desc", [new MercatorPenguin.RunSqlDescriptor.Parameter("@c_id", DataTable.Rows[0]["c_id"])]);

Pour ces deux méthodes, on recevra un résultat sous la forme d'un MercatorPenguin.Classes.GetRunSqlRet<T>. Il contient deux propriétés :

  • Result : le DataSet ou List<T>. En cas d'erreur, null.
  • Error : le message d'erreur le cas échéant, sinon null.

 

Scanner un code-barres avec l'appareil photo

Zoom
private async void button_Clicked(object sender, EventArgs e)
{
    await MercatorTunnel.PlatformMaui.Api.ScanBarCode(MercatorApi.Api.ApiLangue, this, Navigation, button4, Colors.White, ThemeColor, delegateSetResult: BarcodeScanned);
}

private void BarcodeScanned(string barcode)
{
    if (barcode == null) // on a annulé
        _ = MercatorPenguin.Dialogs.Stop(this, "Aucun code-barres lu !");
    else
        _ = MercatorPenguin.Dialogs.Stop(this, $"Code-barres lu = {barcode} !");
}

 

Modifier une fiche de signalétique (un client)

Zoom
private async void button6_Clicked(object sender, EventArgs e)
{
    if (string.IsNullOrWhiteSpace(entryCli.Text))
    {
        _ = MercatorPenguin.Dialogs.Stop(this, "Il faut d'abord choisir un client !");
        return;
    }
    button6.IsEnabled = false;
    string id_cli = await EditSig(new MercatorPenguin.Classes.EditSigDescriptor(this, activityIndicator) { Module = MercatorPenguin.Enums.SigEnum.CLI, IdSig = entryCli.Text });
    if (string.IsNullOrEmpty(id_cli))
        await MercatorPenguin.Dialogs.Stop(this, "La fiche n'a pas été modfiée !");
    else
        await MercatorPenguin.Dialogs.Stop(this, "La fiche a été modfiée et sauvegardée !");
    button6.IsEnabled = true;
}

Notez que les propriétés de l'EditSigDescriptor sont identiques à celles du SigEditButton.

 

Créer une nouvelle fiche de signalétique (un article)

Zoom
private async void button7_Clicked(object sender, EventArgs e)
{
    button7.IsEnabled = false;
    try
    {
        if (!await MercatorPenguin.Dialogs.AnswerYesNo(this, "Réellement créer un nouvel article ?"))
            return;
        string id_art = await EditSig(new MercatorPenguin.Classes.EditSigDescriptor(this, activityIndicator) { Module = MercatorPenguin.Enums.SigEnum.STOCK, IdSig = "" });
        if (!string.IsNullOrEmpty(id_art))
            await MercatorPenguin.Dialogs.Stop(this, $"L'article \"{id_art}\" a été créé !");
    }
    finally
    {
        button7.IsEnabled = true;
    }
}

Notez que les propriétés de l'EditSigDescriptor sont identiques à celles du SigEditButton.

 

Créer une action du CRM

Zoom
private async void button8_Clicked(object sender, EventArgs e)
{
    button8.IsEnabled = false;
    try
    {
        if (!await MercatorPenguin.Dialogs.AnswerYesNo(this, $"Réellement créer une tâche pour le client {DataTable.Rows[0]["c_nom"]}  ?"))
            return;
        MercatorPenguin.Classes.EditActionRet ret = await EditAction(new MercatorPenguin.Classes.EditActionDescriptor(this, activityIndicator)
        {
            ActionId = "",
            ActionModule = MercatorPenguin.Enums.SigEnum.CLI,
            ActionIdSig = DataTable.Rows[0]["c_id"].ToString(),
            ActionTempl = ".A-AF1E247"
        });
        if (ret != null)
            await MercatorPenguin.Dialogs.Stop(this, $"L'action (id =  \"{ret.ActionId}\") a été créée !");
    }
    finally
    {
        button8.IsEnabled = true;
    }
}

Notez que les propriétés de l'EditActionDescriptor sont identiques à celles du ActionEditButton.

 

Créer une vente

Zoom
private async void button9_Clicked(object sender, EventArgs e)
{
    button9.IsEnabled = false;
    try
    {
        if (!await MercatorPenguin.Dialogs.AnswerYesNo(this, $"Réellement créer une vente (brouillon) pour le client {DataTable.Rows[0]["c_nom"]}  ?"))
            return;
        await EditBilling(new MercatorPenguin.Classes.EditBillingDescriptor(this, activityIndicator)
        {
            BillingTypeVA = MercatorPenguin.Enums.TypeVAEnum.V,
            BillingJournal = "Brou",
            BillingId = "",
            BillingPiece = 0,
            BillingType = 7,
            DefaultValues = [new MercatorPenguin.Classes.ItemDefaultValue { ColumnName = "ID_CLI", Value = DataTable.Rows[0]["c_id"].ToString() }]
        });
    }
    finally
    {
        button9.IsEnabled = true;
    }
}

Notez que les propriétés de l'EditBillingDescriptor sont identiques à celles du BillingEditButton.

 

Capter une image (photo, signature)

Dans l'entête du code XAML : 

xmlns:tc="clr-namespace:MercatorPenguin.Controls;assembly=MercatorTunnel"

Dans le corps du code XAML :

<tc:EditableImage x:Name="editableImage" HeightRequest="150" HorizontalOptions="Fill" BytesChanged="editableImage_BytesChanged" />

Dans le code behind :

Zoom
private void editableImage_BytesChanged(object sender, EventArgs e)
{
    if (editableImage.Bytes != null)
        _ = MercatorPenguin.Dialogs.Stop(this, $"Cette image pèse {editableImage.Bytes.Length / 1024} Ko !");
    else
        _ = MercatorPenguin.Dialogs.Stop(this, "Pas d'image !");
}

 

Valider une zone de texte selon un signalétique (saisie d'un client)

Dans l'entête du code XAML : 

xmlns:tc="clr-namespace:MercatorPenguin.Controls;assembly=MercatorTunnel"

Dans le corps du code XAML :

<Grid ColumnSpacing="6" ColumnDefinitions="100, *" Margin="25,10,25,0">
<tc:Entry x:Name="entryCli" TargetSig="CLI" TargetSigNotFound="ToastLongBottom" TargetSigComplWhere="(c_sommeil=0)" HorizontalOptions="Fill" Grid.Column="0" />
<Label x:Name="targetSigLabel" HorizontalOptions="Fill" VerticalOptions="Center" LineBreakMode="TailTruncation" Grid.Column="1" />
</Grid>
<ActivityIndicator x:Name="activityIndicator" Color="White" IsVisible="False" />

Dans le code behind :

Zoom
entryCli.ActivityIndicator = activityIndicator;
entryCli.TargetSigLabel = targetSigLabel;
entryCli.AfterTargetSigSearch += (s, e) => entryCli.Unfocus();

 

Poursuivre le parcours d'un autre bundle existant

Ce code permet de se déplacer vers quelconque étape d'un autre bundle existant et donc de repasser dans les fonctionnalités habituelles de MercatorPenguin.

Zoom
Guid bundleId = new Guid("04774a89-e7c3-4941-b8da-e867fe42f7d4"); // Consultation articles
ContentPage newPage = await JumpToBundle(this, "Détail " + r.Result.Tables[0].Rows[0]["modele"].ToString().TrimEnd(), bundleId, 2, [new MercatorPenguin.RunSqlDescriptor.Parameter("@s_id", r.Result.Tables[0].Rows[0]["id_article"])]);
if (newPage != null)
{
    // faire qqch dans cette page
}

 

Exécuter du code platform specific

Les balises suivantes permettent de déterminer des zones de code platform specific, c.-à-d. propre à Android ou iOS.

  • #if ANDROID
  • #if IOS
  • les mêmes avec #elif ( = else if)
  • #endif

La classe GetDeviceInfo inclut du code platform specific natif pour chacune des plateformes.

Zoom
using System;
#if ANDROID
using AndroidApp = Android.App.Application;
using static Android.Provider.Settings;
#elif IOS
using UIKit;
#endif

namespace TestPenguinCustom
{
    internal static class GetDeviceInfo
    {
        internal static string Id
        {
            get
            {
#if ANDROID
                var context = AndroidApp.Context;
                return Secure.GetString(context.ContentResolver, Secure.AndroidId);
#elif IOS
                return UIDevice.CurrentDevice.IdentifierForVendor.ToString();
#endif
            }
        }
    }
}

 

Exécuter du code sur mesure dans l'instance de Mercator via MercatorPenguinServer

Voir cette page.

 

Note : il n'est pas possible de lister tous les outils mis à disposition des développeurs de CustomPages. Il sont très nombreux et en constante évolution. Le projet repris dans le zip ci-joint reprend les exemples illustrés ci-dessus sous forme compilable. Intellisense vous fera aussi découvrir toutes les fonctionnalités qui se cachent dans le code de Mercator.



A télécharger : 0000003242.zip (15 Kb - 03/10/2024)


Cookies fonctionnels : Cookies nécessaires à l'utilisation du site et cookies de préférence. Ils ne contiennent aucune donnée à caractère personnel. (En savoir plus)

Cookies statistiques : Captation de statistiques liées aux comportements des internautes. (En savoir plus)

Cookies marketing : Pour effectuer le suivi des visiteurs au travers des sites web, à des fins publicitaires. (En savoir plus)