How do you implement validation in custom controls? I am looking to replicate the standard validation logic you would see with a TextBox data-bound to a model or view-model that exposes IDataErrorInfo or INotifyDataErrorInfo.
To implement validation you should add the "ValidationStates" group to the VisualStateManager of the control.
I will illustrate the simple custom control TestControl with the TestProperty property.
Style in the Generic.xaml, depending on the state displays the blue text or the red border with the first error message:
<Style TargetType="local:TestControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:TestControl">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid" />
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InvalidBorder" Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="InvalidBorder" Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock Text="{TemplateBinding TestProperty}" Foreground="Blue" />
<Border x:Name="InvalidBorder" BorderBrush="Red" BorderThickness="2" Visibility="Collapsed">
<TextBlock Text="{Binding (Validation.Errors)[0].ErrorContent, RelativeSource={RelativeSource TemplatedParent}}" Foreground="Red" FontWeight="Bold" />
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There are 3 states:
Valid - No validation errors.
InvalidFocused - Applied when you set the focus to the control in the invalid state. Default controls display the red popup as well as the red border in this state, but in my particular example I don't display it for simplicity. Users can invoke this state by using the Tab keyboard button or by clicking a focusable inner control like TextBox.
InvalidUnfocused - Applied when the control in the invalid state but isn't focused.
Here is the code of the control, it contains only one property:
public class TestControl : Control
{
public TestControl()
{
this.DefaultStyleKey = typeof(TestControl);
}
public string TestProperty
{
get { return (string)GetValue(TestPropertyProperty); }
set { SetValue(TestPropertyProperty, value); }
}
public static readonly DependencyProperty TestPropertyProperty =
DependencyProperty.Register("TestProperty", typeof(string), typeof(TestControl), new PropertyMetadata(null));
}
After that if you use the IDataErrorInfo, the correct xaml is:
<local:TestControl TestProperty="{Binding SomeModelProperty, ValidatesOnDataErrors=True}" />
For the INotifyDataErrorInfo, the correct xaml is even simplier:
<local:TestControl TestProperty="{Binding SomeModelProperty}" />
Related
When using the VisualStateManager in WPF there can be a requirement to transition to a VisualState on control initialization. As far as I can tell there is no way to declare an initial state in Xaml, leaving you with the limited option of transitioning to the required state in your code behind after initialization.
Using code behind is not always desirable, and if you are using a Binding to control your VisualStates then not always possible.
So the question is: how do you set an initial VisualState in WPF without setting it in the code behind?
Too long for a comment
Binding "should" make no difference. If it works fine from code-behind it's bound to work from xaml unless there is something really weird in the Bindings.
All of blend's actions can be considered as a xaml helper tool. End result is you get some xaml that blend creates for you. If you do not want to use blend. Just add the xaml yourself in VS.
For this very thing the GoToStateAction can be coded such as
<Window ...
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...>
...
<Button x:Name="button"
Style="{DynamicResource ButtonStyle1}">
<i:Interaction.Triggers>
<i:EventTrigger>
<ei:GoToStateAction StateName="YourState"
TargetObject="{Binding ElementName=button}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
You'll need the corresponding references in your project as well.
On a side-note do try blend. It has it's advantages in specific places. You prolly would not replace typing xaml directly, but it serves as a good helper tool. Ignoring it completely unless forced to is pointless IMO.
You can directly bind any control with visual state at the time of initialisaton itself in xaml.
You need to create one dependency property to change the state.
hope below code can help you .
<Grid model:StateManager.VisualStateProperty="{Binding VisibilityState}" >
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="97" />
<RowDefinition Height="65" />
<RowDefinition Height="297" />
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisibleStateGroup">
<VisualState x:Name="VisibleState">
<Storyboard Duration="0:0:0">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="myGrid" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="CollapsedState">
<Storyboard Duration="0:0:0">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="myGrid" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="0:0:0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Name="myGrid" Grid.Row="0" Grid.ColumnSpan="2" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="383*" />
<ColumnDefinition Width="383*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0" Orientation="Horizontal" Margin="0,0,15,0" HorizontalAlignment="Right" VerticalAlignment="Center">
<Label Content="MyName"></Label>
</StackPanel>
</Grid>
Dependency property code for visual state change
public class StateManager : DependencyObject
{
public static string GetVisualStateProperty(DependencyObject obj)
{
return (string)obj.GetValue(VisualStatePropertyProperty);
}
public static void SetVisualStateProperty(DependencyObject obj, string value)
{
obj.SetValue(VisualStatePropertyProperty, value);
}
public static readonly DependencyProperty VisualStatePropertyProperty =
DependencyProperty.RegisterAttached(
"VisualStateProperty",
typeof(string),
typeof(StateManager),
new PropertyMetadata((s, e) =>
{
var propertyName = (string)e.NewValue;
var ctrl = s as Grid;
if (ctrl == null)
throw new InvalidOperationException("This attached property only supports types derived from FrameworkElement.");
var transitionWorked = System.Windows.VisualStateManager.GoToElementState(ctrl, (string)e.NewValue, true);
//MessageBox.Show(transitionWorked.ToString());
}));
}
I have some controls in a DataTemplate and I'd like to control it's pressed state behaviour. I did the following where I just put in VisualStateManager in the DataTemplate but it doesn't seem to work. I think it's possible to understand what I'm trying to do below. Is it possible to do it inline inside the DataTemplate tags?
<ItemsControl ItemsSource="{Binding Items}">
....
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid ...>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
...
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderThickness" Storyboard.TargetName="GridItemBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="3"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="Border" ...>
...
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
The short answer is that there is no "Pressed" visual state for the control type you're targeting -- so while you can reference any state in the Visual State Manager, it won't matter, because the control's code will never put it into that state.
You can see which visual states a control supports by looking at its definition (they're declared using the TemplateVisualState attribute), or by looking at this section on MSDN.
The way to go here might be to use the Button (or an override of [ButtonBase][2] that you write), since that has the "Pressed" visual state built in. You'd just have to write a Control Template for it that provides the layouts/styles that you're after.
Edit Here's an example:
Control template (resources section). This is a control template for the Button control, but it's not really a button. I'm just using it to take advantage of the "Pressed" visual state functionality.
<ControlTemplate x:Key="MyButtonTemplate" TargetType="Button">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Border.BorderThickness)" Storyboard.TargetName="GridItemBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="3"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="GridItemBorder" BorderBrush="Orange" BorderThickness="1" Background="White">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</Grid>
</ControlTemplate>
Items control
Define the item template as a "Button" which uses the above ControlTemplate.
<ItemsControl ItemsSource="{Binding SelectedItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Template="{StaticResource MyButtonTemplate}" Content="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
My problem is that I have a Button and I want to access the Storyboard, which is a part of the assigned style.
<Button x:Name="pictureFolderButton" Content="Pictures" Style="{StaticResource ImageTileButtonStyle}" Click="pictureFolderButton_Click" />
The style is very comprehensive so I'll post only a part of it:
<Style x:Key="ImageTileButtonStyle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Resources>
<Storyboard x:Key="OnLoaded1"/>
</ControlTemplate.Resources>
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
...
</VisualStateGroup>
<VisualStateGroup x:Name="AnimationStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:1">
<VisualTransition.GeneratedEasingFunction>
<CircleEase EasingMode="EaseOut"/>
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="ExpandedFull">
<Storyboard x:Name="expandStoryBoard" >
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="border1">
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="47"/>
<EasingDoubleKeyFrame KeyTime="0:0:8" Value="47"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<ContentPresenter RecognizesAccessKey="True" VerticalAlignment="Stretch" Margin="0,47,0,0" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I just want to get notified when the "ExpandedFull" animation has ended. Therefore, I thought I have to get the "expandStoryBoard" programmatically and add a Completed event handler.
The only thing I got working is to access the button's style at runtime:
Style style = pictureFolderButton.FindResource("ImageTileButtonStyle") as Style;
How do I have to proceed?
Thank you very much!
In theory you should be able to go down the visual and logical tree of your button to get to the storyboard, but that is rather tedious, if you name the Grid in the template "grid", something like the following might work:
Grid grid = pictureFolderButton.FindName("grid") as Grid;
IList groups = VisualStateManager.GetVisualStateGroups(grid);
VisualStateGroup targetGroup = null;
foreach (var group in groups)
{
if (group is VisualStateGroup && (group as VisualStateGroup).Name == "AnimationStates")
{
targetGroup = group as VisualStateGroup;
break;
}
}
if (targetGroup != null)
{
IList states = targetGroup.States;
VisualState targetState = null;
foreach (var state in states)
{
if (state is VisualState && (state as VisualState).Name == "ExpandedFull")
{
targetState = state as VisualState;
break;
}
}
if (targetState != null)
{
targetState.Storyboard.Completed += new EventHandler(Expansion_Completed);
}
else throw new Exception("VisualState not found.");
}
else throw new Exception("VisualStateGroup not found.");
Another way that comes to mind is extracting your storyboard to a resource, but i am not sure if this will have any side effects, i.e.:
<ControlTemplate.Resources>
...
<Storyboard x:Key="expandStoryBoard" x:Name="expandStoryBoard">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(FrameworkElement.Height)" Storyboard.TargetName="border1">
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:3" Value="130"/>
<EasingDoubleKeyFrame KeyTime="0:0:4" Value="47"/>
<EasingDoubleKeyFrame KeyTime="0:0:8" Value="47"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</ControlTemplate.Resources>
...
<VisualState x:Name="ExpandedFull" Storyboard="{StaticResource expandStoryBoard}"/>
Then you should be able to use FindResource on the button to get the storyboard.
Hopefully some of that works or at least helps a bit.
Just try with this with StoryBoard name "OnLoaded1":
<Button Height="75" Width="120" Style="{StaticResource ImageTileButtonStyle}" Click="Button_Click" >Hello</Button>
private void Button_Click(object sender, RoutedEventArgs e)
{
Button btn=(Button)sender;
Storyboard stb = btn.TryFindResource("OnLoaded1") as Storyboard;
}
If you add the Storyboard to the resources, you can set the event handler for the Timeline.Completed event in the XAML file and implement the handler in the corresponding class.
Define the Storyboard in the Resources section of your control like this:
<UserControl.Resources>
<Storyboard x:Key="expandStoryBoard" Completed="OnExpandCompleted"> ... </Storyboard>
...
</UserControl.Resources>
Reference the Storyboard as a static resource:
<VisualState x:Name="ExpandedFull" Storyboard="{StaticResource expandStoryBoard}" />
Implement the Completed event handler in the corresponding class:
void OnExpandCompleted(object sender, EventArgs e)
{
...
}
I'm trying to create a custom combobox that shows a loading animation when a bool property is set on it (e.g. IsLoading).
I've created a control template in Blend based on the combobox, and added a textblock inside the togglebutton template. In the code behind I'm calling VisualStateManager.GoToState but it always returns false. I was trying to move to a custom state initially, but I can't even move to states such as Disabled or MouseOver.
I have a usercontrol that just contains a combobox and the style is set to my combobox style containing the control template. I presume GoToState fails because the state is not on the control itself, but inside the combobox. How can I access this?
I'm at a loss as to how to debug this as there are no errors.
thanks
I had a similar problem! I had the Visual States defined within a grid in the ControlTemplate.
<Style x:Key="Image3TextRowButtonStyle" TargetType="Button">
<Setter Property="Foreground" Value="{StaticResource ForegroundBrush}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid x:Name="RootGrid" Background="{StaticResource BackgroundBrush}">
...
<VisualStateManager.VisualStateGroups>
...
<VisualStateGroup x:Name="ActiveStates">
<VisualState x:Name="Active">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Panel.Background)" Storyboard.TargetName="RootGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource BackgroundActiveBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="NotActive" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And the user control:
<UserControl x:Class="Griesser.Presentation.ContactCenterClient.Client.Control.Image3TextRowButtonUC"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
x:Name="Image3TextRowButton">
<Grid>
<Button x:Name="btn" Command="{Binding Command, ElementName=Image3TextRowButton}" Style="{StaticResource Image3TextRowButtonStyle}" />
</Grid>
In the code behind I am looking first for the RootGrid and then use GoToElementState:
private void ChangeVisualActiveState(bool useTransitions)
{
// Search RootGrid, because the Visual States are defined in the ControlTemplate!
FrameworkElement dt = btn.Template.FindName("RootGrid", btn) as FrameworkElement;
if (IsActive)
{
VisualStateManager.GoToElementState(dt, "Active", useTransitions);
}
else
{
VisualStateManager.GoToElementState(dt, "NotActive", useTransitions);
}
}
Is it possible to hide a column of a datagrid, without using codebehind?
E.g. by using the VisualStateManager?
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
x:Class="Buttons.MainPage"
Width="640" Height="480">
<StackPanel x:Name="LayoutRoot" Width="624" HorizontalAlignment="Right" Margin="0,0,8,0" >
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="EditStates">
<VisualState x:Name="ReadOnly" />
<VisualState x:Name="Edit">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ShownInEditMode" Storyboard.TargetProperty="(UIElement.Visibility)" BeginTime="00:00:00" Duration="00:00:00.0010000">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<data:DataGrid AutoGenerateColumns="False" ItemsSource="{Binding BBRNumbers}">
<data:DataGrid.Columns>
<data:DataGridTextColumn Header="AlwaysShown" Width="80" Binding="{Binding Municipality}" />
<data:DataGridTextColumn Header="ShownInEditMode" Width="73" Binding="{Binding Estate}" Visibility="Collapsed" />
</data:DataGrid.Columns>
</data:DataGrid>
</StackPanel>
Calling the following should then hide the column, but this doesnt work.
VisualStateManager.GoToState(this, "Edit", false);
Any ideas?
I haven't been able to come up with a simple solution to this as yet. However its only fair that I at least tell you why this isn't working. In order to animate a property the property needs to be DependencyProperty. The Visibility property of the DataGridColumn is not a DependencyProperty, hence it does not animate.
You can try setting column width = 0
You can either subclass DataGrid or create an attached property to toggle Visibility. However, unlike Opacity, you can't really 'animate' Visibility unless you enable FluidLayout in the VisualStateManager.
For more info regarding the fluid UI, please take a look at http://www.microsoft.com/design/toolbox/tutorials/fluidui/