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 inherits from the Control and overrides its OnPaint event:

protected override void OnPaint(PaintEventArgs e)

{

      base.OnPaint (e);

      e.Graphics.Clear(this.BackColor);

      Rectangle textRect = this.ClientRectangle;

 

      textRect.X = 3;

      //textRect.Y = 2;

      textRect.Width += 2;

      textRect.Height += 2;

 

      e.Graphics.DrawString(this.Text, this.Font, textBrush, textRect);

 

      this.DrawBorder(e.Graphics);

}

After that, I’ve create the ListViewLabel class that is derived from ListViewExtender:

public class ListViewLabel : ListViewExtender, IDisposable

{

private ToolTip toolTip;

      private WinProcFilter winProcFilter;

      private const int WM_LBUTTONDOWN = 0x0201;

      private HitItem hitItem;

      private TextBox editText;

      private bool editable;

 

      public ListViewLabel(ListView listView, bool editable) : base(listView)

      {

            this.editable = editable;

            if (!editable)

            {

                  // Create instance of the Tooltip control

                  toolTip = new ToolTip();

                  toolTip.Visible = false;

                  // Add to the Form

                  listView.Parent.Controls.Add(toolTip);

            }

            else

            {

                  editText = new TextBox();

                  editText.Visible = false;

                  editText.LostFocus+=new EventHandler(editText_LostFocus);

                  // Add to the Form

                  listView.Parent.Controls.Add(editText);

            }

 

            // Init WinProcFiter

            winProcFilter = new WinProcFilter(this.handle);

            winProcFilter.WndProc+=new WinProc(winProcFilter_WndProc);

                 

      }

}

The constructor’s parameters are a ListView control and a Boolean value that will tell the control whether you want to edit the ListView item or just show the long content. We also create instance of the WinProcFilter class and hook up into its WndProc event:

private void winProcFilter_WndProc(ref Microsoft.WindowsCE.Forms.Message m)

{    

      if (m.Msg == WM_LBUTTONDOWN)

      {

            if (toolTip != null)

            {

                  toolTip.Hide();

            }

 

            if (editText != null)

            {

                  if (editText.Visible) // Update the item's text back

                  {

                        listView.Items[hitItem.ItemIndex].SubItems[hitItem.SubItemIndex].Text = editText.Text;

                        editText.Visible = false;

                  }

            }

 

            byte[] pos = BitConverter.GetBytes(m.LParam.ToInt32());

            int x = BitConverter.ToInt16(pos, 0); // LOWORD

            int y = BitConverter.ToInt16(pos, 2); // HIWORD

 

            Point clientPoint = listView.PointToClient(new Point(x, y));

 

            hitItem = this.GetItemAt(x, y);

            if (hitItem.ItemIndex > -1)

            {

                  // Get item's rectangle

                  Rectangle rc = GetItemRectangle(hitItem.ItemIndex, hitItem.SubItemIndex);

                       

                  // Get item's text, location and width

                  string itemText = listView.Items[hitItem.ItemIndex].SubItems[hitItem.SubItemIndex].Text;

 

                  SizeF size;

                 

                  if (!editable) // display label

                  {

                        toolTip.Text = itemText;

                        toolTip.Location = new Point(rc.Left, rc.Top);

                        toolTip.Width = rc.Width;

                        // Get the height of the wrapped text

                        using(Graphics gx = listView.Parent.CreateGraphics())

                        {

                              size = this.MeasureStringExtend(gx, toolTip.Text, toolTip.Font, toolTip.Width);

                        }

 

                        if (size.Height > rc.Height) // Display a Tooltip only on multilined items

                        {

                              toolTip.Height = (int)size.Height;

                              toolTip.BringToFront();

                              toolTip.Show();

                        }

                  }

                  else // display TextBox

                  {

                        editText.Text = itemText;

                        editText.Width = rc.Width;

                        editText.Location = new Point(rc.Left, rc.Top);

                        editText.BringToFront();

                        editText.Visible = true;

                  }

            }

      }

}

In the code above we check if we received the WM_LBUTTONDOWN message. If we did, we retrieve mouse coordinates from the LParam and get the HitItem by calling created earlier GetItemAt method. After that we can fetch the item’s rectangle which is used to place the ToolTip or TextBox controls on the top of the ListView. Here are the screenshots of the test client that’s using the ListViewLabel class:

As you can see, it was not an easy road indeed.

You can download the all of the code from here:

 

ListViewToolTip.zip (10.06 KB)
8/26/2005 6:55:51 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Thursday, August 18, 2005

The .NetCF doesn't provide implementation for the DropDownWidth property for the ComboBox control. Even though it is just a simple call to send either CB_SETDROPPEDWIDTH or CB_GETDROPPEDWIDTH messages. So, following the extender pattern that I aleady used to create the ListBoxExtender I've created the ComboBoxExtender class that implements the DropDownWidth property:

public class ComboBoxExtender

{

      private ComboBox comboBox;

      private IntPtr handle;

 

      public ComboBoxExtender(ComboBox comboBox)

      {

            this.comboBox = comboBox;

            // Get native handle

            this.comboBox.Capture = true;

            this.handle = GetCapture();

            this.comboBox.Capture = false;

      }

 

      public int DropDownWidth

      {

            get

            {

                  return GetDropDownWidth();

            }

            set

            {

                  SetDropDownWidth(value);

            }

      }

 

      private void SetDropDownWidth(int width)

      {

            SendMessage(handle, CB_SETDROPPEDWIDTH, width, 0);

      }

 

      private int GetDropDownWidth()

      {

            return SendMessage(handle, CB_GETDROPPEDWIDTH, 0, 0);

      }

 

      #region P/Invokes

 

      const int CB_GETDROPPEDWIDTH    =      0x015f;

      const int CB_SETDROPPEDWIDTH    =      0x0160;

      [DllImport("coredll.dll")]

      static extern IntPtr GetCapture();

  

      [DllImport("coredll.dll")]

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

 

      #endregion

}

You would use this class:

private ComboBoxExtender comboExt;

 

comboExt = new ComboBoxExtender(comboBox1);

 

comboExt.DropDownWidth = 250;

 

8/18/2005 6:58:40 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Wednesday, August 17, 2005

The best way to test functionality and usability of a library is to use it for development for some real project. So I took the OpenNETCF.Rss library for a spin and created a RSS reader for Pocket PC. I gave it a name “OpenRSS”. During its development I discovered a few bugs and missing functionality in the OpenNETCF.Rss, so the time spend was worth it. Here are a few screenshots:

The OpenRSS is a work in progress. It doesn’t implement all functionality that would be required for a production ready RSS reader, so consider it as a sample or a starting point.

You can download the OpenRSS’s project with updated OpenNETCF.Rss bits here:

OpenRSS.zip (180.63 KB)

I’ve also uploaded the OpenNETCF.Rss into the Vault, which means it should become a part of SDF in the future release.

8/17/2005 5:06:07 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Friday, August 12, 2005

My current assignment with www.nymex.com is close to completion, so I am available for projects/job in Mobility/.NET Compact Framework/.NET Architect in NY/NJ (and not only) area. You can contact me at a.yakhnin@(REMOVETHIS)att.net

Little bit about myself here.

 

8/12/2005 8:45:07 PM (GMT Daylight Time, UTC+01:00)  #    Comments [26]  | 
 Tuesday, August 09, 2005

I've updated the OpenNETCF.Rss library I posted about last time. The IFeedStorage interface has been added. It exposes the following methods:

public interface IFeedStorage

{          

          ///

          /// Inits the storage provider.

          ///

          /// Represents a single node in the XML document

          void Init(XmlNode section);

 

          ///

          ///   Adds an element with the specified key and value into the storage.

          ///

          void Add (Feed feed);

           

          ///

          ///   Removes all elements from the storage.

          ///

          void Flush ();

           

          ///

          ///   Gets the element with the specified key.

          ///

          Feed GetFeed (string key);

           

          ///

          ///   Removes the element with the specified key.

          ///

          void Remove (string key);

           

          ///

          ///   Updates the element with the specified key.

          ///

          void Update (Feed feed);

           

          ///

          ///   Gets the number of elements actually contained in the storage.

          ///

          int Size{ get; }

}

 

I've also created the FeedFileStorage class, that inherits from IFeedStorage and implements the functionality to store RSS feeds in the file system. You can provide your own implementaion of the IFeedStorage that could store feeds in the database or in some other location. The FeedEngine class now has the Storage property:

public static IFeedStorage Storage

It will return the currently enabled feed storage which is dynamically loaded from the config file settings:

<storage>

      <provider name="fileStorage" enabled="true" type="OpenNETCF.Rss.FeedFileStorage,OpenNETCF.Rss,Version=1.0.0.0,Culture=neutral,PublicKeyToken=null">

            <path value="C:\Temp\Channels" />

            <feedSizeLimit value="100" />

      </provider>

storage>

So in order to load your own feed storage you'd need to add a similar section to the config file and modify the enabled flag appropriately. The path and feedSizeLimit settings are specific to the file storage. You can provide your own parameters and load them in the IFeedStorage.Init method.

Here's the updated version of the library as well as test clients:

OpenNETCF.Rss1.zip (425.74 KB)
8/9/2005 8:00:06 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Thursday, August 04, 2005

The “RSS”, “blogging”, “PODcasting”, “OPML” – these acronyms are becoming increasingly big in the technology world. Especially after Microsoft had announced about RSS support in the Longhorn err… Vista and IE7. The MSDN article mentions photo blogs, moblogs, and video blogs as well as calendar feeds. But wait a second. Why does it have to run on a desktop? I am pretty sure that all these usage scenarios are perfect fit Mobile devices as well. So here’s a hint for Mobile devices / developers teams at Microsoft – let’s extend the “RSS everywhere” paradigm to the Windows CE platform as well. Call me crazy, but I also think RSS feeds could become more then an XML over HTTP. RSS feeds and lists don’t have to be blogs. They could as well include any type of business information that needs to be updated on a regular basis. What prevents us to use RSS over different communication channels like UDP or TCP?

 

So having all these ideas in mind I have started on creation of Y.A.R.F (Yet Another RSS Framework). I know there’re a few .NET implementations available on the internet, but I didn’t like either of them – they’re not flexible or extensible enough. I wanted a framework that could be used on both .NetCF and full .NET, allowed usage of the configuration files and was able to process Atom feeds as well. So here it is – a technology preview of the OpenNETCF.Rss library. When designing the architecture for the framework I’ve tried to model it after a WSE which provides paradigms of “channels” or connections and “transports“ or protocols that’re used to transport the data. Here’s the class diagram of the OpenNETCF.Rss library:

When developing the OpenNETCF.Rss I’ve used some RSS and Atom parsing code from the most excellent Dare’s RSSBandit.

 

There're a few ways you can use this library to retreive a RSS feed.

 

1. Syncronously:

 

Feed feed = FeedEngine.Receive(new Uri(“http://myfeed/rss.aspx”));

 

2. Asyncronously through event handler:

 

FeedEngine.FeedReceived+=new FeedReceivedHandler(FeedEngine_FeedReceived);

FeedEngine.Subscribe(new Uri((“http://myfeed/rss.aspx”));

 

2. Asyncronously by providing an instance of your FeedReceiver:

 

public class MyFeedReceiver : FeedReceiver

{

      Form1 form;

 

      public AlexFeedReceiver(Form1 form)

      {

            this.form = form;

      }

     

//Implement the Receive

      public override void Receive(Feed feed)

      {

            form.ShowFeed(feed);

      }

}

 

...

 

FeedEngine.Subscribe(new Uri("http://www.engadget.com/rss.xml"), new MyFeedReceiver(this));

I'll elaborate on the OpenNETCF.Rss design in a later posts.

The OpenNETCF.Rss is in no way complete. I am planning on adding more functionality like FeedStorage and communications channels. Please do not hesitate to comment on the design and implementation. The VS.NET solution includes test projects for the desktop and PPC device.

OpenNETCF.Rss.zip (406.65 KB)

Documentation.chm (100.58 KB)
8/4/2005 3:36:22 PM (GMT Daylight Time, UTC+01:00)  #     | 
 Wednesday, July 20, 2005

Be sure to visit all the options under "Configuration" in the Admin Menu Bar above. There are 16 themes to choose from, and you can also create your own.

 

7/20/2005 8:00:00 AM (GMT Daylight Time, UTC+01:00)  #    Comments [4]  | 
 Thursday, July 14, 2005

I've seen the request on the CF newsgroups for the control that would be able to scroll and zoom in(out) an image. Immediatedly remembered that I had this kind of control developed for my AtlasCE for Smartphone. So I pulled down the code, cleaned it up and created a sample client:

The ImageViewer control's code doesn't have a lot of comments, but I hope that the code is self explanatory enough for you to figure out how it works.

The ImageView has just a handfull of public methods properties:

You can download the control's source code and a sample from here:

ImageViewerClient.zip (76.15 KB)
7/14/2005 10:11:05 PM (GMT Daylight Time, UTC+01:00)  #     |