我写了一个观察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
上使用反射,但我不确定如何挂钩受保护的OnPaintAdornments
方法。而且我不认为IDE开发人员使用那些"脏"技巧。
答案 0 :(得分:8)
我终于有时间实施我的解决方案并希望完整展示它 当然,我减少了代码以仅显示相关部分。
这是我不喜欢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
。然后,我可以使用IDesignerHost
和AddIssue
方法将我的字形添加和修改为RemoveIssue
。
以下是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上时,我可以避免绘制装饰。
由于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
时,会调用覆盖。
最后,我展示了示例结果的屏幕截图。由于这是通过实际实现完成的,因此工具提示更加先进。该按钮未对齐所有组合框,因为它有点太左:
再次感谢Ivan Stoev指出我正确的解决方案。我希望我能说清楚我是如何实现的。
答案 1 :(得分:2)