Code-Sharing zwischen WP, Win8. Teil 2

    Im letzten Artikel haben wir uns mit den Grundlagen der Codefreigabe befasst. Dieser Artikel kann durch die Freigabefunktion ergänzt werden, die auf der Build-Konferenz zwischen W8.1 und WP8.1 gezeigt wurde. Dieser Ansatz ist sehr gut beschrieben hier , jetzt werden wir auf dem Universal - Apps nicht wohnen.

    Im Allgemeinen ist Microsoft mit den Schritten zur Vereinheitlichung des Codes für beide Plattformen zufrieden. Wir haben jedoch immer noch ein Vermächtnis in Form von Windows Phone 7. Außerdem müssen Sie den Code möglicherweise auch auf Desktop, Android usw. fummeln.

    In diesem Artikel werden wir uns eine der am häufigsten verwendeten praktischen Lösungen für die gemeinsame Nutzung von Code ansehen.

    Machen Sie sich bereit, um den Code freizugeben


    Die folgenden „Tools“ vereinfachen das Schreiben plattformübergreifender Anwendungen erheblich. Da jedoch viele Artikel / Bücher zu diesen Ansätzen und Mustern verfasst wurden, werden wir auf diese Punkte nicht näher eingehen.

    Das MVVM-Muster hat sich bei der Entwicklung von Anwendungen für WP, W8, WPF, Silverlight zu einem fast verbindlichen Qualitätsstandard entwickelt. und trotz der Tatsache, dass es nicht notwendig ist, es zu verwenden, spart dieser Ansatz dennoch oft eine Menge Zeit und Code, wenn komplexe Anwendungen erstellt werden.

    Eines der wichtigsten Missverständnisse, auf das ich häufig stoße, ist die Meinung, dass Sie MVVM nur nehmen und verwenden können, und die Qualität wird von selbst kommen. Oder dass dieses Muster zum Beispiel den klassischen Drei-Link ersetzt. Tatsächlich leugnet MVVM die Trennung von Anwendungslogik und Datenspeicherlogik überhaupt nicht. In der Tat ist alles genau umgekehrt: Wenn Sie die gesamte Anwendungslogik und die Datenspeicherlogik direkt in die VM schreiben, erhalten wir in der Tat den gleichen Code-Behind, nur in der VM, und die Anwendung wird sehr schnell verstopft und schwierig zu warten.

    Das beliebteste Framework für WP und WinRT ist wahrscheinlich MVVM Light . In meinen Anwendungen bevorzuge ich jedoch am häufigsten die Verwendung meines kompakten, selbst geschriebenen Frameworks.

    Umkehrung der Kontrolle- Wenn wir beim Schreiben plattformübergreifender Anwendungen auf MVVM verzichten können, ist die Inversion der Steuerung möglicherweise ein unverzichtbares Werkzeug. Selbst bei Anwendungen für eine Plattform vereinfacht IoC die Entwicklung von flexiblen, erweiterbaren und daher für weitere Wartungsanwendungen praktischen Anwendungen erheblich.

    Die Hauptidee für die Verwendung von IoC bei der Entwicklung plattformübergreifender Anwendungen ist die Verallgemeinerung der Funktionen für jede Plattform unter Verwendung der Schnittstelle und eine spezifische Implementierung für jede Plattform.

    Es gibt viele fertige IoC-Container, aber in meinen Anwendungen verwende ich entweder den Self-Service-Service-Locator (der laut vielen Antipatterns ist), und in einigen Projekten verwende ich das einfache SimpleIoC-Framework (das übrigens im Lieferumfang von MVVM Light enthalten ist).

    Betrachten Sie das Beispiel zum Speichern von Text aus dem vorherigen Artikel , in dem wir die Funktionen zum Speichern von Text mithilfe der Direktive "#if WP7" unterteilt haben. Dieses Beispiel könnte etwas anders implementiert werden. Befindet sich diese Methode beispielsweise in einer bestimmten Klasse mit einem DataLayer, könnte unser Code folgendermaßen aussehen:

    In einem Projekt mit einem gemeinsamen Code (z. B. der Portable Library, auf die wir weiter unten eingehen werden) können wir eine gemeinsame Schnittstelle zum Speichern von Text hervorheben:
    public interface IDataLayer
    {
        Task SaveText(string text);
    }

    Wir können diese Schnittstelle in unserer Logikschicht verwenden. Zum Beispiel so:
    public class LogicLayer : ILogicLayer
    {
        private readonly IDataLayer dataLayer;
        public LogicLayer(IDataLayer dataLayer)
        {
            this.dataLayer = dataLayer;
        }
        public void SomeAction()
        {
            dataLayer.SaveText("myText");
        }
    }

    Wie Sie sehen, weiß die Logikebene nichts darüber, wie und wo genau der Text gespeichert wird.
    Dementsprechend könnte die Implementierung zum Speichern von Text für WP7 / WP8 folgendermaßen aussehen:
    public class DataLayerWP : IDataLayer
    {
        public async Task SaveText(string text)
        {
            using (var local = IsolatedStorageFile.GetUserStoreForApplication())
            {
                using (var stream = local.CreateFile("DataFile.txt"))
                {
                    using (var streamWriter = new StreamWriter(stream))
                    {
                        streamWriter.Write(text);
                    }
                }
            }
        }
    }

    Dementsprechend kann es für WP8 / W8 folgenden Code geben:
    public class DataLayerWinRT : IDataLayer
    {
        public async Task SaveText(string text)
        {
            var fileBytes = Encoding.UTF8.GetBytes(text);
            var local = ApplicationData.Current.LocalFolder;
            var file = await local.CreateFileAsync("DataFile.txt", CreationCollisionOption.ReplaceExisting);
            using (var s = await file.OpenStreamForWriteAsync())
            {
                s.Write(fileBytes, 0, fileBytes.Length);
            }
        }
    }

    Natürlich können die Klassennamen übereinstimmen, da diese Klassen spezifische Implementierungen sind und sich jeder von ihnen in einem Projekt mit einer spezifischen Plattform befindet.

    Jetzt muss nur noch eine bestimmte Implementierung in jedem der Projekte registriert werden. Wenn wir das SimpleIoC-Beispiel nehmen, kann die Registrierung wie folgt aussehen (in unserem Fall für WP7): und in einem Projekt mit WP8: Auf diese Weise können wir fast alles gemeinsam nutzen: Paging, Empfangen von Daten von Sensoren, Öffnen einer HTML-Seite auf dem Client usw. .d. usw. Für diejenigen, die Xamarin aktiv nutzen möchten, kann ich Xamarin mobile api empfehlen

    SimpleIoc.Default.Register();

    SimpleIoc.Default.Register();



    Hierbei handelt es sich um eine Reihe spezifischer Implementierungen zum Lösen einer Vielzahl von Aufgaben, z. B. zum Speichern von Daten, Abrufen des Standorts des Benutzers, Kamerabild usw.

    Tragbare Bibliothek. Ab VS2012 hatten wir die Möglichkeit, eine neue Art von Projekt zu verwenden - Portable Library (PL). Genau genommen haben wir diese Möglichkeit auch mit VS2010 erhalten, jedoch als separat installierte Erweiterung. Mit PL können Sie Anwendungen mit gemeinsamem Code erstellen. Der Haupttrick dieses Projekttyps besteht darin, dass PL automatisch nur die Funktionen der Sprache verwendet, die den ausgewählten Projekttypen gemeinsam sind. Dies führt zu den Einschränkungen und Funktionen dieses Tools. Beispielsweise können Sie XAML nicht in PL verwenden. Für Anwendungen mit komplexer Logik bedeutet PL jedoch eine enorme Zeit- und Müheersparnis.
    Erwähnenswert ist möglicherweise das Problem der Verwendung des CallerMember-Attributs, das von PL nicht unterstützt wird und mit dem BaseViewModel die folgende allen VMs gemeinsame Methode hervorhebt:

    public class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName]string propertyName=null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    Womit Sie stattdessen schreiben können:
    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            OnPropertyChanged("Name");
        }
    }

    Alternativer Datensatz ohne Angabe eines Feldes:
    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            OnPropertyChanged();
        }
    }

    Dies vereinfacht die Wartung der Anwendung erheblich.

    Um PL Unterstützung für dieses Attribut hinzuzufügen, muss es manuell deklariert werden:
    CallerMemberNameAttribute.cs

    namespace System.Runtime.CompilerServices
    {
        // Summary:
        //     Allows you to obtain the method or property name of the caller to the method.
        [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
        sealed class CallerMemberNameAttribute : Attribute { }
        // Summary:
        //     Allows you to obtain the line number in the source file at which the method
        //     is called.
        [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
        public sealed class CallerLineNumberAttribute : Attribute { }
        // Summary:
        //     Allows you to obtain the full path of the source file that contains the caller.
        //     This is the file path at the time of compile.
        [AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
        public sealed class CallerFilePathAttribute : Attribute { }
    }


    Einrichtung und Verbindung


    Es gab viele Beispiele für die Verwendung von MVVM und IoC, auch auf Habr. Anstatt
    den Artikel zu erweitern, möchte ich hier einige Links anführen : Xamarin + PCL + MVVM - wie man das Schreiben mobiler Anwendungen für verschiedene Plattformen erleichtert. - Ein ausgezeichneter Artikel über die Verwendung von Xamarin und MVVM.

    Verwenden des MVVM-Musters beim Erstellen von Anwendungen für Windows Phone . In diesem Artikel erfahren Sie mehr über MVVM und die Verwendung in WP.

    Windows Phone + Caliburn.Micro + Autofac . Ein weiterer Artikel zum Konfigurieren und Verwenden des beliebten Caliburn.Micro MVVM-Frameworks und des Autofac IoC-Containers, der beim Erstellen von Web- und Desktopanwendungen nicht weniger beliebt ist.

    Dies ist natürlich weit von der endgültigen Liste entfernt, und auf den Freiflächen der Habr und des Internets finden Sie nicht weniger interessante Artikel zu diesem Thema.

    Darüber hinaus können wir die Funktionen zum Schreiben plattformübergreifender Anwendungen mit diesen Tools berücksichtigen.

    Freigegebene VM


    Die erste naheliegende Lösung besteht darin, für jeden Projekttyp eine gemeinsame VM zu verwenden. In relativ einfachen Projekten können wir genau das tun.

    Beispielsweise könnte eine VM mit einem Taschenrechner, der zwei Zahlen addiert, folgendermaßen aussehen:
    public class MainViewModel : BaseViewModel
    {
        private int valueA;
        public int ValueA
        {
            get { return valueA; }
            set
            {
                valueA = value;
                OnPropertyChanged();
            }
        }
        private int valueB;
        public int ValueB
        {
            get { return valueB; }
            set
            {
                valueB = value;
                OnPropertyChanged();
            }
        }
        public ICommand CalculateCommand
        {
            get
            {
                return new ActionCommand(CalculateResult);
            }
        }
        private void CalculateResult()
        {
            Result = ValueA + ValueB;
        }
        private int result;
        public int Result
        {
            get { return result; }
            private set
            {
                result = value;
                OnPropertyChanged();
            }
        }
    }

    Und wir können diese VM ohne Änderungen auf jeder der Plattformen verwenden.

    In unserem einfachen Fall können wir für beide Plattformen den gleichen einfachen XAML-Code für diese VM verwenden

    Detail-VM für jede Plattform


    Oft müssen Sie die Funktionen jeder Plattform berücksichtigen. Für Win8, wo wir einen großen Bildschirm haben, bestand die Aufgabe darin, das Ergebnis in Worten anzuzeigen, und für WP wurde entschieden, nur das Ergebnis in Worten anzuzeigen, weniger als 100.

    Die erste und normalerweise falsche Lösung, die Sie sofort verwenden möchten, sieht möglicherweise so aus :
    private int result;
    public int Result
    {
        get { return result; }
        private set
        {
            result = value;
            OnPropertyChanged();
            OnPropertyChanged("ResultTextWP");
            OnPropertyChanged("ResultTextWinRT");
        }
    }
    public string ResultTextWP
    {
        get
        {
            if (result < 100)
                return Result.ToString();
            return NumberUtil.ToText(Result);
        }
    }
    public string ResultTextWinRT
    {
        get
        {
            return NumberUtil.ToText(Result);
        }
    }

    Wo können wir jedes der Felder für eine bestimmte Plattform verwenden? Stattdessen können wir die Vererbung als Anpassung der VM verwenden, d. H. In PL in der Klasse MainViewModel können wir ein virtuelles Feld deklarieren. ResultText:
    private int result;
    public int Result
    {
        get { return result; }
        private set
        {
            result = value;
            OnPropertyChanged();
            OnPropertyChanged("ResultText");
        }
    }
    public virtual string ResultText
    {
        get
        {
            throw new NotImplementedException();
        }
    }

    Wo können wir throw new NotImplementedException () durch beispielsweise NumberUtil.ToText (Result) ersetzen, wenn nicht jeder Nachfolger dieses Feld für die Verwendung explizit überschreiben soll

    ? Jetzt können wir das MainViewModel in jedem Projekt erben und seine Eigenschaft überschreiben:
    public class MainViewModelWinRT : MainViewModel
    {
        public override string ResultText
        {
            get { return NumberUtil.ToText(Result); }
        }
    }
    

    Und für WP:
    
    public class MainViewModelWP : MainViewModel
    {
        public override string ResultText
        {
            get
     	 {
         if (result < 100)
      	        return Result.ToString();
       	     return NumberUtil.ToText(Result);
     }
        }
    }

    Natürlich müssen wir im IoC-Container anstelle des allgemeinen MainViewModel eine spezifische Implementierung für jede Plattform registrieren.

    Wenn wir eine VM-Eigenschaft NUR auf einer Plattform benötigen - für sich genommen, muss sie nicht in der Basisklasse definiert werden -, können wir diese Eigenschaft nur für eine bestimmte Plattform angeben. Zum Beispiel war es plötzlich notwendig, das Datum nur für W8 in der Benutzeroberfläche anzuzeigen:
    public class MainViewModelWinRT : MainViewModel
    {
        public override string ResultText
        {
            get { return NumberUtil.ToText(Result); }
        }
        public string Date
        {
            get
            {
                return DateTime.Now.ToString("yy-mm-dd");
            }
        }
    }


    Zusammenfassung


    Im Vergleich zum vorherigen Artikel bekommen wir auf den ersten Blick eine Menge unnötiger Kopfschmerzen mit MVVM, IoC usw., während wir einfach Dateien verknüpfen und Features mit #if-Direktiven teilen können.

    Leider zeigt das Beispiel mit dem Taschenrechner nicht die Vorteile, sondern nur deutlich mehr Code für eine so einfache Anwendung.

    In der Praxis können Sie viel mehr Vorteile erzielen, wenn Sie mittlere und große Anwendungen mit komplexer Logik erstellen, da unser Code einen großen Teil des "Service" -Codes einnimmt. Darüber hinaus wird der Anwendungscode weniger verbunden und die Wartung der Anwendung wird erheblich erleichtert.

    Leider gibt es "über Bord" noch viele Punkte, die ich nicht in diesen Artikel aufgenommen habe, um die Hauptpunkte der Codefreigabe nicht zu verstopfen. Wenn Sie in den Kommentaren angeben, für welche Probleme / Themen / Lösungen Sie sich am meisten interessieren, kann ich dieses Thema durch einen weiteren Artikel ergänzen.

    Jetzt auch beliebt: