Best way to propagate VisualState changes - silverlight

I am currently facing a scenario that I am unsure what is the best way to handle.
Scenario:
ControlA has 2 two custom visualstates, let’s call them “StateOn” and “StateOff”.
I now apply a template on ControlA, let’s call it “templateA”.
“templateA” has one control under it of type ControlB (who’s unaware of StateOn/Off).
ControlB has a Template that handles the visualstate changes of ControlA, namely, StateOn and StateOff.
Problem:
ControlB does not receive changes to the VisualStates fired on ControlA, thus no visual changes happen.
I think the problem has to do with the root element being a control (ControlB), which doesn’t fire gotostate on the desired states. However, I’m wondering what is the simplest/cleanest way to propagate ControlA’s visualstate changes to ControlB.
Thanks for your help!
Henry
Xaml:-
<UserControl x:Class="VisualStateChangePropagation.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:VisualStateChangePropagation"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">
<Grid x:Name="LayoutRoot" Background="White">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<Grid.Resources>
<SolidColorBrush x:Name="Fill_Bg_Red" Color="Red"/>
<SolidColorBrush x:Name="Fill_Bg_Blue" Color="Blue"/>
<ControlTemplate x:Name="templateA" TargetType="Control">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Common">
<VisualState x:Name="StateOn">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="m_rect"
Storyboard.TargetProperty="(Rectangle.Fill)">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource Fill_Bg_Red}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="StateOff"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Rectangle x:Name="m_rect" Fill="{StaticResource Fill_Bg_Blue}"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Name="templateB" TargetType="Control">
<local:ControlB Template="{StaticResource templateA}"/>
</ControlTemplate>
</Grid.Resources>
<local:ControlA x:Name="m_control1" Template="{StaticResource templateA}" Grid.Column="0"/>
<Button Click="Button_Click" Grid.Column="1" Content="swap"/>
<local:ControlA x:Name="m_control2" Template="{StaticResource templateB}" Grid.Column="2"/>
</Grid>
</UserControl>
code behind:
public class ControlA : Control
{
public void ToggleState()
{
m_isSet = !m_isSet;
UpdateVisualState();
}
private void UpdateVisualState()
{
string targetState = m_isSet ? "StateOn" : "StateOff";
VisualStateManager.GoToState(this, targetState, false);
}
private bool m_isSet = false;
}
public class ControlB : Control
{
}

First of all both ControlA and ControlB should have a Dependency Property for IsSet.
public bool IsSet
{
get { return (bool)GetValue(IsSetProperty); }
set { SetValue(IsSetProperty, value); }
}
public static readonly DependencyProperty IsSetProperty =
DependencyProperty.Register(
"IsSet",
typeof(bool),
typeof(ControlA), //Change in ControlB
new PropertyMetadata(false, OnIsSetPropertyChanged));
private static void OnIsSetPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Control source = d as Control;
string newState = (bool)e.NewValue ? "StateOn" : "StateOff";
VisualStateManager.GotoState(source, newState, true);
}
Contrary to your intuition visual states do not propagate at all. The states are only meaningfull to the control to which they are directly attached. However with this dependency property added to both controls you can now propagate the property value via template binding:-
<ControlTemplate x:Name="templateB" TargetType="Control">
<local:ControlB Template="{StaticResource templateA}" IsSet="{TemplateBinding IsSet}" />
</ControlTemplate>
As for you original ControlA code the m_isSet field is no longer required:-
public void ToggleState()
{
IsSet = !IsSet;
}

Related

WPF Attach VisualState to Object Property

I am working on expression Blend for VS2015, I have aListBox binded to an ObservableCollection of custom objects. Those objects expose Properties that arise the NotifyPropertyChanged, and everything works nice.
I can bind parts if the ItemTemplate to those Properties and my list work nice but what I want to do is to set the VisualState according to a certain bool (already configured or not). I also created some events (configured, confLost) and tried to target those events in the triggers panel but .. nothing worked.
How do I bind VisualStates to members of the bound object ??
ItemTemplate property works like any other DependencyProperty, it can be set/reset anytime and it's visual impact will be reflected on UI. see below example where I have bound a bool value to ToggleButton state and ItemControl's ItemTemplate is changed accordingly rendering different visual.
Update: I designed a Device class that has device name and it's state to make a similar situation. And another class MyVisualStateManager to create a bindable property. Cause VisualStateManager class doesn't expose any property to bind directly. code is as below:
XMAL
<Window x:Class="WpfStackOverflowTempProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
xmlns:local="clr-namespace:WpfStackOverflowTempProject"
>
<ItemsControl ItemsSource="{Binding list}" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:UserControl1 DataContext="{Binding Name}" Width="200" BorderBrush="Black" BorderThickness="2" Padding="2">
<local:UserControl1.Style>
<Style TargetType="{x:Type local:UserControl1}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.DeviceState}" Value="0">
<Setter Property="local:MyVisualStateManager.VisualState" Value="State1" />
</DataTrigger>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext.DeviceState}" Value="1">
<Setter Property="local:MyVisualStateManager.VisualState" Value="State2" />
</DataTrigger>
</Style.Triggers>
</Style>
</local:UserControl1.Style>
</local:UserControl1>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
UserControl:
<UserControl x:Class="WpfStackOverflowTempProject.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Common">
<VisualState x:Name="State1">
<Storyboard>
<DoubleAnimation To="1" Duration="0:00:2" Storyboard.TargetName="State1Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
<DoubleAnimation To="0" Duration="0:00:3" Storyboard.TargetName="State2Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
</VisualState>
<VisualState x:Name="State2">
<Storyboard>
<DoubleAnimation To="0" Duration="0:00:3" Storyboard.TargetName="State1Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
<DoubleAnimation To="1" Duration="0:00:2" Storyboard.TargetName="State2Panel" Storyboard.TargetProperty="(UIElement.Opacity)" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Name="State2Panel" Background="Green" Opacity="0"/>
<Border Name="State1Panel" Background="Red" Opacity="1"/>
<TextBlock Text="{Binding Path=.}" Foreground="White" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
DataContext:
public partial class MainWindow : Window
{
public MainWindow()
{
list = new List<Device>();
list.Add(new Device() {Name="Device 1",DeviceState = 0 });
list.Add(new Device() { Name = "Device 2", DeviceState = 1 });
list.Add(new Device() { Name = "Device 3", DeviceState = 0 });
list.Add(new Device() { Name = "Device 4", DeviceState = 2 });
list.Add(new Device() { Name = "Device 5", DeviceState = 1 });
InitializeComponent();
}
public List<Device> list { get; set; }
}
public class Device : INotifyPropertyChanged
{
private string name;
public string Name
{
get { return name; }
set
{
name = value;
updateProperty("Name");
}
}
private int deviceState;
public int DeviceState
{
get { return deviceState; }
set
{
deviceState = value;
updateProperty("DeviceState");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void updateProperty(string name)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(name));
}
}
}
Helper Class: This class exposes an attached property VisualState that could be bound to any value in xaml.
public class MyVisualStateManager
{
public static string GetVisualState(DependencyObject obj)
{
return (string)obj.GetValue(VisualStateProperty);
}
public static void SetVisualState(DependencyObject obj, string value)
{
obj.SetValue(VisualStateProperty, value);
}
// Using a DependencyProperty as the backing store for VisualState. This enables animation, styling, binding, etc...
public static readonly DependencyProperty VisualStateProperty =
DependencyProperty.RegisterAttached("VisualState", typeof(string), typeof(MyVisualStateManager), new PropertyMetadata(new PropertyChangedCallback(VisualStateChanged)));
public static void VisualStateChanged(DependencyObject Do, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue != null)
{
string state = e.NewValue.ToString();
var control = Do as FrameworkElement;
VisualStateManager.GoToState(control, state, true);
}
}
}
Output
Different Item representing different devices and visual is changed on basis of theirDevicestateproperty which causes aTriggerto get executed inUserControl1.
Although Kylo's solution would provably work, the people at Microsoft already worked a code-free, 3-clicks-away solution for such a simple action to do.
Solution is on Behaviors, there is one behavior called GoToStateAction you have to add one of them to your control and there you can set your trigger (that can be set as DataTrigger). In my case I binded to a property of the type enum.
Then you can set the Comparison and the value (equals to "ReadyToUse")
Then as an outcome of the comparison you can trigger a state change for a particular object, you set your object, and you select the state from a nice combobox. There is even a checbox for using your transitions.
I mean in the user interface of Blend, where to click to get the conditional working,
In Blend look for the Objects and Timeline panel.
Select the control in your defined ItemTemplate (you have set one up right?) which you want to have the state changes. In Kylo's example above we would have selected the TextBox.
Right click and select Edit Style the either create a new style (most likely) or Edit a Copy which works of any existing inherited styles.
From there one can work on the properties tab to change specifics. Most likely you will be working directly in the xaml to do specific operations.
Even though these docs for Version 2, they still apply, if nothing else can give you an overview of Blend
Create a style resource
Style and template overview

WPF Change a property in a customcontrol after a button click

I posted a question in this link. maybe I'm not well expressed.
It's very simple, I want to change a property in a usercontrol or CustomControl after a click on a Boutton outside...
The code of the customcontrol is as follows :
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border x:Name="container" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Hidden" Value="true">
<Setter Property="BorderBrush" Value="Blue"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
public class CustomControl1 : Control
{
static CustomControl1()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1)));
}
public bool Hidden
{
get { return (bool)GetValue(HiddenProperty); }
set { SetValue(HiddenProperty, value); }
}
// Using a DependencyProperty as the backing store for Hidder. This enables animation, styling, binding, etc...
public static readonly DependencyProperty HiddenProperty =
DependencyProperty.Register("Hidden", typeof(bool), typeof(CustomControl1), new PropertyMetadata(false));
}
And a simple window for test
<Window x:Class="WpfTestCustomControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfCustomBorder;assembly=WpfCustomBorder"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="400"/>
<ColumnDefinition Width="70"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<local:CustomControl1 x:Name="cc" BorderBrush="Red" BorderThickness="3" Margin="10" Grid.RowSpan="2"/>
<Button Grid.Column="1" Content="Ok" Margin="5" Click="Button_Click"/>
</Grid>
namespace WpfTestCustomControl
{
/// <summary>
/// Logique d'interaction pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
cc.Hidden = true;
}
}
}
The property "Hidden" is a dependency property inside the custom control.
When i click on the button in mainwindow i want to change the hidden property to true. this must fire the trigger inside the custom control to change borderbrush to "blue" color. While nothing happen.
Is there something missing or is not the right way to do it ?
Thanks in advance..
Don't hard-set BorderBrush="Red" in your Control's declaration, it's prioritary over any trigger's setter.
You might want to check msdn's Dependency Property Value Precedence

Slider "animation" in WPF?

I have a button and a slider, when I press the button so do I want the slider to tick one step until it reach its maximum value.
However once I click the button, it sleeps a while and then shows the slider at the maximum value, without showing each tick. Why?
Here's my XAML code:
<StackPanel Orientation="Horizontal">
<Button x:Name="AnimationGoButton" Content="Go" />
<Slider x:Name="AnimationSlider" TickFrequency="1" TickPlacement="BottomRight" IsSnapToTickEnabled="True" Width="200" Maximum="20" Value="0" />
</StackPanel>
And here's my code behind:
Private Sub AnimationGoButton_Click(sender As System.Object, e As System.Windows.RoutedEventArgs) Handles AnimationGoButton.Click
While (Me.AnimationSlider.Value < Me.AnimationSlider.Maximum)
Me.AnimationSlider.Value += 1
System.Threading.Thread.Sleep(100)
End While
End Sub
I have tried to use a dynamic resource, but the result was the same.
XAML:
<Window.Resources>
<sys:Double x:Key="AnimationSliderValue">0</sys:Double>
</Window.Resources>
And then I changed the Value for the slider in XAML to:
Value="{DynamicResource AnimationSliderValue}"
And change the code behind to:
While (Me.AnimationSlider.Value < Me.AnimationSlider.Maximum)
Resources("AnimationSliderValue") += 1
System.Threading.Thread.Sleep(100)
End While
The result was the same. When I press the button the UI doesn't update until it has reached the Maximum value.
How do I create this "animation" I want for the slider?
You can use Storyboard for animations.
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="userControl">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<Storyboard x:Key="SlideUpAnimation">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(RangeBase.Value)" Storyboard.TargetName="slider1">
<EasingDoubleKeyFrame KeyTime="0:0:1" Value="10"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="SlideDownAnimation">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(RangeBase.Value)" Storyboard.TargetName="slider1">
<SplineDoubleKeyFrame KeyTime="0:0:1" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource SlideUpAnimation}"/>
</EventTrigger>
</Window.Triggers>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top">
<Button x:Name="btnSlideDown" Click="btnSlideDown_Click" Content="Slide Down" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Slider Height="23" x:Name="slider1" Width="100" />
<Button x:Name="btnSlideUp" Click="btnSlideUp_Click" HorizontalAlignment="Center" VerticalAlignment="Center" Content="Slide Up" />
</StackPanel>
And then start the storyboards on button clicks:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnSlideUp_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.BeginStoryboard((Storyboard)this.FindResource("SlideUpAnimation"));
}
private void btnSlideDown_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.BeginStoryboard((Storyboard)this.FindResource("SlideDownAnimation"));
}
}
Note: You need to add the PresentationFramework.dll in your project references in order to access the Storyboard class in code.
Update per comment below
You want to increment the Slider.Value by whole integers only using animations. Since the target value type is Double, the animation calculates and applies double values to the target, based on the animation frame rate. (The animation frame rate is 60 fps by default, but even if you did reduce it, that still may or may not give you even values depending on the beginning value). I don't know of any ways to tell the DoubleAnimation to use even values only. There exists an Int32Animation class but you cannot apply that to Slider.Value which is of type double.
Here's my hacky solution (which I don't quite like): Add a SliderIntValue (Int32) dependency property to the parent (e.g. MainWindow or maybe your viewmodel) and bind it to the Slider.Value using two-way binding. The Binding class will magically take care of the type conversion. Then apply the animations to the SliderIntValue instead of the slider itself:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" x:Name="userControl">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<Storyboard x:Key="SlideUpAnimation">
<Int32AnimationUsingKeyFrames Storyboard.TargetProperty="SliderIntValue" Storyboard.TargetName="userControl">
<EasingInt32KeyFrame KeyTime="0:0:1" Value="10"/>
</Int32AnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Key="SlideDownAnimation">
<Int32AnimationUsingKeyFrames Storyboard.TargetProperty="SliderIntValue" Storyboard.TargetName="userControl">
<EasingInt32KeyFrame KeyTime="0:0:1" Value="0"/>
</Int32AnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left" VerticalAlignment="Top">
<Button x:Name="btnSlideDown" Click="btnSlideDown_Click" Content="Slide Down" HorizontalAlignment="Center" VerticalAlignment="Center"/>
<Slider Height="23" x:Name="slider1" Width="100" IsSnapToTickEnabled="True" Value="{Binding SliderIntValue, ElementName=userControl, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<Button x:Name="btnSlideUp" Click="btnSlideUp_Click" HorizontalAlignment="Center" VerticalAlignment="Center" Content="Slide Up" />
<TextBox TextWrapping="Wrap" Text="{Binding Value, ElementName=slider1}" Margin="20,0,0,0"/>
</StackPanel>
And here's the dependency property added to the MainWindow class:
public partial class MainWindow : Window
{
public static readonly DependencyProperty SliderIntValueProperty = DependencyProperty.Register("SliderIntValue",
typeof(int), typeof(MainWindow));
public MainWindow()
{
InitializeComponent();
}
private void btnSlideUp_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.BeginStoryboard((Storyboard)this.FindResource("SlideUpAnimation"));
}
private void btnSlideDown_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.BeginStoryboard((Storyboard)this.FindResource("SlideDownAnimation"));
}
}

In WP7 + Silverlight how can I change the Visual State of an ListBox Item?

I need to change the visual state of my listbox item. Here is the DataTemplate which has the visual states. I'm using WP7 as my environment.
<DataTemplate x:Key="MessageItemTemplate">
<Grid MinWidth="200" MinHeight="90" Width="460" Margin="0,2">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="Modes">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0" To="Normal">
<Storyboard>
<DoubleAnimation Duration="0:0:0.4" To="0" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border" d:IsOptimized="True"/>
</Storyboard>
</VisualTransition>
<VisualTransition GeneratedDuration="0"/>
</VisualStateGroup.Transitions>
<VisualState x:Name="Normal"/>
<VisualState x:Name="Edit">
<Storyboard>
<DoubleAnimation Duration="0:0:0.7" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="border" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<VisualStateManager.CustomVisualStateManager>
<ic:ExtendedVisualStateManager/>
</VisualStateManager.CustomVisualStateManager>
<StackPanel Orientation="Vertical" d:LayoutOverrides="Width, Height" Canvas.ZIndex="10" Margin="7">
<TextBlock x:Name="tbTitle" Text="{Binding Path=Title, Mode=OneWay}" FontSize="24" VerticalAlignment="Top" Foreground="{StaticResource PhoneContrastBackgroundBrush}" FontWeight="Bold" Height="30" FontFamily="Microsoft New Tai Lue"/>
<TextBlock x:Name="tbMessage" Text="{Binding Path=Message, Mode=OneWay}" FontSize="29.333" Foreground="{StaticResource PhoneContrastBackgroundBrush}" Margin="0" FontFamily="Candara" TextWrapping="Wrap" HorizontalAlignment="Left"/>
</StackPanel>
<Border BorderBrush="{StaticResource PhoneAccentBrush}" BorderThickness="2" Background="{StaticResource PhoneBackgroundBrush}" CornerRadius="10" />
<Border x:Name="border" BorderThickness="4" CornerRadius="4" BorderBrush="#FFED1212" Opacity="0" >
<Grid>
<Path Data="M149,0.16666667 L192,36.166332 L189.60141,-2.7298894 z" Fill="#FFED1212" HorizontalAlignment="Right" Margin="0,-3.031,-2.784,38.328" Stretch="Fill" UseLayoutRounding="False" Width="51.629" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<CompositeTransform Rotation="2.523" TranslateX="-0.076551587038494961" TranslateY="-0.0016857129841283403"/>
</Path.RenderTransform>
</Path>
<Image Margin="0" Source="images/pensil.png" Stretch="Fill" Height="26" Width="26" HorizontalAlignment="Right" VerticalAlignment="Top"/>
</Grid>
</Border>
</Grid>
</DataTemplate>
Heres my ListBox:
<ListBox x:Name="SmsMessagesList" Grid.Row="1"
ItemsSource="{Binding Path=Model.Messages}"
SelectionChanged="SmsMessagesList_SelectionChanged"
ItemTemplate="{StaticResource MessageItemTemplate}">
</ListBox>
The ObservableCollection which I bind to this ListBox's ItemsSource is:
public ObservableCollection<SmsMessage> Messages;
public class SmsMessage : EntityBase
{
private string _CurrentState;
public string CurrentState
{
get
{
return _CurrentState;
}
set
{
_CurrentState = value;
PropertyChangedHandler("CurrentState");
}
}
private string _Title;
public string Title
{
get
{
return _Title;
}
set
{
_Title = value;
PropertyChangedHandler("Title");
}
}
private string _Message;
public string Message
{
get
{
return _Message;
}
set
{
_Message = value;
PropertyChangedHandler("Message");
}
}
}
How can I change the visual state of my ListBox to 'Edit' and 'Normal' based on the property 'CurrentState' changing?
Thanks
If you want to stick to a binding approach, your only real choice is a Blend Behavior. However, since Silverlight 3 (and thus WP7) doesn't support data bound behavior properties, your path is a lot more complicated. Yes, it's a PITA and yes, I hope they'll be announcing SL4 features at MIX next week.
Below is a WPF behavior that does the same thing, to give you an idea of what is required by the behavior but it won't work in Silverlight 3 / WP7 due to the above problem. You'll need to change the State property to be of type Binding and go through the convoluted process of getting access to that binding value. You can see examples of how to do this in TailSpin.PhoneClient.Infrastructure.ButtonCommand of the Patterns & Practices WP7 Dev Guide source or from MVVM Light's EventToCommand.
public class StateManagementBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty StateProperty =
DependencyProperty.Register("State", typeof(string),
typeof(StateManagementBehavior),
new UIPropertyMetadata(null, PropertyChangedCallback));
public static readonly DependencyProperty UseTransitionsProperty =
DependencyProperty.Register("UseTransitions", typeof(bool),
typeof(StateManagementBehavior),
new UIPropertyMetadata(true));
public static void PropertyChangedCallback(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var stateManagementBehavior = (StateManagementBehavior)d;
stateManagementBehavior.GoToState();
}
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.Loaded += (s, e) => GoToState();
}
private void GoToState()
{
if (AssociatedObject == null || State == null) return;
VisualStateManager.GoToState(AssociatedObject, State, UseTransitions);
}
public string State
{
get { return (string)GetValue(StateProperty); }
set { SetValue(StateProperty, value); }
}
public bool UseTransitions
{
get { return (bool)GetValue(UseTransitionsProperty); }
set { SetValue(UseTransitionsProperty, value); }
}
}
Assuming you get it all working, you'll use the behavior like this:
<DataTemplate x:Key="MessageItemTemplate">
<Grid MinWidth="200" MinHeight="90" Width="460" Margin="0,2">
<i:Interactivity.Behaviors>
<infrastructure:StateManagementBehavior State="{Binding CurrentState}"
UseTransitions="True" />
</i:Interactivity.Behaviors>
<VisualStateManager.VisualStateGroups>
...
</VisualStateManager.VisualStateGroups>
...
</Grid>
</DataTemplate>
If you provide a control to act as the container for your listbox items. You can then add the logic for changing state to the code for that control using VisualStateManage.GoToState(this, "Your State", true);
Just managed to get this happening on the SL4 project I'm working (so not sure if it's going to work on WP7, but the original library was made for SL3 so it should), the solution was to use DataStateBehavior from the Expression Blend Samples on CodePlex inside the DataTemplate:
<i:Interaction.Behaviors>
<ei:DataStateBehavior Binding="{Binding IsEditMode}" Value="True" TrueState="Edit" FalseState="Normal"/>
</i:Interaction.Behaviors>
If you need more than 2 states, you can also use DataStateSwitchBehavior.

WPF Popup event handling - How to get triggered when Popup opens

I created a WPF Popup which contains a grid with border.
There is some animation associated with the border which I want to be triggered every time the Popup opens.
Currently the code is like this
<Popup x:Name="myPopUp" >
<Border x:Name="myBorder" >
<Border.Triggers>
<EventTrigger RoutedEvent="Popup.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myBorder"
Storyboard.TargetProperty="Height"
From="10" To="255" Duration="0:0:0.20" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Border.Triggers>
<Grid />
</Border>
</Popup>
As per the code the border shows up the animation for the first time the popup opens.
What change do I need to make to trigger the border animation every time the Popup opens?
As per suggestions given here and a little bit expireince now (I asked this a year back :) ), I could figure out the solution.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" >
<Window.Resources>
<Style x:Key="popupStyle" TargetType="{x:Type Popup}" >
<Style.Triggers>
<Trigger Property="IsOpen" Value="True">
<Trigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetProperty="Height"
From="10" To="255" Duration="0:0:0.20" />
</Storyboard>
</BeginStoryboard>
</Trigger.EnterActions>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<Button Width="100" Height="100" Click="Button_Click"></Button>
<Popup Name="popUp" Width="100" Height="100" Style="{StaticResource popupStyle}" >
<Border x:Name="myBorder" Background="Blue"/>
</Popup>
</Grid>
and a sample code behind to trigger the popup..
private void Button_Click(object sender, RoutedEventArgs e)
{
popUp.PlacementTarget = (Button)sender;
popUp.IsOpen = true;
}
Although I can only animate the Popup and not the Border here, it pretty much gives the same result.
I'm not sure if the popup gets focus when it opens, but you could use the GotFocus event if it does. Alternatively, you could try using a datatrigger on the is IsOpen property. I think you'd have to put that in a style though instead of inline.
You can achieve this by listening to the IsOpen dependency property like
public MainWindow()
{
InitializeComponent();
//// Listening to the IsOpen dependency property of the Popup.
this.SetBinding(PopupIsOpenProperty, new Binding() { Source = this.popupContainer, Path = new PropertyPath("IsOpen") });
}
/// <summary>
/// Gets or sets a value indicating whether [popup is open].
/// </summary>
/// <value><c>true</c> if [popup is open]; otherwise, <c>false</c>.</value>
public bool PopupIsOpen
{
get { return (bool)GetValue(PopupIsOpenProperty); }
set { SetValue(PopupIsOpenProperty, value); }
}
// Using a DependencyProperty as the backing store for PopupIsOpen. This enables animation, styling, binding, etc...
public static readonly DependencyProperty PopupIsOpenProperty =
DependencyProperty.Register("PopupIsOpen", typeof(bool), typeof(MainWindow), new PropertyMetadata(false,
(dependencyObject, e) =>
{
var mainWindow = (MainWindow)dependencyObject;
if (mainWindow != null &&
(bool)e.NewValue == true)
{
//// Raise your event here... like
//// mainWindow.RaisePopupOpened();
System.Diagnostics.Debug.WriteLine("Popup Open Triggered");
}
}));
private void button_MouseLeave(object sender, MouseEventArgs e)
{
this.popupContainer.IsOpen = false;
}
private void button_MouseMove(object sender, MouseEventArgs e)
{
//// Setting the popup position
var p = e.GetPosition(sender as UIElement);
this.popupContainer.HorizontalOffset = p.X;
this.popupContainer.VerticalOffset = p.Y;
//// Enabling popup when it is hover on the button
this.popupContainer.IsOpen = true;
}
<!-- XAML Starts here-->
<Grid>
<Button x:Name="button1" Content="This is a sample text" MouseMove="button_MouseMove" MouseLeave="button_MouseLeave" Width="100" Height="25" />
<Popup x:Name="popupContainer" IsHitTestVisible="False" >
<Grid Background="White">
<TextBlock Text="{Binding Content, ElementName=button}" />
</Grid>
</Popup>
</Grid>
HTH
In App.xaml.cs or in another starting class instance you need add:
var field = typeof(PresentationSource).GetField("RootSourceProperty", BindingFlags.NonPublic | BindingFlags.Static);
var property = (DependencyProperty)field.GetValue(null);
property.OverrideMetadata(typeof(DependencyObject), new FrameworkPropertyMetadata(property.DefaultMetadata.DefaultValue, OnHwndSourceChanged));
Where, RootSourceProperty is private field DependecyProperty of PresentationSource. Its property use when HwndSource is created and set RootVisual. So you need just use property changed call back of RootSourceProperty:
private static void OnHwndSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
}
This is nice because, you can use it in your all Application and for all HwndSource (Popup, Window or Custom controls, where you are using HwndSource)
try changing your event trigger to
<EventTrigger RoutedEvent="Popup.Opened">

Resources