问题
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 TextBlock
s 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