Error when using TransformToAncestor: "The specified Visual is not an ancestor of this Visual."

I'm trying to get the offset of the control relative to the top of its window, but I'm having problems using the TransformToAncestor method of the control. Note: this code is in the value converter, which will convert from the control to its relative position Y relative to the window.

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; } 

Calling Window.GetWindow works very well and returns the correct window object within which the control is located.

I do not understand what WPF thinks as an "ancestor"? I would think that given the result of GetWindow this window would be the ancestor of the control. Are there any nesting patterns that could cut off the line of the pedigree at a certain moment?

UPDATE:

It looks like this could be a synchronization issue. When I tried to call the TransformToAncestor method inside the event handler, and not the value converter, it worked fine. It seems that the value converter should work, as certain elements are created before the pedigree relationship is established.

Not sure how to get around this, since I'm trying to use the MVVM pattern (and therefore, I really don't want to use event handlers, and most likely would not have System.Windows components in my ViewModel).

+6
wpf
source share
1 answer

The converter is called while the visual tree is still assembled, so your Visual is not yet a child of the window.

You want to do the transformation after your visual tree is already built. This is done by registering the Dispatcher callback with Dispatcher.BeginInvoke(DispatcherPriority.Render, ...) and doing your work inside the callback.

This cannot be used with the converter because the converter must immediately return the converted value. A simple solution is to use an attached property instead of a converter. Here's how:

Suppose your binding looks like this:

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

Create a subclass of DependencyObject "Independently" containing the attached property "AbcControl", whose PropertyChangedCallback performs the conversion and modifies the property "Abc":

 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); }); } }); } 

Now you can write:

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

Which sets the Abc property to the converted value of Y.

There are many options in this general idea.

+8
source share

All Articles