WPF - How to detect when new Visual child elements are added?
Asked Answered
O

2

7

Based on some custom security settings, I alter window child controls to readonly and disabled. To accomplish this, I loop through child controls when the window loads.

This works just fine. 99% perfect.

In my window I have an ItemsControl whose content is based on a ComboBox. Change the ComboBox, the child controls in the ItemsControl are databound again. But, then security (readonly/disabled) is no longer true.

Before you jump to the solution, I know I could handle the ComboBox changed event; but, I have many such boxes and wnt a generic solution to can apply at the window-level (think: base) no matter what my developers add to the window/form.

My question (sorry for the long lead in) is, how can I detect when a new child is added to the window because of some dynamic activity like databinding? Is there a NewChildAdded event? Is there a DataBindingJustChangedThings event?

There's gotta be something.

If your solution includes a timer, you need not reply. My forms are too complex to handle that extra load - and the delay between ticks is too real of a security issue.

You might be thinking, just make the outer-container readonly or disabled. But this has a negative effect on things like expanders, multi-line textboxes and listboxes. Such an approach is not grainular enough. Of course, it is where we started iterations ago.

If your solution includes a style, you need to include how I can override your approach on a per-control basis. Some controls (like a checkbox) cannot be disabled as they have a purpose in the UI layout.

Sorry for the constraints, but I plan to use the solution in production.

Thank you.

Ozan answered 16/5, 2011 at 22:35 Comment(0)
L
22

Have you tried OnVisualChildrenChanged?

    /// <summary>
    /// Handle visual children being added or removed
    /// </summary>
    /// <param name="visualAdded">Visual child added</param>
    /// <param name="visualRemoved">Visual child removed</param>
    protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
    {
        // Track when objects are added and removed
        if (visualAdded != null)
        {
            // Do stuff with the added object
        }
        if (visualRemoved != null)
        {
            // Do stuff with the removed object
        }

        // Call base function
        base.OnVisualChildrenChanged(visualAdded, visualRemoved);
    }
Loudmouth answered 16/5, 2011 at 23:14 Comment(2)
Unfortunately, OnVisualChildrenChanged does not appear to be a publicly accessible event for Canvas. :( Time to inherit.Helenehelenka
Yeah for Canvas: #5134580Loudmouth
D
5

Very hacky but worked for me, in case you don't inherit from the control so you can't override the OnVisualChildrenChanged method.

You can listen to LayoutUpdated event.

On the example bellow, I want to listen to the first time my Grid, named GridYouWantToListenTo, add one or two elements:

<Window x:Class="WpfApplication23.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication23"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid x:Name="GridYouWantToListenTo">
    </Grid>
</Window>

Code behind:

using System;
using System.Linq;
using System.Windows;

namespace WpfApplication23
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            GridYouWantToListenTo.LayoutUpdated += GridYouWantToListenTo_LayoutUpdated;
        }

        private int _lastNumbreOfGridChildren = 0;
        private void GridYouWantToListenTo_LayoutUpdated(object sender, EventArgs e)
        {
            var children = GridYouWantToListenTo
                    ?.Children
                    ?.OfType<FrameworkElement>() ?? Enumerable.Empty<FrameworkElement>();

            if (!children.Any())
            {
                _lastNumbreOfGridChildren = 0;
            }

            int currentNumberOfItems = children.Count();

            if (_lastNumbreOfGridChildren == 0 && currentNumberOfItems == 1)
            {
                //Your Logic
            }
            else if (_lastNumbreOfGridChildren == 0 && currentNumberOfItems == 2)
            {
                //Your Logic
            }
        }
    }
}
Dentilingual answered 27/11, 2016 at 7:15 Comment(3)
LayoutUpdated has an unfortunate silliness that it always passes null as the sender, so you can't disambiguate listening to multiple controls unless you use unique handlers for each one, which is a bit ridiculous.Remora
@Remora this is the least problem here - you can just write x.LayoutLoaded += (s,e) => GridYouWantToListenTo_LayoutUpdated(x, e);. The problem is you can't really tell when this event is fired, description is quite enigmatic but for sure it will be dispatched way to often for the purpose. Querying for children every time the container is resized by one pixel it's not ideal... not to mention implementing working code which finds the actual children instances that where add...Gibby
Yes, that's a "unique handler for each one", as I said (albeit disguised in a lambda).Remora

© 2022 - 2024 — McMap. All rights reserved.