Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 69 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
69
Dung lượng
634,02 KB
Nội dung
Implementing a read-only sorted view of a collection is relatively straightforward, but implementing a view that is bidirectionally updatable is quite complex. And that’s exactly what SortedBindingList does. Acting As a View Let’s look at the simple things first. The original collection, as an ICollection, has a set of proper- ties, such as Count and SyncRoot, that are simply exposed by SortedBindingList. For instance, here’s the Count property: Public ReadOnly Property Count() As Integer _ Implements System.Collections.ICollection.Count, _ System.Collections.Generic.ICollection(Of T).Count Get Return mList.Count End Get End Property This technique is repeated for all the ICollection, IList, and IEnumerable properties. The not- able exception to this is the default property, which is quite a bit more complex and is discussed later. If the original collection implements IBindingList, it has a broader set of properties. It might be editable and it might not. It might allow adding of new items or not. All these capabilities are exposed through its IBindingList interface, and SortedBindingList merely assumes the same set- tings. For instance, here’s the AllowEdit property: Public ReadOnly Property AllowEdit() As Boolean _ Implements System.ComponentModel.IBindingList.AllowEdit Get If mSupportsBinding Then Return mBindingList.AllowEdit Else Return False End If End Get End Property Recall from the constructor that if the original collection doesn’t implement IBindingList, then mSupportsBinding will be False. In that case, AllowEdit returns False because in-place editing isn’t valid unless the original collection implements IBindingList. This technique is repeated for all the IBindingList properties. Applying a Sort The IBindingList interface allows a sort to be applied to a collection, either ascending or descend- ing, based on a single pr oper ty. This is done through the ApplySort() method. ApplySort Method SortedBindingList implements two overloads of ApplySort(), making it possible to apply a sort based on the string name of the property, as well as by a PropertyDescriptor as required by IBindingList: Public Sub ApplySort( _ ByVal propertyName As String, _ ByVal direction As System.ComponentModel.ListSortDirection) CHAPTER 5 ■ COMPLETING THE FRAMEWORK 253 6315_c05_final.qxd 4/13/06 12:36 PM Page 253 mSortBy = Nothing If Len(propertyName) > 0 Then Dim itemType As Type = GetType(T) For Each prop As PropertyDescriptor In _ TypeDescriptor.GetProperties(itemType) If prop.Name = propertyName Then mSortBy = prop Exit For End If Next End If ApplySort(mSortBy, direction) End Sub Public Sub ApplySort( _ ByVal [property] As System.ComponentModel.PropertyDescriptor, _ ByVal direction As System.ComponentModel.ListSortDirection) _ Implements System.ComponentModel.IBindingList.ApplySort mSortBy = [property] mSortOrder = direction DoSort() End Sub The first overload creates a PropertyDescriptor for the named property and calls the second overload. The second overload will also be called directly by data binding. It sets the mSortBy and mSortOrder fields to indicate the sort parameters, and calls DoSort(). The reason these two instance fields are used to store the parameters is that these values are also exposed by Public properties such as SortDirection: Public ReadOnly Property SortDirection() As _ System.ComponentModel.ListSortDirection _ Implements System.ComponentModel.IBindingList.SortDirection Get Return mSortOrder End Get End Property The DoSort() method actually does the sorting by assembling the key values into a private collection and then sorting those values. Associated with each key value is a reference to the corre- sponding item in the original collection. ListItem Class Associating the value of the property by which to sort with a reference to the corresponding child object in the original collection requires a key/value list, which in turn requires a key/value class. The ListItem class maintains a relationship between a key and a reference to the corresponding child object. The key value is the value of the property from the child object on which the collection is to be sorted. For example, when sorting a collection of Customer objects by their Name property, the key value will be the contents of the Name property from the corresponding child object. R ather than maintaining an actual object r efer ence, ListItem maintains the inde x v alue of the child item in the original collection. This is referred to as the base index: CHAPTER 5 ■ COMPLETING THE FRAMEWORK254 6315_c05_final.qxd 4/13/06 12:36 PM Page 254 Private Class ListItem Implements IComparable(Of ListItem) Private mKey As Object Private mBaseIndex As Integer Public ReadOnly Property Key() As Object Get Return mKey End Get End Property Public Property BaseIndex() As Integer Get Return mBaseIndex End Get Set(ByVal value As Integer) mBaseIndex = value End Set End Property Public Sub New(ByVal key As Object, ByVal baseIndex As Integer) mKey = key mBaseIndex = baseIndex End Sub Public Function CompareTo(ByVal other As ListItem) As Integer _ Implements System.IComparable(Of ListItem).CompareTo Dim target As Object = other.Key If TypeOf Key Is IComparable Then Return DirectCast(Key, IComparable).CompareTo(target) Else If Key.Equals(target) Then Return 0 Else Return Key.ToString.CompareTo(target.ToString) End If End If End Function Public Overrides Function ToString() As String Return Key.ToString End Function End Class I n addition to associating the pr oper ty v alue to the base index of the child object in the original collection, ListItem implements IComparable(Of T). This inter face enables the .NET Framework to sort a collection of ListItem objects. This interface requires implementation of the CompareTo() method, which is responsible for comparing one ListItem object to another. Of course, it is the key value that is to be compared, so CompareTo() simply compares the value of its Key pr operty to the Key pr operty from the other ListItem object. I f the type of the key value implements IComparable, then the call simply delegates to that interface: CHAPTER 5 ■ COMPLETING THE FRAMEWORK 255 6315_c05_final.qxd 4/13/06 12:36 PM Page 255 If TypeOf Key Is IComparable Then Return DirectCast(Key, IComparable).CompareTo(target) Otherwise things are a bit more complex. Obviously, any objects can be compared for equality, so that part is straightforward: I f Key.Equals(target) Then Return 0 However, if the type of the key value doesn’t implement IComparable, then there’s no easy way t o see if one is greater than the other. To overcome this problem, both values are converted to their string representations, which are then compared to each other: Return Key.ToString.CompareTo(target.ToString) While this is not perfect, it is the best we can do. And really this is an extreme edge case since most types are comparable, including strings, numeric types, and dates. Given that most properties are of those types, this solution works well in almost every case. DoSort Method Given the ListItem class and the sorting capabilities of the .NET Framework, the DoSort() method is not hard to implement: Private Sub DoSort() Dim index As Integer mSortIndex.Clear() If mSortBy Is Nothing Then For Each obj As T In mList mSortIndex.Add(New ListItem(obj, index)) index += 1 Next Else For Each obj As T In mList mSortIndex.Add(New ListItem(mSortBy.GetValue(obj), index)) index += 1 Next End If mSortIndex.Sort() mSorted = True OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, 0)) End Sub I f mSortBy is Nothing (which is quite possible , as it is optional), then each child object is sorted as is. In other words, it is the value of the child object itself that determines the sort order, rather than any specific property on the child object. In this case, DoSort() loops through every item in the original collection, creating a ListItem object for which the key value is the child object itself and the index is the location of the child object within the or iginal collection: For Each obj As T In mList mSortIndex.Add(New ListItem(obj, index)) index += 1 Next CHAPTER 5 ■ COMPLETING THE FRAMEWORK256 6315_c05_final.qxd 4/13/06 12:36 PM Page 256 This scenario is quite common when creating a sorted view against an array of type String or Integer, since there’s no meaning in setting an mSortBy value for those types. For more complex child objects, however, an mSortBy value is typically supplied. In that case, a bit of reflection is used to retrieve the specified property value from the child object. That property value is then used as the key value for the ListItem object: F or Each obj As T In mList mSortIndex.Add(New ListItem(mSortBy.GetValue(obj), index)) index += 1 N ext Remember that mSortBy is a System.ComponentModel.PropertyDescriptor object corresponding to the key property. PropertyDescriptor provides a GetValue() method that retrieves the property value from the specified child object. Whether or not mSortBy is Nothing, the end result is a list of ListItem objects in a generic List(Of ListItem) collection named mSortIndex. The List(Of T) class provides a Sort() method that sorts the items in the list. Since ListItem implements IComparable(Of T), that interface is used to order the sort, meaning that the items end up sorted based on the key property value in each ListItem object. Since sorting changes the order of items in the list, the view object’s ListChanged event is raised to tell data binding that the view collection has effectively been reset. Keep in mind that the original collection is entirely unaffected by this process, and doesn’t raise any events due to the sort being applied. Viewing the Sorted Values You may be wondering how descending sorts are handled, since the Sort() method of List(Of T) performed an ascending sort in the DoSort() method. Ascending and descending sorts are handled by the view object’s default property. The IList interface requires that a default property be implemented. To retrieve an item, SortedBindingList must be able to cr oss-r eference from the sorted position of the item to the original position of the item in the original collection. The OriginalIndex() helper method per- for ms this cross-reference operation: Private Function OriginalIndex(ByVal sortedIndex As Integer) As Integer If mSortOrder = ListSortDirection.Ascending Then Return mSortIndex.Item(sortedIndex).BaseIndex Else Return mSortIndex.Item(mSortIndex.Count - 1 - sortedIndex).BaseIndex End If End Function The method checks to see whether the sort is ascending or descending. The supplied index value is then cross-referenced into the mSortIndex list to find the actual index of the child item in the original collection. In the case of an ascending sort, a straight cross-reference from the position in mSortIndex to the or iginal collection is used. And in the case of a descending sor t, the cr oss- r eference process merely starts at the bottom of mSortIndex and wor ks toward the top. The default property simply uses this helper method to retrieve or set the object from the original collection that corresponds to the location in the sorted index: Default Public Overloads Property Item(ByVal index As Integer) As T _ Implements System.Collections.Generic.IList(Of T).Item Get If mSorted Then Return mList(OriginalIndex(index)) CHAPTER 5 ■ COMPLETING THE FRAMEWORK 257 6315_c05_final.qxd 4/13/06 12:36 PM Page 257 Else Return mList(index) End If End Get Set(ByVal value As T) If mSorted Then mList(OriginalIndex(index)) = value Else mList(index) = value End If End Set End Property Notice that the child object is ultimately returned from the original collection. The data in SortedBindingList is merely used to provide a sorted cross-reference to those objects. In the case that a sort hasn’t been applied at all, no cross-reference is performed and the child object is returned from the original collection based directly on the index value: Return mList(index) The same technique is used in the Set block as well. Additionally, the IList interface requires implementation of a loosely typed Item proper ty: Private Property Item1(ByVal index As Integer) As Object _ Implements System.Collections.IList.Item Get Return Me(index) End Get Set(ByVal value As Object) Me(index) = CType(value, T) End Set End Property This property delegates its work to the strongly typed default property implemented previously. Collection Enumerator There are two ways to get items from a collection: the default proper ty and an enumerator. The enumerator is used by the For Each statement to loop through all items in the collection. Obvi- ously, it too needs to perform a cross-reference process, so a For Each loop goes through the sorted index and returns the corresponding item from the original collection. There are two steps to this process. First, the custom enumerator class must understand how to per for m the cr oss-r eference process. Second, SortedBindingList needs to expose a GetEnumerator() method that r eturns an instance of this custom enumerator (or the original collection’s enumerator if no sort has been applied). Custom Enumerator Class An enumer ator is an object that implements either IEnumerator or IEnumerator(Of T). These inter faces define a Current pr operty and MoveNext() and Reset() methods . You can think of an enumerator object as being a cursor or pointer into the collection. Table 5-2 describes these elements. CHAPTER 5 ■ COMPLETING THE FRAMEWORK258 6315_c05_final.qxd 4/13/06 12:36 PM Page 258 Table 5-2. Properties and Methods of an Enumerator Object Member Behavior Current Returns a reference to the current child object in the collection MoveNext() Moves to the next child object in the collection, making that the current object Reset() Moves to just above the top of the collection, so a subsequent MoveNext() call moves to the very first item in the collection When you use a For Each statement in your code, the compiler generates code behind the scenes to get an enumerator object from the collection, and to call the Reset(), MoveNext(), and Current elements to iterate through the items in the collection. Because an enumerator object is a cursor or pointer into the collection, it must maintain a cur- rent index position. The SortedEnumerator class used by SortedBindingList also needs to know the sort order and must have access to the original collection itself: Private Class SortedEnumerator Implements IEnumerator(Of T) Private mList As IList(Of T) Private mSortIndex As List(Of ListItem) Private mSortOrder As ListSortDirection Private mIndex As Integer Public Sub New( _ ByVal list As IList(Of T), _ ByVal sortIndex As List(Of ListItem), _ ByVal direction As ListSortDirection) mList = list mSortIndex = sortIndex mSortOrder = direction Reset() End Sub The constructor accepts a reference to the original collection, a reference to the mSortIndex list containing the sorted list of ListItem objects, and the sort direction. The mIndex field is used to maintain a pointer to the current position of the enumerator within the collection. The Reset() method simply sets index to immediately before the first item in the collection. Of course, when using a descending sort, this is actually immediately after the last item in the col- lection, because the enumerator will walk through the list from bottom to top in that case: Public Sub Reset() Implements System.Collections.IEnumerator.Reset If mSortOrder = ListSortDirection.Ascending Then mIndex = -1 Else mIndex = mSortIndex.Count End If End Sub The MoveNext() method increments mIndex, mo ving to the next item in the collection. Again, when using a descending sort, it actually decrements mIndex, thus moving from the bottom of the collection toward the top. CHAPTER 5 ■ COMPLETING THE FRAMEWORK 259 6315_c05_final.qxd 4/13/06 12:36 PM Page 259 Public Function MoveNext() As Boolean _ Implements System.Collections.IEnumerator.MoveNext If mSortOrder = ListSortDirection.Ascending Then If mIndex < mSortIndex.Count - 1 Then mIndex += 1 Return True Else Return False End If Else If mIndex > 0 Then mIndex -= 1 Return True Else Return False End If End If End Function The MoveNext() method returns a Boolean value, returning False when there are no more items in the collection. In other words, when it reaches the bottom of the list (or the top when doing a descending sor t), it r eturns False to indicate that ther e ar e no more items. The Current property simply returns a reference to the child object corresponding to the current value of mIndex. Of course, mIndex is pointing to an item in the sorted list, and so that value must be cross-referenced back to an item in the original collection. This is the same as in the default pr operty earlier: Public ReadOnly Property Current() As T _ Implements System.Collections.Generic.IEnumerator(Of T).Current Get Return mList(mSortIndex(mIndex).BaseIndex) End Get End Property Private ReadOnly Property CurrentItem() As Object _ Implements System.Collections.IEnumerator.Current Get Return mList(mSortIndex(mIndex).BaseIndex) End Get End Property B ecause SortedEnumerator implements IEnumerator(Of T), it actually has two Current pr oper- ties—one strongly typed for IEnumerator(Of T) itself, and the other loosely typed for IEnumerator (from which IEnumerator(Of T) inherits). Both do the same thing, using the mIndex value to find the appropriate ListItem object in the sor ted list, and then using the BaseIndex pr operty of ListItem to r etrieve the corresponding item in the original collection. That child object is then returned as a result. GetEnumerator Method Collection objects must implement a GetEnumerator() method. This is required by the IEnumerable interface, which is the most basic interface for collection or list objects in the .NET Framework. In CHAPTER 5 ■ COMPLETING THE FRAMEWORK260 6315_c05_final.qxd 4/13/06 12:36 PM Page 260 the case of SortedBindingList, both strongly typed and loosely typed GetEnumerator() methods must be implemented: Public Function GetEnumerator() As _ System.Collections.Generic.IEnumerator(Of T) _ Implements System.Collections.Generic.IEnumerable(Of T).GetEnumerator I f mSorted Then Return New SortedEnumerator(mList, mSortIndex, mSortOrder) Else Return mList.GetEnumerator End If End Function Private Function GetItemEnumerator() As System.Collections.IEnumerator _ Implements System.Collections.IEnumerable.GetEnumerator Return GetEnumerator() End Function These methods merely return an instance of an enumerator object for use by For Each statements that wish to iterate through the items in the collection. If the view is not currently sorted, then it can simply ask the original collection for its enumer- ator. The original collection’s enumerator will already iterate through all the child objects in the collection in their original order: Return mList.GetEnumerator On the other hand, if a sort has been applied, then an instance of the custom SortedEnumerator (implemented in the preceding code) is returned: Return New SortedEnumerator(mList, mSortIndex, mSortOrder) Either way, the compiler-generated code for the For Each statement has an enumerator object that iterates through the items in the collection. Remo ving the Sort The IBindingList interface allows for removal of the sort. The result should be that the items in the collection return to their original order. This is handled by an UndoSort() method: Private Sub UndoSort() mSortIndex.Clear() mSortBy = Nothing mSortOrder = ListSortDirection.Ascending mSorted = False OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, 0)) End Sub Removing a sort is just a matter of setting mSorted to False and clearing the various sort-related fields. Most important is calling Clear() on mSortIndex, as that releases any possible object refer- ences to items in the or iginal collection. Because removing the sort alters the order of items in the view, the ListChanged event is raised to tell the UI that it needs to refresh its display of the collection. CHAPTER 5 ■ COMPLETING THE FRAMEWORK 261 6315_c05_final.qxd 4/13/06 12:36 PM Page 261 Adding and Removing Items Now we get to the complex issues. Remember that SortedBindingList is an updatable view of the original collection. This means that when the user adds or removes an item from the original collec- tion, that change is immediately reflected in the view; the view is even re-sorted, if appropriate. Conversely, if the user adds or removes an item from the view, that change is immediately reflected in the original collection. There’s some work involved in keeping the view and collection in sync. Also remember that collections may raise ListChanged events as they are changed. Table 5-3 l ists the add and remove operations and how they raise events. Table 5-3. Events Raised During Add and Remove Operations Operation Event Behavior AddNew() Called by data binding to add an item to the end of the collection; an ItemAdded type ListChanged event is raised by the collection Insert() Inserts an item into the collection; an ItemAdded type ListChanged event is raised by the collection RemoveAt() Removes an item from the collection; an ItemDeleted type ListChanged event is raised by the collection A ListChanged event is raised when the user adds or removes an item from the original collection. This event must be handled and sometimes reraised by the view. This is illustrated in Figure 5-2. F igur e 5-2 sho ws the simple case , in which both the or iginal collection and the view ar e bound to separate controls on the UI, and an update to the original collection is made. However, when the user adds or removes an item through the view, the view raises a ListChanged ev ent as w ell as updating the original collection. Of course, updating the original collection triggers its ListChanged event. If you’re not careful, this could result in duplicate events being raised, as shown in Figure 5-3. CHAPTER 5 ■ COMPLETING THE FRAMEWORK262 Figure 5-2. Flow of events when the user changes the original collection 6315_c05_final.qxd 4/13/06 12:36 PM Page 262 [...]... rule is used within a business object by associating it with a property A business object does this by overriding the AddBusinessRules() method defined by BusinessBase Such code would look like this (assuming a Using statement for Csla.Validation): 63 15_ c 05_ final.qxd 4/13/06 12:36 PM Page 279 CHAPTER 5 s COMPLETING THE FRAMEWORK _ Public Class Customer Inherits BusinessBase(Of Customer)... defines the parameter as type RuleArgs 63 15_ c 05_ final.qxd 4/13/06 12:36 PM Page 281 CHAPTER 5 s COMPLETING THE FRAMEWORK A business object’s AddBusinessRules() method would associate a property to this rule like this: Protected Overrides Sub AddBusinessRules() ValidationRules.AddRule( _ AddressOf CommonRules.StringMaxLength, _ New CommonRules.MaxLengthRuleArgs("Name", 50 )) End Sub Remember that in Chapter... an instance of SmartDate Using these methods, a developer can write business logic such as this: Dim userDate As DateTime = SmartDate.StringToDate(userDateString) Table 5- 4 shows the results of this function, based on various user text inputs 63 15_ c 05_ final.qxd 4/13/06 12:36 PM Page 271 CHAPTER 5 s COMPLETING THE FRAMEWORK Table 5- 4 Results of the StringToDate Method Based on Various Inputs User Text... building Web Services Business objects should not be returned directly as a result of a web service, as that would break encapsulation In such a case, your business object interface would become part of the web service interface, preventing you from ever adding or changing properties on the object without running the risk of breaking any clients of the web service 2 85 63 15_ c 05_ final.qxd 286 4/13/06...63 15_ c 05_ final.qxd 4/13/06 12:36 PM Page 263 CHAPTER 5 s COMPLETING THE FRAMEWORK Figure 5- 3 Duplicate events raised when the user changes the view In this case, the UI control bound to the sorted view gets the ListChanged event twice, which is wasteful But when the change is applied to the original collection, its event could flow back to the view and then to the UI Figure 5- 4 shows what... for EmptyIsMin, while the second allows the caller to specify the value Neither is hard to implement given the constructors already present in the code 63 15_ c 05_ final.qxd 4/13/06 12:36 PM Page 273 CHAPTER 5 s COMPLETING THE FRAMEWORK Text Functions Next, let’s implement functions in SmartDate that support both text and DateTime access to the underlying DateTime value When business code wants to expose... copied into and out of business objects In the case of Web Forms data binding, data comes from the page in a dictionary of name/value pairs, which must be copied into the business object’s properties With Web Services, the data sent or received over the network often travels through simple data transfer objects (DTOs) The properties of those DTOs must be copied into or out of a business object within... though, is that the business object’s data access code never has to worry about getting a null value from the database The implementation of IDataReader is a lengthy business it contains a lot of methods— so I’m not going to go through all of it here Instead I’ll cover a few methods to illustrate how the overall class is implemented 63 15_ c 05_ final.qxd 4/13/06 12:36 PM Page 283 CHAPTER 5 s COMPLETING THE... a framework class like SmartDate Common Business Rules The BusinessBase class implemented in Chapter 3 includes support for validation rules Each rule is a method with a signature that conforms to the RuleHandler delegate A business object can implement business rules conforming to this delegate, and then associate those rule methods with the properties of the business object Most applications use a... should provide arithmetic manipulation of the date value Since the goal is to emulate a regular DateTime data type, it should provide at least Add() and Subtract() methods: 63 15_ c 05_ final.qxd 4/13/06 12:36 PM Page 2 75 CHAPTER 5 s COMPLETING THE FRAMEWORK Public Function Add(ByVal value As TimeSpan) As Date If IsEmpty Then Return Me.Date Else Return Me.Date.Add(value) End If End Function Public Function . then the call simply delegates to that interface: CHAPTER 5 ■ COMPLETING THE FRAMEWORK 255 63 15_ c 05_ final.qxd 4/13/06 12:36 PM Page 255 If TypeOf Key Is IComparable Then Return DirectCast(Key,. pointer into the collection. Table 5- 2 describes these elements. CHAPTER 5 ■ COMPLETING THE FRAMEWORK 258 63 15_ c 05_ final.qxd 4/13/06 12:36 PM Page 258 Table 5- 2. Properties and Methods of an Enumerator. direction As System.ComponentModel.ListSortDirection) CHAPTER 5 ■ COMPLETING THE FRAMEWORK 253 63 15_ c 05_ final.qxd 4/13/06 12:36 PM Page 253 mSortBy = Nothing If Len(propertyName) > 0 Then Dim