using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using TradeIdeas.TIProData; namespace WindowsFormsApplication1 { /// /// TopLIstForm class. Allows user to get top list data /// upon entering a TopList configuration string. /// public partial class TopListForm : Form { private BindingList _boundList = new BindingList(); /// /// TopListForm Constructor /// public TopListForm() { InitializeComponent(); /* Setting the data source allows us to use any type of object we * want to store the row data. If we added the row directly using * dataGridView1.Rows.Add(myRow), myRow would have to be an array * of objects. That means we'd have to parse and format the data * as we were adding it to the data. That's not completely /* unreasonble, but I'd rather keep it the way it is. */ dataGridView1.DataSource = _boundList; } TopList _topList; private void startButton_Click(object sender, EventArgs e) { if (null != _topList) _topList.Stop(); _topList = Form1.Connection.TopListManager.GetTopList(configTextBox.Text); _topList.TopListStatus += new TopListStatus(_topList_TopListStatus); _topList.TopListData += new TopListData(_topList_TopListData); _topList.Start(); stopButton.Enabled = true; resultsTextBox.AppendText("Start: " + DateTime.Now + "\r\n"); } void _topList_TopListStatus(TopList sender) { if (InvokeRequired) BeginInvoke((MethodInvoker)delegate { _topList_TopListStatus(sender); }); else { if (sender != _topList) resultsTextBox.AppendText("Ignoring TopListStatus.\r\n"); else { // As Text resultsTextBox.AppendText(_topList.TopListInfo.ToString() + "\r\n"); // As Table dataGridView1.Columns.Clear(); dataGridView1.Rows.Clear(); dataGridView1.Columns.Add(DataCell.GetSymbolColumn()); foreach (ColumnInfo columnInfo in _topList.TopListInfo.Columns) dataGridView1.Columns.Add(DataCell.GetColumn(columnInfo)); } } } private int rowCount; private int messageCount; void _topList_TopListData(List rows, DateTime? start, DateTime? end, TopList sender) { if (InvokeRequired) BeginInvoke((MethodInvoker)delegate { _topList_TopListData(rows, start, end, sender); }); else { if (sender != _topList) resultsTextBox.AppendText("Ignoring TopListData.\r\n"); else { // As Text resultsTextBox.AppendText("TopListData " + DateTime.Now + "\r\n"); if (null != start) resultsTextBox.AppendText("Start Time = " + start + "\r\n"); if (null != end) resultsTextBox.AppendText("End Time = " + end + "\r\n"); rowCount += rows.Count; rowCountLabel.Text = "Row Count: " + rowCount; messageCount++; messageCountLabel.Text = "Message Count: " + messageCount; resultsTextBox.AppendText("Received " + rows.Count + " rows.\r\n"); foreach (RowData row in rows) { resultsTextBox.AppendText(row.ToString()); resultsTextBox.AppendText("\r\n"); } // As Array _boundList.Clear(); foreach (RowData row in rows) { _boundList.Add(row); } } } } private void stopButton_Click(object sender, EventArgs e) { _topList.Stop(); _topList = null; stopButton.Enabled = false; } private void TopListForm_FormClosed(object sender, FormClosedEventArgs e) { if (null != _topList) _topList.Stop(); } } // This is good at doing the basic formatting. Excellent proof of // concept. But it needs a lot of cleanup before the user sees it. // At a bare minimum: // 1) Row headers disappear (or replaced by a number (1, 2, 3, etc.)) // 2) Do not jump to the top of the list every time. (TIQ had some // smart rules. At a minimum just never jump.) // 3) Do not allow the user to resize a row. // 4) Replace the title with an icon, when appropriate. (Keep the // tooltip.) We could release without this, but I really don't // want to! // 5) Fix the way we display numbers that are too big. Display an // elipsis to replace the entire number, like TI Pro does with // prices. The current style is probably okay for the symbol // column. The description column will be more complicated, and // probably use word wrap, although we don't need to deal with // that today. // Probably more. Make it look good. // NOTE: A newer version of this can be found in the SimpleTIPro // project. class DataCell : DataGridViewTextBoxCell { private enum Mode { Price, Number, String }; private Mode _mode; private string _format; private string _internalCode; private string _wireName; /// /// This is required! If you don't have a constructor with no arguments, /// the code will fail at runtime with a confusing error message. /// /// Ideally _mode, _format, etc. would all be readonly. But we need to /// be able to use Clone(). base.Clone() calls this constructor, then /// our version of Close() updates these member variables. /// public DataCell() { } public DataCell(string wireName) { _wireName = wireName; _mode = Mode.String; } public DataCell(ColumnInfo columnInfo) { // A reasonable default. _mode = Mode.String; if (columnInfo.Format == "p") _mode = Mode.Price; else { int digits; if (Int32.TryParse(columnInfo.Format, out digits)) { if (digits > 7) digits = 7; else if (digits < 0) digits = 0; _mode = Mode.Number; _format = "N" + digits; } } _internalCode = columnInfo.InternalCode; // This belongs somewhere else! _wireName = columnInfo.WireName; } public DataGridViewContentAlignment Alignment() { if (_mode == Mode.String) return DataGridViewContentAlignment.MiddleLeft; else return DataGridViewContentAlignment.MiddleRight; } protected override object GetFormattedValue(object value, int rowIndex, ref DataGridViewCellStyle cellStyle, TypeConverter valueTypeConverter, TypeConverter formattedValueTypeConverter, DataGridViewDataErrorContexts context) { RowData rowData = ((BindingList)DataGridView.DataSource)[rowIndex]; //return "(" + _wireName + ", " + rowIndex + ")"; // I was planning to cache this next part. But it seems like there's so // much work just getting to here, and then more work rendering the string // on the screen, so this one conversion might not mean much! String cellData = rowData.GetAsString(_wireName); if (_mode == Mode.String) return cellData; // Convert the string we received from the server into a double, then format // it in the appropriate way. It was tempting to have the server do the // formatting for us. But then we might lose precision if we wanted to do // anything else with the value. For example, sorting should work on all the // digits, not just the visible ones. And maybe the user wants to change the // number of digits to display. double cellAsDouble; if (!Double.TryParse(cellData, out cellAsDouble)) return ""; if (_mode == Mode.Number) return cellAsDouble.ToString(_format); else // Mode.Price if (rowData.GetAsString("four_digits") == "1") return cellAsDouble.ToString("N4"); else return cellAsDouble.ToString("N2"); } public override object Clone() { DataCell result = (DataCell)base.Clone(); result._mode = _mode; result._format = _format; result._internalCode = _internalCode; result._wireName = _wireName; return result; } public static DataGridViewColumn GetColumn(String wireName) { return GetColumn(new DataCell(wireName)); } public static DataGridViewColumn GetColumn(ColumnInfo columnInfo) { DataGridViewColumn result = GetColumn(new DataCell(columnInfo)); result.HeaderText = columnInfo.Description + " (" + columnInfo.Units + ')'; // Ideally we'd have a size hint based on how the column would be used. But we don't // have that information here. Temporarily we're using the text description, but // that's a little silly, too. Eventually most columns should use an icon in the // header, and the description does not matter. //result.Width = 12 + TextRenderer.MeasureText(result.HeaderText, result.DataGridView.Font).Width; // Can't set the width here because result.DataGridView is null. result.ToolTipText = result.HeaderText; return result; } private static DataGridViewColumn GetColumn(DataCell dataCell) { DataGridViewTextBoxColumn result = new DataGridViewTextBoxColumn(); result.CellTemplate = dataCell; result.DefaultCellStyle = new DataGridViewCellStyle(); result.DefaultCellStyle.Alignment = dataCell.Alignment(); return result; } public static DataGridViewColumn GetSymbolColumn() { DataGridViewColumn result = GetColumn("symbol"); result.HeaderText = "Symbol"; // Need a better place to set the width. //result.Width = 12 + TextRenderer.MeasureText(result.HeaderText, result.DataGridView.Font).Width; // Can't set the width here because result.DataGridView is null. return result; } } //DataGridView.AutoSizeRowsMode Property could be useful for the description column in the alerts. }