19
votes

I am converting a chat parser for a game i play that i wrote in c# winforms over to wpf, mainly just to get a better handle on MVVM and wpf. Here is a run down of how i have my project set up

View: For now its just a simple ListBox with ItemSource bound to my viewmodels observable chat collection

Model: I have multiple characters that can be logged in at one time and each character has a chat class. The chat class starts a background worker that grabs and next line of chat from the game and fires off an event called IncomingChat with this line.

public event Action<Game.ChatLine> IncomingChat;

I'm using a background worker to fire an event in my backgroundworkers progresschaged event because when i was using a timer i kept getting a threading issue. At first i corrected this by changing my Timer to a DispatchTimer, but this didn't seem right to me to have a DispatchTimer in my model.

ViewModel: Since i have multiple characters i am creating multiple ChatViewModels. I pass a character into the ChatViewModels constructor and subscribe to the Chat event. I create a ObservableColleciton to hold my chat lines when this event is received. Now I'm receiving a threading issue on my viewModel when trying to add the line i receive from my chat event to my observablecollection.

I got around this by making my viewmodels incoming chat event handler look like so

public ObservableCollection<Game.ChatLine) Chat {get; private set;}

void Chat_Incoming(Game.ChatLine line)
{
  App.Current.Dispatcher.Invoke(new Action(delegate
  {
    Chat.Add(line)
  }), null);
}

This doesn't feel right to me though. Although it works, using Dispatcher in my viewmodel like this seems out of place to me.

3

3 Answers

40
votes

Although it works, using Dispatcher in my viewmodel like this seems out of place to me.

This isn't a completely unreasonable approach, and is the approach that many people take. Personally, if you're using WPF (or Silverlight 5), and have access to the TPL, I prefer to use the TPL to handle this.

Assuming your ViewModel is constructed on the UI thread (ie: by the View, or in response to a View related event), which is the case nearly always IMO, you can add this to your constructor:

// Add to class:
TaskFactory uiFactory;

public MyViewModel()
{
    // Construct a TaskFactory that uses the UI thread's context
    uiFactory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
}

Then, when you get your event, you can use this to marshal it:

void Chat_Incoming(Game.ChatLine line)
{
    uiFactory.StartNew( () => Chat.Add(line) );
}

Note that this is slightly different than your original, since it's no longer blocking (this is more like using BeginInvoke instead of Invoke). If you need this to block until the UI finishes processing the message, you can use:

void Chat_Incoming(Game.ChatLine line)
{
    uiFactory.StartNew( () => Chat.Add(line) ).Wait();
}
0
votes

A View Model is a good place to do a Thread Synchronization. Remove the DispatcherTimer from your model and let the VM handle it.

0
votes

I love Reed's answer, and agree with your concerns that something isn't right with your use of the Dispatcher. Your VM references App, which is, in my mind, a reference to a UI artifact (or control). Use Application instead or, better yet, inject the correct Dispatcher instance into your VM, which avoids the need to instantiate your VM in the UI thread.