4

I'm working on windows forms application. Recently, I get new requirements that I don't know how to approach.

  1. Users of the application will have one of two roles: Admin or User.
  2. Admin can set a list of instructions to do when encountering different situations.
  3. Normal users (User role), will get a pop-up window with the next instruction to do for a specific situation.

Instructions configured by Admin are like help or guides to normal and newbie users. So they know what to do next without bothering the supervisor (Admin).

For example, my application has two buttons: Button 1 and Button 2. Admin can input this of instructions:

  • If Button 1 clicked => Message: "Click Button 2"
    • If Button 2 clicked ==> Message: "Send email to xxx"
  • If Button 2 clicked && time > midday => Message: "Click Button 1 first"
  • If Button 2 clicked && time <= midday => Message: "Send email to yyy"
  • ...

I just want to give a simple example to clarify things, but, in reality, I have to handle hundreds of cases to cover all situations. I was thinking about creating a database table containning two columns: Conditions and Message. However, I can not see:

  1. What list of conditions an Admin can see while configuring
  2. How to combine and save these conditions
  3. How to match the Conditions string column to a specific case at run-time

    Any thoughts or suggestions how to approach this requirement?

  • How advanced are the admin users? Rather than creating your own DSL from scratch you could use something like Ironpython to allow admins to write their own code snippets, and then execute these scripts at runtime. – Nick Williams Apr 26 '17 at 22:07
3
+50

For complex conditions, it might be a good idea to design some domain specific language. This does not need to be very complex, you can try if you can fulfill your requirements by using

  • some atomic functions (like "Button('buttonTag').IsClicked()", "CurrentTime() >'hh:mm'")
  • boolean operators (AND, OR, NOT)
  • some comparison operators for time values or other numbers
  • parentheses

Now your conditions will be simply a list of strings, and you can combine them with the related messages as string pairs.

What list of conditions an Admin can see while configuring

In the simplest form, the Admin just has a text editor control for entering the conditions (and the related messages). You can show him a helper window where he can pick the available atomic condition types of the specific form.

How to combine and save these conditions

The Admin combines them as he likes and they are saved as an ordered list of string-pairs (one string for the condition, and one for the message).

How to match the Conditions string column to a specific case at run-time

That is the hardest part - you need to build an interpreter for the condition DSL (which is, for simple DSLs, easier as it might look at a first glance, look for example here). Whenever an event occurs where one of the conditions might become fulfilled, run the interpreter over the full list of conditions, evaluate them one after another and as soon as the first condition matches, show the related message.

You surely should hook your interpreter into all button-clicked events of your form (which is actually pretty simple in Win Forms, since .NET support multi cast events, and you can easily iterate over all controls of a form). In case you need to allow conditions which are only time-triggered, you might need to run the interpreter also automatically every minute. Depending on the other atomic events (like an event when one control gets the focus) you might hook into all these other events as well.

Of course, there is plenty of room for optimizations, you might exclude click events of buttons which are not referred in the current list of conditions so far. You could do the parsing (and syntax checking) of the conditions beforehand, when the Admin enters the condition, and store the abstract syntax tree, so the when it comes to interpreting the conditions, your program does not need to do the parsing again.

  • This is the first time I have heard the term "domain specific language". After reading a little bit upon it, It seems very very interesting. Thanks!!! – Mhd Apr 24 '17 at 21:17
  • @Mhd be careful when choosing a DSL - tooling is very important. Remember that any logic written in a DSL is still 'code', it just happens to be code which is far more meaningful in the context of your real problem domain than a general-purpose language like C#. If you're going to aim for a DSL in C#, then I'd suggest looking at Roslyn which will allow you to write a scripting language based on C# (to take advantage of C# tooling and the .NET framework). Two possible approaches for Roslyn-based scripting here: daveaglick.com/posts/… – Ben Cottrell Apr 27 '17 at 23:58
  • @BenCottrell tooling for end users or tooling for developers? I have always preferred Antlr for developing languages. The Visual Studio SDK doesn't have anything like that. – Frank Hileman May 1 '17 at 18:54
  • @FrankHileman I was actually suggesting not developing a language at all and instead using C# as a scripting language directly using Roslyn – Ben Cottrell May 2 '17 at 8:54
0

A database is a good idea. However, I would prefer a simple XML file, that will do the job.

About the conditions: maybe Regex will help, but I don't think so. I also don't know any suitable built-in solutions, so I would create my own:

  • Create a set of classes, using inheritance: base class CCondition, derived CTimeCondition and similar.
  • Create a class CMessage, that contains the data / text to be displayed and a list of conditions. New CCondition can be added to the list, existing ones can be removed.
  • You will need a GUI for that: create an user control UC-A, that holds a list (of table with 2 columns) of the messages. When a message is selected, show the text and list of Conditions in another user control UC-B, which was inserted into UC-A. When selecting a CCondition, show its content in UCCondition.
  • Like the inherited classes of CCondition, I would create a corresponding set of UC, one for each condition class.
  • Add a create-method (factory design pattern) into both (U)CCondition, create an EnumType inside CCondition. An enum value of that EnumType is then passed into the create(), which returns the CCondition of the desired type.
  • ...

Saving into/ loading from XML:

  • I would add LoadFromXml() and SaveToXml() into each CCondition and into CMessage. (That's how I read/write class data from/to Xml).
  • Simply write the whole XML in every save operation.

This is a very modular approach (one of the principles of object-oriented programming, that I like most!), that has many elements that you can reuse in other situations.

Feel free to ask for more advices.

Edit #1
The connection between these conditions and the actions of the operator could be, that the operator action (or any kind of event in the program) is one of the conditions inside CMessage.
That being said, I think I would rename CMessage into CReaction, and add a CAction (which is the user action or program event).
Data can be organized in many different ways:

  • CReaction, holding CAction and a list of conditions.
  • CAction, holding CReaction and a list of conditions.
  • Dictionary of CAction (key) and CReaction (value), but this would limit to a 1-to-1 mapping between CAction and CReaction. Alternatively put a List of CReaction as value into the dictionary...
  • ... Whatever fits your need.

When a button is pressed or any other event occurs, call the function which

  • cycles through the CAction list to find the relevant entries
  • then checks all conditions of all found entries
  • and executes all CReaction of the CAction with matching conditions.
  • I can't see how to attach a set of conditions to a set of real actions? – Mhd Apr 24 '17 at 17:27
  • @Mhd: enhanced. If this fits your needs, you owe me a beer. ;-) – Tobias Knauss Apr 24 '17 at 17:46
  • Thanks @Tobias. It seems good suggestion. I need some time to implement it though – Mhd Apr 24 '17 at 17:56
  • @Mhd, of course you'll need some time. I have dozens of helper classes in my software and I would still need about 1-2 weeks for that when doing it right. ;-) – Tobias Knauss Apr 24 '17 at 17:58
0

From what I understand you do not need a scripting facility, you just need a dictionary of hint messages and one line of hard code at each user step/junction in your application. Like in the click event handler of button1, you call PresentHint which looks in your dictionary if a hint is available (your key could be "Button1Clicked"). If it is, it will present the hint to the user.

As storage, a plain text file with key-value pairs would do. Xml would be more flexable though, you may want to add attributes to your hint object later.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.