Showing posts with label Silverlight. Show all posts
Showing posts with label Silverlight. Show all posts

Monday, 23 June 2014

From Task to Rx to Streaming data

Here is a simple example of a ViewModel calling a LoadCustomers service:

public async Task LoadCustomers()
{
IsLoading = true;
Customers.Clear();
var customers = await services.GetAllCustomers();
Customers.AddRange(customers);
IsLoading = false;
}
Note: I’m using the PRISM extension method that allows for adding a range to an observable collection, giving less notifications.  The error handling has been removed to focus on the code.

Here’s the equivalent for Rx:
public async Task LoadCustomers()
{
IsLoading = true;
Customers.Clear();
services.GetAllCustomers().ToObservable()
.SubscribeOn(NewThreadScheduler.Default)
.ObserveOnDispatcher()
.Subscribe(
customers => Customers.AddRange(customers),
() => IsLoading = false);
}

The SubscribeOn extension method is ensuring the GetAllCustomers call is being made on a separate thread to keep the UI as free as possible.  Once the response comes back, the ObserveOnDispatcher ensures the UI thread runs our Customers.AddRange code along with the IsLoading = false code which is running on the Observable OnCompleted event.

So any benefits from this?  Not really.  But imagine our GetAllCustomers wasn’t just a call to a web endpoint.

First we’ll remove the ToObservable() extension method and code up the observable ourselves so we understand what’s actually going on under the hood:

public async Task LoadCustomers()
{
IsLoading = true;
Customers.Clear();

var source = Observable.Create<IEnumerable<Customer>>(
async o =>
{
var response = await services.GetAllCustomers();
o.OnNext(response);
o.OnCompleted();
return Disposable.Empty;
});

source
.SubscribeOn(NewThreadScheduler.Default)
.ObserveOnDispatcher()
.Subscribe(
customers => Customers.AddRange(customers),
() => IsLoading = false);
}

Now if we start to require more work being done to retrieve data than just a web call, like perhaps converting DTOs to UI models, this can be done within the observable code.  Remember this work is not being done on the UI thread so our app is nice and responsive.

Now imagine our services returned observables rather than DTOs or UI models:

public void LoadCustomers()
{
services.GetAllCustomers()
.SubscribeOn(NewThreadScheduler.Default)
.ObserveOnDispatcher()
.Subscribe(customers => Customers.AddRange(customers));
}

We now have a viewmodel that is dealing with observables and doesn’t care whether that data is coming from a web service call, file I/O or a Nirvana (Universal) endpoint that is streaming data.

Saturday, 23 November 2013

PropertyListener

Need to react to a property changing but the control doesn’t expose the event?  No problem, you can use the PropertyListener:

using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace Utilities
{
public class PropertyListener
{
/// <summary>
/// Listens for changes to dependency properties on a given FrameworkElement calls a callback on property change.
/// Usage example: PropertyListener.ListenForChange("IsBusy", BusyIndicator, (d,e) => DoStuff())
/// (may cause memory leaks)
/// </summary>
public void ListenForChange(string propertyName, FrameworkElement element, PropertyChangedCallback callback)
{
var b = new Binding(propertyName) { Source = element };
var prop = DependencyProperty.RegisterAttached(
"ListenAttached" + propertyName,
typeof(object),
typeof(Control),
new PropertyMetadata(callback));
element.SetBinding(prop, b);
}
}
}

RichText in a TextBlock

This project contains examples for Silverlight for providing formatting within a text block, either as a behaviour or attached property.  I would have liked to create custom control that derived from TextBlock but the class is sealed in Silverlight.

Source code:  https://github.com/stevenh77/RichTextBlockExample

image

<UserControl x:Class="RichTextBlockExample.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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:RichTextBlockExample"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="400">

<Grid x:Name="LayoutRoot" Background="White">
<StackPanel>
<TextBlock x:Name="TextBlockUsingBehaviour">
<i:Interaction.Behaviors>
<local:RichTextBlockBehaviour/>
</i:Interaction.Behaviors>
</TextBlock>

<TextBlock x:Name="TextBlockUsingAttachedProperty" local:SupportRichText.RichText="" />
</StackPanel>
</Grid>
</UserControl>

namespace RichTextBlockExample
{
public partial class MainPage
{
public MainPage()
{
InitializeComponent();

string output = "Testing <bold>formatted</bold> text <underline>with</underline> a <italic>textblock</italic>";
TextBlockUsingBehaviour.Text = output;
TextBlockUsingAttachedProperty.SetValue(SupportRichText.RichTextProperty, output);
}
}
}

Behaviour


using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Windows.Documents;
using System.Windows.Interactivity;

namespace RichTextBlockExample
{
public class RichTextBlockBehaviour : Behavior<TextBlock>
{
private PropertyListener propertyListener;
protected override void OnAttached()
{
base.OnAttached();
propertyListener = new PropertyListener();
propertyListener.ListenForChange("Text", this.AssociatedObject, TextBlock_TextChanged);
}

private void TextBlock_TextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var textBlock = sender as TextBlock;
if (textBlock != null)
{
textBlock.Inlines.Clear();
textBlock.Inlines.Add(Traverse(e.NewValue.ToString()));
}
}

protected override void OnDetaching()
{
base.OnDetaching();
}

private static Inline Traverse(string value)
{
// Get the sections/inlines
string[] sections = SplitIntoSections(value);

// Check for grouping
if (sections.Length.Equals(1))
{
string section = sections[0];
string token; // E.g <Bold>
int tokenStart, tokenEnd; // Where the token/section starts and ends.

// Check for token
if (GetTokenInfo(section, out token, out tokenStart, out tokenEnd))
{
// Get the content to further examination
string content = token.Length.Equals(tokenEnd - tokenStart)
? null
: section.Substring(token.Length, section.Length - 1 - token.Length * 2);

switch (token.ToLower())
{
case "<bold>":
return new Run() { Text = content, FontWeight = FontWeights.Bold };

case "<italic>":
return new Run() { Text = content, FontStyle = FontStyles.Italic };

case "<underline>":
return new Run() { Text = content, TextDecorations = TextDecorations.Underline };

case "<linebreak/>":
return new LineBreak();

default:
return new Run() { Text = content };
}
}
else return new Run() { Text = section };
}
else // Group together
{
Span span = new Span();

foreach (string section in sections) span.Inlines.Add(Traverse(section));

return span;
}
}

/// <summary>
/// Examines the passed string and find the first token, where it begins and where it ends.
/// </summary>
/// <param name="value">The string to examine.</param>
/// <param name="token">The found token.</param>
/// <param name="startIndex">Where the token begins.</param>
/// <param name="endIndex">Where the end-token ends.</param>
/// <returns>True if a token was found.</returns>
private static bool GetTokenInfo(string value, out string token, out int startIndex, out int endIndex)
{
token = null;
endIndex = -1;

startIndex = value.IndexOf("<");
int startTokenEndIndex = value.IndexOf(">");

// No token here
if (startIndex < 0) return false;

// No token here
if (startTokenEndIndex < 0) return false;

token = value.Substring(startIndex, startTokenEndIndex - startIndex + 1);

// Check for closed token. E.g. <LineBreak/>
if (token.EndsWith("/>"))
{
endIndex = startIndex + token.Length;
return true;
}

string endToken = token.Insert(1, "/");

// Detect nesting;
int nesting = 0;
int temp_startTokenIndex = -1;
int temp_endTokenIndex = -1;
int pos = 0;
do
{
temp_startTokenIndex = value.IndexOf(token, pos);
temp_endTokenIndex = value.IndexOf(endToken, pos);

if (temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex)
{
nesting++;
pos = temp_startTokenIndex + token.Length;
}
else if (temp_endTokenIndex >= 0 && nesting > 0)
{
nesting--;
pos = temp_endTokenIndex + endToken.Length;
}
else // Invalid tokenized string
return false;

}
while (nesting > 0);

endIndex = pos;

return true;
}

/// <summary>
/// Splits the string into sections of tokens and regular text.
/// </summary>
/// <param name="value">The string to split.</param>
/// <returns>An array with the sections.</returns>
private static string[] SplitIntoSections(string value)
{
List<string> sections = new List<string>();

while (!string.IsNullOrEmpty(value))
{
string token;
int tokenStartIndex, tokenEndIndex;

// Check if this is a token section
if (GetTokenInfo(value, out token, out tokenStartIndex, out tokenEndIndex))
{
// Add pretext if the token isn't from the start
if (tokenStartIndex > 0) sections.Add(value.Substring(0, tokenStartIndex));

sections.Add(value.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex));
value = value.Substring(tokenEndIndex); // Trim away
}
else
{
// No tokens, just add the text
sections.Add(value);
value = null;
}
}

return sections.ToArray();
}
}
}

Attached Property


using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;

namespace RichTextBlockExample
{
public class SupportRichText
{
public static string GetRichText(DependencyObject obj)
{
return (string)obj.GetValue(RichTextProperty);
}

public static void SetRichText(DependencyObject obj, string value)
{
obj.SetValue(RichTextProperty, value);
}

public static readonly DependencyProperty RichTextProperty =
DependencyProperty.RegisterAttached(
"RichText",
typeof(string),
typeof(SupportRichText),
new PropertyMetadata("", RichTextChanged));

private static Inline Traverse(string value)
{
// Get the sections/inlines
string[] sections = SplitIntoSections(value);

// Check for grouping
if (sections.Length.Equals(1))
{
string section = sections[0];
string token; // E.g <Bold>
int tokenStart, tokenEnd; // Where the token/section starts and ends.

// Check for token
if (GetTokenInfo(section, out token, out tokenStart, out tokenEnd))
{
// Get the content to further examination
string content = token.Length.Equals(tokenEnd - tokenStart)
? null
: section.Substring(token.Length, section.Length - 1 - token.Length * 2);

switch (token.ToLower())
{
case "<bold>":
return new Run() { Text = content, FontWeight = FontWeights.Bold };

case "<italic>":
return new Run() { Text = content, FontStyle = FontStyles.Italic };

case "<underline>":
return new Run() { Text = content, TextDecorations = TextDecorations.Underline };

case "<linebreak/>":
return new LineBreak();

default:
return new Run() { Text = content };
}
}
else return new Run() { Text = section };
}
else // Group together
{
Span span = new Span();

foreach (string section in sections) span.Inlines.Add(Traverse(section));

return span;
}
}

/// <summary>
/// Examines the passed string and find the first token, where it begins and where it ends.
/// </summary>
/// <param name="value">The string to examine.</param>
/// <param name="token">The found token.</param>
/// <param name="startIndex">Where the token begins.</param>
/// <param name="endIndex">Where the end-token ends.</param>
/// <returns>True if a token was found.</returns>
private static bool GetTokenInfo(string value, out string token, out int startIndex, out int endIndex)
{
token = null;
endIndex = -1;

startIndex = value.IndexOf("<");
int startTokenEndIndex = value.IndexOf(">");

// No token here
if (startIndex < 0) return false;

// No token here
if (startTokenEndIndex < 0) return false;

token = value.Substring(startIndex, startTokenEndIndex - startIndex + 1);

// Check for closed token. E.g. <LineBreak/>
if (token.EndsWith("/>"))
{
endIndex = startIndex + token.Length;
return true;
}

string endToken = token.Insert(1, "/");

// Detect nesting;
int nesting = 0;
int temp_startTokenIndex = -1;
int temp_endTokenIndex = -1;
int pos = 0;
do
{
temp_startTokenIndex = value.IndexOf(token, pos);
temp_endTokenIndex = value.IndexOf(endToken, pos);

if (temp_startTokenIndex >= 0 && temp_startTokenIndex < temp_endTokenIndex)
{
nesting++;
pos = temp_startTokenIndex + token.Length;
}
else if (temp_endTokenIndex >= 0 && nesting > 0)
{
nesting--;
pos = temp_endTokenIndex + endToken.Length;
}
else // Invalid tokenized string
return false;

}
while (nesting > 0);

endIndex = pos;

return true;
}

/// <summary>
/// Splits the string into sections of tokens and regular text.
/// </summary>
/// <param name="value">The string to split.</param>
/// <returns>An array with the sections.</returns>
private static string[] SplitIntoSections(string value)
{
var sections = new List<string>();
while (!string.IsNullOrEmpty(value))
{
string token;
int tokenStartIndex, tokenEndIndex;

// Check if this is a token section
if (GetTokenInfo(value, out token, out tokenStartIndex, out tokenEndIndex))
{
// Add pretext if the token isn't from the start
if (tokenStartIndex > 0) sections.Add(value.Substring(0, tokenStartIndex));

sections.Add(value.Substring(tokenStartIndex, tokenEndIndex - tokenStartIndex));
value = value.Substring(tokenEndIndex); // Trim away
}
else
{
// No tokens, just add the text
sections.Add(value);
value = null;
}
}

return sections.ToArray();
}

private static void RichTextChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
string value = e.NewValue as string;
TextBlock textBlock = sender as TextBlock;
if (textBlock != null) textBlock.Inlines.Add(Traverse(value));
}
}
}

Property Listener


using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace RichTextBlockExample
{
public class PropertyListener
{
/// <summary>
/// Listens for changes to dependency properties on a given FrameworkElement calls a callback on property change.
/// Usage example: PropertyListener.ListenForChange("IsBusy", BusyIndicator, (d,e) => DoStuff())
/// (may cause memory leaks)
/// </summary>
public void ListenForChange(string propertyName, FrameworkElement element, PropertyChangedCallback callback)
{
var b = new Binding(propertyName) { Source = element };
var prop = DependencyProperty.RegisterAttached(
"ListenAttached" + propertyName,
typeof(object),
typeof(Control),
new PropertyMetadata(callback));
element.SetBinding(prop, b);
}
}
}

Wednesday, 20 November 2013

ActualSizeProxy

/// <summary>
/// Allows binding to a container (or other framework element) height or width
///
/// Example:
///     <UserControl.Resources>
///          <utils:ActualSizePropertyProxy Element="{Binding ElementName=SellPanel}"  x:Name="SellPanelSizeProxy"/>
///     </UserControl.Resources>    
///
///     <TextBlock Width="{Binding ElementName=SellPanelSizeProxy, Path=ActualWidthValue}" />
///
/// </summary>
public class ActualSizePropertyProxy : FrameworkElement, INotifyPropertyChanged
{
    public static readonly DependencyProperty ElementProperty = DependencyProperty.Register(
        "Element",
        typeof(FrameworkElement),
        typeof(ActualSizePropertyProxy),
        new PropertyMetadata(null, OnElementPropertyChanged));
    public event PropertyChangedEventHandler PropertyChanged;
    public FrameworkElement Element
    {
        get { return (FrameworkElement)this.GetValue(ElementProperty); }
        set { this.SetValue(ElementProperty, value); }
    }
    public double ActualHeightValue
    {
        get { return this.Element == null ? 0 : this.Element.ActualHeight; }
    }
    public double ActualWidthValue
    {
        get { return this.Element == null ? 0 : this.Element.ActualWidth; }
    }
    private static void OnElementPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((ActualSizePropertyProxy)d).OnElementChanged(e);
    }
    private void OnElementChanged(DependencyPropertyChangedEventArgs e)
    {
        var oldElement = (FrameworkElement)e.OldValue;
        var newElement = (FrameworkElement)e.NewValue;
        newElement.SizeChanged += this.ElementSizeChanged;
        if (oldElement != null)
        {
            oldElement.SizeChanged -= this.ElementSizeChanged;
        }
        this.NotifyPropChange();
    }
    private void ElementSizeChanged(object sender, SizeChangedEventArgs e)
    {
        this.NotifyPropChange();
    }
    private void NotifyPropChange()
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged(this, new PropertyChangedEventArgs("ActualWidthValue"));
            this.PropertyChanged(this, new PropertyChangedEventArgs("ActualHeightValue"));
        }
    }
}

DataContextProxy

using System.Windows;
using System.Windows.Data;
 
namespace Utilities
{
    /// <summary>
    /// Allows binding to a viewmodel from within a data/contenttemplate that is bound to something else.
    ///
    /// Example:
    ///     <UserControl.Resources>
    ///         <DataContextProxy x:Key="dataContextProxy" />
    ///     </UserControl.Resources>    
    ///     ...
    ///     <DataTemplate>
    ///         <Button Content="Delete"
    ///                 Command="{Binding Source={StaticResource dataContextProxy}, Path=DataSource.DeleteClass}" 
    ///                 CommandParameter="{Binding}" />
    ///     </DataTemplate>
    ///
    /// </summary>
    public class DataContextProxy : FrameworkElement
    {
        public DataContextProxy()
        {
            this.Loaded += OnLoaded;
        }
        public string BindingPropertyName { get; set; }
        public BindingMode BindingMode { get; set; }
        public static readonly DependencyProperty DataSourceProperty = DependencyProperty.Register("DataSource", typeof(object), typeof(DataContextProxy), null);
        public object DataSource
        {
            get { return GetValue(DataSourceProperty); }
            set { SetValue(DataSourceProperty, value); }
        }
        private void OnLoaded(object sender, RoutedEventArgs e)
        {
            var binding = new Binding();
            if (!string.IsNullOrEmpty(BindingPropertyName))
            {
                binding.Path = new PropertyPath(BindingPropertyName);
            }
            binding.Source = DataContext;
            binding.Mode = BindingMode;
            SetBinding(DataSourceProperty, binding);
            // this.Loaded -= this.OnLoaded;
        }
    }
}

Saturday, 2 November 2013

Fluid Dialog Wizard for Silverlight

My Fluid Dialog Wizard is a copy from Visual Studio 2012 setup dialog:

Features plenty of cool stuff: my Modern UI tab control, plenty of visual state manager examples with storyboards, animations, render transforms and transitions, behaviours, draggable dialog, styles, a clip attached property (that forces a panel to clip it’s contents),

Source:    https://github.com/stevenh77/FluidDialogWizard

ModernUI Tab Control

This control gives you a ModernUI look and feel for a menu with animated slide in effect when the menu changes.

Source available here:  https://github.com/stevenh77/ModernUITabControl/

Friday, 13 September 2013

Setting format for Telerik RadDatePicker

First up remember there are two date pickers the RadDatePicker and the RadDateTimePicker.

You can specialise with your date hardcoded, or have a property that lets the developer set the value:

public class RadDatePickerWithSwissDates : RadDatePicker
{
public RadDatePickerWithSwissDates()
{
var ci = new CultureInfo("")
{
DateTimeFormat = { ShortDatePattern = "dd.MM.yyyy" }
};

this.Culture = ci;
}
}

Or you can use this approach:

public class TelerikDateFormatWorkaround
{
public CultureInfo CultureWithSwissFormat
{
//Hack to get around the fact that there is no custom date format in the Telerik DatePicker
//http://www.telerik.com/community/forums/wpf/datepicker/changing-dateformat.aspx
get
{
var tempCultureInfo = (CultureInfo)CultureInfo.CurrentCulture.Clone();
tempCultureInfo.DateTimeFormat.ShortDatePattern = "dd.MM.yyyy";
return tempCultureInfo;
}
}
}

// add it to the usercontrol resources in the usual way and then...

<Style TargetType="telerik1:RadDatePicker">
<Setter Property="Culture" Value="{Binding Source={StaticResource TelerikDateFormatWorkaround}, Path=CultureWithSwissFormat}"/>
</Style>

BooleanToVisibilityConverterMarkupExtension

public class BooleanToVisibilityConverterMarkupExtension : MarkupExtension, IValueConverter
{
private static BooleanToVisibilityConverterMarkupExtension converter;

public override object ProvideValue(IServiceProvider serviceProvider)
{
return converter ?? (converter = new BooleanToVisibilityConverterMarkupExtension());
}

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var v = (bool?)value;
return v.HasValue && v.Value ? Visibility.Visible : Visibility.Collapsed;
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var v = (Visibility)value;
return v == Visibility.Visible;
}
}

<!-- usage in xaml -->
Visibility="{Binding IsVisible, Converter={converters:BooleanToVisibilityConverterMarkupExtension}}">>

Wednesday, 28 August 2013

How to find absolute position of control

The following code from MSDN come in very handy today, just change “this” to your control and it’ll give your the absolute position of your control on the page:

GeneralTransform gt = this.TransformToVisual(Application.Current.RootVisual as UIElement);
Point offset = gt.Transform(new Point(0, 0));
double controlTop = offset.Y;
double controlLeft = offset.X;

Friday, 19 July 2013

Sequential vs Parallel Service Requests

Sequential

image
public async Task<HttpResponseMessage> DownloadWithHttpClient()
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "http://stevenhollidge.com/sample.json?" + DateTime.Now.Ticks);
var response = await httpClient.SendAsync(request);
return response;
}

private async void HttpClientGoButton_OnClick(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 9; i++)
{
await DownloadWithHttpClient();
}
}

Parallel


image
public async Task<HttpResponseMessage> DownloadWithHttpClient()
{
var httpClient = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "http://stevenhollidge.com/sample.json?" + DateTime.Now.Ticks);
var response = await httpClient.SendAsync(request);
return response;
}

private async void HttpClientGoButton_OnClick(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 9; i++)
{
DownloadWithHttpClient();
}
}

Tuesday, 2 April 2013

Slow Silverlight debugging

If you’ve added a Xaml binding breakpoint and now have really slow performance when debugging, like your app being slow to load, animations not running smoothly etc then try this…

  1. Remove all xaml breakpoints
  2. Add System.Windows.Data.Binding.IsDebuggingEnabled = false within the App.xaml.cs constructor.

image

Run it once then you can remove the line, hope this helps some peeps out!

Friday, 29 March 2013

Animated Listbox

This example shows a nice fluid movement for items in a wrapping listbox.

Demo

Click here to open the Silverlight page and resize your browser: 

http://stevenhollidge.com/blog-source-code/animatedlistbox

Source code

SL version:  https://github.com/stevenh77/AnimatedListboxSL

WPF version:  https://github.com/stevenh77/AnimatedListboxWPF

Dynamic Layout and Transitions in Silverlight

There’s a fantastic video from Mix 10, given by speaker Kenny Young:

http://channel9.msdn.com/Events/MIX/MIX10/CL55

Here’s the Silverlight project from the video running:

The source code doesn’t seem to be available online anymore so I contacted him and managed to track down the files.

Or here’s a link to the demo: 

http://stevenhollidge.com/blog-source-code/dynamiclayoutandtransitions/

Or download the source, updated for Silverlight 5 here:

https://github.com/stevenh77/DynamicLayoutAndTransitions

Sunday, 16 December 2012

Tracking MouseMove in Silverlight

namespace MouseTracking
{
public partial class MainPage
{
readonly MouseTracker tracker = new MouseTracker();

public MainPage()
{
InitializeComponent();
tracker.StartTracking(this);
}
}
}
using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
using System.Windows.Input;

namespace MouseTracking
{
public class MouseTracker
{
public MouseTracker()
{
TraceMouse = true;
}

public bool TraceMouse { get; set; }

public void StartTracking(UserControl sender)
{
sender.Loaded += sender_Loaded;
//TODO: Hook into specific FrameworkElements events? MouseOver, select, click, etc
}

void sender_Loaded(object sender, RoutedEventArgs e)
{
var page = (UserControl) sender;
page.MouseLeftButtonDown += page_MouseLeftButtonDown;
page.SizeChanged += page_SizeChanged;
page.MouseMove += page_MouseMove;
page.KeyDown += page_KeyDown;
page.Unloaded += page_Unloaded;

var data = string.Format("resolution={0}x{1}",
HtmlPage.Window.Eval("screen.width"),
HtmlPage.Window.Eval("screen.height"));

Write(DateTime.Now, page.ToString(), "Loaded", data);
}

void page_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var data = string.Format("ClickCount={0}", e.ClickCount);

Write(DateTime.Now, sender.ToString(), "page_MouseLeftButtonDown", data);
}

void page_SizeChanged(object sender, SizeChangedEventArgs e)
{
var data = string.Format("screen={0}x{1} page={2}x{3}",
(Application.Current.RootVisual as FrameworkElement).ActualWidth,
(Application.Current.RootVisual as FrameworkElement).ActualHeight,
(sender as FrameworkElement).ActualWidth,
(sender as FrameworkElement).ActualHeight);

Write(DateTime.Now, sender.ToString(), "SizeChanged", data);

}

private void page_KeyDown(object sender, KeyEventArgs e)
{
Write(DateTime.Now, sender.ToString(), "KeyDown", string.Format("key={0}", e.Key));
}

void page_Unloaded(object sender, RoutedEventArgs e)
{
Debug.WriteLine("Removing event handlers");

var page = (UserControl)sender;
page.Loaded -= sender_Loaded;
page.MouseLeftButtonDown -= page_MouseLeftButtonDown;
page.SizeChanged -= page_SizeChanged;
page.MouseMove -= page_MouseMove;
page.KeyDown -= page_KeyDown;
page.Unloaded -= page_Unloaded;
}

void page_MouseMove(object sender, MouseEventArgs e)
{
Write(DateTime.Now, sender.ToString(), "MouseMove", string.Format("coord={0},{1}", e.GetPosition(null).X, e.GetPosition(null).Y));
}

private void Write(DateTime time, string page, string eventName, string data)
{
string ipAddress = Application.Current.Resources["ipAddress"].ToString();

// call service here (ipaddress can be retrieved serverside using HttpContext object)
// for now we'll just output to debug window

if (TraceMouse)
Debug.WriteLine("{0} {1} {2} {3} {4}", ipAddress, time.ToString("MM/dd/yyyy HH:mm:ss.fff"), page, eventName, data);
}
}
}
<body>
<form id="form1" runat="server" style="height:100%">
<div id="silverlightControlHost">
<object data="data:application/x-silverlight-2," type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/MouseTracking.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="5.0.61118.0" />
<param name="initParams" value="ipAddress=<%= Request.UserHostAddress %>"/>
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=5.0.61118.0" style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376" alt="Get Microsoft Silverlight" style="border-style:none"/>
</a>
</object><iframe id="_sl_historyFrame" style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>
</form>
</body>
</html>
private void Application_Startup(object sender, StartupEventArgs e)
{
if (e.InitParams != null)
{
foreach (var item in e.InitParams)
{
this.Resources.Add(item.Key, item.Value);
}
}

this.RootVisual = new MainPage();
}

Source:  https://github.com/stevenh77/MouseTracking

Wednesday, 5 December 2012

PivotViewer ItemsLoaded event

Here is an example of the PivotViewer extended to feature an ItemsLoaded event.

This event fires when all the item data templates/trading cards have finished rendering.

Live demo:  http://stevenhollidge.com/blog-source-code/pivotviewerwithitemsloadedevent

using System;
using System.ComponentModel;
using System.Windows.Controls.Pivot;
using System.Windows.Threading;

namespace PivotViewerDemo
{
public class MetroPivotViewer : PivotViewer, INotifyPropertyChanged
{
private readonly UiHelper layoutUpdatedFinished;

private string title;
public string Title
{
get { return title; }
set
{
if (title == value) return;
title = value;
NotifyPropertyChanged("Title");
}
}

private bool isLoading;
public bool IsLoading
{
get { return isLoading; }
set
{
if (isLoading == value) return;
isLoading = value;
NotifyPropertyChanged("IsLoading");
}
}

public MetroPivotViewer()
{
layoutUpdatedFinished = new UiHelper();
IsLoading = true;

this.StateSynchronizationFinished += MetroPivotViewer_StateSynchronizationFinished;
this.LayoutUpdated += MetroPivotViewer_LayoutUpdated;
}

void MetroPivotViewer_LayoutUpdated(object sender, EventArgs e)
{
layoutUpdatedFinished.Reset();
}

void MetroPivotViewer_StateSynchronizationFinished(object sender, EventArgs e)
{
layoutUpdatedFinished.SetTimeout(300, () =>
{
IsLoading = false;
this.View = this.GraphView;
NotifyItemsLoaded();
});
}

public event EventHandler ItemsLoaded;
public void NotifyItemsLoaded()
{
if (ItemsLoaded != null)
ItemsLoaded(this, null);
}

#region INotifyPropertyChanged

public event PropertyChangedEventHandler PropertyChanged;

public void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}

#endregion

public override void OnApplyTemplate()
{
base.OnApplyTemplate();
}
}

public class UiHelper
{
private readonly DispatcherTimerContainingAction timer;

public UiHelper()
{
timer = new DispatcherTimerContainingAction();
}

public void SetTimeout(int milliseconds, Action action)
{
timer.Interval = new TimeSpan(0, 0, 0, 0, milliseconds);
timer.Action = action;
timer.Tick += OnTimeout;
timer.Start();
}

public void Reset()
{
timer.Stop();
timer.Start();
}

private void OnTimeout(object sender, EventArgs arg)
{
timer.Stop();
timer.Tick -= OnTimeout;
timer.Action();
}
}

public class DispatcherTimerContainingAction : DispatcherTimer
{
public Action Action { get; set; }
}
}

Source:  https://github.com/stevenh77/PivotViewerWithItemsLoadedEvent

Monday, 3 December 2012

Application Library Caching with PRISM

Your main Shell project xap file will reference all your application dependencies with Copy Local = true, which ensures your application has the DLL available.

For each referenced library, if it has been signed and a *.extmap.xml file is found in the same directory as the DLL then a zip file will be created for it in the Web Server ClientBin folder.  If not then the DLL will be be copied directly into the Shell.xap.

All your “Module” project / xap files should reference their dependencies with Copy Local = false.  This will let Visual Studio build each module project xap files but not copy the referenced libraries into the xap.

imageFor example this is the Prism extmap.xml file:

extmap

<?xml version="1.0"?>
<manifest>
<assembly>
<name>Microsoft.Practices.Prism</name>
<version>4.1.0.0</version>
<publickeytoken>31bf3856ad364e35</publickeytoken>
<relpath>Microsoft.Practices.Prism.dll</relpath>
<extension downloadUri="Microsoft.Practices.Prism.zip" />
</assembly>
</manifest>

AppManifest.xaml


This file is contained within each xap file and lets Silverlight know whether dependencies can be found within the xap or externally.


  • The “Deployment.Parts” section contains the libraries with Copy to Local = true.

  • The “Deployment.ExternalParts” section contains libraries with Copy to Local = false.

Note:  When PRISM loads modules it does not automatically download ExternalParts, see here. Here is an example of an AppManifest.xaml file:

<Deployment xmlns="http://schemas.microsoft.com/client/2007/deployment" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" EntryPointAssembly="PivotViewerDemo" EntryPointType="PivotViewerDemo.App" RuntimeVersion="5.0.61118.0">
<Deployment.Parts>
<AssemblyPart x:Name="PivotViewerDemo" Source="PivotViewerDemo.dll" />
<AssemblyPart x:Name="System.Xml.Serialization" Source="System.Xml.Serialization.dll" />
<AssemblyPart x:Name="System.Xml.Linq" Source="System.Xml.Linq.dll" />
</Deployment.Parts>
<Deployment.ExternalParts>
<ExtensionPart Source="System.Windows.Controls.zip" />
<ExtensionPart Source="System.Windows.Controls.Pivot.zip" />
</Deployment.ExternalParts>
</Deployment>

Configuring Silverlight application projects (Xap files)


Don’t forget to switch on application library caching for all your Silverlight applications (Shell and module projects).

image

How to cache your own Silverlight libraries


Simply sign the library (Project > Properties > Signing) by creating a new key file which requires a password then manually create an extmap.xml file:


image


Where to find the extmap.xml files


If you don’t have access to the extmap.xml files just create them yourself. 


You’ll need to know the assembly version number, right click on the file in Windows Explorer, select properties then click the Details tab.


assembly version


To find out the public key token use the sn.exe (strong name) application that comes with Visual Studio:


sn


See earlier in this blog post for an example of an *.extmap.dll file.


I want to store copies of each of my references within my code base, where can I find the DLLs


While many people use Nuget nowadays you may still wish to store the DLLs files yourself.  Here is a quick lust of locations so you can find them.


The Silverlight “core” libraries do not have to be included in either xap or zip files and can always be set to Copy Local False and do not need to be stored within your code base.


image


Other Microsoft and third party libraries can be added to a libraries (libs) folder within your solution, don’t forget to include the extmap.xml files.  Once the files are installed the Microsoft SDK files can be found here:


image


PRISM libraries can be downloaded from CodePlex:  http://compositewpf.codeplex.com/


Expression libraries can be downloaded from Blend SDK, Blend Service Pack, Silverlight Tools SP1, VS 2010 SP1.

Wednesday, 28 November 2012

Telerik Silverlight GridView click events


// Single click
this.TradesGrid.SelectionChanged += TradesGridSelectionChanged;

// Double click
this.TradesGrid.AddHandler(GridViewCellBase.CellDoubleClickEvent,
new EventHandler<RadRoutedEventArgs>(OnCellDoubleClick), true);

......

void TradesGridSelectionChanged(object sender, SelectionChangeEventArgs e)
{
if (e.AddedItems.Count == 0) return;

var trade = e.AddedItems[0] as Trade;
if (trade == null) return;

var vm = DataContext as ViewModel;
if (vm == null) return;

vm.OpenTradeForDocumenting(trade);
}

private void OnCellDoubleClick(object sender, RadRoutedEventArgs e)
{
var cell = e.Source as GridViewCell;
if (cell == null) return;

var row = cell.ParentRow as GridViewRow;
if (row == null) return;

var trade = row.DataContext as Trade;
if (trade == null) return;

var vm = DataContext as ViewModel;
if (vm == null) return;

vm.OpenTradeForDocumenting(trade);
}

Tuesday, 27 November 2012

Sorting PivotViewer Silverlight5

There is a sort property on the PivotViewerProperty but that’s used to sort the filter on the left hand side of the page (see sector is sorted by quantity, then A-Z on sector).

To sort within a group you need to implement an IComparable interface on an aggregate property.  Here’s what I mean:

image

You’ll see this data is “sorted” by Sector, which actually groups but within each group the data is sorted by coupon (the big number in the middle).  Here’s how I did it:

private string sector;
public string Sector
{
get { return sector; }
set { sector = value; NotifyProperty("Sector"); }
}

private ValueSortByCoupon sectorSort;
public ValueSortByCoupon SectorSort
{
get { return sectorSort ?? (sectorSort = new ValueSortByCoupon(Sector, CouponToday)); }
}

....

public class ValueSortByCoupon : IComparable
{
private string Value { get; set; }
private decimal Coupon { get; set; }

public ValueSortByCoupon(string sector, decimal coupon)
{
this.Value = sector;
this.Coupon = coupon;
}

public int CompareTo(object rhs)
{
var other = (ValueSortByCoupon)rhs;
var result = this.Value == other.Value
? other.Coupon.CompareTo(this.Coupon)
: this.Value.CompareTo(other.Value);
return result;
}

public override string ToString()
{
return Value;
}
}
<pivot:PivotViewer>
<pivot:PivotViewer.PivotProperties>
<pivot:PivotViewerStringProperty Id="SectorSort"
DisplayName="Sector"
Options="CanFilter,CanSearchText"
Binding="{Binding SectorSort, Mode=OneWay}" />
</pivot:PivotViewer.PivotProperties>
</pivot:PivotViewer>

Source:  http://github.com/stevenh77/pivotviewerdemo