Search code examples
c#wpfmvvmdependency-injectionprism

The type does not include any accessible constructors WPF - no window application


I'm using this package to create and use notify icon, which means I have no Windows in my app just ResourceDictionary and ViewModel

It all is working fine and well until I changed my constructor to accept an interface using DI framework(I'm using Autofac extension to PRISM [Prism.Autofac]).

If I adding back the parameterless constructor everything works just fine

Should I even use Autofac is is overkill? how can I do the DI?

Notes

  • im using PRISM as my MVVM framework
  • The Interface is located in another project
  • I already read this, this, this and the ObjectDataProvider documentation and could not find any solution

App.xaml.cs

    public partial class App : Application
    {
        private TaskbarIcon notifyIcon;

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            var bootstrapper = new Bootstrapper();
            bootstrapper.Run();

            notifyIcon = (TaskbarIcon)FindResource("NotifyIcon");          
        }

        protected override void OnExit(ExitEventArgs e)
        {
            notifyIcon.Dispose();
            base.OnExit(e);
        }
    }

App.xaml

  <Application.Resources>
    <ResourceDictionary>
      <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="NotifyIconResources.xaml" />
      </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
  </Application.Resources>

NotifyIconResources.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:tb="http://www.hardcodet.net/taskbar"
                    xmlns:local ="clr-namespace:WatchDog"
                    xmlns:interface="clr-namespace:ServiceControllerLibary;assembly=ServiceControllerLibary"
                    >
    <local:ServiceControllerWorkerStatusToIconConverter x:Key="ServiceControllerWorkerStatusToIconConverter"/>

    <ContextMenu x:Shared="false" x:Key="SysTrayMenu">
        <MenuItem Header="Show Window" />
        <MenuItem Header="Hide Window" />
        <Separator />
        <MenuItem Header="Exit" />
    </ContextMenu>  
    

    <tb:TaskbarIcon x:Key="NotifyIcon"
                    ToolTipText ="{Binding ToolTipText}" DoubleClickCommand="{Binding}"
                    ContextMenu="{StaticResource SysTrayMenu}"
                    IconSource="{Binding ToolTipStatus, 
                    Converter={StaticResource ServiceControllerWorkerStatusToIconConverter}
                    , Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" >

        <!-- Original Not Working-->
        <!-- self-assign a data context (could also be done programmatically) -->
        <!--<tb:TaskbarIcon.DataContext>
            <local:NotifyIconViewModel/>
        </tb:TaskbarIcon.DataContext>-->

        <!-- 2nd try Not Working-->
        <tb:TaskbarIcon.DataContext>
            <ObjectDataProvider ObjectType="{x:Type local:NotifyIconViewModel}">
                <ObjectDataProvider.ConstructorParameters>
                    <interface:ServiceControllerWorker />
                </ObjectDataProvider.ConstructorParameters>
            </ObjectDataProvider>
        </tb:TaskbarIcon.DataContext>

    </tb:TaskbarIcon>


</ResourceDictionary>

Bootstrapper.cs

    class Bootstrapper : AutofacBootstrapper 
    {
        protected override void ConfigureContainerBuilder(ContainerBuilder builder)
        {
            base.ConfigureContainerBuilder(builder);

            builder.RegisterType<ServiceControllerWorker>().As<IServiceControllerWorker>().SingleInstance();
    
        }
    }

NotifyIconViewModel.cs (Constructor only)

 public NotifyIconViewModel(IServiceControllerWorker ServiceControllerWorker)
 {    
      _serviceControllerWorker = ServiceControllerWorker;
  }

Solution

  • It is not working because you are setting the ObjectDataProvider instance to the DataContext

    <tb:TaskbarIcon.DataContext>
        <ObjectDataProvider ObjectType="{x:Type local:NotifyIconViewModel}">
            <ObjectDataProvider.ConstructorParameters>
                <interface:ServiceControllerWorker />
            </ObjectDataProvider.ConstructorParameters>
        </ObjectDataProvider>
    </tb:TaskbarIcon.DataContext>
    

    instead of the value of the ObjectDataProvider.
    Declare the provider in a ResourceDictionary:

    <ResourceDictionary>    
        <ObjectDataProvider x:Key="ViewModelProvider" ObjectType="{x:Type local:NotifyIconViewModel}">
            <ObjectDataProvider.ConstructorParameters>
                <interface:ServiceControllerWorker />
            </ObjectDataProvider.ConstructorParameters>
        </ObjectDataProvider>
    </ResourceDictionary>
    

    And bind it to the DataContext:

    <tb:TaskbarIcon DataContext="{Binding Source={StaticResource ViewModelProvider}}" />
    

    The binding will make the provider to instantiate the provided instance.

    But since you are creating the instance with the help of ObjectDataProvider you made the Autofac container or dependency injection redundant. If you want to use dependency injection, you must let Autofac create the instances. This requires the application to be started manually and to rewrite the MainWindow or the hosting Window of the TaskbarIcon to use composition:

    public partial class MainWindow : Window
    {
        public static readonly DependencyProperty NotifyIconProperty = DependencyProperty.Register(
          "NotifyIcon",
          typeof(TaskbarIcon),
          typeof(Window),
          new PropertyMetadata(default(TaskbarIcon)));
    
        public TaskbarIcon NotifyIcon { get { return (TaskbarIcon) GetValue(MainWindow.NotifyIconProperty); } set { SetValue(MainWindow.NotifyIconProperty, value); } }
    
        public MainWindow(TaskbarIcon taskbarIcon, INotifyIconViewModel notifyIconDataContext, IViewModel dataContext)
        {
            this.notifyIcon = taskbarIcon;     
            this.notifyIcon.DataContext = notifyIconDataContext;   
            this.DataContext = dataContext;      
        }
    }
    

    In the MainWindow.xaml bind the property to a ContentPresenter:

    <Window>
        <ContentPresenter Content="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=MainWindow}, Path=NotifyIcon} />
    </Window>
    

    Then configure the Autofac container:

    class Bootstrapper : AutofacBootstrapper 
    {
        public Container ConfigureContainerBuilder()
        {
            var builder = new ContainerBuilder();
    
            builder.RegisterType<ServiceControllerWorker>().As<IServiceControllerWorker>().SingleInstance();
            builder.RegisterType<NotifyIconViewModel>().As<INotifyIconViewModel>().SingleInstance();
            builder.RegisterType<TaskbarIcon>().SingleInstance();
            builder.RegisterType<MainWindow>().SingleInstance();
    
            return builder.Build();
        }
    }
    

    Then bootstrap the application:

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
    
            var bootstrapper = new Bootstrapper();
            var container = bootstrapper.ConfigureContainerBuilder();
            Application.Current.MainWindow = container.Resolve<MainWindow>();
            Application.Current.MainWindow.Show();         
        }
    }
    

    This way you got rid of the ObjectDataProvider, since you are using Autofac instead.