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>
Related
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.
The AutoCompleteBox is pretty great, but one feature it lacks is a click-to-drop-down all the available options. You can come close by setting MinimumPrefixLength=0 -- that way, the user can get the complete drop-down list by deleting the text. This has a couple of limitations, though:
if there is no text to begin with, then the user would have to enter some text and delete it (non-intuitive and inconvenient)
the click-and-keypress sequence seems like suboptimal UX.
How would you tweak this control to make it drop down the complete list of options, when the user clicks on the control (or, also fine, a button to the right)?
It seems I have disappointed #HighCore. So far I tried adding a button to the control template, which triggers opening the Popup. The drawback to this approach is that, if there is text entered, then the list will be filtered per the normal filtering rules. Now, you could clear the text, thus removing the filter, but this has another side effect: de-selecting the currently selected item (in contrast to a ComboBox, whose drop-down you can open without de-selecting). So ... what now? Temporarily remove the filter, and restore it when the popup is closed or the user types anything else?
I built a control like that, the approach I used was to inherit from the AutoComboBox control and remove the filter when the dropdown opens.
EDIT
Code has been updated to include missing resources.
Code is:
public class AutoCompleteComboBox : AutoCompleteBox
{
/// <summary>
/// Occurs when drop down toggle button is clicked.
/// </summary>
public event EventHandler ToggleButtonClick;
private object _holdSelectedItem;
private AutoCompleteFilterMode _holdFilterMode;
/// <summary>
/// Identifies the DisplayMemberPath dependency property.
/// </summary>
public static readonly DependencyProperty DisplayMemberPathProperty = DependencyProperty.Register("DisplayMemberPath", typeof(string), typeof(AutoCompleteComboBox), new PropertyMetadata(string.Empty, DisplayMemberPath_PropertyChanged));
/// <summary>
/// Gets or sets the name or path of the property
/// that is displayed for each the data item in the control.
/// </summary>
public string DisplayMemberPath
{
get { return (string)GetValue(DisplayMemberPathProperty); }
set { SetValue(DisplayMemberPathProperty, value); }
}
private static void DisplayMemberPath_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var accb = (AutoCompleteComboBox)d;
accb.ValueMemberPath = e.NewValue.ToString();
}
/// <summary>
/// Identifies the MaxLength dependency property.
/// </summary>
public static readonly DependencyProperty MaxLengthProperty = DependencyProperty.Register("MaxLength", typeof(int), typeof(AutoCompleteComboBox), null);
/// <summary>
/// Gets or sets the maximum number of characters allowed for user input.
/// </summary>
public int MaxLength
{
get { return (int)GetValue(MaxLengthProperty); }
set { SetValue(MaxLengthProperty, value); }
}
/// <summary>
/// Gets or sets a collection used to generate the content of the control.
/// </summary>
public new IEnumerable ItemsSource
{
get { return GetValue(ItemsSourceProperty) as IEnumerable; }
set
{
SetValue(SelectedItemProperty, null);
SetValue(ItemsSourceProperty, value);
Dispatcher.BeginInvoke(() => SetValue(TextProperty, string.Empty));
_holdSelectedItem = null;
}
}
/// <summary>
/// Initializes a new instance of the AutoCompleteComboBox control.
/// </summary>
public AutoCompleteComboBox()
{
StreamResourceInfo sri = Application.GetResourceStream(new Uri("/UI.Controls;component/AutoCompleteComboBox.xaml", UriKind.Relative));
var sr = new StreamReader(sri.Stream);
Style = (Style)XamlReader.Load(sr.ReadToEnd());
DropDownClosed += AutoCompleteComboBox_DropDownClosed;
DropDownOpened += AutoCompleteComboBox_DropDownOpened;
}
/// <summary>
/// Called when the template's tree is generated.
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var toggle = (ToggleButton)GetTemplateChild("PopupToggleButton");
toggle.Click += DropDownToggle_Click;
var lb = (ListBox)GetTemplateChild("Selector");
lb.DisplayMemberPath = DisplayMemberPath;
_holdFilterMode = FilterMode;
TextChanged += AutoCompleteComboBox_TextChanged;
SelectionChanged += new SelectionChangedEventHandler(AutoCompleteComboBox_SelectionChanged);
}
private void AutoCompleteComboBox_DropDownClosed(object sender, RoutedPropertyChangedEventArgs<bool> e)
{
if (SelectedItem == null && _holdSelectedItem != null && SelectedItem != _holdSelectedItem && ItemsSource.Cast<object>().Contains(_holdSelectedItem))
{
SelectedItem = _holdSelectedItem;
}
_holdSelectedItem = null;
FilterMode = _holdFilterMode;
}
private void AutoCompleteComboBox_DropDownOpened(object sender, RoutedPropertyChangedEventArgs<bool> e)
{
var lb = (ListBox)GetTemplateChild("Selector");
ScrollViewer sv = lb.GetScrollHost();
if (sv != null)
{
sv.ScrollToTop();
}
if (SelectedItem != null)
{
lb.SelectedItem = SelectedItem;
_holdSelectedItem = SelectedItem;
}
}
private void DropDownToggle_Click(object sender, RoutedEventArgs e)
{
IsDropDownOpen = !IsDropDownOpen;
if (ToggleButtonClick != null)
{
ToggleButtonClick(this, e);
}
if (IsDropDownOpen)
{
_holdFilterMode = FilterMode;
FilterMode = AutoCompleteFilterMode.None;
((TextBox)GetTemplateChild("Text")).SelectAll();
}
Focus();
}
private void AutoCompleteComboBox_TextChanged(object sender, RoutedEventArgs e)
{
if (IsDropDownOpen)
{
if (FilterMode == AutoCompleteFilterMode.None && FilterMode != _holdFilterMode)
{
FilterMode = _holdFilterMode;
}
ScrollViewer sv = ((ListBox)GetTemplateChild("Selector")).GetScrollHost();
if (sv != null)
{
sv.ScrollToTop();
}
}
}
private void AutoCompleteComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!IsDropDownOpen && SelectedItem == null)
{
Text = string.Empty;
}
}
}
<Setter Property="Height" Value="24" />
<Setter Property="MinimumPopulateDelay" Value="1" />
<Setter Property="IsTextCompletionEnabled" Value="False" />
<Setter Property="MinimumPrefixLength" Value="0" />
<Setter Property="MaxDropDownHeight" Value="300" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Padding" Value="2" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush" Value="#FF000000" />
<Setter Property="Background" Value="#FFFFFFFF" />
<Setter Property="Foreground" Value="#FF000000" />
<Setter Property="MinWidth" Value="45" />
<Setter Property="FilterMode" Value="Contains" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ctrls:AutoCompleteComboBox">
<Grid >
<Grid.Resources>
<Style x:Name="comboToggleStyle" TargetType="ToggleButton">
<Setter Property="Foreground" Value="#FF333333"/>
<Setter Property="Background" Value="#FF1F3B53"/>
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFA3AEB9" Offset="0"/>
<GradientStop Color="#FF8399A9" Offset="0.375"/>
<GradientStop Color="#FF718597" Offset="0.375"/>
<GradientStop Color="#FF617584" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Padding" Value="3"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundOverlay" Storyboard.TargetProperty="Opacity" To="1"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#7FFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#CCFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#F2FFFFFF"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundOverlay2" Storyboard.TargetProperty="Opacity" To="1"/>
<DoubleAnimation Duration="0" Storyboard.TargetName="Highlight" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#E5FFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#BCFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#6BFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" To="#F2FFFFFF"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled" />
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundOverlay3" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<DoubleAnimation Duration="0" Storyboard.TargetName="Highlight" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<DoubleAnimation Duration="0" Storyboard.TargetName="BackgroundGradient2" Storyboard.TargetProperty="(UIElement.Opacity)" To="1"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient2" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" To="#E5FFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient2" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" To="#BCFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient2" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" To="#6BFFFFFF"/>
<ColorAnimation Duration="0" Storyboard.TargetName="BackgroundGradient2" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" To="#F2FFFFFF"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked"/>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisualElement" Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="Background" RadiusX="3" RadiusY="3" Fill="{TemplateBinding Background}" StrokeThickness="{TemplateBinding BorderThickness}" Stroke="{TemplateBinding BorderBrush}"/>
<Rectangle x:Name="BackgroundOverlay" Opacity="0" RadiusX="3" RadiusY="3" Fill="#FF448DCA" StrokeThickness="{TemplateBinding BorderThickness}" Stroke="#00000000"/>
<Rectangle x:Name="BackgroundOverlay2" Opacity="0" RadiusX="3" RadiusY="3" Fill="#FF448DCA" StrokeThickness="{TemplateBinding BorderThickness}" Stroke="#00000000"/>
<Rectangle x:Name="BackgroundGradient" RadiusX="2" RadiusY="2" StrokeThickness="1" Margin="{TemplateBinding BorderThickness}" Stroke="#FFFFFFFF">
<Rectangle.Fill>
<LinearGradientBrush StartPoint=".7,0" EndPoint=".7,1">
<GradientStop Color="#FFFFFFFF" Offset="0" />
<GradientStop Color="#F9FFFFFF" Offset="0.375" />
<GradientStop Color="#E5FFFFFF" Offset="0.625" />
<GradientStop Color="#C6FFFFFF" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle Opacity="0" x:Name="BackgroundOverlay3" RadiusX="3" RadiusY="3" Fill="#FF448DCA" StrokeThickness="{TemplateBinding BorderThickness}" Stroke="#00000000"/>
<Rectangle Opacity="0" x:Name="BackgroundGradient2" RadiusX="2" RadiusY="2" StrokeThickness="1" Margin="{TemplateBinding BorderThickness}" Stroke="#FFFFFFFF">
<Rectangle.Fill>
<LinearGradientBrush StartPoint=".7,0" EndPoint=".7,1">
<GradientStop Color="#FFFFFFFF" Offset="0" />
<GradientStop Color="#F9FFFFFF" Offset="0.375" />
<GradientStop Color="#E5FFFFFF" Offset="0.625" />
<GradientStop Color="#C6FFFFFF" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle x:Name="Highlight" RadiusX="2" RadiusY="2" Opacity="0" IsHitTestVisible="false" Stroke="#FF6DBDD1" StrokeThickness="1" Margin="{TemplateBinding BorderThickness}" />
<ContentPresenter
x:Name="contentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}"/>
<Rectangle x:Name="FocusVisualElement" RadiusX="3.5" Margin="1" RadiusY="3.5" Stroke="#FF6DBDD1" StrokeThickness="1" Visibility="Collapsed" IsHitTestVisible="false" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ControlTemplate x:Key="CommonValidationToolTipTemplate" TargetType="ToolTip">
<Grid x:Name="Root" Margin="5,0" RenderTransformOrigin="0,0" Opacity="0">
<Grid.RenderTransform>
<TranslateTransform x:Name="Translation" X="-25" />
</Grid.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="OpenStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0" />
<VisualTransition To="Open" GeneratedDuration="0:0:0.2">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Translation" Storyboard.TargetProperty="X" To="0" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<BackEase Amplitude=".3" EasingMode="EaseOut" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2" />
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Closed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To="0" Duration="0" />
</Storyboard>
</VisualState>
<VisualState x:Name="Open">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Translation" Storyboard.TargetProperty="X" To="0" Duration="0" />
<DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To="1" Duration="0" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Margin="4,4,-4,-4" Background="#052A2E31" CornerRadius="5" />
<Border Margin="3,3,-3,-3" Background="#152A2E31" CornerRadius="4" />
<Border Margin="2,2,-2,-2" Background="#252A2E31" CornerRadius="3" />
<Border Margin="1,1,-1,-1" Background="#352A2E31" CornerRadius="2" />
<Border Background="#FFDC000C" CornerRadius="2">
<TextBlock UseLayoutRounding="false" Foreground="White" Margin="8,4,8,4" MaxWidth="250" TextWrapping="Wrap" Text="{Binding (Validation.Errors)[0].ErrorContent}" />
</Border>
</Grid>
</ControlTemplate>
</Grid.Resources>
<TextBox x:Name="Text" VerticalContentAlignment="Center" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" Foreground="{TemplateBinding Foreground}" MaxLength="{TemplateBinding MaxLength}" />
<ToggleButton x:Name="PopupToggleButton" Width="24" HorizontalAlignment="Right" Style="{StaticResource comboToggleStyle}" Margin="1" >
<Path x:Name="BtnArrow" Height="4" Width="8" Stretch="Uniform" Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z " HorizontalAlignment="Center">
<Path.Fill>
<SolidColorBrush x:Name="BtnArrowColor" Color="#FF333333"/>
</Path.Fill>
</Path>
</ToggleButton>
<Border x:Name="ValidationErrorElement" Visibility="Collapsed" BorderBrush="#FFDB000C" BorderThickness="1" CornerRadius="1">
<ToolTipService.ToolTip>
<ToolTip x:Name="validationTooltip" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource CommonValidationToolTipTemplate}" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<ToolTip.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsHitTestVisible">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<system:Boolean>true</system:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
</ToolTipService.ToolTip>
<Grid Height="12" HorizontalAlignment="Right" Margin="1,-4,-4,0" VerticalAlignment="Top" Width="12" Background="Transparent">
<Path Fill="#FFDC000C" Margin="1,3,0,0" Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" />
<Path Fill="#ffffff" Margin="1,3,0,0" Data="M 0,0 L2,0 L 8,6 L8,8" />
</Grid>
</Border>
<Popup x:Name="Popup">
<ListBox x:Name="Selector" SelectionMode="Single" ScrollViewer.HorizontalScrollBarVisibility="Disabled" ScrollViewer.VerticalScrollBarVisibility="Auto" MaxHeight="{TemplateBinding MaxDropDownHeight}" />
</Popup>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PopupStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:0" To="PopupOpened" />
<VisualTransition GeneratedDuration="0:0:0" To="PopupClosed" />
</VisualStateGroup.Transitions>
<VisualState x:Name="PopupOpened">
</VisualState>
<VisualState x:Name="PopupClosed">
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid" />
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<system:Boolean>True</system:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
I worked out one approach. The basic idea is to overlay a button, either in a modified control template, or just flung on top of the view (I'll use the latter here for brevity's sake). The button thing does three things:
Clear the text (in order to restore the complete unfiltered list)
Set IsDropDownOpen = true to open the Popup
Restore the selected item to the ListBox template child
So basically, I have a setup like this:
<Grid>
<mycontrols:ExtendedAutoCompleteBox x:Name="autoCompleteBox" ... />
<Button HorizontalAlignment="Right" Click="Button_Click">
<Path Data="F1 M 301.14,-189.041L 311.57,-189.041L 306.355,-182.942L 301.14,-189.041 Z "
Fill="Black" Stretch="Uniform"
Width="8" Height="4"
/>
</Button>
</Grid>
The purpose of subclassing AutoCompleteBox is just to get access to the template child:
public class ExtendedAutoCompleteBox : AutoCompleteBox
{
private ListBox _listBox;
public override void OnApplyTemplate()
{
_listBox = GetTemplateChild("Selector") as ListBox;
}
public void ShowAllOptions()
{
var selectedItem = SelectedItem;
Text = "";
IsDropDownOpen = true;
_listBox.SelectedItem = selectedItem;
UpdateLayout();
_listBox.ScrollIntoView(selectedItem);
}
}
And so the button click will just call this "ShowAllOptions":
private void Button_Click(object sender, RoutedEventArgs e)
{
autoCompleteBox.ShowAllOptions();
}
It's not perfect -- for some reason the keyboard controls get screwy after clicking the button (if you press enter on an item, it does not select the item, but clears it instead) -- but it is better than nothing.
So just to illustrate what it does: let's say you have an item "x" selected, and you click the arrow button, then you will see the complete list:
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">
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.