Using Unitybootstrapper in WinForms

Topics: Prism v4 - WPF 4
Feb 15, 2011 at 11:11 PM

Hi,

I have been struggling for quite abit now trying to work a unitybootstrapper into our current winform application. There is no getting around the winform and we are stuck with it for now. My discussion here might get abit lengthy, but please hear me out and I would really appreciate any help.

So, the winform(main application window) has a list of logical modules(loaded from a config file) and each module comes with a winform type usercontrol that the developer can attach views to represent the logical modules. We currently have over 50 logical module views built with winform controls. We have slowly been adopting and implementing wpf controls into our winform type control by mounting the wpf control types into an elementhost that gets attached to the winform usercontrol that represents the view of a logical module. In each logical module, there are numerous loosely coupled components that gets loaded from a config file and each component may or may not have a view attached with it.

As you can see these begs for modularity and extensibility that PRISM provides with its framework. My design is to have one UnityBootStrapper per logical module(Is it even possible to have multiple UBS in one app?). The UnityBootStrapper will create a shell(wpf usercontrol type) that will be mounted on to the elementhost(contained in a winform usercontrol) for that particular logical module. Then, the UnityBootStrapper will discover these loosely coupled components from a xaml config file. I intend these loosely coupled components to implement IModule. Each IModule will be instantiated and injected with a UnityContainer and a RegionManager. Depending on the IModule type, each IModule is responsible for creating its own view if necessary and injecting a view model that gets binded with the view in the Initialize function of the IModule. Using the RegionManager that was injected into the IModule, I will add the IModule views to the shell. The shells that get mounted to elementhost are wpf usercontrol types with an ItemsControl that has the RegionManager.RegionName as an attached property. 

I went about going through the PRISM tutorial and I started a new WPF Application project to test my design. Everything worked as plan, it was beautiful, it was really beautiful...until when I started integrating the same design into my winform application that runs on System.Windows.Forms.Application, instead of the wpf application version, which is the System.Windows.Application. The biggest slap in the face was the RegionManager created from the shell as region names specified in the ItemControls no longer gets added to the region list in the RegionManager. This is pretty much a brickwall for me and I desperately need help here.

I guess I am up for any suggestions,solutions and even ideas, and my design might flawed from the beginning, so please do let me know if this is even feasible.

 

Thanks for reading, its lengthy i know haha. 

Feb 16, 2011 at 3:31 AM

I dug deeper into the PRISM lib and it was caused by IsInDesignMode returning true, which blocks the region creation code. 

        private static bool IsInDesignMode(DependencyObject element)
        {
            // Due to a known issue in Cider, GetIsInDesignMode attached property value is not enough to know if it's in design mode.
            return DesignerProperties.GetIsInDesignMode(element) || Application.Current == null
                   || Application.Current.GetType() == typeof(Application);
        }

I changed it to check for only GetIsInDesignMode(element) because Application.Current is null due to the fact that I am running a windows form application. Life is awesome and beautiful again. Just curious, what was the reasoning behind this? Sorry for my ignorance, what is Cider btw? I did some search on it but I couldnt find any relevant topic on it.

 

Thanks. 

Feb 16, 2011 at 1:06 PM
Edited Feb 16, 2011 at 1:08 PM

Cider's just a codename for the wpf designer in VS.

Hmm apparently, I keep answering myself in this thread. Its true, there is an issue in Cider when I removed the Application checks in the IsInDesignMode. I get "The attachable property 'RegionName' was not found in type 'RegionManager'" error when attaching a region to my ContentControl.

 

 

 

WPF Designer
The WPF designer, codenamed Cider,[26] was introduced with Visual Studio 2008. Like the Windows Forms designer it supports the drag and drop metaphor. It is used to author user interfaces targeting Windows Presentation Foundation. It supports all WPF functionality including data binding and automatic layout management. It generates XAML code for the UI. The generated XAML file is compatible with Microsoft Expression Design, the designer-oriented product. The XAML code is linked with code using a code-behind model.

 

Developer
Feb 16, 2011 at 1:40 PM

Hi,

I'm glad that you've found a solution to your issue with the WPF Designer. Thank you for sharing your findings with the rest of the community, as other users might benefit from this.

Guido Leandro Maliandi
http://blogs.southworks.net/gmaliandi

Oct 1, 2011 at 12:04 PM

i too thank u for ur info sharing. :)

Aug 6, 2013 at 6:52 PM
Edited Aug 6, 2013 at 6:59 PM
I too have the same issue.

Our company are developing new modules. Each of those modules are developed in WPF and have a standalone version.

Although those modules can also be called inside a VB6 and/or Windows Forms interface.

So we have created wrappers for the VB6 part with host the module in a Windows Forms control.

We made sure that the bootstrapper gets called in order for PRISM to initialize correctly.

The WPF interface loads correctly but the no interface are displayed in the regions probably due to the same issue described below.

I don't feel comfortable to modify the IsInDesignMode property since i beleive that it must have good reasoning to have implemented it this way. Also i see that Application.Current is used elsewhere also...

Would it be possible to address this issue in the next release or is it impossible to do?

Edit: In other word does having Application.Current == null (due to the fact that the we're in Windows forms) a show stopper for using PRISM?

Thanks.

Slick
Aug 9, 2013 at 5:55 PM
Hi,

Based on our understanding, the IsInDesignMode method checks for the current application due to a known issue in Cider with Silverlight 3, where the DesignerProperties.GetIsInDesignMode method doesn’t work correctly. You can find more information and possible workarounds about this in the following work item:

Regarding if this is a show stopper for using Prism, no it isn't.

Regards,

Federico Martinez
http://blogs.southworks.net/fmartinez
Aug 13, 2013 at 6:16 PM
Hi fmartinez, thanks for your reply.

In order to "improve" prism interop when a WPF application/form is called inside Windows.Forms or VB6, our company did the following modifications that we want to share in case it is found useful for future release, individual or if someone sees a big potential problem with the change.

Change no. 1 - Added an "InteropHelper.cs" class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Microsoft.Practices.Prism
{
   /// <summary>
   /// This class contains some usefull methods/properties used to interact with windows.forms applications
   /// </summary>
 public static class InteropHelper
    {

        /// <summary>
        /// Returns if the current application is a windows.forms application.
        /// </summary>
        /// <returns>Returns if the current application is a windows.forms application.</returns>
        public static bool IsWindowsFormsApplication()
        {
            return System.Windows.Application.Current == null
                && !string.IsNullOrWhiteSpace(System.Windows.Forms.Application.ExecutablePath)
                && System.Windows.Forms.Application.OpenForms.Count > 0;
        }

      private static object _fallBackDispatcherObject;
     /// <summary>
     /// Use to set the dispatcher to be used by prism if no WPF application is running.
     /// </summary>
     /// <remarks>
     /// Can be set to one of the following type:
      /// -System.Windows.Window
      /// -System.Windows.Controls.UserControl
      /// -System.Windows.Threading.Dispatcher
      /// -System.Windows.Forms.Form
      /// -System.Windows.Forms.Control
     /// </remarks>
      public static object FallBackDispatcherObject
      {
          get {

              return _fallBackDispatcherObject;
          }
          set {
              if (value is System.Windows.Window)
              {
                  _fallBackDispatcherObject = ((System.Windows.Window)value).Dispatcher;
              }
              else if (value is System.Windows.Controls.UserControl)
              {
                  _fallBackDispatcherObject = ((System.Windows.Controls.UserControl)value).Dispatcher;
              }
              else if (value is System.Windows.Threading.Dispatcher)
              {
                  _fallBackDispatcherObject = value;
              }
              else if (value is System.Windows.Forms.Form)
              {
                  _fallBackDispatcherObject = ((System.Windows.Forms.Form)value);
              }
              else if (value is System.Windows.Forms.Control)
              {
                  _fallBackDispatcherObject = ((System.Windows.Forms.Control)value);
              }
              else
              {
                  _fallBackDispatcherObject = null;
              }

          }
      }
        
    }
}

Change no. 2 - Modified RegionManager.cs IsInDesignMode property as follow:

 private static bool IsInDesignMode(DependencyObject element)
        {
            //Note: this was modified in order to allow the regions to be loaded when prism 
            //is instantiated inside a windows.Form application

            if (Application.Current != null)
            {
                // Due to a known issue in Cider, GetIsInDesignMode attached property value is not enough to know if it's in design mode.
                return DesignerProperties.GetIsInDesignMode(element) || Application.Current.GetType() == typeof(Application);

            }
            else if (InteropHelper.IsWindowsFormsApplication())
            {
                return false;
            }
            else
                return DesignerProperties.GetIsInDesignMode(element);
            //else if (WindowsFormHelper.FallBackDispatcherObject !=null)
            //{
            //    return false;
            //}
            //else
            //{
            //    // Due to a known issue in Cider, GetIsInDesignMode attached property value is not enough to know if it's in design mode.
            //    return DesignerProperties.GetIsInDesignMode(element)
            //        || Application.Current == null
            //        || Application.Current.GetType() == typeof(Application);
            //}

         
        }

Change no. 3 - Modified BeginInvoke method in DefaultDispatcher.Desktop.cs as follow:

 /// <summary>
        /// Forwards the BeginInvoke to the current application's <see cref="Dispatcher"/>.
        /// </summary>
        /// <param name="method">Method to be invoked.</param>
        /// <param name="arg">Arguments to pass to the invoked method.</param>
        public void BeginInvoke(Delegate method, object arg)
        {
            
            if (Application.Current != null)
            {
                Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, method, arg);
            }
            else if (InteropHelper.IsWindowsFormsApplication())
            {
                System.Windows.Forms.Application.OpenForms[0].BeginInvoke(method, arg);
            }
            else if (InteropHelper.FallBackDispatcherObject is Dispatcher)
            {
                ((Dispatcher)InteropHelper.FallBackDispatcherObject).BeginInvoke(DispatcherPriority.Normal, method, arg);

            }
            else if (InteropHelper.FallBackDispatcherObject is System.Windows.Forms.Form)
            {
                ((System.Windows.Forms.Form)InteropHelper.FallBackDispatcherObject).BeginInvoke(method, arg);

            }
            else if (InteropHelper.FallBackDispatcherObject is System.Windows.Forms.Control)
            {
                ((System.Windows.Forms.Control)InteropHelper.FallBackDispatcherObject).BeginInvoke(method, arg);

            }
        }

Change no. 4 - Changed the DispatcherProxy class in WeakEventHandlerManager.Desktop.cs as follow:

  /// <summary>
        /// Hides the dispatcher mis-match between Silverlight and .Net, largely so code reads a bit easier
        /// </summary>
        private class DispatcherProxy
        {
            Dispatcher innerDispatcher;

            private DispatcherProxy(Dispatcher dispatcher)
            {
                if (dispatcher != null)
                {
                    innerDispatcher = dispatcher;
                }
                else if (InteropHelper.FallBackDispatcherObject !=null) //(WindowsFormHelper.IsWindowsFormsApplication())
                {
                    if (InteropHelper.FallBackDispatcherObject is System.Windows.Threading.Dispatcher)
                    {
                        innerDispatcher = (System.Windows.Threading.Dispatcher)InteropHelper.FallBackDispatcherObject;
                    }                 
                    else
                        innerDispatcher = null;
                }

            }

            public static DispatcherProxy CreateDispatcher()
            {
                DispatcherProxy proxy = null;
#if SILVERLIGHT
                if (Deployment.Current == null)
                    return null;

                proxy = new DispatcherProxy(Deployment.Current.Dispatcher);
#else
                if (InteropHelper.FallBackDispatcherObject != null)
                    return new DispatcherProxy(null);
                else if (InteropHelper.IsWindowsFormsApplication())
                    return new DispatcherProxy(null);
                else if (Application.Current == null)
                    return null;

                proxy = new DispatcherProxy(Application.Current.Dispatcher);
#endif
                return proxy;

            }

            public bool CheckAccess()
            {
                if (innerDispatcher != null)
                {
                    return innerDispatcher.CheckAccess();
                }
                else if (InteropHelper.IsWindowsFormsApplication())
                {
                    return !System.Windows.Forms.Application.OpenForms[0].InvokeRequired;
                }
                else if (InteropHelper.FallBackDispatcherObject is System.Windows.Forms.Form)
                {

                    return !((System.Windows.Forms.Form)InteropHelper.FallBackDispatcherObject).InvokeRequired;                    
                }
                else if (InteropHelper.FallBackDispatcherObject is System.Windows.Forms.Control)
                {

                    return !((System.Windows.Forms.Control)InteropHelper.FallBackDispatcherObject).InvokeRequired;
                }
                return true;
            }

          
            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Portability", "CA1903:UseOnlyApiFromTargetedFramework", MessageId = "System.Windows.Threading.Dispatcher.#BeginInvoke(System.Delegate,System.Windows.Threading.DispatcherPriority,System.Object[])")]
            public DispatcherOperation BeginInvoke(Delegate method, params Object[] args)
            {
#if SILVERLIGHT
                return innerDispatcher.BeginInvoke(method, args);
#else
                if (innerDispatcher != null)
                {
                    return innerDispatcher.BeginInvoke(method, DispatcherPriority.Normal, args);
                }
                else if (InteropHelper.IsWindowsFormsApplication())
                {
                    System.Windows.Forms.Application.OpenForms[0].BeginInvoke(method, args);
                }
                else if (InteropHelper.FallBackDispatcherObject is System.Windows.Forms.Form)
                {
                    ((System.Windows.Forms.Form)InteropHelper.FallBackDispatcherObject).BeginInvoke(method, args);
                                     
                }
                else if (InteropHelper.FallBackDispatcherObject is System.Windows.Forms.Control)
                {
                    ((System.Windows.Forms.Control)InteropHelper.FallBackDispatcherObject).BeginInvoke(method, args);

                }

                return null;
#endif
            }
        }
Aug 13, 2013 at 6:49 PM
Hi,

Thanks for sharing your solution with the community. It would be helpful if you could also post it as a work item so that it could be considered as a suggestion for future releases.

Regards,

Federico Martinez
http://blogs.southworks.net/fmartinez