Showing posts with label .Net User Controls. Show all posts
Showing posts with label .Net User Controls. Show all posts

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
}

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;
}
}
}