CaliburnMicro IEventAggregator won't work with ChildViews

64 views Asked by At

and sorry if this is a somewhat easy question, but I'm still new to CaliburnMicro and EventAggregator is proving to be the worst thing to learn by a long mile. Anyway, let's go to it. I have an app with a main ShellView and a bunch of ChildViews that display various information, and I need certain parameters to be shared between the Views. It did not take very long to find I need to use EventAggregator, but with Net6 I could not make it work, no chance. I found this app someone else did in Net3.1 and CaliburnMicro where a new window is created, on this second window there is a TextBox and a send button. Whatever you type here gets sended to the Main window. I studied the code and replicated the app succesfully, also with the latest version of Caliburn and Net6.

BUT then I decided to modify the app and instead of having a new window, now I have exactly the same but with a ChildView inside the ShellView, and here is where nothing works.

1st on the Bootstrapper

public class Bootstrapper : BootstrapperBase
    {
        private SimpleContainer _container;

        public Bootstrapper()
        {
            Initialize();
        }

        protected override void Configure()
        {
            _container = new SimpleContainer();
            _container.Singleton<IWindowManager, WindowManager>();
            _container.Singleton<IEventAggregator, EventAggregator>();
            _container.PerRequest<ShellViewModel>();
        }

        protected override object GetInstance(Type service, string key)
        {
            var instance = _container.GetInstance(service, key);
            if (instance != null)
                return instance;
            throw new InvalidOperationException("Could not locate any instances.");
        }

        protected override IEnumerable<object> GetAllInstances(Type service)
        {
            return _container.GetAllInstances(service);
        }

        protected override void BuildUp(object instance)
        {
            _container.BuildUp(instance);
        }


        protected override void OnStartup(object sender, StartupEventArgs e)
        {
            DisplayRootViewFor<ShellViewModel>();
        }

As seen, I've created a SimpleContainer, then initialised it as a Singleton. Also _container.Singleton<IWindowManager, WindowManager>(); is no longer needed as I am not opening a new window anymore, so that could be commented but as I had so many issues, I let it be just in case for this last try before posting this question.

2nd I've created a class for the message IHandle to manage.

public class EventMessage
    {
        public string Text { get; set; }
    }

3rd I created and edited the ChildViewModel (still called SecondWindowViewModel as this experiment is directly derivated from the original worknig one) and ChildView (well SecondWindowView still). Note that SecondWindowView is a WPF User Control, not a Window.

class SecondWindowViewModel : Screen
    {
        private readonly IEventAggregator _eventAggregator;
        private string _secondTextBox;

        public string SecondTextBox
        {
            get { return _secondTextBox; }
            set { _secondTextBox = value; NotifyOfPropertyChange(() => SecondTextBox); }
        }

        public SecondWindowViewModel(IEventAggregator eventAggregator)
        {
            this._eventAggregator= eventAggregator;
        }

        public void SendBack()
        {
            EventMessage ToSend = new EventMessage();
            ToSend.Text = SecondTextBox;
            _eventAggregator.PublishOnUIThreadAsync(ToSend);
        }

As seen, I have an IEventAggregator _eventAggregator, then on the constructor of the class I added this._eventAggregator= eventAggregator; and then on the method SendBack that is called upon pressing the button I send the message with SubscribeOnUIThread.

And lastly the ShellViewModel:

public class ShellViewModel : Conductor<Object>, IHandle<EventMessage>
    {
        private readonly IEventAggregator _eventAggregator;

        private string _parentText;

        public string ParentText
        {
            get { return _parentText; }
            set { _parentText = value; NotifyOfPropertyChange(() => ParentText); }
        }

        public ShellViewModel(IEventAggregator eventAggregator)
        {
            ActivateItemAsync(new SecondWindowViewModel(_eventAggregator));
            _eventAggregator = eventAggregator;
            _eventAggregator.Subscribe(this);
        }

        public Task HandleAsync(EventMessage message, CancellationToken cancellationToken)
        {
            ParentText = message.Text;
            return Task.CompletedTask;
        }

        //public void NewWindow()
        //{
            //WindowManager wm = new WindowManager();
            //SecondWindowViewModel swm = new SecondWindowViewModel(_eventAggregator);
            //wm.ShowWindowAsync(swm);
        //}
    }
}

Now here instead of inheriting from Screen and IScreen, I inherit from Conductor because I want to have that ChildView on my form. NewWindow is how it worked before but now that button no longer works as I don't need to launch a new window anymore, that's why it is commented out. As seen, on the contructor I subscribe _eventAggregator, and then HandleAsync does the job of receiving the message and assign it to a variable. Now on the Caliburn Documentatin the method to use is public void Handle() but that no longer works, that's the only way I managed to make it work.

Now when I run this the app does load and seems to work just fine, but as soon as the SendBack() method gets called (in SecondWindowViewModel) the line _eventAggregator.PublishOnUIThreadAsync(ToSend); launches an exception System.NullReferenceException: 'Object reference not set to an instance of an object.'

From my understanding EventAggregator should not care if I'm sending the message to a Window or a user panel or anything. Only thnig I changed is commenting out NewWindow and deleting the old SecondWindowWiew WPF Window and replacing it with a new SecondWindowView WPF User Control with the exact same XAML, then in ShellWiev added bellow a <ContentControl x:Name="ActiveItem"/>.

I'm a bit of a loss here, I've been trying everything, coping the code from the documentation, looking for tutorials online, other StackOverflow questions, and while I could make the UI load and make HandleAsync work, for 3 days straight I could not make it work with ChildViews. That code I wrote does work with a new window. I even run into problems of ShellView straight up not loading unless putting a new constructor that takes no parameters and empty inside, but that's for another day.

Sorry for the extra long post, but I think it's important to put all the information out there. Thank you for your time and again, sorry if this is somewhat of a dumb question, but we all have to start somewhere no?

0

There are 0 answers