Création d'un CRM avec Xamarin.Forms et Azure, partie 2
Une fois que vous avez ajouté le serveur à votre liste de sources de packages, vous pouvez cliquer avec le bouton droit sur la solution et sélectionner "Gérer les packages NuGet pour la solution". Cela vous permettra d'installer un package sur plusieurs projets en même temps. Lorsque le gestionnaire de packages NuGet apparaît, procédez comme suit:
- Sélectionnez "Telerik" comme source du package, puis
- Filtrez par " Telerik.UI.for.Xamarin "
- Sélectionnez le Projets Xamarin.Forms
- Cliquez sur installer
Vous êtes maintenant prêt à commencer à utiliser l'interface utilisateur pour les contrôles Xamarin! Le NuGet l'installation du package est notre approche recommandée. Il est plus rapide de démarrer et grâce à la résolution des dépendances NuGet Package Manager, il installera les autres dépendances requises pour l'interface utilisateur pour Xamarin (par exemple, Xamarin SkiaSharp packages et requis Xamarin Packages de la bibliothèque de support Android ). Si vous choisissez l'itinéraire de référence d'assembly, vous pouvez trouver les assemblys pour chaque projet dans des dossiers nommés dans le dossier d'installation de l'interface utilisateur Telerik pour Xamarin. Le chemin par défaut est: [19659011] C: Program Files (x86) Progress Telerik UI pour Xamarin [version] Binaries Dans le dossier Binaries, vous trouverez des sous-dossiers correspondant aux types de projet. Chacun de ces dossiers contient les assemblages qui doivent être ajoutés au projet. Option 2 – Références d'assembly installées
Pick and Choose Assembly References [19659008] Si vous n'utilisez qu'un ou deux contrôles et que vous ne prévoyez pas d'utiliser l'éditeur de liens pour supprimer les assemblys inutilisés du package d'application compilé, vous pouvez sélectionner et choisir des assemblys individuels dans ces dossiers. Pour connaître les assemblys requis sont pour chaque contrôle, consultez l'article du contrôle « Assemblages Telerik requis ». Vous constaterez que chaque contrôle a cet article sous le dossier «Mise en route». À titre d'exemple, voici l'emplacement de la RadListView Assemblages Telerik requis article: Afin que vous puissiez commencer à créer votre interface utilisateur dès que possible, je vous recommande de les ajouter tout au début pour que vous puissiez être opérationnel rapidement. Ensuite, une fois le projet terminé, vous pouvez revenir en arrière et supprimer les assemblys inutilisés si nécessaire. Comme je l'ai mentionné dans la section Option 1 ci-dessus, il y a quelques dépendances. Si vous choisissez l'itinéraire de référence d'assembly, vous devrez ajouter explicitement ces packages NuGet. Passons en revue les deux maintenant. Dépendance SkiaSharp Plusieurs des interfaces utilisateur des contrôles Xamarin dépendent des bibliothèques SkiaSharp de Xamarin. Ajoutez les packages suivants aux projets: Bibliothèques de support Android Xamarin Les contrôles Android natifs ont besoin de quelques bibliothèques de support Android Xamarin pour fonctionner correctement. Accédez à l'article Bibliothèques de support Android requises pour voir la liste complète. Vous ne les installez que dans le projet Android et il doit s'agir de la même version du SDK Android. Maintenant que l'interface utilisateur Telerik pour les références de Xamarin a été ajoutée , passons au SDK Azure Mobile Service Client. L'une des principales raisons pour lesquelles nous avons commencé avec l'application de démarrage de téléchargement est que le package NuGet du SDK Microsoft Azure Mobile Client est déjà installé et a une classe TodoItemManager qui utilise le SDK pour interagir avec le back-end. Ouvrez le fichier Constants.cs du projet de bibliothèque de classes et notez que l'URL du service sera déjà définie dans la propriété ApplicationURL, comme ceci: Il est maintenant temps de commencez à câbler le projet de bibliothèque de classes Xamarin.Forms pour pouvoir communiquer avec notre nouveau backend! La première chose que nous devons faire est de définir nos modèles. Notez le modèle de données TodoItem.cs fourni avec le projet. Avant de le supprimer, vous pouvez l'utiliser comme modèle pour les quatre classes de modèle de données dont vous avez besoin pour interagir avec le back-end: Une fois les modèles définis, nous nous tournons maintenant vers la classe TodoItemManager. C'est le bourreau de travail qui synchronise les données entre Azure et l'application, mais gère également la synchronisation de la base de données hors connexion si vous l'avez activée. Pour l'application Art Gallery CRM, nous avons besoin de quatre «classes de gestionnaire». Au lieu d'avoir plusieurs méthodes ou des surcharges compliquées, j'ai créé une interface qui définit ces opérations CRUD: [19659008] Nous avons également besoin d'un moyen de partager une seule instance de MobileServiceClient, sinon nous aurions besoin d'avoir des instances séparées du client dans chaque classe de service, ce qui augmenterait inutilement le l'empreinte mémoire de l'application. Une option que nous pouvons utiliser Il s'agit de placer la référence MobileServiceClient dans la classe App pour rendre la même instance disponible pour tous les services. ce didacticiel Xamarin.Forms Master-Details . À partir de ce point de l'article, supposons que l'infrastructure maître-détail est en place et que nous ajoutons des pages à cette configuration. Vous pouvez également référencer le code source de la démo sur GitHub pour suivre. J'ai utilisé une convention de dénomination dans laquelle les modèles d'affichage de liste, de détail et d'édition utilisent le même nom que les pages. Cela permet d'assumer facilement la responsabilité des modèles de vue: Commençons à créer notre interface utilisateur! J'aime parfois commencer avec le modèle de vue. que nous pouvons nous assurer que nous avons les propriétés dont nous avons besoin pour remplir la vue. C'est assez simple, une seule ObservableCollection pour contenir les éléments et une tâche pour charger les données dans la collection: Remarque: dans de nombreux endroits dans toute l'application, j'utilise un ObservableRangeCollection. Cela équivaut à un ObservableCollection, sauf que vous pouvez ajouter de nombreux éléments mais ne provoquer qu'une seule notification CollectionChanged, ce qui améliore les performances de liaison de données. Voyons comment les éléments sont récupérés à partir d'Azure: Cela appellera la méthode sur la classe de service DataStore pour l'entité Employee. Tout le travail lourd est soigneusement emballé dans le SDK lui-même ou dans la classe de service et maintient le modèle de vue très lisible! Avec le modèle de vue prêt, nous pouvons passer à l'interface utilisateur et à notre première interface utilisateur Telerik pour les contrôles Xamarin! Je ne veux pas seulement être un B le pour lister les employés, mais aussi les filtrer rapidement. Nous pouvons le faire avec une combinaison de l'interface utilisateur Telerik pour les contrôles Xamarin: Voici le XAML réduit pour afficher la disposition de haut niveau: <dataControls: RadListView ItemsSource = "{Binding Employees}" Style = "{StaticResource BaseListViewStyle}" Grid.Row = "1"> Nous devons maintenant créer un ItemTemplate pour le RadListView afin d'afficher le Propriétés de l'employé dont nous avons besoin, le nom et la photo. Le BindingContext de l'objet ItemTemplate est l'objet employé individuel de la collection FilteredItems. Par conséquent, nous pouvons lier la propriété Text d'une étiquette à "Nom" et lier le contrôle So d'un contrôle Image urce à "PhotoUri". Nous pouvons maintenant passer au filtrage en utilisant RadEntry et sa propriété Text: <input: RadEntry x: Name = "FilterEntry" Text = "{Binding SearchText, Mode = TwoWay} " WatermarkText =" Search " Margin =" 20,10,10,0 "> Le texte est lié à la propriété SearchText de EmployeesViewModel, où la logique de filtrage est implémentée à l'aide du ApplyFilter méthode. Cette méthode exécute un filtrage personnalisé, puis met à jour la collection Employees afin que seuls les éléments filtrés soient présents et visualisés dans RadListView: public string SearchText { get => _searchText; set { SetProperty (ref _searchText, value); this.ApplyFilter (value); } } Regardons maintenant le RadBusyIndicator . Ce contrôle offre un bel ensemble d'animations personnalisées prêtes à l'emploi, mais vous pouvez également créer vos propres animations personnalisées si vous en avez besoin. Puisque cette application est destinée à un public professionnel, je suis allé avec Animation6 . Voir l'article de documentation sur les animations pour plus de détails, voici une liste des animations intégrées. Lorsque la méthode OnAppearing de la page est appelée, le modèle de vue LoadDataAsync est appelé car nous devons utiliser du code asynchrone. Cela rend l'application beaucoup plus réactive et rapide que si j'avais effectué tous les calculs à partir de la page, le constructeur de modèle de vue ou l'utilisation de .Wait () sur des méthodes asynchrones. Remarque: Vous devez éviter d'utiliser Attendre autant que possible! Voici la méthode OnAppearing: Vous remarquerez que je configure une interface utilisateur ici aussi. La série de graphiques sera automatiquement mise à jour lorsque les ObservableCollections liées sont remplies, mais pour les jauges, je souhaite un contrôle un peu plus fin. Charts Commençons par les graphiques SalesHistory et Compensation. Ce sont simples. La série ItemsSources peut être liée directement aux collections de modèles de vues respectives: LineChart PieChart Jauges Les jauges sont définies dans le XAML, mais sont configurées dans les méthodes clairement nommées que vous avez vues dans OnAppearing. Examinons de plus près Vacation RadialGauge comme exemple de l'une de ces configurations. Voici le XAML, notez les x: Noms des composants de la jauge: Voici la méthode de configuration: [19659008] [19659008] Suppression d'un employé Également sur la page des détails, nous fournissons un moyen pour l'utilisateur de supprimer l'élément. Cela se fait à l'aide d'un événement de commande du bouton Supprimer de la barre d'outils. Cependant, la suppression d'un employé a des conséquences car les enregistrements de la table Commandes peuvent contenir des commandes avec l'ID de cet employé. Je dois d'abord vérifier s'ils sont associés à des commandes ouvertes et avertir l'utilisateur que la suppression de l'employé supprimera également les commandes. Voici l'action de la commande dans EmployeeDetailViewModel: private async Task DeleteEmployeeAsync () var result = await App.Current.MainPage.DisplayAlert ("Supprimer?", "Voulez-vous vraiment supprimer cet élément?", "Oui", "Non"); if (! Result) try // Do Referential Integrity Check if (matchingOrders.Count> 0 ) // Back out if user declines to delete associated orders // Delete each associated Order // Lastly, delete the employee await this.NavigateBackAsync(); Note: There are other ways to handle this scenario. Deleting the related orders is a rather drastic measure. In a real-world app, you’re more likely to re-set the ID or use a nullable field for EmployeeID. Editing an Employee Now we’re ready for some editing! I could have built this so that the edit form is built into the details page. This is a common approach, however I wanted to also provide a way to add an employee with the same edit form. I decided that a separate EmployeeEditPage would be better so I can keep the page code more readable and keep responsibilities separated. The same EmployeeDetailsPage toolbar that has the delete button also has buttons for Edit and New Order. They can all share the same view model Command, but invoke different operations depending on a predefined command parameter. Here's the view model command ToolbarItemTapped Action that will navigate to the EmployeeEditPagethe OrderEditPage or delete. We pass the SelectedEmployee to the constructor of both options. private async void ToolbarItemTapped(object obj) switch (obj.ToString()) Here’s the EmployeeDetailPage at runtime on Windows 10: This view model’s responsibility is simple: To do this use, I the following items in the view model: Let’s look at a shortened version of the UpdateDatabaseAsync method, notice that depending on the value of IsNewEmployeewe choose to either update or insert SelectedEmployee: Note: Some code omitted for clarity, see demo source for full method. So, how do we make the determination as to whether this is a new or existing employee? This is done using the page’s constructor. The Edit page’s XAML is very simple: Here’s the high-level layout: As you can see the DataForm’s Source is bound to the view model’s SelectedEmployee. The button’s click handler just assigns a random photo to the SelectedEmployee’s PhotoUri. The extra interesting part of this page lies in the Employee model’s property attributes that tell the RadDataForm how to create its Editors and how the edit page determines if we’re editing or adding an employee. Let’s start with the Employee model’s DataForm Annotations. Data Annotations The RadDataForm Data Annotations are model property attributes, which are used to set a wide range of DataForm features. You can find a list of the attributes in the RadDataForm documentation under the “Data Annotations” sub folder. For the employee model, I need to exclude the PhotoUri property because I want the button to set that value instead of a user-entered string. To do this, I can leverage the Ignore attribute: The Data Annotations let you set the editor display and validation options as well. On the Employee model, I also use the DisplayOptionsNonEmptyValidator and NumericalRangeValidator attributes on the Employee properties: Page Constructor and Configuring Editors As I mentioned at the beginning of this section, the page needs to decide if we’re editing an employee or creating a new one. This is simply achieved by using two constructors. If the overloaded constructor is used, that means we passed an existing employee object to edit. If the normal constructor was used, then we’re creating a new employee: In the ConfigureEditors method, we set the EditorType for each property. This tells the DataForm to use a specific Editor type, see the full list in the DataForm Editors documentation. We're almost done, the last remaining thing to cover is how the DataForm saves the data in its editors. Saving The DataForm has validation considerations, so the values in the editors are not immediately committed to the bound Source. Instead, you call DataForm.CommitAll and validation is performed against the values, (according to the Data Annotations you've set). The DataForm has a FormValidationCompleted event that will fire when values are committed to the form. This means we can use an EventToCommandBehvaior to hook up the event to a view model command. <input:RadDataForm x:Name="DataForm" In the command, we use the event args to hold a flag on whether or not the SelectedEmployee's changed can be saved. private void FormValidationCompleted(object obj) Just like the EmployeeDetailsPage, the EmployeeEditPage has a toolbar button that invokes a command. In the command's Action, we can save it and automatically navigate back to the details page when it is done. private async void ToolbarItemTapped(object obj) That concludes our deep dive in to the ListPage -> DetailsPage ->EditPage approach! The Art Gallery CRM app uses this pattern for all four of the entity models. Now that you’ve got a handle on the pattern, take a look at the following special scenarios for some other ideas of what you could do: In part three of this seriesI’ll show how to create and hook up an Azure Bot Service leveraging a custom trained Natural Language Understanding (LUIS) with the RadConversationalUI control to create an in-app Support Bot experience. ⬅ Part One of the series where we built the backend Azure Mobile Service Client SDK
public [19659033] statique
classe
Constantes
{
// Remplacez les chaînes par votre point de terminaison Azure Mobile App.
}
Modèles de données
public
class
BaseDataObject: ObservableObject
] {
public
BaseDataObject ()
[19659008]
{
Id = Guid.NewGuid (). ToString ();
}
[JsonProperty(PropertyName=
"id"
)]
[Ignore]
public
string
Id {
get
;
set
; }
[JsonProperty(PropertyName=
"createdAt"
)]
[Ignore]
public
DateTimeOffset CreatedAt {[19659068] obtenir
;
set
; }
[UpdatedAt]
[Ignore]
public
DateTimeOffset UpdatedAt {
get
;
set
; }
[Version]
[Ignore]
public
string
AzureVersion {
get
;
set
; }
}
Classes de service
public
interface
IDataStore
{
Task <
bool
> AddItemAsync (T item);
Task <
bool
> UpdateItemAsync (élément T);
Task <
bool
> DeleteItemAsync (élément T);
Tâche <
bool
> PurgeAsync ();
Tâche
string
id);
Task <
string
> GetIdAsync (
string
name);
Task <IReadOnlyList
bool
forceRefresh =
false
);
Task <
bool
> PullLatestAsync ();
Task <
bool
> SyncAsync ();
}
EmployeesViewModel
await DependencyService.Get <IDataStore
EmployeesPage
RadBusyIndicator
protected
override
async
void
OnAppearing ()
{
base
.Apparaissant ();
wait vm.LoadDataAsync ();
SetupVacationGauge ();
SetupSalesGauge ();
SetupRevenueGauge ();
SetupChartLegend ();
}
private
void
SetupVacationGauge ()
{
// Vacation Radial Gauge
VacationGauge.StartAngle = 90;
VacationGauge.SweepAngle = 360;
VacationGauge.AxisRadiusFactor = 1;
VacationLinearAxis.Maximum = vm.SelectedEmployee.VacationBalance;
VacationLinearAxis.StrokeThickness = 0 020]
VacationLinearAxis.ShowLabels =
false
;
VacationRangesDefinition.StartThickness = 10;
VacationRangesDefinition.EndThickness = 10;
VacationRangesDefinition.EndThickness = 10;
VacationRange.To = vm.SelectedEmployee.VacationBalance;
VacationRange.Color = (Color) Application.Current.Resources [
"GrayBackgroundColor"
];
VacationIndicator.Value = vm.SelectedEmployee.VacationUsed;
VacationIndicator.Fill = (Color) A pplication.Current.Resources [
"AccentColor"
];
VacationIndicator.StartThickness = 10;
VacationIndicator.EndThickness = 10;
VacationIndicator.Start.Cap = GaugeBarIndicator;
VacationIndicator.EndCap = GaugeBarIndicatorCap.Oval;
}
} ] Le même modèle est également utilisé pour les autres jauges.
{
if (this.SelectedEmployee == null)
{
return;
}
{
return;
}
{
this.IsBusy = true;
var orders = wait DependencyService.Get <IDataStore
var matchingOrders = orders.Where (o => o.EmployeeId == this.SelectedEmployee.Id) .ToList ();
{
var deleteAll = wait App.Current.MainPage.DisplayAlert ("!!! WARNING !!!", $ "Il y a des commandes dans la base de données pour {_vm? .SelectedEmployee .Nom}. If you delete this employee, you'll also delete all of the associated orders.rnnDo you wish to delete everything?", "Delete All", "Cancel");
if (!deleteAll)
return;
foreach (var order in matchingOrders)
{
await DependencyService.Get<IDataStore
}
}
await DependencyService.Get<IDataStore
}
catch (Exception ex)
{
// display error to user
}
finally
{
this.IsBusy = false;
}
}[19659446]
<ToolbarItem Text="order"
IconImageSource="add_order.png"
Command="{Binding ToolbarCommand}"
CommandParameter="order" />
<ToolbarItem Text="edit"
IconImageSource="edit.png"
Command="{Binding ToolbarCommand}"
CommandParameter="edit" />
<<ToolbarItem Text="delete"
Icon="delete.png"
Command="{Binding ToolbarCommand}"
CommandParameter="delete" />
{
if (this.SelectedEmployee == null)
{
return;
}
{
case "order":
await this.NavigateForwardAsync(new OrderEditPage(this.SelectedEmployee));
break;
case "edit":
await this.NavigateForwardAsync(new EmployeeEditPage(this.SelectedEmployee));
break;
case "delete":
await this.DeleteEmployeeAsync();
break;
}
}EmployeeEditViewModel
public
async Task UpdateDatabaseAsync()
{
if
(IsNewEmployee)
{
await DependencyService.Get<IDataStore
}
else
{
await DependencyService.Get<IDataStore
}
}
EmployeeEditPage
[Ignore]
public
string
PhotoUri
{
get
=> photoUri;
set
=> SetProperty(
ref
photoUri, value);
}
// If we're adding a new employee
public
EmployeeEditPage()
{
InitializeComponent();
ViewModel.IsNewEmployee =
true
;
ViewModel.SelectedEmployee =
new
Employee();
ConfigureEditors();
}
// If we're editing an existing employee
public
EmployeeEditPage(Employee employee)
{
InitializeComponent();
ViewModel.SelectedEmployee = employee;
ConfigureEditors();
}
private
void
ConfigureEditors()
{
DataForm.RegisterEditor(nameof(Employee.Name), EditorType.TextEditor);
DataForm.RegisterEditor(nameof(Employee.OfficeLocation), EditorType.TextEditor);
DataForm.RegisterEditor(nameof(Employee.HireDate), EditorType.DateEditor);
DataForm.RegisterEditor(nameof(Employee.Salary), EditorType.DecimalEditor);
DataForm.RegisterEditor(nameof(Employee.VacationBalance), EditorType.IntegerEditor);
DataForm.RegisterEditor(nameof(Employee.VacationUsed), EditorType.IntegerEditor);
DataForm.RegisterEditor(nameof(Employee.Notes), EditorType.TextEditor);
}
Source="{Binding SelectedEmployee}"
Grid.Row="1"
Margin="10,0,10,0">
<behaviors:EventToCommandBehavior EventName="FormValidationCompleted"
Command="{Binding FormValidationCompleteCommand}" />
{
if (obj is FormValidationCompletedEventArgs e)
{
this._isReadyToSave = e.IsValid;
}
}
{
switch (obj.ToString())
{
case "save":
// Commit the Editor's values for validation
this.DataFormView.CommitChanges();
// If values pass validation
if (this._isReadyToSave)
{
// Save the record
if (await this.UpdateDatabaseAsync())
{
await this.NavigateBackAsync();
}
}
break;
}
}Wrapping Up
Read Next
➡ Part Three of the series where we train a LUIS AI model using Telerik UI for Xamarin's Conversational UI
Source link