using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Forms; using System.Xml; using TradeIdeas.MiscSupport; using TradeIdeas.TIProData; using TradeIdeas.TIProData.Configuration; using TradeIdeas.TIProData.Interfaces; using TradeIdeas.XML; namespace TradeIdeas.TIProGUI.EnhancedSingleStockWindow { /* This class is specially tailored for the Similar Tab of the Single Stock Window. I had taken the orginal * code of TopListForm.cs and removed lots of the "bells and whistles" to produce this class. I wanted to * keep the graphics of the rows (i.e. shading), but remove a most TopList like header menu items, frozen columns, color menus..etc * Thus this class is a very bare, basic skeleton of what a "true" TopList is (but with a few features unique to this class). Plus, this panel is essentially a Winforms control * that is being hosted within a special WPF host control in the Similar Tab... */ public class TopListPanel : CustomPanel, TopListRequest.Listener { public enum ViewMode { Competitor, PriceAction } public enum Sector { SUB_INDUSTRY, INDUSTRY, INDUSTRY_GROUP, SUB_SECTOR, SECTOR } private DataGridView _dgView = new DataGridView(); private BindingList _boundList = new BindingList(); IConnectionMaster _connectionMaster; private string _currentSector; private string _currentSymbol; private bool _validSymbolEntered = true; private bool _outsideMarketHours = false; // Sorting variables private ColumnInfo _selectedSortColumnInfo; private bool _needToRestoreSortBy = false; private List _serverSortedList = new List(); private bool isFrozen = false; private string _importSortBy; private bool _textHeaders = true; private bool _localSortEnabled = false; private TopListSortType _localSortType = TopListSortType.ServerSort; private string _serverSortField = ""; private ISendManager _sendManager; private RowData _currentRowData = null; private int _recordCount = 35; private Boolean _symbolLinkingEnabled = true; private TopListRequest.Token _requestToken; private string _defaultCompetitorConfig = "form=1&sort=MaxPV&count=35&MinPrice=1&MinVol5D=25000&X_NYSE=on&X_ARCA=on&X_AMEX=on&XN=on&X_OTC=on&X_PINK=on&WN=Single+Stock+Similars&show0=D_Symbol&show1=Price&show2=FCP&show3=RV&show4=EPS&show5=TV&show6=D_Name&show7=&omh=0&col_ver=1"; private string _defaultPriceActionConfig = "form=1&sort=MaxPV&count=35&MinPrice=1&MinVol5D=25000&X_NYSE=on&X_ARCA=on&X_AMEX=on&XN=on&X_OTC=on&X_PINK=on&WN=Single+Stock+Price+Action&show0=D_Symbol&show1=Price&show2=FCP&show3=RV&show4=TV&show5=RY&show6=D_Name&show7=&omh=0&col_ver=1"; private string _config = "form=1&sort=MaxPV&count=35&MinPrice=1&MinVol5D=25000&X_NYSE=on&X_ARCA=on&X_AMEX=on&XN=on&X_OTC=on&X_PINK=on&WN=Single+Stock+Similars&show0=D_Symbol&show1=Price&show2=FCP&show3=RV&show4=EPS&show5=TV&show6=D_Name&show7=&omh=0&col_ver=1"; private TopListInfo _metaData; private XmlNode _competitorNode; private XmlNode _priceActionNode; private IList _previousColumns; private Dictionary _originallyIcon = new Dictionary(); private GradientColorChooser _gradientColorChooser = new GradientColorChooser(); private ColumnListState _columnListState = new ColumnListState(); private ColumnListState _focusColumnListStateCompetitor = null;//used to maintain the columnliststate(for Similar Tab grid) when other tabs are clicked or engaging the toggle button private ColumnListState _focusColumnListStatePriceAction = null;//used to maintain the columnliststate(for Similar Tab grid) when other tabs are clicked or engaging the toggle button private Dictionary _columnColors = new Dictionary(); // For use in showing columns private const int MIN_COLUMN_WIDTH = 5; private const int DEFAULT_ROW_HEIGHT = 20; private ViewMode _currentViewMode = ViewMode.Competitor; private delegate void OnSubindustryDataReceived(string subIndustry); private event OnSubindustryDataReceived subIndustryDataReceivedEvent; //for outside market hours public delegate void MarketHoursSelectionChanged(bool isPrePostMarket); public event MarketHoursSelectionChanged onMarketHoursChangedEvent; //filters private Double _priceAction_RY = -1; // yearly position in range private Double _priceAction_RV = -1; // relative volume private Double _priceActon_FCP = -1; //change from the close percent private Double _priceAction_R10D = -1; // 10 day range private Double _priceAction_R3MO = -1; // 3 month range private Double _priceAction_R6MO = -1; // 6 month range //"raw" strings (trimmed) for subIndustry,Industry,IndustryGroup,and Subsector. We keep track of these each time we get raw data from WPF_SingleStoc.xaml.cs "onRowData" // this will aid if we have to call the server again if current sector query doesn't give us at least 10 rows. private string _rawSubIndustry = ""; private string _rawIndustry = ""; private string _rawIndGroup = ""; private string _rawSubsector = ""; private string _rawSector = ""; private Sector _currentSect = Sector.SUB_SECTOR; // Export copies of the GradientInfo objects used to display the table. public void GetColors(out GradientInfo main, out Dictionary columns) { main = _gradientColorChooser.GradientInfo.DeepCopy(); columns = new Dictionary(); foreach (var kvp in _columnColors) columns[kvp.Key] = kvp.Value; } public TopListPanel() { ExtensionMethods.DoubleBufferedDatagrid(_dgView, true); setDataGridDefaults(); List columnsList = GuiEnvironment.XmlConfig.Node("COMPETITORS").Node("COLUMNS"); if (columnsList.Count > 0) _competitorNode = columnsList.ElementAt(0); columnsList = GuiEnvironment.XmlConfig.Node("PRICE_ACTION").Node("COLUMNS"); if (columnsList.Count > 0) _priceActionNode = columnsList.ElementAt(0); Dock = DockStyle.Fill; _dgView.DataSource = _boundList; // make _dgView auto size - RVH20210616 //_dgView.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill; Controls.Add(_dgView); subIndustryDataReceivedEvent += TopListPanel_subIndustryReceivedEvent; onMarketHoursChangedEvent += TopListPanel_onMarketHoursChangedEvent; _dgView.CellMouseEnter += _dgView_CellMouseEnter; _dgView.CellMouseLeave += _dgView_CellMouseLeave; _connectionMaster = GuiEnvironment.FindConnectionMaster(""); } private void _dgView_CellMouseLeave(object sender, DataGridViewCellEventArgs e) { _currentRowData = null; } private void _dgView_CellMouseEnter(object sender, DataGridViewCellEventArgs e) { int selectedRow = e.RowIndex; if (selectedRow != -1) { _currentRowData = _boundList[selectedRow]; } } public void setSymbolLinking(bool symbolLinkingEnabled) { _symbolLinkingEnabled = symbolLinkingEnabled; } public String getConfig() { return _config; } public ViewMode getViewMode() { return _currentViewMode; } public void setValidSymbolStatus(bool value) { _validSymbolEntered = value; } public bool getValidSymbolStatus() { return _validSymbolEntered; } public void setRawSectorDataStrings(string rawSector, string rawSubsector, string rawIndGroup, string rawIndustry, string rawSubindustry) { _rawSubIndustry = rawSubindustry; _rawIndustry = rawIndustry; _rawIndGroup = rawIndGroup; _rawSubsector = rawSubsector; _rawSector = rawSector; } public void setCurrentSymbol(String symbol) //here's where we will create the new config string upon changin a symbol in Price Actin Mode { bool isSame = false; if (_currentSymbol != null && _currentSymbol == symbol) isSame = true; else isSame = false; _currentSymbol = symbol; if (_currentViewMode == ViewMode.PriceAction && !isSame) activatePriceAction(_priceAction_RY, _priceAction_RV, _priceActon_FCP, _priceAction_R10D, _priceAction_R3MO, _priceAction_R6MO); } public void setOutsideMarkeHours(bool value) { _outsideMarketHours = value; onMarketHoursChangedEvent(value); } private void TopListPanel_onMarketHoursChangedEvent(bool isPrePostMarket) { _config = EnhancedSingleStockWindow.putOutSideMarketHours(_config, isPrePostMarket); SetConfiguration(_config); } private void setDataGridDefaults() { _dgView.AutoGenerateColumns = false; _dgView.ReadOnly = true; _dgView.RowHeadersVisible = false; _dgView.AllowUserToResizeRows = false; _dgView.AllowUserToAddRows = false; _dgView.AllowUserToDeleteRows = false; _dgView.ColumnHeadersHeightSizeMode = DataGridViewColumnHeadersHeightSizeMode.AutoSize; _dgView.Dock = DockStyle.Fill; _dgView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells; _dgView.SelectionMode = DataGridViewSelectionMode.FullRowSelect; //full row select with this "toplist" version _dgView.ColumnWidthChanged += _dgView_ColumnWidthChanged; _dgView.CellDoubleClick += _dgView_CellDoubleClick; _dgView.CellClick += _dgView_CellClick; // Apply global data grid selection mode. chooseRowSelect(); } public void updateColumnListStateOnLostFocus() { if (_currentViewMode == ViewMode.Competitor) { if (_focusColumnListStateCompetitor == null) { _focusColumnListStateCompetitor = new ColumnListState(); _focusColumnListStateCompetitor.LoadFrom(_dgView); } else { _focusColumnListStateCompetitor.LoadFrom(_dgView); } } else { if (_focusColumnListStatePriceAction == null) { _focusColumnListStatePriceAction = new ColumnListState(); _focusColumnListStatePriceAction.LoadFrom(_dgView); } else { _focusColumnListStatePriceAction.LoadFrom(_dgView); } } } public void beginToplist() { if (_validSymbolEntered == false) return; //SimilarTabInitialColumns.xml is for initially setting the column widths of the TopListPanel grid. _boundList.Clear(); updateColumnListState(); _sendManager = _connectionMaster.SendManager; if (_currentViewMode == ViewMode.PriceAction) { activatePriceAction(_priceAction_RY, _priceAction_RV, _priceActon_FCP, _priceAction_R10D, _priceAction_R3MO, _priceAction_R6MO); } else { if (_currentSector == null) _config = _defaultCompetitorConfig; else _config = createCompetitorConfigString(_currentSector); _config = EnhancedSingleStockWindow.putOutSideMarketHours(_config, _outsideMarketHours); SetConfiguration(_config); } _gradientColorChooser.FieldInternalCode = "FCD"; // Up from close, in dollars. GradientInfo gradientInfo = new GradientInfo(); gradientInfo.Add(3, Color.LightGreen); gradientInfo.Add(-3, Color.Pink); bool lotsOfBlack = true; if (lotsOfBlack) { // Anything near 0 is very dark. gradientInfo.Add(1, Color.Green); gradientInfo.Add(0, Color.Black); gradientInfo.Add(-1, Color.Red); } else { // 0 is black. Even a very small distance below 0 turns red. gradientInfo.Add(0, Color.Red, Color.Black, Color.Green); } _gradientColorChooser.GradientInfo = gradientInfo; _gradientColorChooser.OnChange -= _gradientColorChooser_OnChange; _gradientColorChooser.OnChange += new MethodInvoker(_gradientColorChooser_OnChange); } public void setPriceActionNode(XmlNode node) { _priceActionNode = node; } public void setCompetitorNode(XmlNode node) { _competitorNode = node; } public void SetConfiguration(string config) { _metaData = null; if (null != _requestToken) { _requestToken.Cancel(); _requestToken = null; } if (null != config) { TopListRequest topListRequest = new TopListRequest(); topListRequest.Collaborate = config; topListRequest.SaveToMru = false; topListRequest.Streaming = true; topListRequest.SkipMetaData = false; topListRequest.AddStandardRowDataColumns(); if (!_gradientColorChooser.FieldInternalCode.Equals("")) topListRequest.AddExtraColumn(_gradientColorChooser.FieldInternalCode); topListRequest.AddExtraColumn("D_SubIndustry"); topListRequest.AddExtraColumn("RY"); topListRequest.AddExtraColumn("EPS"); topListRequest.ResultCount = _recordCount; if (_sendManager == null) _sendManager = GuiEnvironment.FindConnectionMaster("").SendManager; _requestToken = topListRequest.Send(_sendManager, this); } // Clear selection and scrollbar position when a new symbol is entered. _dgView.ClearSelection(); // While the Similar tab is selected the user could change the size of the // single stock window so that the _dgView is not being displayed or the window // is minimized: these scenarios can cause an unhandled exception. // We can not set the FirstDisplayedScrollingRowIndex when it is negative. if (_dgView.Rows.Count > 0 && _dgView.FirstDisplayedScrollingRowIndex >= 0) _dgView.FirstDisplayedScrollingRowIndex = 0; } private void _dgView_ColumnWidthChanged(object sender, DataGridViewColumnEventArgs e) { Column c = (Column)e.Column; string test = getSectorColumn(c); if (e.Column.Width <= MIN_COLUMN_WIDTH && c.ColumnInfo.InternalCode == test && test != "") { _dgView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None; foreach (DataGridViewRow row in _dgView.Rows) { row.Height = DEFAULT_ROW_HEIGHT; } } else if (c.ColumnInfo.InternalCode != test) { if (isColumnWidthAtMinimum()) { return; } } else { _dgView.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.AllCells; } } public static string getSectorColumn(Column c) //This method is used by both the TopList and Alert forms... { string flag = ""; String test = c.ColumnInfo.InternalCode; foreach (string col in TopListForm.sectorColumns) { if (test == col) { flag = col; break; } } return flag; } private bool isColumnWidthAtMinimum() { /*This little method retrieves the width status of a Sector column...whatever position it may be in the grid*/ foreach (DataGridViewColumn column in _dgView.Columns) { Column c = (Column)column; string test = TopListForm.getSectorColumn(c); if (c.ColumnInfo.InternalCode == test && column.Width <= MIN_COLUMN_WIDTH) { return true; } } return false; } void DoSorting() { List rows = _boundList.ToList(); _boundList.Clear(); foreach (RowData row in SortRows(rows, _selectedSortColumnInfo, _localSortType)) { _boundList.Add(row); } UpdateColors(); fixHeadersAfterSorting(); _dgView.ClearSelection(); } private void fixHeadersAfterSorting() // equal to Top List's "FixSortMenuItems" but without the menu items { DataGridViewColumnCollection columns = (DataGridViewColumnCollection)_dgView.Columns; if (_localSortType == TopListSortType.ServerSort) { foreach (DataGridViewColumn c in columns) { Column col = (Column)c; String sortableCode = col.ColumnInfo.InternalCode; // are we serversorting by this column? if (_serverSortField == sortableCode) FixCellHeader(sortableCode, TopListSortType.Ascending); else FixCellHeader(sortableCode, TopListSortType.ServerSort); } } else { foreach (DataGridViewColumn c in columns) { Column col = (Column)c; String sortableCode = col.ColumnInfo.InternalCode; if (_selectedSortColumnInfo != null && sortableCode == _selectedSortColumnInfo.InternalCode) { FixCellHeader(sortableCode, _localSortType); } else { // setting cell header to "ServerSort" means no local sorting on this column FixCellHeader(sortableCode, TopListSortType.ServerSort); } } } } //called when we use the toggle to change mode public void setSubindustryString(string sString) { if (sString == null) return; string result = sString.Trim(); _currentSector = result; if (_currentViewMode != ViewMode.Competitor) return; if (subIndustryDataReceivedEvent != null) { subIndustryDataReceivedEvent(result); string config = createCompetitorConfigString(result); if (config == "") _config = _defaultCompetitorConfig; else _config = config; _config = EnhancedSingleStockWindow.putOutSideMarketHours(_config, _outsideMarketHours); SetConfiguration(_config); } } //called when changing symbol in text box public void onNewDataForCompetitorSymbol(string sString, Sector sctr) { String result = sString.Trim(); if (_currentSector != null && (_currentSector == sString && _currentSect == sctr)) return; _currentSect = sctr; _currentSector = result; if (_currentViewMode != ViewMode.Competitor) return; String config = createCompetitorConfigString(result); if (config == "") _config = _defaultCompetitorConfig; else _config = config; _config = EnhancedSingleStockWindow.putOutSideMarketHours(_config, _outsideMarketHours); SetConfiguration(_config); } private String createCompetitorConfigString(string subInd) { String rplc = subInd.Replace(" ", "+"); string a = "form=1&sort=MaxPV&count=35&MinPrice=1&MinVol5D=25000&X_NYSE=on&X_ARCA=on&X_AMEX=on&XN=on&X_OTC=on&WN=" + rplc;// We replace space with '+' character "Single + Stock+Competitors"; string b = "=on&X_PINK=on&show0=D_Symbol&show1=Price&show2=FCP&show3=RV&show4=EPS&show5=TV&show6=D_Name&show7=&omh=0&col_ver=1"; string code = ""; try { foreach (SymbolList sL in EnhancedSingleStockWindow._listsForSingleStock.ToList()) { if (sL.ListName == subInd) { code = sL.InternalCode; break; } } } catch { } if (code == "") return ""; else { string result = "&" + code; return a + result + b; } } public void setPriceActionData(Double ry, Double rv, Double fcp, Double r10D, Double r3mo, Double r6mo) { _priceAction_RY = ry; _priceAction_RV = rv; _priceActon_FCP = fcp; _priceAction_R10D = r10D; _priceAction_R3MO = r3mo; _priceAction_R6MO = r6mo; } private String createPriceActionConfigString(Double ry, Double rv, Double fcp, Double r10D, Double r3mo, Double r6mo) //values of stock in question { string first = "form=1&sort=MaxPV&count=35&MinPrice=1&MinVol5D=25000"; string last = "&X_NYSE=on&X_ARCA=on&X_AMEX=on&XN=on&X_OTC=on&X_PINK=on&WN=Single+Stock+Price+Action&show0=D_Symbol&show1=Price&show2=FCP&show3=RV&show4=TV&show5=RY&show6=D_Name&show7=&omh=0&col_ver=1"; if (ry == -1 || rv == -1 || fcp == Double.NaN || r10D == -1 || r3mo == -1 || r6mo == -1) return ""; //position in 10 day range double spread20 = 20; //spread is 20 percent Double min_R10D = Math.Round((r10D - spread20), 2); Double max_R10D = Math.Round((r10D + spread20), 2); string R10D = "&MinR10D=" + min_R10D + "&MaxR10D=" + max_R10D; //position in 3 month range Double min_R3MO = Math.Round((r3mo - spread20), 2); Double max_R3MO = Math.Round((r3mo + spread20), 2); string R3MO = "&MinR3MO=" + min_R3MO + "&MaxR3MO=" + max_R3MO; //position in 6 month range Double min_R6MO = Math.Round((r6mo - spread20), 2); Double max_R6MO = Math.Round((r6mo + spread20), 2); string R6MO = "&MinR6MO=" + min_R6MO + "&MaxR6MO=" + max_R6MO; //yearly position in range double percentSpread = 5; Double minRY = Math.Round((ry - percentSpread), 2); Double maxRY = Math.Round((ry + percentSpread), 2); string RY = "&MinRY=" + minRY + "&MaxRY=" + maxRY; //Change from Close String changeClose = ""; if (fcp < 0 && fcp > -2) changeClose = "&MinFCP=-2&MaxFCP=0"; else if (fcp < -2) changeClose = "&MaxFCP=-2.01"; if (fcp > 0 && fcp < 2) changeClose = "&MinFCP=0&MaxFCP=2"; else if (fcp > 2) changeClose = "&MinFCP= 2.01"; //Relative Volume String relVol = ""; if (rv < 1) relVol = "&MaxRV=1"; else if (rv > 1 && rv < 5) relVol = "&MinRV=1&MaxRV=5"; else if (rv > 5) relVol = "&MinRV=5"; string retVal = first + R10D + R3MO + RY + R6MO + changeClose + relVol + last; return retVal; } public void setViewModeOnRestore(ViewMode mode) { _currentViewMode = mode; } public void setViewMode(ViewMode mode) //called from View Mode context menu items { if (_currentViewMode == mode) return; else { _currentViewMode = mode; if (_currentViewMode == ViewMode.Competitor) { //swap out to columns for competitor _boundList.Clear(); _dgView.Columns.Clear(); updateColumnListState(); if (_validSymbolEntered == false) return; setSubindustryString(_currentSector); } else { //swap out to columns earmarked for priceAction _boundList.Clear(); _dgView.Columns.Clear(); updateColumnListState(); if (_validSymbolEntered == false) return; //make the config here activatePriceAction(_priceAction_RY, _priceAction_RV, _priceActon_FCP, _priceAction_R10D, _priceAction_R3MO, _priceAction_R6MO); } } } private void updateColumnListState() { if (_currentViewMode == ViewMode.Competitor) { if (_focusColumnListStateCompetitor == null) _columnListState.LoadFrom(_competitorNode); else _columnListState = _focusColumnListStateCompetitor; } else { if (_focusColumnListStatePriceAction == null) _columnListState.LoadFrom(_priceActionNode); else _columnListState = _focusColumnListStatePriceAction; } // add parameter for form type - RVH20210528 //_columnListState.SaveTo(_dgView); _columnListState.SaveTo(_dgView, "SINGLE_STOCK_WINDOW"); } private void OnMetaData(TopListInfo metaData) { _metaData = metaData; if (!metaData.SameColumns(_previousColumns) || _serverSortField != metaData.ServerSort) { _serverSortField = metaData.ServerSort; _gradientColorChooser.SetColumnInfo(metaData.Columns); RedrawColumns(metaData.Columns); _previousColumns = metaData.Columns; } } private void RedrawColumns(IList columns) { if (null != columns) { if (null != GuiEnvironment.RowDataExtensions && GuiEnvironment.RowDataExtensions.Count > 0) { foreach (IRowDataExtension rowDataExtension in GuiEnvironment.RowDataExtensions) { if (null != _metaData) _metaData.Columns = rowDataExtension.HandleColumnInfo(this, columns); } } _columnListState.LoadFrom(_dgView); _dgView.RowTemplate.DefaultCellStyle.Padding = new Padding(0); _dgView.RowTemplate.Height = TextRenderer.MeasureText("X", this.Font).Height + 3; _dgView.EnableHeadersVisualStyles = false; _dgView.Columns.Clear(); foreach (ColumnInfo columnInfo in columns) { if (_textHeaders) { if (!_originallyIcon.ContainsKey(columnInfo)) _originallyIcon.Add(columnInfo, columnInfo.TextHeader); columnInfo.TextHeader = true; } DataGridViewColumn column = DataCell.GetColumn(columnInfo, _connectionMaster, this, null, isFrozen); if (_textHeaders) { column.HeaderCell.Style.WrapMode = DataGridViewTriState.True; } _dgView.Columns.Add(column); ApplyColumnColors(); } if (_needToRestoreSortBy) { ColumnInfo oldSortCodeImport = null; // Symbol was the only "special" column under the old format if (_importSortBy == "symbol") _importSortBy = "D_Symbol"; foreach (ColumnInfo columnInfo in columns) { if (columnInfo.InternalCode == _importSortBy) { _selectedSortColumnInfo = columnInfo; break; } // Need to support old layouts that were saving the "c_" version of the codes. // It's probably not necessary but if by some miracle if both the old and new // sort codes exist we assume it's the new. else if (CommonAlertFields.GetFilterWireName(columnInfo.InternalCode) == _importSortBy) { oldSortCodeImport = columnInfo; } } _needToRestoreSortBy = false; if (_selectedSortColumnInfo == null) _selectedSortColumnInfo = oldSortCodeImport; // revert to server sort if we fail to restore the listed sort code if (_selectedSortColumnInfo == null) { _localSortEnabled = false; _localSortType = TopListSortType.ServerSort; } } // add parameter for form type - RVH20210528 //_columnListState.SaveTo(_dgView); _columnListState.SaveTo(_dgView, "SINGLE_STOCK_WINDOW"); fixHeadersAfterSorting(); } } private void ApplyColumnColors() { foreach (string internalCode in _columnColors.Keys) { ColumnInfo columnInfo = GetColumn(internalCode); if (null != columnInfo) columnInfo.GradientInfo = _columnColors[internalCode]; } } private ColumnInfo GetColumn(string internalCode) { if ((null != _metaData) && (null != _metaData.Columns)) { foreach (ColumnInfo column in _metaData.Columns) { if (column.InternalCode == internalCode) return column; } } return null; } private void _dgView_CellClick(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex != -1) { if (GuiEnvironment.IntegrationSupportSingleClick) { // there has been some rare crashes that we haven't been able to reproduce but the errors // lean towards trying to access an index that is out of bounds. if (_symbolLinkingEnabled) { if (_boundList.Count > e.RowIndex) SendToExternalLinking(_currentRowData); } } } } private void _dgView_CellDoubleClick(object sender, DataGridViewCellEventArgs e) { if (e.RowIndex == -1) //here we-re focusing on header { //GuiEnvironment.RecordUseCase("TopList.ColumnHeader.Sort", _sendManager); // header row was double clicked on Column column = (Column)_dgView.Columns[e.ColumnIndex]; if (!_localSortEnabled || (null != _selectedSortColumnInfo && (_selectedSortColumnInfo.InternalCode != column.ColumnInfo.InternalCode))) { _localSortEnabled = true; _selectedSortColumnInfo = column.ColumnInfo; _localSortType = TopListSortType.Descending; DoSorting(); } else { if (_localSortType == TopListSortType.Descending) { _localSortType = TopListSortType.Ascending; } else if (_localSortType == TopListSortType.Ascending) { _localSortType = TopListSortType.ServerSort; _localSortEnabled = false; } DoSorting(); } } else { if (GuiEnvironment.IntegrationSupportSingleClick == false) { // there has been some rare crashes that we haven't been able to reproduce but the errors // lean towards trying to access an index that is out of bounds. if (_symbolLinkingEnabled) { if (_boundList.Count > e.RowIndex) SendToExternalLinking(_currentRowData); } } } } public void SendToExternalLinking(RowData rowData, bool promptUserIfEmpty = false) { if (null == rowData) return; // RecordExternalLinkUseCase(rowData); string symbol = rowData.GetSymbol(); string exchange = rowData.GetExchange(); // Use BeginInvoke to avoid "reentrant call to the SetCurrentCellAddress" // exception when a user uses both keyboard and mouse symbol linking // simultaneously with external linking alternate method disabled. // http://stackoverflow.com/questions/26522927/how-to-evade-reentrant-call-to-setcurrentcelladdresscore BeginInvoke(new MethodInvoker(() => { GuiEnvironment.sendSymbolToExternalConnector(symbol, exchange, "", null, rowData, promptUserIfEmpty: promptUserIfEmpty); })); } void FixCellHeader(string sortableCode, TopListSortType sortType) { if (null == _metaData) return; bool needsToBePressed = false; if (sortType != TopListSortType.ServerSort) needsToBePressed = true; ColumnInfo columnInfo = _metaData.Columns.Single(x => x.InternalCode == sortableCode); DataGridViewColumn column = _dgView.Columns.Cast().Single(x => x.ColumnInfo == columnInfo); Depressible header = column.HeaderCell as Depressible; if (null != header) { if (header.Pressed != needsToBePressed) { header.Pressed = needsToBePressed; _dgView.InvalidateCell(column.HeaderCell); } } } void _gradientColorChooser_OnChange() { if (!IsDisposed) { UpdateColors(); VerifyColorData(); } } private void VerifyColorData() { // Start simple for now. Always assume we need to do the work. Presumably do more later ReRequestData(); } private void UpdateColors() { int rowNumber = 0; foreach (RowData row in _boundList) { GradientColorChooser.ColorPair? colorPair = _gradientColorChooser.GetColors(row); { DataGridViewCellStyle style = _dgView.DefaultCellStyle.Clone(); style.Alignment = DataGridViewContentAlignment.NotSet; if (null != colorPair) { style.BackColor = colorPair.Value.BackGround; style.ForeColor = colorPair.Value.ForeGround; } // Set the font to null here, and then the row will use the form's font. // It seems like we should be able to say // "dataGridView1.DefaultCellStyle.Font = null", but that does nothing. // The operation is ignored, and the default font remains the same. style.Font = null; if (rowNumber < _dgView.RowCount) _dgView.Rows[rowNumber].DefaultCellStyle = style; } rowNumber++; } } private void ReRequestData() { SetConfiguration(_config); } // TODO This is UGLY. We always want nulls to be at the bottom. For doubles, we replace null with + or - // infinity. For strings, we know that "" is the smallest value, like -INF. We are using this as the // biggest value. It will probably work okay, but it's ugly. Really, we should provide our own compare // function. That function can deal with the nulls more explicitly. const string LAST_STRING = "zzzz"; private List SortRows(List rows, ColumnInfo sortColumnInfo, TopListSortType sortType) { if (sortType == TopListSortType.ServerSort || sortColumnInfo == null) { return _serverSortedList; } else { if (sortColumnInfo.Format == "") { if (sortType == TopListSortType.Descending) return rows.OrderByDescending(x => (x.Data.ContainsKey(sortColumnInfo.WireName)) ? x.GetAsString(sortColumnInfo.WireName) : "").ToList(); else return rows.OrderBy(x => (x.Data.ContainsKey(sortColumnInfo.WireName)) ? x.GetAsString(sortColumnInfo.WireName) : LAST_STRING).ToList(); } else { if (sortType == TopListSortType.Descending) return rows.OrderByDescending(x => (x.Data.ContainsKey(sortColumnInfo.WireName)) ? ParseRowElementValue(x.Data[sortColumnInfo.WireName].ToString(), double.NegativeInfinity) : double.NegativeInfinity).ToList(); else return rows.OrderBy(x => (x.Data.ContainsKey(sortColumnInfo.WireName)) ? ParseRowElementValue(x.Data[sortColumnInfo.WireName].ToString(), double.PositiveInfinity) : double.PositiveInfinity).ToList(); } } } private double ParseRowElementValue(string source, double def) { double value; if (ServerFormats.TryParse(source, out value)) return value; else return def; } void TopListRequest.Listener.OnDisconnect(TopListRequest.Token token) { if (token != _requestToken) // Old request. Ignore it. return; // Disconnected. Try again. _requestToken = token.GetRequest().Send(_sendManager, this); } private List eliminateNYSESpecialSymbols(List rows) // we're filtering away any NYSE special symbols. Can't be done server-side since we're also searching a sector in the config string (competitor mode) { List retVal = new List(); foreach (RowData r in rows) { String test = r.GetAsString("c_D_Symbol", ""); if (!test.Contains("-") && (!test.Contains(".")) && retVal.Count != 25) retVal.Add(r); } return retVal; } void TopListRequest.Listener.OnRowData(List rows, DateTime? start, DateTime? end, TopListRequest.Token requestToken) { this.BeginInvokeIfRequired((MethodInvoker)delegate { rows = eliminateNYSESpecialSymbols(rows); if (requestToken != _requestToken) // Old request. Ignore it. return; if (rows.Count < 10 && _currentViewMode == ViewMode.Competitor) { if (_currentSect == Sector.SUB_INDUSTRY && _rawIndustry != "") { _currentSect = Sector.INDUSTRY; _currentSector = _rawIndustry; _config = createCompetitorConfigString(_rawIndustry); _config = EnhancedSingleStockWindow.putOutSideMarketHours(_config, _outsideMarketHours); SetConfiguration(_config); } else if (_currentSect == Sector.INDUSTRY && _rawIndGroup != "") { _currentSect = Sector.INDUSTRY_GROUP; _currentSector = _rawIndGroup; _config = createCompetitorConfigString(_rawIndGroup); _config = EnhancedSingleStockWindow.putOutSideMarketHours(_config, _outsideMarketHours); SetConfiguration(_config); } else if (_currentSect == Sector.INDUSTRY_GROUP && _rawSubsector != "") { _currentSect = Sector.SUB_SECTOR; _currentSector = _rawSubsector; _config = createCompetitorConfigString(_rawSubsector); _config = EnhancedSingleStockWindow.putOutSideMarketHours(_config, _outsideMarketHours); SetConfiguration(_config); } else if (_currentSect == Sector.SUB_SECTOR && _rawSector != "") { _currentSect = Sector.SECTOR; _currentSector = _rawSector; _config = createCompetitorConfigString(_rawSector); _config = EnhancedSingleStockWindow.putOutSideMarketHours(_config, _outsideMarketHours); SetConfiguration(_config); } } else { OnRowData(rows, start, end); } requestToken.GetRequest().LastUpdate = DateTime.Now; }); } void TopListRequest.Listener.OnMetaData(TopListRequest.Token requestToken) { this.BeginInvokeIfRequired((MethodInvoker)delegate { if (requestToken != _requestToken) // Old request. Ignore it. return; OnMetaData(requestToken.GetMetaData()); if (null != _config) requestToken.GetRequest().Collaborate = _config; }); } void OnRowData(List rows, DateTime? start, DateTime? end) { if (_dgView.FirstDisplayedScrollingColumnIndex == -1) { //this is to address case where user may switch back and forth between viewmodes after having originally entered an invalid symbol //and then receiving empty results(when there shouldn't be any) after finally entered a valid symbol. if (rows.Count > 0 && null != _metaData) { RedrawColumns(_metaData.Columns); String sym = rows[0].GetSymbol(); } return; } DataGridViewSelectedCellCollection savedSelection = _dgView.SelectedCells; // saved selection is mostly useless after calling _boundList.Clear(); int[] selectedRows = new int[savedSelection.Count]; int[] selectedCols = new int[savedSelection.Count]; for (int i = 0; i < savedSelection.Count; i++) { selectedRows[i] = savedSelection[i].RowIndex; selectedCols[i] = savedSelection[i].ColumnIndex; } int horizontalScrollPosition = _dgView.HorizontalScrollingOffset; int verticalScrollPosition = _dgView.HorizontalScrollingOffset; int firstCol = _dgView.FirstDisplayedScrollingColumnIndex; int firstRow = _dgView.FirstDisplayedScrollingRowIndex; _boundList.Clear(); // We need to keep track of the server sorted list in case the user goes back to TopListType.ServerSort // This way we can revert to the server sort without waiting for the next refresh from the server _serverSortedList = new List(rows); if (_localSortEnabled) { rows = SortRows(rows, _selectedSortColumnInfo, _localSortType); } foreach (RowData row in rows) { _boundList.Add(row); } UpdateColors(); _dgView.ClearSelection(); bool markedCurrentCell = false; for (int i = 0; i < selectedRows.Length; i++) { int row = selectedRows[i]; int col = selectedCols[i]; if ((row < _dgView.RowCount) && (col <= _dgView.ColumnCount)) { // Need to set both selected rows and current cell to keep the cell/row focus insync. if (!markedCurrentCell) { _dgView.CurrentCell = _dgView.Rows[row].Cells[col]; markedCurrentCell = true; } _dgView.Rows[row].Cells[col].Selected = true; } } try { _dgView.FirstDisplayedScrollingColumnIndex = firstCol; _dgView.HorizontalScrollingOffset = horizontalScrollPosition; _dgView.HorizontalScrollingOffset = verticalScrollPosition; } catch { // For simplicity we just ignore all errors here. It's tempting to try // to catch the errors in advance, but there are a few special conditions // to worry about, like an empty table. And it's tempting to try to do // a better job of there is a problem. For example, if we are at the // bottom, and the table gets shorter, maybe we should still show the // bottom. But for now I think this is good enough. } try { if (firstRow > 0) _dgView.FirstDisplayedScrollingRowIndex = firstRow; } catch { } if (null != GuiEnvironment.RowDataExtensions && GuiEnvironment.RowDataExtensions.Count > 0 && null != _metaData) { try { foreach (IRowDataExtension rowDataExtension in GuiEnvironment.RowDataExtensions) rowDataExtension.HandleRowData(this, _metaData.WindowName, _metaData.Columns, rows); } catch { } } } private void TopListPanel_subIndustryReceivedEvent(string subIndustry) { _currentSector = subIndustry; } //We only want to create new config string when we change modes or change symbols, not every time that we get the 30 sec update with toplist data private void activatePriceAction(double ry, double rv, double fcp, double r10D, double r3mo, double r6mo) { string cfig = createPriceActionConfigString(ry, rv, fcp, r10D, r3mo, r6mo); if (cfig == "") _config = _defaultPriceActionConfig; //TODO: the default may need work else _config = cfig; _config = EnhancedSingleStockWindow.putOutSideMarketHours(_config, _outsideMarketHours); SetConfiguration(_config); } public void stopTopListData() { SetConfiguration(null); subIndustryDataReceivedEvent -= TopListPanel_subIndustryReceivedEvent; onMarketHoursChangedEvent -= TopListPanel_onMarketHoursChangedEvent; _dgView.CellMouseEnter -= _dgView_CellMouseEnter; _dgView.CellMouseLeave -= _dgView_CellMouseLeave; } public void clearData() //when invalid symbol is entered { SetConfiguration(null); _boundList.Clear(); } public void unLoadEventHandlers() { subIndustryDataReceivedEvent -= TopListPanel_subIndustryReceivedEvent; onMarketHoursChangedEvent -= TopListPanel_onMarketHoursChangedEvent; _gradientColorChooser.OnChange -= _gradientColorChooser_OnChange; _dgView.CellMouseEnter -= _dgView_CellMouseEnter; _dgView.CellMouseLeave -= _dgView_CellMouseLeave; } //All of the below is very much like the code that's normally used for duplication of other forms and handled a lot with the Layout Manager. //However, the major difference in this case is that the "window" that's being saved is not a (windows) Form and will be restored as a true (TopList) WinForm. TopListPanel // a custom Winform *component* (i.e. TopListPanel), however it's *not* derived from the Form class at all..Layout Manager handles save/restore/duplicate with WinForm's "Form" class //So the "popping out" functionality to a true Winform (Top List Window) is being done public void duplicate() { XmlDocument result = new XmlDocument(); XmlNode container = result.CreateElement("TEMP"); result.AppendChild(container); XmlNode description = saveLayout(container); restoreLayout(description); } private XmlNode saveBase(XmlNode parent, TopListPanel toSave) { // Start by creating the new node for the window, as that will be a common // activity. XmlNode result = parent.NewNode("WINDOW"); XmlNode baseNode = result.NewNode("BASE"); // Save the position of the windows. { baseNode.SetProperty("Top", toSave.Top); baseNode.SetProperty("Left", toSave.Left); baseNode.SetProperty("Height", toSave.Height); baseNode.SetProperty("Width", toSave.Width); } // Windows 10 changed the the left, right and bottom borders so they are invisible. // To allow us to place the windows side by side we increase the windows width and height // to use the invisble border space. So here we need to decrease by the amount we added when restoring the window. if (GuiEnvironment.RunningWin10) { int normalHeight = baseNode.Property("Height", 0) - GuiEnvironment.HEIGHT_INCREASE; int normalWidth = baseNode.Property("Width", 0) - GuiEnvironment.WIDTH_INCREASE; int normalLeft = baseNode.Property("Left", 0) + GuiEnvironment.WIDTH_INCREASE / 2; baseNode.SetProperty("Height", normalHeight); baseNode.SetProperty("Width", normalWidth); if (!GuiEnvironment.openRelativeToMainWindow) baseNode.SetProperty("Left", normalLeft); } baseNode.SetProperty("Visible", true); return result; } private XmlNode saveLayout(XmlNode parent) { XmlNode description = saveBase(parent, this); if (null != _config) { description.SetProperty("CONFIG", _config); } else { if (_currentViewMode == ViewMode.Competitor) description.SetProperty("CONFIG", _defaultCompetitorConfig); else description.SetProperty("CONFIG", _defaultPriceActionConfig); } _gradientColorChooser.Save(description.NewNode("COLORS")); description.SetProperty("MAX_RECORD_COUNT", _recordCount); description.SetProperty("CONNECTION", GuiEnvironment.ConnectionName(_connectionMaster)); description.SetProperty("LOCALSORTENABLED", _localSortEnabled); if (null != _selectedSortColumnInfo) description.SetProperty("LOCALSORTCODE", _selectedSortColumnInfo.InternalCode); description.SetProperty("LOCALSORTTYPE", _localSortType.ToString()); description.SetProperty("TEXTHEADERS", _textHeaders); description.SetProperty("OUTSIDE_MARKET_HOURS", _outsideMarketHours); _columnListState.LoadFrom(_dgView); _columnListState.SaveTo(description.NewNode("COLUMNS")); XmlNode columnColors = description.NewNode("COLUMN_COLORS"); foreach (string internalCode in _columnColors.Keys) { XmlNode columnColor = columnColors.NewNode("COLUMN_COLOR"); columnColor.SetProperty("COLUMN", internalCode); GradientInfo gradientInfo = _columnColors[internalCode]; gradientInfo.Save(columnColor); } if (GuiEnvironment.RowDataExtensions.Count > 0) { XmlNode extensionsNode = description.NewNode("ROWDATA_EXTENSIONS"); foreach (IRowDataExtension rowDataExtension in GuiEnvironment.RowDataExtensions) { string extensionId = rowDataExtension.GetId(); XmlNode rowDataExtensionNode = extensionsNode.NewNode(extensionId); rowDataExtension.SaveToLayout(this, rowDataExtensionNode); } } return description; } private void restoreLayout(XmlNode description) { IConnectionMaster connectionMaster = GuiEnvironment.FindConnectionMaster(description.Property("CONNECTION")); if (null == connectionMaster) { // We could report an error here, but it's simpler just to do nothing. Any error message we tried to // report would probably be confusing at best to the user. } else { string config = description.Property("CONFIG", null); bool? outsideMarketHours = description.PropertyBool("OUTSIDE_MARKET_HOURS"); string initialHistoryTime = description.Property("HISTORY_TIME", null); string initialCount = description.Property("MAX_RECORD_COUNT", null); TopListForm topListForm = new TopListForm(connectionMaster, config, outsideMarketHours, initialHistoryTime, initialCount); topListForm.Restore(description, true, true); topListForm.RestoredLayout = description; } } public XmlNode saveGridColumnForDuplication(XmlNode description) { _columnListState.LoadFrom(_dgView); _columnListState.SaveTo(description.NewNode("SIMILAR_COLUMNS")); return description; } //The below getters/setters are specifically when we're duplicating the whole Single Stock window and saving parameters pertinent // to the Similar Tab-, which of course, includes the state of the data grid. Unlike the save/restore in this file (which is for actually //popping out to a true Top List Form, the save/restore operation for the "duplicate" operation is done EnhancedSingleStockWindow.cs. public bool getLocalSortEnabled() { return _localSortEnabled; } public void setLocalSortEnabled(bool value) { _localSortEnabled = value; } public String getLocalSortCode() { if (null != _selectedSortColumnInfo) return _selectedSortColumnInfo.InternalCode; else return ""; } public void setLocalSortCode(string code) { //these values are set *only* when 'code' is not null/empty (that's checked in EnhancedSingleStockWindow.cs) _importSortBy = code; _needToRestoreSortBy = true; } public String getLocalSortType() { return _localSortType.ToString(); } public void setLocalSortType(TopListSortType t) { _localSortType = t; } public String getSubindustryString() { return _currentSector; } public void setSubindustryOnRestore(String currentSector) { _currentSector = currentSector; } public void chooseRowSelect() { if (GuiEnvironment.HighlightGridRow) { _dgView.SelectionMode = DataGridViewSelectionMode.FullRowSelect; } else { _dgView.SelectionMode = DataGridViewSelectionMode.CellSelect; } } } }