Visual State Manager or rather Behavior ? (part 2) 19 June 2011 Michael-Van-Wesemael Silverlight, Visual State Manager, Windows Phone In part 1 I set up the service and the WP7-application. The WP7-application will poll the service regularly for asking the distance to the closest friend. Let's now finish the WP7-app so we can show the distance by a little indicator. When closer than 2 (km? miles ? light-years ? You choose) the indicator will show green, less then 5 will be orange, and all the rest is red. Let's first create an indicator using Visual State Manager. In VSM we will define different states. In code we define the state our control is in, and in XAML we define how a state should look like. Let's create our control: 1: [TemplatePart(Name="Core",Type=typeof(FrameworkElement))] 2: [TemplateVisualState(Name="Far",GroupName="DistanceStates")] 3: [TemplateVisualState(Name = "Close", GroupName = "DistanceStates")] 4: [TemplateVisualState(Name = "VeryClose", GroupName = "DistanceStates")] 5: public class DistanceIndicator: Button 6: { 7: Ellipse corePart; 8: 9: private Ellipse CorePart 10: { 11: get { return corePart; } 12: set { corePart = value; } 13: } 14: 15: public override void OnApplyTemplate() 16: { 17: base.OnApplyTemplate(); 18: 19: CorePart = (Ellipse) GetTemplateChild("Core"); 20: VisualStateManager.GoToState(this, "Far", true); 21: DistanceReader.DistanceChanged += 22: new EventHandler<DistanceEventArgs>(DistanceReader_DistanceChanged); 23: } 24: 25: void DistanceReader_DistanceChanged(object sender, DistanceEventArgs e) 26: { 27: if (e.Distance>5) 28: { 29: VisualStateManager.GoToState(this,"Far",true); 30: } 31: else if (e.Distance > 2) 32: { 33: VisualStateManager.GoToState(this,"Close",true); 34: } 35: else 36: { 37: VisualStateManager.GoToState(this, "VeryClose", true); 38: } 39: } 40: } .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } The code-attributes already shows you the different states my Indicator can have : Far, Close and VeryClose (OutOfInspirationException occurred). I add the controltemplate to App.xaml, describing how my three states should look like : 1: <ControlTemplate x:Key="u2uCtrl" TargetType="my:DistanceIndicator"> 2: <Ellipse x:Name="ellipse" Height="{TemplateBinding Height}" Fill="Yellow"> 3: <VisualStateManager.VisualStateGroups> 4: <VisualStateGroup x:Name="DistanceStates"> 5: <VisualState x:Name="Close"> 6: <Storyboard> 7: <ColorAnimation Duration="0" To="Orange" 8: Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 9: Storyboard.TargetName="ellipse" d:IsOptimized="True"/> 10: </Storyboard> 11: </VisualState> 12: <VisualState x:Name="VeryClose"> 13: <Storyboard> 14: <ColorAnimation Duration="0" To="Green" 15: Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 16: Storyboard.TargetName="ellipse" d:IsOptimized="True"/> 17: </Storyboard> 18: </VisualState> 19: <VisualState x:Name="Far"> 20: <Storyboard> 21: <ColorAnimation Duration="0" To="Red" 22: Storyboard.TargetProperty="(Shape.Fill).(SolidColorBrush.Color)" 23: Storyboard.TargetName="ellipse" d:IsOptimized="True"/> 24: </Storyboard> 25: </VisualState> 26: </VisualStateGroup> 27: </VisualStateManager.VisualStateGroups> 28: </Ellipse> 29: </ControlTemplate> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }I cheated, of course: I used blend for creating this template. Nevertheless, I add my control to my MainPage: <my:DistanceIndicator Content="" x:Name="distanceIndicator1" Template="{StaticResource u2uCtrl}" /> .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: consolas, "Courier New", courier, monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; }That's it. Open the WPF-application, change the slider. Run the WP7-application and there you go. Play with the slider, and the WP7-app responds visually (after some time). Nice stuff, but it get's nicer with Behavior. That's for part 3. Tweet
Visual State Manager or rather Behavior(part 1) 16 June 2011 Michael-Van-Wesemael Silverlight, Visual State Manager, Windows Phone During an interesting event last week, discussing the virtues of Silverlight apps out of the browser, I was confronted with Behavior. I should probably be ashamed because I never heard of them before. Because they have a high coolness- and awesomeness-level I checked if they were also available for Windows Phone, and hooray, they are! Now what are they about ? In WPF you can use Triggers, to react on certain "events", like this one : <Button.Triggers> <EventTrigger RoutedEvent="Click"> <BeginStoryboard Storyboard="{StaticResource mySB}"/> </EventTrigger> </Button.Triggers> Specially useful when you're creating your own Controltemplates. Unfortunately they're not available in Silverlight (except for the Loaded-event). When creating your own controls, and you need them to have some kind of visual feedback, we'll have to use the Visual State Manager. In my little example I have a WP7-app that regularly calls a "Distance-service" that gives the shortest distance between you and your geographically closest friend (could be something Foursquare provides). The closest distance will be shown by some small colored ellipse. Whenever the distance changes, the color changes. Let's start by the service: I have a little WCF library that I'm hosting in a WPF-app. Service looks like this: public class DistanceService : IDistanceService { public double GetDistance() { return DistanceHelper.Distance; } } Can't make them smaller than this. What about this DistanceHelper ? public static class DistanceHelper { private static double distance=10; public static double Distance { get { return distance; } set { distance = value; } } } My MainWindow only has a slider going from 0 to 10, with an eventhandler for the ValueChanged. In this eventhandler I simply set the DIstance-property from DIstanceHelper: 1: private void distanceSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) 2: { 3: DistanceHelper.Distance = distanceSlider.Value; 4: } 5: 6: private void Window_Loaded(object sender, RoutedEventArgs e) 7: { 8: host = new ServiceHost(typeof(DistanceServiceLib.DistanceService)); 9: host.Open(); 10: } 11: 12: private void Window_Unloaded(object sender, RoutedEventArgs e) 13: { 14: host.Close(); 15: } I also configured my WCF-service to use simple basicHttpBinding. In my WP7-app I start by creating a service reference towards my service. This will be used by a helper-class DistanceReader: 1: public static class DistanceReader 2: { 3: static DistanceReader() 4: { 5: DispatcherTimer timer = new DispatcherTimer(); 6: timer.Interval = new TimeSpan(0, 0, 10); 7: timer.Tick += (s, ea) => 8: { 9: var proxy = new DistanceServiceRef.DistanceServiceClient(); 10: proxy.GetDistanceCompleted+=(s2,ea2)=> 11: { 12: DistanceReader.LastDistance = ea2.Result; 13: }; 14: proxy.GetDistanceAsync(); 15: }; 16: timer.Start(); 17: } 18: 19: public static event EventHandler<DistanceEventArgs> DistanceChanged; 20: 21: public static void OnDistanceChanged(DistanceEventArgs e) 22: { 23: if (DistanceChanged!=null) 24: { 25: DistanceChanged(typeof(DistanceReader), e); 26: } 27: } 28: 29: private static double lastDistance; 30: public static double LastDistance 31: { 32: get { return lastDistance;} 33: set 34: { 35: if (lastDistance!=value) 36: { 37: lastDistance = value; 38: OnDistanceChanged(new DistanceEventArgs() { Distance=value}); 39: } 40: } 41: } 42: } This class calls the service (every 10 seconds) and raises an event whenever the distance changes. Now, how to show this ? Let's start by the VSM-approach. But that is something I'll leave for part 2. Tweet