Tuesday, 29 September 2009

Custom .Net Two Way Bindable Properties

Often when creating .Net applications that interface with a database a developer will want to create custom controls that bind in a similar way to database fields as the existing controls like TextBoxes and CheckBoxes.

Getting the control to update to reflect the database value is trivial. You can just add a DataBinding as you would with another control. But this will not propogate updates back to the data object. In order to do this you need to implement two way data binding on your custom control. Start by adding the attribute [Bindable(true)] to the property in question and then implement the INotifyPropertyChanged interface. An example of how to do this can be found on MSDN here: http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.aspx.

The catch comes if you ignore the advice to not implement INotifyPropertyChanged and provide an additional event for the property you want to bind to. If you call the additional event Property Name + "Changed" then the .Net framework binding code seems to get confused and the two way binding will not work. Eg if you property is called ClickCount and the additional event is called ClickCountChanged the two way binding will fail.

I was surprised by this as I have seen tutorials on two way data binding that encourage the provision of events for individual properties as well as the generic PropertyChanged event. Just remember that if you must do this be careful with the name of the events.

Here is a quick code sample that demonstrates how to create a control with two way binding:
  1 public class ButtonCounter : Button, INotifyPropertyChanged
2
{
3
private int clickCount;
4
5
public event PropertyChangedEventHandler PropertyChanged;
6
7
[Bindable(true)]
8
public int ClickCount
9
{
10
get
11
{
12
return clickCount;
13
}
14
set
15
{
16
if (value != clickCount)
17
{
18
clickCount = value;
19
Text = clickCount.ToString();
20
DoPropertyChanged("ClickCount");
21
}
22
}
23
}
24
25
public ButtonCounter()
26
{
27
Click += (o, e) =>
28
{
29
ClickCount += 1;
30
};
31
ClickCount = 0;
32
}
33
34
public void DoPropertyChanged(string propertyName)
35
{
36
if (PropertyChanged != null)
37
{
38
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
39
}
40
}
41
}

Monday, 21 September 2009

Channel Swim Progress

I'm currently 4.5 km across the channel, just over an eighth of the way, now 30.9 km to go.

Monday, 14 September 2009

Channel Swim under way!

I'm currently 1 km accross the channel, only 34.4 km to go!

Now all I need is to get some sponsorship...

Monday, 3 August 2009

Creating Synchronous Web Requests in Silverlight

A word of warning out there to all you budding Silverlight developers. Don't try and do something like this...
private class RequestState
{
public HttpWebRequest Resquest { get; set; }
public HttpWebResponse Response { get; set; }
public ManualResetEvent ResetEvent { get; set; }
}

public static IEnumerable<string> GetFolders(string uri)
{
var requestState = new RequestState();
requestState.Resquest = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(new Uri(uri, UriKind.Absolute));
requestState.ResetEvent = new ManualResetEvent(false);
requestState.Resquest.BeginGetResponse(GetFoldersCallback, requestState);
requestState.ResetEvent.WaitOne();
return ProcessFolderResponse(requestState.Response);
}

private static void GetFoldersCallback(IAsyncResult result)
{
var requestState = (RequestState)result.AsyncState;
requestState.Response = (HttpWebResponse)requestState.Resquest.EndGetResponse(result);
requestState.ResetEvent.Set();
}
It won't work and will just hang. Even though the exact same code will work a treat in a non-Silverlight environment. I tried all sorts to get this to work and pulled my hair out for hours. The reason for this is that the Silverlight runtime gets upset if the main thread gets blocked. So you'll just have to stick with the asynchronous methods and callbacks.

The best I could do was to ask for my own callback and wrap that in a call to Dispatcher.BeginInvoke.
public static void GetFolders(Dispatcher dispatcher, string uri, Action<IEnumerable<string>> folderAction)
{
var request = (HttpWebRequest)WebRequestCreator.ClientHttp.Create(new Uri(uri, UriKind.Absolute));
request.BeginGetResponse(ar =>
{
var response = request.EndGetResponse(ar);
var folders = ProcessFolderResponse(response);
dispatcher.BeginInvoke(() => folderAction(folders));
}, null);
}

Sunday, 2 August 2009

Swim it With Dave

I've just signed up to swim the channel!!!

It's not as mad as it seems. I have to swim 22 miles over 12 weeks at my local swimming pool. For more information go to http://www.aspire.org.uk/channelswim.

Wednesday, 29 July 2009

User Control Snap Lines in Visual Studio (Part 2)

The earlier code does indeed only work when the contained label is at position (0,0). Here is a fix that offsets the position of the snap lines based on the label position.
public override IList SnapLines
{
get
{
if (labelDesigner == null)
{
return base.SnapLines;
}
return labelDesigner.SnapLines.Cast<SnapLine>().Select(sn => BuildSnapLine(sn)).ToList();
}
}

private SnapLine BuildSnapLine(SnapLine source)
{
return new SnapLine(source.SnapLineType, BuildOffset(source.Offset, source.IsHorizontal),
source.Filter, source.Priority);
}

private int BuildOffset(int offset, bool isHorizontal)
{
return offset + (isHorizontal ? labelDesigner.Control.Top : labelDesigner.Control.Left);
}

User Control Snap Lines in Visual Studio

When creating a user control in .Net it is often hard to line your new control up with the other controls on the form. This is because the new control with use the default control designer to tell Visual Studio about its layout properties. Where I work we have a number of user controls in which are placed Labels or LinkLabels. It would be very handy if we could override the designer behaviour so that all alignment was done based on the properties of the label contained within the control.

To this end I created a new ControlDesigner that creates a local LabelDesigner and passes its snap lines out to Visual Studio. My experiment works if the label contained in the user control is at position (0,0). Some work changes might be necessary to make this work if the label has a different position.
using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Windows.Forms;
using System.Windows.Forms.Design;

namespace WrappedLabel
{
public interface ICustomSnapLineControl
{
Label GetSnapLineLabel();
}

public class CustomSnapLineControlDesigner : ControlDesigner
{
private ControlDesigner labelDesigner;

public override void Initialize(IComponent component)
{
base.Initialize(component);
if (component is ICustomSnapLineControl)
{
var designAssembly = Assembly.Load("System.Design");
var type = designAssembly.GetType("System.Windows.Forms.Design.LabelDesigner");
labelDesigner = (ControlDesigner)Activator.CreateInstance(type);
labelDesigner.Initialize(((ICustomSnapLineControl)component).GetSnapLineLabel());
}
}

public override IList SnapLines
{
get
{
return labelDesigner != null ? labelDesigner.SnapLines : base.SnapLines;
}
}

}
}
To implement this feature add the Designer attribute to your user control and implement the ICustomSnapLineControl interface. Here is a simple example.
using System.ComponentModel;
using System.Windows.Forms;

namespace WrappedLabel
{
[Designer(typeof(CustomSnapLineControlDesigner))]
public partial class NewLabel : UserControl, ICustomSnapLineControl
{
public NewLabel()
{
InitializeComponent();
}

public override string Text
{
get
{
return label1.Text;
}
set
{
label1.Text = value;
}
}

public Label GetSnapLineLabel()
{
return label1;
}
}
}