0

I'm having trouble figuring out what the best solution is given the following situation. I'm using Prism 4.1, MEF, and .Net 4.0.

I have an object Project that could have a large number (~1000) of Line objects. I'm deciding whether it is better to expose an ObservableCollection<LineViewModel> from my ProjectViewModel and manually create the Line viewmodels there OR set the ListBox as it's own region and activate views that way.

I'd still want my LineViewModel to have Prism's shared services (IEventAggregator, etc.) injected, but I don't know how to do that when I manually create the LineViewModel. Any suggestions or thoughts?

EDIT: My initial thoughts:

Project:

public class Project
{
    public List<Line> Lines { get; set; }
}

ProjectViewModel:

[Export(typeof(ProjectViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class ProjectViewModel : NotificationObject, IRegionMemberLifetime
{
    private Project _localProject;

    /* 
        HERE WILL BE SOME PROPERTIES LIKE COST, PRICE THAT ARE CUMULATIVE FROM THE Lines 
     */

    public ObservableCollection<LineViewModel> Lines { get; private set; }

    private readonly IEventAggregator _eventAggregator;

    [ImportingConstructor]
    public ProjectViewModel(IEventAggregator eventAggregator)
    {
        _eventAggregator = eventAggregator;
        _eventAggregator.GetEvent<ProjectLoaded>().Subscribe(SetupProject, false);
        Lines = new ObservableCollection<LineViewModel>();
    }

    private void SetupProject(Project project)
    {
        _localProject = project;

        foreach(var l in _localProject.Lines)
        {
            LineViewModel lvm = new LineViewModel(l);
            lvm.PropertyChanged += // Some handler here
            Lines.Add(lvm);
        }
    }

    public bool KeepAlive
    {
        get { return false; }
    }
}

LineViewModel:

public class LineViewModel : NotificationObject
{
    private Line _localLine;

    public decimal Cost
    {
        get { return _localLine.Cost; }
        set
        {
            _localLine.Cost = value;
            RaisePropertyChanged(() => Cost);
        }
    }

    public LineViewModel(Line incoming)
    {
        _localLine = incoming;
    }
}
Thelonias
  • 2,918
  • 3
  • 29
  • 63
  • Ah, I see. It doesn't look bad, perhaps you should just move forward with it and see how it works out. As far as using DI, Just change your LineViewModel to have a Line property instead of passing it in the constructor. Resolve a LineViewModel with the container (therefore getting your injected dependencies), then set the LineViewModel.Line = l; And you should be good to go. – Alan Oct 31 '12 at 18:41

2 Answers2

1

To manually create your LineViewModels with Prism/MEF you can use the container to resolve the dependencies, that is what it is for.

For example,

LineViewModel line = container.GetExportedValue<LineViewModel>();

See this link: Managing Dependencies: Resolving Instances With MEF

I'm sort of concerned about your design, is it really necessary for each of your lines to have a ViewModel and be created by the container and have dependencies injected? Is is possible that there could be one object which manages all of the lines and has those injected dependencies? Perhaps some sort of Repository Pattern might benefit you?

There can be considerable overhead if you are resolving thousands of objects through the container. The Prism book also mentions this may not be a good idea Considerations for using the container

Alan
  • 7,875
  • 1
  • 28
  • 48
  • Thanks for the reply. If you wouldn't mind, I'm a bit stumped on my design, so I'm not really sure *what* I want to do. As I mentioned above, I have a Project object with a ProjectView/ViewModel. This Project has Lines, which I want to display somehow in a ListBox. These lines need to be able to notify the ProjectViewModel of property changes so that the Project model can, for example, re-calculate costs. The project object comes from deserialized XML and already contains it's lines. – Thelonias Oct 31 '12 at 02:10
  • I will have something similar to that. I have objects that I will need to read out of an XML file and into a List. That "list" gets graphed on a graph as a single object and that list also gets added to a ListBox to create a graph legend that allows me to configure the Visibility and Color of the graph object which represents the list. So, in my case the objects are read from XML, probably stored in an ObservableCollection and a ViewModel contains that collection and exposes a Name, Color, and Visible property for example. However, in some ways I may have the same problem as you. – Alan Oct 31 '12 at 02:31
  • I haven't gotten to it yet, but oif properties of those objects can change, the graph may need to update to reflect that change. For me, I'll have a graph object which represents the entire list, I guess that object will need to handle the CollectionChanged events and wire/unwire event handlers to the individual objects which should implement INotifyPropertyChanged. Then, the object as a whole can recaculate what it needs, and update itself. This may be a few thousand event handlers, but it could be alright because only a few are ever changing at once. – Alan Oct 31 '12 at 02:31
  • Anyway, it seems to me that your Lines might actually be more of your Model and not the ViewModel. I don't have a reason for my objects to be resolved by the container, they are simply read out of a file. – Alan Oct 31 '12 at 02:35
  • correct, my Lines are part of my model...they're on the Project object. But because I was going to be interacting with properties on the lines via the UI, I had intended to create a collection LineViewModel objects and bind that to the ListBox. I just wasn't sure if that was the correct way to do it or not. My model objects don't implement INotifyPropertyChanged, so I need to wrap them in a viewmodel regardless. – Thelonias Oct 31 '12 at 02:40
1

I could be way off base here, maybe this is too simple, but does this help you at all? I created a quick project which demonstrated a few basics. If you need more info maybe I can use it to help you more.

Sample Application With Binding To "Lines"

View

    <Window x:Class="WpfApplication1.LinesView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="LinesView" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" d:DesignHeight="247" d:DesignWidth="348" SizeToContent="WidthAndHeight" Width="350" Height="250">
    <Window.Resources>
        <DataTemplate x:Key="LineView">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition />
                    <RowDefinition />
                    <RowDefinition />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"/>
                    <ColumnDefinition Width="Auto" MinWidth="50"/>
                </Grid.ColumnDefinitions>

                <TextBlock Grid.Row="0" Grid.Column="0" Text="Line: " />
                <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Name}" />

                <TextBlock Grid.Row="1" Grid.Column="0" Text="X: " />
                <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding X}" />

                <TextBlock Grid.Row="2" Grid.Column="0" Text="Y: " />
                <TextBox Grid.Row="2" Grid.Column="1" Text="{Binding Y}" />
            </Grid>
        </DataTemplate>
    </Window.Resources>
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
        <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
            <TextBlock Text="Total Cost" Margin="5" />
            <TextBlock Text="{Binding Cost}" Margin="5" />
        </StackPanel>
        <ContentControl Name="contentControl1" Content="{Binding ElementName=listBox1, Path=SelectedItem}" ContentTemplate="{StaticResource LineView}" VerticalAlignment="Center" HorizontalAlignment="Center" Width="105" Margin="5" />
        <ListBox Height="234" 
                 HorizontalAlignment="Center"
                 Name="listBox1" 
                 VerticalAlignment="Center"
                 ItemsSource="{Binding Lines}"
                 ItemTemplate="{StaticResource LineView}" Width="152" Margin="5" />
    </StackPanel>
</Window>

ViewModel

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WpfApplication1.Models;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace WpfApplication1
{
    public class LinesViewModel : INotifyPropertyChanged
    {
        public int Cost
        {
            get
            {
                return Lines.Sum(x => x.X + x.Y); 
            }
        }

        public ObservableCollection<Line> Lines
        {
            get;
            private set;
        }

        public LinesViewModel()
        {
            Lines = new ObservableCollection<Line>();
            Lines.Add(new Line()
            {
                Name = "Line1",
                X = 0,
                Y = 1
            });
            Lines.Add(new Line()
            {
                Name = "Line2",
                X = 1,
                Y = 1
            });
            Lines.Add(new Line()
            {
                Name = "Line3",
                X = 2,
                Y = 2
            });

            foreach(Line line in Lines)
            {
                line.XChanged += new EventHandler(lineChanged);
                line.YChanged += new EventHandler(lineChanged);
            }

            Lines.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(Lines_CollectionChanged);
        }

        private void Lines_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
            {
                foreach (Line line in e.NewItems)
                {
                    line.XChanged += new EventHandler(lineChanged);
                    line.YChanged += new EventHandler(lineChanged);
                }
            }
            if (e.OldItems != null)
            {
                foreach (Line line in e.OldItems)
                {
                    line.XChanged -= new EventHandler(lineChanged);
                    line.YChanged -= new EventHandler(lineChanged);
                }
            }
        }

        private void lineChanged(object sender, EventArgs e)
        {
            PropertyChanged(this, new PropertyChangedEventArgs("Cost"));
        }

        public event PropertyChangedEventHandler PropertyChanged = delegate { };
    }
}

Model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WpfApplication1.Models
{
    public class Line
    {
        private int x;
        private int y;

        public String Name { get; set; }

        public int X
        {
            get
            {
                return x;
            }
            set
            {
                x = value;
                XChanged(this, EventArgs.Empty);
            }
        }

        public int Y
        {
            get
            {
                return y;
            }
            set
            {
                y = value;
                YChanged(this, EventArgs.Empty);
            }
        }

        public event EventHandler XChanged = delegate { };
        public event EventHandler YChanged = delegate { };
    }
}
Alan
  • 7,875
  • 1
  • 28
  • 48
  • This is pretty close to my initial design. The difference being that 1) my Line objects don't have events on them and 2) my ProjectViewModel would be the owner of the Lines ObservableCollection. I still think it benefits me to have a collection of LineViewModels that each hold a Line object...it would act as a proxy to the Line object because the Line doesn't have any events or implement INotifyPropertyChanged. – Thelonias Oct 31 '12 at 12:11
  • Think of an order entry system. You have an order, and then line items. The order has some properties whose calculation is based on the line items. The line item object is very basic (just a bunch of auto-properties; cost, price, quantity). I would think a new object (LineViewModel) would keep a line item object and be in control of setting the line item's properties. I also think the "Order" (my ProjectViewModel) object should own all of those LineViewModels. – Thelonias Oct 31 '12 at 12:17
  • Also, thanks so much for taking the time to write out a sample application. Although it's not exactly what I'm looking for, it was nice of you to take the time to help. – Thelonias Oct 31 '12 at 12:50
  • @Ryan If your Lines don't implement INotifyPropertyChanged and have no events, how are you detecting they change when you wrap them in your ViewModel? It seems like you have to in order to update your costs or whatever. – Alan Oct 31 '12 at 13:25
  • I've added code to further explain how I was thinking of setting this up. – Thelonias Oct 31 '12 at 15:22
  • @Ryan See my comments on your post – Alan Oct 31 '12 at 18:42