Question

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;
    }
}
Was it helpful?

Solution

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 { };
    }
}

OTHER TIPS

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

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top