using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Xml; using System.IO; using System.Windows.Forms; using TradeIdeas.TIProGUI; using TradeIdeas.TIProData; using TradeIdeas.TIProData.Configuration; using TradeIdeas.XML; using TradeIdeas.ServerConnection; using System.Diagnostics; using TradeIdeas.MiscSupport; using TradeIdeas.TIProData.Interfaces; //THIS IS A VERY BARE MINIMUM TOPLIST WINDOW USING FOR PROOF-OF-CONCEPT // Much work needed... //For form snapping I had to use two different libraries. Our regular form snapper dll ("snapComponent") //does not work uner MDI condition. Luckily I found an additional library that *does* work for MDI("snapFormExtender") //condition (however it doesn't work under non-mdi condition). So, whenever a window is attached to the //parent, "snapFormExtender.dll" is used. As soon as it becomes detached, then (MDI parent is null), then the //formSnapper component gets used. http://www.codeproject.com/Articles/6338/SnapFormExtender-a-magnet-for-your-MDI-child-forms //to the parent, the namespace MdiDemo { public partial class MdiTopListForm : Form, ISaveLayout,ISnapToGrid { MainForm _main; private BindingList _boundList = new BindingList(); private IList Columns; // Sorting variables private ColumnInfo _selectedSortColumnInfo; //Attached/Deatached window private bool _isAttached = true; private Point _formeLocationBeforeDetach = new Point(0, 0); private const int X_OFFSET = 5; private const int Y_OFFSET = 5; private const int X_RIGHT_OFFSET = 15; private const int X_BOTTOM_OFFSET = 30; int parentRightEdgeX; int parentBottomEdgeY; int thisWindowRightEdgeX; int thisWindowBottomEdgeY; // for restoring layouts private string _importSortBy; private bool _needToRestoreSortBy = false; private List _serverSortedList = new List(); private bool _localSortEnabled = false; private TopListSortType _localSortType = TopListSortType.ServerSort; private string _serverSortField = ""; private Dictionary> _filterMenuItems = new Dictionary>(); private TopList _topList; private string _config; // for tracking currently "selected" row for modifying the right-click menu appropriately private RowData _currentRowData; private RowData _mostRecentRowData; private int _mostRecentRowIndex; private bool _symbolSpecificContextMenuOpen = false; private Dictionary _originallyIcon = new Dictionary(); // For simplicity we're only allowing an object of this type. It seems like an interface // called ITableColorChooser() might be more appropriate. That would allow us to customize // more, including using 3rd party code. private GradientColorChooser _gradientColorChooser = new GradientColorChooser(); ConfigurationWindowManager _cManager = null; private ColumnListState _columnListState = new ColumnListState(); private LayoutManager _layoutManager = LayoutManager.Instance(); private readonly IConnectionMaster _connectionMaster; private List _phrases; private List _sendTo; private string _fileNameSaved; private string _fileNameSavedContents; private string _shortWindowName; private const string FORM_TYPE = "TOP_LIST_WINDOW"; private const string _defaultFileName = "DEFAULT_TOPLIST.WTI"; //public static readonly WindowIconCache WindowIconCache = new WindowIconCache("TOP_LIST"); private FontManager _fontManager; private const float DEFAULT_FONT_SIZE = 8.25F; private ISymbolLinkingForOwner _symbolLinking; private Dictionary _columnColors = new Dictionary(); // For use in showing columns private const int MIN_COLUMN_WIDTH = 5; private const int DEFAULT_ROW_HEIGHT = 20; //viewing and hiding the top panel which contains the strategy combo private bool _isTopPanelHidden = true; private TopListStrategy _topListStrategy; //will use for historical stuff TopListHistoricalTimeFrame _timeFrame = null; TopListHistoryRecords _historyRecords = null; private ContextMenu _contextMenuCylinder = new ContextMenu(); private ColumnInfo _cylinderColumnInfo = null; private string _cylinderColumnCode = ""; //column locking ("Freezing" the symbol column) private bool _columnLocked = false; ToolStripMenuItem _lockColumnMenuItem; private bool _lockMenuItemEnabled = true; private bool _textHeaders = false; public bool TextHeaders { get { return _textHeaders; } set { _textHeaders = value; //UpdateTextHeadersMenuItem(); TODO } } private bool _isChild = false; public bool IsChild { get { return _isChild; } set { _isChild = value; } } /// /// This never has the delayed data disclaimer. /// private string _baseWindowTitle = "Top List"; public Rectangle? ActualSize { get; set; } public TIFormType TIFormType { get { return TIFormType.TopList; } } /// /// This is the xml that was used to restore this form. If this form wasn't layout-restored then this will be null. /// public XmlNode RestoredLayout { get; set; } /// /// This gets called before any other code in the SaveLayout function. Currently this is aimed at the Scottrade addin but anything can set this code. /// public Action
PreSaveLayoutCode { get; set; } /// /// This gets called after SaveBase and it passes the XmlNode that the form is being saved to. This allows extensions to add properties to the layout. Access the layout XmlNode /// from a form using RestoredLayout. /// public Action SaveLayoutCode { get; set; } /// /// Returns the current configuration string used for collaboration. /// /// public string GetConfigString() { return _config; } public ContextMenuStrip MyContextMenuStrip { get { return contextMenuStrip1; } } //The below array accounts for Alert , Multistrategy windows as well as Top List: public static string[] sectorColumns = { "D_Industry", "D_Sector", "D_SubSector", "D_IndGrp", "D_SubIndustry", "D_Name", "D_Desc", "Description", "Strategies", "Strategy Name", "Sub Sector", "Industry Group", "Sub Industry", "Industry", "Sector", "Company Name" }; public MdiTopListForm(MainForm main, IConnectionMaster connectionMaster, String config) { this.MdiParent = main; _main = main; _connectionMaster = connectionMaster; InitializeComponent(); if (!IsHandleCreated) // This is required because the callbacks can come in any thread. InvokeRequired // does not work correctly before the handle is created. This is necessary // in any window which requests data in the constructor. CreateHandle(); /* 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; SetConfiguration(config); _cManager = new ConfigurationWindowManager(); _cManager.LoadFromServer(connectionMaster, ConfigurationType.TopList, OnLoaded, config); // This should all come from a dialog box, and be saved as part of the layout. _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 += new MethodInvoker(_gradientColorChooser_OnChange); // onSymbolClicked += new TopListSymbolClickHandler(TopListForm_onSymbolClicked); // graphicalIndicatorToolStripMenuItem.Click += Column.GraphicsMenuItemCallback; _phrases = GuiEnvironment.XmlConfig.Node("MULTI_STRATEGY_WINDOW").Node("PHRASES"); _sendTo = GuiEnvironment.XmlConfig.Node("TOPLIST_WINDOW").Node("PHRASES"); // PopulateStrings(); setGridLines(); //chooseRowSelect(); //_fontManager = new FontManager(this, DEFAULT_FONT_SIZE); //selectTheFont(); //AddExtraMenuItems(GuiEnvironment.GetExtraMenuItems().Where(x => x.TIFormType == TIFormType.All || x.TIFormType == TIFormType.TopList).ToList(), contextMenuStrip1.Items); // SetSnapToGrid(GuiEnvironment.SnapToGrid); //comboMenu.loadTheCombo(ConfigurationType.TopList, _connectionMaster, _config, this); //chart1.ContextMenu = _contextMenuCylinder; //chart1.Size = new Size(chart1.Size.Width, dataGridView1.RowTemplate.Height); //hideTopPanel(); } private void OnLoaded(ConfigurationWindowManager configurationWindowManager) { /*This OnLoaded method "initializes" the config window when it is first pulled up...*/ if (InvokeRequired) { BeginInvoke((MethodInvoker)delegate { OnLoaded(configurationWindowManager); }); } else { PrepairedStrategy p = _cManager.CurrentSettings; } } public void setGridLines() { if (GuiEnvironment.ShowGridLines) { dataGridView1.CellBorderStyle = DataGridViewCellBorderStyle.Single; } else { dataGridView1.CellBorderStyle = DataGridViewCellBorderStyle.None; } } public void SetConfiguration(string config) { if (null != _topList) { _topList.Stop(); _topList.TopListStatus -= _topList_TopListStatus; _topList.TopListData -= _topList_TopListData; _topList = null; } _config = config; if (null == config) Text = "Top List"; else { _topList = _connectionMaster.TopListManager.GetTopList(config, true); _topList.TopListStatus += new TopListStatus(_topList_TopListStatus); _topList.TopListData += new TopListData(_topList_TopListData); _topList.Start(); Text = ""; } } private void _topList_TopListStatus(TopList sender) { if (InvokeRequired) BeginInvoke((MethodInvoker)delegate { _topList_TopListStatus(sender); }); else { if (sender == _topList) { System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + " TopListStatus " + _topList.TopListInfo.WindowName); GuiEnvironment.LogMessage("TopListStatus " + _topList.TopListInfo.WindowName); _config = _topList.TopListInfo.Config; _boundList.Clear(); if (!_topList.TopListInfo.SameColumns(Columns) || _serverSortField != _topList.TopListInfo.ServerSort) { _serverSortField = _topList.TopListInfo.ServerSort; _gradientColorChooser.SetColumnInfo(_topList.TopListInfo.Columns); RedrawColumns(); } // Window setTitle(_topList.TopListInfo); } } } 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) { System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("MM/dd/yyyy hh:mm:ss.fff tt") + " TopListData " + rows.Count + " " + _topList.TopListInfo.WindowName); GuiEnvironment.LogMessage("TopListData " + rows.Count + " " + _topList.TopListInfo.WindowName); /* This method works, but every time there is new data, you lose the * scroll position and the selection. So I manually save and restore * those values. */ // If no columns are defined then exit routine if (dataGridView1.FirstDisplayedScrollingColumnIndex == -1) { return; } DataGridViewSelectedCellCollection savedSelection = dataGridView1.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 firstCol = dataGridView1.FirstDisplayedScrollingColumnIndex; int firstRow = dataGridView1.FirstDisplayedScrollingRowIndex; dataGridView1.SuspendLayout(); _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); } //_boundList = new BindingList (_sortableList.OrderBy(x => x.Data["symbol"]).ToList()); UpdateColors(); dataGridView1.ClearSelection(); dataGridView1.ResumeLayout(); for (int i = 0; i < selectedRows.Length; i++) { int row = selectedRows[i]; int col = selectedCols[i]; if ((row < dataGridView1.RowCount) && (col <= dataGridView1.ColumnCount)) dataGridView1.Rows[row].Cells[col].Selected = true; } try { dataGridView1.FirstDisplayedScrollingColumnIndex = firstCol; } 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 { dataGridView1.FirstDisplayedScrollingRowIndex = firstRow; } catch { } setTitle(_topList.TopListInfo, start, end); } } } private void UpdateColors() { int rowNumber = 0; foreach (RowData row in _boundList) { GradientColorChooser.ColorPair? colorPair = _gradientColorChooser.GetColors(row); { DataGridViewCellStyle style = dataGridView1.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; dataGridView1.Rows[rowNumber].DefaultCellStyle = style; } rowNumber++; } } static public void RegisterLayout(MainForm mainForm) { //LayoutManager.Instance().AddRestoreRule(FORM_TYPE, (RestoreLayout)delegate(XmlNode description, bool ignorePosition, bool cascadePosition) //{ // 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); // MdiTopListForm topListForm = new MdiTopListForm(mainForm, connectionMaster, config); // topListForm.Restore(description, ignorePosition, cascadePosition); // topListForm.RestoredLayout = description; // } //}); } public void Restore(XmlNode description, bool ignorePosition, bool cascadePosition) { string config = description.Property("CONFIG", null); SetConfiguration(config); LayoutManager.RestoreBase(description, this, ignorePosition, cascadePosition); _gradientColorChooser.Load(description.Node("COLORS")); _columnListState.LoadFrom(description.Node("COLUMNS")); //if (null != description.Property("TEXTHEADERS")) // TextHeaders = description.Property("TEXTHEADERS", false); if (null != description.Property("LOCALSORTENABLED")) _localSortEnabled = bool.Parse(description.Property("LOCALSORTENABLED", "False")); if (null != description.Property("LOCALSORTTYPE")) _localSortType = (TopListSortType)Enum.Parse(typeof(TopListSortType), description.Property("LOCALSORTTYPE", "ServerSort")); if (null != description.Property("LOCALSORTCODE")) { _importSortBy = description.Property("LOCALSORTCODE"); _needToRestoreSortBy = true; } _columnLocked = description.Property("COLUMN_LOCKED", false); _isAttached = description.Property("IS_ATTACHED", true); if (_isAttached) { attachWindow(); } else { detachWindow(); } } public void SaveLayout(XmlNode parent) { if (null != PreSaveLayoutCode) PreSaveLayoutCode(this); XmlNode description = LayoutManager.SaveBase(parent, this, FORM_TYPE, ActualSize); if (null != SaveLayoutCode) SaveLayoutCode(this, description); if (null != _config) description.SetProperty("CONFIG", _config); _gradientColorChooser.Save(description.NewNode("COLORS")); description.SetProperty("IS_ATTACHED", _isAttached.ToString()); 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); _columnListState.LoadFrom(dataGridView1); _columnListState.SaveTo(description.NewNode("COLUMNS")); } // 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"; // I don't know why this next line didn't work. This is a very large value for a unicode character. // I would have expected this string to come after all normal strings. But in fact it came first. //const string LAST_STRING = "\uffe0"; 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; } private void RedrawColumns() { if (null != _topList) { Columns = _topList.TopListInfo.Columns; _columnListState.LoadFrom(dataGridView1); dataGridView1.RowTemplate.DefaultCellStyle.Padding = new Padding(0); dataGridView1.RowTemplate.Height = TextRenderer.MeasureText("X", dataGridView1.Font).Height + 3; dataGridView1.EnableHeadersVisualStyles = false; dataGridView1.Columns.Clear(); _filterMenuItems.Clear(); // _contextMenuCylinder.MenuItems.Clear(); foreach (ColumnInfo columnInfo in _topList.TopListInfo.Columns) { if (columnInfo.IsNumeric()) { MenuItem menuItemCylinder = new MenuItem(); menuItemCylinder.Text = columnInfo.Description; menuItemCylinder.Tag = columnInfo; // menuItemCylinder.Click += menuItemCylinder_Click; // _contextMenuCylinder.MenuItems.Add(menuItemCylinder); } if (_textHeaders) { if (!_originallyIcon.ContainsKey(columnInfo)) _originallyIcon.Add(columnInfo, columnInfo.TextHeader); columnInfo.TextHeader = true; } else { if (_originallyIcon.ContainsKey(columnInfo)) columnInfo.TextHeader = _originallyIcon[columnInfo]; } //DataGridViewColumn column = DataCell.GetColumn(columnInfo, _connectionMaster, this, null, isFrozen); DataGridViewColumn column = DataCell.GetColumn(columnInfo, _connectionMaster, this, null, false); // column.HeaderCell.ContextMenuStrip = BuildColumnContextMenu(columnInfo); // column.HeaderCell.ContextMenuStrip.Opening += ContextMenuStrip_Opening; if (_textHeaders) { column.HeaderCell.Style.WrapMode = DataGridViewTriState.True; } //if (isFrozen) //{ // column.HeaderCell.Style.ForeColor = _frozenHeaderTextColor; //} dataGridView1.Columns.Add(column); //We wish to keep the "Symbol" column frozen should user wish to do so //and *only* if it's the leftmost column. //We go by the DisplayIndex, as the DisplayIndex won't always //be equal to the column index. if (columnInfo.InternalCode == "D_Symbol" && column.DisplayIndex == 0 && _columnLocked) { column.Frozen = true; //for restoring layouts and duplicating window. } // ApplyColumnColors(); } MenuItem customThresholdMenuItem = new MenuItem(); //customThresholdMenuItem.Text = "Set Custom Threshold..."; //customThresholdMenuItem.Click += customThresholdMenuItem_Click; //_contextMenuCylinder.MenuItems.Add(customThresholdMenuItem); 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 this.Columns) { if (columnInfo.InternalCode == _importSortBy) { _selectedSortColumnInfo = columnInfo; break; } // Need to support old layours 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 (columnInfo.InternalCode == _importSortBy.Replace("c_", "")) { 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; } } //if (_lockColumnMenuItem != null) //{ // _lockColumnMenuItem.Enabled = _lockMenuItemEnabled; //} //_columnListState.SaveTo(dataGridView1); //FixSortMenuItems(); } } private void setTitle(TopListInfo topListInfo, DateTime? start = null, DateTime? end = null) { StringBuilder result = new StringBuilder(); if ((null == topListInfo) || (topListInfo.WindowName == "")) result.Append("Top List"); else result.Append(topListInfo.WindowName); // save the short windowname for file saving _shortWindowName = result.ToString(); if ((null != start) || (null != end)) { result.Append(": "); if (null != start) result.Append(((DateTime)start).ToString("h:mm:ss")); if ((null != start) && (null != end)) result.Append(" - "); if (null != end) result.Append(((DateTime)end).ToString("h:mm:ss")); DateTime date = end.HasValue ? end.Value : start.Value; if (date < ServerFormats.Today) { result.Append(" "); try { string dateFormat = GuiEnvironment.XmlConfig.Node("DATE_FORMAT").PropertyForCulture("TEXT", null); if (null != dateFormat) { if (null != end) result.Append(end.Value.ToString(dateFormat)); else result.Append(start.Value.ToString(dateFormat)); } } catch { // Presumaly a bad value in the file could lead us here. By default print nothing. } } } _baseWindowTitle = result.ToString(); UpdateWindowTitle(); } private void UpdateWindowTitle() { string windowTitle = _baseWindowTitle; if (_connectionMaster.LoginManager.IsDemo) { windowTitle += GuiEnvironment.XmlConfig.Node("COMMON_PHRASES").Node("DEMO_DISCLAIMER").PropertyForCulture("TEXT", "***"); //saveToCloudToolStripMenuItem.Enabled = false; } else { // saveToCloudToolStripMenuItem.Enabled = true; } Text = windowTitle; } private void MdiTopListForm_FormClosed(object sender, FormClosedEventArgs e) { SetConfiguration(null); } private void duplicateToolStripMenuItem_Click(object sender, EventArgs e) { LayoutManager.Instance().Duplicate(this); } private void detachWindowToolStripMenuItem_Click(object sender, EventArgs e) { detachWindow(); } private void attachWindowToolStripMenuItem_Click(object sender, EventArgs e) { attachWindow(); } private void attachWindow() { parentRightEdgeX = _main.Location.X + _main.Width; parentBottomEdgeY = _main.Location.Y + _main.Height; thisWindowRightEdgeX = _main.Location.X + this.Right + X_RIGHT_OFFSET; thisWindowBottomEdgeY = _main.Location.Y + this.Bottom + X_BOTTOM_OFFSET; if (null == this.MdiParent) { //TODO - need to research positioning //Point temp = new Point(); //if(_formeLocationBeforeDetach.X < _main.Location.X) //TODO tackle when before-detach state where right edge of this window goes past right edge of parent. //{ // temp.Y = _formeLocationBeforeDetach.Y; // temp.X = X_OFFSET; // _formeLocationBeforeDetach = temp; //} //else if (_formeLocationBeforeDetach.X > parentRightEdgeX) //TODO "hit test.." //{ // temp.Y = _formeLocationBeforeDetach.Y; // temp.X = _main.Right - X_OFFSET; // _formeLocationBeforeDetach = temp; //} //This is kinda like eSignal. You attach by clicking on attach icon of window, not by dragging the window to the MDI container. StartPosition = FormStartPosition.WindowsDefaultLocation; MdiParent = _main; attachWindowToolStripMenuItem.Checked = true; detachWindowToolStripMenuItem.Checked = false; _isAttached = true; SetSnapToGrid(false); MdiParent = _main; //Location = _formeLocationBeforeDetach; //UpdateColors(); } else { attachWindowToolStripMenuItem.Checked = true; } } private void detachWindow() { if (this.MdiParent != null) { //inspiration from http://c-sharpened.blogspot.com/2009/07/pop-out-mdi-child.html Point screenLocation = new Point(); _formeLocationBeforeDetach = Location; screenLocation = _main.PointToScreen(Location); MdiParent = null; Location = screenLocation; SetSnapToGrid(true); // Why is this next line required? If I don't call this, the colors revert to the // alternatating gray and white backgrounds. It gets fixed as soon as there is // another update. UpdateColors(); attachWindowToolStripMenuItem.Checked = false; detachWindowToolStripMenuItem.Checked = true; _isAttached = false; } else { attachWindowToolStripMenuItem.Checked = false; detachWindowToolStripMenuItem.Checked = true; } } private void MdiTopListForm_LocationChanged(object sender, EventArgs e) { } public WindowIconCache WindowIconCache { get { throw new NotImplementedException(); } } public bool Pinned { get { throw new NotImplementedException(); } set { throw new NotImplementedException(); } } public void SetSnapToGrid(bool enabled) { //if (enabled) // formSnapper1.Enabled = true; //else // formSnapper1.Enabled = false; } private void MdiTopListForm_Move(object sender, EventArgs e) { //make sure that this window, when moved, won't overlap the menustrip if (MdiParent != null) //window is attached { if (Location.Y < _main.getMenuStripHeight()) { int x = this.Location.X; Point p = new Point(x, _main.getMenuStripHeight()); Location = p; } Point screenLocation = PointToScreen(Location); parentRightEdgeX = _main.Location.X + _main.Width; parentBottomEdgeY = _main.Location.Y + _main.Height; thisWindowRightEdgeX = _main.Location.X + Right + X_RIGHT_OFFSET; thisWindowBottomEdgeY = _main.Location.Y + Bottom + X_BOTTOM_OFFSET; //RESEARCH MORE ABOUT POSITIONING... if (screenLocation.X < _main.Location.X) //move this window past left edge of parent. { detachWindow(); } else if (thisWindowRightEdgeX > parentRightEdgeX) { detachWindow(); } else if (thisWindowBottomEdgeY > parentBottomEdgeY) { detachWindow(); } } } } }