Hammas
Hammas

Reputation: 1214

Caliburn Micro Stack Overflow Exception using IEventAggregator

I am learning and trying MVVM using Caliburn.Micro. I have a main ShellViewModel and a Child UserControl Module1ViewModel. I am trying to achieve communication between both of them using IEventAggregator.
Now, I can change a property of ShellViewModel (which is parent) from child control ShellViewModel and can change a property of child control from Parent and it works.

Problem : Issue I am facing is that when I enable events.Subscribe(this); in both ViewModels I get an exception.
enter image description here

What I am trying to achieve is TWO WAY kind of communication. Which means I want to change some property of Parent from child at the same time I should be able to change some Property of Child from Parent. Following is my code, please check what's wrong here.

Child User Control Module1ViewModel

    namespace IntelliCoreMVVM.ViewModels
    {
        public class Module1ViewModel:Screen , IHandle<string>
        {
            private IEventAggregator _events;
            public Module1ViewModel(IEventAggregator events)
            {
                _events = events;
                events.Subscribe(this);
            }
            private string _firstName;
            public string FirstName
            {
                get { return _firstName; }
                set
                {
                    _firstName = value;
                    NotifyOfPropertyChange(()=>FirstName);
                    _events.PublishOnUIThread(FirstName);
                }
            }

            public void Handle(string message)
            {
                FirstName = message;
            }
        }
    }  

As you can see, i am publishing and Handling a property.

ShellViewModel


    public class ShellViewModel : Conductor<object> , IHandle<string>
            {
                private Module1ViewModel _module1ViewModel;
                private IEventAggregator _events;
                public ShellViewModel(Module1ViewModel module1ViewModel,IEventAggregator events)
                {
                    _events = events;
                    events.Subscribe(this);
                    _module1ViewModel = module1ViewModel;
                }
                private string _test;
                private string _firstName;
                public string FirstName
              {
               get { return _firstName; }
               set
                 {
                  _firstName = value;
                   NotifyOfPropertyChange(()=>FirstName);
                  _events.PublishOnUIThread(new CustomerModel{FirstName =     FirstName});
                 }
                }
                public string Test
                {
                    get { return _test; }
                    set
                    {
                        _test = value; 
                        NotifyOfPropertyChange(()=>Test);
                    }
                }
                public void Handle(string message)
                {
                    Test = message;
                }
          }

Quick Watch enter image description here
Stack Call
enter image description here

Upvotes: 0

Views: 299

Answers (2)

mm8
mm8

Reputation: 169270

FirstName raises the event which calls Handle which sets FirstName again and then it goes on like this in an infinite loop until you run out of stack space and get a StackOverflowException.

The thing is that Module1ViewModel handles all string events including the ones that it raises itself.

What you probably want to do is define different types of events so you can distinguish between them and choose which to handle. In the below example, Module1ViewModel handles events of type ParentToChildEvent but it raises events of type ChildToParentEvent. The ShellViewModel should do the opposite.

public class Module1ViewModel : Screen, IHandle<ParentToChildEvent>
{
    private IEventAggregator _events;
    public Module1ViewModel(IEventAggregator events)
    {
        _events = events;
        events.Subscribe(this);
    }
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            _events.PublishOnUIThread(new ChildToParentEvent(FirstName));
        }
    }

    public void Handle(string message)
    {
        FirstName = message;
    }
}

Upvotes: 1

Pavel Anikhouski
Pavel Anikhouski

Reputation: 23238

The problem is that your VMs are recursively updating each other and self, e.g. subscribed to IEventAggregator and handle string message, than update a property and publish the same again, some kind of infinite loop. You can introduce a typed events to handle from Parent to Child and vice versa, instead of just strings

public class ChildEvent
{
    public string Name { get; set; }
}

public class ParentEvent
{
    public string Name { get; set; }
}    
public class ShellViewModel : Conductor<object>, IHandle<ChildEvent>
{
    private readonly IEventAggregator _events;
    public ShellViewModel(IEventAggregator events)
    {
        _events = events;
        events.Subscribe(this);
    }
    private string _test;

    public string Test
    {
        get => _test;
        set
        {
            _test = value;
            NotifyOfPropertyChange(() => Test);
        }
    }
    public void Handle(ChildEvent message)
    {
        Test = message.Name;
    }
}

public class Module1ViewModel : Screen, IHandle<ParentEvent>
{
    private readonly IEventAggregator _events;
    public Module1ViewModel(IEventAggregator events)
    {
        _events = events;
        events.Subscribe(this);
    }
    private string _firstName;
    public string FirstName
    {
        get => _firstName;
        set
        {
            _firstName = value;
            NotifyOfPropertyChange(() => FirstName);
        }
    }

    public void Handle(ParentEvent message)
    {
        FirstName = message.Name;
    }
}

Btw, you don't need a Module1ViewModel reference in ShellViewModel in case of using an IEventAggregator. Also do not fire events immediately in setters, because it can fire an infinite event loop between view models

Upvotes: 1

Related Questions