根据系统状态以不同方式处理控制事件
Handle control events differently depending on the state of the system
我正在尝试构建 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.CompanyA
- 2.CompanyB
- 3.CompanyC
这次用户选择 1.CompanyA 然后用户再次单击 btn1。然后 btn1 清除组合框并加载另一组菜单。这次菜单(在组合框上)有 2 个付款选项。
- 1.FullPayment
- 2.ParitalPayment
现在,如果用户单击 btn1 或 btn2,组合框可见变为 false,并且在主屏幕中有一个标签和文本框。文本框允许用户输入用户号码并按下回车键(绿色按钮)。
我已经将 Ingenico 终端图片加载为 jpeg,并在其顶部设置了我的按钮。
我只给出了我的模拟的小版本。在我的应用程序中,用户可以选择的概率为 114。
在我的应用程序中,btn1 被点击的概率为 92,btn2 被点击的概率为 53,依此类推。用户输入订阅者号码并单击绿色按钮后,我的应用程序使用 wfc 服务格式化数据并发送到 sql 服务器。
但是在用户单击我应用程序某个位置的每个按钮组合之前,我将 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 等),屏幕上都会出现新菜单。
每个按钮在任何特定时间都应根据系统状态执行某些特定操作。显然,如果您尝试根据大量不同变量来决定具体操作,您将创建大量分支代码。这样的代码很难正确编写,更难调试和维护。
那么,如果我们将每个可能状态(状态序列)的当前操作封装在某些特定的 class(接口)中会怎么样:
/// <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
现在我们将创建两个 TerminalPresenters - 一个只允许通过组合框选择下一个选项,一个计算两个数字的总和。它们都使用相同的基数 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();
}
}
然后我们在表单的构造函数中初始化它们:
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: 这些代码片段假设您有一个名为 MainForm 的表单,它有两个按钮 - button1、button2、一个组合框、两个文本框 - textBox_Input, textBox_Output.
P.S.2: 使用的模式足够接近 Model-View-Presenter, just without DataBindings.
P.S.3 如果您修改 APlusBPresenter
代码,您可以创建或多或少的通用状态机 Presenter。或者尝试塑造 ChainXxxx...
classes 和接口。
P.S.4: 很抱歉这些代码墙。对于 [SO] 格式来说,这可能太多了,所以我在 GitHub - https://github.com/Podskal/Whosebug_29870164.git 处放置了临时概念证明。它在很多方面都很丑陋,但就目前而言,它至少可以提供一些关于如何实现你自己的系统的想法。
P.S.5: 这段代码中有很多问题的地方,所以你应该非常仔细地考虑如何从中构建你自己的系统。
我正在尝试构建 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.CompanyA
- 2.CompanyB
- 3.CompanyC
这次用户选择 1.CompanyA 然后用户再次单击 btn1。然后 btn1 清除组合框并加载另一组菜单。这次菜单(在组合框上)有 2 个付款选项。
- 1.FullPayment
- 2.ParitalPayment
现在,如果用户单击 btn1 或 btn2,组合框可见变为 false,并且在主屏幕中有一个标签和文本框。文本框允许用户输入用户号码并按下回车键(绿色按钮)。
我已经将 Ingenico 终端图片加载为 jpeg,并在其顶部设置了我的按钮。 我只给出了我的模拟的小版本。在我的应用程序中,用户可以选择的概率为 114。
在我的应用程序中,btn1 被点击的概率为 92,btn2 被点击的概率为 53,依此类推。用户输入订阅者号码并单击绿色按钮后,我的应用程序使用 wfc 服务格式化数据并发送到 sql 服务器。 但是在用户单击我应用程序某个位置的每个按钮组合之前,我将 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 等),屏幕上都会出现新菜单。
每个按钮在任何特定时间都应根据系统状态执行某些特定操作。显然,如果您尝试根据大量不同变量来决定具体操作,您将创建大量分支代码。这样的代码很难正确编写,更难调试和维护。
那么,如果我们将每个可能状态(状态序列)的当前操作封装在某些特定的 class(接口)中会怎么样:
/// <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
现在我们将创建两个 TerminalPresenters - 一个只允许通过组合框选择下一个选项,一个计算两个数字的总和。它们都使用相同的基数 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();
}
}
然后我们在表单的构造函数中初始化它们:
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: 这些代码片段假设您有一个名为 MainForm 的表单,它有两个按钮 - button1、button2、一个组合框、两个文本框 - textBox_Input, textBox_Output.
P.S.2: 使用的模式足够接近 Model-View-Presenter, just without DataBindings.
P.S.3 如果您修改 APlusBPresenter
代码,您可以创建或多或少的通用状态机 Presenter。或者尝试塑造 ChainXxxx...
classes 和接口。
P.S.4: 很抱歉这些代码墙。对于 [SO] 格式来说,这可能太多了,所以我在 GitHub - https://github.com/Podskal/Whosebug_29870164.git 处放置了临时概念证明。它在很多方面都很丑陋,但就目前而言,它至少可以提供一些关于如何实现你自己的系统的想法。
P.S.5: 这段代码中有很多问题的地方,所以你应该非常仔细地考虑如何从中构建你自己的系统。