Helpful Tips

[italic]Thinking Backwards[/italic]

Under normal circumstances when creating a child we want to create the instance, set any properties and then add it to the parent. When working across AppDomains, however, you’ll find that this isn’t always possible.

For example if your child needs to access the DrawingContext from the default domain, you’d be out of luck. Unless you’re going to dive deep into NETMF you can’t really make the DC marshal-friendly. So what to do?

The solution is to have the default domain create the child and pass it over to the new AppDomain. This way the when the marshal-friendly child’s render command is called it is done so with the DrawingContext from the default domain; so neatly avoiding harsh Marshal errors.

The child will still have to be disposed of the from the default domain, but when it’s a native GUI element that’s just fine and is easily made part of the AppDomain clean up routine. If you’re wondering where the benefit is; while the control exists in the default domain (it’s assembly already did) what you’re really saving is loading the [italic]rest[/italic] of the assembly from the called application.

Here’s a small all-in-one example of how to add render enabled objects across AppDomains. It’s dead small so don’t expect to see lovely cleanup code or fancy loading of extra assemblies. Just a quick-and-dirty new AppDomain render. Debug output will show it running from it’s new AppDomain.

This should be a good starting point for anyone looking to do something similar.

using System;
using System.Collections;
using System.Reflection;

using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Touch;

namespace ApplicationShell
{
    public class ApplicationShell : Application
    {

        private static ApplicationShell _application;
        private static ASWindow _desktop;
        private static Controller _controller;

        public static void Main()
        {
            // Instance of the Application
            _application = new ApplicationShell();

            // Instance of the Controller
            _controller = new Controller();

            // Instance of the Desktop
            _desktop = new ASWindow(_controller);
            _controller.setDesktop(_desktop);
            initializeTouch();

            // Run Application
            _application.Run(_desktop);
        }

        private static void initializeTouch()
        {
            Touch.Initialize(_application);
            TouchCollectorConfiguration.CollectionMethod = CollectionMethod.Native;
            TouchCollectorConfiguration.CollectionMode = CollectionMode.GestureOnly;
        }


    }

    interface IController
    {

        form addForm();

    }

    [Serializable]
    public class Controller : MarshalByRefObject, IController
    {

        private ASWindow _desktop;

        public void setDesktop(ASWindow desktop)
        {
            _desktop = desktop;
        }

        public form addForm()
        {
            form frm = new form();
            _desktop.addForm(frm);
            return frm;
        }

    }

    public class ASWindow : Window
    {

        private ArrayList _forms = new ArrayList();
        private DynamicLoading _dl = new DynamicLoading();
        private Controller _controller;

        public ASWindow(Controller controller)
        {
            _controller = controller;
        }

        public void addForm(form form)
        {
            _forms.Add(form);
            Invalidate();
        }

        public override void OnRender(Microsoft.SPOT.Presentation.Media.DrawingContext dc)
        {

            dc.DrawRectangle(new SolidColorBrush(Color.Black), null, 0, 0, 320, 240);
            for (int i = 0; i < _forms.Count; i++)
            {
                IForm frm = (IForm)_forms[i];
                frm.OnRender(dc);
            }
        }

        protected override void OnTouchDown(Microsoft.SPOT.Input.TouchEventArgs e)
        {
            _dl.AddForm(_controller, Colors.Red, 10, 10, 200, 98);
            base.OnTouchDown(e);
        }

        protected override void OnTouchUp(Microsoft.SPOT.Input.TouchEventArgs e)
        {
            base.OnTouchUp(e);
        }

    }


    interface IForm
    {
        Color background
        {
            get;
            set;
        }

        int x
        {
            get;
            set;
        }

        int y
        {
            get;
            set;
        }

        int width
        {
            get;
            set;
        }

        int height
        {
            get;
            set;
        }

        void OnRender(DrawingContext dc);
    }

    [Serializable]
    public class form : MarshalByRefObject, IForm
    {

        private Color _bkg;
        private int _x;
        private int _y;
        private int _w;
        private int _h;

        public Color background
        {
            get { return _bkg; }
            set { _bkg = value; }
        }

        public int x
        {
            get { return _x; }
            set { _x = value; }
        }

        public int y
        {
            get { return _y; }
            set { _y = value; }
        }

        public int width
        {
            get { return _w; }
            set { _w = value; }
        }

        public int height
        {
            get { return _h; }
            set { _h = value; }
        }

        public void OnRender(DrawingContext dc)
        {
            Brush bshBkg = new SolidColorBrush(_bkg);
            dc.DrawRectangle(bshBkg, null, _x, _y, _w, _h);
        }

    }

    public class DynamicLoading
    {
        /// <summary>
        /// The interface for a standlone application
        /// </summary>
        interface IApplication
        {
            /// <summary>
            /// A sample method to invoke
            /// </summary>
            /// <returns></returns>
            void AddTestForm(Controller controller, Color color, int X, int Y, int Width, int Height);
        }

        /// <summary>
        /// A class that can be loaded across applicaiton domains which
        /// implements the IApplicaiton interface
        /// </summary>
        public class Application : MarshalByRefObject, IApplication
        {
            /// <summary>
            /// we need a defautl consturctor to create an instance of this object
            /// across an application domain
            /// </summary>
            public Application()
            {
            }


            void IApplication.AddTestForm(Controller controller, Color color, int X, int Y, int Width, int Height)
            {
                Debug.Print("Current Domain: " + AppDomain.CurrentDomain.FriendlyName);
                form frm = controller.addForm();
                frm.background = color;
                frm.x = X;
                frm.y = Y;
                frm.width = Width;
                frm.height = Height;
            }

        }

        private ArrayList _domains = new ArrayList();
        private ArrayList _apps = new ArrayList();

        /// <summary>
        /// The executable entry point.
        /// </summary>
        public void AddForm(Controller controller, Color color, int X, int Y, int Width, int Height)
        {

            // create a new application domain
            Debug.Print("Creating AppDomain");
            AppDomain ad = AppDomain.CreateDomain("pyxis2.apps");
            _domains.Add(ad);

            // create an instance of the IApplication type
            // please note that the following call will also load the assembly that contains that type
            // which we pass as first parameter 
            Debug.Print("Creating App Launcher Instance...");
            IApplication app;
            try
            {
                app = (IApplication)ad.CreateInstanceAndUnwrap(typeof(IApplication).Assembly.FullName, typeof(Application).FullName);
            }
            catch (Exception e)
            {
                string strInner = e.InnerException.ToString();
                string strStack = e.StackTrace.ToString();
                Debug.Print("FAILED: " + e.Message + "\n" + strInner + strStack);
                return;
            }

            _apps.Add(app);
            Debug.Print("Launching App..");
            try
            {
                //app.StartExecution(controller, path);
                app.AddTestForm(controller, color, X, Y, Width, Height);
            }
            catch (Exception e)
            {
                string strInner = e.InnerException.ToString();
                string strStack = e.StackTrace.ToString();
                Debug.Print("FAILED: " + e.Message + "\n" + strInner + strStack);
                return;
            }
        }

    }
}

Touch Collector bringing you down? Are you noticing when you load new AppDomains or create multiple “empty” Windows the events disconnect no matter how many times you call [object].[touchWhatever] = new TouchEventHandler?

You’ll notice this happening in the previous example I posted. So what’s going on? Is the touch collector just giving up? No. You can manually check the collector and see it’s getting data; I’m not sure why it stops passing at the moment but there is a way around it.

Be your own touch collector! By setting up a DispatchTimer (recommend 5ms interval) and checking TouchCollectorConfiguration.GetLastTouchPoint(ref x, ref y); you can call your own touch methods. An X or Y > SystemMetrics.ScreenWidth/Height means the pen is currently UP.

        void PenDetectionTimer_Tick(object sender, EventArgs e)
        {
            int x = 0;
            int y = 0;
            TouchCollectorConfiguration.GetLastTouchPoint(ref x, ref y);

            if (x < 320 || y < 240)
            {
                // Pen down
                if (_bDown)
                {
                    // We already know about it
                    return;
                }
                else
                {
                    _bDown = true;
                    this.TouchDown(x, y);
                }
            }
            else
            {
                if (_bDown)
                {
                    _bDown = false;
                    this.TouchUp(x, y);
                }
            }           
        }

When having a rosé wine you can pour it in either a red or white wine glass…but if you’re used to putting it in a red glass and put it in a white you’ll pour entirely too much and happily post to this thread. Happy Monday :wink: