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

Monday, 1 November 2010

Silverlight ListBox Multi Select

For those who need to set the selected items (Multi Select) of the list box to a property,
you can use the following class:

     public static class ListBoxSelectedItems 
     {
         private static readonly DependencyProperty SelectedItemsBehaviorProperty =
             DependencyProperty.RegisterAttached(
                 "SelectedItemsBehavior" ,
                 typeof(SelectedItemsBehavior),
                 typeof(ListBox),
                 null);
 
         public static readonly DependencyProperty ItemsProperty = 
    DependencyProperty.RegisterAttached(
                 "Items" ,
                 typeof(IList),
                 typeof(ListBoxSelectedItems),
                 new PropertyMetadata(null, ItemsPropertyChanged));
 
         public static void SetItems(ListBox listBox, IList list) 
     {listBox.SetValue(ItemsProperty, list); }
         public static IList GetItems(ListBox listBox) 
     { return listBox.GetValue(ItemsProperty) as IList; }
 
         private static void ItemsPropertyChanged(DependencyObject d, 
    DependencyPropertyChangedEventArgs  e)
         {
             var  target = d as ListBox ;
             if  (target != null )
             {
                 GetOrCreateBehavior(target, e.NewValue as IList );
             }
         }
 
         private static SelectedItemsBehavior GetOrCreateBehavior(ListBox target, IList list)
         {
             var behavior = target.GetValue(SelectedItemsBehaviorProperty) 
        as SelectedItemsBehavior;
 
             if(behavior == null)
             {
                 behavior = new  SelectedItemsBehavior (target, list);
                 target.SetValue(SelectedItemsBehaviorProperty, behavior);
             }
 
             return behavior;
         }
     }
 
     public class SelectedItemsBehavior 
     {
         private readonly ListBox _listBox;
         private bool _isListChanging = false ;
         private bool _isSelectionChanging = false ;
         private IList _boundList;
 
         public SelectedItemsBehavior(ListBox listBox, IList boundList)
         {
             _boundList = boundList;
             _listBox = listBox;
             SetSelectedItems();
 
             // attach the selection changed event 
             _listBox.SelectionChanged += OnSelectionChanged;
 
             // attach the collection changed event 
             var collectionChanged = _boundList as INotifyCollectionChanged;
             if(collectionChanged != null )
             {
                 collectionChanged.CollectionChanged += BoundList_CollectionChanged;
             }
         }
 
         private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
         {
             // only fire this event when the list is not changing to prevent circular loop 
             if  (!_isListChanging)
             {
                 _isSelectionChanging = true;
 
                 _boundList.Clear();
 
                 foreach  (var item in _listBox.SelectedItems)
                 {
                     _boundList.Add(item);
                 }
 
                 _isSelectionChanging = false;
             }
         }
 
         private void BoundList_CollectionChanged(object sender, 
     System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
         {
             // only fire this event when the list is not changing to prevent circular loop 
             if  (!_isSelectionChanging)
             {
                 _isListChanging = true;
 
                 SetSelectedItems();
 
                 _isListChanging = false;
             }
         }
 
         private void SetSelectedItems()
         {
             if  (listBox.SelectionMode == SelectionMode .Single)
             {
                 if  (_boundList != null && _boundList.Count > 0)
                     _listBox.SelectedItem = _boundList[0];
             }
             else 
             {
                 _listBox.SelectedItems.Clear();
 
                 foreach(object item in _boundList)
                 {
                     // References in _boundList might not be the same as in _listBox.Items  
                     int  i = _listBox.Items.IndexOf(item);
                     if  (i >= 0)
                         _listBox.SelectedItems.Add(_listBox.Items[i]);
                 }
             }
         }
 
         private void ODataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
         {
             SetSelectedItems();
         }
     }
 


Monday, 25 October 2010

Silverlight Toolkit's TreeView SelectedItem problem.

For those who are trying to bind the TreeView's SelectedItem, you will quickly realise that it is a readonly property, which means you cannot bind that property as you would with a ComboBox for example.

One solution to that problem is to wrap each TreeView object in a class that exposes two properties, IsSelected and IsExpanded, which is subsequently binded to the TreeViewItem's IsSelected and IsExpanded properties respectively, and is easily implemented in WPF. Because in Silverlight, you cannot bind the property via the Setter property like WPF, one workaround is to extend the TreeView control and override the GetContainerForItemOverride() method to hardcode the IsSelected and IsExpanded bindings manually as seen in this blog here, which also provides good examples on how to expand all nodes, collapse all nodes etc...

The other solution is to create a simple attached property that you can bind to, which in my view, is a cleaner approach. This way you can bind it to a single object as the selected item without wrapping the view models with extra properties (IsSelected, IsExpanded) and managing them.
This idea is gotten from this blog here, which shows you how you can bind the selectedItem property from the ViewModel to the view, with some performance hit. I simply extended it further to include the binding from the View to the ViewModel so that I do not need to use any code behind, by adding a behavior to attach the selected item changed event to it. Use it like so (Omit AllowSelectedItemBinding if you only require one way binding aka. ViewModel to View) :-

local:TreeViewSelectedItem.SelectedItem="{Binding SelectedObjectToBind}" 
local:TreeViewSelectedItem.AllowSelectedItemBinding="True">
 using  System;
 using  System.Net;
 using  System.Windows;
 using  System.Windows.Controls;
 using  System.Windows.Documents;
 using  System.Windows.Ink;
 using  System.Windows.Input;
 using  System.Windows.Media;
 using  System.Windows.Media.Animation;
 using  System.Windows.Shapes;
 using  System.Collections;
 using  System.Collections.Generic;
 using  System.Collections.ObjectModel;
 using  System.Windows.Controls.Primitives;
 using  System.Collections.Specialized;
 
 namespace  Shared
 {
     public static class TreeViewSelectedItem 
     {
         /// <summary> 
         /// Enable selected item binding 
         /// </summary> 
         public static readonly DependencyProperty SelectedItemProperty =
                DependencyProperty.RegisterAttached("SelectedItem" , 
                 typeof(object), typeof(TreeViewSelectedItem),
                new PropertyMetadata(null, OnSelectedItemChanged));
 
         /// <summary> 
         /// If this value is not set, it will only allow one way binding from view model to ui 
         /// on the selected item 
         /// </summary> 
         public  static  DependencyProperty  AllowSelectedItemBindingProperty =
             DependencyProperty.RegisterAttached("AllowSelectedItemBinding" , 
                 typeof(bool), typeof(TreeViewSelectedItem ),
             new  PropertyMetadata (false, OnAllowSelectedItemBindingChanged));
 
         public static bool GetAllowSelectedItemBinding
             (FrameworkElement e)
         {
             return (bool)e.GetValue(AllowSelectedItemBindingProperty);
         }
 
         public static void SetAllowSelectedItemBinding(
             FrameworkElement e, bool value)
         {
             e.SetValue(AllowSelectedItemBindingProperty, value);
         }
 
         public static void SetSelectedItem(DependencyObject o, object propertyValue)
         {
             o.SetValue(SelectedItemProperty, propertyValue);
         }
 
         public static object  GetSelectedItem(DependencyObject o)
         {
             return o.GetValue(SelectedItemProperty);
         }
 
         private static void OnAllowSelectedItemBindingChanged(DependencyObject d, 
             DependencyPropertyChangedEventArgs e)
         {
             TreeView treeView = d as TreeView;
             if  (treeView == null)
             {
                 return;
             }
 
             CreateBehavior(treeView, (bool)e.NewValue);
         }
 
         private  static  void  OnSelectedItemChanged(DependencyObject d, 
             DependencyPropertyChangedEventArgs e)
         {
             TreeView treeView = d as TreeView;
             if  (treeView == null )
             {
                 return;
             }
 
             TreeViewItem item = 
                 treeView.ItemContainerGenerator.ContainerFromItem(e.NewValue) 
                     as TreeViewItem;
             if  (item != null)
             {
                 item.IsSelected = true;
                 item.UpdateLayout();
                 return;
             }
 
             for  (int  i = 0; i< treeView.Items.Count; i++)
             {
                 SelectItem(e.NewValue, 
                     treeView.ItemContainerGenerator.ContainerFromIndex(i) 
                         as  TreeViewItem);
             }
         }
 
         private static void SelectItem(object o, TreeViewItem parent)
         {
             if(parent == null)
             {
                 return ;
             }
 
             bool isExpanded = parent.IsExpanded;
             if(!isExpanded)
             {
                 parent.IsExpanded = true;
                 parent.UpdateLayout();
             }
 
             TreeViewItem item = 
                 parent.ItemContainerGenerator.ContainerFromItem(o) 
                     as TreeViewItem;
             if(item != null)
             {
                 item.IsSelected = true;
                 return;
             }
 
             for  (int i = 0; i< parent.Items.Count; i++)
             {
                 SelectItem(o, 
                     parent.ItemContainerGenerator.ContainerFromIndex(i) 
                         as TreeViewItem);
             }
 
             if(parent.IsExpanded != isExpanded)
             {
                 parent.IsExpanded = isExpanded;
             }
         }
 
         private static void CreateBehavior(TreeView target, bool attach)
         {
             if(attach)
             {
                 if(target != null)
                 {
                     target.SelectedItemChanged -= _treeView_SelectedItemChanged;
                     target.SelectedItemChanged += _treeView_SelectedItemChanged;
                 }
             }
             else  // detach 
             {
                 if(target != null)
                     target.SelectedItemChanged -= _treeView_SelectedItemChanged;
             }
         }
 
         private static void  _treeView_SelectedItemChanged(object d, 
             RoutedPropertyChangedEventArgs <object > e)
         {
             TreeView treeView = d as TreeView;
             treeView.SetValue(TreeViewSelectedItem.SelectedItemProperty, e.NewValue);
         }
     }
 }

Tuesday, 16 March 2010

Cross Domain Policy File Usage Recommendations for Silverlight 3

Silverlight allows you to make web service calls to web services that are a part of the same website with no restrictions, but by default does not allow cross-domain calls. In order for a Silverlight app coming from one domain to be able to consume data from services in a different domain, the service must allow the app to do so by providing a policy file which grants access. When calling a cross-domain service, Silverlight will check the existence of the ClientAccessPolicy.xml file. This is a format defined by Silverlight and provides a way to define who can access what services. If it is not found, it will then default to look for the CrossDomain.xml policy file, which is the default file implemented for Adobe Flash.

Note: ClientAccessPolicy.xml is more feature rich, but CrossDomain.xml should be used if you want to give access to both Silverlight and Flash applications in one step. It is not necessary to have both files together.

Silverlight 3 currently supports the following WCF Service bindings:

1) customBinding

2) basicHttpBinding

3) pollingDuplexHttpBinding

Policy File Location

The policy file must be located at the root of the “domain” (hostname + port), so if your service is located at http://my.service.com:8000/Service/SomeService.svc/Endpoint, the policy file must be located at http://my.service.com:8000/ClientAccessPolicy.xml.

For IIS-hosted service, simply put the static policy files (ClientAccessPolicy.xml and/or CrossDomain.xml) in the root directory of the service (for example, in the c:\inetpub\wwwroot directory of an IIS web server).

For Self-hosted WCF service, you will have to have your WCF service host serve up the policy file on demand. Here’s an example:

1) First, create a service contract that can serve up the XML content:

[ServiceContract]
public interface IClientAccessPolicy
{
[OperationContract]
[WebGet(UriTemplate = "/ClientAccessPolicy.xml")]
XElement GetClientAccessPolicy();
}

2) Secondly, create a base service class to implement the IClientAccessPolicy interface like so:

public class BaseWebService: IClientAccessPolicy
{
public XElement GetClientAccessPolicy()
{
return new XElement("access-policy",
new XElement("cross-domain-access",
new XElement("policy",
new XElement("allow-from",
new XAttribute("http-request-headers", "*"),
new XElement("domain",
new XAttribute("uri", "*"))),
new XElement("grant-to",
new XElement("resource",
new XAttribute("path", "/"),
new XAttribute("include-subpaths", "true"))))));
}
}

3) Lastly, on the host application:

Uri[] addresses = new Uri[] { "www.mydomain.com" };
var host = new ServiceHost(typeof(SomeService), addresses);
var endpoint = host.AddServiceEndpoint(typeof(IClientAccessPolicy),
new WebHttpBinding(), string.Empty);
endpoint.Behaviors.Add(new WebHttpBehavior());
var smb = new ServiceMetadataBehavior();
smb.HttpGetEnabled = true;
host.Description.Behaviors.Add(smb);

Policy Files

1) ClientAccessPolicy.xml (unlimited cross site access)

<?xml version="1.0" encoding="utf-8"?>

<access-policy>

<cross-domain-access>

<policy>

<allow-from http-request-headers="*">

<domain uri="*"/>

</allow-from>

<grant-to>

<resource include-subpaths="true" path="/"/>

</grant-to>

</policy>

</cross-domain-access>

</access-policy>

You can limit access to the service for a specific domain by providing a URI value like so:

<allow-from http-request-headers="*">

<domain uri="http://www.somedomainalpha.com" />

<domain uri="http://www. somedomainbeta.com" />

</allow-from>

You can use wildcards in the domain names to allow sub-domains. E.g., *.somedomain.com allows requests from mail.somedomain.com, admin.somedomain.com, and so on. You can also limit access to a specific service on the domain by specifying the resource path as follows:

<grant-to>

<resource path="/service/" include-subpaths="true"/>

</grant-to>

2) CrossDomain.xml

<?xml version="1.0"?>

<!DOCTYPE cross-domain-policy SYSTEM

"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">

<cross-domain-policy>

<allow-http-request-headers-from domain="*" headers="*"/>

</cross-domain-policy>

Piped Converters for Silverlight

When working with Silverlight, there will be instances where you will require more than one converter to convert a binding item. As Silverlight supports only a single converter per binding object, what do we do? The solution? Simply create a converter to wrap all other converters in sequence, with the help of Attributes decorations, so that it will call each converter top-down when calling Convert, and bottom-up when calling ConvertBack.

Please note: The follow code is my slight modification (for Silverlight) from the article by Josh Smith located at http://www.codeproject.com/KB/WPF/PipingValueConverters_WPF.aspx which is the solution implemented for WPF. Please have a read through that link to get a better understanding of what this class should be doing

Create two classes:

The Attribute Class

using System;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Ink;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

namespace SilverlightClassLibrary1

{

/// <summary>

/// This attribute is used to decorate the Converters for use with

/// PipeConverter

/// so that more than one converters can be called at anyone time.

/// </summary>

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]

public sealed class PipedConverterAttribute : Attribute

{

// Fields

private Type _parameterType;

private Type _sourceType;

private Type _targetType;

// Methods

public PipedConverterAttribute(Type sourceType,

Type targetType)

{

if (sourceType == null)

{

throw new ArgumentNullException("sourceType");

}

if (targetType == null)

{

throw new ArgumentNullException("targetType");

}

this._sourceType = sourceType;

this._targetType = targetType;

}

public override int GetHashCode()

{

return (this._sourceType.GetHashCode() +

this._targetType.GetHashCode());

}

// Properties

public Type ParameterType

{

get

{

return this._parameterType;

}

set

{

this._parameterType = value;

}

}

public Type SourceType

{

get

{

return this._sourceType;

}

}

public Type TargetType

{

get

{

return this._targetType;

}

}

}

}

The Actual PipedConverter:

using System;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Ink;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

using System.Windows.Data;

using System.Collections.Specialized;

using System.Collections;

using System.Collections.Generic;

using System.Collections.ObjectModel;

namespace SilverlightClassLibrary1

{

/// <summary>

/// This is used to group a list of converters together,

/// calling them in sequence.

/// Eg. If you have three items, the targetType of the

/// firstItem must match the sourceType

/// of the secondItem and so forth.

/// </summary>

[System.Windows.Markup.ContentProperty("Converters")]

public class PipedConverter : IValueConverter

{

#region Backing Fields

private readonly ObservableCollection<IValueConverter>

_converters = new ObservableCollection<IValueConverter>();

private readonly Dictionary<IValueConverter,

PipedConverterAttribute> _cachedAttributes

= new Dictionary<IValueConverter,

PipedConverterAttribute>();

#endregion // Data

#region Properties (Converters)

/// <summary>

/// Returns the list of IValueConverters contained

/// in this converter.

/// </summary>

public ObservableCollection<IValueConverter> Converters

{

get { return this._converters; }

}

#endregion // Converters

#region Constructor

public PipedConverter()

{

this._converters.CollectionChanged +=

this.OnConvertersCollectionChanged;

}

#endregion // Constructor

#region IValueConverter Members

object IValueConverter.Convert(object value, Type targetType,

object parameter, System.Globalization.CultureInfo culture)

{

object output = value;

for (int i = 0; i < this.Converters.Count; ++i)

{

IValueConverter converter = this.Converters[i];

Type currentTargetType =

this.GetTargetType(i, targetType, true);

output = converter.Convert(output,

currentTargetType, parameter, culture);

// If the converter didnt convert as expected then

// the binding operation should terminate.

if (output == DependencyProperty.UnsetValue)

break;

}

return output;

}

object IValueConverter.ConvertBack(object value,

Type targetType, object parameter,

System.Globalization.CultureInfo culture)

{

object output = value;

for (int i = this.Converters.Count - 1; i > -1; --i)

{

IValueConverter converter = this.Converters[i];

Type currentTargetType = this.GetTargetType(i,

targetType, false);

output = converter.ConvertBack(output,

currentTargetType, parameter, culture);

// If the converter didn’t convert as expected

// then the binding operation should terminate.

if (output == DependencyProperty.UnsetValue)

break;

}

return output;

}

#endregion

#region GetTargetType

/// <summary>

/// Returns the target type for a conversion operation.

/// </summary>

/// <param name="converterIndex">The index of the

/// current converter about to be executed.</param>

/// <param name="finalTargetType">The 'targetType'

/// argument passed into the conversion method.</param>

/// <param name="convert">Pass true if calling from the Convert

/// method, or false if calling from ConvertBack.</param>

protected virtual Type GetTargetType(int converterIndex,

Type finalTargetType, bool convert)

{

// If the current converter is not the last/first in the

// list, get a reference to the next/previous converter.

IValueConverter nextConverter = null;

if (convert) // Always forward until last item

{

if (converterIndex < this.Converters.Count - 1)

{

nextConverter =

this.Converters[converterIndex + 1];

if (nextConverter == null)

throw new InvalidOperationException();

}

}

else // Always going backwards until first item

{

if (converterIndex > 0)

{

nextConverter =

this.Converters[converterIndex - 1];

if (nextConverter == null)

throw new InvalidOperationException();

}

}

if (nextConverter != null)

{

// Get the attributes of the next converter

// and return the type

PipedConverterAttribute conversionAttribute =

_cachedAttributes[nextConverter];

// If the Convert method is going to be called,

// we need to use the SourceType of the next

// converter in the list. If ConvertBack is called,

// use the TargetType.

return convert ? conversionAttribute.SourceType :

conversionAttribute.TargetType;

}

// If the current converter is the last one to be executed

// return the target type passed into the conversion method.

return finalTargetType;

}

#endregion // GetTargetType

#region OnConvertersCollectionChanged

void OnConvertersCollectionChanged(object sender,

NotifyCollectionChangedEventArgs e)

{

// The 'Converters' collection has been modified,

// so validate that each value converter it now

// contains is decorated with ConverterAttribute and

// then cache the attribute value.

IList convertersToProcess = null;

if (e.Action == NotifyCollectionChangedAction.Add ||

e.Action == NotifyCollectionChangedAction.Replace)

{

convertersToProcess = e.NewItems;

}

else if (e.Action == NotifyCollectionChangedAction.Remove)

{

foreach (IValueConverter converter in e.OldItems)

this._cachedAttributes.Remove(converter);

}

else if (e.Action == NotifyCollectionChangedAction.Reset)

{

this._cachedAttributes.Clear();

convertersToProcess = this._converters;

}

if (convertersToProcess != null

&& convertersToProcess.Count > 0)

{

foreach (IValueConverter converter in convertersToProcess)

{

object[] attributes = converter.GetType()

.GetCustomAttributes(

typeof(PipedConverterAttribute), false);

if (attributes.Length != 1)

throw new InvalidOperationException();

this._cachedAttributes.Add(converter,

attributes[0]

as PipedConverterAttribute);

}

}

}

#endregion

}

}

Use it as such in your XAML resource file: (converter is the namespace of the PipedConverter)

<converter:PipedConverter x:Key="pipedConverter">
<converter:ConverterA />
<converter:ConverterB />
</converter:PipedConverter >
Also do decorate your converters like so: 

[PipedConverterAttribute(typeof(string), typeof(int))]

public class ConverterA : IValueConverter

Where string is the input type, and integer is the output type of the converter.

Tuesday, 20 October 2009

Visual Studio 2010 & .Net 4 Beta 2 is Out

Visual Studio 2010 is out and will be made available for download starting Wednesday, and immediately for MSDN Subscriber.

As per Scott Gu's blog, here is an overview of the product lineup specifications:

VS 2010 Product Line SKU Simplifications

With VS 2010 we are simplifying the product lineup and pricing options of Visual Studio, as well as adding new benefits for MSDN subscribers. With VS 2010 we will now ship a simpler set of SKU options:

  • Visual Studio Express: Free Express SKUs for Web, VB, C#, and C++
  • Visual Studio 2010 Professional with MSDN: Professional development tools as you are used to today with the addition of source control integration, bug tracking, build automation, and more. It also includes 50 hours/month of Azure cloud computing.
  • Visual Studio 2010 Premium with MSDN: Premium has everything in Professional plus advanced development tools (including richer profiling and debugging, code coverage, code analysis and testing prioritization), advanced database support, UI testing, and more. Rather than buying multiple “Team” SKUs like you would with VS 2008, you can now get this combination of features in one box with VS 2010. It also includes 100 hours/month of Azure cloud computing.
  • Visual Studio 2010 Ultimate with MSDN: Ultimate has everything in Premium plus additional advanced features for developers, testers, and architects including features like Intellitrace (formerly Historical Debugging), the new architecture tools (UML, discovery), test lab management, etc. It also includes 250 hours/month of Azure cloud computing.
Additionally, Silverlight Toolkit October 2009 has be released with main changes for better Visual Studio 2010 integration. Click here to get it http://silverlight.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=30514#ReleaseFiles