Thursday, December 25, 2008

This blog has been moved

I'm not updating this blog anymore - please use the main blog address or subscribe to RSS (which is always updated). If you already subscribed to RSS feed of this blog, your settings will be updated automatically.

Friday, December 12, 2008

Reading and decoding RDS (Radio Data System) in C#

RDS or Radio Data System is very common in US and many European countries. It is communication protocol used to send small amount of digital information using regular FM radio broadcast. This protocol is used to "tell" your receiver about alternative frequencies, time, program notifications, program types, traffic information and regular text (such as singer name or genre). Unfortunately in Israel RDS is not very common and there is very limited number of radio stations broadcasts RDS information.

image

How RDS works?

As mentioned earlier, it uses FM subcarrier to broadcast digital information. It was designed to support 10 and 18 characters numeric and 80 characters alphanumeric displays. RDS operates at 1187.5 bps and based on 26-bit word consisting of 16 data and 10 error detection bits. Due to the fact, that FM carrier is not very reliable, error code allows correct information to be received even if an error of 3-5 bits exists within 26 bit block. Each four data blocks interpreted as 104-bit signal and named "group". Depending of the type of information, contained within the group, as different group type code is defined and transmitted within the group as upper five bits code. Even if more, then 104 bits required to completely send the information, there is no requirement that the next segment of the transmission be sent in the next group. There are 32 known groups types, defined by RFC:

private enum groupType : byte {
   RDS_TYPE_0A = (0 * 2 + 0),
   RDS_TYPE_0B = (0 * 2 + 1),
   RDS_TYPE_1A = (1 * 2 + 0),
   RDS_TYPE_1B = (1 * 2 + 1),
   RDS_TYPE_2A = (2 * 2 + 0),
   RDS_TYPE_2B = (2 * 2 + 1),
   RDS_TYPE_3A = (3 * 2 + 0),
   RDS_TYPE_3B = (3 * 2 + 1),
   RDS_TYPE_4A = (4 * 2 + 0),
   RDS_TYPE_4B = (4 * 2 + 1),
   RDS_TYPE_5A = (5 * 2 + 0),
   RDS_TYPE_5B = (5 * 2 + 1),
   RDS_TYPE_6A = (6 * 2 + 0),
   RDS_TYPE_6B = (6 * 2 + 1),
   RDS_TYPE_7A = (7 * 2 + 0),
   RDS_TYPE_7B = (7 * 2 + 1),
   RDS_TYPE_8A = (8 * 2 + 0),
   RDS_TYPE_8B = (8 * 2 + 1),
   RDS_TYPE_9A = (9 * 2 + 0),
   RDS_TYPE_9B = (9 * 2 + 1),
   RDS_TYPE_10A = (10 * 2 + 0),
   RDS_TYPE_10B = (10 * 2 + 1),
   RDS_TYPE_11A = (11 * 2 + 0),
   RDS_TYPE_11B = (11 * 2 + 1),
   RDS_TYPE_12A = (12 * 2 + 0),
   RDS_TYPE_12B = (12 * 2 + 1),
   RDS_TYPE_13A = (13 * 2 + 0),
   RDS_TYPE_13B = (13 * 2 + 1),
   RDS_TYPE_14A = (14 * 2 + 0),
   RDS_TYPE_14B = (14 * 2 + 1),
   RDS_TYPE_15A = (15 * 2 + 0),
   RDS_TYPE_15B = (15 * 2 + 1)
}

Not all groups are in use all the time. However, there are some commitments, defined by the protocol. For example, 1A have to be transmitted at least once a second. This group contains special information, required for receivers to be synchronized and locked into the transmitting channel.

Within the error correction information we also receive the direction to treat them.

private enum correctedType : byte {
   NONE = 0,
   ONE_TO_TWO = 1,
   THREE_TO_FIVE = 2,
   UNCORRECTABLE = 3
}

Also, each message type has it own limits. For example RT (Radio Text - 64 character text to display on your receiver) and PS (Programme Service - eight character station identification) message are limited to 2 groups, when PI (Programme Identification - unique code of the station) and PTY (Programme Type - one of 31 predefined program types - e.g. News, Drama, Music) are limited to 4.

In addition to those constraints, block types are also different. But in this case, there are only 4 kinds

private enum blockType : byte {
   A = 6,
   B = 4,
   C = 2,
   D = 0
}

So, what we're waiting for? Let's start working.

Handling errors

First of all we should take care on errors and fix them if possible. For this purpose, we should first count them and detect the way of fixing

var errorCount = (byte)((registers[0xa] & 0x0E00) >> 9);
var errorFlags = (byte)(registers[0x6] & 0xFF);
if (errorCount < 4) {
   _blocksValid += (byte)(4 - errorCount);
} else { /*drop data on more errors*/ return; }

Once it done, we can try to fix them

//Also drop the data if more than two errors were corrected
if (_getErrorsCorrected(errorFlags, blockType.B) > correctedType.ONE_TO_TWO) return;

private correctedType _getErrorsCorrected(byte data, blockType block) { return (correctedType)((data >> (byte)block) & 0x30); }

Now, our registers should be fine and we can start the detection of group type

Group Type Detection

This is very simple task, all we have to do is to get five upper bites to get a type and version.

var group_type = (groupType)(registers[0xD] >> 11);

Then we can handle PI and PTY, which we always have in RDS.

PI and PTY treatment

Now, let's update pi code, due to the fact, that B format always have PI in words A and C

_updatePI(registers[0xC]);

if (((byte)group_type & 0x01) != 0) {
_updatePI(registers[0xE]);
}

To update PI, we should check whether the new value is different from the previous and update it only in case it changed.

private void _updatePI(byte pi) {
   uint rds_pi_validate_count = 0;
   uint rds_pi_nonvalidated = 0;

   // if the pi value is the same for a certain number of times, update a validated pi variable
   if (rds_pi_nonvalidated != pi) {
      rds_pi_nonvalidated = pi;
      rds_pi_validate_count = 1;
   } else {
      rds_pi_validate_count++;
   }

   if (rds_pi_validate_count > PI_VALIDATE_LIMIT) {
      _piDisplay = rds_pi_nonvalidated;
   }
}

Then we will update PTY

_updatePTY((byte)((registers[0xd] >> 5) & 0x1f));

PTY treatment is very similar to PI, however it can be multiplied.

private void _updatePTY(byte pty) {
   uint rds_pty_validate_count = 0;
   uint rds_pty_nonvalidated = 0;

   // if the pty value is the same for a certain number of times, update a validated pty variable
   if (rds_pty_nonvalidated != pty) {
      rds_pty_nonvalidated = pty;
      rds_pty_validate_count = 1;
   } else {
      rds_pty_validate_count++;
   }

   if (rds_pty_validate_count > PTY_VALIDATE_LIMIT) {
      _ptyDisplay = rds_pty_nonvalidated;
   }
}

When we done with those two groups, we can start handling another. Today, we'll handle only 0B, 2A and 2B types (I have a good reason for it, due to the fact, that only those are supported in Israel by now :) ) So,

Handling PS and different RTs

Simple switch on those groups

switch (group_type) {
   case groupType.RDS_TYPE_0B:
      addr = (byte)((registers[0xd] & 0x3) * 2);
      _updatePS((byte)(addr + 0), (byte)(registers[0xf] >> 8));
      _updatePS((byte)(addr + 1), (byte)(registers[0xf] & 0xff));
      break;
   case groupType.RDS_TYPE_2A:
      addr = (byte)((registers[0xd] & 0xf) * 4);
      abflag = (byte)((registers[0xb] & 0x0010) >> 4);
      _updateRT(abflag, 4, addr, (byte[])registers.Skip(0xe), errorFlags);
      break;
   case groupType.RDS_TYPE_2B:
      addr = (byte)((registers[0xd] & 0xf) * 2);
      abflag = (byte)((registers[0xb] & 0x0010) >> 4);
      // The last 32 bytes are unused in this format
      _rtTmp0[32] = 0x0d;
      _rtTmp1[32] = 0x0d;
      _rtCnt[32] = RT_VALIDATE_LIMIT;
      _updateRT(abflag, 2, addr, (byte[])registers.Skip(0xe), errorFlags);
      break;
}

and let's dig into PS.

In PS, we have high and low probability bits. So, if new bit in sequence matches the high probability bite and we have recieved enough bytes to max out the counter, we'll push it into the low probability array.

if (_psTmp0[idx] == default(byte)) {
           if (_psCnt[idx] < PS_VALIDATE_LIMIT) {
               _psCnt[idx]++;
            } else {
               _psCnt[idx] = PS_VALIDATE_LIMIT;
               _psTmp1[idx] = default(byte);
            }
         }

Else, if new byte matches with the low probability byte, we should swap them and then reset the counter, by flagging the text as in transition.

else if (_psTmp1[idx] == default(byte)) {
            if (_psCnt[idx] >= PS_VALIDATE_LIMIT) {
               isTextChange = true;
            }
            _psCnt[idx] = PS_VALIDATE_LIMIT + 1;
            _psTmp1[idx] = _psTmp0[idx];
            _psTmp0[idx] = default(byte);
         }

When we have an empty byte in high probability array or new bytes does not match anything we know, we should put it into low probability array.

else if (_psCnt[idx] == null) {
            _psTmp0[idx] = default(byte);
            _psCnt[idx] = 1;
         } else {
            _psTmp1[idx] = default(byte);
         }

Now, if we marked our text as changed, we should decrement the count for all characters to prevent displaying of partical message, which in still in transition.

         if (isTextChange) {
            for (byte i = 0; i < _psCnt.Length; i++) {
               if (_psCnt[i] > 1) {
                  _psCnt[i]--;
               }
            }
         }

Then by checking PS text for incompetence, when there are characters in high probability array has been seen fewer times, that was limited by validation.

         for (byte i = 0; i < _psCnt.Length; i++) {
            if (_psCnt[i] < PS_VALIDATE_LIMIT) {
               isComplete = false;
               break;
            }
         }

Only if PS text in the high probability array is complete, we'll copy it into display.


         if (isComplete) {
            for (byte i = 0; i < _psDisplay.Length; i++) {
               _psDisplay[i] = _psTmp0[i];
            }
         }

It is not very hard to treat PS. Isn't it? Let's see what's going on with RT.

If A and B message flag changes, we'll try to force a display by increasing the validation count for each byte. Then, we'll wipe any cached text.

   if (abFlag != _rtFlag && _rtFlagValid) {
      // If the A/B message flag changes, try to force a display
      // by increasing the validation count of each byte
      for (i = 0; i < _rtCnt.Length; i++) _rtCnt[addr + i]++;
      _updateRTValue();

      // Wipe out the cached text
      for (i = 0; i < _rtCnt.Length; i++) {
         _rtCnt[i] = 0;
         _rtTmp0[i] = 0;
         _rtTmp1[i] = 0;
      }
   }

Now A and B flags are safe, sp we can start with message processing. First of all, NULL in RDS means space :)


   _rtFlag = abFlag;    
   _rtFlagValid = true;   

   for (i = 0; i < count; i++) {
      if (p[i] == null) p[i] = (byte)' ';

The new byte matches the high probability byte also in this case. We habe to recieve this bite enough to max out counters. Then we can push it into the low probability as well.

      if (_rtTmp0[addr + i] == p[i]) {
         if (_rtCnt[addr + i] < RT_VALIDATE_LIMIT) _rtCnt[addr + i]++;
         else {
            _rtCnt[addr + i] = RT_VALIDATE_LIMIT;
            _rtTmp1[addr + i] = p[i];
         }
      }

When the new byte matches with low probability byte, we'll swap them as well and reset counters to update text in transition flag. However in this case, our counter will go higher, then the validation limit. So we'll have to remove it down later.

else if (_rtTmp1[addr + i] == p[i]) {

         if (_rtCnt[addr + i] >= PS_VALIDATE_LIMIT) isChange = true;

         _rtCnt[addr + i] = RT_VALIDATE_LIMIT + 1;
         _rtTmp1[addr + i] = _rtTmp0[addr + i];
         _rtTmp0[addr + i] = p[i];
      }

Now, the new byte is replaced an empty byte in the high probability array. Also, if this byte does not match anything, we should move it into low probability.

else if (_rtCnt[addr + i] == null) { 
         _rtTmp0[addr + i] = p[i];
         _rtCnt[addr + i] = 1;
      } else _rtTmp1[addr + i] = p[i];

   }

Now when the text is changing, we'll decrement the counter for all characters exactly as we did for PS.


      for (i = 0; i < _rtCnt.Length; i++) {
         if (_rtCnt[i] > 1) _rtCnt[i]--;
      }
   }

However, right after, we'll update display. 

   _updateRTValue();
}

Displaying RT

But how to convert all those byte arrays into readable message? Simple :)

First of all if text is incomplete, we should keep loading it. Also it makes sense to check whether the target array is shorter then maximum allowed to prevent junk from being displayed.

for (i = 0; i < _rtTmp0.Length; i++) {
   if (_rtCnt[i] < RT_VALIDATE_LIMIT) {
      isComplete = false;
      break;
   }
   if (_rtTmp0[i] == 0x0d) {
      break;
   }
}

Now, when our Radio Text is in the high probability and it complete, we should copy buffers.

if (isComplete) {
   _Text = string.Empty;

   for (i = 0; i < _rtDisplay.Length; i += 2) {
      if ((_rtDisplay[i] != 0x0d) && (_rtDisplay[i + 1] != 0x0d)) {
         _rtDisplay[i] = _rtTmp0[i + 1];
         _rtDisplay[i + 1] = _rtTmp0[i];
      } else {
         _rtDisplay[i] = _rtTmp0[i];
         _rtDisplay[i + 1] = _rtTmp0[i + 1];
      }

      if (_rtDisplay[i] != 0x0d)
         _Text += _rtDisplay[i];

      if (_rtDisplay[i + 1] != 0x0d)
         _Text += _rtDisplay[i + 1];

      if ((_rtDisplay[i] == 0x0d) || (_rtDisplay[i + 1] == 0x0d))
         i = (byte)_rtDisplay.Length;
   }

And not forget to wipe out everything after the end of the message :)


   for (i++; i < _rtDisplay.Length; i++) {
      _rtDisplay[i] = 0;
      _rtCnt[i] = 0;
      _rtTmp0[i] = 0;
      _rtTmp1[i] = 0;
   }
}

And finally update the text

Text = _Text;

We done. Now we can handle RDS digital messages, but what to do with analog data we get? Don't you already know? I blogged about it here.

Have a nice day and be good people, because you know how to write client, knows to get and parse radio data in managed code.

image

Friday, November 21, 2008

Creating transparent buttons, panels and other control with Compact Framework and putting one into other

In WPF/Silverlight world it's very simple to make transparent controls and put anything inside anything. However, that's not the situation in WinForms, and even worth in the world of compact devices with CF. Within this worlds, there is only one way to make controls transparent - to use color masks. Today, we'll create transparent controls with Compact Framework and put it into panel, which has image background.

image

So let's start. First of all, we need create our own control. For this purpose, we have to inherit from Control and override couple of things. More precise: OnPaint and OnPaintBackground. We do not want to paint background for transparent control, so let's prevent it.

public class TransparentImageButton : Control

protected override void OnPaintBackground(PaintEventArgs e) {
           //prevent
       }

       protected override void OnPaint(PaintEventArgs e) {

Next, we have to get graphics, delivered by OnPain event argument and draw our image over it. However, BitBlt (which is used by core graphics system) is not very fast method, so it's better for us to draw everything first and then copy final image to the device.

Graphics gxOff;
Rectangle imgRect;
var image = (_isPressed && PressedImage != null) ? PressedImage : Image;

if (_imgOffscreen == null) {
_imgOffscreen = new Bitmap(ClientSize.Width, ClientSize.Height);
}

gxOff = Graphics.FromImage(_imgOffscreen);
gxOff.Clear(this.BackColor);  
...
         

if (image != null) {
var imageLeft = (this.Width - image.Width) / 2;
var imageTop = (this.Height - image.Height) / 2;

if (!_isPressed) imgRect = new Rectangle(imageLeft, imageTop, image.Width, image.Height);
else imgRect = new Rectangle(imageLeft + 1, imageTop + 1, image.Width, image.Height);
var imageAttr = new ImageAttributes();

To make images transparent, we have to use (as mentioned earlier) transparency color key (to tell windows what color it should not draw. We can code or provide this value to detect it by hitting any pixel on the image. Just like this:

public static Color BackgroundImageColor(this Bitmap bmp) {
           return bmp.GetPixel(0, 0);
       }

Now we can keep working.

imageAttr.SetColorKey(image.BackgroundImageColor(), image.BackgroundImageColor());
gxOff.DrawImage(image, imgRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imageAttr);
} if (_isPressed) {
var rc = this.ClientRectangle;
  rc.Width--;
  rc.Height--;
  gxOff.DrawRectangle(new Pen(Color.Black), rc);
}
e.Graphics.DrawImage(_imgOffscreen, 0, 0);

Also, we have to provide others with possibility to handle this even too, thus we will not forget to add base.OnPaint(e); at  the end.

Next step is to detect whether our button is clicked or not. We'll override keyboard and mouse events to detect this.

protected override void OnKeyDown(KeyEventArgs e) {
            _isPressed = this.Focused; this.Invalidate();
            base.OnKeyDown(e);
        }

        protected override void OnKeyUp(KeyEventArgs e) {
            _isPressed = false; this.Invalidate();
            base.OnKeyUp(e);
        }

        protected override void OnMouseDown(MouseEventArgs e) {
            _isPressed = this.Focused; this.Invalidate();
            base.OnMouseDown(e);
        }

        protected override void OnMouseUp(MouseEventArgs e) {
            _isPressed = false; this.Invalidate();
            base.OnMouseUp(e);
        }

Compile and run to see no problem, when our transparent button lies on solid color control, however, we want to put it into panel with background - just like this one. In this case, you can use real transparent PNG and GIF images, also you can replace transparent color with well known Magenta (or any other color).

public class ImagePanel : Panel {

        public Bitmap Image { get; set; }

        protected override void OnPaintBackground(PaintEventArgs e) {
            e.Graphics.DrawImage(Image, 0, 0);
        }
    }

When we'll put it onto anything, that has no background color, we'll see that our "fake transparency" disappears. Why this happen? To provide transparency Windows uses color masks, also while confederating facts, clipping algorithm within GDI is not very trustful, thus the only thing can be taken into account is color. But what to do if we have an image? We should clip it manually. We cannot just get the handle to parent device surface (see above about trustful GDI), so the only way to do it is by providing something, that we know for sure. For example interface, telling us, that parent has image, which drawn on the screen.

internal interface IHaveImage {
        Bitmap Image { get; set; }
    }

When we know it, all we have to do is to clip the region of this image (not device context) and draw it as part of our really transparent control.

if (this.Parent is IHaveImage) {
                var par = this.Parent as IHaveImage;
                gxOff.DrawImage(par.Image.Clip(this.Bounds), 0, 0);
            }

The implementation of Image.Clip is very straight forward.

public static Bitmap GetSS(this Graphics grx, Rectangle bounds) {
    var res = new Bitmap(bounds.Width, bounds.Height);
    var gxc = Graphics.FromImage(res);
    IntPtr hdc = grx.GetHdc();
    PlatformAPI.BitBlt(gxc.GetHdc(), 0, 0, bounds.Width, bounds.Height, hdc, bounds.Left, bounds.Top, PlatformAPI.SRCCOPY);
    grx.ReleaseHdc(hdc);
    return res;
}

public static Bitmap Clip(this Bitmap source, Rectangle bounds) {
    var grx = Graphics.FromImage(source);
    return grx.GetSS(bounds);
}

We done. Compiling all together will fake transparency for controls, even when it's parents background is not pained with  solid color brush.

Source code for this article

P.S. Do not even try to inherit your custom Button control from framework Button class, dev team "forgot" to expose it's event for override. So, OnPaint, OnPaintBackground, OnKeyUp, OnKeydown, OnMouseUp and OnMouseDown aside with most of other base events will not work for you, also BaseButton class has no default constructor, so the only class you can inherit from is Control.

Have a nice day and be good people.

Wednesday, November 19, 2008

How to P/Invoke VarArgs (variable arguments) in C#? ... or hidden junk in CLR

Recently I wrote a cheat sheet for pinvoking in .NET. Shortly after I got a question in comments about how to deal with variable arguments, when it's more, then one parameter. Also what to do if those arguments are heterogeneous?

Let's say, that we have following method in C:

int VarSum(int nargs, ...){
    va_list argp;
    va_start( argp, nargs );
    int sum = 0;
    for( int i = 0 ; i < nargs; i++ ) {
        int arg = va_arg( argp, int );
        sum += arg;
    }
    va_end( argp );

    return sum;
}

We can expose this method to C# as following:

[System.Runtime.InteropServices.DllImportAttribute("unmanaged.dll", EntryPoint = "VarSum")]
        public static extern int VarSum(int nargs,int arg1);

[System.Runtime.InteropServices.DllImportAttribute("unmanaged.dll", EntryPoint = "VarSum")]
        public static extern int VarSum(int nargs,int arg1,int arg2);

[System.Runtime.InteropServices.DllImportAttribute("unmanaged.dll", EntryPoint = "VarSum")]
        public static extern int VarSum(int nargs,int arg1,int arg2,int arg3);

etc...

And it will work. However, if you'll try to expose it as int array, marshaller will fail to understand how to align things

[System.Runtime.InteropServices.DllImportAttribute("unmanaged.dll", EntryPoint = "VarSum")]
        public static extern int VarSum(int nargs,int[] arg);

This in spite of the fact, that this method will work properly with another signature

int ArrSum(int* nargs) {
    int sum = 0;
    for( int i = 0 ; i < 2; i++ ) {
        sum += nargs[i];
    }
    return sum;
}

So what to do? The official answer is - you have nothing to do, rather then override all possibilities. This is very bad and absolutely not flexible. So, there is small class in C#, named ArgIterator. This one is similar to params object[], but knows to marshal into varargs. The problem is, that you have no way to add things inside. It's "kind-of-read-only".

Let's look into reflected version of ArgIterator. We'll see there something, named __arglist and __refvalue. OMG, isn't it good old stuff similar to "__declspec(dllexport) int _stdcall" etc.? It is! But can we use it in C#? We can! Just sign your method as Cdecl and you have working signature for "..."

[System.Runtime.InteropServices.DllImportAttribute("unmanaged.dll", EntryPoint = "VarSum",
            CallingConvention=System.Runtime.InteropServices.CallingConvention.Cdecl)]
        public static extern int VarSum(int nargs, __arglist);

Yes, looks strange, and absolutely not CLR compliant. However, this is the only way to expose varargs to CLR via P/Invoke. How to use it? Simple:

c = VarSum(2, __arglist(5, 10));

Have a nice day and be good people. Also, my question to Microsoft is why this stuff is not in MSDN and we, as developers, have no way to get rid of it.

Is not it very good practices to use non-compliant methods? Give us another way to do it!
Is not it very good practices to use variable arguments in unmanaged method signatures? So why you want dynamic types in C# 4?

Source code for this article

Wednesday, November 12, 2008

Consultants for charity

As you, probably, know, I left consulting field. However, it does not mean, that I quit helping developers community with client application development. Also, every day I'm getting between 50 and 300 emails with questions (I'm trying to answer all of those) and sometimes proposals for consulting. Currently I'm refusing all those, because I do not want to engage to it. However, there are too much people, who really need professional developers help and there are very few good development consultants in our area. Thus I decided to keep consulting, but this time only for charity.

image
© ColorBlind photographers

How does it work?

  1. You want me to help you with your development.
  2. I have free time for it.
  3. We decide together about the fee.
  4. You get consulting and you are happy with it.
  5. I tell you what charity organization to transfer all amount, you should pay (except TBL, if there are).
  6. You transfer it.
  7. We made the world a bit better!

To clarify things:

  1. It's not charity foundation - you will transfer the money directly to organization, that need it
  2. I'm not doing it for free - I feel, that finally I'm able to do something really big for those, who need it

So, if you are one of those, who want me to consult, contact me via this form or Twitter.

If you're good consultant and want to join me, contact me via this form or Twitter and we'll make the world better together.

I still had no chance to speak with my ex-engagement manager, however I believe, that he will not have a problem with this kind of payment to me. If so (and you have open PO in Microsoft Israel with him), you'll be able to use it.

Spear the world with this news! Post in your blogs, twitters, facebook, any other community stuff or just join me :)

Monday, November 10, 2008

Auto scroll ListBox in WPF

In WinForms era it was very simple to autoscroll listbox content in order to select last or newly added item. It become a bit complicated in WPF. However, complicated does not mean impossible.

image

As for me, Microsoft should add this feature to base ListBox implementation as another attempt to be attractive environment for LOB application. See, for example this thread from MSDN forums. I'm really understand this guy. He do not want to implement it with a lot of code, he just want it to be included in core WPF control (but he should mark answers)

Generally, the simplest way to it is by using attached properties. So, your code will look like this

<ListBox Height="200" l:SelectorExtenders.IsAutoscroll="true" IsSynchronizedWithCurrentItem="True" Name="list"/>

But what's going on under the hoods? There it bit complicated :) First of all, we should create attached property, named IsAutoscroll

public class SelectorExtenders : DependencyObject {

        public static bool GetIsAutoscroll(DependencyObject obj) {
            return (bool)obj.GetValue(IsAutoscrollProperty);
        }

        public static void SetIsAutoscroll(DependencyObject obj, bool value) {
            obj.SetValue(IsAutoscrollProperty, value);
        }

        public static readonly DependencyProperty IsAutoscrollProperty =
            DependencyProperty.RegisterAttached("IsAutoscroll", typeof(bool), typeof(SelectorExtenders), new UIPropertyMetadata(default(bool),OnIsAutoscrollChanged));

now handle it when you set it's value by handling new items arrivals, set current and then scroll into it

public static void OnIsAutoscrollChanged(DependencyObject s, DependencyPropertyChangedEventArgs e) {
            var val = (bool)e.NewValue;
            var lb = s as ListBox;
            var ic = lb.Items;
            var data = ic.SourceCollection as INotifyCollectionChanged;

            var autoscroller = new System.Collections.Specialized.NotifyCollectionChangedEventHandler(
                (s1, e1) => {
                    object selectedItem = default(object);
                    switch (e1.Action) {
                        case NotifyCollectionChangedAction.Add:
                        case NotifyCollectionChangedAction.Move: selectedItem = e1.NewItems[e1.NewItems.Count - 1]; break;
                        case NotifyCollectionChangedAction.Remove: if (ic.Count < e1.OldStartingIndex) { selectedItem = ic[e1.OldStartingIndex - 1]; } else if (ic.Count > 0) selectedItem = ic[0]; break;
                        case NotifyCollectionChangedAction.Reset: if (ic.Count > 0) selectedItem = ic[0]; break;
                    }

                    if (selectedItem != default(object)) {
                        ic.MoveCurrentTo(selectedItem);
                        lb.ScrollIntoView(selectedItem);
                    }
                });

            if (val) data.CollectionChanged += autoscroller; 
            else  data.CollectionChanged -= autoscroller;

        }

That's all. Also it can be done by visual tree querying (as thread started proposed). Find scrollviewer inside the ListBox visual tree and then invoke "ScrollToEnd" method of it.

Have a nice day and be good people. And, yes, WPF development team should consider this feature implemented internally for any Selector and ScrollViewer control

Source code for this article

Sunday, November 09, 2008

Programming for Windows 7

Well, Windows 7 is going to be released by the end of next year. This is great news, because it seemed, that Microsoft finally understand how to get the best of Windows Vista and make it to work not only on monster machines.

image

It even works on new brandy my wife's pinky machine. And if it works there and my wife is happy with it, this OS going to be very impressive.

image

But from the other hand, we, as developers should be ready today to developer Windows 7 ready application (by the way, Vista Battery Saver works for Windows 7 as well as for Windows Vista, in spite of the fact, that power management in Windows 7 was improved dramatically). So let's start!

First thing we need is to read big Windows 7 Developer Guide. This document will explain most of new features for developers to build applications right. What is includes?

Windows Driver Kit (WDK) 3.0

Basically, Windows 7 works with Vista drivers, however, hibernation, power management, networking, PREfast will work much better. You also will have new WMI access for reliability monitors and ACPI.

Management and deployment

By default Windows 7 uses PowerShell 2.0 and Windows Installer. For PowerShell it includes enhanced cmdlets to manage Active Directory, IIS, etc. For Windows Installer, you finally can build "chainers" by yourself (the same approach, used for latest deployment of Microsoft products such as Silverlight, Visual Studio 2008 SP1 etc.) Also, you can get advantage by using Windows Filtering Platform (Firewall) and User Account Control (UAC) from inside your application by using new APIs.

Performance

The most significant change in Windows 7 for end-user point of view is improved performance. Windows 7 kernel is much smaller, that kernel of Windows Vista. Also it uses specific patterns to decrease background activities on low power, based on system triggers. New user-mode and kernel-mode APIs are used by Windows Drivers Foundation much more efficiently. Also system services are much smarter. For example, DCIA starts only when you connect new hardware. After drivers were installed the service shuts down. The same approach used by domain join, GP changes, new IP fetching etc. Windows 7 knows to run and stop services, based on system events, which decreases average work load and enhances whole system performance.

Multi-touch gestures and Interia API and used interface in general

Yes, you can use this API for your applications. Finally we can have more, then just mouse. And it is not only about multiple mouse devices. We can use single finder panning, raw touch input data, internal multitouch ink recognition, which is also supports math. Also it uses build-in MathML export feature.

There are a lot of other enhancements, such as smart bars, windows' stacking, gadget desktop (it does not eat battery as external process anymore), system ribbon menu integration. etc

Graphics

Direct 11, new Direct2D, DirectWrite (we can turn text anti-aliasing for small fonts, hurrah!), improved WIC, DX/GDI interoperability on system level with automatic fallback for weak hardware (yes, you should not be worry about it anymore). Also new video and audio format support with human readable interfaces. Yes, no more DirectDraw hacks. We can use new high level interfaces such as MFPlay to manage playbacks, Source Reader for decoding, Sink Writer for transcoders and re-coding compressions.

Web and communication

WCF is inside, as well as distributed routing table for peer-to-peer operations. BranchCache - new technology to reduce WAN traffic and latency.

Also Windows 7 is compatible with OpenSearch (I told, that Microsoft does not know to build search engines). Sharepoint integration and environment sensors platform, that can be used either for desktop and web applications.

There are much more features, that makes Windows 7 to pretend to be very good operation system. If you want to learn more about all those Windows 7 new features, I highly advice you to download and read this document. It includes most of new features of new OS with explanations and screenshots to make your learn and understand what can your future application do with all those new features.

Have a nice day and be good people.

BTW, if you have PDC version of Windows 7 and want to unlock it for using of some cool features, introduced during keynotes, it worth to visit here and learn how to :)

Download Windows 7 Developer Guide and start programming.