I ran into a problem binding a localized string to a Silverlight 4 DataGrid Column Header, it turns out the Header property on a DataGridTextColumn isn't a dependency property and so it doesn't support binding. This behavior will let you bind a value to the header.
Monday, March 7, 2011
DataGrid Column Header Binding Behavior
Thursday, February 24, 2011
CheckedItemCollection for Silverlight and WPF CheckBox List or RadioButton List
Saturday, February 12, 2011
Silverlight Fade Control Behavior - FadeyBehavior
<ItemsControl
Margin="0 130 0 0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Opacity="0.0"
ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<local:BooleanPropertyTrigger
Binding="{Binding FinishedLoading}"
TriggerValue="True">
<local:StoryboardAction>
<Storyboard>
<DoubleAnimation
To="1.0"
Duration="00:00:0.7"
Storyboard.TargetProperty="Opacity" />
</Storyboard>
</local:StoryboardAction>
</local:BooleanPropertyTrigger>
<local:BooleanPropertyTrigger
Binding="{Binding FinishedLoading}"
TriggerValue="False">
<local:StoryboardAction>
<Storyboard>
<DoubleAnimation
To="0.0"
Duration="00:00:0.4"
Storyboard.TargetProperty="Opacity" />
</Storyboard>
</local:StoryboardAction>
</local:BooleanPropertyTrigger>
</i:Interaction.Triggers>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Height="30"
Width="200"
Margin="0 2"
BorderBrush="Plum"
BorderThickness="3"
CornerRadius="5">
<TextBlock
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5 0 0 0"
Text="{Binding Name}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Now, the more I looked at all that XAML the more I thought we could make this a lot simpler. By the end of this post we will hopefully end up having something much simpler to use; like this;
<ItemsControl
Margin="0 130 0 0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Opacity="0.0"
ItemsSource="{Binding Items}">
<i:Interaction.Behaviors>
<local:FadeyBehavior
Binding="{Binding FinishedLoading}" />
</i:Interaction.Behaviors>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Height="30"
Width="200"
Margin="0 2"
BorderBrush="Plum"
BorderThickness="3"
CornerRadius="5">
<TextBlock
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5 0 0 0"
Text="{Binding Name}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
So, what we've done is encompassed our two triggers and their storyboards into a new Behavior. Our new FadeyBehavior creates and attaches our to FadeIn and FadeOut triggers along with the associated FadeInAction and FadeOutAction. The secret sauce is in cascading the binding down to the triggers in the code behind.
using System.Windows;
using System.Windows.Data;
using System.Windows.Interactivity;
/// <summary>
/// A behavior for fading an element based on whether a bound property is true or false.
/// </summary>
public class FadeyBehavior : Behavior<FrameworkElement>
{
/// <summary>
/// The <see cref="Binding" /> dependency property's name.
/// </summary>
public const string BindingPropertyName = "Binding";
/// <summary>
/// Gets or sets the value of the <see cref="Binding" />
/// property. This is a dependency property.
/// </summary>
public object Binding
{
get
{
return (object)GetValue(BindingProperty);
}
set
{
SetValue(BindingProperty, value);
}
}
/// <summary>
/// Identifies the <see cref="Binding" /> dependency property.
/// </summary>
public static readonly DependencyProperty BindingProperty = DependencyProperty.Register(
BindingPropertyName,
typeof(object),
typeof(FadeyBehavior),
new PropertyMetadata(null));
protected override void OnAttached()
{
if (AssociatedObject.Opacity != 0.0)
AssociatedObject.Opacity = 0.0;
// Create our fade in/out triggers
var triggerIn = new FadeInTrigger();
var triggerOut = new FadeOutTrigger();
// Bind the Binding property in this behavior to the underlying triggers.
var b = new Binding("Binding") { Source = this };
BindingOperations.SetBinding(triggerIn, BooleanPropertyTrigger.BindingProperty, b);
BindingOperations.SetBinding(triggerOut, BooleanPropertyTrigger.BindingProperty, b);
// Add our triggers to the associated object.
var currTriggers = Interaction.GetTriggers(AssociatedObject);
currTriggers.Add(triggerIn);
currTriggers.Add(triggerOut);
base.OnAttached();
}
protected override void OnDetaching()
{
base.OnDetaching();
}
}
For the time being, I've only had to use this for a boolean property trigger, but it could be expanded into a base class for other trigger types (String, DateTime range, WidgetA, etc.).
You can download the code to mess around with it yourself.
Now Playing - La Roux - In For The Kill
Friday, February 11, 2011
Silverlight Storyboard and FadeIn FadeOut Actions
<ItemsControl
Margin="0 130 0 0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Opacity="0.0"
ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<local:BooleanPropertyTrigger
Binding="{Binding FinishedLoading}"
TriggerValue="True">
<local:StoryboardAction>
<Storyboard>
<DoubleAnimation
To="1.0"
Duration="00:00:0.7"
Storyboard.TargetProperty="Opacity" />
</Storyboard>
</local:StoryboardAction>
</local:BooleanPropertyTrigger>
<local:BooleanPropertyTrigger
Binding="{Binding FinishedLoading}"
TriggerValue="False">
<local:StoryboardAction>
<Storyboard>
<DoubleAnimation
To="0.0"
Duration="00:00:0.4"
Storyboard.TargetProperty="Opacity" />
</Storyboard>
</local:StoryboardAction>
</local:BooleanPropertyTrigger>
</i:Interaction.Triggers>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Height="30"
Width="200"
Margin="0 2"
BorderBrush="Plum"
BorderThickness="3"
CornerRadius="5">
<TextBlock
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5 0 0 0"
Text="{Binding Name}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Today, we added another trigger to fade the items out when we re-load our items. Then, we are going to encapsulate the fade animations in a new action that will reduce our XAML a little bit.
First, lets take a quick look at the StoryboardAction Source.
using System;
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Markup;
using System.Windows.Media.Animation;
[ContentProperty("Story")]
public class StoryboardAction : TriggerAction<FrameworkElement>
{
/// <summary>
/// Holder for a target setting state.
/// </summary>
private bool hasTargetSet = false;
/// <summary>
/// Gets or sets the storyboard for this action.
/// </summary>
/// <value>The storyboard to run when invoked.</value>
public Storyboard Story { get; set; }
/// <summary>
/// The <see cref="Target" /> dependency property's name.
/// </summary>
public const string TargetPropertyName = "Target";
/// <summary>
/// Gets or sets the value of the <see cref="Target" />
/// property. This is a dependency property.
/// You can leave this un-set if you want to use the Trigger's
/// AssociatedObject as the Target.
/// </summary>
public DependencyObject Target
{
get
{
return (DependencyObject)GetValue(TargetProperty);
}
set
{
SetValue(TargetProperty, value);
}
}
/// <summary>
/// Identifies the <see cref="Target" /> dependency property.
/// </summary>
public static readonly DependencyProperty TargetProperty = DependencyProperty.Register(
TargetPropertyName,
typeof(DependencyObject),
typeof(StoryboardAction),
new PropertyMetadata(null));
/// <summary>
/// Invokes the action.
/// </summary>
/// <param name="parameter">The parameter to the action. If the action does not require a parameter, the parameter may be set to a null reference.</param>
protected override void Invoke(object parameter)
{
if (this.Story == null)
return;
if(!hasTargetSet)
{
// Fall back to the associated object if no target defined.
var target = this.Target ?? AssociatedObject;
if (target != null)
{
Storyboard.SetTarget(this.Story, target);
hasTargetSet = true;
}
}
// Stop any previously ran storyboards.
if (this.Story.GetCurrentState() != ClockState.Stopped)
this.Story.Stop();
this.Story.Begin();
}
}
The important parts of this class are the Invoke logic. We are doing some basic null checking, then setting the target for our animation to either the Target or the AssociatedObject if no Target was found. Finally, we make sure the animation is not currently running, then start up our animation.
Extra Snazzy Bonus Implementation Codez
I feel like we are really connecting here, so I'm gonna throw in some extra snazzy bonus material just for you. Here are some short hand versions of our Opacity Fade Animations that should tame the XAML Monster.
using System;
using System.Windows;
using System.Windows.Media.Animation;
public class FadeOutAction : FadeAction
{
public FadeOutAction()
: base(.2, 0.0)
{ }
}
public class FadeInAction : FadeAction
{
public FadeInAction()
: base(.6, 1.0)
{ }
}
public class FadeAction : StoryboardAction
{
public FadeAction(double durationSeconds = .6, double fadeTo = 0.0)
{
var anim = new DoubleAnimation { Duration = new Duration(TimeSpan.FromSeconds(durationSeconds)), To = fadeTo };
Storyboard.SetTargetProperty(anim, new PropertyPath("Opacity"));
this.Story = new Storyboard();
this.Story.Children.Add(anim);
}
}
Now our XAML is looking slightly better.
<ItemsControl
Margin="0 130 0 0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Opacity="0.0"
ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<local:BooleanPropertyTrigger
Binding="{Binding FinishedLoading}"
TriggerValue="True">
<local:FadeInAction />
</local:BooleanPropertyTrigger>
<local:BooleanPropertyTrigger
Binding="{Binding FinishedLoading}"
TriggerValue="False">
<local:FadeOutAction />
</local:BooleanPropertyTrigger>
</i:Interaction.Triggers>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border
Height="30"
Width="200"
Margin="0 2"
BorderBrush="Plum"
BorderThickness="3"
CornerRadius="5">
<TextBlock
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="5 0 0 0"
Text="{Binding Name}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Next time, we will create a new FadeyBehavior to encapsulate our fading triggers.
Now Playing - Jay Z, Rick Ross - Hustlin Remix
Wednesday, February 9, 2011
Silverlight 4 Property Triggers
Triggers and Behaviors are really just ways to attach functionality to an existing element, and the base classes that are included in the newer version of Silverlight 4 really make the job easier. I'm going to walk through adding a trigger that fires when one of the properties on my ViewModel changes to true. Now allegedly there is an existing trigger (DataStoreChangedTrigger) that will fire actions based on when a bound property changes, but I want to only fire my actions when my bound property becomes a specific value.
Our Goal
<ItemsControl
Margin="0 130 0 0"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Opacity="0.0"
ItemsSource="{Binding Items}">
<i:Interaction.Triggers>
<local:BooleanPropertyTrigger
Binding="{Binding FinishedLoading}"
TriggerValue="True">
<local:StoryboardAction>
<Storyboard>
<DoubleAnimation
To="1.0"
Duration="00:00:0.7"
Storyboard.TargetProperty="Opacity" />
</Storyboard>
</local:StoryboardAction>
</local:BooleanPropertyTrigger>
</i:Interaction.Triggers>
</ItemsControl>
The Codez
[Download the PropertyTrigger Example Source Project and play along at home]
To start out with, I create a base PropertyChangedTrigger class that will do most of the heavy lifting for us. Essentially, we want to inherit from the TriggerBase<...> generic base class and specify that we want our Trigger to attach to a FrameworkElement (I suppose you could use another type of control class, but FrameworkElement will encompass just about any element with a DataContext, which I find useful). Our PropertyChangedTrigger will expose a Binding property that will allow us to attach an event handler when our bound property changes so we can invoke our TriggerActions.
/// <summary>
/// A base property changed trigger that
/// fires whenever the bound property changes.
/// </summary>
public class PropertyChangedTrigger : TriggerBase<FrameworkElement>
{
/// <summary>
/// The <see cref="Binding" /> dependency property's name.
/// </summary>
public const string BindingPropertyName = "Binding";
/// <summary>
/// Gets or sets the value of the <see cref="Binding" />
/// property. This is a dependency property.
/// </summary>
public object Binding
{
get
{
return (object)GetValue(BindingProperty);
}
set
{
SetValue(BindingProperty, value);
}
}
/// <summary>
/// Identifies the <see cref="Binding" /> dependency property.
/// </summary>
public static readonly DependencyProperty BindingProperty =
DependencyProperty.Register(
BindingPropertyName,
typeof(object),
typeof(PropertyChangedTrigger),
new PropertyMetadata(null,
new PropertyChangedCallback(Binding_ValueChanged)));
/// <summary>
/// Called after the trigger is attached to an AssociatedObject.
/// </summary>
protected override void OnAttached()
{
base.OnAttached();
}
/// <summary>
/// Called when the trigger is being detached
/// from its AssociatedObject,
/// but before it has actually occurred.
/// </summary>
protected override void OnDetaching()
{
base.OnDetaching();
}
/// <summary>
/// Occurs when Binding's value changes.
/// </summary>
/// <param name="obj">The obj on which the binding changed.</param>
/// <param name="args">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
private static void Binding_ValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
var trig = obj as PropertyChangedTrigger;
if (trig != null && trig.ShouldTriggerFire(args.NewValue))
{
trig.OnPropertyTrigger(args.NewValue);
}
}
/// <summary>
/// Does the change logic test. By default, it will always fire on value change.
/// </summary>
/// <param name="newValue">The new value.</param>
/// <returns>True if the trigger should fire, otherwise false.</returns>
protected virtual bool ShouldTriggerFire(object newValue)
{
return true;
}
/// <summary>
/// Called when [property trigger].
/// </summary>
/// <param name="value">The value of the property.</param>
protected virtual void OnPropertyTrigger(object value)
{
base.InvokeActions(value);
}
}
As you can tell, our PropertyChangedTrigger makes use of a virtual method ShouldTriggerFire(…) that will default to just fire everytime a property changes value. Next, we will override our base class to create an EqualsPropertyTrigger that only fires when the value changes to a specific one that we want.
/// <summary>
/// A base class for property triggers that must be equal to fire.
/// </summary>
/// <typeparam name="TValue">The type of the trigger value.</typeparam>
/// <summary>
/// A base class for property triggers that must be equal to fire.
/// </summary>
/// <typeparam name="TValue">The type of the trigger value.</typeparam>
public class EqualsPropertyTrigger<TValue> : PropertyChangedTrigger
{
/// <summary>
/// Gets or sets the trigger value to match the property value for.
/// </summary>
/// <value>The trigger value.</value>
public TValue TriggerValue { get; set; }
/// <summary>
/// Logic to check whether the trigger should fire.
/// </summary>
/// <param name="newValue">The new value.</param>
/// <returns>True if the trigger should fire, otherwise false.</returns>
protected override bool ShouldTriggerFire(object newValue)
{
if (newValue == null)
return this.TriggerValue == null;
return newValue.Equals(this.TriggerValue);
}
}
So now we have a nice base class for our BooleanPropertyTrigger that makes it's implementation really nice and clean.
/// <summary>
/// A generic object property trigger
/// </summary>
public class PropertyTrigger : EqualsPropertyTrigger<object>
{ }
/// <summary>
/// A Boolean value property trigger
/// </summary>
public class BooleanPropertyTrigger : EqualsPropertyTrigger<bool>
{ }
/// <summary>
/// A string property value trigger
/// </summary>
public class StringPropertyTrigger : EqualsPropertyTrigger<string>
{ }
Now, all that's left to do is hook it up in our XAML by adding the namespace to our trigger and making sure we have a reference to System.Windows.Interactivity (version 4.0.5.0).
Next Steps
Next, we could make a NotEqualsPropertyTrigger that fires when a value is not a certain value. It's implementation would be as easy as inheriting from the EqualsPropertyTrigger and negating the base ShouldFireTrigger(...) method.
Hopefully, like me, you've learned a little about triggers and how they can be useful. For my next blog post I'm going to incorporate the visual state manager and make a GoToStateAction along with talking a little bit about creating the StoryBoardAction you see in the example.
Now Playing - Pretty Lights - Hot Like Sauce
Sunday, August 29, 2010
Show Custom In-House Ads in Win Phone 7 with MoAds
Just want the code? Get it here
There is also the sort of unsettling issue of Microsoft's Developer agreement being sort of fuzzy on advertising services (as pointed out by this MobileTechWorld article about MoAds).
At any rate, here's a brief run down of creating your own custom ad service using a new MVC 2 Website and some JSON services.
The Web Services Site - .Net MVC 2
In the example provided, I've created a new .Net MVC 2 website and added an AdController. The AdController is responsible for handling our AdRequests. In this case, we add two methods; Fetch and Click. The Fetch will be responsible for returning the Ad information (ImageUrl, AdText and ClickUrl) and the Click will redirect to the actual Ad Url after recording the client click.public ActionResult Fetch(string pubId, string clientId = "")
{
// TODO: You'd probably want to keep track of fetches by PubId for analytics, but this is just a demo.
var ad = AdRepository.GetRandomAd();
Impression impression;
var clickUrl = GenerateClickUrl(ad, out impression);
ImpressionRepository.Impressions.Add(impression);
return new JsonResult()
{
JsonRequestBehavior = JsonRequestBehavior.AllowGet,
Data = new FetchResponse()
{
ClickUrl = clickUrl,
AdText = ad.AdText,
ImageUrl = ad.ImageUrl
}
};
}
public ActionResult Click(string id)
{
// TODO: Track the click for analytics and what-not.
var impression = ImpressionRepository.Impressions.FirstOrDefault(x => x.Token == id);
if (impression == null)
return View(); // TODO: return not found result.
var ad = AdRepository.GetAdById(impression.AdId);
return new RedirectResult(ad.AdUrl);
}
Creating a Custom Ad Adapter
The Custom Ad Adapter generally has an underlying service component (in our example it's called CustomAdService) that calls out to some service and gets some ad information, massages the response and fires our GotAdResponse event. Here's a brief run down of what our CustomAdAdapter class looks like.
public class CustomAdAdapter : IAdAdapter
{
private CustomAdService adService = new CustomAdService();
/// <summary>
/// Gets or sets the publisher id.
/// </summary>
/// <value>The publisher id.</value>
public string PublisherId { get; set; }
/// <summary>
/// Gets or sets the client id.
/// </summary>
/// <value>The client id.</value>
public string ClientId { get; set; }
public CustomAdAdapter()
{
adService.GotResult += new EventHandler<Service4u2.Common.EventArgs<CustomAdFetchResponse>>(adService_GotResult);
adService.GotError += new EventHandler<Service4u2.Common.EventArgs<Exception>>(adService_GotError);
}
void adService_GotError(object sender, Service4u2.Common.EventArgs<Exception> e)
{
if (GotError != null)
GotError(this, e);
}
void adService_GotResult(object sender, Service4u2.Common.EventArgs<CustomAdFetchResponse> e)
{
var adResponse = new AdResponse()
{
AdText = e.Argument.AdText,
ImageUrl = e.Argument.ImageUrl,
ClickUrl = e.Argument.ClickUrl
};
if (GotAdResponse != null)
GotAdResponse(this, new Service4u2.Common.EventArgs<AdResponse>() { Argument = adResponse });
}
#region IAdAdapter Members
public event EventHandler<Service4u2.Common.EventArgs<AdResponse>> GotAdResponse;
public event EventHandler<Service4u2.Common.EventArgs<Exception>> GotError;
public void GetAdResponseAsync()
{
adService.FetchAdAsync(this.PublisherId, this.ClientId);
}
#endregion
}
Hooking up our Custom Ad Adapter in XAML
Once we have our Custom Ad Adapter in place we can plug it in to our AdDisplay control and start serving ads.
<!-- Custom Ad Service example -->
<moad:AdDisplay
Style="{StaticResource AnimationAdMobStyle}"
VerticalAlignment="Bottom"
Foreground="White"
RefreshSeconds="30">
<moad:AdDisplay.AdAdapter>
<local:CustomAdAdapter
PublisherId="SomePubId"
ClientId="SomeClientId"/>
</moad:AdDisplay.AdAdapter>
</moad:AdDisplay>
And that's about it. Hopefully I'll find some other ad service providers to implement soon. If anyone has any recommendations I'd be happy to hear them.
Grab the updated example source from here or the Project Page at BitBucket (Codeplex is still Garbage).
Now Playing - Notorious B.I.G - Mo Money Mo Problems
Thursday, July 29, 2010
Clearable TextBox Custom Control
It looks like I've got most of the functionality replicated, including adding two states to help with animations. The states are "Normal" and "Cleared". By default, the Cleared state will cause the clear button text to flash red briefly. You can also now set the ClearButtonTemplate and the ClearButtonContent if you want. In the picture you can see I've set the ClearButtonContent to Clear and used the default template.
<clr:ClearableTextBox
Text="{Binding SomeText, Mode=TwoWay}"
ClearButtonContent="Clear"
VerticalAlignment="Top"/>
I haven't tested any custom state animations, only the default ones that are included. Feel free to let me know if you find any problems. The source and binaries are available below.
Clearable TextBox Custom Control Source
Now Playing: The Roots - Don't Say Nuthin
Monday, July 26, 2010
Create a Win Phone 7 Busy Service with Loady animation
Update 2: I've updated my example to not require changing the RootVisual. But you will still need to have a Grid as the Root of your Page. Check out the Updated Busy Service.
I've been spending a lot of time working on data driven Win Phone 7 apps in the past couple weeks and one of the most useful things I've created is a simple Busy Service for Win Phone 7 apps.

/// <summary>
/// An interface for busy services.
/// </summary>
public interface IBusyService
{
bool IsBusyShown { get; }
void ShowBusy(string message);
void ShowBusy(TimeSpan timeToShow, string message);
void ShowBusy(double milliSecondsToShow, string message);
void HideBusy(double millisecondsToWait);
void HideBusy(TimeSpan delay);
void HideBusy();
}
I decided to implement a RootPanelBusyService, which basically means that it expects a "Panel" type of element, a Grid for example, at the RootVisual. The problem with the Win Phone 7 Beta update is that the RootVisual is no longer defined in the App.Xaml, so it would seem like you can't just wrap the Root level Phone Application Frame in a Grid. It turns out, it just takes a little more work, if you open up the App.xaml.cs and take a look at the CompleteInitializePhoneApplication method you will see where the RootVisual is assigned. So with a little code we can add a Grid wrapper around the PhoneApplicationFrame and have a nice place to add our Busy Loady icon.
// Do not add any additional code to this method
private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
// Set the root visual to allow the application to render
if (RootVisual != RootFrame)
{
// Add a Grid wrapper around the Root application frame.
var root = new Grid();
root.Children.Add(RootFrame);
RootVisual = root;
}
// Remove this handler since it is no longer needed
RootFrame.Navigated -= CompleteInitializePhoneApplication;
}
Now that we have our Grid at the Root, we can instantiate a RootPanelBusyService and pass it to our ViewModel to start "Getting Busy...".
// From MainPage.xaml.cs
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (App.Current.RootVisual is Grid)
{
var busyServ = new RootPanelBusyService(App.Current.RootVisual as Grid);
this.DataContext = new MainPageVM(busyServ);
}
}
// In our MainPage View Model (MainPageVM.cs)
/// <summary>
/// A view model for the Main Page.
/// </summary>
public class MainPageVM : ViewModelBase
{
/// <summary>
/// The busy service to use...
/// </summary>
private IBusyService busyService;
/// <summary>
/// Gets or sets the do busy command.
/// </summary>
/// <value>The do busy command.</value>
public ICommand DoBusy { get; private set; }
public MainPageVM(IBusyService busyServ)
{
busyService = busyServ;
DoBusy = new DelegateCommand<object>(HandleDoBusy);
}
/// <summary>
/// Handles the do busy command.
/// </summary>
/// <param name="arg">Unused.
private void HandleDoBusy(object arg)
{
busyService.ShowBusy(2000, "Getting Busy...");
}
}
And there you have it, you've got a simple and easy to use busy service for your long running operations. One other trick that I occasionally use it to set a Busy Service static property on my App class, so all ViewModels have easy access to it. To do this, just add a static property to your App class, and assign it after setting the RootVisual, like so:
// Easy access to the busy service.
public static IBusyService BusyService { get; private set; }
// Rest of this class omitted for brevity....
// Do not add any additional code to this method
private void CompleteInitializePhoneApplication(object sender, NavigationEventArgs e)
{
// Set the root visual to allow the application to render
if (RootVisual != RootFrame)
{
// Add a Grid wrapper around the Root application frame.
var root = new Grid();
root.Children.Add(RootFrame);
RootVisual = root;
// Set our easy access busy service property.
BusyService = new RootPanelBusyService(root);
}
// Remove this handler since it is no longer needed
RootFrame.Navigated -= CompleteInitializePhoneApplication;
}
If you don't want to download the full example project to get the RootPanelBusyService implementation, here is the source, but you'll need to create your own Loady graphic / control.
/// <summary>
/// An interface for busy services.
/// </summary>
public interface IBusyService
{
bool IsBusyShown { get; }
void ShowBusy(string message);
void ShowBusy(TimeSpan timeToShow, string message);
void ShowBusy(double milliSecondsToShow, string message);
void HideBusy(double millisecondsToWait);
void HideBusy(TimeSpan delay);
void HideBusy();
}
/// <summary>
/// A busy service that expects a Panel / Grid as the root visual.
/// </summary>
public class RootPanelBusyService : IBusyService
{
private UIElement currentBusyElement;
private Panel rootVisual;
/// <summary>
/// Initializes a new instance of the <see cref="RootPanelBusyService"> class.
/// </see></summary>
/// The root visual element.
public RootPanelBusyService(Panel rootVisualElement)
{
rootVisual = rootVisualElement;
}
#region IBusyService Members
private bool _busyShown = false;
public bool IsBusyShown
{
get { return _busyShown; }
}
public void ShowBusy(double milliSecondsToShow, string message = "Loading...")
{
ShowBusy(TimeSpan.FromMilliseconds(milliSecondsToShow), message);
}
public void ShowBusy(TimeSpan timeToShow, string message = "Loading...")
{
ShowBusy(message);
ThreadPool.QueueUserWorkItem(new WaitCallback(time =>
{
Thread.Sleep((TimeSpan)time);
// Hide the busy after a sleep.
rootVisual.Dispatcher.BeginInvoke(() => HideBusy());
}), timeToShow);
}
public void ShowBusy(string message = "Loading...")
{
_busyShown = true;
var busyEl = CreateBusyElement(message);
busyEl.Opacity = 0;
try
{
if (rootVisual is Panel)
{
var root = rootVisual;
root.Children.Add(busyEl);
var anim = CreateAnimation(busyEl, Grid.OpacityProperty, 0.8, 0.00, 900, EasingMode.EaseOut);
var story = new Storyboard();
story.Children.Add(anim);
story.Begin();
}
}
catch
{
Debug.WriteLine("Problem showing busy.");
}
currentBusyElement = busyEl;
}
public void HideBusy(double milliseconds)
{
HideBusy(TimeSpan.FromMilliseconds(milliseconds));
}
public void HideBusy(TimeSpan delay)
{
var root = rootVisual;
ThreadPool.QueueUserWorkItem(new WaitCallback((arg) =>
{
Thread.Sleep(delay);
((Panel)arg).Dispatcher.BeginInvoke(() =>
{
HideBusy();
});
}), root);
}
public void HideBusy()
{
try
{
if (rootVisual is Panel)
{
var root = rootVisual;
var anim = CreateAnimation(currentBusyElement, Grid.OpacityProperty, 0.00, .80, 1000, EasingMode.EaseOut);
var story = new Storyboard();
story.Children.Add(anim);
story.Begin();
// Remove the element a second later; I do it this way because StoryCompleted is flaky.
ThreadPool.QueueUserWorkItem(new WaitCallback(obj =>
{
Thread.Sleep(900);
root.Dispatcher.BeginInvoke(() =>
{
root.Children.Remove(currentBusyElement);
currentBusyElement = null;
});
}), null);
}
}
catch
{
Debug.WriteLine("Problem hiding busy.");
}
_busyShown = false;
}
#endregion
/// <summary>
/// Creates the busy element; A Loady with a text block underneath.
/// </summary>
/// The message to show below the loady.
private UIElement CreateBusyElement(string message = "Loading...")
{
// create a grid to hold the loady and swallow the entire screen.
var root = new Grid()
{
Background = new SolidColorBrush(Colors.Black)
};
// The spinny loady thingy...
root.Children.Add(new Loady()
{
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
});
// The text below the loady...
root.Children.Add(new TextBlock()
{
Text = message,
Margin = new Thickness(0, 150, 0, 0),
FontSize = 30,
HorizontalAlignment = System.Windows.HorizontalAlignment.Center,
VerticalAlignment = System.Windows.VerticalAlignment.Center,
Foreground = new SolidColorBrush(Colors.White)
});
return root;
}
/// <summary>
/// A helper method for creating double animations.
/// </summary>
public DoubleAnimation CreateAnimation(DependencyObject obj, DependencyProperty prop, double value, double milliseconds, EasingMode easing = EasingMode.EaseOut)
{
var from = Convert.ToDouble(obj.GetValue(prop));
return CreateAnimation(obj, prop, value, from, milliseconds, easing);
}
/// <summary>
/// A helper method for creating double animations.
/// </summary>
public DoubleAnimation CreateAnimation(DependencyObject obj, DependencyProperty prop, double value, double from, double milliseconds, EasingMode easing = EasingMode.EaseOut)
{
CubicEase ease = new CubicEase() { EasingMode = easing };
DoubleAnimation animation = new DoubleAnimation
{
Duration = new Duration(TimeSpan.FromMilliseconds(milliseconds)),
From = from,
To = value,
FillBehavior = FillBehavior.HoldEnd,
EasingFunction = ease
};
Storyboard.SetTarget(animation, obj);
Storyboard.SetTargetProperty(animation, new PropertyPath(prop));
return animation;
}
}
Now Playing: The Roots - Get Busy
Tuesday, July 13, 2010
Win Phone 7 "Words" Game - Mo Words
My wife and I have been playing a lot of word games lately on our IPhones. I've also been playing around with the Win Phone 7 SDK and I've been looking for a good project to work with. So, as a result, I made a little "Words" game called Mo Words.
I've just recently got it to a point where I could play a whole game. But there is still some work to be done for the play notifications (I'm hoping to get Push notifications working this weekend).
The game uses an MVC 2 website returning JSON responses as it's service layer. I hope to put together a couple posts on how I make data driven win phone apps with MVC 2 websites, including how to authenticate to an MVC 2 site and all that.
So, without further ado, here is a demo video (sorry it's choppy, the loading animations and dragging don't match the smoothness of the actual game, it's much better in person) and some screen shots.





Now Playing: Animal Collective - My Girls
