Rash thoughts about .NET, C#, F# and Dynamics NAV.


"Every solution will only lead to new problems."

Friday, 6. November 2009


Observing global hotkeys in C# / F#

Filed under: C#, F# — Steffen Forkmann at 11:08 Uhr

I recently had the problem to register a global hotkey, but my “old” Win32-API calls didn’t work with WPF. I looked around the web and found the “Managed Windows API”, but I didn’t want to add another external dependency to my project, so I extracted the core functions for registering hotkeys and condensed the code to a new version.

As the original “Managed Windows API” is licensed by the GNU Lesser General Public License (LGPL) I want to provide my modifications here.

First we need a Window which can dispatch the window messages to our event handlers:

/// <summary>

/// A Win32 native window that delegates window messages to

/// handlers. So several

/// components can use the same native window to save

/// "USER resources". This class

/// is useful when writing your own components.

/// </summary>

public sealed class EventDispatchingNativeWindow : NativeWindow

{

    private static readonly Object MyLock = new Object();

    private static EventDispatchingNativeWindow _instance;

 

    /// <summary>

    /// Create your own event dispatching window.

    /// </summary>

    public EventDispatchingNativeWindow()

    {

        CreateHandle(new CreateParams());

    }

 

    /// <summary>

    /// A global instance which can be used by components

    /// that do not need their own window.

    /// </summary>

    public static EventDispatchingNativeWindow Instance

    {

        get

        {

            lock (MyLock)

            {

                return

                  _instance ??

                     (_instance =

                       new EventDispatchingNativeWindow());

            }

        }

    }

 

    /// <summary>

    /// Attach your event handlers here.

    /// </summary>

    public event WndProcEventHandler EventHandler;

 

    /// <summary>

    /// Parse messages passed to this window and send

    /// them to the event handlers.

    /// </summary>

    /// <param name="m">A System.Windows.Forms.Message

    /// that is associated with the

    /// current Windows message.</param>

    protected override void WndProc(ref Message m)

    {

        bool handled = false;

        if (EventHandler != null)

            EventHandler(ref m, ref handled);

        if (!handled)

            base.WndProc(ref m);

    }

}

Now we can write our global hotkey handler class:

/// <summary>

/// Specifies a class that creates a global keyboard hotkey.

/// </summary>

public class GlobalHotkey

{

    private static readonly Object MyStaticLock = new Object();

    private static int _hotkeyCounter = 0xA000;

 

    private readonly int _hotkeyIndex;

    private readonly IntPtr _hWnd;

 

    /// <summary>

    /// Initializes a new instance of this class.

    /// </summary>

    /// <param name="keys">The keys.</param>

    /// <param name="ctrl">if <c>true</c> [CTRL].</param>

    /// <param name="alt">if <c>true</c> [alt].</param>

    /// <param name="shift">if <c>true</c> [shift].</param>

    /// <param name="winKey">if <c>true</c> [win key].</param>

    public GlobalHotkey(Keys keys, bool ctrl,

               bool alt, bool shift, bool winKey)

    {

        KeyCode = keys;

        Ctrl = ctrl;

        Alt = alt;

        Shift = shift;

        WindowsKey = winKey;

        EventDispatchingNativeWindow.Instance.EventHandler

                += NwEventHandler;

        lock (MyStaticLock)

        {

            _hotkeyIndex = ++_hotkeyCounter;

        }

        _hWnd = EventDispatchingNativeWindow.Instance.Handle;

        Enable();

    }

 

    /// <summary>

    /// The key code of the hotkey.

    /// </summary>

    public Keys KeyCode { get; private set; }

 

    /// <summary>

    /// Whether the shortcut includes the Control modifier.

    /// </summary>

    public bool Ctrl { get; private set; }

 

    /// <summary>

    /// Whether this shortcut includes the Alt modifier.

    /// </summary>

    public bool Alt { get; private set; }

 

    /// <summary>

    /// Whether this shortcut includes the shift modifier.

    /// </summary>

    public bool Shift { get; private set; }

 

    /// <summary>

    /// Whether this shortcut includes the Windows key modifier.

      /// </summary>

    public bool WindowsKey { get; private set; }

 

    ~GlobalHotkey()

    {

        Disable();

        EventDispatchingNativeWindow.Instance.EventHandler

            -= NwEventHandler;

    }

 

    /// <summary>

    /// Enables the hotkey. When the hotkey is enabled,

    /// pressing it causes a

    /// <c>HotkeyPressed</c> event instead of being

    /// handled by the active application.

    /// </summary>

    private void Enable()

    {

        // register hotkey

        int fsModifiers = 0;

        if (Shift) fsModifiers += ModShift;

        if (Ctrl) fsModifiers += ModControl;

        if (Alt) fsModifiers += ModAlt;

        if (WindowsKey) fsModifiers += ModWin;

        bool success =

           RegisterHotKey(_hWnd, _hotkeyIndex,

                fsModifiers, (int) KeyCode);

 

        if (!success)

            throw new

              Exception(

                "Could not register hotkey (already in use).");

    }

 

    /// <summary>

    /// Disables this instance.

    /// </summary>

    private void Disable()

    {

        // unregister hotkey

        UnregisterHotKey(_hWnd, _hotkeyIndex);

    }

 

    /// <summary>

    /// Occurs when the hotkey is pressed.

    /// </summary>

    public event EventHandler HotkeyPressed;

 

    private void NwEventHandler(ref Message m, ref bool handled)

    {

        if (handled) return;

        if (m.Msg != WmHotkey ||

                m.WParam.ToInt32() != _hotkeyIndex)

            return;

        if (HotkeyPressed != null)

            HotkeyPressed(this, EventArgs.Empty);

        handled = true;

    }

 

    #region PInvoke Declarations

 

    private const int ModAlt = 0×0001;

    private const int ModControl = 0×0002;

    private const int ModShift = 0×0004;

    private const int ModWin = 0×0008;

    private const int WmHotkey = 0×0312;

 

    [DllImport("user32.dll", SetLastError = true)]

    private static extern bool RegisterHotKey(IntPtr hWnd,

          int id, int fsModifiers, int vlc);

 

    [DllImport("user32.dll", SetLastError = true)]

    private static extern bool UnregisterHotKey(IntPtr hWnd,

          int id);

 

    #endregion

}

Now we can easily register a global hotkey or use it as an observable in F#:

/// system-wide keyboard hook

let hotkey = new GlobalHotkey(Keys.Q,true,false,false,false)

let hookObserver =

  hotkey.HotkeyPressed 

    |> Observable.subscribe (fun _ -> printfn "Hotkey pressed")

Tags: , , , ,

Wednesday, 1. July 2009


Extensibility of functions with lambdas (in F# and C#)

Filed under: English posts, F# — Steffen Forkmann at 16:02 Uhr

One of the nice properties of functional programming languages is the easy extensibility of custom functions. Let’s consider a simple F# function (from “FAKE – F# Make”) for a recursive directory copy:

open System
open System.IO

/// Copies a directory recursive
/// Thanks to Robert Pickering http://strangelights.com/blog/
///  param target: target directory : string
///  param source: source directory : string
let CopyDir target source =
  Directory.GetFiles(source, "*.*", SearchOption.AllDirectories)
    |> Seq.iter (fun file ->
      let newFile = target + file.Remove(0, source.Length)
      printf "%s => %s" file newFile
      Directory.CreateDirectory(Path.GetDirectoryName(newFile)) |> ignore
      File.Copy(file, newFile, true))

If we want to allow users to set custom file filters, we can add a third parameter:

/// Copies a directory recursive
/// and allows to filter the files
/// Thanks to Robert Pickering http://strangelights.com/blog/
///  param target: target directory : string
///  param source: source directory : string
///  param filterFile: FilterFunction: string -> bool
let CopyDirFiltered target source filterFile =
  Directory.GetFiles(source, "*.*", SearchOption.AllDirectories)
    |> Seq.filter filterFile
    |> Seq.iter (fun file ->
      let newFile = target + file.Remove(0, source.Length)
      printfn "%s => %s" file newFile
      Directory.CreateDirectory(Path.GetDirectoryName(newFile)) |> ignore
      File.Copy(file, newFile, true))

Now we can define some filter functions:

/// Exclude SVN files (path with .svn)
/// excludeSVNFiles: string -> bool
let excludeSVNFiles (path:string) = not <| path.Contains ".svn"

/// Includes all files
/// allFiles: string -> bool 
let allFiles (path:string) = true

Now it is possible to use CopyDirFiltered in the following ways:

/// Copies all files <=> same as CopyDir
CopyDirFiltered "C:\\target" "C:\\source" allFiles

/// Copies all files except SVN files
CopyDirFiltered "C:\\target" "C:\\source" excludeSVNFiles

/// Copies all files only if random number <> 2
let r = new Random()
CopyDirFiltered "C:\\target" "C:\\source" (fun path -> r.Next(5) <> 2)
Extensibility of functions in C#

Of course we can do the same thing in C# 3.0:

/// <summary>
/// Copies a directory recursive
/// and allows to filter the files
/// </summary>
/// <param name="target">The target.</param>
/// <param name="source">The source.</param>
/// <param name="fileFilter">The file filter.</param>
public static void CopyDirFiltered(string target, string source,
                                   Func<string, bool> fileFilter)
{
    string[] allFiles = Directory.GetFiles(
        source, "*.*", SearchOption.AllDirectories);
    foreach (string file in from f in allFiles
                            where fileFilter(f)
                            select f)
    {
        string newFile = target + file.Remove(0, source.Length);
        Console.WriteLine("{0} => {1}", file, newFile);
        Directory.CreateDirectory(Path.GetDirectoryName(newFile));
        File.Copy(file, newFile, true);
    }
}

Now it is easy to use the C# function with lambdas:

“A lambda expression is an anonymous function that can contain expressions and statements, and can be used to create delegates or expression tree types.”

[MSDN]

Func<string, bool> filterSVN = x => !x.Contains(".svn");
Func<string, bool> allFiles = x => true;

/// Copies all files <=> same as CopyDir
CopyDirFiltered("C:\\target", "C:\\source", allFiles);

/// Copies all files except SVN files
CopyDirFiltered("C:\\target", "C:\\source", filterSVN);

/// Copies all files only if random number <> 2
var r = new Random();
CopyDirFiltered("C:\\target", "C:\\source", path => r.Next(5) != 2);

Keeping this simple technique in mind allows to create very flexible functions.

Tags: , , ,

Thursday, 16. October 2008


Debugging in Dynamics NAV 2009

Filed under: .NET 3.0, C#, Dynamics NAV 2009, Visual Studio, msu solutions GmbH — Steffen Forkmann at 13:41 Uhr

Claus Lundstrøm zeigt in einem schönen Blogpost wie man in NAV2009 den Code auf Seite der ServiceTier (also auch remote) debuggen kann – und zwar über Visual Studio 2008 direkt im generierten C#-Code. Mit dieser Variante ist man nicht mehr gezwungen das Debugging über den Classic-Client zu tun, sondern kann direkt aus dem Dynamics NAV RoleTailored-Client debuggen.

Dummerweise ist der generierte C#-Code, wie das bei generiertem Code eigentlich immer der Fall ist, nicht gerade “optisch schöner” C#-Style und hat auch nur noch wenig mit dem Original-C/AL-Code zu tun – ist aber immerhin lesbar.

Das ist ein wirklich interessanter Ansatz und erlaubt mit etwas Geschick auch UnitTesting für NAV 2009. Dafür werde ich demnächst mal versuchen ein kleines Beispiel zu bloggen.

Tags: , , , , ,

Tuesday, 14. October 2008


Technologie Highlights von Heute und Morgen! – Umfrage auf der BASTA! 2008

Filed under: C#, F#, Veranstaltungen — Steffen Forkmann at 9:02 Uhr

Florian Mätschke hat soeben seine auf der BASTA! 2008 in Mainz angekündigte Umfrage auf seinem Blog veröffentlicht. Dabei wurden die BASTA!-Speaker zu der Technologie befragt, die sie im Moment am meisten fasziniert. “Gewinner” ist übrigens Silverlight 2 geworden, dicht gefolgt von funktionaler Programmierung (in F# bzw. LINQ) – wofür ich mich übrigens auch entschieden habe.

Insgesamt ist das Umfrageergebnis, wie für die BASTA! zu erwarten war, sehr .NET-lastig. Obwohl auf der Abendveranstaltung noch Technologien wie Waschmaschine und Auto als faszinierend erachtet wurden, haben sich die meisten Speaker schlussendlich für ihr Vortragsthema im weitesten Sinne entschieden.

Ich muss sagen, dass ich das Konzept der Umfrage sehr interessant finde. Das Problem ist nur, dass man z.B. auf einer Java-Konferenz natürlich vollkommen konträre Ergebnisse erzielt. Um die wirklichen “Technologie Highlights“ zu ermitteln müsste man die Umfrage selbstverständlich viel größer und anonymisiert anlegen.

Tags: , , , , ,

Sunday, 12. October 2008


F# option types und generische Listen in C# verwenden

Filed under: C#, F# — Steffen Forkmann at 14:08 Uhr

Luis Fallas beschreibt in seinem Blog (“Exploring Beautiful Languages”) an einem sehr schönen Beispiel, wie man die F# option types mit Hilfe von Extension Methods in C# verwenden kann.

Hier ist eine generische Variante zu seiner Exists()-Methode:

open System.Runtime.CompilerServices

[<Extension>]
module Extensions =
  [<Extension>]
  let Exists(opt : 'a option) =
    match opt with
      | Some _ -> true
      | None –> false

Auf ähnlichem Wege kann man übrigens auch die generischen F#-Listen in System.Collections.Generic.List<T> umwandeln:

[<Extension>]    
let ToCSharpList(list : 'a list) =
  let csharpList = 
    new System.Collections.Generic.List<'a>()
  list |> List.iter (fun item -> csharpList.Add item)
  csharpList

Der umgekehrte Weg (von C# nach F#) ist fast analog, allerdings muss man die Liste drehen:

static class Extensions
{
  /// <summary>
  /// Converts a System.Collections.Generic.List<T> 
  /// in the corresponding F# list.
  /// </summary>
  public static Microsoft.FSharp.Collections.List<T> 

     ToFSharpList<T>(this List<T> list)
  {
      var fSharpList = 
        Microsoft.FSharp.Collections.List<T>.Empty;
      for (int i = list.Count - 1; i >= 0; i--)
       fSharpList = 
          Microsoft.FSharp.Collections.List<T>.Cons(
            list[i], 
            fSharpList);
      return fSharpList;
  }
}
Tags: , , , , , ,

Saturday, 5. April 2008


Microsoft Dynamics Mobile 1.1 released

Filed under: .NET, C#, Dynamics Mobile, Navision, SQL Server — Steffen Forkmann at 11:53 Uhr

Microsoft hat am 31.3.2008 auf PartnerSource die erste Version von Dynamics Mobile veröffentlicht. Dabei handelt es sich primär um ein auf .NET basierendes Architekturkonzept mit dem man Mobile Endgeräte (mit Windows Mobile 5.0 oder 6.0) an ERP-Systeme (Dynamics NAV oder AX) anbinden kann. Weiterhin wird mit “Mobile Sales” jedoch eine umfangreiche Beispielanwendung mitgeliefert.

Architektur Dynamics Mobile - siehe Mobile Development Tools Whitepaper

Das Release umfasst:

Dynamics Mobile wird momentan unterstützt für:

  • Microsoft Dynamics NAV 5.0 SP1
  • Microsoft Dynamics NAV 4.0 SP3
  • Microsoft Dynamics AX 4.0 SP1
  • Microsoft Dynamics AX 4.0 SP2
Tags: , , ,

Saturday, 6. October 2007


ReSharper 3.0 für Visual Studio 2005 im Test – Refactoring für Profis

Filed under: .NET, ASP.NET, C#, Firmen, Tools, Visual Studio — Steffen Forkmann at 12:03 Uhr

ReSharper ist ein Plugin der Firma JetBrains für Visual Studio, das sich speziell die Produktivitätssteigerung beim Entwickeln als Ziel gesetzt hat. Besonderes Augenmerk wird dabei auf die Refactoring-Unterstüzung gelegt, also auf das nachträgliche Umgestalten von Quellcode. JetBrains wirbt damit das “intelligenteste AddIn für Visual Studio” entwickelt zu haben – doch was kann ReSharper wirklich? 

(weiterlesen…)

Tags: , , , ,

Saturday, 5. May 2007


Vortrag von Roland Weigelt zur Visual Studio 2005 Erweiterbarkeit

Filed under: .NET 3.0, C#, TechTalk, Veranstaltungen, Visual Studio — Steffen Forkmann at 8:57 Uhr

Am 06.07.2007 findet ab 19:30 ein sehr interessantes Event in Leipzig statt. Roland Weigelt wird über die Entwicklung seines mehrfach ausgezeichneten Plugins GhostDoc berichten und einen Einblick in die Visual Studio Erweiterbarkeit geben. GhostDoc ist für mich neben ReSharper das wichtigste Plugin für Visual Studio überhaupt.

Der Vortragende wird folgende Features vorstellen:

  • Code Snippets
  • Item und Project Templates
  • Wizards
  • Makros
  • Add-Ins

Mehr zu dieser Veranstaltung bzw. die Anmeldung findet man auf den Seiten der .NET User Group Leipzig.

Tags: , , , ,

Wednesday, 3. January 2007


Java vs. C# vs. VB.NET

Filed under: C# — Steffen Forkmann at 14:35 Uhr

Für Java-Programmierer bzw. VB-Programmierer die gern in C# einsteigen wollen, gibt es eine Schnellreferenz mit den wichtigsten Syntaxunterschieden. Das funktioniert natürlich auch umgekehrt – aber wer würde schon freiwillig von C# weg wollen? ;-)  

Tags:

Codierung einer Textdatei ändern in C#

Filed under: C# — Steffen Forkmann at 14:24 Uhr

Wenn man die Encodierung einer Textdatei ändern möchte, kann man das mit diesem Code-Fragment tun:

public void ConvertEncoding( string fileName, string newFileName, Encoding oldEncoding, Encoding newEncoding) { using (StreamWriter w = new StreamWriter(newFileName, false, newEncoding)) { using (StreamReader r = new StreamReader(fileName, oldEncoding, false)) { int charsRead; char[] buffer = new char[128 * 1024]; while ((charsRead = r.ReadBlock(buffer, 0, buffer.Length)) > 0) w.Write(buffer, 0, charsRead); } } }
Tags: