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.

No comments: