WPF MVVM DataGrid nie odświeża

0

Witam.

Mam problem z bindowaniem do DataGrid. Mam listę klientów, którą odpalam z menu głównego. Datagrid w tym widoku przedstawia się tak:

<DataGrid Name="ClientsTable" IsReadOnly="True" ItemsSource="{Binding ListOfClients}" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" Height="600" Margin="10" Width="800"/>
            <Grid.InputBindings>
            <MouseBinding Gesture="LeftDoubleClick" Command="{Binding OpenClientDetails}" CommandParameter="{Binding ElementName=ClientsTable,Path=SelectedItem}"/>
        </Grid.InputBindings>

Mam tu dodatkowo opcję, że po podwójnym klknięciu na wiersz przechodzi do tego rekordu oraz generowanie nazw kolumn na podstawie atrybutów pola ClientForView. Ale ja nie o tym.
Otóż jak edytuję klienta to nie odświeża mi tego DataGrida. Nie oczekuję, że samoczynnie go odświeży, tylko zamykam okno z listą klientów, odpalam na nowo i dalej bez zmian... Muszę zamknąć program i odpalić od nowa. Po dodaniu klienta klient pokazuje się w tabeli, tylko podczas edycji nie aktualizuje danych.

Czy ktoś może pomóc?

Poniżej kolekcja, którą binduję oraz metoda, którą wykonuję w konstruktorze ViewModelu okna z datagrid.

private void LoadAllClients()
        {
            listOfClients = new ObservableCollection<ClientForList>();
            var clients = clientService.GetAllClientsForList();
            foreach (var client in clients)
            {
                listOfClients.Add(client);
            }
        }

        private ObservableCollection<ClientForList> listOfClients;
        public ObservableCollection<ClientForList> ListOfClients
        {
            get { return listOfClients; }
            set { SetProperty(ref listOfClients, value); }
        }
0

Musiałbyś podać kod swojego ViewModelu, w którym momencie jest wywołana metoda ładowania danych. Zapewne dane są po edycji nie są przeładowane, a jedynie w momencie ładowania formy po raz pierwszy. Postaw sobie breakpointa na swoją kolekcję w setterze i zobacz kiedy elementy się zmieniają.

0

Tak jak napisałem metoda ładująca dane umieszczona jest w konstruktorze ViewModelu i odpala się za każdym razem mimo to dane pozostają stare.
Po postawieniu breakpointa metoda jest uruchamiana i dane się nie zmieniają. Dopiero jak zamknę aplikacje to mam odświeżone.

0

Witam,

A jesteś pewien że robisz wszystko jak należy? Może lekko zmodyfikuj swój kod by WPF wiedział co ma robić.

private void LoadAllClients()
        {
            ListOfClients.Clear();
            var clients = clientService.GetAllClientsForList();
            foreach (var client in clients)
            {
                ListOfClients.Add(client);
            }
        }

        private ObservableCollection<ClientForList> listOfClients = new ObservableCollection<ClientForList>();
        public ObservableCollection<ClientForList> ListOfClients
        {
            get { return listOfClients; }
            set { SetProperty(ref listOfClients, value); }
        }

Pozdrawiam,

mr-owl

0

@mr-owl: tutaj aż się prosi o async, await dla metody LoadAllClients. :-)
No i nie trzeba czyścić kolekcji tylko utworzyć zwyczajnie nową, za każdym razem kiedy potrzeba załadować klientów.

0

Owszem, przerobię na async, await, w sumie muszę, aby działał mi MetroDialog z biblioteki MahappsMetro, ale najpierw chcę ogarnąć, aby to w ogóle działało.

Zmieniłem kod na:

public ListOfClientsViewModel(IClientService clientService, IEventAggregator eventAggregator)
        {
            this.clientService = clientService;
            this.eventAggregator = eventAggregator;
            ListOfClients.AddRange(clientService.GetAllClientsForList());
        }

        private ObservableCollection<ClientForList> listOfClients = new ObservableCollection<ClientForList>();
        public ObservableCollection<ClientForList> ListOfClients
        {
            get { return listOfClients; }
            set { SetProperty(ref listOfClients, value); }
        }

I nic. Gaszę okno z lista klientów, odpalam je od nowa w tak sposób:

void ExecuteOpenListOfClientsWindow()
        {
            ListOfClients listOfClientsWindow = new ListOfClients();
            listOfClientsWindow.DataContext = new ListOfClientsViewModel(IClientService, EventAggregator);
            listOfClientsWindow.ShowDialog();
        }

Niestety dane pozostają te same mimo, że stawiam breakpointa na metodzie AddRange, która ładuje mi do kolekcji klientów już ze zmienionymi danymi.... Nie ogarniam normalnie. Przecież logicznie myśląc ładuję do kolekcji dane, więc powinno mi je zbindować, a nie trzymać poprzednią wersję. Nie znam aż tak WPF i Prism, może jest tam jakiś cache czy coś...

Edit: Dodam, że szukam jakiś rozwiązań w sieci. Nawet model klienta, który jest ładowany miałem na początku z Auto-Properites, a przerobiłem na:

public class ClientForList : INotifyPropertyChanged
    {

        private string id;
        private string name;
        private string firstname;
        private string lastname;
        private string city;
        private DateTime createdDate;


        [DisplayName("Numer klienta")]
        public string Id
        {
            get { return id; }
            set
            {
                if (value != this.id)
                {
                    this.id = value;
                    OnPropertyChanged();
                }
            }
        }

[.....]

public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

Nawet w ViewModelu ObservableColletion metodę set ustawiłem na prywatną i nic, zero efektu.

0

Witam,

Możesz udostępnić jakiś przykładowy kod który mogę uruchomić na swoim komputerze? Mam wrażenie że w jednym miejscu używasz Prism-a i a resztę robisz z palca.

Pozdrawiam,

mr-owl

0

Kurczę ciężko z kodem, jeśli się nie zalogujesz do aplikacji to nie otworzy tego okna, trzeba najpierw skonfigurować bazę SQL Server. Jutro po południu zmigruję projekt na GitHuba jako publiczne repo i tu dam linka, bo obecnie stoi na VSTS.

Co do tego, że w jednym miejscu używam Prisma, a w innym nie. Chodzi o to, że klasa ClientForList jest w innym projekcie bez Prisma. Jednak myślę, że czy będzie to implementacja metod BindableBase, czy INotifyPropertyChanged nie powinno mieć znaczenia.

Póki co czekając do jutra postaram się to w miarę zwięźle opisać jeśli chodzi o stan obecny.

W pliku xaml mam DataGrida:

<DataGrid Name="ClientsTable" IsReadOnly="True" ItemsSource="{Binding Path=ListOfClients, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGeneratingColumn="DataGrid_AutoGeneratingColumn" Margin="22,10,22,55" Width="800"/>

Odpowiada za niego we ViewModelu tego okna ten kod:

public ListOfClientsViewModel(IClientService clientService, IEventAggregator eventAggregator)
        {
            this.clientService = clientService;
            this.eventAggregator = eventAggregator;
            ListOfClients.AddRange(clientService.GetAllClientsForList());

        }

        private ObservableCollection<ClientForList> listOfClients = new ObservableCollection<ClientForList>();
        public ObservableCollection<ClientForList> ListOfClients
        {
            get { return listOfClients; }
            set { SetProperty(ref listOfClients, value); }
        }

Klasa, której obiekty ładuję do kolekcji, a raczej jej część:

public class ClientForList : INotifyPropertyChanged
    {
        private string id;
        private string name;
        private string firstname;
        private string lastname;
        private string city;
        private DateTime createdDate;



        [DisplayName("Numer klienta")]
        public string Id
        {
            get { return id; }
            set
            {
                if (value != id)
                {
                    id = value;
                    OnPropertyChanged();
                }
            }
        }
        [DisplayName("Nazwa")]
        public string Name
        {
            get { return name; }
            set
            {
                if (value != name)
                {
                    name = value;
                    OnPropertyChanged();
                }
            }
        }
        [......]
        [DisplayName("Data utworzenia")]
        public DateTime CreatedDate
        {
            get { return createdDate; }
            set
            {
                if (value != createdDate)
                {
                    createdDate = value;
                    OnPropertyChanged();
                }
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

I tak, w tym oknie jak kliknę dwa razy na wierszy otwiera mi się okno podglądu klienta. Tam mogę go edytować. Po edycji zapisuję od razu do bazy danych. Zamykam okno z listą klientów. Odpalam je ponownie w ten sposób:

void ExecuteListOfClients()
        {
            ListOfClients listOfClientsWindow = new ListOfClients();
            listOfClientsWindow.DataContext = new ListOfClientsViewModel(IClientService, EventAggregator);
            listOfClientsWindow.ShowDialog();
        }

No i jak już pisałem wcześniej, dane są z przed edycji. Mimo to, że stawiając breakpointa na metodzie ładującej dane do kolekcji ObservableCollection ładowane są obiekty już po edycji.

0

Witam,

To że część jest w osobnym projekcie to chyba nie jest jakiś duży problem, zawsze możesz komunikację zrobić za pomocą EventAgregatora. Co do otwierania okienek to masz dostępną bibliotekę MvvmDialogs, całkiem fajnie to działa

Pozdrawiam,

mr-owl

0

Po prostu w drugim projekcie trzymam obiekty DTO i nie mam tam paczki z Prismem dlatego nie implementowałem BindableBase.

Co do MvvmDialogs, dzięki, pomyślę, ale najpierw muszę rozwiązać ten nie dający mi spokoju problem ;)

0

Ok, część problemu rozwiązałem.

Tworzenie okna okroiłem na wersję bez ręcznego tworzenia ViewModelu na:

 void ExecuteListOfClients()
        {
            ListOfClients listOfClientsWindow = new ListOfClients();
            listOfClientsWindow.ShowDialog();
        }

A w xamlu ListOfClients.xaml zmieniłem na:

prism:ViewModelLocator.AutoWireViewModel="True"

I teraz po ponownym otwarciu okna pokazuje już odświeżone dane ;)

Ale dalej jest problem. Zrobiłem metodę LoadData, która wykonuje się w konstruktorze i przypisuje do ObservableCollection dane. Niby działa. Zbindowałem sobie przycisk do jej wykonania i po edycji klienta klikam, aby załadowało nowe dane i niestety nic się nie dzieje. W przyszłości chcę, aby ta metoda była wywoływana z EventAggregatora po edycji klienta, ale póki co zrobiłem przycisk do przetestowania i niestety nie działa.

0

Dobra. Udało mi się zrobić tak, że przycisk wrzuca mi już odświeżone dane. Nie wiem dlaczego, ale po zmianie danych klienta, klasa ClientService podawała mi stare dane, dopiero przerobienie kodu na ten poniżej działa tak, że po wykonaniu LoadData podaje już odświeżone dane po zmianie.

private void LoadData()
        {
            IClientService clientService2 = new ClientService();
            ListOfClients = clientService2.GetAllClientsForList().ToObservableCollection();
        }

Wcześniej korzystałem z serwisu wstrzykniętego z Unity.
W bootstrapperze mam to:

Container.RegisterType<IClientService, ClientService>();

A w serwisie metoda wygląda tak:

public List<ClientForList> GetAllClientsForList()
        {
            var clients = db.Clients.ToList();
            return Mapper.Map<List<Client>, List<ClientForList>>(clients);
        }

A i dodatkowo w bootstrapperze wywoływana jest ta metoda z tej klasy:

public class MapperInitialization
    {
        public static void Init()
        {
            Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Client, ClientForList>();
                cfg.CreateMap<VoivodeshipDTO, Voivodeship>();
                cfg.CreateMap<Voivodeship, VoivodeshipDTO>();
                cfg.CreateMap<Client, ClientDTO>();
                cfg.CreateMap<ClientDTO, Client>();
            });
        }
    }

Czyli ręcznie tworząc obiekt serwisu mam nowe dane. Korzystając z wstrzykniętego stare...

Edit:
Ok, zrobiłem breakpointa w metodzie serwisu:

public List<ClientForList> GetAllClientsForList()
        {
            var clients = db.Clients.ToList();
            return Mapper.Map<List<Client>, List<ClientForList>>(clients);
        }

I się okazuje, że zmienna clients dostaje stare wartości z przed aktualizacji powiedzmy imienia klienta. Jeśli dodam nowego klienta to jest ok - dostaje zaktualizowaną listę, jeśli edytuję to nie, dostaję starą. I wtedy zamknięcie i ponowne otwarcie okna pomaga. Czyli ponowne utworzenie obiektu SmContext, czyli db contextu.

Może EF ma jakiś cache czy coś...

EDIT2:

Ok działa :)
Zmieniłem w serwisie zapytanie do contextu na:

var clients = db.Clients.AsNoTracking().ToList();

I działa, odświeża ładnie.

1 użytkowników online, w tym zalogowanych: 0, gości: 1