Question

I have created a custom control (CartesianCanvas) that stores some of the properties of any child element when it is added. To do this I have created a new collection property(ItemsInfo) and overrode OnVisualChildrenChanged so that when a child is added or removed the corresponding properties are added or removed from the collection property.

However, when I add children to the control through XAML, OnVisualChildrenChanged seemes to be called before the properties have been set as all the properties have their default values. This is not the case when a child is added after the window has been loaded. How can I ensure that the child's properties have been set when OnVisualChildrenChanged is called?

Here is my code:

Public ReadOnly Property ItemsInfo As Collection(Of CartesianInfo)
    Get
        Return DirectCast(GetValue(ItemsInfoProperty), Collection(Of CartesianInfo))
    End Get
End Property

Friend Shared ReadOnly ItemsInfoKey As System.Windows.DependencyPropertyKey = System.Windows.DependencyProperty.RegisterReadOnly("ItemsInfo", GetType(Collection(Of CartesianInfo)), GetType(CartesianCanvas), New System.Windows.PropertyMetadata(New Collection(Of CartesianInfo)))
Public Shared ReadOnly ItemsInfoProperty As DependencyProperty = ItemsInfoKey.DependencyProperty

Protected Overrides Sub OnVisualChildrenChanged(visualAdded As DependencyObject, visualRemoved As DependencyObject)

        Try
            If Not visualAdded Is Nothing Then
                If Not GetType(FrameworkElement).IsAssignableFrom(visualAdded.GetType) Then
                    Me.Children.Remove(visualAdded)
                    Throw New Exception("The object added:" & visualAdded.ToString & " was not of type or decended from: FrameworkElement and so was removed from CartesianCanvas")
                Else
                    AddItemInfo(visualAdded)
                End If
            End If
        Catch ex As Exception
            Console.WriteLine(ex.Message)
        End Try


        If Not visualRemoved Is Nothing Then
            RemoveItemInfo(visualRemoved)
        End If
        MyBase.OnVisualChildrenChanged(visualAdded, visualRemoved)

    End Sub

Private Sub AddItemInfo(ByRef item As FrameworkElement)
    Dim itemsInfoCollection As New Collection(Of CartesianInfo)
    itemsInfoCollection = ItemsInfo

    Dim ItemXCoordinate As Double
    Dim ItemYCoordinate As Double
    Dim ItemCalculateFromVerticalCenter As Boolean
    Dim ItemCalculateFromHorizontalCenter As Boolean

    If Double.IsNaN(Canvas.GetLeft(item)) Then
        Canvas.SetLeft(item, 0)
    End If

    If Double.IsNaN(Canvas.GetTop(item)) Then
        Canvas.SetTop(item, 0)
    End If

    ItemXCoordinate = Canvas.GetLeft(item)
    ItemYCoordinate = Canvas.GetTop(item)

    If item.VerticalAlignment = Windows.VerticalAlignment.Center Then
        ItemCalculateFromVerticalCenter = True
    End If
    If item.HorizontalAlignment = Windows.HorizontalAlignment.Center Then
        ItemCalculateFromHorizontalCenter = True
    End If

    ItemsInfo.Add(New CartesianInfo(item, ItemXCoordinate, ItemYCoordinate, ItemCalculateFromVerticalCenter, ItemCalculateFromHorizontalCenter))

    SetValue(ItemsInfoKey, ItemsInfo)

    PositionChild(item)
End Sub

Here is my XAML

<local:CartesianCanvas x:Name="MainCanvas">           
    <Button Width="50" Height="100" Canvas.Top="100" Canvas.Left="20"
            Content="test" Click="Button_Click"/>
</local:CartesianCanvas>
Was it helpful?

Solution

WPF is pretty complicated especially when writing custom controls therefore my first suggestion would be - leave it. Take an already existing control or composite few already existing controls into UserControl instead of writing everything from scratch.

Controls in WPF may contain any other control and the control's children are loaded only when needed. That is why you run into your problem.

The solution is when OnVisualChildrenChanged event fires, you should run through all children and subscribe to their Initialized or Loaded event. Once the child is loaded the event will fire and the handler will be called. Furthermore inside the handler you shouldn't forget to unsubscribe to the event.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top