tag:blogger.com,1999:blog-8736002408934345692023-11-16T08:15:11.976-05:00The Merlin WallThe blog of the Merlin Wizard Framework for .NET 2.0+. Because wizards are magical!Yevhttp://www.blogger.com/profile/14558444288426491815noreply@blogger.comBlogger9125tag:blogger.com,1999:blog-873600240893434569.post-2449584392602805062009-03-10T00:54:00.003-04:002009-03-17T18:39:52.180-04:00A Case for Wizards<p>In this post, I’d like to take a step back and consider a fundamental design question: do we really need wizards? Here’s what Alan Cooper et. al. write about wizards in their monumental tome on user interaction design, <em>About Face 3:</em></p> <div style="border-right: 1px solid; padding-right: 10px; border-top: 1px solid; padding-left: 10px; background: #fefeef; padding-bottom: 10px; border-left: 1px solid; width: 515px; padding-top: 10px; border-bottom: 1px solid; position: relative" align="center"> <p align="left"><strong>Wizards</strong> are an idiom unleashed on the world by Microsoft… they are a fine example of interrogation tactics on the program’s part, and violate the design principle: <em>Provide choices, don’t ask questions…</em>  The user is like the conductor of a robot orchestra, swinging the baton to set the tempo but otherwise having no influence on the proceedings…. A better way to create a wizard is to make a simple, automatic function that asks no questions of users but that just goes off and does the job… Wizards were purportedly designed to improve user interfaces, but they are, in many cases, having the opposite effect.</p> <p align="left">                   <em><a href="http://www.amazon.com/About-Face-Essentials-Interaction-Design/dp/0470084111/ref=pd_bbs_sr_1?ie=UTF8&s=books&qid=1236658309&sr=8-1" target="_blank">About Face 3</a> (561-562), Alan Cooper et. al., 2007 Wiley Publishing, Inc.</em></p> </div> <p>Wow! That’s quite a searing indictment. But is it accurate? I can’t help but wonder if Mr. Cooper does his own taxes. Probably not… Because if he had, and if he used any popular tax preparation product, such as <a href="http://turbotax.intuit.com/" target="_blank">TurboTax</a> or <a href="http://www.hrblock.com/taxes/products/product.jsp?productId=31" target="_blank">TaxCut</a>, what he would see is a essentially a spiffed-up wizard. And I would imagine his life (and countless others) would be quite miserable should those programs use any other paradigm.</p> <p>Of course, he does have a point… it’s quite easy to overuse or misuse the wizard paradigm (and Merlin makes it even easier ;-)). For tasks such as creating a new PowerPoint presentation, an example Cooper uses in his text, a Wizard is in fact superfluous. Just let the application create a new document as it sees fit, and let the user make the changes. So <strong>when is it a good idea to use a wizard after all?</strong></p> <p>The following are some of the indicators that a wizard is called for:</p> <ul> <li><strong>A task must be broken down.</strong> That is, when the task performed is too immense for a user to consider in one step. The tax computation example easily comes to mind. <br /><br /></li> <li><strong>Asking for forgiveness is not an option.</strong> In other words, it’s not possible to do the job first and then let the user make changes. An easy example of this is the installer. It doesn’t make sense to install the application first and only then to ask which directory it should be installed into, whether or not certain files should have been overwritten, whether some component should not have been installed at all, etc. Similarly, we wouldn’t want our tax programs to just pre-fill the 1040 form with last year’s data and make us go through the completed IRS form making adjustments. <br /><br /></li> <li><strong>The user’s attention must be directed</strong>. If instead of a wizard we give the user one big jolly tabbed window with options speckled throughout, we have no guarantee that the user will set or even see these options. If sensible defaults are available and the options can be changed later, that’s not a problem. However, if the options presented <em>must </em>be brought to the attention of the user, or if some data <em>must</em> be collected, a Wizard is a preferable way. <br /><br /></li> <li><strong>It is not known in advance which sections are relevant.</strong> At some point in a TurboTax or TaxCut sequence, the application will ask if you have received a 1099-INT form or have interest to report. If you have not and do not, it will not offer you to enter that data. Now imagine this working with any other paradigm. Either visual elements will be appearing and disappearing as the “have 1099-INT” setting is toggled, or they will be disabled and enabled. GUI users are not accustomed to having visual elements manipulated except by their direct command. Harder still will be the times when the user is not sure which setting enables and disables (or hides and shows) other settings… He may spend many a minute wondering why information he just filled out suddenly vanished. <br /></li> </ul> <p>Long story short, the wizard as an idiom is no more great or poor than the icon, the toolbar, or a hyperlinked table of contents. As Cooper and friends themselves proselytize throughout their opus, we must design based the goals and needs of our users and the models that are natural to them.</p>Yevhttp://www.blogger.com/profile/14558444288426491815noreply@blogger.com0tag:blogger.com,1999:blog-873600240893434569.post-28588916385327899852009-01-18T18:08:00.001-05:002009-01-18T18:17:52.417-05:00Step Sequence Manipulation<p>Putting aside the unrelenting bliss over the 1.0 release (and my propensity for exaggerating it), it’s time to lay down some more Merlin features. Specifically, we’re going to cover manipulating the step sequence when the wizard is already running.</p> <p>Here’s how that works:</p> <p>Somewhere in a step (generally in the <font face="Courier New">NextHandler</font>), you would access the wizard controller and call one or more of the following methods on it:</p> <ul> <li><font face="Courier New">public void AddAfterCurrent(IStep step);</font> <br />Adds the specified step immediately after the current step. After the new step executes, the step that originally followed the current step will execute.  <br /><img title="stepadd" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 15px 0px 25px; border-right-width: 0px" height="166" alt="stepadd" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgt-KQ54INBQ0tWMspE4VFoDjQywiKOwvm4b11bI9F2mr5u1-D2IyH60Wg5QjBjrjUPoqUy45w5frhWaMulS2coHa-WqRrPOd20K6ZPTlZ4nrnRlchx6hhyphenhyphen_UM4DzYUnXCDaoLzPigqio8//?imgmax=800" width="492" border="0" /> <br /></li> <li><font face="Courier New">public void AddAfterCurrent(IEnumerable<IStep> steps);</font> <br />Adds all the steps in the argument immediately after the current step. After all the new steps execute, the step that originally followed the current step will execute.  <br /><img title="multiplestepadd" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 15px 0px 25px; border-right-width: 0px" height="182" alt="multiplestepadd" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxyWjKiymdocK2QrhQo2lZqMDggvRM-90aJFqsNOtOWsyVfpLzyGC1x_syhe7q1FBLpaQHopQ0BJdnPA5U_xN2OiPZiJxFuGMcp9AQiRMaCYI-ACo2TN8KV4R72JErVQAy81w1v0moNzY//?imgmax=800" width="533" border="0" /> <br /></li> <li><font face="Courier New">void DeleteAllAfterCurrent();</font> <br />Deletes all steps that follow the current step. <br /><img title="delete" style="border-top-width: 0px; display: inline; border-left-width: 0px; border-bottom-width: 0px; margin: 15px 0px 25px; border-right-width: 0px" height="114" alt="delete" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhwrAtojSotbQNn59dYNkhx55q1wFyxrPLdC3Uvi88dhQcRmCf8w8MY8NCg1Rmyyd0R1vK4I4x_OKEqpBo7KNtkn2l4Ff5TPGMeFWDP0C25JZe-M6djGGRi9NMVev8ipeaIhfCBCbqqz5M//?imgmax=800" width="408" border="0" /> </li> </ul> <p>There are two blatant pitfalls possible here:</p> <ul> <li>If a current step has a “Next” button (i.e. it’s not the last in a sequence), but then you delete all the steps that follow, the user will click Next and “run off the end” of the wizard. The wizard will just close, and the Wizard Controller will consider the wizard successfully completed. <br /> <br /> <li>If the current step is the last one, it will appear with a “Finish” button. If you dynamically add steps afterward, the user will be surprised that clicking the “Finish” button actually leads to more steps. To avoid this phenomenon, just place a dummy step after the first one, so that the first step appears with a “Next” button. Then, you can use <font face="Courier New">DeleteAllAfterCurrent()</font> to delete the placeholder step prior to adding new steps. </li> <p>To see these features in action, check out <a href="http://cid-c7f4a485fd15f03e.skydrive.live.com/self.aspx/.Public/StepSequenceManipulation.zip" target="_blank">our step sequence manipulation demo</a>. In this demo, the user chooses a fruit type (apples or oranges), and the subsequent steps are determined by which fruit type the user has chosen.</p> <p>Bon appétit!</p> Yevhttp://www.blogger.com/profile/14558444288426491815noreply@blogger.com0tag:blogger.com,1999:blog-873600240893434569.post-68535724867925324182009-01-18T12:12:00.003-05:002009-01-18T12:16:55.307-05:00We're 1.0!Pop the champagne, folks! But first, head on down to the <a href="http://www.codeplex.com/merlin/Release/ProjectReleases.aspx">downloads page</a>, and grab yourself a sizzling-hot cup of Merlin magic.<br /><br />As always, we'd love to hear your comments or suggestions either in the blog comments or in <a href="http://www.codeplex.com/merlin/Thread/List.aspx">our forum on Codeplex</a>.Yevhttp://www.blogger.com/profile/14558444288426491815noreply@blogger.com0tag:blogger.com,1999:blog-873600240893434569.post-13716023075201052052009-01-17T14:35:00.001-05:002009-01-17T14:35:29.553-05:00Step Library! Part 2.<p>The last step in the step library for the 1.0 release is a simple text form. Merlin is all about getting you through your wizard creation quickly, so we wanted to give you a way to get text input from the user without needing build your own controls. Of course, if you do need more flexibility than the text form offers, the option to build your own UI is still there.</p> <p>Ok, here’s a 5-second text form:</p> <div style="padding-right: 10px; padding-left: 10px; background: #ffefcf; padding-bottom: 10px; padding-top: 10px"><font face="Courier">var textFormStep = new TextFormStep("Additional Information",  <br />    "Please answer the following questions:", "Name:", "Age:", "Hobby:");</font></div> <p>Once the wizard controller executes this step, here's what we see:</p> <p><img title="textformSimple" style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" height="478" alt="textformSimple" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEij4glv9k31VVKy39fDerWtJjCPjM70tg3vryHLO5mqRy8D-lrz5GKqpwKcf_SKdg5U47dVubBEVN0AlDDJ8ajAIdSPpuMCcthZgEAt9nMUW_Jh_5ZOTuMJpOyd9IGwvHGcaHbBn1wq3kM//?imgmax=800" width="571" border="0" /> </p> <p>After this step has run, the <font face="Courier New">textFormStep.Answers</font> property will return an <font face="Courier New">IEnumerable<string></font> containing the answers in the order in which their respective questions were specified in the constructor. Here’s another way of defining exactly the same form:</p> <div style="padding-right: 10px; padding-left: 10px; background: #ffefcf; padding-bottom: 10px; padding-top: 10px"><font face="Courier">var textFormStep = new TextFormStep("Additional Information", <br />    "Please answer the following questions"); <br />var nameQuestion = textFormStep.AddQuestion("Name:"); <br />var ageQuestion = textFormStep.AddQuestion("Age:"); <br />var hobbyQuestion = textFormStep.AddQuestion("Hobby");</font></div> <p>There are two advantages to this approach: one is that it allows me to retrieve the answers without going through an enumeration and remembering the question order. I can just do:</p> <div style="padding-right: 10px; padding-left: 10px; background: #ffefcf; padding-bottom: 10px; padding-top: 10px"><font face="Courier">MessageBox.Show("Your age is: " + ageQuestion.Answer);</font></div> <p>The second advantage for the longhand approach is that it allows you to provide some additional options. Have you noticed how the “Finish” button appears in the above wizard even though we haven’t typed anything? We’d probably want to impose some minimal validity criteria before we’re allowed to move on:</p> <div style="padding-right: 10px; padding-left: 10px; background: #ffefcf; padding-bottom: 10px; padding-top: 10px"><font face="Courier">var nameQuestion = textFormStep.AddQuestion("Name:", <br />    Validation.MinLength(3)); <br />var ageQuestion = textFormStep.AddQuestion("Age:", Validation.NonEmpty);</font></div> <p>Now, the “Next/Finish” button will not be enabled until at least 3 characters have been typed into the “Name” field and the “Age” field is non-empty. There are more validations available, and you can always plug in your own delegate/lambda expression that takes a <font face="Courier New">string</font> and returns a <font face="Courier New">bool</font>. </p> <p>If you want to ask for a password, you can just as easily use a password masking character:</p> <div style="padding-right: 10px; padding-left: 10px; background: #ffefcf; padding-bottom: 10px; padding-top: 10px"><font face="Courier">var hobbyQuestion = textFormStep.AddQuestion("Password:",  <br />     Validation.Length(6, 12), '*');</font></div> <p>Disclaimer: this UI provides no more password security than a ye olde winform with a textbox. </p> <p>And last but not least, you may want to pre-populate the textbox with a default answer or a previously-specified value. Easier done than said:</p> <div style="padding-right: 10px; padding-left: 10px; background: #ffefcf; padding-bottom: 10px; padding-top: 10px"><font face="Courier">var nameQuestion = textFormStep.AddQuestion("Name:");            nameQuestion.Answer = "Merlin";</font></div> <p>Now, “Merlin” will appear in the “Name:” textbox when this step is executed.</p> <p>Oh, and one really last thing: if your questions are of very uneven length, the textboxes can appear quite far away from the text of the shorter questions:</p> <p><img title="textformdemo" style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" height="481" alt="textformdemo" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhUaDxbv6clwEVMm1jmFZDytH1Df9jD6aS6okrKdonk6iHLbpkdN1SFq1iZ13uWdGyxDYhZ3mYgs9799E-P53Hmz_RMWoMEDJj4pQSDsmL-PmOQXbciEztE_2AdZFfsoK_BHn3YeHXbZzQ//?imgmax=800" width="571" border="0" /></p> <p>If you’d like all the textboxes to start right next to their respective labels, just set the step’s <font face="Courier New">LineUpQuestions</font> property to <font face="Courier New">false</font> (it’s <font face="Courier New">true</font> by default). Then, your question will appear like this:</p> <p><img title="textd2" style="border-top-width: 0px; display: block; border-left-width: 0px; float: none; border-bottom-width: 0px; margin-left: auto; margin-right: auto; border-right-width: 0px" height="481" alt="textd2" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg5nUfOxMzXpQQQh7Bd0nofNzGvw0xxH_u5FuSsxrRUPDRbj4GvXhnuCw_VktONtHOlwCrF6jxAgJgvFv1NZKaN45eomvE6sngrtNhyQSnkR5cT-5DhRPbeGHsVUaMiaxbe9hklCS1ck4Q//?imgmax=800" width="571" border="0" /></p> <p>‘Till next time!</p> Yevhttp://www.blogger.com/profile/14558444288426491815noreply@blogger.com0tag:blogger.com,1999:blog-873600240893434569.post-46431897524817100352009-01-17T00:15:00.002-05:002009-01-30T11:49:37.750-05:00Step Library!<p>Fondest greetings to all. The 1.0 release is microscopically-near, and you can already savor its features in the <a href="http://www.codeplex.com/merlin/Release/ProjectReleases.aspx?ReleaseId=21916" target="_blank">latest release candidate</a>. And among these oh-so-savory features is the new step library! In the previous entries, we covered creating steps from your own Controls. But some kinds of steps are so common, we’ve pre-built them for you. For example, choosing one option out of several: </p><img title="SelectionStep" style="border-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" alt="SelectionStep" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifk3fvLXACW6FOZ4OjqesZ3kj87tT_qmxrhY2fcIenp2vg7f5OEam43GbGT0gzTPMwUTi0oMk8nO2sU9I3Lzm3TPcFz10MHoRh8Z53MI0_pbrcAYvXj2hAihXOsHUnpiasMOBv7NR9yls//?imgmax=800" width="574" border="0" height="482" /> <p></p> <p>This step took all of one line of code to create (highlighted below):</p> <div style="padding: 10px; background: rgb(255, 239, 207) none repeat scroll 0% 0%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"><span style="font-family:Courier;">var steps = new List<IStep>();<br /><strong>var choiceStep = new SelectionStep("Color choice", <br /> "Please pick a color", "Red", "Blue", "Green", "Turqoise");</strong><br />steps.Add(choiceStep);<br />new WizardController(steps).StartWizard("Hello");<br />MessageBox.Show((string)choiceStep.Selected);</span> </div> <p>Once the step has run, you can retrieve the selection via the <span style="font-family:Courier New;">Selected</span> property. </p> <p>Next up on our step library hit parade is the file selection step. Patently simple: often you need the user to select a file. This step will do that:</p> <a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgSuCQebZpfr-if9RGY6GXyvOgTvx8tU3Dra_2ijlMsqm-gvTiZtjx1o2tBDQmbZN9aNxF6hwSOgkaMrMoeSG_yx1I7QXVjAKjrGOSZYyi9bdb-RuNpeHH9z81UnRQlxekyHxGMxKwJQSU/s1600-h/file%5B14%5D.png" target="_blank"><img title="FileSelectionStep" style="border-width: 0px; display: block; float: none; margin-left: auto; margin-right: auto;" alt="FileSelectionStep" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsVn79F8wm-xyIow8mzCVFTzcApwit7JZ2R0-1xpgiMiKTC9PBipswAz4u2uT2Jru89CV5KJAocwq5clvfV6_3qkdJ9RsmLzIqZDC8O_Qv0sRM9QFB4qUWpsRu6uiAh9LIngi59ioql3A//?imgmax=800" width="580" border="0" height="418" /></a> <p>The code to create this step:</p> <div style="padding: 10px; background: rgb(255, 239, 207) none repeat scroll 0% 0%; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;"><span style="font-family:Courier;">var fileStep = new FileSelectionStep("License",<br /> "Please provide your license file"); <br />fileStep.Filter = "License Files (.lnc)|*.lnc";</span></div> <p>After the step has run, <span style="font-family:Courier New;">fileStep.SelectedFullPath</span> will return the full path of the selected file. </p> <p>There’s one more step to cover, but I’ll save it for the next entry. Let me just add that the examples above do not cover every feature and tweak of these steps. We’ll post the complete API reference somewhere after 1.0 is out the door. </p>Yevhttp://www.blogger.com/profile/14558444288426491815noreply@blogger.com0tag:blogger.com,1999:blog-873600240893434569.post-8284405420118691272008-12-23T17:27:00.001-05:002008-12-23T17:30:59.168-05:00Delays, Delays<p>Let's go back to the demo from the <a href="http://themerlinwall.blogspot.com/2008/12/maybes-and-what-ifs.html">previous post</a>, for a moment. Once you got to the "Advanced User Information" step, you may have noticed an unsightly disabled text box at the top of the screen:</p> <p><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; margin: 0px 0px 0px 10px; border-right-width: 0px" height="440" alt="Screen Shot" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhx7fj0Opd6MjJFVLQikl6RACeUwc3t9bgtR5RZLz6vbdCjqNdjv3BpgPfFSOliYNotVeYDaJl0IVOlET1Cl8OJIBZdHrv7QP65biSuHWMER4D6x7OPhLit4_wtApH3fFSSakZw2cuTki8//?imgmax=800" width="535" border="0" /></p> <p>The purpose of that text box is to display the name of the user entered in the previous step. To do that, I need to populate the text box only when the controller reaches this step and not before. The cleanest (but not the only) way to do that is by setting the step's <font face="courier">StepReachedHandler</font> property to a void delegate that will do the initialization for you. Here's the modified <font face="Courier">Main</font> method from the previous post's demo demonstrating how this would be done (changes in bold):</p> <div style="padding-right: 10px; padding-left: 10px; background: #eedaad; padding-bottom: 10px; padding-top: 10px; font-family: courier; font-width: 12">var steps = new List<IStep>(); <br />var simpleEntryUI = new SimpleEntryUI(); <br />var simpleEntryStep = <br />    new TemplateStep(simpleEntryUI, "User Information"); <br /><strong>var advancedEntryUI = new AdvancedEntryUI(); <br />var advancedEntryStep = <br />    new TemplateStep(advancedEntryUI,</strong> "Advanced User Information"<strong>);</strong> <br /><strong>advancedEntryStep.StepReachedHandler = () => <br />{ <br />    advancedEntryUI.FullName = simpleEntryUI.FullName; <br />}; <br /></strong>steps.Add(simpleEntryStep); <br />steps.Add(new ConditionalStep( <br />    () => { return simpleEntryUI.ShowAdvancedOptions; }, <br />    advancedEntryStep)); <br /> <br />Label farewellMessage = new Label(); <br />farewellMessage.Text = "Well, done. Bye, now!"; <br />steps.Add(new TemplateStep(farewellMessage, 10, "Goodbye!")); <br />new WizardController(steps).StartWizard("User Data Wizard");</div> <p>The <font face="Courier">StepReachedHandler</font> does not get evaluated until the controller reaches the step, hence it has access to all the data provided by the preceding steps. Note, that if you run the example above but do not check the "Show Advanced Settings" checkbox, the StepReachedHandler will not run at all. This is what you would expect: if a step is inside a conditional and the condition is false, the <font face="Courier">StepReachedHandler</font> of that step will not run.</p> Yevhttp://www.blogger.com/profile/14558444288426491815noreply@blogger.com0tag:blogger.com,1999:blog-873600240893434569.post-50047935028150101392008-12-23T15:46:00.001-05:002008-12-23T15:55:55.909-05:00Maybes and What-ifs<p>In Merlin, we represent a wizard as a sequence of steps. Of course, a wizard isn't always a fully-defined sequence - sometimes the end user's input determines what step comes next or whether a step occurs at all. In this post we will investigate the simplest technique Merlin offers for dynamically determining whether a step occurs.</p> <p>It's called a <font face="Courier">ConditionalStep</font>, and as the name implies, it occurs only if some condition is true. Creating a <font face="Courier">ConditionalStep</font> is easier than gaining weight on a cruise ship:</p> <div style="padding-right: 10px; padding-left: 10px; background: #eedaad; padding-bottom: 10px; padding-top: 10px" cour?="Cour?"><font face="Courier">var conditionalStep = new ConditionalStep(</font></font><em>condition</em><font face="cour">,</font> <em>actual step</em><font face="Courier">);</font></div> <p>The <em>condition</em> above is a boolean delegate. The <em>actual step</em> is another step. If (and only if, for the mathematical purists) <em>condition</em> is true, <em>actual step</em> occurs. If the condition is false, we skip on down to the next step. This implies, of course, that <strong><em>a conditional step cannot be the last step in the step sequence.</em></strong> If you try to give such a sequence to the wizard controller, it will throw a <font face="Courier">ConditionalStepAtEndException</font>. </p> <p>I put a small demo together to show off this functionality. You can <a href="http://cid-c7f4a485fd15f03e.skydrive.live.com/self.aspx/.Public/ConditionalStepDemo.zip">get the complete code here</a>, but here's the gist: For our first step, we have a simple form (shown below). If the user clicks next, she goes straight to our farewell message. But, if the user checks the "Show Advanced Options" checkbox, she gets to enter some more data, and then sees the farewell message. <br /> <br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjMEzme9VxjIb8T7QYwXhyqdGQ6qzxkgSOUhn5rvtFVqjfJltY8GxqNz_rLlJO-ZKBBmXv1FWRjDezTjENWu1QEpoK0GVAKNk08jNwJxPWbGGq-zAo2zLeuY92UtMYOcNx-VD0yAWNUIfY/s1600-h/image%5B6%5D.png"><img style="border-top-width: 0px; border-left-width: 0px; border-bottom-width: 0px; border-right-width: 0px" height="444" alt="image" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgq7FUn2WQcoMzZYYFcph462U2ZwLmi74uS-70661cAGTM6Ao2fRjrq-BeKzip_g0cU_esmjw2fnsSMGMJl2WMe7sHOLtVeM1BXI8d2eneZkg87iEJ_Bx1Bc_SXFwm1kP4frkHhnKn75A4//?imgmax=800" width="539" border="0" /></a> </p> <p>And here's the code that makes all this magic possible:</p> <div style="padding-right: 10px; padding-left: 10px; background: #eedaad; padding-bottom: 10px; padding-top: 10px; font-family: courier">var steps = new List<IStep>(); <br />var simpleEntryUI = new SimpleEntryUI(); <br />var simpleEntryStep = new TemplateStep(simpleEntryUI, "User Information"); <br />var advancedEntryStep = <br />    new TemplateStep(new AdvancedEntryUI(), "Advanced User Information"); <br />steps.Add(simpleEntryStep); <br /><strong>steps.Add(new ConditionalStep( <br />    () => { return simpleEntryUI.ShowAdvancedOptions; }, <br />    advancedEntryStep));</strong> <br /> <br />Label farewellMessage = new Label(); <br />farewellMessage.Text = "Well, done. Bye, now!"; <br />steps.Add(new TemplateStep(farewellMessage, 10, "Goodbye!")); <br />new WizardController(steps).StartWizard("User Data Wizard");</div> <p>When the wizard runs, the condition does not get evaluated until the controller reaches the conditional step, which allows you to use data from previous steps in your conditions. That data will be available by the time the condition gets evaluated. </p> <p>If you put a breakpoint inside the condition, you will also notice that the condition does not get evaluated on the way back.<em> </em>In other words, if skip down to the farewell message and click the "back" button, the condition will not be evaluated to determine whether or not you see the advanced data step. Instead, the result of the last condition evaluation will be used. But, if you go backward through a conditional step and then go forward again, the condition will be reevaluated. So to summarize: <strong><em>conditions are evaluated only when the conditional step is reached by clicking the "next" button.</em></strong></p> Yevhttp://www.blogger.com/profile/14558444288426491815noreply@blogger.com0tag:blogger.com,1999:blog-873600240893434569.post-29873256378235610972008-12-22T16:30:00.001-05:002008-12-22T16:52:22.760-05:00Getting Picky<p>Just like covering up the wall sockets in a home with small children, Merlin lets you disable certain elements and validate others, so as to prevent your users from doing unsavory things. </p> <p>Let's go back to the example in the previous post. There, we have a step whose UI consists of a single text box. Let's now make sure that the user cannot advance to the next step without having typed something in the box. We rewrite the main method as follows:</p> <div style="padding-right: 10px; padding-left: 10px; background: #eedaad; padding-bottom: 10px; padding-top: 10px; font-family: courier">var steps = new List<IStep>(); //We'll define wizard steps here. <br />var t = new TextBox(); <br />var myStep = new TemplateStep(t, 10, "Name Entry"); <br />myStep.NextHandler = () => { <br />    if (string.IsNullOrEmpty(t.Text)) <br />    { <br />        MessageBox.Show("You gotta enter your name before I let you continue"); <br />        return false; <br />    } <br />    else return true; <br />}; <br />steps.Add(myStep); <br />var result = new WizardController(steps).StartWizard("My SuperCoolWizard"); <br />if (result == WizardController.WizardResult.Finish) <br />MessageBox.Show("Hello, " + t.Text); <br /></div> <p>That's a bit more code than we had before. Let's look at the new stuff:</p> <div style="padding-right: 10px; padding-left: 10px; background: #eedaad; padding-bottom: 10px; padding-top: 10px; font-family: courier">myStep.NextHandler = () => { <br />    if (string.IsNullOrEmpty(t.Text)) <br />    { <br />        MessageBox.Show("You gotta enter your name before I let you continue"); <br />        return false; <br />    } <br />    else return true; <br />}; </div> <p>The <font face="courier">TemplateStep</font> class offers the <font face="courier">NextHandler</font>, <font face="courier">PreviousHandler</font>, and <font face="courier">CancelHandler</font> properties to handle the "Next/Finish", "Previous", and "Cancel" buttons, respectively. The first two are boolean delegates. If they return false, the controller will not leave the current step. Since the purpose of a cancel button is to drop everything and abort, it does not have a return value, and a cancellation will occur no matter what.</p> <p>But why wait for the user to hit the "Next/Finish" button? Why not just gray it out altogether? I'm glad I asked!</p> <div style="padding-right: 10px; padding-left: 10px; background: #eedaad; padding-bottom: 10px; padding-top: 10px; font-family: courier">var steps = new List<IStep>(); <br />var t = new TextBox(); <br />var myStep = new TemplateStep(t, 10, "Name Entry"); <br />myStep.AllowNextStrategy = () => { <br />    return !string.IsNullOrEmpty(t.Text); <br />}; <br />t.KeyUp += (object sender, KeyEventArgs keyArgs) => { <br />   myStep.StateUpdated(); <br />}; <br />steps.Add(myStep); <br />var result = new WizardController(steps).StartWizard("My Wizard"); <br />if (result == WizardController.WizardResult.Finish) <br />    MessageBox.Show("Hello, " + t.Text);</div> <p>This time, in lieu of a handler for the next button, we provided a strategy (in the form of a boolean delegate) to determine if the Next button should be enabled. But we needed one more piece: </p> <div style="padding-right: 10px; padding-left: 10px; background: #eedaad; padding-bottom: 10px; padding-top: 10px; font-family: courier">t.KeyUp += (object sender, KeyEventArgs keyArgs) => { <br />   myStep.StateUpdated(); <br />}; </div> <p>We need to know when to recheck the strategy so that the buttons accurately reflect the validity of the user's input. To do this, we fire <font face="Courier">TemplateStep</font>'s <font face="Courier">StateUpdated</font> method whenever something in our UI has changed. </p> <p>That does it for this lesson. Please leave any questions or feedback in the comments, we'll do our best to answer.</p> Yevhttp://www.blogger.com/profile/14558444288426491815noreply@blogger.com2tag:blogger.com,1999:blog-873600240893434569.post-8148644035320703322008-12-22T12:46:00.001-05:002008-12-22T12:49:43.029-05:00The obligatory Hello, World<p>Those who prefer to beat around the bush best step off here, for we shall plunge head-first into the magical world of Merlin. All examples here, like Merlin itself, are written in C# 3.0.</p> <p>Starting with a new winforms application, let's add a reference to Merlin.dll and </p> <div style="padding-right: 10px; padding-left: 10px; background: #EEDAAD; padding-bottom: 10px; padding-top: 10px"><font face="Courier">Using Merlin;</font> </div> <p>to the top of the main class file. Now we go inside the <font face="Courier">Main(...)</font> method.</p> <div style="padding-right: 10px; padding-left: 10px; background: #EEDAAD; padding-bottom: 10px; padding-top: 10px"><font face="Courier">var steps = new List<IStep>(); //We'll define wizard steps here. <br />var t = new TextBox(); <br />steps.Add(new TemplateStep(t, "Name Entry")); <br />new WizardController(steps).StartWizard("My SuperCoolWizard"); <br />MessageBox.Show("Hello, " + t.Text); </font></div> <p>When you run this, you should see something like this: <br /><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj74hfacac-V5PkBOR1TwcDHyFc8SG-UTIM6KzAJxBYV7qedwv_rO40_t6HLEu9ZAtNuQRJp8MxryDm3mj7NqUzKs-SKtGZrcSLhaiOF3HNN-C_lwu7aU5vUhJIBj2OgQTCTuyDoG6Nldk//" target="_blank"><img height="279" alt="screen" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj3PKiQdcus8MQRpibGdcxT2KSxL_UkXIHH4y7jY_nisqvn9tFcN3PS1CbYa2BVhc-d2mYH2S8EL4O5uBIwxpMKbNh-1_D86v4TF5TlArl3V-ITYlqVH6kaTAe6Ib4ZQO26MytLB_FoIig//" width="340" border="0" /></a></p> <p>Enter your name into the sole textbox in the page, click "Finish", and you will be greeted plainly but warmly. So what exactly happened?</p> <div style="padding-right: 10px; padding-left: 10px; background: #EEDAAD; padding-bottom: 10px; padding-top: 10px"><font face="Courier">var steps = new List<IStep>();</font></div> <p>A "step" in the wizard correlates to a single screen that you see. The "Next" and "Previous" buttons navigate the user from one step to another. Because the step we see in the wizard is the last one, it appears with a "Finish" button instead of a "Next" button.</p> <div style="padding-right: 10px; padding-left: 10px; background: #EEDAAD; padding-bottom: 10px; padding-top: 10px"><font face="Courier">var t = new TextBox(); <br />steps.Add(new TemplateStep(t, "Name Entry")); </font></div> <p>The UI of a step is represented by a control. <font face="Courier">TemplateStep</font>, a simple step implementation, requires (at a minimum) that control as its constructor argument. It attempts to size this control to take up all the space available. Of course, we don't always want the control to be flushed against the edges of the step area. So we can provide a margin around it by changing the statement above to:</p> <div style="padding-right: 10px; padding-left: 10px; background: #EEDAAD; padding-bottom: 10px; padding-top: 10px"><font face="Courier">steps.Add(new TemplateStep(t, 10, "Name Entry"));</font></div> <p>This creates a margin of 10 pixels from each edge of the control. But let's go on examining our code:</p> <div style="padding-right: 10px; padding-left: 10px; background: #EEDAAD; padding-bottom: 10px; padding-top: 10px"><font face="Courier">new WizardController(steps).StartWizard("My SuperCoolWizard"); </font></div> <p>Once we have our sequence of steps, we need to execute them. The <font face="Courier">WizardController</font> object does just that. It accepts as its constructor an IList of steps. When the StartWizard method is invoked, it goes through the steps as the user clicks the "Back" and "Next/Finish" buttons. </p> <p>Ok, simple enough, but now let's run this wizard again, but instead of the "Finish" button, click the "Cancel" button. Whoops! You still get greeted. The cancel button aborts the wizard, but you still have to check afterward to make sure the wizard exited successfully. So let's rewrite the last two lines in our example as follows:</p> <div style="padding-right: 10px; padding-left: 10px; background: #EEDAAD; padding-bottom: 10px; padding-top: 10px"><font face="Courier">var result = new WizardController(steps).StartWizard("My SuperCoolWizard"); <br />if (result == WizardController.WizardResult.Finish) <br />{ <br />    MessageBox.Show("Hello, " + t.Text); <br />}</font></div> <p>Of course, there's a lot more to wizards than single-control steps. Join us next time for more Merlin magic.</p> Yevhttp://www.blogger.com/profile/14558444288426491815noreply@blogger.com0