Data Binding from the eyes of an VC++ developer

Having worked in VC++, MFC for more than a decade and getting to work on .NET WPF projects for a couple of years gave me an insight into the inner workings of data binding concepts in WPF.

Data binding in the user interface layer is nothing new. It has been around for quite some time, in various UI platforms, both for desktop and Web applications. The basic idea is that you "bind" the visual elements (controls) in a user interface to the data objects they are meant to display. The binding infrastructure then manages the data interactions from then on, so that modifications to the UI controls are reflected in the data objects, and vice versa. The major benefit of using data binding is that it reduces the amount of code the application developer needs to write.

Lets see how Data Binding is done in MFC:

In MFC, we have lot of great design concepts introduced like document/view, message mapping, dialog data routines and many more. DDX (Dialog Data Exchange) and DDV (Dialog Data Validation) are two great tools that allow us to easily set and access the values of MFC controls. DDX encapsulates the transfer of data between class member variables and dialog controls in a single place. Data Dialog Validation functions allow you to let Windows automatically restrict the values allowed within a control and is an easy way to validate data entry in the controls.

There are three main parts to DDX; UpdateData, DoDataExchange and the DDX-DDV functions (global functions) listed below from afxdd_.h.

  1. // simple text operations
  2. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, BYTE& value);
  3. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, short& value);
  4. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, int& value);
  5. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, UINT& value);
  6. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, long& value);
  7. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, DWORD& value);
  8. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, LONGLONG& value);
  9. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, ULONGLONG& value);
  10. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, CString& value);
  11. void AFXAPI DDX_Text(_Inout_ CDataExchange* pDX, _In_ int nIDC, _Out_z_cap_(nMaxLen) LPTSTR value, _In_ int nMaxLen);
  12. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, float& value);
  13. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, double& value);
  14. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, COleCurrency& value);
  15. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, COleDateTime& value);
  16. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, GUID& value);
  17. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, DECIMAL& value);
  18. void AFXAPI DDX_Text(CDataExchange* pDX, int nIDC, FILETIME& value);
  19.  
  20. // special control types
  21. void AFXAPI DDX_Check(CDataExchange* pDX, int nIDC, int& value);
  22. void AFXAPI DDX_Radio(CDataExchange* pDX, int nIDC, int& value);
  23. void AFXAPI DDX_LBString(CDataExchange* pDX, int nIDC, CString& value);
  24. void AFXAPI DDX_CBString(CDataExchange* pDX, int nIDC, CString& value);
  25. void AFXAPI DDX_LBIndex(CDataExchange* pDX, int nIDC, int& index);
  26. void AFXAPI DDX_CBIndex(CDataExchange* pDX, int nIDC, int& index);
  27. void AFXAPI DDX_LBStringExact(CDataExchange* pDX, int nIDC, CString& value);
  28. void AFXAPI DDX_CBStringExact(CDataExchange* pDX, int nIDC, CString& value);
  29. void AFXAPI DDX_Scroll(CDataExchange* pDX, int nIDC, int& value);
  30. void AFXAPI DDX_Slider(CDataExchange* pDX, int nIDC, int& value);
  31.  
  32. void AFXAPI DDX_IPAddress(CDataExchange* pDX, int nIDC, DWORD& value);
  33.  
  34. void AFXAPI DDX_MonthCalCtrl(CDataExchange* pDX, int nIDC, CTime& value);
  35. void AFXAPI DDX_MonthCalCtrl(CDataExchange* pDX, int nIDC, COleDateTime& value);
  36. void AFXAPI DDX_MonthCalCtrl(CDataExchange* pDX, int nIDC, FILETIME& value);
  37. void AFXAPI DDX_DateTimeCtrl(CDataExchange* pDX, int nIDC, CString& value);
  38. void AFXAPI DDX_DateTimeCtrl(CDataExchange* pDX, int nIDC, CTime& value);
  39. void AFXAPI DDX_DateTimeCtrl(CDataExchange* pDX, int nIDC, COleDateTime& value);
  40. void AFXAPI DDX_DateTimeCtrl(CDataExchange* pDX, int nIDC, FILETIME& value);
  41.  
  42. // for getting access to the actual controls
  43. void AFXAPI DDX_Control(CDataExchange* pDX, int nIDC, CWnd& rControl);
  44.  
  45. /////////////////////////////////////////////////////////////////////////////
  46. // Standard Dialog Data Validation routines
  47.  
  48. // range – value must be >= minVal and <= maxVal
  49. // NOTE: you will require casts for 'minVal' and 'maxVal' to use the
  50. //   UINT, DWORD or float types
  51. void AFXAPI DDV_MinMaxByte(CDataExchange* pDX, BYTE value, BYTE minVal, BYTE maxVal);
  52. void AFXAPI DDV_MinMaxShort(CDataExchange* pDX, short value, short minVal, short maxVal);
  53. void AFXAPI DDV_MinMaxInt(CDataExchange* pDX, int value, int minVal, int maxVal);
  54. void AFXAPI DDV_MinMaxLong(CDataExchange* pDX, long value, long minVal, long maxVal);
  55. void AFXAPI DDV_MinMaxUInt(CDataExchange* pDX, UINT value, UINT minVal, UINT maxVal);
  56. void AFXAPI DDV_MinMaxDWord(CDataExchange* pDX, DWORD value, DWORD minVal, DWORD maxVal);
  57. void AFXAPI DDV_MinMaxLongLong(CDataExchange* pDX, LONGLONG value, LONGLONG minVal, LONGLONG maxVal);
  58. void AFXAPI DDV_MinMaxULongLong(CDataExchange* pDX, ULONGLONG value, ULONGLONG minVal, ULONGLONG maxVal);
  59. void AFXAPI DDV_MinMaxFloat(CDataExchange* pDX, float const& value, float minVal, float maxVal);
  60. void AFXAPI DDV_MinMaxDouble(CDataExchange* pDX, double const& value, double minVal, double maxVal);
  61.  
  62. // special control types
  63. void AFXAPI DDV_MinMaxSlider(CDataExchange* pDX, DWORD value, DWORD minVal, DWORD maxVal);
  64. void AFXAPI DDV_MinMaxDateTime(CDataExchange* pDX, CTime& refValue, const CTime* refMinRange, const CTime* refMaxRange);
  65. void AFXAPI DDV_MinMaxDateTime(CDataExchange* pDX, COleDateTime& refValue, const COleDateTime* refMinRange, const COleDateTime* refMaxRange);
  66. void AFXAPI DDV_MinMaxMonth(CDataExchange* pDX,    CTime& refValue, const CTime* pMinRange, const CTime* pMaxRange);
  67. void AFXAPI DDV_MinMaxMonth(CDataExchange* pDX, COleDateTime& refValue, const COleDateTime* refMinRange, const COleDateTime* refMaxRange);
  68.  
  69.  
  70. // number of characters
  71. void AFXAPI DDV_MaxChars(CDataExchange* pDX, CString const& value, int nChars);
  72.  
  73. /////////////////////////////////////////////////////////////////////////////

 

 

The framework calls CWnd::DoDataExchange to exchange and validate dialog data.

protected:
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

** Note that DoDataExchange is a virtual function.

You need to override this member function DoDataExchange if you wish to utilize the framework’s automatic data exchange and validation. An implementation of DoDataExchange is shown below. I added a control variable and a data variable of type string to an edit control whose ID is IDC_EDIT1 .

void CMfcSampleDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialogEx::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_EDIT1, controlvar);
    DDX_Text(pDX, IDC_EDIT1, strvariable);
}

It’s to be noted that DoDataExchange is never called directly, it’s called by the CWnd::UpdateData member function found in afxwin.h. UpdateData sets up an object called a CDataExchange that holds information about the direction of the transfer and so on and passes this to DoDataExchange to do the work. The body of DoDataExchange is generally just a whole lot of DDX (and DDV) calls.

Following is the declaration of CWnd::UpdateData member function found in afxwin.h

// Dialog Data support
public:
    BOOL UpdateData(BOOL bSaveAndValidate = TRUE);

UpdateData is called to initialize data in a dialog box, or to retrieve and validate dialog data. The bSaveAndValidate flag indicates whether a dialog box is being initialized (set bSaveAndValidate = FALSE) or data is being retrieved (set bSaveAndValidate = TRUE). The framework automatically calls UpdateData with bSaveAndValidate set to FALSE when a modal dialog box is created in the default implementation of CDialog::OnInitDialog. The call occurs before the dialog box is visible. The default implementation of CDialog::OnOK calls this member function with bSaveAndValidate set to TRUE to retrieve the data, and if successful, will close the dialog box.

To validate data, for ex, if the edit box should allow only 10 chars, then the following validation routine takes care of it.

DDV_MaxChars(pDX, m_streditcontrolvariable, 10);

DataBinding in WPF:

WPF provides a simple and powerful way to auto-update data between the user-data  and the user interface. This mechanism is called DataBinding. Every time when the data of your class model changes, it automatically reflects the updates to the user interface and vice versa. This is similar to UpdateData in c++ and in WPF it’s related to bringing data to the user interface. Data binding in WPF is ubiquitous and seamless. It is so powerful and flexible that it literally forces you to change the way you think about designing and developing user interfaces. With one simple API you can bind to domain/business objects, XML data, visual elements, ADO.NET data containers, collections, and basically anything else you can think of. You can use value converters to execute arbitrary data manipulation operations when bound values are passed back and forth. You can perform data validation by creating custom validation rules and applying them to a binding. The list goes on. Data binding in WPF is really a huge step forward.

Binding is a markup extension. When you use the binding extension to declare a binding, the declaration consists of a series of clauses following the Binding keyword and separated by commas (,).

<TextBox Grid.Row="0" Text="{Binding Source={StaticResource myDataSource}, Path=PONumber, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox Grid.Row="1" Text="{Binding Source= {StaticResource myDataSource}, Path=PONumber}"></TextBox>
<TextBox Grid.Row="2" Text="{Binding Path=PONumber}"></TextBox>

The clauses in the binding declaration can be in any order and there are many possible combinations. The clauses are Name=Value pairs where Name is the name of the Binding property and Value is the value you are setting for the property. Data binding can also mean that if an outer representation of the data in an element changes, then the underlying data can be automatically updated to reflect the change. For example, if the user edits the value in a TextBox element, the underlying data value is automatically updated to reflect that change.

When creating binding declaration strings in markup, they must be attached to the specific dependency property of a target object. The above example shows how to bind the TextBox.Text property using the binding extension, specifying the Source and Path properties.

The source of a databinding can be a normal .NET property or a DependencyProperty. The target property of the binding must be a DependencyProperty.

To make the databinding properly work, both sides of a binding must provide a change notification that tells the binding when to update the target value. On normal .NET properties this is done by raising the PropertyChanged event of the INotifyPropertyChanged interface. On DependencyProperties it is done by the PropertyChanged callback of the property metadata

 

  

image

As illustrated by the above figure, data binding is essentially the bridge between the binding target and the binding source. The figure demonstrates the following fundamental WPF data binding concepts:

  • Typically, each binding has these four components: a binding target object, a target property, a binding source, and a path to the value in the binding source to use. For example, if you want to bind the content of a TextBox to the Name property of an PurchaseOrders object, your target object is the TextBox, the target property is the Text property, the value to use is PONumber, and the source object is the PurchaseOrders object.

  • The target property must be a dependency property. Most UIElement properties are dependency properties and most dependency properties, except read-only ones, support data binding by default. (Only DependencyObject types can define dependency properties and all UIElements derive from DependencyObject.)

  • Although not specified in the figure, it should be noted that the binding source object is not restricted to being a custom CLR object. WPF data binding supports data in the form of CLR objects and XML. To provide some examples, your binding source may be a UIElement, any list object, a CLR object that is associated with ADO.NET data or Web Services, or an XmlNode that contains your XML data.

Direction of the Data Flow

Databinding can be unidirectional (source -> target or target <- source), or bidirectional (source <-> target). As mentioned previously and as indicated by the arrow in the figure above, the data flow of a binding can go from the binding target to the binding source (for example, the source value changes when a user edits the value of a TextBox) and/or from the binding source to the binding target (for example, your TextBox content gets updated with changes in the binding source) if the binding source provides the proper notifications.

You may want your application to enable users to change the data and propagate it back to the source object. Or you may not want to enable users to update the source data. You can control this by setting the Mode property of your Binding object.

  • OneWay binding causes changes to the source property to automatically update the target property, but changes to the target property are not propagated back to the source property. This type of binding is appropriate if the control being bound is implicitly read-only. For instance, you may bind to a source such as a stock ticker or perhaps your target property has no control interface provided for making changes, such as a data-bound background color of a table. If there is no need to monitor the changes of the target property, using the OneWay binding mode avoids the overhead of the TwoWay binding mode.

  • TwoWay binding causes changes to either the source property or the target property to automatically update the other. This type of binding is appropriate for editable forms or other fully-interactive UI scenarios. Most properties default to OneWay binding, but some dependency properties (typically properties of user-editable controls such as the Text property of TextBox and the IsChecked property of CheckBox) default to TwoWay binding. A programmatic way to determine whether a dependency property binds one-way or two-way by default is to get the property metadata of the property using GetMetadata and then check the Boolean value of the BindsTwoWayByDefault property.

  • OneWayToSource is the reverse of OneWay binding; it updates the source property when the target property changes. One example scenario is if you only need to re-evaluate the source value from the UI.

  • OneTime binding, which causes the source property to initialize the target property, but subsequent changes do not propagate. This means that if the data context undergoes a change or the object in the data context changes, then the change is not reflected in the target property. This type of binding is appropriate if you are using data where either a snapshot of the current state is appropriate to use or the data is truly static. This type of binding is also useful if you want to initialize your target property with some value from a source property and the data context is not known in advance. This is essentially a simpler form of OneWay binding that provides better performance in cases where the source value does not change.

You use the Mode property to specify the direction of the binding. The following enumeration list shows the available options for binding updates (to be precise):

  • TwoWay updates the target property or the property whenever either the target property or the source property changes.

  • OneWay updates the target property only when the source property changes.

  • OneTime updates the target property only when the application starts or when the DataContext undergoes a change.

  • OneWayToSource updates the source property when the target property changes.

  • Default causes the default Mode value of target property to be used.

Note that to detect source changes (applicable to OneWay and TwoWay bindings), the source must implement a suitable property change notification mechanism such as INotifyPropertyChanged. To implement INotifyPropertyChanged you need to declare the PropertyChanged event and create the OnPropertyChanged method. Then for each property you want change notifications for, you call OnPropertyChanged whenever the property is updated.

What Triggers Source Updates

Bindings that are TwoWay or OneWayToSource listen for changes in the target property and propagate them back to the source. This is known as updating the source. For example, you may edit the text of a TextBox to change the underlying source value. As described in the last section, the direction of the data flow is determined by the value of the Mode property of the binding.

However, does your source value get updated while you are editing the text or after you finish editing the text and point your mouse away from the TextBox? The UpdateSourceTrigger property of the binding determines what triggers the update of the source. If the UpdateSourceTrigger value is PropertyChanged, then the value pointed to by the right arrow of TwoWay or the OneWayToSource bindings gets updated as soon as the target property changes. However, if the UpdateSourceTrigger value is LostFocus, then that value only gets updated with the new value when the target property loses focus.

Similar to the Mode property, different dependency properties have different default UpdateSourceTrigger values. The default value for most dependency properties is PropertyChanged, while the Text property has a default value of LostFocus. This means that source updates usually happen whenever the target property changes, which is fine for CheckBoxes and other simple controls. However, for text fields, updating after every keystroke can diminish performance and it denies the user the usual opportunity to backspace and fix typing errors before committing to the new value. That is why the Text property has a default value of LostFocus instead of PropertyChanged.

 

UpdateSourceTrigger value

When the Source Value Gets Updated

Example Scenario for TextBox

LostFocus (default for TextBox.Text)

When the TextBox control loses focus

A TextBox that is associated with validation logic

PropertyChanged

As you type into the TextBox

TextBox controls in a chat room window

Explicit

When the application calls UpdateSource

TextBox controls in an editable form (updates the source values only when the user clicks the submit button)

 

Databinding is typically done in XAML by using the {Binding} markup extension. The following example shows a simple binding between the text of a TextBox and a property of a class:

Download code from Microsoft Skydrive

Lead and inspire people. Don’t try to manage and manipulate people. Inventories can be managed but people must be lead. –Ross Perot

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s