WPF Validation ErrorTemplate for Custom TextBox - wpf

Branching off of this question -
When attaching a validation error template to my custom textbox like this -
<local:CustomTextBox CustomText="{Binding ViewModelProperty}" Validation.ErrorTemplate="{StaticResource errorTemplate}"/>
<ControlTemplate x:Key="errorTemplate">
<DockPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder x:Name="controlWithError"/>
</Border>
<TextBlock Foreground="Red" FontSize="20" FontFamily="Segoe UI" Margin="3,0,0,0" MouseDown="Exclamation_MouseDown" Tag="{Binding AdornedElement.(Validation.Errors)[0].ErrorContent, ElementName=controlWithError}">!</TextBlock>
</DockPanel>
</ControlTemplate>
If there was a validation error in the ViewModelProperty, my application was throwing an exception -
Key cannot be null.
Parameter name: key
I'm not sure why this is happening. Is there something that needs to be done in order to assign a new error template to a custom control?
UPDATE:
I've figured out that the issue is with the Tag property in the error template. If I remove the Tag, it works just fine.
Thanks

Alright so the way I managed to fix the issue was by removing the AdornedElement keyword and changing the error template as follows:
<local:CustomTextBox CustomText="{Binding ViewModelProperty}">
<Validation.ErrorTemplate>
<ControlTemplate>
<DockPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder x:Name="controlWithError"/>
</Border>
<TextBlock Foreground="Red" FontSize="20" FontFamily="Segoe UI" Margin="3,0,0,0" MouseDown="Exclamation_MouseDown">!</TextBlock>
</DockPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
<local:CustomTextBox.Style>
<Style TargetType="{x:Type local:CustomTextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Tag" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</local:CustomTextBox.Style>
</local:CustomTextBox>
What I don't understand is why it behaves differently when using the AdornedElement keyword but works fine when binding the Tag/Tooltip using the RelativeSource. While the problem is solved, I would welcome any ideas as to why this happened.
Thanks

Related

Strange black background around an WPF Image

In a MVVM WPF application I have a border around an image:
<Border x:Name="PhotoBorder"
Background="Transparent"
Margin="3 0 5 0"
Grid.Row="3" Grid.RowSpan="7"
Grid.Column="0"
Height="110" Width="110"
HorizontalAlignment="Center"
CornerRadius="5"
BorderThickness="1"
BorderBrush="LightSteelBlue">
<Image x:Name="Photo"
Margin="3"
Height="{Binding ElementName=PhotoBorder, Path=Height-3}"
Width="{Binding ElementName=PhotoBorder, Path=Width-3}">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="{Binding Path=PhotoImg, Converter={StaticResource ImageConverter}}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=PhotoImg}" Value="{x:Null}">
<Setter Property="Source" Value="/Resources/Photo.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Border>
The problem is that black background is shown around the photo as below:
It is only happening in case view model property "PhotoImg" is null in the DataTrigger when attaching directly image "/Resources/Photo.png" to the "Source" property.
.. and I do not know why. Any ideas? Something related to the photo?
The original photo is:
Note: I am using Visual Studio 2008 and .NET Framework 3.5

How to set DataContext of elements based on datacontext of ContentControl on same UserControl

Following is the section from My Shell:
<StackPanel x:Name="stack" Orientation="Horizontal" Height="25" HorizontalAlignment="Right" Margin="0,4,0,0" Grid.Row="0">
<Button DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type StackPanel}}, Path=DataContext}" Content="Back" prism:Click.Command="{Binding Path=GoBackCommand}"/>
<Button DataContext="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type StackPanel}}, Path=DataContext}" Content="Forward" prism:Click.Command="{Binding Path=GoForwardCommand}" Margin="10,0,0,0"/>
</StackPanel>
<ContentControl x:Name="ActionContent" prism:RegionManager.RegionName="{x:Static inf:RegionNames.WorkspaceRegion}" Grid.Row="1">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl" >
<Grid >
<Controls:RoundedBox/>
<ContentPresenter Margin="10,0,10,0" Content="{TemplateBinding Content}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="HasContent" Value="false">
<Setter Property="Visibility" Value="Collapsed" />
</Trigger>
<DataTrigger Binding="{Binding ElementName=stack}">
<Setter Property="DataContext" Value="{Binding RelativeSource={RelativeSource Self},
Path=Content.DataContext}" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</ContentControl.Template>
In ContentControl views are injecting from Modules (using ribbon tab). I want two button inside StackPanel may use ViewModels (DataContext) of injected views, for backward and Forward navigation.
Please Help, thanks!
I would start by having a good read at Prism Navigation Chapter,
and how to use the navigation journal.
Edit:
I don't think you can do what you want directly in XAML, but i can see 2 workarounds:
Define the commands as global CompositeCommand in the shell viewmodel, then have your views reister and unregister their own implementations as they are navigated.
Define a new Region in wich the views can inject their own navigation buttons.
Hope this can help you.

Setting background of content control

<UserControl .....>
<DataTemplate DataType="{x:Type vm:AViewModel}">
<vw:AView />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:BViewModel}">
<vw:BView />
</DataTemplate>
<ContentControl x:Name="chartScreen" Content="{Binding Screen}" Background="Yellow" />
</UserControl>
As you can see from above code, ContentControl is setting its content through binding to ViewModel's Screen property. Screen property will return an instance of AViewModel or BViewModel, depending on some condition. The problem is, when UserControl is loaded on screen, Screen property is null, so there is no content set yet. At this point, I would like to set some background for the ContentControl, but I cannot find a way how to do this? Background="Yellow" does nothing...
Any ideas how to set the background of ContentControl? This backgound should be applied always, even when content is displaying AView or Biew, or null.
Just wrap your ContentControl in a Border
<Border Background="Yellow">
<ContentControl x:Name="chartScreen"
Content="{Binding Screen}" />
</Border>
If all you have in your UserControl is your ContentControl, just set the Background on the UserControl itself. That would remove the extra Border as well.
try something like this :
<ContentControl x:Name="chartScreen" Content="{Binding Screen}" Background="Yellow">
<ContentControl.Triggers>
<Trigger Property="Content" Value="{x:Null}">
<Trigger.Value>
<Border Background="Yellow"/>
</Trigger.Value>
</Trigger>
</ContentControl.Triggers>
</ContentControl>
try something like this in WPF:
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Content}" Value="{x:Null}">
<Setter Property="Content">
<Setter.Value>
<Rectangle Width="100" Height="100" Fill="Blue" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

getting validation into a style

I use IDataErrorInfo on my viewmodels and I have a style (below) for a TextBox with an error template that works ok. I know that "ValidatesOnDataErrors=True" used like:
<TextBox Text="{Binding Path=LastName, ValidatesOnDataErrors=True}"
Style="{StaticResource TextBoxStyle}" />
will force WPF to use IDataErrorInfo but am wondering how to get that baked into my style.
Cheers,
Berryl
<Style x:Key="TextBoxStyle" TargetType="{x:Type TextBox}">
...
<!--
Error handling
-->
<Setter Property="Validation.ErrorTemplate">
<Setter.Value>
<ControlTemplate>
<DockPanel LastChildFill="True">
<TextBlock DockPanel.Dock="Right" Text=" *"
Foreground="Red"
FontWeight="Bold" FontSize="16"
ToolTip="{Binding ElementName=placeholder, Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"/>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder Name="placeholder"></AdornedElementPlaceholder>
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="LightYellow"/>
</Trigger>
</Style.Triggers>
</Style>
If I understand what you're asking, you want to be able to be able to use the ValidatesOnDataError=True in your style, that so you don't have to repeat it every time.
If that's the case you can't, because that is a property of the data binding and not the style; and you can't template the data binding.
I just wonder if you use a Label instead of a TextBox, then in the style of the Label you can probably do something like this,
<ControlTemplate TargetType="sdk:Label">
<TextBlock x:Name="textBlock" Text="{Binding Content, RelativeSource={RelativeSource TemplatedParent}, ValidatesOnDataErrors=True}">
You can't because that is part of the definition of the binding to your property, not how the error is visualized.

WPF dynamic layout: how to enforce square proportions (width equals height)?

I'm learning WPF and can't figure out how to enfore my buttons to take a square shape.
Here is my XAML Markup:
<Window x:Class="Example"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="368" Width="333">
<Window.Resources>
<Style x:Key="ToggleStyle" BasedOn="{StaticResource {x:Type ToggleButton}}"
TargetType="{x:Type RadioButton}">
</Style>
</Window.Resources>
<RadioButton Style="{StaticResource ToggleStyle}">
Very very long text
</RadioButton>
</Window>
Specifying explicit values for Width and Height attributes seems like a wrong idea - the button should calculate its dimensions based on its contents automagically, but keep its width and height equal. Is this possible?
Try this - it seems to work in Kaxaml:
<Button
MinWidth="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"
MinHeight="{Binding ActualWidth, RelativeSource={RelativeSource Self}}">
Some content
</Button>
(To test, I put a TextBox inside the button, because that's an easy way to change content size without re-parsing the Xaml.)
Edit: sorry, should probably have specified it as a style to match your example:
<Style TargetType="Button" x:Key="SquareButton">
<Setter Property="MinWidth" Value="{Binding ActualHeight, RelativeSource={RelativeSource Self}}" />
<Setter Property="MinHeight" Value="{Binding ActualWidth, RelativeSource={RelativeSource Self}}" />
</Style>
The currently accepted answer cannot grow AND shrink. I think I found a better approach, by defining a custom style with a rectangle (stretched uniformly).
<Style x:Key="SquareButton" TargetType="Button">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#3a3a3a" />
<Setter Property="BorderBrush" Value="#5a5a5a"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border HorizontalAlignment="Center">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Rectangle Stretch="Uniform"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="4"
Margin="0"
Fill="{TemplateBinding Background}"
Panel.ZIndex="1"
/>
<ContentPresenter Margin="10"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Panel.ZIndex="2" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="#4a4a4a"/>
</Trigger>
</Style.Triggers>
</Style>
Btw, you can also create a perfect round button this way. Just replace Rectangle with Ellipse.
I think you want to bind button's width to its height, like this:
<Button Name="myButton"
Width="{Binding ElementName=myButton, Path=Height}"
Height="100">
Button Text
</Button>
#Dan Pruzey's answer was good, but did not shrink the square if the window was downsized. What I found to work best was to set the height to auto and the width to that height:
<Button Height="Auto" Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"/>
This kept the button a square when sizing my window bigger and smaller. Hope this helps someone.
I found a simpler solution for the case where you want the button to expand and shrink, tipically when its text changes:
<RadioButton Style="{StaticResource ToggleStyle2}"
HorizontalAlignment="Center" VerticalAlignment="Center"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
Width="{Binding Path=ActualWidth, ElementName=txtblck_innerText }"
Height="{Binding Path=ActualWidth, ElementName=txtblck_innerText }">
<TextBlock x:Name="txtblck_innerText"
MinWidth="{Binding ActualHeight, RelativeSource={RelativeSource Self}}" >
Very very long text.........
</TextBlock>
</RadioButton>
This solution above applies when the size of the button is imposed by its content (comes from inside) as stated in the question.
However, in case you want a square button whose size is imposed by the place where it is going to be (comes from outside), for instance, the size of the grid cell that contains the button, a solution could be:
<RadioButton Style="{StaticResource ToggleStyle2}"
HorizontalAlignment="Center" VerticalAlignment="Center"
HorizontalContentAlignment="Center" VerticalContentAlignment="Center">
<Viewbox Stretch="Uniform">
<Border Width="20" Height="20">
<Viewbox Stretch="Uniform">
<TextBlock x:Name="txtblck_innerText">
Very very long text...........
</TextBlock>
</Viewbox>
</Border>
</Viewbox>
</RadioButton>

Resources