Validation.ErrorTemplate size - wpf

I've got the following control template which I use as a Validation.ErrorTemplate for TextBoxes:-
<ControlTemplate x:Key="ControlValidationErrorTemplate">
<DockPanel LastChildFill="True">
<Border Background="Red"
DockPanel.Dock="right"
Padding="2,0,2,0"
ToolTip="{Binding ElementName=valAdorner, Path=AdornedElement.(Validation.Errors), Converter={x:Static val:ValidationErrorsConverter.Instance}}">
<TextBlock Text="!"
VerticalAlignment="center"
HorizontalAlignment="center"
FontWeight="Bold"
Foreground="white" />
</Border>
<AdornedElementPlaceholder x:Name="valAdorner"
VerticalAlignment="Center">
<Border BorderBrush="red"
BorderThickness="1" />
</AdornedElementPlaceholder>
</DockPanel>
</ControlTemplate>
When a TextBox contains invalid content, the above template applies a red border and adds a red box containing an exclamation mark immediately to the right of the TB.
The problem is, the exclamation mark overlaps anything immediately to the right of the TB, rather than the layout changing to accomomodate the exclamation mark. I have a similar problem in DataGrids - the exclamation mark overlaps the right-hand edge of the containing cell, rather than the column width increasing to accommodate it.
Using Snoop, it appears that the template is being displayed in an "adorner layer" which I assume is a separate visual tree? This would explain why the window's layout isn't recalculated to take into account the exclamation mark. Can anyone suggest a way to achieve what I want?

As I suspected, it's due to the error template being rendered on the adorner layer, so it doesn't affect the layout of the window. See: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/9de3c9e5-5759-4f88-9184-175d3eaabdad/
I'm now using this control template instead:-
<ControlTemplate x:Key="ControlValidationErrorTemplate">
<Grid>
<Polygon Points="9,9 9,0 0,0"
Stroke="Red"
StrokeThickness="1"
Fill="Red"
HorizontalAlignment="Right"
VerticalAlignment="Top"
ToolTip="{Binding ElementName=valAdorner, Path=AdornedElement.(Validation.Errors), Converter={x:Static val:ValidationErrorsConverter.Instance}}" />
<AdornedElementPlaceholder x:Name="valAdorner"
VerticalAlignment="Center">
<Border BorderBrush="red"
BorderThickness="1" />
</AdornedElementPlaceholder>
</Grid>
</ControlTemplate>
This draws a red border around the control, with a small red triangle overlapping the top-right corner of the control - hovering over this displays a tooltip containing the error message.

Related

Horizontal dashed line stretched to container width

I have a layout contained within a ScrollViewer in which I need to draw a horizontal dashed line that stretches to the full width of the container. The closest I've managed is the following
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel>
<Button Width="400" Height="50" VerticalAlignment="Top" Margin="10" />
<Line HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Stroke="Black"
X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
StrokeDashArray="2 2" StrokeThickness="1" />
</StackPanel>
</ScrollViewer>
This nearly works, however once the container (in my case a window) has been enlarged, the line doesn't shrink back down to the appropriate size when the container is sized back down. The below is the screenshot of the same window after I have horizontally sized the window up and down.
Note that the fact that the line is dashed is important as it means that solutions that involve stretching the line don't work (the dashes appear stretched).
I know that this is because of the X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}" binding (by design the line is always the widest thing in the scrollable region, so when I size the window down the scrollable region the line defines the width of the scrollable region), however I can't think of a solution.
How can I fix this problem?
Screenshot of why using ViewportWidth doesn't work
I realised that what I needed was for the Line to ask for zero space during the measure step of layout, but then use up all the available space during the arrange step. I happened to stumble across the question Make WPF/SL grid ignore a child element when determining size which introduced the approach of using a custom decorator which included this logic.
public class NoSizeDecorator : Decorator
{
protected override Size MeasureOverride(Size constraint) {
// Ask for no space
Child.Measure(new Size(0,0));
return new Size(0, 0);
}
}
(I was hoping that some existing layout control incorporated this logic to avoid having to write my own layout logic, however the logic here is so simple that I'm not really that fussed). The modified XAML then becomes
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel>
<Button Width="400" Height="50" VerticalAlignment="Top" Margin="10" />
<local:NoSizeDecorator Height="1">
<Line Stroke="Black" HorizontalAlignment="Stretch"
X2="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
StrokeDashArray="2 2" StrokeThickness="1" />
</local:NoSizeDecorator>
</StackPanel>
</ScrollViewer>
This works perfectly
You may put a very long Line in a left-aligned Canvas with zero Width and ClipToBounds set to false.
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<StackPanel>
<Button Width="400" Height="50" VerticalAlignment="Top" Margin="10" />
<Canvas HorizontalAlignment="Left" Width="0" ClipToBounds="False">
<Line Stroke="Black" StrokeDashArray="2 2" X2="10000"/>
</Canvas>
</StackPanel>
</ScrollViewer>

WPF IDataErrorInfo issues

I've used WPF and IDataErrorInfo in the past apps to display errors to the user via a controltemplate by putting an image in the adorner and adding a tooltip to the image like this;
<Style x:Key="textStyle" TargetType="TextBox">
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<Border BorderBrush="Orange"
BorderThickness="2"
CornerRadius="4"
SnapsToDevicePixels="True">
<Border.Effect>
<DropShadowEffect BlurRadius="10"
ShadowDepth="0"
Color="Orange" />
</Border.Effect>
<DockPanel>
<Image Width="16"
Height="16"
Margin="-20,0,0,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
RenderOptions.BitmapScalingMode="HighQuality"
Source="{StaticResource imgError}"
ToolTip="{Binding ElementName=adornedElement,
Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"
ToolTipService.ShowDuration="30000" />
<AdornedElementPlaceholder Name="adornedElement" />
</DockPanel>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
With the appropriate implementation of IDataErrorInfo in the ViewModel and setting Textbox in the view accordingly the image and tooltip are shown;
<TextBox Name="txt"
Grid.Column="0"
Height="40"
Background="Aqua"
Style="{StaticResource textStyle}"
Text="{Binding Path=Text,
UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" />
<TextBlock Grid.Column="1"
Height="40"
Background="AliceBlue"
Text="{Binding ElementName=txt,
Path=(Validation.Errors).CurrentItem.ErrorContent}" />
The above code displays correctly in my previous apps and shows the error in the image tooltip as confirmed by the Textblock.
However, in my current app which is built using Prism I can't get the Image to display. The TextBlock updates correctly and I can set the error to the TextBox tooltip via a style trigger without any issue. The problem is I can't seem to get the image (or anything else) to display in the Adorner. The Image is not shown and border is not changed.
The difference between previous apps and this is that the view is in a Region in a ContentControl and I've used dependency injection to inject the viewmodel into the view constructor and set the DataContext.
I can't figure out why this doesn't work when it did previously. I think I may need to include an AdornerDecorator somewhere but I'm perplexed as to where having tried it in a few places without success. Any ideas how I can ensure the Adorner is shown?
Used an AdornerDecorator to wrap the element containing the texbox and all works fine.

How do I put a border around an image in WPF?

I have a StackPanel containing five images and I want to put a black border around each image.
The XAML I have at the moment is:
<Image Name="imgPic1"
Width="100"
Height="75"
Stretch="Fill"
VerticalAlignment="Top" />
I thought I would be just able to put a one-unit margin or padding on the image and set a background color to 000000 but Padding and Background are both invalid for images.
What is an easy way to do this in XAML? Do I really have to put each image inside another control to get a border around it or is there some other trickery I can use?
Simply wrap the Image in a Border control
<Border BorderThickness="1">
<Image Name="imgPic1"
Width="100"
Height="75"
Stretch="Fill"
VerticalAlignment="Top" />
</Border>
You could also provide a style you apply to images that does this if you don't want to do it around every image
Final solution from answer and comments added by Pax:
<Border BorderThickness="1"
BorderBrush="#FF000000"
VerticalAlignment="Top">
<Image Name="imgPic1"
Width="100"
Height="75"
Stretch="Fill"
VerticalAlignment="Top"/>
</Border>
The accepted answer will not work because of the problem described here
https://wpf.2000things.com/2011/04/17/279-adding-a-border-around-an-image-control/
I solved it this way.
<Viewbox>
<Border BorderThickness="3" BorderBrush="Red">
<Image Stretch="None" ></Image>
</Border>
</Viewbox>
I just stumbled upon this post and the other answer did not work right. Maybe because I now use framework 4 and this post is old?
In any case - if someone will see this by chance in the future - here is my answer:
<Border Name="brdSiteLogo"
BorderThickness="2"
BorderBrush="#FF000000"
VerticalAlignment="Top"
HorizontalAlignment="Left"
Margin="12,112,0,0"
Height="128"
Width="128">
<Image Name="imgSiteLogo"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Stretch="Fill"/>
</Border>
The border thickness and brush are important (if you wont choose a color - you will not see the border!!!)
Also, the border should be aligned on your window. The image is "inside" the border, so you can use margins or just stretch it like I did.

WPF Groupbox header position alignment

Is it possible to set the position alignment for a GroupBox Header in WPF? The default is to place in the top left corner of the GroupBox outline but I would like it to be centered at the top. I know that you can set the properties of the text using:
<GroupBox Grid.Row="1" HorizontalAlignment="Center">
<GroupBox.Header>
<TextBlock Text="Cash Match" Foreground="Black" FontWeight="Bold"/>
</GroupBox.Header>
</GroupBox>
But I'm looking to set the position of it with respect to the GroupBox outline.
It's simple! Just edit Template of GroupBox:
In Blend, do the following :
Right click GroupBox > Edit Template > Edit a Copy > OK
Search for following section:
<Border x:Name="Header" Padding="3,1,3,0"
Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">
...
</Border>
Change Grid.Column to 2
Also set HorizontalAlignment="Right"
You have just aligned the header to right!!! But bot the white gap behind it. For that,
Now search for following section :
<Border BorderBrush="White" BorderThickness="{TemplateBinding BorderThickness}" CornerRadius="4" Grid.ColumnSpan="4" Grid.Row="1" Grid.RowSpan="3">
<Border.OpacityMask>
...
</Border.OpacityMask>
...
</Border>
Add RenderTransformOrigin="0.5,0.5" to the border
Just above , add following code (this will shift the "white gap" behind header to right:
<Border.RenderTransform>
<ScaleTransform ScaleX="-1"/>
</Border.RenderTransform>
You are done! You just got a GroupBox with right aligned header!!!
Please tell me if this is what you required.
Changing the group-box-header alinment will result in non-OS-conform controls.
Therefore, I think you won't be able to change this using the default styles. A custom template will solve your problem.

Use UIElement as Clip in WPF

Please pardon my ignorance- I'm very new to WPF.
I am looking to implement a minor, visual effect in my application that gives the look of "inner" rounded corners. The window in question has a dark border that encapsulates several UIElements, one of which is a StatusBar, located at the bottom of the window. This StatusBar has a dark background that matches the window's border. Above the StatusBar is a content view, which is currently a Grid- its background is semi-transparent (I think that this is something of a constraint- you can see through the content view to the desktop below). I would like for the content view (represented by the transparent, inner area in the figure below) to have the look of rounded corners, though I expect to have to sort of create the illusion myself.
(Can't post the image because I'm a lurker and not a poster- please find the drawing here)
My first approach was to add a Rectangle (filled with the same, dark color as the border) immediately above the StatusBar and to assign a Border with rounded corners to its OpacityMask (similar to the solution proposed by Chris Cavanagh**). Sadly, the effect that is produced is the exact opposite of that which I am trying to achieve.
I understand that the Clip property can be of use in this sort of situation, but it seems to me that using any sort of Geometry will prove to be inadequate as it won't be dynamically sized to the region in which it resides.
EDIT: Including my XAML:
<Grid Background="{StaticResource ClientBg}" Tag="{Binding OverlayVisible}" Style="{StaticResource mainGridStyle}">
<DockPanel LastChildFill="True">
<!-- Translates to a StackPanel with a Menu and a Button -->
<local:FileMenuView DockPanel.Dock="Top" />
<!-- Translates to a StatusBar -->
<local:StatusView DockPanel.Dock="Bottom" />
<!-- Translates to a Grid -->
<local:ContentView />
</DockPanel>
</Grid>
Any pointers are more than welcome- I'm ready to provide more indepth detail if necessary.
** http://www.dotnetkicks.com/wpf/WPF_easy_rounded_corners_for_anything
EDIT: Now I got what you mean. In fact you can use Path + OpacityMask approach. You have to draw "inverted" path, to use it as opacity mask. But I have simpler and faster solution for you :). Use Border + CornerRadius, and fill the gaps with solid paths. Just try the following code in Kaxaml and let me know if this is what you were looking for:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="240"
Height="320"
AllowsTransparency="True"
Background="Transparent"
WindowStyle="None">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="24"/>
<RowDefinition Height="*"/>
<RowDefinition Height="24"/>
</Grid.RowDefinitions>
<Border Background="Black"/>
<Border Grid.Row="1" BorderBrush="Black" BorderThickness="5">
<Grid>
<Border Background="White" CornerRadius="0, 0, 5, 5" Opacity="0.7"/>
<Path
Width="15"
Height="15"
HorizontalAlignment="Left"
VerticalAlignment="Bottom"
Data="M10,10 L5,10 L5,5 C4.999,8.343 6.656,10 10,10 z"
Fill="Black"
Stretch="Fill"/>
<Path
Width="15"
Height="15"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Data="M10,10 L5,10 L5,5 C4.999,8.343 6.656,10 10,10 z"
Fill="Black"
Stretch="Fill">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="-1"/>
<TranslateTransform X="15"/>
</TransformGroup>
</Path.RenderTransform>
</Path>
</Grid>
</Border>
<Border Grid.Row="2" Background="Black"/>
</Grid>
</Window>
PS: You can simplify this solution by avoiding render transforms, but you got the idea.

Resources