A silly question???

Topics: CAB & Smart Client Software Factory
Feb 14, 2007 at 8:23 PM
Hello,

In my current application, I need to display customer information and allow update on it.

From my understanding, to do that, I need a workitem. This one has to contain the view(+presenter), services (to load/update/persist a customer) as well as a state bag that will contain the customer instance the view has to work with.

Then when the workitem is run, I fill the State bag then show the view that will inject the state into the presenter.

Am I right?

If yes, why do I have to do that? Why can't I just add a private property (Customer _customer) with getter and setter directly in my presenter? (so the presenter's constructor would be "public CustomerViewPresenter(Customer customer)?

Thanks for your explanation :-) what are the con's and pro's? why is it a best practise?

Cheers,

Silat
Feb 15, 2007 at 5:13 AM
You do not have to use the State bag. In fact, I'd advise against it.

The State bag is really only meant to be used in one case: where you want to persist the data to the local hard drive in a cached scenario for rapid retrieval later. I believe the Caching service is meant to be used in conjuntion with the State bag in that scenario. But you don't have to use it.

We don't use it where I work. In fact, we explicitly avoid it for a couple of reasons, clarity of code and simplicity of architecture being a couple.


Besides, there's a perfectly good alternative already available: the WorkItem. A WorkItem is a dependency injection container. If you had a need to "store" a business object somewhere the WorkItem's Item collection would be a better location. When you store a business object that way you can give it a strong name to reference it later. More importantly, since WorkItems operate in a hierarchical fashion, your business object will be available to any subsequent objects in the hierarchy that can handle dependency injection via the ObjectBuilder framework - like views and presenters.

As far as a best practice: I don't know that there's a "best practice" for this kind of thing because what we're really talking about here is system architecture, and you can do things a lot of different ways depending on what technology and patterns you want to implement.

I can only tell you what we do: We have an employee editing Use Case that is similiar to a customer use case. We allow a user to edit multiple employees at one time, but only one employee ever has its data displayed in the view. When a user opens an employee for editing, our presenter makes a call to a service to fetch that Employee object and then adds it to a list of employees. That list is stored in the WorkItem.Items collection under a srong name for easy referencing in subsequent presenters (because our Employee editing screen is really a TabWorkspace, and we have several SmartParts each plugging into a Tab, and each SmartPart has its own Presenter - and they all can easily get a reference to the Employee through the WorkItem.Items collection).

What we store internally in our Presenter is a reference to the currently open EmployeeID. If a new employee is opened for editing, or an already opened employee is selected for editing, then we take the ID and store it locally in the Presenter (as a private member variable), and then use that ID to reference the correct Employee business object contained in the list that is stored in the WorkItem.Items collection. It's then up to the individual SmartParts to grab the Employee object via the EmployeeID and bind whatever data they need to their views.

As to this question: "Why can't I just add a private property (Customer _customer) with getter and setter directly in my presenter?"

You could, but you should probably ask this question first: do you want to fetch the Customer object in the WorkItem and pass it to the Presenter via constructor (or property) injection, or do you want your Presenter to get injected with the service and fetch the customer object itself?

In our case, we (almost) never have WorkItems talk to services. We inject our Presenters with the services they need and then they fetch the business objects they require. We do that for a specific reason: unit testing.

WorkItems are creational for us. We limit what they can do and what responsibilities they have, and for the most part they are charged with one particular thing: creating views, presenters and services. Since they are creational in this regard they are much more difficult to unit test, since you can't mock what they are creating. Thus, we try and extract all other logic out of the WorkItem objects and only use the WorkItems as dependency injection "containers". Not objects that perform a lot of logic.

That means most of our "logic" is housed in the Presenters, which are considerably more testable. We can mock everything that a presenter has access to: business objects, services, views, you name it. I realize not every does TDD, but we've found that it has helped us create more clearly defined roles for the objects in a CAB application, and that has helped us create a more consistent architecture. After all - the CAB is so flexible that there is literally a dozen ways to solve any given problem. We find as as development team that consistency really helps with our productivity, and so we limit what WorkItems do.

Feb 15, 2007 at 7:10 AM
Thank you so much for sharing your point of view!

I am having a better undersanding now.

++
Feb 18, 2007 at 10:43 PM
Chris, could you elaborate on the use of the WorkItem.Items collection as a means to share an employee list amongst SmartParts. Am I correct in assuming that the presenter which has EmployeeID as a private member variable belongs to the view in which an Employee is selected by the user? If so, and the EmployeeID is private, how do other SmartParts access this ID in order for them to use it to get the correct Employee object from the list?

The context for my question is that I'm having trouble deciding the "best" way to pass the selected Business Entity (BE) from a view used to select a BE into a view used to edit an individual BE. I cannot use a single (strong named) element in the WorkItem.Items collection because the UI allows several BEs to be open for edit at the same time (via a tabbed workspace - one tab per record). I cannot fire an event to the edit view (containing a BE parameter), because it does not yet exist (it's created on demand). I have considered several possible solutions, but have yet to find one I'm happy with. My options so far are:

1. Spawn a new WorkItem for each edit view - then I can use a named element in the WorkItem.Items collection to pass in the BE to be edited. Seems a bit heavyweight to have a WI per view/BE.

2. Use a Service to hold a reference to the currently selected BE - set by the selection view prior to launching the record editor view. DI this service into the presenter for the edit view and read the selected BE during initialisation. I'll end up with lots of selection Services for the different BEs in the app (or one big service for all of them).

3. Pass the BE to the view via a constructor parameter, then pass it on to its presenter via a public property. Seems like a hack.

4. Make the presenter a public property of the view and make the BE a public property of the presenter. Same hack as in 3, but at least the view is bypassed when setting the BE property of the presenter.

A WorkItem.Items element containing a list of BEs sounds like a solution, but I'm not sure exactly how. It initially seems that I just swap the problem of how to pass BEs around, for the problem of how to pass BE IDs around.

I'm sure I'm coming at this from the wrong direction, and my question/solutions are therefore a bit dumb. I've only been working with the CAB/SCSF for a couple of months and I'm still on the near-vertical part of the learning curve.

Any guidance or sample code would be greatly appreciated.
Feb 18, 2007 at 11:46 PM
Am I correct in assuming that the presenter which has EmployeeID as a private member variable belongs to the view in which an Employee is selected by the user?

Yes.

If so, and the EmployeeID is private, how do other SmartParts access this ID in order for them to use it to get the correct Employee object from the list?

We fire an EventBroker event and pass the ID as an argument of type EventArgs<int>.

I cannot use a single (strong named) element in the WorkItem.Items collection because the UI allows several BEs to be open for edit at the same time (via a tabbed workspace - one tab per record).

Yes, but doesn't each business entity have it's own unique ID? If so, that's your key.

Our Employee Entities are kept inside a collection called Employees. It's sort of a List<Employee>. The list is then stored in the WorkItem.Items collection under a key "Employees". Whenever a view needs a specifc Employee it just fetches the List<Employee> from the WorkItem.Items collection and then references the specific Employee via EmployeeID (since our list is a custom list, and we've overridden certain methods like Find() to take an EmployeeID as a parameter).

_ I cannot fire an event to the edit view (containing a BE parameter), because it does not yet exist (it's created on demand)_

Sure you can. Don't think too linearly here. Opening an Employee for editing and showing an employee for editing are two different things to us, and as such they have two different EventBroker events. When a user opens an Employee for editing we fire an OpenEmployee event. A service facade is queried for the specific Employee object. It gets added to the list of Employees stored in the Workitem.Items collection. It is at this point in your scenario that you could also create your edit view and now it would be ready to receive Show events.

For us, once the Employee entity is loaded we fire a second event: LoadEmployeeData, passing the EmployeeID as an EventArgs parameter. This is the Event that all of our Employee editing views listen for, since we have several different SmartParts that need to update themselves when an employee needs to have its details displayed.

But this brings up a larger issue. It sounds to me like you are creating a new view for every employee being edited. I would not do that. I would only create one set of unique views for editing an employee or customer or similiar entity. Those views should be created when the Employee editing WorkItem is loaded, under whatever scenario causes it to load (a menu item, an Outlook button, whatever). Then you just load the view with the specific entity being edited. Reload the view data (updated it, basically) anytime a new employee is opened, or a currently opened employee "gets focus", so to speak.

Spawn a new WorkItem for each edit view - then I can use a named element in the WorkItem.Items collection to pass in the BE to be edited. Seems a bit heavyweight to have a WI per view/BE.

Agreed. I would not favor this method.

Use a Service to hold a reference to the currently selected BE - set by the selection view prior to launching the record editor view. DI this service into the presenter for the edit view and read the selected BE during initialisation. I'll end up with lots of selection Services for the different BEs in the app (or one big service for all of them).

There's no need to do this. Too much work. A private member variable in a Presenter is all you need.

Pass the BE to the view via a constructor parameter, then pass it on to its presenter via a public property. Seems like a hack.

Yes, it is. This is why I suggest the WorkItem.Items collection, because the Presenter has direct access to it. No need to circumvent anything.



In these sorts of situations the EventBroker really is your best friend. It's the best way to get several pieces of the puzzle synchronized together and operating in concert. Just don't limit yourself to one EventBroker event, and don't recreate views that already exist. If you have a view that allows you to edit an Employee's name and other data, then you only need one of those views in memory, and you can just reload its data as necessary. Thinking about your architecture in these ways might lead to a simpler design.







Feb 19, 2007 at 10:46 AM
Thanks very much for spending the time to provide such a detailed and illuminating response. The use of two separate events to open/load a view sounds like a good plan.

But this brings up a larger issue. It sounds to me like you are creating a new view for every employee being edited. I would not do that. I would only create one set of unique views for editing an employee or customer or similiar entity. Those views should be created when the Employee editing WorkItem is loaded, under whatever scenario causes it to load (a menu item, an Outlook button, whatever). Then you just load the view with the specific entity being edited. Reload the view data (updated it, basically) anytime a new employee is opened, or a currently opened employee "gets focus", so to speak.

You've hit the nail on the head - that's exactly what I'm doing, creating a new view for each BE that's opened for edit. I take your point that this is a bad plan, one view in RAM would be much better. As I have a tab per record, I'll need to spoof having the same instance of the view on several tabs. I can't just use a tab strip (only one client area for all tabs) because other types of data can be displayed in the same tab workspace at the same time. I guess I need to move the view from tab to tab within the workspace and re-load it with the appropriate BE in response to the tab changed event.

Would a SmartPartPlaceholder make sense here? I can open several tabs in a workspace that just contain a skeleton view with a placeholder in it, then move the edit BE view from one placeholder to another when the active tab changes?

As before I'm sure that I'm over-complicating things...
Feb 19, 2007 at 3:45 PM
Interresting discussion...
For my point of view, I would prefer the solution of one WI per BE because then I could easly transform my TabWorkSpace in a WindowWorkspace to allow the user to compare 2 instances of BEs.
The switch between TabWorkSpace AND WindowWorkSpace could be a cool feature to offer to the user.
Feb 20, 2007 at 12:42 AM
For my point of view, I would prefer the solution of one WI per BE because then I could easly transform my TabWorkSpace in a WindowWorkspace to allow the user to compare 2 instances of BEs.

You could do that anyway. A Workspace doesn't display WorkItems - it displays Views :-)

The question is really this: do you want to instantiate a new view every single time you open a business entity for editing (thus a 1-to-1 ratio of business entities to views), or do you want to limit the amount of views that the user can open (and thus the amount of memory they can actually hog with your application).

The one thing you cannot know for certain is how much memory the user has on their machine. And memory management in C# is not like it was in C or C++. You can't deallocate something and expect to immediately get that memory back. The CG is going to determine when you get your memory back. I think that's a pretty important distinction and I'd be cautious of creating a situation where the user could command huge chunks of memory. You may look at this particular situation and say, "Oh, but it's only this one time and they aren't going to create that many views," but in my experience anytime you make an architectural decision like this you open yourself up to repeat offenders. Another developer sees that you're instantiating a new view every time a business object is opened, and they think that's standard operation procedure, so they do the same thing. Next thing you know you've got this pattern littered throughout your application and memory management starts to affect performance.

I would look at alternate ways to indicate to the user that what you're doing is allowing them to edit multiple customers/employees. For instance, in our case we make use of the OutlookBar on the left hand side of our application. When the user opens an Employee for editing, we put that employee's name, plus a little icon, in a ListView control in the OutlookBar. The employee's details are opened in a TabWorkspace, with each view in the TabWorkspace showing a unique aspect of the those details. The list on the left-hand side in the OutlookBar grows as the user opens more employees, but we never have more than one TabWorkspace with one set of distinct views open. Each time the user clicks on an Employee name in the ListView we fire an event that informs the employee editing views to update themselves by loading that Employee's data. In this way we avoid opening two or more version of the same SmartPart/View.

If we wanted to allow a person to compare two employees directly with two different WindowWorkspaces, we could do that and we'd still never have more than 3 of the same smartpart open at one time (one for the TabWorkspace aspect and one for each WindowWorkspace).

Bottom line (and I don't meant to sound like a jackass here): I think memory management is really important. I am by no means an expert in this area (which probably accounts for some of my caution), but I think it is in the best interest of every developer to really take into account memory management. Do you really need to allow the user to instantiate all of the objects, views and classes that they are instantiating? What are the ramifications of allowing them to do that? How will it affect your overall application performance when they've opened a lot of stuff (and users will do this). I think these are important questions that we have to keep asking and analyzing, or else you could severely hurt your application in the long run.

/steps off soapbox





Feb 20, 2007 at 9:17 AM
As ever, good point well made.
I am certainly won over to the single view approach. I like the visual approach of using a ListView to indicate several records are "open" for edit, but only display one. The problem I have is that our business analysts have already agreed the tab-per-record design with the customer and they are really not keen to change it. Grrr...
I think I'll at least have a stab at the SmartPartPlaceholder approach and see how it goes. The only other thing I can thing of is attempting to add the same view to the TabWorkspace several times, but with different keys - is this possible, or advisable?
Thanks again for all the great guidance guys.
Feb 20, 2007 at 2:51 PM
The only other thing I can thing of is attempting to add the same view to the TabWorkspace several times, but with different keys - is this possible, or advisable?

That would be my other suggestion. I don't know if it is feasible given the way Workspaces behave, but I'd definately give it a shot before abandoning a single view architecture and going with a one-view-per-BE approach.

If you can't get it to work and your business analysts insist on a one-view-per-record approach then just explain to them how that approach could affect memory usage and application performance. Do it their way, but make sure you inform them of the reasons you would prefer to go with a different solution. Always cover your tail. If you have to do one view per record then you have to. That's the hardest part of our jobs; we are beholden to others when it comes to specs.
Feb 22, 2007 at 8:50 AM
The problem I have is that our business analysts have already agreed the tab-per-record design with the customer
I just wanted to expand on this a bit further, because when I read Chris's notes I felt he was describing a solution you could pick up on.
Say you have a Customer View and you display it on a TabWorkspace. If the operator selects another customer to edit, then you show the first view again on another Tab and you change the databinding. If the operator clicks back on the first Customer you react be changing the binding back.

Would that not work?
Feb 22, 2007 at 12:15 PM
If the operator selects another customer to edit, then you show the first view again on another Tab and you change the databinding. If the operator clicks back on the first Customer you react be changing the binding back.

Yes, I hope that will work and that is what I intent to try when I get back to this problem (I've been dragged in to work on another part of the project at the moment). My only concern is that the Tab workspace may not "like" displaying the same view more than once. We'll see.
Feb 28, 2007 at 8:45 PM
Edited Feb 28, 2007 at 9:03 PM
I finally got back to this problem and have (eventually) got to a solution that appears to work.

First off, I cannot just add the same view to the WorkSpace (an Infragistics TabWorkspace) multiple times - when I attempt the 2nd add the workspace checks whether the view is already displayed, if so it just re-activates it. Net effect is that I still only have one tab after the 2nd add attempt. I could tweak the Infragisitics CAB workspace code, but I'd rather not. Furthermore I don't think it could ever work in this trivial way because IIRC the win32 API will not allow a control/window to have >1 parent, so a single UserControl/View instance could not simultaneously exist on two separate tab pages.

What I need is a way to spoof the single view appearing to exist on several different tabs. I have done this via simple container view (TabPageContainer) that contains a SmartPartPlaceholder. I create one instance of TabPageContainer per BE/tab (when the user selects a BE to open for view/edit). Each instance of the container obtains a reference to the single BE editor view in the usual SmartPartPlaceholder way (set the SmartPartName to the appropriate key for the WorkItem.SmartParts collection). The container also has a private variable holding a key into a Dictionary of the BE objects that are currently "open" for edit.

I hook the WorkSpace.SmartPartActivated event in the TabPageContainerPresenter and fire an event to the contained BE editor view, passing the key for the BE to load.

So far so good, but there are two gotchas:

1. When a View is added to a WorkItem any SmartPartPlaceHolders it contains are also added to WorkItem.Items. If the placeholder has a name (Control.Name) then it is used as the key for WorkItem.Items, thus only one instance of a placeholder with a given name can be added to the WorkItem. However, if the placeholder has no name (control.Name.Length == 0) then CAB generates a GUID to use as the WorkItem.Items key. So to allow more than one instance of a View containing a SmartPartPlaceholder to exist in a WorkItem, that placeholder must have its name set to "". I clear the placeholder control's name in the TabPageContainer Constructor to make this work.

2. When a tab becomes active I also have to inform the associated TabPageContainer View to re-attach itself to the BE editor. This is because each TabPageContainer "steals" the BE editor View when it loads - the CAB replaces the SmartPartPlaceholder with the BE editor View, thereby re-parenting it to the current TabPageContainer. The code that does this is:

Get View to attach to Placeholder; code from Microsoft.Practices.CompositeUI.WinForms.ControlSmartPartStrategy
private void ReplaceIfPlaceHolder(WorkItem workItem, Control control)
{
   ISmartPartPlaceholder placeholder = control as ISmartPartPlaceholder;
   if (placeholder != null)
   {
      Control replacement = workItem.Items.Get<Control>(placeholder.SmartPartName);
      if (replacement != null)
      placeholder.SmartPart = replacement;
   }
}

Replace placeholder with view; code from Microsoft.Practices.CompositeUI.WinForms.SmartPartPlaceholder.SmartPart property setter
smartPart = value;
Control spcontrol = (Control) smartPart;
spcontrol.Dock = DockStyle.Fill;
spcontrol.Show();
this.Controls.Clear();
this.Controls.Add(spcontrol); // this line "steals" the view
OnSmartPartShown(spcontrol);
TabPageContainer(s) that were loaded previously still have their SmartPartPlaceholder.SmartPart reference pointing to the BE editor view, but they no longer own/contain it (i.e. SmartPartPlaceholder.Controls.Count==0). This must be due to Windows only allowing a UserControl to have one parent/container control (at a time).

In fact I seem to remember reading somewhere that Win32 windows are immutable, so when one is re-parented via .Net the OS actually tears down the original window and creates a new one?

This behaviour means that I have to explicitly re-parent the BE editor view to a TabPageContainer when it is activated (within its WorkSpace) using:

<myPlaceholder>.SmartPart = <myPlaceholder>.SmartPart;

This causes the SmartPart property setter to "steal" the BE editor view from the previously active tab.

This solution may sound horrendous but the code is short and self-contained in TabPageContainer, so I'm not too worried about the quirk. Client code (including the BE editor view) does not need to worry about it. I've also written TabPageContainer so that it is generic and can contain any BE editor view type, so I only need the logic once in the application.

Any thoughts from the floor? Am I setting myself up for a world of unexpected results and pain???
Nov 11, 2008 at 5:46 PM
I've come across this old post, and I'm in exactly the same scenario.

I have a need to open a few instances of the same view, and need some clean way of passing a parameter (object key) to a presenter on construction.

I don't accept the solution of only using a single view. In this case there has to be distinct instances of the same view open in a single workspace. Take for example the Visual Studio Code editor, we can edit multiple files in separate windows, each window knows what code file it is working on (object key).

Is there no other accepted means of passing a parameter to the presenter upon construction?
Nov 12, 2008 at 5:34 AM
Just pass it to the view (method on interface), and have it relay to the presenter.

These cases do come up, and I've found that just doing the simplest thing works out pretty well.

-Chris