Improved Modal Window Helper Class (based on Sheel's Article "Creating a Custom Add Or Edit Dialog")
-
Wednesday, July 13, 2011 5:22 AM
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)
C#: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 Classusing 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(); } } }
(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
- 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).
All Replies
-
Wednesday, July 13, 2011 1:26 PMVery neat! Thanks, Keith
-
Wednesday, July 13, 2011 1:31 PM
-
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 AMI hope it helps. I'm also open to suggestions if anyone thinks something should be different, or discovers any problem.
(plus ça change, plus c'est la même chose!)
-
Thursday, July 14, 2011 5:57 AMLooks 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.
(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
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);
andusing Microsoft.LightSwitch.Presentation.Extensions.ScreenExtensions;
to
using Microsoft.LightSwitch.Presentation.Extensions;
Keith
-
Tuesday, July 19, 2011 3:04 AM
-
Tuesday, July 19, 2011 3:06 AM
-
Tuesday, July 19, 2011 3:06 AM

