3

I have trying to build a simulator of Ingenico POS terminal (iWL220). The main screen I have a combo-box. Once user enter id and password the combo-box load 6 menus. If User click btn1 then combo-box clear the menu and add another set of menu. If user click btn1 for new loaded menu then again combo-box cleared and load another set of menu so on.

My problem is for each button click (btn1, btn2, btn3, btn4, btn5) I have to code lot of if else statement. Example;

First menu (on combo-box) has 6 sector.

  • 1.SectorA
  • 2.SectorB
  • 3.SectorC
  • 4.SectorD
  • 5.SectorE
  • 6.SectorF

If user choose 1.SectorA then user click btn1. Then btn1 clear the combo-box and loads another set of menu. This time menu (on combo-box) has 3 companies.

  • 1.CompanyA
  • 2.CompanyB
  • 3.CompanyC

This time user choose 1.CompanyA then user click again btn1. Then btn1 clear the combo-box and loads another set of menu. This time menu (on combo-box) has 2 payment option.

  • 1.FullPayment
  • 2.ParitalPayment

Now this time if user click btn1 or btn2 the combo-box visible become false and in main screen there is a label and text box. The text box allows user to enter the subscriber number and press enter (green button).

I already load Ingenico terminal picture as jpeg and top of it I set my buttons. I gave only small version of my simulation. In my app there are 114 probability that user can choose.

In my app btn1 has 92 probability to be clicked, btn2 has 53 probability to be clicked and so on. After user enters the subscriber number and click green button the my app use wfc services to format the data and send to sql server. But before user click each combination of button some where in my app I store the btn number as 422. This 422 means, user chose SectorD + CompanyB + ParitalPayment option. So my wfc will know what it is mean 422.

My question is what is the shortest way to construct my button events for this 114 probability case?


I have 4 buttons. Btn1, Btn2, Btn3 and Btn4. Also I have some arrays as it shown below and 1 combo-box.

1.ArrayMain() = {“1.Water”,”2.Air”,”3.Soil”,”4.Fire”}
   1.1. ArrayWater() = {“1.Salty”,”2.Fresh”, “3.Contaminated”}
      1.1.1.ArraySalty() = {1.”AA”, 2.”BB”, 3.”CC”}
      1.1.2.ArrayFresh() = {1.”DD”, 2.”EE”, 3.”FF”}
      1.1.3.ArrayContaminated() = {1.”XX”, 2.”YY”, 3.”ZZ”}                              

1.2   ArrayAir() = {“1.Fresh”, “2.Contaminated”}
1.3   ArraySoil() = {“1.Normal”, “2.Contaminated”}
1.4   ArrayFire() = {“1.Low”,”2.Mid”,”3.High”}

When my app starts, first array values 1.(ArrayMain) fills the comboBox. This comboBox will have 4 values as, “1.Water”, ”2.Air”, ”3.Soil”, ”4.Fire” in it. If user choose “1.Water” than user clicks Btn1. Than btn1 events clears the comboBox and loads 1.1ArrayWater() values into comboBox.

Second time if user chooses “1.Salty” than user clicks again btn1 and this time btn1 events clears the comboBox and loads 1.1.1ArraySalty() values into comboBox.

Third time if user chooses “2.BB” than user clicks Btn2 and sends the information “BB” for calculation.

Change ComboBox Values with button selection

First you have 5 (more or less) menu item and each time you press any (number) buttons (1 to 9 lilke in pos terminal) pressed than new menu appears on the screen.

JasonMArcher
  • 14,195
  • 22
  • 56
  • 52
NTMS
  • 816
  • 7
  • 22
  • Check this answer out: http://stackoverflow.com/a/10061705/1308385 – jimutt Apr 25 '15 at 21:05
  • thank you, I will look at later. My problem is that I already added buttons manually and set the trans color and loaded image as well so user can fell like entering actual ingenico pos terminal buttons. – NTMS Apr 25 '15 at 21:33
  • @user3362234 I have modified my answer to include working nearly full example. Also, I have rolled back your last question edit to return your question to more streamlined look. – Eugene Podskal Apr 27 '15 at 12:30
  • @user3362234 I have made an update on GitHub - https://github.com/Podskal/StackOverflow_29870164.git . It is very ugly, and probably over engineered, and obviously in a dire need of clean rewrite, but it does the job done (demonstrates possible approach to handle your system). If you have some new specific questions(like some specific modification of this code or some easier alternative approach to solve the whole problem) you can ask them as new separate questions - this was interesting problem to solve, but I am sure that in the format of [SO] I have done more than enough. Good luck. – Eugene Podskal Apr 28 '15 at 23:09
  • Well, sorry, for it seems that I had some misconception about your system and thus more generic solution may be unnecessary when all interactions are very similar. So, everything can be done much simpler and with minimum of code - basically turn your logic into tree where nodes contain Items for combobox (key-string to show, value - value to use later). When you push buttons you will add respective node item into some queue and load new items. In leaf nodes you will contain `Action>` that will call respective service method (for many options it can be the same action). – Eugene Podskal Apr 29 '15 at 08:57

1 Answers1

3

Each button at any specific time shall execute some specific action depending on the state of the system. Obviously, if you try to decide the specific action depending on the multitude of different variables, you will create a lot of branching code. Such code is very difficult to write correctly and even more difficult to debug and maintain.

So, what if we encapsulate current action for each possible state(sequence of state) in some specific class (interface):

/// <summary>
/// Represents internal terminal presenter that is used inside IGlobalTerminalPresenter.
/// </summary>
public interface ITerminalPresenter
{
    void UpdateUI();
    ITerminalPresenter this[Int32 index]
    {
        get;
    }
    ITerminalPresenter Do1();
    ITerminalPresenter Do2();
    ITerminalPresenter Parent
    {
        get;
        set;
    }
    void Reset();
}

Inside the form we will use field of a similar interface that will encapsulate all changes of the presenter.

/// <summary>
/// Represents terminal presenter that UI can operate upon.
/// </summary>
public interface IGlobalTerminalPresenter
{
    void UpdateUI();

    void Do1();

    void Do2();

    Int32 SelectedIndex
    {
        get;
        set;
    }

    void Reset();
}

Our event handlers will become:

    private void comboBox_SelectedIndexChanged(object sender, EventArgs e)
    {
        var senderComboBox = (ComboBox)sender;

        this.globalTerminalPresenter.SelectedIndex = senderComboBox.SelectedIndex;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        this.globalTerminalPresenter.Do1();
    }

    private void button2_Click(object sender, EventArgs e)
    {
        this.globalTerminalPresenter.Do2();
    }

To allow our concrete TerminalPresenters to interoperate with form we will force our form to implement the following interface:

/// <summary>
/// This represents your UI in technology-independent manner
/// </summary>
public interface ITerminalView
{
    String Title { get; set; }
    String Input { get; set; }
    String Output { get; set; }
    String Button1_Text { get; set; }
    String Button2_Text { get; set; }
    IEnumerable<String> SelectionItems { get; set; }
    void Clear();
}

public partial class MainForm : Form,
    ITerminalView
{
    ...
    #region ITerminalView implementation

    public string Title
    {
        get { return this.Text; }
        set { this.Text = value; }
    }

    public String Button1_Text
    {
        get { return this.button1.Text; }
        set { this.button1.Text = value; }
    }

    public String Button2_Text
    {
        get { return this.button2.Text; }
        set { this.button2.Text = value; }
    }

    public string Input
    {
        get { return this.textBox_Input.Text; }
        set { this.textBox_Input.Text = value; }
    }

    public string Output
    {
        get { return this.textBox_Output.Text; }
        set { this.textBox_Output.Text = value; }
    }

    public IEnumerable<string> SelectionItems
    {
        get { return this.comboBox.Items.Cast<String>(); }
        set
        { 
            this.comboBox.Items.Clear();

            if (value == null)
                return;

            foreach (var item in value)
            {
                this.comboBox.Items.Add(item);
            }
        }
    }

    public void Clear()
    {
        this.comboBox.SelectedIndex = -1;
        this.Title = String.Empty;
        this.Input = String.Empty;
        this.Output = String.Empty;
        this.SelectionItems = null;
    }

    #endregion

For now we will create two TerminalPresenters - one to just allow selection of next option through combobox, one that calculates sum of two numbers. Both of them use the same base class.

/// <summary>
/// Base class for all presenters
/// </summary>
public abstract class TerminalPresenterBase : ITerminalPresenter
{
    protected ITerminalView view;

    public TerminalPresenterBase(ITerminalView view)
    {
        if (view == null) 
            throw new ArgumentNullException("view");

        this.view = view;
        this.Parent = this;
    }

    public abstract void UpdateUI();

    public abstract ITerminalPresenter this[int index]
    {
        get;
    }

    public abstract ITerminalPresenter Do1();
    public abstract ITerminalPresenter Do2();

    public virtual ITerminalPresenter Parent
    {
        get;
        set;
    }

    public virtual void Reset()
    {
        this.UpdateUI();
    }
}

/// <summary>
/// Presenter whose sole goal is to allow user to select some other option and press next
/// </summary>
public class SelectOptionPresenter : TerminalPresenterBase
{
    private IList<KeyValuePair<String, ITerminalPresenter>> options;
    private ITerminalPresenter selected;
    private String title;

    public SelectOptionPresenter(ITerminalView view,
        String title, 
        IList<KeyValuePair<String, ITerminalPresenter>> options)
        : base(view)
    {
        if (options == null)
            throw new ArgumentNullException("options");

        this.title = title;

        this.options = options;

        foreach (var item in options)
        {
            item.Value.Parent = this;
        }
    }

    public override void UpdateUI()
    {
        this.view.Clear();

        this.view.Button1_Text = "Confirm selection";
        this.view.Button2_Text = "Go back";
        this.view.Title = title;
        this.view.SelectionItems = options
            .Select(opt => opt.Key);
    }

    public override ITerminalPresenter this[int index]
    {
        get
        {
            this.selected = this.options[index].Value;

            return this;
        }
    }

    public override ITerminalPresenter Do1()
    {
        return this.ConfirmSelection();
    }

    public override ITerminalPresenter Do2()
    {
        return this.GoBack();
    }

    public ITerminalPresenter ConfirmSelection()
    {
        this.selected.UpdateUI();
        return this.selected;
    }

    public ITerminalPresenter GoBack()
    {
        this.Parent.UpdateUI();
        return this.Parent;
    }
}

public enum APlusBState
{
    EnterA,
    EnterB,
    Result
}

public class StepActions
{
    public Action UpdateUI { get; set; }

    public Func<ITerminalPresenter> Do1 { get; set; }

    public Func<ITerminalPresenter> Do2 { get; set; }
}

public class APlusBPresenter : TerminalPresenterBase
{
    private Int32 a, b;
    private APlusBState state;
    private String error = null;

    private Dictionary<APlusBState, StepActions> stateActions;

    private void InitializeStateActions()
    {
        this.stateActions = new Dictionary<APlusBState, StepActions>();

        this.stateActions.Add(APlusBState.EnterA,
            new StepActions()
            {
                UpdateUI = () =>
                {
                    this.view.Title = this.error ?? "Enter A";
                    this.view.Input = this.a.ToString();
                    this.view.Button1_Text = "Confirm A";
                    this.view.Button2_Text = "Exit";
                },
                Do1 = () => // Confirm A
                {
                    if (!Int32.TryParse(this.view.Input, out this.a))
                    {
                        this.error = "A is in incorrect format. Enter A again";
                        return this;
                    }

                    this.error = null;                     
                    this.state = APlusBState.EnterB;

                    return this;
                },
                Do2 = () => // Exit
                {
                    this.Reset();

                    return this.Parent;
                }
            });

        this.stateActions.Add(APlusBState.EnterB,
            new StepActions()
            {
                UpdateUI = () =>
                {
                    this.view.Title = this.error ?? "Enter B";
                    this.view.Input = this.b.ToString();
                    this.view.Button1_Text = "Confirm B";
                    this.view.Button2_Text = "Back to A";
                },
                Do1 = () => // Confirm B
                {
                    if (!Int32.TryParse(this.view.Input, out this.b))
                    {
                        this.error = "B is in incorrect format. Enter B again";
                        return this;
                    }

                    this.error = null;                     
                    this.state = APlusBState.Result;

                    return this;
                },
                Do2 = () => // Back to a
                {
                    this.state = APlusBState.EnterA;

                    return this;
                }
            });

        this.stateActions.Add(APlusBState.Result,
            new StepActions()
            {
                UpdateUI = () =>
                {
                    this.view.Title = String.Format("The result of {0} + {1}", this.a, this.b);
                    this.view.Output = (this.a + this.b).ToString();
                    this.view.Button1_Text = "Exit";
                    this.view.Button2_Text = "Back";
                },
                Do1 = () => // Exit
                {
                    this.Reset();

                    return this.Parent;
                },
                Do2 = () => // Back to B
                {
                    this.state = APlusBState.EnterB;

                    return this;
                }
            });
    }

    public APlusBPresenter(ITerminalView view) : base(view)
    {
        this.InitializeStateActions();
        this.Reset();
    }

    public override void UpdateUI()
    {
        this.view.Clear();

        this.stateActions[this.state].UpdateUI();
    }

    public override ITerminalPresenter this[int index]
    {
        get { throw new NotImplementedException(); }
    }

    public override ITerminalPresenter Do1()
    {
        var nextPresenter = this.stateActions[this.state].Do1();

        nextPresenter.UpdateUI();

        return nextPresenter;
    }

    public override ITerminalPresenter Do2()
    {
        var nextPresenter = this.stateActions[this.state].Do2();

        nextPresenter.UpdateUI();

        return nextPresenter;
    }

    public override void Reset()
    {
        this.state = APlusBState.EnterA;
        this.a = 0;
        this.b = 0;
        this.error = null;
    }
}

/// <summary>
/// Represents terminal presenter to use inside GUI. It handles current ISpecificTerminalPresenter inside itself.
/// </summary>
public class GlobalTerminalPresenter : IGlobalTerminalPresenter
{
    #region Fields

    private ITerminalPresenter current;
    private Int32 selectedIndex;

    #endregion


    #region Constructors

    public GlobalTerminalPresenter(ITerminalPresenter mainPresenter)
    {
        if (mainPresenter == null)
            throw new ArgumentNullException("mainPresenter");

        this.current = mainPresenter;

        this.UpdateUI();
    }

    #endregion

    public void UpdateUI()
    {
        this.current.UpdateUI();
    }

    public void Do1()
    {
        this.current = this.current.Do1();
    }

    public void Do2()
    {
        this.current = this.current.Do2();
    }

    public Int32 SelectedIndex
    {
        get
        {
            return this.selectedIndex;
        }
        set
        {
            this.selectedIndex = value;

            if (value == -1)
                return;

            this.current = this.current[value];
        }
    }

    public void Reset()
    {
        this.current.Reset();
    }
}

Then we initialize them in the constructor of our form:

public partial class MainForm : Form,
    ITerminalView
{
    private IGlobalTerminalPresenter globalTerminalPresenter;

    public MainForm()
    {
        InitializeComponent();

        var nextLevelPresenters = new KeyValuePair<String, ITerminalPresenter>[]
        {
            new KeyValuePair<String, ITerminalPresenter>(
                "A plus B", 
                new APlusBPresenter(this)),
            new KeyValuePair<String, ITerminalPresenter>(
                "Just empty selector", 
                new SelectOptionPresenter(this, 
                    "Selector with no selection choices", 
                    Enumerable
                        .Empty<KeyValuePair<String, ITerminalPresenter>>()
                        .ToArray()))
        };

        var topPresenter = new SelectOptionPresenter(this, "Select the option and press the confirm button",  nextLevelPresenters);

        this.globalTerminalPresenter = new GlobalTerminalPresenter(topPresenter);
    }

P.S.1: These code snippets assume that you have form named MainForm that has two buttons - button1, button2, one combobox, two textBoxes - textBox_Input, textBox_Output.

P.S.2: The pattern used is close enough to Model-View-Presenter, just without DataBindings.

P.S.3 You can create more or less generic state machine Presenters if you modify APlusBPresenter code. Or try to shape up ChainXxxx... classes and interfaces.

P.S.4: And sorry for these walls of code. That's probably too much for [SO] format, so I've put ad-hoc proof of concept at GitHub - https://github.com/Podskal/StackOverflow_29870164.git. It is ugly in many aspects, but as it is, it can at least give few ideas about how to implement your own system.

P.S.5: There are a lot of problematic places in this code, so you should very carefully consider how you will build your own system from it.

Eugene Podskal
  • 10,270
  • 5
  • 31
  • 53
  • Thank you your kind post. My problem is that each button do lot of work. ex: each time click btn1 it loads different value in combo-box. then again if I click btn1 base on the combo-box value then it clears combo-box data and loads another one, so on. I wish I can load my excel example but I cant do it here. – NTMS Apr 25 '15 at 21:38
  • Hi Eugene, Thank you very much. Your first idea is very good. I dont want to put multiple controls. I will also learn better programmşng for using the interfaces. I understand bit more clearly your terminal Contexts. But I am new to C# Interface. So do I have to create ITerminalContext in seperate class? Is this class resides on my application folder? If so then I guess I can manage to adopt to my app. If I stuck any point I will write here. I will also write my code here as well. Kind Regards. – NTMS Apr 27 '15 at 06:58
  • Hi @Eugene, I stuck! First of all I create desperate class file in my form app folder as ITerminalContext.cs. My class look like as you wrote above. – NTMS Apr 27 '15 at 08:03
  • :) I re-write in my questions. Where do I have to use or consturct (add) each combobox values. Your solution is okay. But like me without good tutorial on your solution I will not make it. Is it possible to send me tutorial of your ideas (even if a pdf format)? (your logic is great) Kind regards – NTMS Apr 27 '15 at 08:22
  • Thank you Eugene. Logic you provide for me very good. I like to have git-hub link, so I can study before I try to attempt to create it myself. Feel bit strange, newbie like me to use M-V-P architectural pattern without data. I can see how to use M-V-P pattern to my problem and seems best option to have. Thank you very much. – NTMS Apr 27 '15 at 13:05
  • Thank you @Eugene, I will try to build and debug to see how its work. Kind Regards. – NTMS Apr 28 '15 at 05:35
  • I found "The Model-View-Presenter (MVP) Pattern" in this link: https://msdn.microsoft.com/en-us/library/ff649571.aspx Also I found these videos tutorial on subject: https://www.youtube.com/watch?v=GVYd6-LfQm4&index=1&list=PLI4_Dhk0WbIRHLfOu31N0npWVML0pm7GZ Hopefully I will be understand your logic much clearly and apply to my termianl simulation. Kind Regards – NTMS Apr 28 '15 at 13:14
  • Thank you @Eugene. For me to understand your logic I have to understand MVP (interfaces) first so I can completely move my app to mvp so I can re used the models for different pos terminal simulation. I really loved the logic that you provided. Kind Regards. – NTMS Apr 28 '15 at 19:08
  • Thanks @Eugene. I downloaded it and look at what you did. But actually bit different what I expected. In your example on Git-hub, user must make a selection from combo box. In my terminal simulation user can not touch the screen (means cannot choose something from combo box by just using combo box.). User must use button 1 to select "A plus B" in your example. Menu say like an array must shown in combo box (or list-view as I say) for the first time when app is run. – NTMS Apr 28 '15 at 20:02
  • Say example in list-view or combo-box (DropDownStyle = Simple) If your menu "A plus B" and "Just empty selector" on the screen (say in list-view) user have to use number keypad to make selection. Say If I press 1 means I want to choose "A plus B" in list view. And If I choose that, then combo box (or list-view) must clear all the values and load another 2 menu. (say True and False) Now this time if user choose "True" in list-view than user must press number 1 on key pad (button 1). – NTMS Apr 28 '15 at 20:04
  • I add my pos terminal picture and write some array under it show how combo-box values loaded with user key pad selection. Kind Regards – NTMS Apr 28 '15 at 20:04
  • INFO: Thank you @Eugene. I made it. Here it is. http://stackoverflow.com/questions/29941997/how-to-set-same-button-events-for-listbox-value-index-or-treeview-nodes – NTMS Apr 29 '15 at 18:56