17

I have a data object used to contain my UI data that supports INotifyPropertyChanged and IDataErrorInfo. Originally I had all of the UI controls displaying in one big WPF application and was happily seeing errors flagged via this custom style:

    <!-- Set error style for textboxes -->
    <Style x:Key="txtBoxErrStyle" TargetType="{x:Type TextBox}">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="ToolTip" 
                            Value="{Binding RelativeSource={x:Static RelativeSource.Self}, 
                            Path=(Validation.Errors)[0].ErrorContent}" />
            </Trigger>
        </Style.Triggers>

        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <DockPanel DockPanel.Dock="Right">
                        <AdornedElementPlaceholder />
                        <Image Source="Error.png"
                                   Height="16"
                                   Width="16"
                                   ToolTip="{Binding Path=AdornedElement.ToolTip, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Adorner}}}" />
                    </DockPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

I was reorganizing the program today and decided to distribute the various UI controls over several pages of a TabControl. The structure layout I am using for this is:

<tabcontrol>
    <tabitem>
        <AdornerDecorator>
           [.. various Stack Panels, Groups and UI controls moved from original layout ..]
        </AdornerDecorator>
    </tabItem>
    <tabitem>
        <AdornerDecorator>
           [.. various Stack Panels, Groups and UI controls moved from original layout ..]
        </AdornerDecorator>
    </tabItem>

    ...
 </tabcontrol>

(I am using the AdornerDecorator as I had experienced in a previous program the error style not being re-rendered when swapping tab pages. I can't remember where I saw this but it did help me out.)

Now when I start my program the error style correctly renders on the TabItem that is open when the program starts, but does not correctly render on the other (hidden) TabItems. When I select (and reveal) one of those TabItems the tool-tip of the error style is set, but the error icon image is not displayed.

I also tested removing the custom style and revert back to the default WPF error style for textboxes and I still get a similar behaviour, i.e. no red box around the control on the TabItems that are hidden when the program opens.

So it seems that I am totally missing something that is stopping the error styles from correctly rendering on other than the open tab Item. Any ideas?

Edit Sep 3 Changed description to support a better understanding of what I have seen

Talk about Déjà vu in 2014

It's November 2014 and today I had this stupid WPF problem with error templates not showing up on items presented in a tab controller. Something in the back of my mind suggests that I have seen this problem before. So I google, and the first thing that pops up is my own question from 2009!

This time I see the comment from dkl which was added after I solved things the last time around. So I tried it his way and used this solution (which worked well and I didn't need to sprinkle an Adorner control over my tab controls):

<Style x:Key="TextBoxErrorStyle" TargetType="TextBox">
    <Style.Triggers>
        <MultiTrigger>
            <MultiTrigger.Conditions>
                <Condition Property="Validation.HasError" Value="True" />
                <Condition Property="IsVisible" Value="True" />
            </MultiTrigger.Conditions>
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <DockPanel LastChildFill="True">
                            <TextBlock  DockPanel.Dock="Right" 
                                Foreground="Red"
                                FontSize="14pt" 
                                 Margin="-15,0,0,0" FontWeight="Bold">*
                            </TextBlock>
                            <Border BorderBrush="Red" BorderThickness="2">
                                <AdornedElementPlaceholder Name="controlWithError"/>
                            </Border>
                        </DockPanel>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="ToolTip" 
                    Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors).CurrentItem.ErrorContent}" />
        </MultiTrigger>
    </Style.Triggers>
</Style>
Peter M
  • 7,309
  • 3
  • 50
  • 91

3 Answers3

21

(I am using the AdornerDecorator as I had experienced in a previous program the error style not being re-rendered when swapping tab pages. I can't remember where I saw this but it did help me out)

Presumably this indeed important tip originates from Karl Shifflets blog, at least he's addressing the same topic: WPF Validation Errors Disappear Inside TabControl When Switching TabItems.

Given this your issue might just be related, i.e. the tip/code above ensures there is a dedicated AdornerLayer for every tab item now, as the adorner layer of the parent element is discarded when you switch tabs. This dedicated adorner layer appears to still require some special treatment though, see for example question WPF ErrorTemplate visible when not focused? which is basically dealing with your issue upside down. Consequently I'd suggest you combine and expand the outlined solution for the latter with your style and try the following (untested code as of now though):

<Style x:Key="ErrorTemplate" TargetType="{x:Type TextBox}">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="True">...</Trigger>
        <Trigger Property="IsVisible" Value="false">
            <Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
        </Trigger>
        <Trigger Property="IsVisible" Value="true">
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>...</Setter.Value>
            </Setter>
        </Trigger>
    </Style.Triggers>
</Style>

See my comment regarding your update of Radio Button Error Style too, which tries to similarly address your likely related question; have you actually tried my suggestion there?

See Adorners Overview for more details on the adorner architecture.

Community
  • 1
  • 1
Steffen Opel
  • 63,899
  • 11
  • 192
  • 211
  • Thanks again Steffen. I did see the comment you left on my other question but I think by the time I got to this issue I had forgotten about it. – Peter M Sep 07 '09 at 21:17
  • 1
    This worked for me to reenable validation after switching TabItems. The solution on Karl Shifflets blog didn't work, perhaps because I don't have a fixed number of TabItems but add TabItems dynamically to the TabControl. It looks like the trigger for IsVisible==true to set the ErrorTemplate again is necessary in this situation. Thanks for this hint! – Slauma Mar 29 '11 at 20:18
  • 3
    You can make it simpler with MultiTrigger: `` – dkl Apr 26 '11 at 12:34
  • @dkl 5 years later and I have the same problem. his time around I used your ideas. Thanks! – Peter M Nov 07 '14 at 14:56
  • 1
    Excellent solution. I like this much better than needing to define the AdornerDecorater in all of my tab items. – Michael Norgren Nov 26 '14 at 15:39
  • Occasionally, when coming back to the tab, I get the adorner displaying at the upper left of the screen instead of on the control. Any ideas? – NielW Aug 11 '21 at 17:14
5

Steffen Opel solved my problem with his link : WPF Validation Errors Disappear Inside TabControl When Switching TabItems.

<TabControl>

  <TabItem>
    <AdornerDecorator>
      <StackPanel>
        ...
      </StackPanel>
    </AdornerDecorator>
  </TabItem>

  <TabItem>
    <AdornerDecorator>
      <StackPanel>
        ...
      </StackPanel>
    </AdornerDecorator>
  </TabItem>

  <TabItem>
    <AdornerDecorator>
      <StackPanel>
        ...
      </StackPanel>
    </AdornerDecorator>
  </TabItem>

</TabControl>
Abyte0
  • 852
  • 8
  • 9
  • Since the solution is not too big, could you paste it in? You can use the "edit" link button below your answer to update it. Also that will prevent your answer to become useless if the link eventually breaks. – ForceMagic Dec 03 '13 at 22:58
  • This is just part of the solution. It is used to keep the validations intact when switching between the tabs. It won't work on the tabs which are originally not selected. It might work if the data context gets set on every ``Loaded`` event inside the tab items, an event which by the way will fire on every tab switching. – MoonStom Feb 26 '16 at 07:35
1

Just to add to the answer provided, the error template can be set once in a resource dictionary. Just the triggers have to be copied & pasted for all the default styles of all the relevant element types.

For example:

<ResourceDictionary ...>
    <!-- Add to the default style instead of replacing it -->
    <Style TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="Validation.HasError" Value="True" />
                    <Condition Property="IsVisible" Value="True" />
                </MultiTrigger.Conditions>
                <Setter Property="Validation.ErrorTemplate" Value="{DynamicResource ValidationErrorTemplate}"/>
            </MultiTrigger>
        </Style.Triggers>
    </Style>

    <Style TargetType="PasswordBox" BasedOn="{StaticResource {x:Type PasswordBox}}">
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="Validation.HasError" Value="True" />
                    <Condition Property="IsVisible" Value="True" />
                </MultiTrigger.Conditions>
                <Setter Property="Validation.ErrorTemplate" Value="{DynamicResource ValidationErrorTemplate}"/>
            </MultiTrigger>
        </Style.Triggers>
    </Style>
</ResourceDictionary>

Or, take it one step further and avoid the trigger repetition by combining the styles:

<ResourceDictionary ...>
    <Style x:Key="ErrorControlStyle" TargetType="Control">
        <Style.Triggers>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="Validation.HasError" Value="True" />
                    <Condition Property="IsVisible" Value="True" />
                </MultiTrigger.Conditions>
                <Setter Property="Validation.ErrorTemplate" Value="{DynamicResource ValidationErrorTemplate}"/>
            </MultiTrigger>
        </Style.Triggers>
    </Style>

    <Style TargetType="PasswordBox" BasedOn="{extensions:MultiStyle . ErrorControlStyle}"/>
    <Style TargetType="TextBox" BasedOn="{extensions:MultiStyle . ErrorControlStyle}"/>
</ResourceDictionary>

but I'd stay away from this approach as it will break the designer.

In the examples above I used the template named ValidationErrorTemplate from MahApps.Metro

enter image description here

Also, don't forget to also use the AdornerDecorator inside the TabItem fix, as described by @Abyte0, in order to maintain the validations when tab switching.

techvice
  • 1,315
  • 1
  • 12
  • 24
MoonStom
  • 2,847
  • 1
  • 26
  • 21