Last week I wrote a few different posts about converting an existing WinForms app into a mobile application. What I forgot to do was show what my example application looked like before it was converted. This blog will make up for that. The above image was taken from a screen capture of the program running on Windows Server 2008 R2. Note the blotched out areas. This was done to hide the personal data while still providing a real live example.
What you see is fairly standard for a WinForms program. There are a number of fields (usually TextBox controls) with support for a menu and title bar. This particular app is a bit simplistic and even a bit clunky, but it gets the job done.
Different users can be selected from clicking their names (Manager, Peers, and Direct Reports). Once selected, the information is loaded from Active Directory and displayed on the screen in the correct fields.
The icons are not really following the rules of a typical WinForms application. It was hard to ignore that this app would eventually be a mobile app in some cases. Instead of a standard button, pictures were used instead. These pictures more closely represent the action desired.
Internally, the program is using features that require .Net 3.5 Runtime. The main reason why is that Microsoft provided a better Active Directory collection of classes. In this group, the one that UserInfo uses is UserPrincipal. It is much more straightforward to use than the original DirectoryEntry class. The result is that UserInfo would require .Net 3.5 which is installed as part of Windows 7 and above.
Even though this below has been included before in a previous post, it is valuable to include it here for contrast for before and after.
Note that the information from the first picture is not missing. It is just out of scroll range. If the picture was taken either above or below, you would see the information that I had blocked out from the first one. Also note that I added some features to the mobile version. The two most obvious ones in this image are the date/time of the office location and the map. Both of these were derived from Google API and were not that difficult to implement. The map makes the app look a lot more interesting since it includes a scrollable image.
The goal was to make it possible to support both the old and the new from the same executable. This was largely achieved by breaking out the mobile code and using conditions with IsMobile().
Everything starts from Main in the Program class like any other program.
namespace MobileUserInfo { static class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new UserInfoForm()); } } }
Notice that our main form is called UserInfoForm. It uses the same form for both the mobile and non-mobile cases. The difference is that the mobile code is very dynamic about how the information is displayed. When the UserInfoForm is created, it automatically calls the constructor for the class.
// // UserInfoForm constructor // public UserInfoForm() { Init(); } /// <summary> /// Constructor related routine for UserInfoForm class /// </summary> private void Init() { // intialize the standard WinForms controls InitializeComponent(); // initialize mobile specific settings MobileInit(); // userCommon is used for getting user information userCommon = new UserCommon(userCredentials, Resources.PropertySchema); // Validate the users current credentials. If not in a domain, ask for username/password ValidateUserCredentials(); // create the worker thread for async calls workerThread = new ADWorkerThread(); workerThread.CreateWorkerThread(); UserLoaded += new EventHandler(UserInfoForm_UserLoaded); }
The most relevant line is the one that calls MobileInit(). The MobileInit() function is a file separate from the main code path. In MobileInit(), it will check to see if it should initialize things based on being a mobile device.
/// <summary> /// Called during constructor timeframe for UserInfoForm. /// Mobile related functions are executed here to simplify the /// model of the main program. /// </summary> void MobileInit() { try { MobileAPI = new MobileAPI(); if (IsMobile()) { // hook everything that we want to see MobileAPI.HookOrientationStateChanged(OrientationChanged); MobileAPI.HookViewportInfoChanged(ViewportChanged); MobileAPI.HookSessionConnected(SessionConnected); MobileAPI.HookSessionDisconnected(SessionDisconnected); MobileAPI.HookTouchInputModeChanged(TouchInputModeChanged); MobileAPI.HookDisplayStateChanged(DisplayStateChanged); // enable the mouse raw feedback MobileAPI.SetTouchInputMode(TouchInputMode.RawMouseInput); // Gather together the various settings AcquireMobileSettings(); if (ClientState != null) { Connected = (ClientState.ConnectionState == ConnectionState.Connected); } CityStatePostCode = CreateTextBox("CityStatePostCode", ""); UserPanel = CreatePanel("UserPanel", this, Size); BorderLine = CreatePanel(RowNames.Border, null, null); BorderLine2 = CreatePanel(RowNames.Border2, null, null); BorderLine3 = CreatePanel(RowNames.Border3, null, null); BorderLine4 = CreatePanel(RowNames.Border4, null, null); PhonePanel = CreatePanel("PhonePanel", null, null); AssociatesPanel = CreatePanel("AssociatesPanel", null, null); AddressPanel = CreatePanel("AddressPanel", null, null); StatusPanel = CreatePanel("StatusPanel", null, null); UserDataPanel = CreatePanel("UserDataPanel", null, null); UserScrollPanel = CreatePanel("UserScrollPanel", UserDataPanel, UserDataPanel.Size); MouseMoveHandler = new MobileMouseHandler(); Application.AddMessageFilter(MouseMoveHandler); adTimezone = new ADTimeZone(adLocation); adMap = new ADMap(adLocation); } } catch (Exception ex) { Trace.WriteLine(ex.Message); } }
Since the code is longer, the code snippet has been collapsed by default. Click to expand the code. There is more information than what is needed but it does prove that MobileInit() is checking for IsMobile() before initializing everything. The MobileAPI is a wrapper class for the Mobile SDK for Windows Apps API. If the MobileAPI is created and connected to the device, it will allow IsMobile to work and the initialization code will run. It is also worth mentioning that the MobileInit() routine hooks six different events from the SDK. These events are important for making sure that the application is responsive to changes on the mobile device and the session. It is also interesting that MobileInit() is creating a number of Panels. These elements are unique to the mobile version of the UI and will be better explained later on.
The same model is used for when the form is first loaded. The main code runs and it gives the mobile version of code a chance to process the event.
/// <summary> /// Any kind of Form initialization happens here /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void UserInfoForm_Load(object sender, EventArgs e) { if (Authenticated == false) { // do not go any further if not authenticated ExitApp(); } else { try { String imgFile = Properties.Settings.Default.BackgroundImage; if (imgFile.Length > 0) { Image image = Image.FromFile(imgFile); BackgroundImage = image; } this.Icon = Resources.usericon; // adjust the size to fit the device if (IsMobile()) { AdjustFormSize(); } // seed the UI with the current user information ShowFirstUserDetails(); } catch (Exception ex) { ErrorDialog.Show(ex); ExitApp(); } } } /// <summary> /// Adjust the form to fit the mobile device /// Called from main form load event /// </summary> private void AdjustFormSize() { if (IsMobile()) { // guarantee that we have all the mobile settings we need AcquireMobileSettings(); // change the background color this.BackColor = Color.WhiteSmoke; // hide the menu menuStrip1.Visible = false; // hide the resize border and title bar // do this before our resizing since getting rid of border and caption will cause the window to resize down FormBorderStyle = FormBorderStyle.None; //Clear out our controls from the current group control UserInfoGroup.Controls.Clear(); UserInfoGroup.Visible = false; // Adjust the size of the form and panel based on mobile settings PlaceForm(); MouseMoveHandler.MouseMove += Form_MouseMove; } }
AdjustFormSize() is the function called upon when the form is first loaded. It only runs this function if it is a mobile device. A previous post has disclosed how to remove the title bar and get rid of the controls but they are shown here again in the above collapsed code section. PlaceForm is responsible for making sure the form fits the screen based on the API data.
This brings us a bit closer to understanding the basics of how this works. This blog has covered:
- Before and After shot
- Code path for mobile/non-mobile initialization
- Some real code snips from UserInfo
The main goal was to illustrate what the UI looks like for non-mobile and mobile. The next blog will focus more on the issues brought up last week for converting the UI.