tags:

views:

279

answers:

2

I am new to WPF and have a problem with a small project. I am building a tester to simulate a device that is controlled by UDP. I have a couple of classes that implement the UDP as an async implementation. I had the main window code-behind polling the receive buffer for data and updating. It worked, but was very clunky. So I moved all the code in the async receive where the EndReceiveFrom lived. It is faster and gets rid of polling. But I no longer have access to the window controls to update them.

How can I get access to the controls from outside the window? The window instantiates the UDP class that runs it all, so I can pass things on the constructor call. I have access, of course, to the App. I know I'm missing something here.

Richard

A: 

Been a while - so I won't be providing too many code blocks - but sounds like you may want to apply MVC, or its cousins MVP or MVVM. Don't worry too much about what they stand for, but generally speaking, they attempt to define relationships between View [your window], Data [your results from network layer], and Business [your network layer].

In your case, I would recommend MVVM. If you Google it, you should be able to find a plethora of information about the design pattern and ways to apply it. Again, don't fuss over the theory too much. Just an fyi.

Next, some meat,

Conceptually, there's "data" you want to represent in your "view". Without worrying about the view, let's first encapsulate your data.

// name is a little too general, so just rename it to something
// specific in your source.
public class NetworkViewModel 
{
    // enumerate your data here
    public int Trouble { get; set; }
    public int Tribbles { get; set; }
    ...
}

Your view is your window. Have your view instantiate an instance of this "view model"

public class MyNetworkMonitor
{
    ...
    // advanced: if you were injecting this - say from another source
    // or intended to replace the instance during runtime, there are
    // ways to deal with that. for now, we presume one instance for
    // application lifetime.
    public NetworkViewModel ViewModel { get; set; }
    public MyNetworkMonitor ()
    {
        ViewModel = new NetworkViewModel ();
    }
    ...
}

and in your Xaml, bind to the properties

// Xaml omitted, a limit to my memory :P
// but just regular binding to say ViewModel.Trouble

Alright. So if your view model has an initial value, it would be displayed to the bound control. Unfortunately, if view model properties update, there is no way to tell the bound control the value has changed. Er, unless of course you implement a common interface that Wpf bindings look for,

public class NetworkViewModel : INotifyPropertyChanged
{
    // defined by interface
    public event PropertyChangedEventHandler PropertyChanged;
    private int _trouble = 0;
    public int Trouble 
    {
        get { return _trouble; }
        set 
        {
            // 1. if value changes
            if (_trouble != value)
            {
                _trouble = value;
                // 2. inform whomever is listening!
                if (PropertyChanged != null) 
                {
                    PropertyChanged (
                        this, 
                        new PropertyChangedEventArgs ("Trouble"));
                }
            }
        }
    }
}

Of course, you can wrap and do whatever you like to ease the syntax, but what preceeds is your bare-bones implementation on the model side.

If you were to handle a button press in your window and incrememt ViewModel.Trouble, you would see a real-time update in the bound control. Huzzah!

All that's left then is wiring up your business layer, ie your network layer, to your ViewModel. You can do this any number of ways. You could pass an instance of the view model to your network layer, you could have the view model directly respond to events on the network layer - it really is completely up to you.

Now, if you go ahead and do this, it won't work. Sorry. Now, it's not completely my fault, you see Wpf doesn't like just anyone modifying its data. More specifically, anything that could possibly affect a Wpf control must be invoked on the Gui thread that owns the control. As you may guess, the threads you are using to modify the view model are originating from network interupts and whatnot. Network threads. Pleabs. Morlocks. Pah!

Anyway, I digress. There's a relatively painless way around this. When you detect a change you would like to report, dispatch the change to the Gui thread.

someWpfControl.Dispatcher.Invoke (
    DispatcherPriority.Normal,
    new Action(
    delegate()
    {
        ViewModel.Trouble = someNewValue;
    });

but that's kinda useless, since your network layer shouldn't know anything about Wpf. So how about within the ViewModel

...
// within property, after detecting change and 
// testing for listeners
someWpfControl.Dispatcher.Invoke (
    DispatcherPriority.Normal,
    new Action(
    delegate()
    {
        PropertyChanged (
            this, 
            new PropertyChangedEventArgs ("Trouble"));
    });
...

and same as before, feel free to wrap that up anywhere else. Like in a helper method. Btw, 'someWpfControl' can be the window. So you could pass it in on ctor. I also strongly advise you Google alternatives to "invoking Dispatcher", as there may be cleverer means of doing so that do not involve control references, extensive cutting and pasting in your view model, or whatever.

Just as long as the Gui thread updates the control, you can do whatever you want :)

Also, shameless plug for my mate and Wpf Disciple Kent who knows infinitely more about Wpf than myself. [ http://kentb.blogspot.com/ ]

Cheers :)

johnny g
Btw, your best Wpf resources are 1.) Google, and 2.) Wpf Unleashed [a book]. Give it a go :)
johnny g