// Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // Copyright (c) 2005-2008 Novell, Inc. (http://www.novell.com) // // Authors: // Jonathan Chambers (jonathan.chambers@ansys.com) // Ivan N. Zlatev (contact@i-nz.net) // // // NOT COMPLETE using CustomControls; using System.Collections; using System.ComponentModel; using System.Drawing; using System.Drawing.Design; using System.Windows.Forms.Design; namespace System.Windows.Forms.PropertyGridInternal { internal class PropertyGridView : UserControl, IWindowsFormsEditorService { #region Private Members private const char PASSWORD_PAINT_CHAR = '\u25cf'; // the dot char private const char PASSWORD_TEXT_CHAR = '*'; private int V_INDENT = 16; // dpi dependent private const int ENTRY_SPACING = 2; private const int RESIZE_WIDTH = 3; private int VALUE_PAINT_WIDTH = 19; // dpi dependent private int VALUE_PAINT_INDENT = 22; // dpi dependent private int BOOL_PAINT_WIDTH = 11; private int BOOL_PAINT_INDENT = 15; private double splitter_percent = .5; private int row_height; private int font_height_padding = 3; private PropertyGridTextBox grid_textbox; private PropertyGrid property_grid; private bool resizing_grid; private VScrollBar vbar; private StringFormat string_format; private Font bold_font; private Brush inactive_text_brush; private ListBox dropdown_list; private Drawing.Point last_click; private ToolStripDropDown dropdown; #endregion #region Contructors public PropertyGridView(PropertyGrid propertyGrid) { property_grid = propertyGrid; this.Font = new Font("Segoe UI", 8.5f); bold_font = new Font(this.Font, Drawing.FontStyle.Bold); row_height = Font.Height + font_height_padding; string_format = new StringFormat(); string_format.FormatFlags = StringFormatFlags.NoWrap; string_format.Trimming = StringTrimming.None; grid_textbox = new PropertyGridTextBox(); grid_textbox.DropDownButtonClicked += DropDownButtonClicked; grid_textbox.DialogButtonClicked += DialogButtonClicked; vbar = new VScrollBar(); vbar.Visible = false; vbar.Value = 0; vbar.ValueChanged += VScrollBar_HandleValueChanged; vbar.Dock = DockStyle.Right; this.Controls.Add(vbar); grid_textbox.Visible = false; grid_textbox.Font = this.Font; grid_textbox.BackColor = Drawing.SystemColors.Window; grid_textbox.ToggleValue += grid_textbox_ToggleValue; grid_textbox.KeyDown += grid_textbox_KeyDown; grid_textbox.MouseEnter += (s, e) => this.Cursor = Cursors.Default; // fix issue with splitter cursor grid_textbox.Validate += grid_textbox_Validate; this.Controls.Add(grid_textbox); resizing_grid = false; inactive_text_brush = new SolidBrush(Drawing.SystemColors.GrayText); BackColorChanged += new System.EventHandler(RedrawEvent); SetStyle(ControlStyles.Selectable, true); SetStyle(ControlStyles.DoubleBuffer, true); SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.ResizeRedraw, true); UpdateDpiDependencies(); } #endregion private GridEntry RootGridItem { get { return (GridEntry)property_grid.RootGridItem; } } private GridEntry SelectedGridItem { get { return (GridEntry)property_grid.SelectedGridItem; } set { property_grid.SelectedGridItem = value; } } #region Protected Instance Methods protected override void OnFontChanged(EventArgs e) { base.OnFontChanged(e); bold_font = new Font(this.Font, Drawing.FontStyle.Bold); row_height = Font.Height + font_height_padding; } protected override void OnRightToLeftChanged(EventArgs e) { base.OnRightToLeftChanged(e); if (RightToLeft == RightToLeft.Yes) { string_format.FormatFlags |= StringFormatFlags.DirectionRightToLeft; vbar.Dock = DockStyle.Left; } else { string_format.FormatFlags &= ~StringFormatFlags.DirectionRightToLeft; vbar.Dock = DockStyle.Right; } } private void InvalidateItemLabel(GridEntry item) { Invalidate(new Rectangle(0, item.Top, SplitterLocation, row_height)); } private void InvalidateItem(GridEntry item) { if (item == null) return; Rectangle rect = new Rectangle(0, item.Top, Width, row_height); Invalidate(rect); if (item.Expanded) { rect = new Rectangle(0, item.Top + row_height, Width, Height - (item.Top + row_height)); Invalidate(rect); } } // [+] expanding is handled in OnMouseDown, so in order to prevent // duplicate expanding ignore it here. // protected override void OnDoubleClick(EventArgs e) { if (this.SelectedGridItem != null && this.SelectedGridItem.Expandable && !this.SelectedGridItem.PlusMinusBounds.Contains(last_click)) this.SelectedGridItem.Expanded = !this.SelectedGridItem.Expanded; else ToggleValue(this.SelectedGridItem); } protected override void OnPaint(PaintEventArgs e) { // Background e.Graphics.FillRectangle(ResPool.GetSolidBrush(BackColor), ClientRectangle); int yLoc = -vbar.Value * row_height; if (this.RootGridItem != null) DrawGridItems(this.RootGridItem.GridItems, e, 1, ref yLoc); if (property_grid.BorderStyle != BorderStyle.None) this.DrawVisualStyleBorder(e.Graphics, new Rectangle(0, 0, Width - 1, Height - 1)); UpdateScrollBar(); } protected override void OnMouseWheel(MouseEventArgs e) { if (vbar == null || !vbar.Visible) return; if (e.Delta < 0) vbar.Value = Math.Min(vbar.Maximum - GetVisibleRowsCount() + 1, vbar.Value + 3); else vbar.Value = Math.Max(0, vbar.Value - 3); base.OnMouseWheel(e); } protected override void OnMouseMove(MouseEventArgs e) { if (this.RootGridItem == null) return; if (resizing_grid) { int loc = Math.Max(e.X, 2 * V_INDENT); SplitterPercent = 1.0 * loc / Width; } if (e.X > SplitterLocation - RESIZE_WIDTH && e.X < SplitterLocation + RESIZE_WIDTH) this.Cursor = Cursors.VSplit; else this.Cursor = Cursors.Default; base.OnMouseMove(e); } protected override void OnMouseDown(MouseEventArgs e) { base.OnMouseDown(e); last_click = e.Location; if (this.RootGridItem == null) return; if (e.X > SplitterLocation - RESIZE_WIDTH && e.X < SplitterLocation + RESIZE_WIDTH) { resizing_grid = true; } else { int offset = -vbar.Value * row_height; GridItem foundItem = GetSelectedGridItem(this.RootGridItem.GridItems, e.Y, ref offset); if (foundItem != null) { if (foundItem.Expandable && ((GridEntry)foundItem).PlusMinusBounds.Contains(e.X, e.Y)) foundItem.Expanded = !foundItem.Expanded; this.SelectedGridItem = (GridEntry)foundItem; if (!GridLabelHitTest(e.X)) { if (this.SelectedGridItem.IsBool) ToggleValue(this.SelectedGridItem); else { // send mouse down so we get the carret under cursor grid_textbox.SendMouseDown(e.X - grid_textbox.Left); } } } } } protected override void OnMouseUp(MouseEventArgs e) { resizing_grid = false; base.OnMouseUp(e); } protected override void OnResize(EventArgs e) { base.OnResize(e); if (this.SelectedGridItem != null) // initialized already UpdateView(); } internal override void AfterDpiChange(float rescale) { UpdateDpiDependencies(); base.AfterDpiChange(rescale); } private void UpdateDpiDependencies() { V_INDENT = _dpi(16); VALUE_PAINT_WIDTH = _dpi(19); VALUE_PAINT_INDENT = _dpi(22); BOOL_PAINT_WIDTH = _dpi(11); BOOL_PAINT_INDENT = _dpi(15); } private void UnfocusSelection() { Focus(); } private void FocusSelection() { grid_textbox.Focus(); } protected override bool ProcessDialogKey(Keys keyData) { GridEntry selectedItem = this.SelectedGridItem; if (selectedItem != null && grid_textbox.Visible) { switch (keyData) { case Keys.Enter: if (TrySetEntry(selectedItem, grid_textbox.Text)) UnfocusSelection(); return true; case Keys.Escape: if (selectedItem.IsEditable) UpdateItem(selectedItem); // reset value UnfocusSelection(); return true; case Keys.Tab: FocusSelection(); grid_textbox.SelectAll(); return true; default: return false; } } return base.ProcessDialogKey(keyData); } private bool TrySetEntry(GridEntry entry, object value) { if (entry == null || grid_textbox.Text.Equals(entry.ValueText)) return true; if (entry.IsEditable || !entry.IsEditable && (entry.HasCustomEditor || entry.AcceptedValues != null) || !entry.IsMerged || entry.HasMergedValue || (!entry.HasMergedValue && grid_textbox.Text != String.Empty)) { string error = null; bool changed = entry.SetValue(value, out error); if (!changed && error != null) { if (property_grid.ShowError(error, MessageBoxButtons.OKCancel) == DialogResult.Cancel) { UpdateItem(entry); // restore value, repaint, etc UnfocusSelection(); } return false; } else if (changed) { entry = SelectedGridItem; } } UpdateItem(entry); // restore value, repaint, etc return true; } protected override bool IsInputKey(Keys keyData) { switch (keyData) { case Keys.Left: case Keys.Right: case Keys.Enter: case Keys.Escape: case Keys.Up: case Keys.Down: case Keys.PageDown: case Keys.PageUp: case Keys.Home: case Keys.End: return true; default: return false; } } private GridEntry MoveUpFromItem(GridEntry item, int up_count) { GridItemCollection items; int index; /* move back up the visible rows (and up the hierarchy as necessary) until up_count == 0, or we reach the top of the display */ while (up_count > 0) { items = item.Parent != null ? item.Parent.GridItems : this.RootGridItem.GridItems; index = items.IndexOf(item); if (index == 0) { if (item.Parent.GridItemType == GridItemType.Root) // we're at the top row return item; item = (GridEntry)item.Parent; up_count--; } else { GridEntry prev_item = (GridEntry)items[index - 1]; if (prev_item.Expandable && prev_item.Expanded && prev_item.GridItems.Count > 0) { item = (GridEntry)prev_item.GridItems[prev_item.GridItems.Count - 1]; } else { item = prev_item; } up_count--; } } return item; } private GridEntry MoveDownFromItem(GridEntry item, int down_count) { while (down_count > 0) { /* if we're a parent node and we're expanded, move to our first child */ if (item.Expandable && item.Expanded && item.GridItems.Count > 0) { item = (GridEntry)item.GridItems[0]; down_count--; } else { GridItem searchItem = item; GridItemCollection searchItems = searchItem.Parent.GridItems; int searchIndex = searchItems.IndexOf(searchItem); while (searchIndex == searchItems.Count - 1) { searchItem = searchItem.Parent; if (searchItem == null || searchItem.Parent == null) break; searchItems = searchItem.Parent.GridItems; searchIndex = searchItems.IndexOf(searchItem); } if (searchIndex == searchItems.Count - 1) { /* if we got all the way back to the root with no nodes after us, the original item was the last one */ return item; } else { item = (GridEntry)searchItems[searchIndex + 1]; down_count--; } } } return item; } protected override void OnKeyDown(KeyEventArgs e) { GridEntry selectedItem = this.SelectedGridItem; if (selectedItem == null) { /* XXX not sure what MS does, but at least we shouldn't crash */ base.OnKeyDown(e); return; } switch (e.KeyData & Keys.KeyCode) { case Keys.Left: if (e.Control) { if (SplitterLocation > 2 * V_INDENT) SplitterPercent -= 0.01; e.Handled = true; break; } else { /* if the node is expandable and is expanded, collapse it. otherwise, act just like the user pressed up */ if (selectedItem.Expandable && selectedItem.Expanded) { selectedItem.Expanded = false; e.Handled = true; break; } else goto case Keys.Up; } case Keys.Right: if (e.Control) { if (SplitterLocation < Width) SplitterPercent += 0.01; e.Handled = true; break; } else { /* if the node is expandable and not expanded, expand it. otherwise, act just like the user pressed down */ if (selectedItem.Expandable && !selectedItem.Expanded) { selectedItem.Expanded = true; e.Handled = true; break; } else goto case Keys.Down; } case Keys.Enter: /* toggle the expanded state of the selected item */ if (selectedItem.Expandable) { selectedItem.Expanded = !selectedItem.Expanded; } e.Handled = true; break; case Keys.Up: this.SelectedGridItem = MoveUpFromItem(selectedItem, 1); e.Handled = true; break; case Keys.Down: this.SelectedGridItem = MoveDownFromItem(selectedItem, 1); e.Handled = true; break; case Keys.PageUp: this.SelectedGridItem = MoveUpFromItem(selectedItem, vbar.LargeChange); e.Handled = true; break; case Keys.PageDown: this.SelectedGridItem = MoveDownFromItem(selectedItem, vbar.LargeChange); e.Handled = true; break; case Keys.End: /* find the last, most deeply nested visible item */ GridEntry item = (GridEntry)this.RootGridItem.GridItems[this.RootGridItem.GridItems.Count - 1]; while (item.Expandable && item.Expanded) item = (GridEntry)item.GridItems[item.GridItems.Count - 1]; this.SelectedGridItem = item; e.Handled = true; break; case Keys.Home: this.SelectedGridItem = (GridEntry)this.RootGridItem.GridItems[0]; e.Handled = true; break; } if (!e.Handled) { if (selectedItem != null && selectedItem.IsEditable && grid_textbox.Visible && Focused) { grid_textbox.Text = ""; FocusSelection(); } } base.OnKeyDown(e); } #endregion #region Private Helper Methods private int SplitterLocation { get { return (int)(splitter_percent * Width); } } private double SplitterPercent { set { int old_splitter_location = SplitterLocation; splitter_percent = Math.Max(Math.Min(value, .9), .1); if (old_splitter_location != SplitterLocation) { int x = old_splitter_location > SplitterLocation ? SplitterLocation : old_splitter_location; Invalidate(new Rectangle(x, 0, Width - x - (vbar.Visible ? vbar.Width : 0), Height)); UpdateItem(this.SelectedGridItem); } } get { return splitter_percent; } } private bool GridLabelHitTest(int x) { bool rtl = RightToLeft == RightToLeft.Yes; if (!rtl && x >= 0 && x <= SplitterLocation) return true; if (rtl && x >= SplitterLocation && x <= Width) return true; return false; } private GridItem GetSelectedGridItem(GridItemCollection grid_items, int y, ref int current) { foreach (GridItem child_grid_item in grid_items) { if (y > current && y < current + row_height) { return child_grid_item; } current += row_height; if (child_grid_item.Expanded) { GridItem foundItem = GetSelectedGridItem(child_grid_item.GridItems, y, ref current); if (foundItem != null) return foundItem; } } return null; } private int GetVisibleItemsCount(GridEntry entry) { if (entry == null) return 0; int count = 0; foreach (GridEntry e in entry.GridItems) { count += 1; if (e.Expandable && e.Expanded) count += GetVisibleItemsCount(e); } return count; } private int GetVisibleRowsCount() { if (Height <= 0) return 0; return (this.Height - this.Padding.Vertical) / row_height; } private void UpdateScrollBar() { if (this.RootGridItem == null) return; int visibleRows = GetVisibleRowsCount(); int openedItems = GetVisibleItemsCount(this.RootGridItem); if (openedItems > visibleRows) { vbar.Visible = true; vbar.SmallChange = 1; vbar.LargeChange = visibleRows; vbar.Maximum = Math.Max(0, openedItems); } else { vbar.Value = 0; vbar.Visible = false; } UpdateGridTextBoxBounds(this.SelectedGridItem); } #region Drawing Code private void DrawGridItems(GridItemCollection grid_items, PaintEventArgs pevent, int depth, ref int yLoc) { foreach (GridItem grid_item in grid_items) { DrawGridItem((GridEntry)grid_item, pevent, depth, ref yLoc); if (grid_item.Expanded) DrawGridItems(grid_item.GridItems, pevent, (grid_item.GridItemType == GridItemType.Category) ? depth : depth + 1, ref yLoc); } } private void DrawGridItemLabel(GridEntry grid_item, PaintEventArgs pevent, int depth, Rectangle rect) { bool rtl = RightToLeft == RightToLeft.Yes; Font font = this.Font; Brush brush; if (grid_item.GridItemType == GridItemType.Category) { font = bold_font; brush = ResPool.GetSolidBrush(property_grid.CategoryForeColor); } else { if (grid_item == this.SelectedGridItem) { Rectangle highlight = rect; if (depth > 1) { highlight.X -= rtl ? 0 : (depth - 1) * V_INDENT; highlight.Width += (depth - 1) * V_INDENT; } pevent.Graphics.FillRectangle(SystemBrushes.Highlight, highlight); // Label brush = SystemBrushes.HighlightText; } else { brush = grid_item.IsReadOnly && !grid_item.Expandable ? inactive_text_brush : SystemBrushes.ControlText; } } pevent.Graphics.DrawString(grid_item.Label, font, brush, new Rectangle(rect.X + 1, rect.Y + ENTRY_SPACING, rect.Width - ENTRY_SPACING, rect.Height - ENTRY_SPACING), string_format); } private void DrawBoolValue(GridEntry grid_item, Graphics g, Rectangle r) { if (grid_item.Value is bool b && b == true) { g.FillRectangle(Brushes.Gray, r); g.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias; g.DrawLines(ResPool.GetPen(Color.White, _dpi(1f)), new PointF[] { new PointF(r.X + _dpi(2.5f), r.Y + r.Height / 2), new PointF(r.X + r.Width / 2 - _dpi(0.5f), r.Bottom - _dpi(4f)), new PointF(r.Right - _dpi(2.5f), r.Y + _dpi(3f)) }); g.SmoothingMode = Drawing.Drawing2D.SmoothingMode.None; } g.DrawRectangle(Pens.Gray, r); } private void DrawPaintValue(GridEntry grid_item, Graphics g, Rectangle r) { grid_item.PaintValue(g, r); g.DrawRectangle(Pens.Black, r); } private void DrawGridItemValue(GridEntry grid_item, PaintEventArgs pevent, int y) { if (grid_item.PropertyDescriptor == null) return; bool rtl = RightToLeft == RightToLeft.Yes; int vBarWidth = vbar.Visible ? vbar.Width : 0; int xLoc = (rtl ? vBarWidth : SplitterLocation) + ENTRY_SPACING; int widthAdj = rtl ? 0 : vBarWidth; if (grid_item.IsBool) { int xRect = rtl ? SplitterLocation - BOOL_PAINT_INDENT : xLoc + _dpi(2); DrawBoolValue(grid_item, pevent.Graphics, new Rectangle(xRect, y + (row_height - BOOL_PAINT_WIDTH) / 2, BOOL_PAINT_WIDTH, BOOL_PAINT_WIDTH)); xLoc += rtl ? 0 : BOOL_PAINT_INDENT; widthAdj += rtl ? BOOL_PAINT_INDENT : 0; } if (grid_item.PaintValueSupported) { int xRect = rtl ? SplitterLocation - VALUE_PAINT_WIDTH - ENTRY_SPACING * 2 : xLoc; DrawPaintValue(grid_item, pevent.Graphics, new Rectangle(xRect, y + 2, VALUE_PAINT_WIDTH + 1, row_height - ENTRY_SPACING * 2)); xLoc += rtl ? 0 : VALUE_PAINT_INDENT; widthAdj += rtl ? VALUE_PAINT_INDENT + ENTRY_SPACING : 0; } Font font = this.Font; if (grid_item.IsResetable || !grid_item.HasDefaultValue) font = bold_font; Brush brush = grid_item.IsReadOnly && !grid_item.Expandable ? inactive_text_brush : SystemBrushes.ControlText; string valueText = String.Empty; if (!grid_item.IsMerged || grid_item.IsMerged && grid_item.HasMergedValue) { if (grid_item.IsPassword) valueText = new String(PASSWORD_PAINT_CHAR, grid_item.ValueText.Length); else valueText = grid_item.ValueText; } pevent.Graphics.DrawString(valueText, font, brush, new RectangleF(xLoc, y + ENTRY_SPACING, (rtl ? SplitterLocation - ENTRY_SPACING : Width) - xLoc - widthAdj - 2, row_height - ENTRY_SPACING * 2), string_format); } private void DrawGridItem(GridEntry grid_item, PaintEventArgs pevent, int depth, ref int yLoc) { bool rtl = RightToLeft == RightToLeft.Yes; if (yLoc > -row_height && yLoc < Height) { // Left column pevent.Graphics.FillRectangle(ResPool.GetSolidBrush(property_grid.LineColor), rtl ? Width - V_INDENT : 0, yLoc, V_INDENT, row_height); if (grid_item.GridItemType == GridItemType.Category) { var rect = new Rectangle(rtl ? 0 : depth * V_INDENT, yLoc, Width - (depth * V_INDENT), row_height); pevent.Graphics.FillRectangle(ResPool.GetSolidBrush(property_grid.LineColor), rect); DrawGridItemLabel(grid_item, pevent, depth, rect); } else { int labelWidth = (rtl ? Width - SplitterLocation : SplitterLocation) - depth * V_INDENT; DrawGridItemLabel(grid_item, pevent, depth, new Rectangle(rtl ? SplitterLocation : depth * V_INDENT, yLoc, labelWidth, row_height)); DrawGridItemValue(grid_item, pevent, yLoc); Pen pen = ResPool.GetPen(property_grid.LineColor); // vertical divider line pevent.Graphics.DrawLine(pen, SplitterLocation, yLoc, SplitterLocation, yLoc + row_height); // draw the horizontal line pevent.Graphics.DrawLine(pen, 0, yLoc + row_height, Width, yLoc + row_height); } if (grid_item.Expandable) { int y = yLoc + row_height / 2; grid_item.PlusMinusBounds = DrawPlusMinus(pevent.Graphics, rtl ? Width - (depth - 1) * V_INDENT - ENTRY_SPACING : (depth - 1) * V_INDENT, y, grid_item.Expanded, grid_item == this.SelectedGridItem, depth - 1); } } grid_item.Top = yLoc; yLoc += row_height; } private Rectangle DrawPlusMinus(Graphics g, int x, int y, bool expanded, bool selected, int level) { Color color = selected && level > 0 ? Drawing.SystemColors.HighlightText : property_grid.ViewForeColor; Pen pen = ResPool.GetPen(color, _dpi(1.5f)); g.SmoothingMode = Drawing.Drawing2D.SmoothingMode.AntiAlias; bool rtl = RightToLeft == RightToLeft.Yes; int _rtl = rtl ? -1 : 1; int _4 = _dpi(4); int _8 = _dpi(8); int _10 = _dpi(10); Rectangle bounds = new Rectangle(rtl ? x - _10 : x + _dpi(2), y - _4, _10, _10); if (!expanded) { x += _dpi(5) * _rtl; y -= _4; g.DrawLines(pen, new PointF[] { new PointF(x, y), new PointF(x + _4 * _rtl, y + _4), new PointF(x, y + _8) }); } else { x += _dpi(3) * _rtl; y -= _dpi(1); g.DrawLines(pen, new PointF[] { new PointF(x, y), new PointF(x + _4 * _rtl, y + _4), new PointF(x + _8 * _rtl, y) }); } g.SmoothingMode = Drawing.Drawing2D.SmoothingMode.None; return bounds; } #endregion #region Event Handling private void RedrawEvent(object sender, System.EventArgs e) { Refresh(); } #endregion private void listBox_MouseUp(object sender, MouseEventArgs e) { AcceptListBoxSelection(sender); } private void listBox_KeyDown(object sender, KeyEventArgs e) { switch (e.KeyData & Keys.KeyCode) { case Keys.Enter: AcceptListBoxSelection(sender); return; case Keys.Escape: CloseDropDown(); return; } } private void AcceptListBoxSelection(object sender) { GridEntry entry = this.SelectedGridItem; if (entry != null) { grid_textbox.Text = (string)((ListBox)sender).SelectedItem; CloseDropDown(); if (TrySetEntry(entry, grid_textbox.Text)) UnfocusSelection(); } } private void DropDownButtonClicked(object sender, EventArgs e) { DropDownEdit(); } private void DropDownEdit() { GridEntry entry = this.SelectedGridItem; if (entry == null) return; if (dropdown != null && dropdown.Visible) { CloseDropDown(); return; } if (entry.HasCustomEditor) { entry.EditValue(this); } else { ICollection std_values = entry.AcceptedValues; if (std_values != null) { if (dropdown_list == null) { dropdown_list = new ListBox(); dropdown_list.BorderStyle = BorderStyle.None; dropdown_list.DrawMode = DrawMode.OwnerDrawFixed; dropdown_list.DrawItem += (s, e) => { e.DrawBackground(); TextRenderer.DrawText(e.Graphics, dropdown_list.Items[e.Index].ToString(), e.Font, e.Bounds, e.ForeColor, TextFormatFlags.VerticalCenter); }; dropdown_list.KeyDown += new KeyEventHandler(listBox_KeyDown); dropdown_list.MouseUp += new MouseEventHandler(listBox_MouseUp); } float rescale = (float)dropdown_list.DeviceDpi / DeviceDpi; dropdown_list.Items.Clear(); dropdown_list.ItemHeight = (int)(row_height * rescale); int selected_index = 0; int i = 0; string valueText = entry.ValueText; foreach (object obj in std_values) { dropdown_list.Items.Add(obj); if (valueText != null && valueText.Equals(obj)) selected_index = i; i++; } dropdown_list.Height = dropdown_list.ItemHeight * Math.Min(dropdown_list.Items.Count, 10) + _dpi(2); dropdown_list.Width = Width - SplitterLocation - (vbar.Visible ? vbar.Width : 0) - 1; if (std_values.Count > 0) dropdown_list.SelectedIndex = selected_index; DropDownControl(dropdown_list); } } } private void DialogButtonClicked(object sender, EventArgs e) { GridEntry entry = this.SelectedGridItem as GridEntry; if (entry != null && entry.HasCustomEditor) entry.EditValue((IWindowsFormsEditorService)this); } private void VScrollBar_HandleValueChanged(object sender, EventArgs e) { UpdateView(); } private void grid_textbox_ToggleValue(object sender, EventArgs args) { ToggleValue(this.SelectedGridItem); } private void grid_textbox_KeyDown(object sender, KeyEventArgs e) { switch (e.KeyData & Keys.KeyCode) { case Keys.Down: if (e.Alt) { DropDownEdit(); e.Handled = true; } break; } } private void grid_textbox_Validate(object sender, CancelEventArgs args) { if (!TrySetEntry(this.SelectedGridItem, grid_textbox.Text)) args.Cancel = true; } private void ToggleValue(GridEntry entry) { if (entry != null && !entry.IsReadOnly && entry.GridItemType == GridItemType.Property) entry.ToggleValue(); } internal void UpdateItem(GridEntry entry) { if (entry == null || entry.GridItemType == GridItemType.Category || entry.GridItemType == GridItemType.Root) { grid_textbox.Visible = false; InvalidateItem(entry); return; } if (this.SelectedGridItem == entry) { SuspendLayout(); grid_textbox.Visible = false; if (entry.IsResetable || !entry.HasDefaultValue) grid_textbox.Font = bold_font; else grid_textbox.Font = this.Font; if (entry.IsReadOnly) { grid_textbox.DropDownButtonVisible = false; grid_textbox.DialogButtonVisible = false; grid_textbox.ReadOnly = true; grid_textbox.ForeColor = entry.Expandable ? Drawing.SystemColors.ControlText : Drawing.SystemColors.GrayText; } else { grid_textbox.DropDownButtonVisible = entry.EditorStyle == UITypeEditorEditStyle.DropDown || entry.AcceptedValues != null; grid_textbox.DialogButtonVisible = entry.EditorStyle == UITypeEditorEditStyle.Modal; grid_textbox.ForeColor = Drawing.SystemColors.ControlText; grid_textbox.ReadOnly = !entry.IsEditable; } UpdateGridTextBoxBounds(entry); grid_textbox.PasswordChar = entry.IsPassword ? PASSWORD_TEXT_CHAR : '\0'; grid_textbox.Text = entry.IsMerged && !entry.HasMergedValue ? String.Empty : entry.ValueText; grid_textbox.Visible = true; InvalidateItem(entry); ResumeLayout(false); } else { grid_textbox.Visible = false; } } private void UpdateGridTextBoxBounds(GridEntry entry) { if (entry == null || this.RootGridItem == null) return; bool rtl = RightToLeft == RightToLeft.Yes; int y = -vbar.Value * row_height; CalculateItemY(entry, this.RootGridItem.GridItems, ref y); int vbarWidth = vbar.Visible ? vbar.Width : 0; int pvWidth = ENTRY_SPACING; if (entry.PaintValueSupported) pvWidth += VALUE_PAINT_INDENT + (rtl ? ENTRY_SPACING : 0); if (entry.IsBool) pvWidth += BOOL_PAINT_INDENT + (rtl ? ENTRY_SPACING : 0); int x = rtl ? vbarWidth + 1 : SplitterLocation + pvWidth; int width = rtl ? SplitterLocation - x - pvWidth : Width - x - vbarWidth - 1; int height = row_height - 1; if (y == 0 && DeviceDpi > 120) { y = 1; height -= 1; } grid_textbox.SetBounds(x, y + 1, width, height); } // Calculates the sum of the heights of all items before the one // private bool CalculateItemY(GridEntry entry, GridItemCollection items, ref int y) { foreach (GridItem item in items) { if (item == entry) return true; y += row_height; if (item.Expandable && item.Expanded) if (CalculateItemY(entry, item.GridItems, ref y)) return true; } return false; } private void ScrollToItem(GridEntry item) { if (item == null || this.RootGridItem == null) return; int itemY = -vbar.Value * row_height; int value = vbar.Value; ; CalculateItemY(item, this.RootGridItem.GridItems, ref itemY); if (itemY < 0) // the new item is above the viewable area value += itemY / row_height; else if (itemY + row_height > Height) // the new item is below the viewable area value += ((itemY + row_height) - Height) / row_height + 1; if (value >= vbar.Minimum && value <= vbar.Maximum) vbar.Value = value; } internal void SelectItem(GridEntry oldItem, GridEntry newItem) { if (oldItem != null) { InvalidateItemLabel(oldItem); } if (newItem != null) { UpdateItem(newItem); ScrollToItem(newItem); } else { grid_textbox.Visible = false; vbar.Visible = false; } } internal void UpdateView() { UpdateScrollBar(); Invalidate(); Update(); UpdateItem(this.SelectedGridItem); } internal void ExpandItem(GridEntry item) { UpdateItem(this.SelectedGridItem); Invalidate(new Rectangle(0, item.Top, Width, Height - item.Top)); } internal void CollapseItem(GridEntry item) { UpdateItem(this.SelectedGridItem); Invalidate(new Rectangle(0, item.Top, Width, Height - item.Top)); } private void ShowDropDownControl(Control control, bool resizeable) { float rescale = (float)control.DeviceDpi / this.DeviceDpi; control.Width = (int)(Math.Max(grid_textbox.Width * rescale, control.Width)); control.Font = new Font(Font.FontFamily, Font.Size * rescale, Font.Style); control.RightToLeft = RightToLeft; dropdown = new ToolStripDropDown(); dropdown.Items.Add(new ToolStripControlHost(control)); if (resizeable) { dropdown.Items.Add(new ToolStripControlHost(new DropDownResizer(control))); } int x = 0; if (RightToLeft == RightToLeft.Yes) { x = SystemInformation.MenuDropAlignment ? 0 : control.Width + _dpi(ENTRY_SPACING + 1); } else { x = SystemInformation.MenuDropAlignment ? grid_textbox.Width : (grid_textbox.Width - (int)(control.Width / rescale)) - _dpi(ENTRY_SPACING); } dropdown.Show(grid_textbox, x, row_height - 2); dropdown.Closed += (s, e) => { // control is reused so we need to detach it from parent (s as ToolStripDropDown).Items.Clear(); // SWF consistency: return correct size to the calling code (in case we need to save the control size) if (resizeable) control.Size = new Drawing.Size((int)(control.Width * rescale), (int)(control.Height * rescale)); }; // return to the calling method when dropdown is closed while (dropdown != null && dropdown.Visible) { Application.DoEvents(); } dropdown = null; } #endregion #region IWindowsFormsEditorService Members public void CloseDropDown() { dropdown?.Close(); dropdown = null; } public void DropDownControl(Control control) { bool resizeable = this.SelectedGridItem != null ? SelectedGridItem.EditorResizeable : false; ShowDropDownControl(control, resizeable); } public System.Windows.Forms.DialogResult ShowDialog(Form dialog) { return dialog.ShowDialog(); } #endregion private class DropDownResizer : UserControl { private Control otherControl; protected override void OnMouseMove(MouseEventArgs e) { base.OnMouseMove(e); int _16 = _dpi(16); bool rtl = RightToLeft == RightToLeft.Yes; if (e.Button == MouseButtons.None) { if (e.X < _16 && !rtl) Cursor = Cursors.SizeNESW; else if (e.X > Width - _16 && rtl) Cursor = Cursors.SizeNWSE; else Cursor = Cursors.Default; } if (e.Button == MouseButtons.Left) { var popup = WpfHelper.FindParent(this.control); if (Cursor == Cursors.SizeNESW) { if (!SystemInformation.MenuDropAlignment) popup.HorizontalOffset += e.X / DpiScale; Width -= e.X; } else if (Cursor == Cursors.SizeNWSE) { if (!SystemInformation.MenuDropAlignment) popup.HorizontalOffset += (Width - e.X) / DpiScale; Width = e.X; } otherControl.Width = Width; otherControl.Height += e.Y; } } protected override void OnPaint(PaintEventArgs e) { Graphics g = e.Graphics; Pen p = Pens.Gray; int _4 = _dpi(4); int _8 = _dpi(8); int _12 = _dpi(12); int _16 = _dpi(16); g.DrawLine(p, 0, 0, Width, 0); if (RightToLeft == RightToLeft.Yes) { g.DrawLine(p, Width, _8, Width - _8, _16); g.DrawLine(p, Width, _12, Width - _4, _16); } else { g.DrawLine(p, 0, _8, _8, _16); g.DrawLine(p, 0, _12, _4, _16); } } public DropDownResizer(Control otherControl) { this.otherControl = otherControl; RightToLeft = otherControl.RightToLeft; Height = _dpi(16); Width = otherControl.Width; } } } }