MercatorPenguin 3.0 of later maakt het mogelijk volledig aangepaste schermen te integreren die geen verband houden met de schermtypes die MercatorPenguin al ruim configureerbaar maakt (informatiebestanden, acties, verkopen, aankopen, enz.). CustomPages zijn pagina's die geschreven zijn in de MAUI-ontwikkelomgeving en die MercatorPenguin kan integreren. Het gaat dus om 100% maatwerkprogramma's, uitgevoerd in een autonoom project in Visual Studio.
De uitleg hieronder loopt vooruit op de ontwikkeling voor zowel Android als iOS. Het is echter mogelijk om voor slechts één van deze twee platforms te ontwikkelen. Het enige wat je hoeft te doen is alle onderdelen negeren die betrekking hebben op het platform dat niet wordt geïmplementeerd.
CustomPages vereisen de Core-versie van MercatorPenguinServer. Indien nodig moet de klassieke versie van MercatorPenguinServer worden vervangen door de Core-versie en moet de Mercator die eraan is gekoppeld worden geüpgraded naar de Core-versie.
1. Voorbereiding van het project
Een CustomPage-project moet in Visual Studio worden geïnitieerd als een .net MAUI class library. Dit project moet Android en/of iOS als TargetFramework hebben. De andere platforms die niet worden ondersteund door MercatorPenguin kunnen uit het project worden verwijderd. Ook kunnen in de Platforms-map de niet-gebruikte platforms worden verwijderd.
In het project vinden we bijvoorbeeld het volgende:
<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>
Het project moet minimaal deze nugets refereren:
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="9.9.9" />
<PackageReference Include="BarcodeScanning.Native.Maui" Version="9.9.9" />
</ItemGroup>
Voor deze twee nugets is het noodzakelijk om 9.9.9 te vervangen door de versie die zichtbaar is in het MercatorPenguin-informatievenster ("Nugets" link onderaan).
Het moet ook de versie van MercatorTunnel.dll refereren die overeenkomt met elk van de platforms:
<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>
Deze verschillende versies kunnen vanaf deze pagina worden gedownload.
Zie ook: Configuratie van een CustomPage met één of meerdere assemblies als referentie
2. Creatie van de CustomPage
In het project moet een nieuwe .net MAUI contentpagina worden toegevoegd in XAML-formaat. Deze pagina moet echter erven van MercatorPenguin.BaseCustomPage, die bestaat in MercatorTunnel.dll
Hiervoor moeten de volgende wijzigingen worden aangebracht:
In de XAML-code, in de header waarin de namespaces (xmlns) worden vermeld, voeg je deze regel toe:
xmlns:t="clr-namespace:MercatorPenguin;assembly=MercatorTunnel"
en vervang vervolgens
<ContentPage
door
<t:BaseCustomPage
In de code-behind moet in de partiële definitie van de klasse
: ContentPage
worden vervangen door
: MercatorPenguin.BaseCustomPage
De constructor van de klasse moet ook worden aangepast
public CustomPage1(DataTable dataTable, string xaml)
:base(dataTable, xaml)
Opmerking: Alleen de pagina die door MercatorPenguin wordt geïnstantieerd, moet een BaseCustomPage zijn. Als de maatwerkontwikkeling gebruikmaakt van andere vensters, kunnen deze van een ander type zijn.
Een XAML-pagina kan niet verwijzen naar een weergave die in XAML is gedefinieerd. Een kindcomponent die is opgenomen in een pagina die in XAML is geschreven, moet daarom door code worden opgebouwd en niet via een XAML-representatie. (Zie bijvoorbeeld SearchView.cs in het TestPenguinCustom-project).
3. Beschikbaar stellen van assemblies voor MercatorPenguin
De assemblies moeten in de SQL-database worden geplaatst zodat ze door MercatorPenguin kunnen worden gedownload. Ze moeten worden geplaatst in SQL Bestanden > Anderen (en niet in Assemblies, omdat dit geen assemblies zijn die aan Mercator ERP moeten worden gedistribueerd).
Volgens conventie wordt in SQL Bestanden > Anderen een map aangemaakt met de naam van het project. Deze map bevat twee submappen:
Tijdens het compileren van het project produceert Mercator twee aparte bin-mappen: één voor Android en één voor iOS. Je moet de assembly van dit project voor elk platform vinden en opslaan in de hierboven aangemaakte overeenkomstige map. Deze bin-mappen bevatten een groot aantal DLL's. Alleen de DLL die bij het project hoort, moet in de Mercator-database worden geplaatst. De andere DLL's maken al deel uit van MercatorPenguin.
Samengevat: je moet alleen MijnnProject.dll voor Android en MijnProject.dll voor iOS in de Mercator-database plaatsen. Het PDB-bestand (debuginformatie) wordt automatisch meegeleverd.
Voorbeeld voor een project genaamd TestPenguinCustom.csproj :

4. Configuratie van een bundel die de CustomPage weergeeft
De toegang tot een CustomPage gebeurt altijd vanuit een lijsttype bundel en kan op elk punt van deze bundel plaatsvinden. In die stap moet de ListViewCell in de XAML-code alleen het component <CustomPage /> bevatten in de vorm:
<CustomPage>
<Android>
<MainAssembly><Other\TestPenguinCustom\Android\TestPenguinCustom.dll</MainAssembly>
</Android>
<iOS>
<MainAssembly><Other\TestPenguinCustom\iOS\TestPenguinCustom.dll</MainAssembly>
</iOS>
<ClassName>TestPenguinCustom.CustomPage1</ClassName>
</CustomPage>
Dit component bevat de volgende elementen:
- het pad naar de assembly voor Android,
- het pad naar de assembly voor iOS,
- de naam van de klasse die overeenkomt met de CustomPage die MercatorPenguin gaat instantiëren. Dit is de volledige naam van de klasse: namespace, gevolgd door een punt, gevolgd door de naam van de klasse.
Een bundel kan maar één CustomPage-component bevatten, maar dit kan op elke stap.
Net zoals bij een klassieke lijstbundel, zal de overgang naar de volgende stap de SQL-query van de huidige stap uitvoeren en een DataTable produceren. Normaal gesproken wordt deze DataTable weergegeven als een ListView in de volgende stap. Bij een CustomPage wordt deze weergegeven en ontvangen ze deze DataTable via hun constructor. De CustomPage kan dus rekening houden met gegevens van de vorige stap.
De tweede parameter die naar de xaml constructor wordt doorgegeven, bevat de volledige code van de ListViewCell. Zo kunnen extra informatie en parameters rechtstreeks vanuit de configuratie van de bundel naar de CustomPage worden doorgegeven.
Tips: Elke assembly die een CustomPage bevat, is gekoppeld aan de bijbehorende bundel en wordt samen met de configuratie van de bundel getransporteerd. Dit betekent het volgende:
- Als de assembly opnieuw is gecompileerd en de versie is bijgewerkt in de database, moet de bundellijst worden bijgewerkt (door deze naar beneden te slepen). Er wordt nooit rekening gehouden met de versie van de assembly (bijvoorbeeld in de vorm 1.0.10.1).
- Het wordt aanbevolen om voor elke CustomPage een aparte assembly te maken (dus een apart project). Als een assembly veel CustomPages bevat, wordt deze gekoppeld aan elke bundel die gebruik maakt van een CustomPage.
Indien de CustomPage geen gebruik van data uit de SQL-query van de bundel maakt, dan kunnen wij dit aangeven. Dit bespaart een verbinding met de server om onnodige gegevens te verkrijgen
<CustomPage>
<ClassName>...</ClassName>
<NoData>True</NoData>
</CustomPage>
5. Tools beschikbaar voor ontwikkelaars
Ontwikkelaars van CustomPages hebben toegang tot alle functionaliteiten van MercatorTunnel die zijn overgebracht naar de Android- en iOS-omgevingen. Zo zijn de meeste methoden van MercatorApi en uitbreidingen van MercatorExtensions beschikbaar.
Aangezien Mercator geen toegang heeft tot de SQL-server, zijn de functies die een verbinding met de SQL-server gebruiken echter niet beschikbaar (zoals Zselect, xLookup, enz.). Zie hieronder voor GetRunSqlDataSet en GetRunSqlData.
Weergeven van een eenvoudige dialoog
MercatorPenguin.Dialogs.Stop(this, "Dit is een bericht!");
this komt overeen met de huidige pagina.
Ophalen van gegevens in de vorm van een DataSet vanuit de SQL-database
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"])]);
Ophalen van getypeerde gegevens vanuit de SQL-database (List<T>)
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"])]);
Voor beide methoden wordt het resultaat ontvangen in de vorm van een MercatorPenguin.Classes.GetRunSqlRet<T>, met twee eigenschappen:
- Result : de DataSet of List<T>. Bij een fout is deze null.
- Error : het foutbericht, indien van toepassing, anders null.
Een barcode scannen met de camera
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, "Geen barcode gelezen!");
else
_ = MercatorPenguin.Dialogs.Stop(this, $"Barcode gelezen = {barcode}!");
}
Wijzigen van een informatiebestand (een klant)
private async void button6_Clicked(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(entryCli.Text))
{
_ = MercatorPenguin.Dialogs.Stop(this, "Je moet eerst een klant kiezen!");
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, "De fiche is niet aangepast!");
else
await MercatorPenguin.Dialogs.Stop(this, "De fiche is aangepast en opgeslagen!");
button6.IsEnabled = true;
}
Merk op dat de eigenschappen van de EditSigDescriptor identiek zijn aan die van de SigEditButton.
Een nieuw informatiebestand aanmaken (een artikel)
private async void button7_Clicked(object sender, EventArgs e)
{
button7.IsEnabled = false;
try
{
if (!await MercatorPenguin.Dialogs.AnswerYesNo(this, "Wil je echt een nieuw artikel aanmaken?"))
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, $"Het artikel \"{id_art}\" is aangemaakt!");
}
finally
{
button7.IsEnabled = true;
}
}
Merk op dat de eigenschappen van de EditSigDescriptor identiek zijn aan die van de SigEditButton.
Een CRM-actie aanmaken
private async void button8_Clicked(object sender, EventArgs e)
{
button8.IsEnabled = false;
try
{
if (!await MercatorPenguin.Dialogs.AnswerYesNo(this, $"Wil je echt een taak aanmaken voor klant {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, $"De actie (id = \"{ret.ActionId}\") is aangemaakt!");
}
finally
{
button8.IsEnabled = true;
}
}
Merk op dat de eigenschappen van de EditActionDescriptor identiek zijn aan die van de ActionEditButton.
Een verkoop aanmaken
private async void button9_Clicked(object sender, EventArgs e)
{
button9.IsEnabled = false;
try
{
if (!await MercatorPenguin.Dialogs.AnswerYesNo(this, $"Wil je echt een verkoop (concept) aanmaken voor klant {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;
}
}
Merk op dat de eigenschappen van de EditBillingDescriptor identiek zijn aan die van de BillingEditButton.
Een afbeelding vastleggen (foto, handtekening)
In de header van de XAML-code:
xmlns:tc="clr-namespace:MercatorPenguin.Controls;assembly=MercatorTunnel"
In de body van de XAML-code:
<tc:EditableImage x:Name="editableImage" HeightRequest="150" HorizontalOptions="Fill" BytesChanged="editableImage_BytesChanged" />
In de code-behind:
private void editableImage_BytesChanged(object sender, EventArgs e)
{
if (editableImage.Bytes != null)
_ = MercatorPenguin.Dialogs.Stop(this, $"Deze afbeelding is {editableImage.Bytes.Length / 1024} KB groot!");
else
_ = MercatorPenguin.Dialogs.Stop(this, "Geen afbeelding!");
}
Een tekstveld valideren volgens een informatiebestand (klantinvoer)
In de header van de XAML-code:
xmlns:tc="clr-namespace:MercatorPenguin.Controls;assembly=MercatorTunnel"
In de body van de XAML-code:
<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" />
In de code-behind:
entryCli.ActivityIndicator = activityIndicator;
entryCli.TargetSigLabel = targetSigLabel;
entryCli.AfterTargetSigSearch += (s, e) => entryCli.Unfocus();
Navigeren naar een andere bestaande bundel
Deze code maakt het mogelijk om naar een willekeurige stap van een andere bestaande bundel te navigeren, en zo terug te keren naar de gebruikelijke functies van MercatorPenguin.
Guid bundleId = new Guid("04774a89-e7c3-4941-b8da-e867fe42f7d4"); // Artikelen bekijken
ContentPage newPage = await JumpToBundle(this, "Detail " + 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)
{
// iets doen op deze pagina
}
Platform-specifieke code uitvoeren
De volgende tags bepalen platformspecifieke codeblokken, d.w.z. specifiek voor Android of iOS.
- #if ANDROID
- #if IOS
- hetzelfde als #elif ( = else if)
- #endif
De klasse GetDeviceInfo bevat native platformspecifieke code voor elk platform.
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
}
}
}
}
Op maat gemaakte code uitvoeren in Mercator via MercatorPenguinServer
Zie deze pagina.
Opmerking: het is onmogelijk om alle tools die beschikbaar zijn voor CustomPage-ontwikkelaars op te sommen. Ze zijn talrijk en constant in ontwikkeling. Het project in de bijgevoegde zip bevat voorbeelden van de hierboven geïllustreerde code in compileerbare vorm. IntelliSense onthult ook alle functies die verborgen zijn in de Mercator-code.
Te laden :
0000003242.zip (15 Kb - 03-10-2024)