Will man in einer Winforms-Anwendung einen String URL-encodieren, dann kann mann dafür einen einfachen Aufruf verwenden: HttpUtility.UrlEncode("http://www.artiso.com/page?ID=99"); Das einzige wass man dafür tun muss, ist die System.Web.dll zu referenzieren. Eine kleine Hürde gibt es bei Visual Studio 2010, da hier standardmäßig “.Net Framework 4 Client Profile” als Target-Framework ausgewählt ist und das Client Profile die System.Web nicht enthält. Deshalb muss man in den Eigenschaften des Projektes das Target-Framework umstellen, z.B. auf .NET Framework 4. 
Hype-V ist ein klasse Virtualisierungssystem das vor allem in der neuesten Version eine Reihe nützlicher Funktionen mitbringt. Besonders hilfreich sind Snapshots mit denen es gefahrlos möglich ist, auch auf dem Server mal was zu testen. Man kann sich aber mit Shanpshots leicht selber überlicsten oder gar in’s Knie schießen. Deshalb möchte ich hier ein paar Erfahrungen zum Besten geben: Funktionsweise von Snapshots Die Virtuellen Maschinen von Hyper-V schreiben ihre Daten in virtuelle Harddisks (VHD), also eine Datei auf der Platte des Hosts. Wird nun ein Snapshot gemacht, passiert vereinfach gesagt, Folgendes. Die VHD-Datei wird schreibgeschützt, so dass daran keine Änderungen mehr vorgenommen werden können. Statt dessen wird nun eine AVHD-Datei angelegt und alle Änderungen auf der Platte der VM werden nun dort hineingeschrieben. Wird erneut ein Snapshot erstellt, wird die AVHD gesperrt und eine weitere angelegt. Dies hat nun zweierlei Folgen: - Je mehr Snapshots erstellt werden, desto langsamer wird der Zugriff auf die virtuelle Disk, da Hyper-V ja aus der VHD und den verschiedenen AVHDs nun die Daten zusammensammeln muss.
- Nur die VHD und alle AVHDs zusammen ergeben den aktuellen Stand der virtuellen Disk, mit der VHD alleine hat man nur den Stand vom ersten Snapshot.
Gerade der letzte Punkt ist beim Verschienen der VM oder beim Backup zu berücksichtigen, da man hier schnell Daten verlieren kann, wenn man vergisst die AVHDs zu berücksichtigen. Aus beiden oben genannten Gründen macht es Sinn, Snapshots zu löschen, sobald diese nicht mehr gebraucht werden. Snapshots sind keine Technik zur Erstellung von Backups! Zum löschen eines Snapshots wird dieser einfach selektiert und dann mit “Delete” gelöscht. Aber ACHTUNG! Die Daten liegen nun immer noch in der AVHD. Erst wenn man die VM in den Save-State fährt, beginnt Hyper-V mit einem Merge, d.h. die Änderungen der AVHDs werden jetzt in die jeweils vorausgegangene AVHD bzw. VHD gemerged, sind alle Snapshots gelöscht, bleibt nur noch die VHD übrig. Erst nach Abschluss des Merge-Vorgangs erhält man durch eine Kopie der VHD eine komplette Sicherung der virtuellen Disk der VM.
Eines der coolsten Features in Windows 7 ist für mich der Problem Steps Recorder. Damit lassen sich Benutzeraktionen aufzeichnen und als MHT-File mit Screenshoots ausgeben. Diese Funktion eignet sich sehr gut um z.B. Installationsanleitungen oder auch Dokumentationen von Testdurchläufen zu erstellen. Hierzu wird einfach der Problem Steps Recorder über den Befehl PSR gestartet.  Nach einem Klick auf Start Record werden nun die Benutzeraktionen aufgezeichnet. Heraus kommt dann ungefähr so etwas: Problem Step 15: User left double click on "Name (editable text)" in "WorkItem_Manager_Open_1.0.0_Installer[1]"
Previous Next Problem Step 16: User left click on "Run (push button)" in "Open File - Security Warning"
Previous Next Problem Step 17: User left click on "Next > (push button)" in "artiso Workitem Manager Open"
Diese Aufzeichnungen funktionieren sowohl mit Web- als auch Windows-Anwendungen. Und natürlich können die erzeugten MHT-Files in Word oder anderen Editoren noch bearbeitet werden. So können z.B. überflüssige Zwischenschritte entfernt und Texte editiert werden. Es können auch währen des Aufzeichnungsvorgangs Kommentare erfasst und diesen ein bestimmter Bildausschnitt zugeordnet werden.
Das Thema Test Driven Development oder auch Test First Developent gewinnt immer mehr an Beachtung. Keine Konferenz, keine Zeitschrift, kein Sprecher der was auf sich hält kommt um das Thema herum. Doch nach dem überzeugenden Vortrag sitzt man zu Hause im Büro vor einem leeren Project und wie nun anfangen? Hier scheitern bereits die ersten, weil entsprechende Publikationen oft zwar die Vorteile ausführlich schildern, aber nicht den Einstieg darstellen. Deshalb möchte ich hier einen entsprechenden Einstieg geben und mit einem wirklich leeren Projekt beginnen. Die Theorie um TDD will ich hier einfach weglassen. Hierzu gibt es bereits Informationen genug. Und wir werden verschiedene Vereinfachung vornehmen, über die Profis etwas die Nase rümpfen werden, aber damit erhalten wir ein einfaches und praktikable Einstiegsszenario. Zum Einsatz kommen hierbei die Testfunktionen von Visual Studio 2008 die ab der Professional Edition enthalten sind. Wir beginnen mit einer komplett leeren Solution. Die Frage, die nun im Raum steht, ist: Wie schreibe ich einen Test ohne eine Methode zu haben. Ein Unit-Test besteht ja im Prinzip darin, dass wir eine Methode aufrufen und den Rückgabewert mit einem Erwartungswert vergleichen. Der Test wird aber nicht einmal kompilieren, solange die Methode nicht definiert ist. Der Workaround an dieser Stelle sieht dann oft so aus, dass man von der Methode und ihrer Klasse erst einmal einen Stub anlegt der im wesentlichen eine “ThrowNotImplemented”-Exception wirft. Damit haben wir aber eigentlich schon mehr implementiert als nach dem TDD uns lieb ist. Ein etwas eleganterer Ansatz geht über die Definition von Interfaces. Diese Vorgehensweise eignet sich besonders gut bei einer komponentenorientierten Architektur mit einem Contract First Ansatz. Dabei werden die Schnittstellen der einzelnen Komponenten erst über Contracts (Interfaces) beschrieben bevor diese implementiert werden. Den TDD-Ablauf Rot > Grün > Refactor erweitern wir ein wenig. Damit ergibt sich folgende Abfolge: Contract definieren > Test implementieren > Rot > Funktion implementieren > Grün > Refactor D.h. wir erstellen in einem ersten Schritt einen Contract (genau genommen machen wir damit kein TDD sondern ein Test First. Beim TDD ist der Test das erste was erstellt werden muss, aber das ist in meinen Augen eher Haarspalterei, so funktioniert es einfach in der Praxis). Wir erstellen ein neues ClassLibrary-Projekt und erstellen dort ein Interface. 1: namespace Contracts 2: { 3: public interface IOrderCalculator 4: { 5: decimal CalculateShippinghCosts(decimal sum, decimal freeShippingMin, decimal shippingCosts); 6: } 7: }
Wir wollen hier ein überschaubares, aber auch nicht zu triviales Beispiel verwenden. Die Methode CalculateShippingCosts soll zu einem gegebenen Rechnungsbetrag Versandkosten hinzuaddieren, wenn ein bestimmter Mindestbetrag nicht erreicht ist. So damit haben wir den Contract erstellt. Nun wollen wir einen Test dazu erstellen. Das geht am schnellsten durch einen Rechts-Klick auf die Methode und dann “Create Unit-Tests”.
Hier wird standardmäßig ein neues Test-Projekt angelegt. Darin wird ein entsprechender Unit-Test generiert.
1: [TestMethod()] 2: public void CalculateShippinghCostsTest() 3: { 4: IOrderCalculator target = CreateIOrderCalculator(); // TODO: Initialize to an appropriate value 5: Decimal sum = new Decimal(); // TODO: Initialize to an appropriate value 6: Decimal freeShippingMin = new Decimal(); // TODO: Initialize to an appropriate value 7: Decimal shippingCosts = new Decimal(); // TODO: Initialize to an appropriate value 8: Decimal expected = new Decimal(); // TODO: Initialize to an appropriate value 9: Decimal actual; 10: actual = target.CalculateShippinghCosts(sum, freeShippingMin, shippingCosts); 11: Assert.AreEqual(expected, actual); 12: Assert.Inconclusive("Verify the correctness of this test method."); 13: }
Damit können wir den Test bereits zum ersten mal ausführen und er geht wie erwartet auf Rot. Aber Moment, wie funktioniert das. Wie kann der Test eine Methode auf einem Interface aufrufen? Es gibt ja noch keine Implementierung des Interfaces und ein Interface selbst lässt sich ja nicht instanziieren. Hier baut Visual Studio einen kleinen Workaround. In Zeile 4 im obigen Code sieht man, dass eine Instanz des target-Objektes über die Methode CreateIOrderCalculator() erstellt wird. Diese Methode wollen wir mal etwas genauer anschauen.
1: internal virtual IOrderCalculator CreateIOrderCalculator() 2: { 3: // TODO: Instantiate an appropriate concrete class. 4: IOrderCalculator target = null; 5: return target; 6: }
Hier wird das Objekt einfach mit null initialisiert. Ein einfacher, aber wirkungsvoller Workaround. Damit erreichen wir unser Ziel, dass der Test kompiliert aber fehlschlägt. Nach der Implementierung ersetzen wir das null einfach durch die entsprechende Initialisierung. Damit können wir nun unsere Testcases Implementieren.
1: [TestMethod()] 2: public void CalculateShippinghCosts_Sum_Below_FreeShippingMin() 3: { 4: IOrderCalculator target = CreateIOrderCalculator(); 5: Decimal sum = 1; 6: Decimal freeShippingMin = 10; 7: Decimal shippingCosts = 5; 8: // We are below min, so we have to add shippingCosts 9: Decimal expected = 6; 10: Decimal actual; 11: actual = target.CalculateShippinghCosts(sum, freeShippingMin, shippingCosts); 12: Assert.AreEqual(expected, actual); 13: } 14: 15: [TestMethod()] 16: public void CalculateShippinghCosts_Sum_Above_FreeShippingMin() 17: { 18: IOrderCalculator target = CreateIOrderCalculator(); 19: Decimal sum = 20; 20: Decimal freeShippingMin = 10; 21: Decimal shippingCosts = 5; 22: // We are above min, so we don't add shippingCosts 23: Decimal expected = 20; 24: Decimal actual; 25: actual = target.CalculateShippinghCosts(sum, freeShippingMin, shippingCosts); 26: Assert.AreEqual(expected, actual); 27: } 28: 29: [TestMethod()] 30: public void CalculateShippinghCosts_Sum_Equal_FreeShippingMin() 31: { 32: IOrderCalculator target = CreateIOrderCalculator(); 33: Decimal sum = 10; 34: Decimal freeShippingMin = 10; 35: Decimal shippingCosts = 5; 36: // We are equal min, so we don't add shippingCosts 37: Decimal expected = 10; 38: Decimal actual; 39: actual = target.CalculateShippinghCosts(sum, freeShippingMin, shippingCosts); 40: Assert.AreEqual(expected, actual); 41: }
Damit haben wir unser Szenario ausreichend beschrieben. Wir können nun an die Implementierung gehen.Dazu legen wir ein neues Projekt an in dem wir eine Klasse definieren die wir von unserem Interface ableiten.
1: namespace Components 2: { 3: public class cOrderCalculator : IOrderCalculator 4: { 5: public decimal CalculateShippinghCosts(decimal sum, decimal freeShippingMin, decimal shippingCosts) 6: { 7: // If sum is greater than Min then don't add shipping costs 8: if (sum > freeShippingMin) 9: return sum; 10: else 11: // else add shipping costs 12: return sum + shippingCosts; 13: } 14: } 15: }
Nun müssen wir unbedingt noch daran denken, die Initialisierung des Testobjektes in unserer Testmethode anzupassen.
1: internal virtual IOrderCalculator CreateIOrderCalculator() 2: { 3: IOrderCalculator target = new cOrderCalculator(); 4: return target; 5: }
Nun können wir die Tests ausführen.
Oh, ein Test schlägt fehl. Bei genauerer Betrachtung stellen wir fest, dass wir bei der Implementierung den Fall dass die Summe gleich der Grenze ist nicht richtig berücksichtigt haben. Also hat sich hier der TDD-Ansatz schon bewährt und wir können den Fehler beheben. Damit sind alle Tests grün und wir können weiter fortfahren. Wir könnten nun z.B. auf unserem Interface weitere Methoden definieren und dafür Tests anlegen.
Also eigentlich gar nicht so schwer das mit dem TDD, oder? Freue mich auf euer Feedback.
Die Solution gibt es zum Download.
Happy Testing!
Wie so oft lautet die Antwort es kommt darauf an. Un hier bietet die Seite ShineDraw eine gute Vergleichsmöglichkeit. In der Gallery befinden sich zahlreiche Beispiele die sowohl in Silverlight als auch in Flash implementiert sind. Da kann man nicht nur das Ergebnis in beiden Varianten vergleichen sondern auch den Code der in der jeweiligen Technologie erforderlich ist. Darüber hinaus kann man sich den einen oder anderen effekt abschauen und sieht auch gleich wie der implementiert wird. Der Quellcode ist unter der GNU General Public License, Version 3 bereitgestelt. http://www.shinedraw.com/flash-vs-silverlight-gallery/
In manchen Fällen ist es wünschenswert, eine Liste mit den Tests aus einem Projekt auszudrucken. Leider gibt es hierzu in Visual Studio keine direkte Funktion, aber ein kleiner Trick hilft hier: 1.) Im TestView die gewünschten Spalten einblenden 2.) Gewünschte Tests markieren und dann im Kontext-Menü "Copy" auswählen 3.) In Excel einfügen, fertig! Das selbe funktioniert übrigens auch aus den Test Results, leider ohne Icons :-( 
Bisher war für mich das Pflegen meiner verschiedenen Signaturen im Outlook immer ein Horror. Wenn ein neuer Rechner eingerichtet wurde, musstden die Signaturen wieder mühsam angelegt werden und wenn es Änderungen gab, dann grauste es mich schon davor, diese auf allen Instanzen anzupassen. Dank Lars kenne ich nun aber einen einfacheren Weg. Die Signaturen werden nämlich einfach im Filesystem abgelegt und zwar unter %UserProfile%\AppData\Roaming\Microsoft\Signatures. Diese Files lassen sich einfach auf einen neuen Rechner kopieren (sogar bei laufendem Outlook sind die Signaturen dann sofort verfüpgbar!) oder sie können zwischen mehreren Rechnern einfach synchronisiert werden. Super cool. Vielen Dank Lars, du hast mir das Leben wirklich erleichtert!
Inzwischen gibt es eine verstärkte Diskussion, ob der Windows 2008 Server nicht die bessere Workstation ist. Sicher hängt das vom jeweiligen Einsatzgebiet ab. Bei mir war es so, dass ich für den Team Foundation Server einen Server als Betriebssystem auf meinem Demo-Rechner benötigt habe. Dabei wollte ich aber nicht auf den Komfort und das Design von Vista verzichten und auch Features wie z.B. den Sidebar nutzen. Die erste gute Nachricht ist, dass man Win2008 so anpassen kann, dass es sich optisch kaum von Vista unterscheiden lässt und die zweite gute Nachricht ist, dass es mit Hilfe eines kleinen Tools ganz einfach ist und schnell geht. Selbst Aero ist damit möglich. http://www.win2008workstation.com/wordpress/2008/07/17/windows-server-2008-workstation-converter/
Der artiso Workitem Manager ist ein Tool mit dem sich Workitems hierarchisch organisieren lassen. Diese hierarchische Organisation bietet verschiedene Vorteile. Neben einer besseren Strukturierung und einer erhöhten Übersichtlichkeit vor allem auch eine visuelle Traceability. Damit ist gemein, dass durch die Hierarchie sichtbar wird welche Tests und Implementierungsaufgaben einem Feature zugeordnet sind. Die ist z.B. sehr hilfreich, wenn sich das Feature ändert zu erkennen, welche Workitems auf mögliche Auswirkungen überprüft werden können. Wie hierarchische Workitems in Projekten hilfreich eingesetzt werden, habe ich zusammen mit Christian Binder in diesem MSDN-Webcast erörtert. Leider bringt der TFS in der Version 2008 diese Hierarchie nicht von Haus aus mit. Deshalb hat artiso den Workitem Manager entwickelt. Diesen gibt es nun auch als Open-Version. Die Open-Version ist kostenlos und wird auch als Source-Code bereitgestellt. Wie sich der Workitem Manager Open zur Vollversion unterscheidet kann man der unten stehenden Funktionsmatrix entnehmen. Weiter unten gibt's noch eine Screenshot. Den Donload für das Setup und den Source-Code findet man unter http://www.alm-tools.de. Gerne freue ich mich über euer Feedback zu dem Tool.  
Seit kurzem bin ich stolzer Besitzer einer Microsoft Wireless Notebook Presenter Mouse 8000 (was für ein Name!). Auf meinem Notebook hatte ich aber ständig das Problem dass die Meldung "HDI data has stopped working" kam. Zwar funktionierte das System ohne Probleme aber die Meldung war extrem nervig, vor allem bei Demos und Vorträgen. Nach einigem suchen bin ich dann auf einen Forum-Thread gestossen der genau für dieses Problem eine Lösung beschreibt. Der entscheidende Post hier nochmals kurz zitiert: I was having the same problem with my bluetooth microsoft wireless notebook presenter mouse 8000. The problem is created by an application in the HP quick launch Buttons. I removed the application from the launch buttons.
go to:
C:\Program Files\Hewlett-Packard\HP Quick Launch Buttons
move the following files to a backup folder on your computer:
HidActn.dll Hiddata PushHid.dll
Then restart you computer and you should not have the errors and the other things on your laptop should still be available. "HID data has stopped working" - bluetooth mouse error - Vista Hardware Devices
Microsoft Research hat vor kurzer Zeit PEX zum freien Download veröffentlicht. Hinter diesem unscheinbaren Kürzel verbirgt sich ein Tool das absolut genial und beeindruckend ist und klar mach, warum Microsoft ein research center unterhält. Das Tool verspricht nichts weniger als die automatische Generierung von Unit-Tests und den dazugehörigen Testcases um eine möglichst hohe Code-Abdeckung zu erzielen. Detaillierte Informationen bietet das Whitepaper, wer sich auf die schnelle einen Einblick verschaffen möchte findet einen Überblick im Folgenden (na ja, für einen Überblick ist der Post vielleicht doch ein wenig lang geraden, aber ich konnte mich nicht bremsen vor Begeisterung): Darf ich vorstellen - PEX Gegeben sei folgende Methode die getestet werden soll: 1: public string SimpleTest(int x1, int x2) 2: { 3: if (x1 > x2) 4: return "x1 > x2"; 5: if (x1 < x2) 6: return "x1 < x2"; 7: else 8: return "x1 == x2"; 9: }
Für das versierte Auge eines Entwicklers ist sofort klar, da brauchen wir 3 Testcases um eine vollständige Code-Abdeckung zu erzielen. Mal sehen, was PEX daraus macht. Zunächst mal muss PEX heruntergeladen und installiert sein. Dann kann man einfach einen "Parameterized Unit Test Stub" erzeugen. Dazu in der Methode rechts klicken und den Befehl aus dem Pex-Menü auswählen.
Im folgenden Dialog können Sie verschiedene Parameter angeben. Das wichtigste hier ist das Testprojekt in dem der Stub erzeugt werden soll.
Der erzeugte Stub sieht dann so aus:
1: /// <summary> 2: /// This class contains parameterized unit tests for Calculation 3: /// </summary> 4: [TestClass] 5: [PexClass(typeof(Calculation))] 6: public partial class CalculationTest 7: { 8: [PexMethod] 9: public string SimpleTest( 10: [PexAssumeUnderTest]Calculation target, 11: int x1, 12: int x2 13: ) 14: { 15: string result = target.SimpleTest(x1, x2); 16: return result; 17: // TODO: add assertions to method CalculationTest.SimpleTest(Calculation, Int32, Int32) 18: } 19: 20: }
Bei diesem Stub handelt es such um eine Vorlage für einen parameterisierten Unit-test. Toll, und was lässt sich damit nun machen? Wir können eine "Exploration" starten.
Diese Exploration versucht nun Input-Parameter zu finden die zu einer möglichst 100%igen Code-Abdeckung führen. Und hier beginnt nun die Magic von PEX. Ohne unser Zutun findet PEX 3 Kombinationen von Input-Parametern die tatsächlich eine komplette Code-Abdeckung erzielen- WOW! Dazu analysiert PEX wirklich den von uns erstellten Code und kann daraus definieren, mit welchen Input-Parametern die einzelnen noch nicht abgedeckten Zweige erreicht werden können.
Und das schönste, PEX baut uns automatisch 3 Unit-Tests die diese Testcases implementieren:
1: [TestMethod] 2: [PexGeneratedBy(typeof(CalculationTest))] 3: public void SimpleTest01() 4: { 5: string s; 6: Calculation calculation = new Calculation(); 7: s = this.SimpleTest(calculation, 1, 2); 8: Assert.AreEqual<string>("x1 < x2", s); 9: } 10: 11: [TestMethod] 12: [PexGeneratedBy(typeof(CalculationTest))] 13: public void SimpleTest02() 14: { 15: string s; 16: Calculation calculation = new Calculation(); 17: s = this.SimpleTest(calculation, 1879212556, 1879212556); 18: Assert.AreEqual<string>("x1 == x2", s); 19: } 20: 21: [TestMethod] 22: [PexGeneratedBy(typeof(CalculationTest))] 23: public void SimpleTest03() 24: { 25: string s; 26: Calculation calculation = new Calculation(); 27: s = this.SimpleTest(calculation, 256, 254); 28: Assert.AreEqual<string>("x1 > x2", s); 29: }
Diese Unit-Tests können wir nun starten und sehen, dass diese wie erwartet alle erfolgreich sind.
Damit haben wir einen Test automatisiert erstellt, der sicherstellen kann, dass diese Methode ihr Verhalten nach Außen für die aktuell definierten Test-Cases nicht verändert. Damit können ungewollte Änderungen an der Methode erkannt und beseitigt werden. Ob die Methode allerdings ihre Aufgabe korrekt erledigt, kann PEX natürlich nicht testen. Haben wir die Funktionsweise einer Methode allerdings einmal validiert, kann PEX nun sehr einfach dieses Verhalten prüfen. Und natürlich eignet es sich auch sehr gut um mögliche Test-Cases zu definieren. Es müssen in diesem Fall dann nur noch die einzelnen Ergebnisse je Test validiert werden.
Ändern wir die Methode ab, so dass sich ihr Verhalten ändert, dann alarmiert uns der entsprechende Test.
Soweit sogut - Und was geht sonst noch?
Wenn wir nun ein gewünschte Änderung der Funktionalität implementieren, wie kann PEX dann damit umgehen? Zunächst würden wir die vorhandenen Tests durchführen, damit wir sicher sind, dass die aktuelle Funktionalität noch korrekt läuft. Dann erweitern wir unsere Methode:
1: public string SimpleTest(int x1, int x2) 2: { 3: if (x1 > x2 * 2) 4: return "x1 > x2 * 2"; 5: if (x1 > x2) 6: return "x1 > x2"; 7: if (x1 < x2) 8: return "x1 < x2"; 9: else 10: return "x1 == x2"; 11: }
Zeile 3+4 haben wir neu hinzugefügt. Nun starten wir eine neue Exploration und PEX ermittelt einen weiteren Test-Case um diese Funktion ebenfalls abzudecken.
Schön - darf's noch ein bisschen mehr sein?
Dieses einfache Sample war ja schon sehr beeindruckend. Die Frage, die sich aber natürlich direkt stellt, ist wie weit geht denn das? Wir wollen nun den Schwierigkeitsgrad für PEX schrittweise steigern. Integer-Werte sind ja noch relativ einfach zu handhaben, aber wie sieht's denn beispielsweise mit Strings aus? Hierzu zunächst wieder eine Methode, die wir testen wollen:
1: public class StringOperations 2: { 3: public string CheckString(string Input) 4: { 5: if (Input.StartsWith("abc") && Input.Length > 10) 6: return Input + " Starts with 'abc' and length > 10"; 7: if (Input.StartsWith("abc")) 8: return Input + " Starts with 'abc'"; 9: if (Input.StartsWith("ABC")) 10: return Input + " Starts with 'ABC'"; 11: return "Unknown pattern"; 12: } 13: }
Stubs erzeugen und Exploration starten. Ob PEX wohl solche Operationen wie "StartsWith" und "Length" versteht?
Es findet tatsächlich alle erforderlichen Input-Parameter und sogar noch mehr! PEX stell fest, dass unsere Methode beim Übergeben einer NULL-Referenz eine Exception wirft. Und damit nicht genug, PEX kann uns auch einen Vorschlag machen, wie wir unseren Code verbessern können. Dazu im "Pex Exploration Results" - Fenster unter Views "Show suggestions window" aufrufen.
Durch einen Doppelklick auf den Eintrag am unteren Rand des Bereichs öffnet sich ein Fenster, das die vorgeschlagene Änderung direkt in unseren Code einfügen kann.
1: public string CheckString(string Input) 2: { 3: // <pex> 4: if (Input == (string)null) 5: throw new ArgumentNullException("Input"); 6: // </pex> 7: if (Input.StartsWith("abc") && Input.Length > 10) 8: return Input + " Starts with 'abc' and length > 10"; 9: if (Input.StartsWith("abc")) 10: return Input + " Starts with 'abc'"; 11: if (Input.StartsWith("ABC")) 12: return Input + " Starts with 'ABC'"; 13: return "Unknown pattern"; 14: }
Die Zeilen 3-6 wurden von PEX erzeugt. Natürlich können wir das entsprechende Verhalten im Code direkt ändern und an unsere Vorstellungen anpassen. Vielleicht ist es aber gar keine schlechte Idee, in diesem Fall eine Exception zu werden. Dies ist das Standard-Verhalten von PEX an dieser Stelle. Wird die erwartete Exception nicht mehr geworfen oder eine andere Exception tritt auf, wird dies durch einen fehlgeschlagenen Test angezeigt.
Der nächste bitte!
So nun wollen wir noch einen Schritt weitergehen und sehen, wie PEX mit Listen umgehen kann. Dazu habe ich folgende Testmethode erstellt (über den Sinn einer solchen Methode wollen wir jetzt nicht nachdenken)
1: int result = 0; 2: if (list.Count > 10) 3: { 4: foreach (int i in list) 5: result += i; 6: } 7: else 8: { 9: foreach (int i in list) 10: result *= i; 11: } 12: return result;
Die Methode bekommt eine Liste von Integer-Werten übergeben. Wenn es mehr als 10 Elemente sind, werden diese addiert, sonst werden die Werte miteinander Multipliziert. Mal sehen, wie PEX mit Listen umgeht.
PEX erkennt noch, dass unsere Methode mit Null-References nicht korrekt umgeht, aber dann verließen sie ihn. Aber freundlicherweise bekommen wir noch einen Hinweis "2 Object Creations". klickt man darauf, dann bekommt man schon mehr Informationen.
Aha, PEX kann also eine List<int> nicht erzeugen. Also was tun? Klickt man den unteren der beiden Einträge an, bietet PEX etwas weiter rechts die Möglichkeit eine Factory zu definieren. Eine Factory ist ein Extensibility-Point mit dem PEX beigebracht werden kann mit solchen Objekten umzugehen. Ein Beispiel für eine solche Factory kann so aussehen:
1: namespace System.Collections.Generic 2: { 3: [PexFactoryClass] 4: public partial class ListFactory 5: { 6: [PexFactoryMethod(typeof(List<int>))] 7: public static List<int> Create(int i) 8: { 9: if (i > 100) 10: i = 100; 11: List<int> l = new List<int>(); 12: for (int j = 0; j < i; j++) 13: { 14: l.Add(j * 10); 15: } 16: return l; 17: } 18: } 19: }
Hier teilt man PEX nun mit, welche Elemente es damit erzeugen kann (Zeile 6). Anschließend implementiert man eine Create-Methode die beliebige Parameter übernehmen kann. In Abhängigkeit dieser Parameter wird nun eine Instanz des gewünschten Objektes erzeugt. In unserem Beispiel übernehmen wir nur einen Parameter der die Länge der Liste angibt. In den Zeilen 9/10 begrenzen wir die Länge der Lsite auf 100 Elemente. Die Liste selbst befüllen wir mit einer Reihe von Zahlen. Hier ist es sicher keine gute Idee, z.B. Zufallszahlen zu verwenden, da diese ja bei jedem Testdurchlauf andere Werte liefern und deshalb der Assert nicht erfolgreich ausgeführt werden kann.
Mit hilfe dieser Factory kannPEX nun unsere Testcases definieren. Dazu ermittelt es einfach geeignete Parameter für die Create-Methode in unserer Factory statt das Objekt selbst zu erzeugen.
Die erzeugten Tests sehen dann so aus:
1: [TestMethod] 2: [PexGeneratedBy(typeof(ListCalculationTest))] 3: public void SumList03() 4: { 5: List<int> list; 6: int i; 7: list = ListFactory.Create(2); 8: ListCalculation listCalculation = new ListCalculation(); 9: i = this.SumList(listCalculation, list); 10: Assert.AreEqual<int>(0, i); 11: } 12: 13: [TestMethod] 14: [PexGeneratedBy(typeof(ListCalculationTest))] 15: public void SumList04() 16: { 17: List<int> list; 18: int i; 19: list = ListFactory.Create(536870912); 20: ListCalculation listCalculation = new ListCalculation(); 21: i = this.SumList(listCalculation, list); 22: Assert.AreEqual<int>(49500, i); 23: }
Nun folgt noch die Kür
Nach den Erfahrungen mit der Liste bereits etwas skeptischer geworden, wollen wir's jetzt aber doch wissen. Wie sieht's mit eigenen Objekten aus? Wie weit kommt PEX damit? Auch hier gibt es wieder eine einfache Methode die wir testen wollen:
1: public class ComplexDataCalculation 2: { 3: public string DoComplexDataCalculation(cData d) 4: { 5: if (!d.IsValid) 6: return "NotValid"; 7: 8: if (d.DataValues.x1 > d.DataValues.x2) 9: return "x1 > x2"; 10: if (d.DataValues.x1 < d.DataValues.x2) 11: return "x1 < x2"; 12: else 13: return "x1 == x2"; 14: 15: } 16: } 17: 18: public class cData 19: { 20: public cDataValues DataValues { get; set; } 21: public string ObsoleteParameter { get; set; } 22: public bool IsValid { get; set; } 23: } 24: 25: public class cDataValues 26: { 27: public int x1 { get; set; } 28: public int x2 { get; set; } 29: }
Die Methode bekommt einen komplexen Datentyp übergeben. Und was macht PEX???
PEX erkennt nicht nur, dass wir zwei Null-Exceptions abfangen sollten, sondern kann auch unser Datenobjekt so initialisieren, dass wir wieder eine 100% Codeabdeckung bekommen. Die Unit-Tests sehen ungefähr so aus:
1: [TestMethod] 2: [PexGeneratedBy(typeof(ComplexDataCalculationTest))] 3: public void DoComplexDataCalculation04() 4: { 5: cDataValues cDataValues; 6: cData cData; 7: string s; 8: cDataValues = new cDataValues(); 9: cDataValues.x1 = 3; 10: cDataValues.x2 = 4; 11: cData = new cData(); 12: cData.DataValues = cDataValues; 13: cData.ObsoleteParameter = ""; 14: cData.IsValid = true; 15: ComplexDataCalculation complexDataCalculation = new ComplexDataCalculation(); 16: s = this.DoComplexDataCalculation(complexDataCalculation, cData); 17: Assert.AreEqual<string>("x1 < x2", s); 18: }
Das ist schon extrem beeindruckend, wie PEX tatsächlich eine entsprechende Instanz unseres Datenobjektes erzeugt und initialisiert und das so, dass wir alle Testcases abdecken. Wirklich beeindruckend.
Was haltet ihr davon? Würde mich über Feedback freuen.
Die Integration des TFS mit Visual Studio ist eine richtig schöne Sache. Eine Stelle an der die Vorteile dieser Integration schön sichtbar werden ist die Erzeugung einen Workitems direkt aus einem Test-Result heraus. Wenn also z.B. ein Test fehlgeschlagen ist, kann daraus direkt ein Bug-Workitem erzeugt werden. Der Clou dabei ist, dass das Test-Result automatisch auf dem TFS veröffentlicht und als Attachment an das Workitem angehängt wird. Damit hat der Entwickler der den Bug beheben soll Zugriff auf den durchgeführten Test und die Results. Dies funktioniert mit allen Testarten sofern der Entwickler mit seiner Visual Studio Edition die entsprechenden Testarten ausführen kann. Somit kann der Entwickler z.B. bei einem Unit-Test diesen verwenden um den Testcase einfach zu debuggen. Und bei manuellen Tests stellt die Testspezifikation die Beschreibung der Repro-Steps dar. Auf jeden Fall ein Zeitgewinn. Man kann aber beispielsweise damit auch Aufgaben definieren, dass ein bestimmter Test noch mit zusätzlichen Test-Cases angereichert werden soll etc. Dazu geht man einfach mit der rechten Maustaste auf den entsprechenden Eintrag im Testresults-Fenster und wählt aus dem Kontext-Menü den entsprechenden Workitemtyp aus. Wird statt der Liste der Workitems "No Active Team Project" angezeigt, dann gibt es hier eine einfache Lösung, die aber nicht ganz intuitiv ist. 1.) Sicherstellen dass im Team-Explorer das gewünschte Projekt angezeigt wird in dem man das neue Workitem anlegen möchte. 2.) Dieses Team-Projekt aktiv markieren (das Projekt wird fett dargestellt) Nun werden die WorkitemTypen die im ProcessTemplate dieses Projektes definiert sind zur Auswahl angezeigt. Das Projekt selbst muss nicht unbedingt in der Quellcode-Verwaltung des TFS abgelegt sein.
Möchte man einen Event eines Controls per Code auslösen und somit alle registrierten Delegates aufrufen, wie es beim eigentlichen Auslösen des Events passiert, dann kann dieses Beispiel helfen. Jedes Control besitzt für seine einzelnen Events eine Methode den event auszulösen, z.B. OnClick(EventArgs e). Die Methoden sind allerdings private und können damit von außerhalb des Controls nicht so einfach aufgerufen werden. Bei einem einfachen Control wie z.B. einem Button geht das einfach durch Ableitung: 1: public partial class CustomButton : Button 2: { 3: public CustomButton() 4: { 5: InitializeComponent(); 6: } 7: 8: public void RaiseClickEvent(EventArgs e) 9: { 10: base.OnClick(e); 11: } 12: }
Etwas schwieriger wird es bei komplexeren Controls, z.B. einem MenuStrip. Möchte man den Click-Event eines ToolStripMenuItem aufrufen, müsste man ja das ToolStripMenuItem ableiten, was aber zur Folge hätte, dass das komplette MenuStrip-Control umbauen muss. Hier kann man mit Hilfe von Reflection eine bessere Lösung implementieren. Bei dieser Lösung wird nur das MenuStrip abgeleitet und um eine RaiseClickEvent-Methode erweitert der als Parameter ein ToolStripItem entgegen nimmt. Auf diesem wird dann per Reflection die Private-Methode OnClick aufgerufen.
1: public partial class CustomMenuStrip : MenuStrip 2: { 3: public CustomMenuStrip() 4: { 5: } 6: 7: public void RaiseClickEvent(ToolStripItem m) 8: { 9: Type t = m.GetType(); 10: object[] para = new object[1]; 11: para[0] = null; 12: t.GetMethod("OnClick", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(m, para); 13: } 14: }
SharePoint veröffentlicht die Dokumentbibliotheken per WebDAV, d.h. man kann über \\<Servername>\sites\<ProjektName> beispielsweise auf die Dokumente eines TFS-Projektes zugreifen. Das funktioniert standardmäßig aber nicht von einem Windows 2003 Server aus. Hier muss der WebDAV Client zuerst aktiviert werden. Hierzu muss der Dienst "WebClient" gestartet werden. 
Standardmäßig stellt Visual Studio die Prozessor-.Architektur für Anwendungen auf "Any CPU". Vor allem mit Bibliotheken gibt es damit allerdings öfters Probleme auf x64 Systemen, wenn die Bibliotheken unter 64Bit Betriebssystemen nicht sauber laufen. Eine Möglichkeit ist, die Anwendung explizit für x86 zu kompilieren, dann wird diese auch auf x64 Systemen im 32Bit Modus ausgeführt (siehe auch den Blogeintrag zur TFS-API unter x64).  Allerdings muss man hier mit ClickOnce ein wenig vorsichtig sein. Wenn man eine Anwendung zunächst mit "Any CPU" über ClickOnce verteilt hat und anschließend auf x86 umstellt, dann meldet ClickOnce „The deployment identity does not match the subscription” bzw. „Die Bereitstellungsidentität stimmt nicht mit dem Abonnement überein.“ Verschiedene Einträge im Internet verweisen zunächst darauf, dass das Zertifikat das Problem sein könnte. Da wir in der Zwischenzeit bei uns eine Domänen-Umstellung hatte, war diese Erklärung zunächst auch sehr plausibel. Dann stellte sich allerdings heraus, dass dieses Problem durch die Prozessor-Architektur verursacht wird. ClickOnce verwendet diese wohl als sog. "subscription identity" und behauptet einfach, dass es sich dabei um eine andere Anwendung handelt. Um das Problem zu umgehen sollte man also bereits beim ersten Demployment sehr gut überlegen, ob man die Anwendung nicht unter dem x86-Modus kompiliert.
Für eine ASP.Net Anwendung möchte ich gerne die Versionen meiner Anwendung und aller referenzierten Assemblies ausgeben. Bei Winforms kann ich für die Anwendung mit Application.ProductVersion die Version meiner Anwendung abfragen, die ich in der AssemblyInfo.cs eingestellt habe. Das geht bei ASP.Net nicht. Hier die Lösung, wie man das im Web macht, gleich mit Sortierung: Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
if (assembly != null)
{
lblProductVersion.Text = assembly.GetName().Name + " - " + assembly.GetName().Version.ToString();
var referenceAssemblies = from a in assembly.GetReferencedAssemblies()
orderby a.Name
select a;
foreach (AssemblyName referenceAssemblyName in referenceAssemblies)
{
lblProductVersion.Text += "<br>" + referenceAssemblyName.Name + " - " + referenceAssemblyName.Version;
}
}
Die Ausgabe sieht dann ungefähr so aus:
MyApplication - 1.0.3058.30144 AjaxControlToolkit - 1.0.10618.0 ArtisoAssertLib - 1.0.0.0 CommonComponents - 1.0.3056.28557 CommonContracts - 1.0.3056.28555 CrystalDecisions.CrystalReports.Engine - 11.5.3700.0 CrystalDecisions.ReportSource - 11.5.3700.0 CrystalDecisions.Shared - 11.5.3700.0 cTextBox - 1.0.3058.27781 DataContracts - 1.0.0.0 Infragistics35.WebUI.Misc.v8.1 - 8.1.20081.1000 Infragistics35.WebUI.Shared.v8.1 - 8.1.20081.1000 Infragistics35.WebUI.UltraWebChart.v8.1 - 8.1.20081.1000 Infragistics35.WebUI.UltraWebGrid.v8.1 - 8.1.20081.1000 Infragistics35.WebUI.UltraWebNavigator.v8.1 - 8.1.20081.1000 Infragistics35.WebUI.UltraWebTab.v8.1 - 8.1.20081.1000 Infragistics35.WebUI.UltraWebToolbar.v8.1 - 8.1.20081.1000 Infragistics35.WebUI.WebDataInput.v8.1 - 8.1.20081.1000 Infragistics35.WebUI.WebDateChooser.v8.1 - 8.1.20081.1000 ListValuesComponents - 1.0.3057.30147 ListValuesContracts - 1.0.3057.30146 LoginManagerComponents - 1.0.3033.29632 LoginManagerContracts - 1.0.0.0 mscorlib - 2.0.0.0 NavigationComponents - 1.0.3058.27781 NavigationContracts - 1.0.3058.27780 PCMAreaComponents - 1.0.3058.27781 PCMAreaContracts - 1.0.3058.27779 ProductsAreaComponents - 1.0.3058.27779 ProjectsAreaComponents - 1.0.3058.27780 ProjectsAreaContracts - 1.0.3058.27778 ReportingComponents - 1.0.3058.27781 ReportingContracts - 1.0.3058.27780 SearchComponents - 1.0.3058.27779 SearchContracts - 1.0.3058.27779 System - 2.0.0.0 System.Configuration - 2.0.0.0 System.Core - 3.5.0.0 System.Data - 2.0.0.0 System.Data.DataSetExtensions - 3.5.0.0 System.Data.Linq - 3.5.0.0 System.Drawing - 2.0.0.0 System.Web - 2.0.0.0 System.Web.Extensions - 3.5.0.0 System.Web.Services - 2.0.0.0 System.Xml - 2.0.0.0 TaskListComponent - 1.0.3056.28564 TaskListContract - 1.0.3056.28560 TypesComponents - 1.0.3057.30147 TypesContracts - 1.0.3057.30147 UserManagementContracts - 1.0.0.0 Validators - 1.0.0.0 wwDataBinder - 1.0.2908.21817
Ich wollte aus Excel auf einen Webservice zugreifen. Also kurz gegoogelt (oder gelived ), eigentlich gar nicht so schwer... Aber wie so oft liegt der Teufel im Detail und es waren doch ein paar Kleinigkeiten zu beachten, deshalb hier nochmals der komplette Lösungsweg: Zunächst habe ich einen WebService erstellt, zum Testen was ganz triviales, eigentlich das Webservice Template nur noch um den Parameter Name erweitert: using System;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
public class Service : System.Web.Services.WebService
{
public Service ()
{
}
[WebMethod]
public string HelloWorld(string Name) {
return "Hello World " + Name;
}
}
Die URL um auf den Webservice mit ausgefülltem Parameter zuzugreifen lautet http://localhost:49408/WebSite2/Service.asmx/HelloWorld?Name=Test, jedoch funktioniert das standardmäßig noch nicht. Wir müssen erst noch in der web.config folgenden Eintrag hinzufügen:
<webServices>
<protocols>
<add name="HttpGet"/>
<add name="HttpPost"/>
</protocols>
</webServices>
Nun bekommen wir mit diesem Aufruf den XML-Response des Webservices direkt zurückgegeben. Nun wollen wir den Webservice aus VBA aufrufen.
Sub CallWebService()
Dim MSXML As New MSXML2.DOMDocument
Dim strAnfrage As String
strAnfrage = "http://localhost:49408/WebSite2/Service.asmx/HelloWorld?Name=Test"
With MSXML
.async = False
.preserveWhiteSpace = False
.validateOnParse = True
.resolveExternals = False
End With
If MSXML.Load(strAnfrage) = True Then
Response = MSXML.DocumentElement.Text
Else
Response = "Fehler"
End If
End Sub
Damit das funktioniert müssen wir noch die Bibliothek "Microsoft XML, vx.0" einfügen. Wir werten im Moment das XML-Dokument sehr einfach aus, da wir davon ausgehen, dass der Rückgabewert einfach als Textim XML-Dokument zurückgegeben wird. Damit können wir einfach mit MSXML.DocumentElement.Text den gewünschten Wert auslesen. Natürlich können auf diese Weise auch komplexer Rückgabewerte ausgewertet werden, aber das wollen wir hier nicht näher beleuchten.
Verwendet man ein DataSet mit DateTime-Werten in unterschiedlichen Zeitzonen, wird man zu seiner Überraschung feststellen, dass das DataSet eine Umrechnung der Zeiten vornimmt. Bei uns war das Problem konkret, dass ein DataSet per WCF zu einem Service übertragen wurde. Der Service war aber in einer anderen Zeitzone als der Client. In einem DateTime-Feld wurde ein Datumswert abgelegt, also z.B. 08.05.2008. Die automtische Umrechnung hat davon jedoch eine Stunde abgezogen, so dass am Service 07.05.2008 23:00 ankam. Damit war das Datum immer um einen Tag verschoben. Das Problem kann aber behoben werden, indem man auf dem Client und auf dem Service den DateTime-Wert vor bzw. nach der Serialisierung konvertiert. Auf dem Client sieht das dann z.B. so aus: 1: foreach (DataSet1.DataTable1Row dr in ds.DataTable1.Rows) 2: { 3: dr.Date = dr.Date.ToLocalTime(); 4: }
Und dann auf dem Service das Gegenstück:
1: foreach (DataSet1.DataTable1Row dr in ds.DataTable1.Rows) 2: { 3: dr.Date = dr.Date.ToUniversalTime(); 4: }
Damit kommt genau der Wert, der im Client eingetragen wurde auch im Service an. Zwar gibt es wohl auch eine Möglichkeit, die Datumskonvertierung zu unterdrücken, aber das hat bei mir nicht sauber funktioniert. Wenn also jemand eine elegantere Lösung kenn, nur her damit 
Update:
Wir habe doch noch eine elegantere Lösung gefunden. Auf dem DataSet kann man auf der DateTime Column das Property DateTimeMode auf Unspecified umstellen. Dann wird die Zeitzonen-Konvertierung nicht durchgeführt. Danke an Luke für den Tipp.

Oft hat man einen bestimmten Ordner, auf den man of zugreifen muss. Hier ist mir nun ein Feature von Vista aufgefallen (gab es wohl auch schon in früheren Windows-Versionen, aber da habe ich das gar nicht bemerkt). Man kann Toolbars einrichten und diese einfach oben, rechts oder links am Desktop andocken. Mit der AutoHide-Funktion benötigen die Toolbars auch keinen unnötigen Platz. Ein nettes Feature, das ich bei meinen Vorträgen gut gebrauchen kann. Das Video zeigt, wie's gemacht wird.
Gerade hatte ich eine vermeintlich Fragestellung. ich wollte einfach nur wissen, wieviel Platz auf einem Laufwerk im Netzwerk noch frei ist. Das hat sich aber als problematischer herausgestellt als gedacht, da ich nur eine Freigabe auf dem Laufwerk hatte. Das ist wohl gar nicht so einfach, damit herauszufinden, wieviel Platz auf der entsprechenden Platte noch frei ist. Erst über die gute alte Command-Shell und den dir-Befehl hat das geklappt. Damit wird der freie Plattenplatz problemlos angezeigt. Na ja, es scheint manchmal wirklich so, dass früher einiges besser war  
Möchte man aus Anwendungen wie z.B. Word auf Dokumente auf einem SharePoint Portal zugreifen. kann man dies sowohl über einen UNC-Pfad (\\ServerName\sites\SiteName) oder über eine URL (http://ServerName/sites/SiteName) zugrifen. Nöch schöner wäre allerdings, wenn man eine direkte Verknüpfung hätte, die auch im Browse-Fenster der Anwendungen genutzt werden kann. Wie man eine solche Verknüpfung einrichtet, zeige ich im folgenden Video:
Über das PropertyGrid-Control habe ich ja mehrmals gebloggt (z.B. hier). Dieses Control verwendie ich recht häufig um z.B. den Inhalt eigene Konfigurations-Klassen zu editieren. Zusammen mit der XML-Serialisierung lassen sich so sehr flexible Konfigurationsmöglichkeiten schaffen. Nun hatte ich die Anforderung eine dynamische Datenstruktur an ein PropertyGrid zu binden. Da stand ich zunächst vor einem Problem. Bisher habe ich nur Objekte mit Properties unterschiedlicher Typen an das PropertyGrid gebunden. Nun habe ich eine Liste von Objekten, die die Elemente im PropertyGrid beschreiben. Wie aber diese an das PropertyGrid binden? Das schöne ist, das das PropertyGrid sich hier als sehr flexibel erweist. Man muss folgende Schritte durchführen: - Man brauch eine Klasse für ein einzelnes Property
- Dann brauchen wir eine Collection für diese Properties. Diese leiten wir von CollectionBase und ICustomTypeDescriptor ab und implementieren die Interfaces. Entscheidend ist hier die Methode GetProperties. Hier werden nun ine PropertyDescriptionCollection aus unseren Properties aufgebaut. Diese Methode ruft das PropertyGrid auf um sich dieProperties zu besorgen, die es rendern soll. Hier können wir nun also von einer belibigen Datenstruktur die benötigten
Informationen für das PropertyGrid aufbauen. - Dafür brauch wir jetzt noch einen cCustomPropertyDescriptor. Diesen leiten wir von PropertyDescriptor ab und implementieren es.
- Nun können wir unsere Properties aufbauen und an das PropertyGrid binden. Ich habe das CustomPropertyGrid als eigenes Control angelegt. Der Code ist nun sehr simpel:
1: private void Form1_Load(object sender, EventArgs e) 2: { 3: cPropertyCollection props = new cPropertyCollection(); 4: props.Add(new cPropertyItem("BoolValue", "This is a boolean value", false, true, "Properties")); 5: props.Add(new cPropertyItem("StringValue", "This is a string value", false, "Test123", "Properties")); 6: props.Add(new cPropertyItem("Folder", "Path for folder", false, "", "Path")); 7: 8: this.artisoPropertyGrid1.SelectedObject = props; 9: }
Damit erhält man folgendes Ergebnis. Man sieht die dynamisch angelegten Properties mit ihrem Name, in die Kategorien untergliedert und mit der Beschreibung. Das PropertyGrid wählt automatisch die gewohnten Controls abhängig vom Datentyp aus.
Dis ist schon ganz nett. Ich möchte aber für das Folder-Property einen entsprechenden Editor angeben können. Bei statischen Klassen vrwendet man einfach Attribute, aber bei dynamischen? Dazu wird die verfügbare Dokumentation sehr, sehr dünn. Hierzu haben wir auf der Property-Klasse eine Attribute-Arary. Diese Attribute können wir nun in der GetProperties-Klasse an den cCostomPropertyDescriptor übergeben. Der Aufbau der Properties sieht dann so aus:
1: private void Form1_Load(object sender, EventArgs e) 2: { 3: cPropertyCollection props = new cPropertyCollection(); 4: props.Add(new cPropertyItem("BoolValue", "This is a boolean value", false, true, "Properties")); 5: props.Add(new cPropertyItem("StringValue", "This is a string value", false, "Test123", "Properties")); 6: props.Add(new cPropertyItem("Folder", "Path for folder", false, "", "Path", 7: new TypeConverterAttribute(), 8: new EditorAttribute(typeof(System.Windows.Forms.Design.FolderNameEditor), typeof(System.Drawing.Design.UITypeEditor)))); 9: 10: this.artisoPropertyGrid1.SelectedObject = props; 11: }
Nun kann man im Feld für den Wert für das Property "Folder" auf einen Button klicken und erhält einen Dialog zur Auswahl eines Verzeichnisses.
Weitgehend unbemerkt von der breiten Masse der Entwickler bringt das .net Framework 3.0 auch eine Bibliothek zum Erstellen von automatisierten UI-Tests mit. Unter %PROGRAMFILES%\Reference Assemblies\Microsoft\Framework\v3.0 finden sich folgen sich die benötigten DLLs. Damit kann man UI-Tests selbst programmieren und auch in Unit-Tests integrieren. Das entspricht zwar nicht der weit verbreiteten Erwartungshaltung, die eher von einer "Record & Play" Methode ausgehen, bietet aber verschiedene Vorteile in Bezug auf Wartbarkeit und Stabilität. Ich habe hier mal ein kleines Beispiel gebaut, das den Einsatz demonstriert: Ich werde in Kürze weitere Details bloggen und verschiedene Einsatzgebiete, Erweiterungen und auch Grenzen aufzeigen. Auch ein Webcast ist zu dem Thema geplant. Bis dahin erst mal ein paar weiterführende Dokumente. Leider ist da im Moment noch nicht sehr viel publiziert worden. http://msdn2.microsoft.com/en-us/magazine/cc163288.aspx http://msdn2.microsoft.com/en-us/accessibility/bb892133.aspx Das Tool ist auf jeden Fall interessant und da es kostenlos mit dem .net Framework mitkommt, sollte jeder der sich mit Software-Tests beschäftigt mal einen Blick drauf werfen.
Ich war am 13.02 und 14.02 auf der VSOne und habe dort insgesamt 4 Sessions gehalten. Für mich war es eine tolle Veranstaltung, die Resonanz auf meine Vorträge war sehr positiv und ich konnte viele interessante Diskussionen mit Teilnehmern und Sprecherkollegen führen. Hier nun meine Slides und den Code zum Download: Session Requirement Management mit Team Foundation Server Bei dieser Session habe ich vorgestellt, wie wir bei uns im Hause mit Hilfe des Team Foundation Servers Requirements verwalten. Hierbei kommt auch der artiso Workitem-Manager zum Einsatz um Workitems hierarchisch zu strukturieren. Spezifikationsdokumente Verwalten Spezifikationsdokumente werden oft als monolithische Worddokumente verwaltet Daraus ergeben sich eine Reihe von Problemen, die wir mit unserem Ansatz lösen, die Spezifikation in kleine Dokumente aufzuteilen und so jede einzelne Funktion zu Spezifizieren. Hierzu nutzen wir ein Word-AddIn das Bestandteil des artiso Workitem-Managers ist. Mit 3 Schichten zum Erfolg Das war der Titel meiner Architektur-Session. Hier habe ich zunächst den Aufbau und die Vorteile einer 3-Schicht-Architektur beschrieben. Anschließend erläuterte ich Komponentenorientierung. An einem kleinen Demo-Projekt zeigte ich die Planung und den Aufbau eines Projektes vom Architekturdesign bis zur Implementierung und demonstrierte dabei einige Best Practices aus unseren Projekten. Am Ende beschrieb ich noch die Auswirkungen für verteilte Systeme. Interessant für mich war, dass der Meister der Komponentenorientierung Ralf Westphal der Session beiwohnte uund den Vortrag mit interessanten Fragen bereicherte. XML-Serialisierung zur Persistierung von Objekten XML-Serialisierung ist eine Technologie, mit der Objekte schnell und einfach in ein XML-Format überführt und auch wieder zurück konvertiert werden kann. Dies Technik ist nicht neu und viele Entwickler kennen und nutzen sie. Ich habe in der Session verschiedene Möglichkeiten aufgezeigt, wie sich XML-Serialisierung nutzen lässt, von der Persistierung von kompletten Datenobjekten über Konfigurationsdateien bis hin zur Optimierung der Speicherung von Listenobjekten in der Datenbank. Nächste Woche bin ich dann auf dem Launch Event als ATE. Das wird sicher interessant bei diesem Mega-Event (6.500 Teilnehmer!).
Unter Access 2003 gabe es Access-Datenbankprojekte (*.adp) mit denen man wunderbar auf bestehende SQL-Datenbanken zugreifen konnten. Sind diese mit Access 2007 verloren gegangen? Das nicht, aber sie sind jetzt gut versteckt. Um ein Datenbankprojekt mit Access 2007 anzulegen geht man wie folgt vor: - Access ganz normal starten
- Access fragt nun nach einer Vorlage für eine neue Datei. Hier das Icon "Blank Database" auswählen.
- Im rechten Bereich kann man nun den Dateiname angeben. Hier den File-Dialog öffnen.
- In diesem Dialog kann man nun als Dateityp "Microsoft Office Access Projects" auswählen.
- Wenn mann dann auf den "Create"-Button klickt kann man noch auswählen, ob man sich zu einer bestehenden Datenbank verbinden möchte oder eine neue anlegen will. Der Rest dürfte dann wieder vertraut erscheinen.
Schick ist es, wenn man in der Anwendung demBenutzer die verfügbaren SQL-Server als Auswahl anzeigt. Die kann man mit folgendem Code bewerkstelligen: using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace SQLInstances
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
DataTable dataTable = System.Data.Sql.SqlDataSourceEnumerator.Instance.GetDataSources();
foreach (DataRow datarow in dataTable.Rows)
{
string datasource = datarow["ServerName"].ToString();
if (datarow["InstanceName"] != DBNull.Value)
{
datasource += String.Format("\\{0}", datarow["InstanceName"]);
ComboBox1.Items.Add(datasource);
}
DataGridView1.DataSource = dataTable;
}
}
}
}
Das Beispiel gibt die verfügbaren SQL-Server in einem GridVie aus.
Um auch Zuisatzinformationen wie Version etc. zu erhalten, muss auf dem SQL-Server der SQL-Browser laufen:

Durch Zufall habe ich gerade herausgefunden, dass man im IE auch mehrere Startseiten eintragen kann. Einfach in der Liste mehrere zeilen mit den verschiedenen URLs eingeben. Die werden dann beim Start alle geöffnet. 
Das Problem:
Ich habe in C# ein Objekt vom Typ System.Drawing.Color. Dieses möchte ich in einen String-Konvertieren, den ich im HTML verwenden kann. Das funktioniert gut bei benannten Farben wie z.B. "red". Dort kann man Color.Name verwenden. Aber bei nicht benannten Farben liefert dies einen ARGB-Wert mit dem HTML nichts anfangen kann. Dehalb habe ich diese kleine Mthode geschrieben:
private string GetColorString(Color color)
{
if (color.ToKnownColor() != 0)
return color.Name;
else
return "#" + color.Name.Substring(2);
}
Wenn jemand eine einfachere Lösung weiss, einfach her damit!
Update:
Jürgen hat noch eine viel einfacher Lösung (siehe Kommentar) mit string htmlcolor = ColorTranslator.ToHtml(Color.Bisque) Kannte ich bisher noch nicht, aber man lernt ja nie aus!
Damit Ich habe ein Web User Control bei dem ich eine TextBox als Property nach außen geben möchte, damit man die Eigenschaften der Textbox editieren kann. Das kann man durch ein paar Attribute erreichen. [Browsable(true),
NotifyParentProperty(true),
PersistenceMode(PersistenceMode.InnerProperty),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
RefreshProperties(RefreshProperties.Repaint)]
public TextBox DropDownTextbox
{
get
{
return this.TextBox1;
}
set
{
this.TextBox1 = value;
}
}
Damit werden auf dem UserControls zusätzliche Properties angelegt und zwar alle Properties einer Textbox mit dem Prefix "DropDownTexbox" also dem Namen des Properties auf dem Control.
Im Markup werden die Properties ganz normal als Attribute eingefügt. Was mich allerdings etwas gewundert hat war, dass ich im Markup über Intellisense die Textbox allerdings auch als separates Tag angeben kann und darauf meine Attribute definiere. Allerding wird das denn im Designer nicht mehr sauber gerendert.
<uc1:WebUserControl1 ID="WebUserControl11" runat="server" DropDownTextBox-BackColor="#FFCC00"> <DropDownTextbox BackColor="red"></DropDownTextbox> </uc1:WebUserControl1>
Ich hatte gerade ein Aha-Erlebnis der etwas anderen Art. Im Infragistics-Chart gibt es eine Zoom-Funktion für die Achsen. Bei einem Composite-Chart scheint das aber nicht zu funktionieren. Ich habe folgendes im Code stehen: ChartObj.CompositeChart.ChartAreas[0].Axes[0]. Aber mein Intellisense zeigt kein passendes Property an. Im Forum steht auch die Aussage von einem Infragistics-Mitarbeiter, dass das nicht geht. Wenn ich dann "blind" das vervollständige zu ChartObj.CompositeChart.ChartAreas[0].Axes[0].ScrollScale.Visible = true;
funktioniert der Code wunderbar! Mal abgesehen davon, dass ich gar nicht wüsste, wie man Properties im Intellisense versteckt aber dennoch verfügbar macht, warum um alles in der Welt haben die Jungs dieses Property nicht öffentlich zugänglich gemacht?
Wenn man eine Datei löscht, die in der Quellcode-Verwaltung eingecheckt war, kann man diese leicht aus der Quellcode-Verwaltung wiederherstellen. Dazu muss man aber zuerst eine kleine Einstellung vornehmen. Unter Tools / Options / Source Control / Visual Studio Team Foundation Server kann man die Option "Show deleted items in the Source Control Explorer" aktivieren. Dann werden im Source Control Explorer gelöschte Elemente angezeugt, die man einfach "undeleten" kann. 
Ich hatte gerade ein kleines Problem mit der Object Test Bench. Die Object Test Bench ermöglicht es, im Visual Studio Instanzen von Objekten zu erzeugen, auf denen man dann direkt Methoden aufrufen kann, ohne die Anwendung zu starten. Dazu geht man in den Class View oder den Class Designer, klickt die entsprechende Klasse mit der rechten Maustaste an und ruft dann aus dem Kontext-Menü den Befehl "Create Instance" auf ... Tja, wenn der Befehl denn da wäre. Bei mir war da nichts zu sehen. Nach einigem Versuchen habe ich dann rausgefunden, dass dies wohl nur auf dem Projekt funktioniert, das als Startprojekt ausgewählt wurde. Also habe ich meine Library als Startprojekt gekennzeichnet und siehe da, es funktioniert. Schade ist allerdings, dass Methoden, die generische Datentypen zurückliefern nicht von der OTB unterstützt werden. Weiterführende Informationen findet man hier: http://blogs.msdn.com/parthopdas/archive/2005/05/04/414704.aspx
Ich habe gerade eben eine Seite, die ich mit VS 2008 und dem Framework 3.5 erstellt habe, auf einen Test-Server deployed. Alles hat wunderbar funktioniert, aber bei einer Seite bekam ich die Meldung, dass die Ressource nicht gefunden werden kann. Das konnte aber nicht sein, die Seite war da. Nach einigen Versuchen und einigem Wundern bin ich zufällig auf die Idee gekommen, mir mal den Source der Seite anzuzeigen. Und siehe da, hier steht nun endlich ein brauchbarar Hinweis. Im Source-Code war als Kommentar die Fehlermeldung versteckt. Der Grund war, dass eine Komponente auf ein verzeichnis zugreifen möchte, das auf dem Server nicht existiert. Ich habe versucht, den Fehler mit einer Test-Seite zu reproduzieren, aber erfolglos. Hat jemand eine Idee, woran das liegt und wie man das abstellen kann? Na jedenfalls wenn mal jemand ein ähnliches Problem hat, kennt er jetzt den Workaround. Nicht schön, aber es funktioniert. <html> <head> <title>Die Ressource kann nicht gefunden werden.</title> <style> ... </style> </head> <body bgcolor="white"> <span><H1>Serverfehler in der Anwendung /ValuePlanner_2008.<hr width=100% size=1 color=silver></H1> <h2> <i>Die Ressource kann nicht gefunden werden.</i> </h2></span> ... </body> </html> <!-- [DirectoryNotFoundException]: Ein Teil des Pfades c:\temp\text.txt konnte nicht gefunden werden. bei System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) bei System.IO.File.Delete(String path) ... --><!-- Diese Seite enthält möglicherweise vertrauliche Informationen, weil ASP.NET für die Anzeige ausführlicher Fehlermeldungen mit <customErrors mode="Off"/> konfiguriert ist. In Produktionsumgebungen sollten Sie <customErrors mode="On"/> oder <customErrors mode="RemoteOnly"/> verwenden.-->
Folgende Aufgabenstellung: In einem Infragistics WinGrid soll eine DataTable angezeigt werden. Am Ende des Grids soll eine TemplateAddRow, also eine leere Zeile zum Anlegen einer neuen Row angezeigt werden. Das Infragistics WinGrid unterstützt diese Funktion von Haus aus so, dass man in diese leere Zeile klicken kann und sobald man beginnt hier Werte einzutragen, dann wird in der DataSource tatsächlich eine neue Row angelegt und in dieser die Werte abgelegt. Gleichzeitig wird eine neue TemplateAddRow angelegt, mit der man wieder eine neue Zeile anlegen kann. Was aber nun, wenn man die Werte nicht im Grid selbst eingeben möchte, sondern in separaten Eingabefeldern? Dann geht man folgendermaßen vor: 1.) Zuerst erstellt man eine Form mit einer Bindingsource, einem Grid und diversen Eingabefeldern. Das Grid und die Eingabefelder werden an die BindingSource gebunden. 2.) Dann stellt man das Grid so ein, dass die TemplateAddRow angezeigt wird. this .ultraGrid1.DisplayLayout.Override.AllowAddNew = Infragistics.Win.UltraWinGrid.AllowAddNew.TemplateOnBottom; 2.) Damit nach dem neu Anlegen einer Zeile das Databinding auf den Eingabefeldern funktioniert muss nun manuell eine neue Zeile angelegt werden. Das Grid erzeugt diese Row in der DataSource erst wenn Werte eingegeben werden, das ist für unseren Fall zu spät. Deshalb nutzen wir das BeforeRowIndert Event. private void ultraGrid1_BeforeRowInsert(object sender, Infragistics.Win.UltraWinGrid.BeforeRowInsertEventArgs e) { this.myBindingSource.AddNew(); this.myBindingSource.Position++; e.Cancel = true; } 3.) Jetzt funktioniert bereits das erste Einfügen. Allerdings erscheint danach keine neue TemplateAddRow mehr. Hier muss man einen kleinen Trick anwenden: Man setzt die ActiveRow auf die TemplateAddRow. Dadurch wird eigentlich die TemplateAddRow nun in eine "echte Row" umgewandelt (was wir bereits ja zuvor manuell gemacht haben. Danach erstellt das Grid eine neue TemplateAddRow (das ist genau das was wir brauchen). Etwas unschön ist nun, dass die neue TemplateAddRow automatisch selektiert wird und wir damit schon wieder einen neuen Datensatz anlegen. Schöner wäre, wenn der gerade erzeugte Datensatz aktiv bleibt. Dazu merkt man sich einfach die aktive Row und setzt die danach wieder. Das Ganze habe ich auf einen Button gelegt, mit dem der Anwender die Eingabe abschließt. Alternativ kann natürlich auch ein anderes Event dafür verwendet werden. Sinnvoll ist hier sicher auch das BeforeSelectChange-Event des Grids entsprechend einzubinden. private void button1_Click(object sender, EventArgs e) { this.myBindingSource.EndEdit(); UltraGridRow temp = this.ultraGrid1.ActiveRow; this.ultraGrid1.ActiveRow = this.ultraGrid1.Rows.TemplateAddRow; this.ultraGrid1.ActiveRow = temp; }
Ist eine ASP.Net Seite scrollbar, gibt es den unschönen Effekt, dass die Seite nach dem Postback wieder ganz oben steht. Ich habe mal eine kleine Beispiel-Seite, die diesen Effekt demonstriert:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="ValuePlanner_2008.Web.INN_ProjectArea.WebForm2" %> <%@ Register Assembly="cTextBox" Namespace="artiso_lib.UserControls" TagPrefix="cc1" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>Untitled Page </title> </head> <body> <form id="form1" runat="server"> <div> <% 1: for (int i = 0; i < 50; i++) 2: { 3: Response.Write(i + "<br>"); 4: } 5: %> </div> </form> </body> </html>
Dieses Problem kann man ganz einfach umgehen, indem man in der Page-Direktive das Attribut MaintainScrollPositionOnPostback="true" einfügt, also:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="ValuePlanner_2008.Web.INN_ProjectArea.WebForm2" MaintainScrollPositionOnPostback="true" %>
Im ASP.Net AJAX Toolkit findet man ein ModalPopup, das sehr gute Dienste leistet, will man Meldungen etwas eleganter ausgeben als mit einem schlichten alert(). Die Handhabung ist relativ einfach. Man nehme ein beliebiges Serverseitiges Control und weise diesem den ModalPopupExtender zu. Dann gibt man noch mindestens die ID des Controls an, das den Inhalt des Popups repräsentieren soll und fertig. Will man das Popup allerdings client-seitig per Java-Script aufrufen, muss man ein wenig in die Trickkiste greifen. Das Problem dabei ist, dass der ModalPopupExtender immer ein TargetControl braucht. Ich habe dafür einfach ein unsichtbares DIV erzeugt, das mit runat="server" auch auf dem Server sichtbar gemacht wurde. Dann kann man mit $Find('<ID des ModalPopupExtenders>').show() das Popup aufrufen.
Neu war an diesem Beispiel für mich auch der Style filter: alpha(opacity = 70) mit dem man eine Transparenz erzeugen kann.
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm1.aspx.cs" Inherits="ValuePlanner_2008.Web.INN_ProjectArea.WebForm1" %>
<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Untitled Page</title>
<style type="text/css">
.modalBackground
{
filter: alpha(opacity = 70);
background-color: #CCCCCC;
}
.ErrorMessage
{
background-color: #800000;
color: #FFFFFF;
}
</style>
</head>
<script language="javascript" type="text/javascript"> 1: 2: function okScript() 3: { 4: alert ("OK-Button wurde gedrückt"); 5: } </script>
<body>
<form id="form1" runat="server">
<div id="PopupPseudoPanel" style="display: none" runat="server">
</div>
<cc1:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
</cc1:ToolkitScriptManager>
<cc1:ModalPopupExtender ID="ModalPopupExtender" runat="server" PopupControlID="Panel1"
DynamicServicePath="" Enabled="True" TargetControlID="PopupPseudoPanel" BackgroundCssClass="modalBackground"
OkControlID="OkButton" CancelControlID="CancelButton" OnOkScript="okScript();">
</cc1:ModalPopupExtender>
<br />
<br />
<input id="Button2" type="button" value="button" onclick="$find('ModalPopupExtender').show();" />
<asp:Panel ID="Panel1" runat="server" CssClass="ErrorMessage" style="display:none">
<h1>
Popup</h1>
<p>
Das ist ein Popup!</p>
<center>
<asp:Button ID="OkButton" runat="server" Text="OK"></asp:Button>
<asp:Button ID="CancelButton" runat="server" Text="Cancel"></asp:Button>
</center>
</asp:Panel>
</form>
</body>
</html>
Will man in einem Infragistics WebGrid ein Control in einer Zelle einer TemplatedColumn ansprechen, dann ist das gar nicht so einfach wie zunächst vermutet. Man muss ein paar Umwege einlegen. So funktionierts aber dann: protected void UltraWebGrid1_InitializeRow(object sender, Infragistics.WebUI.UltraWebGrid.RowEventArgs e) { DropDownList ddlUnit = ((DropDownList)((CellItem)((TemplatedColumn)e.Row.Cells.FromKey("Unit").Column).CellItems[e.Row.Index]).Controls[1]); .... } Statt dem Controls[1] könnte man auch noch ein FindControl() nutzen, das wäre noch etwas eleganter.
Gerade hatte ich mal mit den Settings für Winform-Anwendungen etwas rumgespielt und bin dabei darauf gestoßen dass man auch für Klassenbibliotheken eine Settings-Datei anlegen und darin Werte erfassen kann. Die Frage war nun, wie kann ich diese Werte einstellen, wenn ich die Klassenbibliothek in eine Anwendung integriere? Nach einigem Googeln und Testen hier nun meine Ergebnisse:
- Die Konfiguration der Anwendung inkl. aller Klassenbibliotheken werden über eine zentrale konfigurationsdatei (app.config bzw. <Anwendungsname>.exe.config) vorgenommen.
- Die Konfigurationsparameter der Klassenbibliothek werden in diese zentrale Konfigurationsdatei leider nicht automatisch übertragen.
- Werden keine Konfigurationsparameter für die Klassenbibliothek angegeben, behält diese ihre Default-Werte (das sind die, die beim Kompilieren in der app.config der Klassenbibliothek standen). Diese Default-Werte sind in der Settings.Designer.cs hinterlegt und werden in die DLL reinkompiliert.
- Man kann diese Default-Werte übersteuern, indem man in der app.config der Anwendung einen zusätzlichen Settings-Block einfügt (im unteren Beispiel der Bereich ClassLibrary1.Properties.Settings). Diesen kopiert man am besten aus der app.config der Klassenbibliothek. Wichtig ist, dass dieser neue Block noch registriert wird. Dies passiert im Bereich <configSections> mit der Zeile.
<section name="ClassLibrary1.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
Sonst erhält man eine Exception, dass die Configuration ungültig ist.
Nun kann man aus der Anwendung heraus auch die Parameter der Klassenbibliothek einstellen. Ich habe das z.B. verwendet um mir die Übergabe von Konfigurationsparametern an die Klassenbibliothek zu ersparen.
Was mir allerdings nicht besonders gut gefällt, ist die Tatsache, dass ich beim hinzufügen von neuen Config-Parametern für die Klassenbibliothek immer daran denken muss, auch in der Anwendung diese hinzuzufügen. Vergesse ich das, erhalte ich zwar keinen Fehler aber es wird einfach mit dem Default-Wert gearbeitet. Das kann manchmal durchaus erwünscht sein, in anderen Fällen ist das aber eher problematisch.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" > <section name="WindowsApplication1.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> <section name="ClassLibrary1.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </sectionGroup> </configSections> <applicationSettings> <WindowsApplication1.Properties.Settings> <setting name="Setting" serializeAs="String"> <value>abc</value> </setting> </WindowsApplication1.Properties.Settings> <ClassLibrary1.Properties.Settings> <setting name="Setting" serializeAs="String"> <value>zzzz</value> </setting> </ClassLibrary1.Properties.Settings> </applicationSettings> </configuration>
Mit der statischen Codeanalyse in VS kann der Quellcode nach verschiedenen Regeln analysiert werden. Es lassen sich auch eigene Regeln erstellen. Beginnt man damit zu arbeiten, ergibt sich die Frage, wie kann man die benutzerdefinierten Regeln und die Einstellung, welche Regeln verwendet werden sollen auf mehrere Projekte und für mehrere Entwickler anwenden.
Dazu ein paar Überlegungen:
1.) Benutzerdefinierte Regeln können durch Kopieren der entsprechenden DLL-Dateien auf die einzelnen Rechner verteilt werden. Dies kann z.B. über das Login-Script erledigt werden.
2.) Die Einstellungen welche Regeln aktiviert sind, sind im Projekt hinterlegt. Über die Quellcodeverwaltung nutzen somit alle Entwickler, die das Projekt bearbeiten, die gleiche Einstellung.
3.) Um die Einstellungen für neue Projekte zu übernehmen bietet sich die Erstellung einer entsprechenden Projektvorlage an, in der die entsprechenden Einstellungen vorgenommen werden. Wird ein neues Projekt auf dieser Vorlage erstellt, so erbt dieses die Einstellungen aus der Vorlage.
Vor allem wenn man mit Services programmiert, hat man oft die Anforderung, dass beim Debuggen mehrere Projekte innerhalb der Solution gestartet werden müssen, z.B. ein Service und ein entsprechender Client. Der manuelle Weg war hier, dass man den Service als Startup-Projekt eingertragen hat, das debuggen startet und dann auf den Client im Solution Explorer mit der rechten Maustaste klickt und dann hier Debug / Start new Instance auswählt. Es gibt aber auch noch eine elegantere Möglichkeit. Auf der Eigenschaftsseite der Solution (rechte Maustaste / Properties) kann man unter Startup Project auch die Option Multiple startup projects wählen. Hier kann man nun bei mehreren Projekten die Action auf Start bzw. Start without debugging einstellen. Sogar die Startreihenfolge der einzlnen Projekte lässt sich definieren. Damit starten nun die eingestellten Anwendungen auf einmal.
Will man die Kalenderwoche zu einem Datum herausfinden, dann kann man die Funktion GetWeekOfYear nutzen.
CalendarRow.Week = Application.CurrentCulture.Calendar.GetWeekOfYear(CurrentDate, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
Diese Funktion hat allerdings einen kleinen Bug. So wird beispielsweise für den 31.12.2007 KW 53 zurückgegeben. Der darauffolgende Tag, ein Dienstag, ist dann allerdings in KW 1. Eigentlich müssten die Tage einer Woche aber immer in der selben KW sein. Abhilfe schafft hier eine kleine Korrektur. Wir haben ja eingestellt, dass unsere Woche am Montag beginnen soll (DayOfWeek.Monday) und dass die Woche immer dann als KW 1 betrachtet werden soll, wenn wenigstens 4 Tage davon im neuen Jahr liegen (System.Globalization.CalendarWeekRule.FirstFourDayWeek). Der Trick ist nun, dass man einfach immer 3 Tage hinzuzählt, wenn das gesuchte Datum zwischen Montag und Mittwoch liegt. Damit wird aus dem Montag Donnerstag, aus Dienstag der Freitag und aus dem Mittwoch ein Samstag. Da der Donnerstag der Tag ist, von dem abhängt, ob die Woche im neuen oder im alten Jahr liegt, haben wir Montag bis Mittwoch einfach nach hinten geschoben, so dass diese auf jeden Fall im neuen Jahr liegen und damit auch korrekterweise die KW 1 ergeben.
CalendarRow.Week = Application.CurrentCulture.Calendar.GetWeekOfYear(CurrentDate.DayOfWeek >= DayOfWeek.Monday && CurrentDate.DayOfWeek <= DayOfWeek.Wednesday?CurrentDate.Add(new TimeSpan(3,0,0,0)):CurrentDate, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
 George Shepherd hat eine Vielzahl von wirklich guten Tipps & Tricks zur Windows- und Web-Programmierung in zwei FAQ-Listen zusammengetragen. Wirklich ein Quell von Anregungen, Problemlösern und Tipps & Tricks. Nicht nur zum Nachschlagen, sondern auch mal zum durchlesen empfohlen. Link zu Windows Forms FAQ Link zu ASP.NET FAQ
Um den Inhalt einer DataRow in eine andere DataRow zu kopieren kann einfach folgende Anweisung verwendet werden:
newRow.ItemArray = ( object[])copyRow.ItemArray.Clone();
Das PropertyGrid ist ein sehr hilfreiches Control, mit dem man einfach die Inhalte der Properties einer Klasse an der Oberfläche anzeigen und bearbeiten kann. Ich hatte hierzu schon zwei Posts : Verwendung des Property-Grids, Dateien und Ordner im Property-Grid auswählen
Das Property-Grid stößt allerdings an seine Grenzen, wenn man damit mehrsprachige Umgebungen unterstützen möchte. Die Anzeige im PropertyGrid ist immer direkt vom Namen der Properties abgeleitet und die Description etc. wird über Attribute angegeben, die nicht über Ressource-Files lokalisiert werden können.
Auf Code-Project habe ich ein erweitertes Property-Grid gefunden, das genau dieses Problem beseitigt.

Link to PropertyGrid utilities - The Code Project - C# Programming
Danke an Chris für den Link!
In dem folgenden Beispiel wird aus einer Auflistung der Typ der Elemente ermittelt und anschließend eine neue Instanz dieses Typs angelegt. Dann werden die Properties die über DisplayMember und ValueMember übergeben wurden mit Werten befüllt. Damit kann die Liste ergänzt werden, ohne Zugriff auf den Datentyp zu haben. Einzige Voraussetzung ist, dass in der DataSource bereits Elemente vorhanden sind.
foreach (cListEntry item in Items) { Type t = ((IList)dataSource)[0].GetType(); object o = Activator.CreateInstance(t); if (this.DisplayMember!= null && this.DisplayMember!= "") t.GetProperty(this.DisplayMember).SetValue(o, item.Text, null); if (this.ValueMember!= null && this.ValueMember!= "") t.GetProperty(this.ValueMember).SetValue(o, item.Value, null); }
Nachtrag:
Wenn der Typ, von dem eine neue Instanz erzeugt werden soll, keinen parameterlosen Konstruktor hat, funktioniert das obige Beispiel nicht. Abhilfe kann man hier schaffen, indem man einen parameterlosen Konstruktor anlegt und diesen als private deklariert. Dadurch kann ich beim Instanzieren weiterhin sicherstellen, dass Pflichtangaben gemacht werden, weil der parameterlose Konstruktor ja nicht sichtbar ist.
Mit object o = Activator.CreateInstance(t, true); kann nun in obigem Beispiel eine Instanz des Objektes durch Aufruf des privaten parameterlosen Konstruktors erzeugt werden. Hier muss man natürlich jetzt sicherstellen, dass die Pflichtobjekte entsprechend befüllt werden.
Mit OleDB lassen sich sehr einfach Excel-Dateien lesen. Man macht sich einen Connectionstring der ungefähr so aussieht:
Dim connectionString As String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & ExcelFileName & ";Extended Properties='Excel 8.0;HDR=No;"
Nun kann man eine ganz normale Connection öffnen und wie gewohnt z.B. über ein Select-Statement ein DataSet befüllen.
Dim conn As OleDbConnection = New OleDbConnection(connectionString) conn.Open() Dim dt As DataTable = New DataTable() Dim oleDbDataAdapter As OleDbDataAdapter = New OleDbDataAdapter("SELECT * FROM [Tabelle1]", conn) oleDbDataAdapter.Fill(dt) oleDbDataAdapter.Dispose() oleDbDataAdapter = Nothing conn.Close()
Das Problem ist dabei, wenn in Excel z.B. Integer und Textwerte in einer Spalte gemischt stehen und die Integerwerte dabei überwiegen, wird diese Spalte als Integer angelegt und die Textwerte stehen im Dataset dann als NULL. Das kann man umgehen, wenn man am Connectionstring den Parameter IMEX=1 anhängt. Damit wird ein "ImportMixedTypes"-Mode aktiviert, der gemischte Spalten immer als Text interpretiert.
Dim connectionString As String = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Me.tbImportdatei.Text & ";Extended Properties='Excel 8.0;HDR=No;IMEX=1'"
Das Problem bei der ganzen Sache ist nur, dass nun standardmäßig die ersten 8 Zeilen geprüft werden. Sind diese alle Integers, wird die Saplte auch als Integer definiert, auch wenn weiter unten noch Textwerte kommen. Man findet hier an verschiedenen Stellen den Hinweis auf einen weiteren Parameter MaxScanRows. Dieser scheint aber nicht zu funktionieren, wie man auch aus diesem MSDN-Artikel ersehen kann. Statt dessen muss man die Einstellung in der Registry vornehmen, wenn man eine andere Anzahl prüfen möchte. Hierzu passt man im Zweig
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Jet\4.0\Engines\Excel
den Parameter TypeGuessRows an. Dieser Wert kann auf 1-16 Zeilen eingestellt werden. Ein Wert von 0 bedeutet dass die ersten 16384 Zeilen geprüft werden, um festzustellen, ob der mixed Mode verwendet werden muss oder ob der Datentyp eindeutig bestimmt werden kann. Dass das eigentlich keine saubere Lösung ist, ist klar. Wer will schon seinen Kunden zumuten, in der Registry rumzupfuschen? Aber es scheint im Moment keine bessere Lösung zu geben.
HDR=No bedeutet im obigen Fall übrigens, dass die Excel-Datei keine Header-Zeile hat.
Um einen Label auf einer WinForm per Code einfach fett zu machen, funktioniert leider folgende Methode nicht:
TitleLbl.Font.Bold = true;
Statt dessen kann man aber folgenden Code verwenden:
TitleLbl.Font = new Font(TitleLbl.Font, FontStyle.Bold);
Möchte man einen enum-Wert an eine ComboBox binden, dann muss man hier einen kleinen Trick anwenden. Ich habe das mal am Beispiel mit dem UltraComboEditor aus der Infragistics-Bibliothek realisiert. Man braucht zunächst einen Parse_Event auf dem Binding:
this.ultraComboEditor1.DataBindings[0].Parse += new ConvertEventHandler(ValueInputEnum_Parse);
Dann kann man in dem Eventhandler den Wert in den Enum konvertieren:
void ValueInputEnum_Parse(object sender, ConvertEventArgs e) { e.Value = Enum.Parse(typeof(ePeriodInterval), e.Value.ToString()); }
Unter .net 1.1 hat man das Problem, dass Panels und andere scrollbaren Controls keinen Event liefern, wenn das Control gescrollt wird. Wie man das beheben kann, beschreibt Rick Brewster hier: http://blogs.msdn.com/rickbrew/archive/2004/06/24/165493.aspx
Bei .net 2.0 besteht das Problem übrigens nicht mehr, hier bringen die Controls den Event auch Eigenschaften über die horizontale und vertikale Bildlaufleiste mit.
Will man in einem Web-Service Sessions nutzen, so muss der Client Cookies handeln können. Ist der Client eine Web-Applikation, so ist das kein Problem. Bei WinForms muss man da zu einem kleinen Kniff greifen.
Man muss für den Service einen CookieContainer erstellen. Dazu kann man im Client folgenden Code verwenden:
WebService.MyService ws = new WebService.MyService(); ws.CookieContainer = new CookieContainer();
Damit kann dann auch der Windows-Client Cookies handeln.
Es gibt ein Control mit dem Namen DSOFramer, das es erlaubt, Office-Anwendungen in eine eigene WinForms-Anwendung zu integrieren. Das Control hat allerdings einen Schönheitsfehler, es funktioniert nicht auf MDIChild-Forms. Versucht man innerhalb eines MDIChilds per Code eine Datei im DSOFramer zu laden, erhält man eine nichtssagende Fehlermeldung. Über einen kleinen Trick lässt sich das Problem allerdings beseitigen. Man baut sich einfach ein Usercontrol, in das man den DSOFramer steckt. danach funktioniert das Control auch in MDIChilds.
Infos und Download des DSOFramer_Controls unter http://support.microsoft.com/kb/311765
Dank an Christopher für den Tipp.
Um den aktuell angemeldeten Windows-User zu ermitteln gibt es zwei Möglichkeiten:
1.) string userName = Environment.UserName Diese Variante liest aus der Systemumgebung die entsprechende Einstellung aus. Nachteil: Diese Einstellung kann leicht manipuliert werden, z.B. in der Eingabeaufforderung mit Set USERNAME = Administrator
2.) string userName = System.Security.Principal.WindowsIdentity.GetCurrent().Name; Diese Variante fragt den User direkt im Windows Sicherheitsystem ab und kann deshalb nicht manipuliert werden.
Will man eine Liste mit allen Werten eines enums befüllen, z.B. zur Ausgabe in einer DropDown-List, kann man folgenden Code verwenden:
foreach (int Entry in Enum.GetValues(typeof(DataLayer.LogDataLayer.LogDataLayer.LogEntryTypes))) { vl.ValueListItems.Add(Entry, ((DataLayer.LogDataLayer.LogDataLayer.LogEntryTypes)Entry).ToString()); }
Ich erstelle gerade eine Anwendung, die verschiedene Berechnungen durchführt. Um möglichst flexibel zu sein, habe ich jede Berechnungsmethode in eine eigene DLL gepackt. Diese DLLs sollten nun nicht zur Compile-Zeit eingebunden werden sondern erst zu Laufzeit. Dadurch kann man jederzeit zusätzliche Berechnungsmethoden erstellen und diese dann einfach in das Anwendungsverzeichnis kopieren ohne die gesamte Anwendung neu compilieren zu müssen. Das Ganze funktioniert sogar zur Laufzeit, d.h. die Anwendung muss nicht neu gestartet werden, wenn eine neue DLL hinzukommt.
Das Ganze funktioniert so:
- Ich habe ein Interface erstellt, das als Schnittstelle für alle Berechnungsmethoden dient.
public interface IBaseEngine { void StartCalculation(); }
- Dann habe ich ein Berechnungsmodul erstellt, das die Schnittstelle implementiert. Berechnungsmodul und Interface sind im gleichen Namespace:
public class cCapaPlanerEngine : IBaseEngine { public cCapaPlanerEngine() { }
public void StartCalculation() { // Hier kommt die Berechnung } }
- Nach dem Compilieren wird die DLL des Berechnungsmoduls in das Anwendungsverzeichnis kopiert.
- Nun kann das Berechnungsmodul instanziiert werden:
IBaseEngine calculationEngine = (IBaseEngine)Activator.CreateInstanceFrom("CalculationEngines\\" + _CalculationEngineName + ".dll", "ScenarioGenerator.CalculationEngines.c" + _CalculationEngineName).Unwrap();
Da sind noch ein paar statische Sachen drin, die besser in die config ausgelagert bzw. automatisch ermittelt werden, aber so ist es momentan etwas verständlicher. Wichtig ist das Unwrap() am Ende, da sonst der Typecast nicht funktioniert. Eine optimierte Version könnte etwa so aussehen, wobei hier in derm Berechnungsmodul nur eine Klasse stehen sollte:
Assembly calculationEngineAssembly = Assembly.LoadFrom(Properties.Settings.Default.CalculationEnginesPath + "\\" + _CalculationEngineName + ".dll"); calculationEngine = (IBaseEngine)Activator.CreateInstance(calculationEngineAssembly.GetTypes()[0]);
- Nun kann die Berechnungsmethode einfach aufgerufen werden:
calculationEngine.StartCalculation();
Weitere Berechnungsmodule lassen sich nun einfach erstellen indem sie die selbe Schnittstelle implementieren und dann einfach in das Anwendungsverzeichnis kopiert werden.
Man kann eigene enums in den Settings einer Anwendung avblegen. Dazu muss zunächst der enum definiert werden. Anschließend öffnet man die Settings-Datei des Projektes (liegt unter Properties). Im Feld Type kann man hier nun Browse... auswählen. Dann gibt man bei SelectedType den vollqualifizierten Namen inkl. Namespace ein. Nun kann man in der Spalte Value über eine DropDownList den gewünschten Wert für die Konfiguration auswählen. In der app.config sieht das dann so aus: < CalculationUnitApplication.Properties.Settings> <setting name="Setting" serializeAs="String"> <value>CapaPlaner</value> </setting> </CalculationUnitApplication.Properties.Settings> Auf den Eintrag kann man dann aus dem Code so zugreifen: Properties. Settings.Default.mySetting wobei mySetting der Name ist, der dem Eintrag in der Settings-Einstellung im Feld Name vergeben wurde. Das Ganze ist jetzt typsicher, d.h. wenn jemand die Config-Datei bearbeitet und gibt einen Wert ein, der nicht im enum vorkommt, wird eine Exception geworfen.
Wenn auf einer DataTable ein ColumnChanged-Event eingesetzt wird um bei Änderungen eines Wertes in der DataTable andere Werte in der selben table neu zu berechnen, ergibt sich das Problem, dass das zurückschreiben der neuberechneten Werte wieder zu einem ColumnChanged führt, was den Evbenthandler erneut aufruft und so in einer Endlosschleife endet, die mit einem StackOverflow endet.
Hier gibt es eine recht simple Abhilfe. Man deregistriert den EventHandler bevor die geänderten Werte geschrieben werden und registriert ihn danach einfach wieder. Das kann dann z.B. so aussehen:
void myTable_ColumnChanged(object sender, DataColumnChangeEventArgs e) { myDataSet.myTable.ColumnChanged -= new DataColumnChangeEventHandler(myTable_ColumnChanged); RecalculateValues(); myDataSet.myTable.ColumnChanged += new DataColumnChangeEventHandler(myTable_ColumnChanged); }
Folgender Code zeigt, wie man in eine Zelle im Infragistics-Grid Prozentwerte aus- und eingeben kann. Zu beachten ist hierbei, dass beim BeforeExitEditMode auf die Text-Eigenschaft zugegriffen werden muss, da der Value zu diesem Zeitpunkt noch nicht gesetzt ist.
private void ugShiftDetails_BeforeEnterEditMode(object sender, CancelEventArgs e) { if (this.ugShiftDetails.ActiveCell.Column.Key == "PercentColumn") { UltraGridCell c = this.ugShiftDetails.ActiveCell; c.Value = ((double)c.Value) * 100; } }
private void ugShiftDetails_BeforeExitEditMode(object sender, Infragistics.Win.UltraWinGrid.BeforeExitEditModeEventArgs e) { if (this.ugShiftDetails.ActiveCell.Column.Key == "PercentColumn") { UltraGridCell c = this.ugShiftDetails.ActiveCell; try { c.Value = Double.Parse(c.Text) / 100; } catch { MessageBox.Show("Invalid Input"); } } }
Beim Databinding möchte man oft Einfluss auf die Darstellung der Werte auf der Oberfläche nehmen. Ein typischer Fall hierzu ist z.B. das Databinding mit Prozentwerten. Hier hat man z.B. einen Wert 0,5 der als 50% angezeigt werden soll. Wenn der Benutzer dann 40% eingibt, soll dieser Wert in 0,4 konvertiert werden. Hierzu kann man die Format- und Parse-Events des Databindings verwenden.
Zuerst müssen die Events definiert werden. Dabei gehen wir in dem Fall davon aus, dass es sich um das erste Databinding auf dem Control handelt.
this.txtValue.DataBindings[0].Format += new ConvertEventHandler(ValueInputPercent_Format); this.txtValue.DataBindings[0].Parse += new ConvertEventHandler(ValueInputPercent_Parse);
Dann können in den Eventhandlern die entsprechenden Formatierungen vorgenommen werden. Dies können natürlich auch noch mit entsprechenden Fehlerprüfungen versehen werden.
void ValueInputPercent_Parse(object sender, ConvertEventArgs e) { e.Value = Double.Parse(e.Value.ToString().TrimEnd('%')) / 100; }
void ValueInputPercent_Format(object sender, ConvertEventArgs e) { e.Value = ((double)e.Value) * 100 + "%"; }
User-Controls werden im Designer von Visual Studio genauso instanziert wie zur Laufzeit. Um hier unterscheiden zu können, kann man mit der Variable DesignMode abfragen, in welchem Modus die Komponente momentan initialisiert wird. Die ist z.B. sinnvoll, wenn man beim instanzieren auf Objekte zugreift, die im DesignMode gar nicht zur Verfügung stehen. Hier ein kleines Beispiel:
public MyControl() { InitializeComponent(); if (!DesignMode) { try { shiftConfiguration.ReadXml(ConfigurationManager.AppSettings["ShiftConfigFile"]); } catch (Exception ex) { MessageBox.Show("Error reading Shift Configuration File\n\nError : " + ex.Message); } } }
Als Erweiterung zu meinem Beitrag zum Übernehmen von Excel-Daten über die Zwischenablage (http://www.artiso.com/ProBlog/PermaLink,guid,2017b61d-19f4-4e4a-a033-cbf820d2b18e.aspx) hier nun das Gegenstück, Daten über die Zwischenablage an Excel übergeben:
string Data = "1,1;2,7;3,1;4,8;5,9"; Byte[] ClipboardData = Encoding.UTF8.GetBytes(Data); MemoryStream ClipboardStream = new MemoryStream(ClipboardData); DataObject DataObj = new DataObject(DataFormats.CommaSeparatedValue, ClipboardStream); Clipboard.SetDataObject(DataObj, true);
Daten über mehrere Zeilen werden übrigens mit Environment.NewLine getrennt.
Der nachfolgende Code zeigt, wie JPEG Grafiken (und ebenso auch andere Formate) verlustlos rotiert werden können:
Image i = Image.FromFile(this.imageFilename); ImageCodecInfo usedIC = this.GetEncoderInfo("image/jpeg");
System.Drawing.Imaging.Encoder encoder = System.Drawing.Imaging.Encoder.Transformation;
EncoderParameters encparams = new EncoderParameters(1); EncoderParameter encparam = new EncoderParameter(encoder, (long)EncoderValue.TransformRotate270); encparams.Param[0] = encparam;
i.Save("filename.jpg", usedIC, encparams );
i.Dispose(); i = null; GC.Collect();
Quelle: http://blog.norberteder.com/index.php?entry=entry060920-223506
User-Controls können auch mit eigenen Events erweitert werden. Das ist eigenlich recht simpel. Man definiert einfach einen Event, z.B.
public event EventHandler LinesChanged;
Dann ruft man an der Stelle, an der der Event ausgelöst werden soll einfach den Event auf. Das geht am einfachsten über:
LinesChanged(this, EventArgs.Empty);
Der Event kann dann außerhalb des User-Controls wie gewohnt mit einem Eventhandler ausgewertet werden. Möchte man mehr Informationen als den Sender übergeben, erstellt man sich einfach eine Klasse, die man von EventArgs ableitet und übergibt dann eine Instanz dieser Klasse beim Aufruf des Events, z.B.
public class MyEventArgs : EventArgs { private string msg;
public MyEventArgs( string messageData ) { msg = messageData; } public string Message { get { return msg; } set { msg = value; } } }
Damit der Eventhandler nun auch noch den richtigen Datentyp für die EventArgs übergeben bekommt braucht mann noch ein Delegate, z.B.
public delegate void MyEventHandler(object sender, MyEventArgs e);
Der Event muss nun entsprechend deklariert werden:
public event MyEventHandler LinesChanged;
Um den Eventhandler nun zu erzeugen, legt man in der umgebenden Klasse eine Instanz des Objektes an, das den Event wirft. Auf dieser Instanz kann man nun den Event mit dem Handler verbinden. Das geht in VS ganz einfach. Man gibt z.B. ein:
MyControl.LinesChanged +=
Dann drückt man zweimal Tab und schon hat man den Eventhandler angelegt. Fertig sieht das dann ungefähr so aus:
MyControl.LinesChanged += new UControl.MyEventHandler(MyControl_LinesChanged)
void MyControl_LinesChanged(object sender, MyEventArgs e) { throw new Exception("The method or operation is not implemented."); }
Vor kurzem habe ich hier gepostet, wie man Debug-Informationen ausgeben kann. Hier noch eine kleine Ergänzung. Man kann den Debug-Informationen auch noch Informationen über die Quelldatei, das Modul und die Zeile ausgeben. Dazu dient der folgende Code:
Debug.WriteLine("Quelledatei = " + new StackFrame(0, true).GetFileName() + "\nMethode = " + new StackFrame(0, true).GetMethod() + "\nZeile = " + new StackFrame(0, true).GetFileLineNumber() + "\n");
Gefunden bei Dani.Net
Heute habe ich mir mal Outlook 2007 näher angeschaut und dabei bin ich über die Funktion gestolpert, mit der Outlook RSS Feeds lesen kann. Eigentlich bin ich mit dem SharpReader, den ich bisher einsetze, ganz zufrieden. Einzig sört mich, dass ich auf verschiedenen Rechnern nicht sauber synchronisieren kann, welche Einträge ich schon gelesen habe.
In Outlook werden die Einträge der Feeds werden in Outlook als Nachrichten auf dem Exchange-Server abgelegt. Daraus ergeben sich zwei Vorteile:
1.) Die Einträge stehen auf allen Rechnern zur Verfügung. Auch der Status (gelesen / ungelesen) wird für alle Outlook-Clients zur Verfügung gestellt.
2.) Ich kann die Einträge auch offline lesen. Dazu bietet Outlook auch die Möglichkeit, den kompletten Eintrag als HTML-File lokal zu speichern.
Total begeistert war ich dann, als ich feststellte, dass ich sogar mit meinem Outlook 2003 auf die heruntergeladenen Einträge Zugriff habe. OPML-Files können übrigens über die Import / Export-Funktion von Outlook importiert und auch wieder exportiert werden.
Nachträgliche Einstellungen können über einen etwas versteckten Dialog vorgenommen werden: Tools / Options / Mail Setup / E-mail Accounts / RSS-Feeds
In manchen Situationen hilft ein Breakpoint beim Debugen einfach nicht weiter. Haben Sie schon mal versucht, einen Eventhandler für ein Mouse-Move-Ereignis mit einem Breakpoint zu debuggen? Das ist schlicht unmöglich.
Abhilfe schafft hier das Debug-Objekt. Sie können damit z.B. Informationen im Ausgabefenster von VS ausgeben. In oben beschriebenen Fall würden Sie also z.B. folgendes verwenden:
private void Form2_MouseMove(object sender, MouseEventArgs e) { System.Diagnostics.Debug.WriteLine(e.X + " - " + e.Y); }
Das Ergebnis sieht man wie gesagt im Ausgabefenster von Visual Studio (Einblenden über Strg + Alt + O).
Welche Entwickler hat denn schon die Namespaces aller Objekte des Frameworks im Kopf? Da hilft oft nur in der Hilfe nachschauen - oder man kennt eine kleine aber sehr nette Funktion im Visual Studio 2005!
Man gibt einfach den Namen des Objektes ein. Ist das Objekt im .net Framework enthalten, erkennt VS das automatisch und zeigt dies durch einen kleinen roten Strich am Ende des Objektnamens ein. Klickt man darauf, kann man auswählen, ob der Namespace vor dem Objekt eingefügt l oder ob automatisch ein Using für den Namespace eingefügt werden soll. Unten sieht man ein Beispiel mit dem Objekt MailMessage.
Eigentlich dachte ich, das sei eine ganz simple Aufgabe, aber dann hat mich das Ganze doch 2 Stunden Zeit gekostet. Das Ziel war, zwei Panels über einen separaten Scrollbar zu scrollen. Alse Panels in der Form platziert, AutoScroll auf false gesetzt, damit nicht jedes Panel mit einem Scrollbar versehen wird und den Scrollbar hinzugefügt. Der Event für das Scrollen war auch schenll gefunden, aber dann gab es Probleme.
Deshalb hier die Vorgehensweise, die nach meinen Versuchen am besten funktioniert hat.
private void Form2_Load(object sender, EventArgs e) { this.panel1.AutoScroll = true; this.panel2.AutoScroll = true; this.ultraScrollBar1.Minimum = this.panel1.HorizontalScroll.Minimum; this.ultraScrollBar1.Maximum = this.panel1.HorizontalScroll.Maximum; this.ultraScrollBar1.Value = this.panel1.HorizontalScroll.Value; this.ultraScrollBar1.SmallChange = this.panel1.HorizontalScroll.SmallChange; this.ultraScrollBar1.LargeChange = this.panel1.HorizontalScroll.LargeChange; this.panel1.AutoScroll = false; this.panel2.AutoScroll = false; }
private void ultraScrollBar1_Scroll(object sender, ScrollEventArgs e) { this.panel1.AutoScrollPosition = new Point(e.NewValue, 0); this.panel2.AutoScrollPosition = new Point(e.NewValue, 0); }
Hierzu ein paar Anmerkungen: 1.) Die Werte für Minimum, Maximum, SmalChange und LargeChange können vom Panel nur dann sauber abgefragt werden, wenn AutoScroll = true gesetzt ist. 2.) Wird die Scrollposition über this.panel1.HorizontalScroll.Value gesetzt, was naheliegend wäre, kommt es bei abgeschaltetem AutoScroll zu einem seltsamen Flimmer-Effekt. Statt dessen muss die AutoScrollPosition gesetzt werden. 3.) Auch für das zweite Panel muss das AutoScroll einmal aktiviert und dann wieder deaktiviert werden, sonst scrollt das nicht mit.
Also diese Lösung funktioniert mal.
Diese Vorgehensweise scheint mir jedoch recht unlogisch und sehr umständlich. Ob das Framework da nichts besseres bietet oder ob ich da einfach noch nicht den richtigen Dreh gefunden hab, weiss ich im Moment noch nicht. Ich werde bei Gelegenheit das mal vertiefen und an dieser Stelle wieder posten.
Oft möchte man von einem DateTime-Wert die Kalenderwoche abfragen. Hierzu gibt es eine einfache Funktion:
Application .CurrentCulture.Calendar.GetWeekOfYear(CurrentDate, System.Globalization.CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
Dabei steht in CurrentDate das aktuelle Datum und die beiden anderen Parametern definieren Regeln, welche Woche als erste KW gilt. Standard in Deutschland ist dass der 1. Janur in KW 1 liegt, wenn dieser Tag ein Montag, ein Dienstag oder ein Mittwoch ist. Andernfalls zählt der 1. Januar noch zur letzten Woche des alten Jahrs und die KW1 beginnt am darauffolgenden Montag.
Um Einträge in die Toolbox von VS hinzuzufügen gibt es die Standard-Methode - rechte Maustaste, Elemente auswählen und sich dann die Elemente einzeln auswählen um sie hinzuzufügen. Weniger bekannt ist eine Methode, die gerade bei einer größeren Anzahl hinzuzufügender Controls schneller ist. Man kann die zugehörigen DLL-Dateien einfach per Drag & Drop aus dem Windows-Explorer auf die Toolbox ziehen.
Der kostenlose Internet Explorer Developer Toolbar von Microsoft bietet eine Fülle von sehr nützlichen Funktionen für Web-Entwickler. Der Toolbar integriert sich im Internet-Explorer. Man kann sich z.B. Objekte wie Tabellen, Bilder, DIVs etc. durch eine Umrandung hervorheben lassen oder man kann die komplette DOM-Struktur dursuchen und die Eigenschaften der Elemente abfragen und sogar verändern. Die Änderungen werden sofort sichtbar. Darüber hinaus werden noch eine vielzahl nützlicher Helfer angeboten, z.B. um Cookies anzuzeigen und zu löschen etc. Ein Tool, das bei keinem Web-Entwickler fehlen sollte!
http://www.microsoft.com/downloads/details.aspx?FamilyID=E59C3964-672D-4511-BB3E-2D5E1DB91038&displaylang=en
Jede Menge Code-Snippets für alle Lebenslagen finden sich unter
http://dotnet-snippets.de/dns
Achtung Cookies müssen zugelassen werden, sonst funktioniert die Seite nicht korrekt.
Recht bekannt ist die Option im Infragistics Grid Datensätze durch einen Klick auf den Kopf einer Spalte zu filtern.

Es gibt aber auch die Möglichkeit, eine Zeile einzublenden, in der die Filterbedingung für jede Spalte eingegeben wird. Der Vorteil liegt dabei darin, dass auch nur Anfangsbuchstaben eingegeben werden können. Um dies zu erreichen, stellt man einfach im Designer Dialog unter Feature Picker die Option Filtering / Filter UI Type auf Filter Row. Das Ergebnis sieht dan so aus:
Die aktuelle Bildschirmauflösung abzufragen ist in .NET denkbar einfach, möchte man jedoch alle verfügbaren Auflösungen erfragen und eventuell sogar eine neue Auflösung setzen, muss man auf die Win32 API-Mittel zurückgreifen. Dieser Beitrag stellt eine Klasse zur Verfügung mit der mit jeweils einer Zeile Code über die verfügbaren Auflösungen iteriert werden bzw. eine neue Auflösung festgelegt werden kann
http://www.codegod.de/WebAppCodeGod/Main.aspx?pid=168
Ein wenig bekanntes Control ist das FlowLayoutPanel, das in VS 2005 standardmäßig enthalten ist. Mit diesem Control kann man Unterelemente in einer fließenden Ansicht darstellen wie das z.B. auch von Web-Seiten bekannt ist. Sie möchten eine Liste von Textboxen die untereinander stehen? Kein Problem mit dem FlowLayoutPanel. Sie können eine beliebige Richtung wählen (von rechts nach links, von oben nach unten etc.). Passen nicht alle Control in die Reihe, wird automatisch ein Umgruch eingefügt. Damit ist das FlowLayoutPanel wesentlich flexibler als man diese Funktion z.B. mit Docking erreichen könnte. Vor allem bei dynamisch generierten Control leistet das FlowLayoutPanel gute Dienste!
Oft möchte man Daten, die aus Excel in die Zwischenablage kopiert wurden, in der Anwendung verarbeiten. Dies geht recht einfach, da die Excel-Daten in der Zwischenablage einfach als CSV-Format vorliegen.
string ExcelValue = ""; IDataObject ClipboadData = Clipboard.GetDataObject(); if (ClipboadData.GetDataPresent(DataFormats.CommaSeparatedValue)) { StreamReader ExcelReader = new StreamReader((Stream)ClipboadData.GetData(DataFormats.CommaSeparatedValue)); while (!ExcelReader.EndOfStream) { ExcelValue = ExcelReader.ReadLine(); } }
Man holt sich die Daten aus der Zwischenablage und prüft diese, ob sie im CSV-Format vorliegen. Dann kann man mit Hilfe eines StreamReaders die Daten lesen. Die Daten kommen dann in einem Format wie z.B. 5,8;8,9;7,5. Das lässt sich nun problemlos in der eigenen Anwendung verarbeiten.
Um von generischen Objekten den Typ abzufragen kann man folgenden Code verwenden:
Dictionary<string, int> MyObject = new Dictionary<string, int>(); Type T = MyObject.GetType(); Type[] Ts = T.GetGenericArguments();
Dieser Code liefert ein Array zurück, das im ersten Element System.String und im zweiten System.Double enthält.
Weitere Infos unter http://msdn2.microsoft.com/en-us/library/b8ytshk6.aspx
Um die Work Items aller Team-Projekte eines Team-Servers anzeigen zu können, kann man einfach eine entsprechende Query anlegen. Hierzu einfach im Team-Explorer auf Work Items mit der rechten Maustaste klicken und dann "Add Query" auswählen. Die Query is dann schon mit einem Filter nach einem Projekt vorbelegt. Diesen einfach löschen, dann erscheinen alle Work Items.
Oft erhält mein eine Meldung in einer Message-Box und möchte den text kopieren. Mit Alt-Druck erhält man einen Screen-Shoot, aber das ist eine Grafik. Wie kommt man nun an den Text?
Mit Strg-C !
Oft müssem am Anfang und am Ende eines Strings überflüssige Zeichen gelöscht werden. Dazu kann in .Net der Befehl Trimm eingesetzt werden.
string t = " Test "; string Result = t.Trim(' ');
Mit Trim lassen sich aber auch mehrere Zeichen auf einmal löschen. Dabei ist die Reihenfolge der Zeichen egal!
string t = " \r\n Test \r \n "; string Result = t.Trim('\r', '\n', ' ');
In einer Anwendung aus der ich auf Access via COM zugreife, wollte ich das Access-Fenster ausblenden. Die "visible" Eigenschaft kann man auch setzten - es passiert nur nix, daher das ganze über DLLImport. Wichtig ist, dass man sich den/die "handle" auf das/die Fenster merkt - die findet man sonst nicht mehr und kann sie sonst auch nicht mehr sichbar schalten. Das Beispiel kann man natürlich auch auf jede andere Anwendung übertragen und das Process-Objekt hat ja auch noch paar andere Methoden, um ein bestimmtes Fenster zu finden, "GetProcessByID()" usw. sind auch vorhanden.
z.B. ausblenden von allen Access-Fenstern:
ArrayList processHandles = new ArrayList(); foreach (Process p in Process.GetProcessesByName("msaccess")) { ShowWindow((int)p.MainWindowHandle, 0); // 0 steht für unsichtbar processHandles.Add((int)p.MainWindowHandle); }
einblenden
foreach (int processHandle in processHandles) { ShowWindow(processHandle, 9); // 9 bedeutet restore - sprich so, wie das Fenster vor dem ausblenden aussah (Position, Größe) }
und der dllimport, damit die Funktion genutzet werden kann:
[DllImport("User32")]
private static extern int ShowWindow (int hwnd, int nCmdShow);
Unter IDesign gibt es einen Download, mit dem das My-Objekt von VB auch unter C# genutzt werden kann.
http://www.idesign.net/idesign/DesktopDefault.aspx?tabindex=-1&tabid=19&download=141
The My class in VB often simplifies and streamlines many operations, from Network programming to clipboard, to audio access, and so on. What takes sometimes a programming fit in C# can be done in one line using the My class in VB. If VB has Me and My, then C# should have this and That. The That class is the C# equivalent of the VB My class. It is a static class that uses the VB implementation as much as possible, and it requires adding a referencing to Microsoft.VisualBasic. The That class is instrumental when working in heterogeneous environments and when dealing with in porting of VB to C# or visa-versa.
Weitere interessante Downloads gibt's unter http://www.idesign.net/idesign/DesktopDefault.aspx?tabindex=5&tabid=11#ES
Jeder kennt das Problem. Man hat einen ellenlangen SQL-String und spätestens nach der dritten Klammer hat man den Überblick verloren, wenn allles in einer Zeile steht. Hier hilft ein kleines Tool weiter, das SQL-Strings, auch komplexe, übersichtlich formatiert. Das Tool gibt es als kostenlose Online-Version und als kostenpflichtige Offline-Version oder auch als API.
http://www.sqlinform.com/
|
Copyright © 2010 Thomas. All rights reserved.
DasBlog 'Portal' theme by Johnny Hughes.
Pick a theme:
|
|