Wednesday, August 11, 2010

SilverBlight

As a first test to the Silverlight I decide to do a simple label + button with a message box popping.
It took a short while to figure that using the “mbox” snippet won’t work (actually because the message box is not in “Forms”). The resulting line of code is:

System.Windows.MessageBox.Show("XXX");

It can be used with caption and buttons parameters, but that’s for a later usage (maybe).
Next step run the project. It appears that the “run-time” environment for debugging is not installed automatically with the SDK, god help M$... Another download, a quick installation and it was over.
Page loads. Page is shown, button is shown, label is gone. Huh???
I repeated the process with different styling, to no result.
Once I added the setting of its content to the “Loaded” of either Layout or the label itself it worked:

private void label1_Loaded(object sender, RoutedEventArgs e)
{

label1.Content = "ZZZ";

}


There was no need to do any styling, just insert the content.
Next, let’s have some fun. Adding a label to the form on the click, assign a function that moves it around on mouse enter.

private void button1_Click(object sender, RoutedEventArgs e)
{

Label x = new Label();

x.Content = "XXXX";

x.FontSize = 30;

x.VerticalAlignment = System.Windows.VerticalAlignment.Top;

x.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;

x.Foreground = new SolidColorBrush(Color.FromArgb(255, 235, 24, 24));

x.Margin = new Thickness(30, 40, 0, 0);

x.MouseEnter += new MouseEventHandler(x_MouseEnter);

LayoutRoot.Children.Add(x);

}


And the events function

void x_MouseEnter(object sender, MouseEventArgs e)
{

if (sender is Label)

{

Label x = ((Label)sender);

double left = x.Margin.Left;

double top = x.Margin.Top;

if (left < 245)

{

left += 10;

} else {

left = 0;

}

top += 10;

x.Margin = new Thickness(left, top, x.Margin.Right, x.Margin.Bottom);

x.Foreground = new SolidColorBrush(Color.FromArgb(255, System.Convert.ToByte(left), 24, 24));

}

}


Nice and easy. Next step put a grid, and fill it with data from an SQL server table.
Hmmmm… That’s not possible directly in Silverlight.
Datagrid component exists in the toolbox. The problem is with the binding. So I used a WCF as data delegate:

private void button1_Click(object sender, RoutedEventArgs e)
{

label1.Content = "Loading...";

svc.GetAccountsCompleted += new
EventHandler(svc_GetAccountsCompleted);

svc.GetAccountsAsync();

}


Note: the call is Asynchronous. There’s no synchronous method for Silverlight WCF.
And the “Completed” method:

void svc_GetAccountsCompleted(object sender, ServiceReference1.GetAccountsCompletedEventArgs e)
{

label1.Content = "Done";

dataGrid1.ItemsSource = e.Result;

}


The WCF (declared on the supporting site) has:

[OperationContract]
public System.Collections.Generic.List GetAccounts()

{

DataClasses1DataContext db = new DataClasses1DataContext();

System.Collections.Generic.List res = new System.Collections.Generic.List();

var tbl = from x in db.TBL_ACCOUNTs where !x.isDeleted select x;

foreach (TBL_ACCOUNT item in tbl.Take(100))

{

res.Add(new AccountClass(item.accountID, item.accountName));

}

return res;

}


public class AccountClass
{

public int AccountID { get; set; }

public string AccountName { get; set; }


public AccountClass(int ID, string Name)
{

AccountID = ID;

AccountName = Name;

}

}


I used LINQ in order to live the DB without junk procedures. For real life DON’T USE IT! It’s by far slower and as might be understood from the code – resource waster.

Last test was multi-threading. Update 3 labels from 3 different threads, non of them is the main thread.
My first version was something like this:

private void button2_Click(object sender, RoutedEventArgs e)
{

System.Threading.Thread th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label1 });

th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label2 });

th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label3 });

}


public void DoThread(object bag)

{

int count = 0;

object[] real_bag = (object[])bag;

Label lbl = (Label)real_bag[0];

while (true)

{

count++;

lbl.Content = count.ToString();

System.Threading.Thread.Sleep(1000);

}

}

It didn’t work (actually I was surprised it compiled…) with a very good reason it’s not thread-safe to manipulate the UI from inside a thread. The problem was that it just terminated the thread without any notice. Thread is alive, poof, no thread exists.

After digging a bit, and adding some beatification the result is:

private static Random r = new Random();
private bool _done = true;


private void button2_Click(object sender, RoutedEventArgs e)
{

if (!_done)

{

_done = true;

}

else

{

StartThreads();

}

}


private void StartThreads()

{

_done = false;

System.Threading.Thread th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label1 });

th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label2 });

th = new System.Threading.Thread(DoThread);

th.Start(new object[] { label3 });

}

public void DoThread(object bag)

{

int count = 0;

object[] real_bag = (object[])bag;

Label lbl = (Label)real_bag[0];

while (!_done)

{

count++;

LayoutRoot.Dispatcher.BeginInvoke(delegate()

{

lbl.Content = count.ToString();

});

System.Threading.Thread.Sleep(r.Next(1000));

}

}


Actually the most important thing in here is the LayoutRoot.Dispatcher.BeginInvoke. That’s the magic command that tells the CLI: use the main thread.

Monday, August 9, 2010

Embedding ASCX web control in a dll

Implementing ascx in a dll is not as simple as it should.
I began by following the steps suggested in this post http://www.timgittos.com/compile-a-user-control-as-a-dll, it partially worked.
I’ve embedded the ascx as resource (build action), deleted the AutoEventWireup and declared all the components. When I tried to access it from the actual website – NOTHING. The component does not exist.
Searching back and forth I’ve found that the namespace is a MUST. If you omit it, it doesn’t show.
The resulting code is something like

namespace MyControls
{

public partial class MyControls : System.Web.UI.UserControl

{

HiddenField hfStatus;

protected void Page_Load(object sender, EventArgs e)

{

String content = String.Empty;

System.IO.Stream stream = System.Reflection.Assembly.
GetExecutingAssembly().GetManifestResourceStream(GetType(), "MyControl.ascx");
using (System.IO.StreamReader reader = new System.IO.StreamReader(stream)
)
{

content = reader.ReadToEnd();

}

Control control = Page.ParseControl(content);

this.Controls.Add(control);

hfStatus = ((HiddenField)control.
FindControl("Status"));
}

}

}


So far so good, but, I had to add a javascript file into the pot.
Plus I wanted this javascript to appear only once on the page, even if the control appears twice or more. In order to achieve this I added a restriction: use of the ID.
But first thing first, meaning, add the Javascript file as embedded resource.
Now move the loading code to OnInit (where it should be placed) instead of Page_Load:
protected override void OnInit(EventArgs e)

{

base.OnInit(e);

String content = String.Empty;

System.IO.Stream stream = System.Reflection.Assembly.
GetExecutingAssembly().GetManifestResourceStream(GetType(), "MyControl.ascx");
using (System.IO.StreamReader reader = new System.IO.StreamReader(stream)
)
{

content = reader.ReadToEnd();

}

Control control = Page.ParseControl(content);

this.Controls.Add(control);

hfStatus = ((HiddenField)control.
FindControl("Status"));
if ((Session["MyControlScript"] == null) || (Session["MyControlScript"].
ToString() == this.ID))
{

Session["MyControlScript"] = this.ID;

ScriptManager.GetCurrent(Page)
.Scripts.Add(new ScriptReference("MyControls.Script.js", " MyControls"));
}

}


And in the AssemblyInfo.cs I added the following line:

[assembly: System.Web.UI.WebResource("MyControls.Script.js", "application/x-javascript")]

And voila! It compiles.

Now all we need is a test page (inside a site)

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register TagPrefix="tg" Assembly="MyControls" Namespace="MyControls" %>
<!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></title>
</head>
<body>
<script type="text/javascript">
function check() {

alert(document.getElementById(
"Cont1_Status").value);
alert(document.getElementById(
"Cont2_Status").value);
}

</script>
<form id="form1" runat="server">
<div>
<input type="button" onclick="check();" value="TEST!!!" />
</div>
<br />
<div>
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<tg: MyControl ID="Cont1" runat="server />
<br />
<tg: MyControl ID="Cont2" runat="server />
</div>
</form>
</body>
</html>

note: DON'T USE WORD, it took twice the time just to fix the mess this bloody editor did:(