Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 749
Jerry Nixon Avatar asked May 16 '11 22:05

Jerry Nixon


2 Answers

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);
    }
like image 189
Ed Bayiates Avatar answered Nov 16 '22 02:11

Ed Bayiates


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
            }
        }
    }
}
like image 43
Eli Dagan Avatar answered Nov 16 '22 02:11

Eli Dagan