vladeck's junkyard

designer wannabe


Leave a comment

Type-safe getter and setter for Kendo UI (using Typescript) – Part 2

In my previous post Type-safe getter and setter for Kendo UI (using Typescript) – Part 1 I wrote about type-safe way to use Kendo UI Observable’s getter and setter with Typescript, little Javascript magic and one helper class.

Like with any code, one can always iterate and improve upon. So lets dive in…

For starters, I did introduce type-safety and constraints on the strings I was using; however, mere fact that I need to have ObservableHelper and constantly use it to get or set Observable’s value is a real pain. Ideally, we want to get rid of the helper and “transfer” the responsibility of get and set to actual Observable instance.

In order to do that, first thing is to define interface of the Observable:

interface IObservable<T> {
    get<K extends keyof T>(key: K): T[K];
    set<K extends keyof T>(key: K, value: T[K]): void;
}

Next, I will get rid of the ObservableHelper (parts I do not need) and just keep (parts I do need; a little modified) newObservable as a exported function which can easily be scoped by using any module system you prefer (I will omit export keyword).

function newObservable<TIn, TOut extends IObservable<TIn>>(obj: TIn): TOut {
    return kendo.observable(obj) as any;
}

Now that I have that in place, I can write something like:

interface IPerson {
    name: string;
    birth: Date;
    address: string;
}

interface IObservablePerson extends IPerson, IObservable<IPerson> {}

// Create observable instance of IPerson.
const p: IObservablePerson = newObservable<IPerson, IObservablePerson>({
    name: "John",
    birth: new Date(),
    address: "Some Street 27/3",
});

const address: string = p.get("address");
// address === "Some Street 27/3"

p.set("address", "New Street 42/7");
// p.address === "New Street 42/7"

Much better 🙂


Leave a comment

Type-safe getter and setter for Kendo UI (using Typescript) – Part 1

Kendo’s Typescript definitions are not, well, very type-safe. Let’s take a look at a simple instance of Observable object in Kendo UI:

const p: any = kendo.observable({
    name: "John",
    birth: new Date(),
    address: "Some Street 27/3",
});

Now, in order to piggy-back on Kendo’s MVVM framework, one would need to use Kendo’s get and set methods:

const address: string = p.get("address");
// address === "Some Street 27/3"

p.set("address", "New Street 42/7");
// p.address === "New Street 42/7"

For me, this is a big no-no as using strings in your code can be error prone and there is not really much of type-safety here. However, if we are to use the keyof Index Type Query introduced in Typescript 2.1 and some Javascript magic, we can write a little helper:

class ObservableHelper {
    public static newObservable<T>(obj: T): T {
        return kendo.observable(obj) as any;
    }

    public static get<T, K extends keyof T>(obj: T, key: K): T[K] {
        return (obj as any).get(key);
    }

    public static set<T, K extends keyof T>(obj: T, key: K, value: T[K]): void {
        (obj as any).set(key, value);
    }
}

Lets now try to use this helper:

interface IPerson {
    name: string;
    birth: Date;
    address: string;
}

const p: IPerson = ObservableHelper.newObservable({
    name: "John",
    birth: new Date(),
    address: "Some Street 27/3",
});

const address: string = ObservableHelper.get(p, "address");
// address === "Some Street 27/3"

ObservableHelper.set(p, "address", "New Street 42/7");
// p.address === "New Street 42/7"

On the outside, it looks as nothing drastic has changed; how is this better? For starters, keyof will make sure that you don’t enter “adress” or “addres” by mistake – you are using strings, but these string are constrained to the type you are using them for. Another benefit is the type-safety for your Observable instances, as well as types of get’s return and set’s value.

Part 2


1 Comment

SearchTextBox Behavior

Damn, this was a tricky one – a bit of coding, a sprinkle of XAML and here it is. And it works. You can set this Behavior to any TextBox and turn it into Windows 7 search box like thingy. You can define your own Search and Clear icons (ImageSource) and (if you don’t want search-as-you-type) Search command (ICommand). The good thing is that it doesn’t affect the style/template you set for the TextBox, as it is implemented as Adorner.

Download here (rename .DOC to .RAR – wordpress limitation).


2 Comments

Button Popup Behavior

Another behavior that I wrote for my project. The code is simple and there aren’t many things to configure, but it is a good starting point for a nice button popup (many other examples I’ve seen are coded as custom controls; the same can be accomplished using this behavior):

namespace Foo
{
    public class ButtonPopupBehavior : Behavior<Button>
    {
        private readonly Popup popup_;

        public static DependencyProperty PopupProperty =
            DependencyProperty.Register(
                "Popup",
                typeof(UIElement),
                typeof(ButtonPopupBehavior),
                null
            );

        public ButtonPopupBehavior()
        {
            popup_ = new Popup() { StaysOpen = false };
        }

        public UIElement Popup
        {
            get { return (UIElement)GetValue(PopupProperty); }
            set { SetValue(PopupProperty, value); }
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            
            AssociatedObject.Click += OnClick;
            AssociatedObject.IsVisibleChanged += OnIsVisibleChanged;

            popup_.PlacementTarget = AssociatedObject;
            popup_.Child = Popup;
        }

        protected override void OnDetaching()
        {
            AssociatedObject.Click -= OnClick;
            AssociatedObject.IsVisibleChanged -= OnIsVisibleChanged;
            
            base.OnDetaching();
        }

        private void OnClick(object sender, RoutedEventArgs e)
        {
            popup_.IsOpen = true;
        }

        private void OnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            if (!(bool)e.NewValue)
                popup_.IsOpen = false;
        }
    }
}


Leave a comment

Conditional referencing in .NET

If you ever happen to stumble upon a problem of having the need to support multiple versions of the same assembly in your .NET project, read on… Take these two assemblies for example:

Foo.dll (v1.0)

namespace Foo
{
    class Bar
    {
        public void Do()
        {
            Console.WriteLine("Hello, from v1.0 of Bar");
        }
    }
}

Foo.dll (v2.0)

namespace Foo
{
    class Bar
    {
        public void Do(string parameter)
        {
            Console.WriteLine("Hello, from v2.0 of Bar with parameter: {0}", parameter);
        }
    }
}

So, now we have two Foo.dll assemblies that are of different version and we want to use them in our project. First, copy these assemblies to project directory and put them under v1 and v2 directories (at the same level as the .csproj file); then, open the .csproj file and add the following configurations (I’ll do this for DEBUG versions – just copy DEBUG that is already there and modify it; the RELEASE is similar):

<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug - v1|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\Debug\v1\</OutputPath>
    <DefineConstants>TRACE;DEBUG;V_ONE</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug - v2|AnyCPU' ">
    <DebugSymbols>true</DebugSymbols>
    <OutputPath>bin\Debug\v2\</OutputPath>
    <DefineConstants>TRACE;DEBUG;V_TWO</DefineConstants>
    <DebugType>full</DebugType>
    <PlatformTarget>AnyCPU</PlatformTarget>
    <ErrorReport>prompt</ErrorReport>
</PropertyGroup>

And here comes the good part, just below previous modification:

<Choose>
    <When Condition=" '$(Configuration)' == 'Debug - v1'">
      <ItemGroup>
        <Reference Include="Foo">
          <SpecificVersion>False</SpecificVersion>
          <HintPath>v1\Foo.dll</HintPath>
        </Reference>
      </ItemGroup>
    </When>
    <When Condition=" '$(Configuration)' == 'Debug - v2'">
      <ItemGroup>
        <Reference Include="Foo">
          <SpecificVersion>False</SpecificVersion>
          <HintPath>v2\Foo.dll</HintPath>
        </Reference>
      </ItemGroup>
    </When>
</Choose>

Now you are ready to choose your build configuration, and have code support multiple version of the same assembly:

var b = new Foo.Bar();

#if (V_ONE)
   b.Do();
#elif (V_TWO)
   b.Do("Hello");
#endif


Leave a comment

Animated ErrorInfo Behavior

Ever since I wrote Animated Height Behavior, I wanted to create Behavior that will give me nice eye-candy for all my IDataErrorInfo validation needs. You can see the result in the video below, as well as the code of the Behavior. Please note that the image I’m using here is from the Resources (just add 16×16 png image to Resources and name it Error16). GetBitmapSource() is Extension that I wrote for converting System.Drawing.Bitmap to BitmapSource.

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace Foo
{
    public class AnimatedErrorInfoBehavior : Behavior<FrameworkElement>
    {
        private readonly TextBlock textBlock_;
        private Grid errorGrid_;

        private readonly DoubleAnimation increaseHeightAnimation_;
        private readonly Storyboard increaseHeightStoryboard_;

        private readonly DoubleAnimation showAnimation_;
        private readonly Storyboard showStoryboard_;

        private readonly DoubleAnimation decreaseHeightAnimation_;
        private readonly Storyboard descreaseHeightStoryboard_;

        private readonly DoubleAnimation hideAnimation_;
        private readonly Storyboard hideStoryboard_;

        private readonly Dictionary<string, int> errors_;

        public AnimatedErrorInfoBehavior()
        {
            errors_ = new Dictionary<string, int>();
            textBlock_ = new TextBlock();

            increaseHeightAnimation_ = new DoubleAnimation(
                0.0,
                23.0,
                TimeSpan.FromMilliseconds(200)
            );

            increaseHeightAnimation_.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };
            increaseHeightStoryboard_ = new Storyboard();

            Storyboard.SetTarget(increaseHeightAnimation_, errorGrid_);
            Storyboard.SetTargetProperty(increaseHeightAnimation_, new PropertyPath(FrameworkElement.HeightProperty));
            increaseHeightStoryboard_.Children.Add(increaseHeightAnimation_);

            showAnimation_ = new DoubleAnimation(0.0, 1.0, TimeSpan.FromMilliseconds(200));
            showAnimation_.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseIn };
            showStoryboard_ = new Storyboard();

            Storyboard.SetTarget(showAnimation_, textBlock_);
            Storyboard.SetTargetProperty(showAnimation_, new PropertyPath(UIElement.OpacityProperty));
            showStoryboard_.Children.Add(showAnimation_);

            decreaseHeightAnimation_ = new DoubleAnimation(
                23.0,
                0.0,
                TimeSpan.FromMilliseconds(100)
            );

            descreaseHeightStoryboard_ = new Storyboard();

            Storyboard.SetTarget(decreaseHeightAnimation_, errorGrid_);
            Storyboard.SetTargetProperty(decreaseHeightAnimation_, new PropertyPath(FrameworkElement.HeightProperty));
            descreaseHeightStoryboard_.Children.Add(decreaseHeightAnimation_);

            hideAnimation_ = new DoubleAnimation(1.0, 0.0, TimeSpan.FromMilliseconds(100));
            hideAnimation_.EasingFunction = new CubicEase() { EasingMode = EasingMode.EaseOut };

            hideStoryboard_ = new Storyboard();

            Storyboard.SetTarget(hideAnimation_, textBlock_);
            Storyboard.SetTargetProperty(hideAnimation_, new PropertyPath(UIElement.OpacityProperty));
            hideStoryboard_.Children.Add(hideAnimation_);
        }

        protected override void OnAttached()
        {
            var parent = VisualTreeHelper.GetParent(AssociatedObject) as Grid;

            if (parent == null)
                return;

            var row_index = Grid.GetRow(AssociatedObject);
            var row_span = Grid.GetRowSpan(AssociatedObject);

            var column_index = Grid.GetColumn(AssociatedObject);
            var column_span = Grid.GetColumnSpan(AssociatedObject);

            parent.Children.Remove(AssociatedObject);

            var grid = new Grid();
            grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
            grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });

            Grid.SetRow(grid, row_index);
            Grid.SetRowSpan(grid, row_span);

            Grid.SetColumn(grid, column_index);
            Grid.SetColumnSpan(grid, column_span);

            grid.Children.Add(AssociatedObject);
            Grid.SetRow(AssociatedObject, 0);

            errorGrid_ = new Grid();
            errorGrid_.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
            errorGrid_.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });

            errorGrid_.Height = 0;

            var image = new Image();
            image.Source = Resources.Error16.GetBitmapSource();
            image.Margin = new Thickness(0,0,2,7);

            errorGrid_.Children.Add(image);
            Grid.SetColumn(image, 0);

            textBlock_.Foreground = Brushes.Red;

            errorGrid_.Children.Add(textBlock_);
            Grid.SetColumn(textBlock_, 1);

            grid.Children.Add(errorGrid_);
            Grid.SetRow(errorGrid_, 1);

            parent.Children.Add(grid);

            Validation.AddErrorHandler(AssociatedObject, OnError);
            Validation.SetErrorTemplate(AssociatedObject, null);

            base.OnAttached();
        }

        private void OnError(object sender, ValidationErrorEventArgs e)
        {
            var error_msg = e.Error.ErrorContent.ToString();

            if (e.Action == ValidationErrorEventAction.Added)
            {
                if (errors_.ContainsKey(error_msg))
                    errors_[error_msg]++;
                else
                    errors_.Add(error_msg, 1);

                if (errors_[error_msg] == 1)
                {
                    textBlock_.Text = e.Error.ErrorContent.ToString();

                    increaseHeightStoryboard_.Begin(errorGrid_);
                    showStoryboard_.Begin(textBlock_);
                }
            }

            if (e.Action == ValidationErrorEventAction.Removed)
            {
                errors_[error_msg]--;

                if (errors_[error_msg] == 0)
                {
                    descreaseHeightStoryboard_.Begin(errorGrid_);
                    hideStoryboard_.Begin(textBlock_);
                }
            }
        }
    }
}


4 Comments

System.Drawing.Bitmap GetBitmapSource() Extension – Convert System.Drawing.Bitmap to BitmapSource

If you are like me, you probably keep bitmaps (png files) of your WPF project in Resources. Since Resources uses System.Drawing.Bitmap for storing bitmaps, I wrote (some code modifications were done by Bojan) this little extension that will convert bitmap resource to BitmapSource.

using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Foo
{
    public static class BitmapExtension
    {
        public static BitmapSource GetBitmapSource(this Bitmap image)
        {
            var rect = new Rectangle(0, 0, image.Width, image.Height);
            var bitmap_data = image.LockBits(rect, ImageLockMode.ReadOnly, image.PixelFormat);

            try
            {
                BitmapPalette palette = null;
                
                if (image.Palette.Entries.Length > 0)
                {
                    var palette_colors = image.Palette.Entries.Select(entry => System.Windows.Media.Color.FromArgb(entry.A, entry.R, entry.G, entry.B)).ToList();
                    palette = new BitmapPalette(palette_colors);
                }

                return BitmapSource.Create(
                    image.Width,
                    image.Height,
                    image.HorizontalResolution,
                    image.VerticalResolution,
                    ConvertPixelFormat(image.PixelFormat),
                    palette,
                    bitmap_data.Scan0,
                    bitmap_data.Stride * image.Height,
                    bitmap_data.Stride
                );
            }
            finally
            {
                image.UnlockBits(bitmap_data);
            }
        }

        private static System.Windows.Media.PixelFormat ConvertPixelFormat(System.Drawing.Imaging.PixelFormat sourceFormat)
        {
            switch (sourceFormat)
            {
                case System.Drawing.Imaging.PixelFormat.Format24bppRgb:
                    return PixelFormats.Bgr24;

                case System.Drawing.Imaging.PixelFormat.Format32bppArgb:
                    return PixelFormats.Bgra32;

                case System.Drawing.Imaging.PixelFormat.Format32bppRgb:
                    return PixelFormats.Bgr32;
            }

            return new System.Windows.Media.PixelFormat();
        }
    }
}