I'm working on a custom button for my project.
I'm not using a default Button but just a ContentControl with a ControlTemplate assigned to it.
For the beginning just a simple template:
<ControlTemplate x:Key="MySampleTemplate">
<Image x:Name="img" Source="/AppName;component/Res/Img/normalstate.png" />
</ControlTemplate>
Now I would like to animate the image by using VisualStateManager.
First I created a "Hover" and a "Normal" state:
<ControlTemplate x:Key="MySampleTemplate">
<Image x:Name="img" Source="/AppName;component/Res/Img/normalstate.png">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal" />
<VisualState Name="Hover">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="0"
Storyboard.TargetName="img" Storyboard.TargetProperty="Source">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<BitmapImage UriSource="/AppName;component/Res/Img/hoverstate.png" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Image>
</ControlTemplate>
In Code behind:
btnSampleButton.MouseEnter += (s,e) => VisualStateManager.GoToState(btnSampleButton, "Hover", false);
btnSampleButton.MouseLeave += (s,e) => VisualStateManager.GoToState(btnSampleButton, "Normal", false);
Everything works just fine. If I hover over the button hoverstate.png is shown and if I don't hover over normalstate.png is shown.
Now I would like to animate LeftMouseButtonDown and LeftMouseButtonUp to achieve a pressing animation if the user click on the button.
I created a VisualState called "Pressed" and just set it up like "Hover" (just replaced hoverstate.png with pressedstate.png).
In the code behind I did this:
btnSampleButton.MouseLeftButtonDown += (s,e) => VisualStateManager.GoToState(btnSampleButton, "Pressed", false);
btnSampleButton.MouseLeftButtonUp += (s,e) => VisualStateManager.GoToState(btnSampleButton, btnSampleButton.IsMouseOver ? "Hover" : "Normal", false);
However it does not work. MouseEnter and MouseLeave work fine but I can't see any changes to the button if I press/release my left mouse button.
Do you have any advice to get it work?
Edit:
Control:
<ContentControl x:Name="btnSampleButton" Template="{StaticResource SamplButtonTemplate}" />
ControlTemplate:
<ControlTemplate x:Key="SamplButtonTemplate">
<Image x:Name="Image" Source="/AppName;component/Res/Img/normalstate.png">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="CommonStates">
<VisualState Name="Normal" />
<VisualState Name="Hover">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="0"
Storyboard.TargetName="Image" Storyboard.TargetProperty="Source">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<BitmapImage UriSource="/AppName;component/Res/Img/hoverstate.png" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="0" FillBehavior="Stop"
Storyboard.TargetName="Image" Storyboard.TargetProperty="Source">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<BitmapImage UriSource="/AppName;component/Res/Img/pressedstate.png" />
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Image>
</ControlTemplate>
Code behind:
btnSampleButton.MouseEnter += (s, e) => VisualStateManager.GoToState(btnSampleButton, "Hover", false);
btnSampleButton.MouseLeave += (s, e) => VisualStateManager.GoToState(btnSampleButton, "Normal", false);
btnSampleButton.PreviewMouseLeftButtonDown +=
(s, e) => VisualStateManager.GoToState(btnSampleButton, "Pressed", false);
btnSampleButton.PreviewMouseLeftButtonUp +=
(s, e) => VisualStateManager.GoToState(btnSampleButton, btnSampleButton.IsMouseOver ? "Hover" : "Normal", false);
MouseLeftButtonDown and MouseLeftButtonUp gets swallowed by Click event of Button that's why these events are never raised for button and no change in VisualState.
Instead hook corresponding Preview events i.e. PreviewMouseLeftButtonDown and PreviewMouseLeftButtonUp :
btnSampleButton.PreviewMouseLeftButtonDown += (s,e) =>
VisualStateManager.GoToState(btnSampleButton, "Pressed", false);
btnSampleButton.PreviewMouseLeftButtonUp += (s,e) =>
VisualStateManager.GoToState(btnSampleButton, btnSampleButton.IsMouseOver ?
"Hover" : "Normal", false);
Remove FillBehavior="Stop" from second StoryBoard and your code works fine.
<VisualState Name="Pressed">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="0"
FillBehavior="Stop" <-- Remove this
Storyboard.TargetName="Image"
Storyboard.TargetProperty="Source">
Related
I have a custom control MediaPlayerControl. In the its Template I define a VisualStateGroup:
<VisualStateGroup x:Name="SizeState">
<VisualState x:Name="Normal" />
<VisualState x:Name="LargeSize" >
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MediaInfo_Border" Storyboard.TargetProperty="(Grid.ColumnSpan)" >
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MediaInfo_Border" Storyboard.TargetProperty="(Grid.RowSpan)" >
<DiscreteObjectKeyFrame KeyTime="0" Value="2"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PrimaryMediaControl_Border" Storyboard.TargetProperty="HorizontalAlignment" >
<DiscreteObjectKeyFrame KeyTime="0" Value="Center"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MediaTransportControls_Timeline_Border" Storyboard.TargetProperty="(Grid.Column)" >
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MediaTransportControls_Timeline_Border" Storyboard.TargetProperty="(Grid.ColumnSpan)" >
<DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="MoreButton" Storyboard.TargetProperty="Visibility" >
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="SecondaryTrim_MenuFlyOut" Storyboard.TargetProperty="Visibility" >
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="TrimButton" Storyboard.TargetProperty="Visibility" >
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
And in the SizeChanged event I call ChangeState() function:
private void MediaPlayerControl_SizeChanged(object sender, object e)
{
ChangeState();
}
private void ChangeState()
{
string state = "";
if (Window.Current.Bounds.Width < 600)
{
state = "Normal";
}
else
{
state = "LargeSize";
}
VisualStateManager.GoToState(this, state, false);
}
Although VisualStateManager.GoToState return true and UI change to LargeSize state but in short time it automatically changes to Normal state.
If I use LayoutUpdated event, it work properly, but performance is not good.
Can I use GotoState with SizeChanged event?
I have a form for adding an item. The item requires an author which could be search, in which the component for the author was a search box. Also included a code in which the background of the search box will turn red when empty otherwise white. Also have a list for suggestion. When the time I select an author in the suggestions, the search box don't turn it's color. But when I hover the search box, that's the only time it goes to the costumed color. No user want's to hover the search box every time just to see if it is valid or not.
Here's a sample code:
XAML
<SearchBox x:Name="SearchBoxColor" SearchHistoryEnabled="False" SuggestionsRequested="SearchBoxColor_SuggestionsRequested" QueryChanged="SearchBoxColor_QueryChanged" QuerySubmitted="SearchBoxColor_QuerySubmitted" Background="White" />
<Button Content="Turn Color"Click="ButtonColor_Click" />
CS
private void SearchBoxColor_SuggestionsRequested(SearchBox sender, SearchBoxSuggestionsRequestedEventArgs args) {
// When this event is called the background will change instantly
ChangeSearchBoxColor();
}
private void SearchBoxColor_QueryChanged(SearchBox sender, SearchBoxQueryChangedEventArgs args) {
// When this event is called the background will change instantly
ChangeSearchBoxColor();
}
private void SearchBoxColor_QuerySubmitted(SearchBox sender, SearchBoxQuerySubmittedEventArgs args) {
// When this event is called the background will change instantly
ChangeSearchBoxColor();
}
private void ButtonColor_Click(object sender, RoutedEventArgs e) {
// When this event is called the background will change only when the search box is hovered
ChangeSearchBoxColor();
}
private void ChangeSearchBoxColor() {
SearchBoxColor.Background = new SolidColorBrush(Colors.Red);
}
You can achieve this with code-behind on your view which would set the background to the red color you're looking for however I'd recommend using the Behaviors SDK, which you can reference in your 8.1 project, to set a VisualState on the control if the text is invalid. You can do this as follows:
Action:
public class SearchBoxTextErrorVisualStateAction : DependencyObject, IAction
{
public static readonly DependencyProperty ErrorVisualStateProperty = DependencyProperty.Register(
"ErrorVisualState",
typeof(string),
typeof(SearchBoxTextErrorVisualStateAction),
new PropertyMetadata(string.Empty));
public string ErrorVisualState
{
get
{
return (string)this.GetValue(ErrorVisualStateProperty);
}
set
{
this.SetValue(ErrorVisualStateProperty, value);
}
}
public static readonly DependencyProperty ValidVisualStateProperty =
DependencyProperty.Register(
"ValidVisualState",
typeof(string),
typeof(SearchBoxTextErrorVisualStateAction),
new PropertyMetadata(string.Empty));
public string ValidVisualState
{
get
{
return (string)this.GetValue(ValidVisualStateProperty);
}
set
{
this.SetValue(ValidVisualStateProperty, value);
}
}
public object Execute(object sender, object parameter)
{
var searchBox = sender as SearchBox;
if (searchBox != null)
{
VisualStateManager.GoToState(
searchBox,
string.IsNullOrWhiteSpace(searchBox.QueryText) ? this.ErrorVisualState : this.ValidVisualState,
true);
}
return parameter;
}
}
XAML example:
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.Resources>
<Color x:Key="AppErrorColor">#FFD32F2F</Color>
<SolidColorBrush x:Key="ThemeErrorShade" Color="{ThemeResource AppErrorColor}" />
<Style x:Key="SearchBoxStyle" TargetType="SearchBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="SearchBox">
<Grid x:Name="SearchBoxGrid">
...
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="SearchBoxGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Background, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="SearchBoxBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding BorderBrush, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="SearchButton">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Foreground, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="SearchBoxGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxDisabledBackgroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="SearchBoxBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxDisabledBorderThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="SearchButton">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SearchBoxDisabledTextThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="SearchTextBox">
<DiscreteObjectKeyFrame KeyTime="0" Value="Transparent"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ErrorStates">
<VisualState x:Name="TextInvalid">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="SearchBoxGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource ThemeErrorShade}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="TextValid">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="SearchBoxGrid">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Background, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="SearchBoxBorder">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding BorderBrush, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Foreground" Storyboard.TargetName="SearchButton">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Foreground, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
...
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Grid.Resources>
<SearchBox Style="{StaticResource SearchBoxStyle}">
<interactivity:Interaction.Behaviors>
<core:EventTriggerBehavior EventName="Loaded">
<core:EventTriggerBehavior.Actions>
<actions:SearchBoxTextErrorVisualStateAction ErrorVisualState="TextInvalid" ValidVisualState="TextValid" />
</core:EventTriggerBehavior.Actions>
</core:EventTriggerBehavior>
<core:EventTriggerBehavior EventName="QueryChanged">
<core:EventTriggerBehavior.Actions>
<actions:SearchBoxTextErrorVisualStateAction ErrorVisualState="TextInvalid" ValidVisualState="TextValid" />
</core:EventTriggerBehavior.Actions>
</core:EventTriggerBehavior>
</interactivity:Interaction.Behaviors>
</SearchBox>
</Grid>
Due to the limits of answers, I can't paste the entirety of the XAML but you want to add the default SearchBox to your view, in the design window, right click it and go to 'Edit Template -> Edit a copy'. It will create a copy of the default style in which you can replace the root Grid's VisualStateGroups with the ones above.
Edit: The reason for firing the action on the Loaded event is so that you can enable the color change when the control is brought into view and not just when the text changes.
I have a tree which represents XML document. And I show XML validations errors using a bound class which implements INotifyDataErrorInfo.
It works all fine on editable controls like text box or combo.
But some nodes are just text blocks. And they have errors too.
How do I go about it?
I recommend you wrap your TextBlock in a ValidationErrorBorder:
<ValidationErrorBorder DataContextValidationTargetPath="MyTextProperty">
<TextBlock Text="{Binding Path=MyTextProperty}"/>
</ValidationErrorBorder>
ValidationErrorBorder control code:
/*
* Use in conjuction with other Controls that do not have the appropriate
* visualStates to indicate ValidationErrors.
* Either set DataContextValidationTargetPath to have this
* ValidationErrorBorder show validation errors that occur at the targeted
* property of the DataContext
* or bind ValidationTarget to any target you wish to have this
* ValidationErrorBorder show validation errors occuring for the bound target.
*/
[TemplateVisualState(GroupName="ValidationStates", Name = "Valid")]
[TemplateVisualState(GroupName="ValidationStates", Name = "InvalidUnfocused")]
[TemplateVisualState(GroupName="ValidationStates", Name = "InvalidFocused")]
public class ValidationErrorBorder : ContentControl
{
public object ValidationTarget
{
get { return GetValue( ValidationTargetProperty ); }
set { SetValue( ValidationTargetProperty, value ); }
}
public static readonly DependencyProperty ValidationTargetProperty =
DependencyProperty.Register( "ValidationTarget", typeof( object ),
typeof( ValidationErrorBorder ), new PropertyMetadata( null ) );
public string DataContextValidationTargetPath
{
get
{
return (string) GetValue( DataContextValidationTargetPathProperty );
}
set { SetValue( DataContextValidationTargetPathProperty, value ); }
}
public static readonly DependencyProperty
DataContextValidationTargetPathProperty =
DependencyProperty.Register( "DataContextValidationTargetPath",
typeof( string ), typeof( ValidationErrorBorder ),
new PropertyMetadata( null, HandlePathChanged ) );
private static void HandlePathChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
((ValidationErrorBorder) d).HandlePathChanged();
}
private void HandlePathChanged()
{
if (DataContextValidationTargetPath != null)
SetBinding(ValidationTargetProperty,
new Binding(DataContextValidationTargetPath));
else
ClearValue( ValidationTargetProperty );
}
public ValidationErrorBorder()
{
DefaultStyleKey = typeof( ValidationErrorBorder );
}
}
and the control template:
<Style TargetType="controls:ValidationErrorBorder">
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ValidationErrorBorder">
<Grid x:Name="RootElement" Background="{x:Null}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid"/>
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<system:Boolean>True</system:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" />
<Border x:Name="ValidationErrorElement" BorderBrush="#FFDB000C" BorderThickness="1" CornerRadius="1" Visibility="Collapsed">
<ToolTipService.ToolTip>
<ToolTip x:Name="validationTooltip" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource ValidationToolTipTemplate}">
<ToolTip.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<system:Boolean>True</system:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
</ToolTipService.ToolTip>
<Grid Background="Transparent" HorizontalAlignment="Right" Height="12" Margin="1,-4,-4,0" VerticalAlignment="Top" Width="12">
<Path Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C" Margin="1,3,0,0"/>
<Path Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff" Margin="1,3,0,0"/>
</Grid>
</Border>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and the validationTooltipTemplate:
<ControlTemplate x:Key="ValidationToolTipTemplate">
<Grid x:Name="Root" Margin="5,0" Opacity="0" RenderTransformOrigin="0,0">
<Grid.RenderTransform>
<TranslateTransform x:Name="xform" X="-25"/>
</Grid.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="OpenStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
<VisualTransition GeneratedDuration="0:0:0.2" To="Open">
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform">
<DoubleAnimation.EasingFunction>
<BackEase Amplitude=".3" EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Duration="0:0:0.2" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Closed">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Open">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetProperty="X" Storyboard.TargetName="xform"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="Root"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Background="#052A2E31" CornerRadius="5" Margin="4,4,-4,-4"/>
<Border Background="#152A2E31" CornerRadius="4" Margin="3,3,-3,-3"/>
<Border Background="#252A2E31" CornerRadius="3" Margin="2,2,-2,-2"/>
<Border Background="#352A2E31" CornerRadius="2" Margin="1,1,-1,-1"/>
<Border Background="#FFDC000C" CornerRadius="2"/>
<Border CornerRadius="2">
<TextBlock Foreground="White" MaxWidth="250" Margin="8,4,8,4" TextWrapping="Wrap" Text="{Binding (Validation.Errors)[0].ErrorContent}" UseLayoutRounding="false"/>
</Border>
</Grid>
</ControlTemplate>
It is not really supported in Silverlight by default. One way that I have found to work well in the past is to make a control that inherits from Textblock. You can then handle the IDatanotify interface and exceptions and turn the textblock different colors based off of those.
I have an animation that animates a Canvas by turning it 360 degrees indefinitely (it basically spins). What I want is for this animation to start when the control is shown and then stop when the control is hidden. I figured I could tie this in, somehow, to the VisualStateManager. I have seen an example of fading in and out controls here which could work but I just dont know how to use VSM to start and stop the storyboard
<Canvas.Resources>
<Storyboard x:Name="spinnerBoard">
<DoubleAnimation
Storyboard.TargetName="SpinnerRotate"
Storyboard.TargetProperty="Angle"
From="0" To="360" Duration="0:0:01.3"
RepeatBehavior="Forever" />
</Storyboard>
</Canvas.Resources>
<Canvas.RenderTransform>
<RotateTransform x:Name="SpinnerRotate" Angle="0" />
</Canvas.RenderTransform>
Example VSM
<VisualState x:Name="Show">
<Storyboard>
<!-- Start the story board here -->
</Storyboard>
</VisualState>
<VisualState x:Name="Hide">
<Storyboard>
<!-- Stop the story board here -->
</Storyboard>
</VisualState>
A global answer of your different questions :
ExtendedVisualStateManager.GoToElementState returns false in Silverlight
Default binding to UserControl for custom DP
You can do something like this :
Use a template control that extend ContentControl to play with
IsEnabled of content (prevent action during waiting) ;
Create a DP IsWaiting that switch your control visual state ;
Create the two states in XAML : Use DoubleAnimation with RepeatBehavior="Forever"
After you can add a overlay and a Waiting message dependency property like the busy indicator control...
I use a picture for the Waiting visual part but you can use a canvas, grid etc...
C#
[TemplateVisualState(GroupName = "WaitGroup", Name = WaitSpinner.IsWaitingStateName)]
[TemplateVisualState(GroupName = "WaitGroup", Name = WaitSpinner.NotWaitingStateName)]
public class WaitSpinner : ContentControl
{
#region States names
internal const String IsWaitingStateName = "IsWaitingState";
internal const String NotWaitingStateName = "NotWaitingState";
#endregion States names
public bool IsWaiting
{
get { return (bool)GetValue(IsWaitingProperty); }
set { SetValue(IsWaitingProperty, value); }
}
public static readonly DependencyProperty IsWaitingProperty =
DependencyProperty.Register("IsWaiting", typeof(bool), typeof(WaitSpinner), new PropertyMetadata(false, OnIsWaitingPropertyChanged));
private static void OnIsWaitingPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
WaitSpinner waitSpinner = (WaitSpinner)sender;
waitSpinner.ChangeVisualState(true);
}
public WaitSpinner()
{
DefaultStyleKey = typeof(WaitSpinner);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
ChangeVisualState(false);
}
protected virtual void ChangeVisualState(bool useTransitions)
{
VisualStateManager.GoToState(this, IsWaiting ? IsWaitingStateName : NotWaitingStateName, useTransitions);
}
}
Xaml :
<VisualStateGroup x:Name="WaitGroup">
<VisualState x:Name="NotWaitingState" >
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Control.IsEnabled)" Storyboard.TargetName="content">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Boolean>True</System:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="IsWaitingState">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="WaitPart">
<DiscreteObjectKeyFrame KeyTime="0:0:0.200" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" Storyboard.TargetName="WaitPart" To="360" RepeatBehavior="Forever" Duration="0:0:1" />
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(Control.IsEnabled)" Storyboard.TargetName="content">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<System:Boolean>False</System:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
<!-- ............. -->
<ContentControl
IsTabStop="False"
x:Name="content"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Foreground="{TemplateBinding Foreground}"
ScrollViewer.HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
ScrollViewer.VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"/>
<Image Source="CirclePicture.png"
x:Name="WaitPart"
RenderTransformOrigin="0.5,0.5"
Width="16"
Height="16"
Visibility="Collapsed"
IsHitTestVisible="False">
<Image.RenderTransform>
<RotateTransform />
</Image.RenderTransform>
</Image>
I want to have a button that responds to the Touch.FrameReported Up & Down events instead of the usual MouseDown and MouseUp events that would be used so this button can be used at the same time on Windows Phone as another button.
I already have a custom Button control with a MouseDown and MouseUp state, but am unsure how to make the Up and Down events there trigger the correct look - probably something with the VisualStateManager needs set but cannot figure out how to use it - solution needs to use the standard Button control as I'm merely extending it for the two states - as a button control with a normal and "pressed" state.
This is for a game screen within a larger Silverlight project, the rest of the project is standard Silverlight with the standard buttons and their normal behaviour, however in one place this needs to be Multitouch so this cannot be an XNA project instead as this would require porting 99% of the app to XNA where other features used are not supported - I've been able to extend custom controls to support multitouch but want the button to react this way too - plus I'm sure this will be of use to others, especially as this will most likely apply to Windows 7/8 development too.
Edit: Here is the Code and Generic.xaml for my button with the normal behaviour (OnMouseUp/OnMouseDown)
Code:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
using System.Windows.Input;
using System.Diagnostics;
namespace UXLibrary
{
[TemplatePart(Name = "Pressed", Type = typeof(BitmapSource))]
[TemplatePart(Name = "Normal", Type = typeof(BitmapSource))]
public class UXButton : Button
{
public static readonly DependencyProperty PressedProperty =
DependencyProperty.Register("Pressed", typeof(BitmapSource),
typeof(UXButton), null);
public static readonly DependencyProperty NormalProperty =
DependencyProperty.Register("Normal", typeof(BitmapSource),
typeof(UXButton), null);
public BitmapSource Pressed
{
get { return (BitmapSource)GetValue(PressedProperty); }
set { SetValue(PressedProperty, value); }
}
public BitmapSource Normal
{
get { return (BitmapSource)GetValue(NormalProperty); }
set { SetValue(NormalProperty, value); }
}
/// <summary>Constructor</summary>
public UXButton()
{
DefaultStyleKey = typeof(UXButton);
}
/// <summary>OnApplyTemplate</summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
}
}
Generic.xaml
<Style TargetType="local:UXButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:UXButton">
<Grid>
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Opacity)" To="0.5"/>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Normal">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PressedImage" Storyboard.TargetProperty="(UIElement.Visibility)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Visibility)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="MouseOver"/>
<vsm:VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PressedImage" Storyboard.TargetProperty="(UIElement.Visibility)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Visibility)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="FocusStates">
<vsm:VisualState x:Name="Focused"/>
<vsm:VisualState x:Name="Unfocused"/>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<Image x:Name="PressedImage" Stretch="Uniform" Source="{TemplateBinding Pressed}"/>
<Image x:Name="NormalImage" Stretch="Uniform" Source="{TemplateBinding Normal}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Solution
<Style TargetType="local:UXButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:UXButton">
<Grid>
<vsm:VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="MultiTouchStates">
<vsm:VisualState x:Name="Normal">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="PressedImage" Storyboard.TargetProperty="(UIElement.Visibility)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="NormalImage" Storyboard.TargetProperty="(UIElement.Visibility)">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<VisualState x:Name="SpecialTouch">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="PressedImage">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="NormalImage">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<Image x:Name="PressedImage" Stretch="Uniform" Source="{TemplateBinding Pressed}"/>
<Image x:Name="NormalImage" Stretch="Uniform" Source="{TemplateBinding Normal}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Code:
/// <summary>Button</summary>
[TemplatePart(Name = "Wrapper", Type = typeof(Grid))]
[TemplateVisualState(Name = "SpecialTouch", GroupName = "MultiTouchStates")]
public class UXButton : Button
{
public static readonly DependencyProperty PressedProperty =
DependencyProperty.Register("Pressed", typeof(BitmapSource),
typeof(UXButton), null);
public static readonly DependencyProperty NormalProperty =
DependencyProperty.Register("Normal", typeof(BitmapSource),
typeof(UXButton), null);
public BitmapSource Pressed
{
get { return (BitmapSource)GetValue(PressedProperty); }
set { SetValue(PressedProperty, value); }
}
public BitmapSource Normal
{
get { return (BitmapSource)GetValue(NormalProperty); }
set { SetValue(NormalProperty, value); }
}
/// <summary>Constructor</summary>
public UXButton()
{
DefaultStyleKey = typeof(UXButton);
}
/// <summary>OnApplyTemplate</summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Touch.FrameReported += (object sender, TouchFrameEventArgs e) =>
{
Image pressed = (Image)GetTemplateChild("PressedImage");
Image normal = (Image)GetTemplateChild("NormalImage");
TouchPointCollection points = e.GetTouchPoints(null);
foreach (TouchPoint point in points)
{
if (point.Action == TouchAction.Down && (point.TouchDevice.DirectlyOver == normal || point.TouchDevice.DirectlyOver == pressed))
{
VisualStateManager.GoToState(this, "SpecialTouch", false);
}
else if (point.Action == TouchAction.Up)
{
VisualStateManager.GoToState(this, "Normal", false);
}
}
};
}
}
If i understand your question correctly, i think you need to create one more visual state rather than two parts ("pressed" & "normal").
// UPDATE: you need to get the Grid in order to know the touch area
[TemplatePart(Name = "Wrapper", Type = typeof(Grid))]
[TemplateVisualState(Name = "SpecialTouch", GroupName = "MultiTouchStates")]
public class UXButton : Button
Then in your custom button's constructor, subscribe to the FrameReported event,
public UXButton()
{
DefaultStyleKey = typeof(UXButton);
Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);
}
void Touch_FrameReported(object sender, TouchFrameEventArgs e)
{
// UPDATE: get the Grid
var wrapper = GetTemplateChild("Wrapper") as Grid;
TouchPointCollection points = e.GetTouchPoints(null);
foreach (TouchPoint point in points)
{
// UPDATE: also do the touch area check here
// specify what touch you want
if (point.Action == TouchAction.Down && point.TouchDevice.DirectlyOver == wrapper)
{
VisualStateManager.GoToState(this, "SpecialTouch", false);
}
}
}
Then in the style, you do the hide and show images in this visual state you just created. If you want to be able to dynamically change the normal and pressed images, of course you can just add your TemplateParts back in.
UPDATE: Also you need to give your root element which is the Grid a name and a background color,
like this,
<Grid x:Name="Wrapper" Background="Transparent">
<VisualStateGroup x:Name="MultiTouchStates">
<VisualState x:Name="SpecialTouch">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="PressedImage">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="NormalImage">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
Hope this helps.