Monday, January 26, 2009

WPF & The Data Context

I love me some WPF, though I have to admit that when I first started, I did things wrong. I held onto my old-fangled way of doing things as much as possible, and only hesitantly took steps out into the deep waters where the big fish play.

Which means it wasn't until I started slapping my head up against the datagrid that I first really looked at the Data Context object.

Afterwards, I thoroughly smacked my forehead repeatedly. Imagine it, thousands of lines of code, and a good bit of it can be culled if I had the time or energy to go retrofit it all with Data Contexts.

Now, for the uninitiated, a Data Context is just as it says: an object which holds the data that a WPF form uses as a display source for its UI elements.

So, looking at the eComic application; every CBR file can potentially have a xml fragment/document within it which contains details about that CBR file. This would contain information such as number of pages, page descriptions, artists, writers, series title, issue title, chapter number, volume number, etc. All the important meta data, that's truly unimportant to reading an actual issue of a comic.

So, what we have is this standard kind of programming structure: UI - Business Logic - Data Store. These three things are supposed to work together, while remaining within their own domains and never quite touching. Now, the way I had always done this in the past is that either the UI was aware of the Logic layer or the other way around.

In my mind, they had to be in order to talk to one another. It just made sense.

Enter the joys of WPF.

What happens here is that I have my UI and Data Store as usual. Then I have my business logic, and within it is this Data Context object which holds the data from the data store that I am currently interested in.

Taking eComic, let's keep it simple, and only deal with Series Title and Issue Title.

What happens is that I'll create a class, which exposes properties for each of those elements. I can either have the class populate itself, or populate it elsewhere, that's fundamentally irrelevant to the discussion. All we need is the class itself.

The next step is that when we're building our UI in XAML, we need to utilize the Binding command.

So, if have a Series Title textbox that I want to display in the UI, I'd have this code:

<Textbox Name="SeriesTitle" Margin="5,5,5,5" Text="{Binding SeriesTitle}" />

Now, here's where it gets real fun. All I have to do to get the SeriesTitle from my Data Context to that UI is just assign it to that SeriesTitle's parent container's Data Context. Any changes to the textbox will instantly be promulgated back to the Data Context so I never have to manually check the textbox or any other UI element. All I have to look at is the Data Context. This is accomplished via the PropertyChanged event, which is brought to us courtesy of the INotifyPropertyChanged interface found in the System.ComponentModel Namespace.

So, I'm using the Data Context, and now I have to determine if I need to save changes to the data store based on user input or not.

It is important here that I not waste CPU cycles in saving data that hasn't changed. So, this is where the PropertyChanged event really comes into play.

First off, since we were smart, we implemented access to the PropertyChanged event via a OnPropertyChanged method which takes in the name of the property being changed.

Once the event has been risen, then we would set an IsDirty flag to true. Then, we can check that flag to determine if we need to attempt to save the data or not.

This Data Context example can be viewed here:


   1:  Public Class eComicDataContext

   2:         Implements INotifyPropertyChanged

   3:   

   4:  Private _isDirty As Boolean

   5:  Private _series As String

   6:  Private _issue As String

   7:   

   8:  Public Sub New()

   9:         SeriesTitle = ""

  10:         IssueTitle = ""

  11:         IsDirty = False

  12:  End Sub

  13:   

  14:  Public Property SeriesTitle() As String

  15:         Get

  16:                Return _series

  17:         End Get

  18:         Set(ByVal value As String)

  19:                _series = value

  20:                OnPropertyChanged("SeriesTitle")

  21:         End Set

  22:  End Property

  23:   

  24:  Public Property IssueTitle() As String

  25:         Get

  26:                Return _series

  27:         End Get

  28:         Set(ByVal value As String)

  29:                _series = value

  30:                OnPropertyChanged("IssueTitle")

  31:         End Set

  32:  End Property

  33:   

  34:  Public Property IsDirty() As Boolean

  35:         Get

  36:                Return _isDirty

  37:         End Get

  38:         Private Set(ByVal value As Boolean)

  39:                _isDirty = value

  40:         End Set

  41:  End Property

  42:   

  43:  Public Sub ResetDirtyBit()

  44:         Me.IsDirty = False

  45:  End Sub

  46:   

  47:  Public Sub OnPropertyChanged(ByVal propertyName As String)

  48:         RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))

  49:         IsDirty = True

  50:  End Sub

  51:   

  52:  Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged

  53:  End Class

No comments:

Blog Widget by LinkWithin