none
How can I get controls inside the custom control? RRS feed

  • Question

  • I made a Custom Control, here is the XAML:

        <Style TargetType="{x:Type local:Sheet}">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type local:Sheet}">
                            <Grid Grid.IsSharedSizeScope="True">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="71"></ColumnDefinition>
                                    <ColumnDefinition Width="auto"></ColumnDefinition>
                                </Grid.ColumnDefinitions>
                                <Grid.RowDefinitions>
                                    <RowDefinition Height="18"></RowDefinition>
                                    <RowDefinition Height="auto"></RowDefinition>
                                </Grid.RowDefinitions>
                                <Border Background="{TemplateBinding Background}" Grid.Column="1" Grid.Row="1"
                                    BorderBrush="{TemplateBinding BorderBrush}"
                                    BorderThickness="{TemplateBinding BorderThickness}" VerticalAlignment="{TemplateBinding VerticalAlignment}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}">
                                    <Grid x:Name="G">
                                        <Border Background="#59217346"></Border>
                                    </Grid>
                                </Border>
                                <Button Background="#e6e6e6" BorderBrush="#999999" BorderThickness="0,0,1,1" Visibility="Collapsed">◢</Button>
                            </Grid>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>

    I need to access the Grid which `x:Name="G"` to add&remove its children.

    I found a topic about this:https://stackoverflow.com/questions/50096254/access-children-of-custom-control-wpf-c-sharp

    It suggests using a `Dependency Property`. However, I can't bind the `Children` of the `Grid` yet.

    There is another solution by using a `VisualTreeHelper` to get the `Grid` directly.

    Meanwhile, the `VisualTreeHelper` can only get the child while the control is completely loaded, or it only returns 0 children.

    I need to initialize its children while creates it and hardly wait the control loaded. For example, whenever I set a `Dependency Property` Rows of the Custom Control to 4, the `Grid` which `x:Name="G"` will add&remove its `RowDefinition` to 4.

    How can I solve this? Please help me.
    Sunday, June 16, 2019 11:44 AM

Answers


  • Hi     mywatermelon,

    You can try to overridden OnApplyTemplate and use the GetTemplateChild method(This method gets called automatically when you apply a template to your custom control) like so:

    public class YourcoustomControl {
            private Grid contentGrid;
            protected override void OnApplyTemplate() {
            base.OnApplyTemplate();
            contentGrid = GetTemplateChild("G") as Grid;
        }
    }

    Best regards

    Yong Lu

    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    • Marked as answer by mywatermelon Friday, June 21, 2019 3:13 AM
    Monday, June 17, 2019 5:52 AM
    Moderator

All replies

  • The custom control should be in charge of maintaining its content.  You can create methods to call from outside the control to add/remove content.

    Lloyd Sheen

    Sunday, June 16, 2019 1:02 PM
  • The custom control should be in charge of maintaining its content.  You can create methods to call from outside the control to add/remove content.

    Lloyd Sheen

    As you said, I should create a public void which inside the custom control. By using this new  void to add/remove the content. Meanwhile, how can I control the Grid which `x:Name="G"` directly inside the void? As we know, not like in the page/window, the X:Name not works in the custom control so that I can hardly get this Grid yet.
    Sunday, June 16, 2019 1:13 PM
  • Hi,
    you can use the System.Windows.Interactivity from Nuget to get the reference to the Grid (in the Style) in ViewModel. If the UserControl is in a separete Project (dll) you can use a interface. 

    Try this demo:

    XAML of UserControl:

    <UserControl x:Class="Window38UC1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfControlLibrary1" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <UserControl.Resources> <Style TargetType="{x:Type TextBox}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <Grid Grid.IsSharedSizeScope="True"> <Grid.ColumnDefinitions> <ColumnDefinition Width="71"></ColumnDefinition> <ColumnDefinition Width="auto"></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="18"></RowDefinition> <RowDefinition Height="auto"></RowDefinition> </Grid.RowDefinitions> <Border Background="{TemplateBinding Background}" Grid.Column="1" Grid.Row="1" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" VerticalAlignment="{TemplateBinding VerticalAlignment}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"> <Grid x:Name="G"> <i:Interaction.Behaviors> <local:Window38UC1Behavior/> </i:Interaction.Behaviors> </Grid> </Border> <Button Background="#e6e6e6" BorderBrush="#999999" BorderThickness="0,0,1,1" Visibility="Collapsed">◢</Button> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </UserControl.Resources> <TextBox/> </UserControl>


    The Behavior and interface (in VB.NET):

    Imports System.Windows.Interactivity
    
    Public Class Window38UC1Behavior
      Inherits Behavior(Of Grid)
    
      Protected Overrides Sub OnAttached()
        AddHandler AssociatedObject.Loaded, AddressOf Grid_Loaded
      End Sub
    
      Private Sub Grid_Loaded(sender As Object, e As RoutedEventArgs)
        Dim grd = CType(sender, Grid)
        CType(grd.DataContext, IWindow38VM).GridControl = grd
      End Sub
    End Class
    
    Public Interface IWindow38VM
      Property GridControl As Grid
    End Interface

    XAML: using the UserCopntrol:

    <Window x:Class="Window38"
            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:WpfApp1"
            xmlns:uc="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"
            mc:Ignorable="d"
            Title="Window38" Height="450" Width="800">
      <Window.DataContext>
        <local:Window38VM/>
      </Window.DataContext>
      <StackPanel>
        <Button Content="Add Controls to UserControl" Command="{Binding Cmd}"/>
        <uc:Window38UC1 />
      </StackPanel>
    </Window>

    and the ViewModel:

    Imports System.ComponentModel
    Imports System.Runtime.CompilerServices
    Imports WpfControlLibrary1
    
    Public Class Window38VM
      Implements IWindow38VM
    
      Public ReadOnly Property Cmd As ICommand
        Get
          Return New RelayCommand(AddressOf CmdExec)
        End Get
      End Property
    
      Private Sub CmdExec(obj As Object)
        GridControl.Children.Add(New Label With {.Content = $"Label {Now.ToLongTimeString}"})
      End Sub
    
      Private Property GridControl As Grid Implements IWindow38VM.GridControl
    
    End Class


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks



    Sunday, June 16, 2019 2:54 PM
  • Hi,
    you can use the System.Windows.Interactivity from Nuget to get the reference to the Grid (in the Style) in ViewModel. If the UserControl is in a separete Project (dll) you can use a interface. 

    Try this demo:

    XAML of UserControl:

    <UserControl x:Class="Window38UC1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:WpfControlLibrary1" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <UserControl.Resources> <Style TargetType="{x:Type TextBox}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type TextBox}"> <Grid Grid.IsSharedSizeScope="True"> <Grid.ColumnDefinitions> <ColumnDefinition Width="71"></ColumnDefinition> <ColumnDefinition Width="auto"></ColumnDefinition> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="18"></RowDefinition> <RowDefinition Height="auto"></RowDefinition> </Grid.RowDefinitions> <Border Background="{TemplateBinding Background}" Grid.Column="1" Grid.Row="1" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" VerticalAlignment="{TemplateBinding VerticalAlignment}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"> <Grid x:Name="G"> <i:Interaction.Behaviors> <local:Window38UC1Behavior/> </i:Interaction.Behaviors> </Grid> </Border> <Button Background="#e6e6e6" BorderBrush="#999999" BorderThickness="0,0,1,1" Visibility="Collapsed">◢</Button> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </UserControl.Resources> <TextBox/> </UserControl>


    The Behavior and interface (in VB.NET):

    Imports System.Windows.Interactivity
    
    Public Class Window38UC1Behavior
      Inherits Behavior(Of Grid)
    
      Protected Overrides Sub OnAttached()
        AddHandler AssociatedObject.Loaded, AddressOf Grid_Loaded
      End Sub
    
      Private Sub Grid_Loaded(sender As Object, e As RoutedEventArgs)
        Dim grd = CType(sender, Grid)
        CType(grd.DataContext, IWindow38VM).GridControl = grd
      End Sub
    End Class
    
    Public Interface IWindow38VM
      Property GridControl As Grid
    End Interface

    XAML: using the UserCopntrol:

    <Window x:Class="Window38"
            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:WpfApp1"
            xmlns:uc="clr-namespace:WpfControlLibrary1;assembly=WpfControlLibrary1"
            mc:Ignorable="d"
            Title="Window38" Height="450" Width="800">
      <Window.DataContext>
        <local:Window38VM/>
      </Window.DataContext>
      <StackPanel>
        <Button Content="Add Controls to UserControl" Command="{Binding Cmd}"/>
        <uc:Window38UC1 />
      </StackPanel>
    </Window>

    and the ViewModel:

    Imports System.ComponentModel
    Imports System.Runtime.CompilerServices
    Imports WpfControlLibrary1
    
    Public Class Window38VM
      Implements IWindow38VM
    
      Public ReadOnly Property Cmd As ICommand
        Get
          Return New RelayCommand(AddressOf CmdExec)
        End Get
      End Property
    
      Private Sub CmdExec(obj As Object)
        GridControl.Children.Add(New Label With {.Content = $"Label {Now.ToLongTimeString}"})
      End Sub
    
      Private Property GridControl As Grid Implements IWindow38VM.GridControl
    
    End Class


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks



    I know little about VB.net so that can hardly know what is the code use for. It seems is much more different from C# yet.

    What's more, I googled about the System.Windows.Interactivity but found less about it.

    And Microsoft has announced no longer update it in https://docs.microsoft.com/en-us/previous-versions/visualstudio/design-tools/expression-studio-4/ff726541(v%3Dexpression.40)

    Monday, June 17, 2019 1:03 AM
  • Hi,
    instead of using System.Windows.Interactivity you can write your own attached property. To load the Interactivity dll you can use the Nuget.

    In C#.NET the code look like this:

    Behavior and interface:

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;
    
    namespace WpfControlLibrary1
    {
      public class Window38UC1Behavior : Behavior<Grid>
      {
        protected override void OnAttached()
        {
          AssociatedObject.Loaded += Grid_Loaded;
        }
    
        private void Grid_Loaded(object sender, RoutedEventArgs e)
        {
          var grd = (Grid)sender;
          ((IWindow38VM)(grd.DataContext)).GridControl = grd;
        }
      }
    
      public interface IWindow38VM
      {
        Grid GridControl { get; set; }
      }
    }

    ViewModel:

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using WpfControlLibrary1;
    
    namespace WpfApp1
    {
      public class Window38VM : IWindow38VM
      {
        public ICommand Cmd
        {
          get
          {
            return new RelayCommand((obj) => GridControl.Children.Add(new Label()
            { Content = $"Label {DateTime.Now.ToLongTimeString()}" }));
          }
        }
        public Grid GridControl { get; set; }
      }
    }



    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Monday, June 17, 2019 4:44 AM
  • Hi,
    instead of using System.Windows.Interactivity you can write your own attached property. To load the Interactivity dll you can use the Nuget.

    In C#.NET the code look like this:

    Behavior and interface:

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;
    
    namespace WpfControlLibrary1
    {
      public class Window38UC1Behavior : Behavior<Grid>
      {
        protected override void OnAttached()
        {
          AssociatedObject.Loaded += Grid_Loaded;
        }
    
        private void Grid_Loaded(object sender, RoutedEventArgs e)
        {
          var grd = (Grid)sender;
          ((IWindow38VM)(grd.DataContext)).GridControl = grd;
        }
      }
    
      public interface IWindow38VM
      {
        Grid GridControl { get; set; }
      }
    }

    ViewModel:

    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Input;
    using WpfControlLibrary1;
    
    namespace WpfApp1
    {
      public class Window38VM : IWindow38VM
      {
        public ICommand Cmd
        {
          get
          {
            return new RelayCommand((obj) => GridControl.Children.Add(new Label()
            { Content = $"Label {DateTime.Now.ToLongTimeString()}" }));
          }
        }
        public Grid GridControl { get; set; }
      }
    }



    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Someone tell me another way:

    public override void OnApplyTemplate()
    {
      var grid = GetTemplateChild("G") as Grid;
      grid.RowDefinitions.Add(new RowDefinition());
    }

    It seems much eaiser than using a Nuget package.....

    Monday, June 17, 2019 5:44 AM

  • Hi     mywatermelon,

    You can try to overridden OnApplyTemplate and use the GetTemplateChild method(This method gets called automatically when you apply a template to your custom control) like so:

    public class YourcoustomControl {
            private Grid contentGrid;
            protected override void OnApplyTemplate() {
            base.OnApplyTemplate();
            contentGrid = GetTemplateChild("G") as Grid;
        }
    }

    Best regards

    Yong Lu

    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    • Marked as answer by mywatermelon Friday, June 21, 2019 3:13 AM
    Monday, June 17, 2019 5:52 AM
    Moderator

  • Hi     mywatermelon,

    You can try to overridden OnApplyTemplate and use the GetTemplateChild method(This method gets called automatically when you apply a template to your custom control) like so:

    public class YourcoustomControl {
            private Grid contentGrid;
            protected override void OnApplyTemplate() {
            base.OnApplyTemplate();
            contentGrid = GetTemplateChild("G") as Grid;
        }
    }

    Best regards

    Yong Lu

    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    I tried this. However, I found a new problem that whenever I create a new control and set the property of contentGrid before it loaded. I should use a ApplyTemplate() first or it will throw an error that the contentGrid is null.

    I feel it is so troublesome to use the ApplyTemplate() everytime. Is there any better way to do this? Thank you.

    Monday, June 17, 2019 7:57 AM
  • Hi,
    the best way is to catch the reference to the embedded grid (in style) and to use this reference for any purpose in the ViewModel. It is best to catch the reference during instantiation using attached property or the System.Windows.Interactivity.

    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Monday, June 17, 2019 8:11 AM