-1

I am trying to make a nice error message for a text box. The text box:

            <DockPanel DockPanel.Dock="Top">
              <Label Content="Name: "/>
              <TextBox DockPanel.Dock="Top" Text="{Binding Name, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource textBoxInError}">
              </TextBox>
            </DockPanel>

I started with a tooltip. Error style for the textbox:

    <!-- Style for the error tooltip for fields -->
    <Style x:Key="textBoxInError" TargetType="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>
    </Style>

That is not sufficient, a better message is required. With this solution the error is not visible until you hover the field: enter image description here

So I found that I can create another node that will receive the error message from the text box:

                <DockPanel DockPanel.Dock="Top">
                  <Label Content="Name: "/>
                  <TextBox DockPanel.Dock="Top" Name="editNodeName" Text="{Binding Name, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" Style="{StaticResource textBoxInError}">
                  </TextBox>
                  <TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent, ElementName=editNodeName}" TextWrapping="Wrap"/>
                </DockPanel>

That looks like this - very ugly and it somehow compressed the text box:
enter image description here

Here's a mockup of what I'd like instead, this should not require mouse hover to be visible:

enter image description here

It doesn't have to be exactly like that, but it shouldn't compress the checkbox or otherwise intrude in the form and it should not be visible when there is no error.

Can I float an error message text next to the field? Maybe somehow bind the position to the field's position using binding?

Tomáš Zato
  • 50,171
  • 52
  • 268
  • 778
  • 1
    https://stackoverflow.com/q/5390895/1506454 – ASh Mar 02 '20 at 13:44
  • @ASh I don't want to show actual tooltip, but some element that I can style. Also I don't really understand what the accepted answer to that question is saying. – Tomáš Zato Mar 02 '20 at 13:47
  • 1
    WPF Tooltip is not a simple text, it is element whcih can be styled. – ASh Mar 02 '20 at 13:58
  • [Wpf ToolTip Style](https://stackoverflow.com/questions/20138528/wpf-tooltip-style) – Rekshino Mar 02 '20 at 14:01
  • It's still a tooltip that requires mouse hover. I want the message to appear right after a wrong value is detected. I clarified that in my question. – Tomáš Zato Mar 02 '20 at 14:02
  • Do use `PopUp` then. [How to open a WPF Popup when another control is clicked, using XAML markup only?](https://stackoverflow.com/questions/361209/how-to-open-a-wpf-popup-when-another-control-is-clicked-using-xaml-markup-only) – Rekshino Mar 02 '20 at 14:30
  • Do you really want it to be there forever and is it really going to be outside the window? An error template is already placed in an adorner so it will go on top of everything in the window. It won't', however, break out of a window. You could make a tooltip show automatically. A context menu is another alternative which'd disappear as soon as you click elsewhere. A popup would be my least favourite option. They go on top of everything and will be a nuisance if your user ever wants to do something else whilst something is invalid. – Andy Mar 02 '20 at 15:21

2 Answers2

0

You could define a Validation.ErrorTemplate that contains a Popup that stays open. Something like this:

<Style x:Key="textBoxInError" TargetType="TextBox">
    <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
            <ControlTemplate>
                <DockPanel>
                    <Popup IsOpen="True" StaysOpen="True" PlacementTarget="{Binding ElementName=border}" Placement="Right">
                        <Border Background="Yellow" BorderBrush="Black" BorderThickness="1">
                            <TextBlock Text="{Binding [0].ErrorContent}" />
                        </Border>
                    </Popup>
                    <Border x:Name="border" BorderBrush="Red" BorderThickness="1">
                        <AdornedElementPlaceholder />
                    </Border>
                </DockPanel>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
    <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>
</Style>

You may also want to look at this answer.

mm8
  • 163,881
  • 10
  • 57
  • 88
0

Alternatively to the answer by mm8, instead of a popup you can use as a ControlTemplate the following, as an inspiration:

<Style x:Key="textBoxInError" TargetType="TextBox">
    <Setter Property="Validation.ErrorTemplate">
        <Setter.Value>
    <ControlTemplate>
      <StackPanel Orientation="Vertical">
        <Border CornerRadius="10,10,10,10" Margin="20">
          <Border.Effect>
            <DropShadowEffect Color="#FF474747" />
          </Border.Effect>
          <Border.Background>
            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
              <GradientStop Color="#FFFF99" Offset="0" />
              <GradientStop Color="#FFFF99" Offset="1" />
            </LinearGradientBrush>
          </Border.Background>
          <Grid Name="grid1">
            <TextBlock Text="Your message here" Padding="10" HorizontalAlignment="Center" Name="lblWarningHeader" VerticalAlignment="Top" FontSize="16"/>
          </Grid>
        </Border>
      </StackPanel>
    </ControlTemplate>
</Setter.Value>
    </Setter>
    <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>
</Style>
tombobadil
  • 142
  • 9