Forum: PC-Programmierung Hilfe bei VS2022 C# XAML MVVM DataBinding


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Gunnar F. (gufi36)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,
ich soll eine Anwendung für eine automatische CNC-gesteuerte 
Messvorrichtung schreiben. Schon seit ein paar Jahren mache ich sowas in 
C#, aber hatte mich bislang immer noch gegen die MFC gewehrt und bin bei 
Windows Forms geblieben. Diesmal packte mich der Ehrgeiz (oder der 
Teufel?) und ich wollte es mit XAML WPF versuchen, wegen des 
leistungsfähigen DataBinding. Und weil es so schön ist, wollte ich dann 
auch noch ein Model-View-ViewModel-Pattern daraus machen. Ich habe das 
Buch C#6 mit Visual Studio 2015, wo in Kapitel30 gar wundervoll ein 
Beispielprojekt dargestellt ist (das angehängte MainWindow.png), was 
geradezu ideal zu meiner Anwendung passt. Nur brauche ich keine Liste 
von Personen, sondern Positionen, mit X- und Y-Koordinaten sowie 
dazugehörige DALI-Kommandos. Und auch wenn ich noch nicht wirklich 
sattelfest in XAML bin und auch nicht vollständig das Programmiermodell 
verstehe, habe ich mich entschlossen, es auf meine Anforderungen 
anzupassen.
Mein Projekt K11-CNC sowie das Beispielprojekt aus dem Buch habe ich 
angehängt. Letzteres funktioniert! :-(
In meinem Projekt K11-CNC wird das MainWindow angezeigt, aber die 
ListView bleibt leer. Obwohl bei der Ableitung des MainViewModel 
erfolgreich die XML-Tabelle mit vorgefertigten Positionen gelesen und in 
der Auflistungsklasse gespeichert wird. Ich vermute, dass ich einen 
Fehler im DataBinding meines MainWindow habe. Aber ich finde ihn nicht 
und ich sehe auch nicht wirklich die Möglichkeiten, das zu debuggen.
Kann mir da jemand helfen? Das wäre klasse, denn ansonsten bin ich fast 
so weit, alles von vorne in WindowsForms zu schreiben. Das wäre doch 
wirklich schade...
Bitte zu bedenken, dass das MainWindow noch Baustelle ist. Ich wollte 
einfach mal runde Buttons probieren und auf die Schnelle ein paar 
Steuerelemente, die Kommandos an ein USB-CDC-Device senden. DAS 
funktioniert auch so weit.
Ich wäre sehr dankbar für Hilfe, die mich in meiner .Net-Evolution eine 
Ebene höher bringen könnte! ;-)

von Der M. (steinadler)


Lesenswert?

Mahlzeit, hast du dem MainWindow auch den DataContext zugewiesen, aus 
dem er die Daten binden soll?

Schreib mal in den Konstruktor von MainWindow noch "DataContext = vm".

Dann könnte es funktionieren.

von Gunnar F. (gufi36)


Angehängte Dateien:

Lesenswert?

Danke für den Tipp... Die Hoffnung keimte schon auf.
So sieht jetzt der Konstruktor aus:
1
       public MainWindow()
2
       {
3
           DataContext = vm;
4
           InitializeComponent();
5
           vm = (MainViewModel)this.TryFindResource("vm");
6
           if (vm != null)
7
           {
8
               this.CommandBindings.Add(vm.NewCommandBinding);
9
               this.CommandBindings.Add(vm.DeleteCommandBinding);
10
               this.CommandBindings.Add(vm.SaveCommandBinding);
11
               this.CommandBindings.Add(vm.UndoCommandBinding);
12
           }
13
           this.Closing += MainWindow_Closing;
14
           vm.ConfirmDeleting += vm_ConfirmDeleting;
15
16
           //myCOM.PortName = Ports[0];
17
... und noch mehr Kram.
18
       }
Die Zeile DataContext habe ich hinzugefügt. Aber die ListView bleibt 
noch leer.

von Der M. (steinadler)


Lesenswert?

Nein, zu dem Zeitpunkt ist vm noch null.
Du musst erst die Zuweisung vm=...FindResource machen.
Und danach die Zuweisung mit dem DataContext

von Gunnar F. (gufi36)


Lesenswert?

Bingo, Du hast Recht! Habe die Zeile hinter die geschweiften Klammern 
gesetzt, jetzt wird die Tabelle gefüllt und Editieren/Navigieren 
funktioniert!
Danke vielmals!
Und ich merke, was ich da noch für Lücken zu füllen habe, von wegen
1
this.TryFindResource("vm");
ist mir noch komplett spanisch...
Aber jetzt kann es ja weiter gehen!

von Der M. (steinadler)


Lesenswert?

Gunnar F. schrieb:
> this.TryFindResource("vm");

Du hast "vm" in der XAML-Datei definiert und deklariert. Damit landet 
dein ViewModel in den Ressourcen.
Mit ...TryFindResource holst du die Instanz bzw. einen Zeiger darauf 
dort heraus.

von Gunnar F. (gufi36)


Lesenswert?

Boah! Danke Dir so sehr! In dem Bereich fühle ich mich noch sehr 
hilflos, das Debuggen der Zusammenarbeit von XAML und CodeBehind. Aber 
mit Deiner Hilfe kann ich ja jetzt wieder weiter kommen!

von Gunnar F. (gufi36)


Lesenswert?

Guten Morgen, ich melde mich nochmal in der Hoffnung auf Hilfe.
Das DataBinding der GUI scheint jetzt wunderbar zu funktionieren.
Jetzt habe ich wieder aktiviert, was zu Beginn Probleme machte:
Die Navigationsbuttons sollten mit Icons gefüllt sein und je nach 
Zustand (enabled/disabled) auch das entsprechende Icon aus dem Ordner 
\Images einblenden.
Der Code war bis gestern:
1
<Button Width="50" Content="|c" Command="{Binding FirstCommand}" Margin="3"/>
2
<Button Width="50" Content="c" Command="{Binding PreviousCommand}" Margin="3"/>
3
<Button Width="50" Content=">" Command="{Binding NextCommand}" Margin="80,0,0,3"/>
4
<Button Width="50" Content=">|" Command="{Binding LastCommand}" Margin="3"/>
Ich habe also die Icons durch Content-Text emuliert. War häßlich!
Jetzt wie aus dem Programmbeispiel:
1
<Button Width="50" Command="{Binding FirstCommand}"  Template="{StaticResource navFirst}"  Margin="3"/>
2
<Button Width="50" Command="{Binding PreviousCommand}" Template="{StaticResource navPrevious}" Margin="3"/>
3
<Button Width="50" Command="{Binding NextCommand}" Template="{StaticResource navNext}" Margin="80,0,0,3"/>
4
<Button Width="50" Command="{Binding LastCommand}" Template="{StaticResource navLast}" Margin="3"/>
Sofort nach Eingabe der Template-Verweise, erschienen die Buttons in der 
Entwurfsansicht mit dem enabled-Icon!

Nur beim Start der Compilierung erhalte ich jetzt eine Ausnahme:
1
System.Windows.Markup.XamlParseException
2
  HResult=0x80131501
3
  Nachricht = Zeilennummer "13" und Zeilenposition "75" von "Die Angabe eines Werts für "System.Windows.Baml2006.Typ...
4
...
5
Innere Ausnahme 1:
6
IOException: Die Ressource "images/first_dis.png" kann nicht gefunden werden.
Ich habe viel probiert, die Ressourcen auf "immer kopieren" gestellt, 
die Pfadangaben in der NavigationButtons.XAML auf "/" statt "\\" 
gestellt, die Icon-Dateien manuell ins Ausgabeverzeichnis kopiert.
Kurzum: Der Fehler kommt immer noch.
Danke nochmal im Voraus!

von Gunnar F. (gufi36)


Angehängte Dateien:

Lesenswert?

Liebe Helfer,

ob ich vielleicht nochmal Hilfe erhalten kann?
Nach Steinadlers Hilfe hat das DataBinding zu der Listview bestens 
funktioniert. Das Problem vom 13.08. konnte ich lösen, das waren nur 
Einstellungen in der IDE, dass die Icons als Content zu builden sind und 
wenn neuer, ins Ausgabeverzeichnis zu kopieren.
Die Applikation soll im Einsatz Schrittmotoren treiben (eigenes Projekt 
mit STM32, per USB CDC). Ich möchte mit den Buttons Positionen anfahren 
und dann in der Listview abspeichern, so dass diese dann automatisch 
abgefahren werden können.
Ich bin über das ganze Wochenende daran gescheitert, die neue Position 
in den beiden Labels unten am Bildschirmrand, per DataBinding 
anzuzeigen!
Zunächst habe ich eine Klasse Motor angelegt, INotifyPropertyChanged 
darin implementiert.
1
    public partial class Motor : INotifyPropertyChanged
2
    {
3
        public string posX = "-----";
4
        private string posY = "-----";
5
6
        public event PropertyChangedEventHandler? PropertyChanged;
7
8
        public Motor()
9
        {
10
        public string PosX
11
        {
12
            get { return posX; }
13
            set
14
            {
15
                posX = value;
16
                OnPropertyChanged();
17
            }
18
        }
19
        public string PosY
20
        {
21
            get { return posY; }
22
            set
23
            {
24
                posY = value;
25
                //NotifyPropChange("PosY");
26
                //SetProperty(ref posY, value);
27
            }
28
        }
29
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
30
        {
31
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
32
}   }   }

Darin habe ich verschiedene Möglichkeiten getestet, das Event 
auszulösen. Die SetProperty <T> stammt aus dem Buchbeispiel und ist 
generisch aufgebaut:
1
    public abstract class ViewModelBase : INotifyPropertyChanged
2
    {
3
        public event PropertyChangedEventHandler? PropertyChanged;
4
5
        protected void SetProperty<T>(ref T storage, T value, [CallerMemberName] string property = null)
6
        {
7
            if (Object.Equals(storage, value)) return;
8
9
            storage = value;
10
            if (PropertyChanged != null)
11
                PropertyChanged(this, new PropertyChangedEventArgs(property));
12
        }
13
    }

Aber auf beide Methoden bleiben die Label leer. Das heißt, das 
DataBinding funktioniert schon, denn zur Entwurfszeit werden die 
Platzhalter "-----" schon angezeigt.

Auch in XAML habe ich mehreres probiert. Eine lokale Resource vom Motor 
erzeugen und daran binden.
1
       </ResourceDictionary.MergedDictionaries>
2
            <local:MainViewModel x:Key="vm" />
3
      <local:Motor x:Key="mot" />
4
        </ResourceDictionary>
1
   <StackPanel x:Name="stpMotorData" Grid.Row="6" Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center">
2
    <Label Content="X:" HorizontalAlignment="Left" Margin="4,0,0,0" VerticalAlignment="Center"/>
3
    <Label Content="{Binding Path=PosX}" x:Name="lblXPos" HorizontalAlignment="Left" VerticalAlignment="Center"/>
4
    <Label Content="Y:" HorizontalAlignment="Left" Margin="4,0,0,0" VerticalAlignment="Center"/>
5
    <Label Content="{Binding Path=PosY}"  x:Name="lblYPos"  HorizontalAlignment="Left" VerticalAlignment="Center"/>
6
  </StackPanel>

Versucht, im MainViewModel öffentliche Eigenschaften bereit zu stellen 
und daran zu binden:
1
        #region "Öffentliche Eigenschaften"
2
        public string AbsXPos { get { return _Motor.PosX; } }
3
        public string AbsYPos { get { return _Motor.PosY; } }
.. oder auch direkt an die im Motor gespeicherten Eigenschaften. Alles 
ohne Erfolg!
Ich war drauf und dran, alles hin zu werfen und wieder mit Windows.Forms 
anzufangen.
Als alternativen Workaround habe ich dann direkt in die Label.Contents 
geschrieben.
Das geht dann! Aber das ist ja auch ein Schritt zurück in die alte Welt.

Ich habe das Projekt als 7zip ohne binaries angehängt. Wäre toll wenn 
mich nochmal jemand erleuchten könnte! Danke!

von Gunnar F. (gufi36)


Lesenswert?

Toll, jetzt geht's!

In MainWindow.xaml.cs
1
public Motor? myMot = null;
2
...
3
myMot = new();
4
stpMotorData.DataContext = myMot;   // Das StackPanel mit den Labels X:/Y:

Verstehe auch nicht, warum das am Wochenende nicht klappen wollte.
Ich war nüchtern, ehrlich! ;-)

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.