Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF, MVVM and PRISM - No Parameterless Constructor defined for this object

Tags:

c#

mvvm

wpf

xaml

prism

Answer OK so adding the suggested code given by E-Bat didn't have any affect until I started a new project and copied all the code across verbatim. I can only assume there must be some background code within the ViewModelLocator on http://prismlibrary.com/ which did not update to take the parameterless constructor into account. Hope this helps anyone else with the same issue

Original Question I have set up a MVVM project using prism. I have a MainWindow.xaml and 5 Views; ButtonsView, HeaderView, ProcessInputView, ProcessLogView and ProcessSelectionView which I am using, each View has an associated ViewModel.

MainWindow.xaml

<Window x:Class="TransactionAutomationTool.Views.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:TransactionAutomationTool"
    xmlns:views="clr-namespace:TransactionAutomationTool.Views"
    xmlns:prism="http://prismlibrary.com/"
    prism:ViewModelLocator.AutoWireViewModel="True"
    mc:Ignorable="d"
    Title="MainWindow" Height="600" Width="800">
<Grid>
    <views:HeaderView x:Name="HeaderViewControl" Margin="20,21,0,0" />
    <views:ProcessSelectionView x:Name="ProcessSelectionViewControl" Margin="20,119,0,0" />
    <views:ProcessInputView x:Name="ProcessInputViewControl" Margin="20,280,0,0" />
    <views:ProcessLogView x:Name="ProcessLogView" Margin="298,105,0,0" />
    <views:ButtonsView x:Name="ButtonViewControl" Margin="0,513,0,0" />
</Grid>

MainWindowViewModel

public class MainWindowViewModel: BindableBase
{

    public IEventAggregator _events;
    private UserPrincipal userPrincipal;
    public MainWindowViewModel(IEventAggregator events)
    {
        _events = events;
        userPrincipal = UserPrincipal.Current;
        _events.GetEvent<HeaderLoaded>().Subscribe(HeaderHasBeenLoaded);

    }

    private void HeaderHasBeenLoaded()
    {
        _events.GetEvent<UserNameUpdate>().Publish(string.Format("{0} {1}", userPrincipal.GivenName, userPrincipal.Surname));
    }
}

When I try to view the MainWindow in design mode I get the following Error Screenshot of MainWindow In design Mode

No Parameterless constructor found for this object - This Highlights the HeaderView and the ButtonsView

Both the HeaderViewModel and ButtonsViewModel take IEventAggregator as a parameter within their constructor where as the rest of the ViewModels do not. I am assuming this is where the errors are coming from.

HeaderViewModel

public class HeaderViewModel: BindableBase
{
    private string userName;
    private string runTime;

    public string UserName
    {
        get { return userName; }
        set { SetProperty(ref userName, value); }
    }

    public string RunTime
    {
        get { return runTime; }
        set { SetProperty(ref runTime, value); }
    }

    public HeaderViewModel(IEventAggregator events)
    {
        events.GetEvent<RunTimeUpdate>().Subscribe(RunTimeUpdated);
        events.GetEvent<UserNameUpdate>().Subscribe(UserNameUpdated);
        events.GetEvent<HeaderLoaded>().Publish();
    }

    private void RunTimeUpdated(string newRunTime)
    {
        RunTime = newRunTime;
    }

    private void UserNameUpdated(string userName)
    {
        UserName = userName;
    }
}

So how can I get round this error if I need to subscribe to these events and hence need the IEventAggregator passed into my ViewModels?

Do I need to register this within the Bootstrap via an override of the ConfigureContainer method? If so I'm not entirely sure how to do this.

Bootstrap

class Bootstraper: UnityBootstrapper
{
    protected override DependencyObject CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }

    protected override void InitializeShell()
    {
        Application.Current.MainWindow.Show();
    }
}

The application builds successfully and runs successfully but it is just when trying to view the MainWindow in the designer that I get this message.

Any help would be greatly appreciated. EDIT All my view constructors just have the initalizeComponent methods and take no parameters

like image 693
gheff Avatar asked Dec 21 '16 14:12

gheff


1 Answers

The answer marked as accepted addresses the exception, but doesn't answer the question about why. Also, that approach will make unit testing quite difficult as you will be setting the datacontext to a specific object instead of passing in a dependency.

The reason you are getting the exception is because the HeaderView is not being instantiated by the container(by default it's UnityContainer).

You are constructing the entirety of MainWindow at design time instead of individual pieces. Try the following in MainWindow

<Grid>
<Grid.RowDefinitions>
 <RowDefinitions />
 <RowDefinitions />
 <RowDefinitions />
 <RowDefinitions />
 <RowDefinitions />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0" prism.RegionManager.RegionName="Row0Region" />
<ContentControl Grid.Row="1" prism.RegionManager.RegionName="Row1Region" />
<ContentControl Grid.Row="2" prism.RegionManager.RegionName="Row2Region" />
<ContentControl Grid.Row="3" prism.RegionManager.RegionName="Row3Region" />
<ContentControl Grid.Row="4" prism.RegionManager.RegionName="Row4Region" />
</Grid>

Then you can either use View Discovery or View Injection. For View Discovery, you can do something like

this.RegionManager.RegisterViewWithRegion("Row0Region", HeaderView()) and so on for rest of the views.

You can register views with regions in initialize method of modules or somewhere else. Upto you as to where you do that. You could override the Run method of the bootstrapper. Once the base run method has finished, you can register your views.

When the main window is displayed, all the regions will be discovered and RegionManager will populate the regions with the views that have been registered with each of the regions.

The regionmanager will instantiate the views using the container. When the container constructs each of the views, their viewmodels will be auto wired up. Also the IEventAggregator will be provided to the HeaderView's viewmodel.

This article is based on prism 4 - https://www.codeproject.com/Articles/165376/A-Prism-Application-Checklist but it talks about how to construct views.

like image 181
Neil Avatar answered Sep 23 '22 14:09

Neil