Can I use DotRas is WPF?

Mar 20, 2010 at 9:28 PM

Hey,

I completed a project a good while back now where I had an app manage and dial all remote connections from VPN to VNC.

Trying to push the bar up on its fourth release at work now and so am moving into WPF.

I cant seem to get DotRas components into it! :(.  I can add the refrence and code what ever I like, but then I cant figureout how to assigen events to a programatically added phonebook.

 

Any ideas?

Coordinator
Mar 21, 2010 at 12:14 AM

Yes you should be able to use them, as long as the app is being deployed to a supported Windows operating system. The components seem to work fine, however the components won't show in the page editor or toolbox within Visual Studio. I'll have to do some investigation and see what I can do to fix this for the 1.2 release (if it even can be fixed).

For now, you'll have to deal with the event handlers on your own. Here's the basic syntax if you're unsure how to manage it yourself...

// C#:

void Go() {
{
    RasDialer dialer = new RasDialer();
    dialer.StateChanged += new EventHandler<StateChangedEventArgs>(this.dialer_StateChanged);
}

void dialer_StateChanged(object sender, StateChangedEventArgs e)
{
}
' VB.NET:

Private Sub Go()
    Dim dialer As New RasDialer()

    AddHandler dialer.StateChanged, AddressOf Me.dialer_StateChanged
End Sub

Private Sub dialer_StateChanged(ByVal sender As Object, ByVal e As StateChangedEventArgs)

End Sub
Coordinator
Mar 21, 2010 at 3:32 AM

So I did a little research on the matter, and it looks like the designer support for WPF is different than support for Windows Forms designer. Since the base classes are completely different, I won't be able to add designer support for WPF without completely rewriting the components. They still work, there just isn't any nice designer support in Visual Studio.

Mar 21, 2010 at 3:09 PM

Thanks alot.

I did think it would work by writing it out but my first attempts failed - hard lol. Ill try today and see where I get to.

 

Mar 21, 2010 at 3:59 PM

Made some progress.

It will dial the vpn so main concerns over, But I cant use the events. Ive set them up right as far as I can see but nothing Iput in there executes. Something is still missing.

 

public void Go()
        {
            // Connect vpn
            tbDetails.Clear();
            RasDialer dialer = new RasDialer();
            dialer.StateChanged += new EventHandler<StateChangedEventArgs>(dialer_StateChanged);
            dialer.DialCompleted += new EventHandler<DialCompletedEventArgs>(dialer_DialCompleted);
            dialer.EntryName = getVPN(cbBranch.SelectedItem.ToString());
            dialer.PhoneBookPath = someCustomPath;

            try
            {

                dialer.DialAsync();

            }
            catch (Exception)
            {
            }
        }

public void dialer_StateChanged(object sender, StateChangedEventArgs e)
        {
            tbDetails.AppendText(string.Format("{0}\r\n", e.State.ToString()));
            MessageBox.Show("Im working>");
        }

 

 

Coordinator
Mar 21, 2010 at 9:23 PM
Edited Mar 21, 2010 at 9:53 PM

When using asynchronous dialing, you need to marshal the thread that raises the event back to the UI thread. I'm not sure if this is still the case with WPF as it is with Windows Forms, but typically when using DialAsync with a user interface you need to set the SynchronizingObject property on the dialer. From what I can tell, there is no conversion between the dispatcher class WPF uses to marshal threads for the UI, so I wrote a small class to handle it.

    internal class DispatcherSynchronizingObject : System.ComponentModel.ISynchronizeInvoke
    {
        private System.Windows.Threading.Dispatcher dispatcher;

        public DispatcherSynchronizingObject(System.Windows.Threading.Dispatcher dispatcher)
        {
            this.dispatcher = dispatcher;
        }

        public bool InvokeRequired
        {
            get { return true; }
        }

        public System.IAsyncResult BeginInvoke(System.Delegate method, object[] args)
        {
            return new DispatcherAsyncResult(this.dispatcher.BeginInvoke(method, args));
        }

        public object EndInvoke(System.IAsyncResult result)
        {
            DispatcherAsyncResult asyncResult = result as DispatcherAsyncResult;
            if (asyncResult != null)
            {
                System.Windows.Threading.DispatcherOperation dispatcherOperation = asyncResult.AsyncState as System.Windows.Threading.DispatcherOperation;
                if (dispatcherOperation != null)
                {
                    return dispatcherOperation.Wait();
                }
            }

            return null;
        }

        public object Invoke(System.Delegate method, object[] args)
        {
            return this.dispatcher.Invoke(method, args);
        }
    }

    internal class DispatcherAsyncResult : System.IAsyncResult
    {
        private System.Windows.Threading.DispatcherOperation dispatcherOperation;

        public DispatcherAsyncResult(System.Windows.Threading.DispatcherOperation dispatcherOperation)
        {
            this.dispatcherOperation = dispatcherOperation;
        }

        public object AsyncState
        {
            get { return this.dispatcherOperation; }
        }

        public System.Threading.WaitHandle AsyncWaitHandle
        {
            get { return null; }
        }

        public bool CompletedSynchronously
        {
            get { return false; }
        }

        public bool IsCompleted
        {
            get { return this.dispatcherOperation.Status == System.Windows.Threading.DispatcherOperationStatus.Completed; }
        }
    }

To use the class you simply have to pass an instance of the DispatcherSynchronizingObject to the dialer.SynchronizingObject property. That will marshal the events from the component correctly to the user interface to prevent any cross-thread exceptions that may occur.

RasDialer dialer = new RasDialer();
dialer.StateChanged += new EventHandler<StateChangedEventArgs>(this.dialer_StateChanged);
dialer.DialCompleted += new EventHandler<DialCompletedEventArgs>(this.dialer_DialCompleted);
dialer.EntryName = "My Entry";
dialer.PhoneBookPath = RasPhoneBook.GetPhoneBookPath(RasPhoneBookType.AllUsers);
dialer.SynchronizingObject = new DispatcherSynchronizingObject(this.Dispatcher);

dialer.DialAsync();

This should get your events working correctly in your application.

Edit: I wanted to add, the dispatcher passed into the synchronizing object is a property on the Window class. Also, I just did some further testing on this functionality in WPF and it seems if you do not pass a synchronizing object to the dialer and attempt to append text to your textbox as shown above, the thread will simply terminate. I only say terminate because when I attempted it, the event would raise again for the next state changed which normally would not happen until the previous call had completed.

Mar 22, 2010 at 1:24 PM

You dam genius. Thats done the trick. 

I did notice the only thing I was missing in ref to the dialer was the synchronizing Object, which error ed when trying the same one as in win forms.

But its all working great now, I will have no more trouble with the conversion - Thanks a lot, The most intuitive parts of this app were based around the Dialer competed events, for auto RDP etc.

 

BOOYA!

Coordinator
Mar 22, 2010 at 2:48 PM

Glad to hear it's working correctly, happy coding!

- Jeff