Autor Beitrag
Kasko
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 22



BeitragVerfasst: So 10.06.18 20:12 
Wie kann man die Add Methode der ControlCollection einer custom user control überschreiben?


Es geht darum dass ich in meiner UserControl nur bestimmte Elemente haben möchte und deshalb mag ich ungewollte abfangen. Zudem mag ich sie auch nicht der Control sondern einer Untereinheit, einem Panel, hinzufügen. Dafür habe ich eine extra Methode die man auch von außen aufrufen kann und der man diese Elemente übergeben kann. Das Problem ist, dass man auch ungewollt durch Controls.Add() ein beliebiges Element hinzufügen kann, was nicht gewollt ist. Da Add eine virtual void Methode ist, muss es rein theoretisch ja auch möglich sein also hier nochmal die Frage:


Wie kann man die Controls.Add Methode überschreiben?
Frühlingsrolle
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2003
Erhaltene Danke: 366

[Win NT] 5.1 x86 6.1 x64
[Delphi] 7 PE, 2006, 10.1 Starter, Lazarus - [C#] VS Exp 2012 - [Android API 15] VS Com 2015, Eclipse, AIDE - [C++] Builder 10.1
BeitragVerfasst: Mo 11.06.18 00:18 
Guten Abend Kasko,

dafür musst du eine eigene Collection-Klasse erstellen und von der ControlCollection-Klasse erben. Darin überschreibst du u.a. auch die Add() Methode nach Bedarf.
Schlussendlich muss in deiner UserControl-Klasse die Eigenschaft .Controls überschrieben werden, also vom Typ "NeueCollection"-Klasse abstammen.

_________________
„Politicians are put there to give you the idea that you have freedom of choice. You don’t. You have no choice. You have owners. They own you. They own everything." (George Denis Patrick Carlin)
Kasko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 22



BeitragVerfasst: Mo 11.06.18 00:54 
Okay ich habe jetzt die Klasse erstellt

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
public class ButtonListViewCollection : Control.ControlCollection
{
    public ButtonListViewCollection(Control owner) : base(owner) { }

    public override void Add(Control value)
    {
        if (value is BunifuFlatButton)
        {
            value.Dock = DockStyle.Top;
            base.Add(value);
        }
        else
            throw new ArgumentException();
    }
}


Aber wie soll ich this.Controls überschreiben? Das ist ja schreibgeschützt.
Frühlingsrolle
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2003
Erhaltene Danke: 366

[Win NT] 5.1 x86 6.1 x64
[Delphi] 7 PE, 2006, 10.1 Starter, Lazarus - [C#] VS Exp 2012 - [Android API 15] VS Com 2015, Eclipse, AIDE - [C++] Builder 10.1
BeitragVerfasst: Mo 11.06.18 00:58 
Statische "Elemente" überschreibst du mit new. Virtuelle und abstrakte "Elemente" mit override.

_________________
„Politicians are put there to give you the idea that you have freedom of choice. You don’t. You have no choice. You have owners. They own you. They own everything." (George Denis Patrick Carlin)
Kasko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 22



BeitragVerfasst: Mo 11.06.18 01:04 
Das weiß ich aber this.Controls = new ButtonListViewCollection(this); ist nicht legitim weil es schreibgeschützt ist

ausblenden C#-Quelltext
1:
public Control.ControlCollection Controls { get; }					
Frühlingsrolle
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2003
Erhaltene Danke: 366

[Win NT] 5.1 x86 6.1 x64
[Delphi] 7 PE, 2006, 10.1 Starter, Lazarus - [C#] VS Exp 2012 - [Android API 15] VS Com 2015, Eclipse, AIDE - [C++] Builder 10.1
BeitragVerfasst: Mo 11.06.18 01:16 
Ich dachte etwas in die Richtung:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
public class DummyControl : UserControl
{
    private ButtonListViewCollection _buttonListViewCollection = new ButtonListViewCollection();

    public new ButtonListViewCollection Controls 
    { 
        get { return _buttonListViewCollection; } 
    }
}

_________________
„Politicians are put there to give you the idea that you have freedom of choice. You don’t. You have no choice. You have owners. They own you. They own everything." (George Denis Patrick Carlin)

Für diesen Beitrag haben gedankt: Kasko
Kasko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 22



BeitragVerfasst: Mo 11.06.18 01:21 
oh ja kleiner Denkfehler mit dem new Danke ;)
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1151
Erhaltene Danke: 147

Windows 10 x64 Home Premium
C# (VS 2015 Enterprise)
BeitragVerfasst: Mo 11.06.18 12:23 
Sei sehr vorsichtig mit dem new...
Das ist im Prinzip das Gegenteil von Objektorientierung und es würde mich auch wundern, wenn das funktioniert.

Der Grund ist ganz einfach:
Wenn Du mit override einen Member überschreibst, dann gilt das immer, egal mit welcher Instanz und unabhängig von dem Typ der Variable/Property.
Wenn Du new verwendest, blendest Du für diesen konkreten Typ die originale Variante der Basis-Klasse aus. Bei der Verwendung wird deine Variante also nur dann verwendet, wenn die Variable auch genau diesen Typ hat. Hat sie den Typ der Basis-Klasse, wird deine Änderung nicht verwendet.

Für dich heißt das:
Wenn Du dein Control, was eine eigene Collection nutzen soll, irgendwo verwendest, dann arbeitet WinForms grundsätzlich mit dem Typ der Basis, weil es deinen Typ streng genommen nicht kennt.
Es wird also weiterhin die ursprüngliche Collection verwenden und nicht deine angepasste Version.


Viel passender klingen dagegen diese Events:
Control.ControlAdded
Control.ControlRemoved
Damit brauchst Du keine schmutzigen Umwege zu gehen, das ist ja scheinbar genau das, was Du haben willst.

Übrigens hab ich das sehr schnell gefunden, indem ich ein Control abgeleitet und dann "base.Control" getippt habe. In der IntelliSense-Liste ist dann gleich aufgetaucht, was ich gesucht habe.
Das ist übrigens immer ein guter Tipp, einfach mal die IntelliSense-Liste durchzugehen und zu sehen, was da so herum schwirrt.
Frühlingsrolle
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 2003
Erhaltene Danke: 366

[Win NT] 5.1 x86 6.1 x64
[Delphi] 7 PE, 2006, 10.1 Starter, Lazarus - [C#] VS Exp 2012 - [Android API 15] VS Com 2015, Eclipse, AIDE - [C++] Builder 10.1
BeitragVerfasst: Mo 11.06.18 17:04 
Der new-Modifizierer ist garnicht so böse, wie du ihn darstellst, und gehört, neben override, ebenso in die OOP!

Wenn du es dir nicht vorstellen kannst, dass es funktionieren kann, dann würde ich es an deiner Stelle einfach mal ausprobieren. :nixweiss:

Mit dem Ereignis ControlAdded hat man das Problem, dass es "zu spät" reagiert. Es wird erst dann ausgelöst, nachdem .Controls.Add() ausgeführt wurde, d.h. im Fall vom TE, wird jedes Control aufgenommen, auch ein unerwünschtes Control, das ungleich BunifuFlatButton() entspricht.

_________________
„Politicians are put there to give you the idea that you have freedom of choice. You don’t. You have no choice. You have owners. They own you. They own everything." (George Denis Patrick Carlin)
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1151
Erhaltene Danke: 147

Windows 10 x64 Home Premium
C# (VS 2015 Enterprise)
BeitragVerfasst: Mo 11.06.18 17:29 
Das Konzept von override ist in der OOP die Polymorphie. Das new-Schlüsselwort ist das exakte Gegenteil, es widerspricht jedem Ziel von der OOP.

Dein Vorschlag funktioniert nämlich nur so lange, wie man den konkreten Control-Typ kennt. Sobald man z.B. das macht:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
// in der Form
this.Controls[0].Controls.Add(new Label()
{
    Text = "My Label",
});
... funktioniert es nicht mehr, weil der Typ, auf den der Zugriff angewendet wird, nur noch Control ist und nicht die eigene Variante, sodass das new-Schlüsselwort nicht mehr greift.
Ich hab keinen Überblick, was WinForms alles kann, aber jedes Laufzeit-Feature, was die ControlCollection verwendet, wird nicht die neue Variante verwenden.

Ich wiederhole daher: Sei sehr vorsichtig mit dem new-Schlüsselwort. Das ist nicht böse, aber auf jeden Fall gefärhlich :wink:
Ich persönlich verwende es nur um z.B. in einer Ableitung einen konkreten Typ anbieten zu können, wenn ich genau weiß, was das für ein Typ ist. Bei Properties beinhaltet das dann nur einen Cast, sonst nichts. Für funktionale Dinge würde ich das new-Schlüsselwort niemals verwenden, das führt nur zu ätzenden Fehlern, weil man mit sauberer OOP rechnet.

Abgesehen davon gibt's einen viel besseren Weg: protected override ControlCollection CreateControlsInstance()
Die Methode von der Control-Klasse ist genau dafür da und wird vermutlich auch intern von WinForms verwendet.
Darüber kannst Du eine eigene Ableitung der ControlCollection zurück geben und die wird dann sauber nach der OOP überall verwendet.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4259
Erhaltene Danke: 851


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Mo 11.06.18 19:44 
Edit : Ups hat schon jemand empfohlen. Übersehen :oops:


Das spezifische Control sollte einfach CreateControlsInstance überschreiben. Die von dieser Methode zurückgegebene Instanz wird dann von dem Control überall benutzt (auch schon im Designer).

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
public class MeinLiebesButtonListView : UserControl
{
    protected override ControlCollection CreateControlsInstance()
    {
        return new ButtonListViewCollection(this);
    }
}
Palladin007
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starofftopic star
Beiträge: 1151
Erhaltene Danke: 147

Windows 10 x64 Home Premium
C# (VS 2015 Enterprise)
BeitragVerfasst: Mo 11.06.18 20:20 
PS:

Ich würde das gar nicht über diese Collection machen ^^

Irgendwo wird ja wohl das Element der Auflistung hinzu gefügt werden, meiner Meinung nach wäre es klüger, an genau dieser Stelle zu filtern.
Kasko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 22



BeitragVerfasst: Mo 11.06.18 20:33 
Vielen Dank. Die Existenz dieser Methode war mir bisher nicht bewusst.
Kasko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 22



BeitragVerfasst: Di 12.06.18 21:20 
Vielleicht könnt ihr mir ja nochmal helfen. Meine Add Methode sieht im Moment so aus und das ist wie man so schön sagt "bad practice":

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
public override void Add(Control value)
{
    if (value is BunifuFlatButton)
    {
        value.Dock = DockStyle.Top;
        Panel panel = (base.Find("panel_Playlist"true)[0as Panel);
        panel.Controls.Add(value);
    }
    else if (value is Panel)
        base.Add(value);
    else
        throw new ArgumentException();
}



Dabei gibt es aber einige Probleme:


1. Ein Codeabschnitt erlaubt das Hinzufügen von Panels, was eigentlich nicht gewollt ist, aber leider notwendig ist, da ein Panel vorhanden sein muss und welches in der InitializeComponent Methode hinzugefügt wird. Daher muss dieser Abschnitt vorhanden sein damit das Hinzufügen dieses Panels keine Exception auswirft. Gleichzeitig erlaubt er aber das weitere Hinzufügen von Panels was ganz und gar nicht gewollt ist.

2. Beim Hinzufügen eines BunifuFlatButtons möchte ich ihn nicht dem Formular hinzufügen sondern einer Untereinheit, dem Panel. Dafür muss ich das Panel aber erstmal herausfiltern und das kann theoretisch zu einem Crash führen. Zudem ist diese Lösung alles andere als elegant.

Meine Frage ist jetzt also, ob ihr eine Methode kennt das ganze weitaus eleganter zu lösen als ich es gemacht habe.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4259
Erhaltene Danke: 851


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Di 12.06.18 21:52 
Zwei Gedanken.

a.) So wie das programmiert ist in der ControlsCollection dieses Controls nur ein einziges weiteres Control, halt das genannte Panel. Anstatt da auf Panel zu testen (bzw. nur auf Panel zu testen) würde ich also testen ob das das erste Control in der Collection ist und nur dieses hinzufügen. Nebenbei könnte man sich dann dieses erste Control merken und im folgenden die Buttons diesem gemerkten Control hinzufügen. Dann entfällt das suchen des Controls via Name. Etwa

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
private Control container;
public override void Add(Control value)
{
    if (Count == 0)
    {
        container = value;
        base.Add(container);
    }
    else if (value is BunifuFlatButton)
        container.Controls.Add(value);
    else
        throw new ArgumentException();                
}


b.) Wenn auf diesem Control nur das Panel liegt warum hast du nicht gleich von Panel abgeleitet und das entsprechende dort getan? Was hat das umliegende Control noch das nicht einfach auch zu diesem Panel gehören könnte?
Kasko Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 22



BeitragVerfasst: Mi 13.06.18 13:02 
Das Panel soll den Inhalt enthalten und das Formular der Control soll einen Rahmen darum bilden, sodass durch auf und ab bewegen des Panels ein Scrolleffekt zustande kommt. Wenn ich einfach nur den scroll value des Panels setze kommt dieser Effekt zwar auch zustande aber es zeigen sich die paneleigenen ScrollBars welche man nicht dauerhaft ausblenden kann. Daher diese Vorgehensweise.