Lloyd.NET

Programing experiments

DirectX made simple

With Windows 8, WinRT, C++/Cx I think the time to write an elegant C# / XAML app using some DirectX rendering in C++ has finally come! Thanks WinRT! :-)

Here I just plan to describe my attempt at learning DirectX and C++ and integrate it nicely in a C# XAML app.

My first exercise was to attempt to create a simple DirectX “Context” as WinRT C++/Cx component that can target multiple DirectX hosts: SwapPanel, CoreWindow, ImageSource and render an independent scene and initialize and use it from C#.

Note this is a metro app. It requires VS2012 and Windows 8.

 

First the appetizers, here is my simple scene:

SimpleSample

And it is created with the code below, mostly one giant C# (5, async inside!) object initializer:

public class Universes
{
    public async static Task<Universe> CreateUniverse1(DXContext ctxt = null)
    {
        ctxt = ctxt ?? new DXContext();

        var cubetex = await CreateSceneTexture(ctxt);

        var earth = new BasicTexture(ctxt);
        await earth.Load("earth600.jpg");

        var cube = new BasicShape(ctxt);
        cube.CreateCube();
        var sphere = new BasicShape(ctxt);
        sphere.CreateSphere();

        var u = new Universe(ctxt)
        {
            Scene =
            {
                Background = Colors.Aquamarine,
                Camera =
                {
                    EyeLocation = dx.vector3(0, 0.0f, 0.0f),
                    LookDirection = dx.vector3(0, 0, 100),
                    UpDirection = dx.vector3(0, 1, 0),
                }
            },
            Items =
            {
                new SpaceBody(ctxt)
                {
                    FTransform = t => dx.identity().Scale(10, 10, 10).RotationY(36 * t),
                    FLocation = t => dx.vector3(0, 0, 50),
                    SceneItem =
                    {
                        Shape = cube,
                        Texture = cubetex,
                    }
                },
                new SpaceBody(ctxt)
                {
                    FTransform = t => dx.identity().Scale(8, 6, 8).RotationY(96 * t),
                    FLocation = t => new float3().Translate(15, 0, 0).RotationY(24 * t).Translate(0, 15, 50),
                    SceneItem =
                    {
                        Shape = sphere,
                        Texture = earth,
                    },
                    Items =
                    {
                        new SpaceBody(ctxt)
                        {
                            FTransform = t => dx.identity().RotationY(84 * t),
                            FLocation = t => new float3().Translate(12, 0, 0).RotationY(24 * t),
                            SceneItem =
                            {
                                Shape = sphere,
                                Texture = earth,
                            }
                        }
                    },
                },
                new SpaceBody(ctxt)
                {
                    FTransform = t => dx.identity().Scale(6, 5, 6).RotationY(48 * t),
                    FLocation = t => new float3().Translate(-15, -15, 55),
                    SceneItem =
                    {
                        Shape = sphere,
                    }
                },
            },
        };

        return u;
    }

    public async static Task<BasicTexture> CreateSceneTexture(DXContext ctxt)
    {
        var tex = new BasicTexture(ctxt);
        tex.Create(300, 300);
        ctxt.SetTarget(tex);

        var scene = new Scene(ctxt);
        scene.Background = Windows.UI.Colors.DarkGoldenrod;
        scene.Add(new DXBase.Scenes.CubeRenderer());
        scene.Add(new DXBase.Scenes.HelloDWrite());
        await scene.LoadAsync().AsTask();
        scene.RenderFrame();
        return tex;
    }
}

There is much to say about this sample but I won’t go into the detail of DirectX too much (this is a very basic sample as far as DirectX is concerned and the source code is available, at the bottom), instead I will mostly speak about C++/Cx – C# communication.

 

1. The main DirectX C++/Cx components

1.1. DXContext

First there is the DirectX context, here is an extract of its important methods and properties

  public ref class DXContext sealed :  Windows::UI::Xaml::Data::INotifyPropertyChanged
  {
  public:
      DXContext();

      // Target the top level CoreWindow
      void SetTarget();
      // Target the argument top level SwapChainBackgroundPanel
      void SetTarget(Windows::UI::Xaml::Controls::SwapChainBackgroundPanel^ swapChainPanel);
      // Target the argument ImageSource
      void SetTarget(Windows::UI::Xaml::Media::Imaging::SurfaceImageSource^ image, int w, int h);
      // Target a texture
      void SetTarget(DXBase::Utils::BasicTexture^ texture);

      property float Dpi;
      property Windows::Foundation::Size Size;
      property Windows::Foundation::Rect Viewport;

      DXBase::Utils::BasicTexture^ Snapshot();

      // internal (shared) DirectX variables
  internal:
      // device independent resources
      Microsoft::WRL::ComPtr<ID2D1Factory1> m_d2dFactory;
      Microsoft::WRL::ComPtr<IDWriteFactory1> m_dwriteFactory;
      Microsoft::WRL::ComPtr<IWICImagingFactory2> m_wicFactory;

      // device resource
      D3D_FEATURE_LEVEL m_featureLevel;
      Microsoft::WRL::ComPtr<ID3D11Device1> m_d3dDevice;
      Microsoft::WRL::ComPtr<ID3D11DeviceContext1> m_d3dContext;
      Microsoft::WRL::ComPtr<ID2D1Device> m_d2dDevice;
      Microsoft::WRL::ComPtr<ID2D1DeviceContext> m_d2dContext;

      // target and size dependent resources
      DirectX::XMFLOAT4X4 mDisplayOrientation;
      Microsoft::WRL::ComPtr<ID3D11RenderTargetView> m_renderTargetView;
      Microsoft::WRL::ComPtr<ID3D11DepthStencilView> m_depthStencilView;
};

DXContext is a ‘public ref class’ meaning it’s a shared component (Can be used by C#), it must be sealed (unfortunately… Except those inheriting from DependencyObject, all C++ public ref class must be sealed, as explained here, inheritance section)

All the public members are accessible from C#, the most important are the overloaded “SetTarget()” methods that will set the DirectX Rendering target. Can be changed anytime (although it seems to be an expensive operation, I think rendering on a Texture should probably be done an other way, when I will know better).

Finally it hold all DirectX device information as internal variables. These can’t be public or protected as they are not WinRT component. But, being internal, they can be accessed by other component in the library, it’s how the scene can render. I tried to trim the fat to the minimum number of DirectX variable that such an object should contains.

Note plain C++ doesn’t have the ‘internal’ visibility, this is a C++/Cx extension and it means the same thing as in C#, i.e. members are accessible by all code in the same library.

ComPtr<T> is a shared COM Pointer. Take care of all reference counting for you.

DXContext implements INotifyPropertyChanged and can be observed by XAML component or data binding!
I also created a macro for the INotifyPropertyChanged implementation as it is repetitive and I had to write a long winded implementation due to some mysterious bug in the pure C++ sample.

It has a Snapshot() method to take a screen capture! And BasicTexture have a method to save to file.

 

1.2 Scene

My first attempt at using this DXContext was to create a Scene object which contains ISceneData object.

An ISceneData can be ripped of, more or less, verbatim from various DirectX sample around the web. And the Scene object will take care of initializing it and rendering it when the time is right. I have 2 ISceneData implementations: CubeRenderer, HelloDWrite.

 

1.3 BasicScene, BasicShape, BasicTexture

Unfortunately all the sample on the web often have a lot variables, all mixed up and trying to sort out what does what takes some thinking.

So I created a BasicScene which takes a list of shapes with texture and location (transform) and renders it

public ref class BasicSceneItem sealed
{
public:
    BasicSceneItem(DXContext^ ctxt);
    property DXContext^ Context;
    property DXBase::Utils::BasicShape^ Shape;
    property DXBase::Utils::BasicTexture^ Texture;
    property DXBase::Utils::float4x4 WorldTransform;
};

public ref class BasicScene sealed
{
public:
    BasicScene();
    BasicScene(DXContext^ ctxt);

    property PerformanceTimer^ Timer;
    property Windows::UI::Color Background;
    property DXBase::DXContext^ Context;
    property DXBase::Utils::BasicCamera^ Camera;

    property Windows::Foundation::Collections::IVectorView<BasicSceneItem^>^ Shapes;
    void Add(BasicSceneItem^ item);
    void Remove(BasicSceneItem^ item);
    void RemoveAt(int index);

    property bool IsLoaded;

    void RenderFrame();
    void RenderFrame(SceneRenderArgs^ args);
};

It also has some Background and a Camera, all WinRT component that can be controlled by C++.

The BasicShape contains point and index buffer for triangles and has various create method that will populate the buffers.

The BasicTexture can load a file or be created directly in memory (and rendered to by using Context.SetTarget(texture)), and contains the texture and textureView used by the rendering process.

Each of these class has very few DirectX specific variables making it relatively easy to understand what’s going on.

 

2. C++/Cx to C# mapping

When C++/Cx components are called from C#, the .NET runtime does some type mapping for you. There is the obvious, the basic types (int, float, etc..) and value types (struct) are used as is. But there is more, mapping for exception and important interfaces (such as IEnumerable).

It’s worth having a look at this MSDN page which details the various mapping happening.

Also, to refresh my C++ skill I found this interesting web site where most Google query lead to anytime I had a C++ syntax or STL issue!

 

3. Exception across ABI

You can’t pass custom exception or exception’s message across ABI (C++ / C# / JavaScript boundary). All that can pass is an HRESULT, basically a number. Some special number will pass some special exception as explained on this MSDN page.

If you want to pass some specific exception you have to use some unreserved HRESULT (as described here) and have some helper class to turn the HRESULT in a meaningful number.

Here comes the ExHelper class just for this purpose

// this range is free: 0x0200-0xFFFF
public enum class ErrorCodes;

// You can't throw custom exception with custom message across ABI
// This will help throw custom Exception with known HRESULT value
public ref class ExHelper sealed
{
public:
    static void Throw(ErrorCodes c);
    static ErrorCodes GetCode(Windows::Foundation::HResult ex);
    static Windows::Foundation::HResult CreateWinRTException(ErrorCodes c);
};

Note you can’t expose Platform::Exception publicly either (well maybe you can, but it was troublesome). But you can expose an HRESULT. The runtime will automatically turn it into a System.Exception when called from C#.

 

4. Reference counting and weak pointer

C++/Cx is pure C++. There is no garbage collection happening when writing pure C++ app, even if one use the C++/Cx extension. The hat (^) pointer is a ref counted pointer that can automatically be turned into a C# reference.

That can lead to a problem when 2 C++/Cx components reference each other as in the following (simplified) scenario

public ref class A sealed
{
    A^ other;
public:
    property A^ Other
    {
        A^ get() { return other; }
        void set(A^ value) { this.other = value; }
    }
};

{
    auto a1 = ref new A();
} // a1 is automatically destroyed

{
    auto a1 = ref new A();
    auto a2 = ref new A();
    a1.Other = a2;
    a2.Other = a1;
} // no automatic destruction takes place!

To solve such problem WinRT comes with a WeakReference. The class A can be modified as follow to not hold strong reference:

public ref class A sealed
{
    WeakReference other;
public:
    property A^ Other
    {
        A^ get() { return other.Resolve<A>(); }
        void set(A^ value)
        {
            if (value)
                this.other = value;
            else
                this.other = WeakReference();
        }
    }
};

 

5. debugging / logging

Sometimes logging is helpful for debugging. For example I log creation and deletion of some items to be sure I don’t have any memory leak. However, printf, cout<<, System::Console::WriteLine won’t work in a metro app.

One has to use OutputDebugString, output will appears in Visual Studio output window.

 

6. IEnumerable, IList

If you use C# you must love IEnumerable, IEnumerator, IList and LINQ. When writing a C++ component you should make sure it plays nice with all that.

The .NET runtime does some automatic mapping when calling in C++/Cx component, as explained here.

6.1 IEnumerable

In C++ one shall expose Windows::Foundation::Collection::IIterable<T> to be consumed in C# a System.Collections.Generic.IEnumerable<T>.

IIterable has a single method First() that return and IIterator. That will be mapped to an IEnumerator.

However there is a a little gotcha. Unlike C# IEnumerator which starts before the first element (one has to call bool MoveNext()) IIterator starts on the first element.

6.2 IList

One can return an Windows::Foundation::Collections::IVector<T> to be mapped to an IList<T>. There is already a class implementing it:

Platform::Collections::Vector<T>.

Or one can use vector->GetView() to return a Windows::Foundation::Collections::IVectorView<T> that will be mapped to an IReadonlyList<T>.

 

7. Function pointers and lambda

C++ 0x (or whatever is called the latest C++ standard) introduced lambda expression to create inline function, much like in C#.

There is a long description of on MSDN.

Basically it has the following syntax

[capture variable](parameters) –> optional return type specification { body }

It’s all quite intuitive except for the capture part. You have to specify which value you want to capture (this, local variable) and you can specify by value or reference (using the ‘&’ prefix), or all local variables and this with equal as in: ‘[=]’

 

In some instance I had problem assigning lambda to a function pointer, for example the code below didn’t compile for me (maybe I missed something?)

IAsyncOperation<bool>^ (*func)(Platform::Object^) = [] (Object^ ome) -> IAsyncOperation<bool>^ { ... };

Fortunately C++0x introduce “function object” which works fine

#include <functional>
//....
std::function<IAsyncOperation<bool>^(Object^)> func = [] (Object^ ome) -> IAsyncOperation<bool>^ { ... };

Remark the function object will keep reference to the captured variable as long as it exists! Be careful with circular reference and WinRT component (hat pointers ‘^’).

 

8. Async

With Metro Async programming is an inescapable reality!

Of course in your C++/Cx code you can use the class from System.Threading.Tasks, but there is also some C++ native API just for that: task<T>.

One can create task from .NET IAsyncOperation or a simple C function:

#include <ppltasks.h>
#include <ppl.h>

using namespace concurrency;
using namespace Windows::Foundation;

bool (*func)() = []()->bool { return true; };
task<bool> t1 = create_task(func);

IAsyncOperation<bool>^ loader = ...;
task<bool> t2 = create_task(loader);

Conversely one can create .NET IAsyncOperation from task or C function with create_async, as in:

#include <ppltasks.h>
#include <ppl.h>

using namespace concurrency;
using namespace Windows::Foundation;

bool (*func)() = []()->bool { return true; };
task<bool> t1 = create_task(func);
IAsyncOperation<bool>^ ao1 = create_async([t1] { return t1; });
IAsyncOperation<bool>^ ao2 = create_async(func);

Tasks can be chained with ‘then’ and one can wait on multiple task by adding them with ‘&&’ such as in:

task<void> t1 = ...
task<void> t2 = ...

auto t3 = (t1 && t2).then([] -> void
{
    OutputDebugString(L"It is done");
});

Remark tasks are value type and start executing immediately once created (in another thread).

 

When chaining tasks with ‘then’ you can capture exception from previous task by taking a task<T> argument instead of T. And put a try/catch around task.get(). If you do not catch exception it will eventually brings the program down.

task<bool> theTask = ....
task<void> task = theTask.then([](concurrency::task<bool> t) -> void
{
    try
    {
        t.get();
        // success...
    }
    catch (Exception^ ex)
    {
        // failure
        auto msg = "Exception: " + ex->ToString() + "\r\n";
        OutputDebugString(msg->Data());
    }
});

 

9. Conclusion

It proved pleasantly surprisingly easy to have the C++ and C# works together with WinRT. Smooth and painless. C++ 11 was easier to use that my memory of C++ was telling me. And in the end I mixed and matched them all with great fun. To boot my C# app starts real quick (like a plain C++ app)! It’s way better than C++ CLI!

A few frustrating point with C++/Cx still stands out though:

  • Microsoft value types (Windows::Foundation::Size for example) have custom constructors, methods and operator, yours cannot.
  • You can’t create a type hierarchy! (Can be worked around tediously with an interface hierarchy, but still!)

 

10. Source code

download it from here!

 

 

.


Tags: , , , ,
Permalink | Comments (0) | Post RSSRSS comment feed

Silverlight Menu

Recently I needed a Silverlight menu. I found the following implementations:

But none behave like an ordinary HeaderedItemsControl and all did impose some constraint which make using databinding, templating or MVVM more awkward than it should be. So here is my take on this control.

 image

Implementation:

 

Below I’m going to quickly explain the salient point of the implementation and then show some usage samples.

 

Implementation

 

Basic ItemsControl

The main class is MenuItem. Inspired by WinForm, WPF, I did create a Menu class but it’s just an optional container with a different layout (and it also hide the arrow on the side).

The UI, at its most basic is defined as follows (non necessary styling element removed)

<Style TargetType="local:MenuItem">
    <Setter Property="ItemsPanel">
        <Setter.Value>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Vertical"/>
            </ItemsPanelTemplate>
        </Setter.Value>
    </Setter>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:MenuItem">
                <Grid Background="Transparent">
                    <Border x:Name="PART_HighlightBg" Margin="2" Opacity="0"
                            Background="{Binding HighlightBrush, Source={StaticResource SystemBrushes}}"
                        />
                    <ContentPresenter
                        Margin="4"
                        Content="{TemplateBinding Header}"
                        ContentTemplate="{TemplateBinding HeaderTemplate}"
                            />
                    <Popup x:Name="PART_Popup" IsOpen="False">
                        <ItemsPresenter />
                    </Popup>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

There is a Transparent Grid background grid for hit testing, a border at lowest Z order for highlight, the content presenter for the header and an ItemsPresenter in a Popup for children.

 

Now the most basic implementation of an ItemsControl just setup children.
I also copy the style in PrepareContainerForItemOverride, as MenuItem’s children item are whole new MenuItem with the default style. This code make sure all children and children’s children and so on looks the same as the top level MenuItem, in case it has been styled.

public class MenuItem : HeaderedItemsControl
{
    public MenuItem()
    {
        this.DefaultStyleKey = typeof(MenuItem);
        ((INotifyCollectionChanged)Items).CollectionChanged += delegate { HasChildren = Items.Count > 0; };
    }

    #region ItemsControl override

    protected override DependencyObject GetContainerForItemOverride()
    {
        return new MenuItem();
    }
    protected override bool IsItemItsOwnContainerOverride(object item)
    {
        return item is MenuItem;
    }
    protected override void ClearContainerForItemOverride(DependencyObject element, object item)
    {
        base.ClearContainerForItemOverride(element, item);
    }
    protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
    {
        // copy import UI value to child element automatically...
        // copy first in case something is set in the template (would be set in base.XXX())
        var mi = (MenuItem)element;
        mi.Style = Style;
        mi.Background = Background;
        mi.Foreground = Foreground;
        mi.BorderBrush = BorderBrush;
        mi.BorderThickness = BorderThickness;

        base.PrepareContainerForItemOverride(element, item);
    }

    #endregion
}

 

To manage the popup by code I added an IsOpen property

#region IsOpen

internal Popup GetPopup()
{
    return this.GetTemplateChild("PART_Popup") as Popup;
}

public bool IsOpen
{
    get
    {
        var p = GetPopup();
        return p != null && p.IsOpen;
    }
    set
    {
        // separator don't open
        value = value && !IsSeparator;

        var p = GetPopup();
        if (p == null)
            return;

        if (p.IsOpen == value)
            return;

        p.IsOpen = value && Items.Count() > 0;
        MenuPopupManager.OnOpen(this, value);
    }
}

#endregion

 

Mouse Handling

Now all is needed is to overrides OnMouseEnter, OnMouseLeave, OnMouseLeftButtonDown, OnMouseLeftButtonUp.

Most of them do little and delegate the thinking to the class MenuPopupManager. This class maintains a list of all open popup, position the Popup appropriately, hide those Popup that need be hidden and close all MenuItem after a timeout.

Ideally it should close all menu when the user click outside the MenuItem but I could not get it to work reliably.

A skeleton implementation looks like that

public static class MenuPopupManager
{
    #region PopupData, CurrentPopupData

    internal class PopupData
    {
        public PopupData()
        {
        }

        public List<MenuItem> Items = new List<MenuItem>();
        public Timer Timer;
        public Dictionary<MenuItem, PlacementMode> Placements = new Dictionary<MenuItem, PlacementMode>();

        public MenuItem Hovering;
    }

    static PopupData CurrentPopupData;

    #endregion

    #region PlacePopup

    internal static void PlacePopup(MenuItem mi, Popup p)
    {
        Action place = delegate
        {
            // placement logi
        };

        // placement needs size calculation, need be done after being shown
        // otherwise DesiredSize will always be {0,0}, despite calls to Measure() !!??
        if (mi.IsOpen)
        {
            place();
        }
        else
        {
            EventHandler onOpen = null;
            onOpen = delegate
            {
                place();
                p.Opened -= onOpen;
            };
            p.Opened += onOpen;
        }
    }

    #endregion

    #region OnMouseEnter(), OnMouseLeave(), OnOpen(), OnFinish(),

    internal static void OnMouseEnter(MenuItem item)
    {
        var pd = CurrentPopupData;
        if (pd == null)
            return;
        pd.Hovering = item;
    }
    internal static void OnMouseLeave(MenuItem item)
    {
        var pd = CurrentPopupData;
        if (pd == null)
            return;
        pd.Hovering = null;
    }
    internal static void OnOpen(MenuItem item, bool open)
    {
        var pd = CurrentPopupData;
        var pi = item.GetParentItem();
        // update CurrentPopupData and close popup that should be closed
    }
    internal static void OnFinish(MenuItem item)
    {
        // close everything and update CurrentPopupData
    }

    #endregion
}

 

Obviously MenuItem implement Click and triggering an ICommand and hiding itself after that. If no Click event handler or Command is defined it will do nothing (including not closing the menu), making it easy to embed advanced control in popup menu.

#region Command

public ICommand Command
{
    get { return (ICommand)GetValue(CommandProperty); }
    set { SetValue(CommandProperty, value); }
}

public static readonly DependencyProperty CommandProperty =
    DependencyProperty.Register("Command", typeof(ICommand), typeof(MenuItem), new PropertyMetadata(null));

#endregion

#region CommandParameter

public object CommandParameter
{
    get { return (object)GetValue(CommandParameterProperty); }
    set { SetValue(CommandParameterProperty, value); }
}

// Using a DependencyProperty as the backing store for CommandParameter.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty CommandParameterProperty =
    DependencyProperty.Register("CommandParameter", typeof(object), typeof(MenuItem), new PropertyMetadata(null));

#endregion

#region Click, OnClick()

public event RoutedEventHandler Click;

protected virtual void OnClick(RoutedEventArgs e)
{
    if (IsSeparator)
        return;

    var c = Command;
    var p = CommandParameter;
    if (c != null && c.CanExecute(p))
    {
        c.Execute(p);
        MenuPopupManager.OnFinish(this);
    }

    var he = Click;
    if (he != null)
    {
        he.Invoke(this, e);
        MenuPopupManager.OnFinish(this);
    }
}

#endregion

 

Styling

And lastly I added a bit of Styling. I downloaded the Silverlight toolkit and took the SystemBrushes class from the SystemColor theme’s source code. Used various SystemBrushes’s Brush for styling the Menu and MenuItem.

The nice silverish background brush of the Menu is SystemBrushes.ButtonGradient
image

 

For the mouse hover effect where an highlight color (quickly but progressively) appears I finally took the time to use animation and view state. It proved to be real easy!

First I setup 2 states. One which progressively increased the opacity of  the background control (PART_HighlightBg). The second state does nothing, in effect reverting the opacity change instantly. I could have used an animation too but I liked the shorter template and result is good too.

<ControlTemplate TargetType="local:MenuItem">
    <Grid Background="Transparent">
        <vsm:VisualStateManager.VisualStateGroups>
            <vsm:VisualStateGroup x:Name="Highlight">
                <vsm:VisualState x:Name="HighlightOn">
                    <Storyboard>
                        <DoubleAnimation
                            Duration="0:0:0.3" To="0.4"
                            Storyboard.TargetName="PART_HighlightBg" Storyboard.TargetProperty="Opacity">
                            <DoubleAnimation.EasingFunction>
                                <ExponentialEase Exponent="3" EasingMode="EaseInOut"/>
                            </DoubleAnimation.EasingFunction>
                        </DoubleAnimation>
                    </Storyboard>
                </vsm:VisualState>
                <vsm:VisualState x:Name="HighlightOff"/>
            </vsm:VisualStateGroup>
        </vsm:VisualStateManager.VisualStateGroups>
        <Border x:Name="PART_HighlightBg"
            Margin="2" Opacity="0"
            Background="{Binding HighlightBrush, Source={StaticResource SystemBrushes}}"
            BorderBrush="{Binding ControlDarkBrush, Source={StaticResource SystemBrushes}}"
            BorderThickness="{TemplateBinding BorderThickness}"
            />
        <!-- other controls -->
    </Grid>
</ControlTemplate>

 

Then I simply setup the state in mouse handler

protected override void OnMouseEnter(MouseEventArgs e)
{
    base.OnMouseEnter(e);
    MenuPopupManager.OnMouseEnter(this);
    VisualStateManager.GoToState(this, "HighlightOn", true);
}
protected override void OnMouseLeave(MouseEventArgs e)
{
    base.OnMouseLeave(e);
    MenuPopupManager.OnMouseLeave(this);
    VisualStateManager.GoToState(this, "HighlightOff", true);
}

 

Examples

And now some example on how to use this control.

 

Inline definition

image

<Grid x:Name="LayoutRoot" Background="White">

    <local:Menu Margin="10,0">
        <local:MenuItem Header="File lala">
            <local:MenuItem Header="Foo">
                <local:MenuItem Header="tadat"/>
                <local:MenuItem Header="Footaise">
                    <local:MenuItem Header="tadat"/>
                    <local:MenuItem Header="tadat"/>
                    <local:MenuItem Header="tadat"/>
                </local:MenuItem>
                <local:MenuItem Header="tadat"/>
                <local:MenuItem Header="tadat"/>
            </local:MenuItem>
            <local:MenuItem IsSeparator="True"/>
            <local:MenuItem Header="tadat"/>
            <local:MenuItem Header="tadat"/>
        </local:MenuItem>
    </local:Menu>
    
</Grid>

 

MVVM - databinding

First I’ll define a data model used for my MVVM usage

public class ModelCommand : ICommand
{
    ModelItem item;
    public ModelCommand(ModelItem item) { this.item = item; }
    public bool CanExecute(object parameter) { return true; }
    public event EventHandler CanExecuteChanged;
    public void Execute(object parameter) { MessageBox.Show("Clicked: " + item.Name); }
}
public class ModelItem
{
    public ModelItem()
    {
        Items = new List<ModelItem>();
        Command = new ModelCommand(this);
    }
    public string Name { get; set; }
    public List<ModelItem> Items { get; private set; }
    public ICommand Command { get; private set; }
}
public class Model
{
    public Model()
    {
        // initialize a recursive list of Items and their children
        // and their children and their children's children and so on...
    }
    public List<ModelItem> Items { get; private set; }
}

 

Then I can do some simple binding

image

<local:Menu Margin="10,0" HorizontalAlignment="Right">
    <local:MenuItem Header="Persons" ItemsSource="{Binding Items, Source={StaticResource MM}}">
        <local:MenuItem.ItemContainerStyle>
            <Style TargetType="local:MenuItem">
                <Setter Property="Command" Value="{Binding Command}"/>
            </Style>
        </local:MenuItem.ItemContainerStyle>
        <local:MenuItem.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Name}"/>
            </DataTemplate>
        </local:MenuItem.ItemTemplate>
    </local:MenuItem>
</local:Menu>

Remark to setup the Command on the MenuItem I should use the ItemContainerStyle property.

 

Hierarchical binding

image

<local:Menu
    Margin="10,0" HorizontalAlignment="Right" VerticalAlignment="Bottom" PopupPlacement="Top" CornerRadius="5,5,0,0" BorderThickness="1,1,1,0">
    <local:MenuItem Header="Persons" Command="{Binding Command}" ItemsSource="{Binding Items, Source={StaticResource MM}}">
        <local:MenuItem.ItemContainerStyle>
            <Style TargetType="local:MenuItem">
                <Setter Property="Command" Value="{Binding Command}"/>
            </Style>
        </local:MenuItem.ItemContainerStyle>
        <local:MenuItem.ItemTemplate>
            <sdk:HierarchicalDataTemplate ItemsSource="{Binding Items}">
                <TextBlock Text="{Binding Name}"/>
            </sdk:HierarchicalDataTemplate>
        </local:MenuItem.ItemTemplate>
    </local:MenuItem>
</local:Menu>

 

Styling

I can also add a single parentless MenuItem anywhere. And I can style it too, setup the background brush for instance (whether it’s parentless or not) and the value will flow down.

image

<local:MenuItem
    Margin="100,0" HorizontalAlignment="Right" VerticalAlignment="Bottom" PopupPlacement="Top" BorderThickness="1,1,1,0"
    Background="Aquamarine" Header="Persons" ItemsSource="{Binding Items, Source={StaticResource MM}}">
    <local:MenuItem.ItemContainerStyle>
        <Style TargetType="local:MenuItem">
            <Setter Property="Command" Value="{Binding Command}"/>
        </Style>
    </local:MenuItem.ItemContainerStyle>
    <local:MenuItem.ItemTemplate>
        <sdk:HierarchicalDataTemplate ItemsSource="{Binding Items}">
            <TextBlock Text="{Binding Name}"/>
        </sdk:HierarchicalDataTemplate>
    </local:MenuItem.ItemTemplate>
</local:MenuItem>

Permalink | Comments (0) | Post RSSRSS comment feed

Discovering D and Visual Studio (continued…)

Thanks for the feedback from the previous article, I know now that:

  • DFL should work with the BCL in D2, but it just doesn’t at the moment, due to some repository snafu…
  • Visual D had building problem due to… tool chain issues! This page about Visual D known issues explain what’s going on and how to fix it!

Hence by modifying C:\D\dmd2\windows\bin\sc.ini

To be like (changes in maroon)

[Version]
version=7.51 Build 020

[Environment]
LIB="%@P%\..\lib";\dm\lib;%DMD_LIB%
DFLAGS="-I%@P%\..\..\src\phobos" "-I%@P%\..\..\src\druntime\import"
LINKCMD=%@P%\link.exe

 

I was able to make do with a much more satisfactory config:

image

 

Community support

Maybe I should mention that D, despite being a marginal language (as in: little know) has a surprisingly pleasantly vibrant community support.

Also the Digitalmars page on D is full of interesting link (particularly Tools => More Tools) but I’d like to grab the attention on 2 communities:

  • D Source forums
  • Digitalmars newsgroups (news server: news.digitalmars.com). Can be accessed with Windows Live Mail or Thunderbird for example. Just create a new newsgroup account

    image

 

Initial sample reloaded

I though it might be a good idea to have DGui in my projects (i.e. as source code / solution’s project) and link my test project against it. This way I can look, play with and learn from some D code written by a more knowledgeable D programmer than I!

It turns out there are key difference with what would happen with trying to do that with C#. Perhaps it behave like it will with Visual C++, I couldn’t tell, not having using it enough…

At any rate here is what happened

  • I created a new Visual D static library
  • Created the directory hierarchy of the DGui source (it’s important for D, just like Java, it reflects package organisation)
  • Added the existing file from DGui
  • And build, successfully first try! (Nicely enough DGui has no other dependency that Phobos / BCL)

image

Now, when looking at the file system, there was no D file in my project’s directory! All these files in the solution explorer linked to the one and only original files.

Hence, I shouldn’t change the import file path of my test project. For the record they remained pointing to
C:\D\dgui

image

 

 

Updating the dependencies

But I still needed to point to my build version of DGui, hence change the library path of my test project. And I also need to set my test project to depends on my (local) dgui project.

Right click on project => Project dependencies => select dgui

 

image image

Set the library search path to the project build location: C:\Dev\DTest\dgui\Debug.

image

F5, it builds and run!

Now for the surprisingly good part, in my test app, if I F12 on one of the class I use, VS actually goes there!

But no method list (yet?).

Just for fun I added a “std.stdio.writeln("hello");” in initialization looking method (I still knows nothing of D, hey!) and .. it printed!

 

Getting rid of the console

I’m trying a GUI sample, yet I have a nagging MS-Dos console appearing every time I run. To get rid of it I should pass a special flags to the linker (in Additional options) so it will mark the executable as being a windows application (i.e. no console attached!)

-L/SUBSYSTEM:windows:4

image

 

 

A brief look at the source code

So far I haven’t coded anything yet! Just wanted to feel comfortable with the development experience… But will post about source code next time!

Anyhow, below is the source code of my test app (straight from events.d in DGui’s samples):

module events;

import dgui.all;

class MainForm: Form
{
    private Button _btnOk;
    
    public this()
    {
        this.text = "DGui Events";
        this.size = Size(300, 250);
        this.startPosition = FormStartPosition.CENTER_SCREEN; // Set Form Position
        
        this._btnOk = new Button();
        this._btnOk.text = "Click Me!";
        this._btnOk.dock = DockStyle.FILL; // Fill the whole form area
        this._btnOk.parent = this;
        this._btnOk.click.attach(&this.onBtnOkClick); //Attach the click event with the selected procedure
    }
    
    private void onBtnOkClick(Control sender, EventArgs e)
    {
        // Display a message box
        MsgBox.show("OnClick", "Button.onClick()");
    }
}

int main(string[] args)
{
    return Application.run(new MainForm()); // Start the application
}     

The exciting things I can see from a quick look at the source is that D supports properties, events and DGui looks quite similar to WinForm!

Except for the lack (at the moment) of designer Sad smile 

Ho well, I just plan to make simple wizards anyway…


Tags:
Categories: D | Source Code
Permalink | Comments (0) | Post RSSRSS comment feed

D for .NET programmer

Recently it appeared to me that, with D, I could finally write solve a long standing problem, that is write a good advanced installer.

What appealed to me where the following features:

  • Statically linked. Produce an exe with no dependency! (Save for win32 that is, fair enough!)
  • Elegant syntax on par with C# and Java
  • Compile time executable code, making it easy to include “resource” files (zip and include file to install inside the installer)
  • Finally with a nice IDE and GUI library (almost as good as a slim WinForm)
  • Can directly call into C (I plan to call into the MsiXXX function) at no cost!

So I started to try to use it.

Ho boy, just having 1 program, using 1 custom library to compile was quite an odyssey! Hopefully this blog entry could save future language explorer!

 

1. Installation

First thing first, you’ll need to download and installer the D SDK (the compiler, linker, runtime library, etc… The whole shebang!) That can be found there:

http://www.digitalmars.com/d/download.html

Unzip the latest version of DMD somewhere, you are done! Now that was easy.

And if you heard about DM / DMC, don’t worry about it. It was for D1, just ignore it.

 

2. IDE

Well, arguably you can go with the command line. The compiler flags and linker flags are simple enough. Yet I’m too spoiled by VS, I need my IDE!
In fact, on the D2 page, following the Editor link on the left (in Tools), I found 3 which were attractive: D-IDE, Entice and Visual D.

D-IDE is cool because it’s C# but that’s about it (it’s still basic and buggy, sadly).

Entice is a GUI designer. It was exciting at first, but I deleted it in the end because it is used for DFL or DWT which both requires to overwrite the base runtime library (aka Phobos). Which I don’t quite feel like doing yet.

And, finally, Visual D, a plugin for Visual Studio. Create D projects in Visual Studio, yoohoo! Download and install it now.

image

 

3. My first program

You can create a new module with Visual D (new D project), press F5, and voila, hello D world!

 

Now I wanted to create a GUI Hello world!

I had a look at DFL and DWT. DFL looks lighter / smaller and good enough for my need. Yet they both required Tango, which seem to be a popular D BCL (aka Phobos) replacement. Well that ended it for me. Tango might be popular but I’m not going to replace my BCL when I can’t even compile a(n advanced) program yet (you’ll see my library trouble next).

Finally I found DGui. Looking like WinForm. Depending just on Phobos (the BCL).

I downloaded, unzipped, copied a sample (in the sample directory) into my “hello.d” file and tried to compile.

Now the problems started…

First error would be:

Error    1    Error: module all is in file 'dgui\all.d' which cannot be read    C:\Dev\DTest\DTest1\hello.d    3

 

This is due to the import statement at the top:

import dgui.all;

 

3.a. file and hierarchy structure

D has 2 ‘code unit’ if I may call it that way. Module and package. It’s a bit like classes and packages in Java. A module is everything in one file. I’m not yet sure if the module should be named after the file it’s in, but that, at least, seems to be the convention.

The package is a folder of source code.

When I wrote “import dgui.all” the compiler looked for the module “all” (i.e. the file “all.d”) in the package (i.e. directory) “dgui”.

I need to specify the search path for those modules / packages for this to work. Go to project properties => Configuration => DMD and add the path to the source of the library.

image

Press F5, now we get an other error! (i.e. we progressed!…)

 

3.b. declaration file

So, what’s going on? And why does it look for a ‘.d’ file (a source file! I want to use the library not recompile it!)

Here is what happen, the compiler needs some declaration to describe the function that are going to be called. They could be defined inline in the program (if calling into Win32 from D for example, much like DllImport in C#), but more generally you it will look at declaration file defined by the library. There are 2 options there:

  1. Unlike C, where the developer should maintain and synchronise a definition (header / .h) and an implementation (.c /.cpp) file, D can use a single implementation file for both purpose. Hence it will look in the original D source file.
  2. If there is a need to protect some intellectual property, the developer can an generate an ‘interface file’ when compiling (ordinary D file with the ‘.di’ extension and only declaration inside) and deploy these instead of the source code.

 

3.c. linking

Much like C, C++ and other native environment, compiling D is a 2 stage program. First compiling the source files into object files (.o) and then linking all those files into various target (library (.lib or .dll), executable (.exe)).

But it’s all done on the key stroke of F5, hence a catch all usage meaning of compile for both compiling (making object files) and linking (creating the final output).

When I ‘compile’ now I get those kind of errors:

Error    1    Error 42: Symbol Undefined _D4dgui4form4Form13startPositionMFNdE4dgui4core5enums17FormStartPositionZv    C:\Dev\DTest\DTest1\   
Error    2    Error 42: Symbol Undefined _D4dgui7control7Control4sizeMFNdS4dgui4core8geometry4SizeZv    C:\Dev\DTest\DTest1\   
Error    3    Error 42: Symbol Undefined _D4dgui6button6Button6__ctorMFZC4dgui6button6Button    C:\Dev\DTest\DTest1\   
etc…

And so on.

'Symbol alien_looking_name’ generally mean a linker problem. It just happened that Visual D create a “buildlog.html” as well as a “$(project).build.cmd” file in the output folder. So you can see what command it ran to compile and the link the program.

Now I guess I need to include dgui.lib in my project. So go to project properties => linker and set the libraries  and search path.

image

Now it still doesn’t compile and give me this warning:

Warning    1    Warning 2: File Not Found dgui.lib    C:\Dev\DTest\DTest1\

 

I can see in the cmd file or build log that it build with the following command

Command Line set PATH=C:\D\dmd2\windows\bin;C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\\bin;%PATH% set DMD_LIB=;C:\D\dgui\lib dmd …

Now I can see in D Linker’s documentation that DMD_LIB is not used, it is LIB that is used. I guess it’s a little bug with Visual D (maybe it was made for D1?) at any rate, I solved it by setting the whole path to the DGui library!

image

F5… build succeeded!

 

4. Bonus, compile DGui

Well, one could add all the file of DGui in Visual D I guess. But DGui comes with a “dgui_build_rel.bat”, I should use it, don’t you think?

Well all I had to do was to add the path to dmd.exe (i.e. C:\D\dmd2\windows\bin\) to the PATH environment variable.

As found in System Property => Advance System Settings => Advanced => Environment Variables…

image

Now I can just click on the bat file and… DGui build successfully, that was easy! Smile

 

5. Done

That’s it, I show how to install D, install a library and compile a program using it!


Tags:
Categories: D | Source Code | Visual Studio
Permalink | Comments (0) | Post RSSRSS comment feed

Waitblock

Sometimes that’s the little thing that one find useful.
Here is such a thing!

 

In our app there are lots of time where an operation can be long and we needs to display a wait cursor (Note: unfortunately we use 3rd party data which are impossible to thread, literally!).

I often found this operation cumbersome. You know, store the original cursor, change the cursor, make a try/finally block, restore the original cursor.

So here is a very simple class which do just that very nicely, just with a single line using statement, as in:

using (new WaitCursorBlock()){…}

 

And here is the code of this simple class:

public class WaitCursorBlock : IDisposable
{
  Cursor originalCursor;
  public WaitCursorBlock()
  {
    originalCursor = Mouse.OverrideCursor;
    Mouse.OverrideCursor = Cursors.Wait;
  }
  public void Dispose() {Mouse.OverrideCursor = originalCursor;}
}


Categories: General | Source Code | WPF
Permalink | Comments (0) | Post RSSRSS comment feed

The Last IValueConverter

source code

  • update (2008/09/25): added a ScriptExtension and mentioned the LambdaExtension.

 

Introduction

Back when I started WPF I though Data Binding was great but, sometimes, I just couldn’t get the information I needed from the value easily enough.

Here I would describe a simple fictitious problem. Let says I want to display a list of integer, with a background color depending whether the number is even or odd.

image

The XAML for this might looks like that:

<ListBox ItemsSource="{Binding List, ElementName=root}"><ListBox.ItemTemplate><DataTemplate><TextBlock Text="{Binding}"Background="{?? what shall I put here ??}"/></DataTemplate></ListBox.ItemTemplate></ListBox>

 

Upon initial investigation

Initially I was thinking to write my own MarkupExtension which would have an expression property and run some sort of script.

It turns out:

  1. You can’t do that. The Binding extension do some things you can’t do. For example you can’t always know when a property change. You can’t find an object by name (as with the ElementName property of the Binding extension), etc…
  2. There is a better way. The Binding extension already does all the work, all you need is a smart IValueConverter to set to the Binding.Converter property.

 

What’s an IValueConverter

When you do data binding in WPF, the framework automatically do some conversion for you, so that the provided value can be used to set the target property. But what if you want to modify the behavior a little bit and decide yourself how to do the conversion?
You can, by setting the Converter property of the binding to an IValueConverter!

public class MyConverter : IValueConverter{#region IValueConverter Memberspublic object Convert(object value, Type targetType, object parameter, CultureInfo culture){throw new NotImplementedException();}public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture){throw new NotImplementedException();}#endregion}

 

Now the code can look like that:

Window1.xaml

<Window.Resources><local:ColorConverter x:Key="int2color"/></Window.Resources><Grid><ListBox ItemsSource="{Binding List, ElementName=root}"><ListBox.ItemTemplate><DataTemplate><TextBlock Text="{Binding}"Background="{Binding Converter={StaticResource int2color}}"/></DataTemplate></ListBox.ItemTemplate></ListBox></Grid>

ColorConverter.cs

public class ColorConverter : IValueConverter{public object Convert(object value, Type targetType, object parameter, CultureInfo culture){if (!(value is int))return null;int i = (int)value;return i % 2 == 0 ? Brushes.Red : Brushes.Green;}}

 

Now every time I encounter such a problem I can write a new IValueConverter.
Wait, NOOOOOOOOOOO!!!!…….

Seriously can’t I just write some build-in (in the XAML) quick code?

 

Script investigation

Initially I found Ken Boggart converters (Sorry, no link, my Google skills have apparently decreased with old age). They even have a script based converter.
I didn’t like them enough because it was using a quick and dirty script engine, tightly coupled with the value converter.

 

I also thought of using IronPython as the script engine. And while this is a great and mature project there is one thing which kills me with Python. Code blocks are defined with the indentation! Seriously…
And it’s just not an aesthetic problem. Imagine a moment: write some embedded python script into your xaml, “accidentally” reformat the document (CTRL+E,D) and voila, you have just broken your python script!
Tsss.. too bad.

 

I thought of writing my own simple interpreter. My initial plan was to use ANTLR. And while it’s a great project, with a great IDE, it’s a bit tedious to test / develop a grammar in C#, as all the tools are geared towards Java. Then I discovered Irony.
I haven’t used it much (compared to ANTLR) but my first impression is:

  • Writing the grammar is easier, better looking and definitely more C# friendly than ANTLR.
  • The next stage, the interpreter or AST visiting is much more tedious.
  • Plus ANTLR makes it very super easy to embed action directly into the parser, enabling a all in one parsing / interpreting phase for example.

But then, it turns out, I didn’t need to write my own Irony powered interpreter / script engine, there is already one: Script.NET.

 

The ScriptConverter

Script.NET is a quick and easy script engine that can, as all self-respecting .NET script engine, call upon all .NET methods and classes. It can easily be embedded into your application and the syntax is so simple, you can teach it yourself in 21 minutes! Great, just what I was looking for. And, of course, it uses curly braces to define code blocks!

The current version of Script.NET, at this time (Sept. 2008), doesn’t support the latest Irony. It ships as 3 DLLs on it own (plus the outdated Irony) and it has a dependency on Windows Forms. I decided to “fix” all of that and the version shipped in the zipped source code is not the original Script.NET source code, but a custom version which comes as only 1 DLL, with no WinForms dependency.

In my converter I had to, obviously, add an expression property (for the script). Less obvious but as critical, I had to add a known types property, so that the script could call on static property or on some classes constructor.

 

How does it works

Well to starts with you create a script interpreter with just one line

Script script = Script.Compile("my script string");

Then you can define some global variables or add known types as follows:

script.Context.SetItem("value", ContextItem.Variable, value);script.Context.SetItem("Brushes", ContextItem.Type, typeof(Brushes));

And then you can evaluate it in one line as well

return script.Execute();

 

A little problem which stopped me for a few minute was, how do I return something from my script?

Well a simple value is an expression and the last computed expression is the returned value. For example “1” is a valid script which return 1.

Script.NET doesn’t support the (bool_expr ? true_expr : false_expr) operator, but you can express it as:

if (bool_expr)true_expr;elsefalse_expr; 

 

First Solution

Now let’s write my script converter.

ScriptConverter.cs

[ContentPropertyAttribute("Expression")]public class ScriptConverter : IValueConverter{Script script;TypeDictionary knownTypes = new TypeDictionary();string expression;public string Expression{get { return expression; }set{expression = value;script = Script.Compile(expression);SetTypes(script, knownTypes);}}public class TypeDictionary : Dictionary<string, Type> { }public TypeDictionary KnownTypes{get { return knownTypes; }set{knownTypes = value;SetTypes(script, knownTypes);}}static void SetTypes(Script ascript, TypeDictionary types){foreach (var item in types)ascript.Context.SetItem(item.Key, ContextItem.Type, item.Value);}public object Convert(object value, Type targetType, object parameter, CultureInfo culture){script.Context.SetItem("value", ContextItem.Variable, value);script.Context.SetItem("param", ContextItem.Variable, parameter);script.Context.SetItem("culture", ContextItem.Variable, culture); 
return script.Execute();}}

 

Armed with this ultra cool converter, my XAML is now ready to rock!
You will notice that I initialized the Script with a list of type it need to know to evaluate the expression.

Window1.xaml

<Window.Resources><local:ScriptConverter x:Key="int2color"><local:ScriptConverter.KnownTypes><local:TypeDictionary><x:Type x:Key="Brushes" TypeName="Brushes"/></local:TypeDictionary></local:ScriptConverter.KnownTypes>if( value % 2 == 0 )Brushes.Red;elseBrushes.Green;</local:ScriptConverter></Window.Resources><Grid><ListBox ItemsSource="{Binding List, ElementName=root}"><ListBox.ItemTemplate><DataTemplate><TextBlock Text="{Binding}"Background="{Binding Converter={StaticResource int2color}}"/></DataTemplate></ListBox.ItemTemplate></ListBox></Grid>

 

Further Simplification

Okay now I have done it all in XAML, and it works well. But I exchanged a useless C# file for a bloated XAML! smile_embaressed

What I would like to is just put the expression in the converter, like a lambda expression.

It’s where MarkupExtension(s) will come to the rescue. Basically extension are custom way to create object with code running in the XAML. {x:Type …}, {x:Static …}, {Binding …} are all MarkupExtensions.

Create your own extension is easy. Okay let’s write the simplified XAML I really would like to see, and then write the extension:

<ListBox ItemsSource="{Binding List, ElementName=root}"><ListBox.ItemTemplate><DataTemplate><WrapPanel Orientation="Horizontal"><TextBlock Text="{Binding}"Background="{Binding 
Converter={local:Script
'if(value%2==0) Brushes.Red; else Brushes.Green;',
{
x:Type Brushes}}}"/></WrapPanel></DataTemplate></ListBox.ItemTemplate></ListBox>

Here we go, it’s a much simpler XAML code! In the Converter parameter of the binding I passed an extension which directly created my converter. This extension will take the following parameter: a script string and a list of type necessary for the script to run.

Initially I was planning to use an extension constructor like that:

public ScriptExtension(string code, params Type[] types){//....}

But it won’t work because XAML won’t recognize the “params Type[]” construct, and it will expect 2 and only 2 parameter, the second being array of types. There are also a few other problems with markup extension parameter (XAML choose them sorely on the number of parameter, it doesn’t do any type matching). Finally I used this code

public class ScriptExtension : MarkupExtension{public ScriptExtension(string code) { Init(code); }public ScriptExtension(string code, Type type) { Init(code, type); }public ScriptExtension(string code, Type type, Type t2) { Init(code, type, t2); }protected ScriptExtension(string code, params Type[] types) { Init(code, types); }void Init(string code, params Type[] types){Code = code;TypeDictionary dict = new TypeDictionary();foreach (var aType in types)dict[aType.Name] = aType;KnownTypes = dict;}public string Code { get; private set; }public TypeDictionary KnownTypes { get; private set; }

// return the converter herepublic override object ProvideValue(IServiceProvider isp){return new ScriptConverter(Code) { KnownTypes = KnownTypes };}}

 

 

 

 

 

Designer Issue

It worked quite nicely at runtime. But there was a problem with the designer. It just could not work with those multiple constructor. In the end I created multiple ScriptExtension subclass called Script0Extension, Script1Extension, Script2Extension, etc… depending on the number of type parameters.

Hence the final XAML look like that

<ListBox.ItemTemplate><DataTemplate><WrapPanel Orientation="Horizontal"><TextBlock Text="{Binding}"Background="{Binding 
Converter={local:Script1
'if(value%2==0) Brushes.Red; else Brushes.Green;',
{
x:Type Brushes}}}"/></WrapPanel></DataTemplate></ListBox.ItemTemplate>

 

Alternate Solution

At this stage we got a pretty good solution for many problem. But then I investigated this LambdaExtension. It’s really cool and I added it to my personal toolbox and to this ExpressionExplorer project as well.

It lets you define converters with Lambda expression, for exemple:

<TextBlock Text='{Binding Source={x:Static s:DateTime.Now},Converter={fix:Lambda "dt=>dt.ToShortTimeString()"}}'>  

It’s pretty good and I imagine Lambda expression would be way faster that my script expression. However it suffers from a few problems:

  • you cannot use the other parameters of the converter method (parameter and culture).
  • you cannot reference any type, you cannot return a Brush, i.e. it cannot solve my simple problem!

But I guess that, even with these shortcomings, it can still make an excellent converter in DataTriggers.

 

Additional Information

Here are some people or pages that have inspired me while working on this code.

Later on I had these additional feedback which proved to be very valuable.

First Orcun Topdagi has improved his LambdaExtension further, as you can check out on his blog about WPFix2 and WPFix3. Wow those are real hot goodies. I might even ditch my extension in his favor! But I have to spend some more time playing with it first (kind of super busy right now).

Also Daniel Paull has some very interesting blog posts (here, here and here) on how to do something similar with the DLR. What I really like about these article it’s that they are an easy introduction to the DLR.


source code.

 

 


Categories: Source Code | WPF
Permalink | Comments (1) | Post RSSRSS comment feed