Laden...

Strukturierung von Code

Letzter Beitrag vor 11 Monaten 24 Posts 836 Views
Strukturierung von Code

Hallo,

Ich bin recht neu in der GUI Programmiereung in C# und arbeite mich in WPF ein. Ich habe eine grundsätzliche Frage. Und zwar, wenn man Ein Window bestehen aus 4 Fenstern bauen möchte und für jedes Fenster noch eine zusätzliche Klasse mit GUi Elementen (als Referenz übergeben) und Methoden baut ist das gut strukturiert?

Ich bin auf das Problem gestoßen, dass die Dui Elemente (Attribute) und Methoden teilweise auch in der 2., und 3. Klasse benötigt werden und andersrum. Ich müsste alle Klassen wild ineinander übergeben, weil es immer dieselben Daten seien sollen und nicht mehrere Instanzen von Klasse 1, 2, 3 und 4 geben sollen.

Geht das auch eleganter?

Wenn du dein Problem etwas eleganter beschreiben könntest, könnte man dir bestimmt helfen.

z.B. Ein Window (auf deutsch Fenster) besteht aus 4 Fenstern … ah, ja

Ich kann mir nicht vorstellen, was du wirklich haben möchtest.

Hat die Blume einen Knick, war der Schmetterling zu dick.

Hallo CSharpNewbie2022

Wie du schon richtig erkannt hast, ist es wichtig, Daten und UI voneinander zu trennen. Bei WPF gibt es dafür das MVVM-Pattern.

https://mycsharp.de/forum/threads/118261/artikel-mvvm-und-databinding

https://www.c-sharpcorner.com/UploadFile/ptmujeeb/wpf-mvvm-pattern-a-simple-tutorial-for-absolute-beginners/

Ausserdem empfehle ich dir noch diesen Artikel:
https://mycsharp.de/forum/threads/111860/artikel-drei-schichten-architektur

Gruss
Alf

Ich meinte aus 4 Pages entschuldige.

Ich seh mir das mVVM Pattern mal a Wochenende an. Hast du auch einen Vorschlag wie ich Buttons und Textblöcke dynamisch erzeugen kann? Ich mache das über eine Schleife.

Allerdings möchte ich eine Tabelle mit 75x8 Elementen (wobei die letzte Spalte aus Buttons besteht) bauen und dann laggt das ersetzen der Inhalte stark. Hast du da eine Idee?

Ja, diese Anforderung wird mit MVVM quasi miterledigt.

Es gibt mehrere Möglichkeiten das zu machen. Hier mal ein ganz einfaches Beispiel:

<ListView ItemsSource="{Binding AllMitarbeiter}">
    <ListView.ItemTemplate>
        <DataTemplate DataType="local:Mitarbeiter">
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding Vorname}" Width="100" />
                <TextBlock Text="{Binding Nachname}" Width="100" />
                <TextBlock Text="{Binding Geburtsort}" Width="100" />
                <Button Content="Details" Width="60" />
            </StackPanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

Hallo, erstmal danke ich dir für deinen hilfreichen Beitrag. Ich bin schon um einiges weiter, aber merke, dass ich etwas grundlegendes nicht verstanden habe. Ich habe das MVVM Pattern angewandt und auch die Dependency Injektion und wollte mein Projekt verbessern. Allerdings habe ich das Problem, dass ich auf einem Window mehrere Seiten hintereinander öffne und auf irgendeine Weise navigiere ich von Seite 1 auf Seite 2. Jetzt ist mein Problem, dass ich einen Button auf dem Window drücken möchte und je nachdem, was gedrückt ist, soll etwas in der page passieren. Der Seite soll Inhalt aus einer Klasse gegeben werden. Aber ich kriege das irgendwie nicht hin. Die Textboxen sollen nur gefüllt werden, wenn ich den Button drücke oder wenn ich andere Buttons drücke. Also eine User Interaction ist der Trigger. Ich habe mit dem MVVM Community Toolkit es geschafft messages zwischen dem Fenster und der Seite zu versenden, aber es funktioniert nicht zuverlässig und beim Start überhaupt nicht oder total zeitversetzt, was könnte man da machen?

Schwer zu sagen, ohne den relevanten Code zu kennen.

Grundsätzlich werden die Daten ins ViewModel geladen und nicht direkt auf der View geändert. Das ViewModel hat ICommand-Properties, die an die Buttons in der View gebunden sind (https://www.c-sharpcorner.com/UploadFile/851045/command-design-pattern-in-C-Sharp/).

Wird der Command ausgelöst, wird eine Methode im ViewModel ausgeführt, die die Daten lädt. Mit CommunityToolkit.Mvvm wird die entsprechende Methode einfach mit [RelayCommand] attributiert. Das Command-Property wird generiert und nicht händisch implementiert.

Das ViewModel informiert dann die View über das INotifyPropertyChanged-Event. Mit CommunityToolkit wird das Property mit [ObservableProperty] attributiert, anstatt das NotifyPropertyChanged-Event händisch auszulösen.

Beispiel:

    internal partial class MainViewModel : ObservableRecipient
    {
        [ObservableProperty]
        private ObservableCollection<string>? data;

        [RelayCommand]
        void LoadData()
        {
            Data = new ObservableCollection<string>
            {
                "Data A",
                "Data B",
                "Data C"
            };
        }
    }
    <StackPanel>
        <ListView ItemsSource="{Binding Data}" />
        <Button Content="Load Data" Command="{Binding LoadDataCommand}" />
    </StackPanel>

Benutzt du denn ein ViewModel pro Page oder eines für alle?

Ich versuche mal zu ergründen was du so willst.

Button auf dem Window drücken [...] soll etwas in der page passieren

Eine Möglichkeit ist beschrieben in Befehlsübersicht

Hat die Blume einen Knick, war der Schmetterling zu dick.

Also erstmal Danke für eure Hilfe. Ich poste hier mal, was ich vorhabe.

Ich habe Ein Window mit einer beliebigen Anzahl an Pages... Der Einfachheitshalber 2. Die Page besteht aus einer "Custome Navigation Bar" und einem Content Control. Mit einem Klick auf Buttons ändert sich der Content des Content Controls. Soweit so gut. Aber ich möchte, dass mit einem Klick auf einen weiteren Button oder des NavigationsButtons, Inhalt auf den User Controls in dem Content Control ändern.

Hier die Dateien:

MainWindow.XAML:

<Window x:Class="_22_Page_Navigation.View.MainWindow"
       xmlns:local="clr-namespace:_22_Page_Navigation.View"
       xmlns:viewModel="clr-namespace:_22_Page_Navigation.ViewModel">
   <Window.DataContext>
       <viewModel:MainWindowViewModel></viewModel:MainWindowViewModel>
   </Window.DataContext>
   <StackPanel>
       <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
           <Button Height="50" Width="100 " Content="Page 1" Command="{Binding NavigationCommand}" CommandParameter="Page 1" Margin="10"></Button>
           <Button Height="50" Width="100" Content="Page 2" Command="{Binding NavigationCommand}" CommandParameter="Page 2" Margin="10"></Button>
       </StackPanel>
       <ContentControl Content="{Binding CurrentViewModel}" Grid.Row="2"></ContentControl>
    </StackPanel>   
</Window>

MainWindow.XAML.cs: Ohne Änderung

MainWindowViewModel.cs:

using WPFBasics;
using _22_Page_Navigation.Model;

namespace _22_Page_Navigation.ViewModel
{
   public class MainWindowViewModel:BaseViewModel
   {
       public Page1ViewModel Page1ViewModel { get; set; }
       public Page2ViewModel Page2ViewModel { get; set; }
       private BaseViewModel currentViewModel;
       
       public BaseViewModel CurrentViewModel
       {
           get => currentViewModel;
           set
           {
               if (currentViewModel != value)
               {
                   currentViewModel = value;
                   this.RaisedPropertyChanged();
               }
           }
       }
       
       private string firstName;
       public string FirstName
       {
           get => firstName;
           set
           {
               if (firstName != value)
               {
                   firstName = value;
                   this.RaisedPropertyChanged();
               }
           }
       }



       public DelegateCommand NavigationCommand { get; set; }

       public MainWindowViewModel()
       {
           NavigationCommand = new DelegateCommand(
               (o) => NavigationThroughPages(o));

           Page1ViewModel = new Page1ViewModel();
           Page2ViewModel = new Page2ViewModel();
           CurrentViewModel = Page1ViewModel;
           UserRepository UserRepository = new UserRepository();
           List<User> users = UserRepository.GetUserList();

       }
       void NavigationThroughPages(object o)
       {
           switch (o.ToString())
           {
               case "Page 1":
                   CurrentViewModel = Page1ViewModel;
                   FirstName = "Max";
                   break;
               case "Page 2":
                   CurrentViewModel = Page2ViewModel;
                   FirstName = users[1].FirstName ;
                   break;
           }
       }
   }
}

Page1.XAML:

<UserControl x:Class="_22_Page_Navigation.View.Page1"
            xmlns:local="clr-namespace:_22_Page_Navigation.View"
            xmlns:viewModel="clr-namespace:_22_Page_Navigation.ViewModel"
			Background="DarkRed">
   <UserControl.DataContext>
       <viewModel:Page1ViewModel></viewModel:Page1ViewModel>
   </UserControl.DataContext>
       <TextBox Text="{Binding FirstName}"></TextBox>
</UserControl>

Page1.XAML.cs: Ohne Änderung

Page1ViewModel.cs:

using WPFBasics;
namespace _22_Page_Navigation.ViewModel
{
   public class Page1ViewModel: BaseViewModel
   {
       private string firstName;
       public string FirstName
       {
           get => firstName;
           set
           {
               if (firstName != value)
               {
                   firstName = value;
                   this.RaisedPropertyChanged();
               }
           }
       }
       
       public Page1ViewModel()
       {
           FirstName = "-";
       }
   }
}

... Page 2 ist identisch, aber mit einer anderen Farbe.

Notwendige Klassen in WPFBasic:

NotifyableBaseObject.cs:


using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WPFBasics
{
   public class BaseViewModel: INotifyPropertyChanged
   {

       public event PropertyChangedEventHandler PropertyChanged;

       protected virtual void RaisedPropertyChanged([CallerMemberName] string _propertyName = "")
       {
           if (!string.IsNullOrEmpty(_propertyName))
           {
               this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(_propertyName));
           }
       }
   }
}

DelegateCommand .cs:

using System;
using System.Windows.Input;
namespace WPFBasics
{
   public class DelegateCommand : ICommand
   {
       public event EventHandler CanExecuteChanged;
       private Action<object> ExecuteAction { get; set; }
       private Predicate<object> CanExecutePredicate { get; set; }

       public DelegateCommand(Predicate<object> _canExecutePredicate, Action<object> _executeAction)
       {
           ExecuteAction = _executeAction;
           CanExecutePredicate = _canExecutePredicate;
       }
       public DelegateCommand(Action<object> _executeAction) : this(null, _executeAction) { }

       public bool CanExecute(object _object)
       {
           if (CanExecutePredicate != null)
           {
              return CanExecutePredicate.Invoke(_object);
           }
           else
           {
               return true;
           }
       }
       public void Execute(object _object)
       {
           
           if (ExecuteAction != null)
           {
               ExecuteAction?.Invoke(_object);
           }
          
       }

       public void RaiseCanExecuteChanged()
       {
           CanExecuteChanged?.Invoke(this, EventArgs.Empty);
       }
   }
}

Da ist doch mal wesentlich mehr Fleisch dran.

Wenn du jetzt noch verraten könntest WO der ominöse Button sein soll (im MainWindow, Page1 oder Page2) und welche Eigenschaft (also von welcher Klasse und Instanz) damit geändert werden soll, dann wäre das Bild komplett.

Zitat von CSharpNewbie2022

Ich habe [...] und auch die Dependency Injektion und wollte mein Projekt verbessern.

Davon sehe ich hier aber nichts.

Hat die Blume einen Knick, war der Schmetterling zu dick.

Hallo,

du möchtest anscheinend FirstName in den beiden Page-Objekten ändern?

Dann hast du noch einen logischen Fehler in deinem Code: du hast je PageXViewModel eine eigene Eigenschaft FirstName gebunden, änderst aber im MainWindowViewModel (in  NavigationThroughPages(...)nur dessen eigene FirstName-Eigenschaft!


Außerdem noch ein paar andere Fehler/Verbesserungen:

  • Im MainWindowViewModel() erzeugst du eine lokale Variable users, benutzt diese aber in einer anderen Methode. Dieser Code dürfte also gar nicht kompilieren (außer du hast noch eine andere Variable users definiert)!
  • Du könntest deine Eigenschaften und Commands verkürzen, wenn du, wie von Alf Ator vorgeschlagen, die CommunityToolkit.Mvvm-Attribute benutzt.
  • Deine DelegateCommand-Klasse läßt sich noch codetechnisch etwas vereinfachen:
    • In CanExecute reicht  return CanExecutePredicate?.Invoke(_object) : true;
    • In Execute ist die if-Anweisung überflüssig (da du ja ?. beim Aufruf benutzt)

Das Community Toolkit funktionicht komplett, weil ich .net 4.7.2 nutze und demnach ein altes Cshsrp, da meckert der compiler bei den Relay Commands, aber das ist ja ich sag mal ‚nur‘ eine Optimierung.

Bei dem Delegate Command, das habe ich erstellt, da kannte ich mich mit den Lambda Expressions nicht so gut aus, aber will das noch optimieren.

Mein Wunsch ist es, sobald ich die Page öffne, sollen informationen aus dem Window und aus einer externen Klasse (entweder statsich oder DI Container) auf dem Fenster angezeigt werden, aber das klappt nicht.

Ich möchte das ändern von ViewModel durch das MainWindow initiieren, also muss ich das Attribit aus dem MainWindow mit den anderen Attributen irgendwie verknüpfen oder?

Blonder Hans:

Der button ist im MainWindow. Also der Navigier Button. Im Prinzip sollen die Attribute der Pages mit einem Button Click aktualisiert werden.

In dem Fall öffne ich Page ein, bitte aktualisiere FirstName aus Page1.

Das habe ich doch geschrieben, daß du die falsche FirstName-Eigenschaft änderst!

Schau dir nochmal genau den Code in NavigationThroughPages an (bzw. debugge ihn)!

Die Frage ist auch, ob du die MainViewModel.FirstName-Eigenschaft überhaupt benötigst (bzw. was sie genau darstellen soll)?

Edit:

Jetzt erst sehe ich deinen Hauptfehler.

Du erzeugst verschiedene PageXViewModel-Instanzen, einmal im MainViewModel und dann jeweils in dem XAML-Code der UserControl-Objekte (und nur diese sind an DataContext gebunden)!

Edit2:

Ich kann dir nur raten, dich nochmal intensiv mit den Grundlagen von MVVM (DataBinding perDataContext) zu beschäftigen bzw. andere MVVM-Tutorial-Projekte anzuschauen, s.a. mein Beitrag in DataGrid / Dateneingabe in MS SQL Datenbank speichern.

So ich habe jetzt geschaut und gesehen, dass ich den DataContext mehrmals gesetzt habe.

Jetzt klappt es mit

        <ContentControl Grid.Row="2" Content="{StaticResource local:Page1}">
           <local:Page1 FirstName="{Binding FirstName}" />
       </ContentControl>

Aber ich möchte ja auch die Pages wechseln und dann geht das irgendwie nicht. Die Pages wechseln kann ich so

        <ContentControl Grid.Row="2">
           <ContentControl.Style>
               <Style TargetType="ContentControl">
                   <Style.Triggers>
                       <DataTrigger 
                           Binding="{Binding CurrentPage, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"
                           Value="{x:Static model:PageIdentification.Page1}">
                           <Setter Property="ContentTemplate" Value="{StaticResource Page1}"></Setter>
                       </DataTrigger>
                   </Style.Triggers>
               </Style>
           </ContentControl.Style>
       </ContentControl> 

Je nach CurrentPage ändert sich nun die Seite, aber ich schaff es einfach nicht jetzt wieder Die Variable FirstName zu übergeben.

Danke für die Hilfe.

Ich habe jetzt einen Ansatz, damit klappt es, aber ich habe 3 Probleme damit:

  1. Die Variablen der Pages sind in der XAML.cs Datei, statt dem ViewModel, hat irgendwie nicht geklappt, wegen dem Dependency Code ( Zeige ich an der Stelle in Rot)
  2. Der Dependency Code ist rech aufwendig, aber da kann man bestimmt nichts machen.
  3. Ich habe die UserControls übereinander und blende die nicht notwendigen nur aus, ist das so ok?

Wenn einem eine Optimierung einfällt bitte melden:

MainWindow.XAML:

<Window x:Class="_22_Page_Navigation.View.MainWindow"
       ...
       
   <Window.DataContext>
       <viewModel:MainWindowViewModel></viewModel:MainWindowViewModel>
   </Window.DataContext
   
   <Window.Resources>

        <Style TargetType="Button">
            <Setter Property="Height" Value="50"></Setter>
            <Setter Property="Width" Value="100"></Setter>
            <Setter Property="Margin" Value="10"></Setter>
        </Style>
   
       <DataTemplate x:Key="Page1">
           <view:Page1 />
       </DataTemplate>
       
       <DataTemplate x:Key="Page2">
           <view:Page2 />
       </DataTemplate>
       
   </Window.Resources>
   
   <Grid>
       <Grid.ColumnDefinitions></Grid.ColumnDefinitions>
       <Grid.RowDefinitions>
           <RowDefinition Height="20"></RowDefinition>
           <RowDefinition Height="70"></RowDefinition>
           <RowDefinition></RowDefinition>
       </Grid.RowDefinitions>
       
       <StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center">
           <Button Content="Page 1" Command="{Binding NavigationCommand}" CommandParameter="{x:Static model:PageIdentification.Page1}"/>
           <Button Content="Page 2" Command="{Binding NavigationCommand}" CommandParameter="{x:Static model:PageIdentification.Page2}"/>
           <Button Content="Page 3" Command="{Binding NavigationCommand}" CommandParameter="{x:Static model:PageIdentification.Page3}"/>
       </StackPanel>
       
       <ContentControl Grid.Row="2" Visibility="{Binding Page1Visibility}">
           <local:Page1 Id="{Binding Id}" FirstName="{Binding FirstName}" LastName="{Binding LastName}" />
       </ContentControl>
       
       <ContentControl Grid.Row="2"  Visibility="{Binding Page2Visibility}">
           <local:Page2 Id="{Binding Id}" FirstName="{Binding FirstName}" LastName="{Binding LastName}" />
       </ContentControl>
       
       <ContentControl Grid.Row="2"  Visibility="{Binding Page3Visibility}">
           <local:Page3 Id="{Binding Id}" FirstName="{Binding FirstName}" LastName="{Binding LastName}" />
       </ContentControl>
   </Grid>
</Window>      

MainWindow.XAML.cs: Leer

MainWindowViewModel.XAML:

namespace _22_Page_Navigation.ViewModel
{
   public class MainWindowViewModel:BaseViewModel
   {
       private Visibility page1Visibility;
       public Visibility Page1Visibility
       {
           get => page1Visibility;
           set
           {
               if (page1Visibility != value)
               {
                   page1Visibility = value;
                   this.RaisedPropertyChanged();
               }
           }
       }
       
       private Visibility page2Visibility;
       public Visibility Page2Visibility
       {
           get => page2Visibility;
           set
           {
               if (page2Visibility != value)
               {
                   page2Visibility = value;
                   this.RaisedPropertyChanged();
               }
           }
       }
       
       private Visibility page3Visibility;
       public Visibility Page3Visibility
       {
           get => page3Visibility;
           set
           {
               if (page3Visibility != value)
               {
                   page3Visibility = value;
                   this.RaisedPropertyChanged();
               }
           }
       }

       public Page1ViewModel Page1ViewModel { get; set; }
       public Page2ViewModel Page2ViewModel { get; set; }
       public Page3ViewModel Page3ViewModel { get; set; }
       private int id;
       public int Id
       {
           get => id;
           set
           {
               if (id != value)
               {
                   id = value;
                   this.RaisedPropertyChanged();
               }
           }
       }
       private string firstName;
       public string FirstName
       {
           get => firstName;
           set
           {
               if (firstName != value)
               {
                   firstName = value;
                   this.RaisedPropertyChanged();
               }
           }
       }
       private string lastName;
       public string LastName
       {
           get => lastName;
           set
           {
               if (lastName != value)
               {
                   lastName = value;
                   this.RaisedPropertyChanged();
               }
           }
       }


       public DelegateCommand NavigationCommand { get; set; }

       public MainWindowViewModel()
       {
           NavigationCommand = new DelegateCommand(
               (o) => NavigationThroughPages(o));
               
           Page1ViewModel = new Page1ViewModel();
           Page2ViewModel = new Page2ViewModel();
           Page3ViewModel = new Page3ViewModel();

           Id =1;
           FirstName = "FirstNameAusPage1";
           LastName = "LastNameAusPage1";
           
           Page1Visibility = Visibility.Visible;
           Page2Visibility = Visibility.Collapsed;
           Page3Visibility = Visibility.Collapsed;
       }
       
       void NavigationThroughPages(object o)
       {
           switch ((PageIdentification)o)
           {
               case PageIdentification.Page1:
               
                   Id = 1;
                   FirstName = "FirstNameAusPage1";
                   LastName = "LastNameAusPage1";
                   
                   Page1Visibility = Visibility.Visible;
                   Page2Visibility = Visibility.Hidden;
                   Page3Visibility = Visibility.Hidden;
                   
                   break;
                   
               case PageIdentification.Page2:
               
                   Id = 2;
                   FirstName = "FirstNameAusPage2";
                   LastName = "LastNameAusPage2";
                   
                   Page1Visibility = Visibility.Hidden;
                   Page2Visibility = Visibility.Visible;
                   Page3Visibility = Visibility.Hidden;
                   
                   break;
                   
               case PageIdentification.Page3:
               
                   Id = 3;
                   FirstName = "FirstNameAusPage3";
                   LastName = "LastNameAusPage3";
                   
                   Page1Visibility = Visibility.Hidden;
                   Page2Visibility = Visibility.Visible;
                   Page3Visibility = Visibility.Hidden;
                   
                   break;
           }
       }
   }
}

Page1.XAML:

<UserControl x:Class="_22_Page_Navigation.View.Page1"
             .... 
             Background="DarkRed">

    <UserControl.Resources>

        <Style TargetType="TextBox">
            <Setter Property="Background" Value="Beige"></Setter>
            <Setter Property="Height" Value="30"></Setter>
            <Setter Property="Width" Value="200"></Setter>
            <Setter Property="Margin" Value="10"></Setter>
            <Setter Property="BorderBrush" Value="Black"></Setter>
            <Setter Property="BorderThickness" Value="1"></Setter>
            <Setter Property="VerticalContentAlignment" Value="Center"></Setter>
            <Setter Property="HorizontalContentAlignment" Value="Center"></Setter>
        </Style>
     </UserControl.Resources>

        <!--<UserControl.DataContext>
        <viewModel:Page1ViewModel></viewModel:Page1ViewModel>
    </UserControl.DataContext>-->
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
        <TextBox Text="{Binding Id}"></TextBox>
        <TextBox Text="{Binding FirstName}" ></TextBox>
        <TextBox Text="{Binding LastName}"  ></TextBox>
    </StackPanel>
</UserControl>

Page1.XAML.cs:

namespace _22_Page_Navigation.View
{
   public partial class Page1 : UserControl
   {
       public static readonly DependencyProperty IdProperty =
       DependencyProperty.Register(
       nameof(Page1.Id),
       typeof(int),
       typeof(Page1),
       new FrameworkPropertyMetadata(default(int), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
       
       public static readonly DependencyProperty FirstNameProperty =
           DependencyProperty.Register(
               nameof(Page1.FirstName),
               typeof(string),
               typeof(Page1),
               new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
               
       public static readonly DependencyProperty LastNameProperty =
           DependencyProperty.Register(
               nameof(Page1.LastName),
               typeof(string),
               typeof(Page1),
               new FrameworkPropertyMetadata(default(string), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));

       public int Id
       {
           get => (int)GetValue(IdProperty);
           set => SetValue(IdProperty, value);
       }
       
       public string FirstName
       {
           get => (string)GetValue(FirstNameProperty);
           set => SetValue(FirstNameProperty, value);
       }
       
       public string LastName
       {
           get => (string)GetValue(LastNameProperty);
           set => SetValue(LastNameProperty, value);
       }

       public Page1()
       {
           InitializeComponent();
       }
   }
}

Page1ViewModel.cs: Leer, nicht verwendet..

Alle Weiteren Fenster identisch..

Kann man das optimieren?

Jetzt hast du aber eine 180° Wende vollzogen. Dies hat dann mit MVVM nichts mehr zu tun. Warum benutzt du denn jetzt DependencyProperty(dies benötigt man, wenn man in einer View bzw. einem Control eine Eigenschaft bindungsfähig machen möchte)? Oder hast du dich von dem Begriff Dependency Injection  fehlleiten lassen?

Du hattest doch schon

<ContentControl Content="{Binding CurrentViewModel}" ... />

zum Wechseln der View (anhand des ViewModels).

Hast du denn auch das passende DataTemplate(in Window.Resources) dafür erstellt gehabt (für die Zuordnung ViewModel → View)? Dies hattest du bisher nicht gezeigt, ansonsten s. z.B. die Antworten in WPF DataContext nicht über Code-Behind? (ab "Im Folgenden ...) und Binding ContentControl Content for dynamic content.

In deinem vorherigen Code hast du einfach die zu dem PageXViewModel gehörende Eigenschaft zu setzen:

Page1ViewModel.FirstName = "Max";

Ich hoffe, du hast diesen Stand noch gesichert.

Und wenn du damit immer noch nicht weiterkommst, dann hänge mal dein gesamtes Projekt als Anhang an deinen Beitrag.

Im Anhang habe ich dir mal ein Beispiel-Projekt gepackt, was deinem recht nahe kommt.

Inklusive Dependency-Injection (Registrierung erfolgt in der Program.cs) und einen Framework für MVVM (reactiveUI) was das Handling erheblich vereinfacht.

So sehen z.B. die ViewModels aus:

public class MainWindowViewModel : ViewModelBase
{
    private readonly Page1ViewModel _page1;
    private readonly Page2ViewModel _page2;

    public ReactiveCommand<Unit, Unit> Page1Command { get; }
    public ReactiveCommand<Unit, Unit> Page2Command { get; }
    [Reactive] public PageViewModelBase CurrentPage { get; private set; }

    public MainWindowViewModel( Page1ViewModel page1, Page2ViewModel page2 )
    {
        _page1 = page1;
        _page2 = page2;

        Page1Command = ReactiveCommand.CreateFromTask( OnPage1 );
        Page2Command = ReactiveCommand.CreateFromTask( OnPage2 );
    }

    private async Task OnPage1()
    {
        CurrentPage = _page1;
        await _page1.InitializeAsync();
    }
    
    private async Task OnPage2()
    {
        CurrentPage = _page2;
        await _page1.InitializeAsync();
    }
}

sowie

public class Page1ViewModel : PageViewModelBase
{
    [Reactive] public string Id { get; set; }
    [Reactive] public string Firstname { get; set; }
    [Reactive] public string Lastname { get; set; }
}

und die View

<Window x:Class="WpfApp.Views.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:WpfApp.Views"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:viewmodels="clr-namespace:WpfApp.ViewModels"
        Title="MainWindow"
        Width="800"
        Height="450"
        d:DataContext="{d:DesignInstance IsDesignTimeCreatable=False,
                                   Type={x:Type viewmodels:MainWindowViewModel}}"
        mc:Ignorable="d">
    <DockPanel>
        <StackPanel DockPanel.Dock="Left">
            <Button Command="{Binding Page1Command}"
                    Content="Page1" />
            <Button Command="{Binding Page2Command}"
                    Content="Page2" />
        </StackPanel>
        <ContentControl Content="{Binding CurrentPage}">
            <ContentControl.Resources>
                <DataTemplate DataType="{x:Type viewmodels:Page1ViewModel}">
                    <GroupBox Background="LightCoral"
                              Header="Page 1">
                        <StackPanel>
                            <DockPanel>
                                <Label Content="Id:" />
                                <TextBox Text="{Binding Id, UpdateSourceTrigger=PropertyChanged}" />
                            </DockPanel>
                            <DockPanel>
                                <Label Content="Firstname:" />
                                <TextBox Text="{Binding Firstname, UpdateSourceTrigger=PropertyChanged}" />
                            </DockPanel>
                            <DockPanel>
                                <Label Content="Lastname:" />
                                <TextBox Text="{Binding Lastname, UpdateSourceTrigger=PropertyChanged}" />
                            </DockPanel>
                        </StackPanel>
                    </GroupBox>
                </DataTemplate>
                <DataTemplate DataType="{x:Type viewmodels:Page2ViewModel}">
                    <local:Page2Control />
                </DataTemplate>
            </ContentControl.Resources>
        </ContentControl>
    </DockPanel>
</Window>

Das kannst da als Basis für weitere Experimente verwenden.

Hat die Blume einen Knick, war der Schmetterling zu dick.

So ich bin zurückgerudert, habe jetzt alles in den Viewmodels, keine doppelte zuweisung zwischen view und viewmodel, DI Container verwendet und kann seiten navigieren mit variablenübergabe.

Es war wirklich nicht so schwer, aber all das neu gelernte hat mich gut verwirrt ...

Danke an alle!

Blonder Hans, danke für die Mühe das hat mir beim DI Container geholfen. Ich hatte ein paar Sachebn einfach falsch verstanden...

BlonderHans, du hast noch einen kleinen C&P Fehler in

private async Task OnPage2()
{
     CurrentPage = _page2;
     await _page1.InitializeAsync();
}

Zitat von Th69

BlonderHans, du hast noch einen kleinen C&P Fehler in

Jo, das kommt davon, wenn man keine Unit-Tests schreibt.

Hat die Blume einen Knick, war der Schmetterling zu dick.