DesignMode z zagnieżdżonymi kontrolkami


87

Czy ktoś znalazł przydatne rozwiązanie problemu DesignMode podczas tworzenia kontrolek?

Problem polega na tym, że jeśli zagnieżdżasz kontrolki, DesignMode działa tylko na pierwszym poziomie. Drugi i niższy poziom DesignMode zawsze zwróci FALSE.

Standardowy hack polegał na sprawdzeniu nazwy uruchomionego procesu i jeśli jest to „DevEnv.EXE”, to musi to być studio, więc DesignMode jest naprawdę TRUE.

Problem z wyszukiwaniem nazwy procesu przebiega przez rejestr i inne dziwne części, w wyniku czego użytkownik może nie mieć wymaganych uprawnień do wyświetlania nazwy procesu. W dodatku ta dziwna trasa jest bardzo powolna. Musieliśmy więc zebrać dodatkowe hacki, aby użyć singletona, a jeśli podczas pytania o nazwę procesu zostanie wyrzucony błąd, załóżmy, że DesignMode ma wartość FALSE.

Dobry, czysty sposób określenia DesignMode jest w porządku. Faktyczne przekonanie Microsoftu do wewnętrznego naprawienia tego frameworka byłoby jeszcze lepsze!



8
+1 za „przekonanie Microsoftu, żeby naprawił to wewnętrznie we frameworku, byłoby jeszcze lepsze” - dziesięć minut czyjegoś czasu pozwoliłoby zaoszczędzić dziesiątki tysięcy godzin na sztukę. Jeśli jest jeden program, który polega na błędzie i 100 000 jest przez niego niewygodnych, nie ma sensu zatrzymywanie błędu, aby uniknąć niedogodności dla jednego programu!
BlueRaja - Danny Pflughoeft

Cześć, zostało to opublikowane w 2008 roku. Czy to już zostało naprawione?
Jake

W VS 2012 jest teraz tak samo
Boogier,

1
Zauważ, że jeśli używasz niestandardowego projektanta dla UserControl (np. Testowałem z klasą pochodzącą z ControlDesigner), to wywołanie EnableDesignMode (subControl) wydaje się sprawić, że właściwość DesignMode kontroli podrzędnej działa. Nie jest to jednak skuteczne rozwiązanie problemu, ponieważ nie zawsze tworzymy kontener, w którym znajduje się nasza kontrola.
Protongun

Odpowiedzi:


80

Wracając do tego pytania, teraz `` odkryłem '' 5 różnych sposobów na zrobienie tego, które są następujące:

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"))
}

Aby spróbować zrozumieć trzy proponowane rozwiązania, stworzyłem małe rozwiązanie testowe - z trzema projektami:

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

Następnie osadziłem SubSubControl w SubControl, a następnie po jednym w TestApp.Form.

Ten zrzut ekranu przedstawia wynik po uruchomieniu. Zrzut ekranu z uruchomieniem

Ten zrzut ekranu przedstawia wynik z formularzem otwartym w programie Visual Studio:

Zrzut ekranu nie działa

Wniosek: wydawałoby się, że bez refleksji jedynym niezawodnym w konstruktorze jest LicenseUsage, a jedynym niezawodnym poza konstruktorem jest „IsDesignedHosted” ( poniżej BlueRaja )

PS: Zobacz komentarz ToolmakerSteve poniżej (którego nie testowałem): „Zwróć uwagę, że odpowiedź IsDesignerHosted została zaktualizowana i obejmuje LicenseUsage…, więc teraz test może być po prostu if (IsDesignerHosted). Alternatywnym podejściem jest test LicenseManager w konstruktorze i zapisz wynik w pamięci podręcznej . "


@Benjol: A co z IsDesignerHosted (poniżej)? (Myślę też, że masz zamienione czas projektowania i czas wykonywania, sprawdź, co jest napisane w czasie wykonywania)
BlueRaja - Danny Pflughoeft

@BlueRaja, nadal muszę mieć ten projekt leżący gdzieś na dysku, może powinienem go gdzieś
wrzucić

1
+1 za wyjaśnienie eksperymentem empirycznym. @Benjol, Jeśli będziesz mieć okazję do ponownego odwiedzenia tego, możesz dodać wielkość liter dla wartości w samym formularzu, ponieważ kontrolki podrzędne mogą być traktowane inaczej niż klasa faktycznie edytowana w projektancie. (Zwróć uwagę, że konstruktor edytowanej klasy nie jest wykonywany w projektancie).
Rob Parker

2
Czy zatem bez refleksji if(LicenseUseage == LicenseUsageMode.Designtime || IsDesignerHosted)byłoby w 100% poprawnym podejściem?
Scott Chamberlain

1
Zauważ, że odpowiedź IsDesignerHosted została zaktualizowana, aby uwzględnić LicenseUsage..., więc teraz test może być po prostu if (IsDesignerHosted). Alternatywnym podejściem jest przetestowanie LicenseManager w konstruktorze i buforowanie wyniku .
ToolmakerSteve

32

Z tej strony :

( [Edytuj 2013] Edytowane do pracy w konstruktorach, przy użyciu metody dostarczonej przez @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;
    }
}

Mam złożony bug-raport z Microsoft; Wątpię, czy coś pójdzie gdziekolwiek, ale i tak zagłosuj na to, ponieważ jest to oczywiście błąd (niezależnie od tego, czy jest to zgodne z projektem” ).


29

Dlaczego nie sprawdzisz LicenseManager.UsageMode. Ta właściwość może mieć wartości LicenseUsageMode.Runtime lub LicenseUsageMode.Designtime.

Jeśli chcesz, aby kod działał tylko w czasie wykonywania, użyj następującego kodu:

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

8
+1 Ja też tego użyłem. To, co przeszkadza ludziom, to fakt, że DesignMode nie będzie działać w konstruktorze.
Nicholas Piasecki

1
@Nicholas: To również nie działa w kontrolkach podrzędnych. Jest po prostu zepsuty.
BlueRaja - Danny Pflughoeft,

+1 - działa również na kontrolkach bazowych konstruowanych podczas projektowania kontrolek pochodnych.
mcw

7

Oto metoda, której używam wewnątrz formularzy:

    /// <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; }
    }

W ten sposób wynik będzie poprawny, nawet jeśli zawodzi jedna z właściwości DesignMode lub LicenseManager.


1
Tak, to zadziała w formach, jak mówisz. Chciałbym jednak zaznaczyć, że to nie działa poza konstruktorem w kontrolkach użytkownika wnuka.
Anlo

5

Używam metody LicenseManager, ale buforuję wartość z konstruktora do użytku przez cały okres istnienia instancji.

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

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

Wersja 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

1
Jonathan, dodałem (przetestowaną) wersję VB do twojej odpowiedzi.
ToolmakerSteve

3

Używamy tego kodu z powodzeniem:

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";
  }
}

3

Moja sugestia jest optymalizacja @ blueraja-danny-pflughoeft odpowiedzi . To rozwiązanie nie oblicza wyniku za każdym razem, ale tylko za pierwszym razem (obiekt nie może zmienić UsageMode z projektu na środowisko uruchomieniowe)

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;
        }
    }
}

Jeśli zamierzasz buforować wartość, nie ma powodu, aby przechodzić do tej złożoności. Zamiast tego użyj odpowiedzi Jonathana , która używa prostego testu LicenseManager w konstruktorze , buforując wynik.
ToolmakerSteve

Myślę, że zaletą tej metody jest to, że w ogóle nie wymaga ona nawet testu LicenserManager, jeśli właściwość nigdy nie jest potrzebna w jakimś przypadku.
Sebastian Werk,

2

Sam nigdy nie zostałem przez to złapany, ale czy nie mógłbyś po prostu przejść z powrotem do łańcucha nadrzędnego z kontrolki, aby sprawdzić, czy DesignMode jest ustawiony gdzieś nad tobą?


2

Ponieważ żadna z metod nie jest niezawodna (DesignMode, LicenseManager) ani wydajna (proces, kontrole rekurencyjne), używam public static bool Runtime { get; private set }na poziomie programu i jawnie ustawiam ją w metodzie Main ().


1

DesignMode jest własnością prywatną (z tego, co wiem). Odpowiedzią jest podanie właściwości publicznej, która uwidacznia właściwość DesignMode. Następnie można kaskadowo tworzyć kopię zapasową łańcucha kontrolek użytkownika, aż napotkasz kontrolkę niebędącą użytkownikiem lub kontrolkę, która jest w trybie projektowania. Coś takiego....

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

     return DesignMode;
  }

Gdzie wszystkie Twoje UserControls dziedziczą po MyBaseUserControl. Alternatywnie możesz zaimplementować interfejs, który ujawnia "RealDeisgnMode".

Należy pamiętać, że ten kod nie jest kodem na żywo, tylko z zamyślenia mankietu. :)


1

Nie zdawałem sobie sprawy, że nie można wywołać Parent.DesignMode (i dowiedziałem się też czegoś o 'protected' w C # ...)

Oto wersja odblaskowa: (podejrzewam, że uczynienie designModeProperty polem statycznym może mieć przewagę w wydajności)

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;
}

0

Musiałem ostatnio walczyć z tym problemem w programie Visual Studio 2017 podczas korzystania z zagnieżdżonych UserControls. Łączę kilka podejść wspomnianych powyżej i gdzie indziej, a następnie poprawiałem kod, aż uzyskałem przyzwoitą metodę rozszerzenia, która działa do tej pory akceptowalnie. Wykonuje sekwencję sprawdzeń, przechowując wynik w statycznych zmiennych boolowskich, więc każde sprawdzenie jest wykonywane najwyżej tylko raz w czasie wykonywania. Ten proces może być przesadą, ale uniemożliwia wykonanie kodu w studio. Mam nadzieję, że to komuś pomoże.

  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;
  }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.