Thomas Wölfers Baustatik-Blog

Thomas Wölfers Baustatik Blog

Ein Makro zum auswählen und bearbeiten


Heute kam die Frage auf, ob wir nicht ein Beispiel-Makro fürs auswählen und bearbeiten von Objekten hätten. Haben wir… Smiley

Der einfachste Weg ist natürlich der, einfach ein Objekt auszuwählen und dann den Eigenschaften-Dialog anzuzeigen. Fürs auswählen selbst braucht man kein Makro: Das geht ja “einfach so”. Ein Beispiel für das anzeigen des Eigenschaften-Dialoges gibt es im Makro “AuswahlBearbeiten”.

image

Was aber tun, wenn man in einer Schleife nacheinander mehrere Objekte auswählen und dann bearbeiten will. Also: Objekt auswähle, Objekt bearbeiten –> und dann wieder von vorne anfangen? Das ist etwas komplizierter (das vorige Makro ist ein Einzeiler), und darum gibts hier eine kurze Erklärung dazu, was das Ding tut. Der Quellcode dazu findet sich in “ObjektAuswählenUndBearbeiten”.

Neben dem normalen immer für Makros benötigten Kram gibt es im wesentlichen 3 Methoden:

1.) DoIt()

Das ist die Funktion, die die eigentliche Arbeit tut: Sie wird auch ganz am Anfang im Execute() des Makros aufgerufen. Das Ding tut folgendes:

  • Ein neues MouseTool vom Typ “ObjectSelectionMouseTool” erzeugen. MouseTools sind die Dinger, mit denen man in der Graphik interaktiv arbeiten kann. Wenn man z.B. in der Graphik mit der Maus irgendwo draufklickt und das angeklickte Objekt wird rot, dann war da das normale “SelectionMouseTool”. Wenn man mit der Maus einen Zoom durchführt, dann passiert das mit dem ZoomMouseTool etc.. Das ObjectSelectionMouseTool ist dafür da, ein (oder mehrere) Objekt(e) beliebigen Typs in der Graphik auszuwählen.
  • Das MouseTool hat mehrere Ereignisse, die man mitbekommen kann. Die beiden die man hier braucht sind “SelectCanceled” und “SelectFinished”. Das erste Ereignis tritt ein, wenn die Auswahl abgebrochen wurde (Rechte Maustaste gedrückt), das zweite tritt ein, wenn eine Auswahl durchgeführt und ein Objekt ausgewählt wurde.
  • Das Makro hängt sich nun an beide Ereignisse dran: Im Cancel-Fall wird dann die Methode Cancel() aufgerufen (die zeigt einfach nur eine Meldung an, damit man mitbekommt, das die Sache nun ein Ende hat), im anderen Fall wird Finished() aufgerufen.
  • Das MouseTool ist nun fertig und kann aktiviert werden. Das passiert mit FormBase.ActivateMouseTool().

2.) Cancel()

  • Tut wie gesagt nicht viel: Auf die Meldung darin kann man eigentlich verzichten, aber damit man hier im Beispiel mitbekommt, das irgendwas passiert, wird sie eben angezeigt.

3.) Finished()

  • Hier wird zunächst das erste der ausgewählten Objekte aus den Argumenten der Funktion extrahiert. Das Objekt ist garantiert vom Typ IDocObject (weil das MouseTool nichts anderes auswählen kann.). Das ganze sieht darum etwas kompliziert aus, weil das MouseTool eigentlich darauf ausgelegt ist, mehrere Objekte auszuwählen. In unserem Fall gibt es aber immer nur ein Objekt, und darum kann das aus dem Array-Parameter mit “[0]” rausgeholt werden.
  • Dann wird eine neue “DocObjectCollection” angelegt, und das eine ausgewählte Objekt wird in diese Sammlung reingesteckt. Die Sammlung braucht man – obwohl es nur um eine Element geht – weil auch der ganze restliche Code immer darauf ausgelegt ist, mehrere Objekte gleichzeitig zu bearbeiten.
  • Dann wird mit Hilfe des FactoryManagers ein Eigenschafts-Dialog erzeugt. Der Eigenschafts-Dialog ist dabei einer, der für den Typ Objekt zuständig ist, das ausgewählt wurde. Darum muss der Typ des Objektes übergeben werden. (Dieser Typ ist also der konkrete Typ “Beam”, “Node”, etc. – und nicht der “allgemeine” Fall “IDocObject”)
  • Als nächstes wird die zuvor vorbereitete Sammlung in den Dialog gesteckt.
  • Dann hängt sich der Code noch an das “Closed” Ereignis des Dialogs. Dieses Ereignis tritt ein, wenn das Fenster geschlossen wird. Ist das der Fall, wird wieder “DoIt” aufgerufen – die ganze Sache geht also wieder von vorne los.
  • Und schließlich wird die Dialogbox angezeigt.

Ich hoffe mal, mit dieser Erklärung wird das alles etwas klarer….


Die nächtlichen Testläufe


Ich habe schon hin und wieder darauf hingewiesen, das wir die jeweils tagesaktuelle Version der Baustatik jede Nacht automatisiert testen. Wie hat man sich das aber vorzustellen?

So:

Am Quellcode der Baustatik wird natürlich von mehreren Personen gleichzeitig gearbeitet – und die befinden sich auch an unterschiedlichen Standorten. Damit das klappt gibt es einen zentralen Server, der für die Verwaltung der richtigen Versionen des Quellcodes zuständig ist. Von diesem Server kann jede Workstation in unseren Standorten eine Kopie des Quellcodes abholen, und man kann dann auf dieser Workstation damit arbeiten. Lokal vorgenommene Änderungen werden dann wieder in den zentralen Server zurückübertragen.

Im Münchner Netzwerk gibt es nun zwei Server die automatisiert laufen. Der eine ist für die 64bit-Version der Baustatik zuständig, der andere für die 32bit Version. Beide holen einmal am Tag ebenfalls den aktuellen Quellcode vom zentralen Server ab. Danach wird auf dem einen Server die tagesaktuelle Version der Baustatik als 64bit Version gebaut, auf dem anderen die 32bit Version.

Wenn das ging, wird auf beiden Rechner die Baustatik mit speziellen Kommandozeilen-Argumenten gestartet: Die führen dazu, das die Baustatik das spezielle Test-Projekt lädt. Dieses Testprojekt enthält mehrere tausend (Tendenz: steigend) Baustatik-Dokumente. Jedes einzelne dieser Dokumente wird dann durchgerechnet, und jedes einzelne Ergebnis mit dem Ergebnis des Rechenganges vom Vortag verglichen.

Gibt es einen Unterschied, dann wird ein entsprechender “Vermerk” angelegt: Sind alle Dokumente durchgerechnet, dann erhalten alle Beteiligten noch eine EMail, an der man sofort ablesen kann, ob der letzte Durchlauf “geklappt” hat oder nicht – wenn nicht, muss die Ursache dafür beseitigt werden.

Nachdem das Programm immer umfangreicher wird – und dadurch auch immer mehr Testcases anfallen – wird ist die Sache inzwischen relativ zeitaufwendig geworden: Ein kompletter Durchlauf dauert mittlerweile etwa 6 Stunden. Nachdem wird das ganze nur einmal pro Tag machen, ist aber noch jede Menge Luft – bis wir den Prozess irgendwann dann mal anders portionieren müssen.


Ein paar Dinge die ich beim bauen des Index gelernt habe


  • Der in der Theorie einfachste Weg eine gute Suche zusammenzubasteln besteht darin, den Search Server (Express) zu verwenden.  In der Praxis scheitert das aber daran, das das Ding praktisch nicht installiert werden kann.
  • Ein weiterer einfacher Weg ist einfach die Volltextsuche vom SQL Server zu verwenden: Auch die Express-Variante hat einen Katalog für Volltextsuchen. Man braucht dann aber noch einen Spider, der die Webseiten einsammelt – und für ein paar tausend Seiten, so wie bei www.die.de, ist der SQL-Server meiner Ansicht nach ein bisschen übertrieben. Ein .net Dictionary tut es da auch.
  • Für das Spidern der Webseiten kann man – auch wenn eigentlich alle Foren anderer Ansicht sind – prima das WebBrowser Control von .net verwenden: Woran offenbar irgendwie alle scheitern: Man muss an “passender” Stelle Application.DoEvents aufrufen. Damit kann man das Browser-Control auch ein einer Kommandozeilen-Anwendung verwenden. Wenn man das Control verwendet, dann hat man Vor- und Nachteile. Die Vorteile sind klar: Man bekommt Javascript redirects und sonstige Dinge die in der Webseite per Javscript passieren “umsonst”: Die ausgewertete Seite (die man einfach per DOM des Browsers auswerten kann) sieht (von der Struktur her betrachtet) genau so aus, wie sie auch im Browser aussehen würde. Wer sich nicht mit dem eher aufwendigeren auswerten des HTML auseinandersetzen will, der ist mit dem Control gut aufgehoben. Die Nachteile: Das ganze ist recht langsam, und man kann das Control nicht in mehreren parallel Threads verwenden. Ich habe das Control in der ersten Version meines Spiders verwendet, und das hat gut geklappt.
  • Wer das Control nicht verwenden will – zum Beispiel weil man eben mehrere Threads (oder Tasks) braucht, der kann das Html Agility Pack verwenden. Das nehme ich in der aktuellen Version des Spiders: Man hat mehr Arbeit beim auswerten, aber die Sache geht dramatisch viel schneller. (Der erste Spider brauchte für www.die.de ca. 50 Minuten, meine aktuelle Variante braucht unter 2.)
  • Hat man den Spider braucht man noch einen Index: Da kann man wie gesagt den Sql Server hernehmen, oder aber man steckt den Index einfach in ein paar Dictionaries. (Das mache ich.). Dazu ist es ganz gut zu wissen, das es den “Porter Stemmer” Algorithmus gibt. Der führt im Prinzip beliebige Worte in eine “Grundform” über. Das ist dafür gut, damit man zum Beispiel auch das Wort “Preis” findet, wenn “Preise” gesucht wurden. Vom Algorithmus gibt es fertige Implementierungen auch in C#.
  • Im Gegensatz zum Download/Html auswerten geht das indizieren so schnell, das es sich nicht lohnt, selbiges zu parallelisieren.
  • Fürs vernünftige parallelisieren der Downloads braucht man den SerivcePointManager.
  • Nach einer einigermaßen brauchbaren Filterung der Seiten und des Inhalts darauf (inklusive stemming) überrascht einen der geringe Umfang des dann resultierenden Kataloges: Im Fall von www.die.de sieht es so aus, das von ca. 12.000 Seiten “hinterher” gerade noch knapp 4500 Seiten übrigbleiben – und die haben auch noch unter 30.000 unterschiedliche Worte. Eigentlich wirklich nicht besonders viel. Dabei ist die Sache mit den Seiten sicherlich stark von der Webseite abhängig – im Fall von die.de kommen die “vielen unnützen” Seiten durch die diversen Übersichtsseiten der Blogs zustande. Was die Anzahl der Worte angeht: Ich habe das auch mit “anderen” Webseiten ausprobiert, und da sah die Sache ähnlich aus.

Unabhängig davon: Falls jemand Interesse an einem Spider/Indexer in C# für  eine Suche auf der eigenen Asp.Net Webseite hat: Ping me Smiley


Batch-Job: In einen Ordner mit Tages-Nummer kopieren


Musste ich heute machen, und weil man so was immer wieder mal braucht, hier ein kleiner Tipp, wie man in einem Batch-Job an die Nummer des aktuellen Tages im aktuellen Monat (also 1 bis 31) kommt, und diese Nummer dann als Zielordner beim kopieren verwendet.

Einfacher ausgedrückt: Man will jeden Tag ein Backup von irgendwas anlegen, und das Backup soll in einem nummerierten Ordner landen. Die Nummer des Ordners ist dabei die Nummer des Tages.

Geht so (das interessante ist die Zweite Zeile. Da werden 2 Zeichen aus dem Date-String entnommen, und zwar ab dem ‘0’ten Zeichen.):

set DATE=%date%
set DAY=%DATE:~0,2%
copy quelle\*.* ziel\%DAY%


FileNotFoundException beim XmlSerializer


Folgendes passiert: Man versucht Instanzen mit dem XmlSerializer zu serialisieren, bekommt aber im Konstruktor der Serializers eine FileNotFoundException. Was ist zu tun?

Dazu muss man zunächst mal wissen, das es im Wesentlichen zwei Methoden gibt, die Serialisierungs-Assemblies zu erzeugen:

  1. Zur Kompilierzeit, also beim bauen des Projektes
  2. Zur Laufzeit der Anwendung

Der erste Fall ist manchmal ganz gut dafür geeignet, die Ladezeit der Anwendung zu verkürzen: In diesem Fall ist die DLL aber garantiert da, denn wenn das nicht der Fall wäre, wäre bereits beim bauen der Anwendung ein Fehler aufgetreten.

Es geht also um den zweiten Fall. Da passiert folgendes: Der Konstruktor des XmlSerializers versucht zunächst eine passende Assembly zu finden (die hat den Namen der DLL, in der der zu serialisierende Typ steckt, plus die Erweiterung “Xmlserializer.dll”). Findet er die nicht, wird zur Laufzeit Code zum serialisieren erzeugt und übersetzt – und die daraus resultierende Assembly wird dann geladen und zum serialisieren eingesetzt.

Die FileNotFound Exception kommt dann, wenn das erzeugen der Serialisierungs-Assembly nicht geklappt hat. Dafür kann es jede Menge Gründe geben – nicht zuletzt Fehler in der Struktur der zu serialisierenden Klassen oder deren Auszeichnung.

Wie findet man nun das Problem? So:

  1. Einen (Visual Studio) Command-Prompt öffnen
  2. In das Verzeichnis wechseln, in der sich die Anwendung befindet
  3. “sgen NameDerAusgabgsDll.Dll /v” eingben

Sgen gibt dann eine im allgemeinen einfach verständliche Fehlermeldung aus: Korrigiert man den Fehler, ist man auch die FileNotFoundException los.


Wir wir die Performance verbessern


Ich hatte in den letzten Tagen mehrfach darauf hingewiesen, das wir die Performance der Baustatik spürbar verbessert haben. Das ist natürlich schön – aber wie macht man das eigentlich ?

image

Es gibt verschiedene Möglichkeiten, aber im Wesentlichen läuft es darauf hinaus, das man die Zeit misst, die die einzelnen Funktionen des Programms brauchen. Dann sucht man die Funktion die am langsamsten ist und überlegt sich, ob die benötigte Zeit “angemessen” ist, oder nicht. Wenn nicht, hat man einen Kandidaten für die Optimierung. Die kann daraus bestehen, das man einfach nur den Code ein bisschen umstellt (hauptsächlich in Fällen, in denen Funktionen extrem oft aufgerufen werden), oder auch daraus, das man tatsächlich einen völlig neuen Algorithmus verwendet.

Das wichtige ist aber immer das Messen – vor allem auch das erneute Messen nach der Änderung: Die Dinge sollen ja nicht langsamer werden…

Damit man sowas messen kann, wird das Programm nicht direkt, sondern unter der Kontrolle eines anderen Programmes gestartet. Das kontrollierende Programm, das auch die Messungen durchführt, nennt man Profiler. Davon gibt es eine ganz Reihe – in unserem konkreten Fall haben wir das Programm GlowCode verwendet. (Das übrigens wirklich erstaunlich schnell ist, wenn ich dessen Performance mit anderen Profilern vergleiche, die ich ausprobiert habe.)

Letzten Endes ist es also ganz einfach: Messen, ändern, wieder messen: Wenn es dann schneller ist – prima, sonst zurück zum Anfang.


Sauer auf Microsoft


Im Normalfall bin ich Microsoft gegenüber eher positiv eingestellt: Letzthin haben die sich aber etwas geleistet, was ich alles andere als lustig finde. Das Visual Studio hat seit Version 2003 eine Projektart namens “Setup and Deployment Project”.

Wir installieren die Baustatik damit seit der ersten Version, und haben das Projekt auch brav nach VS 2005, nach VS 2008 und nun nach VS 2010 portiert. Und zwar nicht mit wenig Arbeit: Zur Zeit installieren wir damit circa 5000 Files. Wenn ich raten müssten würde ich sagen, das im Laufe der Zeit mindestens 4 Monate Arbeit in dieses Projekt gesteckt wurden.

Und mit VS 2010 geht nicht mehr. Das Projekt erzeugt zwar noch ein MSI – aber das kann man eigentlich nicht mehr richtig nutzen: Installieren kann man nur noch auf einem Rechner, auf dem es keine Vorgängerversion gibt – und der von Microsoft gelieferte Workaround führt zwar zu installierten Dateien, aber auch zu nicht mehr funktionierenden Links im Startmenü.

Das ist auch bei Connect alles prima dokumentiert (und der Fehler ist auch schon bestätigt) – nur interessiert es irgendwie niemand. Die letzten 7 Tage habe ich auf irgendein Feedback irgend einer Art gewartet – jetzt geht’s nicht mehr länger: Ich werde in den sauren Apfel beißen und mich nach einem anderen Werkzeug zur Herstellung des Setups umsehen – und dann den ganzen Spaß von vorne aufbauen.

Bin regelrecht stinkig.