我编写了一个观察Windows.Forms设计器窗口的Visual Studio 2013扩展。当开发人员在设计器窗口中更改控件时,扩展会尝试验证结果是否与我们的ui样式指南一致。如果发现可能的违规,它们将列在工具窗口中。一切正常。但是现在我想在设计器窗口中标记不一致的控件,例如使用红框或类似的东西。
不幸的是,我没有找到在设计器窗口中绘制控件装饰的方法。我知道如果你开发自己的ControlDesigner
,你可以绘制那些装饰品,但我需要从控制设计师的“外部”进行。我只有来自IDesignerHost
的Dte2.ActiveWindow
,可以通过该主机访问Controls和ControlDesigners。我找不到任何方法来从ControlDesigners的“外部”添加装饰品。我现在的解决方法是捕获控件的Paint-Events并尝试从那里绘制我的装饰品。这对所有控件(即ComboBox等)都不适用,因为并非所有控件都可以使用它们。所以我不得不使用他们的父控件的Paint事件。此解决方案还有其他缺点。
我希望有人可以告诉我是否有更好的方法。我很确定必须有一个:如果你使用Menu-> View-> Tab Order(不确定正确的英文菜单标题,我使用的是德语IDE),你可以看到IDE本身就是能够装饰控件(没有截图,因为它是我在SO上的第一篇文章),我确信它不会像我一样使用解决方法。它是如何做到的?
我几周来一直在谷歌上搜索。感谢您的帮助,建议,研究起点......
更新:
也许这个截图有点清晰:
从View菜单中选择Tab顺序时,Visual Studio显示的是蓝色编号的插入符号。我的问题是如何通过IDE完成这项工作。
如上所述,我试图在控件的Paint
事件中执行此操作,但是ComboBox实际上并不支持这一事件。如果我使用父级的Paint
事件,我只能在子控件周围绘制,因为它们是在父级之后绘制的。
我还想过在控件或ControlDesigner
s上使用反射,但我不知道如何挂钩受保护的OnPaintAdornments
方法。我不认为IDE开发人员使用那些“肮脏”的技巧。
我相信你正在寻找BehaviorService建筑。 Behavior解释了Adorner,Glyph和Behavior Service Overview等支持部分的架构和一些例子。例如
扩展设计时用户界面
BehaviorService模型使新功能可以轻松地在现有设计器用户界面上分层。新UI保持独立于其他先前定义的Glyph和Behavior对象。例如,某些控件上的智能标记由控件右上角的Glyph访问(智能标记字形)。
智能标记代码创建自己的Adorner图层,并将Glyph对象添加到此图层。这使智能标记Glyph对象与选择Glyph对象分开。将新的Adorner添加到Adorners集合中的必要代码非常简单。
等等
希望有所帮助。
我终于有时间实施我的解决方案,并希望展示它的完整性。 当然,我减少了代码以仅显示相关部分。
这是我不喜欢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
的RootComponent
获得IDesignerHost
并添加一个新的System.Windows.Forms.Design.Behavior.Adorner
。然后我可以使用我的AddIssue
和RemoveIssue
方法添加和修改我的字形到Adorner
。
这是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);
}
}
可以在接受的答案中发布的链接中研究覆盖的详细信息。
我在控件周围(但内部)绘制一个红色边框,并添加一个包含已发现问题数量的小矩形。
有一点需要注意的是,我检查Control.Visible
是否是true
。因此,当控件是 - 例如 - 在当前未被选中的TabPage上时,我可以避免绘制装饰。
由于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
时,将调用覆盖。
最后,我展示了示例结果的屏幕截图。由于这是通过实际实现完成的,因此工具提示更加先进。该按钮未对齐所有组合框,因为它有点太左:
再次感谢Ivan Stoev指出我正确的解决方案。我希望我能说清楚我是如何实现它的。