1

I have two textboxes with custom validation, one in the main window and one in a user control. The validation error gets displayed correctly in both cases. I am trying to enable the OK button only when both textboxes don't have any validation errors. The issue is with the XAML for MultiDataTrigger where I'm getting a binding failure: System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=TBUserControl'. BindingExpression:Path=(0); DataItem=null; target element is 'Button' (Name=''); target property is 'NoTarget' (type 'Object')

Here is the main window xaml:

<Window x:Class="minimalExample.MainWindow"
        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:minimalExample"
        xmlns:uc="clr-namespace:minimalExample.Views"
        mc:Ignorable="d"
        Title="MainWindow"
        Height="150"
        Width="300">
    <Window.Resources>
        <ResourceDictionary Source="Dictionary1.xaml" />
    </Window.Resources>
    <Grid>
        <StackPanel>
            <TextBox Name="TBMain"
                     Width="100"
                     Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
                     Text="{Binding MainTextBox.Text, UpdateSourceTrigger=PropertyChanged}" />
            <uc:MyUserControl Margin="10"/>
            <Button Content="OK"
                    Width="50"
                    Margin="20"
                    Style="{StaticResource CanOK}">

            </Button>
        </StackPanel>
    </Grid>
</Window>

This is the User Control XAML:

<UserControl x:Class="minimalExample.Views.MyUserControl"
             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:minimalExample.Views"
             mc:Ignorable="d"
             d:DesignHeight="50" d:DesignWidth="150">
    <UserControl.Resources>
        <ResourceDictionary Source="../Dictionary1.xaml" />
    </UserControl.Resources>
    <Grid >
        <StackPanel >
            <TextBox x:Name="TBUserControl"
                     Width="100"
                     Validation.ErrorTemplate="{StaticResource ValidationTemplate}"
                     Text="{Binding UserControlTextBox.Text, UpdateSourceTrigger=PropertyChanged}" />

        </StackPanel>            
    </Grid>
</UserControl>

Here is the resource dictionary:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

    <ControlTemplate x:Key="ValidationTemplate">
        <StackPanel Orientation="Vertical">
            <DockPanel>
                <Border BorderThickness="1"
                        BorderBrush="Red"
                        DockPanel.Dock="Left">
                    <AdornedElementPlaceholder Name="ErrorAdorner" />
                </Border>
                <TextBlock Text="" />
            </DockPanel>
            <TextBlock Foreground="Red"
                       Background="#f5f5f5"
                       FontWeight="bold"
                       FontFamily="Segoe UI"
                       FontSize="11"
                       Text="{Binding ElementName=ErrorAdorner, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}" />
        </StackPanel>
    </ControlTemplate>

    <Style x:Key="CanOK"
           TargetType="Button">
        <Setter Property="IsEnabled"
                Value="False" />
        <Setter Property="MinWidth"
                Value="100" />
        <Style.Triggers>
            <MultiDataTrigger>
                <MultiDataTrigger.Conditions>
                    <Condition Binding="{Binding Path=(Validation.HasError), ElementName=TBMain}"
                               Value="False" />
                    <Condition Binding="{Binding Path=(Validation.HasError), ElementName=TBUserControl}"
                               Value="False" />
                </MultiDataTrigger.Conditions>
                <Setter Property="IsEnabled"
                        Value="True" />
            </MultiDataTrigger>
        </Style.Triggers>
    </Style>

</ResourceDictionary>

MainWindowViewModel:

namespace minimalExample
{
    public partial class MainWindowViewModel: ObservableValidator
    {
        [ObservableProperty]
        public Param mainTextBox;

        [ObservableProperty]
        public Param userControlTextBox;

        public MainWindowViewModel() 
        {
            MainTextBox = new Param(10, -100, 100);
            UserControlTextBox = new Param(5, -50, 50);
        }
    }
}

Param class that contains the custom validator:

namespace minimalExample
{
    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel.DataAnnotations;
    using System.Diagnostics;
    using System.Globalization;
    using CommunityToolkit.Mvvm.ComponentModel;

    public partial class Param : ObservableValidator
    {
     <snip>

        private string text;
        [CustomValidation(typeof(Param), nameof(ValidateMe))]
        public string Text
        {
            get
            {
                text = Value.ToString();
                return text;
            }
            set => SetProperty(ref text, value, true);
        }


        public static ValidationResult ValidateMe(string val, ValidationContext context)
        {
            Param instance = (Param)context.ObjectInstance;

            if (instance != null)
            {
                string test = instance.Validate(val);

                if (test == string.Empty)
                {
                    return ValidationResult.Success!;
                }
                return new(test);
            }
            return ValidationResult.Success!;
        }
    }
}

I found the following post which is similar to my situation but there is no accepted answer and I don't fully understand what is suggested in the comments.

Here is a suggestion from another post I tried for the second MultiDataTrigger condition:

<Condition Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type uc:MyUserControl}}, Path=TBUserControl.(Validation.HasError)}"
                               Value="False" />

I can't figure out what I'm doing wrong after looking at so many similar use cases which mostly deal with Dependency properties, but that is not where my issue is, rather with the visibility of the (named) TextBox inside the User Control.

slampe
  • 11
  • 3

1 Answers1

0

I found a post that contained the answer for reaching the TextBox inside the UserControl: Wpf, How to find Element name from one user control in another

The fixes are to add a public property in the UserControl code behind:

public object InnerButton { get { return TBUserControl; } }

then, name the UserControl in the MainWindow xaml:

<uc:MyUserControl x:Name="MyUC" Margin="10" /> 

and finally the problematic MultiDataTrigger Condition needs to be changed to:

<Condition Binding="{Binding InnerButton.(Validation.HasError), ElementName=MyUC}" Value="False" />
slampe
  • 11
  • 3