WPF中的父级更改

3
有没有一种方法,在不重写OnVisualParentChanged方法的情况下,仅通过使用事件来通知FrameworkElement的可视父级发生更改?与此处提供的“Loaded”事件相比,这种方法似乎并不安全。是否有一种事件可以确切地在可视父级发生更改/已更改时触发?

我不确定你需要做什么,但如果我需要等待所有内容加载完成,我经常使用IsVisibleChanged事件。 - jugg1es
3个回答

3
在我的测试中,当父级更改时,Loaded Unloaded确实会被触发,因此我认为可以安全地依赖它们。但是,如前所述,如果您钩取OnVisualParentChanged,则只能访问旧父级(在调用_Unloaded时,VisualParent已经为null)。如果这是您关心的问题,重写OnVisualParentChanged非常简单。
    void _Loaded(object sender, RoutedEventArgs e)
    {
        DoSmthng();
    }

    void _Unloaded(object sender, RoutedEventArgs e)
    {
        DoSmthngElse();
    }

    protected override void OnVisualParentChanged(DependencyObject oldParent)
    {
        DoSmthngOther();

        // Call base class to perform standard event handling. 
        base.OnVisualParentChanged(oldParent);
    }

1
这与OnVisualParentChanged有些关联。在FrameworkElement.cs实现中,它会执行private void TryFireInitialized(),进而调用protected virtual void OnInitialized(EventArgs e),因此您可以在那里挂钩。
这取决于您需要了解的信息。据我所知,唯一能够被通知父项正在更改并且能够访问旧父项的地方是OnVisualParentChanged(旧父项作为参数传递)。否则,在任何其他挂钩的地方,您只能访问新父项,因为它已经被更改。
您还可以调查一些继承FrameworkElement的类,看看它们是否公开了任何其他属性或方法,以帮助您。

TryFireInitialized() 只会触发一次 OnInitialized - HerpDerpington
@user1574054,你说得完全正确,感谢你发现了这个问题。 - Malcolm O'Hare

1

我最近遇到了一个情况,我真的需要知道任意元素何时获取其父级(一个我没有创建过的元素,所以我无法访问覆盖方法)。我编写了一个肮脏的hack,虽然它可能不是“安全”的,但确实可以工作。

令人烦恼的是,Visual实际上有一个VisualAncestorChanged事件!它正是我们想要的:在父项更改时立即触发。...唯一的问题是它是internal的,所以你必须进入WPF源代码才能看到它。幸运的是,通过反射,像访问修饰符这样的东西变得更像建议。

这是我构建的类来钩住那个内部事件。该类公开了一个公共事件,您可以正常处理,它会由内部事件触发。

    ''' <summary>
    ''' Raises an event when the visual parent of a <see cref="Visual"/> changes.
    ''' </summary>
    Public Class VisualAncestorWatcher
        Implements IDisposable

        Public Sub New(Visual As Visual)
            Me.Visual = Visual

            Dim HandlerType = InternalVisualAncestorChangedEvent.EventHandlerType
            Dim Ctor = HandlerType.GetConstructor({GetType(Object), GetType(IntPtr)})
            Dim HandlerMethod = [GetType].GetMethod("InternalVisualAncestorChangedHandler", BindingFlags.NonPublic Or BindingFlags.Instance)
            CreatedDelegate = Ctor.Invoke({Me, HandlerMethod.MethodHandle.GetFunctionPointer})
            InternalVisualAncestorChangedEvent.GetAddMethod(True).Invoke(Visual, {CreatedDelegate})
        End Sub

        Public ReadOnly Property Visual As Visual
        Private CreatedDelegate As [Delegate]

        Private Sub InternalVisualAncestorChangedHandler(sender As Object, e As Object)
            RaiseEvent VisualAncestorChanged(sender, New AncestorChangedEventArgs(e))
        End Sub

        Public Event VisualAncestorChanged As EventHandler(Of AncestorChangedEventArgs)

        Private Shared _InternalVisualAncestorChangedEvent As Reflection.EventInfo
        Public Shared ReadOnly Property InternalVisualAncestorChangedEvent As Reflection.EventInfo
            Get
                If _InternalVisualAncestorChangedEvent Is Nothing Then _InternalVisualAncestorChangedEvent = GetType(Visual).GetEvent("VisualAncestorChanged", Reflection.BindingFlags.Instance Or Reflection.BindingFlags.NonPublic)
                Return _InternalVisualAncestorChangedEvent
            End Get
        End Property

#Region "IDisposable"
        Private disposedValue As Boolean

        Protected Overridable Sub Dispose(disposing As Boolean)
            If Not disposedValue Then
                If disposing Then
                    InternalVisualAncestorChangedEvent.RemoveMethod.Invoke(Visual, {CreatedDelegate})
                End If

                disposedValue = True
            End If
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose
            ' Do not change this code. Put cleanup code in 'Dispose(disposing As Boolean)' method
            Dispose(disposing:=True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region
    End Class

    Public Class AncestorChangedEventArgs
        Public Sub New(internalArgs As Object)
            Ancestor = internalArgs.Ancestor
            OldParent = internalArgs.OldParent
        End Sub

        Public ReadOnly Property Ancestor As DependencyObject
        Public ReadOnly Property OldParent As DependencyObject
    End Class

现在,我有义务警告这段代码不安全并且可能在未来失效。因为我正在挂钩的事件不是公共的,微软没有任何要求保留它。名称技术上可以更改,或者整个内部实现可能会更改,那么这段代码突然就会开始抛出错误。
另一方面,我们在这里讨论的是WPF的一些非常基本的部分。如果它改变了,我会感到惊讶,所以我愿意在我编写此项目时承担这种风险。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接