What is this blog post all about?
It started when I was trying to use one of my screens as the default screen for an entity, and discovered that if the screen has more than one parameter, Lightswitch won’t let you! The reason for the second parameter was so that when you create a new entity, you can set a related property automatically, so the user doesn’t need to. This is explained in more detail below.
Whilst trying to find an answer, I got involved in a fairly protracted discussion in a forum thread, where we were trying to handle multiple parameters on a screen. The thread was started by Steve Naughton, who lives a few miles west of me, and who (thankfully) has decided to turn his considerable talents (seen in his fantastic support for the Microsoft fiasco known as Dynamic Data) to Lightswitch. He had been grappling with the same problem, and had come up with a solution that was, with all due respect to him, somewhat convoluted. Yann Duran, who has long been one of the main community supporters of Lightswitch was questioning his approach. I came in and started arguing with both of them!
In order to follow this post properly, you will need to have watched the MSDN videos "How Do I: Create a Screen that can Both Edit and Add Records in a LightSwitch Application?"and “How Do I: Pass a Parameter into a Screen from the Command Bar in a LightSwitch Application?”, or at least be familiar with the concepts explained in them.
A more detailed explanation of the problem
I’m going to use the scenario of an e-commerce catalogue here, as it’s simple and familiar enough that you won’t have to expend any brain power on the domain, and can concentrate on my wafflings about the solution to the problem instead! Imagine we have a simple database with two tables, one for categories and one for products. All products have to be in exactly one category.
What I wanted to do, was have a custom screen that you can use for adding and editing a product, and set it as the default screen for the Product entity. This means that however the user ends up at a product screen, the custom screen will be shown, not the one Lightswitch generates for you. See the first video above if you aren’t familiar with how to do this.
My categories screen listed the products in that category, and clicking the Add or Edit button above the product list, or clicking the product itself (I had the products in a List, as opposed to a DataGrid, and had set the product to be shown as a link) should open up the product screen. The extra detail that caused all the problems was that when the user clicked the Add button, the new product screen should have the product’s category filled in already. This is logical, as they clicked the Add button above a list of products for that category, so we can assume they wanted to add the new product to that category. They can always alter the category if they didn’t actually want this.
The second video above explains how to do this, which basically involves adding a parameter for the CategoryID, which can be passed in when creating a new product. The problem is that the video only shows the method when clicking an Edit button to edit an existing product. It didn’t mention how you would tackle a user’s click on the link in the List.
Now in theory, this should be easy. When you click a link, Lightswitch uses the default screen for the entity, so setting the custom product screen to be the default screen for the Product entity should do the trick. Except you can’t! I discovered that Lightswitch only allows you to use screens with exactly one parameter as the default entity screen. The logic (apparently) is that if you have more than one parameter, Lightswitch won’t know what to pass in for the second. I don’t accept that answer, as if the second parameter is nullable, Lightswitch can just pass in null. Seems simple and logical to me, but the Lightswitch team obviously disagree.
However, my disagreeing with their design decision didn’t help me solve the problem, which is how I ended up in that forum thread. Despite Lightswitch allowing you to override a certain amount of code, it doesn’t allow you to touch the code that is executed when a link is clicked, so I was stuck.
The suggested solutions so far, and the problems they have
Just use the automatically generated Lightswitch screens. OK, so this wasn’t a serious suggestion, but needs to be mentioned as it does work, although it suffers from the fact that you don’t have any control over the screen. This is basically useless for anything other than the simplest of entities, and so wasn’t really considered. It was really the need to avoid these automatic screens that led us here in the first place!
Yann’s solution was simple, but suffers from a usability problem. He doesn’t use List controls, and forces the user to select the product in the grid, then click the Edit button. This avoids the problem of not being able to customise the code that is executed when the user clicks a link, but makes life (slightly) more difficult for the user. I’m a big fan of usability, and don’t like making my users have to work harder, just because of flaws in the product I choose to use for development. If this doesn’t strike you as being a major problem, then I seriously recommend you read About Face 3: The Essentials of Interaction Design by Alan Cooper (the father of the original Visual Basic, and one of the world’s foremost usability experts). It’s very challenging, and it changes a lot of what we developers think, but it opens your eyes to writing UIs that are based on the users’ needs, not on what is convenient for the developer.
Steve suggested a different solution, and this was to have a dummy screen that only had one parameter, so could be set as the default screen, and have that dummy screen call the real add/edit screen, and pass in the appropriate second parameter. This works, but requires a fair bit of code, and a dummy screen for every entity. This can quickly get out of hand and is, to be perfectly blunt, a bit of a hack (hopefully Steven knows me well enough by now not to take that the wrong way!).
So whichever way you do it, you haven’t completely solved the problem… until now! Whilst contemplating this issue late last night (OK, so it was early this morning by the time it struck me, but don’t tell the wife or I’ll be in big trouble for working at that time!), a thought struck me that led me to a fairly elegant and simple solution that solves all problems…
The Furry Ferret Approach (OK, so it doesn’t have anything to do with ferrets really, but I couldn't think of a better name for it!)
What struck me was that whichever of the two scenarios you are in, adding a new entity or editing an existing one, you only need one ID. If you are adding a new product, you only need the category ID, as there won’t be a product ID yet. If you are editing a product, then you don’t need the category ID (which will exist), as it will be a property of the existing product.
If so, then why not use a single screen parameter for both, and provide a way for the screen to decide which it is? The simplest way I could think of doing this was to make one of the IDs negative before passing it in. As database IDs are (to my knowledge) never normally less than zero, this allows the product screen to distinguish between the two scenarios and act accordingly. As the screen only has one parameter, Lightswitch will let you use it as the default one for the entity.
It turned out to be every bit as easy to implement as I had thought. For those wishing to play along at home, here is a step-by-step explanation:
Start up Visual Studio and create a new Lightswitch application.
Add a new table called Category, and add a string field called Name. In reality, you would have more fields in there, but that’s all we need for now.
Add a second table called Product, and add some fields, say Name, Price and Description.
Now click the “Relationship” button at the top of the designer screen, and add a relationship between the tables as follows…
Now add a screen for the categories. I used a “List and Details Screen” but you may choose to use another template. I included the products in the screen data…
I didn’t do anything to this screen just yet. Next I added a “New Data Screen” for the product…
In the designer for this product screen, I clicked the “Add Data Item” button, and added a local integer property called ProductOrCategoryID, and made it required…
I clicked on this new parameter (in the left panel on the screen designer), and in the Properties panel, checked the “Is Parameter” checkbox. I then went to the Product entity, and set this screen to be the default screen for the Product entity.
Back in the screen designer, I clicked the “Write Code…” button, and added code for the InitializeWorkspace method. Apologies to those who find my choice of colour scheme somewhat odd, but I find it very relaxing…
What this does is look at the parameter passed (ProductOrCategoryID) to see if it is positive or negative. If it is positive, we assume it’s a product ID, so we find the product from the application data, and set the ProductProperty (created for you you when you create the screen) to the correct product. If the parameter is negative, we assume it’s a category ID, and we are adding a new product. We create the new product, and set the ProductProperty to the new product. Then we use the category ID (remembering to negate it) to find the category, and set the new product’s Category property to it.
Note that I have used Linq queries to find the product and category in this example. You could probably do this with Lightswitch queries, but this was the first way that came into my head, and seems natural to me as I am used to working with Linq.
All that was needed now was to wire up the “New” button above the product list on the categories screen…
As you can see, all this does is pass the negated category ID to the screen.
So there you have it. A fairly simple and elegant solution to the problem. Took a bit of work to find it, but once I found it, it was very easy to implement, and doesn’t sacrifice any usability.