从扩展程序

时间:2015-11-05 10:50:39

标签: c# wpf visual-studio windows-forms-designer vsix

我写了一个观察Windows.Forms设计器窗口的Visual Studio 2013扩展。当开发人员在设计器窗口中更改控件时,扩展会尝试验证结果是否与我们的ui样式指南一致。如果发现可能的违规,它们将列在工具窗口中。一切正常。 但是现在我想在设计器窗口中标记不一致的控件,例如使用红框或类似的东西。

不幸的是,我没有找到在设计器窗口中绘制控件装饰的方法。我知道如果你开发自己的ControlDesigner,你可以绘制这些装饰品,但我需要在"外面"对照组的设计师。我只有IDesignerHost中的Dte2.ActiveWindow,可以通过该主机访问Controls和ControlDesigners。我找不到任何方法来添加来自"外部" ControlDesigners。 我现在的解决方法是捕获控件的Paint-Events并尝试从那里绘制我的装饰品。这并不适用于所有控件(即ComboBox等),因为并非所有控件都允许您使用它们。所以我不得不使用他们的父控件的Paint事件。此解决方案还有其他缺点。

我希望有人能告诉我是否有更好的方法。我非常确定必须有一个:如果你使用Menu-> View-> Tab Order(不确定正确的英文菜单标题,我使用的是德语IDE),你可以看看IDE本身能够装饰控件(没有截图,因为它是我在SO上的第一篇文章),我确信它没有像我一样使用解决方法。它是如何做到的?

我已经在谷歌上搜索了好几个星期了。感谢您的任何帮助,建议,研究起点......

更新:

使用此屏幕截图可能会更清楚一点:

Screenshot tab order

从View菜单中选择Tab顺序时,Visual Studio显示的是蓝色编号的插入符号。我的问题是如何通过IDE完成这项工作。

如上所述,我尝试在控件的Paint事件中执行此操作,但是ComboBox实际上并不支持该事件。如果我使用父母的Paint事件,我只能画"周围"子控件因为它们是在父级之后绘制的。

我还想过在控件或ControlDesigner上使用反射,但我不确定如何挂钩受保护的OnPaintAdornments方法。而且我不认为IDE开发人员使用那些"脏"技巧。

2 个答案:

答案 0 :(得分:8)

我终于有时间实施我的解决方案并希望完整展示它 当然,我减少了代码以仅显示相关部分。

1。获取BehaviorService

这是我不喜欢service locator (anti) pattern的原因之一。虽然阅读了很多文章,但我并没有想到我可以从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的{​​{1}}获取了RootComponent,并为其添加了新的System.Windows.Forms.Design.Behavior.Adorner。然后,我可以使用IDesignerHostAddIssue方法将我的字形添加和修改为RemoveIssue

2。我的字形实现

以下是Adorner的实现,这是一个继承自`System.Windows.Forms.Design.Behavior.Glyph的类:

MyGlyph

可以在接受的答案中发布的链接中研究覆盖的详细信息 我在控件周围(但在内部)绘制一个红色边框,并添加一个包含已发现问题数量的小矩形 需要注意的一点是,我检查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); } } 是否为Control.Visible。因此,当控件是 - 例如 - 在当前未选中的TabPage上时,我可以避免绘制装饰。

3。我的行为实现

由于true基类的构造函数需要继承自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时,会调用覆盖。

4。结果

最后,我展示了示例结果的屏幕截图。由于这是通过实际实现完成的,因此工具提示更加先进。该按钮未对齐所有组合框,因为它有点太左:

enter image description here

再次感谢Ivan Stoev指出我正确的解决方案。我希望我能说清楚我是如何实现的。

答案 1 :(得分:2)

使用System.Drawing.Graphics.FromHwnd方法,传入设计器窗口的HWND。

通过pinvoke向下钻入视觉工作室的窗口手柄来获取HWND。也许使用像Inspect这样的工具来查找窗口classes以及其他可能有助于您识别正确(设计者)窗口的信息。

我已经编写了一个C#程序来帮助您入门here

screenshot