Here is a simple little behavior for updating a TextBox's Text property in Silverlight when the text changes instead of when it loses focus.
Quick and Easy. Gotta Love Behaviors.
Jacob
/// The timer class.
var timer = function () {
this.started = new ko.observable(false);
this.totalSeconds = new ko.observable(0);
this.seconds = new ko.dependentObservable(function () {
return (this.totalSeconds() % 60).toFixed(0);
}, this);
this.secondsDisplay = new ko.dependentObservable(function () {
var secs = this.seconds();
var display = '' + secs;
if (secs < 10) { display = '0' + secs; }
// Hack for weird edge case because of setInterval.
if (display == '010') { display = '10'; }
return display;
}, this);
this.minutes = new ko.dependentObservable(function () {
return ((this.totalSeconds() / 60) % 60).toFixed(0);
}, this);
this.hours = new ko.dependentObservable(function () {
return (((this.totalSeconds() / 60) / 60) % 60).toFixed(0);
}, this);
this.secondHandAngle = new ko.dependentObservable(function () {
return this.seconds() * 6;
}, this);
this.minuteHandAngle = new ko.dependentObservable(function () {
return this.minutes() * 6;
}, this);
this.hourHandAngle = new ko.dependentObservable(function () {
return this.hours() * 6;
}, this);
this.alarm = function () {
log('alarm fired');
};
};
// timer.start
timer.prototype.start = function () {
this.started(true);
this.startTime = new Date();
var self = this;
this.intervalId = setInterval(function () {
var oldTime = self.startTime;
self.startTime = new Date();
var diff = secondsBetween(self.startTime, oldTime);
var currSeconds = self.totalSeconds();
self.totalSeconds(currSeconds + diff);
}, 100);
};
// timer.stop
timer.prototype.stop = function () {
this.started(false);
if (this.intervalId) {
clearInterval(this.intervalId);
}
};
// helper...
function secondsBetween(date1, date2) {
return (date1.getTime() - date2.getTime()) / 1000;
};
@{
Page.Title = "Home Page";
}
@section ScriptSection {
<script id="faceTemplate" type="text/x-jquery-tmpl">
@* Our SVG Code in a partial view *@
@Html.Partial("_WatchFace")
<div id="timerInfo" style="font-weight: bold; font-size: 24px; float: left;">
<span class="minutes" data-bind="text: minutes()"></span>
<span>:</span>
<span class="seconds" data-bind="text: secondsDisplay()"></span>
</div>
</script>
<script type="text/javascript">
// Our custom svg Rotate transform binding...
ko.bindingHandlers.svgRotate = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
// This will be called when the binding is first applied to an element
// Set up any initial state, event handlers, etc. here
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
// This will be called once when the binding is first applied to an element,
// and again whenever the associated observable changes value.
// Update the DOM element based on the supplied values here.
var value = valueAccessor(), allBindings = allBindingsAccessor();
var rotation = ko.utils.unwrapObservable(value);
var originX = allBindings.originX || 0;
var originY = allBindings.originY || 0;
var rotateText = 'rotate(' + rotation + ', ' + originX + ', ' + originY + ')';
var id = $(element).attr('id');
// Using the old school doc getElement because jquery's attr() is not setting the value correctly
var elem = document.getElementById(id);
if (!elem) { log('rotate binding element not found'); return; }
elem.setAttribute('transform', rotateText);
}
};
// Our page ViewModel
var viewModel = {
watch: new ko.observable(new timer()),
start: function () {
this.watch().start();
},
stop: function () {
this.watch().stop();
},
toggleTimer: function () {
this.watch().started() ? this.stop() : this.start();
}
};
ko.applyBindings(viewModel);
</script>
}
<p>
This is an example project using <a href="http://html5boilerplate.com">HTML5 Boilerplate</a> patterns, <a href="http://knockoutjs.com">Knockout.js</a> MVVM Binding and Templating, <a href="http://docs.jquery.com/Qunit">QUnit</a> unit tests and SVG Graphics.
</p><br />
<div id="watchContainer" style="cursor: pointer;" data-bind='template: { name: "faceTemplate", data: watch()}, click: toggleTimer'>
</div>
<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>
<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>
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();
}
}
<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>
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();
}
}
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);
}
}
<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>
<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>
/// <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);
}
}
/// <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);
}
}
/// <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>
{ }