Laden...

Eigenschaften des Model aus dynamisch generiertem ViewModel zugreifen

Erstellt von torka vor 3 Jahren Letzter Beitrag vor 3 Jahren 1.962 Views
T
torka Themenstarter:in
8 Beiträge seit 2020
vor 3 Jahren
Eigenschaften des Model aus dynamisch generiertem ViewModel zugreifen

Hallo,

ich versuche gerade eine Applikation zu bauen, in der aus einer ObservableCollection von Models dynamisch per ItemsContol template die zugehörigen ViewModels geladen werden. Das binding der Daten aus den Objekten der ObservableCollection zum XAML funktioniert soweit, die richtigen Einträge werden dargestellt. Was mir jedoch nicht gelingt ist das Ausführen von Methoden auf dem Objekt des zugehörigen ViewModels, bspw. wenn man einen Button in einem VM klickt.
Ich hoffe ich konnte mein Problem ausreichend beschreiben sowie, dass jemand mir helfen kann, per google/Forensuche hatte ich keinen Erfolg - vielleicht sind es auch die falschen Begriffe

Gruß

301 Beiträge seit 2009
vor 3 Jahren

Hast du auf deinen Item ViewModels einen Command den du ausführen könntest? Mir ist nicht ganz klar was du bisher versucht hast.

16.807 Beiträge seit 2008
vor 3 Jahren

Erklärt doch mal insgesamt was Dein Ziel ist.
Evtl. ist Dein Vorgehen schon grundlegend suboptimal, wonach es riecht.

3.170 Beiträge seit 2006
vor 3 Jahren

Hallo,

in der aus einer ObservableCollection von Models dynamisch per ItemsContol template die zugehörigen ViewModels geladen werden. Klingt seltsam. Gänging wäre eher eine ObservableCollection von ViewModels, und die View wird dann automatisch per ItemsControl.ItemTemplate erstellt.

Wenn Deine ObservableCollection tatsächlich Models enthält, und Du dann sagst:

Das binding der Daten aus den Objekten der ObservableCollection zum XAML funktioniert soweit, Dann klingt das danach, dass Du die Models direkt ans zum XAML bindest. Wo bleiben dann die ViewModels?

Kannst Du mal zeigen, was Du da eigentlich machst?

Gruß, MarsSTein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

T
torka Themenstarter:in
8 Beiträge seit 2020
vor 3 Jahren

Mein Ziel ist, eine Liste von ViewModels in meiner MainView in einem Dock zu erzeugen, die physischen Netzwerkgeräten folgt. Jedes Gerät soll dynamisch sein ViewModel bekommen, das ViewModel soll in der Lage sein, Funktionen auf dem betreffenden Viewmodel für ein Gerät auszuführen. Um die Eigenschaften eines Gerätes zu organisieren habe ich ein Model erstellt, mit Eigenschaften wie IP, Verbindung, etc.
In meinem MainViewModel baue ich dann die ObservableCollection<Gerät> Geräte und binde dann in das XAML die Collection wie folgt:

  
<Grid>
        <avalonDock:DockingManager Name="dockingManager" Loaded="OnDockManagerLoaded">
            <avalonDock:LayoutRoot>
                <avalonDock:LayoutPanel Orientation="Horizontal" CanRepositionItems="False">
                    <avalonDock:LayoutAnchorablePane DockMinWidth="500" 
                                                        DockWidth="500" 
                                                        CanRepositionItems="False" >
                        <avalonDock:LayoutAnchorable x:Name="dockGeräte" 
                                                        Title="Geräte" 
                                                        AutoHideMinHeight="500"
                                                        AutoHideMinWidth="495"
                                                        CanClose="False" 
                                                        CanDockAsTabbedDocument="False" 
                                                        CanFloat="False" 
                                                        CanHide="False">
                            <ScrollViewer VerticalScrollBarVisibility="Visible">
                                <ItemsControl ItemsSource="{Binding Geräte}"  Width="460">
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <local:Gerät Margin="5"/>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </ScrollViewer>
                        </avalonDock:LayoutAnchorable>
                    </avalonDock:LayoutAnchorablePane>
                    <avalonDock:LayoutDocumentPane ShowHeader="False">
                        <avalonDock:LayoutDocument CanClose="False"
                                                    CanFloat="False"
                                                    IsMaximized="True">
                            <!-- Hier kommen dann weitere (auswählbare) control-ViewModels für die Geräte rein, welche aus dem Ribbon für ein ausgewähltes Gerät bestimmte Funktionen ausführen (bspw. Kommunikation aneigen) -->
                        </avalonDock:LayoutDocument>
                    </avalonDock:LayoutDocumentPane>
                </avalonDock:LayoutPanel>
            </avalonDock:LayoutRoot>
        </avalonDock:DockingManager>
    </Grid>

Gebundene Funktionen auf die ViewModels der Geräte funktionieren, ich kann jedoch nicht die Eigenschaften des Models abrufen, dessen Button auf dem ViewModel geklickt wurde.

Ich hoffe, das war etwas verständlicher, vielen Dank schon mal für Eure Antworten.

301 Beiträge seit 2009
vor 3 Jahren

ich kann jedoch nicht die Eigenschaften des Models abrufen, dessen Button auf dem ViewModel geklickt wurde.

Das musst du näher ausführen. Was ist dein Model ( Gerät? ). Wie willst du die Eigenschaften abrufen?

EIn Button wird nicht auf einem ViewModel geklickt. Du hast vielleicht einen Command in deinem ViewModel welcher per Binding auf einen Button in deinem DataTemplate gebunden ist.

Dein DataTemplate besteht hier jedoch nur aus einem Gerät Control. Das Control müsste man mal sehen.

Ich hoffe der Typ Gerät aus ObservableCollection<Gerät> entspricht nicht demselben Typ Gerät deines Controls ( local:Gerät ) ansonsten hast du etwas missverstanden bei MVVM Konzept.

T
torka Themenstarter:in
8 Beiträge seit 2020
vor 3 Jahren

Mein Model Gerät ist eine Klasse, die von der Klasse ModelBase (ich verwende catel) erbt. Da hab ich Eigenschaften des Gerätes drin, bspw.


    public class Gerät : ModelBase
    {
        #region Properties
        /// <summary>
        /// IP-Address of the device
        /// </summary>
        public IPAddress IP
        {
            get { return GetValue<IPAddress>(IPProperty); }
            set { SetValue(IPProperty, value); }
        }
        public static readonly PropertyData IPProperty = RegisterProperty(nameof(IP), typeof(IPAddress), null);

Der Typ aus der ObservableCollection<Gerät> ist das Model, also ObservableCollection<Models.Gerät>, das Control das in dem DataTemplate steckt ist ein Viewmodel mit ebenfalls dem Namen "Gerät". Dort hab ich Commands die im ViewModel Gerät drin stehen und bei Tastendruck ausgeführt werden. Ich verstehe nur nicht, wie ich jetzt Daten aus dem Model in das Viewmodel bekomme. Wo ist mein Denkfehler?

Was ich eigentlich wollte:
Eine ObservableCollection die ich schön speichern kann, organisiert in den Models (das sollte doch eigentlich richtig so sein) und ein ViewModel, welches sich an den Objekten in der Collection bedient und die Eigenschaften darstellt.

16.807 Beiträge seit 2008
vor 3 Jahren

Wenn Du schon unbedingt in Deutsch Quellcode schreibst, was ohnehin nicht schön ist, lass wenigstens die Umlaute weg.
Umlaute werden prinzipiell unterstützt (in C#) - machen trotzdem an einigen Stellen hin und wieder Probleme.

T
torka Themenstarter:in
8 Beiträge seit 2020
vor 3 Jahren

Jep, wird geändert

3.170 Beiträge seit 2006
vor 3 Jahren

Hallo,

Wo ist mein Denkfehler?

Hier:

das Control das in dem DataTemplate steckt ist ein Viewmodel

Ein Control ist niemals ein ViewModel, sondern eine View.
Das Objekt, das Du per Binding mit dieser View verbindest, ist ein ViewModel - also das, was Du hier als Model bezeichnest.

Schau Dir nochmal ein paar Artikel zu MVVM an, z.B. [Artikel] MVVM und DataBinding , hier liegt offenbar noch ein grundsätzliches Verständnisproblem vor.

Gruß, MarsStein

Non quia difficilia sunt, non audemus, sed quia non audemus, difficilia sunt! - Seneca

T
torka Themenstarter:in
8 Beiträge seit 2020
vor 3 Jahren

Ja, es ist ein View. Das ViewModel heißt DeviceViewModel, die View' Device' (vorher Gerät). Aber ist denn mein Ansatz mit der ObservableCollection richtig?

T
torka Themenstarter:in
8 Beiträge seit 2020
vor 3 Jahren

Ich hab noch mal drüber nachgedacht und glaube, dass es vom Prinzip her richtig aufgebaut ist. Hier mal etwas ausführlicher die Struktur:

MainViewModel.cs


    public class MainViewModel : ViewModelBase
    {
        private readonly ICommandManager _commandManager;
        private readonly IMessageService _messageService;
        private readonly ISensorsService _sensorsService;
        private readonly IUIVisualizerService _uiVisualizerService;
        public MainViewModel(ICommandManager commandManager, IMessageService messageService, ISensorsService sensorsService, IUIVisualizerService uiVisualizerService)
        {
            // Services
            Argument.IsNotNull(() => commandManager);
            _commandManager = commandManager;
            Argument.IsNotNull(() => sensorsService);
            _sensorsService = sensorsService; 
            Argument.IsNotNull(() => uiVisualizerService);
            _uiVisualizerService = uiVisualizerService;
            Argument.IsNotNull(() => messageService);
            _messageService = messageService;
            // Commands            
           }       
        public ObservableCollection<Models.Device> Devices
        {
            get { return GetValue<ObservableCollection<Models.Device>>(DevicesProperty); }
            set { SetValue(DevicesProperty, value); }
        }
        public static readonly PropertyData DevicesProperty = RegisterProperty(nameof(Sensors), typeof(ObservableCollection<Models.Device>), null);

MainView.xaml


   <Grid>
        <avalonDock:DockingManager Name="dockingManager" Loaded="OnDockManagerLoaded">
            <avalonDock:LayoutRoot>
                <avalonDock:LayoutPanel Orientation="Horizontal" CanRepositionItems="False">
                    <avalonDock:LayoutAnchorablePane DockMinWidth="500" 
                                                        DockWidth="500" 
                                                        CanRepositionItems="False" >
                        <avalonDock:LayoutAnchorable x:Name="dockDevices" 
                                                        Title="Geräte" 
                                                        AutoHideMinHeight="500"
                                                        AutoHideMinWidth="495"
                                                        CanClose="False" 
                                                        CanDockAsTabbedDocument="False" 
                                                        CanFloat="False" 
                                                        CanHide="False">
                            <ScrollViewer VerticalScrollBarVisibility="Visible">
                                <ItemsControl ItemsSource="{Binding Devices}"  Width="460">
                                    <ItemsControl.ItemTemplate>
                                        <DataTemplate>
                                            <local:Device Margin="5"/>
                                        </DataTemplate>
                                    </ItemsControl.ItemTemplate>
                                </ItemsControl>
                            </ScrollViewer>
                        </avalonDock:LayoutAnchorable>
                    </avalonDock:LayoutAnchorablePane>
                    <avalonDock:LayoutDocumentPane ShowHeader="False">
                        <avalonDock:LayoutDocument CanClose="False"
                                                    CanFloat="False"
                                                    IsMaximized="True">
                            <!-- Hier kommen dann weitere control-ViewModels für die Geräte rein-->
                        </avalonDock:LayoutDocument>
                    </avalonDock:LayoutDocumentPane>
                </avalonDock:LayoutPanel>
            </avalonDock:LayoutRoot>
        </avalonDock:DockingManager>
    </Grid>

Device.xaml


<Grid>
	<Border BorderBrush="Black" BorderThickness="1" HorizontalAlignment="Left" Height="233" VerticalAlignment="Top" Width="450" CornerRadius="10" Background="Transparent">
		<Grid x:Name="deviceGrid" Background="Transparent">			
			<Border x:Name="topBorder" Background="{StaticResource CiDarkGrey}" CornerRadius="10,10,0,0">
				<Grid>
					<Grid.ColumnDefinitions>
						<ColumnDefinition Width="*"/>
						<ColumnDefinition Width="30"/>
					</Grid.ColumnDefinitions>
					<TextBlock Grid.Column="0" Foreground="White"
						   HorizontalAlignment="Center"
						   VerticalAlignment="Top"                          
						   Height="25">
						<!-- Header row text -->
						<Run Text="Device: Serial Number"/>
						<Run Text="{Binding SerialNumber}"/>
						<Run Text=" - ("/>
						<Run Text="{Binding Path=IP, Converter={StaticResource IpAddressToString}}"/>
						<Run Text=")"/>
					</TextBlock>
					<!-- Close Button-->
					<Button Grid.Column="1" Margin="-15,-15"  Width="20" Height="20"  Background="{StaticResource CiLightGrey}" Command="{Binding RemoveDevice}">
						<TextBlock FontWeight="UltraBold" Text="&#xE106;" FontFamily="Segoe MDL2 Assets" FontSize="10" />
						<Button.Resources>
							<Style TargetType="Border">
								<Setter Property="CornerRadius" Value="10"/>
							</Style>
						</Button.Resources>
					</Button>
				</Grid>
			</Border>
		</Grid>
	</Border>
</Grid>

DeviceViewModel.cs


public class DeviceViewModel : ViewModelBase
{
	#region Public Properties
	/// <summary>
	/// device serial number
	/// </summary>
	public String SerialNumber { get; set; }
	/// <summary>
	/// device IP
	/// </summary>
	public String IP { get; set; }
	#endregion
	#region Commands
	/// <summary>
	/// Remove device
	/// </summary>
	public ICommand RemoveDevice { get; set; }
	#endregion
	#region Constructor
	public DeviceViewModel()
	{            
		RemoveDevice = new TaskCommand(RemoveDevice);
	}

	private async Task RemoveDevice)
	{
		System.Windows.Forms.MessageBox.Show("RemoveDevice aufgerufen");
	}
 }

Der RemoveDevice command wird aufgerufen, das binding funktioniert in dieser Richtung. Was mir (und meinem Verständnis noch) fehlt ist das Binding auf die Eigenschaften aus der ObesservableCollection, die im MainViewModel steckt. Es ist auf jeden Fall schon mal so, dass mir in meiner ItemsControl zwei Device-Views angezeigt werden, wenn die OC auch zwei Einträge hat. Was schief läuft ist wohl das binding. Bin ich da auf nem Holzweg?

5.657 Beiträge seit 2006
vor 3 Jahren

Die Antwort hat dir doch MarsStein schon im ersten Post gegeben. Und wenn das nicht verständlich ist, schau mal in den Artikel, der dir verlinkt wurde. Da gibt es Beispiel-Code und ein Beispiel-Projekt.

Weeks of programming can save you hours of planning

301 Beiträge seit 2009
vor 3 Jahren

Warum möchtest du in der ObservableCollection denn unbedingt Models haben?

Wäre es nicht logischer deine DeviceViewModels in dieser Collection zu halten und jedes DeviceViewModel ist der Container für ein Model? Damit sollte dein Problem der Hierarchie doch einfach lösbar sein?

T
torka Themenstarter:in
8 Beiträge seit 2020
vor 3 Jahren

Meine Idee war, dass ich dann bei Programmstart und -ende die Devices besser als XML mit einem XMLSerializer laden bzw. speichern kann. Ist das ein sinnvolles Konzept?
Ich bin relativ unerfahren in der Richtung, hatte bisher nur schnell mal die ein oder andere C#-Applikation zusammengetippt, das hier ist mein erstes MVVM-Konzept.

5.657 Beiträge seit 2006
vor 3 Jahren

Ist das ein sinnvolles Konzept?

Du siehst doch selbst, daß es nicht funktioniert 😃
Daher, nein. Die ViewModels haben eine Funktion, und die Models haben eine andere Funktion, und der Sinn der Schichtentrennung ist es, diese beiden Verantwortlichkeiten voneinander zu entkoppeln. Verwende daher in der View die ViewModels, um die Daten anzuzeigen und zu bearbeiten, und die Models beim Datenzugriff, um sie zu laden und zu speichern.

Hier gibt es ausführlichere Infos dazu:
[Artikel] Drei-Schichten-Architektur
[Artikel] MVVM und DataBinding

Weeks of programming can save you hours of planning

T
torka Themenstarter:in
8 Beiträge seit 2020
vor 3 Jahren

Genau so mache ich es doch - kam wohl anders rüber.

F
10.010 Beiträge seit 2004
vor 3 Jahren

Nein, Du benutzt EIN ViewModel um alle Models zu verwalten und anzuzeigen.

Du sollst eine ObservableCollection<DeviceViewModel> benutzen.

Natürlich kann dein MainViewModel eine List<Model> beinhalten, die Du am ende dann serialisieren kannst.
Und ganz bestimmt kein Static benutzen