Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Wie aktualisiere ich eine ObservableCollection mit MySQL als Quelle (MySQL/MVVM)?
echdeneth
myCSharp.de - Member



Dabei seit:
Beiträge: 148
Herkunft: Sachsen, Herrnhut/Zittau

Themenstarter:

Wie aktualisiere ich eine ObservableCollection mit MySQL als Quelle (MySQL/MVVM)?

beantworten | zitieren | melden

Moin, ich habe 2 Tabellen, in einer stehen Lieferungen/Bestellungen und in der anderen
die dazugehörigen Einträge. (ListView/GridView)

Die 1. Tabelle hat ein SelectedItem="{Binding SelectedUnbeliefert}" mit dessen Hilfe für die
Fremdschlüsselbeziehung und SQL Abfrage notwendige ID ausgelesen wird.

SQL Abfrage - von dem Aufruf: public IEnumerable... gäbe es mehrere in der Klasse

    
public class MySQL 
    {
        private readonly string login = Globals.Login;
        private MySqlDataReader reader;

        public IEnumerable<ArtikelViewModel> GetArtikel()
        {
            List<ArtikelViewModel> artikels = new List<ArtikelViewModel>();

            using (MySqlConnection conn = new MySqlConnection(login))
            {
                conn.Open();

                //StringBuilder sb = new StringBuilder();
                //sb.AppendLine("SELECT *");
                string query = "CALL `NormalArtikelliste`();";
                using (MySqlCommand cmd = new MySqlCommand(query, conn))
                {
                    using (reader = cmd.ExecuteReader())
                    {
                        while (reader.Read())
                        {
                            artikels.Add(new ArtikelViewModel()
                            {
                                ArtikellisteIndex = reader.GetInt32(0),
                                ArtikelNummer = reader.GetString(1),
                                ArtikelName = reader.GetString(2),
                                BestellMenge = reader.GetInt32(3),
                                Geliefert = reader.GetInt32(4),
                                PrioID = reader.GetInt32(5),
                                PrioMenge = reader.GetInt32(6),
                                Datum = reader.GetDateTime(7),
                            });
                        }
                    }
                }
                ...
                }
            }

            return artikels;
        }
...

Dann eine:


        private ObservableCollection<LieferungenViewModel> unbeliefert;
        public ObservableCollection<LieferungenViewModel> Unbeliefert
        {
            get { return unbeliefert; }
            set
            {
                if (unbeliefert == value)
                    return;
                unbeliefert = value;
                NotifyPropertyChanged("Unbeliefert");
            }
        }

und


        private MySQL _sql = new MySQL();
        public BestellungenVM()
        {

            IEnumerable<LieferungenViewModel> __unbeliefert = _sql.GetUnbeliefert();
            Unbeliefert = new ObservableCollection<LieferungenViewModel>(__unbeliefert); // 1. Tabelle statisch(Lieferuingen...)

            IEnumerable<LieferungenViewModel> __ausgeliefert = _sql.GetAusgeliefert();
            Ausgeliefert = new ObservableCollection<LieferungenViewModel>(__ausgeliefert); // Unwichtig

        }
        void getArticle_OnSelect(int index)
        {
            IEnumerable<ArtikelViewModel> _selectUnbeliefert = _sql.GetBestellung(index);
            Artikelliste = new ObservableCollection<ArtikelViewModel>(_selectUnbeliefert); // 2. Tabelle dynamisch (Artikel)
        }


Ich weiss leider nicht wie ich die Collection nur aktualisiere ohne sie jedes mal neu zu instanzieren was so auch passiert wie ich annehme
getArticle_OnSelect(int index) wird über eine Eigenschaft ausgelöst, was mir auch nicht sehr gefällt - siehe Oben, das Bi´nding


        private LieferungenViewModel selectedUnbeliefert;
        public LieferungenViewModel SelectedUnbeliefert
        {
            get => selectedUnbeliefert;
            set
            {
                selectedUnbeliefert = value;
                getArticle_OnSelect(selectedUnbeliefert.NormalBestellungenIndex);
                NotifyPropertyChanged();
                NotifyPropertyChanged("LCheck");
            }
        }

Hawbe garantiert irgendwas Vergessen zu Erwähnen, also meine Bitte um Milde

Danke
"Man muß die Dinge so einfach wie möglich machen. Aber nicht einfacher." Albert Einstein
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4001

beantworten | zitieren | melden

Mir scheint, es fehlt die Logik-Schicht. Du wurdest ja schon mehrfach auf [Artikel] Drei-Schichten-Architektur hingewiesen, nun kannst du es praktisch einsetzen.

Die Lieferungen, Bestellungen und die Artikelliste sollten also in der Logik-Schicht verwaltet werden.

Außerdem sollte die DB-Klasse (und auch die Logik-Schicht) nichts von ViewModels wissen, sondern du solltest Modelldaten dafür erzeugen und verwenden.
Die UI-Schicht erzeugt dann aus diesen Modelldaten die passenden ViewModel-Daten (generell ist es aber normal, daß die ObservableCollection<T> jedesmal wieder neu erzeugt wird, wenn sich die Daten ändern - evtl. kannst du aber ein Caching einbauen, wenn es performancemäßig relevant ist).
private Nachricht | Beiträge des Benutzers
witte
myCSharp.de - Member



Dabei seit:
Beiträge: 966

beantworten | zitieren | melden

... man könnte auch alle abhängigen Modelle laden und dann mit einer CollectionView arbeiten.
private Nachricht | Beiträge des Benutzers
echdeneth
myCSharp.de - Member



Dabei seit:
Beiträge: 148
Herkunft: Sachsen, Herrnhut/Zittau

Themenstarter:

beantworten | zitieren | melden

Zitat von Th69
Mir scheint, es fehlt die Logik-Schicht. Du wurdest ja schon mehrfach auf [Artikel] Drei-Schichten-Architektur hingewiesen, nun kannst du es praktisch einsetzen...

Genau hier liegt der Hund begraben.

ich habe kein praktischen Ansatz um Code in die 3 Schichten zu trennen. Vielleicht wird dies im
3. Lj. noch dran genommen, viel Hoffnung hege ich hierbei nicht.

Oftmals ist es auch so dass, wenn ich angenommen habe ich hätte es gefressen - ich hatte hier
mal ein Codebeispiel präsentiert - is hieß: "Nein so wird es nicht gemacht"

Kumulativ lässt sich sagen: Ich weiss nicht wie!
"Man muß die Dinge so einfach wie möglich machen. Aber nicht einfacher." Albert Einstein
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15690
Herkunft: BW

beantworten | zitieren | melden

Der Beruf des Software Entwicklers ist leider ganz ganz ganz arg von Eigeninitiative getrieben und die Kunst ist die Anwendung von Erfahrung.

Du wirst weder in der Schule noch im Studium wirklich was über Pattern oder deren Anwendung im Allgemeinen lernen.
Da musst Du durch Bücher, Videos, Blogs... und ausprobieren. "Den einen" praktischen Ansatz gibt es auch nicht.

Mit einem "Ich weiß nicht wie" wirst Du da nicht arg weiter kommen - und darauf hoffen, dass Dir das andere Personen reinpressen, würde ich nicht tun ;-)
private Nachricht | Beiträge des Benutzers
JimStark
myCSharp.de - Member

Avatar #dOpLzh7hN1az1g0eGRc0.jpg


Dabei seit:
Beiträge: 226

beantworten | zitieren | melden

Zitat von Th69
Außerdem sollte die DB-Klasse (und auch die Logik-Schicht) nichts von ViewModels wissen, sondern du solltest Modelldaten dafür erzeugen und verwenden.
Die UI-Schicht erzeugt dann aus diesen Modelldaten die passenden ViewModel-Daten (generell ist es aber normal, daß die ObservableCollection<T> jedesmal wieder neu erzeugt wird, wenn sich die Daten ändern - evtl. kannst du aber ein Caching einbauen, wenn es performancemäßig relevant ist).

Ich hatte genau die gleiche Fragestellung wie der Threadsteller, da ich derzeit ein kleines Testprojekt in WPF mache:
der avService liefert mir die Models aus einer Datenbank (Order, Customer,...).
Der Konstruktur des OrderViewModels mappt sich die dann vom Model. (könnte man noch auslagern um Abhängigkeiten zu verringern)

Würde man das so machen oder gibt es da noch etwas eleganteres? Wenn ein Objekt bearbeitet wird triggere ich dann z.B. nur den avService.UpdateOrder(...) und refreshe das ganze?


    public partial class MainWindow : Window
    {
        private AvService _avService;
        private OrdersViewModel _ordersView = new OrdersViewModel();

        public MainWindow()
        {
            InitializeComponent();
            this._avService = new AvService("test.db");
            refresh();
        }

        private void refresh()
        {
            this._ordersView.Orders.Clear();
            foreach (var order in this._avService.GetOrders())
            {
                this._ordersView.Orders.Add(new OrderViewModel(order));
            }

            dgOrders.ItemsSource = this._ordersView.Orders;
        }
    }



    public class OrdersViewModel : BaseViewModel
    {
        private ObservableCollection<OrderViewModel> _Orders = new ObservableCollection<OrderViewModel>();

        public ObservableCollection<OrderViewModel> Orders
        {
            get { return _Orders; }
            set
            {
                if (value != _Orders)
                {
                    _Orders = value;
                    OnPropertyChanged(nameof(Orders));
                }
            }
        }
    }
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15690
Herkunft: BW

beantworten | zitieren | melden

Zitat
Würde man das so machen oder gibt es da noch etwas eleganteres?

Zweiteres wenn Du so fragst.

a) man würde Basics wie Dependency Injection und Co verwenden
b) man würde alles async machen (=> [FAQ] Warum blockiert mein GUI? )
c) in einem modernen Umfeld würde man das eben über bereits genannte Reactive Extensions machen, sodass Du Applikationsweit nur eine einzige DataSource hast und das nicht im ViewModel selbst machst (Overview of Dynamic Data)
d) man bindet nicht das DataGrid aus dem Source, sondern man deklariert die ObservableCollection im Source und referenziert diese aus dem XAML.
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5963
Herkunft: Leipzig

beantworten | zitieren | melden

foreach (var order in this._avService.GetOrders())
            {
                this._ordersView.Orders.Add(new OrderViewModel(order));
            }

Hier hast du offensichtlich denn Sinn einer ObservableCollection nicht verstanden. Die ObservableCollection ermöglicht es, eine veränderbare Liste mit Daten an die View zu binden, damit diese sich bei Datenänderungen automatisch aktualisieren kann.

Wenn du auf MVVM verzichtest, und die Daten sowieso einzeln in die View kopierst, brauchst du keine ObservableCollection.

Es wäre aber wesentlich einfacher, es so zu nutzen, wie es gedacht ist:
<ItemsControl ItemsSource="{Binding Orders}" />


Siehe: [Artikel] MVVM und DataBinding
Weeks of programming can save you hours of planning
private Nachricht | Beiträge des Benutzers
JimStark
myCSharp.de - Member

Avatar #dOpLzh7hN1az1g0eGRc0.jpg


Dabei seit:
Beiträge: 226

beantworten | zitieren | melden

Zitat von MrSparkle
Hier hast du offensichtlich denn Sinn einer ObservableCollection nicht verstanden. Die ObservableCollection ermöglicht es, eine veränderbare Liste mit Daten an die View zu binden, damit diese sich bei Datenänderungen automatisch aktualisieren kann.

Das ist genau mein Problem,
wie komme ich von den Models auf eine ObservableCollection mit den ViewModels, sodass ich WPF auch effektiv nutze.

@Abt danke für die Links vielleicht erklärt sich ja dadurch dann meine Frage

Danke euch!
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5963
Herkunft: Leipzig

beantworten | zitieren | melden

Du erstellst dir für jedes Model in der Liste mit Models ein ViewModel in der Liste mit ViewModels. So z.B.:


var viewModels = models.Select(model => new ViewModel() { Data = model.Data });
Items = new ObservableCollection<ViewModel>(viewModels);
Weeks of programming can save you hours of planning
private Nachricht | Beiträge des Benutzers
JimStark
myCSharp.de - Member

Avatar #dOpLzh7hN1az1g0eGRc0.jpg


Dabei seit:
Beiträge: 226

beantworten | zitieren | melden

Danke für deine Antwort,

nur verstehe ich da jetzt nicht ganz den Unterschied:


var viewModels = models.Select(model => new ViewModel() { Data = model.Data });
Items = new ObservableCollection<ViewModel>(viewModels);

Du erstellst doch da auch nur eine neue ObservableCollection mit den ViewModels.

Der Code bei mir macht doch das gleiche oder habe ich einen Denkfehler?!
ordersView.Orders ist ja die ObservableCollection des OrdersViewModel (entspricht Company in deinem Artikel), und der füge ich die ViewModels der Models hinzu.
Sorry bin noch relativ frisch in der Materie und mir fehlt da ein bisschen der Überblick.


foreach (var order in this._avService.GetOrders())
            {
                this._ordersView.Orders.Add(new OrderViewModel(order));
            }


    public class OrdersViewModel : BaseViewModel
    {
        private ObservableCollection<OrderViewModel> _Orders = new ObservableCollection<OrderViewModel>();

        public ObservableCollection<OrderViewModel> Orders
        {
...

private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5963
Herkunft: Leipzig

beantworten | zitieren | melden

Du verwendest kein Binding sondern greifst aus dem Code direkt auf die View zu, jedenfalls verstehe ich den Feldnamen _ordersView so. Lies dir doch mal den Artikel dazu durch, dann sollte es klarer werden: [Artikel] MVVM und DataBinding
Weeks of programming can save you hours of planning
private Nachricht | Beiträge des Benutzers
JimStark
myCSharp.de - Member

Avatar #dOpLzh7hN1az1g0eGRc0.jpg


Dabei seit:
Beiträge: 226

beantworten | zitieren | melden

Mein ganzes Wissen darüber habe ich bisher nur aus diesem Artikel :D
OrdersViewModel entspricht dem CompanyViewModel.
Ich schätze ich kann die


private ObservableCollection<OrderViewModel> _Orders

auch direkt in der MainForm deklarieren, und die dann in XML ansprechen.

Danke euch, wenn ich noch Fragen habe mache ich einen neuen Thread auf um diesen nicht zu arg voll zu spamen.

Viele Grüße
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15690
Herkunft: BW

beantworten | zitieren | melden

Nein, in der MainForm deklarierst Du gar nichts; steht auch so nicht im Artikel.
Das, was Du als MainForm bezeichnest, ist der Container - meist das Window. Dort kannst Du den DataContext dann auf das ViewModel setzen.
Nein, hier wird nicht direkt auf die Collection verwiesen.

Das ViewModel des Containers (also eines Windows, eines Controls, was auch immer Du da hast) beinhaltet die ObservableCollection.
Und in der ObservableCollection liegen ViewModels der einzelnen Entitäten / Business Objekte.

Im XAML des Containers (also eines Windows, eines Controls, was auch immer Du da hast) kannst Du dann direkt auf die Collection binden.

Steht alles mit Schritt für Schritt Anleitung im Artikel.
private Nachricht | Beiträge des Benutzers
JimStark
myCSharp.de - Member

Avatar #dOpLzh7hN1az1g0eGRc0.jpg


Dabei seit:
Beiträge: 226

beantworten | zitieren | melden

Ja, ich habs gerade auch versucht und es ging natürlich nicht =), sorry das hat mich alles ein bisschen verwirrt.


        private AvService _avService;
        private OrdersViewModel _orders = new OrdersViewModel();

        public MainWindow()
        {
            InitializeComponent();
            this._avService = new AvService("test.db");


            this.DataContext = this._orders;

            refreshOrders();
        }

        private void refreshOrders()
        {

            var viewModels = this._avService.GetOrders().Select(model => new OrderViewModel(model) );
            this._orders.Orders = new ObservableCollection<OrderViewModel>(viewModels);


        }

Und so verweise ich darauf:


<DataGrid ItemsSource="{Binding Orders}"/>
private Nachricht | Beiträge des Benutzers
Duesmannr
myCSharp.de - Member



Dabei seit:
Beiträge: 127
Herkunft: Münster

beantworten | zitieren | melden

@JimStark ist das jetzt dein aktualisierter Code?

Wenn ja, lies die letzte Nachricht von @Abt nochmal.
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15690
Herkunft: BW

beantworten | zitieren | melden

refreshOrders() erstellt immer eine neue Instanz der Collection.

Während also das Binding auf Refererenz A durchgeführt würde, erstellt bei Dir refreshOrders() dann Referenz B, C.....

Wie soll sowas funktionieren?
Wirds nie.

Mach es (endlich) so, wie es im Artikel steht.
private Nachricht | Beiträge des Benutzers
JimStark
myCSharp.de - Member

Avatar #dOpLzh7hN1az1g0eGRc0.jpg


Dabei seit:
Beiträge: 226

beantworten | zitieren | melden

Der Artikel erklärts ja recht schön nur verstehe ich dann den Einbezug von Models nicht.

Meinst du z.B. ich injecte den avService in das OrdersViewModel und manage da drüber alles?

D.h. einmalig die Liste so initalisieren,
und dann über einen Command z.B. avService.CreateOrder() und davon ein ViewModel in die Collection geben? ==> Also dass das OrdersViewModel nochmal eine Schicht zw. Datenbank Service und GUI ist?
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von JimStark am .
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15690
Herkunft: BW

beantworten | zitieren | melden

Na, Du verstehst das ganze Thema MVVM einfach nicht ;-)
Und da hilft nur: lesen und lernen. Bringt nichts wenn wir Dir den Code korrigieren und Du verstehst es einfach nicht.

Du machst auch alles aber nicht das, was im Artikel steht :-)

Willst Du die Ansicht der Collection aktualisieren, dann darfst Du einfach nur die Inhalte tauschen; nicht die ganze Collection.

- Liste leeren
- Liste füllen
> fertig.

Willst Du eine komplette Collection tauschen, also eine neue Instanz binden, dann musst Du auch hier mit PropertyChanged arbeiten, damit die gebundene View wie bei MVVM eben weiß, dass sie nun auf eine neue Referenz hören muss.

Mit dem Service oder irgendwelchen Schichten hat das alles nichts zutun.
Du bindest einfach falsch.

PseudoCode:

    public class OrdersViewModel
    {
        public ObservableCollection Orders = new ObservableCollection();
    }

    public class MainWindow
    {
        private AvService _avService;
        private OrdersViewModel _ordersViewModel = new OrdersViewModel();

        public MainWindow()
        {
            InitializeComponent();
            _avService = new AvService("test.db");

            DataContext = _ordersViewModel;

            refreshOrders();
        }

        private void LoadOrders() // sollte async sein
        {
            var orders = _avService.GetOrders();

            _ordersViewModel.Orders.Clear();
            _ordersViewModel.Orders.AddRange(orders.Select(o=> new OrderViewModel(o)));
        }
    }
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5963
Herkunft: Leipzig

beantworten | zitieren | melden

Zitat von JimStark
Der Artikel erklärts ja recht schön nur verstehe ich dann den Einbezug von Models nicht.

Ja, das stimmt. Der Artikel erklärt nicht, wo die Daten herkommen. Aber das gehört ja auch nicht zum MVVM. Hier geht es ja nur um die Benutzeroberfläche. Im Artikel wird daher davon ausgegangen, daß die Daten (wie in jeder anderen Anwendung) irgendwo herkommen (Datenbank, Dateien, Webservice...). Wie und warum man die Datenhaltung von der Benutzeroberfläche trennen sollte, steht u.a. in [Artikel] Drei-Schichten-Architektur

Zu deinem konrekten Problem:
Das, was in deiner refreshOrders-Methode (bzw. in Abts LoadOrders) ausgeführt wird, gehört ins ViewModel. Das Window macht eigentlich nichts weiter, als das ViewModel zu instanziieren und den DataContext zu setzen. Den Service zur Abfrage der Datenbank kannst du innerhalb des ViewModels erzeugen, oder von irgendwoher (z.B. per Dependency Injection) als Konstruktor-Parameter übergeben lassen.

Und wie Abt schon sagte: Eine ObservableCollection brauchst du nur, wenn du Elemente der Liste entfernen, hinzufügen, oder in der Reihenfolge ändern willst. Wenn du jedesmal die ganze Liste ersetzt, reicht eine ViewModel-Eigenschaft vom Typ List<T> aus.
Weeks of programming can save you hours of planning
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1450
Herkunft: Düsseldorf

beantworten | zitieren | melden

Zitat
Wenn du jedesmal die ganze Liste ersetzt, reicht eine ViewModel-Eigenschaft vom Typ List<T> aus

Ich würde auf etwas unveränderliches (IReadOnlyCollection, IReadOnlyList oder IEnumerable) setzen.
Dann kann man gar nicht erst auf die Idee kommen, dort irgendwas entfernen oder hinzufügen zu wollen, was dann natürlich nicht in der UI aktualisiert werden würde.
Außerdem bin ich dann nicht an eine Liste gebunden, sondern kann auch Anderes nutzen - sofern notwendig.
private Nachricht | Beiträge des Benutzers
JimStark
myCSharp.de - Member

Avatar #dOpLzh7hN1az1g0eGRc0.jpg


Dabei seit:
Beiträge: 226

beantworten | zitieren | melden

Super danke euch!

Ich denke ich nutze dann ehr die List bzw. IReadOnlyCollection Methode, da ich die Daten ja nur über den Service beziehe.

Danke euch!
private Nachricht | Beiträge des Benutzers
JimStark
myCSharp.de - Member

Avatar #dOpLzh7hN1az1g0eGRc0.jpg


Dabei seit:
Beiträge: 226

beantworten | zitieren | melden

Ich muss nochmal kurz nerven,
jetzt wenn man ein bisschen was gemacht hat und auch mal selbst einen Command angelegt hat muss man schon sagen es ist irgendwie um einiges komfortabler als WinForms

Per DI injecte ich jetzt den Datenservice in das OrdersViewModel, darin habe ich mir zwei Commands angelegt:


    public class OrdersViewModel : BaseViewModel
    {
        private List<OrderViewModel> _Orders = new List<OrderViewModel>();
        private AvService _avService;
        public ICommand ShowDetailsCommand { get; set; }
        public ICommand UpdateOrderCommand { get; set; }

        public OrderViewModel SelectedOrder { get; set; }

...


        private void showDetails(OrderViewModel order)
        {
            // simple window
            Window frmDetails = new Window();
            frmDetails.Content = new OrderUserControl(this, order);
            frmDetails.Show();
        }

        private Order UpdateOrder(Order order, OrderViewModel orderView)
        {
            order.Title = orderView.Title;
            // ... 
            return order;
        }

        public OrdersViewModel(AvService avService)
        {
            this._avService = avService;

            // ShowDetails Command, TODO Can execute
            ShowDetailsCommand = new RelayCommand( p =>
                   showDetails(this.SelectedOrder)
                ) ;

            UpdateOrderCommand = new RelayCommand(p => 
                avService.UpdateOrder( this.UpdateOrder(
                    avService.GetOrders()
                        .Where( x=> x.OrderId == (p as OrderViewModel).Id)
                        .First(), (p as OrderViewModel))
                    )
            );
        }


Der ShowDetailsCommand ist an einen Button im MainWindow gebunden, der öffnet dann mein Usercontrol testweise in einem temporären Fenster.

Nun zum UpdateOrderCommand, der soll das geänderte OrderViewModel in der Datenbank speichern, dazu mappe ich die Änderungen des ViewModels auf die des Order-Models und speichere sie in der Datenbank.

Würde man das so machen dass dem UserControl die ViewModel Instanz übergeben wird oder würde man sich da ehr an ein Event dranhängen?

User-Control:


    public partial class OrderUserControl : UserControl
    {
        private OrdersViewModel _orders;
        private OrderViewModel _order;

        public OrderUserControl(OrdersViewModel orders, OrderViewModel order)
        {
            InitializeComponent();
            this._orders = orders;
            this._order = order;

            this.DataContext = new
            {
                order = _order,
                orders = _orders
            };

            
        }
    }


        <Label Content="Title:"/>
        <TextBox Text="{Binding order.Title}"/>

        <Button Content="Save" Command="{Binding orders.UpdateOrderCommand}" CommandParameter="{Binding order}" />

Das ganze funktioniert ohne Probleme, die Frage ist aber ob das der way to go ist sollte das Projekt mal größer werden?

Danke euch schonmal für eure Geduld 8)
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5963
Herkunft: Leipzig

beantworten | zitieren | melden

Vergiß die UserControls wieder.

In WPF braucht man UserControls nur, wenn man Steruerlemente für die Anzeige und Änderung von Werten in mehreren Projekten verwenden möchte. Die Werte sind aber eher strings und ints usw., keine ViewModels oder andere poprietäre Klassen.

Was du erreichen willst, kann man viel einfacher mit DataTemplates umsetzen (siehe [Artikel] MVVM und DataBinding, Abschnitt Templates).
Weeks of programming can save you hours of planning
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1450
Herkunft: Düsseldorf

beantworten | zitieren | melden

@MrSparkle:

Naja, UserControls machen auch dann Sinn, wenn Du die UI aufsplitten möchtest, um Teilaufgaben zu separieren und den Code übersichtlicher zu machen.
Ich mag WPF/XAML sehr, aber sobald es komplexer wird, wird es leider sehr schnell sehr unübersichtlich, in so einem Fall teile ich meine View in einzelne UserControls auf.
Teilweise kann man das mit Templates abdecken, aber wenn man (wie bei komplexen UIs üblich) viele Controls kombinieren will, sollte man irgendwann auf ein CustomControl oder UserControl umsteigen.

Das macht natürlich nur Sinn, wenn der Inhalt auch mehr als ein anderes Control ist.

@JimStark:

Sei verdammt vorsichtig mit Events.
Die sind toll, man kann leicht auf Änderungen reagieren und Abhängigkeiten entkoppeln, aber wenn Du nicht aufpasst, hast Du ein verknotetes Wirrwarr von Events, die sich gegenseitig anstoßen und da blickt dann niemand mehr durch.

Und nein, die UserControls haben möglichst einen parameterlosen Konstruktor.
Mit WPF und etwas KnowHow über die Tricks kannst Du auch ganze Anwendungen mit einer sauberen View-ViewModel-Trennung schreiben, ohne auch nur ein Zeichen im CodeBehind geschrieben zu haben.

Das ViewModel wird im XAML an den DataContext gebunden.
Wenn Du zwei ViewModels für ein Control hast, hast Du vermutlich etwas falsch gemacht ;)
Fasse sie zusammen (z.B. zwei Properties in einer Klasse) oder überlege, warum sie getrennt sind oder trenne das Control in zwei Teile auf.

Und bezüglich des Codes:
Das Prinzip von RelayCommand verleitet ja dazu, den Inhalt schnell im Lambda zu schreiben, aber vermeide das lieber.
Sobald bei mir ein Command ein Minimum eigener Logik enthält, die über das simple Aufrufen einer Methode hinaus geht, bekommt der Command eine eigene Methode.
Am Ende hast Du nur noch eine Liste von Command-Initialisierungen, denen Du eine Methode übergibst, spätestens wenn mehr Commands dazu kommen, wirst Du darüber froh sein.
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5963
Herkunft: Leipzig

beantworten | zitieren | melden

Zitat von Palladin007
Naja, UserControls machen auch dann Sinn, wenn Du die UI aufsplitten möchtest, um Teilaufgaben zu separieren und den Code übersichtlicher zu machen.

Dazu sind sie aber nicht gedacht. Warum sollte man extra ein Control mit ControlTemplates und DependencyPropertys definieren und je eine zusätzliche C#- und XAML-Datei erstellen, um den Code übersichtlicher zu machen? Was ist übersichtlicher als ein ViewModel mit dazugehörige(n/m) DataTemplate(s)? :)
Zitat von Palladin007
Und nein, die UserControls haben möglichst einen parameterlosen Konstruktor.

Der Grund dafür ist, daß der Designer sonst das Control nicht erstellen könnte. Er weiß ja nicht, welche Daten er injizieren soll, daher nimmt er immer den parameterlosen Konstruktor.
Weeks of programming can save you hours of planning
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1450
Herkunft: Düsseldorf

beantworten | zitieren | melden

Zitat
Dazu sind sie aber nicht gedacht. Warum sollte man extra ein Control mit ControlTemplates und DependencyPropertys definieren und je eine zusätzliche C#- und XAML-Datei erstellen, um den Code übersichtlicher zu machen? Was ist übersichtlicher als ein ViewModel mit dazugehörige(n/m) DataTemplate(s)? :)

Du hast bei einem UserControl nicht direkt ein ControlTemplate. Hast Du schon, aber damit würdest Du den Sinn eines UserControls ad absurdum führen, das arbeitet nämlich mit Content.
Was Du meinst, ist das CustomControl, das funktioniert nur mit einem eigenem ControlTemplate, oder man verwendet ein "fremdes" Template z.B. von dem Basis-Control und erweitert somit dessen Funktionalität.


Nehmen wir an, Du baust ein Outlook mit WPF nach. Prinzipiell kein Problem, aber wenn Du das nicht aufteilst, hast Du schnell ein paar Tausend Zeilen XAML-Code. Besonders die Kalender-Anzeige in der Mitte ist sehr komplex, die will/sollte man daher vom Rest trennen, schon allein wegen der Übersichtlichkeit.

Man kann nun ein ContentControl mit einem passenden Template dafür verwenden. Das funktioniert auch, allerdings steht dann überall ContentControl, das mMn. nicht gerade der Übersichtlichkeit dient, außerdem hast Du keinen CodeBehind, deine einzige Spielwiese ist das Template, sonst nichts.

Oder man nutzt ein CustomControl, mal leitet von irgendetwas ab, definiert gff. DependencyProperties und schreibt in einer eigenen Datei ein Template dafür. Das funktioniert und hat die Nachteile vom ContentControl nicht, allerdings hat man die gezwungene Trennung zwischen Control und Template. Diese Trennung ist toll - wenn man sie braucht. Sie erlaubt dir das leichte Tauschen der "wirklichen" UI, aber willst Du dabei wirklich noch auf ein ViewModel achten müssen? Solche Templates sind dafür gedacht, generische Controls (wie TextBox und Co.) dazustellen, man definiert einen Style dafür (kein Template!), diesen Style kann man leicht (an Themes gebunden) tauschen und ggf. in eigene Projekte oder Teams auslagern.
Das funktioniert aber nur, wenn man nicht an einem ViewModel hängt, weshalb man stattdessen DependencyProperties definiert. Diese DependencyProperties steuern das Verhalten bzw. die Optik und man kann sie leicht binden oder durch Styles setzen oder dynamisch aktualisieren lassen.
Bei einem CustomControl hast Du auch keinen klassischen CodeBehind mit Membern, Du musst stattdessen mit TemplateParts arbeiten, aber das ist umständlich und man muss auch darauf achten, dass ein TemplatePart fehlen könnte.

Oder man nutzt das UserControl.
Abgesehen davon, dass das für Viele leichter zu verstehen sein dürfte (näher an WinForms), hat man in diesem Fall den Content, den man direkt füllen kann. Es gibt keine Trennung von Control und Template, die oft (nicht immer) überflüssig ist und im CodeBehind kann man sehr leicht auf alle Bestandteile zugreifen, man muss ihnen nur einen Namen geben.

In den meisten Fällen ist ein UserControl die bessere Alternative, da man damit die konkreten Anforderungen schnell ohne unnötige Komplexität umsetzen kann.
Sobald man generische Controls hat, die man wiederverwenden und dynamisch umstylen möchte, ist das CustomControl die bessere Alternative.

Wenn man nun die klassische Business-Anforderung mit einem CustomControl umsetzen möchte, geht das zwar auch, aber Du kettest dir die Trennung Control-Template ans Bein und musst umständlich mit TemplateParts arbeiten und darfst dabei nicht mal ViewModels nutzen, denn wenn Du es doch nutzt, gibst Du Stück für Stück die Flexibilität wieder ab.

Für ein Outlook-Klon würde ich daher UserControls nutzen.
Soll es ein Framework werden, was die Einbindung eines Kalenders im Outlook-Stil erlaubt, dann ist das CustomControl das Mittel der Wahl.
Zitat
Der Grund dafür ist, daß der Designer sonst das Control nicht erstellen könnte.

Stimmt, aber davon würde ich es nicht abhängig machen.
Ich persönlich rate grundsätzlich davon ab, den Designer zu nutzen - naja, außer vielleicht mal zu gucken, wie es aussieht.
Viel wichtiger: Man kann sie im XAML auch nicht nutzen, zumindest nicht ganz so einfach.

Das Problem, was ich eher sehe, ist, dass man sich dadurch an irgendwelche Abhängigkeiten kettet, die es eigentlich nicht geben dürfte.
Ein Control/Windows sollte nur eine Abhängigkeit haben: Das ViewModel. Alle anderen Abhängigkeiten sind direkt im XAML definiert oder gehören ins ViewModel.




Ich hab aber das Gefühlt, dass ich damit weit über das hinaus geschossen habe, was JimStark eigentlich wissen wollte :D
private Nachricht | Beiträge des Benutzers
Th69
myCSharp.de - Experte

Avatar #avatar-2578.jpg


Dabei seit:
Beiträge: 4001

beantworten | zitieren | melden

Palladin: Sehe ich auch so. Ich habe schon einige XAML-Dateien gesehen, die viel zu viel Template-Funktionalität drin haben und dadurch unwartbar sind (vom Source-Code erwartet man ja Wartbar- und Wiederverwendbarkeit, aber bei XAML-Dateien wird oftmals alles irgendwie zusammengestopft).

Aber verstehe ich das falsch, wenn man ein DI-Framework verwendet, benötigt man doch zusätzlich den passenden Konstruktor (im Codebehind dafür)?


public MainWindow(IViewModel viewmodel)
{
  DataContext = viewmodel;
} 
Oder gibt es DI-Framework, welche das über die XAML-Datei abhandeln (bei Castle Windsor, welche ich mal benutzt hatte, ist mir dies nicht bekannt - aber das gibt es ja jetzt schon in Version 5).
private Nachricht | Beiträge des Benutzers
Palladin007
myCSharp.de - Member

Avatar #avatar-4140.png


Dabei seit:
Beiträge: 1450
Herkunft: Düsseldorf

beantworten | zitieren | melden

Wenn das Control dein ViewModel im Konstruktur entgegen nehmen soll, dann musst Du das wohl so machen, allerdings hast Du dann das Problem, dass Du Controls nicht mehr frei verwenden kannst. Ob das auch für ein Window gilt (speziell in Verbindung mit dem Designer), müsste ich erst testen. Aber allgemein würde ich von Konstructor-Parametern absehen, gerade weil es solche Fragezeichen gibt.

Ich weiß aber von Autofac, dass es Property-Injection ganz gut kann und auch andere Wege bietet, das den DataContext zu setzen.
Bei Microsofts DI-Container Variante müsstest Du auf eine Factory zurückgreifen, die dann den DataContext setzt.

Vermutlich würde ich aber auch bei Autofac auf eine eigene Factory setzen, die als Parameter das ViewModel übergeben bekommt, weil die allermeisten ViewModels (außer vielleicht das vom MainWindow) sind ja auf Basis eines bestimmten Zustands entstanden.
Diesen Zustand kennt der DI-Container nicht, man kann vielleicht einen Weg einbauen, dass eine Transient-Registrierung doch an eine korrekte ViewModel-Instanz kommen kann, allerdings würde man dadurch auch direkte Prozess-Abläufe im Programm sehr intransparent machen.

Dann doch lieber ein "IMyDialogService" mit einer "Show(MyDialogViewModel)"-Methode, das ist viel leichter bei der der DI-Container-Registrierung und sehr viel offensichtlicher bei der Nutzung. Man sollte den Service natürlich so aufbauen, dass er durch eine Mock-Implementierung ersetzt werden kann.
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von Palladin007 am .
private Nachricht | Beiträge des Benutzers
page99
myCSharp.de - Member



Dabei seit:
Beiträge: 5

beantworten | zitieren | melden

Zitat von Th69

Aber verstehe ich das falsch, wenn man ein DI-Framework verwendet, benötigt man doch zusätzlich den passenden Konstruktor (im Codebehind dafür)?

Dafür gibt es MVVM Frameworks. Caliburn Micro benutzt dafür ein ViewLocator und binded die View an das entsprechende ViewModel. (Siehe ViewLocator Conventions )

Im Bootstrapper kann man seinen eigenen IOC Container setzen und ruft nur noch


       protected override void OnStartup(object sender, StartupEventArgs e) 
       {
            DisplayRootViewFor<ShellViewModel>();
       }

auf und der Rest funktioniert von alleine :)
private Nachricht | Beiträge des Benutzers