Showing posts with label XAML. Show all posts
Showing posts with label XAML. Show all posts

Saturday, January 9, 2016

The differences between TextBlock and Label in WPF

You can use two elements to show text in GUI, so-called "Label". These are
  • Label: <Label>Text</Label>
  • TextBlock: <TextBlock>Text</TextBlock>
Both produce nearly the same result. But there are differences. TextBlock inherits from FrameworkElement whereas Label inherits from ContentControl --> Control --> FrameworkElement.

This difference lead to all differences that these elements have.

One consequence can directly shown if we rewrite the code from above.
  • Label: <Label Content="Text" />
  • TextBlock: <TextBlock Text="Text" />
TextBlock has no Content, just Text. So TextBlock is restricted to strings, while Label can also have other types as content like images.
<Label>
    <Image Source="text.png" />
</Label>
One other frequently mentioned consequence from the different roots of Label and TextBlock is that Label supports Access Keys (Mnemonics), while TextBlock don't.
<Label Content="_Text"
       Target="{Binding ElementName=InputField}" />
<TextBox x:Name="InputField" />

Furthermore a Label is grayed out, if its IsEnabled property is false, whereas TextBlock isn't. You can also use the Template property for a custom control template or the ContentTemplate property to apply a DataTemplate to its content.

Summarized you can say the difference is that with a Label you can do more than with a TextBlock. You can do whatever a ContentControl can do, while a TextBlock can only do what a FrameworkElement can do. But the huge advantage of the TextBlock is that it is much more lightweight.

So use TextBlock, whenever you just need text. If text is insufficient, use Label.


Thursday, December 17, 2015

Setting the Background of a WPF TextBox depending on Validation in a Template/Style using TemplateBinding 2


I showed how to change the Background of a TextBox, if some own ValidationRule failed in the post Setting the Background of a WPF TextBox depending on Validation in a Template/Style using TemplateBinding. But the solution has a minor issue.

If the VisualState is ReadOnly and the Trigger Validation.HasError is called the Background changes as expected. But if the Trigger is not any longer true, the Background will not set back to the ReadOnly style, again.

To fix this issue the VisualState of ReadOnly is removed.

                                <VisualState x:Name="ReadOnly" />

And a Trigger for the IsReadOnly property is defined. This Trigger must be placed above the Validation.HasError Trigger. Otherwise the TextBox would have always the ReadOnly color and never the HasError color.

        <Style.Triggers>
            <Trigger Property="IsReadOnly"
                     Value="True">
                <Setter Property="Background"
                        Value="LightBlue" />
            </Trigger>
            <Trigger Property="Validation.HasError"
                     Value="True">
                <Setter Property="ToolTip"
                        Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                                        Path=(Validation.Errors).CurrentItem.ErrorContent}" />
                <Setter Property="Background"
                        Value="Orange" />
                <Setter Property="BorderThickness"
                        Value="2" />
                <Setter Property="BorderBrush"
                        Value="Red" />
            </Trigger>
        </Style.Triggers>
 

Saturday, July 25, 2015

Minimum column width in WPF ListView/GridView

It seems that it is not possible to set the minimum width of a column within XAML. But it can be forced withn code-behind. Laurent Bugnion has found the solution (http://geekswithblogs.net/lbugnion/archive/2008/05/06/wpf-listviewgridview-minimum-and-maximum-width-for-a-column.aspx).

The ListView is defined in the XAML part.
 
            <ListView x:Name="MyListView">
                <!-- ... -->
            <ListView.ContextMenu>

In the constructor of the code-behind part the event handler is added.
 
            ResultDataView.AddHandler(Thumb.DragDeltaEvent,
                new DragDeltaEventHandler(Thumb_DragDelta), true);

The event handler takes care that column width is not set under 20 (in this example).
 
        private void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Thumb senderAsThumb = e.OriginalSource as Thumb;
            GridViewColumnHeader header = senderAsThumb.TemplatedParent as
                                                            GridViewColumnHeader;
            if (header == null)
            {
                return;
            }
 
            if (header.Column.ActualWidth < 20)
            {
                header.Column.Width = 20;
            }
        }


Tuesday, June 23, 2015

Setting the Background of a WPF TextBox depending on Validation in a Template/Style using TemplateBinding

Recently I wanted to change the Background of a TextBox, if some own ValidationRule failed. But I couldn't get this to work. I tried several things and I gained more insights into styling and especially into styling wrong validated elements. And finally I get it to work.

If you are not familiar with WPF validation, here you can find excellent descriptions of how to use validation in WPF:

Now, I come to to initial situation.

Using ValidationRule in WPF

I had a ValidationRule, something like that.
namespace ErrorValidation
{
    public class RangeValidationRule : ValidationRule
    {
        public double Min { getset; }
        public double Max { getset; }
 
        public override ValidationResult Validate(object value,
            CultureInfo cultureInfo)
        {
            double enteredValue;
            if (double.TryParse(value.ToString(), out enteredValue))
            {
                if ((enteredValue < Min) || (enteredValue > Max))
                {
                    return new ValidationResult(false,
                      string.Format("Entered value must be in the range [{0};{1}].",
                      Min, Max));
                }
            }
            else
            {
                return new ValidationResult(false,
                    string.Format("Value '{0}' is not of type double.", value));
            }
 
            return new ValidationResult(truenull);
        }
    }
}
To get access to the ValidationRule the XML namespace is defined in the XAML.
xmlns:validation="clr-namespace:ErrorValidation"
I get access to the ValidationRule in the Window/UserControl Resources.
<validation:RangeValidationRule x:Key="RangeValidationRule" />
Now I can use the ValidationRule to validate user input.
        <TextBox Margin="12,0,12,12">
            <TextBox.Text>
                <Binding Path="InputField1"
                         UpdateSourceTrigger="PropertyChanged">
                    <Binding.ValidationRules>
                        <validation:RangeValidationRule Min="5"
                                                        Max="10" />
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>
        </TextBox>
The default style is used to mark user input that violates the ValidationRule.

Validation styles

So far, so good. But now I wanted to extend my existing TextBox style. If the validation failed, I wanted to change the Background of the TextBox. This was one of my not working tries.
        <Style x:Key="MyWrongTextBoxStyle"
               TargetType="{x:Type TextBox}">
            <Setter Property="Margin"
                    Value="4" />
            <Setter Property="Foreground"
                    Value="DarkBlue" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TextBoxBase}">
                        <Border Name="Border"
                                CornerRadius="2"
                                BorderThickness="1"
                                Background="White"
                                BorderBrush="LightBlue">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal" />
                                    <VisualState x:Name="Disabled">
                                        <Storyboard>
                                            <ColorAnimationUsingKeyFrames
                                                Storyboard.TargetName="Border"
                                                Storyboard.TargetProperty=
                                                "(Panel.Background).
                                                (SolidColorBrush.Color)">
                                                <EasingColorKeyFrame
                                                    KeyTime="0"
                                                    Value="LightGray" />
                                            </ColorAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="ReadOnly">
                                        <Storyboard>
                                            <ColorAnimationUsingKeyFrames
                                                Storyboard.TargetName="Border"
                                                Storyboard.TargetProperty=
                                                "(Panel.Background).
                                                (SolidColorBrush.Color)">
                                                <EasingColorKeyFrame
                                                    KeyTime="0"
                                                    Value="LightBlue" />
                                            </ColorAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="MouseOver" />
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <ScrollViewer Margin="0"
                                          x:Name="PART_ContentHost" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <StackPanel>
                            <DockPanel>
                                <AdornedElementPlaceholder x:Name="Placeholder" />
                                <TextBlock Foreground="Red"
                                           FontSize="18"
                                           Text="!" />
                            </DockPanel>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Foreground="Red"
                                           Text="The value '" />
                                <TextBlock Foreground="Red"
                                           Text="{Binding ElementName=Placeholder,
                                                      Path=AdornedElement.Text}" />
                                <TextBlock Foreground="Red"
                                           Text="' causes an error (see tooltip)" />
                            </StackPanel>
                        </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).CurrentItem.ErrorContent}" />
                    <Setter Property="Background"
                            Value="Orange" />
                    <Setter Property="BorderThickness"
                            Value="2" />
                    <Setter Property="BorderBrush"
                            Value="Red" />
                </Trigger>
            </Style.Triggers>
        </Style>

I tried several combinations with Validation.ErrorTemplate and Validation.HasError. But when should I use Validation.ErrorTemplate and when Validation.HasError? I have not thought much about it, until now. But the answer is not very surprising.

Validation.ErrorTemplate is used to add elements that decorate an existing element. So I cannot use it to change the Background of an existing element. But it can be used to add an image, an exclamation mark, TextBlock or something else next to the evaluated element.


Validation.HasError can be used to add a ToolTip or to change properties of an existing element.
So I should use it to change the color of the Background. But why is it not working?

The problem is how the Template style is defined. If I remove the Setter of the Template property in the Style, it is working. So I have to change the Template.

TemplateBinding

To allow changing a property of a defined Template, TemplateBinding is used. TemplateBinding is a markup extension. The properties that are set by TemplateBinding in the Template Setter can be referrenced outside the Setter of the Template. The default property values of the properties that are set by TemplateBinding can be set by other Setters. It is also possible to set the values in XAML parts that are using the Style. And now it is also possible to set the values by a Trigger, like the Background as I wanted. This is the working code that changes the Background of a TextBox in case of error.
        <Style x:Key="MyTextBoxStyle"
               TargetType="{x:Type TextBox}">
            <Setter Property="Margin"
                    Value="4" />
            <Setter Property="Foreground"
                    Value="DarkBlue" />
            <Setter Property="Background"
                    Value="White" />
            <Setter Property="BorderBrush"
                    Value="LightBlue" />
            <Setter Property="BorderThickness"
                    Value="1" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TextBoxBase}">
                        <Border Name="Border"
                                CornerRadius="2"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}">
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal" />
                                    <VisualState x:Name="Disabled">
                                        <Storyboard>
                                            <ColorAnimationUsingKeyFrames
                                                Storyboard.TargetName="Border"
                                                Storyboard.TargetProperty=
                                                "(Panel.Background).
                                                (SolidColorBrush.Color)">
                                                <EasingColorKeyFrame
                                                    KeyTime="0"
                                                    Value="LightGray" />
                                            </ColorAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="ReadOnly">
                                        <Storyboard>
                                            <ColorAnimationUsingKeyFrames
                                                Storyboard.TargetName="Border"
                                                Storyboard.TargetProperty=
                                                "(Panel.Background).
                                                (SolidColorBrush.Color)">
                                                <EasingColorKeyFrame
                                                    KeyTime="0"
                                                    Value="LightBlue" />
                                            </ColorAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="MouseOver" />
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <ScrollViewer Margin="0"
                                          x:Name="PART_ContentHost" />
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="Validation.ErrorTemplate">
                <Setter.Value>
                    <ControlTemplate>
                        <StackPanel>
                            <DockPanel>
                                <AdornedElementPlaceholder x:Name="Placeholder" />
                                <TextBlock Foreground="Red"
                                           FontSize="18"
                                           Text="!" />
                            </DockPanel>
                            <StackPanel Orientation="Horizontal">
                                <TextBlock Foreground="Red"
                                           Text="The value '" />
                                <TextBlock Foreground="Red"
                                           Text="{Binding ElementName=Placeholder,
                                                      Path=AdornedElement.Text}" />
                                <TextBlock Foreground="Red"
                                           Text="' causes an error (see tooltip)" />
                            </StackPanel>
                        </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).CurrentItem.ErrorContent}" />
                    <Setter Property="Background"
                            Value="Orange" />
                    <Setter Property="BorderThickness"
                            Value="2" />
                    <Setter Property="BorderBrush"
                            Value="Red" />
                </Trigger>
            </Style.Triggers>
        </Style>
And this is the result.