Thomas Woelfers Baustatik Blog

Baustatik-Software und was sonst des Weges kommt

Ein besserer Taschenrechner

Jeder Statiker hat vermutlich sowieso immer einen Taschenrechner auf seinem Schreibtisch liegen - aber eigentlich wäre das ja gar nicht notwendig: Schließlich hat Windows ja einen prima Taschenrechner eingebaut - und der kann (im richtigen Betriebsmodus) auch mit allen notwendigen Formeln umgehen.

Für das normale 'Calc' gibt es aber auch einen aufwendigeren (und kostenlosen) Ersatz: Calculator Plus. Zusätzlich zu normalen Rechnenoperationen und den wissenschaftlichen Funktionen hat der Calculator weitere Features: Er kann nämlich zusätzlich noch umrechnen. Und zwar alles mögliche: Währungen, Längen, Volumen, Energie - und so weiter und so fort. 'Meter' kann man zum Beispiel in 'Feet' umrechnen - aber auch in Lichtjahre, in 'hunh' (was immer das ist), in parsecs, in picas und duzend von anderen Einheiten: Wirklich sehr praktisch - für jeden Ingenieur ein guter Ersatz für den normalen Windows-Rechner.

Natürlich kein Ersatz für unsere Statiksoftware :-)

Hilfe bei Speicher-Lecks

Der Garbage Collector ist (für einen Programmierer mit C/C++ Hintergrund) einer der großen Vorteile der .Net CLR. Man erhält deutlich mehr Freiheiten beim programmieren, weil man sich eben nicht mehr darum kümmern muss Speicher auch wieder freizugeben.

Nur stimmt das leider nicht: Man kann auch in .Net Programmen prima Speicherlecks produzieren - und aufgrund bestimmter Funktionalitäten sind die sogar noch besser versteckt als das bei nativem Win32 Code der Fall ist.

Bei .Net tritt ein Speicherleck dann ein, wenn man unbeabsichtigterweise einen Zeiger auf ein Objekt behält: Solange diese Zeiger (diese 'Referenz') auf das Objekt besteht, kann das Objekt nicht garbage collected werden - und stellt effektiv ein Leck dar.

Das klingt zunächst nicht so dramatisch - aber man muss sich vergegenwärtigen, wie leicht man so eine Referenz erzeugen und vergessen kann: Angenommen Sie haben einen Eventhandler für ein bestimmtes Ereignis in einem anderen Objekt. Wenn Sie den nicht irgendwann wieder entfernen, dann bleibt die Referenz bestehen - und das 'andere' Objekt ist effektiv ein Speicherleck sobald es nicht mehr gebraucht wird. Genau, Sie haben richtig gelesen: Natürlich ist auch ein Eventhandler eine Referenz die dazu führt das der Garbage Collector nicht anspringt.

Solche Probleme sind schwer zu finden, es gibt aber Tools die dabei helfen. Zum Beispiel das hier: Der SciTech .NET Memory Profiler. Das Programm verschafft einen jede Menge interessante Einblicke - und ist mit 100 Dollarn auch nicht besonders teuer.

Lokalisierbare Enums

Um Objekte bzw. deren Texte lokalisierbar zu machen kommen im Fall von 'Enums' die TypeConverter ins Spiel. Die Idee dabei ist, das man einfach einen TypConverter der die Konvertierung von und nach 'string' beherrscht an beliebige Enums dranhängt - und die dann im Zuge der Konvertierung gleich die lokalisierten Strings liefern.

Nichts einfacher als das - es gibt sogar eine praktische Basisklasse dafür: Den EnumConverter. Die Dokumentation dazu macht einen aber reichlich kirre: Zwar gibt es einen extra Hinweis für Leute die davon ableiten wollen - nur gibt es dann absolut nichts zum Thema Konstruktor. Der EnumConverter hat nämlich einen privaten Defaultkonstruktor, und mindestens einen weiteren der einen Parameter bekommt. Der ist aber in der normalen Doku nicht dokumentiert.

Mit anderen Worten: Auf der anderen Seite soll man zwar erben können, auf der anderen Seite wird aber nicht verraten wie. Wer nun meint 'Kein Problem, dann erbt man eben einfach von 'TypeConverter' - der hat natürlich Recht, und genau das habe ich vor etwa zwei Jahren auch getan.

Heute bin ich aber wieder mal über den Code gestolpert und habe nachgesehen - und mittlerweile ist der Konstruktor zum EnumConverter dokumentiert und kann damit (offiziell) benutzt werden. (Die Doku sagt zwar, der Konstruktor wäre Teil der Infrasturktur die man nicht benutzen soll, aber der Hinweis für 'Erbende' widerspricht diesem Teil der Dokumentation ja eindeutig....)

Lange Rede kurzer Sinn: Geerbt von EnumConverter ist der Lokalisierbare EnumConverter extrem einfach implementiert. (Tipp: Der Parameter ist natürch der Typ des Enums, aber das hätte man auch mit Reflector herausfinden können.)

Mehrere Schriftköpfe für den Ausdruck

Wenn Sie Ergebnisse oder Eingabedaten unserer Statikprogramme ausdrucken möchten, dann können Sie das im 'klassischen' oder 'normalen' Format tun. Beide Formate haben ihre Vor- und Nachteile - es ist aber in beiden Formaten möglich unterschiedliche Schriftköpfe auf dem Ausdruck zu verwenden.

Beim 'normalen' Format ist das relativ klar: Sie verwenden einfach eine andere Layout-Datei - fertig.

Beim 'klassischen' Format ist das weniger klar. Was Sie hier tun müssen ist, Sie müssen einen 'Schriftkopfsatz' anlegen. Ein 'Schriftkopfsatz' enthält Informationen über die Texte in der Kopf- und Fußzeile, über die Schriftgröße und Schriftart usw.

Sie können mehrere Schriftkopfsätze definieren - welcher dann verwendet wird können Sie in der Dialogbox 'Schriftkopf einstellen' auswählen: Dazu gibt es auf dieser Dialogbox ganz oben eine Auswahl-Liste zum ausklappen. Es reicht, wenn Sie den gewünschten Satz auswählen und dann OK klicken - der nächste Ausdruck erscheint dann mit dem ausgewählten Schriftkopf.

Nochmal: Context-Menus, diesmal in einer TreeView

Irgendwie scheint sich das bei mir zum Monat der Context-Menüs zu entwickeln... hier und hier gibt schon mehr...

Trotzdem nochmal ein Nachschlag - diesmal gehts um ein Context-Menü in einer TreeView. Einmal angenommen ein rechter Mausklick auf eine Node in der TreeView soll die Node als selektiert markieren und dann ein Popup-Menü für die Node öffnen.

Nichts leichter als das: Man legt in der TreeView einen Handler für das Popup-Event des Menüs an, und wenn der Eintritt dann setzt man die Node eben als selektiert (oder verändert deren Farben, je nachdem welchen Effekt man will), füllt das Menü ganz nach Wunsch mit Befehlen auf - und Windows zeigt das Ding dann an.

Das klappt auch wunderbar - nur leider bekommt man dann einen extrem unschönen Flacker-Effekt in die TreeView: Das Ding wird komplett neu gemalt und das führt zu einem echt irritierenden Blitzen.

Grund: Wenn das Popup-geöffnet wird darf man eben nichts mehr malen, der PopUp-Event ist einfach der falsche Zeitpunkt.  Das steht zwar nirgends (zumindest habe ich nichts gefunden), ist aber relativ leicht mit Spy++ herauszufinden wenn man sich die WM_ERASEBKGNDs und die WM_PAINTs ansieht - vor allem deren Reihenfolge, denn die ändert sich wenn man das malen während des Popup-Handlers weglässt.

Man muss also folgendes tun: Im MouseUp-Event führt man die Selektion durch und setzt die Node eben als ausgewählt und im Popup-Event füllt man nur das Menü auf. Dann ist beides an der richtigen Stelle und das flackern hat ein Ende.

Fürs Wochenende: Zweimal verkehrt

Als Wochenend-Unterhaltung gibt es diesmal: Reverse - eine interessante Art die Hand-Auge Koordinaten zu trainieren und elgooG damit das suchen wieder interessanter wird.

Achtung: Beide Links zeigen auf gespiegelte Seiten, der Rest des Internet bleibt aber dankbarerweise unverändert.

Unterstützung für Din 1045-1

Wir haben da nicht besonders drauf hingewiesen - aber natürlich unterstützen unsere Statikprogramme auch die kommende DIN 1045-1. Die Unterstützung für Bemessung nach Din 1045-1 ist im Laufe des Jahres Stück für Stück in die Programme eingebaut worden und wird zur Zeit in einigen Stellen noch weiter integriert: Prinzipiell ist die 'neue' DIN aber in (nahezu) allen Programmen fertig eingebaut. Das gilt zum Beispiel für den Durchlaufträger, das FEM-Plattenprogramm und auch das Tragwerksprogramm.

In den online verfügbaren Programmbeschreibungen wird darauf noch nicht in allen Fällen hingewiesen - eingebaut ist die Sache aber trotzdem.

Dabei zeigt sich auch wieder einmal der Vorteil eines Work&Cash Vertrages: Wer unsere Programme im Rahmen von Work&Cash nutzt hat alle Updates bezüglich der neuen DIN völlig kostenlos erhalten - genau wie allen anderen anfallenden Programm-Updates und Neuerungen. Anders als andere Hersteller haben wir die neue DIN eben nicht zum Anlass kostenpflichtigen Zwangs-Updates (immehin kommt man ja nicht daran vorbei nach den neuen DIN zu rechnen) zu verkaufen - Work&Cash Kunden sparen also mal wieder bares Geld.

Nicht vergessen: Idle-Handler entfernen

Wenn man in einer Dialogbox (Form) das UI updaten will, dann bietet sich dafür ein Idle-Handler an: In dem kann man prima Buttons disablen, Texte setzen und ähnliches tun. Den Handler installiert man ganz einfach nach InitializeComponent(), den Code dazu plaziert man einfach als private Methode in die Form:

Application.Idle += new EventHandler(Application_Idle);

Das Problem: Damit hat man der Anwendung einen neuen Idle-Handler zugewiesen - und was man dabei leicht vergisst: Der verschwindet natürlich nicht von selbst, nur weil man das Fenster schliesst. Resultat: Die Anwendung hat einen Zeiger auf den Idle-Hander, der Idle-Handler befindet sich in der Form - und die Form wird deshalb nie Disposed(). Einer der bösen Fälle wie man mal so eben auf die schnelle mit .Net ein Speicherleck erzeugt.

Lösung: Man merkt sich den Idlehandler in einer privaten Variable und trennt sich in Form.Closing() oder Form.Closed() wieder von der Anwendung ab.

// Init ....
idleHandler =
new EventHandler(Application_Idle);
Application.Idle += idleHandler;

// Closed ...
Application.Idle -= idleHandler;

Morgen: Wieder etwas über unsere Statikprogramme.

Nachschlag: Dynamisch veränderte Menüs

Hier noch ein kurzer Nachschlag zum Beitrag übers verhindern von Flackern bei der dynamischen Veränderung von Menüs von vorgestern.

Wenn man wie dort angegeben das neuzeichnen verhindert, dann passiert es natürlich auch: Es wird nichts neu gezeichnet. Das ist schön und macht kein Flackern - wenn sich aber auch das 'MainMenu' der Form zwischenzeitlich ändert, dann wird das natürlich auch nicht neu ausgegeben.

Resultat: Das sichtbare Menü hat dann nicht mehr viel mit dem echten zu tun. Fährt man dann mit der Maus über die Menupunkte, dann kommt dabei nach und nach das 'richtige' Menü zutage.

Man muss sich also darum kümmern, das das Menü nach der Aenderung am Menü zumindest einmal ausgegben wird.

Invalidate( true) der Parent-Form reicht dabei nicht aus, weil dabei wie früher bei Win32 zwar die Kindfenster, nicht aber die Non-Client Area und auch nicht das Menü neu gemalt werden. Statt dessen braucht es eine weitere Methode die man nur per Interop erreichen kann: DrawMenuBar(). Das Ding erwartet einen Parameter, und das ist das Handle des Fensters dessen Menü neu gezeichnet werden soll. Die Signatur für Interop sieht wie folgt aus:

[DllImport("User32.dll", CharSet=CharSet.Auto)]
private static extern int DrawMenuBar(IntPtr hWnd);

Der Aufruf (irgendwo im Form-Code) ist dann einfach:

Invalidate( true);  // die restlichen Kinder auch invalidieren
DrawMenuBar(
this.Handle);  // das Menu neu zeichnen

Wiesn-Eröffnung: Laternenhalter bewegen sich langsam

Alle Jahre wieder: Die Wiesn  ('Oktoberfest') ist eröffnet. Was ich wirklich erstaunlich daran finde ist nicht die Tatsache das es von hier nach dort fast 3.5 Kilometer sind. Was mich aber sehr wohl erstaunt ist die Tatsache das es trotzdem keine Seltenheit ist wenn man bei mir vor der Haustür an Volltrunkenen vorbeikommt die sich ihren Rausch offensichtlich auf der Wiesn geholt haben.

Wie zum Henker schaffen diese Leute es, in diesem Zustand fast 3.5 Kilomenter weit zu gehen? Und vor allem: Sind die schon vor einer Woche losgegangen? Man ist ja nicht besonders schnell, wenn man sich an einem Laternenpfahl festhält....

Fragen über Fragen - nächste Woche geh' ich mal nachsehen was denn dieses Jahr geboten wird... :-)

Popup-Event wird im Context-Menu nicht ausgeloest

Wenn man ein Menu dynamisch zusammenbaut, dann braucht man dringend das Popup-Ereignis: Wenn das eintritt, dann kann man die MenuItems das 'gleich' angezeigten Menus manipulieren. Man kann zum Beispiel den 'Enabled' State verändern, den Text setzen, oder aber auch neue Items hinzufügen oder vorhandene entfernen.

Das klappt ganz wunderbar - nur leider nicht bei Kontextmenus, zumindest nicht immer. Angenommen man hat ein ContextMenu mit Untermenüs, und man zeigt das Menü selbst per ContextMenu.Show() an. In diesem Fall gibt es zwar ein Popup-Event fürs Menü selbst, nicht aber für die Untermenüs.

Es gibt einen Workaround, allerdings einen ärgerlichen: Man muss den Code umstrukturieren und darf sich nicht länger selbst um die Anzeige des ContextMenus kümmern. Wenn man statt dessen dem Control über dem das Menü angezeigt werden soll per

control.ContextMenu = new ContextMenu();

ein solches Menü zuweist, dann wird dieses Menü von selbst geöffnet - und es wird auch das Popup-Event für Untermenüs darin ausgelöst.

Das macht zunächst nichts weiter aus, wenn man aber selbst schon Code hat der vor ContextMenu.Show() dafür zuständig ist das Menü zu manipulieren oder erst 'richtig' zusammenzusetzen, dann muss man den umbauen und die Menümanipulation im Popup-Event vornehmen.

Bei der Gelegenheit ist mir noch was anderes bei MainMenus aufgefallen: Wenn man in ein Untermenü eines MainMenus MenuItems reintut, dann führt das zu sehr unschönen Flicker-Reaktionen und die sind relativ schwer wegzubekommen: Menus haben kein BeginUpdate()/EndUpdate(), und SuspendLayout() / ResumeLayout() der zugehörigen Form bringen auch keinerlei Verbesserung.

Und das flackern wegzubekommen habe ich nur zwei Möglichkeiten gefunden. Beide gehen nur mit Interop: Im einen Fall verwendet man LockWindowUpdate() für die Form in der das MainMenu ist. Laut Raymond sollte man das aber besser bleiben lassen.

Ganz gut funktioniert das folgende: Man hält das malen der Form (und das zugehörige verarbeiten von Messages) an, fügt die MenuItems hinzu, und startet die malerei dann wieder. Das geht in etwas so:

private const int WM_SETREDRAW = 0x000B;
private const int
WM_USER = 0x400;
private const int
EM_GETEVENTMASK = (WM_USER + 59);
private const int
EM_SETEVENTMASK = (WM_USER + 69);

[DllImport("user32", CharSet = CharSet.Auto)]
private extern static IntPtr SendMessage(IntPtr hWnd, int msg, int wParam, IntPtr lParam);

// hier ist dann die methode die die aenderung durchfuehren soll...
void foo()
{
   IntPtr eventMask = IntPtr.Zero;

   try
   {
     
SendMessage( TheApplication.MainWindow.MainForm.Handle, WM_SETREDRAW, 0, IntPtr.Zero);
     
eventMask = SendMessage( TheApplication.MainWindow.MainForm.Handle, EM_GETEVENTMASK, 0, IntPtr.Zero);
      // hier fuegt man dann die neue MenuItems ein....
  
}
  
finally
  
{
     
SendMessage( TheApplication.MainWindow.MainForm.Handle, EM_SETEVENTMASK, 0, eventMask);
     
SendMessage( TheApplication.MainWindow.MainForm.Handle, WM_SETREDRAW, 1, IntPtr.Zero);
  
}
}

MSDN online mit neuen Features auf neuer Seite

Es ist noch eine Alpha-Version, aber man kann schon eine Menge sehen vom 'neuen' MSDN unter http://msdn2.microsoft.com/library.

Nachdem die Online-Hilfe per MSDN im Internet auf meinem Rechner aus unerfindlichen Gründen deutlich schneller ist als die Variante mit Inhalten von der Festplatte bin ich über ein besseres MSDN Online ganz dankbar.

Praktisch beim neuem MSDN: Man kann einfach einen .Net Namespace-Namen in der URL angeben und landet automatisch in der zugehörigen Dokumentation. Das funktioniert natürlich auch mit Klassennamen: Sucht man die Doku vom Button aus 'System.Windows.Forms', dann findet man die unter http://msdn2.microsoft.com/library/system.windows.forms.button.aspx

Windows Mediaplayer 10: Hübsche Bilder

Es gibt seit einigen Tagen einen neue (fertige) Version des Windows Media Players. Der Player mit der Nummer 10 hat einige sehr praktische neue Features bei der Verwaltung und beim Anlegen von Playlisten, und arbeitet mit Online-Musikshops zusammen (was aber in Deutschland nicht geht). Im großen und ganzen ist das ganze ein hübsches Programm - zwar kein Riesensprung von Version 9, doch wer was älteres hat sollte auf jeden Fall mal ein Update wagen.

Das beste am ganzen hat aber meiner Ansicht nach nicht viel mit dem abspielen von Musik und Videos zu tun: Die 'Energy Bliss' Visualisierung ist einfach nur hüsch anzusehen.

Achtung: Das Installationsprogramm dafür will unbedingt als Administrator ausgeführt werden, sagt aber nichts dergleichen wenn man es mit einem anderen Account startet: Statt dessen passiert einfach gar nichts - 'run as' ist also angesagt.