Kean Walmsley


  • About the Author
    Kean on Google+

July 2014

Sun Mon Tue Wed Thu Fri Sat
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    








« AU 2012 Handout: Developing a simple Metro-style application for Windows 8 – Part 1 | Main | AU 2012 Handout: Moving code to the cloud – it’s easier than you think – Part 1 »

November 19, 2012

AU 2012 Handout: Developing a simple Metro-style application for Windows 8 – Part 2

This post contains the second part of the handout for my Windows 8-related session at AU 2012 (here’s the first part). Here’s the accompanying source project for the MetroCAD application, which has now been upgraded to work on the shipping version of Windows 8.

What can be done with AutoCAD?

Something that’s being promoted by Microsoft to developers of “heavyweight” desktop apps is the idea of a companion app. These are essentially Windows Store apps that complement desktop apps such as AutoCAD.

We’re going to see one such companion app in this session: an app called MetroCAD that is basically a browser for MRU (Most Recently Used) file information that gets created by AutoCAD on the current system.

This browser app will work as a companion to AutoCAD, showing file-level information – including drawing thumbnails – and even provide the capability to launch AutoCAD (or the application that has been most recently associated with the DWG file-type).

Browsing AutoCAD’s MRU data in WinRT

AutoCAD stores is MRU-related information in the Registry – which those of you who have been paying attention will remember cannot be accessed directly from WinRT – so in order for this app to work we will need a desktop-resident component that extracts this information and makes it available to WinRT apps with appropriate permissions.

This extraction component could work from within AutoCAD or be completely standalone, and ideally would be automated.

In our case, we’re actually going to implement something in-between that makes use of a new feature in AutoCAD 2013, the Core Console.

Extracting AutoCAD MRU data from the Registry

We need a simple app that can query the Registry and populate some data on our file-system. We’ll store an overall manifest of the browsed DWGs in an XML file and supplement that with a set of thumbnail images we extract from the various DWG files.

We’ll also copy the source DWG into a specific folder, as launching a DWG from an arbitrary location is going to be problematic (this is one downside of the launching capability that we’ll see in more detail later on).

Here’s the C# code for our app. The project containing is a standard C# Class Library with project references to System.Drawing, AcDbMgd.dll and AcCoreMgd.dll.

using Autodesk.AutoCAD.ApplicationServices.Core;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.EditorInput;

using Autodesk.AutoCAD.Runtime;

using Microsoft.Win32;

using System.Collections.Generic;

using System.Drawing.Imaging;

using System.Drawing;

using System.IO;

using System;

 

namespace MruExtraction

{

  public class Commands

  {

    string basePath =

      Environment.GetFolderPath(

        Environment.SpecialFolder.MyDocuments

      ) + "\\MetroCAD\\";

    const char stringSep = '|';

 

    private string[] GetMruFiles()

    {

      List<string> files = new List<string>();

      RegistryKey ack =

        Registry.CurrentUser.OpenSubKey(

          HostApplicationServices.Current.UserRegistryProductRootKey,

          true

        );

      using (ack)

      {

        RegistryKey mruk = ack.CreateSubKey("Recent File List");

        using (mruk)

        {

          for (int i = 1; i <= 35; i++)

          {

            string file =

              (string)mruk.GetValue("File" + i.ToString());

            if (string.IsNullOrEmpty(file))

              break;

            else

            {

              if (Path.GetExtension(file) == ".dwg")

              {

                string fileTime =

                  (string)mruk.GetValue("FileTime" + i.ToString());

 

                files.Add(file + stringSep + fileTime);

              }

            }

          }

        }

      }

      return files.ToArray();

    }

 

    public static DateTime UnixTimeStampToDateTime(long timeStamp)

    {

      // Unix timestamp is seconds past epoch

 

      DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, 0);

      return epoch.AddSeconds(timeStamp).ToLocalTime();

    }

 

    [CommandMethod("EMI")]

    public void ExtractMruInformation()

    {

      string[] files = GetMruFiles();

 

      int created = 0;

 

      StreamWriter sw = File.CreateText(basePath + "mru.xml");

      sw.WriteLine("<MruFiles>");

      using (sw)

      {

        for (int i = 0; i < files.Length; i++)

        {

          int sepLoc = files[i].IndexOf(stringSep);

          string file = files[i].Substring(0, sepLoc);

          string fileTime = files[i].Substring(sepLoc + 1);

 

          DateTime lastRead =

            UnixTimeStampToDateTime(long.Parse(fileTime));

 

          using (Database db = new Database(false, true))

          {

            db.ReadDwgFile(

              file, FileOpenMode.OpenForReadAndReadShare, false, ""

            );

 

            Bitmap thumb = db.ThumbnailBitmap;

            if (thumb != null)

            {

              // Generate a filename and save the image to it

 

              string imageFile = "MruImage" + i.ToString() + ".png";

              thumb.Save(basePath + imageFile, ImageFormat.Png);

 

              // Get the time the drawing was last modified

 

              DateTime lastMod = File.GetLastAccessTime(file);

 

              // Get the base file name

 

              string baseName =

                Path.GetFileNameWithoutExtension(file);

 

              // Write a line of XML for each file

 

              sw.WriteLine(

                "<MruFile Name=\"{0}\" " +

                "LastAccess=\"{1}\" " +

                "DayOfWeek=\"{2}\" " +

                "LastEdit=\"{3}\" " +

                "FilePath=\"{4}\" " +

                "ImageFile=\"{5}\" />",

                baseName,

                lastRead.ToShortDateString(),

                lastRead.DayOfWeek,

                lastMod.ToShortDateString(),

                file,

                imageFile

              );

 

              // Make a local copy of the file

 

              var outPath = basePath + baseName + ".dwg";

              if (!file.StartsWith("\\\\") && !File.Exists(outPath))

                File.Copy(file, outPath);

 

              // Increment our count to report at the end

 

              created++;

            }

          }

        }

        sw.WriteLine("</MruFiles>");

        sw.Close();

      }

      Application.DocumentManager.MdiActiveDocument.Editor.

        WriteMessage(

          "\nExtracted thumbnails for {0} drawing{1}" +

          " in the MRU list.",

          created, created == 1 ? "" : "s"

        );

    }

  }

}

This app can be loaded and run in full AutoCAD or the Core Console. Let’s see that inside the Core Console. We’ll use the NETLOAD command to load the DWG and then the EMI command to extract the various MRU data and place it under our Documents folder.

MRU Extractor in the Core ConsoleWe should now find the image files along with the XML “manifest” under My Documents\MetroCAD.

Creating the Basic Browser

Grid View project templateNow that we have the data we want to browse stored in a place that’s accessible to a WinRT, we can go ahead and create our basic browser.

Visual Studio 2012 provides some excellent foundational project templates for WinRT apps. The one that best fits our purposes is the Grid View template. We’re not going to go through the detailed process of adding all the functionality we need to this project, but we will be looking at areas of code that are implement various interesting capabilities.

If you really want to see all the differences in detail, you might create a basic Grid View app and then perform a “diff” with the files in the finished project.

The first “big ticket” item is to adjust the data-source for our items to reflect the data we want to store for each DWG. Then it’s clearly necessary to go ahead and adjust the XAML to bind to this data in various locations.

Now we’re going to take a look at various interesting features of Windows Store apps, to see how they can be implemented in our MRU browser app.

Semantic Zoom

Semantic zoom is an interesting capability for any app that’s display lots of data. It lets the user “zoom out” on the data, showing it at a lower level of detail. For instance, rather than having the items listed with larger icons and their title (which is already a level of detail less than the full item view) we can implement a view that just shows smaller thumbnails for all the DWGs accessed on a particular day.

Here’s the general, “zoomed in” view of our browser app:

Our zoomed-in group view

We can pinch-zoom to zoom out and get a different level of detail:

Our zoomed-out group view

Some work is needed from the developer to support this view: they basically need to add a SemanticZoom element with ZoomedInView and ZoomedOutView sub-elements for the respective views.

The user can choose to display this view by simply pinch-zooming the display (or pressing the Ctrl key and using the mouse-wheel if relying on mouse and keyboard).

Contracts

Charmed, I'm sureFor an application to hook into the core Windows 8 UI at a user-level – allowing the user to interact with the application via the standard Windows 8 Charms – it can implement various “contracts”. We’ll now take a look at code to implement contracts for Share, Search and Settings.

Implementing a contract typically means registering an event-handler for core WinRT events. These events get fired by WinRT and allow your application to provide the appropriate behavior for the specific scenario.
The application calls the static GetForCurrentView() method on the appropriate class and uses the returned object to register the event handler.

Contracts – Share

For instance, the contract for sharing – in the case of acting as a source for the sharing operation – requires handling of the DataTransferManager.DataRequested event. The application’s code then populates a DataTarget object with the data it wants to share.

You can share various types of data with other applications, but be warned: the standard Mail app – which is the one most commonly used to test out your application’s ability to act as a sharing source – doesn’t handle the title and subject information if attachments have been provided.

To more effectively test your app’s sharing capabilities, there are both source and target sample applications on MSDN that will prove more helpful:

http://code.msdn.microsoft.com/Sharing-Content-Source-App-d9bffd84
http://code.msdn.microsoft.com/Sharing-Content-Target-App-e2689782

Contracts – Search

To implement the search contract, your app needs to implement a handler for the SearchPane.QuerySubmitted event. When the event is called, your code needs to generate a group of items that meet the search criteria.

Generating the group is straightforward with LINQ:

var query =

  from item in Items

  where item.Content.Contains(queryText)

  select item;

One thing to note about our implementation: we actually clone the items into the new group, as this makes it simpler to avoid issues related to items having links to their containing group.

Once we have the group we can simply navigate to it, causing it to be displayed in the UI.

Contracts – Settings

For an application to have its settings displayed via the Settings charm, it needs to implement the settings contract. It does this by responding to SettingsPane.CommandsRequested, registering its commands here to be integrated with the charms UI.

When the specific command-handler executes, it updates the application’s settings. If stored via the Application.Current.RoamingSettings object the settings will follow the user from device to device via their Live ID. Once the settings has been either retrieved or stored, the application can then adjust the UI to reflect the user’s intention.

Live Tile Notifications

An application’s Start screen tile is not only the way the user launches an application, it’s the window a user has into any activity that’s related to the application. Static tiles are just that – they don’t display updated information – but “live” tiles are more interesting: they display dynamic information that’s relevant to the app.

For instance, the Mail app’s live tile displays recent email messages the user has received.

Application developers have different formats available to them. Here are examples of the wide format – the tile on the left is static and just displays the app’s logo, the one on the right is dynamic and shows information about one of the recently opened DWG files:

Wide, static tile

Wide, live tile

 

 

 

 

If the developer has chosen a wide format for their application’s tile, the user can still choose to shrink it to be square:

Square, static tile

Square, live tile

 

 

 

 

The specific notifications that get presented to the user are managed via Windows.UI.Notification.TileUpdateManager. Up to 5 notifications can be displayed to the user in a rotating queue.

Snapping

One interesting capability of Windows Store apps is that they can dock: something that’s especially useful considering their otherwise full-screen nature. And for companion apps it can prove to be even more important, as they can dock to the side of the desktop.

A snapped app has up to 320 pixels wide of screen real estate available to it. Within this area it can show the view it feels appropriate, whether a single item or a group of them.

The implementation of snapping occurs in XAML (for a C#/VB/C++ app) or HTML (for a JavaScript app). There’s a particular VisualStyle with the “Snapped” name, and this controls the animation of the UI to the snapped state.

Launching

One feature that’s of great interest to companion apps is launching. Applications can launch the desktop application associated with a particular file-type via a file that the app can access. Which means it needs to be in a known folder the app can access and using a file-type that the app has an association with.

Here’s some code that will launch AutoCAD for a DWG file found in the current user’s Documents folder:

var file = await KnownFolders.DocumentsLibrary.GetFileAsync("A.dwg");

await Launcher.LaunchFileAsync(file);

For our browser app we will perform this when an item is double-clicked. The standalone MRU extractor copies the DWG files into a specific location inside the Documents folder, so when we launch we’re not actually editing the original document (something that could be considered a significant limitation). If all DWG files are stored beneath the Documents folder then this limitation could presumably be lifted with modest coding effort.

Summary

WinRT is a brave departure – and a big risk – from Microsoft. They are maintaining the ability for legacy apps to be used on the new OS – at least when installed on a device powered by an Intel chip – but presenting a brand new application model that risks confusing users as well as bringing some pain to application developers.

The restrictions imposed by the WinRT programming model are for the best of reasons: to increase trust and improve the control users have over application functionality, but it remains to be seen whether these restrictions in some way limit the generation of usable Windows Store applications: it’s far from the case that existing apps of any complexity will be ported across to WinRT with ease (and it’s not Microsoft’s expectation that this will happen: presumably they would be happy if application developers in significant numbers invested in creating the next generation Windows experience using WinRT).

There does appear to be an interesting possibility for developers of heavyweight desktop apps to create companion apps that can be installed from the Windows Store and connect in some way to the desktop environment.

WinRT apps have some interesting UI capabilities that we saw in this session:

  • Semantic Zoom, allowing different levels of detail in summarizing items
  • Contracts enabling apps to integrate with the Search, Share and Settings charms
  • Live tile notifications, allowing apps to present information to users via the Start screen
  • Snapping, allowing apps to dock with other apps
  • Launching of desktop apps associated with a particular file-type

Blog References

Attended the Windows 8 TechConference

A simple MRU browser for AutoCAD data using WinRT

Implementing contracts in the AutoCAD MRU browser using WinRT

Adding snapping to the AutoCAD MRU browser using WinRT

blog comments powered by Disqus

10 Random Posts