Eine Art neues Grid für den Designer

Es ist im Designer des Visual Studio 2008 sehr viel einfacher geworden als früher etwa mit dem VB6-Designer, gutaussehende Forms zu produzieren, vor allem weil dank der Snaplines das Positionieren von Controls sehr viel einfacher geworden ist.

Dies ist auch gut so, wenn die Megos ihre Moneysoft-Applikationen, bisher entwickelt in einem einem eigenen System namens EMBASSY, in den nächsten Jahren in .NET neu schreiben will, denn wie man an folgendem, schon etwas älteren Screenshot sehen kann, wird es da oft Forms geben mit Dutzenden von Controls:

Valor Transaktionen

(Die vielen Controls in dieser Form sind nicht etwa schlechtes Design, sondern es gibt schlicht und einfach zu sogenannten Transaktionen in unserer Wertschriftenbuchhaltung Valor eine Menge Angaben, und die wollen alle erfasst werden. Für Kontrollen ist zudem ein schneller Überblick nötig, ohne dass man x Tabs wechseln muss.)

Als ich einmal eine solche Form testweise mit dem Visual-Studio-Designer nachbaute, merkte ich allerdings schnell, dass selbst die Unterstützung durch Snaplines noch nicht das ist, was ich eigentlich gerne hätte, um wirklich produktiv arbeiten zu können. Die Crux ist die: Forms, die praktisch nur aus vielen Labels, TextBoxes und GroupBoxes bestehen, sehen am besten aus (und die Controls haben am besten Platz), wenn man die Controls so positioniert und ihre Grössen so setzt, als würde man mit einem sehr speziellen Grid arbeiten – einem Grid, das mit einer „Zeilenhöhe“ und einer „Durchschnittszeichenbreite“ funktioniert.

Der Visual-Studio-Designer ist sehr modular aufgebaut und bietet eine Menge Möglichkeiten, Design-Prozesse zu beeinflussen, etwa mit Hilfe von ControlDesigner-Klassen, aber eine Möglichkeit, das Verhalten des Grids zu steuern oder zu übersteuern, scheint es nicht zu geben. Will man trotzdem mit einem speziellen Grid wie dem soeben beschriebenen arbeiten, muss man also zu einem Trick greifen.

Mein Trick ist der folgende: Controls haben eine protected virtual, also überdefinierbare Methode namens SetBoundsCore, die für das Setzen der Position und der Grösse eines Controls zuständig ist. Hier greife ich ein und runde Koordinaten auf mein Spezial-Grid, bevor ich sie an die Basis-Methode übergebe.

Das ist eine zwar drastische, aber einfache und effektive Methode, um x und y auf bestimmte Werte zu zwingen. Nichts und niemand im ganzen System kommt um die von mir etablierte Rundung der Koordinaten herum, auch der Designer nicht. Der Effekt im Designer ist der, dass man zwar ein solches Control nach wie vor mit der Maus frei herumziehen kann, aber sobald man es „absetzt“, kommt die geänderte SetBoundsCore-Methode zum Zug, und das Control hüpft sozusagen von sich aus auf die „richtige“ Position gemäss Grid. Das ist natürlich ungewohnt und am Anfang auch etwas gewöhnungsbedürftig, fühlte sich aber für mich schon nach kurzer Zeit sehr angenehm an.

Folgendes kleine Code-Fragment zeigt ein Label-Control, welches man nur gemäss einem Grid positionieren kann, dessen Zellen 30 Pixel breit und 15 Pixel hoch sind (vielleicht nicht unbedingt nützlich, zeigt aber den Effekt im Designer sehr schön):

  public class GriddedLabel : Label {
    protected override void SetBoundsCore(int x, int y, int width, int height, BoundsSpecified specified) {
      x = (x / 30) * 30;
      y = (y / 15) * 15;
      base.SetBoundsCore(x, y, width, height, specified);
    }
  }
Advertisements
Veröffentlicht in Keine Kategorie. Schlagwörter: , , , . Leave a Comment »

Technologie-Inflation

Ein Artikel in der Zeitschrift iX hat mich heute darauf aufmerksam gemacht, dass zusammen mit dem Service Pack 1 für das .NET Framework 3.5 nun nach langem Warten die erste definitive Version des sogenannten ADO.Net Entity Framework zur Verfügung steht.

Es wurde schon eine ganze Menge darüber geschrieben, was es mit diesen „Entities“ auf sich hat. Z.B. ist die entsprechende Wikipedia-Seite ein guter Startpunkt, um sich einzulesen. Ich will deshalb nicht noch mehr Technisches darüber schreiben, sondern die Gelegenheit ergreifen, um ein bisschen über die Sache zu „philosophieren“.

Applikationen für vorwiegend administrative Zwecke, wie sie die Megos anbietet, sind sehr stark geprägt durch den Zugriff auf die Daten. Seit wir vor einiger Zeit beschlossen hatten, für die Programm-Entwicklung auf .NET umzusatteln, verfolgte ich deshalb ziemlich aufmerksam, was sich bei .NET in Sachen Datenzugriff und Datenverwaltung tut, und wurde schnell neugierig, als ich vor einiger Zeit zum ersten Mal von diesem Entity Framework hörte.

Was ich sah, als ich mich näher damit beschäftigte, hat mir zunächst fast die Sprache verschlagen: So ein Riesen-Ding, mit eigener SQL-Variante für die Abfrage, mit eigener Art von Datenbank-Modell, das über das relationale Modell hinausgeht, samt zugehörigem, nicht zu klein geratenem Modellierungs-Tool, plus jeder Menge neuer .NET-Klassen natürlich. Und fast noch bemerkenswerter als die Grösse der ganzen Geschichte kam mir deren Komplexität vor.

Dann wurde mir klar, dass .NET mit LINQ-to-SQL und dem Entity Framework gleich zwei Technologien enthalten wird, welche sich bezüglich der grundlegenden Aufgabe des sogenannten OR-Mapping überschneiden und konkurrenzieren. Die mögliche Antwort von Microsoft wird vielleicht – nicht unbedingt überraschend – noch mehr Neues, noch mehr Grosses und Komplexes sein, in Form von LINQ-to-Entities.

Ich habe mich unweigerlich gefragt, wie viele Leute wohl auf der Welt eine Datenbank bewirtschaften müssen mit einer Struktur, die so vielfältig und komplex ist, dass man dieses Entity Framework zum Einsatz bringen kann und netto etwas herausschaut dabei, will sagen: Der Gewinn an Funktionalität und Flexibilität beim Umgang mit der Datenbank in den .NET-Programmen wiegt die Arbeit auf, die man aufwenden muss, um sich in diese riesige Geschichte einzuarbeiten, und wiegt dazu noch den Ärger auf, den man hat durch Bugs, die es fast unweigerlich geben muss in so viel neuem Code.

Und die Frage „Soll ich auf das Entity Framework setzen?“ hat natürlich noch ganz andere Seiten. Soll ich meine Applikation um eine Technologie herum bauen, die ziemlich Microsoft-spezifisch ist? Wie langlebig wird diese Sache sein? Die Liste der Datenbank-Zugriffs-Technologien, die Microsoft zu „Legacy“ heruntergestuft hat, enthält schon einige Einträge (ODBC, DAO, OLE DB), und vielleicht kommt ja schon in relativ kurzer Zeit ein Eintrag dazu, wenn Microsoft sich eingestehen müsste, dass zwei OR Mappers einer zuviel ist.

Interessanterweise ist es mir bei einer Google-Suche als Vorbereitung auf das Schreiben dieses Blog-Eintrags nicht gelungen, Leute zu finden, die sich wie ich angesichts der Grösse, der Komplexität und des proprietären Anstrichs des Entity Framework Sorgen machen und erst einmal abwarten, bevor sie es einsetzen. Aber vielleicht ist es dafür einfach noch etwas zu früh…

Veröffentlicht in Keine Kategorie. Schlagwörter: , . Leave a Comment »

ReadOnlyCollection

Jeder, der eine halbwegs umfangreiche Klassen-Sammlung in C# designen muss, hat gute Chancen, auf folgende grundlegende Design-Frage zu stossen: Eine Klasse soll eine List enthalten, die zwar gegen aussen sichtbar ist, die man aber nicht direkt verändern darf – sämtliche Änderungen sollen quasi indirekt mit Hilfe von Methoden der Klasse erfolgen. Für eine solche Anforderung kann es viele denkbare Gründe geben; schon nur, wenn man die Anzahl Änderungen an der List zählen möchte, sollte es besser nicht möglich sein, dass sich jemand an der Zählung vorbeimogelt, indem er sich die Liste geben lässt und sie dann selbst verändert.

Ein kleines synthetisches Codebeispiel hierzu:

namespace ReadOnly {
  class Container {

    private List<int> numbers;
    public List<int> Numbers {
      get { return numbers; }
    }

    public void AddNumber(int n) {
      numbers.Add(n);
    }

    public Container() {
      numbers = new List<int>();
    }
  }

  class Program {
    static void Main(string[] args) {
      Container c=new Container();
      c.AddNumber(5);     // soll nur so funktionieren
      c.Numbers.Add(5);   // soll verboten sein
    }
  }
}

Mein erster Gedanke war, dass es in C# vielleicht ein Äquivalent zu C++ const gibt und man damit etwas machen kann. Eine Abklärung ergab, dass die CLR zwar eine gewisse Unterstützung bietet in dieser Richtung (für Managed C++), diese aber in C# nicht zur Verfügung steht.

Dann stiess ich auf die Property IsReadOnly, welche im Interface IList enthalten ist. Das sieht zwar auf den ersten Blick hoffnungsvoll aus, aber die Anwesenheit dieser Property im Interface heisst noch lange nicht, dass man im Falle einer „templated class“ etwas damit machen kann. Ich habe auf jeden Fall keinen Weg gefunden, im konkreten Falle einer List<int> deren Property IsReadOnly irgendwie zu überdefinieren, und selbst wenn dies gelänge, würden sich wohl Methoden wie Add der Template-basierten Klasse überhaupt nicht dafür interessieren.

Schliesslich förderte meine Suche die Klasse ReadOnlyCollection zu Tage, welche man wie folgt auf das Problem ansetzen kann:

namespace ReadOnly {
  class Container {

    private List<int> numbers;
    public ReadOnlyCollection<int> Numbers {
      get { return numbers.AsReadOnly(); }
    }

    public void AddNumber(int n) {
      numbers.Add(n);
    }

    public Container() {
      numbers = new List<int>();
    }
  }

  class Program {
    static void Main(string[] args) {
      Container c = new Container();
      c.AddNumber(5);     // funktioniert nur so
      c.Numbers.Add(5);   // compiliert nicht, 'ReadOnlyCollection.Add' gibt es nicht
    }
  }
}

Etwas unbefriedigend an dieser Lösung ist, dass mit Hilfe der Methode AsReadOnly ein neues Objekt erzeugt werden muss.

In irgendeinem Forum habe ich dann folgende Lösungs-Variante gefunden, welche ohne ein neues Objekt auskommt:

namespace ReadOnly {
  class Container {

    private List<int> numbers;
    public IEnumerable<int> Numbers {
      get { return numbers; }
    }

    public void AddNumber(int n) {
      numbers.Add(n);
    }

    public Container() {
      numbers = new List<int>();
    }
  }

  class Program {
    static void Main(string[] args) {
      Container c = new Container();
      c.AddNumber(5);     // funktioniert nur so
      c.Numbers.Add(5);   // compiliert nicht, 'IEnumerable.Add' gibt es nicht
    }
  }
}

Eine List<int> implementiert natürlich selbst bereits das Interface IEnumerable, so dass die Sache rein zur Compilierzeit stattfindet und die Property Number technisch gesehen gleich die Liste selbst zurückgibt. Unschön ist allerdings, dass man mit einem IEnumerable-Objekt nichts weiter machen kann als per foreach die Elemente durchgehen; noch nicht einmal die Anzahl Elemente kann man direkt abfragen.

Nach einigem Abwägen habe ich beschlossen, für die Megos-eigene Klassen-Bibliothek Triton, die gerade entsteht, das Problem zu ignorieren und keine technische Vorkehrungen zu treffen, um mit Hilfe einer der geschilderten Methoden Veränderungen an solchen Listen zu verhindern. Wir sind wenige, erfahrene, disziplinierte Programmierer, die keine Mühe haben mit dem Befolgen von Konventionen wie „Verändere keine Listen-Properties direkt“.

Natürlich sähe das anders aus, wenn man z.B. eine kommerziell vertriebene Klassen-Bibliothek für den Gebrauch durch viele andere Programmierer bauen müsste. Aber auch da gäbe es vielleicht die Möglichkeit, die direkten Veränderungen selbst nicht zu verunmöglichen, sie aber dennoch zu entdecken und mit einer Exception zu beantworten, indem man einerseits die Anzahl Aufrufe von AddNumber zählt und andererseits an „strategischen“ Orten im Code die Anzahl Elemente der Liste gegen diesen Zähler prüft.

Veröffentlicht in Keine Kategorie. Schlagwörter: , . Leave a Comment »