2010-05-14 5 views
6

Ich versuche, den Offset eines Steuerelements relativ zum oberen Rand seines Fensters zu erhalten, aber ich stehe bei der Verwendung der TransformToAncestor-Methode des Steuerelements in Schwierigkeiten. Hinweis: Dieser Code befindet sich in einem Wertkonverter, der von einem Steuerelement in seine relative Y-Position in Bezug auf das Fenster konvertiert wird.Fehler bei der Verwendung von TransformToAncestor: "Das angegebene Visual ist kein Vorfahr dieses Visual."

public object Convert(object value, Type targetType, object parameter, CultureInfo culture) 
{ 
    var ctrl = (Control) value; 
    var win = Window.GetWindow(ctrl); 
    var transform = ctrl.TransformToAncestor(win); // Exception thrown here. 
    var pt = transform.Transform(new Point(0, 0)); 
    return pt.Y; 
} 

Der Aufruf von Window.GetWindow funktioniert gut und gibt das richtige Fenster-Objekt in dem das Steuerelement befindet.

Bin ich falsch verstanden, was WPF als "Vorfahre" betrachtet? Ich würde denken, dass angesichts des Ergebnisses GetWindow dieses Fenster ein Vorfahr der Kontrolle wäre. Gibt es bestimmte Verschachtelungsmuster, die dazu führen, dass die Abstammungslinie an einem bestimmten Punkt abgeschnitten wird?

UPDATE:

Es sieht aus wie dies ein Timing-Problem sein kann. Als ich versuchte, die TransformToAncestor Methode in einem Event-Handler anstatt den Wertkonverter aufzurufen, funktionierte es gut. Es scheint, dass der Wertkonverter ausgeführt werden muss, da bestimmte Elemente instanziiert werden, bevor die Abstammungsbeziehung hergestellt wird.

Nicht sicher, wie man das umgehen kann, da ich versuche, das MVVM-Muster zu verwenden (und daher nicht wirklich Event-Handler verwenden möchte und keine System.Windows-Sachen in meinem ViewModel haben möchte).

Antwort

8

Der Konverter wird aufgerufen, während der visuelle Baum noch zusammengebaut wird, daher ist Ihr Visual noch kein Nachkomme des Fensters.

Sie möchten die Konvertierung durchführen, sobald Ihr visueller Baum bereits erstellt wurde. Dies geschieht, indem Sie einen Dispatcher-Callback unter Verwendung von Dispatcher.BeginInvoke(DispatcherPriority.Render, ...) registrieren und Ihre Arbeit innerhalb des Callbacks ausführen.

Dies kann jedoch nicht mit einem Konverter verwendet werden, da ein Konverter den konvertierten Wert sofort zurückgeben muss. Die einfache Problemumgehung besteht darin, eine angefügte Eigenschaft anstelle eines Konverters zu verwenden. Hier ist, wie:

Angenommen, Ihre Bindung ist wie folgt:

<SomeObject Abc="{Binding Xyz, Converter={x:Static my:Converter.Instance}}" /> 

erstellen DependencyObject Unterklasse „Was auch immer“ enthält eine angefügte Eigenschaft „AbcControl“, deren PropertyChangedCallback die Umwandlung tut und modifiziert die „Abc“ Eigenschaft:

public class AttachedProperties : DependencyObject 
{ 
    public Control GetAbcControl(... 
    public void SetAbcControl(... 
    ... AbcControlProperty = RegisterAttached("AbcControl", typeof(Control), typeof(AttachedProperties), new PropertyMetadata 
    { 
    PropertyChangedCallback = (obj, e) => 
    { 
     var ctrl = (Control)e.NewValue; 
     Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => 
     { 
     var win = Window.GetWindow(ctrl); 
     var transform = ctrl.TransformToAncestor(win); // Exception thrown here. 
     var pt = transform.Transform(new Point(0, 0)); 
     obj.SetValue(AbcProperty, pt.Y); 
     }); 
    } 
    }); 
} 

Jetzt können Sie schreiben:

<SomeObject AbcControl="{Binding Xyz}" /> 

Dies setzt die Abc-Eigenschaft auf den transformierten Y-Wert.

Für diese allgemeine Idee sind viele Variationen möglich.

Verwandte Themen