Improved Modal Window Helper Class (based on Sheel's Article "Creating a Custom Add Or Edit Dialog")

Locked Improved Modal Window Helper Class (based on Sheel's Article "Creating a Custom Add Or Edit Dialog")

  • Wednesday, July 13, 2011 5:22 AM
     
      Has Code
    In a recent post on the Visual Studio LightSwitch Team Blog, Sheel Shah post an excellent little article called "Creating a Custom Add or Edit Dialog". 

    After trying it out, I decided to make some changes, some to more suit how I program, others to simplify things a bit, & one to allow the technique to be used in B2 (handling the window being closed by clicking the x in the top right corner doesn't currently work in B2 - this has apparently been fixed in RTM).

    What Did I Change & Why?

    • The first thing I did was "tidy up" some of the method & variable names. It made reading the code a little clearer for me (YMMV). 

    • There was a little bit of code duplication, so I cleaned that up as well.

    • I added a couple of private variables, to store references to the modal window itself (in the Initialise method), & to stored an optional "entity name", for cases where the collection's Details.Getmodel.ElementType.Name returns an akward value (such as MultipleWordEntityName). The ElementType name value is still used if no other value is provided in the contructor call.

    • I removed the concept of "editing". I don't use "edit" buttons, I have a "view" button instead. My theory is that you would want to "view" an entity's details, THEN you may want to edit them (if your permissions allow, otherwise the details will be read-only anyway). This also meant that I could remove "_isEditing" as it no longer served any useful purpose. Changes from an "add" or an "edit" are either contained in the collection's SelectedItem (_entity), or discarded when the window is closed (either by calling DialogCancel, or clicking on the x in the title bar).

    • I also did away with all the "BeginEdit", "EndEdit" & "CancelEdit" calls, mainly because I figured that modal windows would be far more likely to be used in conjunction with a read-only grid, than with an editable grid which Sheel was obviously catering for. Personally, I never use editable grids, unless it's a quick & dirty temporary thing, where I'd be using the generated modal windows, not custom modal windows.

    • When cancelling the dialog, I replaced Sheel's call to _entity.Delete with a call to a private method called DiscardChanges (created so I could call it from more than once place). This method simply calls _entity.Details.DiscardChanges, which applies whether a new entity has been added to the collection, or changes have been made to the entity that was selected in the collection when the modal window was opened to view it.

    • Finally, I added a public DialogClosed method, that could be called from a screen's ModalWindow_Closed event handler. I call this method internally as well, from the event handler code that doesn't currently work in B2.

    I had some problems when I was using Sheel's code (or maybe it was the fault of my early refactoring attempts) that didn't work properly when I added an entity, clicked the cancel button, then tried to add an entity again. I only discovered the "problem" by accident, while I was testing out my "cancelling" code. Normally, I guess you wouldn't necessarily do that sequence of actions all that often. All of the AutoCompleteBoxes in my modal window ended up with "wait isons" on them, & stayed that way no matter how long I left them. Of course, no value was able to be selected either.

    So now, thanks to Sheel's article, I have a very nifty Modal Window class that does exactly what I want it to do. It it manages to be useful to someone else, so much the better.

    There was a reader of the original article who commented that he had created an overload for AddEntity, to be able to pass an initialised entity to the modal window helper class. I needed to be able to do that as well, but I found a different way to accomplish the same thing. In his button code, he was newing up an instance of the entity, initialising whatever properties required values, then passing that initialised entity to the overloded AddEntity method. 

    What I do is make the normal call to AddEntity (which adds a new entity the the collection anyway), then by using a reference to CollectionName.SelectedItem, I can do all the initialisation I want. It seems much cleaner this way, & doesn't require an extra overloaded method to be created.

    Here's my class's code:

    VB:
    Imports System.ComponentModel
    Imports System.Windows.Controls
    Imports Microsoft.LightSwitch
    Imports Microsoft.LightSwitch.Client
    Imports Microsoft.LightSwitch.Presentation
    Imports Microsoft.LightSwitch.Threading
    Imports Microsoft.LightSwitch.Presentation.Extensions
    
    Public Class ModalWindow
     Private _collection As IVisualCollection
     Private _dialogName As String
     Private _entityName As String
     Private _screen As IScreenObject
     Private _window As IContentItemProxy
     Private _entity As IEntityObject
    
     Public Sub New( _
     ByVal visualCollection As IVisualCollection _
     , ByVal dialogName As String _
     , Optional entityName As String = "" _
     )
     _collection = visualCollection
     _dialogName = dialogName
     _entityName = If(entityName <> "", entityName, _collection.Details.GetModel.ElementType.Name)
     _screen = _collection.Screen
     End Sub
    
     Public Sub Initialise()
     _window = _screen.FindControl(_dialogName)
    
     AddHandler _window.ControlAvailable, _
     Sub(s As Object, e As ControlAvailableEventArgs)
     Dim window = DirectCast(e.Control, ChildWindow)
    
     AddHandler window.Closed, _
      Sub(s1 As Object, e1 As EventArgs)
      DialogClosed(s1)
      End Sub
     End Sub
     End Sub
    
     Public Function CanAdd() As Boolean
     Return (_collection.CanAddNew = True)
     End Function
    
     Public Function CanView() As Boolean
     Return (_collection.SelectedItem IsNot Nothing)
     End Function
    
     Public Sub AddEntity()
     Dim result As IEntityObject = Nothing
    
     _window.DisplayName = String.Format("Add {0}", _entityName)
     _collection.AddNew()
    
     OpenModalWindow()
     End Sub
    
     Public Sub ViewEntity()
     _window.DisplayName = String.Format("View {0}", _entityName)
    
     OpenModalWindow()
     End Sub
    
     Private Sub OpenModalWindow()
     _entity = TryCast(_collection.SelectedItem, IEntityObject)
     _screen.OpenModalWindow(_dialogName)
     End Sub
    
     Public Sub DialogOk()
     If (_entity IsNot Nothing) _
     Then
     _screen.CloseModalWindow(_dialogName)
     End If
     End Sub
    
     Public Sub DialogCancel()
     If (_entity IsNot Nothing) _
     Then
    
     _screen.CloseModalWindow(_dialogName)
    
     DiscardChanges()
    
     End If
     End Sub
    
     Public Sub DialogClosed(sender As Object)
     Dim window = DirectCast(sender, ChildWindow)
    
     If (window.DialogResult.HasValue = False) _
     Then
     DiscardChanges()
     End If
     End Sub
    
     Private Sub DiscardChanges()
     If (_entity IsNot Nothing) _
     Then
     _entity.Details.DiscardChanges()
     End If
     End Sub
    
    End Class
    
    C#:
    using System.ComponentModel;
    using System.Windows.Controls;
    using Microsoft.LightSwitch;
    using Microsoft.LightSwitch.Client;
    using Microsoft.LightSwitch.Presentation;
    using Microsoft.LightSwitch.Threading;
    using Microsoft.LightSwitch.Presentation.Extensions;
    
    public class ModalWindow
    {
    	private IVisualCollection _collection;
    	private string _dialogName;
    	private string _entityName;
    	private IScreenObject _screen;
    	private IContentItemProxy _window;
    	private IEntityObject _entity;
    
    	public ModalWindow(IVisualCollection visualCollection, string dialogName, string entityName = "")
    	{
    		_collection = visualCollection;
    		_dialogName = dialogName;
    		_entityName = ((entityName != "") ? entityName : _collection.Details.GetModel().ElementType.Name);
    		_screen = _collection.Screen;
    	}
    
    	public void Initialise()
    	{
    		_window = _screen.FindControl(_dialogName);
    
    		_window.ControlAvailable += (object s, ControlAvailableEventArgs e) => {
    				var window = (ChildWindow)e.Control;
    
    				window.Closed += (object s1, EventArgs e1) => {
    						DialogClosed(s1);
    					};
    			};
    	}
    
    	public bool CanAdd()
    	{
    		return (_collection.CanAddNew == true);
    	}
    
    	public bool CanView()
    	{
    		return (_collection.SelectedItem != null);
    	}
    
    	public void AddEntity()
    	{
    		IEntityObject result = null;
    
    		_window.DisplayName = string.Format("Add {0}", _entityName);
    		_collection.AddNew();
    
    		OpenModalWindow();
    	}
    
    	public void ViewEntity()
    	{
    		_window.DisplayName = string.Format("View {0}", _entityName);
    
    		OpenModalWindow();
    	}
    
    	private void OpenModalWindow()
    	{
    		_entity = _collection.SelectedItem as IEntityObject;
    		_screen.OpenModalWindow(_dialogName);
    	}
    
    	public void DialogOk()
    	{
    		if (_entity != null)
    		{
    			_screen.CloseModalWindow(_dialogName);
    		}
    	}
    
    	public void DialogCancel()
    	{
    		if (_entity != null)
    		{
    
    			_screen.CloseModalWindow(_dialogName);
    
    			DiscardChanges();
    
    		}
    	}
    
    	public void DialogClosed(object sender)
    	{
    		var window = (ChildWindow)sender;
    
    		if (window.DialogResult.HasValue == false)
    		{
    			DiscardChanges();
    		}
    	}
    
    	private void DiscardChanges()
    	{
    		if (_entity != null)
    		{
    			_entity.Details.DiscardChanges();
    		}
    	}
    }

    Yann

    (plus ça change, plus c'est la même chose!)




    • Edited by Yann Duran Monday, July 18, 2011 1:56 AM Moved DiscardChanges, in DialogCancel, to *after* CloseModalWindow
    • Edited by Yann Duran Monday, July 18, 2011 2:01 AM
    • Edited by Yann Duran Tuesday, July 19, 2011 3:05 AM Added Keith's suggestions
    •  

All Replies

  • Wednesday, July 13, 2011 1:26 PM
     
     
    Very neat! Thanks, Keith
  • Wednesday, July 13, 2011 1:31 PM
     
     
    You're very welcome!

    Yann

    (plus ça change, plus c'est la même chose!)

  • Wednesday, July 13, 2011 11:24 PM
     
     

    Nice one Yann! I'll be doing a lot of modal windows shortly, so this will come in very handy.

    Thanks


    Xander
  • Thursday, July 14, 2011 12:08 AM
     
     
    I hope it helps. I'm also open to suggestions if anyone thinks something should be different, or discovers any problem.

    Yann

    (plus ça change, plus c'est la même chose!)

  • Thursday, July 14, 2011 5:57 AM
     
     
    Looks very useful thanks
    David Severn
  • Monday, July 18, 2011 2:01 AM
     
     

    I just made a small change to the DialogCancel method's code to put the call to DiscardChanges *after* the closing of the modal window.

    What I found was happening, was that the window was displaying the selected item (after the new record had been discarded) momentarily before the window actually closed.

    So it was just a "visual" thing.


    Yann

    (plus ça change, plus c'est la même chose!)

  • Monday, July 18, 2011 4:54 PM
     
     

    Very useful contribution Yann, works well.

    Jaime

  • Monday, July 18, 2011 7:15 PM
     
      Has Code

    Hi Yann,

     

    Perhaps you might change the C# line

    _entityName = ((entityName != "") ? entityName : _collection.Details.GetModel.ElementType.Name);
    
    to
    _entityName = ((entityName != "") ? entityName : _collection.Details.GetModel().ElementType.Name);
    

    and

    using Microsoft.LightSwitch.Presentation.Extensions.ScreenExtensions;

    to

    using Microsoft.LightSwitch.Presentation.Extensions;

    Keith

  • Tuesday, July 19, 2011 3:04 AM
     
     
    Thanks Jaime!

    Yann

    (plus ça change, plus c'est la même chose!)

  • Tuesday, July 19, 2011 3:06 AM
     
     
    You're welcome!

    Yann

    (plus ça change, plus c'est la même chose!)

  • Tuesday, July 19, 2011 3:06 AM
     
     
    Done! Thanks for the tips Keith!

    Yann

    (plus ça change, plus c'est la même chose!)