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> { }