[SOLVED] ListConverterServiceBase Method
Printed From: IdeaBlade
Category: DevForce
Forum Name: DevForce Classic
Forum Discription: For .NET 2.0
URL: http://www.ideablade.com/forum/forum_posts.asp?TID=514
Printed Date: 25-Mar-2025 at 4:03am
Topic: [SOLVED] ListConverterServiceBase Method
Posted By: Linguinut
Subject: [SOLVED] ListConverterServiceBase Method
Date Posted: 19-Oct-2007 at 12:25pm
What does this method do?
AddListManager_KeepExistingEntitiesOnly(converter);
|
Replies:
Posted By: Bill Jensen
Date Posted: 19-Oct-2007 at 2:30pm
I concede--this is a rough neighborhood for walking the dog.
It adds a list manager to the EntityList of a ListConverter. It is invoked by the ListService (in the Cabana solution, Foundation module) when creating ListConverters for certain entity types.
It supplies the newly-created ListManager with a delegate to a predicate function that determines if a specific entity should be kept in the list.
This particular predicate function (IsExistingEntity) returns true only if the entity is either modified or unchanged and returns false if the entity is added or deleted.
I suspect Ward intended this as an example.
If you're calling this on your ListConverter, it might explain why your newly-added items don't show up.
On the other hand, this code also suggests how to add a ListManager to filter the entities contained in the list.
Bill J.
|
Posted By: Linguinut
Date Posted: 19-Oct-2007 at 3:39pm
Thanks for the info. I have employed this method a dozen times in the ListConverterService. The service is a good candidate for futher encapsulation. There is quite a bit of redundancy as time goes on.
Where is this method defined? I thought it should be in ListConverterServiceBase, but I do not see it in there. Actually, I can see it in the object browser, but not when I try to go to the definition itself. I'll open the LIB solution and see what I can find there. I am not sure how this usage suggests filtering is possible.
|
Posted By: Bill Jensen
Date Posted: 19-Oct-2007 at 4:29pm
It IS in ListConverterServiceBase.
Be aware that the Go To Definition function won't navigate into the source files under ExternalSource in the Cabana solution since they are not built as part of this solution. They are there for reference only.
You could attach a ListManager to your List, passing a delegate to a different predicate--one that determines if the customer address is for the current customer, for example.
Bill J.
|
Posted By: Linguinut
Date Posted: 19-Oct-2007 at 4:35pm
The method is a protected static method of ListConverterServiceBase in IdeaBlade.Cab.UI.Services (hence, no "go to definition").
protected static void AddListManager_KeepExistingEntitiesOnly<T>(EntityListConverter<T> pListConverter) where T : Entity
{
EntityList<T> aList = (EntityList<T>) pListConverter.ListSource.List;
EntityListManager<T> listManager = new EntityListManager<T>(pListConverter.EntityManager.PersistenceManager, IsExistingEntity, null); // No hint available; DF can't listen for changes to RowState (yet).
aList.ListManager = listManager;
}
First of all...what is meant by "no hint available..."? Second, are you suggesting that another method could be created that would accept a filter parameter, but, obviously, do roughly the same thing? The IsExistingEntity would be replaced by a customer index, and the null would be replaced by the foreign key column name, right?
|
Posted By: Linguinut
Date Posted: 19-Oct-2007 at 4:43pm
From the comments in the EntityListManager:
"The IdeaBlade.Persistence.EntityListManager<T>.Filter uses a .NET Predicate<T>, a delegate that defines a set of criteria and determines whether the specified object passed to it meets those criteria. The Filter receives every addition, deletion or modification (based on the IdeaBlade.Persistence.EntityListManager<T>.FilterColumns specified) of entities of the type watched, and determines whether the entity belongs in the list. If the Filter returns true the entity is added to or kept in the list; if the Filter returns false the entity is removed from the list. (Note that entities removed from the managed list are not removed from the PersistenceManager, nor are they deleted.) Performance will be needlessly poor if the EntityListManager tests an entity every time any of its columns change. To avoid this, specify in the constructor or the IdeaBlade.Persistence.EntityListManager<T>.FilterColumns property which entity columns are relevant to the filtering."
Once I unravel what this is saying, I think I could get this to work. Time to do a little homework on .NET Predicate<T>.
|
Posted By: Linguinut
Date Posted: 20-Oct-2007 at 9:41am
Interesting statement:
// Summary:
// Gets or sets the System.Predicate used to set filtering criteria for the
// list(s).
//
// Remarks:
// The filter should return true if the supplied Entity belongs in the list(s)
// managed.
// Setting or resetting the filter does not automatically refresh the list(s).
public Predicate<T> Filter { get; set; }
If the predicate changes, the list does not change. A list refresh will need to be provoked. Am I reading that correctly?
|
Posted By: Linguinut
Date Posted: 20-Oct-2007 at 10:19am
Would this work in the ListConverterService?
protected void AddListManager_FilteredList<T>(EntityListConverter<T> pListConverter, int pFilter, EntityColumn pColumn) where T : Entity
{
mIndex = pFilter;
mColumn = pColumn;
EntityList<T> aList = (EntityList<T>)pListConverter.ListSource.List;
EntityListManager<T> listManager = new EntityListManager<T>
(pListConverter.EntityManager.PersistenceManager,
IsExistingEntity,
null);
aList.ListManager = listManager;
}
private bool IsExistingEntity<T>(T pEntity) where T : Entity
{
// Is the foreign key equal to the presented index
if (mIndex == (int)pEntity.GetColumnValue(mColumn.ColumnName)) { return true; };
return false;
}
private int mIndex = 0;
private EntityColumn mColumn;
How do I pass the index on which to filter the list? The ListConverterService takes no arguments.
|
Posted By: Linguinut
Date Posted: 20-Oct-2007 at 10:45am
Thinking about this again, I should be able to pass the column in order to create the EntityListManager. What is stumping me is this Predicate<T> animal. Even after reading and seeing other examples, I am not clear on how this thing works. The biggest question remains...how do I pass the parent entity index on which the child list is filtered? This seems so fundamental that there has to be a solution staring me in the face and I am just not seeing it.
|
Posted By: Linguinut
Date Posted: 20-Oct-2007 at 2:49pm
Here's a good article: http://msdn.microsoft.com/msdnmag/issues/06/09/AdvancedBasics/ - http://msdn.microsoft.com/msdnmag/issues/06/09/AdvancedBasics/
Sure wished they taught this in kindergarten.
|
Posted By: Linguinut
Date Posted: 22-Oct-2007 at 10:52am
Well, I've been pondering, meditating and researching. I am more confused than ever. If someone could step in here and straighten out my thinking, I would really appreciate it.
|
Posted By: Bill Jensen
Date Posted: 22-Oct-2007 at 12:24pm
Take heart, you're on the right track.
Your code is basically correct--I see you corrected the assignment in the conditional in the predicate.
A Predicate<T>() is just a delegate to a method that accepts a parameter of type T and returns a boolean. The method can determine its return value however it pleases.
Your remaining problem is "how does the ListConverterService, and hence the predicate, know what to compare against to decide if an entity should be included in the list?" From what I understand, you want the list (of customer addresses, for example) to track the currently selected item (e.g., customer) in some BindingSource<Customer> somewhere. Here are two possibilities:
1. In the module that owns the customer binding source, add it to the root workitem's Items collection (as early as possible so it's there when the ListConverterService needs it). Then, when asked for a CustomerAddressListConverter, the ListConverterService can retrieve it and supply it to the newly created CustomerListConverter.
Note that unlike Commands, Services, Workspaces, etc., the Items collection is not inherited from parent workitems. This means that
(a) You could add the BindingSource to the module's workitem if all requirements for synchronized list converters are contained within that module or
(b) You would need to add it to the root workitem if synchronization requirements cross module boundaries.
2. Modify your ListConverterService to implement an additional interface ISynchronizedListConverterService<T> with a single member that accepts a BindingSource<T>. This method merely stores the BindingSource in a member so it can be supplied to new ListConverters. Register your ListConverterService under this interface and inject it into the controller owning the customer binding source.
In any case, your list converter may need to attach to the CurrentChanged property of the binding source in order to refresh the list.
Hope this gets you moving.
Bill J.
|
Posted By: Linguinut
Date Posted: 22-Oct-2007 at 2:14pm
In my page controller, I have overridden the InitializeMainBindingSource method, like this:
protected override void InitializeMainBindingSource()
{
base.InitializeMainBindingSource();
WorkItem.Items.Add(MainBindingSource, "SalesOrderBindingSource");
}
Now, when I want to reference the current item, I would use something like this:
EntityBindingSource mBS = WorkItem.Items.Get<EntityBindingSource>("SalesOrderBindingSource");
SalesOrderMaster mSO = (SalesOrderMaster)mBS.Current;
int mIndex = mSO.Custindex;
When I run this code, the EntityBindingSource (mBS) comes back null. Am I not adding the binding source early enough?
|
Posted By: Linguinut
Date Posted: 22-Oct-2007 at 2:42pm
The code needs to read
WorkItem.RootWorkItem.Items.Add(MainBindingSource, "SalesOrderBindingSource");
The ListConverterService is not aware of the modules work item. Now that it is with the use of the RootWorkItem Items collection, there is still a problem. The ListConverterService is not running MakeMyListConverter but once when the view is created. If I am wanting the ListManager to reference the current customer index, then I need to do something else, somewhere else. The predicate has to exist elsewhere, it seems.
Am I getting derailed?
|
Posted By: Linguinut
Date Posted: 22-Oct-2007 at 2:58pm
So why doesn't this work:
protected void AddListManager_FilteredList<T>(EntityListConverter<T> pListConverter, EntityColumn pColumn) where T : Entity
{
mColumn = pColumn;
EntityList<T> aList = (EntityList<T>)pListConverter.ListSource.List;
EntityListManager<T> listManager = new EntityListManager<T>
(pListConverter.EntityManager.PersistenceManager,
IsExistingEntity,
null);
aList.ListManager = listManager;
}
private bool IsExistingEntity<T>(T pEntity) where T : Entity
{
// Is the foreign key equal to the presented index
EntityBindingSource mBS = WorkItem.Items.Get<EntityBindingSource>("SalesOrderBindingSource");
SalesOrderMaster mSO = (SalesOrderMaster)mBS.Current;
if (mSO != null) { mIndex = mSO.Custindex; }
if (mIndex == (int)pEntity.GetColumnValue(mColumn.ColumnName)) { return true; };
return false;
}
private int mIndex = 0;
private EntityColumn mColumn;
Shouldn't the ListManager be using the predicate method to detemine the inclusion of a given entity? Perhaps I am misunderstanding the usage of this predicate thing.
|
Posted By: Linguinut
Date Posted: 22-Oct-2007 at 3:21pm
I walked this dog. Man, is he big! Each entity in the list was run through the predicate. That seems to be working. Now, I need to get the list to "refresh" when the SalesOrderMaster.Custindex changes. What is the slick way of doing that? How is it done with the ListConverter?
By the way, this code will only work with the SalesOrder entity. I should think of a way of getting this to work for other entites, as well, and not limit its implementation.
|
Posted By: Bill Jensen
Date Posted: 22-Oct-2007 at 3:54pm
Keep walking, man...
As I mentioned, your ListConverter will need to listen for the CurrentChanged event of the binding source and refresh the list. This will cause the predicate to be re-evaluated for each member of the list.
B.
|
Posted By: Linguinut
Date Posted: 22-Oct-2007 at 4:40pm
I have the Event Pub/Sub working fine, so the app knows when the customer changes on the sales order; however, how do I tell the BindingSource of the ListConverter to refresh? This is a bit of a mystery. In the ListConverterServiceBase, the "hack" iterates the binding sources and reset the bindings on each one. In what way am I to call just the binding source of the list that is populating the control on my view?
|
Posted By: Linguinut
Date Posted: 22-Oct-2007 at 5:12pm
I tried adding the ListConverter to the RootWorkItem Items collection:
WorkItem.RootWorkItem.Items.Add(converter, "BillToListConverter");
Then, in the view's presenter, I added the EventSubscription:
[EventSubscription(EventTopicNames.SalesOrderCustomerChange)]
public void OnChangeCustomer(object sender, DataEventArgs<int> e)
{
//somehow update the view's address list
ListConverter mConverter = WorkItem.RootWorkItem.Items.Get<ListConverter>("BillToListConverter");
mConverter.ListSource.ResetBindings(false);
}
This is getting called every time I change the customer number (navigate to a different sales order, type a customer number, pick customer from list, etc.). The predicate is *not* running, though. It runs the first time the converter is created, but not any subsequent times. Is there another way to get the list converter to refresh?
|
Posted By: Linguinut
Date Posted: 22-Oct-2007 at 5:38pm
Here's how I referenced the ListConverter (rather than adding it to the RootWorkItem items collection):
ListConverter mConverter = ListConverterService.Get(ListConverterNames.BillToList);
That works nicely.
Now, the next problem...the parent binding source (SalesOrder) is not being retrieved from the WorkItem. It keeps coming up with the current value of null in the EntityBindingSource. Am I doing something wrong in the page controller with this code?
WorkItem.RootWorkItem.Items.Add(MainBindingSource, "SalesOrderBindingSource");
|
Posted By: Linguinut
Date Posted: 23-Oct-2007 at 9:51am
As soon as I did this (instead of trying to use the WorkItem) in the ListConverterService:
[EventSubscription(EventTopicNames.SalesOrderCustomerChange)]
public void OnChangeCustomer(object sender, DataEventArgs<int> e)
{
mCustIndex = e.Data;
}
...the ListConverter stopped reevaluating when I sent the ResetBindings command. I can walk the code through to the ResetBindings command, but it now does not engage the predicate. Why would the addition of the EventSubscription changed things?
|
Posted By: Linguinut
Date Posted: 23-Oct-2007 at 10:17am
Let me summarize a little:
In an attempt to understand the AddListManager_KeepExistingEntitiesOnly(converter) method in ListConverterServiceBase, I embarked on a journey to see if a filter could be applied using a similar approach. I added the following code to the ListConverterService in the Foundation module:
protected void AddListManager_FilteredList<T>(EntityListConverter<T> pListConverter, EntityColumn pColumn) where T : Entity
{
mColumn = pColumn;
EntityList<T> aList = (EntityList<T>)pListConverter.ListSource.List;
EntityListManager<T> listManager = new EntityListManager<T>
(pListConverter.EntityManager.PersistenceManager,
IsExistingEntity,
null);
aList.ListManager = listManager;
}
private bool IsExistingEntity<T>(T pEntity) where T : Entity
{
if (mCustIndex == (int)pEntity.GetColumnValue(mColumn.ColumnName)) { return true; };
return false;
}
[EventSubscription(EventTopicNames.SalesOrderCustomerChange)]
public void OnChangeCustomer(object sender, DataEventArgs<int> e)
{
mCustIndex = e.Data;
}
private int mCustIndex = 0;
private EntityColumn mColumn;
I have removed comments for brevity. In my sales order module, and on the view that presents the sales order data (including customer), I added the EventPublication code. I am not reproducing it here since it it not directly relevant. In my view that presents the address list, I have the following code running:
[EventSubscription(EventTopicNames.SalesOrderCustomerChange)]
public void OnChangeCustomer(object sender, DataEventArgs<int> e)
{
ListConverter mConverter = ListConverterService.Get(ListConverterNames.BillToList);
mConverter.ListSource.ResetBindings(false);
}
Note that both the ListConverterService and my AddressView are listening to the customer number change. The ListConverterService updates the variable that stores the current customer index which will be used in the predicate. The AddressView fires off a ResetBindings on the listconverter underlying the list control on the form. This looks like it should work, but once I introduced the EventSubscription to the ListConverterService, the ResetBindings no longer evaluated the objects in the list by using the predicate.
What fundamental principle am I violating that would cause this?
Thanks,
Bill
|
Posted By: Bill Jensen
Date Posted: 23-Oct-2007 at 11:25am
ResetBindings may not do the job. Try
EntityList<T> aList = (EntityList<T>)pListConverter.ListSource.List;
EntityListManager<T> listManager = (EntityListManager<T>)aList.ListManager;
listManager.RefreshAllLists();
Moreover, since there is only one ListConverter with these filtering conditions (and hence only one ListManager), you could keep the ListManager around in the ListConverterService (in a mListManager member along with mCustIndex and mColumn), then put
mListManager.RefreshAllLists()
in the event hander in the service. Then you don't need the event handler in your view.
There are other factoring and coupling considerations, but we'll discuss those later.
Bill J.
|
Posted By: Linguinut
Date Posted: 23-Oct-2007 at 12:12pm
The aList.ListManager is an implementation IListManager. The EntityListManager<T> has the RefreshAllLists method. Looks like I would have to sacrifice generics in order to make this work, since I need an EntityListManager object to do the work. Right?
|
Posted By: Bill Jensen
Date Posted: 23-Oct-2007 at 1:10pm
Why? You created it here:
protected void AddListManager_FilteredList<T>(EntityListConverter<T> pListConverter, EntityColumn pColumn) where T : Entity
{
mColumn = pColumn;
EntityList<T> aList = (EntityList<T>)pListConverter.ListSource.List;
EntityListManager<T> listManager = new EntityListManager<T>
(pListConverter.EntityManager.PersistenceManager,
IsExistingEntity,
null);
aList.ListManager = listManager;
}
Can't you just cast it back to an EntityListManager<yourtype>.
|
Posted By: Linguinut
Date Posted: 23-Oct-2007 at 1:16pm
I cast it back in the EventSubscription. I was trying to do it elsewhere.
Here is my adjusted code that works...
protected void AddListManager_FilteredList<T>(EntityListConverter<T> pListConverter, EntityColumn pColumn) where T : Entity
{
mColumn = pColumn;
EntityList<T> aList = (EntityList<T>)pListConverter.ListSource.List;
EntityListManager<T> listManager = new EntityListManager<T>
(pListConverter.EntityManager.PersistenceManager,
IsExistingEntity,
null);
aList.ListManager = listManager;
mListManager = aList.ListManager;
}
private bool IsExistingEntity<T>(T pEntity) where T : Entity
{
if (mCustIndex == (int)pEntity.GetColumnValue(mColumn.ColumnName)) { return true; };
return false;
}
[EventSubscription(EventTopicNames.SalesOrderCustomerChange)]
public void OnChangeCustomer(object sender, DataEventArgs<int> e)
{
mCustIndex = e.Data;
EntityListManager<AddressMaster> mBillToListManager = EntityListManager<AddressMaster>)mListManager;
mBillToListManager.RefreshAllLists();
}
private IListManager mListManager;
private int mCustIndex = 0;
private EntityColumn mColumn;
Bill...thanks for all of your help on this. Now, that I have gotten it working, I will try to separate the functionality a bit more so that I can reuse the code for other entity lists. For now, I am glad that it works.
|
Posted By: Bill Jensen
Date Posted: 23-Oct-2007 at 3:04pm
Great. Glad to hear it's working. There are a few structural issues to consider:
1. Currently, the ListConverterService knows about the event topic and holds the member variables used by the predicate. It might be better to create a custom ListConverter class ("SynchronizedCustomerListConverter") that inherits from EntityListConverter<Customer>. It could add the ListManager (perhaps using a generic method in the ListConverterService) but retain the member variables, predicate and subscribe to the event topic.
2. Since there is actually only one "SynchronizedCustomerListConverter", all views that use it will be synchronized to the same customer index published by the SalesOrderCustomerChange event. This presumes there is one master selected customer for the entire application. If you needed to edit sales orders for two different customers simultaneously (in popup windows for example) this mechanism would break down.
3. An alternative is to not use the ListConverterService at all, but let the presenter of the view containing the customer combo box create it and supply it to the view. The presenter could then learn of the binding source holding the current customer (from its view context) and subscribe to its CurrentChanged event. When the current customer changed, it would refresh the list, either using the predicate mechanism or by re-querying and assigning to the binding source.
Bill J.
|
Posted By: Linguinut
Date Posted: 23-Oct-2007 at 3:11pm
I am pondering the first approach. #2 will definitely need to be considered as I lay this out.
#3 didn't seem to work, unless I wasn't doing something correctly. The problem with the this approach was that the list would not synch with the parent entity. I would have a filled list with the proper filtered data, but it would always default to none selected. Somehow the reassigning of the binding source corrupted the original controlbindingmanager.
Thanks for the additional tips!
|
Posted By: orcities
Date Posted: 24-Oct-2007 at 1:51pm
Bill,
I have been doing your (3) example. Letting the presenter control the information. The problem is that I, on occassion, don't get the correct response. For example the combo box will fill with the data needed but it will not bind the current item to it.
Or you can select an item in the box but it never holds. As soon as you leave the box it goes back to being blank. The value is usually set correctly but it will not keep the current selection in the box.
Do you know why this could be happening.
|
Posted By: Linguinut
Date Posted: 24-Oct-2007 at 6:01pm
I did get the same behavior on the .NET controls. The DevEx LookUpEdit control works like a charm. I am systematically replacing all .NET controls with DevEx controls (grids, toolbars, textboxes, etc.). By the way, I just got a license for the controls.
Also, I am using the two-stage approach to the ListConverters (per Cabana examples). One ListConverter is for the master list designed to display the proper string. The second ListConverter is a copy of the first but is designed for the primary index of the entity. When I add a LookUpEdit, I am binding it to the foreign key field, not the related entity list. So far, this is working nicely for me (although, I have not dug into how this two-stage approach works).
Is anything that I said different than what you are doing?
Bill
|
|