none
Implementing MVVM for Hierarchy of Data RRS feed

  • Вопрос

  • Hi All,

    I am new to WPF and MVVM. I need to implement MVVM pattern for the following scenario...

    class Galaxy
    {
    string _name;
    ObservableCollection<Galaxy> _subGalaxies;
    ObservableCollection<Planet> _planets;
    }
    
    class Planet
    {
    string _name;
    int _age;
    double _rotationAxis;
    }
    

    I need to implement Model and ViewModel for the above classes. I would like to list the number of subgalaxies and planets in a selected galaxy and bind them to View. How can I achieve this?



    Regards, Shrishail.

    20 июня 2012 г. 23:47

Ответы

  • Hi ShrishailHalijol,

    I'd probably set it up a bit like this...

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <ListView Grid.Column="0" ItemsSource="{Binding AllGalaxys}" SelectedItem="{Binding SelectedGalaxy}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn>
                    <GridViewColumn Header="Sub Galaxys" DisplayMemberBinding="{Binding SubGalaxys.Count}"></GridViewColumn>
                    <GridViewColumn Header="Planets" DisplayMemberBinding="{Binding AllPlanets.Count}"></GridViewColumn>                    
                </GridView>
            </ListView.View>
        </ListView>
        <ListView Grid.Column="1" ItemsSource="{Binding SelectedGalaxy.SubGalaxys}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn>                    
                </GridView>
            </ListView.View>
        </ListView>
        <ListView Grid.Column="2" ItemsSource="{Binding SelectedGalaxy.AllPlanets}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn>
                    <GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}"></GridViewColumn>
                    <GridViewColumn Header="Rotation Axis" DisplayMemberBinding="{Binding RotationAxis}"></GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>

    public class ViewModelBase : INotifyPropertyChanged
    {
        #region Implementation of INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        #endregion
    
        public virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }
    
        public virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyNameExpression)
        {
            OnPropertyChanged(((MemberExpression) propertyNameExpression.Body).Member.Name);
        }
    }
    
    public class GalaxyViewModel : ViewModelBase
    {
        private string _name;
        private ObservableCollection<PlanetViewModel> _planets = new ObservableCollection<PlanetViewModel>();
        private ObservableCollection<GalaxyViewModel> _subGalaxys = new ObservableCollection<GalaxyViewModel>();
    
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
    
                OnPropertyChanged(() => Name);
            }
        }
    
        public ObservableCollection<GalaxyViewModel> SubGalaxys
        {
            get { return _subGalaxys; }
            set
            {
                _subGalaxys = value;
    
                OnPropertyChanged(() => SubGalaxys);
            }
        }
    
    
        public ObservableCollection<PlanetViewModel> Planets
        {
            get { return _planets; }
            set
            {
                _planets = value;
    
                OnPropertyChanged(() => Planets);
            }
        }
    
        public List<PlanetViewModel> AllPlanets
        {
            get 
            {
                if (SubGalaxys.Count == 0) return Planets.ToList();
    
                List<PlanetViewModel> result = new List<PlanetViewModel>();
    
                result.AddRange(Planets);
    
                foreach (var galaxyViewModel in SubGalaxys)
                {
                    result.AddRange(galaxyViewModel.AllPlanets);
                }
                    
                return result; 
            }
        }        
    }
    
    public class PlanetViewModel : ViewModelBase
    {
        private long _age;
        private string _name;
        private double _rotationAxis;
    
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
    
                OnPropertyChanged(() => Name);
            }
        }
    
    
        public long Age
        {
            get { return _age; }
            set
            {
                _age = value;
    
                OnPropertyChanged(() => Age);
            }
        }
    
    
        public double RotationAxis
        {
            get { return _rotationAxis; }
            set
            {
                _rotationAxis = value;
    
                OnPropertyChanged(() => RotationAxis);
            }
        }
    }
    
    public class AllGalaxysViewModel : ViewModelBase
    {
        private GalaxyViewModel _selectedGalaxy;
    
        public AllGalaxysViewModel()
        {
            GalaxyViewModel galaxy1 = new GalaxyViewModel();
            galaxy1.Name = "Alpha Centauri";
            galaxy1.Planets.Add(new PlanetViewModel() { Name = "Earth", Age = 4540000000, RotationAxis = 23.4 });
    
            GalaxyViewModel galaxy2 = new GalaxyViewModel();
    
            galaxy2.Name = "Orion";
            galaxy2.Planets.Add(new PlanetViewModel() { Name = "Betelgeuse", Age = 10000000000, RotationAxis = 13.4 });
            galaxy1.SubGalaxys.Add(galaxy2);
    
            _allGalaxys.Add(galaxy1);
            _allGalaxys.Add(galaxy2);
        }
    
        private ObservableCollection<GalaxyViewModel> _allGalaxys = new ObservableCollection<GalaxyViewModel>();
        public ObservableCollection<GalaxyViewModel> AllGalaxys
        {
            get { return _allGalaxys; }
            set { _allGalaxys = value; }
        }
    
        public GalaxyViewModel SelectedGalaxy
        {
            get { return _selectedGalaxy; }
            set { _selectedGalaxy = value; 
                
                OnPropertyChanged(() => SelectedGalaxy);
            }
        }
    }

    Warm regards,

    Matt

    • Помечено в качестве ответа ShrishailHalijol 21 июня 2012 г. 7:27
    • Снята пометка об ответе ShrishailHalijol 21 июня 2012 г. 9:37
    • Помечено в качестве ответа ShrishailHalijol 27 июня 2012 г. 10:54
    21 июня 2012 г. 2:21
  • 1) Well, generally you'd some sort of MainViewModel and MainView and if it's not a big project (less than a couple of thousand lines say), I'd just store it there. You'd have a public property like AllModels and AllViewModels.

    2) You'll serialize/deserialize XML into Model classes. So your XML will look something like

    <GalaxyModel Name="Ursa Minor>
      <Planets>
        <PlanetModel Name="Earth"/>
      </Plants>
      <Galaxys>
        <GalaxyModel Name="Orion"/>
      </Galaxys>
    </GalaxyModel>


    When you create/insert a new Galaxy/Planet, you'll probably want to create a GalaxyView and PlanetView. These would have fields for the user to enter Name etc. and a Save button. The GalaxyView has a DataContext of GalaxyViewModel. The GalaxyViewModel wraps the GalaxyModel fields.

    eg.

    public string Name
    {
      get
      {
        return _model.Name;
      }
      set 
      {
        _model.Name = value;
    
        OnPropertyChanged(() => Name);
      }
    }
    

    3. They are changed in the Model via the ViewModel. See 2). When you create a NEW Planet/Galaxy, assign a NEW GalaxyModel/PlanetModel to the ViewModel.

    Regards,

    Matt

    22 июня 2012 г. 5:20

Все ответы

  • Hi ShrishailHalijol,

    I'd probably set it up a bit like this...

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <ListView Grid.Column="0" ItemsSource="{Binding AllGalaxys}" SelectedItem="{Binding SelectedGalaxy}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn>
                    <GridViewColumn Header="Sub Galaxys" DisplayMemberBinding="{Binding SubGalaxys.Count}"></GridViewColumn>
                    <GridViewColumn Header="Planets" DisplayMemberBinding="{Binding AllPlanets.Count}"></GridViewColumn>                    
                </GridView>
            </ListView.View>
        </ListView>
        <ListView Grid.Column="1" ItemsSource="{Binding SelectedGalaxy.SubGalaxys}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn>                    
                </GridView>
            </ListView.View>
        </ListView>
        <ListView Grid.Column="2" ItemsSource="{Binding SelectedGalaxy.AllPlanets}">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}"></GridViewColumn>
                    <GridViewColumn Header="Age" DisplayMemberBinding="{Binding Age}"></GridViewColumn>
                    <GridViewColumn Header="Rotation Axis" DisplayMemberBinding="{Binding RotationAxis}"></GridViewColumn>
                </GridView>
            </ListView.View>
        </ListView>
    </Grid>

    public class ViewModelBase : INotifyPropertyChanged
    {
        #region Implementation of INotifyPropertyChanged
    
        public event PropertyChangedEventHandler PropertyChanged;
    
        #endregion
    
        public virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                var e = new PropertyChangedEventArgs(propertyName);
                handler(this, e);
            }
        }
    
        public virtual void OnPropertyChanged<T>(Expression<Func<T>> propertyNameExpression)
        {
            OnPropertyChanged(((MemberExpression) propertyNameExpression.Body).Member.Name);
        }
    }
    
    public class GalaxyViewModel : ViewModelBase
    {
        private string _name;
        private ObservableCollection<PlanetViewModel> _planets = new ObservableCollection<PlanetViewModel>();
        private ObservableCollection<GalaxyViewModel> _subGalaxys = new ObservableCollection<GalaxyViewModel>();
    
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
    
                OnPropertyChanged(() => Name);
            }
        }
    
        public ObservableCollection<GalaxyViewModel> SubGalaxys
        {
            get { return _subGalaxys; }
            set
            {
                _subGalaxys = value;
    
                OnPropertyChanged(() => SubGalaxys);
            }
        }
    
    
        public ObservableCollection<PlanetViewModel> Planets
        {
            get { return _planets; }
            set
            {
                _planets = value;
    
                OnPropertyChanged(() => Planets);
            }
        }
    
        public List<PlanetViewModel> AllPlanets
        {
            get 
            {
                if (SubGalaxys.Count == 0) return Planets.ToList();
    
                List<PlanetViewModel> result = new List<PlanetViewModel>();
    
                result.AddRange(Planets);
    
                foreach (var galaxyViewModel in SubGalaxys)
                {
                    result.AddRange(galaxyViewModel.AllPlanets);
                }
                    
                return result; 
            }
        }        
    }
    
    public class PlanetViewModel : ViewModelBase
    {
        private long _age;
        private string _name;
        private double _rotationAxis;
    
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
    
                OnPropertyChanged(() => Name);
            }
        }
    
    
        public long Age
        {
            get { return _age; }
            set
            {
                _age = value;
    
                OnPropertyChanged(() => Age);
            }
        }
    
    
        public double RotationAxis
        {
            get { return _rotationAxis; }
            set
            {
                _rotationAxis = value;
    
                OnPropertyChanged(() => RotationAxis);
            }
        }
    }
    
    public class AllGalaxysViewModel : ViewModelBase
    {
        private GalaxyViewModel _selectedGalaxy;
    
        public AllGalaxysViewModel()
        {
            GalaxyViewModel galaxy1 = new GalaxyViewModel();
            galaxy1.Name = "Alpha Centauri";
            galaxy1.Planets.Add(new PlanetViewModel() { Name = "Earth", Age = 4540000000, RotationAxis = 23.4 });
    
            GalaxyViewModel galaxy2 = new GalaxyViewModel();
    
            galaxy2.Name = "Orion";
            galaxy2.Planets.Add(new PlanetViewModel() { Name = "Betelgeuse", Age = 10000000000, RotationAxis = 13.4 });
            galaxy1.SubGalaxys.Add(galaxy2);
    
            _allGalaxys.Add(galaxy1);
            _allGalaxys.Add(galaxy2);
        }
    
        private ObservableCollection<GalaxyViewModel> _allGalaxys = new ObservableCollection<GalaxyViewModel>();
        public ObservableCollection<GalaxyViewModel> AllGalaxys
        {
            get { return _allGalaxys; }
            set { _allGalaxys = value; }
        }
    
        public GalaxyViewModel SelectedGalaxy
        {
            get { return _selectedGalaxy; }
            set { _selectedGalaxy = value; 
                
                OnPropertyChanged(() => SelectedGalaxy);
            }
        }
    }

    Warm regards,

    Matt

    • Помечено в качестве ответа ShrishailHalijol 21 июня 2012 г. 7:27
    • Снята пометка об ответе ShrishailHalijol 21 июня 2012 г. 9:37
    • Помечено в качестве ответа ShrishailHalijol 27 июня 2012 г. 10:54
    21 июня 2012 г. 2:21
  • Hi Matt,

    I thousand thanks :),

    I believe Galaxy and Planet classes act as Model here. I was in a wrong impression that I need to create another Model class which has these objects. Correct me if I am wrong...

    I have some simple questions....

    1. In my application, I have a single Galaxy (Root) which contains subgalaxies and planets, so on so forth, like a TreeView. In this case do I still need to have AllGalaxysViewModel?

    2.I am allowing user to Add/Remove subgalaxies and planets and explicitly use "Save" option to have the hierarchy saved in DB. How can this be achieved? 


    Regards, Shrishail.

    21 июня 2012 г. 6:03
  • Yeah they're OK for Model classes. Good form in my opinion to label classes as such though. Ie, GalaxyModel, PlanetModel, GalaxyViewModel etc.

    1) Nope, if you've a single Galaxy, you'd probably just make the DataContext of the View that single GalaxyViewModel

    2) Fairly easily. You've seen above how to bind the SelectedItem of a ListView to a ViewModel, all you need is a button, which when clicked will say, "OK database, this is the selected Planet or Galaxy, DELETE FROM Planets WHERE PlanetID = 5" for example. At that point, remove the ViewModel from you ObservableCollection and well.. thats about it. Same goes for an insert :) 

    • Помечено в качестве ответа ShrishailHalijol 21 июня 2012 г. 7:27
    • Снята пометка об ответе ShrishailHalijol 21 июня 2012 г. 7:35
    21 июня 2012 г. 6:45
  • Matt,

    Sorry for the trouble, but this seems bit confusing for me.

    Can you elaborate on the 2nd point above by insert Galaxy and Planet action ?


    Regards, Shrishail.

    21 июня 2012 г. 7:37
  • My actual scenario is: Galaxy may contain Galaxies and planets. (Hierarchical tree fashion). Inturn the galaxies may further contain galaxies and planets. This entire hierarchical info is read from XML. 

    My doubts are:

    1. Who will hold the data read from the XML?

    2. Where is the instance of Model created?

    3. When user makes changes, where are the changes available before saving and after saving?


    Regards, Shrishail.

    21 июня 2012 г. 9:29
  • 1) Well, generally you'd some sort of MainViewModel and MainView and if it's not a big project (less than a couple of thousand lines say), I'd just store it there. You'd have a public property like AllModels and AllViewModels.

    2) You'll serialize/deserialize XML into Model classes. So your XML will look something like

    <GalaxyModel Name="Ursa Minor>
      <Planets>
        <PlanetModel Name="Earth"/>
      </Plants>
      <Galaxys>
        <GalaxyModel Name="Orion"/>
      </Galaxys>
    </GalaxyModel>


    When you create/insert a new Galaxy/Planet, you'll probably want to create a GalaxyView and PlanetView. These would have fields for the user to enter Name etc. and a Save button. The GalaxyView has a DataContext of GalaxyViewModel. The GalaxyViewModel wraps the GalaxyModel fields.

    eg.

    public string Name
    {
      get
      {
        return _model.Name;
      }
      set 
      {
        _model.Name = value;
    
        OnPropertyChanged(() => Name);
      }
    }
    

    3. They are changed in the Model via the ViewModel. See 2). When you create a NEW Planet/Galaxy, assign a NEW GalaxyModel/PlanetModel to the ViewModel.

    Regards,

    Matt

    22 июня 2012 г. 5:20
  • Matt, Thanks again.

    I am thinking of the following approach...

    Please comment on this...


    Regards, Shrishail.

    22 июня 2012 г. 11:01
  • Yeah, that's it :)
    25 июня 2012 г. 2:11
  • Hi ShrishailHalijol,

    How about your issue?

    Best regards,


    Sheldon _Xiao[MSFT]
    MSDN Community Support | Feedback to us
    Microsoft
    Please remember to mark the replies as answers if they help and unmark them if they provide no help.

    26 июня 2012 г. 5:49
    Модератор
  • Hi,

    Sorry for the delay... Matt thanks again for the wonderful responses :)


    Regards, Shrishail.

    27 июня 2012 г. 10:55
  • My pleasure Shrishail, good luck with your project!
    28 июня 2012 г. 2:15