Using NotesUI* Classes in C# with COM

The NotesUI* Classes are not accesible in Visual Studio via the References-Dialog. Also in the DesignerHelp it is said, that you can not use the UI-Classes.

 

But there is a very simple Solution in C# by using the dynamic – Keyword and the .NET System.Activator Class.

 

The following Code-Sample connects to an running Notes Client and performs some actions:

class Program
    {
        static void Main(string[] args)
        {
            dynamic session = Activator.CreateInstance(Type.GetTypeFromProgID("Notes.NotesSession"));
            dynamic uiws = Activator.CreateInstance(Type.GetTypeFromProgID("Notes.NotesUIWorkspace"));
            dynamic uidb = uiws.CurrentDatabase;
            dynamic dbDir = session.GetDbDirectory("NTSDEV2008");

            Console.WriteLine("Benutzer : {0}", session.UserName);
            Console.WriteLine("Datenbank: {0}", uidb.Database.FilePath);



            uiws.OpenDatabase("NTSDEV2008", "names.nsf");

            uiws.URLOpen("https://www.google.at");
                       
            


            Console.ReadKey(); 
        }
     }

 

I found no current usecase for this at the moment, but good to know that this is possible …

Sparse Matrix in C#

Hin und wieder will man wieder etwas einfacheres machen und etwas, dass es schon 100.000mal auf dieser Welt gibt auch wieder neu erfinden. Ich hoffe ich habe jetzt hier nicht irgendein Softwarepatent gröber verletzt, wenn ja – mea culpa.

Die Sparse-Matrix hat gegenüber einem Array vor allem Vorteile, wenn man nur wenige Elemente der Matrix (bzw. des Arrays) belegt, da wäre eine Array eine Speicherverschwendung (überhaupt bei grossen Dimensionen). Anbei meine einfache Implementierung, ich habe bewusst nur eine einfache .NET Liste mit Tupeln verwendet, zur Abfrage der Liste verwende ich einfach LINQ (es ist einfach zu praktisch…) und hab noch eine kleine API draufgesetzt.

Sicherlich gäbe es auch einen effizientere Datenstrukturen dafür, aber ich denke für die meisten Fälle wird wohl die einfache Liste ausreichend sein, bzw. müsste man dann ja auch noch Vergleichsmessungen anstellen.

Mit dabei sind auch ein paar klitzekleine Tests, natürlich hätte die man auch als Unit-Tests auch ausführen können, aber sooo viel Bock hatte ich dann auch nicht.

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

namespace SparseMatrix {
  class Program {
    static void Main(string[] args) {

      var sm = new SparseMatrix();

      sm.SetAt(5, 6, "Hallo");
      sm.SetAt(6, 7, "Welt!");

      Console.WriteLine(sm.GetAt(5, 6));
      Console.WriteLine(sm.GetAt(6, 7));

      sm.SetAt(6, 7, "Harald");

      Console.WriteLine(sm.GetAt(6, 7));
      Console.WriteLine("RowCount row=7: {0}", sm.GetRowCount(6));
      Console.WriteLine("ColCount col=6: {0}", sm.GetColumnCount(7));

      Console.WriteLine("Entferne Element auf row=6 / col=7");
      sm.SetAt(6, 7, null);
      Console.WriteLine("RowCount row=7: {0}", sm.GetRowCount(6));
      Console.WriteLine("ColCount col=6: {0}", sm.GetColumnCount(7));

      sm[6, 6] = "Blogleser!";

      List results = sm.GetRowData(6).ToList();
      // Sollte "Hallo Blogleser " ausgeben
      foreach (string s in results) Console.Write("{0} ", s);
      Console.WriteLine();

      Console.WriteLine("Auf Postion 5/6 steht {0}", sm[5, 6]);


      Console.ReadLine();


    }
  }
  
  public class SparseMatrix {
    private List<Tuple<int, int, T>> data = new List<Tuple<int, int, T>>();

    private Tuple<int, int, T> getTuple(int row, int col) {
      return data.Where(e => e.Item1 == row && e.Item2 == col).FirstOrDefault();
    }

    public T GetAt(int row, int col) {
      var e = getTuple(row, col);
      if (e == null) {
        return default(T);
      } else {
        return e.Item3;
      }
    }

    public void RemoveAt(int row, int col) {
      var e = getTuple(row, col);
      if (e != null) data.Remove(e);
      return;
    }

    public void SetAt(int row, int col, T value) {
      var e = getTuple(row, col);
      if (e == null && EqualityComparer.Default.Equals(value, default(T))) {
        return;
      }
      if (e != null && EqualityComparer.Default.Equals(value, default(T))) {
        data.Remove(e);
        return;
      }
      if (e == null) {
        data.Add(new Tuple<int, int, T>(row, col, value));
        return;
      }

      data.Remove(e);
      data.Add(new Tuple<int, int, T>(row, col, value));
    }

    public IEnumerable GetRowData(int row) {
      return data.Where(e => e.Item2 == row).Select(e => e.Item3);
    }

    public IEnumerable GetColumnData(int col) {
      return data.Where(e => e.Item1 == col).Select(e => e.Item3);
    }

    public int GetColumnCount(int col) {
      return data.Where(e => e.Item2 == col).Count();
    }

    public int GetRowCount(int row) {
      return data.Where(e => e.Item1 == row).Count();
    }

    public T this[int row, int col] {
      get {
        return GetAt(row, col);
      }
      set {
        SetAt(row, col, value);
      }

    }


  }
}

 

Visual Studio Lightswitch 2010 – Einen Search Screen bei Änderungen automatisch aktualisieren.

Eine Applikation mit Visual Studio Lightswitch zu machen, ist eine spaßige Angelegenheit, man kommt schnell voran weil man sich nicht um die Infrastruktur der Applikation kümmern muss, aber trotzdem eine schöne, mehrschichtige Anwendung bekommt. Der Anfänger kann leicht einfache Use-Cases umsetzen, die Fortgeschrittenen können sich viel anpassen und auch erweitern.

Ein kleines Manko, welches mir bei der Umsetzung einer Anwendung aufgefallen ist (bzw. deren Anwender):

Man hat eine Listenansicht (SearchScreen) die eine bestimmte Menge an Datensätzen anzeigt. Fügt man nun in einen anderen Bildschirm (z.B. einer Detailansicht (Details Screen) oder einer Maske zum Hinzufügen (Add New Screen)) einen neuen Datensatz zu dieser Menge hinzu, schließt den Bildschirm und kehrt wieder zur Listenansicht zurück, so stellt man fest, dass der neue Datensatz nicht angezeigt wird, erst nachdem man auf die standardmäßig vorhandene Schaltfläche „Aktualisieren“ drückt, erscheinen die neuen Daten.

Ich möchte hier einen Weg vorstellen wie das auch automatisch funktioniert, erfunden habe es aber nicht ich, sondern Sheel Shah vom Visual Studio Lightswitch Team. Den Originalbeitrag findet ihr hier. Die Idee daran ist es, das ganze über einen eigenen EventHandler abzuhandeln. Das heisst, die Applikation bekommt einen Eventhandler verpasst, der Search Screen registriert sich für alle Events dieses Handlers. Wird nun in einem anderen Screen ein Datensatz hinzugefügt, oder verändert, so wird beim speichern dieses Event ausgelöst. Der Search Screen bekommt das Event mitgeteilt und kann damit die Datenquelle aktualiseren. Hier greifen dann die normalen Lightswitch-Mechanismen und die GUI wird aktualisiert.

Der einzige zu Sheel’s Beitrag ist, dass ich bei mir einen Search Screen aktualisiere, und kein einzelnes Steuerelement.

Ausgangslage, die Tabelle mit den Namen „Kunden“:

Tabelle Kunden - Übersicht
Tabelle Kunden

Ausgehend von dieser Tabelle gibt es zum Einem den Search Screen „Kunden“ sowie die Eingabemaske zum Anlegen eines neuen Kunden „Neuer Kunde“

Beginnen wir nur nun mit den Adaptionen an der Eingabemaske „Neuer Kunde“, die erste Änderung muss im Code an der Stelle „KundeNeu_CanRun“ vorgenommen werden.

Bearbeiten von "KundeNeu_CanRun"
Bearbeiten von „KundeNeu_CanRun“

Folgende Anpassungen sind dort zu machen: Anlegen eines Delegaten und eines Eventhandlers, sowie einer Methode die das Event „feuert“.

public delegate void RecordAdded();
public event RecordAdded CustomerAdded;

public void RaiseCustomerAdded() {
  if (this.Details.Dispatcher.CheckAccess()) {
    CustomerAdded();
  } else {
    this.Details.Dispatcher.BeginInvoke(delegate() { CustomerAdded(); });
  }
}

Wir bleiben im Detailformular, jetzt wird etwas Code zum Ereignis „KundeNeu_Saved“ hinzugefügt, die Ergänzung ist einfach, es wird einfach das Event „abgefeuert“:

partial void KundeNeu_Saved() {
  this.Close(false);
  Application.RaiseCustomerAdded();
}

So, wir haben nun den Event-Handler definiert, wir haben dafür gesorgt, dass das Event auch ausgelöst wird, nun brauchen wir noch jemanden, der sich für diese Events auch interessiert, und das ist nun das Suchformular „Kunden“. In dieser Ansicht gibt es oben im Ribbon eine Schaltfläche, mit der man einen Kunden neu anlegen kann – hier registriert man sich für das Event:

partial void NewCustomer_Execute() {
  Application.CustomerAdded += new Application.RecordAdded(CustomerAdded);
  Application.ShowKundeNeu();
}

Das ist die Methode, die dann aufgerufen wird, es wird einfach ein Refresh der Datenquelle vorgenommen, die dem Search Screen zugrundeliegt. Der Rest wird dann vom internen Eventsystem abgehandelt, und die GUI initialisiert.

private void CustomerAdded() {
  this.Details.Dispatcher.BeginInvoke(() => this.CustomersByName.Refresh());
}

Zu guterletzt sollte man auch noch aufräumen, ich denke aber, dass diese Lösung hier noch nicht unbedingt sauber ist, das muss ich mir bei Gelegenheit noch genauer ansehen, wenn der Search Screen geschlossen wird, wird der der Eventhandler wieder „deregistriert“:

partial void Kunden_Closing(ref bool cancel) {
  Application.CustomerAdded -= new Application.RecordAdded(CustomerAdded);
}

Kurzes Fazit: Die Implementierung ist doch ein wenig aufwändig, vor allem wenn man die Mechanismen des Event-Handlings noch nicht so gut versteht, aber es ist eine saubere und zuverlässige Umsetzung.

Mit Lambda-Expressions in C# den Code verkürzen

Lambda-Expressions sind zwar schon ein alter Hut in C#, trotzdem möchte ich auch mal ein kleines Beispiel bringen, wo diese Dinger ungemein praktisch sind. Sie verkürzen nicht nur die LOC sonder tragen meiner Meinung nach ganz wesentlich dazu bei, den Code auch übersichtlich zu behalten.

Ich verwende es um in einem Silverlight WCF-RIA Projekt die Callbacks des asynchronen Webserviceaufrufs entgegenzunehmen. Um den Unterschied zu verdeutlichen, das ganze natürlich in der klassischen „Vorher / Nachher“-Variante

Vorher: Natürlich zuerst einmal ohne Lambdas, 3 Methoden, 17 Zeilen Code:

public override void LoadData() {
  LoadOperation loadOperationCountry = _context.Load(_context.GetCountrySetQuery(), loadOperationCountryCallback, null);
  LoadOperation loadOperationCustomer = _context.Load(_context.GetCustomerSetQuery(), loadOperationCustomerCallback, null);
}

void loadOperationCustomerCallback(LoadOperation customers) {
  if (customers != null) {
    Customers = new ObservableCollection(customers.Entities);
    CurrentCustomer = Customers[0];
  }
}

void loadOperationBranchCallback(LoadOperation branches) {
  if (branches != null) {
    Branches = new ObservableCollection(branches.Entities);
  }
}

Nachher: 11 Zeilen Code, also ca. 1/3 weniger, und nur mehr 1 Methode. Man muss also nicht mehr aufpassen, dass man die Methoden im Quellcode zusammenhält um später mal nicht so lange suchen zu müssen wo denn nu der verdammte Quellcode für den Callback ist.

public override void LoadData() {
  LoadOperation loadOperationCountry = _context.Load(_context.GetCountrySetQuery(), 
    (e) => {
      if (e!=null) Countries = new ObservableCollection(e.Entities);
    }, null);

  LoadOperation loadOperationCustomer = _context.Load(_context.GetCustomerSetQuery(), 
    (e) => { 
      if (e != null) Customers = new ObservableCollection(e.Entities); 
    }, null);
}

 

Lotus Domino und C#

Mittlerweile darf ich mich beruflich ja ein wenig mit Lotus Domino Softwareentwicklung auseinandersetzen. Die Dominowelt ist eine ganz ganz eigene Welt – aber schön langsam gewöhne ich mich daran.

Meine ersten Schritte passierten allerdings von ausserhalb, Ziel war der Zugriff auf Dominodatenbanken von Java Programmen (Servlets) aus. Dies funktioniert auch ganz gut, vielleicht veröffentliche ich später einmal ein paar Codeschnipsel.

Heute hab ich mich einmal daran versucht das ganz in C# zu testen, und als Ergebnis gilt es zu sagen, dass das auch viel besser funktioniert, vor allem weil man die COM Klassen ja direkt aus C# raus ansprechen kann.

Dabei ist folgender Codeschnippsel entstanden, der nix anderes macht als die lokale Maildatenbank zu öffnen, alle Mails zu durchwühlen, die Attachments die sich darin befinden an einen „sicheren“ Ort speicher und dann aus dem eigentlichen Mail rauslöscht, aber nicht ohne vorher einen Hinweis zu hinterlassen wo sich denn nun der Anhang befindet.

Vielleicht braucht ja sowas mal wer wirklich. 🙂

Read moreLotus Domino und C#