Нарисуйте украшения на windows.forms.controls в Visual Studio Designer из расширения
Я написал расширение Visual Studio 2013, которое поддерживает конструктор Windows.Forms. Когда разработчик меняет элементы управления в окне дизайнера, расширение пытается убедиться, что результат соответствует нашим рекомендациям по стилю пользовательского интерфейса. Если обнаружены возможные нарушения, они перечислены в окне инструмента. Это все отлично работает. Но теперь я хотел бы отметить несовместимые элементы управления в окне дизайнера, например, красной рамкой или чем-то вроде этого.
К сожалению, я не нашел способа рисовать украшения на элементах управления в окне дизайнера. Я знаю, что вы можете рисовать эти украшения, если разрабатываете свой собственный ControlDesigner
, но мне нужно сделать это "извне" дизайнера элементов управления. У меня есть только IDesignerHost
из Dte2.ActiveWindow
и я могу получить доступ к Controls и ControlDesigners через этот хост. Я не мог найти способ добавить украшения "извне" ControlDesigners. Мой обходной путь на данный момент - поймать Paint-Events элементов управления и попытаться нарисовать оттуда мои украшения. Это не работает хорошо для всех элементов управления (например, ComboBox и т.д.), Потому что не все элементы управления позволяют вам рисовать на них. Поэтому мне пришлось использовать их родительский элемент управления Paint. И у этого решения есть и другие недостатки.
Я надеюсь, что кто-то может сказать мне, если есть лучший способ. Я почти уверен, что должен быть один: если вы используете Menu-> View-> Tab Order (не уверен насчет правильного заголовка меню на английском, я использую немецкую IDE), вы можете видеть, что сама IDE Умеет украшать элементы управления (нет скриншота, потому что это мой первый пост на SO), и я уверен, что он не использует такую работу, как я. Как оно это делает?
Я гуглил это уже несколько недель. Спасибо за любую помощь, совет, исследование отправных точек....
ОБНОВИТЬ:
Может быть, с этим снимком экрана становится немного яснее:
![Screenshot tab order]()
Эти синие пронумерованные вставки - это то, что Visual Studio показывает при выборе порядка табуляции в меню "Вид". И мой вопрос, как это делается в IDE.
Как уже упоминалось, я пытался сделать это в событии Paint
элементов управления, но, например, ComboBox на самом деле не поддерживает это событие. И если я использую родительское событие Paint
я могу рисовать только "вокруг" дочерних элементов управления, потому что они нарисованы после родительского.
Я также думал об использовании отражения на элементах управления или ControlDesigner
, но я не уверен, как подключить защищенный метод OnPaintAdornments
. И я не думаю, что разработчики IDE использовали эти "грязные" трюки.
Ответы
Ответ 1
Я считаю, что вы ищете BehaviorService. Архитектура с поддерживающими частями, такими как Behavior, Adorner и Glyph, и здесь описаны некоторые примеры Обзор службы поведения. Например,
Расширение пользовательского интерфейса времени разработки
Модель BehaviorService позволяет легко добавлять новые функциональные возможности в существующий пользовательский интерфейс разработчика. Новый пользовательский интерфейс остается независимым от других ранее определенных объектов Glyph и Behavior. Например, смарт-теги на некоторых элементах управления доступны с помощью Glyph в верхнем правом углу элемента управления (Smart Tag Glyph).
Код смарт-тега создает собственный слой Adorner и добавляет к этому слою объекты Glyph. Это позволяет изолировать интеллектуальные теги Glyph от объектов Glyph. Необходимый код для добавления нового Adorner в коллекцию Adorners прост.
и т.д..
Надеюсь, что это поможет.
Ответ 2
Наконец-то у меня появилось время реализовать свое решение и хочу показать его для полноты.
Конечно, я сократил код, чтобы показать только соответствующие части.
1. Получение поведенческой услуги
Это одна из причин, почему мне не нравится шаблон поиска служб (анти). Хотя я прочитал много статей, мне не пришло в голову, что я могу получить BehaviorService
от моего IDesignerHost
.
Теперь у меня есть что-то вроде этого класса данных:
public class DesignerIssuesModel
{
private readonly BehaviorService m_BehaviorService;
private readonly Adorner m_Adorner = new Adorner();
private readonly Dictionary<Control, MyGlyph> m_Glyphs = new Dictionary<Control, MyGlyph>();
public IDesignerHost DesignerHost { get; private set; }
public DesignerIssuesModel(IDesignerHost designerHost)
{
DesignerHost = designerHost;
m_BehaviorService = (BehaviorService)DesignerHost.RootComponent.Site.GetService(typeof(BehaviorService));
m_BehaviorService.Adornders.Add(m_Adorner);
}
public void AddIssue(Control control)
{
if (!m_Glyphs.ContainsKey(control))
{
MyGlyph g = new MyGlyph(m_BehaviorService, control);
m_Glyphs[control] = g;
m_Adorner.Glyphs.Add(g);
}
m_Glyphs[control].Issues += 1;
}
public void RemoveIssue(Control control)
{
if (!m_Glyphs.ContainsKey(control)) return;
MyGlyph g = m_Glyphs[control];
g.Issues -= 1;
if (g.Issues > 0) return;
m_Glyphs.Remove(control);
m_Adorner.Glyphs.Remove(g);
}
}
Поэтому я получаю BehaviorService
от RootComponent
компонента IDesignerHost
и добавляю в него новый System.Windows.Forms.Design.Behavior.Adorner
. Затем я могу использовать методы AddIssue
и RemoveIssue
для добавления и изменения моих глифов в Adorner
.
2. Моя реализация Glyph
Вот реализация MyGlyph
, класса, унаследованного от System.Windows.Forms.Design.Behavior.Glyph
:
public class MyGlyph : Glyph
{
private readonly BehaviorService m_BehaviorService;
private readonly Control m_Control;
public int Issues { get; set; }
public Control Control { get { return m_Control; } }
public VolkerIssueGlyph(BehaviorService behaviorService, Control control) : base(new MyBehavior())
{
m_Control = control;
m_BehaviorService = behaviorService;
}
public override Rectangle Bounds
{
get
{
Point p = m_BehaviorService.ControlToAdornerWindow(m_Control);
Graphics g = Graphics.FromHwnd(m_Control.Handle);
SizeF size = g.MeasureString(Issues.ToString(), m_Font);
return new Rectangle(p.X + 1, p.Y + m_Control.Height - (int)size.Height - 2, (int)size.Width + 1, (int)size.Height + 1);
}
}
public override Cursor GetHitTest(Point p)
{
return m_Control.Visible && Bounds.Contains(p) ? Cursors.Cross : null;
}
public override void Paint(PaintEventArgs pe)
{
if (!m_Control.Visible) return;
Point topLeft = m_BehaviorService.ControlToAdornerWindow(m_Control);
using (Pen pen = new Pen(Color.Red, 2))
pe.Graphics.DrawRectangle(pen, topLeft.X, topLeft.Y, m_Control.Width, m_Control.Height);
Rectangle bounds = Bounds;
pe.Graphics.FillRectangle(Brushes.Red, bounds);
pe.Graphics.DrawString(Issues.ToString(), m_Font, Brushes.Black, bounds);
}
}
Подробности переопределений можно изучить по ссылкам, размещенным в принятом ответе.
Я рисую красную рамку вокруг (но внутри) элемента управления и добавляю маленький прямоугольник, содержащий количество найденных проблем.
Стоит отметить, что я проверяю, true
ли Control.Visible
. Поэтому я могу избежать рисования украшений, когда элемент управления находится, например, на вкладке TabPage, которая в данный момент не выбрана.
3. Моя реализация поведения
Поскольку для конструктора базового класса Glyph
требуется экземпляр класса, унаследованного от Behavior
, мне нужно было создать новый класс. Это можно оставить пустым, но я использовал его, чтобы показать всплывающую подсказку, когда мышь входит в прямоугольник, показывающий количество ошибок:
public class MyBehavior : Behavior
{
private static readonly ToolTip ToolTip = new ToolTip
{
ToolTipTitle = "UI guide line issues found",
ToolTipIcon = ToolTipIcon.Warning
};
public override bool OnMouseEnter(Glyph g)
{
MyGlyph glyph = (MyGlyph)g;
if (!glyph.Control.Visible) return false;
lock(ToolTip)
ToolTip.Show(GetText(glyph), glyph.Control, glyph.Control.PointToClient(Control.MousePosition), 2000);
return true;
}
public override bool OnMouseLeave(Glyph g)
{
lock (ToolTip)
ToolTip.Hide(((MyGlyph)g).Control);
return true;
}
private static string GetText(MyGlyph glyph)
{
return string.Format("{0} has {1} conflicts!", glyph.Control.Name, glyph.Issues);
}
}
Переопределения вызываются, когда мышь входит/выходит из Bounds
возвращаемых реализацией MyGlyph
.
4. Результаты
Наконец я показываю скриншот с примером результата. Поскольку это было сделано реальной реализацией, всплывающая подсказка немного более продвинута. Кнопка смещена ко всем выпадающим спискам, потому что она слишком левая:
![enter image description here]()
Еще раз спасибо Ивану Стоеву за то, что он указал мне на правильное решение. Я надеюсь, что смогу прояснить, как я это реализовал.
Ответ 3
Используйте метод System.Drawing.Graphics.FromHwnd, передав HWND для окна конструктора.
Получите HWND, свернув в ручки окна для визуальной студии, через pinvoke. Возможно, используйте такие инструменты, как Осмотреть, чтобы найти окно classes и другую информацию, которая может помочь вам определить правильное окно (дизайнер).
Я написал программу на С#, чтобы вы начали здесь.
![screenshot]()