Autor Beitrag
Cabur
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Mi 23.10.19 23:05 
Hallo zusammen,

bis jetzt konnte mir schon oft geholfen werden, indem ich Beiträge mitgelesen habe. Aber jetzt stehe ich vor ein Problem, zu dem ich keine Lösung finden konnte. Ich hoffe die Überschrift ist verständlich genug.

Ich habe eine MainWindow (welches beim Start des Programms geöffnet wird) und das dazugehörige ViewModel. In dem MainWindow wir in einem Frame eine Page (auch mit dazugehörigem ViewModel)geöffnet. In dem MainWindow gibt es zudem ein Panel, welches zu Beginn nicht angezeigt wird, aber bei Bedarf ähnlich wie ein Popup-Fenster eingeblendet und über alle anderen Controls des MainWindows gelagert werden soll. (Es soll explizit kein neues Fenster sein!) Über einen Button im MainWindow klappt das auch soweit ganz gut. Jetzt möchte ich aber in der Page auch ein Button hinzufügen, mit dem ich das Panel im MainWindow ein- und ausblenden kann. Und genau da besteht mein Problem. Das bekomme ich nicht hin. Die MessageBox wird angezeigt, "OpenPanelAttribute" wird auch auf "Visible" gesetzt, das Panel aber nicht sichtbar.

Ich hoffe, ich habe mich verständlich ausgedrückt.

Mein MainWindowViewModel:
ausblenden volle Höhe C#-Quelltext
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:
public class MainWindowViewModel : BaseViewModel
{
   public NavigationService nav;
   public RelayCommand CmdOpenPage { get; private set; }
   public RelayCommand CmdOpenPanel { get; private set; }

   public MainWindowViewModel()
   {
      OpenPanelAttribute = "Collapsed";
      CmdOpenPanel = new RelayCommand(OpenPanel);
      CmdOpenPage = new RelayCommand(OpenPage);
   }

   private string openPanelAttribute;
   public string OpenPanelAttribute
   {
      get { return openPanelAttribute; }
      set
      {
         if (value != openPanelAttribute)
         {
            openPanelAttribute = value;
            OnPropertyChanged("OpenPanelAttribute");
         }
      }
   }

   private void OpenPage(object open)
   {
      nav.Navigate(new PageView());
   }

   private void OpenPanel(object open)
   {
      MessageBox.Show("Testbox""Überschrift", MessageBoxButton.OK);
      OpenPanelAttribute = "Visible";
   }
}


Mein PageViewModel:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
public class PageViewModel : BaseViewModel
{
   public RelayCommand CmdOpenMainPanel;

   public PageViewModel
   {
      CmdOpenMainPanel = new RelayCommand(OpenMainPanel);
   }

   private void OpenMainPanel(object open)
   {
      //Hier soll der Zugriff zum Einblenden des Panels aus dem MainWindow stattfinden.
   }
}


Zuletzt bearbeitet von Cabur am Sa 26.10.19 16:33, insgesamt 1-mal bearbeitet
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Do 24.10.19 08:51 
Hallo und :welcome:,

wie sieht denn das zugehörige XAML aus (also das Binding an die Eigenschaft OpenPanelAttribute)?

Evtl. liegt es auch einfach daran, daß du Visibility anstatt string als Datentyp verwenden solltest?
Cabur Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Fr 25.10.19 17:30 
Hier der Ausschnitt aus meiner XAML-Datei:

ausblenden XML-Daten
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
<Canvas x:Name="MainWindow">
   <DockPanel x:Name="Menu">
      <!--Menuansicht-->
   </DockPanel>

   <DockPanel x:Name="OpenPanel" 
      Background="Red" 
      Visibility="{Binding OpenPanelAttribute, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}">
      <!--Inhalt-->
   </DockPanel>
</Canvas>
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Sa 26.10.19 09:21 
Jetzt wo ich deinen Code noch mal ansehe, fällt mir auf, daß deine beiden VM-Klassen nicht die Schnittstelle INotifyPropertyChanged als Basisklasse (bzw. Basisinterface) haben:
ausblenden C#-Quelltext
1:
public class MainWindowViewModel : INotifyPropertyChanged					

Du hast zwar OnPropertyChanged implementiert, aber WPF schaut zuerst auf die Interface-Definitionen der Klasse (s.a. Vorgehensweise: Implementieren der INotifyPropertyChanged-Schnittstelle).
Üblicherweise wird diese Methode aber in einer Basisklasse ViewModelBase implementiert und diese dann vererbt, s. z.B. Basic MVVM Base class / INotifyPropertyChanged implementation sowie The Evolution of INotifyPropertyChanged (beachte besonders das Attribut CallerMemberName, s.a. Use CallerMemberName Attribute to Make INotifyPropertyChanged Implementation Cleaner).
Cabur Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Sa 26.10.19 16:33 
Doch, dass habe ich implementiert, aber irgendwie ist das nicht mit eingefügt worden.

ausblenden C#-Quelltext
1:
2:
3:
4:
public class MainWindowViewModel : BaseViewModel
{
   //Inhalt
}


ausblenden C#-Quelltext
1:
2:
3:
4:
public class PageViewModel : BaseViewModel
{
   //Inhalt
}
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: So 27.10.19 06:44 
Hast du es denn mal mit Visibility als Datentyp ausprobiert?
Cabur Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Di 29.10.19 12:50 
So ich habe jetzt folgendes probiert bzw. geändert:

MainWindowViewModel:
ausblenden volle Höhe C#-Quelltext
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:
public class MainWindowViewModel : BaseViewModel
{
   public NavigationService nav;
   public RelayCommand CmdOpenPage { get; private set; }
   public RelayCommand CmdOpenPanel { get; private set; }

   public MainWindowViewModel()
   {
      OpenPanelAttribute = Visibility.Collapsed;
      CmdOpenPanel = new RelayCommand(OpenPanel);
      CmdOpenPage = new RelayCommand(OpenPage);
   }

   private Enum openPanelAttribute;
   public Enum OpenPanelAttribute
   {
      get { return openPanelAttribute; }
      set
      {
         if (value != openPanelAttribute)
         {
            openPanelAttribute = value;
            OnPropertyChanged("OpenPanelAttribute");
         }
      }
   }

   //Hier wird die Seite geöffnet
   private void OpenPage(object open)
   {
      nav.Navigate(new PageView());
   }

   //Hier wird das Panel geöffnet. Aus dem MainWindowModel klappt auch alles wie gewünscht!
   //Die MessageBoxen dienen nur zum Test und zeigen die aktuelle Sichtbarkeit des Panel an
   private void OpenPanel(object open)
   {
      MessageBox.Show(Convert.ToString(OpenPanelAttribute), "", MessageBoxButton.OK);
      OpenPanelAttribute = Visibility.Visible;
      MessageBox.Show(Convert.ToString(OpenPanelAttribute), "", MessageBoxButton.OK);
   }
}


PageViewModel:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
public class PageViewModel : BaseViewModel
{
   public RelayCommand CmdOpenMainPanel;

   public PageViewModel
   {
      CmdOpenMainPanel = new RelayCommand(OpenMainPanel);
   }

   private void OpenMainPanel(object open)
   {
      //Hier soll der Zugriff zum Einblenden des Panels aus dem MainWindow stattfinden.
      //Folgende Varianten habe ich schon ausprobiert:

      //Variante 1:
      private MainWindowViewModel mainWindow = new MainWindowViewModel();
      mainWindow.OpenPanelAttribute = Visibility.Visible;

      //Variante 2:
      private MainWindowViewModel mainWindow = new MainWindowViewModel();
      mainWindow.OpenPanel(open);
   }
}


In beiden Fällen wird zwar aus PageViewModel Visibility auf Visible gesetzt, aber das Panel wird nicht angezeigt. Wenn ich aber direkt aus dem MainWindowViewModel Visibility auf Visible setze, dann wird auch das Panel angezeigt.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Di 29.10.19 15:43 
So kann der Zugriff (in OpenMainPanel) ja auch nicht klappen:
- was soll das private dort (das kompiliert doch gar nicht)?
- du erzeugst dort ein neues MainWindowViewModel, welches nichts mit der existierenden Instanz zu tun hat.
-> du mußt dem PageViewModel die aktuelle MainPageViewModel-Instanz (z.B. als Konstruktorparameter) übergeben oder noch besser ein Ereignis verwenden (s.a. meinen Artikel Kommunikation von 2 Forms - primär zwar für WinForms geschrieben, aber die generelle Vorgehensweise gilt auch für WPF und MVVM).

PS: Wieso denn jetzt Enum? Ich meinte explizit
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
private Visibility openPanelAttribute;
public Visibility OpenPanelAttribute
{
  // ...
}
Cabur Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Di 05.11.19 23:33 
Danke für deine Hilfe. Ich habe jetzt Enum in Visibility geändert. Jedoch stehe ich anscheinend mit dem Ereignis total auf dem Schlauch. Ich habe es in den letzten Tagen probiert, aber eine Lösung habe ich dafür nicht gefunden. Hast du mal einen Denkanstoß für mich, wie ich das mit einem Ereignis hinbekomme?
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Mi 06.11.19 10:08 
Wenn du das mit dem Ereignis noch nicht hinbekommst, dann nimm die einfachere Möglichkeit und übergebe dem PageViewModel die aktuelle MainPageViewModel-Instanz (eine der beiden Klassen muß die andere kennen).

Wie und wo genau erstellst du denn diese Instanzen (im View-Codebehind oder per XAML oder ...)?

Wenn ich deinen Code richtig interpretiere, dann erzeugst du doch mittels
ausblenden C#-Quelltext
1:
2:
3:
4:
private void OpenPage(object open)
{
  nav.Navigate(new PageView());
}

die neue Page. Du brauchst dort dann einfach im Konstruktor this (d.h. die MainPageViewModel-Instanz) übergeben und im PageView(...)-Konstruktor diese Instanz dann an das PageViewModel weiterreichen.

Bei dem Ereignis wäre es umgekehrt, so daß das (übergeordnete) MainWindowViewModel das Ereignis bei dem PageViewModel abonnieren würde.

PS: Nur damit du das Stichwort schon mal gehört hast - bei professionellen Programmen benutzt man häufig eine Technik namens "Inversion of Control" bzw. konkret Dependency Injection.
Cabur Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Do 07.11.19 22:15 
user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Wie und wo genau erstellst du denn diese Instanzen (im View-Codebehind oder per XAML oder ...)?


Ich erstelle die Instanz per XAML
ausblenden XML-Daten
1:
2:
3:
<Page.DataContext>
   <viewmodel:PageViewModel/>
</Page.DataContext>
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Fr 08.11.19 07:23 
Dann wirst du das wohl ändern müssen, so daß du Zugriff darauf bekommst (also in den Codebehind einfügen).
Die verschiedenen Möglichkeiten sind z.B. in Different Ways to Bind WPF View And View Model (wenn auch auf englisch, aber der Source-Code sollte selbsterklärend sein) erklärt.

Alternativ könntest du auch in deinem MainPageViewModel schon das PageViewModel erzeugen (s. 2. Ansatz "View Model First Approach" in obigen Artikel) und übergibst das dann an die PageView als Parameter (dies ist dann quasi schon der "Dependency Injection"-Ansatz):
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
public PageView(PageViewModel pageViewModel)
{
  InitializeComponent();

  DataContext = pageViewModel;
}

Für diesen Beitrag haben gedankt: Cabur
Cabur Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Do 14.11.19 11:40 
Vielen Dank, mit deiner Hilfe konnte ich mein Problem endlich lösen. Vielen, vielen Dank!