Refaktorisieren

Heute wollen wir uns mal mit dem Thema Refaktorisieren/Refactoring beschäftigen. Wir schauen uns mal an, was es ist, und wie und ob es bei Softwareprojekten sinnvoll einzusetzen ist. Kurze Antwort: Refaktorisierungen sind ein extrem nützliches Werkzeug zur Verbesserung der Codequalität.

Definition

Die Wikipedia schreibt dazu: Refaktorisierung (auch Refactoring, Refaktorierung oder Restrukturierung) bezeichnet in der Software-Entwicklung die manuelle oder automatisierte Strukturverbesserung von Quelltexten unter Beibehaltung des beobachtbaren Programmverhaltens. Dabei sollen Lesbarkeit, Verständlichkeit, Wartbarkeit und Erweiterbarkeit verbessert werden, mit dem Ziel, den jeweiligen Aufwand für Fehleranalyse und funktionale Erweiterungen deutlich zu senken.

Martin Fowler, Autor des Buchs Refactoring, hat das Thema populär gemacht. Sehen wir uns hier seine Definition an: Refactoring is a controlled technique for improving the design of an existing code base. Its essence is applying a series of small behavior-preserving transformations, each of which „too small to be worth doing“. However the cumulative effect of each of these transformations is quite significant. By doing them in small steps you reduce the risk of introducing errors. You also avoid having the system broken while you are carrying out the restructuring – which allows you to gradually refactor a system over an extended period of time.

Hintergrund

Hinter dem Refaktorisieren steht eine mathematische Theorie. Es handelt sich um Gruppenopertionen und man kann die Beweise führen, dass gewisse Refaktorisierungsoperationen den Programmkode bedeutungsidentisch transformieren. So, als ob funktionell nichts geschehen sei. Per Induktion kann man dann daraus ableiten, dass eine Folge von Refaktorisierungsoperationen auch Bedeutungserhaltend sind. Somit ist also der Gesamte Vorgang nicht Bedeutungsändernd.

Praxis

In der Praxis jedoch wird man aber genau deshalb Refaktorisieren, weil man eine Änderung oder Erweiterung mit den bisherigen Strukturen nicht mehr leisten kann. Man benötigt also eine Abstraktion und sobald man diese eingezogen hat, wird man wechselweise Kodeänderungen und Refaktorisierungen durchführen. Somit ist die Gesamtoperation natürlich nicht Bedeutungserhaltend. Allerdings war ja auch das glatte Gegenteil das Ziel: Ein Entwickler sollte ja die Kodebasis irgendwie erweitern. Und damit das Verhalten Ändern.
Das Gute an der Sache ist eben, dass das Refaktorisieren zunächst ein Schritt ist, der in der Theorie keine Bedeutungsänderung bringt.

Beispiele

An dieser Stelle sollten wir den Blick mal auf einige Beispiele von Refaktorisierungsoperationen werfen, um ein wenig mehr Gefühl dafür zu bekommen.

Hier einige typische Refaktorisierungen:

Umbenennen

Das einfachste ist sicherlich das Umbenennen. Natürlich stellt sich die Frage was. Man kann ja eine lokale Variable, eine Elementvariable, Klasse, Templateparameter, Elementfunktion, Namespace u.v.a.m umbenennen. Die Implikationen sind jeweils ähnlich; können aber weite Kreise ziehen. Inklusive eventueller Dateiumbenennungen. So lange keine Konflikte entstehen, sollte evident sein, dass dies eine harmlose Operation ist:

private static void Gelder(int zahl)
{
    int doppelt = zahl + zahl;
    Console.WriteLine($"{zahl} Heller verdoppelt sind {doppelt} Heller");
}

public static void Main(string[] args)
{
    Gelder(zahl: 4);
}

Durch Umbenennung des Parameters zahl in nummer werden alle Stellen kohärent angepasst.

private static void Gelder(int nummer)
{
    int doppelt = nummer + nummer;
    Console.WriteLine($"{nummer} Heller verdoppelt sind {doppelt} Heller");
}

public static void Main(string[] args)
{
    Gelder(nummer: 4);
}

Extrahieren und Entrollen einer Variable

Hierbei handelt es sich um zwei gegenteilige Operationen. Extrahieren bedeutet, dass aus einem Ausdruck eine neue Variable entsteht. Entrollen ist das Gegenteil. Alle Vorkommen einer Variable werden durch den Ausdruck ersetzt. Sehen wir folgendes Beispiel an:

private static (double umfang, double fläche) Kreis(double radius)
{
    double rp = radius * Math.PI;
    double umfang = 2 * rp;
    double fläche = radius * rp;
    return (umfang, fläche);
}

Der Teilausdruck radius * Math.PI wurde hier ein eine Variable rp gepackt. Das könnte vielleicht eine Rechnung ersparen, verringert aber eher die Lesbarkeit. Daher konnte die Variable rp entrollt werden und an ihrer Stelle steht nun der Ausdruck. Dieser Umbau kann durch Extraktion umgekehrt werden.

private static (double umfang, double fläche) Kreis(double radius)
{
    double umfang = 2 * radius * Math.PI;
    double fläche = radius * radius * Math.PI;
    return (umfang, fläche);
}

Skalar zu Tupel-Transformation

Diese Refaktorisierungsoperation habe ich mir wohl selbst ausgedacht, da ich sie so noch nie gesehen habe. Es handelt sich um das umkehrbare aufblasen einer skalaren Variable in eine Kompositvariable. Also typischerweise in eine Liste, Feld, Tupel oder Struktur/Objekt. Der Anwendungsfall ist in typischen Applikationen recht häufig. Werfen wir einen Blick auf diese sehr vereinfachte Businessanwendung, die eine Person mit ihrer Adresse ausgibt:

private class Anschrift
{
    public string Straße;
    public int Nr;
}
private struct Person
{
    public string Name;
    public Anschrift Adresse;
}
private static void WriteAdresse(Person p)
{
    Console.WriteLine("Name :" + p.Name);
    Console.WriteLine($"Wohnhaft in {p.Adresse.Straße} {p.Adresse.Nr}");
}

Nun stellt sich heraus, dass eine Person mehrere Adressen haben kann. Also aus dem Skalar Adresse wird eine Liste von Adressen. Die Funktion bleibt zunächst dieselbe. Allerdings wird der Zugriff auf die Adresse nun mit einem Indexzugriff verziert. Die Funktion bleibt dieselbe:

private class Anschrift
{
    public string Straße;
    public int Nr;
}
private struct Person
{
    public string Name;
    public List<Anschrift> Adresse;
}
private static void WriteAdresse(Person p)
{
    Console.WriteLine("Name :" + p.Name);
    Console.WriteLine($"Wohnhaft in {p.Adresse[0].Straße} {p.Adresse[0].Nr}");
}

In einem nächsten Schritt kann man in WriteAdresse eine for-Schleife einbauen, die alle Adressen ausgibt.

Klasse extrahieren

Bei dieser Operation werden Methoden mit all ihren Abhängigkeiten aus einer Klasse in eine neue Klasse extrahiert. Diese Refaktorisierung hilft insbesonder beim S in SOLID, der „Single Responsibility„. In diesem Beispiel sehen wir, wie die beiden Methoden völlig unabhängig voneinander in derselben Klasse stehen. Über die sinnhaftigkeit ist hinwegzusehen:

public class RechenKnecht
{
    private readonly int _num;
    private readonly string _name;

    public RechenKnecht(int num, string name)
    {
        _num = num;
        _name = name;
    }

    public int Rechne()
    {
        return _num * 2;
    }

    public string Gruß()
    {
        return "Edler von " + _name;
    }
}

Hier wurde die Methode Gruß() in die Klasse Ansprache extrahiert. Man hätte es natürlich auch umgekehrt machen können:

public class RechenKnecht
{
    private readonly int _num;

    public RechenKnecht(int num)
    {
        _num = num;
    }

    public int Rechne()
    {
        return(_num * 2);
    }
}
public class Ritter
{
    private readonly string _name;

    public Ritter(string name)
    {
        _name = name;
    }

    public string Gruß()
    {
        return "Edler von " + _name;
    }
}

Auch diese Opertion ist umkehrbar…. bis am Ende alles Methoden und der Gesamtzustand der Anwendung in einer Klasse ist. Das ist aber natürlich unwartbar.

Das war natürlich ein triviales Beispiel. Wäre aber der Gruß nun zusätzlich mit der berechneten Zahl ausgestattet gewesen, dann geht es so nicht. Aber man kann dann mit Komposition arbeiten. Stellen wir uns also diese Gruß()-Methode vor:

    public string Gruß()
    {
        int x = Rechne();
        return $"{x}ter Edler von {_name}";
    }

In diesem Fall kann man mit folgender Refaktorisierung arbeiten. Hierbei wurde RechenKnecht auf seine Essenz redzuiert und dem Ritter als Vehikel gegeben (Komposition). Es sind zwei Klassen entstanden:

public class RechenKnecht
{
    private readonly int _num;

    public RechenKnecht(int num)
    {
        _num = num;
    }

    public int Rechne()
    {
        return(_num * 2);
    }
}
public class Ritter
{
    private readonly string _name;
    private readonly RechenKnecht _knecht;

    public Ritter(string name, int num)
    {
        _name = name;
        _knecht = new RechenKnecht(num);

    }

    public string Gruß()
    {
        int x = _knecht.Rechne();
        return $"{x}ter Edler von {_name}";
    }
}

Elemente rauf- und runterziehen

Eine Refaktorisierungsoperation möchte ich noch zeigen, ehe ich zum Aha übergehe, eine objektorientierte: Elementfunktionen in der Erbhierarchie raufschieben oder runterziehen. Auch hier besteht wieder (muss ja) eine Umkehrbarkeit und Bedeutungsgleiche in beiden Richtungen. Sehen wir uns dieses Beispiel an. Dort wird eine Elementfunktion aus der Vaterklasse aufgerufen. Außerdem ist die Methode in der Schnittstelle deklariert. Da aber niemand sonst darauf zugreift, kann der Kode kürzer und enger gefasst werden:

public interface IRechner
{
    int Quadrat(int num);
    void Rechne(int num);
}

public class BasisRechner : IRechner
{
    public virtual int Quadrat(int num)
    {
        return num + num;
    }

    public virtual void Rechne(int num)
    {
        Console.WriteLine(num);
    }
}

public class QuadratRechner : BasisRechner
{
    public override void Rechne(int num)
    {
        Console.WriteLine(Quadrat(num));
    }
}

Da die Methode Quadrat() in BasisRechner überhaupt nicht verwendet wird, kann man sie runterschieben in QuadratRechner. Man kann sie sogar aus der Schnittstelle entfernen (soweit nie benutzt) und dabei die Funktionalität beibehalten:

public interface IRechner
{
    void Rechne(int num);
}

public class BasisRechner : IRechner
{
    public virtual void Rechne(int num)
    {
        Console.WriteLine(num);
    }
}

public class QuadratRechner : BasisRechner
{
    public override void Rechne(int num)
    {
        Console.WriteLine(Quadrat(num));
    }

    public virtual int Quadrat(int num)
    {
        return num + num;
    }
}

Es gibt natürlich noch viele weitere Refaktorisierungsoperationen. Dies sollte nur eine kleine Auswahl sein, um einen Eindruck zu vermitteln.

Anwendung

Das Ganze lässt sich natürlich wunderbar automatisieren und in Werkzeuge gießen. So sind mit z.B. ReSharper, Eclipse, Visual Studio oder IntelliJ wunderbare Refaktorisierungswerkzeuge entstanden, die das Leben eines Entwicklers wirklich erleichtern. Vor allem aber arbeiten sie als Automaten viel konistenter und korrekter als es ein Entwickler langfristig könnte….

Insofern rate ich auch grundsätzlich dazu, immer Werkzeuge für solcherlei Vorhaben zu benutzen und sich dabei unbedingt vom Rechner unterstützen lassen. Ausnahmen bestätigen die Regel…

Mit diesem Wissen und diesen Werkzeugen im Gepäck kann man natürlich auch die Qualität des Quelltexts verbessern. Ich unterscheide hier mal alltägliche und gezielte Anwendung.

Alltägliche Anwendung

In der alltäglichen Anwendung nutzt ein Entwickler die Werkzeuge während des Kodeschreibens. Erstens, um architektonische Anpassungen zu machen, um die neue Funktion einzubauen. Und zweitens, um schlicht schneller zu sein. Stellt er fest, dass ein Dreizeiler öfter mehrfach im Kode steht, kann er ‚mal eben‘ eine Funktion extrahieren. Das Werkzeug macht den Rest. Im Anschluss kann er sie noch schnell umbenennen. Oder: Im Gefecht der Entstehung neuer Kodezeilen hat der Entwickler zunächst einige temporäre Variablen angelegt. Im Weiteren Verlauf stellt er fest, dass eine davon nur an einer Stelle verwendet wird. Ergo kann er sie automatisch Ausrollen lassen und direkt durch die rechte Seite der Variable austauschen. Ein weiteres gutes Beispiel ist ein fehlender/überflüssiger Parameter an einer Methode. Werkzeug anstarten und alle Aufrufe werden angepasst. Z.B. mit einem definierbaren Standardwert.

Refaktorisieren und die Werkzeuge stellen einfach einen Produktivitätsfaktor im Entwickleralltag dar und sind heute nicht mehr wegzudenken.

Gezielte Anwendung

Unter gezielter Anwendung verstehe ich dedizierte Sitzungen, in denen Refaktorisert wird. Entweder alleine oder mehrere Leute. Also z.B. ein Codereview, Paarprogrammierung oder doch wieder der einzelne Entwickler. Es geht dabei darum, dass ein Kodeabschnitt identifiziert wurde, der architektonisch oder in seiner Art so nicht mehr tragbar ist. Man macht sich also alleine oder als Gruppe Gedanken darüber, wie man umbauen sollte und tut dies dann auch. In aller Regel kommt etwas Besseres dabei heraus. Dabei sollte man sich nicht gleich ins Bochshorn jagen lassen, wenn sich zunächst ein Tal der Tränen auftut. Mit etwas gezieltem Handeln und unbeirrbarkeit bekommt man das regelmäßig wieder hin und freut sich nacher über ein besseres Ergebnis.

Probleme und Gefahren

Die Realität ist natürlich wieder viel komplexer und gemeiner als die graue Theorie und mein Geschwafel. Gerade in der imperativen objektorientierten Programmierung (OOP) haben Objekte regelmäßig Zustand und damit werden Instanzen, Referenzen und Vielfachheiten sowie die Reihenfolge interessant. Tatsächlich funktioniert die Theorie hier auch in der Praxis, aber man refaktorisiert ja gerade an Programmstücken, die es besonders nötig haben.

Die Gottklasse entmachten

Eine häufige Ursache für Wust ist, wenn eine Klasse mehrere Aufgaben erledigt = mehrere Klassen in sich vereint. Wenn das extrem wird, nennt man das Gottklasse. Quasi immer ist das die Ursache für nicht auflösbare zirkuläre Referenzen. Daher sollte man genau danach ausschau halten und die Operation Klasse Extrahieren verwenden. Lieber einmal mehr auf Verdacht anwenden. Wenn sich eine Aufteilung von Methoden und Elementvariablen finden lässt, dann war es der Fall und es sollte dann in mehrere Klassen Aufgespalten werden.

Dies ist eine der erfolgversprechendsten Refaktorisierungen. Im Nachgang ist es häufig so, dass eine der beiden extrahierten Klassen eine Vielfachheit bekommt (Skalar zu Tupel-Transformation).

Asynchron und Multithreading

Hier wird es spannend. Grundsätzlich sind auch hier alle Refaktorisierungen sinnvoll verwendbar und machen wenig Probleme. Die Probleme liegen allerdings im Detail. So kann es passieren, dass sich durch die neue Architektur die Lebenszyklen von Objekten verändern und dann bei Async-Programmierung Zugriffsfehler passieren. Oder Zustände sich früher/später als vor der Refaktorisierung verändern. Wie gesagt alles kann und es kommt immer auf die tatsächliche Implementierung an. Allgemein lässt sich aber sagen, dass man mit einer Architektur gut Async+Multithreading besteht, wenn sie „Single Responsibility“, „Interface Segregation“ und „Immutable Pattern“ beherzigt. Doch in diese Richtung kann und sollte man „hinrefaktorisieren“.

Resumé

Damit schließe ich diesen Artikel mal ab und resumiere, dass Refaktorisierung eine sehr nützliche Sache ist und sich fast immer lohnt. Insofern sollte ein jeder Projektleiter in der Software regelmäßig anberaumte Refaktorisierungs-Sitzungen veranstalten, in denen der Code an problematischen Stellen verbessert wird. Nebenbei helfen solche „öffentlichen Reviews“ auch frischen Etnwicklern etwas Neues zu lernen.

Unit-Tests über fremdem Code mit „Microsoft Fakes“

Ja sowas geiles. Testen von statischen Methoden, Interfaces und sammeln von Ergebnissen (Aufrufen). Das alles geht mit dem ehemaligen MS-Research-Tool „Moles“, welches heute in „Microsoft Fakes“ aufgegangen ist und seit VS2012 mitgeliefert wird. Da allerdings schon jemand einen tollen Artikel darüber geschrieben hat, verweise ich einfach darauf:

Unit-Tests mit MS-Fakes…. Links:

Böser Dispatcher

An dieser Stelle eines der vielen Gotchas bei .Net-Entwicklung.

Es gibt manchmal Klassen (oft ViewModel), die haben prim​är mal keine GUI-Referenz, werden aber manchmal wecheslweise in einem Worker-Thread ausgeführt. Problematisch wird dann eine eventuell daran gebundene GUI. Oder man muss einfach nur unterscheiden, ob man im Worker-Thread oder im GUI-Thread lebt.

Der Dispatcher ist mehr oder weniger das unterscheidbare Merkmal, an dem ein GUI-Thread von anderen unterschieden werden kann:

Gleicher Thread wie der Dispatcher… Gut, möchte man meinen. Doch diese Prüfung hat einen bösen Fehler.CurrentDispatcher hat Seiteneffekte: Existiert kein Dispatcher zum aktuellen Thread, wird ein neuer erzeugt. Dispatcher sind nämlich keineswegs GUI-Spezifisch, sondern ein allgmeiner Dispatch-Mechanismus. Der Name ist irreführend.

Korrekt ist dagegen die Prüfung mit diesem Idiom:

Doch leider zu kurz gedacht. Im Prinzip ist das Richtig. Doch nur so lange, als keiner das erstere Idiom mit Dispatcher.CurrentDispatcher verwendet hat und uns somit einen parasitären Dispatcher auf den Worker-Thread gesetzt hat. Irgendwann hat dann jeder Worker-Thread einen Dispatcher.

Also was tun? Gleich auf Application zugreifen … tja leider. Denn Applicaiton ist der einzig gute Ankerpunkt in diesem Fall.

Dabei soll aber nicht unerwähnt bleiben, dass es in einer GUI-Applikation durchaus mehrere UI-Threads geben kann. Typischerweise dann in mehreren Fenstern. Es ist unwahrscheinlich, aber es geht und dann wäre auch dieser Code hier wahrscheinlich inkorrekt.

Bei WinXP-Fotoanzeige Diashow-Intervall einstellen

Womit man auf seine alten Tage noch so zu tun bekommt. Windows XP glaubte ich ja eigentlich schon hinter mich gebracht zu haben. Doch da gibt es Leute, die es tatsächlich noch einsetzen. Es war ja auch nicht so schlecht… nur ein wenig unsicher sonst aber…

Und wer es einsetzt um damit Urlaubsfotos durchzublättern kommt auf die Diashow der Fotoanzeige. Da die Zeitintervalle zwischen den Bildern mit 3 sek nicht allen Leuten taugen, kommen dann Fragen, wie man dieses Intervall wohl ändert. Da es keine Oberfläche dafür gibt, bleibt nur die Registrierung zu bearbeiten. Konkret muss man unter HKEY_CURRENT_USERSoftwareMicrosoftWindowsCurrentVersionExplorerShellImageView gehen und dort den DWORD-Wert Timeout erstellen. Hinein kommt die Zeit in Millisekunden, die die Fotoanzeige zwischen zwei Bildern warten soll.

Aus gegebenem Anlass habe ich für diese einfache Aufgabe ein Programm geschrieben. Verwendet habe ich dazu WinForms und C#. Man wird also das .Net Framework 2.0 benötigen. Das Programm bietet eine simple Oberfläche um genau diese Eine Aufgabe zu erledigen. Gut für Leute, die häufiger mal die Zeitabstände ändern müssen, mit der Registrierung aber nix am Hut haben.

Download Programmdatei

Download Quellcode

Anmerkung: Das Programm ist nach dem QnD-Prinzip entstanden. Daher nur schnell zackzack entstanden… einfach so.

WPF Dependency Properties von innen Setzen

Entwicklung eines WPF-Composite-Controls mit Dependency Properties (aka. Abhängkgeitseigenschaften)

Ab und zu muss man bei der Etnwicklung von WPF-Oberflächen neue Controls erstellen. Nun gibt es verschiedene Arten von Controls. Man unterscheided sog. Lookless Controls und Composite Controls. Erstere sind quasi rein nur eine von UIElement abgeleitete Datenstruktur. Das gesamte Verhalten, das Aussehen und die modifikation der Datenfelder (die Dependency Properties) geschieht über Styles und dort wiederum mit Triggern und Binungen. Darum soll es hier aber nicht gehen. Hier geht es um die andere Art von Control: Um Kompositum-Kontrollelemente bzw. User-Controls. Also in Etwa ein Panel oder ein Window. Mehrere Controls sind in einem neuen Control zusammengepfärcht und interagieren intern miteinander, während sie nach außen hin wie ein einziges auftreten. Dies lässt sich mit und ohne View-Model machen. Man hat also die Wahl zwischen MVVM und code behind. Je nach komplexität des Kontrollements ist es entweder sinnvoll oder einfach Overhead, ein View-Model dazu zu bauen. Hier soll es jetzt um ein einfaches Control gehen und daher greifen wir auf code behind zurück.

Das Control

Entwickelt wird ein Datei-Auswahl-Control. Es besteht aus einem Label und einem Button. Klickt man den Button, so erscheint ein Datei-Öffnen-Dialog und die fürderhin ausgewählte Datei wird angezeigt. Gleichzeitig hat auch die extern sichtbare Eigenschaft „FileName“ ihren Wert geändert und alle Bindungen  darauf ändern sich mit. Die Wahl fällt auf ein Control mit Code-Behind. Somit können wir einfach auf das Click-Ereignis des Buttons reagieren. Dort muss dan aktiv der Wert der Eigenschaft geändert werden. Tatsächlich ist hier par Bindung das Control sein eigenes View-Model.
Bestandteile:

Vorgehen

Zunächst benötigen wir ein Control.
Daher legen wir eine XAML-Datei an und passend dazu eine Code-Behind-Datei. Wir erben von System.Windows.Control.

 

und

 

Controlaufbau

Das Control besteht aus zwei weiteren Controls: Button und Label. Wir fügen beide ein und Binden das Label an die noch zu erstellende Eigenschaft FileName. Damit das nacher funktioniert, muss die Source noch korrekt sein. Wir erreichen das recht einfach, indem wir dem Conttol(!) den DataContext setzen und auf sich selbs verweisen lassen. Das Control ist
so gesehen sein eigenes View-Model.

 

Abhängigkeitseigenschaften

Zur erfolgreichen Bindung benötigt das Control noch eine Dependency Property FileName:

mah beachte, wie per FrameworPropertyMetadata ein Standardwert mitgegeben wurde und die Bindungsoptionen standardmäßgig auf TwoWay definiert wurden. Dies hat im Folgenden den Vorteil, dass man von Extern (bei Verwendung) nicht bei Binungen Mode=TwoWay angeben muss.

Events

Wir wollten es einfacher mit dem Button. Nun fehlt noch der Eventhandler:

Man beachte hier die beiden Aufrufe der von DependencyObject stammenden Methodena: SetCurrenValue und ClearValue. Damit wird der Wert bzw. die Bindung hinter einem Dependency-Property geänder bzw. auf den Std.-Wert (aus den Metadaten) zurückgesetztgesetzt. Verwendet man vergleichsweise dazu GetValue/SetValue wie in der Implementierung des CLR-Properties, zerstört man die Bindung. Das wäre fatal, da dann die Funktionalität zusammenbricht. An dieser Stelle sein noch kurz auf die Doku verwiesen… Demnach seien CLR-Getter/Setter nur so zu implementieren, wie hier gezeigt. Nur Aufrufe von GetValue/SetValue und keine weiteren Aktionen nebenher. Denn WPF ruft gerne selbst GetValue/SetValue mit passenden Parametern auf und umgeht dabei die CLR-Properties. Zusatzaktionen
muss man daher in passenden PropertyChanged– bzw. CoerceValue– oder ValidateValue-Callbacks machen. Auf selbige wurde hier verzichtet.

Eingebaut

Nun sehen wir uns noch an, wie dieses Control zu verwenden ist. Dazu wurde ein WPF-Fenster gestaltet, dass dieses Control verwendet und gleichzeitig einen Textblock an unsere neue Dependency-Property bindet:

 

… es ist kein Code-Behind nötig….
Man beachte, wie zunächst der text „aus window“ im Control steht, und später der Wert aus dem Eventhandler des Controls (siehe im Code: cancel oder OK-Zweig).