code it

Martins Tech Blog

Nachlese zum Treffen der .NET Usergroup am 25.08.2010

Beim gestrigen Treffen der .NET Usergroup Dresden hatten wir wieder zwei sehr unterschiedliche Themen und etwa 15 interessierte Teilnehmer.

Nachdem Robert beim letzten Treffen eine Einführung ins Buildmanagement mit TFS gegeben hat, führte er das Thema weiter fort, indem er auf unterschiedliche Branching- und Merging-Strategien einging und die daraus resultierenden Probleme und Möglichkeiten näher beleuchtete. Hier zeigte sich, dass schon viele eine Branching-Strategie verfolgen, die aber je nach Projekt und Kunde unterschiedlich sein kann, um auf die Anforderungen des Projektes passend zu sein. Allen Interessierten sei der Visual Studio TFS Branching Guide 2010 ans Herz gelegt, um sich etwas tiefer einzulesen.

Im Anschluss stellte ich die Basics der Windows Phone 7 Entwicklung vor. Dabei ging es einerseits darum, was Windows Phone 7 bietet und was man bei der Entwicklung beachten muss. Das Thema sorgte doch für eine sehr angeregte Diskussion. Ich denke, man hier thematisch anknüpfen kann und bei einem der nächsten Treffen etwas tiefer eintauchen kann.

Im Anschluss an die Usergroup konnte man dann bei einem Getränk in der Terrasse am Bischofsplatz noch fachsimpeln und den Abend ausklingen lassen.

Mehrfachaktionen in kontextsensitiven Ribbons als Sandboxed Solution

Sharepoint 2010 bietet einige spannende Möglichkeiten, die es so bisher nicht gab. Zum einen gibt es das Client Object Model, das es erlaubt, auf Clientseite auf das Objektmodell von SharePoint zuzugreifen. Zum anderen bietet es die Möglichkeit, sogenannte Sandboxed Solutions bereitzustellen, die zwar mit geringeren Rechten ausgestattet sind, aber dafür von einer breiteren Anwendergruppe installiert werden können.

In diesem Blogpost wird gezeigt wie man diese Features kombinieren und in einer Aufgabenliste die Mehrfachaktion "Alle Aufgaben erledigen" implementieren kann, die dann als Sandboxed Solution bereitgestellt wird. Eine kurze Einführung in das Thema Sandboxed Solutions gibt Thorsten Hans in seinem Blogpost und auch das MSDN Magazine wartet mit einem Artikel dazu auf.

Erster Schritt ist es, ein neues SharePoint-Projekt in Visual Studio anzulegen. Dazu verwende ich das Projekttemplate "Empty SharePoint Project". Die Auswahl der Programmiersprache ist in diesem Fall irrelevant, da alles über XML und JavaScript gelöst wird. Visual Studio legt daraufhin eine neue Solution an, die bereits ein Feature beinhaltet. Allerdings ist der Name des Features recht unaussagekräftig und sollte direkt angepasst werden. In meinem Beispiel soll es TaskBatchCompleteFeature heißen. Hinzu kommt noch ein neues Element, da die Anpassungen über die Elements.xml durchgeführt werden. Damit auch hier klar ist, was das Element macht, bekommt es den Namen TaskBatchCompleteElement. Da die Solution komplett aus Konfigurationsdateien besteht, ist es sinnvoll, in den Projekteigenschaften noch "Include Assembly In Package" auf false zu setzen. Die Visual Studio Solution sollte nun ungefähr so aussehen:

 

Die nächsten Anpassungen erfolgen in der Elements.xml. Hier ist die Konfiguration der Ribbon-Anpassung zu hinterlegen. In der CustomAction definiert man, dass diese Aktion nur auf Listen des Typs Aufgabe im Ribbon sehen möchte. Dafür sind die Attribute RegistrationType und RegistrationId zuständig, wobei die List Template Id 107 für Aufgabenliste steht.

Über den Tag CommandUIDefinition kann man nun einen Button hinzufügen. Eine Kurzzusammenfassung, welche XML-Fragmente notwendig sind, um welches Ergebnis zu erreichen findet man in folgendem Post. Kurz erklärt definiert die Location dabei, in welcher Ribbon-Gruppe der Button hinzugefügt werden soll. Die Standard-Ribbons werden über die Datei CMDUI.xml im Ordner 14\template\global\xml definiert. Möchte man bestehende Ribbon-Gruppen anpassen, so kann man die Namen in dieser Datei ermitteln und auch anhand der bestehenden Sequence-Ids die Reihenfolge-Einordnung für das neue Control in der Gruppe bestimmen.

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">

  <CustomAction
    Id="TaskBatchCompleteRibbonAction"
    Location="CommandUI.Ribbon"
    RegistrationType="List"
    RegistrationId="107">
  <CommandUIExtension>
      
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.ListItem.Manage.Controls._children">
          <Button
              Id="TaskBatchCompleteRibbonButton"
              Alt="Aufgaben erledigen"
              LabelText="Aufgaben erledigen"
              Sequence="21"
              Command="TaskBatchCompleteRibbonCommand"
              Image32by32="_layouts/images/TaskDone.gif"
              Image16by16="_layouts/images/TaskDone.gif"
              TemplateAlias="o2"
              />
        </CommandUIDefinition>
      </CommandUIDefinitions>

      <!--TODO: add CommandUIHandler-->
    </CommandUIExtension>
    
    <!--TODO: add ScriptLink-->
  </CustomAction>
</Elements>

Da der Button nur dann aktiv sein soll, wenn mindestens ein Element gewählt wurde, wird ein CommandUIHandler hinzugefügt, der ein EnabledScript zugewiesen bekommt. Hier erfolgt die Prüfung auf die Anzahl der selektierten Elemente mit Hilfe der Funktion SP.ListOperation.Selection.getSelectedItems()

<CommandUIHandlers>
  <CommandUIHandler
     Command="TaskBatchCompleteRibbonCommand"
     CommandAction="javascript:BatchCompleteTasks();"
     EnabledScript="javascript:function isOneOrMoreEnabled() { 
                          var items = SP.ListOperation.Selection.getSelectedItems(); 
                          var ci = CountDictionary(items); 
                            return (ci > 0); 
                           } 
                          isOneOrMoreEnabled();" />
</CommandUIHandlers>

Als letzter Schritt wird nun noch die eigentliche Aktion benötigt. Diese wird in einer weiteren CustomAction hinterlegt. Die eigentliche Funktion ist recht simpel. Über die gleiche Methode wie eben werden die selektierten Elemente ermittelt und über die Kontextinformationen das zugehörige Web und die Liste geladen. Im Anschluss daran iteriert man durch die Elemente und setzt die Felder - bei einer Aufgabe sind das der Status und der Erledigungsgrad. Nachdem dies vorgenommen wurde, müssen die Daten noch zum Server übertragen werden. Dazu ruft man die Methode executeQueryAsync auf. Dieser übergibt man zwei Callbacks - in meinem Fall "success" und "failed", die dann noch eine Benachrichtigung für den Benutzer auslösen.

<CustomAction Id="TaskBatchCompleteRibbonScriptAction"
     Location ="ScriptLink"
     ScriptBlock="
BatchCompleteTasks = function () {
    var selectedItems = SP.ListOperation.Selection.getSelectedItems();
    var currentListGuid = SP.ListOperation.Selection.getSelectedList();

    var context = SP.ClientContext.get_current();
    var currentWeb = context.get_web();
    var currentList = currentWeb.get_lists().getById(currentListGuid);

    var k;
    for (k in selectedItems) {
        var listitem = currentList.getItemById(selectedItems[k].id);
        listitem.set_item('Status', 'Completed')
        listitem.set_item('PercentComplete', '1')
        listitem.update();
        context.executeQueryAsync(Function.createDelegate(this, this.success), Function.createDelegate(this, this.failed))
    }

}
             

function success() {
    SP.UI.Notify.addNotification('Aufgabe erfolgreich abgeschlossen');
}

function failed(sender, args) {
    var statusId = SP.UI.Status.addStatus(args.get_message());
    SP.UI.Status.setStatusPriColor(statusId, 'red');
    latestId = statusId;
}"
      />

Damit ist die Arbeit im Grunde auch schon getan. Nach einem Klick auf "Package" erscheint im Ausgabepfad eine wsp-Datei.

Sandboxed Solutions benötigen keine Farmadministratorrechte, um installiert zu werden. Aus diesem Grund erfolgt die Installation auch nicht in der Central Adminsitration sondern in der Solution Gallery der Site. Nachdem man die WSP-Datei hierher hochgeladen und die Solution aktiviert hat, steht das neue Feature in der Oberfläche zur Verfügung.

Keine Fotos bitte!

Auch wenn der Titel dieses Posts darauf schließen lassen könnte, dass es dieses Mal um Klatsch zu Prominenten geht - das ist nicht der Fall. Vielmehr geht es um das seltsame Verhalten der Windows Phone 7 Beta im Umgang mit Choosern und Launchern.

Der Anwendungsfall ist schnell erklärt: Ausgelöst durch einen Klick auf einen Button soll ein Foto von der Kamera aufgenommen und dieses dann in einem Image auf der Page dargestellt werden. Hört sich nicht kompliziert an und war es auch in der CTP von Windows Phone 7 nicht. Mit der Beta hat sich an der API hier einiges zum Vorteil geändert - man kann jetzt einen Callback hinterlegen und muss nicht mehr Methoden der Basisklasse überschreiben. Lange Rede, kurzer Sinn - mein Code sieht nun wie folgt aus:

private void OnCaptureCamera_Click(object sender, RoutedEventArgs e)
{
    CameraCaptureTask cameraCaptureTask = new CameraCaptureTask();
    cameraCaptureTask.Completed += OnCameraCaptured;
    cameraCaptureTask.Show();
}

void OnCameraCaptured(object sender, PhotoResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        var source = new BitmapImage(); 
        source.SetSource(e.ChosenPhoto);

        image1.Source = source; 
    }
}

Der Effekt ist allerdings ernüchternd. Ein Klick auf den Button sorgt zwar dafür, dass sich die Kameraanwendung öffnet, diese wird allerdings nach kurzer Zeit schwarz. Durch Zoomen kann man erreichen, dass zeitweise doch etwas zu sehen ist und man den Auslöseknopf findet. Ist das erledigt, kommt die nächste Ernüchterung: Inzwischen hat sich die eigene Anwendung im Emulator beendet.

Nach einiger Recherche brachte mich ein Blogpost von Nick Randolph auf die richtige Spur, der auch die Ursachen beleuchtet. Es liegt gar nicht an meinem Code, sondern ganz einfach daran, dass es noch eine Beta ist und es hier sehr viele Umbauarbeiten im Vergleich zur CTP gegeben hat, die wohl noch nicht ganz abgeschlossen sind.

Es gibt einen Trick, mit dem man zumindest prüfen kann, ob der Code funktioniert - auch wenn er sehr umständlich ist. Dazu verschiebt man zunächst den CameraCaptureTask in eine Membervariable und setzt den Callback im Konstruktur der Seite. Die Anwendung wird sich beim Klick auf den Button noch immer beenden, aber wenn man direkt im Anschluss noch einmal auf F5 drückt, sieht man das Ergebnis des CameraCaptureTasks.

public MainPage()
{
    InitializeComponent();
    cameraCaptureTask.Completed += OnCameraCaptured;
}

CameraCaptureTask cameraCaptureTask = new CameraCaptureTask();

private void OnCaptureCamera_Click(object sender, RoutedEventArgs e)
{
    cameraCaptureTask.Show();
}

void OnCameraCaptured(object sender, PhotoResult e)
{
    if (e.TaskResult == TaskResult.OK)
    {
        var source = new BitmapImage(); 
        source.SetSource(e.ChosenPhoto);

        image1.Source = source; 
    }
}

Ich gehe ganz stark davon aus, dass sich dieses Verhalten bis zum Release noch ändert und ein kleiner Absatz in den Release-Notes der Beta über dieses Problem hätte mich gefreut.

MultiBindings im Tooltip

Um in WPF mehrere Werte der Datenquelle in einem Ziel zusammenzufassen, gibt es MultiBindings. Hier kann man auf ähnliche Weise wie in string.Format definieren, wie die Daten angezeigt werden sollen.

<TextBlock>
    <TextBlock.Text>
        <MultiBinding StringFormat="Name: {0}, {1}" >
            <Binding Path="LastName"></Binding>
            <Binding Path="FirstName"></Binding>
        </MultiBinding>
    </TextBlock.Text>
</TextBlock>

Das gleiche kann man auch im Tooltip erreichen. Dafür gibt es einen kleinen Trick: Weil der Tooltip ein ContentControl ist können dort nicht nur Texte angezeigt werden, sondern auch Controls hinzugefügt werden. Für den Anwendungsfall bedeutet das, dass einfach in den Tooltip ein mit Multibinding versehener Textblock eingefügt wird.

<Image Width="180" Source="file:///C:/Temp/avatar.jpg">
    <Image.ToolTip>
        <TextBlock>
            <TextBlock.Text>
                <MultiBinding StringFormat="Name: {0}, {1}" >
                    <Binding Path="LastName"></Binding>
                    <Binding Path="FirstName"></Binding>
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </Image.ToolTip>
</Image>

Exam 70-511: Windows Applications Development with Microsoft .NET Framework 4

Heute war es mal wieder so weit und ich habe das Testing Center mit meiner Anwesenheit beehrt. Ich wollte herausfinden, ob ich den Anforderungen von Microsoft in Bezug auf den Themenkomplex WPF gerecht werden kann. Das Ergebnis: Passed.

Die Prüfung bestand aus insgesamt 40 Fragen rund um das Thema Anwendungsentwicklung mit WPF. Die Themengebiete waren:

  • Building a User Interface by Using Basic Techniques
  • Enhancing a User Interface by Using Advanced Techniques
  • Managing Data at the User Interface Layer
  • Enhancing the Functionality and Usability of a Solution
  • Stabilizing and Releasing a Solution
Um mal ein paar Vergleiche zur Prüfung .NET Framework 2.0 Windows Applications Development zu ziehen: Es kamen ebenfalls Fragen zu Basics wie Background Workern und Click Once Deployment dran, aber der Fokus hat sich (verständlicherweise) sehr Richtung XAML verschoben und so hat es mich nicht verwundert, dass ich aus einem Screenshot ableiten sollte, wie der zugehörige XAML-Code auszusehen hat oder erklären sollte wie man wann wohin welche Resourcen legt.

Alles in allem ist mein Fazit: Mit etwas Vorbereitung und Erfahrung im Bereich WPF durchaus machbar.