DesignMode с вложенными элементами управления
Кто-нибудь нашел полезное решение проблемы DesignMode при разработке элементов управления?
Проблема в том, что если вы вкладываете элементы управления, то DesignMode работает только для первого уровня. Второй и нижний уровни DesignMode всегда будут возвращать FALSE.
Стандартный взлом состоял в том, чтобы посмотреть на имя процесса, который выполняется, и если это "DevEnv.EXE", то он должен быть студийным, поэтому DesignMode действительно TRUE.
Проблема с поиском ProcessName проходит через реестр и другие странные части, в результате чего пользователь может не иметь необходимых прав для просмотра имени процесса. Кроме того, этот странный маршрут очень медленный. Таким образом, нам пришлось накапливать дополнительные хаки, чтобы использовать одиночный код, и если при запросе имени процесса выдается ошибка, тогда предположим, что DesignMode равен FALSE.
Хороший чистый способ определить DesignMode в порядке. Если бы Microsoft исправила это внутренне, это было бы еще лучше!
14 ответов
Возвращаясь к этому вопросу, я "открыл" 5 различных способов сделать это, а именно:
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"))
}
Чтобы попытаться освоить три предложенных решения, я создал небольшое тестовое решение с тремя проектами:
- TestApp (приложение winforms),
- SubControl (dll)
- SubSubControl (dll)
Затем я встроил SubSubControl в SubControl, затем по одному в TestApp.Form.
Этот скриншот показывает результат при запуске.
Этот снимок экрана показывает результат с формой, открытой в Visual Studio:
Вывод: может показаться, что без отражения единственным надежным в конструкторе является LicenseUsage, а единственным надежным вне конструктора является IsDesignedHosted (ниже BlueRaja)
PS: См. Комментарий ToolmakerSteve ниже (который я не тестировал): "Обратите внимание, что ответ IsDesignerHosted был обновлен и теперь включает LicenseUsage..., поэтому теперь тест может быть просто if (IsDesignerHosted). Альтернативным подходом является тестирование LicenseManager в конструкторе. и кешируем результат."
([Редактировать 2013] Отредактировано для работы в конструкторах, используя метод, предоставленный @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://stackru.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;
}
}
Я отправил отчет об ошибке в Microsoft; Я сомневаюсь, что это пойдет куда угодно, но все равно проголосуйте за это, поскольку это, очевидно, ошибка (независимо от того, "намеренно" или нет).
Почему бы вам не проверить LicenseManager.UsageMode. Это свойство может иметь значения LicenseUsageMode.Runtime или LicenseUsageMode.Designtime.
Если вы хотите, чтобы код выполнялся только во время выполнения, используйте следующий код:
if (LicenseManager.UsageMode == LicenseUsageMode.Runtime)
{
bla bla bla...
}
Это метод, который я использую внутри форм:
/// <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; }
}
Таким образом, результат будет правильным, даже если произойдет сбой в свойствах DesignMode или LicenseManager.
Я использую метод LicenseManager, но кеширую значение из конструктора для использования в течение всего времени существования экземпляра.
public MyUserControl()
{
InitializeComponent();
m_IsInDesignMode = (LicenseManager.UsageMode == LicenseUsageMode.Designtime);
}
private bool m_IsInDesignMode = true;
public bool IsInDesignMode { get { return m_IsInDesignMode; } }
VB версия:
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
Мое предложение - это оптимизация ответа @blueraja-danny-pflughoeft. Это решение не вычисляет результат каждый раз, а только в первый раз (объект не может изменить UsageMode от проектирования до времени выполнения)
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://stackru.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;
}
}
}
Мы успешно используем этот код:
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";
}
}
Поскольку ни один из методов не является надежным (DesignMode, LicenseManager) или эффективным (процесс, рекурсивные проверки), я использую public static bool Runtime { get; private set }
на уровне программы и явно устанавливая его внутри метода Main().
Я никогда не был пойман этим самим, но не могли бы вы просто пройтись по цепочке Parent от элемента управления, чтобы увидеть, установлен ли DesignMode где-нибудь над вами?
Я не понял, что вы не можете вызвать Parent.DesignMode (и я тоже кое-что узнал о "защищенном" в C#...)
Вот рефлексивная версия: (Я подозреваю, что может быть преимущество в производительности, если сделать designModeProperty статическим полем)
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;
}
DesignMode является частной собственностью (из того, что я могу сказать). Ответ заключается в том, чтобы предоставить публичное свойство, которое предоставляет реквизит DesignMode. Затем вы можете создать резервную копию цепочки пользовательских элементов управления, пока не столкнетесь с не пользовательским элементом управления или элементом управления, находящимся в режиме разработки. Что-то вроде этого....
public bool RealDesignMode()
{
if (Parent is MyBaseUserControl)
{
return (DesignMode ? true : (MyBaseUserControl) Parent.RealDesignMode;
}
return DesignMode;
}
Где все ваши UserControls наследуются от MyBaseUserControl. В качестве альтернативы вы можете реализовать интерфейс, который предоставляет "RealDeisgnMode".
Пожалуйста, обратите внимание, что этот код не является живым кодом, он не относится к размышлениям.:)
Я решил это в .NET 5 следующим образом:
public static bool InDesignMode()
{
return Process.GetCurrentProcess().ProcessName.Contains("DesignToolsServer");
}
Недавно мне пришлось бороться с этой проблемой в Visual Studio 2017 при использовании вложенных UserControls. Я комбинирую несколько подходов, упомянутых выше и в других местах, затем корректирую код, пока у меня не появится достойный метод расширения, который до сих пор работает приемлемо. Он выполняет последовательность проверок, сохраняя результат в статических логических переменных, поэтому каждая проверка выполняется не более одного раза во время выполнения. Процесс может быть излишним, но он препятствует выполнению кода в студии. Надеюсь, это кому-нибудь поможет.
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://stackru.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;
}
В .Net 6Control.IsAncestorSiteInDesignMode
имущество доступно.