Create your own local Azure IoT Edge dashboard

Earlier this year, when Azure IoT Edge was still in Public Preview, I wrote a couple of blogs about Visualizing Azure IoT Edge using local dashboard.

Back then, I had to do some magic with both a C# IoT Edge module, a custom NodeJS docker container, and a Docker network to get it running.

Since then, a lot has changed. Microsoft already released a ton of new features. a And there is still more to come regarding the Azure IoT platform.

But that awkward local dashboard solution was nagging me. A few months ago, Microsoft introduced a NodeJS module as a first-class citizen for IoT Edge modules.

So it was time to pick up the gauntlet and use NodeJS for this awesome local IoT Edge dashboard:

#tldr;  If you like to dig into the code, zip it, clone it, extend it or even make a pull request, I made this project open source. If you only want to use it the easy-going way, pull it from docker eg. ‘svelde/localdashboard:1.0.1-amd64′.

At this moment, only Linux containers are supported. It is tested both on Windows and Ubuntu as host OS.

Interested in this module? Let’s see how you can use it.

Continue reading “Create your own local Azure IoT Edge dashboard”

Advertenties

Cheap Arduino mesh using RF24 radio modules

Communication between devices is key when you want to combine one or more devices. In my previous blog, I used I2C, Bluetooth and RF24 modules. The latter ones, the RF24 have a few advantages: they are cheap and do not need pairing. In my previous blog, I used them for a peer-to-peer (or better: Master-Slave) communication. This time, we will look at a mesh network using these devices.

Continue reading “Cheap Arduino mesh using RF24 radio modules”

Getting IP-Address in Win 10 IoT Core

I was working on a new pet project for Windows 10 IoT Core.

At this moment Win10 IoT Core is not supporting the Wi-pi Wi-Fi dongle. So I am using a Dlink 505 mobile companion to attach my Raspberry Pi b2 to my local network. This works great although Visual Studio has some trouble detecting the device.

But because the default app (yes, this is just an app) on the Pi shows the current IP-address, I can simply submit that address when deploying (in the project settings).

pack1

So far so good.

But when I deployed my own app I ran into a little problem. My Pi gets the current IP-address dynamically so at one point my deployment was not working anymore. The device had a new IP-address….

And this address is also needed for RDP so it is rather important.

I wanted to read the current IP address in C# code so I could show it in my own app. This seems a bit trivial, but Google was not helping me this time. All examples were based on System.Net so that was not working.

Then I was thinking, could it be that the default app is just open sourced? That way I could look in the code MSFT provided.

And yes it is! Just go to the github location.

And there I found the code to read the IP4 address (now a bit modified):

public static string GetCurrentIpv4Address()
{
  var icp = NetworkInformation.GetInternetConnectionProfile();
  if (icp != null
        && icp.NetworkAdapter != null
        && icp.NetworkAdapter.NetworkAdapterId != null)
  {
    var name = icp.ProfileName;

    var hostnames = NetworkInformation.GetHostNames();

    foreach (var hn in hostnames)
    {
      if (hn.IPInformation != null
          && hn.IPInformation.NetworkAdapter != null
          && hn.IPInformation.NetworkAdapter.NetworkAdapterId
                                                     != null
          && hn.IPInformation.NetworkAdapter.NetworkAdapterId
                      == icp.NetworkAdapter.NetworkAdapterId
          && hn.Type == HostNameType.Ipv4)
      {
        return hn.CanonicalName;
      }
    }
  }

  return "---";
}

Resolve the namespaces if needed…

So thank you MSFT for making the code available.

jsTreeview in ASP.NET MVC gevuld met Json action

Op dit moment heeft jQuery UI nog geen treeview control. Als alternatief zijn er enkele andere aanbieders van Treeview controls. In het verleden heb ik al eens bericht over de Telerik controls. Deze kan ik zonder meer aanbevelen maar helaas zijn de gratis extensies alleen voor open source projecten toe te passen.

Een andere treeview is de jsTreeview. Deze is heel aardig (ondersteunt ajax, de jQuery themes en bv. ook checkboxes). Helaas is de deocumentatie nogal fragmentarisch. Daarom geef ik hier een opstap hoe jsTreeview is toe te passen in ASP.NET MVC. We maken hierbij gebruik van Json actions om de kinderen op te vragen vanuit de controller.

Stap 1: Bestanden plaatsen

Haal eerst de laatste versie op vanaf http://www.jstree.com/ . Dit is de versie pre 1.0 fix 2.

Plaats in het ASP.NET MVC4 project de jquery.jstree.js in de \Scripts map.

Plaats d.gif (voor ie6 gebruik), d.png, styles.css en throbber.gif in \Scripts\themes\treeview map.

Stap 2: bundles aanpassen

Vul de bundles aan in de BundleConfig.cs:

bundles.Add(new StyleBundle("~/Scripts/themes/treeview").Include(
                         "~/Scripts/themes/treeview/style.css"));
bundles.Add(new ScriptBundle("~/bundles/jqueryplugins").Include(
                        [skip other plugins],
                        "~/Scripts/jquery.jstree.js"));

En deze bundles moeten op de _Layout.cshtml aangeroepen worden:

…
 @Styles.Render("~/Scripts/themes/treeview")
…
</head>
<body>
…
 @Scripts.Render("~/bundles/jquery", "~/bundles/jqueryui", "~/bundles/jqueryplugins")
…

Stap 3: HTML placeholder

Plaats een placeholder op de _Layout.cshtml

<div id="treeviewEdit"> Tree view placeholder</div>

Stap 4: JavaScript

Plaats daarna het volgende  script onder aan de _Layout.cshtml pagina  om te worden uitgevoerd als de pagina laadt (dit moet dus in een javascript blok):

$(function () {
    $("#treeviewEdit").jstree({
        "json_data": {
            "ajax": {
                "url": "/Home/FillEditTree",
                "data": function (n) {
                    return { id: n.attr ? n.attr("id") : 0 };
                }
            }
        },
        "themes": {
            dots: false,
            icons: true
        },
        "plugins": ["themes", "json_data"]

    });
});

Deze jQuery functie zal op de treeview div losgelaten worden om het treeview tekenen te forceren.

Stap 5: de (view) modellen

We praten straks in Json formaat met de treeview, maar we willen dit in typed classes samenstellen. Voeg dus de volgende viewmodellen toe:

jstreeview1

De code wordt dan:

public class JsTreeModel
{
    public JsTreeData data { get; set; }

    public JsTreeAttribute attr { get; set; }

    // "open" or "closed" or null (not available for ajax)
    public string state { get; set; }

    public List<JsTreeModel> children { get; set; }
}
public class JsTreeAttribute
{
    public string id { get; set; }
}
public class JsTreeData
{
    public string title { get; set; }

    public JsTreeDataAttribute attr { get; set; }

    // "folder"  or "/" for no icon thus file
    public string icon { get; set; }
}
public class JsTreeDataAttribute
{
    public string href { get; set; }
}

Laatste stap 6: Controller action

Voeg de volgende action toe aan de controller. Deze geeft ter demonstratie als antwoord altijd de opgevraagde node met daaronder twee kinderen. De eerste keer wordt node 0 opgevraagd. het eerste kind heeft zelf ook kinderen (niet meegestuurd in deze aanroep) en de tweede heeft geen kinderen:

public ActionResult FillEditTree(int id)
{
    // just to show some nodes
    var rootId = id + 2;

    var children = new List<JsTreeModel>();

    // first child containing one or more children
    children.Add(
        new JsTreeModel
            {
                attr = new JsTreeAttribute { id = (rootId + 1).ToString() },
                data = new JsTreeData
                        {
                            title = "Child " + (rootId + 1).ToString(),
                            attr =
                                new JsTreeDataAttribute
                                    {
                                        href =
                                            @"\home\edit\"
                                            + (rootId + 1).ToString()
                                    }
                        },
                state = "closed",
            });

    // second child containing NO children
    children.Add(
        new JsTreeModel
            {
                attr = new JsTreeAttribute { id = (rootId + 2).ToString() },
                data = new JsTreeData { icon = "/", title = "Child " + (rootId + 2).ToString(), },
            });

    // de ‘root’
    var requestedSubTree = new List<JsTreeModel>();

    requestedSubTree.Add(
            new JsTreeModel()
            {
                attr = new JsTreeAttribute { id = rootId.ToString() },
                state = "open",
                data =
                    new JsTreeData
                        {
                            title = "Title " + rootId.ToString(),
                            attr =
                                new JsTreeDataAttribute { href = @"\home\edit\23" }
                        },
                children = children
            });

    return Json(requestedSubTree, JsonRequestBehavior.AllowGet);
}

Dit geeft het volgende plaatje (na enkele keren kinderen opvragen):

jstreeview2

Er zijn een aantal zaken die opvallen:

  • De ‘root’ moet standaard de state geopend (“open”) krijgen
  • Je moet dus onderzoeken of de getoonde kinderen op hun beurt ook kinderen hebben
    • De kinderen moeten standaard de state te openen (“closed”) krijgen als deze op hun beurt kinderen hebben.
    • Een kind zonder kinderen moet geen state krijgen (dus null). Dan komt er ook geen ajax call mogelijkheid.

Bonus stap 7: jQuery Thema

Als laatste in het rijtej plugins kan “themeroller” toevoegen worden.  Deze kan je toepassen als alternatief voor thema:

$(function () {
    $("#treeviewEdit").jstree({
        [skip],
        "themes": {
            dots: false,
            icons: true
        },
        "plugins": ["json_data", "themeroller"]
    });
});

Dit geeft een aardige uitbreiding op het standaard aanwezige thema:

jstreeview3

Bonus stap 8: cookies

Je kunt ook plugin “cookies” toepassen. Dan wordt bij een F5 de selectie bewaard en niet opnieuw opgevraagd. Maar de laatste kan ook op de server in de sessie onthouden worden en bij de initiële aanroep (node id is 0) toegepast kunnen worden.

Conclusie 

Deze plugin is heel aardig om voor weinig geld en snel een treeview toe te passen in ASP.NET MVC.

Seeing anything you like? Google translate does not work out? Drop me a note and I will translate this post.

“Don’t shoot me, I’m just the In Process messenger”

In need of an English translation? Please drop me a note.

Voor ons huidige project zochten we naar een goed ontkoppeling tussen de user interface en de communicatie met hardware. Omdat het een WPF applicatie is en we gebruik maken van MVVM draait alles in hetzelfde applicatie.  De hardware willen we real time uitlezen en reageren op het gedrag.

Er werd dus nagedacht over het gebruik van Events. Dat is een prima oplossing voor “Loose coupling”. Maar hierbij is de ontkoppeling maar één kant op. De ‘luisteraar’ kent de verstrekker van de gebeurtenissen want hij moet zich daarop abonneren.

Omdat we MVVMLight gebruiken kregen we de mogelijkheid om nog verder te gaan met ontkoppeling. MVVMLight bezit namelijk ook een Messagebus.

De messagebus kennen we vooral vanuit SOA en Enterprise architectuur:

Maar hier hebben we te maken met messsages die In Process verstuurd worden. Hierbij zijn de volgende klassen te herkennen:

  1. De boodschap, de Message
  2. Diegene die ‘m verstuurt, de Sender
  3. Diegene die zich abonneert op dit type bericht, de Target of algemener, de Receiver
  4. De Messenger

Bij MVVMLight is dit opgelost met een aantal objecten die globaal beschikbaar zijn en via locking threadsafe zijn gemaakt. Hierdoor wordt het mogelijk om overal in de applicatie boodschappen te verzenden en eventueel te ontvangen.

Een boodschap is in het algemeen een overerving  van MessageBase, een standaard klasse beschikbaar in MVVMLight:

public class DeviceMessage : MessageBase
{
  public DeviceMessage(string message)
  {
    Message = message;
  }

  public string Message { get; private set; }

  public override string ToString()
  {
    return "Device message: " + Message;
  }
}

Hier hebben we een DeviceMessage die één property genaamd Message bezit.
Een message wordt verstuurd via de Send methode op de static Messenger:

Messenger.Default.Send<DeviceMessage>
    (new DeviceMessage("Time on device: " + DateTime.Now.ToString()));

Meer is er niet nodig om een boodschap op de Messagebus te zetten. In dit voorbeeld kan ik berichten met een datum/tijd verwachten. Zo kan ik ook vanuit een ViewModel juist een start- of stopcommando doorgeven aan een bepaald apparaat (of beter: een instanciering van iets wat de interface van dat apparaat implementeert). Ik heb geen kennis van de luisteraar, ik weet niet eens of die wel bestaat.

Hoe wordt het bericht ontvangen? Via een constructor of initialisatie heeft een andere partij zich geabonneerd op deze berichten:

Messenger.Default.Register<DeviceMessage>
  (this, true, m => {   Message = m.Message; });

In dit geval wordt de boodschap in op een ViewModel de m.Message uitgelezen en op het scherm getoond (via een bindable property).

Maar er is ook een bonus. De MessageBus is globaal beschikbaar en iedereen kan zich abonneren op berichten. MVVMLight biedt de mogelijkheid om met een Message ook een filtertoken op te nemen.

Messenger.Default.Send<SwitchMessage>
  (new SwitchMessage(this, "AnotherDevice"), "DeviceSwitchToken");

In dit geval kunnen meerdere partijen zich abonneren op een SwitchMessage. Maar door de meegestuurde “DeviceSwitchToken” zal wellicht niet iedereen de SwitchMessage verwerken, ze wachten op zo’n bericht vergezeld met een ander token.

En het is ook mogelijk om zich juist op een scala van berichten te abonneren. Het is mogelijk om alle overervingen van een baseclass te ontvangen:

public class LogProvider : ILogProvider
{
  public void Initialize()
  {
    // log all messages
    Messenger.Default.Register<MessageBase>(this, true, m =>
    {
      Debug.WriteLine(
        String.Format(
           "At {0} we received: {1}", DateTime.Now.ToShortTimeString(), m));
    });
  }
}

Hier heb ik een logger geabonneerd op alle overervingen van MessageBase.

Door de totale ontkoppeling van de zenders en ontvangers kan je het overzicht verliezen. Maar juist door deze logger heb ik een mooie tijdslijn van wat er onderling verstuurd is en kan ik analyseren wat er mis is gegaan. Daarom implementeer ik ook de ToString() zodat ik de messages hun eigen te loggen boodschap kan laten beschrijven.

Ik kan ook als een soort van watchdog een pulse afgeven. Als ik niet de gewenste antwoorden ontvang kan ik ‘alarm slaan’. En dit kan ik doen zonder dat ik de daadwerkelijke implementatie van de te controleren instanties niet ken.

Er zijn in MVVMLight nog een aantal andere boodschappen mogelijk. Ik wil er nog één aanstippen.

MVVMLight kent ook een generic message. Deze bevat standaard een Content property waarin een andere klasse gestopt kan worden. In bovenstaande voorbeelden is steeds gebruik gemaakt van een MessageBase overerving met extra properties. Met de generic oplossing kunnen die properties in de Content geplaatst worden. De Content zier er dan zo uit:

public class SwitchContent
{
  public SwitchContent(int id, string message)
  {
    ID = id;
    Message = message;
  }

  public int ID { get; private set; }

  public string Message { get; private set; }
}

De message wordt een stuk eenvoudiger:

public class GenericSwitchMessage : GenericMessage<SwitchContent>
{
  public GenericSwitchMessage(SwitchContent switchParameter)
        : base(switchParameter)
  {
  }

  public override string ToString()
  {
    return String.Format(
      "Switch message: sender{0}; target={1}", Sender, Target);
  }
}

Het te verzenden bericht wordt nu gecombineerd met de context:

Messenger.Default.Send<GenericSwitchMessage>
  (new GenericSwitchMessage(new SwitchContent (1, "Hallo")));

En het uitluisten ziet er ook wat rustiger uit:

public class LogProvider : ILogProvider
{
  public void Initialize()
  {
    // log all messages
    Messenger.Default.Register<GenericSwitchMessage>(this, true, m =>
    {
      // m.Content.ID
      // m.Content.Message
    });
  }
}

Conclusie:

De MVVMLight Messenger biedt een fraaie oplossing voor de totale ontkoppeling. Er zijn ook andere implementaties beschikbaar (iedere MVVM implementatie lijkt er wel één te bieden) dus het is eenvoudig om er aan te komen en om het eens uit te proberen.

Een WPF StoryBoard starten vanuit het MVVM Viewmodel

 

In need of an English translation? Please drop me a note.

Sinds enige tijd werk ik aan een project waarbij de UI volledige in WPF4 is geschreven. Hierbij heb ik mezelf het doel gesteld om de architectuur volledig op MVVM te baseren.

Dit betekent concreet dat de schermen (de Views) geen code-behind hebben maar via een ViewModel interactie hebben met het Model. Alle schermwijzigingen worden met bindings en commands afgehandeld.

Zelf MVVM implementeren kan vast wel maar er zijn ook veel frameworks: grote, kleine, goede en foute… Uiteindelijk ben ik bij MVVMLight uitgekomen.

Ontwikkelen in WPF blijft door het gebruik van MVVM een feest, ondanks de steile leercurve maar dankzij de strikte scheiding tussen de te tonen data (ViewModel) en de representatie (XAML).

En ik ben ook begonnen met het gebruik van StoryBoards. Dit zijn notaties om de vorm verschijning en plaats van Xaml objecten aan te passen op een tijdslijn. Zo is het mogelijk om animaties in de UI te verwerken die wel degelijk van functioneel nut kunnen zijn.

StoryBoards worden bij voorkeur aan gebeurtenissen gehangen. Als voorbeeld toon ik hier hoe een plaatje langzaam zichtbaar wordt bij het starten van de applicatie door de Opacity in vijf seconden van 0 naar 1 te laten lopen.

Hieronder is de XAML beschreven en de code in het viewmodel. Dit zijn Snippets die zo te combineren zijn met een MvvmLight WPF4 template project.

<Grid x:Name="LayoutRoot"
      Width="284">
  <TextBox FontSize="36"
           FontWeight="Bold"
           Foreground="Purple"
           Text="{Binding WelcomeTitle}"
           VerticalAlignment="Center"
           HorizontalAlignment="Center"
           TextWrapping="Wrap"
           Margin="12,12,42,100" />
  <Image Height="51"
         Name="image"
         Source="Assets\troll-face.png"
         Opacity="0.5"
         Stretch="Uniform"
         HorizontalAlignment="Left"
         Margin="178,182,0,0"
         VerticalAlignment="Top"
         Width="61">
    <Image.Triggers>
      <EventTrigger RoutedEvent="Image.Loaded">
        <BeginStoryboard Name="MyBeginStoryboard">
          <Storyboard>
            <DoubleAnimation Storyboard.TargetProperty="Opacity"
                             From="0"
                             To="1"
                             Duration="0:0:5" />
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger>
    </Image.Triggers>
  </Image>
</Grid>

Er staat een Textbox op de pagina voor de invoer van een string en een plaatje. Dit plaatje bevat een StoryBoard dit wordt afgevuurd als de pagina wordt geladen voor vertoning.

De Textbox is gebonden aan de WelcomeTitle op het ViewModel.

public class MainViewModel : ViewModelBase
{
  public MainViewModel()
  {
    WelcomeTitle = "Hello";
  }

  public const string WelcomeTitlePropertyName = "WelcomeTitle";
  private string _welcomeTitle = string.Empty;
  public string WelcomeTitle
  {
    get
    {
      return _welcomeTitle;
    }
    set
    {
      if (_welcomeTitle == value)
      {
        return;
      }

      _welcomeTitle = value;

      RaisePropertyChanged(WelcomeTitlePropertyName);
    }
  }
}

De uiteindelijke functionaliteit is eenvoudig. Indien de Textbox wordt verlaten zal de Getter van WelcomeTitle uitgevoerd worden.

Het scherm zier er zo uit:

Mijn wens is dat het Troll-plaatje pas getoond wordt als de textbox wordt verlaten en de WelcomeText korter dan drie karakters is.

Maar door de MVVM ‘belemmering’ (ik kan geen (visuele) XAML onderdelen direct vanuit code aanraken en aanspreken) wordt het lastig om StoryBoards aan te spreken. Een Command werkt ook niet want die vuurt juist in de richting van het viewmodel af (bv. indien de gebruiker een knop indruk of via de MVVMLight EventToCommand).

Ik moet iets anders gebruiken en het antwoord is: DataTriggers.

Het is mogelijk om de XAML naar wijzigingen op het ViewModel te laten luisteren en hierop te laten reageren.

Wat heb ik nodig?

  1. Een bindable (boolean) property op het ViewModel. Hiervoor voeg ik de “ShowTroll” property toe aan het ViewModel.
  2. Op het Viewmodel moet de nieuwe property van waarde kunnen veranderen. Dit gebeurt in de Setter van de WelcomeTitle. Als een waarde met een lengte kleiner dan drie karakters voorbij komt, dan wordt de ShowTroll op True gezet.
  3. Een Button in de Xaml. Deze doet op zich niets maar kan wel de Focus krijgen. Als deze aangeraakt wordt, zal de TextBox de focus verliezen en wordt de Setter van WelcomeTitle uitgevoerd. Een dummy dus.
  4. Een DataTrigger op de Image, gekoppeld aan de ShowTroll boolean
  5. Twee StoryBoards. Als de ShowTroll op true wordt gezet, zal de ene afgespeeld worden en anders de andere.

Dit betekent dus dat het viewmodel met een ShowTroll property wordt  uitgebreid en dat deze verandert in de Setter van de WelcomeTitle property:

public class MainViewModel : ViewModelBase
{
  public MainViewModel()
  {
    WelcomeTitle = "Hello";
  }

  public const string WelcomeTitlePropertyName = "WelcomeTitle";
  private string _welcomeTitle = string.Empty;
  public string WelcomeTitle
  {
    get
    {
      return _welcomeTitle;
    }
    set
    {
      if (_welcomeTitle == value)
      {
        return;
      }

      _welcomeTitle = value;
      ShowTroll = (_welcomeTitle.Length < 3); // Force data change
      RaisePropertyChanged(WelcomeTitlePropertyName);
    }
  }

  public const string ShowTrollPropertyName = "ShowTroll";
  private bool _ShowTroll = false;
  public bool ShowTroll
  {
    get
    {
      return _ShowTroll;
    }
    set
    {
      if (_ShowTroll == value)
      {
        return;
      }

      _ShowTroll = value;
      RaisePropertyChanged(ShowTrollPropertyName);
    }
  }
}

En er wordt dus in de Xaml een Button toegevoegd en de Image wordt iets aangepast. Zie dat de Opacity standaard nul (volledige doorzichtig) is. En er zijn twee StoryBoards om de Opacity binnen twee seconden op één of op nul te brengen.

De DataTrigger is aan de ShowTroll property gekoppeld en reageert zowel op True als op False.

<Grid x:Name="LayoutRoot"
      Width="284">
  <TextBox FontSize="36"
           FontWeight="Bold"
           Foreground="Purple"
           Text="{Binding WelcomeTitle}"
           VerticalAlignment="Center"
           HorizontalAlignment="Center"
           TextWrapping="Wrap"
           Margin="12,12,42,100" />
  <Button Content="Button"
          Height="23"
          HorizontalAlignment="Left"
          Margin="31,226,0,0"
          Name="button1"
          VerticalAlignment="Top"
          Width="75" />
  <Image Height="51"
         Name="image"
         Source="Assets\troll-face.png"
         Opacity="0"
         Stretch="Uniform"
         HorizontalAlignment="Left"
         Margin="178,182,0,0"
         VerticalAlignment="Top"
         Width="61">
    <Image.Resources>
      <Storyboard x:Key="TrollBeginStoryboard">
        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                         From="0"
                         To="1"
                         Duration="0:0:2" />
      </Storyboard>
      <Storyboard x:Key="TrollEndStoryboard">
        <DoubleAnimation Storyboard.TargetProperty="Opacity"
                         From="1"
                         To="0"
                         Duration="0:0:2" />
      </Storyboard>
    </Image.Resources>
    <Image.Style>
      <Style>
        <Style.Triggers>
          <DataTrigger Binding="{Binding ShowTroll}"
                       Value="true">
            <DataTrigger.EnterActions>
              <BeginStoryboard>
                <Storyboard>
                  <StaticResource ResourceKey="TrollBeginStoryboard" />
                </Storyboard>
              </BeginStoryboard>
            </DataTrigger.EnterActions>
            <DataTrigger.ExitActions>
              <BeginStoryboard>
                <Storyboard>
                  <StaticResource ResourceKey="TrollEndStoryboard" />
                </Storyboard>
              </BeginStoryboard>
            </DataTrigger.ExitActions>
          </DataTrigger>
        </Style.Triggers>
      </Style>
    </Image.Style>
  </Image>
</Grid>

Hiervoor gebruiken we zowel de EnterActions als de ExitActions.

Dit geeft dus als effect dat als er een langere tekst is ingevoerd, de ‘troll’ niet zichtbaar is.

Maar als de gebruiker een tekst korter dan drie karakters invult, dan wordt binnen twee seconden de Troll zichtbaar.

En indien de tekst weer langer wordt gemaakt, dan is de Troll weer verstopt.

Ik wil in dit voorbeeld aangeven dat het relatief eenvoudig is om complexe UI veranderingen door te voeren door slecht een property op een ViewModel te manipuleren. Hiermee wordt het opeens mogelijk om binnen StoryBoards meerdere objecten te wijzigen. Dit is oogsnoep voor de gebruiker en zo wordt het een visueel feestje voor zowel de ontwikkelaar (of moet ik zeggen: Interactive Designer) en de gebruiker.

Eenvoudig PDF printen in C#

In need of an English translation? Please drop me a note!

Printen in het .Net framework is op zich niet zo lastig. En met de komst van WPF en de bijbehorende XpsDocumentWriter kan bv. handig gebruik gemaakt worden van de FlowDocument klasse en de PrintQueue klasse met bijbehorende CreateXpsDocumentWriter methode. Hiermee kun je zelf je templates opmaken, mergen en printen.

Maar specifiek een aangeboden PDF printen is een stuk lastiger. Met de hand lijkt dit eenvoudig, je start gewoon Adobe Reader of iets dergelijks.

Maar een PDF vanuit code uitprinten is niet eenvoudig. De opmaak binnen PDF’s wordt niet direct door de meeste printers herkend. Op het internet zwerven veel oplossingen rond allemaal met hun eigen voor- en nadelen. Adobe Reader is bv. gratis maar geeft een ‘nag’ screen en er zijn commerciële oplossingen maar die zijn niet bepaald goedkoop.

Voor mijn huidige project heeft het team gekeken naar de mogelijkheid om GhostScript te gebruiken. Dit is een bibliotheek om PDF naar ‘niet-postscript’ om te zetten. We zijn met name geïnteresseerd in PCL, dit kunnen de meeste (HP) laserprinters wel aan. Op GhostScript zit een GNU licentie met beperkt commercieel gebruik.

En wat blijkt, in slechts enkele stappen komt het printen van PDF ook voor .Net ontwikkelaars binnen bereik!

Stap 1: Download GhostScript en installeer deze lokaal. De 32 bits versie is een prettige keuze, vooral in combinatie met de andere benodigdheden. Deze werkt ook met een 64 bits Windows7. Refereer naar de gsdll32.dll in je C# project.

We zijn dus geïnteresseerd in die gsdll32.dll en deze is geschreven in C . Gelukkig heeft Matthew Ephraim al de moeite genomen om deze in C# te verpakken. Deze bibliotheek heet heel toepasselijk GhostScriptSharp. De broncode kan je ophalen bv. op GitHub.

Deze wrapper rond de 32 bits GhostScript dll is heel aardig. Er is via de methode GenerateOutput() de mogelijkheid beschikbaar om een PDF om te zetten naar PCL. Als onderdeel van de instellingen moet wel een ‘Device’ meegegeven worden. Hier heb ik een simpele aanpassing moeten maken op GhostScriptSharp. De GhostscriptDevices enum krijgt nu de extra ljet4 voor PCL support want die ontbrak. Waarom deze in de huidige versie niet aanwezig is, is mij onbekend.

Stap 2: download nu de GhostScriptsharp code, integreer deze in je eigen code en breid de enum even uit met de keuze ‘ljet4’. Zie dat die broncode standaard naar de 32 bits versie van GhostScript refereert.

Als laatste moet nu de, door GhostScript uit de PDF, gegenereerde PCL naar de printer gestuurd worden. Jouw printer dient dus wel PCL te ondersteunen :-).

Hiervoor gebruiken we een voorbeeld op de MSDN van Microsoft zelf. Dit voorbeeld gebruikt de Win32 Spooler Application Programmer Interfaces (APIs) en dit werkt ook prima samen met Windows7. De in het voorbeeld genoemde RawPrinterHelper klasse is prima. Er zitten wel enkele geheugenlekjes in (geen usings op de FileStream en de BinaryReader) dus een kleine aanpassing is gewenst.

Stap 3: Integreer de voorbeeldcode van Microsoft in je project en repareer even de RawPrinterHelper.SendFileToPrinter() methode.

Nu wordt het mogelijk om alles bij elkaar te brengen in één aanroep:

string pdfFileName = "test.pdf";
string tempFileName =
    Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
GhostscriptSettings gss = new GhostscriptSettings
{
  Size = new GhostscriptPageSize() { Native = GhostscriptPageSizes.a4 },
  Device = GhostscriptDevices.ljet4,
  Resolution = new Size(600, 600)
};

GhostscriptSharp.GhostscriptWrapper.GenerateOutput
    (pdfFileName, tempFileName, gss);
RawPrinterHelper.SendFileToPrinter(printerName, tempFileName);
File.Delete(tempFileName);

Eerst stellen we een naam samen voor een tijdelijk bestand. Vervolgens laten we via GhostScript vanuit een aangeboden PDF dit tijdelijk bestand vullen met PCL code (op A4 formaat met een resolutie van 600 dpi). En vervolgens laten we dit PCL bestand ruw naar de printer sturen. En achteraf verwijderen we natuurlijk nog even het tijdelijk bestand.

Dit moet voldoende zijn. Natuurlijk moet deze oplossing uitvoerig getest worden doordat het een heleboel zaken integreert… Maar in onze testopstelling functioneert het heel aardig. Persoonlijk had ik liever niet met een tijdelijk bestand op schijf gewerkt, maar met bijvoorbeeld een memorystream.