Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

1

22.04.2015, 22:40

[C# WPF] Vererbte Klassen mit vererbten Steuerelementen darstellen

Hallo Leute,

ich plane im Augenblick ein bisschen größeres Projekt. Ich schreibe eine Anwendung und möchte dazu C# und WPF verwenden. Ich habe mir schon einen Plan gemacht und überlege grade an der Umsetzung. Dabei habe ich folgende Designfrage:

Ich habe eine Liste an Objekten. Die haben alle verschiedene Typen und erben alle von der Basisklasse Kanal. Nun möchte ich meine Liste in einem Stackpanel anzeigen lassen. Dabe soll für jedes Element ein Steuerelement generiert werden. Welches das ist, kommt aber auf den Typ an. Allerdings gibt es ein Basissteuerelement, dass den Rahmen und ein Eingabefeld vorsieht. Die einzelnen Elemente sollen nun dieses Basissteuerelement benutzen und um ihren klassenspezifischen Inhalt (Steuerelemente und Beschriftung des Eingabefeldes) ergänzen. Das resultierende wird dann auf dem Stackpanel angezeigt.

Meine bisherige Idee: Meine Basisklasse Kanal enthält auch XAML Code und definiert darin das Basissteuerelement. Nun werden alle weiteren Klassen davon abgeleitet. Diese müssten dann irgendwie das Basissteuerelement modifizieren. Meine Liste enthält letztendlich die Steuerelemente, die dann einfach vom Stackpanel angezeigt werden.

Das Problem dabei: Wie vererbe ich XAML-Code bzw. wie modifiziere ich diesen in einer abgeleiteten Klasse? Und ist meine Idee überhaupt die beste/einfachste ode gibts da noch was besseres?

Mir ist jede Lösung willkommen, solange ich sehr schnell das Basissteuerelement modifizieren kann (was dann in alle abgeleiteten Klassen übernommen wird) und ich schnell, weitere Klassen ableiten kann, die dann ohne große Codeänderung auch angezeigt werden können.

Ich hoffe, meine Formulierung ist verständlich! Wenn ihr noch weitere Informationen oder ein Visuelles Beispiel wollt, dann kann ich das gerne nachreichen!

Falls sich meine Frage durch ein schnelles Googeln eurerseits lösen lässt, so tut mir das Leid, ich hab versucht, Google klarzumachen, was ich will, aber das hat zu keiner Lösung geführt...
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

2

22.04.2015, 22:50

Da überschreiben deine abgeleiteten Objekte eine 'InsertControls'-Methode und fügen in dieser ihre eigenen Steuerelemente in das Fenster.

Tobiking

1x Rätselkönig

  • Private Nachricht senden

3

23.04.2015, 00:42

Das sieht nach dem aus was du suchst: http://stackoverflow.com/questions/44436…eritance-in-wpf

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

4

25.04.2015, 23:37

@Roflo: Gibts diesen Weg auch über XAML? So wie unten beschrieben über die Content-Eigenschaft eines eigenen Steuerelements.

@Tobiking: Super Link! Habe mir ne Menge durchgelesen - und eine Menge Ideen bekommen...

Liege ich da richtig, dass ich das UI von der "Daten"-Klasse trennen muss? Und das mache ich mit DataTemplates, die von einem Basiselement erben?

Ist es Möglich, ein eigenes Steuerelement zu programmieren, das als Content Steuerelemente akzeptiert, die es dann an eine bestimmte Stelle in sich selbst einfügt? Dann könnte ich DataTemplates programmieren, die dieses Steuerelement als Haupt-Element haben. Das kann ich dann wiederum über die Contenteigenschaft anpassen. Wäre das auch eine schöne Lösung? Oder ist das "schlechte" Vererbung? :D

Das ContainerControl würde dann mithilfe der DataType-Eigenschaft der Templates das passende Steuerelement auswählen. Oder ist das einfacher mir einem DataTemplateSelector zu arbeiten?
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

5

27.04.2015, 15:36

Liege ich da richtig, dass ich das UI von der "Daten"-Klasse trennen muss? Und das mache ich mit DataTemplates, die von einem Basiselement erben?
Es ist empfehlenswert, in WPF UI und Daten voneinander zu trennen. Somit kannst du per DataTemplate deine Daten in verschiedenen Steuerelementen darstellen.

Ist es Möglich, ein eigenes Steuerelement zu programmieren, das als Content Steuerelemente akzeptiert, die es dann an eine bestimmte Stelle in sich selbst einfügt? Dann könnte ich DataTemplates programmieren, die dieses Steuerelement als Haupt-Element haben. Das kann ich dann wiederum über die Contenteigenschaft anpassen. Wäre das auch eine schöne Lösung? Oder ist das "schlechte" Vererbung?
Es ist möglich, zum Beispiel einer ListView oder einem anderen Listensteuerelement eine Collection von Daten zu übergeben (Mit der ObservableCollection bekommt dein Steuerelement sogar noch mit, ob sich die Liste durch Benutzerinteraktion o.Ä. geändert hat). Innerhalb dieses Steuerelements kannst du dann eigene ItemTemplates schreiben, welche dann eigene Darstellungen für jeden Typ innerhalb der Liste haben. Damit brauchst du dann keine Steuerelemente zu übergeben, sondern nur per Binding deine Datenelemente übergeben.

Als Stichwort zur Trennung von Daten und UI sei vielleicht das MVVM-Pattern genannt, wo Daten, Kontrollschicht und UI voneinander getrennt werden.

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

6

27.04.2015, 21:42

Danke, für die schnelle Rückmeldung! Ich werde mich mal über MVVM belesen.

Es ist möglich, zum Beispiel einer ListView oder einem anderen Listensteuerelement eine Collection von Daten zu übergeben (Mit der ObservableCollection bekommt dein Steuerelement sogar noch mit, ob sich die Liste durch Benutzerinteraktion o.Ä. geändert hat). Innerhalb dieses Steuerelements kannst du dann eigene ItemTemplates schreiben, welche dann eigene Darstellungen für jeden Typ innerhalb der Liste haben. Damit brauchst du dann keine Steuerelemente zu übergeben, sondern nur per Binding deine Datenelemente übergeben.
Ich glaube, du hast meine Fragestellung ein wenig falsch verstanden... Mit Content waren eher andere Steuerelemente gemeint, die dann in dem übergeordneten erscheinen. Ich lege ja zum Beispiel bei einem Grid Steuerelemente als Content fest, die das Grid dann in sich positioniert. Kann ich eigene Steuerelemente programmieren, die auch so ein Verhalten haben? Also wo ich die Steuerelemente, die als Content übergeben werden, an einer bestimmten Stelle eingefügt werden. Ist soetwas sinnvoll, um Vererbung zu ersetzen bzw. nachzuahmen?

EDIT: Inzwischen habe ich den Fachbegriff für diese Art von Content gefunden: "Direct Content". So wie es für mich aussieht ist das nicht möglich, wie ich mir das vorstelle... Kann mir da wer zustimmen oder widersprechen?
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »CeDoMain« (27.04.2015, 22:22)


7

28.04.2015, 13:04

Ich hoffe, ich verstehe dich nicht wieder falsch :D

Du kannst natürlich auch wie in WinForms ein Steuerelement erstellen, es mit Daten befüllen und dann an einen beliebigen Container hängen (Grid, StackPanel, Canvas, etc.) - Da gibt es an (Fast) allen Controls eine Children-Collection, der du ein Steuerelement anhängen kannst.
Positionierung hängt dann vom Verhalten des Steuerelements ab, dem du die Children-Elemente anhängst. Wenn du z.B. Grid oder Canvas nimmst, dann würden deine Controls wahrscheinlich erstmal links oben in der Ecke hängen, wenn du keine Logik für die Positionierung angibst. StackPanel und WrapPanel würden deine Controls dann nebeneinander bzw. Untereinander anordnen, so wie du es im Verhalten des Panels angegeben hast.

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

8

29.04.2015, 20:24

Hmm leider ist das immernochnicht das, was ich suche... :(

Ich versuche es nochmal - diesmal etwas konkreter:

Ich habe ein DataTemplate, das besteht aus drei Border-Elementen, in denen abhängig von den Daten etwas angezeigt wird. In der oberen und mittleren Border befinden sich mehrere Steuerelemente, die untere ist leer. (Siehe Basis.png) Nun habe ich mehrere spezielle Datentypen, die mit den anderen zusammen auftreten, die sollen das gleiche DataTemplate als Basis haben. Einziger Unterschied: In der unteren Border sollen nun auch Steuerelemente angezeigt werden. Jeweils verschiedene für die verschiedenen Datentypen. (Siehe Spezial A.png oder Spezial B.png)

Wie mache ich das? Ich kann schlecht ein einziges DataTemplate programmieren, dass für jeden Datentyp die passenden Steuerelemente in der unteren Border einfügt oder weglässt. Aber mehrere verschiedene, bei denen ich das Basislayout immer neu aufbauen muss ist auch schlecht, denn bei einer Änderung an der Basis muss ich alle anderen auch ändern. Kann ich dieses Basislayout irgendwie vererben oder als Ressource (z.B. Style oder DataTemplate?) festlegen? Das muss dann aber so geschehen, dass ich in diese Basis noch Steuerelemente einfügen kann.

Ich hoffe, das ist nun etwas verständlicher. :)
»CeDoMain« hat folgende Bilder angehängt:
  • Basis.png
  • Spezial A.png
  • Spezial B.png
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

CeDoMain

Alter Hase

  • »CeDoMain« ist der Autor dieses Themas

Beiträge: 587

Wohnort: Ilmenau

Beruf: Student für Mechatronik

  • Private Nachricht senden

9

11.05.2015, 18:36

Da anscheinend keiner eine bessere Idee hat, wie die Vorgestellten, habe ich mich nun zu Tobikings vorschlag entschieden. Ich habe mich dabei an folgendem Code orientiert:

Zitat

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- Content for the template (note: not a template itself) -->
<Border x:Shared="False" 
        x:Key="Foo" 
        BorderBrush="Red" 
        BorderThickness="1" 
        CornerRadius="4">
    <TextBlock Text="{Binding SomeProp}" />
</Border>

<DataTemplate x:Key="TemplateA">
    <!-- Static resource - No binding needed -->
    <ContentPresenter Content="{StaticResource Foo}" /> 
</DataTemplate>

<DataTemplate x:Key="TemplateB">
    <!-- Static resource - No binding needed -->
    <ContentPresenter Content="{StaticResource Foo}" />
</DataTemplate>
Von Hier zweite Antwort.


Ich habe also eine XAML-Ressource vom Typ "Grid" programmiert, der die beiden oberen Borderelemente angehören. Die Bindung habe ich mittels {Binding Path=PropertyName} festgelegt. Nun habe ich für jede abgeleitete Klasse ein DataTemplate erstellt, dass aus einer "Border FunctionBorder + Stackpanel FunctionStackPanel" als Stammelement und dem Grid FunctionBase aus der Ressource als Unterelement besteht. Dazu kommen dann jeweils noch spezifische Elemente, die unterhalb des Ressourcen-Grids positioniert werden. Damit "Border + Stackpanel" drumherum bei allen Datatemplates gleich aussehen und sich gleich Verhalten habe ich dafür einen Style definiert. Gekapselt wird das ganze von einem ItemsControl, dass die Datatemplates dann auf eine gegebene Auflistung anwendet.

Mein gekürzter Code, für alle, dies interessiert:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
    <ItemsControl Grid.Row="1" x:Name="FunctionsPanel" ItemsSource="{Binding Path=ActualFixture.Functions}" Margin="0,190,0,0">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <WrapPanel/>
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
            <ItemsControl.Resources>
                <Style TargetType="Border" x:Key="FunctionBorder">
                    <Setter Property="BorderBrush" Value="#FF6C6C6C"/>
                    <Setter Property="Background" Value="#FFEAEAEA"/>
                    <Setter Property="BorderThickness" Value="3"/>
                    <Setter Property="CornerRadius" Value="20"/>
                    <Setter Property="Margin" Value="5"/>
                </Style>
                <Style TargetType="StackPanel" x:Key="FunctionStackPanel">
                    <Setter Property="Margin" Value="15"/>
                </Style>
                <Grid x:Key="FunctionBase" x:Shared="false">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="64"/>
                        <RowDefinition Height="10"/>
                        <RowDefinition Height="24"/>
                        <RowDefinition Height="10"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="10"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="64"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="64"/>
                    </Grid.ColumnDefinitions>
                    <Grid.Resources>
                        <Style TargetType="Border" x:Key="PictureBorder">
                            <Setter Property="BorderBrush" Value="#FF6C6C6C"/>
                            <Setter Property="Background" Value="#FFEAEAEA"/>
                            <Setter Property="BorderThickness" Value="2"/>
                            <Setter Property="CornerRadius" Value="5"/>
                        </Style>
                    </Grid.Resources>
                    <!-- Typ -->
                    <Border Grid.Row="0" Grid.Column="0" Style="{StaticResource PictureBorder}">
                        <Image Margin="0" Source="Resources/Images/None.ico"/>
                    </Border>
                    <Label Grid.Row="0" Grid.Column="1" Content="{Binding Path=Type}" HorizontalContentAlignment="Center" FontSize="20" FontWeight="Bold" VerticalContentAlignment="Center"/>
                    <Border Grid.Row="0" Grid.Column="2" Style="{StaticResource PictureBorder}">
                        <Image Margin="0" Source="{Binding Path=PictureFileName}" />
                    </Border>
                    <!-- Name -->
                    <Label Grid.Row="2" Grid.Column="0" Content="Name:"/>
                    <TextBox Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding Path=Name}"/>
                    <!-- Kanäle -->
                    <GroupBox Header="Kanäle" Grid.Row="4" Grid.ColumnSpan="3">
                        <!-- Kanaltemplates [...] -->
                    </GroupBox>
                </Grid>
                <DataTemplate DataType="{x:Type l:FunctionRGB}">
                    <Border Style="{StaticResource FunctionBorder}">
                        <StackPanel Style="{StaticResource FunctionStackPanel}">
                            <ContentPresenter Content="{StaticResource FunctionBase}"/>
                            <Button>
                                RGB
                            </Button>
                        </StackPanel>
                    </Border>
                </DataTemplate>
                <DataTemplate DataType="{x:Type l:FunctionDimmer}">
                    <Border Style="{StaticResource FunctionBorder}">
                        <StackPanel Style="{StaticResource FunctionStackPanel}">
                            <ContentPresenter Content="{StaticResource FunctionBase}"/>
                            <Button>
                                Dimmer
                            </Button>
                        </StackPanel>
                    </Border>
                </DataTemplate>
            </ItemsControl.Resources>
        </ItemsControl>
Im Anhang findet ihr ein Screenshot, was der ungekürzte Code bisher auf dem Bildschirm zaubert. :) Danke für eure Unterstützung und die Tipps. Wer noch Vorschläge und Verbesserungen zum Gezeigten hat, darf sie gerne posten, ich bin für alles offen!
»CeDoMain« hat folgendes Bild angehängt:
  • Bild.png
Mit freundlichem Gruß
CeDo
Discord: #6996 | Skype: cedomain

Lass solche persönlichen Angriffe lieber bleiben, meine sind härter.

Werbeanzeige