locked
Nested UserControl / Nested Dependeny properties RRS feed

  • Question

  • Hi all,

     

    I am quite new to WPF an I am confused about all the informations about Dependency properties. I want to have 2 Usercontrols:

    a) "Port_UserControl" (which needs 2 int properties "ChannelNr" and "PortNr" )
    b) "Channel_UserControl" (needs 1 int propertiy "ChannelNumber"

    The "Channel_UserControl" consists of 2 instances of "Port_UserControl", whereas the "Channel_UserControl" inszance show forward its "ChannelNr" down to the 2 "Port_UserControls" for example:

    - Channel 1:
         - Port 10 on Channel 1
         - Port 20 on Channel 1

    - Channel 2:
        - Port 10 on Channel 2
        - Port 20 on Channel 2

    I have tried all day and had no success but I cab supply my test code here:
    a) XAML for Main Window

    <Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:NestedUserControls" x:Class="NestedUserControls.MainWindow"
        Title="MainWindow" Height="294" Width="652">
      <Grid>
      	<local:Cannel_UserControl x:Name="myChannel1" Channel="1"  HorizontalAlignment="Left" Margin="24,24,0,0" Width="288" Height="216" VerticalAlignment="Top"/>
    		<local:Cannel_UserControl x:Name="myChannel2" Channel="2"  HorizontalAlignment="Left" Margin="328,24,0,0" Width="288" Height="216" VerticalAlignment="Top"/>
      </Grid>
    </Window>
    
    

     

    b) XAML for Channel_UserControl:

    <UserControl
    	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:NestedUserControls"
    	mc:Ignorable="d"
    	x:Class="NestedUserControls.Cannel_UserControl"
    	x:Name="UserControl"
    	UseLayoutRounding="True" Height="200" Width="296">
    
    	<Grid x:Name="LayoutRoot" Background="#FFE5D4D4">
    		<TextBlock x:Name="TextBlock_Channel" HorizontalAlignment="Left" Height="16" Margin="8,8,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Width="180"/>
    		<local:Port_UserControl x:Name="myPort1" Port="10" HorizontalAlignment="Left" Height="56" Margin="8,40,0,0" VerticalAlignment="Top" Width="272" d:LayoutOverrides="VerticalAlignment"/>
    		<local:Port_UserControl x:Name="myPort2" Port="20" HorizontalAlignment="Left" Height="56" Margin="8,112,0,0" VerticalAlignment="Top" Width="272"/>
    	</Grid>
    </UserControl>
    

     

    c) XAML for Port_UserControl

    <UserControl
    	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"
    	mc:Ignorable="d"
    	x:Class="NestedUserControls.Port_UserControl"
    	x:Name="UserControl"
    	UseLayoutRounding="True" Height="48" Width="200" Background="#FF999999">
    
    	<Grid x:Name="LayoutRoot">
    		<TextBlock x:Name="TextBlock_ChannelNumber" HorizontalAlignment="Left" Height="16" Margin="8,8,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Width="184"/>
    		<TextBlock x:Name="TextBlock_PortNumber" HorizontalAlignment="Left" Height="16" Margin="8,28,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Width="184"/>
    	</Grid>
    </UserControl>
    

     

    d) C# for class Channel_UserControl

    	public partial class Cannel_UserControl : UserControl {
    
    		// Define a new property "ChannelProperty" and a callback function which is called when the property changes
    		// ---------------------------------------------------------------------------------------------------------
    		public static readonly DependencyProperty ChannelProperty = DependencyProperty.Register(
    			"Channel",
    			typeof( int ),
    			typeof(Cannel_UserControl),
    			new FrameworkPropertyMetadata( new PropertyChangedCallback( OnChannelChanged ) )
    		);
    
    		public int Channel {
    			get { return (int)GetValue(ChannelProperty); }
    			set { SetValue(ChannelProperty, value); }
    		}
    
    		public Cannel_UserControl() {
    			this.InitializeComponent();
    		}
    
    		private void InitializeUserControl( int iChannelNr ) {
    			this.TextBlock_Channel.Text = "Cannel_UserControl: Channel=" + iChannelNr.ToString();
    			// Do something
    		}
    
    		public static void OnChannelChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
    			System.Console.WriteLine("Cannel_UserControl.OnChannelChanged: Channel=" + e.NewValue.ToString());
    			Cannel_UserControl _this = (Cannel_UserControl)d;
    			int iChannelNr = (int)e.NewValue;
    			_this.InitializeUserControl( iChannelNr );
    		}
    	}
    
    

    e) C# for class Port_UserControl

    	public partial class Port_UserControl : UserControl {
    
    		// Define a new property "PortProperty" and a callback function which is called when the property changes
    		// ------------------------------------------------------------------------------------------------------
    		public static readonly DependencyProperty PortProperty = DependencyProperty.Register(
    			"Port",
    			typeof(int),
    			typeof(Port_UserControl),
    			new FrameworkPropertyMetadata(new PropertyChangedCallback( OnPortChanged ))
    		);
    
    		public int Port {
    			get { return (int)GetValue(PortProperty); }
    			set { SetValue(PortProperty, value); }
    		}
    
    	
    		// *********************************************************************************
    		// Definitions (code) to be inserted that calls "OnChannelChanged" to retrieve the
    		// channel number of the overlaying "Channel_UserControl" instance
    		// ***********************************************************************************
    
    		public Port_UserControl() {
    			this.InitializeComponent();
    		}
    
    		private void InitializeUserControl( int iPortNr ) {
    			this.TextBlock_PortNumber.Text  = "Port_UserControl: Port=" + iPortNr.ToString();
    			this.TextBlock_ChannelNumber.Text = "Port_UserControl: Channel=" + "???????????";
    			// Do something
    		}
    
    		public static void OnPortChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
    			System.Console.WriteLine("Port_UserControl.OnPortChanged: Port=" + e.NewValue.ToString());
    			Port_UserControl _this = (Port_UserControl)d;
    			int iPortNr = (int)e.NewValue;
    			_this.InitializeUserControl( iPortNr );
    		}
    
    		public static void OnChannelChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
    			System.Console.WriteLine("Port_UserControl.OnChannelChanged: Channel=" + e.NewValue.ToString());
    			Cannel_UserControl _this = (Cannel_UserControl)d;
    			int iChannelNr = (int)e.NewValue;
    		}
    	}
    

    The example works quite fine but I have no idea how to get the "ChannelNr" down to the 2 "Port_UserControl" instances. I have read and tried many things but don't even know if I have to use AddOwner or RegisterAttached or even another technique to get code to work as expected.

    Thank you very much for helping a lost newbie :-)

    Michael

     

     

     

    Tuesday, July 27, 2010 4:36 PM

Answers

  • Sounds like you want an Attached Property. You need to define the Channel property on the Channel_UserControl using DependencyProperty.RegisterAttached making sure to include FrameworkPropertyMetadataOptions.Inherits in the FrameworkPropertyMetadata. Then, in the Port_UserControl you will use Channel_UserControl.AddOwner to add the Attached property to the Port_UserControl. Now, when you set the Channel in the parent Channel_UserControl, it should automatically cascade down to the Port_UserControl.
    • Marked as answer by Snnoopy33 Wednesday, July 28, 2010 7:34 AM
    Tuesday, July 27, 2010 5:11 PM

All replies

  • Sounds like you want an Attached Property. You need to define the Channel property on the Channel_UserControl using DependencyProperty.RegisterAttached making sure to include FrameworkPropertyMetadataOptions.Inherits in the FrameworkPropertyMetadata. Then, in the Port_UserControl you will use Channel_UserControl.AddOwner to add the Attached property to the Port_UserControl. Now, when you set the Channel in the parent Channel_UserControl, it should automatically cascade down to the Port_UserControl.
    • Marked as answer by Snnoopy33 Wednesday, July 28, 2010 7:34 AM
    Tuesday, July 27, 2010 5:11 PM
  • Thank you very much. Your tips were of great help to me and it really worked! Obviously, the callback function "OnChannelChanged" in "Channel_UserControl" is now call many times from different sources (see output below) and therefore I also had to change the casting section here, to prevent a Cast exception:

     

    			if ( d.ToString().Equals( "NestedUserControls.Cannel_UserControl" )) {
    				Cannel_UserControl _this = (Cannel_UserControl)d;
    				int iChannelNr = (int)e.NewValue;
    				_this.InitializeUserControl(iChannelNr);
    			}
    
    
    

    Output:

    Cannel_UserControl.OnChannelChanged: DependencyObject = NestedUserControls.Cannel_UserControl   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.Grid   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.TextBlock   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = NestedUserControls.Port_UserControl   Channel=2
    Port_UserControl.OnChannelChanged: DependencyObject = NestedUserControls.Port_UserControl   Channel=2
    Port_UserControl.InitializeUserControl: Channel=2   Port=10
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.Grid   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.TextBlock   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.TextBlock   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = NestedUserControls.Port_UserControl   Channel=2
    Port_UserControl.OnChannelChanged: DependencyObject = NestedUserControls.Port_UserControl   Channel=2
    Port_UserControl.InitializeUserControl: Channel=2   Port=20
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.Grid   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.TextBlock   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.TextBlock   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.Border   Channel=1
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.ContentPresenter   Channel=1
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.Border   Channel=1
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.ContentPresenter   Channel=1
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.Border   Channel=1
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.ContentPresenter   Channel=1
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.Border   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.ContentPresenter   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.Border   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.ContentPresenter   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.Border   Channel=2
    Cannel_UserControl.OnChannelChanged: DependencyObject = System.Windows.Controls.ContentPresenter   Channel=2

    For completeness of the example I include the entire code for the two classes here again:

    a) class Channel_UserControl

    namespace NestedUserControls {
    	public partial class Cannel_UserControl : UserControl {
    
    		// Define a new property "ChannelProperty" and a callback function which is called when the property changes.
    		// We register it using "RegisterAttached", so that changes in the channel number can cascade down from the
    		// parent to other (child) sub controls (here: Port_UserControl)
    		// ---------------------------------------------------------------------------------------------------------
    		public static readonly DependencyProperty ChannelProperty = DependencyProperty.RegisterAttached(
    			"Channel",
    			typeof( int ),
    			typeof(Cannel_UserControl),
    			new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnChannelChanged) )
    		);
    
    		public int Channel {
    			get { return (int)GetValue(ChannelProperty); }
    			set { SetValue(ChannelProperty, value); }
    		}
    
    		public Cannel_UserControl() {
    			this.InitializeComponent();
    		}
    
    		private void InitializeUserControl( int iChannelNr ) {
    			this.TextBlock_Channel.Text = "Cannel_UserControl: Channel=" + iChannelNr.ToString();
    			// Do something
    		}
    
    		public static void OnChannelChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
    			System.Console.WriteLine("Cannel_UserControl.OnChannelChanged: DependencyObject = " + d.ToString() + "  Channel=" + e.NewValue.ToString());
    
    			if ( d.ToString().Equals( "NestedUserControls.Cannel_UserControl" )) {
    				Cannel_UserControl _this = (Cannel_UserControl)d;
    				int iChannelNr = (int)e.NewValue;
    				_this.InitializeUserControl(iChannelNr);
    			}
    		}
    	}
    }
    


    b) class Port_UserControl

    namespace NestedUserControls {
    	public partial class Port_UserControl : UserControl {
    
    		// Define a new property "PortProperty" and a callback function which is called when the property changes
    		// ------------------------------------------------------------------------------------------------------
    		public static readonly DependencyProperty PortProperty = DependencyProperty.Register(
    			"Port",
    			typeof(int),
    			typeof(Port_UserControl),
    			new FrameworkPropertyMetadata(new PropertyChangedCallback( OnPortChanged ))
    		);
    
    		public int Port {
    			get { return (int)GetValue(PortProperty); }
    			set { SetValue(PortProperty, value); }
    		}
    
    		// Define a "ChannelProperty" which inherits from the "Cannel_UserControl" to allow changes of the channel number
    		// in the parent "Channel_UserControl" to cascade down
    		// ---------------------------------------------------------------------------------------------------------------
    		public static readonly DependencyProperty ChannelProperty = Cannel_UserControl.ChannelProperty.AddOwner(typeof(Port_UserControl),
    			new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.Inherits, new PropertyChangedCallback(OnChannelChanged))
    		);
    
    		public int Channel {
    			get { return (int)GetValue(ChannelProperty); }
    			set { SetValue(ChannelProperty, value); }
    		}
    
    		public Port_UserControl() {
    			this.InitializeComponent();
    		}
    
    		private void InitializeUserControl() {
    			System.Console.WriteLine("Port_UserControl.InitializeUserControl: Channel=" + Channel.ToString() + "  Port=" + Port.ToString() );
    
    			// We may do intialization only when both parameters are set correctly
    			// -------------------------------------------------------------------
    			if (( Channel > 0 ) && ( Port > 0 )) {
    				this.TextBlock_PortNumber.Text =  "Port_UserControl: Port=" + Port.ToString();
    				this.TextBlock_ChannelNumber.Text = "Port_UserControl: Channel=" + Channel.ToString();
    				// Do rest of initialization
    			}
    		}
    
    		public static void OnPortChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
    			System.Console.WriteLine("Port_UserControl.OnPortChanged: Port=" + e.NewValue.ToString());
    			Port_UserControl _this = (Port_UserControl)d;
    			int iPortNr = (int)e.NewValue;
    			_this.Port = iPortNr;
    			_this.InitializeUserControl();
    		}
    
    		public static void OnChannelChanged( DependencyObject d, DependencyPropertyChangedEventArgs e ) {
    			System.Console.WriteLine("Port_UserControl.OnChannelChanged: DependencyObject = " + d.ToString() + "  Channel=" + e.NewValue.ToString());
    
    			Port_UserControl _this = (Port_UserControl)d;
    			int iChannelNr = (int)e.NewValue;
    			_this.Channel = iChannelNr;
    			_this.InitializeUserControl();
    		}
    	}
    }
    

    Wednesday, July 28, 2010 7:33 AM
  • Thanks to "wjouts" help, I could solve the problem. But the questions comes up, if it is also possible to pass both parameters (ChannelNr & PortNr) to the two instances of "Port_UserControl" using only XAML, that is to pass the current value for the ChannelNr together with a fixed value for the PortNr something like that:

    <UserControl
    	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:NestedUserControls"
    	mc:Ignorable="d"
    	x:Class="NestedUserControls.Cannel_UserControl"
    	x:Name="UserControl"
    	UseLayoutRounding="True" Height="200" Width="296">
    
    	<Grid x:Name="LayoutRoot" Background="#FFE5D4D4">
    		<TextBlock x:Name="TextBlock_Channel" HorizontalAlignment="Left" Height="16" Margin="8,8,0,0" TextWrapping="Wrap" Text="TextBlock" VerticalAlignment="Top" Width="180"/>
    		<local:Port_UserControl x:Name="myPort1" Channel="????????" Port="10" HorizontalAlignment="Left" Height="56" Margin="8,40,0,0" VerticalAlignment="Top" Width="272" d:LayoutOverrides="VerticalAlignment"/>
    		<local:Port_UserControl x:Name="myPort2" Channel="????????" Port="20" HorizontalAlignment="Left" Height="56" Margin="8,112,0,0" VerticalAlignment="Top" Width="272"/>
    	</Grid>
    </UserControl>
    

    What would be the correct syntax to pass the current channel number? (Replacement for Channel="????????")

     

    Wednesday, July 28, 2010 7:45 AM