whitehouserenovation

This is a repost from Citrixblogger.org.

Everything needs to be updated eventually.  What I hope to share is how to update a WinForms application to support mobile devices. This post will cover the basics of the things to be concerned about.

  • Size and position of forms and controls
  • Orientation
  • Fonts
  • Buttons
  • Non-mobile friendly controls

Size and Position

The first concept to understand that unlike standard desktops, mobile devices come in many different sizes.  And, the size can change at any time based on changing the orientation or displaying the on-screen keyboard.  Assuming a fixed size and layout is a bad thing to do.  The application needs to adjust based on the display changes.  Not only does the Form need to be re-sized, but the content needs a new layout.  This step can be a bit tricky for a standard Windows Forms application.

Landscape

Portrait

In general, this occurs during application start up and during orientation changed events. The application needs to gather the information and then apply that information against the container (form) and content (controls).  It also needs to make sure that it gets updates when they happen (events).

Since we are using WinForms, we are using the CMP.NET component from the SDK to access the information and hook the events.  CMP.NET is contained in the assembly Citrix.CMP.dll and is located in the same directory as the application.  It is important to remember that some files of the Mobile SDK for Windows Apps are allowed to be distributed with the applications that use them.  The namespace is Citrix.CMP and the documentation is available online at Citrix.CMP docs site.

The typical location for the Citrix.CMP.dll assembly is:

C:\Program Files (x86)\Citrix\MobilitySDK\bin\AnyCPU

In order to use it with your WinForms program, you need to reference it from the Visual Studio project. You also should include a using reference to simplify the naming.


using Citrix.Cmp;

In order for the interface to be used, it needs to be initialized.  Overall, this just means creating a new object.


///
/// CMP.NET object for Citrix Mobility Pack
///
CmpApi cmpApi = null;

// create the CMP.NET object
cmpApi = new CmpApi();

To test if CMP.NET is actually available, you need to call IsCmpAvailable from the SessionManager. The CmpApi object can be created when there is no connection to Citrix Receiver. This can happen either when the session is disconnected or the Receiver does not support the mobile extensions.


///
/// Internal flag to reveal if SDK is available
///bool cmpAvail = false;

// confirm that the Citrix Mobility Pack is active
if (cmpApi.SessionManager.IsCmpAvailable == true)
{
    cmpAvail = true;
}
else
{
    cmpAvail = false;
}

This can be wrapped in a simple function


///
/// Determines if we have a mobile device available
///
/// bool - true(mobile) false(non-mobile)
public bool IsMobile()
{
    return (cmpAvail);
}

This IsMobile() function can be widely used to determine application behavior. For example, the layout can be determined based on this result. It can also be used to safely ignore mobile-related requests for functions trying to use the SDK.


///
/// Get the current orientation state
///
/// OrientationState
public OrientationState GetOrientationState()
{
    OrientationState state = null;

    if (IsMobile())
    {
         state = cmpApi.Display.GetOrientationState();
    }

    return (state);
}

You only need to get one instance of CmpApi for an application. Also, you should only create the object on the main UI thread to guarantee event notification. If you do not do this, there is a good chance that you will not receive events you want. In the Mobile User Info program example, I created a class just to manage the interfaces with the SDK. I did this for a few reasons but the main reason was to better control what was used from the interface. Instead of changing many different places related to the SDK, having one main class makes it easier to manage and standardize. It is not a requirement to do this but it can make things more friendly.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Citrix.Cmp;
using System.Diagnostics;

namespace MobileUserInfo
{
    /// <summary>
    /// Wrapper class for mobility calls.  The intent is to reduce what is available to what is used
    /// and also simplify to a small degree.
    /// </summary>
    public class MobileAPI
    {
        /// <summary>
        /// CMP.NET object for Citrix Mobility Pack
        /// </summary>
        private CmpApi cmpApi = null;

        /// <summary>
        /// Internal flag to reveal if SDK is available
        /// </summary>
        private bool cmpAvail = false;

        /// <summary>
        /// Constructor for MobileAPI object
        /// </summary>
        public MobileAPI()
        {
            // create the CMP.NET object
            cmpApi = new CmpApi();

            // confirm that the Citrix Mobility Pack is active
            if (cmpApi.SessionManager.IsCmpAvailable == true)
            {
                cmpAvail = true;
            }
            else
            {
                cmpAvail = false;
            }
        }

        /// <summary>
        /// Determines if we have a mobile device available
        /// </summary>
        /// bool - true(mobile) false(non-mobile)
        public bool IsMobile()
        {
            return (cmpAvail);
        }

This is only a small part of what the class contains. To see more, you will have to wait for the sample to be available.

Returning to the topic of the form size and position, let’s use some SDK APIs to get what we want. There are a couple of concepts to explain. Their are two main ways to determine the size of the mobile screen. The first is GetDisplayState. The second technique is to use GetViewportInfo. The difference between the two is that GetViewportInfo is more descriptive of the area that the application can use. GetDisplayState just returns the entire screen size. The viewport information contains: ServerViewport, ClientViewport, and ZoomFactor.

Viewport information
Viewport item Type Description
Client viewport Rectangle Area available on mobile device
Server viewport Rectangle Server area currently displayed
Zoom factor Integer Current zoom factor (default=100 no zoom)

The field that we want is Client viewport. With this information, we can set the size of the form. The client viewport origin is always (0,0). The width and height vary based on device size, orientation, and keyboard popup. To gather the current viewport information, call GetViewport.


/// <summary>
/// Get the current viewport settings
/// </summary>
/// ViewportInfo
public ViewportInfo GetViewport()
{
    ViewportInfo state = null;

    if (IsMobile())
    {
        state = cmpApi.Display.GetViewport();
    }

    return (state);
}

To apply this to the form, simply change the Form’s size.


/// <summary>
/// Place the form at the origin and correct size for mobile device
/// </summary>
private void PlaceForm()
{
    ViewportInfo Viewport = MobileAPI.GetViewport();

    if ((Viewport != null) &amp;&amp; (Viewport.ClientViewport.HasValue))
    {
        Size = new Size((int)Viewport.ClientViewport.Value.Width, (int)Viewport.ClientViewport.Value.Height);
    }

    // make sure the form is at 0,0 on screen
    Location = new Point(0, 0);
}

This is the approach to use when the form is created but we also need to know when the client viewport changes. There is an event for viewport changes that can be hooked. It is called ViewportInfoChanged. The event provides the new viewport information in the arguments.


/// <summary>
/// Hook into the ViewportInfoChanged
/// </summary>
/// <param name="viewportEventHandler">ViewportInfoChanged Event handler</param>
public void HookViewportInfoChanged(EventHandler viewportEventHandler)
{
    if (IsMobile())
    {
        cmpApi.Display.ViewportInfoChanged += viewportEventHandler;
    }
}

Form init
...
    MobileAPI.HookViewportInfoChanged(ViewportChanged);
...

//
// whenever the viewport changes, we need to re-adjust our form
//
private void ViewportChanged(object sender, ViewportInfoChangedArgs args)
{
    // if it has changed, then we need to re-layout our fields
    Viewport = args.NewState;

    ReflowForm();
}

If the viewport changes, we will know and the form will be resized

When it comes to the controls, something new needs to happen. Either the controls need to be moved and resized or we need to create a new way of displaying content. Originally when I was investigating the best way to handle this, it seemed like using the real control was the right direction. Over time, this changed to preferring techniques of refactoring. The primary reason why is that by refactoring the information, it gave a better result. The end result is a collection of code that can handle either approach.

Given that I have been working on this post on and off for the last three days, I am going to continue in the next post for the more detailed information.

Did you notice the remodeling of the White House? It is the picture at the top and it links to the history of this serious renovation between 1948 and 1952. A few of the pictures reveal that they gutted the White House. I promise that you will not have to gut your WinForms application to play nicely on mobile devices. The idea here is to make things as easy as possible.

In this post, we have covered

  • Where to get CMP.NET
  • How to include CMP.NET
  • How to get an CMP object
  • Viewport is explained
  • Viewport event hooking
  • Form resizing
  • Concept of mobile and non-mobile code

Not as far as I planned for this post but a great start. The next post should arrive in a couple of days.