DCSIMG
February 2008 - Posts - Just code - Tamir Khason

February 2008 - Posts

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/real-estate-super-picks/]


Don't you ever want to buy a house? Now it's possible. For only $100, you have brilliant opportunity to turn your drams true. 3 beds, 690 ft² or 1,451 ft². What can be better? Less, then 33 hours trip to your office. If you want it closer, you'll pay at least $170,000 for 1 bed. Sure, you can also find your next job closer. So, it looks like there are some, looking for other place to live.

image 
Illustration by AP

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/please-someone-can-take-care-on-sluggish-javascript-on-this-blog-platform/]


I tired, really tired of sluggish JavaScript and bad HTML in templates of this aggregator! Please, someone can take care on it?

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/weird-russians-think-different/]


Just in case. Following the poll, appears in main page of russian version of Silverlight Challenge (MS official???)

image

Let me translate you options:

Have you Silverlight installed?

  • Yes
  • No
  • Still not (planning)
  • I do not like Flash

WTF? Is it legit option to ask? There is no such poll on english version of the same web site. The only poll there is

image

and there are no other, even archived polls.

So, I want to ask you the simple question: WTF?

http://***.ru/?p=446

For some reason "i*n*j*u*n #576871" was filtered. Use your favorite search engine to fine it (remove stars)

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/headhunting-30/]


Sarah E. Needleman wrote very interesting article in the Wall Street Journal about headhunting and job seeking in new internet era. What can you tell about me, after reading this and my blog?

image

Image copyright - Hannah Gal (Photodisc)

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/7-xna-community-games-for-xbox-are-free-now/]


Xbox Live Community Games is new program, announced by XNA Creators Club. It gives you a chance to download for free seven games for XBox absolutely free.  So, what are you waiting for? Download it now from http://creators.xna.com website.

What's included?

  • The Dishwasher: Dead Samurai
  • JelloCar
  • Little Gamers
  • Proximity HD
  • Rocketball
  • TriLinea
  • Culture

You can either watch an "XBox insider" video to learn how to download and run those games.

However, the most interesting news, is that Microsoft deciding be become an authority for distributing community games via Xbox Live Marketplace, thus now, you can write not only for yourself, but wider your game distribution with Microsoft.

So what are you waiting for? Download and install XNA Game Studio 2.0, come to my presentation in TechEd '08 to learn how to do it and begin to make your own gaming business (or do it just for your fun).

image

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/wpf-net-35-whats-next-the-future-roadmap-of-windows-presentation-foundation/]


Today, Scott Guthrie, Microsoft's VP of .NET Developer Platform, blogged about the future plans of WPF.

Upcoming changes will mostly concentrated around LOB applications development, startup times and workset, text, bitmap effects and media performance improvements. As well there are plans about new WPF controls, such as Grid, Ribbon and Date Picker.

Read the whole article >>

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/sound-tone-and-dtmf-generation-by-using-managed-directsound-and-c-and-sine-tone-detection-with-pure-managed-goertzel-algorithm-implementation/]


Well, today, we'll speak about math. A lot of math. We have a number of challenges today.

  • Generate sine, square or dual sine tone (DTMF - sounds, that your phone keyboard produces)
  • Playing it in managed code
  • Detection of base frequencies by using Goertzel (Fast Fourier) algorithm

Let's start with generation of tone. All we need is DirectSound. First of all, we should create format of wave sound

using DXS = Microsoft.DirectX.DirectSound;

Now, let's create our Device

Microsoft.DirectX.DirectSound.Device m_device = new Microsoft.DirectX.DirectSound.Device();
SourceHost = App.Current.MainWindow.Content as FrameworkElement;

m_device.SetCooperativeLevel(((HwndSource)PresentationSource.FromVisual(SourceHost)).Handle, DXS.CooperativeLevel.Priority);

As you can see, it, actually Control, thus we have to give him handle to sit on. 90%, that when you'll run it, you'll get strange exception: "DLL 'C:\Windows\assembly\GAC\Microsoft.DirectX\1.0.2902.0__31bf3856ad364e35\Microsoft.DirectX.dll' is attempting managed execution inside OS Loader lock. Do not attempt to run managed code inside a DllMain or image initialization function since doing so can cause the application to hang.". In order to get rid of it, just disable handling of LoaderLock exceptions under Managed Debugging Assistants section in Visual Studio Debug menu. Not very clear why it happens, but it is. After doing it, we can continue with in memory wave generation.

DXS.WaveFormat format = new DXS.WaveFormat();
            format.BitsPerSample = BitsPerSample;
            format.Channels = Channels;
            format.BlockAlign = BlockAlign;
            format.FormatTag = DXS.WaveFormatTag.Pcm;
            format.SamplesPerSecond = SampleRate;
            format.AverageBytesPerSecond = format.SamplesPerSecond * format.BlockAlign;

            DXS.BufferDescription desc = new DXS.BufferDescription(format);
            desc.DeferLocation = true;
            desc.BufferBytes = sample.Length;         

Now, when we'll create SecondaryBuffer to play with the Device

CurrentSample = sample;
CurrentBuffer = new DXS.SecondaryBuffer(desc, m_device);
CurrentBuffer.Write(0, sample, DXS.LockFlag.EntireBuffer);
CurrentBuffer.Play(0, DXS.BufferPlayFlags.Default);           

We done. Now let'sw create buffer to play. We'll start from the simplest sine wave. This type of wave looks like this

image

Not too hard to understand how to generate it

int length = (int)(SampleRate * BlockAlign * duration);
byte[] buffer = new byte[length];

double A = frequency * 2 * Math.PI / (double)SampleRate;
for (int i = 0; i < length; i++)
{
     //buffer[i] = (byte)(Math.Sin(i*A));
}

However, phone tones (DTMF) have double wave format. It looks like this

 image

As you can see, we take two waves and create it's composition. Here the table of tones to be used for DTMF creation.

     

Upper

Band

 
    1209 Hz 1336 Hz 1477 Hz 1633 Hz

 

697 Hz

1

2

3

A

Lower

770 Hz

4

5

6

B

Band

852 Hz

7

8

9

C

941 Hz

*

0

#

D

 

There are also events in telephony. They build on two frequencies as well

  Lower Band Upper Band
Busy signal 480 Hz 620 Hz
Dial tone 350 Hz 440 Hz
Flash (ringback) 440 Hz 480 Hz

So, in order to create such waves, we'll have to use following function (the simple one)

(128 + 63 * Math.Sin(n * 2 * Math.PI * LowFreq / rate) + 63 * Math.Sin(n * 2 * Math.PI * HighFreq / rate))

Actually, dual (or triple) sines should be generated by using something like this

int length = (int)(SampleRate * BlockAlign * duration);
            byte[] buffer = new byte[length];

            double A = frequency * 2 * Math.PI / (double)SampleRate;
            for (int i = 0; i < length; i++)
            {
                if (i > 1) buffer[i] = (byte)(2 * Math.Cos(A) * buffer[i - 1] - buffer[i - 2]);
                else if (i > 0) buffer[i] = (byte)(2 * Math.Cos(A) * buffer[i - 1] - (Math.Cos(A)));
                else buffer[i] = (byte)(2 * Math.Cos(A) * Math.Cos(A) - Math.Cos(2 * A));
            }

Done. We can generate and play sounds, tones and DTMF. How to detect what has been played? For this purpose, we should use Fourier transform, but it's very slow, thus Dr' Gerald Goertzel developed fast Fourrier Transform algorithm, that used almost in every DSP related product. A little theory about it can be found in Wikipedia (implementation there rather bad).

I simplified it, due to fact, that we actually know source possible frequencies, thus all we have to do is to check the result with well-known values

public double CalculateGoertzel(byte[] sample, double frequency, int samplerate)
{
    double Skn, Skn1, Skn2;
    Skn = Skn1 = Skn2 = 0;
    for (int i = 0; i < sample.Length; i++)
    {
        Skn2 = Skn1;
        Skn1 = Skn;
        Skn = 2 * Math.Cos(2 * Math.PI * frequency / samplerate) * Skn1 - Skn2 + sample[i];
    }

    double WNk = Math.Exp(-2 * Math.PI * frequency / samplerate);

    return 20* Math.Log10(Math.Abs((Skn - WNk * Skn1)));
}

This method checks it will steps of 5

public int TestGoertzel(int frequency, byte[] sample)
       {
           int stepsize = frequency / 5;
           Dictionary<int,double> res = new Dictionary<int,double>();
           for (int i = 0; i < 10; i++)
           {
               int freq = stepsize * i;
               res.Add(freq,CalculateGoertzel(sample,freq,SampleRate));
           }

And the actual result is rather clear

1200 - -272.273842954695
2400 - -198.538262992751
3600 - -214.476846236253
4800 - -224.242925995697
6000 - 87.1653837206957 <- here result
7200 - -225.52220053475
8400 - -222.836254116698
9600 - -230.526410368965
10800 - -220.587695185849

Have a fun with sines, waves and math and be good people. BTW, you can implement full telephone keypad, that actually working with one of my previous posts. To test it, just call any service, that required you to press any of phone buttons and sound the phone you just generated - it will accept it :)

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/zionism-is-not-language-zionism-is-action/]


Referring my reply post of the Zionism and the language you are writing in your blog, I want to post a little bit about Zionism, as I see it. (All images are CC by me. See my Flickr for more information)

Zionism is traveling your land without 4X4 vehicle

Israel desert tree

... is looking for small habitants without killing them

Desert habitant

is seeing the nature not through the car window

Alone trees on sunset

...is understanding, that it is not necessarily to travel only well-known places

North trip 2007 232

Zionism is to see wide

Sataf large overview

... to see even in dark

Soreq caves panorama

... to understand, that old buildings have their own history and soul

Bikur Cholim

... to see well-known places differently

Night Tel Aviv - 360 degrees from south

... to know, that you have nothing of your own

Sunset over the sea

... even if it looks friendly for you

Andromeda sound in Acre

Zionism is to feel your soul, traveling empty streets

Ancient street of Jerusalem

...is to allow yourself a nature wideness

Water in Israel

...without tramping it

Leafage in the water

... and even when your are very high

DSCF1769-1.JPG

...you should never forget to think about others with yourself

Thoughts

...even if it looks dead

DSCF1740-1.JPG

because, you never can know, who did it

DSCF1695-1.JPG

...or where the life hides

DSCF0868.jpg

...or where you can take a rest of everyday live

DSCF0950.jpg

Zionism is the understanding, that the place, you are living in is the best place in the world

Vista view

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/%d0%bd%d0%b5-%d0%bd%d0%b0%d0%b4%d0%be-%d0%b4%d0%b5%d0%bb%d0%b0%d1%82%d1%8c-%d0%b8%d0%b7-%d1%81%d0%b5%d0%b1%d1%8f-%d0%b2%d0%b5%d0%bb%d0%b8%d0%ba%d0%b8%d1%85-%d0%b8-%d0%bd%d0%b5%d1%81%d1%87%d0%b0%d1%81/]


Declaimer: For my following, this post has no relation to programming. It written as response for someone from this blog platform, who thinks, that all bread should be backed in one oven. So, baking it differently...

За последние двое суток в очередной раз встал вопрос века: "На каком языке писать в своём блоге". Ответ очевиден - на том, на котором тебе удобнее всего писать. Нравится на китайском - пиши на китайском, иврит - так иврит. Нет никакой связи с платформой, на которой ты находишься. Важно понять, что вы, ребята, не находитесь в "блогах Майкрософта". Блоги Майкрософта - это http://blogs.msdn.com А тут - комьюнити тех, кому не безразличны технологии компании. Те, кто с ними работают каждый день. Но ещё более важен тот факт, что эти блоги нужны больше самому израильскому представительству Майкрософта, чем вам самим. А ещё тут можно коллегам помочь. Там, которые не звонят в тех. поддержку, ибо понимают, что это пустая трата времени.

Вы чувствуете себя обиженным и огорчённым? Сионизм в попе проснулся? Что вдруг? Давайте тогда и программы на иврите/русском/китайском писать? Почему нет. Оно работает, да и сионизм тоже... Не верите? Попробуйте написать имя метода или класса на иврите или хинди и откомпелировать. Получится. Вот только потом почему-то никто такой код поддерживать не хочет.

Сказать честно, надоело. Не надо делать из себя великих мучеников Сиона и порекать языком, на котором добровольно пишется качественный контент. Просто скажите спасибо. Да и вам бы стоило что-то полезное написать на языке, на котором вам удобно. А то ведь что получается. Весь ваш блог похож на каталог ссылок на внешний ресурс. Пусть даже ваш. PR повышаем или по простоте душевной?

После такого полёта мысли пошло-поехало. Зачем? Зачем посылать лучи диареи, если рыльце в пушку? А забивать токбэки истоптанными фразами - все мастера. Господа, пишите так, чтобы другим помочь, а не так, чтобы выпендриться перед остальными. Пишите много и качественно. И все у вас получится. Честное слово.

Спасибо за внимание. И простите, если что не так. Ах, да. Забыл сказать, что результаты машинного перевода этого поста могут оказаться непредсказуемыми.

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/internet-safety-day-and-red-fish-soup/]


A couple of days age, there was the Internet Safety Day in Israel (for some reason nothing about it in official government website, so last year news). I begun to think, what websites can be useful for my kids? Hop tv-channel web site is pretty nice, but it teaches nothing our kids. Tipo is absolutely unuseful. There are more? Not exactly...

After some research, I found great french web site, named Poisson Rouge (Red Fish Soup). This how should build web for kids. Shame to all locals...

image

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/the-strangest-office-i-ever-worked-in/]


What the strangest place, you worked in? Post your photo of strangest places to comments.

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/building-virtual-keyboard-in-wpf-custom-editors-accessibility-and-attached-properties/]


The challenge - build alternative on-screen keyboard to appear on each textbox, marked to use such keyboard.

The reason - custom editor, ability to use touch screen input, etc

Realization: WPF, two windows, a little hooking, attached properties, custom commands and gestures.

image

Let's start working. First of all, we should provide ability to "attach" new control to any textbox (richtextbox) in any application. For this purpose, we'll use attached events, so, syntax will looks like this

<TextBox/>
<TextBox l:VKeyboard.AttachVKeyboard="true"/>

Also, we do not want to attach it to really any textbox, we should be able to do it for upper containers. Something like this

<StackPanel l:VKeyboard.AttachVKeyboard="true">
            <TextBox/>
            <TextBox/>
            <Button>Duh!</Button>
        </StackPanel>

Our custom keypad should appear above the window, under the control it attached to and should not be worry about lost focus. Thus we can not use neither Popup and Tooltip. We'll use custom borderless window

public class VKeyboard : Window

We should be able to change skins and override it's templates, so we'll create ResourceDictionary for this window and write it's default template.

<Style TargetType="{x:Type local:VKeyboard}">
        <Setter Property="Topmost" Value="True"/>
        <Setter Property="ShowInTaskbar" Value="False"/>
        <Setter Property="Focusable" Value="False"/>
        <Setter Property="Background" Value="White"/>
        <Setter Property="Width" Value="300"/>
        <Setter Property="Height" Value="300"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:VKeyboard}">
                    <Border Background="{TemplateBinding Background}"
                            BorderBrush="{TemplateBinding BorderBrush}"
                            BorderThickness="{TemplateBinding BorderThickness}"
                            Focusable="False">
                        <Grid>

We must allow of using input from this control externally, so we'll create RoutedUICommand for each of our buttons

<Button Command="local:VKeyboard.ButtonCallPressedCommand" Grid.Column="0" Grid.Row="0" Content="{StaticResource CallImg}"/>
                            <Button Command="local:VKeyboard.ButtonEndPressedCommand" Grid.Column="2" Grid.Row="0" Content="{StaticResource EndCallImg}"/>
                            <Button Command="local:VKeyboard.Button1PressedCommand" Grid.Column="0" Grid.Row="1" Content="1"/>
                            <Button Command="local:VKeyboard.Button2PressedCommand" Grid.Column="1" Grid.Row="1" Content="2 ABC"/>
                            <Button Command="local:VKeyboard.Button3PressedCommand" Grid.Column="2" Grid.Row="1" Content="3 DEF"/>
                            <Button Command="local:VKeyboard.Button4PressedCommand" Grid.Column="0" Grid.Row="2" Content="4 GHI"/>
                            <Button Command="local:VKeyboard.Button5PressedCommand" Grid.Column="1" Grid.Row="2" Content="5 JKL"/>
                            <Button Command="local:VKeyboard.Button6PressedCommand" Grid.Column="2" Grid.Row="2" Content="6 MNO"/>
                            <Button Command="local:VKeyboard.Button7PressedCommand" Grid.Column="0" Grid.Row="3" Content="7 PQRS"/>
                            <Button Command="local:VKeyboard.Button8PressedCommand" Grid.Column="1" Grid.Row="3" Content="8 TUV"/>
                            <Button Command="local:VKeyboard.Button9PressedCommand" Grid.Column="2" Grid.Row="3" Content="9 WXYZ"/>
                            <Button Command="local:VKeyboard.ButtonStarPressedCommand" Grid.Column="0" Grid.Row="4" Content="*"/>
                            <Button Command="local:VKeyboard.Button0PressedCommand" Grid.Column="1" Grid.Row="4" Content="0"/>
                            <Button Command="local:VKeyboard.ButtonHashPressedCommand" Grid.Column="2" Grid.Row="4" Content="#"/>

A little "beauty" and we done

<Grid.Resources>
                                <Path x:Key="CallImg" Stretch="Uniform" Fill="#FFFFFFFF" Margin="8,8,8,8" Data="F1 M 0.65625,31L 0,30.5C 0.247479,21.7008 -0.845665,11.3964 4.625,4.5C 5.99938,2.76746 7.50086,0.945534 9.5,0C 10.8189,-0.623779 25.7734,-0.289833 27,0.5C 30.6887,2.87514 30.7009,8.45779 32.4063,12.5C 34.0938,16.5 36.8862,20.1979 37.4688,24.5C 38.2858,30.5337 30.8386,34.8158 28.6563,40.5C 27.4057,43.7572 32.0234,46.625 34,49.5C 38.7071,56.3467 44.4278,62.889 51.5,67.25C 54.5603,69.1371 57.6135,71.0564 60.8333,72.6563C 61.9444,73.2083 63.0556,73.7604 64.1667,74.3125C 65.2778,74.8646 66.3323,76.388 67.5,75.9688C 69.4896,75.2544 70.659,73.1342 72,71.5C 72.5881,70.7833 75.9005,66.8194 77,65.5C 78.7419,63.4098 80.8023,60.2078 83.5,60.5625C 88.2582,61.1881 92.1,64.825 96.4,66.9563C 97.8333,67.6667 99.2667,68.3771 100.7,69.0875C 102.133,69.7979 103.57,70.5016 105,71.2188C 106.337,71.8891 108.2,71.9867 109,73.25C 109.725,74.3947 108.906,75.9583 108.859,77.3125C 108.813,78.6667 108.766,80.0208 108.719,81.375C 108.672,82.7292 108.625,84.0833 108.578,85.4375C 108.531,86.7917 109.037,88.285 108.438,89.5C 105.738,94.9684 99.4967,98.9543 93.5,100.063C 89.3677,100.826 73.8381,99.7891 72.5,99.375C 71.6472,99.1111 66.269,97.6981 65.5,97.375C 58.3355,94.3649 51.1421,91.284 44.5,87.25C 24.181,74.9095 8.33594,53.4982 0.65625,31 Z "/>
                                <Path x:Key="EndCallImg" Stretch="Uniform" Fill="#FFFFFFFF" Margin="15,15,15,15" Data="F1 M 108.66,31.2772L 109.485,31.2593C 114.774,38.2957 122.05,45.6741 122.068,54.4768C 122.073,56.6882 122.033,59.0488 121.059,61.034C 120.416,62.3437 108.512,71.4007 107.06,71.5473C 102.695,71.988 99.2068,67.6293 95.3543,65.5303C 91.5421,63.4532 87.0538,62.3009 83.9175,59.2991C 79.5188,55.089 82.6754,47.0995 80.8404,41.2938C 79.7889,37.967 74.3904,38.6013 71.0529,37.5842C 63.1051,35.1622 54.5542,33.6098 46.3054,34.6056C 42.736,35.0365 39.152,35.4378 35.6368,36.1928C 34.4237,36.4534 33.2107,36.7139 31.9976,36.9744C 30.7846,37.235 29.0106,36.7005 28.3585,37.756C 27.2475,39.5545 27.654,41.9414 27.6234,44.0552C 27.6099,44.9822 27.4891,50.1465 27.4513,51.8635C 27.3914,54.5837 27.7749,58.372 25.444,59.7755C 21.3326,62.2511 16.0616,61.8004 11.3704,62.8128C 9.80669,63.1502 8.24296,63.4877 6.67923,63.8252C 5.1155,64.1627 3.55334,64.5075 1.98804,64.8376C 0.524829,65.1462 -0.993193,66.2309 -2.40619,65.7413C -3.68651,65.2977 -4.02044,63.5646 -4.82757,62.4762C -5.63469,61.3879 -6.44182,60.2995 -7.24894,59.2112C -8.05607,58.1228 -8.86319,57.0345 -9.67032,55.9461C -10.4774,54.8578 -11.8037,54.0051 -12.0917,52.6811C -13.388,46.7222 -10.9898,39.7154 -6.99004,35.1121C -4.23386,31.94 8.5586,23.0745 9.86325,22.5646C 10.6947,22.2396 15.7816,19.9936 16.5844,19.7672C 24.0637,17.6572 31.6096,15.5847 39.3183,14.6011C 62.8999,11.5922 88.6345,18.4657 108.66,31.2772 Z "/>
                                <LinearGradientBrush x:Key="BackgroundBrush" EndPoint="0.5,1" StartPoint="0.5,0">
                                    <GradientStop Color="#FFBADAC0" Offset="0"/>
                                    <GradientStop Color="#FF3EAF44" Offset="0.379"/>
                                </LinearGradientBrush>
                                <LinearGradientBrush x:Key="BackgroundPressBrush" EndPoint="0.5,1" StartPoint="0.5,0">
                                    <GradientStop Color="#FFBADAC0" Offset="1"/>
                                    <GradientStop Color="#FF3EAF44" Offset="0.308"/>
                                </LinearGradientBrush>
                                <Style TargetType="{x:Type Button}" BasedOn="{x:Null}">
                                    <Setter Property="Background" Value="{StaticResource BackgroundBrush}"/>
                                    <Setter Property="BorderBrush" Value="{StaticResource BackgroundBrush}"/>
                                    <Setter Property="Foreground" Value="White"/>
                                    <Setter Property="FontStretch" Value="UltraExpanded"/>
                                    <Setter Property="FontWeight" Value="Black"/>
                                    <Setter Property="FontSize" Value="20"/>
                                    <Setter Property="Margin" Value="2,2,2,2"/>
                                    <Setter Property="Template">
                                        <Setter.Value>
                                            <ControlTemplate TargetType="{x:Type Button}">
                                                <Grid x:Name="Grid">
                                                    <Rectangle x:Name="Background" RadiusX="3" RadiusY="3" Stroke="{TemplateBinding BorderBrush}" Fill="{TemplateBinding BorderBrush}"/>
                                                    <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True"/>
                                                </Grid>
                                                <ControlTemplate.Triggers>
                                                    <Trigger Property="IsPressed" Value="true">
                                                        <Setter Property="Fill" Value="{StaticResource BackgroundPressBrush}" TargetName="Background"/>
                                                    </Trigger>
                                                </ControlTemplate.Triggers>
                                            </ControlTemplate>
                                        </Setter.Value>
                                    </Setter>
                                </Style>
                            </Grid.Resources>

Another small touch - if we'll AllowTransparency and set WindowStyle within our resource dictionary or it's style we'll get an exception "Cannot change AllowsTransparency after Window has been shown." The reason is simple - styles applies after the window handler created, thus we can not change transparency within template. Thus WindowStyle none cannot be applied there as well. We'll put it into default constructor.

public VKeyboard()
{
    this.AllowsTransparency = true;
    this.WindowStyle = WindowStyle.None;           
}

After we done with the control, let's start to build business logic. First of all routed commands

public static RoutedUICommand ButtonCallPressedCommand = new RoutedUICommand("Call","Call",typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.Enter) } ));
        public static RoutedUICommand ButtonEndPressedCommand = new RoutedUICommand("End call", "End call", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.Delete), new KeyGesture(Key.Back) }));
        public static RoutedUICommand Button1PressedCommand = new RoutedUICommand("1", "1", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.NumPad1)/*, new KeyGesture(Key.D1)*/ }));
        public static RoutedUICommand Button2PressedCommand = new RoutedUICommand("2", "2", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.NumPad2)/*, new KeyGesture(Key.D2)*/  }));
        public static RoutedUICommand Button3PressedCommand = new RoutedUICommand("3", "3", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.NumPad3)/*, new KeyGesture(Key.D3)*/  }));
        public static RoutedUICommand Button4PressedCommand = new RoutedUICommand("4", "4", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.NumPad4)/*, new KeyGesture(Key.D4)*/  }));
        public static RoutedUICommand Button5PressedCommand = new RoutedUICommand("5", "5", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.NumPad5)/*, new KeyGesture(Key.D5)*/  }));
        public static RoutedUICommand Button6PressedCommand = new RoutedUICommand("6", "6", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.NumPad6)/*, new KeyGesture(Key.D6) */ }));
        public static RoutedUICommand Button7PressedCommand = new RoutedUICommand("7", "7", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.NumPad7)/*, new KeyGesture(Key.D7)*/  }));
        public static RoutedUICommand Button8PressedCommand = new RoutedUICommand("8", "8", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.NumPad8)/*, new KeyGesture(Key.D8) */ }));
        public static RoutedUICommand Button9PressedCommand = new RoutedUICommand("9", "9", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.NumPad9)/*, new KeyGesture(Key.D9)*/  }));
        public static RoutedUICommand Button0PressedCommand = new RoutedUICommand("0", "0", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.NumPad0)/*, new KeyGesture(Key.D0)*/  }));
        public static RoutedUICommand ButtonStarPressedCommand = new RoutedUICommand("Star", "Star", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.Multiply)/*, new KeyGesture(Key.D8, ModifierKeys.Shift)*/ }));
        public static RoutedUICommand ButtonHashPressedCommand = new RoutedUICommand("Hash", "Hash", typeof(Button),
            new InputGestureCollection(new KeyGesture[] { new KeyGesture(Key.Divide)/*, new KeyGesture(Key.D3,ModifierKeys.Shift)*/ }));

As you can see, we have small problem here. For some reason Key.D... gestures cannot be use in WPF. The reason is silly, but currently there is no workaround for it. I'll think about this issue and maybe I'll find kind'of trick to do. For now, we'll just leave it as is and create command bindings.

CommandBinding bCall = new CommandBinding(ButtonCallPressedCommand, ExecutedButtonPressedCommand);
            CommandBinding bEnd = new CommandBinding(ButtonEndPressedCommand, ExecutedButtonPressedCommand);
            CommandBinding b1 = new CommandBinding(Button1PressedCommand, ExecutedButtonPressedCommand);
            CommandBinding b2 = new CommandBinding(Button2PressedCommand, ExecutedButtonPressedCommand);
            CommandBinding b3 = new CommandBinding(Button3PressedCommand, ExecutedButtonPressedCommand);
            CommandBinding b4 = new CommandBinding(Button4PressedCommand, ExecutedButtonPressedCommand);
            CommandBinding b5 = new CommandBinding(Button5PressedCommand, ExecutedButtonPressedCommand);
            CommandBinding b6 = new CommandBinding(Button6PressedCommand, ExecutedButtonPressedCommand);
            CommandBinding b7 = new CommandBinding(Button7PressedCommand, ExecutedButtonPressedCommand);
            CommandBinding b8 = new CommandBinding(Button8PressedCommand, ExecutedButtonPressedCommand);
            CommandBinding b9 = new CommandBinding(Button9PressedCommand, ExecutedButtonPressedCommand);
            CommandBinding b0 = new CommandBinding(Button0PressedCommand, ExecutedButtonPressedCommand);
            CommandBinding bStar = new CommandBinding(ButtonStarPressedCommand, ExecutedButtonPressedCommand);
            CommandBinding bHash = new CommandBinding(ButtonHashPressedCommand, ExecutedButtonPressedCommand);

            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), bCall);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), bEnd);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), b1);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), b2);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), b3);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), b4);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), b5);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), b6);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), b7);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), b8);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), b9);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), b0);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), bStar);
            CommandManager.RegisterClassCommandBinding(typeof(VKeyboard), bHash);

We'll create Dependency Property to hold dialed number and add routed event to handle dial press

public string DialedNumber
        {
            get { return (string)GetValue(DialedNumberProperty); }
            private set { SetValue(DialedNumberPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey DialedNumberPropertyKey =
            DependencyProperty.RegisterReadOnly("DialedNumber", typeof(string), typeof(VKeyboard), new UIPropertyMetadata(default(string)));
        public static readonly DependencyProperty DialedNumberProperty = DialedNumberPropertyKey.DependencyProperty;

        public static readonly RoutedEvent CallEvent = EventManager.RegisterRoutedEvent("Call", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(VKeyboard));

        public event RoutedEventHandler Call
        {
            add { AddHandler(CallEvent, value); }
            remove { RemoveHandler(CallEvent, value); }
        }

        void RaiseCallEvent()
        {
            RoutedEventArgs newEventArgs = new RoutedEventArgs(VKeyboard.CallEvent);
            RaiseEvent(newEventArgs);
        }

So far, so good. Now the turn of complicated things. Let's treat our attached event.

public static bool GetAttachVKeyboard(DependencyObject obj)
        {
            return (bool)obj.GetValue(AttachVKeyboardProperty);
        }

        public static void SetAttachVKeyboard(DependencyObject obj, bool value)
        {
            obj.SetValue(AttachVKeyboardProperty, value);
        }

        public static readonly DependencyProperty AttachVKeyboardProperty =
            DependencyProperty.RegisterAttached("AttachVKeyboard", typeof(bool), typeof(VKeyboard), new UIPropertyMetadata(default(bool), AttachVKeyboardPropertyChanged));

        static void AttachVKeyboardPropertyChanged(DependencyObject s, DependencyPropertyChangedEventArgs e)
        {

AttachVKeyboardPropertyChanged handler occurs when someone changes the value. So, we can get the requester from this handler and work with it. We'll check what the type of the sender and do whatever we should do.

If the sender is TextBox we know what to do

if (s is TextBoxBase)
            {
                TextBoxBase control = s as TextBoxBase;
                if ((bool)e.NewValue)
                {
                    control.AddHandler(TextBoxBase.GotFocusEvent, new RoutedEventHandler(OnHostFocused), true);
                    control.AddHandler(TextBoxBase.LostFocusEvent, new RoutedEventHandler(OnHostUnFocused), true);
                }
                else
                {
                    control.RemoveHandler(TextBoxBase.GotFocusEvent, new RoutedEventHandler(OnHostFocused));
                    control.RemoveHandler(TextBoxBase.LostFocusEvent, new RoutedEventHandler(OnHostUnFocused));
                }
            }

However, it is it not we should run recursively and find all textboxes inside it. We have two types of containers - Panels (where there is a number of children) and Decorators (where there is only one). Another problem, that when the control changes the value it still has no children inside. Other works it is not loaded. We should know it to get appropriate action after the sender will load itself with all dependencies. So the code will looks like this:

if (s is Panel)
            {
                Panel p = s as Panel;
                if (p.IsLoaded)
                {
                    OnMultiHostLoaded(p, null);
                }
                else
                {
                    p.AddHandler(Panel.LoadedEvent, new RoutedEventHandler(OnMultiHostLoaded), true);
                }
            }
            else if (s is Decorator)
            {
                Decorator d = s as Decorator;
                if (d.IsLoaded)
                {
                    OnSingleHostLoaded(d, null);
                }
                else
                {
                    d.AddHandler(Panel.LoadedEvent, new RoutedEventHandler(OnSingleHostLoaded), true);
                }
            }

Now, the only thing we should do it to look into it's children and attach there (we wont forget to remove handlers)

static void OnSingleHostLoaded(object s, RoutedEventArgs e)
        {
            Decorator d = s as Decorator;
            bool val = GetAttachVKeyboard(d);
            DependencyPropertyChangedEventArgs ev = new DependencyPropertyChangedEventArgs(VKeyboard.AttachVKeyboardProperty, !val, val);
            AttachVKeyboardPropertyChanged(d.Child, ev);
            d.RemoveHandler(Decorator.LoadedEvent, new RoutedEventHandler(OnSingleHostLoaded));

        }

        static void OnMultiHostLoaded(object s, RoutedEventArgs e)
        {
            Panel p = s as Panel;
            bool val = GetAttachVKeyboard(p);
            DependencyPropertyChangedEventArgs ev = new DependencyPropertyChangedEventArgs(VKeyboard.AttachVKeyboardProperty, !val, val);
            for (int i = 0; i < p.Children.Count; i++)
            {
                AttachVKeyboardPropertyChanged(p.Children[i], ev);
            }
            p.RemoveHandler(Panel.LoadedEvent, new RoutedEventHandler(OnMultiHostLoaded));
        }

Done, let's treat Focus and Unfocus of Textboxes. We have only one keyboard at one time, thus, we'll make it singleton. Also we should know who is the client of the keypad.

static void OnHostFocused(object s, RoutedEventArgs e)
        {
            TextBox tb = s as TextBox;
            if (CurrentKeyboard == null)
            {
                CurrentKeyboard = new VKeyboard();
                tb.Unloaded += new RoutedEventHandler(tb_Unloaded);
                CurrentKeyboard.HookToHandle((HwndSource)PresentationSource.FromVisual(tb));
            }
            CurrentKeyboard.SetValue(VKeyboard.DialedNumberPropertyKey, tb.Text);
            Binding b = new Binding();
            b.Source = CurrentKeyboard;
            b.Path = new PropertyPath(VKeyboard.DialedNumberProperty);
            b.Mode = BindingMode.OneWay;
            tb.SetBinding(TextBox.TextProperty, b);
            CurrentKeyboard.Client = tb;
        }

Very similar thing happens on unfocus.

static void OnHostUnFocused(object s, RoutedEventArgs e)
        {
            TextBox tb = s as TextBox;
            string str = tb.Text;
            BindingOperations.ClearBinding(tb,TextBox.TextProperty);
            tb.Text = str;
            if (CurrentKeyboard != null)
            {
                CurrentKeyboard.DialedNumber = default(string);
                CurrentKeyboard.Client = null;
            }
        }

Now we should take a look into the location of our virtual keypad. There are some problems with it. One - we should translate the location of client textbox into screen coordinates, due to fact, that we should place another window. FrameworkElement.PointToScreen will do the work.

Another problem is what happen when the whole window moving? How we'll synchronize the position of virtual upper window with underlying one? Small "unsafe" trick with old good WinProc will help us

internal void HookToHandle(HwndSource source)
        {
            source.AddHook(new HwndSourceHook(WindowProc));
        }

System.IntPtr WindowProc(
              System.IntPtr hwnd,
              int msg,
              System.IntPtr wParam,
              System.IntPtr lParam,
              ref bool handled)
        {
            switch (msg)
            {
                case 0x0003:/* WM_MOVE  */
                    setPosition();
                    break;
            }

            return IntPtr.Zero;
        }

void setPosition()
        {
            if (m_client != null)
            {

                Point p = new Point(0, m_client.ActualHeight + 2);
                Point sp = m_client.PointToScreen(p);
                this.Left = sp.X;
                this.Top = sp.Y;
                this.Show();
            }
            else
            {
                this.Hide();
            }
        }

We done. Have a nice accessible day with your software and nice fully managed virtual keyboard in WPF.

Source code for this article.

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/how-to-disconnect-ui-and-data-in-wpf-cachedobservablecollection-and-some-updates-regarding-threadsafeobservablecollection/]


Sometimes, we want to "freeze" current displayed collection, while the original collection keep being updates. How to do it? There are some ways to get such functionality. One is to use CollectionView DeferRefresh method and release (dispose) it after you finished. However, it can not provide us with full functionality, due to fact, that the lock applies on original collection, thus we wont update it until the lock will be released.

image 

Another approach is to keep disconnected underlying collection, that synchronized with the original collection while it reaches "cache limit". After reaching it, we'll disconnect the original collection, keeping to be updated and reconnect (or, even refill) the underlying collection when possible.

image

Let's see the example of such functionality. We have the underlying collection, that rapidly changing in background (another thread)

class MyData : ThreadSafeObservableCollection<string>
   {
       public MyData()
       {
           ThreadPool.QueueUserWorkItem(delegate
           {
               int i = 0;
               while (true)
               {
                   base.Add(string.Format("Test {0}", i++));
                   Thread.Sleep(100);
                   if (base.Count > 10)
                       base.RemoveAt(0);
               }
           });

Also, we have another collection, that initialized by original collection and number of items should be cached.

dta = Resources["data"] as MyData;
cache = new CachedObservableCollection<string>(dta,5);

Then, we just create CollectionView to be able to sort/filter displayed data and bind it into ListBox

CollectionView view = new CollectionView(cache);
Binding b = new Binding();
b.Source = view;
lst.SetBinding(ListBox.ItemsSourceProperty, b);

So, what CachedObservableCollection<T> actually doing? First of all, it holds the reference to our original data source. It also, subscribes to CollectionChanged event of the original source in order to check whether it reach the cache limit.

public CachedObservableCollection(ObservableCollection<T> collection, int maxItems):base()
        {
            m_cached = collection;
            m_cached.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(m_cached_CollectionChanged);
            MaxItems = maxItems;
        }

It it is, we just disconnect the source and clean up unnecessary items.

void m_cached_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)
            {
                if (base.Count < MaxItems)
                {
                    for (int i = 0; i < e.NewItems.Count; i++)
                    {
                        base.Add((T)e.NewItems[i]);
                    }
                }
                else
                {
                    for (int i = base.Count - 1; i >= MaxItems; i--)
                    {
                        base.RemoveItem(i);
                    }
                }
            }
        }

We should treat RemoveItem in order to be sure, that items, removed from cache will be removed from underlying data source as well (if they still there)

protected override void RemoveItem(int index)
        {
            T obj = base[index];
            if(m_cached.Contains(obj))
                m_cached.Remove(obj);
            base.RemoveItem(index);
        }

Actually, we finished. The only thing we should do it to interface MaxItem property and assure it to rise OnPropertyChanged event. ObservableCollection is not DependencyObject, thus we cannot use DependencyProperty there. INotifyPropertyChanged is still implemented.

int m_maxItems;
        public int MaxItems
        {
            get { return m_maxItems; }
            set { m_maxItems = value;
            base.OnPropertyChanged(new PropertyChangedEventArgs("MaxItems"));
            }
        }

Now, we can bind this property to Slider and manage maximum number of cached items by using binding.

Binding b1 = new Binding();
b1.Source = cache;
b1.Path = new PropertyPath("MaxItems");
sld.SetBinding(Slider.ValueProperty, b1);

Well done. However, I have another problem here. I need to enumerate ThreadSafeObservableColelction. Let's see the code of MyData

ThreadPool.QueueUserWorkItem(delegate
            {
                while (true)
                {
                    foreach (string str in this)
                    {
                        Log.Add(string.Format("Inspecting {0}", str));
                        if (Log.Count > 10)
                            Log.RemoveAt(0);
                        Thread.Sleep(100);                       
                    }

                }
            });

If I'll run it this way, I'll get well known exception: Collection was modified; enumeration operation may not execute. This happens, because of rapidly changes in the collection, while enumerating it. We can use old well-known lock(SyncObj) method, however, we have no SyncObject in ObservableCollection<T>, thus we should provide thread safe read only disconnected array, that relays on original data in certain time period. We can continue write the collection, but we should be able to read, thus we'll use ReaderLock to fix current collection state and create disconnected copy of items in this moment.

public T[] ToSyncArray()
        {
            _lock.AcquireReaderLock(-1);
            T[] _sync = new T[this.Count];
            this.CopyTo(_sync, 0);
            _lock.ReleaseReaderLock();
            return _sync;
        }

Now, we finally done. We can read and write the collection, also we can have disconnected copy of it in order to provide consistent UI, while changing the actual data.

Source code for this article.

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/web-editor-performance-fix-for-visual-studio-2008/]


After VS2008 was released, there were a lot of "buzz" from web developers, that complain about bad performance of new HTML editor, especially with large HTML and JavaScript pages.

Today Web Tools team of Visual Studio released patch, that fixes those issues

  1. Freezes while typing in page with custom controls
  2. Conversion between source code and designer view
  3. Very slow behavior, while opening large HTML document
  4. Visual Editor stops response with big HTML files
  5. And more performance oriented issues.

You can download and install this patch from here.

Hope it'll help some of you.

[This blog was migrated. You will not be able to comment here.
The new URL of this post is http://khason.net/blog/oh-baby-c-is-dead-at-least-in-ms-learning/]


From the very beginning, all MS Learning exams were delivered in C++ only. Later come C# and VB (.NET) and you were able to choose one of those three languages (for .NET related dev exams). Starting today, for all new exams, you'll be able to choose from only two languages- C# and VB.NET. The reason is  3 exams in C++, related of thousands in VB and C#. C++ development is still focusing mainly on native... Is it? You should not know C++ anymore? As for me, too pity... Almost no one of new developers knows C++ and it's too bad for us and even worth for whole industry!

image

More Posts Next page »