Search code examples
c#xamlxamarinmvvmdata-binding

How to do databinding with two ObservableCollection?


I have 2 models, 2 views, and 1 viewmodel. Basically I want to load the data from a GTSLocation object into a ListItem, but also add two more values from a GTSWorkingSite object that has the same Working Site Guid as the GTSLocation object have.

The item fooA from ObservableCollection<GTSLocation>:

WorkingSiteId : "A-A-A-A"
Longitude: 60
Latitude: 120

The item fooB from ObservableCollection<GTSLocation>:

WorkingSiteId : null
Longitude: 70
Latitude: 130

The item barA from ObservableCollection<GTSWorkingSite>:

Id: "A-A-A-A"
Name: "WacDonald's"
Description: "A Fastfood resturaunt."

The listitem for fooA:

WorkingSiteName: "WacDonald's"
WorkingSiteDescription: "A Fastfood resturaunt."
Longitude: 60
Latitude: 120

The listitem for fooB:

WorkingSiteName: "Unknown Place"
WorkingSiteDescription: "No info to provide"
Longitude: 70
Latitude: 130

I can only come up with the solution that is to create another model which contain all the properties I want. Actually is it the only way to go? There must be a more elegant way I guess?

To add up, the reason I have two ObservableCollection is because GTSWorkingSite is also used on the other view (WorkingSiteView.xaml) seperately.

◎Model

    public class GTSLocation 
    {
        public string WorkingSiteId {get;set;} //GUID
        public double Longitude {get;set;}
        public double Latitude {get;set;}
        public DateTime Timestamp {get;set;}
    }
    public class GTSWorkingSite
    {
        public string Id {get;set;} //GUID
        public string Name {get;set;}
        public string Description {get;set;}
    }

◎ViewModel

    public class GeneralViewModel : INotifyPropertyChanged 
    {
        //Assume these two collection filled with data
        public ObservableCollection<GTSLocation> UserLocations { get; set; }
        public ObservableCollection<GTSWorkingSite> WorkingSites { get; set; }
        //Ignored the rest of the code...
    }

◎View: UserItemsPage.xaml

   <ListView ItemsSource="{Binding UserLocations}">
   <ListView.ItemTemplate>
       <DataTemplate>
           <ViewCell>
               <Grid>
                   <Grid.RowDefinitions>
                       <RowDefinition Height="1*" />
                       <RowDefinition Height="1*" />
                       <RowDefinition Height="1*" />
                   </Grid.RowDefinitions>
                   <Grid.ColumnDefinitions>
                       <ColumnDefinition Width="100" />
                       <ColumnDefinition Width="1*" />
                       <ColumnDefinition Width="2*" />
                   </Grid.ColumnDefinitions>
                   <Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" />
                   <Label Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding WorkingSiteName}" />
                   <Label Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding WorkingSiteDescription}" />
                   <Label Grid.Row="2" Grid.Column="1" Text="{Binding Longitude}" />
                   <Label Grid.Row="2" Grid.Column="2" Text="{Timestamp}" HorizontalTextAlignment="End" />
               </Grid>
           </ViewCell>
       </DataTemplate>
   </ListView.ItemTemplate>
</ListView>

If this is a bad design from the very begining, I am willing to refactor my code, if anyone could give me suggestion.


Solution

  • It's pretty bad because you rely on string (or whatever GUID) mapping between the two classes. If you don't want to create a third model but can refactor current model and if GTSLocation has a 1-1 mapping with GTSWorkingSite anyway then i think you could just do:

    1) Refactor the model so that GTSLocation knows about the fact it has a GTSWotkingSite:

    public class GTSLocation 
    {
        public string WorkingSiteId {get;set;} //GUID
        public double Longitude {get;set;}
        public double Latitude {get;set;}
        public DateTime Timestamp {get;set;}
        //New property -> We will use it in the binding!
        public GTSWorkingSite WorkingSite {get;set;}
    }
    public class GTSWorkingSite
    {
        public string Id {get;set;} //GUID
        public string Name {get;set;}
        public string Description {get;set;}
    }
    

    2) Then in your XAML it becomes easy to retrieve the info when populated correctly (note the binding ["."] slight change using the new added WorkingSite property):

    <ListView ItemsSource="{Binding UserLocations}">
       <ListView.ItemTemplate>
           <DataTemplate>
               <ViewCell>
                   <Grid>
                       <Grid.RowDefinitions>
                           <RowDefinition Height="1*" />
                           <RowDefinition Height="1*" />
                           <RowDefinition Height="1*" />
                       </Grid.RowDefinitions>
                       <Grid.ColumnDefinitions>
                           <ColumnDefinition Width="100" />
                           <ColumnDefinition Width="1*" />
                           <ColumnDefinition Width="2*" />
                       </Grid.ColumnDefinitions>
                       <Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="3" />
                       <Label Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding WorkingSite.Name}" />
                       <Label Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Text="{Binding WorkingSite.Description}" />
                       <Label Grid.Row="2" Grid.Column="1" Text="{Binding Longitude}" />
                       <Label Grid.Row="2" Grid.Column="2" Text="{Timestamp}" HorizontalTextAlignment="End" />
                   </Grid>
               </ViewCell>
           </DataTemplate>
       </ListView.ItemTemplate>
    </ListView>
    

    Hope it helps and happy coding!