Chris
Chris

Reputation: 51

Control Template in a control affecting other controls in UI validation

I am quite proficient in WPF but new to the area of implementing UI validation where I need to ensure that certain values are ready for saving to a database. In my very large application I have a lot of different types of validation, which includes the simple single (TextBox requires a value or minimum characters), (Item must be selected), (At least one option must be chosen) and so on. I have implemented the validation using INotifyDataErrorInfo and this works really well, except for a few issues that I am going around in circles with and need some guidance. In fact, this is probably more of styling question. The following is just one of my issues but if I solve this then it may solve the others so I’ll stick to this for now: I have a set of radio button controls where one must be selected by the user but I do not want any to be selected by default, so are forced to make the choice. Until they choose one, a red border needs to be displayed around the radio buttons which are in a stack panel. Because this is something that I want to do in several places where I have a group of controls, I thought it would be good to create a Border control called ErrorBorderControl, that manages data errors using a property and then pop the items into this control. I create a DependecyProperty on this control called ValidationObject of type object, that just takes a property that can be tested to see if there is an error. This works perfectly and the red border is displayed when not selected and not when selected. Great so far. However, the ControlTemplate defined in the ErrorBorderControl bleeds to all other Border based controls in the UI, including the Border around the RadioButton controls. I work a lot with styles and understand scope but this is very odd. The below is what I have done, although very basic as a first attempt:

User Control:

<UserControl 
  x:Class="Itec.Common.Wpf.CustomControls.Controls.ErrorBorderControl"
  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"
  mc:Ignorable="d">

  <!--
    Provides a border around grouped controls that needs error feedback  
  -->

  <Border>

    <!-- Style -->
    <Border.Style>
      <Style
        TargetType="{x:Type Border}"
        x:Name="TheTemplate">

        <!-- Error template definition -->
        <Setter Property="Validation.ErrorTemplate">
          <Setter.Value>
            <ControlTemplate>

              <!-- Adorner for the error visual -->
              <AdornedElementPlaceholder>

                <!-- Simple border around the control -->
                <Border 
                  BorderBrush="#ec7063" 
                  BorderThickness="1"/>

              </AdornedElementPlaceholder>

            </ControlTemplate>
          </Setter.Value>
        </Setter>
      </Style>
    </Border.Style>
  </Border>
</UserControl>

Code Behind:

/// <summary>
  /// Interaction logic for ErrorBorderControl.xaml
  /// </summary>
  public partial class ErrorBorderControl : UserControl
  {
    #region Dependency Properties

    /// <summary>
    /// The validation object property
    /// </summary>
    public static readonly DependencyProperty ValidationObjectProperty =
        DependencyProperty.Register("ValidationObject", typeof(object), typeof(ErrorBorderControl),
          new FrameworkPropertyMetadata(OnValidationObjectChanged));

    #endregion Dependency Properties

    #region Ctors

    /// <summary>
    /// Initializes a new instance of the <see cref="ErrorBorderControl"/> class.
    /// </summary>
    public ErrorBorderControl()
    {
      InitializeComponent();
    }

    #endregion Ctors

    #region Public Properties

    /// <summary>
    /// Gets or sets the validation object.
    /// </summary>
    /// <value>The validation object.</value>
    public object ValidationObject
    {
      get { return (object)GetValue(ValidationObjectProperty); }
      set { SetCurrentValue(ValidationObjectProperty, value); }
    }

    #endregion Public Properties

    #region Private Methods

    /// <summary>
    /// Raises when the ValidationObject property value changes
    /// </summary>
    /// <param name="d">The d.</param>
    /// <param name="e">The <see cref="DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
    private static void OnValidationObjectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      ((ErrorBorderControl)d).ValidationObject = e.NewValue;
    }

    #endregion Private Methods
  }

Implementation:

<!-- Owner type -->
          <itc:ErrorBorderControl
            Grid.Row="1"
            ValidationObject="{Binding Path=RecordType, ValidatesOnNotifyDataErrors=True}">

            <StackPanel
              Orientation="Horizontal">

              <!-- Owner -->
              <itc:ItecRadioButton
                Content="{DynamicResource Language.SingleLine.Owner}"
                Margin="0,4,4,4"
                IsChecked="{Binding Path=RecordType, Converter={itc:EnumToBooleanConverter EnumValue={x:Static recordOwners:RecordOwnerRecordType.Owner}}}"/>

              <!-- FAO -->
              <itc:ItecRadioButton
                Content="{DynamicResource Language.SingleLine.FAO}"
                Margin="0,4,4,4"
                IsChecked="{Binding Path=RecordType, Converter={itc:EnumToBooleanConverter EnumValue={x:Static recordOwners:RecordOwnerRecordType.Fao}}}"/>

              <!-- Account Manager -->
              <itc:ItecRadioButton
                Content="{DynamicResource Language.SingleLine.Account_Manager}"
                Margin="0,4,4,4"
                IsChecked="{Binding Path=RecordType, Converter={itc:EnumToBooleanConverter EnumValue={x:Static recordOwners:RecordOwnerRecordType.AccountManager}}}"/>

              <!-- Watcher -->
              <itc:ItecRadioButton
                Content="{DynamicResource Language.SingleLine.Watcher}"
                Margin="0,4,4,4"
                IsChecked="{Binding Path=RecordType, Converter={itc:EnumToBooleanConverter EnumValue={x:Static recordOwners:RecordOwnerRecordType.Watcher}}}"/>

            </StackPanel>
          </itc:ErrorBorderControl>

Output:

Invalid Selection

Notice that it looks like the template, although defined inside the user control, is affecting other Border controls inside other control. Look at when I have made the selection:

Valid Selection

The controls left in red do not event take part in validation. How does a control template inside another control affect all Borders? I just don't get it. What I need to do is to define a template that I can apply to the control I want it to be applied to only, and to be able to re-use it.

Upvotes: 1

Views: 146

Answers (0)

Related Questions