none
Eigenener persistenter Typ im Form Designer RRS feed

  • Frage

  • Hallo,

    ich erstelle eine neue Komponente, vererbt von UserControl, die ich dann im Windows Form Designer in das Fenster ziehen kann. Nun hat diese Komponente ein Attribut von einem eigenen Typ. Ich habe schon ein eigenes Fenster/Form, also einen Editor, zum bearbeiten dieses Attributs erstellt. Das wird mit [Editor(typeof(MeinEditor), typeof(UITypeEditor))] an das Attribut "geheftet". So ist das Attribut sehr schön im Eigenschaften Fenster sichtbar und wenn ich auf die Eigenschaft klicke, wird der Editor aufgerufen und ich kann das Attribut verändern. Das funktioniert alles sehr gut.

    Aber nun sollen die Änderungen des Attributs auch persistent sein. Sie müssen also als Text in die Form1.Designer.cs Datei unter der Region "Windows Form Designer generated Code" auftauchen. Aber wie bekomme ich das hin? Ich habe schon einen eigenen TypeConverter geschrieben, der das Attribut in einen String konvertiert und ein neues Attribut aus einem String erzeugt. Der wird aber nicht aufgerufen.

    Im TypeConverter kann man auch ein ConvertTo mit InstanceDescriptor schreiben, der ein neues Objekt erstellt. Allerdings aus einem Objekt, welches schon als value übergeben wird. Muss das vielleicht benutzt werden? Aber in value habe ich doch schon ein richtiges Objekt. Zumindest wird im Beispiel ein neues Objekt mit den Eigenschaften von value (auf den richtigen Typ) gecastet) aufgerufen.

    Ich habe von längerer Zeit schon mal länger an diesem Problem gesucht, aber auch nichts gefunden. Geht das also gar nicht? Dann dürfte man aber doch nur Attribute vom bekannten Typ in meinen Klassen nehmen, damit die Änderungen im Eigenschaften Fenster vom Form Designer auch in die .Designer.cs Datei übernehmen.

    Ich bin über jeden Hinweis dankbar.

    Erwin

    Samstag, 6. Januar 2018 22:22

Antworten

  • Hi Erwin,
    ich habe Dein Szenario mal nachgestellt und es funktioniert. Geänderte Eigenschaftswerte werden vollständig im Designercode abgelegt. da ist nichts zu serialisieren und dann wieder zu deserialisieren.

    1. WindowsFormsControlLibrary mit

    2. UserControl mit 3 Label, die an die Eigenschaften eines Datenobjektes gebunden sind.

    using System;
    using System.ComponentModel;
    using System.Drawing.Design;
    using System.Windows.Forms;
    
    namespace WindowsFormsControlLibrary1
    {
      public partial class Form16UC : UserControl
      {
        public Form16UC()
        {
          InitializeComponent();
        }
        [Browsable(true),
          EditorBrowsable(EditorBrowsableState.Always),
          DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
          RefreshProperties(RefreshProperties.Repaint),
          Editor(typeof(Form16UcPropEdit), typeof(UITypeEditor)),
          Description("Die Werte der Eigenschaft wird in einem separaten Fenster zur Bearbeitung angezeigt.")]
    
        [CategoryAttribute("Behavior")]
        public Form16UcPropType Prop { get; set; } = new Form16UcPropType() { Prop1 = -1, Prop2 = -2, Prop3 = -3 };
    
        private void Form16UC_Load(object sender, EventArgs e)
        {
          this.lbl1.DataBindings.Add("Text", this.Prop, "Prop1");
          this.lbl2.DataBindings.Add("Text", this.Prop, "Prop2");
          this.lbl3.DataBindings.Add("Text", this.Prop, "Prop3");
        }
      }
    }
    

    3. Datenklasse mit 3 Eigenschaften, die im Eigenschaftseditor bearbeitet werden können.

    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    
    namespace WindowsFormsControlLibrary1
    {
      public class Form16UcPropType : INotifyPropertyChanged
      {
        private int p1 = 0;
        private int p2 = 0;
        private int p3 = 0;
    
        [DefaultValueAttribute(0), CategoryAttribute("Behavior")]
        public int Prop1 { get { return p1; } set { p1 = value; OnPropertyChanged(); } }
    
        [DefaultValueAttribute(0), CategoryAttribute("Behavior")]
        public int Prop2 { get { return p2; } set { p2 = value; OnPropertyChanged(); } }
    
        [DefaultValueAttribute(0), CategoryAttribute("Behavior")]
        public int Prop3 { get { return p3; } set { p3 = value; OnPropertyChanged(); } }
    
        public override string ToString() => $"{Prop1}; {Prop2}; {Prop3}";
    
        public event PropertyChangedEventHandler PropertyChanged;
        internal void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    
      }
    }

    4. PropertyEditor, mit dem die Eigenschaften des Objektes bearbeitet werden, welches über eine Eigenschaft des UserControls bereitgestellt wird.

    using System;
    using System.ComponentModel;
    using System.Drawing.Design;
    using System.Windows.Forms;
    
    namespace WindowsFormsControlLibrary1
    {
      public class Form16UcPropEdit : UITypeEditor
      {
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal;
    
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
          Form16UcPropType obj = value as Form16UcPropType;
          Form frm = new Form();
          TextBox tb1 = new TextBox() { Text = obj.Prop1.ToString(), Dock = DockStyle.Top };
          TextBox tb2 = new TextBox() { Text = obj.Prop2.ToString(), Dock = DockStyle.Top };
          TextBox tb3 = new TextBox() { Text = obj.Prop3.ToString(), Dock = DockStyle.Top };
          frm.Controls.AddRange(new Control[] { tb3, tb2, tb1 });
          frm.ShowDialog();
          int i = 0;
          obj.Prop1 = (int.TryParse(tb1.Text, out i)) ? i : obj.Prop1;
          obj.Prop2 = (int.TryParse(tb2.Text, out i)) ? i : obj.Prop2;
          obj.Prop3 = (int.TryParse(tb3.Text, out i)) ? i : obj.Prop3;
          return value;
        }
      }
    }
    


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Freitag, 12. Januar 2018 20:17
  • Hi Erwin,
    bei mir funktioniert es problemlos. Das NotifyPropertyChanged wird nur benötigt, wenn es Änderungen im Objekt der Eigenschaft gibt, die z.B. an die Oberfläche zu melden sind. Leider erkennt der Designer dieses Ereignis nicht für eine erneute Serialisierung geänderter Werte. Deshalb ist es notwendig, über eine geänderte Referenz (neues Objekt) dem Designer mitzuteilen, dass erneut zu serialisieren ist.

    Damit wir hier weiterkommen, solltest Du nochmals genauer Dein Ansinnen formulieren. Eine Serialisierung einer Eigenschaft eines UserControls führt der Designer nicht aus. Deshalb muss man einen Typ als Eigenschaft nutzen, der eine eigene Eigenschaft einschließt, die dann serialisiert wird.

    Bei mir funktioniert:

    Das UserControl hat eine zusätzliche Eigenschaft von einem eigenen Typ.

    Dieser eigene Typ enthält eine Eigenschaft eines eigenen Sybtyps.

    Dieser Subtyp implementiert ISerializable und kann damit beliebige Daten serialisiert in den Ressourcen der Form ablegen.

    Der Editor bearbeitet die Daten des eigenen Typs incl. des Subtyps und erzeugt nach den Editiervorgang eine neue Instanz des eigenen Typs, indem die alten und die neuer Werte reinkopiert werden.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Samstag, 3. Februar 2018 07:31

Alle Antworten

  • Hi Erwin,
    meinst Du mit Attribut eine zusätzliche eigene Eigenschaft des UserControls? Wenn das eine Eigenschaft ist, dann funktioniert das. Wenn das ein Attribut ist, dann beschreibe mal etwas genauer, was Du damit erreichen willst.

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks



    Sonntag, 7. Januar 2018 08:26
  • Hallo Peter,

    ich meine ein zusätzliche Eigenschaft. Allerdings hätte ich auch Probleme zu sagen, was der Unterschied zwischen einer Eigenschaft und einem Attribut ist :-).

    Genauer habe ich halt meine vererbte UserControl Klasse, die eine andere Klasse als Eigenschaft(/Attribut?) hat. Diese andere Klasse hat wieder Eigenschaften bzw. Werte.

    class A : UserControl
    {
         public class B
         {
               public int V1;
               public int V2;
          }

          private B b = new B();
          public B eB
          {
              get { return b;}
              set { etwas mit value;}
          }
    }

    B ist nun also die Eigenschaft von A. Für B habe ich auch einen Editor geschrieben, so dass ich B im Designer verändern und als Kopie an A übergeben kann.

    Nun könnte B ja auch einzelne Eigenschaften haben, die der Designer als "Untereigenschaft" von A in die *.Designer.cs  Datei schreiben kann. Das bekomme ich aber auch nicht hin. Der Designer kennt anscheinend nur die Eigenschaft B und nichts mehr darin. 

    Noch schwieriger wird es, wenn B eine Liste von Objekten enthält (oder A als Eigenschaft eine Liste von B enthält). Da soll der Designer ja auch irgendwie können. Zum Schreiben der Werte in die Designer Datei muss es ja "serialisiert" werden, was für Standardtypen wie int oder string ja auch schon standardmäßig funktioniert.

    Ich hoffe, die Erklärungen helfen.

    Viele Grüße
    Erwin

    Sonntag, 7. Januar 2018 15:54
  • Hi Erwin,
    ein Attribut ist eine Klasse mit dem Suffix Attribut, die keinen Code zur Ausführungszeit erzeugt, aber durch den Designer, die CLR in der Reflection genutzt wird. Solch ein Attribut wird in C# dem entsprechenden Schlüsselwort (Class oder Member) in eckigen Klammern vorangestellt.

    Eine Eigenschaft ist ein member in einer Klasse.

    Ich versuche mal in den nächsten Tagen, ein Beispiel in C# zu erstellen. Ich habe so etwas das letzte Mal vor 9 Jahren in VB.NET gemacht.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Sonntag, 7. Januar 2018 18:39
  • Ok, habe ich verstanden und auch einige Beispiele gefunden.

    Ich erzeuge definitiv kein neues Attribute.

    Im Moment habe ich es so gelöst, dass ich die Werte der Klasse B (in meinem obigen Beispiel, die ja eine Eigenschaft in der Klasse A ist) in einen ConfString schreibe. B kann die Werte auch aus diesem ConfString herauslesen. B hat nun eine Eigenschaft vom Typ string mit dem Namen B_ConfString. Diese Eigenschaft verbinde ich (mit einem Attribut :-) ) mit meinem selbstgeschriebenen Editor. Wenn ich also  nun im Eigenschaftsfenster von A (in dem Form habe ich also A selektiert, so dass alle Eigenschaften angezeigt werden) auf die Eigenschaft B_ConfString klicke (genauer auf das kleine Symbol, welches am rechten Rand eingeblendet wird) erscheint mein Editor. Dieser bekommt den ConfString dann vom Designer übergeben, ich kann mir eine Kopie von B erstellen und im Editor verändern. Beim Schließen des Editors bekomme ich nun einen neuen ConfString gemäß der neuen Werte in B. Dieser String gehört ja zur Eigenschaft B_ConfString und der Designer schreibt diesen (durchaus etwas längeren String) in die entsprechende *.Designer.cs Datei. Beim nächsten Starten des Designers oder auch des Programms habe ich also ein B mit den neuen Settings, eingebettet in A.

    Das funktioniert. Aber ohne einen Umweg über den ConfString fände ich es eleganter. Andere Klassen, bei denen ich zur Design Zeit im Designer neue Unterobjekte erzeugt (DrawGrid oder StatusStrip) können das ja auch. Da erscheinen die neuen Objekte auch in der Designer.cs Datei.

    Viele Grüße

    Erwin

    Sonntag, 7. Januar 2018 22:53
  • Hi Erwin,
    ich habe Dein Szenario mal nachgestellt und es funktioniert. Geänderte Eigenschaftswerte werden vollständig im Designercode abgelegt. da ist nichts zu serialisieren und dann wieder zu deserialisieren.

    1. WindowsFormsControlLibrary mit

    2. UserControl mit 3 Label, die an die Eigenschaften eines Datenobjektes gebunden sind.

    using System;
    using System.ComponentModel;
    using System.Drawing.Design;
    using System.Windows.Forms;
    
    namespace WindowsFormsControlLibrary1
    {
      public partial class Form16UC : UserControl
      {
        public Form16UC()
        {
          InitializeComponent();
        }
        [Browsable(true),
          EditorBrowsable(EditorBrowsableState.Always),
          DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
          RefreshProperties(RefreshProperties.Repaint),
          Editor(typeof(Form16UcPropEdit), typeof(UITypeEditor)),
          Description("Die Werte der Eigenschaft wird in einem separaten Fenster zur Bearbeitung angezeigt.")]
    
        [CategoryAttribute("Behavior")]
        public Form16UcPropType Prop { get; set; } = new Form16UcPropType() { Prop1 = -1, Prop2 = -2, Prop3 = -3 };
    
        private void Form16UC_Load(object sender, EventArgs e)
        {
          this.lbl1.DataBindings.Add("Text", this.Prop, "Prop1");
          this.lbl2.DataBindings.Add("Text", this.Prop, "Prop2");
          this.lbl3.DataBindings.Add("Text", this.Prop, "Prop3");
        }
      }
    }
    

    3. Datenklasse mit 3 Eigenschaften, die im Eigenschaftseditor bearbeitet werden können.

    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    
    namespace WindowsFormsControlLibrary1
    {
      public class Form16UcPropType : INotifyPropertyChanged
      {
        private int p1 = 0;
        private int p2 = 0;
        private int p3 = 0;
    
        [DefaultValueAttribute(0), CategoryAttribute("Behavior")]
        public int Prop1 { get { return p1; } set { p1 = value; OnPropertyChanged(); } }
    
        [DefaultValueAttribute(0), CategoryAttribute("Behavior")]
        public int Prop2 { get { return p2; } set { p2 = value; OnPropertyChanged(); } }
    
        [DefaultValueAttribute(0), CategoryAttribute("Behavior")]
        public int Prop3 { get { return p3; } set { p3 = value; OnPropertyChanged(); } }
    
        public override string ToString() => $"{Prop1}; {Prop2}; {Prop3}";
    
        public event PropertyChangedEventHandler PropertyChanged;
        internal void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    
      }
    }

    4. PropertyEditor, mit dem die Eigenschaften des Objektes bearbeitet werden, welches über eine Eigenschaft des UserControls bereitgestellt wird.

    using System;
    using System.ComponentModel;
    using System.Drawing.Design;
    using System.Windows.Forms;
    
    namespace WindowsFormsControlLibrary1
    {
      public class Form16UcPropEdit : UITypeEditor
      {
        public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal;
    
        public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
        {
          Form16UcPropType obj = value as Form16UcPropType;
          Form frm = new Form();
          TextBox tb1 = new TextBox() { Text = obj.Prop1.ToString(), Dock = DockStyle.Top };
          TextBox tb2 = new TextBox() { Text = obj.Prop2.ToString(), Dock = DockStyle.Top };
          TextBox tb3 = new TextBox() { Text = obj.Prop3.ToString(), Dock = DockStyle.Top };
          frm.Controls.AddRange(new Control[] { tb3, tb2, tb1 });
          frm.ShowDialog();
          int i = 0;
          obj.Prop1 = (int.TryParse(tb1.Text, out i)) ? i : obj.Prop1;
          obj.Prop2 = (int.TryParse(tb2.Text, out i)) ? i : obj.Prop2;
          obj.Prop3 = (int.TryParse(tb3.Text, out i)) ? i : obj.Prop3;
          return value;
        }
      }
    }
    


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Freitag, 12. Januar 2018 20:17
  • Super und Danke.

    Da sind mehrere Sachen drin, die ich noch nie gemacht habe (einige Attribute habe ich noch nie gesehen und ein DataBinding habe ich auch noch nie benutzt). Ich werde da in den nächsten Tagen mal versuchen, einige zu übernehmen.

    Viele Grüße
    Erwin

    Samstag, 13. Januar 2018 21:14
  • Hi Erwin,
    DataBindings hat mit der eigentlichen Frage nichts zu tun. Das habe ich nur genutzt, um im UserControl die Eigenschaftswerte zu sehen.

    Für das Persisteren der Werte wichtig ist DesignerSerializationVisibility.Content.

    Beim Testen empfehle ich Dir, eine WindowsFormsControlLibrary mit Hochzählen der Assembly-Version (mit *) zu nutzen. Das Visual Studio puffert intensiv, so dass ohne Hochzählen u.U. eine ältere Version in den Tests genutzt wird und Deine letzten Änderungen nicht wirksam werden.

    Wenn Du Deine Versionen debuggen willst, musst Du eine zweite Instanz des Visual Studios mit dem unveränderten Quellcode der WindowsFormsControlLibrary starten, in der dann die Haltepunkte gesetzt werden können. Zum Debuggen muss diese zweite Instanz an den Prozess der ersten Instanz gehängt werden.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks


    Sonntag, 14. Januar 2018 07:24
  • Hallo Peter,

    ich konnte dein Beispiel nachvollziehen und es funktioniert auch. Allerdings ist mir ein Unterschied aufgefallen. Bei dir gibt es eine feste Anzahl von Eigenschaften (prop1, prop2, prop3), so dass dann in der Designer Datei steht:

    this.form16UC1.Prop.Prop1 = -1;
    this.form16UC1.Prop.Prop2 = -4;
    this.form16UC1.Prop.Prop3 = -3;

    Ich benötige aber eine Liste von Werten, so dass ich in dem PropertyEditor Fenster Instanzen hinzufügen und auch löschen kann. Wie geht so etwas?

    Ich meine, ich wäre bei meinem ganzen Googlen in der letzten Zeit mal darüber gestolpert, kann es nun aber nicht finden. Kann es vielleicht sein, dass meine "Unterinstanzen" auch von Control abgeleitet werden müssen und ich diese dann an den übergeordneten Control übergeben muss? Dann kann der Designer die Contentstruktur daraus ableiten. 

    Schön wäre aber eine Eigenschaft, die z.B. eine List<int> enthält. 

    Viele Grüße
    Erwin

    Montag, 22. Januar 2018 21:33
  • Hi Erwin,
    wenn Du eine Liste von Datenobjekten nutzt, dann werden diese in den Ressourcen der Form abgelegt und der Designer schreibt in sein File:

          this.form16UC1.Prop.Prop4 = ((System.Collections.Generic.List<WindowsFormsControlLibrary1.Form16UcSubType>)(resources.GetObject("resource.Prop4")));
    

    Beschreibe mal, was bei Dir nicht funktioniert.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Dienstag, 23. Januar 2018 07:10
  • Hallo Peter,

    ich habe in dem Beispiel Prop1 als List<int> definiert, Prop2 und Prop3 gelöscht und das Editorfenster für Prop1 auch angepasst. Das hat alles gut funktioniert und, wie du gesagt hat, gibt es nun im Form Ressourcen File resource.Prop1 vom Typ System.Collections.Generic.List`1[[System.Int32:

      <data name="resource.Prop1" mimetype="application/x-microsoft.net.object.binary.base64">
        <value>
            AAEAAAD/////AQAAAAAAAAAMAgAAAJoBbXNjb3JsaWIsIFZlcnNpb249NC4wLjAuMCwgQ3VsdHVyZT1u
            ZXV0cmFsLCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5XV0sIG1zY29ybGliLCBWZXJzaW9u
            PTQuMC4wLjAsIEN1bHR1cmU9bmV1dHJhbCwgUHVibGljS2V5VG9rZW49Yjc3YTVjNTYxOTM0ZTA4OQUB
            AAAAL1N5c3RlbS5Db2xsZWN0aW9ucy5HZW5lcmljLkxpc3RgMVtbU3lzdGVtLkludDMyAwAAAAZfaXRl
            bXMFX3NpemUIX3ZlcnNpb24HAAAICAgCAAAACQMAAAAFAAAADAAAAA8DAAAACAAAAAgAAAAAAQAAAAIA
            AAADAAAABAAAAAAAAAAAAAAAAAAAAAs=
    </value>
      </data>

    Das List Objekt scheint nun irgendetwas zu besitzen, was die enthaltenen Daten als bas64 wegschreibt und auch davon lesen kann. 

    Das ist ja praktisch schon das, was ich jetzt als Workaround bei mir schon drin habe.

    Wenn ich nun eine Eigenschaft habe, die sich selber in irgendeinem Format in die Form.resx schreibt und auch von daher wieder geladen werden kann, könnte ich das ja sehr universell nutzen. Aber wie geht das? Ich schätze, das hat was mit serialization Settings zu tun.

    Ich werde da noch mal weiter googlen, wäre aber für Hilfe dankbar.

    Samstag, 27. Januar 2018 21:51
  • Hi Erwin,
    die Eigenschaft muss vom Typ einer Klasse sein, die serialisierbar ist. Wenn das gewährleistet ist und auch die Typen in der Klasse serialisierbar sind, dann steht dem nichts im Wege.

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Samstag, 27. Januar 2018 22:47
  • Hi,

    ich dachte, ich hätte es verstanden, es funktioniert aber nicht. Ich poste mal den Source Code

    Als erstes ist da die Komponente Form16UC selber, die von UserControl vererbt ist. Die Komponente hat die Eigenschaft Prop vom Typ Form16UCPropType.

        public partial class Form16UC : UserControl
        {
            public Form16UC()
            {
            }
    
            [Browsable(true),
              EditorBrowsable(EditorBrowsableState.Always),
              DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
              RefreshProperties(RefreshProperties.Repaint),
              Editor(typeof(Form16UcPropEdit), typeof(UITypeEditor)),
              Description("Die Werte der Eigenschaft wird in einem separaten Fenster zur Bearbeitung angezeigt.")]
    
            [CategoryAttribute("Behavior")]
            public Form16UcPropType Prop { get; set; } = new Form16UcPropType();
    
        }
    

    Die Eigenschaft selber soll nun serialisiert und in Resource der Resource Datei gespeichert werden. Das Serialisieren ist komplett unter meiner Kontrolle, also implementiere ich das ISerializable Interface.

       public class Form16UcPropType : ISerializable
        {
            private List<int> Werte = new List<int>();
    
            public Form16UcPropType()
            {
    
            }
    
            protected Form16UcPropType(SerializationInfo info, StreamingContext context)
            {
                int n = info.GetInt32("n");
                for (int i = 0; i < n; i++)
                {
                    Werte.Add(info.GetInt32("W" + i));
                }
            }
    
            public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
            {
                info.AddValue("n", Werte.Count);
                for (int i = 0; i < Werte.Count; i++)
                {
                    info.AddValue("W" + i, Werte[i]);
                }
            }
    
            public int this[int index]
            {
                get
                {
                    if (index < Werte.Count) return Werte[index];
                    else return 0;
                }
                set
                {
                    while (index >= Werte.Count) Werte.Add(0);
                    Werte[index] = value;
                    OnPropertyChanged();
                }
            }
    
            public int Count { get { return Werte.Count; } }
        }

    Dann gibt es noch ein Fenster/Form, welches eine Listbox enthält, um die Werte anzuzeigen und ein Menü, um neue Werte hinzuzufügen.

      public partial class Form16UcPropEditFenster : Form
        {
            public Form16UcPropEditFenster()
            {
                InitializeComponent();
            }
    
            private void toolStripMenuItem1_Click(object sender, EventArgs e)
            {
                listBox1.Items.Add(listBox1.Items.Count);
            }
        }

    Als letztes gibt es dann noch den Editor, der die Werte in die Liste des Fensters schreibt und die neuen Werte zurück.

      public class Form16UcPropEdit : UITypeEditor
        {
            public override UITypeEditorEditStyle GetEditStyle(ITypeDescriptorContext context) => UITypeEditorEditStyle.Modal;
    
            public override object EditValue(ITypeDescriptorContext context, IServiceProvider provider, object value)
            {
                Form16UcPropType Prop = value as Form16UcPropType;
                Form16UcPropEditFenster F = new Form16UcPropEditFenster();
                for (int i = 0; i < Prop.Count; i++)
                    F.listBox1.Items.Add(Prop[i]);
                F.ShowDialog();
                List<int> L = new List<int>();
                for (int i = 0; i < F.listBox1.Items.Count; i++)
                    Prop[i] = (int)F.listBox1.Items[i];
                return value;
    
            }
        }

    Ziehe ich nun ein Form16UC auf ein neues Fenster, wird es angezeigt. Bei der Eigenschaft Prop steht dann auch Form16UcPropType und es erscheint das Fenster, mit dem ich neue Werte hinzufügen kann. Das funktioniert alles sehr gut. Nur wird Form16UC nicht serialisiert und wenn ich das komplette Visual Studio (nach dem hinzufügen einiger Werte) schließe und neu öffne, sind die Werte weg. In der Designer Datei des Fensters steht auch kein GetObject.

    Definiere ich in Form16UcPropType nun eine Eigenschaft mit einer Liste von ints, steht die in der Designer Datei mit einem entsprechenden GetObject.

        public class Form16UcPropType : ISerializable
        {
            private List<int> Werte = new List<int>();
    
            [Category("Behavior")]
            public List<int> Prop1
            {
                get
                {
                    return Werte;
                }
                set
                {
                    Werte.Clear();
                    for (int i = 0; i < value.Count; i++)
                        Werte.Add(value[i]);
                    OnPropertyChanged();
                }
            }
    
        Hier kommt noch das von ISerializable und der Indexer
    }

    Ich hätte aber gerne das gesamte Objekt serialisiert und per Designer in einer Datei gespeichert.

    Viele Grüße
    Erwin

    Sonntag, 28. Januar 2018 17:43
  • Hi Erwin,
    das sind nur Codeauszüge, z.B. ist unklar im Form16UcPropType.

                    OnPropertyChanged();

    Ich kann das nicht prüfen, da Dein Code nicht vollständig ist und bei mir Fehler bringt.

    Auch habe ich nicht verstanden, was Du genau erreichen willst. In meinem Beispiel werden Eigenschaften des Form16UcPropType, die nicht von skalaren Wertetypen sind, serialisiert in den Ressourcen des Form mit dem platzierten UserControl abgelegt. Ich habe jetzt nicht getestet, ob eine Eigenschaft im Form16UcPropType von einem eigenen Typ (bei mir Form16UcSubType) mit eigener Serialisierung passend abgelegt wird.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Sonntag, 28. Januar 2018 18:24
  • Hallo Peter,

    das PropertyChanged kam aus deinem Beispiel, hat aber keine Auswirkungen. Die Zeile hätte ich noch beim Kopieren des Source Code herausnehmen sollen.

    Ich dachte, ich könnte nun eine Eigenschaft designen, die mehrere Variablen von verschiedenen Typen, und auch durchaus Listen von anderen Typen enthält. Implementiere ich nun das Serialisieren Interface, was ich sehr flexibel für mich mit GetObjectData und einem speziellen Konstruktor implementieren kann, wird diese Eigenschaft automatisch vom Fenster Designer in eine Datei gespeichert. Aber das scheint nicht zu gehen.

    Für mich sieht es so aus, als ob das Serialisieren der Liste in der Listenklasse selber implementiert ist. Sonst wird das über Sub-Eigenschaften realisiert. In dem Beispiel mit der Liste ist es ja auch eine Sub-Eigenschaft (Prop1)meiner eigentlichen Eigenschaft Form16UC. 

    Viele Grüße
    Erwin

    Sonntag, 28. Januar 2018 19:46
  • Hi Erwin,
    ich hatte Dich so verstanden, dass Dein UserControl eine Eigenschaft Deines eigenen Typs hat (bei mir "Prop" vom eigenen Typ "Form16UcPropType"). Diese Eigenschaft hat wiederum mehrere Eigenschaften unterschiedlichen Typs, die per Designer persistiert werden sollen (bei mir Prop1, Prop2, Prop3 einfach und Prop4 vom eigenen komplexen Typ Form16UcSubType). Die einfachen Typen werden direkt im Designer-Code persistiert, der komplexe Typ in den Ressourcen serialisiert.

    Ob Dein Ansinnen, eine Eigenschaft eines UserControls direkt als serialisierte Ressource zuzuweisen, geht bestimmt auch; ich weiß aber nicht, wie die Klasse zu gestalten ist. Auch weiß ich nicht, wofür diese spezielle Arbeitsweise sinnvoll sein soll.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Sonntag, 28. Januar 2018 20:57
  • Hallo Peter,

    schade. Ich sehe einen Vorteil in der serialisierten Resource, dass ich verschiedene Versionen gut behandeln kann. Fall ich eine neue Version meines Controls mache, welche mehr Funktionalität enthält, kann ich das bei dem Serialisieren des Designers nicht erkennen. Bei eigenen Routinen kann ich zuerst eine Versionsnummer serialisieren und dann die Daten. Beim "lesen" kann ich nun abhängig von der Versionsnummer die neuen oder veränderten Daten auch lesen. So mache ich das schon lange, wenn ich Sachen in einen Stream schreibe.

    Bei "normalen" Subeigenschaften, die einen Default Wert haben, würde das auch zu keinen Problemen führen. Die neuen Eigenschaften würden ja mit einem Default Wert starten, der auch nicht serialisiert wird. Aber beim serialisieren in die Resource Datei werden die Daten doch komplett unstrukturiert hintereinander weggeschrieben. Ein Einfügen eines neuen Wertes führt also zu falschen Ergebnissen beim Lesen.

    Viele Grüße
    Erwin

    Sonntag, 28. Januar 2018 22:41
  • Hi Erwin,
    die zusätzliche Eigenschaft des UserControls muss von einem konkreten Typ sein, der z.B. auch Änderungen meldet, damit der Designer die Anzeige aktualisiert. Deshalb ist es nicht möglich, einen allgemeinen vorher nicht definierten Typ durch den Designer allgemein nutzen zu wollen. 

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Montag, 29. Januar 2018 07:04
  • Ich habe nun meine ganze Konfiguration der Eigenschaftsklasse in einen ConfString konvertiert, der dann als String Eigenschaft vorliegt, für das auch eine Änderungsnotiz implementiert ist. Auf die Form16UcPropType Klasse angewandt wäre das

        public class Form16UcPropType : INotifyPropertyChanged
        {
            private List<int> Werte = new List<int>();
    
            public string ConfString
            {
                get
                {
                    string S = "";
                    for (int i = 0; i < Werte.Count; i++)
                        S += Werte[i];
                    return S;
                }
                set
                {
                    Werte.Clear();
                    for (int i = 0; i < value.Length; i++)
                        Werte.Add(i);
                    //PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ConfString"));
                    OnPropertyChanged();
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
            internal void OnPropertyChanged([CallerMemberName] string propName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
    
            public int this[int index]
            {
                get
                {
                    if (index < Werte.Count) return Werte[index];
                    else return 0;
                }
            }
    
            public int Count { get { return Werte.Count; } }
    
            public void Add()
            {
                Werte.Add(Werte.Count);
            }
        }
    

    Das hat sogar einmal kurz funktioniert, danach aber nicht mehr. Natürlich habe ich meines Wissens nach nichts relevantes geändert, aber das sagen sie ja alle.

    Beim Debuggen habe ich festgestellt, PropertyChanged null ist. Dann bekommt der Designer die Änderungen nicht mit und die Änderungen werden nicht mit dem Form abgespeichert. Wie bringe ich den Fenster Designer nun dazu, dass es sich bei PropertyChanged registriert? Beim Googlen finde ich einiges über einen DataContext. Aber gibt es so etwas bei mir auch? Oder es wird von DataBinding gesprochen, was das auch wohl macht.

    Irgendwie scheine ich bei dem einem Male das so gemacht zu haben (wie auch immer), dass der Designer sich registriert hat. Dann hat es auch funktioniert (die Änderungen am ConfString wurden in die Form Datei geschrieben). Danach aber nicht mehr.

    Ich bin mir relativ sicher, wenn dieses Registrieren bzw. Änderungen Erkennen Problem gelöst ist, habe ich alles, was ich brauche und es funktioniert, wie ich möchte.

    Viele Grüße
    Erwin

    Freitag, 2. Februar 2018 20:55
  • Hi Erwin,
    bei mir funktioniert es problemlos. Das NotifyPropertyChanged wird nur benötigt, wenn es Änderungen im Objekt der Eigenschaft gibt, die z.B. an die Oberfläche zu melden sind. Leider erkennt der Designer dieses Ereignis nicht für eine erneute Serialisierung geänderter Werte. Deshalb ist es notwendig, über eine geänderte Referenz (neues Objekt) dem Designer mitzuteilen, dass erneut zu serialisieren ist.

    Damit wir hier weiterkommen, solltest Du nochmals genauer Dein Ansinnen formulieren. Eine Serialisierung einer Eigenschaft eines UserControls führt der Designer nicht aus. Deshalb muss man einen Typ als Eigenschaft nutzen, der eine eigene Eigenschaft einschließt, die dann serialisiert wird.

    Bei mir funktioniert:

    Das UserControl hat eine zusätzliche Eigenschaft von einem eigenen Typ.

    Dieser eigene Typ enthält eine Eigenschaft eines eigenen Sybtyps.

    Dieser Subtyp implementiert ISerializable und kann damit beliebige Daten serialisiert in den Ressourcen der Form ablegen.

    Der Editor bearbeitet die Daten des eigenen Typs incl. des Subtyps und erzeugt nach den Editiervorgang eine neue Instanz des eigenen Typs, indem die alten und die neuer Werte reinkopiert werden.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Samstag, 3. Februar 2018 07:31
  • Hallo Peter,

    ich habe jetzt das, was ich möchte. Wenn man eine neue Instanz der Eigenschaft-Klasse der Set-Funktion übergibt, wird der String, in dem ich die komplette Konfiguration meiner Eigenschaft gestreamt habe, auch in den Resourcen abgespeichert. Ich habe noch mal kurz probiert, ob die Eigenschaft selber serialisiert werden kann und dann auch in den Resourcen landet, das hat aber nicht funktioniert. Es ist aber kein Problem in meiner Eigenschafts-Klasse wieder eine ConfString Eigenschaft zu haben.

    Viele Dank.

    Sollte ich diese Frage nun noch irgendwie als gelöst markieren? Ich habe so etwas hier noch nicht gemacht. Auch kann ich Antworten, die ich (besonders) hilfreich fand, mit einem Klick auf die Zahl daneben markieren, richtig?  Dann erhöht sich die Zahl und die Summe wird insgesamt als "x Stimmen" in der Übersicht angezeigt?

    Ich hatte zwischendurch schon Zweifel, ob das doch was wird. Aber hat ja dank der Hilfe geklappt.

    Viele Grüße
    Erwin

    Dienstag, 6. Februar 2018 21:04
  • Hi Erwin,
    schön, dass es funktioniert. Markiere alle hilfreichen Antwirten als gelöst, damit auch andere Mitleser die einzelnen Beiträge als Lösung erkennen.

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Dienstag, 6. Februar 2018 21:25
  • Wie markiere ich denn eine Antwort als Gelöst? Bei einigen Antworten habe ich schon als Hilfreich abgestimmt.
    Mittwoch, 7. Februar 2018 20:03
  • Hallo Erwin,

    Wie markiere ich denn eine Antwort als Gelöst?

    Durch Klick auf Als Antwort markieren unter dem Beitrag kannst Du den entsprechenden Beitrag/die entsprechenden Beiträge als Lösung kennzeichnen.

    Gruß,
    Dimitar


    Bitte haben Sie Verständnis dafür, dass im Rahmen dieses Forums, welches auf dem Community-Prinzip „IT-Pros helfen IT-Pros“ beruht, kein technischer Support geleistet werden kann oder sonst welche garantierten Maßnahmen seitens Microsoft zugesichert werden können.

    Dienstag, 13. Februar 2018 12:56
    Administrator
  • Noch etwas, was mir gerade klar geworden ist (zumindest erklärt es meine Beobachtungen) und was anderen vielleicht auch helfen kann.

    Der Designer speichert Änderungen persistent ab, wenn er für die Eigenschaft eine neue Instanz bekommt. Das wurde oben gesagt und ist auch korrekt. Allerdings speichert er wohl keine Änderungen, wenn die neue Instanz mit dem "Equal"-Vergleich mit der aktuellen/alten Instanz "true" zurückgibt. 

    Ich hatte in meinem Editor halt schon eine neue Instanz erstellt, die Orginalinstanz aber auch geändert. Die Änderungen waren somit in beiden Instanzen enthalten. Dann werden die Änderungen nicht gespeichert.

    Beim Schreiben kommt mir aber noch eine Erklärung, die mir sogar plausibler erscheint. Wie oben beschrieben, habe ich ja die Konfiguration meines Eigenschaftsobjekt in einen String geschrieben, der dann als public Eigenschaft vom Designer gespeichert wird. Habe ich nun im Editor nicht nur die neue Instanz sondern auch die Orginalinstanz verändert, liefern beide Instanzen ja den gleichen Konfigurationsstring. Ich habe nun also die Situation, dass in der Datei (also persistent) der Konfigurationsstring vor der Änderung steht, der Konfigurationsstring des neu erzeugten Objekts aber mit dem aktuellen Konfigurationsstring des Orginalobjekts übereinstimmt. Obwohl nun die Strings von beiden Objekten nicht mit dem in der Datei übereinstimmen, wird der neue String nicht in die Datei geschrieben; beide Strings sind halt identisch.

    Sonntag, 4. März 2018 21:37