percentum
percentum

Reputation: 153

Bind DataGrid to Model Collection via ViewModel

I'm new to Caliburn.Micro, but have done some basic WPF MVVM programming so aware of the concepts. I tend to develop in a Model first style and so having an issue binding a WPF DataGrid to a collection defined and updated in the Model. How best should I expose a reference to the Collection via the ViewModel to the View, so that any updates in the Model are propagated via the ViewModel reference, rather than having to wire up in the ViewModel?

Model:

public class Market : PropertyChangedBase
{
    private string _MarketGroup;
    public string MarketGroup
    {
        get
        {
            return _MarketGroup;
        }
        set
        {
            if (_MarketGroup != value)
            {
                _MarketGroup = value;
                NotifyOfPropertyChange(() => MarketGroup);
            }
        }
    }

    private string _Expiry;
    public string Expiry
    {
        get
        {
            return _Expiry;
        }
        set
        {
            if (_Expiry != value)
            {
                _Expiry = value;
                NotifyOfPropertyChange(() => Expiry);
            }
        }
    }

    private string _MarketStatus;
    public string MarketStatus
    {
        get
        {
            return _MarketStatus;
        }
        set
        {
            if (_MarketStatus != value)
            {
                _MarketStatus = value;
                NotifyOfPropertyChange(() => MarketStatus);
            }
        }
    }

    private string _Epic;
    public string Epic
    {
        get
        {
            return _Epic;
        }
        set
        {
            if (_Epic != value)
            {
                _Epic = value;
                NotifyOfPropertyChange(() => Epic);
            }
        }
    }

    private string _InstrumentName;
    public string InstrumentName
    {
        get
        {
            return _InstrumentName;
        }
        set
        {
            if (_InstrumentName != value)
            {
                _InstrumentName = value;
                NotifyOfPropertyChange(() => InstrumentName);
            }
        }
    }
}

public interface IMarketService : InotifyPropertyChanged
{
    BindableCollection<Market> Markets {get;}
    void RefreshMarkets();
}

public class MarketService : PropertyChangedBase, IMarketService
{
    public MarketService()
    {
        Markets = new BindableCollection<IGMarket>();
    }

    private BindableCollection<Market> _Markets;
    public BindableCollection<Market> Markets
    {
        get
        {
            return _Markets;
        }
        private set
        {
            if (_Markets!= value)
            {
                _Markets = value;
                NotifyOfPropertyChange(() => Markets);
            }
        }
    }

    public void RefreshMarkets()
    {
        Markets.Clear();

        var markets = GetMarkets();

        foreach (var market in markets)
        {
            Markets.Add(market);
        }
    }
}

ViewModel:

public class ShellViewModel : PropertyChangedBase
{
    private readonly IMarketService _MarketService;
    private readonly IAccountService _AccountService;

    public ShellViewModel(IMarketService marketService, IAccountService accountService)
    {
        _MarketService = marketService;
        _AccountService = accountService;

        Markets = new BindableCollection<IGMarket>(_MarketService.Markets);
    }

    public void Login()
    {
        Task.Run(() =>
        {
            if (_AccountService.Login())
            {
                LoggedIn = true;

                _MarketService.RefreshMarkets();
            }
        });
    }

    private BindableCollection<Market> _Markets;
    public BindableCollection<Market> Markets
    {
        get { return _Markets; }
        set
        {
            if (_Markets != value)
            {
                _Markets = value;
                NotifyOfPropertyChange(() => Markets);
            }
        }
    }
}

AccountService and MarketService are both registered as singletons in the AppBootsrapper using Simple Injector as the DI framework.

View:

<DataGrid AutoGenerateColumns="False" ColumnWidth="*" HorizontalAlignment="Stretch" x:Name="Markets" Margin="0,0,0,0" CanUserAddRows="False" CanUserDeleteRows="False"  VerticalAlignment="Stretch" Height="auto" Width="auto" Grid.ColumnSpan="2">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding InstrumentName}" Header="Instrument Name" IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding Expiry}" Header="Expiry" IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding MarketGroup}" Header="Market Group" IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding InstrumentType}" Header="Instrument Type" IsReadOnly="True"/>
        <DataGridTextColumn Binding="{Binding MarketStatus}" Header="Market Status" IsReadOnly="True"/>
    </DataGrid.Columns>
</DataGrid>

Essentially nothing appears on the Grid, even though _MarketService.Markets contains items. I'd like to make the View and ViewModel respond to changes/updates to the Model BindableCollection if possible?

Thanks.

Upvotes: 0

Views: 924

Answers (2)

Rachel
Rachel

Reputation: 132598

Why are you using a separate collection of Markets? The common design is to use

public BindableCollection<Market> Markets
{
    get { return _MarketService.Markets; }
    set
    {
        if (_MarketService.Markets != value)
        {
            _MarketService.Markets = value;
            NotifyOfPropertyChange(() => Markets);
        }
    }
}

Right now you have two separate collections, one in ShellViewModel.Markets and one in ShellViewModel.MarketService.Markets, and they are not referring to the same reference in memory or being synchronized in any way.

Upvotes: 1

A.Caillet
A.Caillet

Reputation: 101

you need to bind your datagrid on your BindableCollection

Try this in your xaml ItemsSource={Binding Markets}

Upvotes: 0

Related Questions