Подтвердить что ты не робот

Нарисуйте украшения на 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 использовали эти "грязные" трюки.

4b9b3361

Ответ 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