具有嵌套控件的 DesignMode

在开发控件时,是否有人找到了解决 DesignMode 问题的有用方案?

问题是,如果您嵌套控件,那么 DesignMode 仅适用于第一级。第二级和更低级别的 DesignMode 将始终返回 FALSE。

标准的方法是查看正在运行的进程的名称,如果它是“ DevEnv.EXE”,那么它必须是工作室,因此 DesignMode 是真实的。

问题是寻找 ProcessName 在注册表和其他奇怪的部分中循环,最终的结果是用户可能没有查看进程名所需的权限。此外,这条奇怪的路线非常缓慢。因此,我们必须堆积额外的技巧来使用一个单例,如果在询问进程名称时抛出一个错误,那么假设 DesignMode 是 FALSE。

确定 DesignMode 的一种干净的方法是合适的。实际上,让微软内部修复它的框架会更好!

21868 次浏览

I've never been caught by this myself, but couldn't you just walk back up the Parent chain from the control to see if DesignMode is set anywhere above you?

DesignMode is a private property (from what I can tell). The answer is to provide a public property that exposes the DesignMode prop. Then you can cascasde back up the chain of user controls until you run into a non-user control or a control that is in design mode. Something like this....

  public bool RealDesignMode()
{
if (Parent is MyBaseUserControl)
{
return (DesignMode ? true : (MyBaseUserControl) Parent.RealDesignMode;
}


return DesignMode;
}

Where all your UserControls inherit from MyBaseUserControl. Alternatively you could implement an interface that exposes the "RealDeisgnMode".

Please note this code is not live code, just off the cuff musings. :)

I hadn't realised that you can't call Parent.DesignMode (and I have learned something about 'protected' in C# too...)

Here's a reflective version: (I suspect there might be a performance advantage to making designModeProperty a static field)

static bool IsDesignMode(Control control)
{
PropertyInfo designModeProperty = typeof(Component).
GetProperty("DesignMode", BindingFlags.Instance | BindingFlags.NonPublic);


while (designModeProperty != null && control != null)
{
if((bool)designModeProperty.GetValue(control, null))
{
return true;
}
control = control.Parent;
}
return false;
}

Why don't you check LicenseManager.UsageMode. This property can have the values LicenseUsageMode.Runtime or LicenseUsageMode.Designtime.

Is you want code to only run in runtime, use the following code:

if (LicenseManager.UsageMode == LicenseUsageMode.Runtime)
{
bla bla bla...
}

Revisiting this question, I have now 'discovered' 5 different ways of doing this, which are as follows:

System.ComponentModel.DesignMode property


System.ComponentModel.LicenseManager.UsageMode property


private string ServiceString()
{
if (GetService(typeof(System.ComponentModel.Design.IDesignerHost)) != null)
return "Present";
else
return "Not present";
}


public bool IsDesignerHosted
{
get
{
Control ctrl = this;


while(ctrl != null)
{
if((ctrl.Site != null) && ctrl.Site.DesignMode)
return true;
ctrl = ctrl.Parent;
}
return false;
}
}
public static bool IsInDesignMode()
{
return System.Reflection.Assembly.GetExecutingAssembly()
.Location.Contains("VisualStudio"))
}

To try and get a hang on the three solutions proposed, I created a little test solution - with three projects:

  • TestApp (winforms application),
  • SubControl (dll)
  • SubSubControl (dll)

I then embedded the SubSubControl in the SubControl, then one of each in the TestApp.Form.

This screenshot shows the result when running. Screenshot of running

This screenshot shows the result with the form open in Visual Studio:

Screenshot of not running

Conclusion: It would appear that without reflection the only one that is reliable within the constructor is LicenseUsage, and the only one which is reliable outside the constructor is 'IsDesignedHosted' (by BlueRaja below)

PS: See ToolmakerSteve's comment below (which I haven't tested): "Note that IsDesignerHosted answer has been updated to include LicenseUsage..., so now the test can simply be if (IsDesignerHosted). An alternative approach is test LicenseManager in constructor and cache the result."

From this page:

([Edit 2013] Edited to work in constructors, using the method provided by @hopla)

/// <summary>
/// The DesignMode property does not correctly tell you if
/// you are in design mode.  IsDesignerHosted is a corrected
/// version of that property.
/// (see https://connect.microsoft.com/VisualStudio/feedback/details/553305
/// and http://stackoverflow.com/a/2693338/238419 )
/// </summary>
public bool IsDesignerHosted
{
get
{
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
return true;


Control ctrl = this;
while (ctrl != null)
{
if ((ctrl.Site != null) && ctrl.Site.DesignMode)
return true;
ctrl = ctrl.Parent;
}
return false;
}
}

I've submitted a bug-report with Microsoft; I doubt it will go anywhere, but vote it up anyways, as this is obviously a bug (whether or not it's "by design").

I use the LicenseManager method, but cache the value from the constructor for use throughout the lifetime of the instance.

public MyUserControl()
{
InitializeComponent();
m_IsInDesignMode = (LicenseManager.UsageMode == LicenseUsageMode.Designtime);
}


private bool m_IsInDesignMode = true;
public bool IsInDesignMode { get { return m_IsInDesignMode; } }

VB version:

Sub New()
InitializeComponent()


m_IsInDesignMode = (LicenseManager.UsageMode = LicenseUsageMode.Designtime)
End Sub


Private ReadOnly m_IsInDesignMode As Boolean = True
Public ReadOnly Property IsInDesignMode As Boolean
Get
Return m_IsInDesignMode
End Get
End Property

This is the method I use inside forms:

    /// <summary>
/// Gets a value indicating whether this instance is in design mode.
/// </summary>
/// <value>
///     <c>true</c> if this instance is in design mode; otherwise, <c>false</c>.
/// </value>
protected bool IsDesignMode
{
get { return DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime; }
}

This way, the result will be correct, even if either of DesignMode or LicenseManager properties fail.

Since none of the methods are reliable (DesignMode, LicenseManager) or efficient (Process, recursive checks) I'm using a public static bool Runtime { get; private set } at program level and explicitly setting it inside the Main() method.

We use this code with success:

public static bool IsRealDesignerMode(this Control c)
{
if (System.ComponentModel.LicenseManager.UsageMode == System.ComponentModel.LicenseUsageMode.Designtime)
return true;
else
{
Control ctrl = c;


while (ctrl != null)
{
if (ctrl.Site != null && ctrl.Site.DesignMode)
return true;
ctrl = ctrl.Parent;
}


return System.Diagnostics.Process.GetCurrentProcess().ProcessName == "devenv";
}
}

My suggestion is an optimization of @blueraja-danny-pflughoeft reply. This solution doesn't calculate result every time but only at the first time (an object cannot change UsageMode from design to runtime)

private bool? m_IsDesignerHosted = null; //contains information about design mode state
/// <summary>
/// The DesignMode property does not correctly tell you if
/// you are in design mode.  IsDesignerHosted is a corrected
/// version of that property.
/// (see https://connect.microsoft.com/VisualStudio/feedback/details/553305
/// and https://stackoverflow.com/a/2693338/238419 )
/// </summary>
[Browsable(false)]
public bool IsDesignerHosted
{
get
{
if (m_IsDesignerHosted.HasValue)
return m_IsDesignerHosted.Value;
else
{
if (LicenseManager.UsageMode == LicenseUsageMode.Designtime)
{
m_IsDesignerHosted = true;
return true;
}
Control ctrl = this;
while (ctrl != null)
{
if ((ctrl.Site != null) && ctrl.Site.DesignMode)
{
m_IsDesignerHosted = true;
return true;
}
ctrl = ctrl.Parent;
}
m_IsDesignerHosted = false;
return false;
}
}
}

I had to fight this problem recently in Visual Studio 2017 when using nested UserControls. I combine several of the approaches mentioned above and elsewhere, then tweaked the code until I had a decent extension method which works acceptably so far. It performs a sequence of checks, storing the result in static boolean variables so so each check is only performed at most only once at run time. The process may be overkill, but it is keeping the code from executing in studio. Hope this helps someone.

  public static class DesignTimeHelper
{
private static bool? _isAssemblyVisualStudio;
private static bool? _isLicenseDesignTime;
private static bool? _isProcessDevEnv;
private static bool? _mIsDesignerHosted;


/// <summary>
///   Property <see cref="Form.DesignMode"/> does not correctly report if a nested <see cref="UserControl"/>
///   is in design mode.  InDesignMode is a corrected that property which .
///   (see https://connect.microsoft.com/VisualStudio/feedback/details/553305
///   and https://stackoverflow.com/a/2693338/238419 )
/// </summary>
public static bool InDesignMode(
this Control userControl,
string source = null)
=> IsLicenseDesignTime
|| IsProcessDevEnv
|| IsExecutingAssemblyVisualStudio
|| IsDesignerHosted(userControl);


private static bool IsExecutingAssemblyVisualStudio
=> _isAssemblyVisualStudio
?? (_isAssemblyVisualStudio = Assembly
.GetExecutingAssembly()
.Location.Contains(value: "VisualStudio"))
.Value;


private static bool IsLicenseDesignTime
=> _isLicenseDesignTime
?? (_isLicenseDesignTime = LicenseManager.UsageMode == LicenseUsageMode.Designtime)
.Value;


private static bool IsDesignerHosted(
Control control)
{
if (_mIsDesignerHosted.HasValue)
return _mIsDesignerHosted.Value;


while (control != null)
{
if (control.Site?.DesignMode == true)
{
_mIsDesignerHosted = true;
return true;
}


control = control.Parent;
}


_mIsDesignerHosted = false;
return false;
}


private static bool IsProcessDevEnv
=> _isProcessDevEnv
?? (_isProcessDevEnv = Process.GetCurrentProcess()
.ProcessName == "devenv")
.Value;
}

I solved this in .NET 5 with the following:

public static bool InDesignMode()
{
return Process.GetCurrentProcess().ProcessName.Contains("DesignToolsServer");
}

In .Net 6, the Control.IsAncestorSiteInDesignMode property is available.