Search code examples
wpftemplatesxamlbindingtemplatebinding

WPF Nested binding in a controltemplate


I have successfully created a textbox that displays / collapses an error message depending upon a validation rule set in its model / vm. The code goes like this for the email for ex.:

    <StackPanel Grid.Row="3" Grid.Column="1">
        <TextBox MaxLength="200" x:Name="mailTextBox"
                Style="{StaticResource SectionEditPropertyTextBox}"
                Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
        <ContentPresenter Visibility="{Binding ElementName=mailTextBox, Path=(Validation.HasError), Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=True }"
                Content="{Binding ElementName=mailTextBox, Path=(Validation.Errors).CurrentItem}"
                HorizontalAlignment="Left">
            <ContentPresenter.ContentTemplate>
                <DataTemplate>
                    <Label Style="{StaticResource SectionEditErrorLabel}" Content="{Binding Path=ErrorContent}"/>
                </DataTemplate>
            </ContentPresenter.ContentTemplate>
        </ContentPresenter>
    </StackPanel>

Since I have a bunch of these, I would have liked to put all of this in a control template and relocate this in a common resource file.

My template looks like this:

<ControlTemplate x:Key="FormTextBox" TargetType="{x:Type TextBox}">
    <StackPanel Grid.Row="{TemplateBinding Grid.Row}" Grid.Column="{TemplateBinding Grid.Column}">
        <TextBox x:Name="validableText" MaxLength="{TemplateBinding MaxLength}"
                Style="{StaticResource SectionEditPropertyTextBox}"
                Text="{TemplateBinding Text}" />
        <ContentPresenter Visibility="{Binding ElementName=validableText, Path=(Validation.HasError), Converter={StaticResource BooleanToVisibilityConverter}, ConverterParameter=True }"
                Content="{Binding ElementName=validableText, Path=(Validation.Errors).CurrentItem}"
                HorizontalAlignment="Left">
            <ContentPresenter.ContentTemplate>
                <DataTemplate>
                    <Label Style="{StaticResource SectionEditErrorLabel}" Content="{Binding Path=ErrorContent}"/>
                </DataTemplate>
            </ContentPresenter.ContentTemplate>
        </ContentPresenter>
    </StackPanel>
</ControlTemplate>

and I link to it like this:

    <TextBox Grid.Row="3" Grid.Column="1" MaxLength="200" Template="{StaticResource FormTextBox}"
             Text="{Binding Email, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />

Unfortunately, it doesn't perform any validation so the binding must be broken somehow...

Please advise...

Thanks.


Solution

  • You won't need the Grid.Row and Grid.Column bindings in the Template StackPanel since the StackPanel won't be the direct child of a Grid anyway,

    TemplateBinding is always a OneWay binding so the Text property for the Templated TextBox will never get updated. Change it to a regular Binding with RelativeSource and TwoWay

    Change ElementName=validableText to RelativeSource={RelativeSource TemplatedParent} in the bindings for ContentPresenter since we want to perform the validation check on the Templated TextBox and not the TextBox inside the Template.

    <ControlTemplate x:Key="FormTextBox" TargetType="{x:Type TextBox}">
        <StackPanel>
            <TextBox x:Name="validableText"
                     MaxLength="{TemplateBinding MaxLength}"
                     Style="{StaticResource SectionEditPropertyTextBox}"
                     Text="{Binding RelativeSource={RelativeSource TemplatedParent},
                                    Path=Text,
                                    Mode=TwoWay,
                                    UpdateSourceTrigger=PropertyChanged}" />
            <ContentPresenter Visibility="{Binding RelativeSource={RelativeSource TemplatedParent},
                                                   Path=(Validation.HasError),
                                                   Converter={StaticResource BooleanToVisibilityConverter}
                                                   ConverterParameter=True}"
                                Content="{Binding RelativeSource={RelativeSource TemplatedParent},
                                                  Path=(Validation.Errors).CurrentItem}"
                                HorizontalAlignment="Left">
                <ContentPresenter.ContentTemplate>
                    <DataTemplate>
                        <Label Style="{StaticResource SectionEditErrorLabel}" Content="{Binding Path=ErrorContent}"/>
                    </DataTemplate>
                </ContentPresenter.ContentTemplate>
            </ContentPresenter>
        </StackPanel>
    </ControlTemplate>
    

    On a side note, another alternative that you have here is to create a UserControl with the original piece of Xaml that you had. You could introduce the Dependency Properties needed for your scenario (Text etc.). It would only require small changes.