Bart
Bart

Reputation: 294

Dynamically updating Data Grid in WPF

I'm building WPF application which downloads links to articles from website and displays them in a grid.

Summary of the problem:

The problem I'm hitting is that I have downloading logic in the model which does not have access to the ObservableCollection Articles (which is defined in my ViewModel and bound to DataGrid).

How should I access this collection to update it as I'm downloading new articles (so that data grid will keep adding new ones one by one)?

Details:

Here's overview of my app which implements MVVM pattern (I cut some of the not important parts to keep it easy to read):

DisplayWindow.xaml Is binding source to PresenterViewModel.cs and ItemSource is set to Articles which is ObservableCollection (see below)

<Grid DataContext="{Binding Source={StaticResource presenterViewModel}}">
        <DataGrid ItemsSource="{Binding Articles}">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Url" Binding="{Binding Url}"/>
...
            </DataGrid.Columns>
        </DataGrid>
</Grid>

it also has button which triggers downloading of the article links via

        <Button Content="Download" Command="{Binding DownloadArticles, Mode=OneWay}"/>

DownloadArticles method is part of PresenterViewModel and returns instance of DownloadCommand which implements interface ICommand.

PresenterViewModel.cs

contains ObservableCollection<Article> Articles

private ObservableCollection<Article> articles;

        public ObservableCollection<Article> Articles
        {
            get { return articles; }

            set
            {
                articles = value;
                RaisePropertyChangedEvent("Articles");
            }
        }
        public ICommand DownloadArticles
        {
            get
            {
                return downloadCommand;
            }
        }

DownloadCommand contains reference to PresenterViewModel and in Execute method calls it's method DownloadArticles which calls DownloadArticles in the Article model itself.

in DownloadCommand.cs:

        public void Execute(object parameter)
        {
            presenterViewModel.DownloadArticles();
        }

Solutions I was considering:

Now, the problem is I'm in DownloadArticles method in Article.cs model and need to update Articles ObservableCollection which is part of PresenterViewModel... how do I do that? The DownloadArticles method runs downloading logic in a separate thread.

Should I pass the reference to PresenterViewModel to the DownloadArticles method of the model?

This seems like an easy way to do it, however it just doesn't feel right - I don't think model should be coupled to the ViewModel and in this case one of the methods would accept PresenterViewModel object.

Another option would be having downloading logic directly in PresenterViewModel, but that doesn't feels right either as I'd prefer my ViewModel to be lightweight and not containing any logic.

What would be the best solution in this case? If you think my architecture is fundamentally wrong, please let me know what would be the best way of structuring it in this case.

I really appreciate your advices on this!

Upvotes: 0

Views: 1740

Answers (1)

Alex
Alex

Reputation: 1461

Now, the problem is I'm in DownloadArticles method in Article.cs model and need to update Articles ObservableCollection which is part of PresenterViewModel... how do I do that?

What you need to do is to separate your data access logic into another layer and inject it into your ViewModel as a dependency. By doing so you won't couple any of data access logic to your ViewModel because its task should be only to expose Model to the View and respond to user input.

Suppose that your model looks something like this:

public class Article : INotifyPropertyChanged
{
    private string _url;
    public string Url
    {
        get
        {
            return _url;
        }
        set
        {
            _url = value;
            PropertyChanged("Url");
        }
    }
}

All the data access logic goes to a separate layer which I will call DataService. Its job is to access the external resource and download articles:

public interface IDataService
{
    IList<Article> DownloadArticles();
}

public class DataService : IDataService
{
    public IList<Article> DownloadArticles()
    {
        var articles = new List<Article>();
        // you actually download articles here
        return articles;
    }
}

The data access layer is then injected in your ViewModel

public class PresenterViewModel : BaseViewModel
{
    private readonly IDataService _dataService;

    public PresenterViewModel(IDataService dataService)
    {
        _dataService = dataService;
    }
}

Finally when user requests to download articles by triggering DownloadCommand you delegate this job to your service and wait for result which then will be exposed to your View by ObservableCollection<Article> Articles property of your ViewModel:

// your DownlodArticles command's execute method
public void Execute(object parameter)
{
    var articles = _dataService.DownloadArticles();
    Articles = new ObservableCollection(articles);
}

If for some reason you don't want to use dependency injection, you can implement your DataService as singleton and call it from your ViewModel:

public class DataService
{
    private static DataService _instance;
    public static DataService Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new DataService();
            }

            return _instance;
        }
    }

    public IList<Articles> DownloadArticles()
    {
        // ...
    }
}

public void Execute(object parameter)
{
    var articles = _DataService.Instance.DownloadArticles();
    Articles = new ObservableCollection(articles);
}

UPDATE:

Your data service GetArticles method:

public Task DownloadArticles(ICollection<Article> articles)
{
    // clear the collection if neccessary
    articles.Clear();

    return Task.Run(() =>
    {
        // load articles one by one and add them to the collection
    }
}

Your ViewModel command execute method:

private async void Execute(object parameter)
{
    await _dataService.LoadArticles(Articles);
}

Upvotes: 2

Related Questions