sigsys|dk


Blog om udvikling

Et af de steder hvor app udvikling differentierer sig, hvis man kigger på udvikling af en app til windows desktop versus udvikling af en app til Windows Phone, er reordering af elementer i et listview via drag and drop. For man skal nemlig ikke gøre det samme på WP, som på desktop, for at aktivere denne form for reordering. Jeg vil tilskynde mig at sige, at det faktisk er lettere at aktivere på Windows Phone, da man kun skal sætte ReorderMode="Enabled", hvor imod man skal sætte AllowDrop og CanReorderItems til true på desktop.

For at fange en reordering, skal man sørge for at ens ListView er binded op på en ObservableCollection. På denne collection skal man lytte på CollectionChanged eventet.

Lad mig lave et eksempel. Hvis vi tager udgangspunkt i dette xaml

<Page>
    <Page.DataContext>
        <Runtime:ViewModel/>
    </Page.DataContext>
    <Grid>
        <ListView ItemsSource="{Binding Models}" ReorderMode="Enabled">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Text}"/>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</Page>

Her har, som forskrevet, bundet mit ListView op til en property, Models, som er af typen ObservableCollection på min ViewModel, ved navn ViewModel. Koden til min ViewModel er som følger:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MVVM
{
    public class ViewModel : INotifyPropertyChanged
    {
        public System.Collections.ObjectModel.ObservableCollection<Model> Models { get; set; }
        private string _text;
        public string Text
        {
            get { return _text; }
            set
            {
                _text = value;
                if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("Text"));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public ViewModel()
        {
            Models = new System.Collections.ObjectModel.ObservableCollection<Model>();
            Models.CollectionChanged += Models_CollectionChanged;

            Models.Add(new Model() { Text = "Text" });
            Models.Add(new Model() { Text = "Text" });
            Models.Add(new Model() { Text = "Text" });
            Models.Add(new Model() { Text = "Text" });
            Models.Add(new Model() { Text = "Text" });
            Models.Add(new Model() { Text = "Text" });
            Models.Add(new Model() { Text = "Text" });
            Models.Add(new Model() { Text = "Text" });
            Models.Add(new Model() { Text = "Text" });
            Models.Add(new Model() { Text = "Text" });
        }

        void Models_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {

        }

    }
}

Her kan I se at Models er af typen ObservableCollection. Jeg har i min constructor, tilføjet en funktion, som skal køres når denne ObservableCollection ændres. Selve implementeringen af denne funktion har jeg undladt.

For de af jer der er nysgerrige, så ser min klasse Model således ud:

namespace MVVM
{
    public class Model : INotifyPropertyChanged
    {
        private string _text;
        public string Text { get { return _text; } set { 
            _text = value; 
            if(PropertyChanged !=null)
            PropertyChanged(this, new PropertyChangedEventArgs("Text")); 
        } }

        public event PropertyChangedEventHandler PropertyChanged;
    }
}

Lige til. I skal se bort fra min dårlige kodestruktur og navngivning, da dette er underordnet, i dette eksempel. Jeg håber I kan se udover dette.

Det ser sker, når app'ens ListView bliver reorderet, er at den underliggende ObservableCollection fyrer eventet af, og kalder vores funktion, hvorefter vi kan reagere på det, og opdatere vores domæne kode.

Grunden til at jeg laver et indlæg om noget så trivielt er, at man til Windows Phone ikke skal kalde de samme properties, som man skal til desktop, for at opsætte hele reorderingen via drag and drop. Derfor kan det forvirre yderligere (som jeg gjorde i mit tilfælde), når man derefter skal finde ud af hvordan man skal lytte på selve reorderingen, for er dette det samme som på desktoppen, når nu det differentiere sig ved selve opsætningen? Det er det ikke, men det tog mig lidt tid at finde ud af, så derfor har jeg skrevet et indlæg om det. Det er desuden meget svært at google sig frem til løsningen med at man på Windows Phone skal aktivere reordering via drag and drop med ReorderMode="Enabled", og ikke AllowDrop og CanReorderItems til true som på desktop. Jeg fandt svaret i en kommentar på et svar til et spørgsmål på StackOverflow.

Mono.Cecil

udgivet 1/25/2015 4:30:13 PM

Mono.Cecil er smart. Det er et bibliotek som kan bruges til manipulere managed dll filer efter de er kompileret. Det er et godt stykke værktøj hvis du fx står med en dll, som du ikke kan debugge, da du ikke source filen, eller måske ikke kan installere windbg på serveren. Her kan man hente dll’en ned, og fx smide trace information ind de steder som skal kigges efter i sømmene. Jeg har altid kigget lidt på code injection, da jeg synes det er sjovt. Man får styrket IL gymnastikken lidt, og får rørt lidt ved aspekt orienteret programmering (det er ihvertfald min indgangsvinkel til at lege med Mono.Cecil).

Jeg har fx lavet en lille WriterLibrary, som jeg gerne vil bruge i en anden dll, kaldet WebApplication.dll. Mit lille tankespil går ud på at WebApplication.dll er min, ikke source kode defineret, ikke windbg-able, applikation, som jeg gerne vil trace. WriterLibrary er min “store” debug og trace bibliotek som jeg gerne vil bruge til at kigge WebApplication efter i sømmene. Der er ikke meget dokumentation omkring Mono.Cecil på nettet, dog er det meget simpelt at komme igang med. For at komme igang vil jeg springe lige ud i det. Jeg har lavet et konsol program som ser sådan her ud

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CecilTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Mono.Cecil.AssemblyDefinition source =
                Mono.Cecil.AssemblyDefinition.ReadAssembly(@"C:sti\til\\WebApplication.dll");

            Mono.Cecil.AssemblyDefinition lib =
                Mono.Cecil.AssemblyDefinition.ReadAssembly(@"C:\sti\til\WriterLibrary.dll");

            source.MainModule.Import(lib.MainModule.Types[1]);
            source.MainModule.Write("WebApplicationModified.dll");
        }
    }
}

For at alt dette virker skal vi også have fat i Mono.Cecil, men dette er let. Man henter Mono.Cecil med NuGet. Åbn Package Manager Console og kør “Install-Package Mono.Cecil”. Kommandoen henter de dll’er der skal til for man kan gøre brug af Mono.Cecil. Min WriterLibrary er her blot en simpel class library som ser sådan her ud

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WriterLibrary
{
    public class Writer
    {
        public void WriteLine(string s)
        {
            Console.WriteLine("wrote:" + s);
        }
    }
}

Når man laver en Import på en hvilken som helst type fra WriterLibrary til WebApplication dll’en, så sørger Mono.Cecil også for at lave en reference til WriterLibrary dll’en. Du kan selv tjekke efter ved at inspicere WebApplication dll’en før konsol applikationen er kørt, og efter. Da man skal betale for Reflector, vil jeg råde alle til at bruge IlSpy. Google den, og hent programmet. Den kan ikke alt det som Reflector kan, men den er gratis, og man kan kigge på dll’er. Meget praktisk Smiley

Ved at inspicere WebApplication dll’en efter konsol applikationen er kørt, kan man se at referencen er tilføjet. Meget smart. Man kan nu bruge WriterLibrary til at lave trace på WebApplication. Jeg har endnu ikke lavet noget injection, men det er noget jeg vil skrive om efterfølgende, da jeg selv lige skal lære at bruge Mono.Cecil. Min vision med dette eksempel er at jeg vil prøve at injecte WriteLine funktionskaldet ind i WebApplication.dll’ens Global.Application_Error, da jeg ved at WebApplication gør brug af en Global.asax.

Håber at der er nogen der kan bruge mit lille eksempel til noget.