WPF Routed Events Exposed

What are Routed events? A question that always routed in my mind. The concept is such simple and beautiful. Lets take it with an example from mother nature. Suppose a mother bird comes to the nest with food for her children. One way is she put the food in the nest and the birdlings eat with there own beaks. Other way is the mother feeds each and every child from her own beak. Both methods work, but the later one is more natural, beautiful, efficient and secure.

Similar to routed events in WPF, its very simple. Say there are three buttons in a Grid. Each having its own click event. What that does is it adds some extra code to the code behind for method declarations. What if there is only one function that handles all the click events of buttons on the page. How it works is as below:

//A Common void method to handle all clicks

private void Window_Click(object sender, RoutedEventArgs e)
{
FrameworkElement elem = e.Source as Button;

    MessageBox.Show((elem as Button).Content.ToString());
}

The Xaml would be like this

<Window x:Class=”TestingRoutedEvents.MainWindow”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml
Title=”MainWindow” Height=”350″ Width=”525″>
<Grid>
<StackPanel>
<StackPanel ButtonBase.Click=”Window_Click”>
<TextBox x:Name=”Hello” Text=”Hello World”/>
<TextBox/>
<Button Content=”btnok”/>
<Button Content=”btnCancel”/>
<Button Content=”btnExit”/>
</StackPanel>
<StackPanel>
<TextBox/>
<TextBox/>
<Button Content=”btnok1″/>
<Button Content=”btnCancel1″/>
<Button Content=”btnExit1″/>
</StackPanel>
</StackPanel>
</Grid>
</Window>

In Xaml code above, ButtonBase.Click=”Window_Click” does the trick, the routed events use two type of routines to fire, one is bubble-up apprach and other is Tunnel-down approach. A window click event is a bubble up event. So lets say, user clicks on btnok, this event will bubble up the visual tree and see if there is a implementation of routed event ButtonBase.Click=”Window_Click”, if it finds one, if fires that. The above code will not fire for btnok1, as in its visual tree, its direct parent is Stack Panel and StackPanel’s parent is a Window. So if we want that all the buttons should get the routed event, then either we should add ButtonBase.Click=”Window_Click” to both Stack Panels, or only on windows implementation like this;

 <Window x:Class=”TestingRoutedEvents.MainWindow”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml
Title=”MainWindow” Height=”350″ Width=”525″ ButtonBase.Click=”Window_Click”>
<Grid>
<StackPanel>
<StackPanel>
<TextBox x:Name=”Hello” Text=”Hello World”/>
<TextBox/>
<Button Content=”btnok”/>
<Button Content=”btnCancel”/>
<Button Content=”btnExit”/>
</StackPanel>
<StackPanel>
<TextBox/>
<TextBox/>
<Button Content=”btnok1″/>
<Button Content=”btnCancel1″/>
<Button Content=”btnExit1″/>
</StackPanel>
</StackPanel>
</Grid>
</Window>

The myth of routed events i hope is now resolved.

WPF UI Automation using WebBrowser Control and WebClient

One smart day i was sitting on my seat and was feeling the fresh air, when i got an email about developing a Proof of concept for a upcoming project. I felt like a super hero as it was like saving the day from monsters and Alien Laser guns. Jokes apart, i was tasked to download a file from a website. Simple isn’t it, but i do not have to do it through mouse and keyboard, my application should go to website, login to website, click some links, navigate automatically through pages, reach the specific download page, initiate the download request and save the file to some place.
Requirements :
– Development platforms can be Silverlight, WPF or windows
– Should go to a website say http://www.gmail.com
– Login to an account
– Click Inbox
– Find emails with attachments
– Download Attachement
Development Phase:
I started to develop it in Silverlight as i was provided with a sample UI and some implemented code in Silverlight. Soon i came to know that in Silverlight we do not have much support for DOM processing. I decided to develop in WPF. I thought of a new scenerio that is below:
– Go to telerik.com
– Login to site
– Move to free tials page
– Download MVC package from the list

My User interface looked like below:

XAML
<Window x:Class=”WebBrowserAutomationWPF.MainWindow”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml
Title=”MainWindow” Height=”350″ Width=”525″ Loaded=”Window_Loaded”>
<Grid>
<WebBrowser HorizontalAlignment=”Stretch” Name=”webBrowser1″ VerticalAlignment=”Stretch” Margin=”0,38,0,0″ />
<Button Content=”Idle” Height=”23″ HorizontalAlignment=”Left” Name=”showHtml” VerticalAlignment=”Top” Width=”186″ Click=”showHtml_Click” />

</Grid>
</Window>

It has a Web Browser control and a Button. I am using the button for showing the progress only, nothing fancy.
On Windows loaded event i ask it to move to http://www.telerik.com/ and also subscribe to two events which will become handy as we go on.

C#
private void Window_Loaded(object sender, RoutedEventArgs e)
{
webBrowser1.Navigate(new Uri(“http://www.telerik.com/“));
webBrowser1.LoadCompleted += new LoadCompletedEventHandler(webBrowser1_LoadCompleted);
webBrowser1.Navigating += new NavigatingCancelEventHandler(webBrowser1_Navigating);
}

In Load completed event i switch each page with a flow id, so on each navigation event i process something different to navigate further.

C#
void webBrowser1_LoadCompleted(object sender, NavigationEventArgs e)
{
mshtml.HTMLDocument doc = (mshtml.HTMLDocument)webBrowser1.Document;

if (flowid == 1)
{
IHTMLElementCollection theElementCollection;
theElementCollection = doc.getElementsByTagName(“input”);
foreach (IHTMLElement curElement in theElementCollection)
{
if (curElement.getAttribute(“name”, 0) != null && curElement.getAttribute(“name”, 0) != String.Empty)
if (curElement.getAttribute(“name”, 0).StartsWith(“ctl00$YourAccount$tm_usercontrols_public_clientnet_loginnameandstatus_ascx1$btnLogin”))
{
curElement.click();
}
}
}
else if (flowid == 2)
{
IHTMLElementCollection theElementCollection;
theElementCollection = doc.getElementsByTagName(“input”);
foreach (IHTMLElement curElement in theElementCollection)
{
if(curElement.getAttribute(“name”, 0)!=null)
if ((curElement.getAttribute(“name”, 0).ToString() == “ctl00$PageContent$usercontrols_public_clientnet_signinform_ascx5$loginForm$UserName”))
{
curElement.setAttribute(“Value”, “<Your loginid email here >”, 0);
}
else if ((curElement.getAttribute(“name”, 0).ToString() == “ctl00$PageContent$usercontrols_public_clientnet_signinform_ascx5$loginForm$Password$tbSanitized”))
{
curElement.setAttribute(“Value”, “<Your password here>”,0);
}
//else if ((curElement.getAttribute(“name”, 0).ToString() == “ctl00$PageContent$usercontrols_public_clientnet_signinform_ascx5$loginForm$RememberMe”))
//{
//    curElement.setAttribute(“checked”, true, 0);
//}
}
theElementCollection = doc.getElementsByTagName(“input”);
foreach (IHTMLElement curElement in theElementCollection)
{
if(curElement.getAttribute(“name”, 0) !=null)
if (curElement.getAttribute(“name”, 0).Equals(“ctl00$PageContent$usercontrols_public_clientnet_signinform_ascx5$loginForm$LoginButton”))
{
curElement.click();
}
}
}
else if (flowid == 3)
{

IHTMLElementCollection theElementCollection;
theElementCollection = doc.getElementsByTagName(“a”);
foreach (IHTMLElement curElement in theElementCollection)
{
if(curElement.getAttribute(“innerText”, 0)!=null)
if (curElement.getAttribute(“innerText”, 0).Equals(“Free Trials”))
{
curElement.click();
}
}

}
else if (flowid == 4)
{
IHTMLElementCollection theElementCollection;
theElementCollection = doc.getElementsByTagName(“span”);
foreach (IHTMLElement curElement in theElementCollection)
{
if (curElement.getAttribute(“innerText”, 0) != null)
if (curElement.getAttribute(“innerText”, 0).Equals(“Download Open Source Version”))
{
curElement.click();
}
}

}
else if (flowid == 5)
{
IHTMLElementCollection theElementCollection;
theElementCollection = doc.getElementsByTagName(“a”);
foreach (IHTMLElement curElement in theElementCollection)
{
if(curElement.getAttribute(“title”, 0) !=null)
if (curElement.getAttribute(“title”, 0).Equals(“Telerik.Web.Mvc.Examples-Razor_OpenSource.zip”))
{
downloadfile((curElement as IHTMLAnchorElement).href);
}
}
}

flowid += 1;
}

Here is the download file function:

C#
private void downloadfile(string path)
{

client.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
client.CookieContainer = cookies;
client.DownloadFileCompleted += new System.ComponentModel.AsyncCompletedEventHandler(client_DownloadFileCompleted);
client.DownloadProgressChanged += new DownloadProgressChangedEventHandler(client_DownloadProgressChanged);
showHtml.Content = “Downloading .. “;
client.DownloadFileAsync(new Uri(path), @”C://web.zip”);
NavigationPath = path;

}

I used webclient to download the file, the problem was to execute the whole procedure silently, so i used webclient else i could have used WebBrowser1.Navigate(((curElement as IHTMLAnchorElement).href); and it would have open a dialog.
The issue arise here, webclient and webbrowser do not share the same session. When we attempt to download the file, through webclient it redirects internally to the login page. And what we get downloaded in web.zip is infact the login.html page. To prevent it, we need to find a way to syncronize the cookie container of webclient and webbrowser document.
I searched a thousand blogs, hundreds of articles, but i seemed it required the Super Hero Magic to do the trick. I keep on hitting the monster, from left, from right, from up and down. And suddenly the monster was defeated and was lying flat in front of me.
The magic was making the webclient CookieAware and using the webbrowser’s navigating event. Webbrowser Only release the cookie container in its event arguments of navigating event. So here is what i did.
C#
public class CookieAwareWebClient : WebClient {

public CookieAwareWebClient() { CookieContainer = new CookieContainer(); }
public CookieContainer CookieContainer { get; set; }
protected override WebRequest GetWebRequest(Uri address)
{
WebRequest request = base.GetWebRequest(address);
if (request is HttpWebRequest)
{
(request as HttpWebRequest).CookieContainer = CookieContainer;
}
return request;
}
}

And for navigating event;

C#
public CookieContainer GetCookieContainer()
{
CookieContainer container = new CookieContainer();

foreach (string cookie in ((mshtml.HTMLDocument)webBrowser1.Document).cookie.Split(‘;’))
{
string name = cookie.Split(‘=’)[0];
string value = cookie.Substring(name.Length + 1);
string path = “/”;
string domain = “.telerik.com”;
container.Add(new Cookie(name.Trim(), value.Trim(), path, domain));
}
return container;
}

void webBrowser1_Navigating(object sender, NavigatingCancelEventArgs e)
{
cookies=GetCookieContainer();
}

The tricky part with telerik.com was that, along with cookie it requires another string, “Application=1;” so we need to ammend it with the cookie, manually and add in the container.
That was how i lived the day of a super hero and defeated a bunch of monsters. I hope these superhero tricks will help the upcoming heroes to take up such tasks with ease and less research. The whole time i spent was fighting with the webbrowser and webclient to plug-in togther. What i achieved was that webclient by nature is not cookie aware, so first we need to make it cookie aware, and second most important thing is that webbrowser only disclose its cookie container in its navigating event. These two things make life easier, and sky is the limit as we can make scrappers, ui automation tools, Ui testing tools out of it. Hope this will help the people who need this functionality in there application. Thankyou All.