Wednesday, 6 April 2011

Creating a clickable TextBlock (or any control) in WP7 using a Custom Control

I came across a need on my current windows phone 7 project to have different parts of my item in a listbox clickable with different events. Plus I’m using MVVM Light. Sounds easy, right? Well I thought so until I realised that the TextBlock control doesn’t have a click event.

First thought was putting the TextBlock into a button as content. This works, although you get this inverted effect on clicking which really didn’t look good on the controls I had. I could change this effect using states, but the button still clicked when you happened to be dragging and the starting point was the button.

Next I looked at the GestureService/Listener which is included in the Silverlight Toolkit. This is very useful but doesn’t allow binding to MVVM Light using eventToCommand behaviours.
Then I looked at a TextBlock having MouseLeftButtonDown & Up events which I could capture. But that still fires even if I’m just dragging the list around but happened to start on the control.
So I figured I could get around this by tracking the point of click. My app is using MVVM however and I didn’t want to start putting either
  1. Mouse event handling code in my ViewModel
  2. Lots of events in my View
So instead I thought I’d create a custom control that has a data bindable click event.
Whilst creating this reusable control I thought I’d add the ability to choose it’s click detection type.
  • Point based would look to see if your finger/mouse is in the same spot when you press down and then up (good for scrolling)
  • Border based would look to see if you’re in the bounds of the zone (like a traditional button).
Here’s the steps I took:
Create the new a Windows Phone Class Library project. This will be where the control lives.
image
Rename the Class1.cs using the Solution Explorer to your new control name. In this instance I’ve called it ClickZone.
Just above the class declaration (or in a separate file if preferred) add a public enumeration to identify the possible click types.
This control will inherit from the ContentControl. This will easily allow it to contain content (other controls), and will act as a click zone for them all.
Add a private Point variable to track the point when the finger/mouse is pressed down. Also add a boolean flag to track when the finger/mouse is in the control.
Your code should look similar to this now:
using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Controls;

namespace CustControlTest
{
public enum ClickDetectType { PointBased, BorderBased };

public class ClickZone : ContentControl
{
//Reference for point based click
private Point startPoint = new Point();

//Flag for border based click
private bool isInBorder;
}
}


Now to add a click Event which can be captured, and a public property for the detection type.


public event EventHandler Click;
protected virtual void OnClick()
{
EventHandler handler = Click;
if (handler != null)
handler(this, new EventArgs());
}

private ClickDetectType _detectionType = ClickDetectType.PointBased;
public ClickDetectType DetectionType
{
get { return _detectionType; }
set { _detectionType = value; }
}



Next I need to capture the key mouse/finger events and track when it’s entering or leaving the control and when pressing/releasing. It’s on the release that I need to decide whether to fire the Click event or not.


protected override void OnMouseLeave(MouseEventArgs e)
{
//track border exit
isInBorder = false;
base.OnMouseLeave(e);
}

protected override void OnMouseEnter(MouseEventArgs e)
{
//track border entry
isInBorder = true;
base.OnMouseEnter(e);
}

protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
//track point entry
startPoint = e.GetPosition(null);
base.OnMouseLeftButtonDown(e);
}

protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
{
switch (_detectionType)
{
case ClickDetectType.BorderBased:
if (isInBorder)
OnClick();
break;
case ClickDetectType.PointBased:
if (startPoint.X == e.GetPosition(null).X && startPoint.Y == e.GetPosition(null).Y)
OnClick();
break;
}

isInBorder = false;
startPoint = new Point();

base.OnMouseLeftButtonUp(e);
}



Now that’s the class done, but it still needs a template. This is where you need a ResourceDictionary xaml file. Create a folder called Themes, and in it add a new xaml file called generic.xaml. Make sure you switch the Build Action to Resource, as it needs to be compiled into the DLL.


image


For my control layout I went for a simple structure of a Border control, with the content inside inside it. For more details on creating a ResourceDictionary, see the links at the bottom.


<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
xmlns:controls="clr-namespace:CustControlTest">
<Style TargetType="controls:ClickZone">
<Setter Property="Background" 
Value="Transparent"/>
<Setter Property="BorderBrush" 
Value="{StaticResource PhoneTextBoxBrush}"/>
<Setter Property="BorderThickness" 
Value="{StaticResource PhoneBorderThickness}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:ClickZone">
<Grid>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ContentPresenter
x:Name="ContentContainer"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"/>
</Border>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>



Using the TemplateBindings meant that the properties can be changed when reusing the control later.



Hopefully that should all compile fine now. So to test, create a new Project and reference this project. I’m going to use an MVVM Light one to show binding. You should now see the ClickZone control in the Toolbox


image


You can now drop this control on your page, and put content inside it. For this I’ll use Blend to show Blendability.


image


As you can see, I am able to add an EventToCommand behaviour to the ClickZone.


You can see on the Properties of the EventToCommand that I can now bind to the Click event.


image






Finally adding a command to the ViewModel which updates a property I can see the result!


image


Links I found useful:


http://www.windowsphonegeek.com/articles/Creating-a-WP7-Custom-Control-in-7-Steps


http://www.windowsphonegeek.com/articles/WP7-WatermarkedTextBox-custom-control


http://msdn.microsoft.com/en-us/library/system.windows.dependencyproperty%28v=vs.95%29.aspx


I have included all the code and sample MVVM Light application in the following file:


Thursday, 31 March 2011

Changing ApplicationBar items at runtime on Windows Phone 7

I'm currently developing a Windows Phone 7 application for work (that adds c#.net to the list of languages I've worked on since joining 6 months ago, the others being python and java - gotta love the labs environment!)


Something small which vexed me briefly was trying to change icons and menu bar items at runtime. If you've tried to reference them by name in code then you'll have most likely been presented with a null reference error.


The trick is to reference them by index number (zero based), for example:


var iconbutt = (IApplicationBarIconButton)ApplicationBar.Buttons[0];
var menuitem = (IApplicationBarMenuItem)ApplicationBar.MenuItems[0];


You can then modify them as required.


The reason that you have to do this is because the ApplicationBar and it's child items stem from Microsoft.Phone.Shell namespace rather than System.Windows.Controls. This also explains why you can't simply data bind, although there are workarounds for that.



Wednesday, 23 February 2011

Installing Django in VirtualEnv (Ubuntu - also with postgres & piston)

Due to work, I've recently switched from working with MS products and C# development to working in ubuntu developing python.

One of the first bits of advice I was given was to use virtualenv which allows you to isolate python environments, rather than just using the machines environment (and cluttering it/get clashes of versions etc)

The project I'm working on uses Django with a Postgres database, so here are the steps I needed to get it working. I'm using the default version of python however you can install different ones.
#install virtualenv
sudo apt-get install python-virtualenv

#install headers needed for psycopg2
#You can skip the next line if you're not using postgres as the database
sudo apt-get install libpq-dev python-dev

#create a folder for the virtual environment
mkdir mypyenv

#create the virtual environment
#no-site-packages ensures the environment doesn't also include packages installed at machine level
virtualenv --no-site-packages --distribute mypyenv

#switch to environment (environment name will be in brackets on terminal)
source mypyenv/bin/activate

#install django
pip install -E mypyenv Django

#I'm using piston also for my api - skip if you don't want it
pip install -E mypyenv django-piston

#install django
pip install -E mypyenv psycopg2
That should now be installed. Now just switch to your project folder and you should be able to work with it. If this is your first project then I'd highly recommend the official tutorial

Once you're finished using virtual environment, use the deactivate command to come out of it.

Monday, 8 March 2010

Enabling .Net 3.5 features in SharePoint 2007

Below are the changes needed to the web.config of a sharepoint site in order to enable .net 3.5 features (ajax etc)

In the Web.Config of the Site (inetpub\wwwroot\wss\virtualdirectories\nnnnn\)

Add to configSections
<sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<sectionGroup name="scripting" type="System.Web.Configuration.ScriptingSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="scriptResourceHandler" type="System.Web.Configuration.ScriptingScriptResourceHandlerSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
<sectionGroup name="webServices" type="System.Web.Configuration.ScriptingWebServicesSectionGroup, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<section name="jsonSerialization" type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="Everywhere" />
<section name="profileService" type="System.Web.Configuration.ScriptingProfileServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
<section name="authenticationService" type="System.Web.Configuration.ScriptingAuthenticationServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
<section name="roleService" type="System.Web.Configuration.ScriptingRoleServiceSection, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" allowDefinition="MachineToApplication" />
</sectionGroup>
</sectionGroup>
</sectionGroup>

Add the following to the <SharePoint><SafeControls>
<SafeControl Assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Web.UI" TypeName="*" Safe="True" />

Add to <system.web><httpHandlers>
<add verb="*" path="*.asmx" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add verb="*" path="*_AppService.axd" validate="false" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" validate="false" />

Add to <system.web><httpModules>
<add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />

Add to <system.web><compilation><assemblies>
<add assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<add assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add assembly="System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />
<add assembly="System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089" />

Add to <system.web><pages>
<controls>
<add tagPrefix="asp" namespace="System.Web.UI" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add tagPrefix="asp" namespace="System.Web.UI.WebControls" assembly="System.Web.Extensions, Version=3.5.0.0, Culture=neutral,PublicKeyToken=31BF3856AD364E35" />
</controls>

Add to <runtime><assemblyBinding>
<dependentAssembly>
<assemblyIdentity name="System.Web.Extensions" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Web.Extensions.Design" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="3.5.0.0" />
</dependentAssembly>

At the bottom, just above </configuration> add
<system.web.extensions>
<scripting>
<webServices>
</webServices>
</scripting>
</system.web.extensions>
<system.webServer>
<validation validateIntegratedModeConfiguration="false" />
<modules>
<remove name="ScriptModule" />
<add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</modules>
<handlers>
<remove name="WebServiceHandlerFactory-Integrated" />
<remove name="ScriptHandlerFactory" />
<remove name="ScriptHandlerFactoryAppServices" />
<remove name="ScriptResource" />
<add name="ScriptHandlerFactory" verb="*" path="*.asmx" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add name="ScriptHandlerFactoryAppServices" verb="*" path="*_AppService.axd" preCondition="integratedMode" type="System.Web.Script.Services.ScriptHandlerFactory, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<add name="ScriptResource" preCondition="integratedMode" verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
</handlers>
</system.webServer>

Tuesday, 8 September 2009

BDC Becomes BCS

It looks like the new version of Sharepoint 2010 will rebadge the Business Data Catalog (BDC) as Business Connectivity Services (BCS).

This will be providing read/write access to external data from not only Line Of Business (LOB) systems but also web services, databases or other data in sharepoint.

The BCS tools to do this will be in the Sharepoint Designer 2010 and VS2010 - lets hope it's easier than '07 where you ended up crafting lovely metadata xml files by hand or buying 3rd party tools.

For a look at the BCS and other new features of Sharepoint 2010, check out the sneak peaks...

Saturday, 28 March 2009

Asynchronous ASP.Net

This entry is actually the one of a write up of my notes from previous DevWeeks. Although from 2007, this is still relevant today...
These notes follow on from a Jeff Prosise talk on this subject at DevWeek 2007.


Asynchronous ASP.NET Programming – Jeff Prosise
Most sites that access external data (databases, web services etc) are written are done so in a synchronous way, and when the number of users increases the site can suddenly slow right down to a crawl, or even fail totally.

When ASP.NET receives a request for a page, it grabs a thread from a thread pool and assigns that request to the thread. A normal, or synchronous, page holds onto the thread for the duration of the request, preventing the thread from being used to process other requests.


If a synchronous request becomes I/O bound - for example, if it calls out to a Web service or queries a database and waits for the call to come back - then the thread assigned to the request is stuck doing nothing until the call returns. That impedes scalability because the thread pool has a finite number of threads available.

If all request-processing threads are blocked waiting for I/O operations to complete, additional requests get queued up waiting for threads to be free. At best, throughput decreases because requests wait longer to be processed.

At worst, the queue fills up and ASP.NET fails subsequent requests with 503 "Server Unavailable" errors.

ASP.NET 2.0 however supports asynchronous pages.




When the request arrives, it's assigned a thread by ASP.NET. The request begins processing on that thread, but when the time comes to hit the external data, the request launches an asynchronous request and returns the thread to the thread pool. When the query completes, the asynchronous request calls back to ASP.NET, and ASP.NET grabs another thread from the thread pool and resumes processing the request.
While the query is outstanding, zero thread pool threads are consumed, leaving all of the threads free to service incoming requests. A request that's processed asynchronously doesn't execute any faster. But other requests execute faster because they don't have to wait for threads to become free. Requests incur less delay in entering the pipeline, and overall throughout goes up.

Asynchronous HTML Handler Pages
The second asynchronous programming model featured in ASP.NET is the asynchronous HTTP handler. An HTTP handler is an object that serves as an endpoint for requests. Requests for ASPX files, for example, are processed by an HTTP handler for ASPX files. Likewise, requests for ASMX files are handled by an HTTP handler that knows how to deal with ASMX services.

You can extend ASP.NET to support additional file types by writing custom HTTP handlers. But even more interesting is the fact that you can deploy custom HTTP handlers in ASHX files and use them as targets of HTTP requests. This is the proper way to build Web endpoints that generate images on the fly or retrieve images from databases. You simply include an <img> tag (or Image control) in the page and point it to an ASHX that creates or fetches the image. Targeting an ASHX file with requests is more efficient than targeting an ASPX file because an ASHX file incurs much less overhead at processing time.


By definition, HTTP handlers implement the IHttpHandler interface. Handlers that implement that interface do their processing synchronously
HTTP handlers don't have to be synchronous. By implementing the IHttpAsyncHandler interface, which itself derives from IHttpHandler, an HTTP handler can be asynchronous. When used correctly, an asynchronous handler utilizes ASP.NET threads more efficiently. This is done in the same manner as an asynchronous page. In fact, asynchronous pages leverage the asynchronous handler support that predated asynchronous pages in ASP.NET.


See http://msdn.microsoft.com/en-gb/magazine/cc163725.aspx and
http://msdn.microsoft.com/msdnmag/issues/07/03/WickedCode/default.aspx for more information.

Friday, 27 March 2009

New Profile Pic

Quick post to say thanks to my friend and colleague Rob Baines, who is not only a talented architect, but also a great cartoonist, and did the "creator" avatar I'm using here.

Check out
his site for more details.