Ben
Ben

Reputation: 1221

Microsoft Prism Event Aggregator send value or address

I am new to Prism and have a problem with Event Aggregator. I modify the HelloWorld example given by Microsoft to show my problem, which is described at the end.

First, I define a MyMessage class to be send as the payload of an event as follow:

public class MyMessage
{
    public int id { get; set; }
    public string content { get; set; }

    public override string ToString()
    {
        return this.content;
    }
}

Secondly, I define an event as follow

public class MessageArrivedEvent : PubSubEvent<MyMessage>
{
}

After that, I add a button to Shell.xaml and its click function is as follow

private void button_Click(object sender, RoutedEventArgs e)
    { 
        message.content = message.content == "hello1" ? "hello2" : "hello1";

        ServiceLocator.Current.GetInstance<IEventAggregator>()
                .GetEvent<MessageArrivedEvent>()
                .Publish(message);
    }

Here, message is a property in public partial class Shell : Window class.

Furthermore, I add a combo box to HelloWorldView and bind the ItemsSource to its corresponding viewmodel.

private ObservableCollection<MyMessage> _comboboxitemsource;

    public ObservableCollection<MyMessage> comboboxitemsource
    {
        get { return this._comboboxitemsource; }
        set
        {
            this._comboboxitemsource = value;
            this.OnPropertyChanged("comboboxitemsource");
        }
    }

The way I handle the event is as follow:

public HelloWorldViewModel()
    {
        this.comboboxitemsource = new ObservableCollection<MyMessage>();
        ServiceLocator.Current.GetInstance<IEventAggregator>().GetEvent<MessageArrivedEvent>().Subscribe(this.MessageArrived, ThreadOption.UIThread, false);
    }

    private void MessageArrived(MyMessage message)
    {
        for (int i = 0; i < this.comboboxitemsource.Count; i++)
        {
            if (this.comboboxitemsource[i].id == message.id)
            {
                this.comboboxitemsource[i].content = message.content;
                break;
            }
        }

        if (this.comboboxitemsource.Count == 0)
            this.comboboxitemsource.Add(message);

    }

Now I set the content of the message in Shell part to "hello1". When I click the button, its content will be changed to "hello2" and then message will be sent to the HelloWorldModule. Since at this time, there is no element in comboboxitemsource, the message will be added into this itemsource. When the button is clicked again, the content of the message will be changed to "hello1" and the message will be sent to the module. The problem is before the message is changed in MessageArrived function, the content is already changed automatically to "hello1". After running MessageArrived, the content in the dropdown list is still "hello2", rather than "hello1".

Can anyone help to explain the problem? Thank you

Upvotes: 0

Views: 1018

Answers (1)

Bart
Bart

Reputation: 10015

The real problem is that you're using 1 single message object all over your application and you're changing that single object. That's basicly how reference types work.

A reference type contains a pointer to another memory location that holds the data. Reference types include the following:

  • String
  • All arrays, even if their elements are value types
  • Class types, such as Form
  • Delegates

Source: https://msdn.microsoft.com/en-us/library/t63sy5hs.aspx

To recap what you're actually doing:

  • You create a message object (id=1 content="hello1") and store the reference to it in the property on your Shell.
  • When you click the button, content="hello2" and you use the EventAggregator to send the (reference) of the message to the Module.
  • Since the ComboBox is empty, you add this message.

Now you end up with multiple pointers (one in your property, one in your ObservableCollection to the same Message object in memory.

  • You click the button again, and update content="hello1". Since it's 1 object referenced everywhere, all variables already see content="hello1".

Note that you don't see it on the UI in the ComboBox without INotifyPropertyChanged, but when you debug the code, the value is already changed (as we're still talking about the same 1 single message object).


How to fix it?

  • Drop the property on your shell holding a reference to the Message object, in the scenario described above there's no use in holding a reference to it. If you want to track whether you have to send hello1 or hello2, you can do it with a simple boolean or string flag.
  • When sending a message over the EventAggregator, always create a new object as it's a new message you're sending.

This way you will end up with a list of different items in your ObservableCollection. It's then up to you to decide if you want to show one item for each message send (simply add every incoming message) or filter out duplicate strings (apply a check).

Upvotes: 1

Related Questions