Friday, September 16, 2011

MVP on WinForm

(1) Model ---Reference Data
Data Entry windows almost always need reference data to guide user input. A dropdown list filled with 50 US States is a perfect example. Naturally, we would want a simple type structure class and a complex type storage class to hold 50 states for databinding purposes:

public struct State
{
public State(int? id, string name)
{
_StateID = id;
_StateName = name;
}

private int? _StateID;
public int? StateID
{
get { return _StateID;}
set { _StateID = value; }
}

private string _StateName;
public string StateName
{
get { return _StateName; }
set { _StateName = value; }
}
}

public class States
{
private static List _StateList = new List();
static States()
{
_StateList.Add(new State(0, "MA"));
....

_StateList.Add(new State(null, "(empty)")); // add (empty) for user to de-select to
}
public static List StateList
{
get { return _StateList; }
}
}

(2) Model --- Entity Data
Suppose UI is designed to present User an address for View/Edit/Create, we then need an entity to represent Address:

public class Address
{
private int? _StateID;
public int? StateID
{
get { return _StateID; }
set { _StateID = value; }
}
}

Note Database normalization requires only saving StateID to database and avoid duplicating StateName.


(3) Presenter --- one way databinding to View
Upon View calling its single entry point, a Presenter will initialize View, Load Data from Model and Execute Business logic (e.g. show, update, add):

public interface IAddressView
{
void ShowReferenceData();
void ShowCurrentAddress();
}

class AddressPresenter
{
private IAddressView _view;
public AddressPresenter(IAddressView view)
{
_view = view;
}
public void Render(int? UserID)
{
LoadEntityData(UserID);
_view.ShowReferenceData();
_view.ShowCurrentAddress();
}

private ASC.ReferenceData.State _State;
public ASC.ReferenceData.State State
{
get { return _State;}
set { _State=value;}
}

private ASC.Entity.Address _Address;
private void LoadEntityData(int? UserID)
{
// simulate retrival of Address from Database
_Address = new ASC.Entity.Address();
if (UserID >0) _Address.StateID = 3;
// All UserID <=0 will have empty selection of state in its Address Entity;

foreach (ASC.ReferenceData.State s in ASC.ReferenceData.States.StateList)
{
if (s.StateID == _Address.StateID)
{
_State = new ASC.ReferenceData.State(_Address.StateID, s.StateName);
break;
}
}

}
public void Update()
{
// Save Entity to Database
}
public void Add()
{
// Insert Entity to Database
}
}
Note that two properties in Address Entity Data are represented as a single property of type ASC.ReferenceData.State. This will enable loading into a ComboBox for ease of databinding.


(4) View --- Implement IView and Reverse DataBind
As before, View need to implement IView with consideration of reference data
public partial class AddressChangeForm : Form, IAddressView
{
public void ShowReferenceData()
{
this.cbState.DataSource = ASC.ReferenceData.States.StateList;
this.cbState.DisplayMember = "StateName";
}

public void ShowCurrentAddress()
{
cbState.DataBindings.Add("SelectedItem", _Presenter, "State",false,DataSourceUpdateMode.OnPropertyChanged, new ASC.ReferenceData.State(null,"(empty)"));
}
Note that Reference data are push to View using one-way databinding, while Presenter data are push to View with change probagated backwards through OnPropertyChanged.
Presenter entry point will be called upon user action:
private void btnGetCurrent_Click(object sender, EventArgs e)
{
cbState.DataBindings.Clear();
_Presenter = new AddressPresenter(this);
_Presenter.Render(1078);
}

And a new presenter need to be created upon User Add New action:
private void btnAdd_Click(object sender, EventArgs e)
{
.....
cbState.DataBindings.Clear();
_Presenter = new AddressPresenter(this);
_Presenter.Render(-1);
......
_Presenter.Add();
cbState.DataBindings.Clear();
.....
}

Friday, September 2, 2011

WinForm Google Suggest AutoComplete



Initiated from BB Quick Access Button
dataGridViewBC.ReadOnly = false;
dataGridViewBC.Columns[1].Visible = false;
this.toolStripStatusLabel1.Text = "";
this.textBoxSecurity.Text = "";
string BBSymbol = ((Button)sender).Text;
BBSymbol = (BBSymbol.Length == 1) ? BBSymbol.PadRight(2, ' ') : BBSymbol;
using (SqlConnection cn = new SqlConnection(connString))
{
cn.Open();
SqlCommand cmd = new SqlCommand(@"
select distinct
substring(ext_sec_id,1,len(ext_sec_id)-2),
substring(ticker,1,len(ticker)-2),
sec_name from csm_security where sec_typ_cd='Fut' and ticker like '" + BBSymbol + "__'", cn);
SqlDataReader r = cmd.ExecuteReader();
AutoCompleteStringCollection col = new AutoCompleteStringCollection();

string s = "";
while (r.Read())
{
s = " " + r.GetValue(0).ToString() + " " + r.GetValue(1).ToString() + " " + r.GetValue(2).ToString();
col.Add(s);
}


this.textBoxSecurity.AutoCompleteCustomSource = col;
this.textBoxSecurity.Focus();

if (col.Count == 1)
{
this.textBoxSecurity.Text = s;
FillGridView();
SetSelectedValueForAllComboEdit();
}
if (col.Count >= 2) SendKeys.Send(" ");


Initiated from Search Button

private void btnSearch_Click(object sender, EventArgs e)
{
dataGridViewBC.ReadOnly = false;
dataGridViewBC.Columns[1].Visible = false;
this.toolStripStatusLabel1.Text = "";
if (this.textBoxSecurity.Text.Length <= 1) return;

if (this.btnSearch.Text == "Get Rates" && this.textBoxSecurity.Text.Trim() != "No Match")
{
FillGridView();
SetSelectedValueForAllComboEdit();
}
if (this.btnSearch.Text == "Search")
{
List list = FindMatch(_SecurityList, this.textBoxSecurity.Text.Trim());
_MatchedCollection = new AutoCompleteStringCollection();
foreach (string s in list)
{
_MatchedCollection.Add(s);
}
if (_MatchedCollection.Count == 0) _MatchedCollection.Add(" No Match");
this.textBoxSecurity.AutoCompleteCustomSource = _MatchedCollection;
this.textBoxSecurity.Focus();
this.textBoxSecurity.Clear();
SendKeys.Send(" ");
this.btnSearch.Text = "Get Rates";

}