Antoine Loos

Just another .Net Blog

This content shows Simple View


Programmation

UWP MessageBox class

Certaines habitudes ont la vie dure quand on developpe depuis un petit moment sur une technologie aussi directive que .Net et WPF. On prend donc de bonnes ou de mauvaises habitudes comme par exemple faire ses sortie de Debug dans des MessageBox ce qui en WPF est très pratique.

Cette possiblité ayant disparue en UWP voici une petite classe statique qui permet de retrouver cet outils et de s’en servir comme avant. (chacun se sentira libre de juger de la pertinence ou du bien fondé de cette pratique) :

public class MessageBox
{
public static async void Show(string ex )
{
await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
{
var dlg = new MessageDialog(ex);
await dlg.ShowAsync();
});

}
}

 

Je me sens un peu obligé de me justifier de donner ces petits trucs et astuces pas forcement recommandables mais voici une illustration que je trouve très parlante à  ce propos :

Design-VS-Experience

Ceci est également valable pour le developpement



UWP default border on ContentDialog

Je pense que la technologie UWP est une réelle avancée dans l’histoire du .Net et ajout beaucoup de choses indispensables pour ne pas perdre de temps lorsqu’on programme. Cependant Certains des controls UWP possèdent des propriétés par défaut qui sont franchement génantes dès qu’on veut presonnaliser un peu l’UI et qu’on ne souhaite pas intervenir avec Blend sur ce détail.

L’exemple parfait est la bordure par défault qui est présente autours du ContentDialog. C’est une bordure de 1px de la couleur de votre thème windows 10.

Voici la petite ligne de code qui permet de l’enlever (dans le code behind)

Application.Current.Resources["ContentDialogBorderWidth"] = new Thickness(0);


Text Binding RichEditBox in UWP

Quand on a besoins d’utiliser le control RichEditBox et que l’on veut binder du text à  celui ci celà devient un peu compliqué et je me suis souvent retrouvé à renoncer ou passer par des chemin détourné comme utiliser des control de librairies externes. Cependant je suis tombé sur un post de stackoverflow  qui donne une réponse parfaite : http://stackoverflow.com/questions/26549156/winrt-binding-a-rtf-string-to-a-richeditbox

public class RichEditBoxExtended : RichEditBox
{
    public static readonly DependencyProperty RtfTextProperty = 
        DependencyProperty.Register(
        "RtfText", typeof (string), typeof (RichEditBoxExtended),
        new PropertyMetadata(default(string), RtfTextPropertyChanged));

    private bool _lockChangeExecution;

    public RichEditBoxExtended()
    {
        TextChanged += RichEditBoxExtended_TextChanged;
    }

    public string RtfText
    {
        get { return (string) GetValue(RtfTextProperty); }
        set { SetValue(RtfTextProperty, value); }
    }

    private void RichEditBoxExtended_TextChanged(object sender, RoutedEventArgs e)
    {
        if (!_lockChangeExecution)
        {
            _lockChangeExecution = true;
            string text;
            Document.GetText(TextGetOptions.None, out text);
            if (string.IsNullOrWhiteSpace(text))
            {
                RtfText = "";
            }
            else
            {
                Document.GetText(TextGetOptions.FormatRtf, out text);
                RtfText = text;
            }
            _lockChangeExecution = false;
        }
    }

    private static void RtfTextPropertyChanged(DependencyObject dependencyObject,
        DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        var rtb = dependencyObject as RichEditBoxExtended;
        if (rtb == null) return;
        if (!rtb._lockChangeExecution)
        {
            rtb._lockChangeExecution = true;
            rtb.Document.SetText(TextSetOptions.FormatRtf, rtb.RtfText);
            rtb._lockChangeExecution = false;
        }
    }
}

Ceci permet de faire ce genre de chose :
<utils:RichEditBoxExtended TextWrapping="Wrap" RtfText="{Binding Text}"/>

avec Text une propriété de type string coté VM.

 



Introduction au MVVM

Le MVVM est la seule manière de coder proprement et efficacement en .Net intégrant une couche UI.

Voilà c’est dit et je ne vais pas me justifier outre mesure. Vous pouvez aller voir mon article sur l’enfumage autour de ce pattern.

Certains pourrons me traiter d’intégriste de la programmation peu me chaut.

Le MVVM est un pattern et il n’est pas exclusif au .Net (en js par exemple avec knockout.js ou même Angular.js dans une autre mesure avec le MV*)

Comme tout pattern il demande une mise en place particulière.

Pour appliquer ce pattern en WPF par exemple (cela s’applique aussi au SilverLight il me semble) nous allons utiliser le puissant système de Databindingmais pour commencer il faut que notre view model c’est-à-dire, tout ce que vous mettiez avant dans le code behind et que vous avez mis dans une classe séparée, puisse signaler à qui veut bien l’entendre qu’une de ses propriétés à changer. Pour cela on utilise un système qui s’appelle l’implémentation du INotifyPropertyChanged. Ne prenez pas peur c’est plutôt simple il suffit d’ajouter à votre solution la classe  NotifyPropertyChangedObject dont le code est à ce lien : http://blog.soat.fr/2012/02/simplifier-lecriture-de-inotifypropertychanged-en-c/

Ensuite, il vous suffit de faire dériver vos classes de votre modèle de votre classe NotifyPropertyChangedObjectPuis, pour faire en sorte que votre modèle notifie un changement sur une de ses propriétés dans sa déclaration il va falloir écrire :

 

 

 

public string FirstName {
        get { return GetValue<string>("FirstName"); }
        set { SetValue("FirstName", value); }
    }

Et voilà au moindre changement sur votre propriété FirstName ceux que ça interesse seront informé.

Maintenant il va falloir que la Vue puisse dire qu’elle est interressée, mais attendez jeune programmeurs fougueux, il faut lui dire où aller chercher les infos et où allé écouter les notifications il faut donc lui donner un DataContext pour cela rien de plus simple vous pouvez passer par le xaml par exemple : <UserControl.DataContext>

Mais vous pouvez aussi passer par le code behind de votre vue où vous êtes exceptionnellement autorisé à mettre en dessous du  InitializeComponent();

un petit this.DataContext = new MyViewModel();

Suite à cela votre vue sait où allé chercher l’information en gros dans quel sac piocher il reste à faire le lien avec les éléments graphiques.

Ceci fonctionne comme suit pour l’exemple que l’on a pris : <Label Content=”{Binding FirstName”/>

Le label prendra la valeur de FirstName qu’il sera allé chercher dans le datacontext. Il sera automatiquement changé dès qu’il aura une notification que la propriété a été modifiée.

Alors, à ce stade vous allez me dire : c’est très bien tout ça mais l’intérêt d’une vue c’est quand même de permettre l’interaction entre l’utilisateur et le programme et on fait ça comment en MVVM sans code behind ?

La solution : Les Command

 

Comme précédement il va falloir implémenter une classe qui va nous être très utile. Voici le lien vers un exemple de cette implémentation : http://www.wpftutorial.net/delegatecommand.html

Ensuite dans votre VM vous pouvez  instancier une DelegateCommand et lui assigner une fonction à déclencher si cette commande est appelée.  On est ici toujours dans la logique d’exposer des éléments à la vue donc on expose un moyen d’action sur les données

ensuite la Vue peut être Bindé sur cette commande comme ceci : <Button Content=”blublu” Command=“{Binding MyCommand}“/>

Donc finalement rien de plus simple que d’interagir avec votre programme en MVVM.

Nous verrons dans un prochain article comment faire dans des situations moins standards.

En résumé, voici la démonstration qu’il est possible de ne pas poser une seule ligne de code dans le code-behind et de faire la même chose qu’avant et à ça c’est bien !!!

Pour aller un peu plus loin il est possible de remarquer que nous utiliserons toujours ces quelques classes dont je vous ai parlé et qu’il serait pratique de mettre dans un framework mais, nous verrons ça la prochaine fois …

 



Petite amélioration du DynamicDirectoryModuleCatalog

Récemment je suis tombé sur un post génial du célèbre créateur du framework Prism, Brian Lagunas qui expliquait comment charger des modules dans un ModuleCatalog à partir d’un dossier.

Voici le lien vers le post original  : http://www.infragistics.com/community/blogs/blagunas/archive/2013/08/06/prism-dynamically-discover-and-load-modules-at-runtime.aspx

Cependant, je tenais à vous faire partager la petite amélioration qui permet d’organiser les modules au sein d’un dossier Modules.

en gros l’idée est d’avoir cette hiérarchie : Release -> Modules -> Module1, Module2, Module3 ….

Pour cela rien de plus simple

Voici le code de la classe :

public class DynamicDirectoryModuleCatalog : ModuleCatalog
{
SynchronizationContext _context;

/// <summary>
/// Directory containing modules to search for.
/// </summary>
public string ModulePath { get; set; }

public DynamicDirectoryModuleCatalog(string modulePath)
{
_context = SynchronizationContext.Current;

ModulePath = modulePath;

if (!Directory.Exists(ModulePath))
{

try
{
Directory.CreateDirectory(ModulePath);
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}

}
// we need to watch our folder for newly added modules

FileSystemWatcher fileWatcher = new FileSystemWatcher(ModulePath, "*.dll");

fileWatcher.Created += FileWatcher_Created;
fileWatcher.EnableRaisingEvents = true;
fileWatcher.IncludeSubdirectories = true;
}

/// <summary>
/// Rasied when a new file is added to the ModulePath directory
/// </summary>
void FileWatcher_Created(object sender, FileSystemEventArgs e)
{
if (e.ChangeType == WatcherChangeTypes.Created)
{
LoadModuleCatalog(e.FullPath, true);
}
}

/// <summary>
/// Drives the main logic of building the child domain and searching for the assemblies.
/// </summary>
protected override void InnerLoad()
{
LoadModuleCatalog(ModulePath);
}

void LoadModuleCatalog(string path, bool isFile = false)
{
if (string.IsNullOrEmpty(path))
throw new InvalidOperationException("Path cannot be null.");

if (isFile)
{
if (!File.Exists(path))
throw new InvalidOperationException(string.Format("File {0} could not be found.", path));
}
else
{
if (!Directory.Exists(path))
throw new InvalidOperationException(string.Format("Directory {0} could not be found.", path));
}

AppDomain childDomain = this.BuildChildDomain(AppDomain.CurrentDomain);

try
{
List<string> loadedAssemblies = new List<string>();

var assemblies = (
from Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()
where !(assembly is System.Reflection.Emit.AssemblyBuilder)
&& assembly.GetType().FullName != "System.Reflection.Emit.InternalAssemblyBuilder"
&& !String.IsNullOrEmpty(assembly.Location)
select assembly.Location
);

loadedAssemblies.AddRange(assemblies);

Type loaderType = typeof(InnerModuleInfoLoader);
if (loaderType.Assembly != null)
{
var loader = (InnerModuleInfoLoader)childDomain.CreateInstanceFrom(loaderType.Assembly.Location, loaderType.FullName).Unwrap();
loader.LoadAssemblies(loadedAssemblies);

//get all the ModuleInfos
ModuleInfo[] modules = loader.GetModuleInfos(path, isFile);

//add modules to catalog
this.Items.AddRange(modules);

//we are dealing with a file from our file watcher, so let's notify that it needs to be loaded
if (isFile)
{
LoadModules(modules);
}
}
}
finally
{
AppDomain.Unload(childDomain);
}
}

/// <summary>
/// Uses the IModuleManager to load the modules into memory
/// </summary>
/// <param name="modules"></param>
private void LoadModules(ModuleInfo[] modules)
{
if (_context == null)
return;

IModuleManager manager = ServiceLocator.Current.GetInstance<IModuleManager>();

_context.Send(new SendOrPostCallback(delegate(object state)
{
foreach (var module in modules)
{
manager.LoadModule(module.ModuleName);
}
}), null);
}

/// <summary>
/// Creates a new child domain and copies the evidence from a parent domain.
/// </summary>
/// <param name="parentDomain">The parent domain.</param>
/// <returns>The new child domain.</returns>
/// <remarks>
/// Grabs the <paramref name="parentDomain"/> evidence and uses it to construct the new
/// <see cref="AppDomain"/> because in a ClickOnce execution environment, creating an
/// <see cref="AppDomain"/> will by default pick up the partial trust environment of
/// the AppLaunch.exe, which was the root executable. The AppLaunch.exe does a
/// create domain and applies the evidence from the ClickOnce manifests to
/// create the domain that the application is actually executing in. This will
/// need to be Full Trust for Composite Application Library applications.
/// </remarks>
/// <exception cref="ArgumentNullException">An <see cref="ArgumentNullException"/> is thrown if <paramref name="parentDomain"/> is null.</exception>
protected virtual AppDomain BuildChildDomain(AppDomain parentDomain)
{
if (parentDomain == null) throw new System.ArgumentNullException("parentDomain");

Evidence evidence = new Evidence(parentDomain.Evidence);
AppDomainSetup setup = parentDomain.SetupInformation;
return AppDomain.CreateDomain("DiscoveryRegion", evidence, setup);
}

private class InnerModuleInfoLoader : MarshalByRefObject
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal ModuleInfo[] GetModuleInfos(string path, bool isFile = false)
{
Assembly moduleReflectionOnlyAssembly =
AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().First(
asm => asm.FullName == typeof(IModule).Assembly.FullName);

Type IModuleType = moduleReflectionOnlyAssembly.GetType(typeof(IModule).FullName);

FileSystemInfo info = null;
if (isFile)
info = new FileInfo(path);
else
info = new DirectoryInfo(path);

ResolveEventHandler resolveEventHandler = delegate(object sender, ResolveEventArgs args) { return OnReflectionOnlyResolve(args, info); };
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += resolveEventHandler;
IEnumerable<ModuleInfo> modules = GetNotAllreadyLoadedModuleInfos(info, IModuleType);
AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve -= resolveEventHandler;

return modules.ToArray();
}

private static IEnumerable<ModuleInfo> GetNotAllreadyLoadedModuleInfos(FileSystemInfo info, Type IModuleType)
{
List<FileInfo> validAssemblies = new List<FileInfo>();
Assembly[] alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();

FileInfo fileInfo = info as FileInfo;
if (fileInfo != null)
{
if (alreadyLoadedAssemblies.FirstOrDefault(assembly => String.Compare(Path.GetFileName(assembly.Location), fileInfo.Name, StringComparison.OrdinalIgnoreCase) == 0) == null)
{
var moduleInfos = Assembly.ReflectionOnlyLoadFrom(fileInfo.FullName).GetExportedTypes()
.Where(IModuleType.IsAssignableFrom)
.Where(t => t != IModuleType)
.Where(t => !t.IsAbstract).Select(t => CreateModuleInfo(t));

return moduleInfos;
}
}

DirectoryInfo directory = info as DirectoryInfo;

foreach (DirectoryInfo elem in directory.GetDirectories())
{
alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
var files = elem.GetFiles("*.dll").Where(file => alreadyLoadedAssemblies.
FirstOrDefault(assembly => String.Compare(Path.GetFileName(assembly.Location), file.Name, StringComparison.OrdinalIgnoreCase) == 0) == null);

foreach (FileInfo file in files)
{
try
{

Assembly.ReflectionOnlyLoadFrom(file.FullName);

validAssemblies.Add(file);

}
catch (BadImageFormatException)
{
// skip non-.NET Dlls
}
}
}

return validAssemblies.SelectMany(file => Assembly.ReflectionOnlyLoadFrom(file.FullName)
.GetExportedTypes()
.Where(IModuleType.IsAssignableFrom)
.Where(t => t != IModuleType)
.Where(t => !t.IsAbstract)
.Select(type => CreateModuleInfo(type)));
}

private static Assembly OnReflectionOnlyResolve(ResolveEventArgs args, FileSystemInfo info)
{
Assembly loadedAssembly = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies().FirstOrDefault(
asm => string.Equals(asm.FullName, args.Name, StringComparison.OrdinalIgnoreCase));
if (loadedAssembly != null)
{
return loadedAssembly;
}

DirectoryInfo directory = info as DirectoryInfo;
if (directory != null)
{
AssemblyName assemblyName = new AssemblyName(args.Name);
string dependentAssemblyFilename = Path.Combine(directory.FullName, assemblyName.Name + ".dll");
if (File.Exists(dependentAssemblyFilename))
{
return Assembly.ReflectionOnlyLoadFrom(dependentAssemblyFilename);
}
}

return Assembly.ReflectionOnlyLoad(args.Name);
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal void LoadAssemblies(IEnumerable<string> assemblies)
{
foreach (string assemblyPath in assemblies)
{
try
{
Assembly.ReflectionOnlyLoadFrom(assemblyPath);
}
catch (FileNotFoundException)
{
// Continue loading assemblies even if an assembly can not be loaded in the new AppDomain
}
}
}

private static ModuleInfo CreateModuleInfo(Type type)
{
string moduleName = type.Name;
List<string> dependsOn = new List<string>();
bool onDemand = false;
var moduleAttribute = CustomAttributeData.GetCustomAttributes(type).FirstOrDefault(cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleAttribute).FullName);

if (moduleAttribute != null)
{
foreach (CustomAttributeNamedArgument argument in moduleAttribute.NamedArguments)
{
string argumentName = argument.MemberInfo.Name;
switch (argumentName)
{
case "ModuleName":
moduleName = (string)argument.TypedValue.Value;
break;

case "OnDemand":
onDemand = (bool)argument.TypedValue.Value;
break;

case "StartupLoaded":
onDemand = !((bool)argument.TypedValue.Value);
break;
}
}
}

var moduleDependencyAttributes = CustomAttributeData.GetCustomAttributes(type).Where(cad => cad.Constructor.DeclaringType.FullName == typeof(ModuleDependencyAttribute).FullName);
foreach (CustomAttributeData cad in moduleDependencyAttributes)
{
dependsOn.Add((string)cad.ConstructorArguments[0].Value);
}

ModuleInfo moduleInfo = new ModuleInfo(moduleName, type.AssemblyQualifiedName)
{
InitializationMode =
onDemand
? InitializationMode.OnDemand
: InitializationMode.WhenAvailable,
Ref = type.Assembly.CodeBase,
};
moduleInfo.DependsOn.AddRange(dependsOn);
return moduleInfo;
}
}
}

/// <summary>
/// Class that provides extension methods to Collection
/// </summary>
public static class CollectionExtensions
{
/// <summary>
/// Add a range of items to a collection.
/// </summary>
/// <typeparam name="T">Type of objects within the collection.</typeparam>
/// <param name="collection">The collection to add items to.</param>
/// <param name="items">The items to add to the collection.</param>
/// <returns>The collection.</returns>
/// <exception cref="System.ArgumentNullException">An <see cref="System.ArgumentNullException"/> is thrown if <paramref name="collection"/> or <paramref name="items"/> is <see langword="null"/>.</exception>
public static Collection<T> AddRange<T>(this Collection<T> collection, IEnumerable<T> items)
{
if (collection == null) throw new System.ArgumentNullException("collection");
if (items == null) throw new System.ArgumentNullException("items");

foreach (var each in items)
{
collection.Add(each);
}

return collection;
}
}

Le seule vrai changement se trouve dans la méthode GetNotAllreadyLoadedModulInfos() avec ce code :


DirectoryInfo directory = info as DirectoryInfo;

foreach (DirectoryInfo elem in directory.GetDirectories())
{
alreadyLoadedAssemblies = AppDomain.CurrentDomain.ReflectionOnlyGetAssemblies();
var files = elem.GetFiles("*.dll").Where(file => alreadyLoadedAssemblies.
FirstOrDefault(assembly => String.Compare(Path.GetFileName(assembly.Location), file.Name, StringComparison.OrdinalIgnoreCase) == 0) == null);

foreach (FileInfo file in files)
{
try
{

Assembly.ReflectionOnlyLoadFrom(file.FullName);

validAssemblies.Add(file);

}
catch (BadImageFormatException)
{
// skip non-.NET Dlls
}
}
}

Une astuce importante : il s’agit de reconstruire à chaque itération de dossier la liste des alreadyLoadedAssemblies pour que vous n’ayez pas de problème de l’imitation API qui vous dirait que vous ne pouvez pas charger l’assembly à partir de deux emplacements.

Voilà une manière (je ne dis pas que c’est la meilleure ou la seule) d’organiser vos modules dans une application modulaire avec Prism et Unity.

 



MVVM : arretons l’enfumage

Certains petits malin ont pris pour habitude de faire peur au jeune souhaitant apprendre la programmation en .Net. En effet après les avoir laissé barboter entre la pataugeoire du xaml et le

tobogand du code behind, ces “programmeur confirmé” vont insidieusement et avec les meilleures intentions du monde les diriger vers les posts sur le MVVM. Et là c’est le drame !

Ces jeunes programmeurs vont tomber sur des posts aussi variés et hors de sujet que les superbes introductions du MSDN, les post sur MVVMLight ou Prism ou encore certains posts ou certaines vidéos rendant très compliqué le moindre concept.

Et le pire sur les forums on va leur dire que :” est-ce que c’est vraiment adapté à leurs situations…. et est-ce que ce serait pas juste un outil pour l’accès aux données dont ils n’aurait pas besoins et est-ce qu’il vaudrait mieux utiliser prism direct”…. Bref un tas de conneries.

Le MVVM c’est simple et ça s’applique partout !!!!!

Comment ?

Il faut se le représenter comme un jeu : le but du jeu et de ne plus rien mettre dans le code behind.

L’idée est d’utiliser toute la puissance du système de Databinding.

Imaginez vous êtes dans le futur en 2124 vous allez acheter un gâteau du futur dans un supermarché de l’espace. Et ben votre  et votre ViewModel c’est votre gâteau et votre View c’est l’emballage. Il est génial parce que c’est lui qui va chercher les informations sur le gâteau qu’il contient et qu’il va les afficher. Petit exemple votre gâteau est au chocolat et pèse 250g et contient de la noisette aussi. et ben votre emballage ayant ces informations à disposition va de lui-même les afficher. Si le poids du gâteau change parce que vous avez changé de planète entre temps l’emballage va de lui-même aller chercher l’information du nouveau poids et vous l’afficher.

Arrêtons les métaphore douteuses une seconde et revenons sur ce principe : L’idée principale c’est : le coeur de l’application (VM&M&DataAccesLayer...) expose des informations. Il expose aussi des moyens d’agir sur ces informations donc par exemple : Il va exposer le nombre de part restantes de notre gâteau de l’espace et va exposer une commande pour manger une part de ce gâteau. C’est l’emballage (la vue) qui va dire : cette information m’intéresse je veux la prendre l’afficher comme je veux et être prévenu si elle change. Voilà elle est là la force du Databinding l’emballage et le gâteau sont deux objets distincts et n’ont pas de liens ils vont se transmettre des notifications, exposer des propriétés (pour le VM) et s’abonner (pour les Vues) . Une dernière comparaison pour bien comprendre le coeur de votre application devrait toujours ressembler à une sorte de gros noyaux (dans le cas où elle n’est pas modulaire) d’où sortirait des accroches sur lesquels les vues pourraient venir s’amarrer et qui seraient des canaux de communication.

 

Dans tous mes exemples il est bien entendu possible que plusieurs vues observent les propriétés d’un même VueModele. Il est évident aussi que si l’utilisateur souhaite agir sur le modèle et il faut que la vue puisse agir sur la VM. Pour ce faire la où les VM expose des leviers d’action aux vues. Celles-ci sont libre de dire : je souhaite pouvoir actionner ce levier d’action avec ce bouton de la manière dont je le souhaite (par un click ou par un évènement tactile ou autre …) et ce levier va ensuite actionner un changement dans la VM.

Pour résumer : L’idée est de reverser son système de pensés : le logiciel ne se construit plus en partant de l’interface mais, en partant du noyau (composé de VM et de M etc) et c’est ensuite qu’on va venir emballer notre application dans des vues et montrer les données du système comme on le souhaite.

Il y a quelque chose de génial à cela : dans la vraie vie je vous souhaite pouvoir changer de style vestimentaire régulièrement, les modes et notre époque évoluent vite. Or ici c’est pareil : le client après un projet peut vous demander de rajouter des fonctionnalités (on verra comment le faire proprement dans un autre post) ou alors plus communément vous demander d’actualiser ou de changer l’interface de son application. Vous pourrez donc lui dire avec fierté qu’il ne vous faudra que quelques jours pour effectuer le changement, car vos vues sont interchangeables et indépendantes et vous verrez la reconnaissance dans ses yeux (et dans votre porte feuille également).

 

Pourquoi ?

Certains parmi débutants, dont je fait parti il y a très peu de temps me répliqueront : Pourquoi ?

Pourquoi s’embêter à s’imposer des contraintes pareilles. Pour eux le code behind est un outil fantastique et ils ne voient pas pourquoi ils devraient se passer de mettre du code à l’intérieur.

Je leur répondrais que pour un petit projet avec trois boutons ce n’est pas une évidence d’utiliser le MVVM mais, dès que le projet grandi cela devient la seule manière de le maintenir sans ramer dans un océan de code spaghetti.

En réalité au lieu de venter les mérites de cette méthode il vaut mieux expliquer ce qui se passe si elle n’est pas utilisée.

Si un projet se contente des contrôles et fenêtres classiques et de leur code behind tout va très vite devenir interdépendant et ça c’est très mauvais, car quand tout est lié votre code est un bloque ou chaques changements que vous voudrez faire, deviendra un combat de tous les moments pour que votre application entière ne s’écroule pas.

L’interdépendance est l’ennemi d’un projet informatique. Votre application deviendra de plus en plus compliqué à maintenir même avec la meilleure structure du monde et vous ne pourrez pas tester la plupart de votre code, car les tests unitaires sont quasiment impossible à mettre en place dans le code behind (et oui vous n’avez qu’une partie des informations dans celui-ci ).

Mon dernièrs argument est celui qui en général, a le plus de mal à passer mais c’est le plus important : Penser à celui qui passe après vous. Même quand vous êtes stagiaire pensé que dans certaines entreprises votre code va être repris par un autre ensuite et qui lui n’est pas dans votre tête et ne pense pas comme vous. Alors, pour qu’il ne fasse pas une dépression chronique en voyant votre tas de spaghetti il faut découpler votre code en faire des petits morceaux indépendant, communiquant et stable.

 

Enfin pour ceux qui disent que tout ceci prend du temps et qu’il faut prendre en compte le coup de développement : c’est vrai mais, pensez bien à la mine réjouit de votre employeur quand il reviendra vous voir à la fin du projet pour la dernière modification inattendu et que vous pourrez la faire sans mobiliser des mois de travail à tout modifier. Penser bien que si vous démarrez votre projet sur les chapeaux de roue sans penser à la structure et à l’évolutivité et autant que vous pourrez gagner par la suite vous avez beaucoup plus de chance de vous planter.

Quels chiffres pour aérer un peu : le temps dans un projet de développement se répartit comme ceci : 15% de développement pure , 25% de réunion à la con avec dedans 5% de recueil du besoin et 60% du temps sera consacré à maintenir ce logiciel . 60% de votre temps ça compte donc ne le rendez pas plus difficile à passer.

Simplifiez votre vie future de développeur

Dans un prochain post (plus court que celui-ci ^^), je vous montrerais comment réaliser simplement un petit programme en MVVM

 

 

 




top