【问题标题】:Handle control events differently depending on the state of the system根据系统状态以不同方式处理控制事件
【发布时间】:2015-07-04 09:30:44
【问题描述】:

我正在尝试构建 Ingenico POS 终端 (iWL220) 的模拟器。 主屏幕我有一个组合框。一旦用户输入 ID 和密码,组合框就会加载 6 个菜单。如果用户单击 btn1,则组合框清除菜单并添加另一组菜单。如果用户单击 btn1 以获取新加载的菜单,则再次清除组合框并加载另一组菜单,依此类推。

我的问题是对于每个按钮单击(btn1、btn2、btn3、btn4、btn5),我必须编写大量 if else 语句。示例;

第一个菜单(在组合框上)有 6 个扇区。

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

如果用户选择 1.SectorA 然后用户点击 btn1。然后 btn1 清除组合框并加载另一组菜单。这个时间菜单(在组合框上)有 3 个公司。

  • 1.公司A
  • 2.公司B
  • 3.CompanyC

这次用户选择 1.CompanyA 然后用户再次点击 btn1。然后 btn1 清除组合框并加载另一组菜单。这个时间菜单(在组合框上)有 2 个付款选项。

  • 1.全额付款
  • 2.ParitalPayment

现在,如果用户单击 btn1 或 btn2,可见的组合框变为 false,并且在主屏幕中有一个标签和文本框。文本框允许用户输入用户号码并按下回车键(绿色按钮)。

我已经将 Ingenico 终端图片加载为 jpeg,并在其中设置了按钮。 我只给出了我的模拟的小版本。在我的应用程序中,用户可以选择 114 种概率。

在我的应用中,btn1 有 92 的概率被点击,btn2 有 53 的概率被点击等等。用户输入订户号码并单击绿色按钮后,我的应用程序使用 wfc 服务格式化数据并发送到 sql server。 但是在用户单击每个按钮组合之前,我在我的应用程序中的某个位置将 btn 编号存储为 422。这个 422 表示,用户选择了 SectorD + CompanyB + ParitalPayment 选项。所以我的 wfc 会知道 422 是什么意思。

我的问题是为这个 114 概率案例构建按钮事件的最短方法是什么?


我有 4 个按钮。 Btn1、Btn2、Btn3 和 Btn4。我还有一些如下所示的数组和 1 个组合框。

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”}

当我的应用程序启动时,第一个数组值 1.(ArrayMain) 填充组合框。这个组合框将有 4 个值,分别是“1.Water”、“2.Air”、“3.Soil”、“4.Fire”。如果用户选择“1.Water”,则用户点击 Btn1。比 btn1 事件清除组合框并将 1.1ArrayWater() 值加载到组合框。

如果用户第二次选择“1.Salty”而不是用户再次单击 btn1,这一次 btn1 事件清除组合框并将 1.1.1ArraySalty() 值加载到组合框。

如果用户第三次选择“2.BB”,则用户点击Btn2并发送信息“BB”进行计算。

首先,您有 5 个(或多或少)菜单项,每次按下任何(数字)按钮(在 pos 终端中为 1 到 9 类似),屏幕上就会出现新菜单。

【问题讨论】:

  • 谢谢,我稍后再看。我的问题是我已经手动添加了按钮并设置了反式颜色和加载的图像,因此用户可以像输入实际的 ingenico pos 终端按钮一样。
  • @user3362234 我已经修改了我的答案以包括工作几乎完整的示例。此外,我已回滚您的上一个问题编辑,以使您的问题恢复到更精简的外观。
  • @user3362234 我在 GitHub 上做了更新 - github.com/Podskal/StackOverflow_29870164.git。它非常丑陋,并且可能过度设计,并且显然迫切需要彻底重写,但它完成了工作(展示了处理系统的可能方法)。如果您有一些新的特定问题(例如对此代码的一些特定修改或解决整个问题的一些更简单的替代方法),您可以将它们作为新的单独问题提出 - 这是一个有趣的问题要解决,但我相信在格式的Stack Overflow 我做得已经够多了。祝你好运。
  • 好吧,抱歉,我似乎对您的系统有一些误解,因此当所有交互都非常相似时,可能不需要更通用的解决方案。因此,一切都可以用最少的代码更简单地完成 - 基本上将您的逻辑变成树,其中节点包含组合框的项目(要显示的键字符串,值 - 稍后使用的值)。当您按下按钮时,您会将相应的节点项目添加到某个队列中并加载新项目。在叶节点中,您将包含Action<Queue<NodeItem>>,它将调用相应的服务方法(对于许多选项,它可以是相同的操作)。

标签: c# winforms simulation simulator


【解决方案1】:

每个按钮在任何特定时间都应根据系统状态执行某些特定操作。显然,如果您尝试根据众多不同的变量来决定具体的操作,您将创建大量的分支代码。这样的代码很难正确编写,更难调试和维护。

那么,如果我们将每个可能状态(状态序列)的当前动作封装在某个特定的类(接口)中会怎样:

/// <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();
}

在表单内部,我们将使用类似接口的字段,该字段将封装演示者的所有更改。

/// <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();
}

我们的事件处理程序将变为:

    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();
    }

为了让我们的具体 TerminalPresenters 与表单互操作,我们将强制我们的表单实现以下接口:

/// <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

现在我们将创建两个 TerminalPresenter - 一个只允许通过组合框选择下一个选项,一个计算两个数字的总和。它们都使用相同的基类。

/// <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();
    }
}

然后我们在表单的构造函数中初始化它们:

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:这些代码 sn-ps 假设您有一个名为 MainForm 的表单,它有两个按钮 - button1、button2、一个组合框、两个文本框 - textBox_Input、textBox_Output。

P.S.2:使用的模式与Model-View-Presenter 足够接近,只是没有DataBindings

P.S.3 如果您修改 APlusBPresenter 代码,您可以创建或多或少的通用状态机 Presenter。或者尝试构建ChainXxxx... 类和接口。

P.S.4: 很抱歉这些代码墙。对于 [SO] 格式来说,这可能太多了,所以我在 GitHub 上放置了临时概念证明 - https://github.com/Podskal/StackOverflow_29870164.git。它在许多方面都很丑陋,但就其本身而言,它至少可以提供一些关于如何实现自己的系统的想法。

P.S.5:这段代码有很多有问题的地方,所以你应该非常仔细地考虑如何用它来构建你自己的系统。

【讨论】:

  • 谢谢你的好帖。我的问题是每个按钮都做了很多工作。例如:每次单击 btn1 它都会在组合框中加载不同的值。然后再次,如果我基于组合框值单击 btn1 则它会清除组合框数据并加载另一个,依此类推。我希望我可以加载我的 excel 示例,但我不能在这里加载。
  • 嗨,尤金,非常感谢。你的第一个想法非常好。我不想放置多个控件。我还将学习更好的程序来使用接口。我更清楚地了解您的终端上下文。但我是 C# 接口的新手。那么我必须在单独的类中创建 ITerminalContext 吗?此类是否位于我的应用程序文件夹中?如果是这样,那么我想我可以设法采用我的应用程序。如果我坚持任何一点,我会在这里写。我也会在这里写我的代码。亲切的问候。
  • 嗨@Eugene,我卡住了!首先,我在我的表单应用程序文件夹中创建绝望的类文件作为 ITerminalContext.cs。我的班级就像你在上面写的那样。
  • :) 我重写了我的问题。我必须在哪里使用或构造(添加)每个组合框值。你的解决方案没问题。但是像我一样没有关于你的解决方案的好教程,我不会成功。是否可以向我发送您的想法教程(即使是 pdf 格式)? (你的逻辑很棒)亲切的问候
  • 谢谢尤金。你为我提供的逻辑非常好。我喜欢有 git-hub 链接,所以我可以在尝试自己创建它之前学习。感觉有点奇怪,像我这样的新手在没有数据的情况下使用 M-V-P 架构模式。我可以看到如何使用 M-V-P 模式来解决我的问题,并且似乎是最好的选择。非常感谢。
猜你喜欢
  • 2020-09-04
  • 1970-01-01
  • 1970-01-01
  • 2021-05-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多