Custom MenuItemPanel (How add Items on Design Time?) - c#

The problem is as follows:
In my User Control i have Label List (List ) which fills adding Labels which UC through a "Smart Tag" (using a DesignerActionMethodItem).
The problem is that it works correctly when we are in design time, for example, I add 3 items at design time, but when I test the application these items disappear as if they had never added.
P.S.:
I have:
MyControl class,
[Designer(typeof(MenuItemPanelDesigner ))]
public partial class MenuItemPanel : UserControl
{
private List<Label> _listaItems;
public MenuItemPanel()
{
InitializeComponent();
}
public List<Label> ListaItems
{
get
{
if (this._listaItems == null)
{
this._listaItems = new List<Label>();
}
return this._listaItems;
}
}
public void AgregarItem()
{
Label nuevoItem = new Label();
nuevoItem.Text = "Item " + this._listaItems.Count;
nuevoItem.AutoSize = false;
nuevoItem.TextAlign = ContentAlignment.MiddleCenter;
nuevoItem.Cursor = Cursors.Hand;
this.ListaItems.Add(nuevoItem);
this.Controls.Add(nuevoItem);
nuevoItem.Dock = DockStyle.Top;
nuevoItem.Height = 50;
}
}
MyControlDesigner class
class MenuItemPanelDesigner : System.Windows.Forms.Design.ControlDesigner
{
private DesignerActionListCollection actionLists;
public override DesignerActionListCollection ActionLists
{
get
{
if (null == actionLists)
{
actionLists = new DesignerActionListCollection();
actionLists.Add(new MenuItemPanelDesignerActionList(this.Component));
}
return actionLists;
}
}
}
and MyControlDesignerActionList
class MenuItemPanelDesignerActionList : DesignerActionList
{
private MenuItemPanel colUserControl;
private DesignerActionUIService designerActionUISvc = null;
//The constructor associates the control with the smart tag list.
public MenuItemPanelDesignerActionList(IComponent component) : base(component)
{
this.colUserControl = (MenuItemPanel)component;
this.designerActionUISvc = (DesignerActionUIService)GetService(typeof(DesignerActionUIService));
}
// Implementation of this abstract method creates smart tag items,
// associates their targets, and collects into list.
public override DesignerActionItemCollection GetSortedActionItems()
{
DesignerActionItemCollection items = new DesignerActionItemCollection();
//Define static section header entries.
items.Add(new DesignerActionHeaderItem("Items"));
items.Add(new DesignerActionMethodItem(this,"AgregarItem","Agregar Item"));
return items;
}
// Metodos
public void AgregarItem()
{
this.colUserControl.AgregarItem();
}
}

Related

Android - Check custom listView items on the start of the App

I have a custom Listview, each row contains one textviewand one checkbox. I am saving the value (or the text) of the selected row's textview in a public list named usercoin. Each time the user opens the app, the list usercoin will contain the text of the his textview selected items, and I am doing that using SQLite. The problem is I want to re-check the items which the usaer have previously selected which are available in the usercoin list. I am not able to do so.
MyActivity.cs
ListView mListView;
MyAdapter adapter;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.activity_main);
mListView = FindViewById<ListView>(Resource.Id.listview);
List<TableList> list = new List<TableList>();
list.Add(new TableList("Germany",false));
list.Add(new TableList("France", false));
list.Add(new TableList("Finland", false));
list.Add(new TableList("Germany", false));
list.Add(new TableList("France", false));
list.Add(new TableList("Germany", false));
list.Add(new TableList("France", false));
list.Add(new TableList("Finland", false));
adapter = new MyAdapter(this, list);
mListView.Adapter = adapter;
mListView.ItemClick += MListView_ItemClick;
}
private void MListView_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
var t = list[e.Position];
string selected = t.name;
var ll = e.View as LinearLayout;
var cb = ll.GetChildAt(2) as CheckBox;
if (cb.Checked)
{
cb.Checked = false;
adapter.changeState((int)cb.Tag, false);
}
else
{
cb.Checked = true;
adapter.changeState((int)cb.Tag, true);
}
}
class MyAdapter : BaseAdapter
{
Context mContext;
List<TableList> mitems;
public MyAdapter(Context context, List<TableList> list)
{
this.mContext = context;
this.mitems = list;
}
public override int Count
{
get
{
return mitems.Count;
}
}
public override Java.Lang.Object GetItem(int position)
{
return mitems[position];
}
public override long GetItemId(int position)
{
return position;
}
public override View GetView(int position, View convertView, ViewGroup parent)
{
DataViewHolder holder = null;
if (convertView == null)
{
convertView = LayoutInflater.From(mContext).Inflate(Resource.Layout.CoinList, null, false);
holder = new DataViewHolder();
holder.tv = convertView.FindViewById<TextView>(Resource.Id.CoinName);
holder.iv = convertView.FindViewById<ImageView>(Resource.Id.imageView1);
holder.cb = convertView.FindViewById<CheckBox>(Resource.Id.checkBox1);
convertView.Tag = holder;
}
else
{
holder = convertView.Tag as DataViewHolder;
}
holder.cb.Tag = position;
holder.tv.Text = mitems[position].Name;
holder.cb.Focusable = false;
holder.cb.Checked = mitems[position].bl;
holder.iv.SetImageResource(Resource.Drawable.dapao);
holder.cb.CheckedChange += Cb_CheckedChange;
return convertView;
}
private void Cb_CheckedChange(object sender, CompoundButton.CheckedChangeEventArgs e)
{
var cb = sender as CheckBox;
if (e.IsChecked && !mitems[(int)cb.Tag].bl)
{
mitems[(int)cb.Tag].bl = true;
this.NotifyDataSetChanged();
}
else if (!e.IsChecked && mitems[(int)cb.Tag].bl)
{
mitems[(int)cb.Tag].bl = false;
this.NotifyDataSetChanged();
}
}
internal void changeState(int tag, bool v)
{
mitems[tag].bl = v;
this.NotifyDataSetChanged();
}
}
public class DataViewHolder : Java.Lang.Object
{
public ImageView iv { get; set; }
public TextView tv { get; set; }
public CheckBox cb { get; set; }
}
public class TableList : Java.Lang.Object
{
private string v;
public TableList(string name, bool b)
{
this.Name = name;
this.bl = b;
}
public string Name { get; set; }
public bool bl { get; set; }
}
}
}
For example, when the user run the app and select France and Germany from the listview, next time he opens the app, the usercoin list will contain France and Germany. Now the question is how can I check the checkboxes corresponding to those values in the listview. I have tried to do so by including this code in MyAdapter : BaseAdapter class:
if (Class1.usercoin.Contains(item.CoinAbr))
{
Class1.adapter[(int)holder.cb.Tag].bl = true;
this.NotifyDataSetChanged();
}
But when this code get executed, the previously checked items are checked plus some other items which the user haven't checked previously are also checked. So how can I check the previously checked items in the Listview on the app start ? Please help me to find a solution.
I have stored the data(useritems) in both DataBase and memory.
If your app is killed by system or user, you can restore the data from DataBase.
If your app isn't killed by system or user, but user jump to other activity, when he back to this activity, you can use memory to restore the data.
About DataBase, I am use SQLite.Net. I am using DBHelper to operation the DataBase.
And I have add Application class in the app.
I have update the demo. Here is gif.

How to access method from view inside a Xamarin Forms custom renderer?

I have the following code:
public partial class PhrasesFrameRendererClass : Frame
{
.....
void getRandomWords() {
// more code here that involves getting random numbers
// and updating a grid's bindingcontext
}
}
In my custom renderer I want to be able to call the getRandomWords on swipe left gesture like below:
public class PhraseFrameCustomRenderer : FrameRenderer
{
UISwipeGestureRecognizer leftSwipeGestureRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
// Call getRandomWords() here
});
}
}
Is this possible? Any ideas on how this could be done?
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
// Call getRandomWords() here
var frame = Element as PhrasesFrameRendererClass ;
if(frame!=null){
frame.getRandomWords();
}
});
You can create a BindableProperty of type Command in your custom frame class, call that Command from your renderer and bind your ViewModel's getRandomWords method as a Command
//Your custom control in your PCL project
public partial class PhrasesFrameRendererClass : Frame
{
public static readonly BindableProperty SwipeLeftCommandProperty =
BindableProperty.Create(nameof(SwipeLeftCommand), typeof(ICommand), typeof(PhrasesFrameRendererClass ), null);
public ICommand SwipeLeftCommand
{
get { return (ICommand)GetValue(CommandProperty); }
set { SetValue(CommandProperty, value); }
}
}
//Your custom control renderer
public class PhraseFrameCustomRenderer : FrameRenderer
{
UISwipeGestureRecognizer leftSwipeGestureRecognizer;
protected override void OnElementChanged(ElementChangedEventArgs<Frame> e)
{
base.OnElementChanged(e);
leftSwipeGestureRecognizer = new UISwipeGestureRecognizer();
leftSwipeGestureRecognizer.Direction = UISwipeGestureRecognizerDirection.Left;
leftSwipeGestureRecognizer.NumberOfTouchesRequired = 1;
leftSwipeGestureRecognizer.AddTarget((obj) =>
{
var myFrame = Element as PhrasesFrameRendererClassl
if(myFrame != null){
if(myFrame.SwipeLeftCommand != null && myFrame.SwipeLeftCommand.CanExecute()){
myFrame.SwipeLeftCommand.Execute();
}
}
});
}
}
//Your ViewModel
public class PhrasesViewModel{
public Command GetRandomWordsCommand {get;set;}
public PhrasesViewModel(){
GetRandomWordsCommand = new Command(ExecuteGetRandomWords);
}
private void ExecuteGetRandomWords(){
//Your method goes here
}
}
//Your XAML
<yourControls:PhrasesFrameRendererClass SwipeLeftCommand="{Binding GetRandomWordsCommand }"/>
It may seem more complicated this way, but using commands allows you to separate your application code (Such as getting random phrases) from your rendering code

C# Calling a main form method via other class

I have this project where i contain all my panel instances in my main form.
PanelA aPanelA = new PanelA;
PanelB aPanelB = new PanelB;
This is the form where the program.cs load when it starts.
Because i would like to have a centralize place for each panel calling one another method within them hence i declare them in main. In main, i also make some methods doing certain function in these panel since they are declare in main.
void setPanelA (int iNumber){...}
void setPanelB (string strString){...}
The problem is how would a widget in PanelA call the method setPanelB() via main?
Main.setPanelB("Hello World);
I know i can declare PanelA and PanelB as static. But is this the only way to do it? Because if i declare static to both Panel, i will need to declare some instances within Panel as static too..
I also do not wish to declare PanelA in PanelB or via versa because i could have many type of panels and this would make my code very untidy.
*Edited I had add a sample code here
namespace TestPanel
{
public partial class Form1 : Form
{
PanelA aPanelA = new PanelA();
PanelB aPanelB = new PanelB();
//IT IS POSSIBLE TO HAVE TENS OF DIFFERENT TYPE OF PANEL
public Form1()
{
InitializeComponent();
}
//CENTRAL LOCATION WHERE ALL PANEL COULD CALL EACH OTHER METHOD
public void setPanelACentral(int iNew)
{
aPanelA.setPanelA(iNew);
}
public void setPanelBCentral(string strNew)
{
aPanelB.setPanelB(strNew);
}
}
public class PanelA
{
int i = 0;
public void setPanelA(int iNew)
{
i = iNew;
}
}
public class PanelB
{
string str = "";
public void setPanelB(string strNew)
{
str = strNew;
}
//PROBLEM HERE HOW TO ACCESS MAIN setPanelACentral
public void changePanelA()
{
int i = 1000;
Form1.setPanelACentral(i); //<- This the part where i am asking
}
}
}
The following code demonstrates adding Events to both your Panel types and Form1. By doing this, you can raise an event in your Panel that Form1 will have registered to handle.
public partial class Form1 : Form
{
protected EventHandler<PanelEventArg> OnSetPanelA = new EventHandler<PanelEventArg>((sender, e) => { }); //stub
protected EventHandler<PanelEventArg> OnSetPanelB = new EventHandler<PanelEventArg>((sender, e) => { }); //stub
protected List<PanelBase> panels;
public Form1() : base()
{
panels = new List<PanelBase>
{
new PanelA(),
new PanelB()
};
foreach (var panel in panels)
{
OnSetPanelA += panel.OnSetPanel;
OnSetPanelB += panel.OnSetPanel;
panel.OnSomeEvent += Form1_OnSomeEvent;
}
foreach (var panel in panels.OfType<PanelB>())
{
panel.OnChangePanelA += Form1_OnChangePanelA;
}
InitializeComponent();
}
protected void SetPanelA(int iNew)
{
foreach (var panel in panels.OfType<PanelA>())
{
panel.SetPanelA(iNew);
OnSetPanelA(this, new PanelEventArg
{
Panel = panel
});
}
}
protected void SetPanelB(string strNew)
{
foreach (var panel in panels.OfType<PanelB>())
{
panel.SetPanelB(strNew);
OnSetPanelA(this, new PanelEventArg
{
Panel = panel
});
}
}
protected void Form1_OnSomeEvent(object sender, EventArgs e)
{
// handles events raised by the panel.
}
protected void Form1_OnChangePanelA(object sender, int iNew)
{
SetPanelA(iNew);
}
}
Helper Types I'm including: PanelEventArg, PanelBase
public class PanelEventArg : EventArgs
{
public PanelBase Panel { get; set; }
}
public class PanelBase //: Panel
{
public EventHandler OnSomeEvent = new EventHandler((sender, e) => { }); //stub;
public void OnSetPanel(object sender, PanelEventArg e)
{
if (!Equals(e.Panel, this))
{
//the panel being set is not this panel instance
}
}
}
Declaring PanelA and PanelB, with inheritance and new Event for PanelB
public class PanelA : PanelBase
{
int i = 0;
public void SetPanelA(int iNew)
{
i = iNew;
}
}
public class PanelB : PanelBase
{
public EventHandler<int> OnChangePanelA = new EventHandler<int>((sender, e) => { }); //stub
string str = "";
public void SetPanelB(string strNew)
{
str = strNew;
}
//PROBLEM HERE HOW TO ACCESS MAIN setPanelACentral
public void ChangePanelA()
{
OnChangePanelA(this, 1000);
}
}

WPF/C# entirely programmatically binding an array of objects to a static ObservableCollection

Please assume this entire question deals in code, without any XAML.
I have a static ObservableCollection named myStaticList. It's a part of a non-static class named myClass.
public class myClass
{
public static ObservableCollection<CheckBoxStructure> myStaticList { get; set; }
static myClass()
{
myStaticList = new ObservableCollection<CheckBoxStructure>();
}
}
And the definition of CheckBoxStructure:
public class CheckBoxStructure
{
public string Description { get; set; }
public bool IsSelected { get; set; }
}
In addition, there's an array of checkboxes called checkBoxArray[], holding 3 elements. each checkbox has as content a textbox.
What I want to do is programmatically bind (two-way) these two, in such a manner that the IsChecked property of the checkboxes in the checkBoxArray[] array will bind to the IsSelected property of the myStaticList's CheckBoxStructure, and similarly so between the text of the textboxes inthe checkboxes' content and the Description property of the myStaticList's CheckBoxStructure.
In addition, I would like to avoid using loops, since it is preferable that this two lists will update each other if they change in size.
How is this possible?
Thanks!
Using XAML, an easy way would be to the declare an ItemsControl and a DataTemplate for it so that you can have a UserControl (CheckBox and TextBox inside) with its DataContext being a CheckBoxStructure. This way the bindings work between CheckBox.IsChecked and IsSelected property and between TextBox.Text and Description property.
If you need to this only in code then you would have to create same behavior (ItemsControl with a DataTemplate). You have at least 2 options
1.
DataTemplate template = new DataTemplate();
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));
template.VisualTree = factory;
FrameworkElementFactory childFactory = new FrameworkElementFactory(typeof(CheckBox));
childFactory.SetBinding(CheckBox.IsChecked, new Binding("IsSelected"));
factory.AppendChild(childFactory);
childFactory = new FrameworkElementFactory(typeof(TextBox));
childFactory.SetBinding(Label.ContentProperty, new Binding("Description"));
factory.AppendChild(childFactory);
2.
MemoryStream sr = null;
ParserContext pc = null;
string xaml = string.Empty;
xaml = "<DataTemplate><StackPanel><TextBlock Text="{Binding Description"/><CheckBox IsChecked="{Binding IsSelected"/></StackPanel></DataTemplate>";
sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));
pc = new ParserContext();
pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
DataTemplate datatemplate = (DataTemplate)XamlReader.Load(sr, pc);
this.Resources.Add("dt", datatemplate);
Later edit, after discussion from comments; this example works only one way of binding but is easily to make it two ways. Please note that this is only a trivial example of a concept and is not complete: you need to modify the list classes to suit how you wish for objects to be paired, you may need to add more guards for corner cases, you may need to make it thread safe and so on...
First the basic binding objects:
class Binder
{
public Binder()
{
_bindings = new Dictionary<string, List<string>>();
}
private INotifyPropertyChanged _dataContext;
public INotifyPropertyChanged DataContext
{
get { return _dataContext; }
set
{
if (_dataContext != null)
{
_dataContext.PropertyChanged -= _dataContext_PropertyChanged;
}
_dataContext = value;
_dataContext.PropertyChanged += _dataContext_PropertyChanged;
}
}
void _dataContext_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_bindings.ContainsKey(e.PropertyName))
{
var bindableType = _dataContext.GetType();
var bindableProp = bindableType.GetProperty(e.PropertyName);
if (bindableProp == null)
{
return;
}
var binderType = this.GetType();
foreach (var binderPropName in _bindings[e.PropertyName])
{
var binderProp = binderType.GetProperty(binderPropName);
if (binderProp == null)
{
continue;
}
var value = bindableProp.GetValue(_dataContext);
binderProp.SetValue(this, value);
}
}
}
Dictionary<string, List<string>> _bindings;
public void AddBinding(string binderPropertyName, string bindablePropertyName)
{
if (!_bindings.ContainsKey(bindablePropertyName))
{
_bindings.Add(bindablePropertyName, new List<string>());
}
_bindings[bindablePropertyName].Add(bindablePropertyName);
}
}
class Bindable : INotifyPropertyChanged
{
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then the holding lists for them:
class BindableList<T> : List<T> where T : Bindable
{
public event Action<T> ItemAdded;
public new void Add(T item)
{
base.Add(item);
NotifyItemAdded(item);
}
private void NotifyItemAdded(T item)
{
if (ItemAdded != null)
{
ItemAdded(item);
}
}
}
class BinderList<T> : List<T> where T : Binder
{
public BinderList()
{
_bindingRules = new Dictionary<string, string>();
}
private BindableList<Bindable> _dataContextList;
public BindableList<Bindable> DataContextList
{
get { return _dataContextList; }
set
{
if (_dataContextList != null)
{
_dataContextList.ItemAdded -= _dataContextList_ItemAdded;
}
_dataContextList = value;
_dataContextList.ItemAdded += _dataContextList_ItemAdded;
}
}
void _dataContextList_ItemAdded(Bindable obj)
{
foreach (var pair in _bindingRules)
{
this[Count-1].AddBinding(pair.Key, pair.Value);
this[Count - 1].DataContext = obj;
}
}
private Dictionary<string, string> _bindingRules;
public void AddBindingRule(string binderPropertyName, string bindablePropertyName)
{
_bindingRules.Add(binderPropertyName, bindablePropertyName);
}
}
Now the actual classes with properties:
class BinderElement : Binder
{
private string _description;
public string Description
{
get { return _description; }
set { _description = value; }
}
}
class BindableElement : Bindable
{
private string _description;
public string Description
{
get
{
return _description;
}
set
{
_description = value;
NotifyPropertyChanged("Description");
}
}
}
And an example to use them:
static void Main(string[] args)
{
var bindableList = new BindableList<Bindable>();
var binderList = new BinderList<BinderElement>()
{
new BinderElement(),
new BinderElement()
};
binderList.DataContextList = bindableList;
binderList.AddBindingRule("Description", "Description");
bindableList.Add(new BindableElement());
bindableList.Add(new BindableElement());
((BindableElement)bindableList[1]).Description = "This should arrive in BinderElement Description property";
Console.WriteLine(binderList[1].Description);
Console.ReadLine();
}

Handle control events differently depending on the state of the system

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

Categories

Resources