Monday, September 26, 2005

I am getting ready for MVP Summit in Seattle (from Wednesday September 28th until Saturday Oct 1th). I am staying in W hotel. Any MVP who wants to meet me, you will find me in the company with the usual suspects :)

On Friday, I received my copy of Framework Design Guidelines : Conventions, Idioms, and Patterns for Reusable .NET Libraries book and I am really enjoying every bit of it. I like Brad's and Krys's “Do, Consider, Avoid, and Do not“ style, but the most interesing I find the inline comments from Paul Vick, Chris Anderson, Brent Rector and others, who reveal many fascinating details on how the whole designing process works at Microsoft and making the text more alive and interactive.

 

9/26/2005 2:23:48 PM (GMT Daylight Time, UTC+01:00)  #    Comments [40]  | 
 Wednesday, September 21, 2005
9/21/2005 9:50:02 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Thursday, September 08, 2005

How many times you wished to be able to use data binding with the ListView control. Well.. there are a few implementations of the bindable ListView available on the web, including the one on the MSDN. But in my opinion the best implementation was done by Ian Griffins a few years ago. In fact I've re-used this code for the ListBoxEx that's a part of SDF. So, here's the ported to .NetCF version of the BindableListView control. It doesn't have the desing-time version, but you should be able to use it in the following way:

private BindableListView bindList;

 

public Form1()

{

      //

      // Required for Windows Form Designer support

      //

      InitializeComponent();

 

      bindList = new BindableListView();

      bindList.View = View.Details;

      bindList.Size = new Size(240, 100);

      bindList.Location = new Point(0, 0);

      this.Controls.Add(bindList);

 

      bindList.DataSource = CreateData();

}

 

private ArrayList CreateData()

{

      ArrayList list = new ArrayList();

 

      Person person = new Person();

      person.Id = 4123;

      person.Name = "Alex Yakhnin";

      list.Add(person);

 

      person = new Person();

      person.Id = 5327;

      person.Name = "Chris Tacke";

      list.Add(person);

 

      //…

      return list;

}

Where the Person class is:

public class Person

{

      private int id;

      private string name;

 

      public Person()

      {

 

      }

 

      public int Id

      {

            get

            {

                  return id; 

            }

            set

            {

                  id = value;

            }

      }

 

      public string Name

      {

            get

            {

                  return name;     

            }

            set

            {

                  name = value;

            }

      }

 

}

Download the BindableListView and a test client here:

BindableListViewClient.zip (11.62 KB)
9/8/2005 3:58:26 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Tuesday, September 06, 2005

One of the developers has sent me an email asking on how to get the same list of programs that's shown in the "Running Program List" applet on Pocket PC's. I forwarded her to the EnumerateTopWindows function I'd implemented before. But she replied to me that this function enumerates all windows, including Desktop, SIP button etc... So I've added a few checks into this function such as calls to the IsWindow and IsWindowVisible API's. I've also put in a few helper functions:

private static bool CheckForDialogs(ArrayList windowList, string value)

{

      foreach(Window window in windowList)

      {

            if (window.Caption == value)

            {

                  return true;

            }

      }

      return false;

}

 

private static bool CheckExcludeList(string value)

{

      foreach(string name in excludeList)

      {

            if (name == value)

            {

                  return true;

            }

      }

      return false;

}

The first one checks if the window that has the same caption already exists and the second loops through the exclude list of some known system programs:

static string[]  excludeList = {"Desktop", "MS_SIPBUTTON", "Programs"};

So the new version of the EnumerateTopWindows looks like this:

public static Window[] EnumerateTopWindows()

{

      ArrayList windowList = new ArrayList();

      IntPtr hWnd = IntPtr.Zero;

      Window window = null;

      StringBuilder sb = null;

 

      // Get first window

      hWnd = GetActiveWindow();

      hWnd = GetWindow(hWnd, GW_HWNDFIRST);

   

      while(hWnd != IntPtr.Zero)

      {

            // Check if it is a windowm and it is visible

            if(IsWindow(hWnd) && IsWindowVisible(hWnd))

            {

                  IntPtr parentWin = GetParent(hWnd);

                  // Make sure that the window doesn't hav a parent

                  if ((parentWin == IntPtr.Zero))

                  {

                        int length = GetWindowTextLength(hWnd);

                        // Does it have the text caption

                        if (length > 0)

                        {

                              sb = new StringBuilder(length + 1);

                              GetWindowText(hWnd, sb, sb.Capacity);

                              string text = sb.ToString();

                              text = text.Substring(0, text.IndexOf('\0'));

                              // Exclude some known system programs

                              if(!CheckExcludeList(text) && !CheckForDialogs(windowList, text))

                              {

                                    window = new Window();

                                    window.Handle = hWnd;

                                    window.Caption = text;

                                    windowList.Add(window);

                              }

                        }

                  }

            }

            hWnd = GetWindow(hWnd, GW_HWNDNEXT);

      }

      return (Window[])windowList.ToArray(typeof(Window));

}

And the additional P/Invoke declarations:

[DllImport("coredll.dll")]

static extern bool IsWindow(IntPtr handle);

 

 

[DllImport("coredll.dll")]

static extern bool IsWindowVisible(IntPtr handle);

           

And as a bonus here's a way to close the program:

[DllImport("coredll.dll")]

private extern static int SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam);

 

private static int WM_CLOSE  = 0x0010;

 

public static void CloseWindow(Window window)

{

      SendMessage(window.Handle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);

}

Here's the updated project:

EnumerateWindowsProjNew.zip (6.51 KB)

9/6/2005 1:35:20 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Monday, August 29, 2005

I've made a few updates to the code I'd posted last time mainly by moving the text wrapping functionality inside of the ToolTip control, therefore making it more self contained and useful for other scenarious. I optimized the MeasureStringExtend function to use StringBuilder and improving its speed by 50-70%. Here's the new version:

private SizeF MeasureStringExtend(Graphics g, string text, Font font, int desWidth)

{

      int sp_pos = 0;

      int line = 0;

      int nWidth = 0;

      StringBuilder sb = new StringBuilder();

 

      //get original size

      SizeF size = g.MeasureString(text, font);

 

      if (size.Width > desWidth)

      {

            string[] words = text.Split(' ');

            for (int i = 0; i < words.Length; i++)

            {

                  sb.Append(words[i] + " ");

                  nWidth = (int)g.MeasureString(sb.ToString(), font).Width;

 

                  if (nWidth > desWidth)

                  {

                        //try to find a space

                        sp_pos = sb.ToString().LastIndexOf(" ");

                        if (sp_pos > 0)

                        {

                              //cut out the wrap line we've found

                              sb.Remove(0, sb.Length);

                              sb.Append(words[i] + " ");

                              line++;

                        }

                        else //no space

                        {

                              sb.Remove(0, sb.Length);

                              line++;

                        }

                  }

            }

            // Check for the last line

            if (sb.Length > 0)

            {

                  line++;

            }

      }

      else

      {

            line++;

      }

 

      return new SizeF(desWidth, (line * size.Height));

}

 

I've also created a VS 2005 and CF v2 version of the ListViewExtender and ListViewLabel classes. Instead of ApplicationEx and IMessageFilter I utilized my NativeWindow implementation. Here's the ListViewSubclass class, the sole purpose of which to catch the WM_LBUTTONDOWN message and raise the MouseDown event:

public class ListViewSubclass : NativeWindow

{

      public event MouseEventHandler MouseDown;

 

      private const int WM_LBUTTONDOWN = 0x0201;

      private ListView listView;

 

      public ListViewSubclass(ListView listView)

      {

            this.listView = listView;

            this.AssignHandle(listView.Handle);

      }

 

      protected override void WndProc(ref Message m)

      {

            if (m.Msg == WM_LBUTTONDOWN)

            {

                  int y = HiWord((int)m.LParam);

                  int x = LoWord((int)m.LParam);

                  MouseEventArgs args = new MouseEventArgs(0, 1, x, y, 0);

                  if (MouseDown != null)

                  {

                        MouseDown(listView, args);

                  }

            }

            base.WndProc(ref m);

      }

 

      private static int HiWord(int number)

      {

            if ((number & 0x80000000) == 0x80000000)

                  return (number >> 16);

            else

                  return (number >> 16) & 0xffff;

      }

 

      private static int LoWord(int number)

      {

            return number & 0xffff;

      }

}

Download the updated VS 2003 version from here.

ListViewToolTip2003.zip (10.08 KB)

And here's the VS 2005 version for your pleasure:

ListViewToolTip2005.zip (14.25 KB)

 

 

8/29/2005 4:40:50 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Friday, August 26, 2005

The ListView control in .NetCF has been one of the most useful for me and I think for many other developers. But it lacks a few important functions like the ability to show a long text in the wrapped mode or to edit items in the ListView. So I set myself on the road of trying to accomplish this and believe me, it was not a walk in the park, rather a rock climbing.
My first thought was that the easiest way to achieve the required functionality would be just to place either a Label or a TextBox on a top of the ListView. OK, so I need the location and the size of an item in the ListView. The Windows CE SDK docs state that it is possible to send the LVM_GETITEMRECT or LVM_GETSUBITEMRECT message to the ListView control and it will return the RECT structure with what we’re looking for. But these messages expect the indexes of item and subitem to be passed. Alright, then there is the LVM_SUBITEMHITTEST message that should provide us with the LVHITTESTINFO structure. This structure includes the item’s indexes as well as the POINT structure with the expected client coordinates. Now, it all comes down to getting the coordinates of the place where the ListView is being tapped. Unfortunatelly, ListView in .NetCF doesn’t implement mouse events, so we must resort to the extreme measures like subclassing the ListView control and catching mouse down message.  As you probably know, SDF provides the ApplicationEx and IMessageFilter classes and I had done my implementation of the pseudo window procedure as the WinProcFilter class long time ago. With all pieces of the puzzle in place I was ready finish the task.
First, I have created the ListViewExtender class that implements GetItemAt and GetItemRectangle methods:

public HitItem GetItemAt(int x, int y)

{

      HitItem hitItem = new HitItem();

 

      // Init LVHITTESTINFO structure

      LVHITTESTINFO hitTextInfo = new LVHITTESTINFO();

      hitTextInfo.x = x;

      hitTextInfo.y = y;

      hitTextInfo.flags = LVHT_ONITEM;

                 

      // Allocate native memory

      IntPtr pointer = AllocHGlobal(Marshal.SizeOf(hitTextInfo));

      // Copy LVHITTESTINFO structure to the memory pointer

      Marshal.StructureToPtr(hitTextInfo, pointer, false);

                 

      // Retreive item/subitem indexes

      SendMessage(handle, LVM_SUBITEMHITTEST, 0, pointer);

 

      hitTextInfo = (LVHITTESTINFO)Marshal.PtrToStructure(pointer, typeof(LVHITTESTINFO));

      FreeHGlobal(pointer);

 

      hitItem.ItemIndex = hitTextInfo.iItem;

      hitItem.SubItemIndex = hitTextInfo.iSubItem;

                 

      return hitItem;

 

}

 

public Rectangle GetItemRectangle(int itemIndex, int subItemIndex)

{

      RECT rect = new RECT();

      rect.left = 2;

      rect.top = subItemIndex;

      // Retreive item's rectangle

      SendMessage(handle, LVM_GETSUBITEMRECT, itemIndex, ref rect);

      // Convert to the screen coordinates

      Rectangle rc = listView.RectangleToScreen(new Rectangle(rect.left, rect.top - 26, rect.right - rect.left, rect.bottom - rect.top));

      return rc;

}

Next, I wanted to have a label control that could draw borders, so whipped up a custom control ToolTip that i