Creating a Message Only window using WPF

Classic Win32 applications use a Window as basic unit of abstraction, including using it for Inter Process Communication. For example, a message only window is hidden, and we only listen to to for messages that are posted from elsewhere. To create a message only window, it must have its parent HWND set to HWND_MESSAGE (i.e. IntPtr(-3)). Then custom messages can be sent as long as the message id falls within the bounds specified here.

While WPF does away with Windows, the interop library provides the basic building blocks for us to create Win32 windows.

/// <summary>
///     Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
    private readonly IntPtr _sourceHandle;

    // Valid ranges here:
    // https://msdn.microsoft.com/en-us/library/windows/desktop/ms644927(v=vs.85).aspx#app_defined
    private const uint CustomMessage = 0x8801; 

    public MainWindow()
    {
        InitializeComponent();

        _sourceHandle = CreateMessageOnlyWindow();
        Button1.Click += SendMessageToMessageOnlyWindow;
    }

    private IntPtr CreateMessageOnlyWindow()
    {
        IntPtr HWND_MESSAGE = new IntPtr(-3);
        var source =
            new HwndSource(new HwndSourceParameters() { ParentWindow = HWND_MESSAGE});
        source.AddHook(WndProc);
        return source.Handle;
    }

    private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam, ref bool handled)
    {
        if (msg == CustomMessage)
        {
            Button1.Dispatcher.Invoke(
                new Action(delegate { Button1.Content = lparam.ToInt32().ToString(); }));
                
            handled = true;

            return new IntPtr(20);
        }
        return IntPtr.Zero;
    }

    private void SendMessageToMessageOnlyWindow(object sender, RoutedEventArgs e)
    {
        var result = SendMessage(_sourceHandle, CustomMessage, IntPtr.Zero, new IntPtr(DateTime.Now.Second));
        Contract.Assert(result.ToInt32() == 20);
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
}

Managed Metadata in Sharepoint 2013 DisplayTemplate

Applicability: Sharepoint 2013, Sharepoint Online

Display Templates in Sharepoint allow individual content types to be displayed differently in Sharepoint Content Search results (template files are here _catalogs/masterpage/Display Templates/Content Web Parts. In addition, since each content type can have different columns, the display templates allow properties to be remapped to rendered fields as well.

Display templates are by default deactivated, and requires a manual activation step in the Site Settings, under Publishing Infrastructure.

Each display template has an ugly looking metadata segment that makes it looks like MS Word-generated HTML.

<mso:ManagedPropertyMapping msdt:dt="string">
  'RenderPropertyName'{Web Part Label}:'PublishingImage;RefinableText01;...',
  'Link URL'{Link URL}:'Path',
  'Line 1'{Line 1}:'Title',
  'Line 2'{Line 2}:'Description',
  'Line 3'{Line 3}:'',
  'SecondaryFileExtension',
  'ContentTypeId'
</mso:ManagedPropertyMapping>

The key take away here are:

  • when rendering in the display templates, use RenderPropertyName;
  • when presenting the property to be customised in the Web Part – use Web Part Label;
  • to set the default properties, set them in the final segment.

The actual rendering appears to be done on the client-side using a custom Javascript templating language that is reminiscent of PHP.

<!--#_
var propertyValue = $getItemValue(ctx, "RenderPropertyName");
 _#-->
_#= propertyValue =#_

Hat tip to @cann0nf0dder for working this out!

Clean Code with AngularJS on Windows – Part 2

This is Part 2 of a series on AngularJS testing.

Our basic test case is a user registration service.

using a TDD philosophy, we start with our failing test.

// file: tests/userReg.js
describe('Test Suite', function() {

  var scope;

  // before each test is run, load the userReg module
  beforeEach(angular.mock.module('userRegApp'));

  // before each test is run, sets the scope
  beforeEach(angular.mock.inject(function($rootScope, $controller) {
     scope = $rootScope.$new();
  }))

  // single asynchronous unit test
  // by including 'done' argument, the test will not complete
  // until done() is called.
  it('service should asynchronously return true', function(done) {

    // inject() is kind of like a DI constructor
    // note that userRegistrationService is registered as a 
    // factory in the userRegApp module
    angular.mock.inject(function($q, userRegistrationService) {

        // mock the userRegistrationService.usernameExists()
        var deferred = $q.defer();
        spyOn(userRegistrationService, 'usernameExists').and.returnValue(deferred.promise);
        deferred.resolve(true);

        userRegistrationService.usernameExists('admin')
            .then(function(result) {
                expect(result).toBe(true);
            })
            .finally(done);

        scope.$apply();

    })
  });

})

We also need to configure karma.conf.js to load angular.js, angular-mocks.js, our source files and test files.

// Karma configuration

  // list of files / patterns to load in the browser
  files: [
    'bower_components/angular/angular.js',
    'bower_components/angular-mocks/angular-mocks.js',
    'app/*.js',
    'tests/**/*.js'
  ],

Launch karma to test

$ node_modules/karma/bin/karma start karma.conf.js

Karma should spit out long error messages such as this:

 Error: [$injector:modulerr] Failed to instantiate module userRegApp due to:
 Error: [$injector:nomod] Module 'userRegApp' is not available! You either misspelled the module name or forgot to load it.

Create a new file called app/userRegistration.js

angular.module('userRegApp', [])
.factory('userRegistrationService', ['$http', '$q', function($http, $q) {
  return {
    usernameExists: function(userName) {

      // .NET developers may recognize deferred as
      // being the equivalent of TaskCompletionSource
      var deferred = $q.defer();
      $http.get('/webapi/v1/usernameExists/' + userName)
        .success(function(payload) { deferred.resolve(payload.data); })
      return deferred.promise;
    }
  }
}]);

Karma should report that the unit test passed.

INFO [watcher]: Added file "...../ngtest/app/userRegistration.js".
Chrome 39.0.2171 (Windows 8.1): Executed 1 of 1 SUCCESS (0.036 secs / 0.032 secs)

Clean Code with AngularJS on Windows – Part 1

Writing clean, testable code with AngularJS requires a specific toolchain to be present, these are:

  • npm (package manager for back end)
  • karma
  • bower (package manager for front end – to install angularjs components)

This exercise takes approximately 5 minutes (for packages to download). It installs the basic components necessary.

If there’s an existing project, use npm to automatically install all dependencies specified in package.json. Use a special switch so that binaries are built using Visual Studio 2013 (otherwise defaults to VS2010)
$ npm install --msvs-version=2013

Create an empty package.json file

file: package.json
{
}

Install Karma

# install karma
$ npm install karma --save-dev --msvs-version=2013
 
#install plugins
$ npm install karma-jasmine karma-chrome-launcher --save-dev --msvs-version=2013
 
#configure karma
$ node_modules/karma/bin/karma init karma.conf.js
 
Which testing framework do you want to use ?
Press tab to list possible options. Enter to move to the next question.
> jasmine
 
Do you want to use Require.js ?
This will add Require.js plugin.
Press tab to list possible options. Enter to move to the next question.
> no
 
Do you want to capture any browsers automatically ?
Press tab to list possible options. Enter empty string to move to the next question.
> Chrome
>
 
What is the location of your source and test files ?
You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js".
Enter empty string to move to the next question.
>
 
Should any of the files included by the previous patterns be excluded ?
You can use glob patterns, eg. "**/*.swp".
Enter empty string to move to the next question.
>
 
Do you want Karma to watch all the files and run the tests on change ?
Press tab to list possible options.
> yes
 
Config file generated at "....\karma.conf.js".

Install Bower


# install bower (front-end package manager)
$ npm install -g bower --save
 
# creates bower.json
$ bower init
 
# install angular and angular mocks
$ bower install --save angular angular-mocks

git-tf notes

Working with Xamarin iOS with an existing project means there’s a new tool to pick up. The source code repository is on TFS, while my development machine is a Macbook Pro.

Luckily, Microsoft has provided a java based command line client called git-tf.

One of the sub-requirements is that I need to fork the entire TFS repository to git, as my development build machine will be building off a git repository. I’ll need to regularly merge back changes into the TFS.

The first step is to take a full history of the TFS repository.


$ git-tf clone --deep https://tfs2012.mydomain.com/tfs/mycollection $/myproject/Trunk MyLocalDir
username: chui.tey
password:

Next, push it into my remote git repository on Bitbucket.


git remote add bitbucket git@bitbucket.org:/teyc/myproject.git

Calibre E-book – Authoring Plugins with a Custom Context Menu

I’ve been hacking with the Calibre ebook reader for a few hours, and I’d like to share with you some notes of how to add a custom context menu to a book, so that we can open a secondary folder where the book has been exported to.

A good place to start is to download the interface_demo.zip from the Plugins documentation. Unzip the directory, and run Calibre in debug mode from that directory.

mkdir demo
cd demo
unzip interface_demo.zip

REM loads calibre plugin from the current directory
../calibre-customize.exe  -b .

REM launch calibre gui in debug mode
../calibre-debug.exe -g

Next, add the custom action to the context menu manually through the preference toolbar. I’ve taken some screenshots below:

That done, we can go in and hack on the plugin until it works.

In my particular case, I’ve got an alternate filepath stored in a custom metadata field, and I’d like to open the alternate containing folder.

#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai

__license__   = 'GPL v3'
__copyright__ = '2014 Chui Tey teyc@cognoware.com'
__docformat__ = 'restructuredtext en'


from calibre.gui2.actions import InterfaceAction
import os

class InterfacePlugin(InterfaceAction):

    name = 'Open Oracle Folder'
    action_spec = ('Open Oracle folder', 'images/blank.png',
                   'Open the file in the Oracle folder', 'CTRL+O')
    dont_add_to = frozenset(['context-menu-device'])
    action_type = 'current'

    def genesis(self):
        #icon = get_icons("images/blank.png")
        #self.qaction.setIcon(icon)
        #self.qaction.setEnabled(True)
        self.qaction.triggered.connect(self.open_folder)

    def open_folder(self):
        rows = self.gui.current_view().selectionModel().selectedRows()
        if not rows or len(rows) == 0:
            return
        not_found = []
        for i, row in enumerate(rows):
            if i > 5: continue
            mi = self.gui.library_view.model().db.get_metadata(row.row())
            filepath = mi.get('#filepath', None)
            if filepath:
                import os
                folderpath = os.path.split(filepath)[0]
                if os.path.isdir(folderpath):
                    os.startfile(folderpath)
                else:
                    not_found.append(folderpath)
        if not_found:
            if len(not_found) > 1:
                raise Exception("Folders %s not there!" % ", ".join(not_found))
            else:
                raise Exception("Folder %s not there!" % ", ".join(not_found))

            # actual path
            # path = self.gui.library_view.model().db.abspath(row.row())

    def location_selected(self, loc):

        enabled = loc == 'library'
        self.qaction.setEnabled(enabled)

Tip: Create an App icon for your website

If you already have a web site, and you are thinking about creating a mobile app version of this, there is a less expensive approach.

Mobile users who bookmark your website can get essentially an App experience because your website will have a proper App icon, rather than a screenshot of your website.

Here’s a flow chart of what you will need to support iOS, Android and Windows