Управление с пользовательским TypeConverter не работает после перестройки проекта - PullRequest
0 голосов
/ 08 марта 2019

Обзор

У меня есть проект C # ControlsLib, который содержит следующее (код для последующего позже):

  1. Пользовательский элемент управления C # Control_Audible, который воспроизводит WAV-файл, выбранный во время разработки на панели свойств
  2. A SoundItem class как класс для поддержки того, какой файл будет воспроизводиться
  3. A SoundItemConverter класс как TypeConverter для элегантного выбора звукового файла для воспроизведения в окне свойств.

Что работает

Мой код работает должным образом, когда мое решение открыто ПЕРВЫМ, а мой ControlsLib проект НЕ построен.

Когда я добавляю Control_Audible к форме, я могу выбрать аудиофайл из доступных аудиоресурсов:

Select audio file from available audio resources;

И файл конструктора генерирует код, как и следовало ожидать, если выбран SoundItem не по умолчанию:

Expected designer code generated from non-default SoundItem

Что НЕ работает

Мой код, кажется, НЕ работает должным образом после сборки / перестройки моего ControlsLib проекта и добавления моего Control_Audible элемента управления в форму ...

Доступные файлы аудиоресурсов заполняются в раскрывающемся списке, как и ожидалось, но SoundItem, указанный в качестве DefaultValue («Нет»), равен Полужирный , когда это действительно не должно быть:

Select audio file from available audio resources; DefaultValue

Кроме того, всякий раз, когда я пытаюсь сохранить форму, я получаю следующую ошибку:

Code generation ... failed ... Unable to convert ... to InstanceDescriptor

В свое время я мог сгенерировать некоторый авто-код для моего Control_Audible, но даже тогда он не генерировал код, который я ожидал, и теперь, похоже, он вообще не генерирует никакого кода и мою функцию ConvertTo испорчен.

Вопрос

Кажется, что это нормально работает, если я не создаю свой ControlsLib проект, но я не могу ожидать, что мне придется перезапускать Visual Studio каждый раз, когда я хочу, чтобы эта штука работала. Я должен быть в состоянии перестроить свой проект столько, сколько пожелаю, и при этом у меня все еще будет должный контроль. Я упомяну, что я использую Visual Studio 2015 .

У кого-нибудь есть идеи, почему так себя ведут? Я потратил немало времени на это и не могу понять, почему это происходит!

Код

Control_Audible.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design.Serialization;
using System.Drawing;
using System.Globalization;
using System.Media;
using System.Reflection;
using System.Resources;
using System.Windows.Forms;

namespace ControlsLib
{
   public class Control_Audible : UserControl
   {
      public Control_Audible() : base()
      {
         FileToPlay = SoundItem.None;
         InitializeComponent();
      }

      private void InitializeComponent()
      {
         this.SuspendLayout();
         // 
         // Control_Audible
         // 
         this.AutoScaleDimensions = new SizeF(6F, 13F);
         this.Name = "Control_Audible";
         this.ResumeLayout(false);
      }

      [Category("Sounds")]
      [DisplayName("SoundToPlay")]
      [Description("File to play")]
      [DefaultValue(typeof(SoundItem), "None")]
      //[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
      public SoundItem FileToPlay
      {
         get;
         set;
      }
   }

   [TypeConverter(typeof(SoundItemConverter))]
   [Serializable]
   public struct SoundItem
   {
      public static readonly SoundItem None = new SoundItem("None");

      //[Browsable(false)]
      //[DefaultValue("None")]
      //[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
      internal string Name
      {
         get;
         set;
      }

      // Todo: Create a valid flag
      public SoundItem(string Name) : this()
      {
         this.Name = Name;
      }

      public SoundPlayer GetSoundPlayer()
      {
         SoundPlayer retVal = null;

         if (!string.IsNullOrEmpty(Name))
         {
            retVal = new SoundPlayer(AudibleSounds.ResourceManager.GetStream(Name));
         }

         return retVal;
      }

      public override string ToString()
      {
         return Name;
      }

      public static bool operator ==(SoundItem left, SoundItem right)
      {
         return left.Name == right.Name;
      }

      public static bool operator !=(SoundItem left, SoundItem right)
      {
         return !(left == right);
      }

      public override bool Equals(object obj)
      {
         // override Equals and write code to actually
         // test for equality so that the SoundItem created
         // from the call to ConvertFrom will be equal to
         // the field you created

         if (!(obj is SoundItem))
         {
            return false;
         }

         SoundItem comp = (SoundItem)obj;

         // Note value types can't have derived classes, so we don't need 
         // to check the types of the objects here.  -- Microsoft, 2/21/2001
         return comp.Name == Name;
      }

      public override int GetHashCode()
      {
         return Name.GetHashCode();
      }
   }

   public class SoundItemConverter : TypeConverter
   {
      private Dictionary<string, object> _resourceValues = new Dictionary<string, object>()
      {
         {"None", SoundItem.None },
      };

      public SoundItemConverter()
      {
         // Initializes the standard values list with defaults.
         ResourceSet _resourceSet = AudibleSounds.ResourceManager.GetResourceSet(CultureInfo.CurrentCulture, true, true);

         if (_resourceSet == null)
         {
            throw new Exception("Missing Resource Audio files");
         }

         // Make a dictionary now for easier use
         foreach (DictionaryEntry entry in _resourceSet)
         {
            string resourceKey = entry.Key.ToString();
            //object resource = entry.Value;
            _resourceValues.Add(resourceKey,
                                new SoundItem(resourceKey));
         }
      }

      // Indicates this converter provides a list of standard values.
      public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
      {
         return true;
      }

      // Returns a StandardValuesCollection of standard value objects.
      public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
      {
         return new StandardValuesCollection( _resourceValues.Values);
      }

      // Returns true for a sourceType of string to indicate that 
      // conversions from string are supported. (The 
      // GetStandardValues method requires a string to native type 
      // conversion because the items in the drop-down list are 
      // translated to string.)
      public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
      {
         if (sourceType == typeof(string))
         {
            return true;
         }

         return base.CanConvertFrom(context, sourceType);
      }

      // If the type of the value to convert is string, parses the string 
      // to set the value of the property to.
      public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
      {
         // This is the method that is called when you specify a default value for your type.
         // Convert a string representation to SoundItem.
         string strValue = value as string;

         if (strValue != null)
         {
            string text = strValue.Trim();

            if (text.Length == 0)
            {
               return SoundItem.None;
            }
            else
            {
               return new SoundItem(text);
            }
         }

         return base.ConvertFrom(context, culture, value);
      }

      public override bool CanConvertTo(ITypeDescriptorContext context, Type destType)
      {
         if (destType == typeof(InstanceDescriptor))
         {
            return true;
         }

         return base.CanConvertTo(context, destType);
      }

      public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destType)
      {
         if (destType == null)
         {
            throw new ArgumentNullException("destinationType");
         }

         if (value is SoundItem)
         {
            SoundItem si = (SoundItem)value;

            if (destType == typeof(string))
            {
               TypeConverter stringConverter = TypeDescriptor.GetConverter(typeof(string));

               // Note: ConvertToString will raise exception if value cannot be converted.
               return stringConverter.ConvertToString(context, culture, si.Name);
            }
            else if (destType == typeof(InstanceDescriptor))
            {
               ConstructorInfo ctor = typeof(SoundItem).GetConstructor(new Type[] { typeof(string) });

               if (ctor != null)
               {
                  return new InstanceDescriptor(ctor, new object[] { si.Name });
               }
            }
         }

         return base.ConvertTo(context, culture, value, destType);
      }

      //
      // Summary:
      //     Creates an instance of this type given a set of property values for the object.
      //
      // Parameters:
      //   context:
      //     A type descriptor through which additional context can be provided.
      //
      //   propertyValues:
      //     A dictionary of new property values. The dictionary contains a series of name-value
      //     pairs, one for each property returned from SoundItemConverter.GetProperties(System.ComponentModel.ITypeDescriptorContext,System.Object,System.Attribute[]).
      //
      // Returns:
      //     The newly created object, or null if the object could not be created. The default
      //     implementation returns null.
      public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
      {
         if (propertyValues == null)
         {
            throw new ArgumentNullException("propertyValues");
         }

         object name = propertyValues["Name"];

         if (name == null || !(name is string))
         {
            throw new ArgumentException();
         }

         return new SoundItem((string)name);
      }

      //
      // Summary:
      //     Determines if changing a value on this object should require a call to SoundItemConverter.CreateInstance(System.ComponentModel.ITypeDescriptorContext,System.Collections.IDictionary)
      //     to create a new value.
      //
      // Parameters:
      //   context:
      //     A System.ComponentModel.TypeDescriptor through which additional context can be
      //     provided.
      //
      // Returns:
      //     true if the SoundItemConverter.CreateInstance(System.ComponentModel.ITypeDescriptorContext,System.Collections.IDictionary)
      //     method should be called when a change is made to one or more properties of this
      //     object; otherwise, false.
      public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
      {
         return true;
      }

      //
      // Summary:
      //     Retrieves the set of properties for this type. By default, a type does not return
      //     any properties.
      //
      // Parameters:
      //   context:
      //     A type descriptor through which additional context can be provided.
      //
      //   value:
      //     The value of the object to get the properties for.
      //
      //   attributes:
      //     An array of System.Attribute objects that describe the properties.
      //
      // Returns:
      //     The set of properties that are exposed for this data type. If no properties are
      //     exposed, this method might return null. The default implementation always returns
      //     null.
      public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
      {
         PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(SoundItem), attributes);
         return props.Sort(new string[] { "Name" });
      }

      //
      // Summary:
      //     Determines if this object supports properties. By default, this is false.
      //
      // Parameters:
      //   context:
      //     A System.ComponentModel.TypeDescriptor through which additional context can be
      //     provided.
      //
      // Returns:
      //     true if SoundItemCoverter.GetProperties(System.ComponentModel.ITypeDescriptorContext,System.Object,System.Attribute[])
      //     should be called to find the properties of this object; otherwise, false.
      public override bool GetPropertiesSupported(ITypeDescriptorContext context)
      {
         return true;
      }
   }
}

AudibleSounds.Designer.cs

//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a tool.
//     Runtime Version:4.0.30319.42000
//
//     Changes to this file may cause incorrect behavior and will be lost if
//     the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace ControlsLib {
    using System;


    /// <summary>
    ///   A strongly-typed resource class, for looking up localized strings, etc.
    /// </summary>
    // This class was auto-generated by the StronglyTypedResourceBuilder
    // class via a tool like ResGen or Visual Studio.
    // To add or remove a member, edit your .ResX file then rerun ResGen
    // with the /str option, or rebuild your VS project.
    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    public class AudibleSounds {

        private static global::System.Resources.ResourceManager resourceMan;

        private static global::System.Globalization.CultureInfo resourceCulture;

        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        internal AudibleSounds() {
        }

        /// <summary>
        ///   Returns the cached ResourceManager instance used by this class.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        public static global::System.Resources.ResourceManager ResourceManager {
            get {
                if (object.ReferenceEquals(resourceMan, null)) {
                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ControlsLib.AudibleSounds", typeof(AudibleSounds).Assembly);
                    resourceMan = temp;
                }
                return resourceMan;
            }
        }

        /// <summary>
        ///   Overrides the current thread's CurrentUICulture property for all
        ///   resource lookups using this strongly typed resource class.
        /// </summary>
        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
        public static global::System.Globalization.CultureInfo Culture {
            get {
                return resourceCulture;
            }
            set {
                resourceCulture = value;
            }
        }

        /// <summary>
        ///   Looks up a localized resource of type System.IO.UnmanagedMemoryStream similar to System.IO.MemoryStream.
        /// </summary>
        public static System.IO.UnmanagedMemoryStream Horn {
            get {
                return ResourceManager.GetStream("Horn", resourceCulture);
            }
        }

        /// <summary>
        ///   Looks up a localized resource of type System.IO.UnmanagedMemoryStream similar to System.IO.MemoryStream.
        /// </summary>
        public static System.IO.UnmanagedMemoryStream HornOff {
            get {
                return ResourceManager.GetStream("HornOff", resourceCulture);
            }
        }
    }
}

AudibleSounds.resx

<?xml version="1.0" encoding="utf-8"?>
<root>
  <!-- 
    Microsoft ResX Schema 

    Version 2.0

    The primary goals of this format is to allow a simple XML format 
    that is mostly human readable. The generation and parsing of the 
    various data types are done through the TypeConverter classes 
    associated with the data types.

    Example:

    ... ado.net/XML headers & schema ...
    <resheader name="resmimetype">text/microsoft-resx</resheader>
    <resheader name="version">2.0</resheader>
    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>[base64 mime encoded serialized .NET Framework object]</value>
    </data>
    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
        <comment>This is a comment</comment>
    </data>

    There are any number of "resheader" rows that contain simple 
    name/value pairs.

    Each data row contains a name, and value. The row also contains a 
    type or mimetype. Type corresponds to a .NET class that support 
    text/value conversion through the TypeConverter architecture. 
    Classes that don't support this are serialized and stored with the 
    mimetype set.

    The mimetype is used for serialized objects, and tells the 
    ResXResourceReader how to depersist the object. This is currently not 
    extensible. For a given mimetype the value must be set accordingly:

    Note - application/x-microsoft.net.object.binary.base64 is the format 
    that the ResXResourceWriter will generate, however the reader can 
    read any of the formats listed below.

    mimetype: application/x-microsoft.net.object.binary.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.soap.base64
    value   : The object must be serialized with 
            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
            : and then encoded with base64 encoding.

    mimetype: application/x-microsoft.net.object.bytearray.base64
    value   : The object must be serialized into a byte array 
            : using a System.ComponentModel.TypeConverter
            : and then encoded with base64 encoding.
    -->
  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
    <xsd:element name="root" msdata:IsDataSet="true">
      <xsd:complexType>
        <xsd:choice maxOccurs="unbounded">
          <xsd:element name="metadata">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" />
              </xsd:sequence>
              <xsd:attribute name="name" use="required" type="xsd:string" />
              <xsd:attribute name="type" type="xsd:string" />
              <xsd:attribute name="mimetype" type="xsd:string" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="assembly">
            <xsd:complexType>
              <xsd:attribute name="alias" type="xsd:string" />
              <xsd:attribute name="name" type="xsd:string" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="data">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
              <xsd:attribute ref="xml:space" />
            </xsd:complexType>
          </xsd:element>
          <xsd:element name="resheader">
            <xsd:complexType>
              <xsd:sequence>
                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
              </xsd:sequence>
              <xsd:attribute name="name" type="xsd:string" use="required" />
            </xsd:complexType>
          </xsd:element>
        </xsd:choice>
      </xsd:complexType>
    </xsd:element>
  </xsd:schema>
  <resheader name="resmimetype">
    <value>text/microsoft-resx</value>
  </resheader>
  <resheader name="version">
    <value>2.0</value>
  </resheader>
  <resheader name="reader">
    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <resheader name="writer">
    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </resheader>
  <assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
  <data name="HornOff" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>Resources\HornOff.wav;System.IO.MemoryStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </data>
  <data name="Horn" type="System.Resources.ResXFileRef, System.Windows.Forms">
    <value>Resources\Horn.wav;System.IO.MemoryStream, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
  </data>
</root>

Редактирование

Редактировать # 1

Я должен также упомянуть, что мой код работает должным образом, когда решение отлажено при запуске Visual Studio как внешней программы, так:

Control works as expected when debugging with external Visual Studio

Но код НЕ работает после сборки моего ControlsLib проекта, и я добавляю свой элемент управления Control_Audible в форму БЕЗ отладки через внешнюю Visual Studio (как это делают большинство обычных пользователей ...).

...