Access children of Custom Control wpf c#

…衆ロ難τιáo~ 提交于 2019-12-08 14:05:01

问题


Being quite experienced with WPF UserControl, I thought of trying out custom controls. Here's what I have so far :

Generic.XAML

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:testapp">
    <Style TargetType="{x:Type local:testcontrol}">
        <Setter Property="Height" Value="45"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:testcontrol}">
                    <TextBlock x:Name="tb" Text="test"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

CustomControl.Cs

using System.Windows.Controls.Primitives;

public class testcontrol : System.Windows.Controls.Control
{
    public TextBlock tb;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        tb = this.GetTemplateChild("tb");
    }

    public static test()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof (testcontrol), new FrameworkPropertyMetadata(typeof (testcontrol)));
    }

    private void testcontrol_MouseDown(object sender, MouseButtonEventArgs e)
    {
        tb.Text = "Abc";
    }
}

Now when the MouseDown event is fired, the TextBlock's text changes which means the textblock is found/accessible...

But when i do the same thing from my window as follows :

 private void test()
 {
     testcontrol tt = new testcontrol();
     tt.tb.Text = "abc";
 }

a NullReferenceException is thrown. I wonder why it fails to find the TextBlock? Any explanation will be highly appreciated.

One last thing, I just got started with custom controls so please be kind to indicate if my approach is correct :)


回答1:


As discussed on comments, you should use the Dependency Property or Routed Events to update the child templated parent instead of direct accessing.

Below is simple example of Custom Control which has Header (TextBlock) before it.

[TemplatePart(Name = "PreText", Type = typeof(TextBlock))]
public class ExtendedTextBox : TextBox
{
    public static readonly DependencyProperty PreTextDependency = DependencyProperty.Register("PreText", typeof(string), typeof(ExtendedTextBox));

    public string PreText
    {
        get
        {
            return (string)GetValue(PreTextDependency);
        }

        set
        {
            SetValue(PreTextDependency, value);
        }
    }

    private TextBlock preTextBlock;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        preTextBlock = GetTemplateChild("PreText") as TextBlock;
        Binding preTextBinding = new Binding("PreText");
        preTextBinding.Source = this;
        preTextBinding.Mode = BindingMode.TwoWay;
        preTextBlock.SetBinding(TextBlock.TextProperty, preTextBinding);
    }
}

XAML of this ExtendedTextBox:

    <SolidColorBrush x:Key="TextBox.Static.Border" Color="#FFABAdB3"/>
    <SolidColorBrush x:Key="TextBox.MouseOver.Border" Color="#FF7EB4EA"/>
    <SolidColorBrush x:Key="TextBox.Focus.Border" Color="#FF569DE5"/>
    <Style x:Key="ExtendedTextBoxStyle" TargetType="{x:Type local:ExtendedTextBox}">
        <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"/>
        <Setter Property="BorderBrush" Value="{StaticResource TextBox.Static.Border}"/>
        <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="KeyboardNavigation.TabNavigation" Value="None"/>
        <Setter Property="HorizontalContentAlignment" Value="Left"/>
        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
        <Setter Property="AllowDrop" Value="true"/>
        <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
        <Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:ExtendedTextBox}">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto" />
                            <ColumnDefinition Width="*" />
                        </Grid.ColumnDefinitions>
                        <TextBlock Grid.Column="0" Text="{Binding PreText}" Name="PreText" IsHitTestVisible="False" />
                        <Border Grid.Column="1" x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
                            <ScrollViewer x:Name="PART_ContentHost" Focusable="false" HorizontalScrollBarVisibility="Hidden" VerticalScrollBarVisibility="Hidden"/>
                        </Border>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsEnabled" Value="false">
                            <Setter Property="Opacity" TargetName="border" Value="0.56"/>
                        </Trigger>
                        <Trigger Property="IsMouseOver" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.MouseOver.Border}"/>
                        </Trigger>
                        <Trigger Property="IsKeyboardFocused" Value="true">
                            <Setter Property="BorderBrush" TargetName="border" Value="{StaticResource TextBox.Focus.Border}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsInactiveSelectionHighlightEnabled" Value="true"/>
                    <Condition Property="IsSelectionActive" Value="false"/>
                </MultiTrigger.Conditions>
                <Setter Property="SelectionBrush" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
            </MultiTrigger>
        </Style.Triggers>
    </Style>

How to define in XAML :

    <local:ExtendedTextBox x:Name="extendedTextBox" Grid.Row="1" Style="{StaticResource ExtendedTextBoxStyle}" Text="Some Text!!" PreText="Pre Text :" />

Now how you should update the template child Value (I've give a name of extendedTextBox):

this.extendedTextBox.PreText = "I'm clicked";



回答2:


You could put this extension method in a helper class

public static IEnumerable<T> GetVisualChildren<T>(this DependencyObject parent) where T : DependencyObject
{
    if (parent == null)
    {
        yield return null;
    }

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(parent, i);

        if (child is T t)
        {
            yield return t;
        }

        foreach (T childrensChild in child.FindVisualChildren<T>())
        {
            yield return childrensChild;
        }
    }
}

and then you can use it like this:

testcontrol tt = new testcontrol();
IEnumerable<TextBlock> allTextBlocks = tt.GetVisualChildren<TextBlock>();

to get all TextBlocks inside your testcontrol.


For your case especially, you could use

testcontroll tt = new testcontrol();
IEnumerable<TextBlock> allTextBlocks = tt.GetVisualChildren<TextBlock>();
TextBlock tb = allTextBlocks.Single(t => t.Name = "tb");

if(tb != null)
{
    tb.Text = "abc";
}

I'm not sure if FindTemplateChild<T> could be of use here; at least it's already inbuilt, so you could give it a try.



来源:https://stackoverflow.com/questions/50096254/access-children-of-custom-control-wpf-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!