Jeg har for lang tid siden skrevet et indlæg på dotninjas om, hvordan man laver en single file generator (også kaldet et custom tool) til Visual Studio.  Desværre ligger det så lang tid tilbage, at jeg ikke kan grave et link frem.  En single file generator benyttes mange steder til at genere kode ud fra indholdet af en anden fil.  For eksempel benyttes det af Entity Framework til at lave klasser ud fra en XML fil som vist på billedet herunder, hvor Custom Tool er sat til “EntityModelCodeGenerator”.

CustomTool

For at lave en Single File Generator skal man implementere nogle bestemte COM interfaces og registrere sin dll på en bestemt måde i registry, og metoden fra min gamle (bortkomne) artikel har fungeret fint indtil fornylig, hvor jeg langt om længe fik mig en Windows 7 64 bit boks på arbejdet.  Jeg har prøvet alt på den maskine både med Wow32To64, og hvad ved jeg.  Men VS 2010 blev ved med fuldstændig at ignorere mit gamle custom tool.

På MSDN er der dokumentation af, hvordan man laver Single File Generators, og der findes også et sample i VS SDK’en, som beskriver, hvordan man kan lave en Single File Generator vha. VS Extensions.  Eksemplet virker fint, men det er ikke særlig godt beskrevet.  Især er det ikke beskrevet, hvordan man sætter projektet op til at blive kompileret helt korrekt, så registry entries bliver genereret korrekt.  Det håber jeg, at dette indlæg kan råde bod på.

Målet er at lave en Single File Generator, der nemt kan distribueres vha. af en .vsix pakke, så den nemt kan installeres som en Visual Studio Extension.  Dette er noget nemmere end min gamle metode, hvor der skulle registreres en dll vha. regasm.exe.

Det er en forudsætning, at man har Visual Studio Service Pack 1 samt Visual Studio SDK Service Pack 1 installeret.  Når SDK’en er installeret, er der adgang til et par VS projekttemplates, som kan bruges til at lave VS Extensions.  Til at lave en Single File Generator, skal man bruge den template, der hedder “VSIX Project”.

vsixproject

Efter projektet er oprettet, skal man først tilføje nogle referencer til en række dll’er for at få fat i de rigtige COM interfaces.  Det drejer sig om følgende:

  • EnvDTE
  • EnvDTE80
  • Microsoft.VisualStudio.Designer.Interfaces
  • Microsoft.VisualStudio.OLE.Interop
  • Microsoft.VisualStudio.Shell.10.0
  • Microsoft.VisualStudio.Immutable.10.0
  • Microsoft.VisualStudio.Interop
  • Microsoft.VisualStudio.Interop.8.0
  • Microsoft.VisualStudio.Interop.9.0
  • Microsoft.VisualStudio.Interop.10.0
  • VSLangProj
  • VSLangProj2
  • VSLangProj80

Det er muligt, at nogle enkelte referencer ikke er nødvendige.  Det har jeg ikke testet.  Det er i øvrigt vigtigt, at alle referencer har “Embed Interop Types” sat til “False”.

EmbedInteropType

Med det ovennævnte sample følger nogle standardimplementationer af to interfaces IVsSingleFileGenerator og IObjectWithSite.  Det nemmeste er at bruge disse implementationer, så vi kopierer BaseCodeGenerator.cs og BaseCodeGeneratorWithSite.cs.  Bemærk, at BaseCodeGenerator indeholder nogle Trace.WriteLine statements, man skal fjerne for at kompilere, hvis man kopierer de to klasser direkte fra eksempelkoden.

Dernæst tilføjes filen CodeGeneratorRegistrationAttribute.cs, som kommer med SDK’en, og som standard ligger under “C:\Program Files (x86)\Microsoft Visual Studio 2010 SDK SP1\VisualStudioIntegration\Common\Source\CSharp\RegistrationAttributes\”.  Denne klasse indeholder en implementation af en custom attribute, som VS benytter til at generere de nødvendige registry entries i en såkaldt .pkgdef fil.  Mere om det om lidt.

Det er nu tiden til at implementere selve kodegenereringen.  Dette gøres ved at tilføje en klasse, som skal nedarve fra BaseCodeGeneratorWithSite.

    [ComVisible(true)]
    [Guid("A4008ECD-02C6-418F-A0BD-083461479064")]
    [CodeGeneratorRegistration(typeof(DotNinjasCodeGenerator), 
        "DotNinjas sample C# code generator", 
        vsContextGuids.vsContextGuidVCSProject, 
        GeneratesDesignTimeSource = true)]
    [ProvideObject(typeof(DotNinjasCodeGenerator))]
    public class DotNinjasCodeGenerator : BaseCodeGeneratorWithSite
    {
        protected override byte[] GenerateCode(string inputFileContent)
        {
            return Encoding.ASCII.GetBytes("// Some generated code goes here");
        }
    }

Ikke overraskende er det metoden GenerateCode, der kaldes af VS, når man kører sit custom tool.  Parameteren inputFileContent vil indeholde indholdet af den fil, som vores custom tool bliver anvendt på.

Bemærk den imponerende række attributes på klassen.

Klassen skal nødvendigvis være COM visible og dermed også have en Guid attribute (husk også at gøre hele assembly COM visible).  CodeGeneratorRegistration benyttes som nævnt til at generere de nødvendige registry settings automatisk.  Disse registry settings ryger i en .pkgdef fil.  Bemærk vsContextGuids.vsContextGuidVCSProject som angiver, at vores kodegenerator vil generere C# kode.  ProvideObject fortæller VS, hvilken klasse der skal oprettes til at generere koden.  Det er et krav, at klassen markeret med ProvideObject skal have en default constructor.  Bemærk at det lidt overraskende er ProvideObjectAttribute, der bestemmer navnet på vores generator.  Dvs. i dette eksempel er det “DotNinjasCodeGenerator”, der skal benyttes som navnet på vores custom tool i VS.

Projektet er nu klar til at kompilere, og der kommer ganske rigtigt en .vsix fil ud som resultat.  Desværre er det ikke nok.  Der mangler en dll og der mangler en .pkgdef fil med de nødvendige registry entries.  For mig var det lidt et mysterium, hvordan jeg fik disse filer genereret, men det kræver, at man redigerer direkte i .csproj filen.  Så start din favorit csproj editor (såsom notepad) og slet følgende linjer:

    <GeneratePkgDefFile>false</GeneratePkgDefFile>
    <IncludeAssemblyInVSIXContainer>false</IncludeAssemblyInVSIXContainer>
    <IncludeDebugSymbolsInVSIXContainer>false</IncludeDebugSymbolsInVSIXContainer>
    <IncludeDebugSymbolsInLocalVSIXDeployment>false</IncludeDebugSymbolsInLocalVSIXDeployment>
    <CopyBuildOutputToOutputDirectory>false</CopyBuildOutputToOutputDirectory>
    <CopyOutputSymbolsToOutputDirectory>false</CopyOutputSymbolsToOutputDirectory>

GeneratePkgDefFile skal væk, så vi får vores .pkgdef fil, og CopyBuiltOutputToOutputDirectory forhindrer, at der bliver kompileret en dll.  Tilføj desuden følgende PropertyGroup i .csproj filen og gem den:

  <PropertyGroup>
    <RegisterOutputPackage>true</RegisterOutputPackage>
    <RegisterWithCodebase>true</RegisterWithCodebase>
  </PropertyGroup>

Efter reload af den nye projektfil, vil en kompilering give følgende output:

projectoutput

Man kan nu teste pakken ved at trykke F5 for debug, hvilket vil starte en ny instans af VS, hvor man kan afprøve vores custom tool.  Vi vil i stedet prøve vores vores custom tool i den virkelige verden.  Så start med at lukke alle instanser af VS og dobbeltklik på .vsix filen og tryk “Install”.  Dette vil installere vores custom tool som en VS Extension, og ved opspart af VS vil man ganske rigtigt kunne finde DotNinjasSingleFileGenerator som installeret extension.  Ønsker man at angive en lidt mere sigende beskrivelse end bare “Empty VSIX project”, kan det gøre ved at rette i source.extension.vsixmanifest filen i VS.

extension

For at teste DotNinjasSingleFileGenerator, opret et nyt C# projekt, tilføj en fil af en type, som normalt ikke benyttes i et C# projekt (f.eks. en .txt fil men ikke en .cs fil) og angiv DotNinjasCodeGenerator som custom tool, da det var DotNinjasCodeGenerator, der blev angivet som type til ProviderObjectAttribute.

customtoolinaction

Højreklik på din fil og vælg “Run custom tool”, hvilket vil resultere i en .cs fil indholdende den genererede kode.

Det er et meget positivt tiltag fra Apple, at de understøtter Nikon’s RAW format (dvs. .nef filer).  Det eneste, der er påkrævet, er Apples Camera Connection Kit til sølle 200 kr., og så er man kørende.  Sæt camera connector i iPad’en og forbind med USB kabel til kameraet, så importeres billederne til iPad’en.

Man skal dog lige være opmærksom på en lille detalje.  Billederne ligger i iPad’en som .nef filer, medmindre man i vanvare kommer til at trykke rotér-knappen (se skærmbillede).  Uden yderligere advarsel bliver billedet konverteret til .jpg under roteringen.  Det skal man lige være opmærksom på, hvis man har tænkt sig senere at flytte RAW billederne til PC til videre efterbehandling.

foto

Benytter man HTTP stacken fra Silverlight – herunder kald af WCF services over HTTP – sker kaldet asynkront.  Derfor skal man passe på, hvis man opdaterer brugerkontroller i sin callback funktion, da den kan blive kaldt på en anden tråd end den, der ejer brugerkontrollen.  Det er gamle nyheder.  Bruger man et pattern som f.eks. MVVM, hvor opdatering af ens view sker gennem binding til properties på en viewmodel, sker marshalling til UI tråden automatisk af Silverlight’s binding mekanisme.  I andre tilfælde er man nødt til selv at sørge for, at brugerinterfacet opdateres på den rigtige tråd f.eks. gennem Dispatcher.

Jeg har først fornylig opdaget, at callbacks fra WCF services i nogle tilfælde automatisk bliver flyttet til den kaldende tråd i Silverlight.  Det gælder også Silverlight til WP7.  Det sker i det tilfælde, hvor man benytter client proxy klasserne, der genereres, når man vælger “Add service reference” fra Visual Studio. 

Lad os sige, at jeg har en service kaldet Service1 med en metode DoWork.  Jeg har tilføjet en reference til servicen gennem VS, hvorved et namespace ServiceReference1 med en proxy klasse kaldet Service1Client er blevet oprettet.  Følgende kode er dermed fuldt lovlig:

label1.Text = Thread.CurrentThread.ManagedThreadId.ToString();

ServiceReference1.Service1Client service = new ServiceReference1.Service1Client();
service.DoWorkCompleted += (s, args) =>
    {
        // Dette kald er fuldt lovligt, og label1 og label2 vil vise samme thread id.
        label2.Text = Thread.CurrentThread.ManagedThreadId.ToString();
    };
service.DoWorkAsync();

Kigger man dybt nok i de genererede klasser, kan man se, at de klassen AsyncOperation benyttes til at flytte kaldet tilbage til den kaldende tråd.  Har man brug for at lave noget arbejde i sin callback fra sin service, må man undlade at bruge den genererede client proxy og f.eks. bruge ChannelFactory eller noget andet.  Forrige eksempel er funktionelt ækvivalent med nedenstående, hvor marshalling til UI tråden sker manuelt gennem brug af AsyncOperation.  Bemærk at AsyncOperation skal oprettes gennem AsyncOperationManager.CreateOperation:

AsyncOperation asyncOp;

void CallWcf()
{
    asyncOp = AsyncOperationManager.CreateOperation(1);
    ServiceReference1.IService1Channel channel 
        = new ChannelFactory<ServiceReference1.IService1Channel>("BasicHttpBinding_IService1")
        .CreateChannel();
    channel.BeginDoWork(DoWorkCallback, channel);
}

void DoWorkCallback(IAsyncResult result)
{
    ServiceReference1.IService1Channel channel = result.AsyncState as ServiceReference1.IService1Channel;
    channel.EndDoWork(result);

    asyncOp.PostOperationCompleted(new SendOrPostCallback(o =>
        {
            label2.Text = (string) o;
        }), Thread.CurrentThread.ManagedThreadId.ToString());
}

I stedet for AsyncOperation kunne man også vælge at bruge SynchronizationContext eller Dispatcher direkte.  Faktisk er AsyncOperation blot en wrapper omkring SynchronizationContext, og i Silverlight er SynchronizationContext en instans af DispatcherSynchronizationContext, som igen er en wrapper omkring Dispatcher.  Så Dispatcher er den rigtige helt her.

Som jeg har nævnt før, og som alle nok efterhånden har hørt, så kan WP7 ikke synkroniseres med Outlook.  Det kom som lidt af en overraskelse for mig.  Jeg havde helt klart en forventning om, at Zune ville kunne synkronisere Outlook.  Efter det første chok havde lagt sig, begyndte jeg naturligvis at søge efter andre metoder.

WP7 synkroniserer helt perfekt med OWA, Windows Live og GMail.  Min første tanke var derfor at bruge firmaets OWA.  Desværre arbejder jeg i et firma, hvor OWA er et fy-ord, og man skal gennem VPN, Citrix og alt muligt, for at komme i nærheden af sin mailbox.

Næste bud på listen var Windows Live.  Mærkeligt nok var jeg ikke i stand til finde en mulighed for at synkronisere Outlook til Windows Live.  Der er mulighed for at få sin Windows Live kalender vist i Outlook, men det er den forkerte vej.  Hvis nogen kender en måde at synkronisere mellem Outlook og Windows Live, så lad mig endelig høre.

Sidste mulighed var GMail.  Google har deres egen Google Calendar Sync.  Desværre kunne jeg ikke få den til at virke overhovedet.  Den kom med en underlig fejl, og jeg har Google mistænkt for at have skrinlagt komponenten.  Ærgerligt.  Efter noget søgen faldt jeg over Ogg Sync.  Ogg Sync kan ifølge eget udsagn synkronisere både kalender og kontakter, hvilket var, hvad jeg havde brug for.  Jeg købte Ogg Sync (for $30 tror jeg), og synkronisering af Outlook kalender med GMail fungerede perfekt.  Men desværre kan man ikke bruge samme licens til synkronisering af kalender og kontakter.  Om det er en bug i Ogg Sync vides ikke, for support svarer ikke på mails, og der er intet nævnt på web sitet.  Så pas på med Ogg Sync!

Efter lidt mere søgning fandt jeg GO Contact Sync Mod, som kan synkronisere Outlook kontakter med GMail.  Det fungerer næsten perfekt.  Der er dog nogle problemer med at synkronisere kontakter, som bare har et firmanavn.  Men det er småting.

Efter møge og besvær, begyndte Google Calendar Sync på magisk vis også at virke, så Ogg Sync er sendt på pension, og jeg er endt med følgende setup:

  • Google Calendar Sync til at synkronisere kalender med GMail.
  • GO Contact Sync Mod til at synkronisere kontakter med GMail.

Det er lidt noget rod at skulle have flere “services” kørende bare for at kunne synkronisere.  Men der er også fordele ved, at Zune ikke kan synkronisere på desktoppen.  Sålænge min computer bare er tændt på arbejdet, vil Exchange på arbejdet være opdateret med de aftaler, jeg ligger ind i min WP7 – og omvendt.  Det er faktisk genialt, og nu hvor det bare spiller, synes jeg klart det er en bedre løsning en synkronisering med Outlook. 

Samtidig er min WP7 synkroniseret med en OWA fra mit eget firma.  I WP7’s kalender bliver aftaler fra de forskellige kalendere vist med forskellige farver, så man har fuldstændig styr på det.

Er der andre, som har erfaringer med synkroniseringen, og som måske har en smartere løsning, så vil jeg gerne høre det.

Siden april 2010 har jeg overladt dotninjas.dk fuldstændig i ne0san’s og pandasan’s kompetente hænder.  Men jeg har fornylig anskaffet mig en LG Optimus 7, der kører med Windows Phone 7, og sådan et indkøb er et oplagt emne til en blog, for der skal lidt arbejde til for at få det til at køre optimalt.  Mere om det nedenfor og forhåbentlig i nogle følgende indlæg.

Jeg vil egentlig ikke bruge så meget tid på at anmelde selve telefonen.  Den er fin og lækker i designet (men nok ikke “iPhone 4 fin og lækker”) med en dejlig stor skærm, og selve WP7 OS er et frisk pust ovenpå iOS og alle iOS klonerne, der er derude.  Den kan ikke multitaske og andet sjov, men det fungerer rigtig godt med tiles og de såkaldte panorama views, og når apps følger WP7’s design guide kaldet Metro er tingene meget lækre at arbejde med.

Dette var min anmeldelse af WP7 og LG Optimus 7.  Nu videre til opsætning, så man kan få lov til at bruge sine surt optjente penge i marketplace.

Et af de største problemer med WP7 i Danmark er, at den ikke officielt er kommet til Danmark.  Det betyder, at man som dansker ikke umiddelbart har adgang til marketplace, hvilket er Microsoft’s svar på iTunes App Store.  Men fortvivl ej – der er (vistnok flere) måder at omgå det på.  Følgende er, hvad jeg gjorde for at få adgang til marketplace i UK:

  1. Opret en Windows Live konto, hvor du angiver UK som dit hjemland.
  2. Opret en en Zune konto, hvor du angiver en rigtig adresse i UK.  Jeg brugte min storesøster’s adresse, idet hun tilfældigvis bor i England.  Denne adresse benyttes som billing address, men jeg har ikke oplevet, at der rent faktisk bliver sendt noget fysisk brev til adressen fra Zune.
  3. Tilknyt et kreditkort til den nyoprettede Zune konto.
  4. Download og installer Zune softwaren.  Zune benyttes til at synkronisere musik, video og billeder med telefonen.  Zune synkroniserer ikke med Outlook, og der er ingen umiddelbar måde at gøre det på.  Det vil jeg måske skrive om på et andet tidspunkt. 
  5. Zune klienten benytter de regionale settings på computeren, og så længe de er sat til Danmark, vil Zune ikke tillade login og adgang til marketplace via din pc.  Gå derfor i kontrolpanelet og vælg “Skift grænsefladesprog”.  Gå til fanen “Placering” og vælg “Storbritannien”.    Udklip
  6. Start Zune op igen og du vil opdage en “Sign in” knap øverst til højre.

Så er det tid til at sætte selve telefonen op.  Jeg kan ikke helt huske fremgangsmåden her, men jeg mener, det var noget i retning af følgende:

  1. Vælg “Settings”.
  2. Vælg “email & accounts”. 
  3. Vælg “add an account” og klik “Windows Live”.  Jeg mener at kunne huske, at WP7 kommer med en advarsel om, at den første oprettede Windows Live konto vil være den, der fremover er tilknyttet telefonen.  Derfor skal du bruge den Windows Live UK konto, du oprettede tidligere.

Det var det.  Det tog mig lidt tid at rode rundt mellem for at få det til at spille, men i virkeligheden er det meget få trin, der skal til.  Så vidt jeg kan se, er det desværre nødvendigt at oprette en ny Windows Live konto, hvor man angiver UK som land, da det land, der optræder i Zune profilen, er styret af ens Windows Live login.  ´

Diabetikere bør (i hvert fald i perioder) føre dagbog over blodsukkermålinger som minimum foretaget før og efter hvert hovedmåltid og umiddelbart før sengetid.  Samtidig skal man tælle kulhydrater og løbende holde styr på sammenhængen mellem kulhydratindtaget og antallet af insulinenheder, man injicerer.

I anledning af halvårsdagen for konstateringen af min type 1 diabetes, har jeg arbejdet på et website til online registrering af blodsukkermålinger samt af indtag af insulin og kulhydrater.  Udover at give mig selv og andre diabetikere et centralt sted at føre dagbog, er det også mit første rigtige “live” ASP.NET MVC 2 og .NET 4.0 projekt.

Bayer Diabetes lever bl.a. af at lave blodsukkerapparater.  Hvert år udbyder de en sum penge til projekter, som på en eller anden måde gør livet nemmere for diabetikere, og mit website er blevet valgt som en af tre finalister til at modtage hjælp fra deres fond.  Den endelige vinder findes ved afstemning, hvor alle interesserede kan stemme.

Derfor beder jeg dig, kære læser, om at gå ind på og stemmemit bidrag.

Plz, plz, plz

P.S. Mit website er endnu ikke online, men du kan se et par skærmbilleder herunder.

simplewins1 simplewins2 simplewins3

Extension methods har vist sig yderst bekvemme.  Det mest kendte eksempel er naturligvis det væld af extension methods, man finder i LINQ.  De giver en mere præcis og kort notation.  Jeg synes dog, at det kan tage overhånd, fordi folk har tendens til at bruge extension methods til at “dekorere” typer med metoder, som egentlig ikke hører hjemme på typen.  Når frameworks som Rhino Mocks og testdelen af MVC Contrib bruger extension methods på strenge og andre ret grundlæggende typer, kan jeg acceptere det, fordi brugen af test frameworks er så præcist afgrænset.  Men når folk f.eks. begynder at putte extension methods for query string parsing og andet godt på alle strenge i et projekt, mener jeg, at man er gået over grænsen.  Man skal stadig følge OO-reglerne for godt design af en type.

Min egen brug af extension methods har derfor været yderst begrænset.  Jeg har dog defineret to meget nyttige extension methods til WPF typen DependencyObject: GetChild og GetParent.

Metoderne kan benyttes til rekursivt at vandre op eller ned i det visuelle træ for et DependencyObject.  De returnerer det første objekt, de støder på i træet, som er af den type, der er angivet som typeparameter til metoden. 

Koden ser ud som følger.  GetChild har jeg tyvstjålet fra et sted på nettet, hvor man kan finde et utal af nærmest identiske implementationer af metoden.

public static class VisualHelpers
{
    public static T GetChild<T>(this DependencyObject referenceVisual) where T : DependencyObject
    {
        DependencyObject child = null;
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(referenceVisual); i++)
        {
            child = VisualTreeHelper.GetChild(referenceVisual, i) as DependencyObject;
            if (child != null && child is T)
            {
                break;
            }

            child = GetChild<T>(child);
            if (child != null && child is T)
            {
                break;
            }
        }
        return child as T;
    }

    public static T GetParent<T>(this DependencyObject referenceVisual) where T : DependencyObject
    {
        if (referenceVisual == null)
            return null;
        if (referenceVisual is T)
            return referenceVisual as T;
        var parent = VisualTreeHelper.GetParent(referenceVisual);
        return parent.GetParent<T>();
    }
}

Så hvad kan man bruge metoderne til?  F.eks. kan man bruge GetChild til at style textbox-delen af en combobox.  Det er der nemlig ikke mulighed for “out-of-the-box”.  Det kan gøres ved at nedarve fra ComboBox og lave en override af OnApplyTemplate.  I OnApplyTemplate benyttes GetChild til at finde combobox’ens ContentPresenter og ændre dens ContentTemplate:

public class ComboBoxEx : ComboBox
{
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        var selectionBoxHost = GetChild<ContentPresenter>(this);
        if (selectionBoxHost != null)
        {
            selectionBoxHost.ContentTemplate = ...;
        }
    }

}

I WPF benyttes INotifyPropertyChanged hyppigt ifm. binding af view model til view.  De fleste er nok stødt på en variant af følgende standardimplementation:

public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
    PropertyChangedEventHandler handler = this.PropertyChanged;
    if (handler != null)
    {
        var e = new PropertyChangedEventArgs(propertyName);
        handler(this, e);
    }
}

I ens view model kaldes OnPropertyChanged i get’eren i de properties, der indgår i binding til view:

public string SomeProperty
{
    get { return _someProperty; }
    set
    {
        if (_someProperty != value)
        {
            _someProperty = value;
            OnPropertyChanged("SomeProperty");
        }
    }
}
 

Det er naturligvis vigtigt, at parameteren til OnPropertyChanged er det samme som navnet på ens property.  Jeg ved ikke, hvor mange gange, jeg har glemt at opdatere det navn, når jeg har ændret navn på min property.  Man opdager hurtigt fejlen, men hvor det dog irriterende.  Så jeg har lavet en alternativ og meget simpel version af OnPropertyChanged, som benytter expression trees.  Den ser således ud:

protected virtual void OnPropertyChanged<T>(Expression<Func<T>> e)
{
    var member = (MemberExpression) e.Body;
    OnPropertyChanged(member.Member.Name);
}

 

Den nye overload kaldes med en lambda:

OnPropertyChanged(() => SomeProperty);

Hvis jeg fremover ændrer navnet på min property, vil VS2008 automatisk opdatere i min lambda.  Lamdaudtrykket skal have den form, som er vist ovenfor, ellers får man en exception.

Desværre løser det ikke alle problemer med renames, da jeg stadigvæk selv skal huske at opdatere i XAML-filen.

Jeg har eksperimenteret lidt med at streame film, musik og billeder fra min PC’er til min PS3.  Jeg har indtil videre forsøgt mig med tre forskellige produkter som media server.  Hver især har sine fordele.  Det drejer sig om følgende produkter: Microsoft Windows Media Player, PS3 Media Server samt WinAmp Remote (også kaldet Orb).

Microsoft Windows Media Player

Opsætningen er nem direkte fra Media Player’s “Stream” menu, og PS3 har ikke nogen problemer med at finde Media Player.  Jeg har ikke haft nogen problemer med at streame hverken film, musik eller billeder via Media Player så længe man holder sig til de gængse formater som avi, jpg og wma/mp3.  Media Player streamer ikke “låste” musikfiler fra f.eks. TDC Play.

PS3 Media Server

PS3 Media Server er til fri download, og det er utroligt fleksibelt.  Desværre går den øgede fleksibilitet ud over brugervenligheden.  Jeg må erkende, at jeg ikke ikke forstår halvdelen af de opsætningsmuligheder, der findes.  PS3 Media Server streamer film, musik og billeder uden problemer.  Den kender flere formater end Windows Media Player herunder Nikons RAW NEF billedformat, hvilket er ganske sejt.  Jeg ved dog ikke, om det kan anbefales, eftersom den genererer thumbnails af billederne, hvilket tager en rum tid, hvis en folder indeholder et par hundrede billeder af 10+ MB hver.

Streaming af film kræver at VLC Media Player er installeret.  Film kan encodes på forskellige måder, hvis man har forstand på sådan noget.

PS3 Media Server kan også streame YouTube film og efter sigende internet TV.  Jeg fik det til at virke for YouTube en kort overgang, men var en stakket frist.  Det skyldes enten, at jeg opgraderede til Windows 7, eller at jeg opgraderede VLC Media Player.

PS3 Media Server kan ikke håndtere filer fra TDC Play.

WinAmp Remote

WinAmp Remote er en del af den sædvanlige WinAmp download (man skal huske at tilvælge Remote i installationen).  Sjovt nok virker selve WinAmp ikke på min Windows 7 boks (til trods for WinAmps “Windows 7 Compliant” statement), men WinAmp Remote virker fint.  Selve brugerinterfacet til at konfigurere WinAmp Remote er sindssygt ringe.

Streaming af film fungerer ok, men kvaliteten er ret dårlig.  Dette kan måske konfigureres, men jeg har ikke fundet ud af hvordan.  WinAmp Remote afspiller YouTube film uden problemer, men jeg mistænker den for at gøre det med nedsat kvalitet.

Musikfiler afspilles uden problemer.  Det gælder også TDC Play!  Det skulle også virke med iTunes.

En anden sej ting ved WinAmp Remote er, at hvis man opretter et login, kan det streame musik fra ens PC til en browser hvor som helst.  F.eks. kan man via sin iPhone afspille musik hjemmefra.

Konklusion

Hvis man vil kunne det hele, må man nok ty til en kombination af ovenstående.  PS3 Media Server kan næsten det hele, men det er svært at få konfigureret rigtigt.  WinAmp Remote er dårligt til film, men med muligheden for at afspille filer fra TDC Play, har man adgang til alverdens musik gratis (hvis man vel at mærke er TDC kunde). 

Jeg har ikke forsøgt mig med TVersity, så jeg aner ikke, hvad det kan. 

Jeg vil også nævne VidZone, som er et nyt program fra PlayStation Store, der giver adgang til et pænt udvalg af gratis musikvideoer.

Det er nok ikke ukendt for de fleste, at Windows kontroller kun må tilgås af den tråd, som oprettede kontrollen.  Sådan er det også i WPF.  Heldigvis kan man sige.  Det er svært at forestille sig et brugerinterface, der kan opdateres af flere tråde.

Forleden lavede jeg en WPF applikation med et antal arbejdertråde, som hver især kommunikerede status tilbage til brugerinterfacet. Statusfelterne i brugerinterfacet gjorde brug af binding til properties på de bagvedliggende viewmodels.  Der var en viewmodel og et view til hver tråd, så der var ikke brug for synkronisering imellem trådene, men pludselig gik det op for mig, at disse properties blev opdateret af en anden tråd end min GUI tråd!   

Eftersom det hele virkede upåklageligt, måtte det være WPF binding, der gjorde min marshalling for mig.  Det måtte undersøges.

Til at illustrere har jeg lavet en lille WPF applikation med et vindue (view) indeholdende en textbox og en button.  Indholdet af textbox’en er bundet op vha. binding til en property (SomeData) på en klasse, som implementerer INotifyPropertyChanged.  Klassen ser således ud:

class ViewModel : INotifyPropertyChanged
{
    int _someData = 0;

    public int SomeData
    {
        get { return _someData; }
        set
        {
            _someData = value;
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs("SomeData"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

 

I koden til vinduet sættes binding op.  Når brugeren trykker på knappen, startes en tråd op, som opdaterer SomeData property, hvorved textbox’ens indhold opdateres.  Det er her magien sker, og WPF tager over og på en eller anden måde sørger for, at min binding sker på den rigtige tråd.  Koden til vinduet ser således ud:

 

public partial class Window1 : Window
{
    ViewModel _viewModel;
    Thread _t;

    public Window1()
    {
        InitializeComponent();

        _viewModel = new ViewModel();
        gridLayout.DataContext = _viewModel;

        textBoxSomeData.SetBinding(TextBox.TextProperty, new Binding("SomeData"));
    }

    private void button1_Click(object sender, RoutedEventArgs e)
    {
        _t = new Thread(new ThreadStart(ThreadStart));
        _t.IsBackground = true;
        _t.Start();
    }

    void ThreadStart()
    {
        for (int i = 0; i &lt; 100; i++)
        {
            _viewModel.SomeData = i;
            Thread.Sleep(100);
        }
    }
}

 

Sætter man et breakpoint i ThreadStart funktionen og begynder at debugge sig vej et stykke ind i Microsoft’s kode, rammer man på et tidspunkt en intern klasse ved navn MS.Internal.Data.ClrBindingWorker.  Denne klasse har en metode OnSourcePropertyChanged.  Inden i den metode finder man (blandt så meget andet) følgende kode:

 

if (Dispatcher.Thread == Thread.CurrentThread)
{ 
    PW.OnPropertyChangedAtLevel(level);
} 
else 
{
    // otherwise invoke an operation to do the work on the right context 
    SetTransferIsPending(true);
    Dispatcher.BeginInvoke(
        DispatcherPriority.DataBind,
        new DispatcherOperationCallback(ScheduleTransferOperation), 
        new object[]{o, propName});
} 

 

Af denne kode fremgår det, at WPF selv finder ud af, at der sker binding på tværs af tråde, og derfor benyttes Dispatcher.BeginInvoke og korrekt marshalling sker.

Det er smukt.  Endnu engang stor respekt for WPF binding.