访问自定义控件的子项wpf c#

时间:2018-04-30 07:57:31

标签: c# wpf custom-controls

对WPF UserControl非常有经验,我想过尝试自定义控件。这就是我到目前为止所拥有的:

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";
    }
}

现在,当MouseDown事件被触发时,TextBlock的文字会发生变化,这意味着找到/可访问文本块...

但是当我在窗口做同样的事情时如下:

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

抛出NullReferenceException。我想知道为什么找不到TextBlock?任何解释都将受到高度赞赏。

最后一件事,我刚开始使用自定义控件,所以请善意表明我的方法是否正确:)

2 个答案:

答案 0 :(得分:1)

您可以将此扩展方法放在帮助程序类

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;
        }
    }
}

然后你可以像这样使用它:

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

TextBlock 中获取所有testcontrol

特别是对于您的情况,您可以使用

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

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

我不确定FindTemplateChild<T>是否可以在这里使用;至少它已经内置了,所以你可以尝试一下。

答案 1 :(得分:1)

正如在评论中所讨论的,您应该使用依赖属性或路由事件来更新子模板化父级而不是直接访问。

以下是自定义控件的简单示例,其前面有Header(TextBlock)。

[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);
    }
}

ExtendedTextBox的XAML:

    <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>

如何在XAML中定义:

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

现在你应该如何更新模板子值(我的名字是extendedTextBox):

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