# Monday, January 07, 2008

Mit OutlookMoveTo kann man sehr schnell Objekte innerhalb der Outlook Verzeichnisstruktur verschieben. Entstanden ist dieses kleine Outlook Add-In zunächst für Outlook 2003. Inzwischen arbeite ich mit Outlook 2007, und mit Visual Studio 2008 gibt es jetzt auch das passenden Visual Studio Tools  for Office (VSTO). Es war also dringend eine Version für das neueste Outlook fällig.

Die Motivation für die erste Version und eine kleine Anleitung findet sich bei den älteren Beiträgen.

Die Anpassung verlief einfach. Aus der Entwicklungsumgebung heraus lief die neue Version sehr bald, aber wie sieht es mit dem Setup aus? Bei VSTO für Visual Studio 2005 und Office 2003 war dieser Schritt relativ aufwendig. Zu meiner großen Freude wird in Visual Studio 2008 auch bei VSTO für Office 2007 Add-Ins das ClickOnce Deployment unterstützt. Für Office 2003 gibt es diesen Komfort noch nicht.

Die naheliegendste Verwendung von ClickOnce, auf dem Web Server, scheidet für mich aus. Mein ASP.NET Hoster, erlaubt mir im Moment leider nicht, die Mime-Types zu editieren. Das wäre aber nötig, um das VSTO Manifest auf dem Server so zu registrieren, dass es richtig ausgeliefert wird. Und auch dann wäre ClickOnce nur möglich, wenn der zugreifende Client schon die Visual Studio Tools for the Office system 3.0 Runtime bereits installiert hätte. Das ist eine zu starke Einschränkung.

Alternativ kann man als auch CD/DVD als Verteilungsmedium wählen. Man erhält dann einfach ein Verzeichnis mit allen nötigen Setup-Dateien. Aus Platzgründen habe ich das benötigte .NET Framework 3.5 weggelassen.

Viel Spass mit dem Add-In und bitte die MIT/X11 Lizenz in der Zip-Datei beachten:

OutlookMoveTo2007.zip (4.5 MB)

.NET | Admin | Tools
Monday, January 07, 2008 9:54:17 AM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [1]  | 
# Friday, December 14, 2007

Beim Umzug dieses Blogs habe ich von der alten Adresse auf die neue Adresse per ASP.NET HttpHandler weitergeleitet. Einer der Vorteile erschien mir die größere Flexibilität. Nur hatte ich die damals gar nicht benötigt.

Inzwischen ist mir aber aufgefallen, dass mein neuer Web-Hoster einen URL Filter einsetzt der Umlaute filtert. Die verwendete Blog-Engine nutzt aber den Titel als Link-Adresse. Bei Titel mit Umlauten gibt es jetzt leider Probleme.

Für das neue Blog ist die Lösung relativ einfach: Ich habe alte Einträge so umbenannt, dass keine Umlaute mehr vorkommen. Jetzt geht aber die Weiterleitung bei den veränderten Einträgen ins Leere.

Kein Problem für den HttpHandler, der modifiziert jetzt einfach einzelne Adressen:

if (oldRequest.Contains("OutlookAddInJetztHier")) 
    oldRequest = "/OutlookAddInJetztHierErhaeltlich.aspx"; 

Jetzt sieht für Google meine Web-Welt wieder in Ordnung aus.

Friday, December 14, 2007 4:49:53 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Thursday, December 06, 2007

Wenn man eine Webseite oder Teile einer Webseite permanent umziehen möchte, sollte man den Clients mitteilen, wo sie die Inhalte jetzt finden können. Der beste Weg dazu ist, mit einem HTTP Status Code 301 Moved Permanently zu antworten. In der Antwort wird die neue Adresse mitgeliefert.

Im Browser ändert sich dadurch beispielsweise automatisch die angezeigte Adresse und auch die Suchmaschinen wissen, dass der Inhalt umgezogen ist. In SEO Kreisen wird diese Form der Weiterleitung als die einzige akzeptable Möglichkeit gewertet.

Ich wollte mein ASP.NET basiertes Blog umziehen, also lag es nahe, auch die Weiterleitung mit ASP.NET zu realisieren. Um alle ASP.NET Antworten auf einen Schlag zu erwischen, empfiehlt sich ein HttpHandler. Damit können zentral alle Requests behandelt werden, bevor weitere Verarbeitungen in der Engine stattfinden.

Es genügt einen minimale Web Applikation anzulegen. In App_Code kommt eine von IHttpHandler abgeleitete Klasse Redirector:

using System;
using System.Web;

public class Redirector : IHttpModule
{
    public void Dispose() { }

    public void Init(HttpApplication context)
    {
        context.BeginRequest += new EventHandler(context_BeginRequest);
    }

    const string OldPath = "http://www.cptec.de/blogit";
    const string NewPath = "http://www.deger-it.de";

    void context_BeginRequest(object sender, EventArgs e)
    {
        HttpApplication application = sender as HttpApplication;
        string absoluteUri = application.Context.Request.Url.AbsoluteUri;

        if (absoluteUri.ToLower().StartsWith(OldPath))
        {
            application.Context.Response.StatusCode = 301;
            application.Context.Response.StatusDescription = "Moved Permanently";
            string oldRequest = absoluteUri.Substring(OldPath.Length);
            application.Context.Response.RedirectLocation = NewPath + oldRequest;
            application.Context.Response.End();
        }
    }
}

Bei allen Requests, die auf die alte Adresse des Blogs zugreifen wollen, wird mit einer einfachen String Bearbeitung die neue Adresse ausgerechnet. Der Status Code 301 samt Beschreibung und neuer Adresse wird gesetzt und als Antwort zurückgeschickt.

Damit dieser HttpHandler auch aktive ist, muss er noch in der Web.Config eingetragen werden:

<system.web>
    <httpModules>
        <add name="Redirection" type="Redirector"/>
    </httpModules>
</system.web>

Jeder ASP.NET Zugriff wird jetzt sauber an die neue Adresse weitergeleitet. Alle Zugriffe auf Ressourcen, die nicht über ASP.NET ausgeliefert werden, sind davon aber nicht betroffen. Bei diesem Blog werden die Bilder beispielsweise statisch über den Webserver ausgeliefert. Davon kriegt der HttpHandler gar nichts mit.

Handler Mapping IIS6

Beim IIS6 kann man das über den IIS Manager konfigurieren. Bei der entsprechenden Webseite oder dem Virtual Directory in der Application Configuration unter Mappings kann man die ASP.NET Engine auf weitere Dateien anwenden.

IIS6_Virtual_Directory

IIS6_Application_Configuration

Dazu klickt man Add und trägt bei Executable den ASP.NET ISAPI Filter ein. Der Pfad wird ungefähr so aussehen:

c:\Windows\Microsft.NET\Framework\2.0.xxx\aspnet_isapi.dll

Bei Extension gibt man die gewünschte Dateiendung ein. Beispielsweise .jpg für JPEG Bilder.

IIS6_Application_Extension_Mapping

Jetzt fühlt sich ASP.NET auch für JPEG Dateien zuständig und der neue HttpHandler wird durchlaufen und leitet entsprechend weiter.

Handler Mapping IIS7

Leider ist das bei einigen Dateitypen relativ mühselig. Mit dem IIS7 kann man das komplett über die Web.Config einstellen:

<system.webServer>
    <handlers>
        <add name="Redirector" path="*.jpg" verb="GET" type="Redirector" resourceType="File" />
    </handlers>
</system.webServer>

Wie ich leider zu spät erfahren habe, kann man auch direkt mit den Bordwerkzeugen von IIS6 sehr flexibel Weiterleitungen konfigurieren: Redirect Reference

Wenn man aber wie bei einem Web-Hoster nicht zugriff auf die kompletten Admin-Werkzeuge hat, ist die vorgestellte Variante immer noch möglich. Außerdem ist ein HttpHandler wesentlich flexibler.

Thursday, December 06, 2007 2:26:22 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Friday, November 23, 2007

Der Zugriff auf den Team Foundation Server 2008 mit Authentifizierung und über eine SSL Verbindung ist fast identisch zu dem Vorgehen mit der Version 2005. Leider hat sich aber auch nichts verbessert. Es ist die gleiche, fehleranfällige Bastelei: Walkthrough: Setting up Team Foundation Server with Secure Sockets Layer (SSL) and an ISAPI Filter

Dieser Artikel ist fast identisch zur vorherigen Version. Bei den Unterschieden ist der Artikel aber noch inkonsistent. Zum Beispiel ist der neue ISAPI FIlter AuthenticationFilter.dll im Tools Verzeichnis. An einigen Stellen nennt der Walkthrough aber noch fälschlich das TF Setup Verzeichnis. Amüsanterweise gibt es genau dazu noch eine Important Note ohne echte Aussage:

Do not change the directory of either the AuthenticationFilter.ini file or the AuthenticationFilter.dll file. Doing so increases the likelihood that you will have problems upgrading from Team Foundation SP1 to Team Foundation SP1.

Wenn man der Anleitung gründlich folgt, klappt es Ende tatsächlich und der externe Zugriff ist möglich.

Ich empfehle, dabei wirklich sorgfältig zu sein. Ich habe an einer Stelle den TFS Application Server und das SharePoint Portal vertauscht. Irgendwann sehen die Port Adressen alle so ähnlich aus. Die resultierenden Fehlermeldungen haben mich leider eine Zeit beschäftigt.

Zusätzlich sollte man auch alle ursprünglichen Einstellungen und die vorgenommen Änderungen dokumentieren. Es ist damit zu rechnen, dass auch das nächste Update nur eingespielt werden kann, wenn das System wieder im Ausgangszustand ist. Zumindest erging es mir bei diesem Update so.

.NET | Admin | Tools
Friday, November 23, 2007 3:09:02 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, November 21, 2007

In allen Blogs ist es zu lesen: Visual Studio 2008 ist fertig. MSDN Abonnenten können die RTM Version downloaden. Zusammen mit dem Visual Studio gibt es auch einen neuen Team Foundation Server (TFS).

Nach meinen Erfahrungen beim Service Pack 1 für die Version 2005, habe ich vor dem Upgrade ein Image der Serverplatte angelegt. Und natürlich kam beim Setup eine Fehlermeldung:

TF220064: An error occurred while the Setup program was querying the settings from the server that is running SQL Server Reporting Services. This error is most likely caused by your account not having the required administrative permissions on the server that is running Reporting Services. Click Next to try again. For more information about this error, see the installation logs. For more information about the installation logs, see “Troubleshooting Installation for Team Foundation” in the Team Foundation Installation Guide.

Der Setup kann nicht mit den SQL Server Reporting Services reden. Die Konfiguration der Schnittstelle zu den Reporting Services findet sich in der TfsIntegration Datenbank in der Tabelle tbl_service_interface. Die Einstellungen hatte ich aber geändert. Mein TFS war so konfiguriert, dass er auch externe Verbindungen zulässt. Der Setup kann offensichtlich nicht über eine https-Verbindung mit Authentifizierung kommunizieren. Das nehme ich ihm nicht übel und habe wieder die unverschlüsselten, lokalen http-Endpunkte eingetragen.

Vorher:

3    BaseReportsUrl    https://team.cptec.org:589/Reports
3    DataSourceServer    https://team.cptec.org:589/ReportServer
3    ReportsService    https://team.cptec.org:589/ReportServer/ReportService.asmx
4    BaseServerUrl    https://team.cptec.org:589
4    BaseSiteUrl    https://team.cptec.org:589/sites
4    WssAdminService    https://team.cptec.org:590/_vti_adm/admin.asmx

Nacher:

3    BaseReportsUrl    http://r8/Reports
3    DataSourceServer    http://r8/ReportServer
3    ReportsService    http://r8/ReportServer/ReportService.asmx
4    BaseServerUrl    http://r8
4    BaseSiteUrl    http://r8/sites
4    WssAdminService    http://r8:17012/_vti_adm/admin.asmx

Praktischerweise hatte der Fehler Dialog einen Retry Button. Man kann also schnell im offenen Herzen operieren und es noch mal probieren. Damit lief der Setup dann auch wieder einige Zeit weiter.

Error 28925.TFServerStatusValidator: The tool could not call the Team Foundation Server ServerStatus Web service. Verify that your network is configured to allow communication between the Team Foundation application tier and the Team Foundation data tier. For more information, see the setup log.

Jetzt können die einzelnen Schichten vom TFS nicht miteinander reden. Bestimmt auch wegen den nachträglich konfigurierten externen Verbindungen. Und richtig, diesmal war es der zusätzliche ISAPI Filter AuthenticationFilter.dll. Nachdem der Filter im IIS Manager wieder entfernt war, ging auch an dieser Stelle wieder der Retry. Diesmal ohne weitere Fehler und der interne Zugriff mit Visual Studio 2008 funktioniert.

Man sollte alle Änderungen am TFS 2005, die im Walkthrough: Setting up Team Foundation Server with Secure Sockets Layer (SSL) and an ISAPI Filter durchgeführt werden vor dem Upgrade rückgängig machen.

Jetzt kommt der nächste Schritt: Auch TFS 2008 möchte ich von Heimarbeitsplatz erreichen. Die externe Verbindung muss also wieder eingerichtet werden...

.NET | Admin | Tools
Wednesday, November 21, 2007 5:12:38 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Thursday, October 04, 2007

ScottGu hat die Neuigkeit gerade bekannt gegeben: Releasing the Source Code for the .NET Framework Libraries 

One of the things my team has been working to enable has been the ability for .NET developers to download and browse the source code of the .NET Framework libraries, and to easily enable debugging support in them.

Today I'm excited to announce that we'll be providing this with the .NET 3.5 and VS 2008 release later this year.

Das bedeutet das Debugging endet nicht an den Basisklassen. Wenn man bisher eine Ahnung davon kriegen wollte, wie es nach dem eigenen Code im Framework weitergeht, musste man mit Reflector selber die Libraries untersuchen. Das ist aber kein Vergleich zu einem echten Debugging. Jetzt sollen wohl sogar die Kommentare zur Verfügung stehen.

Zusätzlich wird dabei sicher die Code Qualität des Frameworks verbessert. Wenn jetzt viele Entwickler problemlos den Framework Code im Kontext Ihrer eigenen Programme debuggen können, ist das ein sehr tiefgreifender Code Review.

Super tolle Sache. Danke Microsoft, danke ScottGu!

Update: Golem hat den Inhalt des Artikel weitergehend ins Deutsche übertragen.

Thursday, October 04, 2007 9:53:48 AM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Thursday, September 06, 2007

Ein Spruch, der sich immer wieder bewahrheitet:

Daten leben länger als die Programme, die die Daten erzeugen

Mein Fall ist nicht so drastisch, das Programm, das die Daten erzeugen kann, lebt noch. Eine Visual Foxpro Applikation hat die Daten erzeugt, eine .NET Applikation soll jetzt damit weiter arbeiten.

Über den Visual FoxPro OLE DB Provider kann man direkt auf die VFP Daten zugreifen. Die restliche Logik kann in der inzwischen eher vertrauten Visual Studio Umgebung programmiert werden:

            string connectionString = "Provider=vfpoledb.1;Data Source=c:\\data.dbc";
            OleDbConnection connection = new OleDbConnection(connectionString);
            connection.Open();
            string selectCmd = "SELECT * FROM table";
            OleDbCommand command = new OleDbCommand(selectCmd, connection);
            OleDbDataReader reader = command.ExecuteReader();
            while (reader.Read())
            {
                // Daten verarbeiten
            }
            reader.Close();
            connection.Close();

Wenn die VFP Anwendung bereits eine saubere Business Schicht implementiert hätte, wäre es besser, auf dieser Ebene die Informationen auszutauschen und nicht auf die rohen Daten zuzugreifen. Ist aber hier nicht der Fall.

Wenn kein Visual Foxpro installiert ist, sollte obiger Download genügen um den OLE DB Provider zu installieren. Unter Vista x64 klappt die Installation zwar problemlos. Nur der Treiber ist eine reine 32Bit Anwendung und Visual Studio erzeugt normalerweise Code für Any CPU. Dieser Code läuft auf einem 64Bit OS, dann entsprechend als 64Bit Anwendung. Der Versuch auf vfpoledb zuzugreifen scheitert:

The 'vfpoledb.1' provider is not registered on the local machine.

Die einzige Lösung, die ich kenne, ist die komplette Solution auf Platform target x86 zu schalten:

VS Build Configuration Manager

Jetzt funktioniert der Zugriff auf die VFP Daten einwandfrei.

Wieder holt einen die Vergangenheit ein und das tolle neue System kann nicht so agieren wie es möchte. Aber das ist der Preis, wenn man nicht alles immer neu erfinden will.

Thursday, September 06, 2007 2:16:56 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Monday, August 27, 2007

JavaScript ist eine dynamische Sprache. Der Code wird nicht kompiliert sondern zur Laufzeit interpretiert. Dadurch wird unter anderem die eval Funktion ermöglicht, die innerhalb eines Skripts einfach JavaScript Code als Parameter entgegen nimmt und ausführt.

Im .NET Framework gibt (gab) es eine Variante von JavaScript: JScript. Damit sollte es möglich sein, aus einem C# Programm heraus diese JScript Funktion aufzurufen. Und tatsächlich ist dies sogar sehr einfach möglich. Man muss sich nur darüber im klaren sein, dass das nur eine Übergangslösung ist.

Aus MSDN VsaEngine:

Use of this type is not recommended because it is being deprecated in Visual Studio 2005; there will be no replacement for this feature. Please see the ICodeCompiler documentation for additional help.

In .NET 3.0 ist es damit bereits Schluss. In Zukunft wird wohl eher die Dynamic Language Runtime DLR dafür verwendet.

Das hilft mir aber jetzt nichts. Die einfachste Lösung habe ich in einem Kommentar zu einem Blog-Eintrag von Rick Strahl gefunden: Evaluating JavaScript code from C#

Man erzeugt sich eine JScript Dll:

  1. Mit Notepad eine Datei mit folgendem Text erzeugen:
    class EvalClass { function Evaluate(expression: String) { return eval(expression, "unsafe"); } }
  2. Die Datei als C:\MyEval.js speichern.
  3. Einen VS2005 Command Prompt (Start, Programs, VS2005, VS2005 Tools) öffnen.
  4. Auf C:\ wechseln.
  5. Folgenden Befehl absetzen:
    jsc /t:library C:\MyEval.js
  6. Es wird eine neue Datei mit dem Namen MyEval.dll erzeugt.
  7. MyEval.dll in das gewünschte C# Projekt kopieren und referenzieren. Zusätzlich muss noch Microsoft.Jscript.dll referenziert werden.
  8. So kann jetzt die JavaScript Eval Funktion aufgerufen werden:
    EvalClass evalClass = new EvalClass();
    object result = evalClass.Evaluate("2+3");

 

Das  funktioniert wirklich so einfach und gut. Was ich später noch ändern musste, war der zusätzlich String Parameter "unsafe" bei der JScript eval Funktion. Ohne diesen Parameter gibt es eine Security Exception, wenn der übergebene JavaScript Code eine neue Funktion deklariert.

Monday, August 27, 2007 10:57:25 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Tuesday, August 14, 2007

Die Überschrift ist kein Roman, sondern, auch wenn die Länge es nicht erahnen lässt, eine Erweiterung der Team Edition for Database Professionals. Microsoft setzt damit die Tradition fort, dass es zu vielen Produkten ein paar kostenlose Erweiterung direkt aus den Entwickler Teams gibt. Über diese Power Tools kann Microsoft auch zwischen den großen Release Zyklen neue Funktionen veröffentlichen:

Microsoft® Visual Studio® 2005 Team Edition for Database Professionals Service Release 1 (Voraussetzung für die Power Tools)

Microsoft® Visual Studio® 2005 Team Edition for Database Professionals Power Tools

Details zu den neuen Funktionen gibt es beim DataDude:

    • Dependency Viewer
    • Refactoring
      • Move Schema
      • Expand Wildcard
      • Fully Quality Name
      • Refactor in to strongly typed DataSet definitions
      • Refactor Command Generator
    • Data Generation
      • Sequential Data Bound Generator
      • Editors for the Data Bound Generator, Sequential Data Bound Generator and RegEx String Generator to make configuration easier
      • The RegEx editor also tries to interpret your CHECK CONSTRAINTs and create a matching RegEx expression that you can use to generate data values that match the constraint definition
      • The RegEx editor can also be used for interactively defining and testing RegEx expressions and evaluate the output visually, which makes it a lot easier to create the right RegEx expression for your value domain.
    • MSBuild Tasks
      • SqlSchemaCompareTask; allows you to compare schemas between two database from the command line using MSBuild.
      • SqlDataCompareTask; allows you to compare the content of tables within two databases from the command line using MSBuild.
    • T-SQL Static Code Analysis
    •  Miscellaneous tools
      • SQL script pre-processor command-line utility, which will expand all SQLCMD includes and variable definitions (sqlspp.exe)
    • Schema Manager API

 

Vorsicht: Nach diesen Updates werden Database Projekte beim ersten Öffnen durch einen Wizard konvertiert und sind dann für ältere Versionen von Visual Studio unbrauchbar.

Viel gespielt habe noch nicht mit den Tools:

Die statische Code Analyse hat mir aber schon mal 66 Warnings in einem Projekt ausgespuckt. Keine dramatischen Verfehlungen, aber leider funktioniert bei mir die "Show Error Help" Funktion nicht. Auch die ausgegeben IDs findet Google noch nicht. Mir fehlen also die Erläuterungen zu den Ausgaben.

Der Schema Dependency Viewer gefällt mir auch sehr gut. Als CodeRush Anwender ist man von dessen References Viewer im C# Code aber verwöhnt. Einfach Shift+F12 und alle Referenzen werden blitzartig dargestellt. Das Gleiche im T-SQL Editor wäre super. Bis dahin dürfen wir jetzt wenigstens schon mit der Maus im Schema Viewer das Kontext Menü aufrufen.

Tuesday, August 14, 2007 11:10:41 AM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, May 23, 2007

IsolatedStorage funktioniert bei mir unter Vista x64 nicht wie erwartet. Ich arbeite als Administrator mit ausgeschaltetem UAC. Ich kann in der IsolatedStorage Dateien erzeugen und anschließend wieder lesen. Was ich nicht kann, ist die abgelegten Dateien auslesen oder löschen.

Zuerst habe ich erfolgreich eine Datei LoginTicket.xml angelegt. Eine Datei Test.xml existiert nicht. Bei folgendem Beispiel Code passiert dann etwas seltsames:

IsolatedStorageFile isoStore = IsolatedStorageFile.GetStore(IsolatedStorageScope.User | IsolatedStorageScope.Assembly, null, null);
// liefert keine Einträge:
string[] test = isoStore.GetFileNames("*");
// liefert den richtigen Stream:
IsolatedStorageFileStream isoStream = new IsolatedStorageFileStream("LoginTicket.xml", FileMode.Open);
// Wirft wie zu erwarten eine FileNotFoundException:
IsolatedStorageFileStream isoStream2 = new IsolatedStorageFileStream("Test.xml", FileMode.Open);

Bei isoStore.GetFileNames("*") sollte eigentlich auch die Datei LoginTicket.xml im Ergebnis dabei sein. Ist sie aber nicht.

Danach kann ich problemlos die Datei öffnen. Der Inhalt würde auch stimmen. Natürlich kann ich keine nicht vorhandene Datei Test.xml öffnen. An der Stelle bin ich dann schon verzweifelt :).

Was geht da schief?

Wednesday, May 23, 2007 3:49:16 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [1]  | 
# Saturday, May 19, 2007

Visual Studio Edition Team Edition for Database Professionals ist (noch) kein Werkzeug zur Datenbank Modellierung. Das Tool spricht eigentlich nur reines T-SQL. Wobei hilft es dann dem Datenbank Entwickler?

Die Datenbank oder genauer das Datenbank Schema wird sehr weitgehend in die Entwicklungsumgebung integriert. Am wichtigsten ist dabei die gelungene Integration in die Versionsverwaltung. Wie der restliche Source Code auch, steckt das Datenbank Schema im Source Control.

Das ging vorher auch schon ansatzweise. Über den Server Explorer konnte man sich alle Objekte aus der Datenbank in ein SQL Server Projekt als Skript importieren lassen. Dabei hat man ein paar Nachteile:

  • Aus den Skripten kann man nicht direkt die Datenbank erzeugen.
  • Die Daten aus der Datenbank stehen nicht zur Verfügung.
  • Die erzeugten T-SQL Skripte enthalten meist mehrere Datenbank Objekte.
  • Änderungen sind schwer nachzuvollziehen.
  • Man hatte keinerlei Unterstützung bezüglich des Inhalts der Skripte.
  • Es ist nicht sichergestellt, dass die Schema Version zum restlichen Code passt.

Natürlich ist es trotzdem wichtig, dass die Datenbank unter Versionskontrolle kommt. Aber bisher war das eher eine Einbahnstraße. Die T-SQL Skripte sind zwar gesichert und versioniert. Man kann mit einiger Handarbeit daraus auch wieder die gewünschte Datenbank erzeugen. Eine echte Integration ist das aber nicht.

Falls man die erzeugenden Skripte einer Datenbank hat, kann man diese einfach mit einem Wizard importieren:

Bei dem Import wird die Information in möglichst kleine Einheiten aufgeteilt, die einzeln als SQL Skripte gespeichert werden.

Eine Testtabelle wird dann wie hier automatisch in unterschiedliche Skripte zerlegt abgespeichert. Dadurch lassen sich Änderungen später leichter nachvollziehen. Der Vergleich mit älteren Versionen im Source Control fällt leichter und ist sinnvoller.

Falls man anschließend an der originalen Datenbank weiter arbeitet, dann ist es nicht praktisch, jedesmal das Schema über diesen Import Wizard zu laden. Dazu gibt es ein sehr mächtiges Tool, den Schema Compare.

Update: Hier geht es weiter: VSTS Database Professional (3)

Saturday, May 19, 2007 7:43:07 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Friday, May 11, 2007

Ursprünglich gab es für die Visual Studio Team System 3 Editionen:

  • Team Edition for Software Architects
  • Team Edition for Software Develepers
  • Team Edition for Software Testers

Mittlerweile hat Microsoft eine weitere Edition nachgereicht, die Team Edition for Database Professionals.

Ich bin kein Database Administrator (DBA) und erst recht kein Database Professional. Dennoch ist die meiste Zeit das SQL Server Management Studio offen, denn das Datenbank Design kann ich leider nicht delegieren. Somit ist jede Tool-Unterstützung willkommen, insbesondere in Form einer integrierten Team Edition.

Der Einstieg in die Team Edition begann mit einer Enttäuschung. Ich habe eine neues Database Projekt angelegt und Add New Table gewählt:

Ups, kein Table-Designer? T-SQL ist nicht meine Muttersprache. Die üblichen Selects und Joins kriege ich schon hin. Meine Tabellen möchte ich aber mit mehr Unterstützung entwerfen. Wir werden später noch erfahren, dass hier ein anderes Konzept verfolgt wird. Hinter vorgehaltener Hand erfährt man auch, dass für spätere Versionen eventuell mehr Unterstützung geplant ist. Im Moment sollten man seine Schema Objekte anders erzeugen.

Wenn auch ohne Designer kriegt man aber bezüglich Schema Visualisierung und Validierung schon einiges geboten:

Schema View

Im Schema View sieht man alle Schema Objekte, die in vielen einzelnen sql-Dateien definiert werden, übersichtlich angeordnet. Änderungen am Schema werden direkt validiert und man erhält entsprechende Warnungen.

Man kann auch die Eigenschaften der Objekte sich im Property Grid darstellen lassen:

Properties

Es hat einige Zeit gedauert, aber irgendwann habe ich aufgegeben dieser Eigenschaften Anzeige das graue Mäntelchen auszuziehen. Es ist nur eine Anzeige. Ändern kann man nichts.

An der Stelle sollte man aber nicht aufgeben. Wie bei vielen Tools, muss man erst die Philosophie verstehen. Dann arbeitet man nicht mehr gegen das Tool, sondern damit. In Kürze werde ich mehr dazu schreiben, wie ich mittlerweile den Datenbank Profi einsetze. (Update: VSTS Database Professional (2))

Friday, May 11, 2007 11:34:29 AM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Friday, May 04, 2007

Eine der Nebenwirkungen der Softwareentwicklung sind die entstehenden Anwendungen. Es kommt tatsächlich vor, dass die eigene Software verwendet wird. Das erfreut zunächst den Entwickler auch. Für jedes abgeschlossene Projekt gibt es als Bonus oft auch eine neue, spannende Herausforderung.

Doch hin und wieder passiert es: Alte Applikationen müssen erweitert oder angepasst werden. Am Namen erkennt man das Projekt meist noch. Das war doch noch in dieser älteren Version der Entwicklungsumgebung geschrieben? Funktioniert das überhaupt noch? Verstehe ich die Applikation noch? Brrrr.

Wie soll man reagieren?

Den Job hinwerfen? Neuer Arbeitgeber, neue Projekte?

Geht im Moment nicht.

Alles leugnen? Das war doch der andere Programmierer!

Geht nicht, ich war schnell als Übeltäter Urheber identifiziert.

Ausflüchte? Der Source Code ist verloren gegangen!

Glaubt mir keiner. Dafür bin ich zu sehr mit Backups beschäftigt.

Wichtigere Projekte vorschieben?

Das funktioniert sogar. Öfters stimmt das auch. Hilft aber nicht immer.

Gestern hatte ich noch Glück. Die Applikation war nur 2 Jahre alt. Darunter lagen aber noch Teile eines 3 Jahre alten eigenen Frameworks, dass auf dem damaligen Mere Mortals .NET Framework (MM .NET) basiert. Natürlich alles in C# mit dem .NET 1.1 Framework entwickelt.

Visual Studio 2003 läuft nicht mehr unter Vista und MM.NET war für mich eine Sackgasse. Das Framework war zu nahe an seiner Wurzel, dem guten alten Visual Foxpro, und hatte zu wenig Verständnis für .NET selbst. Also habe ich alles nach .NET 2.0 und Visual Studio 2005 portiert und die Abhängigkeiten zu Altlasten minimiert.

Jetzt habe ich einen Tag später die gleiche Funktionalität wie vorher und kann mit den Anpassungen anfangen. Bei der nächsten gewünschten Änderungen an gereifter Software werde ich meine List der möglichen Ausweichstrategien überdenken! :)

Friday, May 04, 2007 10:31:07 AM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, April 18, 2007

Es gibt viele Möglichkeiten, die Felder einer Eingabemaske zu beschriften. Die Labels darüber oder daneben? Fette Schrift oder normal? Rechts- oder linksbündig.

Geschmacksfrage? Sicher auch! Nur nach wessen Geschmack?

James Avery hat ein paar Artikel zu dem Thema in The Art of Label Placement zusammengetragen. Unter anderem wurde untersucht, wie die Augen der Anwender über den Bildschirm gleiten und wie lange sie zur Erfassung der Informationen brauchen. Sein Ergebnis: Nicht fette Beschriftung über dem Eingabefeld.

In den Artikeln steckt aber noch mehr Interessantes. Selber lesen schadet nicht :)

Und wie der Zufall so will, habe ich gerade berichtet, wie toll man jetzt mit ASP.NET seine Controls komplett mit CSS gestalten kann. Also habe ich den die Stylesheets kurz bearbeitet und die Labels umpositioniert. Das war eine Sache von wenigen Minuten. Schon sind in der Applikation alle ASP.NET DetailsViews verbessert.

Vorher:

Nachher:

Ich gebe zu, als ich gerade dabei war, habe ich auch ein wenig an den Farben gedreht. Mehr möchte ich dann aber mit CSS gar nicht machen. Die Idee sollte ja sein, dass ein Designer mit mehr ästhetischem Empfinden diese Aufgabe übernimmt. Nur habe ich gerade keinen Designer zur Hand gehabt :).

Wednesday, April 18, 2007 9:15:21 AM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Monday, April 16, 2007

Meine letzten Projekte waren alles Windows Forms Projekte. Eines dieser Projekte hat sogar noch einen kurzen Ausflug Richtung WPF erlebt. Also durfte ich kurz in die Zukunft der Desktop Applikationen hineinschnuppern. Ein sehr geniales, zukunftsweisendes Konzept. Mit vielen Ideen, die ihre Wurzeln auch bei ASP.NET haben. Alles ist noch sehr frisch und spannend. Dafür liefert Google nur sehr zögerlich Antworten, selbst für triviale Probleme.

Jetzt bin ich nach langer Zeit mal wieder bei ASP.NET gelandet. Eine im Vergleich zu WPF etablierte Technologie. Weniger coole neue Konzepte, dafür erprobt und gut dokumentiert. Insbesondere die erweiterbare Architektur, wie zum Beispiel das Provider Model, wird gelobt.

Ein weitere gute Architektur sind die Control Adapter. Für jedes eingebaute ASP.NET Control lässt sich über einen Control Adapter die HTML Ausgabe komplett neu programmieren, ohne dass sich die Verwendung und Programmierung des Controls selbst ändert. Leider haben die eingebauten ASP.NET Controls das Problem, recht unsauberen HTML Code zu erzeugen: Viele Inline Styles, Tabellenkonstrukte und das Ganze ist nicht W3C konform.

Mit Schmunzeln denke ich an eine ältere DotNetRocks Show zurück. Carl Franklin, der Host, wurde von seinem Gast gefragt:

Wozu verwendet man bei HTML eigentlich Tabellen?

Um Elemente auf der Webseite zu arrangieren.

Nein, für Tabellen!

Heute sollten man Webseiten mit XHTML und CSS entwickeln. Tabellen sollten man sich für tabellarische Daten aufsparen. Um da auch mit ASP.NET mitspielen zu können hat Microsoft vor kurzem die ASP.NET 2.0 CSS Friendly Control Adapters 1.0 veröffentlicht. Mit relativ wenig Aufwand kann man damit seine bestehende Applikation aufrüsten:

  • Microsoft hört auf seine Entwickler und verbessert eine existierende Technologie.
  • Microsoft liefert die Adapter mit Source Code. Die Ausgabe kann also angepasst werden.
  • Die ursprüngliche Architektur war so flexibel, dass man ohne tiefgreifende Änderung diese Anpassung nachrüsten kann.

Auch mit diesen Controls sind die ausgegeben Seiten noch nicht komplett W3C konform. Die Validierung bemängelt noch ein paar Kleinigkeiten. Dafür gäbe es zwar auch Lösungen. Die sind aber eher "Hacks", die ich nicht implementieren werde.

Monday, April 16, 2007 4:21:04 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Sunday, March 18, 2007

Ich spüre die Verwirrung und habe das Gefühl mich zu rechtfertigen :).

Es gibt das .NET 1.0. Das war vor meiner Zeit. Soweit ich weiß, gibt es aber keine ausgelieferten Applikation damit. Der Version 1.1 kam sehr schnell hinterher.

.NET 1.1 war dann sehr lange das einzige verwendete .NET Framework. Alles war einfach. Wollte jemand ohne Installationsroutine ein .NET Programm laufen lassen, brauchte er die Laufzeitumgebung des Frameworks. Das hat man damals auch so dokumentiert und alles war gut.

Jetzt gibt es .NET 2.0 und hui auch schon .NET 3.0. Wer mutig ist hat sogar schon .NET 3.5 Beta dabei.

Leider wissen das die System Requirements Dokumente von damals nicht mehr. Die schreiben ganz brav noch von einem .NET 1.1 Framework. Nur dummerweise kann es sein, dass Software, die mit dem .NET 1.1 Framework entwickelt wurde auch mit .NET 2.0 läuft. Jetzt wird es zu kompliziert. Jetzt beginnen die Anwender mitzudenken. Und werden verwirrt!

Die Software verlangt nach 1.1, läuft aber auch mit 2.0. Betrug? Absichtliche Verwirrung?

Die sauberste Lösung, wäre per Manifest das gewünschte Framework zu verlangen. Und zwar das, gegen das die Applikation entwickelt und getestet wurde. Dann ist alles wie es der Anwender erwartet und der Entwickler hat ein Freiheitsgrad weniger zu testen.

Natürlich bliebe noch die Methode, einen großen Installer zu bauen, dort das benötigte Framework mit reinzupacken und das alles zu verteilen. Das geht auch immer. Nur hat man dann alle Blogger am Hals, die die Größe der Installationspakete kritisieren. Amüsanter weise wäre man mit den .NET Framework Laufzeitinstallationspaketen ziemlich genau in Größenordnung vom Adobe Acrobat Reader. Und der kriegt seine 20MB ziemlich oft um die Ohren gehauen.

Lustig finde ich dann die Befürchtung, dass Softwareentwickler "[...] Meldungen wohl jeweils manuell programmieren" müssen: Wenn es mal soweit kommt, dass Entwickler selbst programmieren, dann ist aber alles zu spät!

:)

Wenn meine Zeitmaschine endlich fertig ist, werde ich nicht nur gegen vorhandene, sondern auch zukünftige Laufzeitumgebungen testen. Oder noch viel besser, ich werde alle alten Applikation immer auf das neueste Framework portieren. Das wäre toll!

Sunday, March 18, 2007 12:03:55 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Friday, March 16, 2007

Das bisherige WPF Project wurde jetzt zu Gunsten eines dringenderen ASP.NET Projekts auf Eis gelegt. Schade, WPF hatte gerade angefangen Spaß zu machen. Ein bisschen Warten schadet dabei aber sicher nicht. Vielleicht finden sich beim nächsten Ansatz mehr Antworten als Fragen über Google.

Gegen WPF ist ASP.NET 2.0 eine sehr ausgereifte und weit verbreitete Technologie. Viele Fragestellungen sind gelöst und gut dokumentiert. Dafür haben wir einen sehr jungen Webserver in Vista gekriegt IIS7. Der geht mit ASP.NET nativ sehr viel besser um. Hat man gelesen. Umso überraschter war ich, daß das F5 Debugging mit Visual Studio nicht geht.

Es gibt diverse Lösungen. Die im Moment aber noch in die Kategorie Workaround gehören:

Fix problems with Visual Studio F5 debugging of ASP.NET applications on IIS7 Vista

Friday, March 16, 2007 8:47:48 AM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Friday, February 23, 2007

Inzwischen konnte ich das Service Pack 1 für den Team Foundation Server erfolgreich installieren. Eine der neuen Features ist die Möglichkeit externe Verbindungen zu dem Server zu ermöglichen.

Der TFS stellt seine Dienste über Web Services zur Verfügung. Mit dem Service Pack unterstützt er jetzt auch Basic und Digest Authentifizierung auf diesen Diensten. Bisher ging nur Integrated Windows Authentifizierung. Windows Authentifizierung geht aber nicht über das Internet. Jetzt ist aber zusammen mit HTTPS/SSL und den neuen Authentifizierungen der sichere externe Zugriff möglich.

Das liest sich ganz einfach und wie vom TFS gewohnt, ist die Implementierung nur für ganz Mutige: Walkthrough: Setting up Team Foundation Server with Secure Sockets Layer (SSL) and an ISAPI Filter

Mit den üblichen Zutaten:

  • Rumspielen in der Registry.
  • Editieren von Tabelleneinträgen mit dem SQL Server  Management Studio.
  • Ausführen von Programmen über die Shell.
  • Erstellen von Konfigurationsdateien mit Notepad.
  • Viel Herumklicken in diversen Management Konsolen.

Rootzertifikate kann man unter Vista x64 übrigens nicht mehr wie in dem Walkthrough beschrieben installieren. Über die CA Console direkt geht es aber.

Natürlich müssen alle beteiligten Firewalls den Zugriff erlauben, bzw. richtig weiterleiten. In unserem Fall also noch einmal ISA 2003 gefolgt von einmal Suse Linux mit iptables.

Schließlich hat es aber geklappt. Jetzt kann ich mit meinem neuen PC von zu Hause auf unsere Team Projekte zugreifen und fleißig weiter arbeiten. Muss ich jetzt ja auch, wenn ich vorher die ganze Zeit am TFS konfigurieren bin.

Friday, February 23, 2007 11:59:56 AM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, February 14, 2007

Mit dem Präfix clr-namespace kann man in XAML .NET Namespaces auf XML Namespaces mappen. Nur leider hat das bei mir nicht sauber funktioniert. Siehe auch XAML - clr-namespace - XamlParseException

Inzwischen habe ich das Problem genauer untersucht. Der XAML Parser versucht auf eine bestimmte Version der Assembly zuzugreifen: "Could not load file or assembly 'SPCatClient, Version=1.0.2596.20327...". Nur leider befindet sich zu dem Zeitpunkt bereits eine neuere Version im Debug Verzeichnis. Die Fehlermeldung ist also richtig. Aber wieso wird die Versionsnummer getestet? Die kann ja in diesem Fall gar nicht stimmen. Wahrscheinlich liegt es daran, dass ich alle Assemblies mit unserem CPTec Schlüssel signiere. Sie also strong-named Assemblies sind.

Die Lösung ist dann ganz einfach. Man muß nur bei den Projekteigenschaften, die automatische Nummerierung der Assembly Version abschalten. Standardmässig verwendet Visual Studio 2005 die Assembly Version "1.0*". Damit wird die Build Number und Revision Number automatisch bei jeder Compilierung erzeugt. Wenn man statt dessen eine feste Versionsnummer einträgt (z.B: 1.0.0.0), ist der XamlParser glücklich.

Assembly Information - Project Properties

Wednesday, February 14, 2007 10:46:42 AM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Monday, February 12, 2007

Ich möchte Windows Forms und WPF Windows zusammen in einer Applikation verwenden. Jetzt ist mir aufgefallen, dass mein erster Ansatz noch nicht vollständig funktioniert: In den WPF Windows ist keine normale Tastatureingabe möglich!

Der Message Loop läuft im Windows Forms Kontext und da müssen wir die WPF Windows mit einklinken. Das geht dann doch nur über die Interop Assembly WindowsFormsIntegration. Also habe ich eine Referenz darauf zu dem Projekt hinzugefügt. Jedes WPF Window wird jetzt so geöffnet:

            Window1 window = new Window1();
            System.Windows.Forms.Integration.ElementHost.EnableModelessKeyboardInterop(window);
            window.Show();

ElementHost.EnableModelessKeyboardInterop() sorgt dafür, dass das übergebene Window alle Tastatureingaben von dem Windows Form Message Loop erhält.

Bis jetzt sieht alles gut aus.

Monday, February 12, 2007 5:33:28 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Friday, February 09, 2007

Update: Inzwischen habe ich einen Workaround gefunden.

 

Ich möchte bei der Ausgabe das Datum gemäß den Benutzereinstellungen formatieren. Dazu habe ich einen DateTimeConverter geschrieben, der von IValueConverter abgeleitet ist. Dieses Objekt möchte ich natürlich an mehreren Stellen in der Applikation verwenden. Dann gehört das in die Application.Resources:

<Application x:Class="CPTec.SPCat.SPCatClient.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:CPTec.SPCat.SPCatClient"

    Startup="AppStartup"
    Exit="AppExit"
    ShutdownMode="OnMainWindowClose"
    >
    <Application.Resources>
      <local:DateTimeConverter x:Key="dateTimeConverter" />
    </Application.Resources>
</Application>

Damit steht unter dem Key "dateTimeConverter" dieser Converter über zur Verfügung. Um Beispielsweise eine Tabellenspalte darüber darzustellen, ist folgende Definition möglich:

<GridViewColumn 
    DisplayMemberBinding="{Binding Path=LastUpload, Converter={StaticResource dateTimeConverter}}"
    Header="Last Upload" />

Bei der Datenbindung laufen damit alle Ausgaben über den DateTimeConverter und die Formatierung passt.

Das Problem ist nur, dass dieser Code nicht reproduzierbar funktioniert. Die meiste Zeit wirft das InitializeComponent() des Windows mit der ListView eine XamlParseException:

"Could not load file or assembly 'SPCatClient, Version=1.0.2596.20327, Culture=neutral, PublicKeyToken=af25da8d2987a9b1' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) Error in markup file 'SPCatClient;component/app.xaml' Line 11 Position 8."

Durch Neustart von Visual Studio und wildem "Rebuild Solution" funktioniert es dann wieder. Nur um dann im nächsten Versuch wieder über die gleiche Stelle zu stolpern.

Soweit ich das verstehe, muss ich bei der Definition xmlns:local="clr-namespace:CPTec.SPCat.SPCatClient" keine Assembly eingeben, wenn ich mich in genau dieser Assembly befinde. Aber irgendwas verknotet sich da. Im Moment ist das Ganze wieder auskommentiert. Das sollte aber nicht so bleiben.

Friday, February 09, 2007 11:48:05 AM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Thursday, February 01, 2007

 Hurra! Hurra! So ein schöner Dialog:

Und nochmals Hurra!

Wieso die ganze Aufregung? Bisher ist jeder Versuch, dieses Service Pack zu installieren gescheitert. Die Wiederherstellung des Servers war beim ersten Versuch auch nicht gerade einfach. Bei allen weiteren Versuchen hatte ich vorher ein Image der Festplatte angelegt. Das ging dann schon schneller.

Jetzt kam über WSUS ein Update für den SharePoint Portal Server. Nach den bisherigen Erfahrungen war mir das zu heikel. Über Nacht hatte ich wieder eine Sicherung der kompletten Platte durchgeführt. Wie zum Trotz installierte sich das Update aber ohne Probleme. Mutig wollte ich es noch mal mit dem Service Pack für den Team Foundation Server probieren.

Inzwischen hatte sich Microsoft des Feedbacks angenommen und den Fall geschlossen (der gemeldete Workaround ist jetzt von mir :). Denkste. Ging natürlich wieder nicht. Genau wie bei allen vorherigen Versuchen. Diesmal habe ich aber eine neue mögliche Lösung gefunden. Der Vorschlag stammt aus der Microsoft Forum:

Im Fehler Protokoll kommt folgender Eintrag vor:

Setting SQL Server database 'TFSWorkItemTracking' into single user mode...
User does not have permission to alter database 'TFSWorkItemTracking' or the database does not exist.
ALTER DATABASE statement failed.

Was der Installer nicht kann, kann vielleicht der Setup User? Also SQL Server Management geöffnet und folgenden Befehl ausgeführt:

ALTER DATABASE TFSWorkItemTracking SET SINGLE_USER

Das geht seltsamerweise. Noch mal das Service Pack anstoßen: Es klappt! Der obige Dialog ist Belohnung für viel Mühe.

Thursday, February 01, 2007 12:53:55 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, January 31, 2007

Im Moment bin ich ein bisschen irritiert, wie sich WPF unter die CultureInfo verhält.

Ich verwende ein englisches Windows XP und habe im Control Panel unter Regional Options die "Standards and formats" auf German gestellt. Unter Windows Forms hat das standardmäßig dazu geführt, dass z.B: das Datum deutsch formatiert wurde. Auch Office und andere Anwendungen respektieren diese Einstellung.

Jeder Thread hat unter anderem zwei Properties: CurrentCulture und CurrentUICulture.

CurrentCulture: Das ist die Eigenschaft die ich im Control Panel eingestellt habe und bestimmt, wie Ausgaben formatiert werden. Bei mir "de-DE".

CurrentUICulture: Das ist die Sprache des Betriebssystems und bestimmt welche Elemente aus den Ressourcen verwendet werden. Quasi die Übersetzung. Bei mir "en-US".

Unter WPF kriege ich aber bisher nur die CurrentUICulture zu sehen. In meinem Fall eben "en-US". Folglich ist auch das Datum falsch formatiert. Ich möchte, dass die Einstellungen im Control Panel honoriert werden. Also basteln wir einen DateTimeConverter:

using System;
using System.Windows.Data;
using System.Globalization;
using System.Threading;

namespace CPTec.SPCat.SPCatClient
{
    /// <summary>
    /// Kurze Datums+Zeit Darstellung. Verwendet CurrentCulture und nicht CurrentUICulture
    /// </summary>
    [ValueConversion(typeof(DateTime), typeof(String))]
    public class DateTimeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            DateTime date = (DateTime)value;
            return date.ToString("g", Thread.CurrentThread.CurrentCulture);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            string strValue = value.ToString();
            DateTime resultDateTime;
            if (DateTime.TryParse(strValue, Thread.CurrentThread.CurrentCulture, DateTimeStyles.None, out resultDateTime))
            {
                return resultDateTime;
            }
            return value;
        }
    }
}

Die Klasse implementiert IValueConverter einem neuen Interface in .NET 3.0 das beim Data Binding die Konvertierung von dargestelltem Wert und gebundenem Wert in beide Richtungen erlaubt.

Das Datum wird gezielt mit dem IFormatProvider aus CurrentCulture und nicht CurrentUICulture formatiert.

Der DateTimeConverter soll in der ganzen Applikation zur Verfügung stehen, also kommt er zu den Application.Resources in App.xaml:

<Application x:Class="CPTec.SPCat.SPCatClient.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:test="clr-namespace:CPTec.SPCat.SPCatClient" 

    Startup="AppStartup"
    >
    <Application.Resources>
      <test:DateTimeConverter x:Key="dateTimeConverter" />
    </Application.Resources>
</Application>

Und so verwende ich diese Resource dann beispielsweise in einer GridViewColumn:

  <GridViewColumn Header="Last Upload" 
    DisplayMemberBinding="{Binding Path=LastUpload,
      Converter={StaticResource dateTimeConverter}}" />

Dabei ist LastUpload vom Typ DateTime.

Obwohl das sicher eine Lösung für das Problem ist, frage ich mich schon, wieso das sinnvolle Konzept der getrennten Einstellung von Sprache und Formatierung wieder verschwunden ist? Oder zumindest nicht standardmäßig so wie bisher funktioniert. Oder übersehe ich etwas?

Wednesday, January 31, 2007 5:38:34 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Friday, January 26, 2007

Die Benutzeroberfläche soll auf den Anwender reagieren, obwohl gerade über HTTP Daten von einem SQL Server geladen werden. Also wird diese Arbeit über einen Background Thread erledigt. Der Fortschritt und das Ende dieser Aufgabe soll natürlich dem Anwender dargestellt werden. Nur darf dieser neue Thread nicht auf die Elemente der Benutzeroberfläche zugreifen. Die gehört dem Thread, der sie erzeugt hat. Andere Threads dürfen da nicht ran. Das gilt sowohl für Windows Forms als auch für die Windows Presentation Foundation.

In Windows Forms haben dazu die Controls ein Property InvokeRequired, das angibt, ob man mit Invoke oder BeginInvoke in den UI Thread Kontext wechseln muss. Um das ganze zu Kapseln habe ich den Background Threads immer eine Referenz auf ein Control mitgegeben. Damit konnte die Callbacks wieder auf dem UI Thread stattfinden. Ist aufwendig und man hat in der Business Logik Referenzen auf UI Elemente.

Mit .NET 3.0 gibt es für WPF den Dispatcher und ein DispatcherObject. Damit wird die Kontrolle über den Thread Kontext vereinfacht. Man leitet seine Hintergrund-Arbeiter von DispatcherObject ab und kriegt die Möglichkeit für UI Thread Aufrufe quasi geschenkt.

Und jetzt das Angenehme: Dieser neue Dispatcher funktioniert mit Windows Forms auch. Es wird also nicht nur einfacher, sondern man muss nicht mit unterschiedlichen Methoden für jede UI Technologie arbeiten.

Friday, January 26, 2007 10:37:18 AM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, January 24, 2007

Mit System.Windows.Forms.Integration kann man Windows Forms Controls in WPF Fenstern und umgekehrt darstellen. Ich möchte aber klassische Windows Forms Fenster und neue WPF Fenster parallel darstellen. Das heißt ich mische auf Fenster und nicht auf der darunter liegenden Control Ebene.

Ein einem WPF Projekt kann man ganz einfach auch eine Windows Forms anzeigen. Man muss nur eine Referenz auf System.Windows.Forms hinzufügen und kann wie bisher Formen erzeugen:

Form1 form = new Form1()
form.Show()

Die so erzeugte Windows Form funktioniert offensichtlich problemlos. Nach einigen Tests ist mir aber ein irritierendes Verhalten aufgefallen: Beim Login setze System.Threading.Thread.CurrentPrincipal auf einen eigenen IPrincipal. Nach einem Klick auf einen Button in der Windows Form ist der CurrentPrincipal aber wieder auf GenericPrincipal gesetzt. Der Thread ist aber der gleiche. Irgendwie ist in der Message Loop dieser Event anders aufgerufen wurden, als in meiner reinen Windows Form Applikation davor.

Nach einigem Debuggen und Stirnrunzeln habe ich eine Lösung gefunden. In der Windows Form Applikation wurde der Message Loop mit

System.Windows.Forms.Application.Run(new LaunchForm());

gestartet. Die WPF Applikation startet aber über System.Windows.Application. Soweit ich das verstehe, kriegen die Windows Forms davon nichts mit. eine Lösung scheint darin zu bestehen, als letzten Befehl im System.Windows.Application.Startup Event noch eine Windows Forms Applikation zu starten

System.Windows.Forms.Application.Run();

Dieser Workaround scheint zu funktionieren. Der Thread.CurrentPrincipal ist auch nach Windows Forms Messages richtig gesetzt. Ich hoffe, dass ich mir damit keine anderen, unerwünschten Nebeneffekte einhandle.

Wednesday, January 24, 2007 2:54:59 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Tuesday, January 23, 2007

Irgendetwas verstehe ich am ObjectDataProvider nicht oder das Design hat eine Schwäche.

Im ersten Versuch habe ich den ObjectDataProvider in XAML definiert und über IsInitialLoadEnabled="False" wollte ich verhindern, dass der ObjectDataProvider bei der Initialisierung bereits Daten zieht. Der ObjectDataProvider soll seine Daten über eine statische Methode mit einem Parameter beziehen. Den Objekt Typ, den Methodennamen und einen Null-Paramater hatte ich in XAML definiert. Erst beim Aufruf einer Methode im Code-behind sollte die Daten erstmalig gelesen werden. Vorher ist der Parameter noch nicht bekannt.

Das hat auch geklappt. Aber nur mit einem Fehler im Debug-Output. Über Reflection konnte der ObjectDataProvider keine passende Signatur in angegeben Klasse finden. Klar, der Parameter ist ja auch nicht vom Typ System.Object, sondern ein eigener Typ BinaryHash. Auf den ersten Blick sah das natürlich so aus, als ob gar nicht versucht wurde, Daten zu laden.

Aber wieso wurde versucht Daten zu laden, obwohl IsInitialLoadEnabled="False" gesetzt war? Soweit ich das verstehe, hilft dieser Parameter nichts, wenn man den ObjectDataProvider in XAML definiert.

Der ObjectDataProvider (vererbt über DataSourceProvider) implementiert ISupportInitialize und der XAML Loader ruft entsprechend BeginInit und EndInit auf. In EndInit vom DataSourceProvider wird über Umwege schließlich Refresh aufgerufen. IsInitialLoadedEnabled wird dabei gar nicht getestet.

Wenn ich das alles richtig verstehe, dann darf muss man in diesem Fall den ObjectDataProvider im Code erzeugen. Nur dann ist sicher gestellt, dass das Objekt erst Daten bezieht, wenn man sie wirklich benötigt.

Tuesday, January 23, 2007 6:19:38 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 

Update: Die Lösung erzeugt einen Fehler im Debug-Output. Mehr im nächsten Posting.

 

Die Windows Presentation Foundation (WPF) ist die neue Technologie von Microsoft für grafische Benutzeroberflächen. Mit .NET 3.0 steht diese Erweiterung unter Windows XP und Vista zur Verfügung.

Weil das Ganze aber noch sehr neu ist, sind weder die Tools, noch die Dokumentation oder Beispiele ausgereift. Im Netz findet man viel Code, der mit den Betas, aber nicht mit der Release Version funktioniert. Viele stürzen sich mit Begeisterung auf Animationen und die 3D Funktionen. Das alltägliche Basteln von eher normalen Dialogen ist noch die Ausnahme.

Ich in meinem aktuellen Projekt, wird ein Großteil der Dialoge noch mit der ausgereifteren Windows Forms Technologie erstellt. Aber an einigen Stellen möchte ich doch die Vorteile der WPF nutzen. Insbesondere der Umgang mit Grafiken soll darauf basieren. Auch in diesen Bereichen will man aber mal eine Liste oder ein Grid darstellen. Der Inhalt soll natürlich per Datenbindung aus den Business Objekten kommen.

Die erste Aufgabe war, eine kleine Liste mit Dateinamen und einem Datum darzustellen. Aus dem Business Objekt kriege ich diese Liste als BindingList<T>. In Windows Forms erzeugt man einfach eine ObjectDataSource zu den Detailinformationen (hier UploadedFilesNameDateInfo). Zieht eine BindingSource auf die Form. Setzt dessen DataSource auf die gerade erzeugte ObjectDataSource. Am DataGridView setzt man die DataSource auf diese BindingSource. Es erschienen die passenden Zellen im Grid und im Designer arrangiert man alles wie gewünscht. Im Exzerpt sieht das dann in *.Designer.cs etwa so aus:

this.uploadedFilesNameDateInfoBindingSource.DataSource = 
	typeof(CPTec.SPCat.Business.Blobs.UploadedFilesNameDateInfo);
this.dataGridView1.DataSource = this.uploadedFilesNameDateInfoBindingSource;

Die Daten sollen nicht beim Laden der Form gezogen werden, sondern erst zu einem späteren Zeitpunkt. Das Business Objekt braucht auch einen Parameter:

private void DisplayHashInfo(BinaryHash binaryHash)
{
    uploadedFilesNameDateInfoBindingSource.DataSource = 
        UploadedFilesNameDateList.GetUploadedFilesNameDateList(binaryHash));
}

In WPF ist die ganze Datenbindung wesentlich mächtiger geworden, dafür sind die einfachen Dinge zumindest am Anfang nicht so offensichtlich. Man kann einen ObjectDataProvider definieren und diesem eine Methode mit Parametern angeben, von dem er die Daten beziehen soll. Die ListView mit GridView wird an diesen DataProvider gebunden.

Fast alle Beispiele definieren diese Objekte über XAML. Damit werden alle Objekte beim Laden der Form initialisiert. Ich will aber erst später die Daten beziehen.
Alternative könnte man die Objekte im Code erzeugen. Damit hat man die Kontrolle über den Ablauf. Grundsätzlich ist aber XAML gerade für die Objekte-Erzeugung gedacht und deswegen möchte ich diesen Weg nicht bei der ersten Schwierigkeit verlassen.

Mit IsInitialLoadEnabled kann man dem ObjectDataProvider sagen, dass er ohne Daten erzeugt werden soll. Als ersten Parameter erhält er Null. Dieser Parameter wird dann per Code später gesetzt und der DataProvider zum Laden der Daten veranlasst.

XAML:

<Window x:Class="CPTec.SPCat.SPCatClient.Window2"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:blobs="clr-namespace:CPTec.SPCat.Business.Blobs;assembly=SPCat.Business"
    xmlns:system="clr-namespace:System;assembly=mscorlib" 
    Title="CPTec.SPCat.SPCatClient" Height="100" Width="300"
    >
  <Window.Resources>
    <ObjectDataProvider x:Key="odpUploadedFiles"
                     ObjectType="{x:Type blobs:UploadedFilesNameDateList}"
                     MethodName="GetUploadedFilesNameDateList"
                     IsInitialLoadEnabled="False" >
      <ObjectDataProvider.MethodParameters>
        <system:Object>null</system:Object>
      </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
  </Window.Resources>

  <ListView Grid.Row="1" Name="UploadedFilesInfos"
            ItemsSource="{Binding Source={StaticResource odpUploadedFiles}}"
            MouseDoubleClick="DoubleClick" >
    <ListView.View>
      <GridView>
        <GridViewColumn DisplayMemberBinding="{Binding Path=Name}"
                        Header="Name" />
        <GridViewColumn DisplayMemberBinding="{Binding Path=LastUpload}"
                        Header="Last Upload" />
      </GridView>
    </ListView.View>
  </ListView>
</Window>

Code-behind:

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;
using System.Windows.Data;

using CPTec.SPCat.Business.Settings;
using CPTec.SPCat.Business.Blobs;

namespace CPTec.SPCat.SPCatClient
{
    public partial class Window2 : System.Windows.Window
    {
        public Window2()
        {
            InitializeComponent();
        }

        private void DoubleClick(object sender, MouseButtonEventArgs e)
        {
            SettingsHashList list = SettingsHashList.GetSettingsHashList();
            DisplayHashInfo(list[0].Value);
        }

        private void DisplayHashInfo(BinaryHash binaryHash)
        {
            ObjectDataProvider odb = 
                    (ObjectDataProvider)this.Resources["odpUploadedFiles"];
            odb.MethodParameters[0] = binaryHash;
            odb.Refresh();
        }
    }
}

Der MouseDoubleClick Event am ListView dient nur dazu, um das nachträgliche Laden der Liste zu verdeutlichen.

Das sieht ja schon mal ganz gut aus. Nur wieso ist das Datum nicht richtig formatiert? Ich denke es geht mit dem IValueConverter weiter...

Tuesday, January 23, 2007 11:43:41 AM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, December 20, 2006

Update: Inzwischen hat es geklappt.

Es sieht wohl so aus, dass auch der neue Server allergisch auf das Service Pack reagiert.

Ich bin aber offensichtlich nicht allein. Bei Microsoft Connect ist der Fehler schon als Feedback gemeldet: TFS Service Pack 1 (VS80sp1-KB926738-X86-ENU.exe) installation fails with error 1603 and leaves TFS installation in inconsistent state

Jetzt spiele ich das Image zurück und warte mal auf das Service Pack 1a :(

Wednesday, December 20, 2006 6:53:39 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 

Das lang erwartete Visual Studio 2005 Service Pack 1 wurde veröffentlicht. Alleine die Tatsache, dass von diesem Service Pack eine Beta veröffentlicht wurde, zeigt wie umfangreich die Änderungen sind. Auf meinem lokalen Rechner läuft die Team Edition "Software Developer". Das Service Pack hat einige Zeit geackert, sich aber erfolgreich installiert.

Die Sourcen liegen alle im Team Foundation Server. Der wurde natürlich auch mit einem Service Pack bedacht. Also auch gleich installieren. Vor dem Service Pack braucht es noch ein separates Update. Dieser Teil ging problemlos. Jetzt also das Service Pack selbst installieren. Alles Fragen abnicken und los. Das dauert natürlich. Nebenbei gibt es andere Dinge zu erledigen. Später noch mal auf den Server geschielt: Kein Setup mehr. Normalerweise muss kriegt man ja noch eine Erfolgsmeldung präsentiert. Egal, die Verbindung testen.

Fehler! Irgendwas von Zugriff nicht erlaubt. IP Restrictions. Der IIS Manager bestätigt das. Auf die Seiten Team Foundation Servers (TFS) darf nur lokal zugegriffen werden. Das macht natürlich keinen Sinn. Das gleiche Phänomen bei dem SharePoint Seiten. Nur 127.0.0.1 soll da hindürfen? Also beide Seiten umkonfiguriert. Jetzt kommt ein anderer Fehler. Argh!

Inzwischen ist der Event Viewer übersäht mit roten Einträgen. Das Setup hat sich mit einem  Verweis auf  ein 1,8 MB großes, nichtssagendes Logfile abgemeldet. Ohne Dialog! Es starten so viele Dienste mit allen möglichen Erklärungen nicht mehr, dass ich Ursache und Wirkung nicht mehr verstehe. Auch weitere Versuche das Service Pack zu installieren scheitern. Kaputt.

Bei der ursprünglichen Installation fand ich die Anleitung äußerst verwirrend. Die Abhängigkeiten der verschiedenen Dienste sind sehr stark. Nur mit der genau richtigen Version und der passenden Sammlung an Hotfixes lief das System am Ende. Unten drunter Windows Server 2003, dann SQL Server 2005 mit Report Services, Analysis Services und Integration Services, dazwischen der IIS6 ohne Frontpage Extensions, weil darüber noch SharePointPortal Services mit Service Pack 2. Eine nicht notwendig solide Basis, um dann endlich den Team Foundation Server zu installieren. Zur Garnierung noch 2 Service Accounts und ein Setup Account.

Mit einem aktuellen Backup der Datenbanken konnte ich den Server neu aufsetzen. Die überarbeitete Installationsanleitung liest sich besser und es hat fast alles auf Anhieb geklappt. Nur das SQL Server Service Pack 1 wollte einen frisch installierten SQL Server nicht updaten. Erst das cumulative hotfix package hat geholfen. Package stimmt aber nicht ganz. Man darf nacheinander in vorgegebener Reihenfolge 6 Pakete installieren.

Die Rücksicherung der Daten ist dann ein Spaß für sich: How to: Move Your Team Foundation Server from One Hardware Configuration to Another.  Das sind ausgedruckt 8 A4-Seiten Handlungsanweisungen. Puh. Also mit dem Stift Häkchen machen und konzentriert durch die einzelnen Schritte gehen. Unter anderem sichert man einzeln 8 Datenbanken zurück, startet Kommandozeilen-Tools mit Parametern über mehrere Zeilen, stoppt und startet Dienste und ändert Konfigurationsdateien. So gegen Ende wird es dann immer spannender:

Im "Configure Report Server" soll ich den Dienst wieder starten. Na los, Server Status - Start gedrückt. Der Mauszeiger wird zur Sanduhr. Sanduhr, Sanduhr, Sanduhr. Urgs. Sanduhr, Sanduhr.
Fehler! Fehler? NEIN! Was für ein Fehler? Timeout. Bange Frage, hat es wirklich so lange gedauert oder ist alle wieder kaputt? Noch mal Start gedrückt. Fehler! Argh! Doch was lese ich: "Der Dienst läuft schon, Du hibbeliger Admin!". Oder so ähnlich. Nicht umsonst hatte das Setup gewarnt, dass die Hardware unter den Anforderungen liegt :)

Innerhalb von nur 80 Minuten waren alle Daten und Einstellungen wieder auf dem alten Stand. Ich kann mich mit dem Team Explorer wieder mit dem Server verbinden und im Source Control stöbern.

Jetzt erzeuge ich gerade in Image von der funktionierenden Installation. Ohne doppelten Boden traue ich mich nicht mehr an dieses Service Pack. In dem Team Foundation Server - Setup Forum ist die Frage nach erfolgreicher Installation immer noch unbeantwortet. Dafür gibt es einige Einträge mit Fehlermeldungen...

Wednesday, December 20, 2006 5:41:42 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Tuesday, December 05, 2006

Bei der Page Turn Demo sieht das Ergebnis im Internet Explorer deutlich besser aus. Firefox meldet schon auf der Startseite 10 JavaScript Fehler "Error setting property on scriptable plugin object!". Der Blätter-Effekt fehlt.

Nachdem WPF/E über ein Plugin realisiert wird und Teile der Interaktivität über JavaScript realisiert sind, bleiben also noch ein paar Browser-Abhängigkeiten. Hoffentlich nicht zu viele. Die grundsätzliche Funktionalität ist ja da und wir sind bei der ersten CTP.

Tuesday, December 05, 2006 12:46:36 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 

WPF bedeutet Windows Presentation Foundation. Und gemeint ist damit die Grundlage für die neue Generation von Benutzeroberflächen im Microsoft Umfeld. Eingeführt wird diese Technologie mit Vista und als installierbares .NET 3.0 auch auf XP und Server 2003. Zum einen kann man damit ganz tolle, bunte, neue Desktop Applikationen basteln. Oder aber auch mit der gleichen Technologie über den Browser im Web präsentieren.

Das funktioniert dann aber wieder nur auf der Windows Plattform. Das Web ist aber kein Microsoft Markt. Mac User sind dort reichlich vertreten. Also gibt es auch noch WPF/E oder länglich Windows Presentation Foundation Everywhere. Das ist ein Browser Plugin um ein Subset von WPF in jedem Browser zu ermöglichen.

Als ich von WPF/E hörte, dachte ich, dass ist ein fernes Versprechen von Microsoft, das ganz auch irgendwie Cross-Browser tauglich zu machen. Später halt. Und jetzt staune ich nicht schlecht. Die ersten WPF/E Lebenszeichen laufen gerade in meinem Firefox und IE7!

Es gibt seid Heute einen dick gefüllten WPF/E Dev Center. Dort gibt es schon ein Community Technology Preview (CTP) dieses Plugins und ein paar Demos. Cool!

Technologisch haben sich für mich ein paar Lücken gefüllt. Jetzt kann man mit einer Technologie im Web und auf dem Desktop seine Applikation darstellen.

Adobe SVG Viewer?

Adobe (Macromedia) Flash?

Wozu?

XAML überall! So sieht es jetzt wohl aus. Das gibt mal einen SPCat on steroids :).

.NET | SVG
Tuesday, December 05, 2006 11:53:37 AM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [1]  | 
# Wednesday, August 30, 2006
Gestern hatte ich bei Dustin Campbell einen interessanten Artikel gelesen: Getting a little more graceful with generics.
Im wesentlich ging es darum, dass der C# Compiler beim Aufruf einer generischen Methode selbst schließt welchen Typ er verwenden soll. Er sieht sich einfach die Parameter beim Aufruf an.

Fast exakt dieses Beispiel hatte ich in meinem aktuellen Projekt bereits verwandt. Nur habe ich eine Warnung von der Code Analyse bekommen, dass ich mit meinem Code diesen Rückschluss verhindern würde (CA1004 Microsoft.Design Warning GenericMethodsShouldProvideTypeParameter). Stimmt aber nicht. Es funktioniert wunderbar.

Die Methode

public static void GenerischeMethode<T>(List<T> arg);

kann immer so aufgerufen werden:

GenerischeMethode<string>(new List<string>);
GenerischeMethode<int>(new List<int>);

Aber der Compiler kann durch Inference auch den Typ selbst ermitteln und diese Aufrufe funktionieren auch:

GenerischeMethode(new List<string>);
GenerischeMethode(new List<int>);

Die Warnung CA1004 geht davon aus, dass der generische Typ direkt als Parameter verwendet werden soll und nicht wie in diesem Fall indirekt über List<T>.

Dustin Campbell hat dieses Verhalten in Microsoft bug? You decide! nach meinem Kommentar ausführlicher dargelegt.

Wednesday, August 30, 2006 3:00:34 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Tuesday, August 29, 2006
Ja macht denn der gar nichts anderes mehr? Immer nur dem Scott Hanselman alles nachmachen?
Tja, wenn einer soviel zu schreiben hat, dann liest man mal besser mit.

Inzwischen ist sie ja berühmt, die Liste mit Windows Tools für und Entwickler und erfahrene Anwender: Scott Hanselman's 2006 Ultimate Developer and Power Users Tool List for Windows

Und wie immer gibt es eine Menge an wirklich nützlichen Helfern. Viele davon habe ich schon mal ausprobiert. Einige sind auch installiert geblieben. Auch diesmal sind ein paar Neuigkeiten dabei, die sich in der nächsten Zeit bewähren dürfen. Jeder hat eine andere Arbeitsweise und braucht dabei andere Unterstützung.

Unter anderem sind diese Utilities auf meiner Platte gelandet:
Notepad++ Bisher war Notepad2 im Einsatz, aber Notepad++ wird aktiver weiterentwickelt.
WinSnap Wieder ein Screenshot Utility. Vielleicht ist das einfach genug für mich :)

Es bleibt die immer wieder gleiche Frage: Woher nimmt man nur die Zeit, das alles zu testen? Für jedes gute Tool in der Liste gibt es ja auch einige weniger gute. Nach eigenen Aussagen probiert Scott davon auch viele aus. Mir fehlt schon die Zeit, alles aus dieser Liste genauer anzusehen. Aber wenn man schön am Ball bleibt, hat man ein schönes Arsenal an nützlichen Werkzeugen.
Tuesday, August 29, 2006 11:21:23 AM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Saturday, August 26, 2006
Stark verdichtete gute Informationen gibt es immer bei Scott Hanselman und seinem Podcast Hanselminutes.

Die neueste Episode Hanselminutes Podcast 30 - Outlook Add-Ins and Personal Productivity Enhancers ist diese Woche erschienen. Natürlich wird unser kleines Outlook Tool nicht erwähnt :). Dabei ist unser Konzept zum Zugriff auf verschiedene Folder mit weniger Tasten als bei Speedfiler möglich. Dafür muss man sich lediglich die Abkürzungen einmal definieren. Außerdem sind wir billiger.

"Zero Inbox Messages" ist ein schöner Zustand. Kein Schaudern mehr, wenn sich Outlook öffnet.
Meine Inbox ist jetzt seit Wochen "leer". Mein Outlook schreit nicht mehr bei jeder eingehenden Nachricht. Es bleibt mehr Zeit für wichtigere Dinge.

.NET | Admin | Tools
Saturday, August 26, 2006 9:18:56 AM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [1]  | 
# Friday, July 21, 2006
Das Add-In OutlookMoveTo ist bei uns jetzt seit einiger Zeit im Einsatz. Ein paar kleine Verbesserung hat es erfahren und der Setup scheint inzwischen zu funktionieren.
Für mich ist es aber noch keine Software, die man ernsthaft verkaufen kann. Sicher wird es jemanden geben, der bereit wäre dafür Geld zu zahlen. Der Aufwand, eine Software wirklich verkaufsfertig zu machen, ist aber nicht zu unterschätzen.
Als über dieses Blog jetzt eine Anfrage bezüglich dem Erwerb der Software kam, haben wir uns entschlossen, den jetzigen Stand einfach per E-Mail zu versenden.
Folgende Rückmeldung ist dann schon sehr nett. Nochmals Danke dafür:
Ich habe OutlookMoveTo erfolgreich (mit Ihrer freundlichen Hilfe) installiert und es funktioniert hervorragend. Mittlerweile ist das Tool zu einem unverzichtbaren Helfer geworden und es rangiert mit an der Spitze der Tools, die ich täglich verwende. Wo früher eine volle und unaufgeräumte Inbox zu sehen war, so sieht man jetzt Ordnung. Sie sehen - ich bin wirklich begeistert; zumal ich bis dato auch keinen Fehler feststellen konnte.

Wer über dieses Blog Kontakt mit mir aufnimmt, der wird diese Tool sicher auch bekommen.

Natürlich mit dem üblichen Vorbehalten:
  • Benutzung auf eigenes Risiko
  • Support von uns ist freiwillig
  • Keine Weitergabe der Software

 

Update: OutlookMoveTo für Outlook 2007

.NET | Admin | Tools
Friday, July 21, 2006 4:05:49 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, June 14, 2006

Ich habe ein kleines Add-In für Outlook geschrieben, um möglichst schnell und einfach Outlook Objekte in definierte Ordner zu verschieben. Getauft habe ich dieses Add-In "OutlookMoveTo". Die Motivation für dieses Tool ist in meinem vorherigen Eintrag Outlook - Posteingang aufräumen nachzulesen.
Nach der Installation erhält man einen zusätzlichen Button in der Symbolleiste von Outlook:



Durch einen Klick auf diesen Button oder der Tastaturabkürzung "Alt + M" öffnet sich der Auswahl-Dialog von OutlookMoveTo:



Bevor man mit OutlookMoveTo arbeiten kann müssen die Zielordner definiert werden. Dazu navigiert man in der Ordnerliste von Outlook an die gewünschte Stelle und öffnet OutlookMoveTo. Durch einen Klick auf "Add" wird der Edit-Dialog mit dem aktuellen Ordner als "Path" geöffnet.



"Shortcut" ist die gewünschte Tastaturabkürzung für diesen Ordner. Diese Abkürzungen sollten sich aus dem Zielordner ableiten lassen. Z.B: Rechnungen Kunde A -> "ra", Auftrag Kunde B -> "ab".
"Name" ist der erklärende Name des Ordners und dient als Stütze, solange die Abkürzungen noch nicht verinnerlicht sind.
"Path" ist der interne Pfad den Outlook für diesen Ordner verwendet. Diesen Wert sollte man nicht normalerweise nicht editieren.
Mit "Ok" wird der neue Ordner gespeichert.
Über "Edit" vom Auswahl-Dialog lassen sich diese Angaben jederzeit ändern.
Diese Liste wird automatisch in den lokalen Benutzerdaten gespeichert.

Und schließlich das eigentliche Verschieben der Outlook-Objekte: Einfach die zu verschiebenden Objekte markieren und OutlookMoveTo öffnen.



Der Cursor steht im "Shortcut" Textfeld. Nach jedem Tastendruck springt die Tabelle an die passende Stelle. Noch kurz "Enter" gedrückt und alle markierten Outlook Objekte werden in den ausgewählten Ordner verschoben. In diesem Beispiel sind insgesamt folgende Tasten gedrückt worden: "Alt + M" "r" "a" "Enter"

Für wenige Ordner und eine flache Hierarchie ist das sicher zuviel Aufwand. Aber bei der unserer Strukturtiefe ist diese Vorgehensweise deutlich effizienter.

Outlook Add-In - Jetzt hier erhaeltlich

OutlookMoveTo für Outlook 2007

.NET | Admin | Tools
Wednesday, June 14, 2006 6:00:54 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Monday, May 15, 2006

Das papierlose Büro hätte es ja schon vor langer Zeit geben sollen. Noch ist es nicht soweit. Aber dennoch ist sehr viel Kommunikation heute elektronisch. Bei uns ist der Exchange Server zusammen mit Outlook als Client die zentrale Plattform.

Jeder Benutzer hat seinen eigenen Posteingang. Eingehende Faxe werden automatisch in einem öffentlichen Ordner abgelegt. Dann gibt es weitere öffentliche Ordner für generische E-Mails, die an unsere öffentlichen Adressen gesendet werden.

Der Posteingang sollte aber nicht zur Sammelstelle bearbeiteter und unbearbeiteter Dokumente verkommen. Mittlerweile ist die Suche nach E-Mails sehr einfach und effizient geworden. Diverse Desktop-Suchmaschinen machen das möglich. Auch Outlook selbst erlaubt eine Suche. Für eingehende geschäftliche Dokumente sollte es dazu aber nach meiner Meinung nicht kommen. Das gehört klassisch sortiert und abgelegt. Spätestens bei der Archivierung dieser Daten geht es eigentlich nicht mehr anders.

Somit müssen eingehende Dokumente im Laufe der Bearbeitung verschoben werden. Der nahe liegende Weg, per Drag & Drop die Dokumente in der Ordnerliste zu verschieben, wird bei vielen verschachtelten Ebenen zur nervigen Übung. Wer zügig arbeiten will, ist mit der Tastatur immer schneller als mit der Maus.

So entstand die Anforderung, per Tastaturabkürzung Objekte innerhalb von Outlook verschieben zu können. Dabei soll jedem Zielordner eine Tastaturabkürzung zugeordnet werden können.

In einem der nächsten Einträge gibt es mehr zur Realisierung zu lesen. Soviel vorab: Mit Visual Studio 2005 (VSTO) habe ich ein Outlook-Add-In gebastelt. Dieses Add-In ist seit einigen Tagen im Einsatz und funktioniert sehr gut. Ein paar Eindrücke von dieser Technologie werde ich noch dokumentieren und das Ergebnis als kostenlosen Download zur Verfügung stellen.

OutlookMoveTo für Outlook 2007

.NET | Admin | Tools
Monday, May 15, 2006 5:58:23 PM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  | 
# Thursday, March 16, 2006
Wir vertreiben ein Tool zur Datensicherung: CPTec-Backup. Dieses Programm verwenden wir natürlich auch selbst. Und das ist gut so. Weil wir so leichter erkennen, an welchen Stellen wir nachbessern müssen.

Ein wichtiger Aspekt war für uns, dass wir auf externe USB Festplatten sichern können. Die kann man einfach abstecken und an sicheren Ort bringen. Natürlich verwendet man rotierende Sätze und die nächste Sicherung schreibt auf eine andere Platte. Dabei muss man darauf achten, dass man nicht von einem festen Laufwerksbuchstaben ausgehen kann. Also kann man CPTec-Backup so konfigurieren, dass es die Festplatte an den ersten Zeichen des Datenträgernamens (Volume Name) erkennt. Das funktioniert auch sehr gut. Alle externen Platten für einen Sicherungs-Job beginnen mit dem gleichen Präfix. Problem gelöst.

Im Laufe der Zeit ist uns aber noch eine andere Schwäche an dem Konzept aufgefallen: Externe Platte an einem sicheren Ort sind gut für die bereits gesicherten "alten" Daten. Aber ganz schlecht für neue Daten die zu einem späteren Zeitpunkt gesichert werden sollen. Dazu müssen diese USB-Platten nämlich ihren Weg zurück in die Firma finden. Das konnten wir nicht immer sicherstellen ;).
CPTec-Backup hatte erkannt, dass nicht alle Ziel-Laufwerke für die anstehende Sicherung vorhanden sind, dies gemeldet und die Sicherung abgebrochen. Schade.

Zusätzlich zur Sicherung auf die externe Festplatte sichern wir auch auf eine interne Platte. Jetzt gibt es ein neues Feature in CPTec-Backup, mit dem Ziele als optional markiert werden können. Fehlende optionale Ziele werden beim Start des Backups unter anderem per E-Mail gemeldet und unmittelbar vor dem ersten Zugriff erneut überprüft. Falls die Festplatte dann immer noch fehlt, wird das protokolliert und der Backup fortgesetzt.

Thursday, March 16, 2006 6:17:53 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Friday, November 25, 2005

Es gibt einige kleine Tools, die wir uns für den internen Gebrauch selber basteln. Oft gibt es keine passende fertige Lösung. Manchmal ist das entsprechende Produkt auch zu teuer. Das war auch der Fall, als wir unsere Backup-Lösung überdachten. Festplatten werden immer billiger, aber Bandlaufwerke und die passenden Bänder nicht in dem gleichen Masse. Wechselmedien sollten es aber schon geben, schließlich sollen die wichtigen Daten an einem entfernten Standort gelagert werden. Wir haben uns deshalb für eine Datensicherung auf USB-Festplatten entschlossen.

Natürlich gibt es Backup-Software, die auf externe Platten sichern kann. Wir hatten aber weitergehende Ansprüche:
  • Unser Vertrauen in IDE-Festplatten, insbesondere solche, die häufig transportiert werden, ist nicht sehr groß. Also wird alles auf 2 Platten gesichert.
  • Der Platz auf den Platten soll optimal genutzt werden. Alte Daten bleiben solange wie möglich auf der Platte. Sie werden erst automatisch gelöscht, wenn der Platz für die aktuellen Daten wirklich benötigt wird.
  • Als Kompromiss zwischen Redundanz und Verwahrungszeit der Daten soll automatisch bei jedem Backup die die Zielplatte gewechselt werden können.
  • Alle kopierten Daten werden in eine Datenbank protokolliert. Mit einfachen SQL Befehlen kann man gesicherte Dateien aufspüren.
  • Die Berechtigungen der Quelldateien sollen auf die Zieldateien übertragen werden. Dadurch ist ein Rücksichern der Daten einfacher und man kann den Anwendern über eine Readonly-Freigabe Zugriff auf die Sicherungen geben.
  • Fehler oder Erfolgsmeldungen werden in das Event Log geschrieben und per E-Mail verschickt.
Inzwischen läuft dieses "CPTec Backup" seit einiger Zeit und wir sind mit dem Ergebnis sehr zufrieden. Ein paar kleine Änderungen und Optimierungen an der Software und der Konfiguration gab es natürlich auch zwischendurch. Die Konfiguration ist als XML-Datei abgelegt und der Backup Job wird mit dem Taskplaner gestartet. Alles ganz einfach. Täglich kamen E-Mails, die den Erfolg der Inkrementellen Sicherung verkündeten. Prima!

Drei CPTec-Mitarbeiter bekamen diese E-Mails. Keinem ist es aufgefallen, als über ein Woche lang keine Meldung mehr kam. Vor lauter Korrespondenz und Spam fällt es nicht auf, wenn eine tägliche Mail nicht ankommt. Was war passiert? Ich hatte eine minimale Änderung an der Konfiguration vorgenommen. Als Entwickler der Software macht man so was auch ganz selbstverständlich. Ich weiß ja was ich mache. Denkste! Die Datei war kein valides XML mehr. CPTec Backup hat das natürlich dem Event Log anvertraut. Aber woher sollte es ohne Konfigurations-Datei wissen, wohin die E-Mails geschickt werden sollen? Kann es nicht, macht es nicht.

Zum Glück ist nur das Inkrementelle Backup betroffen gewesen. Die Komplettsicherung lief separat und ungestört weiter.

Natürlich will man aus Fehlern lernen. Die erste Konsequenz ist, dass CPTec Backup jetzt einen Kommandozeilen-Parameter zum Testen der Konfiguration bekommen hat. Ohne dass tatsächlich eine Sicherung stattfindet, wird die Datenbank getestet und eine Test E-Mail versendet. Zusätzlich wird auch vor dem Start der eigentlichen Sicherung eine E-Mail verschickt. Um die Auswirkung einer überquellenden Inbox zu lindern sortiere ich in Outlook auch noch alle Backup-Meldungen anhand des Absenders in einen separaten Ordner. Jetzt sind wir hoffentlich vor meinen Tippfehlern besser geschützt.

Und bevor ich es vergesse: Wir haben uns dazu entschlossen, auch anderen Interessenten diese Backup-Software zur Verfügung zu stellen. Die Anwendung ist mit C# unter .Net 1.1 entwickelt. Sogar der Quelltext ist erhältlich. Wer sich dafür interessiert, kann auf unter CPTec Software mehr dazu lesen :).

Friday, November 25, 2005 4:50:57 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Monday, November 07, 2005

Jetzt beginnen die lustigen Spielchen mit dem neuen .NET 2.0 Framework. Ich möchte ein internes Tool auf die neue Version umstellen. Beim Kompilieren kommen nur noch Warnings, keine Errors, also mal fröhlich F5 gedrückt, um die Applikation im Debugger zu starten. Haste wohl gedacht. Visual Studio 2005 überrascht mit folgender Meldung:

Error while trying to run project: Unable to start debugging.
The binding handle is invalid.

Na prima! Was ist da kaputt? Im Microsoft Forum habe ich diesen Thread gefunden: Unable to debug: The binding handle is invalid
Damit konnte ich das Problem lösen. Wobei mich aber die Lösung schon sehr irritiert hat:

Vor einiger Zeit bin ich mal die Dienste (Services) auf meinem Rechner durchgegangen und habe alles ausgeschaltet, was ich wirklich nicht gebraucht habe. Wofür brauche ich auf meinem Desktop Rechner zum Beispiel die Terminal Services? Fast User Switching will ich nicht und Remote Zugriff gibt es auch nicht. Also stand der Autostarttyp (Startup type) auf Disabled. Jetzt lerne ich, dass man deswegen nicht mit Visual Studio 2005 lokal debuggen kann.

Was da intern alles abläuft, möchte ich gar nicht wissen. Brrr. Wenn der Terminal Services Dienst auf Manual steht, dann startet der Dienst, sobald VS 2005 startet. Danach geht auch das Debugging.

Wichtig dabei ist noch, das man den Rechner neu starten muss, nachdem man den Autostarttyp geändert hat

Zusammengefasst:
  1. Visual Studio 2005 will nicht debuggen.
  2. Control Panel -> Administrative Tools -> Services -> Terminal Services -> Startup type -> Manual.
  3. Reboot.
  4. Visual Studio 2005 ist zufrieden, ich bin verwundert.

Monday, November 07, 2005 5:14:57 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [1]  | 
# Friday, November 04, 2005

Mit .NET 1.1 ist nicht direkt möglich eine kontextsensitive HTML Hilfe (CHM-Datei) mit einer ganzzahligen Topic ID aufzurufen. Die HelpNavigator Aufzählung definiert die verschiedenen Möglichkeiten eine bestimme Stelle in der Hilfe anzuspringen. Leider ist keine Topic ID dabei, obwohl die HTML Hilfe diese unterstützen würde.
Eine kurze Recherche hatte damals eine  Lösung geliefert. Das FrameWork gibt den HelpNavigator Wert als ganze Zahl an die eigentlich Hilfe Darstellung weiter. Ein Wert von 15 erlaubt eine ganzzahlige Topic ID. Ein Cast auf HelpNavigator und siehe da, es geht.

Die Lösung sah dann im Wesentlichen so aus:

public static void ShowHelpTopic(System.Windows.Forms.Control parent, int topicID)
{
    string helpFile = @"c:\\help.chm";
    System.Windows.Forms.Help.ShowHelp(parent, helpFile, (System.Windows.Forms.HelpNavigator)15, topicID);
}

Jetzt, da das .NET Framework 2.0 erschienen ist, habe ich getestet, ob die Applikation direkt unter dieser Version funktioniert. Also ohne ein .Net 1.1 vorauszusetzen. Es hat eigentlich alles funktioniert, bis auf die kontextsensitive Hilfe. In Version 2.0 erlaubt HelpNavigator nämlich eine Topic ID. Der übergebene Parameter ist dann aber kein ganze Zahl, sondern die ID als Zeichenkette.
Da die Applikation auch weiterhin unter .Net 1.1 kompilierbar sein soll, kann die Topic ID nicht direkt aus der HelpNavigater Aufzählung verwendet werden. Also verwende ich wieder die Entsprechung dieses Wertes als ganze Zahl. Über eine Fallunterscheidung werden dann in Abhängigkeit von dem verwendeten .NET Framework die passenden Parameter übergeben. Damit funktioniert die HTML Hilfe auch unter .Net 2.0 kontextsensitiv.

Vereinfacht sieht die neue Methode dann so aus:

public static void ShowHelpTopic(System.Windows.Forms.Control parent, int topicID)
{
    string helpFile = @"c:\\help.chm";

    if (System.Runtime.InteropServices.RuntimeEnvironment.GetSystemVersion().Substring(1,3) == "1.1")
    {
        // .NET 1.1
        System.Windows.Forms.Help.ShowHelp(parent, helpFile, (System.Windows.Forms.HelpNavigator)15, topicID);
    }
    else
    {
        // .NET 2.0
        System.Windows.Forms.Help.ShowHelp(parent, helpFile, (System.Windows.Forms.HelpNavigator)(-2147483641), topicID.ToString());
    }
}

Mit dieser Erweiterung können unsere Applikationen jetzt kontextsensitive HTML Hilfe unter beiden .NET Versionen darstellen.
Als nächstes werde ich damit beginnen unser LinkEdit, eine Applikation zum Setzen von HotSpots für SPCat, auf .NET 2.0 umzustellen. Dann kann ich ganz einfach nur HelpNavigator.TopicID verwenden. Wie langweilig :).

Friday, November 04, 2005 2:53:37 PM (W. Europe Standard Time, UTC+01:00)  #
  Disclaimer  |  Comments [0]  | 
# Wednesday, October 26, 2005

Die meisten Zeichnungen, die im SPCat verwendet werden sollen, liegen als Schwarz-Weiß-Grafik im Tiff-Format vor. Um diese Grafiken aufzubereiten habe ich in C# eine .NET Applikation "TiffSPCat" geschrieben. Unter anderem werden dabei Gif-Grafiken für die Bildschirmdarstellung erzeugt. Da in der Ausgangs-Grafik keine Farben vorkommen, werden die Gif-Grafiken mit einer festen Palette von 256 Graustufen erzeugt.
Inzwischen ist TiffSPCat deutlich flexibler und erlaubt auch die Konvertierung anderer Pixel-Formate. Diese müssen nicht notwendig in Schwarz-Weiß oder als Graustufen vorliegen. Also kann ich nicht mit einer Graustufen-Palette bei den Gifs arbeiten.
Der naheliegende Weg wäre also, einfach im System.Drawing.Imaging Namensraum den Gif Encoder auszuwählen und damit auf der erzeugten Bitmap die Save Methode aufzurufen. GDI+ wird es schon richten.
Bei Ausgangs-Grafiken mit wenigen Farben, also einer typischen technischen Zeichnung, sieht das Ergebnis passabel aus und ich war zunächst zufrieden.
Leider störte dann bei der weiter Verwendung der Gif-Grafiken deren Transparenz. Bei den Grafiken mit der selbst erzeugten, festen Graustufen-Palette waren die Gif-Grafiken nicht transparent. Also doch weiter in den Eingeweiden von GDI+ und dem .NET Wrapper System.Drawing.Imaging stöbern.

Als erstes fällt auf, daß immer eine Standard-Palette mit 256 Farben verwendet wird. Die Palette kann man in der GDI+ FAQ Creating Transparent GIF Images von Bob Powell schön sehen. Es gibt keine Möglichkeit über zusätzliche Parameter eine optimierte, adaptive Palette erzeugen zu lassen. Schade, also von Hand die Palette erzeugen. Aber erstmal Google fragen, keine Räder neu erfinden.
Das Beispiel aus diesem MSDN Artikel Optimizing Color Quantization for ASP.NET Images quantisiert mit einem Octree Algorithmus die Echtfarben auf eine optimierte Palette. Mit ein paar kleinen Veränderungen konnte ich den Quelltext in TiffSPCat einbauen. Die erzeugten Paletten sehen gleich viel sinnvoller und die dazugehörigen Gif-Grafiken gleich viel besser aus.

Trotzdem blieb eine Farbe in dem erzeugten Gif transparent. Jetzt habe ich aber Kontrolle über die verwendete Palette. Der letzte Schritt klärt sich dann mit diesem Knowledge Base Artikel KB319061, How to save a .gif file with a new color table by using Visual C# .NET. Die erste Farbe in der Palette mit einem Alpha Wert gleich Null, wird transparent. Also alle Alpha Werte auf 255 setzen:

// Palette von der Bitmap holen.
ColorPalette pal = this.activeBitmap.Palette;

// Alpha auf 255 setzen.
for(int i = 0; i < pal.Entries.Length; i++)
{
Color col = pal.Entries[i];
pal.Entries[i] = Color.FromArgb(255, col.R, col.G, col.B);
}

// Palette zuweisen.
this.activeBitmap.Palette = pal;

Die fertige Bitmap im PixelFormat.Format8bppIndexed und allen Alpha Werten ungleich Null wird dann mit dem Gif Encoder von GDI+ ohne Transparenz gespeichert.

Wednesday, October 26, 2005 11:18:36 AM (W. Europe Daylight Time, UTC+02:00)  #
  Disclaimer  |  Comments [0]  |