Warning User Before Navigating Away From A View

Topics: CAB & Smart Client Software Factory
Mar 5, 2007 at 7:29 PM
I have a SCSF app that has multiple independent modules. One of the modules consists of a deck workspace with it's own toolstrip buttons. Each toolstrip button fires an event which adds (on first time button click) and displays a view in the workspace. In one of the views, there are some controls that the user can update and there is a "Save" button. The data is stored in the WorkItem state for the module's workspace.

I have a requirement that the user should be notified that there are pending changes if they try to navigate away from this view without first clicking the "Save" button (i.e., if they try to navigate away from a "dirty" view).

To meet this requirement, I first tried to add code to the VisibleChanged event of the view to check if the state has been saved. However, this event does not seem to fire when a different view is displayed (i.e., added to the deck workspace on top of the "dirty" view in question).

I then proceeded to add code in the module wherever I had code that displays a view, to check to see if the current view is the one in question and to see if the data is dirty. This also means I have to maintain a state variable to indicate which view is the currently active one.

But even worse than this, the user can still click on the main toolbar and open up a different module or exit the app and the logic I have added will not execute.

I want to keep this logic isolated to the module in question because there should be no interdepencies between my business modules.

Anybody have any ideas on how to do this better? Any suggestions would be appreciated.
Mar 6, 2007 at 6:16 PM
Nobody has any thoughts on this? Is there no way to know if a view is losing it's position as the active view?
Mar 6, 2007 at 8:57 PM
Thoughts? Sure, but probably not answers :-)

This is one of the drawbacks to a decoupled, modular design. A view can't rightly know about a toolstripitem from another module. And if it can't know about that toolstripitem, then it can't know it's losing focus and needs to be saved.

We have a similiar feature we want to add to our application at some point in time (it's not a formal user request yet, but I think we're getting there). What I'm looking at is the user experience. What do we really need to accomplish? Do we need to prevent then from navigating away from the view, or do we need to prevent them from closing the application when there is unsaved data? I think the latter is more what most people are shooting for and is consistent with Windows application design. After all, Excel doesn't prompt you to save if you navigate toward a different file or open a new one. It only prompts you when you attempt to shut down the application.

Mar 6, 2007 at 9:15 PM
True. Maybe I can get by with my current hack of checking anytime there is navigation from one view to another WITHIN the module. And then add your suggestion of checking on application close. I already have a "Logging Off..." dialog which is wired to the application close event. I guess I could try doing the same with this. However, the user needs to have the option of canceling the close operation so they can commit the changes if necessary. I'll look into that and report back if that works. (Or if anything else works for that matter.)

I agree that this is a drawback to a decoupled design. I was hoping that I could isolate the code to an event on the view in question. That would be ideal. For example, OnVisibleChanged or OnLeave... but I haven't figured out an event that will work yet...

Thanks for your input Chris. Anybody else? ;)
Mar 9, 2007 at 10:40 PM
Yes, visibility and "on leave" are completely unreliable in .NET.

I have considered (but not yet implemented) creating a service for this purpose. By convention, the active workitem (or whatever defines the scope) registers a "hold" with the service using an arbitrary "context id" that it shares with its dependent smartparts. Every smartpart activation checks to see if it belongs to the active context. If so, no problem: we're just moving around within the context, not leaving it.

If not, the service invokes a method (registered with the context at the same time). That method asks if it is ok to take possession of the current context. This is a call back to the current context "owner". That method can release ownership of the context, in which case the new smartpart (workitem, whatever) takes over and now owns the context. Alternatively, the method can refuse to release the context, in which case it provides the means for returning to some safe point in that owning context.

Obviously details are omitted. It is essential that every one play by the same rules and that no child workitem hijacks the service. I'd like to think that a reliance on base classes could facilitate "enforcement" of the convention.

Anything can ask the service if a context switch is allowed so one could bake this into context switching commands as well. I should think it plays well also with application exit.

I haven't gotten around to doing this yet but I think it has the appropriately decoupled characteristics.
Coordinator
Mar 10, 2007 at 1:43 PM
I wrote something about this a while ago. Let me know if it helps
http://staff.southworks.net/blogs/matiaswoloski/archive/2006/10/07/Structured-Shutdown-_2D00_-Cancelable-events-with-CAB.aspx

Thanks
Matias
May 3, 2007 at 1:37 PM
Hi Matias,

I am not able to go to the posted link.....is it changed
May 3, 2007 at 2:52 PM
My app had this same requirement and I used the View Navigation pattern as the standard way to communicate between views. I published a Navigation event (global) with a View name event arg before every View invocation (Show, Activate). This was usually done in a CommandHandler or a Module Controller. The Views that needed to update, save data, or do other processing subscribed to the Navigation event. This meets your desire to have "no interdepencies between my business modules." The "action" is isolated to the View, but all the Presenters associated with the Views communicate through events.
May 3, 2007 at 3:12 PM
Greyman,
Can you be more specific or share some code example.

Thanks!
May 3, 2007 at 8:50 PM
Use the Command Pattern and Event Publish/Subscribe as documented in CAB/SCSF to manage user and View interaction. I don't know how you manage views, but this is a simple way in code. ViewNames is a Constants class with the view name strings.

// Event Publication
EventPublication(EventTopicNames.Navigation, PublicationScope.Global)
public event EventHandler<EventArgs> Navigation;


// Publish Navigation Event
protected virtual void OnNavigation(string viewName)
{
if (Navigation != null)
{
Navigation(this, new EventArgs<string>(viewName));
}
}


// In the Command Handler that navigates to a view
// Publish the Navigation event, create the view and show the view
OnNavigation(ViewNames.Relationship);
if (WorkItem.SmartParts.Contains(ViewNames.Relationship) == false)
{
_relationshipsView = WorkItem.SmartParts.AddNew<RelationshipsView>(ViewNames.Relationship);
}
else
{
_relationshipsView = WorkItem.SmartParts.Get<RelationshipsView>(ViewNames.Relationship);
}
topWorkspace.Show(relationshipsView,
new SmartPartInfo(ViewNames.Relationship, String.Empty));


// In the Presenter of the View with pending changes
EventSubscription(EventTopicNames.Navigation, ThreadOption.UserInterface)
public void OnNavigation(object sender, EventArgs<string> viewName)
{
//TODO: Notify user that there are pending changes and save code here
}