Thursday, 20 August 2009

Silverlight Application – Access to Server side variables

The question was asked in the Silvelright.net forum how can the Silverlight application get the user IP address. One of the ways this can be done trough the Silverlight initParams.

Here is a working example that gives the application copy of the REMOTE_ADDR and HTTP_USER_AGNET proeprties from the Request.ServerVariables on the server.

To pass parameters to the Silverlight application a new parameter with name initialParams need to be added to the object that hosts the application:

<param name="initParams" value='ip=<%=(Request.ServerVariables["REMOTE_ADDR"])%>,agent=<%=(Request.ServerVariables["HTTP_USER_AGENT"])%>' />



Practically any parameter can be made available. Each parameter is passed as “key”=”value” pair and the parameters are separated by a comma (“,”). The only issue that needs to be handled is when the data contains a comma – then it needs to be escaped.



Here is a working example code (testpage.aspx):



<%@ Page Language="C#" AutoEventWireup="true" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<
html xmlns="http://www.w3.org/1999/xhtml" >
<
head runat="server">
<
title>silverlight.net</title>
<
style type="text/css">
html, body {
height: 100%;
overflow: auto;
}
body {
padding: 0;
margin: 0;
}
#silverlightControlHost {
height: 100%;
text-align:center;
}
</style>
<
script type="text/javascript" src="Silverlight.js"></script>
<
script type="text/javascript">
function
onSilverlightError(sender, args) {
var appSource = "";
if (sender != null && sender != 0) {
appSource = sender.getHost().Source;
}

var errorType = args.ErrorType;
var iErrorCode = args.ErrorCode;

if (errorType == "ImageError" || errorType == "MediaError") {
return;
}

var errMsg = "Unhandled Error in Silverlight Application " + appSource + "\n" ;

errMsg += "Code: "+ iErrorCode + " \n";
errMsg += "Category: " + errorType + " \n";
errMsg += "Message: " + args.ErrorMessage + " \n";

if (errorType == "ParserError") {
errMsg += "File: " + args.xamlFile + " \n";
errMsg += "Line: " + args.lineNumber + " \n";
errMsg += "Position: " + args.charPosition + " \n";
}
else if (errorType == "RuntimeError") {
if (args.lineNumber != 0) {
errMsg += "Line: " + args.lineNumber + " \n";
errMsg += "Position: " + args.charPosition + " \n";
}
errMsg += "MethodName: " + args.methodName + " \n";
}

throw new Error(errMsg);
}
</script>
</
head>
<
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/silverlight.net.xap"/>
<
param name="onError" value="onSilverlightError" />
<
param name="background" value="white" />
<
param name="minRuntimeVersion" value="3.0.40624.0" />
<
param name="autoUpgrade" value="true" />
<
param name="initParams" value='ip=<%=(Request.ServerVariables["REMOTE_ADDR"])%>,agent=<%=(Request.ServerVariables["HTTP_USER_AGENT"])%>' />
<
a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=3.0.40624.0" style="text-decoration:none">
<
img src="http://go.microsoft.com/fwlink/?LinkId=108181" 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>



Here is the Silverlight Application code (App.xaml.cs) :



using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace silverlight.net
{
public partial class App : Application
{

public App()
{
this.Startup += this.Application_Startup;
this.Exit += this.Application_Exit;
this.UnhandledException += this.Application_UnhandledException;

InitializeComponent();
}

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 UserIPAddress();
}

private void Application_Exit(object sender, EventArgs e)
{

}
private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
// If the app is running outside of the debugger then report the exception using
// the browser's exception mechanism. On IE this will display it a yellow alert
// icon in the status bar and Firefox will display a script error.
if (!System.Diagnostics.Debugger.IsAttached)
{

// NOTE: This will allow the application to continue running after an exception has been thrown
// but not handled.
// For production applications this error handling should be replaced with something that will
// report the error to the website and stop the application.
e.Handled = true;
Deployment.Current.Dispatcher.BeginInvoke(delegate { ReportErrorToDOM(e); });
}
}
private void ReportErrorToDOM(ApplicationUnhandledExceptionEventArgs e)
{
try
{
string errorMsg = e.ExceptionObject.Message + e.ExceptionObject.StackTrace;
errorMsg = errorMsg.Replace('"', '\'').Replace("\r\n", @"\n");

System.Windows.Browser.HtmlPage.Window.Eval("throw new Error(\"Unhandled Error in Silverlight Application " + errorMsg + "\");");
}
catch (Exception)
{
}
}
}
}



Here is the Page XAML (UserIPAddress.xaml):



<navigation:Page x:Class="silverlight.net.UserIPAddress" 
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"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
d:DesignWidth="640" d:DesignHeight="480"
Title="UserIPAddress Page">
<
Grid x:Name="LayoutRoot">
<
StackPanel>
<
TextBlock x:Name="IpAddress"></TextBlock>
<
TextBlock x:Name="UserAgent"></TextBlock>
</
StackPanel>
</
Grid>
</
navigation:Page>



Here is the Silverlight Page codebehind  (UserIPAddress.xaml.cs):



using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Navigation;

namespace silverlight.net
{
public partial class UserIPAddress : Page
{
public System.String ClinetIP;
public UserIPAddress()
{
InitializeComponent();

if (App.Current.Resources.Contains("ip"))
IpAddress.Text = (System.String)App.Current.Resources["ip"];

if (App.Current.Resources.Contains("agent"))
UserAgent.Text = (System.String)App.Current.Resources["agent"];
}
}
}



I hope this helps!



Tzanimir

Thursday, 13 August 2009

Silverlight TreeView – Binding ItemContainerStyle properties to DataContext

If you are doing work with TreeView in Silverlight you most likely already know that you cannot natively do Binding for ItemContainerStyle properties like IsSelected or Visibility. Recent question in silverlight.net forum, at first confused me – this is no problem in WPF, and then made me spend couple of hours finding a “workaround”. So here is one way to do this in Silverlight (in WPF this is not needed as native Bidning is supported)

The workaround is based around the ContainerFromItem method of the ItemContainerGenerator for the TreeView (or any other ItemsControl).  What the example below does is to create a test data Structure that consists of DataItem objects and apply it to the DataContext of the TreeView trough a object called DataContainer.

The “Binding” is fired trough the Setter method of the Binded Property (IsVisible or IsExpanded) in the data structure. What the setter needs to know is a reference to the ItemsControl (the TreeView in this case) in order to use the ContainerFromItem to get reference to the actual container for the Item. This is information can be passed to the DataItem in multiple ways, but in the example this is done trough a Method of the DataContainer that updates all DataItems with the necessary reference to the TreeView.

The handling of the LayoutUpdated event handler of the TreeView is used for Initial setting of the properties as passed.

The rest is pretty simple. Hese is fully working example- the Xaml:

<navigation:Page x:Class="silverlight.net.TreeViewBinding" 
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"
mc:Ignorable="d"
xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:Windows="clr-namespace:System.Windows;assembly=System.Windows.Controls"
d:DesignWidth="640" d:DesignHeight="480"
Title="TreeViewBinding Page">
<
UserControl.Resources>
<
Windows:HierarchicalDataTemplate x:Key="ItemDataTemplate" ItemsSource="{Binding Children}">
<
TextBlock Text="{Binding Name}"></TextBlock>
</
Windows:HierarchicalDataTemplate>
</
UserControl.Resources>
<
Grid x:Name="LayoutRoot">
<
StackPanel>
<
Button Content="Togle Item 2 Visibility" Click="Button_Toggle_Visibility" HorizontalAlignment="Left" VerticalAlignment="Top"></Button>
<
Button Content="Togle Item 2 IsExpanded" Click="Button_Toggle_IsExpanded" HorizontalAlignment="Left" VerticalAlignment="Top"></Button>
<
Controls:TreeView x:Name="TestTreeView" ItemTemplate="{StaticResource ItemDataTemplate}" ItemsSource="{Binding Children}">
</
Controls:TreeView>
</
StackPanel>
</
Grid>
</
navigation:Page>


And here is the Code behind:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Navigation;

namespace silverlight.net
{
public partial class TreeViewBinding : Page
{
DataContainer dataContainer = new DataContainer();

public TreeViewBinding()
{
InitializeComponent();
// Create some test Data
DataItem item1 = new DataItem() { Name = "Test1" };
DataItem item2 = new DataItem() { Name = "Test2" };
DataItem item2_1 = new DataItem() { Name = "Test2.1" };
DataItem item2_2 = new DataItem() { Name = "Test2.2" };
item2.Children.Add(item2_1);
item2.Children.Add(item2_2);

DataItem item3 = new DataItem() { Name = "Test3" };

dataContainer.Children.Add(item1);
dataContainer.Children.Add(item2);
dataContainer.Children.Add(item3);

dataContainer.ItemsControl = TestTreeView;

TestTreeView.DataContext = dataContainer;
TestTreeView.LayoutUpdated += new EventHandler(treeMatrix_LayoutUpdated);
}

void treeMatrix_LayoutUpdated(object sender, EventArgs e)
{
dataContainer.UpdateAdditionalBindings();
}


private void Button_Toggle_Visibility(object sender, RoutedEventArgs e)
{
if (dataContainer.Children[1].IsVisible)
dataContainer.Children[1].IsVisible = false;
else
dataContainer.Children[1].IsVisible = true;
}


private void Button_Toggle_IsExpanded(object sender, RoutedEventArgs e)
{
if (dataContainer.Children[1].IsExpanded)
dataContainer.Children[1].IsExpanded = false;
else
dataContainer.Children[1].IsExpanded = true;
}
}
public class DataContainer
{
public System.Boolean UpdateLayout = true;
public List<DataItem> Children {get; set;}

// Reference to the Items control
private ItemsControl _ItemsControl;
public ItemsControl ItemsControl
{
get { return _ItemsControl; }
set
{
_ItemsControl = value;

// Propagade the Items control to the children
foreach (DataItem data_item in Children)
{
data_item.ItemsControl = value;
}
}
}

public void UpdateAdditionalBindings()
{
foreach (DataItem item in Children)
item.UpdateAdditionalBindings();
}

public DataContainer()
{
Children = new List<DataItem>();
}
}

public class DataItem
{
// Reference to the Items control
private ItemsControl _ItemsControl;
public ItemsControl ItemsControl
{
get { return _ItemsControl; }
set
{
_ItemsControl = value;

// Propagade the Items control to the children
foreach (DataItem data_item in Children)
{
data_item.ItemsControl = value;
}
}
}

public List<DataItem> Children {get; set;}

public override string ToString()
{
return Name;
}

public System.String Name { get; set; }

private System.Boolean _IsVisible = true;
public System.Boolean IsVisible
{
get { return _IsVisible; }
set
{
if (_IsVisible != value)
UpdateVisibility(value);

_IsVisible = value;
}
}

private System.Boolean _IsExpanded = true;
public System.Boolean IsExpanded
{
get { return _IsExpanded; }
set
{
if (_IsExpanded != value)
UpdateIsExpanded(value);

_IsExpanded = value;
}
}

public void UpdateAdditionalBindings()
{
// Here update all properties
this.UpdateVisibility(_IsVisible);
this.UpdateIsExpanded(_IsExpanded);

foreach (DataItem dataItem in Children)
dataItem.UpdateAdditionalBindings();
}

private void UpdateVisibility(System.Boolean isVisible)
{
if (ItemsControl == null) return;

UIElement container = (UIElement)ItemsControl.ItemContainerGenerator.ContainerFromItem(this);
if (container != null)
{
if (isVisible)
container.SetValue(UIElement.VisibilityProperty, Visibility.Visible);
else
container.SetValue(UIElement.VisibilityProperty, Visibility.Collapsed);
}
}
private void UpdateIsExpanded(System.Boolean isExpanded)
{
if (ItemsControl == null) return;

TreeViewItem container = (TreeViewItem)ItemsControl.ItemContainerGenerator.ContainerFromItem(this);
if (container != null)
{
container.SetValue(TreeViewItem.IsExpandedProperty, isExpanded);
}
}

public DataItem()
{
Children = new List<DataItem>();
}
}
}




I hope this helps you !



Tzanimir

Friday, 7 August 2009

WPF TreeView – Different DataTemplate for Different Data Types

Have you wondered how you can show or edit different types of data using different DataTemplates in a TreeView? It is actually pretty easy – here is a way to do it:

The key is simple DataType="{x:Type System:String}" attribute for the DataTemplate which identifies for which Type will the DataTemplate be used. Here the System: is just a namespace import and String is the type:

xmlns:System="clr-namespace:System;assembly=mscorlib"
Here is the complete Example that shows data of types String, Int32 and Double in different colors and a note of the type before the value:


<Window x:Class="Blog.TreeViewExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation%22
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml%22
xmlns:System="clr-namespace:System;assembly=mscorlib"
Title="TreeViewTest" Height="300" Width="300">

<Grid>
<TreeView x:Name="TreeView" ItemsSource="{Binding}">
<TreeView.Resources>
<DataTemplate DataType="{x:Type System:String}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="(System.String): "></TextBlock>
<TextBlock Foreground="Blue" FontWeight="Bold" Text="{Binding}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type System:Double}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="(System.Double): "></TextBlock>
<TextBlock Foreground="Green" FontWeight="Bold" Text="{Binding}" />
</StackPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type System:Int32}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="(System.Int32): "></TextBlock>
<TextBlock Foreground="Red" FontWeight="Bold" Text="{Binding}" />
</StackPanel>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
</Window>


And the Code behind:


namespace Blog
{
/// <summary>
///
Interaction logic for TreeViewTest.xaml
/// </summary>
public partial class TreeViewExample : Window
{
public TreeViewExample()
{
InitializeComponent();

// Some sample data with different Types
TreeView.DataContext = new List<System.Object> {
(System.String) "Item1",
(System.Int32) 1,
(System.Double) 2.0 };
}
}
}
Enjoy ! Tzanimir