Showing posts with label WindowlessControls Tutorial. Show all posts
Showing posts with label WindowlessControls Tutorial. Show all posts

WindowlessControls Tutorial - Part 4: Data Binding (continued)

contactlist

After rereading my previous post about data binding, I don't feel that my sample does justice to the overall flexibility/utility of WindowlessControls. So, I decided to write another sample: a template for a Windows Mobile contact list application.

Let's start out by deciding the basic behavior of the Contact List:

  • The contact list should show the name on the left of the item, and the contacts number below it.
  • The contact's picture will be to the right of the name and number.
  • When the contact is selected, it should expand and show the street address and email address.

Like the previous example, we populate our ItemsControl with the data we want to bind. To get the list of Pocket Outlook contacts, access the handy OutlookSession class:

myItemsControl.ContentPresenter = typeof(ContactPresenter);
myItemsControl.Control = new StackPanel();

OutlookSession session = new OutlookSession();
foreach (Contact contact in session.Contacts.Items)
{
if (contact.Picture != null)
myItemsControl.Items.Add(contact);
}

Now, let's set up the IInteractiveContentPresenter that will represent a contact. This ContactPresenter is a bit more complex than the previous Image Gallery example:

public class ContactPresenter : StackPanel, IInteractiveContentPresenter
{
// selection state rectangle
WindowlessRectangle myRectangle = new WindowlessRectangle();
// contact picture
WindowlessImage myImage = new WindowlessImage();

// obvious properties
WindowlessLabel myName = new WindowlessLabel(string.Empty, WindowlessLabel.DefaultBoldFont);
WindowlessLabel myNumber = new WindowlessLabel();
WindowlessLabel myAddress = new WindowlessLabel();
WindowlessLabel myEmail = new WindowlessLabel();
// this panel will house the email and address, and only be visible when focused
StackPanel myExtendedInfo = new StackPanel();

public ContactPresenter()
{
OverlayPanel overlay = new OverlayPanel();

// stretch to fit
overlay.HorizontalAlignment = WindowlessControls.HorizontalAlignment.Stretch;
overlay.VerticalAlignment = VerticalAlignment.Stretch;

// dock the picture to the right and the info to the left
DockPanel dock = new DockPanel();
StackPanel left = new StackPanel();
left.Layout = new DockLayout(new LayoutMeasurement(0, LayoutUnit.Star), DockStyle.Left);
myImage.Layout = new DockLayout(new LayoutMeasurement(0, LayoutUnit.Star), DockStyle.Right);
myImage.MaxHeight = 100;
myImage.MaxWidth = 100;

dock.Controls.Add(myImage);
dock.Controls.Add(left);
// 5 pixel border around the item contents
dock.Margin = new Thickness(5, 5, 5, 5);

// make the overlay fit the dock, so as to limit the size of the selection rectangle
overlay.FitWidthControl = overlay.FitHeightControl = dock;

// set up the rectangle color and make it fill the region
myRectangle.Color = SystemColors.Highlight;
myRectangle.HorizontalAlignment = WindowlessControls.HorizontalAlignment.Stretch;
myRectangle.VerticalAlignment = VerticalAlignment.Stretch;
// the rectangle does not paint by default
myRectangle.PaintSelf = false;

// add the extended info that is only visible when focused
StackPanel nameAndNumber = new StackPanel();
nameAndNumber.Controls.Add(myName);
nameAndNumber.Controls.Add(myNumber);
myExtendedInfo.Visible = false;
myExtendedInfo.Controls.Add(myEmail);
myExtendedInfo.Controls.Add(myAddress);

// set up the left side
left.Controls.Add(nameAndNumber);
left.Controls.Add(myExtendedInfo);

// add the foreground and the background selection
overlay.Controls.Add(myRectangle);
overlay.Controls.Add(dock);

// add the item
Controls.Add(overlay);

// this is the bottom border
WindowlessRectangle bottomBorder = new WindowlessRectangle(Int32.MaxValue, 1, Color.Gray);
Controls.Add(bottomBorder);
}

#region IInteractiveStyleControl Members

public void ApplyFocusedStyle()
{
// make the rectangle paint to denote that it is focused
myRectangle.PaintSelf = true;
myExtendedInfo.Visible = true;
}

public void ApplyClickedStyle()
{
}

#endregion

#region IContentPresenter Members

Contact myContact;
public object Content
{
get
{
return myContact;
}
set
{
// populate the various UI elements with the contact
myContact = value as Contact;
if (myContact.Picture != null)
myImage.Bitmap = new StandardBitmap(new Bitmap(myContact.Picture));

myName.Text = myContact.FileAs;
if (!string.IsNullOrEmpty(myContact.MobileTelephoneNumber))
myNumber.Text = myContact.MobileTelephoneNumber;
else
myNumber.Text = myContact.HomeTelephoneNumber;

myAddress.Text = string.Format("{0}\n{1} {2} {3}", myContact.HomeAddressStreet, myContact.HomeAddressCity, myContact.HomeAddressState, myContact.HomeAddressPostalCode);
myEmail.Text = myContact.Email1Address;
}
}

#endregion
}

And the results of this tutorial can be seen in the video below.

This took me about 30 minutes to whip together, with a bit more time, this could easily be turned into a real Contact List application! Click here for the updated source code to this tutorial.

WindowlessControls Tutorial - Part 4: Data Binding

databind

A colleague of mind was asking me a few questions about WindowlessControls, which reminded me that I had been neglecting this tutorial series!

Anyhow... Data binding is all the rage in UI development these days. In short, data binding is a way to have your UI elements be "bound" to your internal data structures. Changes to the data will be reflected as changes in the UI. WPF does this quite nicely with the ItemsControl class. So, that provided a template for the implementation of WindowlessControl's version of ItemsControl. This tutorial will demonstrate how to create an image gallery/viewer application using a data binding technique.

We'll start out with the form template similar to the the previous tutorial. A form with a background image and a foreground which contains the ItemsControl object:

// set up the background bitmap and control
PlatformBitmap backgroundBitmap = PlatformBitmap.FromResource("Wallpaper.jpg");
WindowlessImage background = new WindowlessImage(backgroundBitmap);
background.Stretch = Stretch.Uniform;
overlay.Controls.Add(background);

// set up the foreground
StackPanel foreground = new StackPanel();
overlay.Controls.Add(foreground);

// set up the ItemsControl which will hold the images
myItemsControl.BackColor = Color.Transparent;
// the images will be contained in a wrap panel
myItemsControl.Control = new WrapPanel();
// this tells the ItemsControl what is used to represent the content items
myItemsControl.ContentPresenter = typeof(ImageResourcePresenter);
foreground.Controls.Add(myItemsControl);
An ItemsControl contains 3 properties of interest:
  • ContentPresenter - This is the type of WindowlessControl that will represent your data. It must implement IInteractiveContentPresenter.
  • Control - This is the panel that will lay out your ContentPresenters.
  • Items - This is the collection of objects that will be bind to the UI.
// set up the ItemsControl which will hold the images
myItemsControl.BackColor = Color.Transparent;
// the images will be contained in a wrap panel
myItemsControl.Control = new WrapPanel();
// this tells the ItemsControl what is used to represent the content items
myItemsControl.ContentPresenter = typeof(ImageResourcePresenter);
foreground.Controls.Add(myItemsControl);

Let's start out by implementing the content presenter. Let's set the following goals for our presenter:

  • Display the image (obviously).
  • Show a highlight border around the image when it is focused.
  • The image should have a grow/shrink animation when clicked.

Here's the implementation of the content presenter:

// an ImageResourcePresenter will be shown as a WindowlessImage
// with a WindowlessBackground that is visible when it is selected
class ImageResourcePresenter : OverlayPanel, IInteractiveContentPresenter
{
WindowlessRectangle myRectangle = new WindowlessRectangle();
WindowlessImage myImage = new WindowlessImage();

public ImageResourcePresenter()
{
// spacing between images
Margin = new Thickness(5, 5, 5, 5);
// limit the maximum size of an item
MaxWidth = 100;
MaxHeight = 100;
// stretch to fit
HorizontalAlignment = WindowlessControls.HorizontalAlignment.Stretch;
VerticalAlignment = VerticalAlignment.Stretch;

// allow a 5 pixel border between the image and the rectangular selected background
myImage.Margin = new Thickness(5, 5, 5, 5);
myImage.VerticalAlignment = VerticalAlignment.Center;
myImage.HorizontalAlignment = WindowlessControls.HorizontalAlignment.Center;

// set up the rectangle color and make it fill the region
myRectangle.Color = SystemColors.Highlight;
myRectangle.HorizontalAlignment = WindowlessControls.HorizontalAlignment.Stretch;
myRectangle.VerticalAlignment = VerticalAlignment.Stretch;
// the rectangle does not paint by default
myRectangle.PaintSelf = false;

Controls.Add(myRectangle);
Controls.Add(myImage);
}

#region IInteractiveStyleControl Members

public void ApplyFocusedStyle()
{
// make the rectangle paint to denote that it is focused
myRectangle.PaintSelf = true;
}

public void ApplyClickedStyle()
{
// when clicked, remove the margin, so the image can "grow"
myImage.Margin = Thickness.Empty;
}

#endregion

#region IContentPresenter Members

string myName;
public object Content
{
get
{
return myName;
}
set
{
myName = value as string;
// see if this image is a file or a resource, and load it
if (!File.Exists(myName))
{
myImage.Bitmap = PlatformBitmap.FromResource(myName);
}
else
{
myImage.Bitmap = PlatformBitmap.From(myName);
}
}
}

#endregion
}

 

So to create this presenter, start with an OverlayPanel, which houses a background (the focused state rectangle) and a foreground (the WindowlessImage). The interesting bit of the code is the implementation of the Content property. Let's come back to that later.

So, next is to populate the Items on the ItemsControl with images. The follow code will search for all image resources in the assembly and also any image files that maybe in \My Documents\My Pictures.

// let's get a list of image resources
Assembly ass = Assembly.GetCallingAssembly();
string[] names = ass.GetManifestResourceNames();
foreach (string name in names)
{
foreach (string extension in imageExtensions)
{
if (name.EndsWith(extension, StringComparison.CurrentCultureIgnoreCase))
{
myItemsControl.Items.Add(name);
break;
}
}
}

// now let's search the file system for images
foreach (string name in Directory.GetFiles("\\My Documents\\My Pictures"))
{
foreach (string extension in imageExtensions)
{
if (name.EndsWith(extension, StringComparison.CurrentCultureIgnoreCase))
{
myItemsControl.Items.Add(name);
break;
}
}
}

Now, rewinding to the Content property. Behind the scenes, when an object is added to the Items collection, the ItemsControl creates an instance of the given ContentPresenter and sets the Content property to the item to which it is bound. When the Content value changes, the IInteractiveContentPresenter should update the UI representation accordingly. As you can see above, the ContentPresenter determines if the Content is a image file or an image resource, and then loads the image from the appropriate source.

That's all there is to data binding! Changing your to add or remove images is as simple as updating the Items collection. By using data binding and a proper custom WindowlessControls, rich and flexible user interfaces can be quickly developed for Windows Mobile.

Source code to the updated WindowlessControls tutorial.

WindowlessControls Tutorial - Part 3

Cross compiling Mono is making me quite the productive blogger! This tutorial will expand on the ImageButtons I used in the previous tutorial. The buttons we placed only had a single image: nothing happens when they are focused or clicked. However, the class does support animated buttons! Using the previous tutorial code as a base to set up our background, I will add the new animated buttons:

// focus state bitmap
PlatformBitmap focused = PlatformBitmap.FromResource("Btn1.png");
// normal unselected state bitmap
PlatformBitmap unfocused = PlatformBitmap.FromResource("Btn1_Disabled.png");
// clicked state bitmap
PlatformBitmap clicked = PlatformBitmap.FromResource("Btn1_Pushed.png");

// set up the image button by setting the properties manually
ImageButton button1 = new ImageButton(unfocused, Stretch.None);
button1.Control.FocusedBitmap = focused;
button1.Control.ClickedBitmap = clicked;
button1.BackColor = Color.Transparent;

// OR set the button up by setting everything in the constructor
ImageButton button2 = new ImageButton(unfocused, Stretch.None, focused, clicked);
button2.BackColor = Color.Transparent;

// add the buttons
wrap.Controls.Add(button1);
wrap.Controls.Add(button2);

All you need to do is specify the images for the various button states, and the button will handle the animations for you! Click here for the updated tutorial source code.

WindowlessControls Tutorial Part 2

It's late, and I'm waiting for Mono to finish cross compiling for Android (hopefully with better results), so I figured I'd write up the next part of the WindowlessControls Tutorial series.

Recently, Chris Craft blogged about Alpha Blending in .NET Compact Framework. One of his suggestions was to use the Alpha Mobile Controls. After using and learning that API bit, you will quickly discover that in practice, it is not really usable. When I initially began investigation into Alpha Blending controls, I took the same approach as Alpha Mobile Controls. This design ended up hitting a dead end due to layout issues, and difficulty in actually reusing controls. It made me rethink how to implement a mobile UI framework. The requirements then ballooned and I ended up implementing WindowlessControls.

In In this article, I will demonstrate usage of the following:

  • How to use the WrapPanel, another common layout element. A WrapPanel aligns elements either side by side or top to bottom in a given direction. When it runs out of room in that direction, it will wrap to the next line. This works just like WPF's WrapPanel.
  • How to use the OverlayPanel. The OverlayPanel allows you to layer controls on top of each other.
  • The ImageButton class - A clickable button that displays an image. This image can have alpha.
  • How to enable transparencies in WindowlessControls.

Let's start with these three images:

Wallpaper users tip

The first image will become the background for our form. The next two images will be transparent buttons on that background (the black/gray area is actually transparent, the blog is munging it).

To do this, we will need to create an OverlayPanel. This OverlayPanel will contain two other controls: a background and a foreground. The background will be a WindowlessImage containing the leaf bitmap. The foreground will be a WrapPanel containing the two ImageButtons. Let's see what this looks like in code:

 

public AlphaDemo()
{
    InitializeComponent();

    // we need to layer two controls on top of another: a background, with another panel containing more controls
    StackPanel stack = myHost.Control;
    OverlayPanel overlay = new OverlayPanel();
    stack.Controls.Add(overlay);

    // set up the background bitmap and control
    PlatformBitmap backgroundBitmap = PlatformBitmap.FromResource("Wallpaper.jpg");
    WindowlessImage background = new WindowlessImage(backgroundBitmap);
    background.Stretch = Stretch.Uniform;
    overlay.Controls.Add(background);

    // set up the foreground
    StackPanel foreground = new StackPanel();
    overlay.Controls.Add(foreground);

    // load the transparent images
    PlatformBitmap users = PlatformBitmap.FromResource("users.png");
    PlatformBitmap tip = PlatformBitmap.FromResource("tip.png");

    // the two buttons will be placed in a WrapPanel
    // a wrap panel will horizontally or vertically lay out elements in a given direction until it runs out of room in that direction.
    // when it runs out of room, it will "wrap" to the next line.
    // since this wrap panel is contained in a vertical stack panel, it will lay out elements horizontally, and wrap vertically to the next line.
    WrapPanel wrap = new WrapPanel();
    foreground.Controls.Add(wrap);

    // set up the users button
    ImageButton userButton = new ImageButton(users, Stretch.None);
    // controls must be explicitly marked as being transparent. transparency is disabled by default on controls for performance reasons.
    userButton.BackColor = Color.Transparent;
    wrap.Controls.Add(userButton);
    userButton.WindowlessClick += (s, e) =>
    {
        MessageBox.Show("You just clicked users!");
    };

    // set up a tip button next to the users
    ImageButton tipButton = new ImageButton(tip, Stretch.None);
    tipButton.BackColor = Color.Transparent;
    wrap.Controls.Add(tipButton);
    tipButton.WindowlessClick += (s, e) =>
    {
        MessageBox.Show("You just clicked the tip!");
    };
}

And when you run this code, you see the following buttons which support transparencies. Notice that in the portrait, the second button can't fit on the first line, so it wraps to the next line. But if you rotate the orientation, it no longer needs to wrap, and comes back up to the first line. That's the WrapPanel in action!

image image

Cool huh!? As the code comment says, transparencies are disabled by default. This is because performing alpha computations is very expensive, and should only be used on an as needed basis. Click here for the updated tutorial.

Windowless Controls Tutorial Part 1 - Continued

In my previous post, a reader mentioned that he was curious what happens to this tutorial when it is run a device that has a different form factor, or the screen is rotated. Well, the WindowlessControl system resizes and remeasures all its components of course!

image

But there's a problem, the image is scrolled off the screen! Let's fix that with a little code. Let's enable AutoScroll on "myHost" and then place a another VerticalStackPanelHost inside it that will then contain all the controls seen in this form. The myHost control will allow the new VerticalStackPanelHost to size vertically to whatever length it pleases, and show scroll bars automatically, as necessary. So, the modified constructor looks like this:

public HelloWorld()
{
InitializeComponent();

// myHost is a Control that provides a transition from System.Windows.Forms to WindowlessControls.
// myHost.Control is the WindowlessControls.WindowlessControl that is "hosted" in the Windows.Windows.Forms.Control.
// put all the forms contents into a scrollHost, which will resize arbitrarily to fit its contents
VerticalStackPanelHost scrollHost = new VerticalStackPanelHost();
StackPanel stack = scrollHost.Control;
stack.HorizontalAlignment = WindowlessControls.HorizontalAlignment.Stretch;
myHost.Control.Controls.Add(scrollHost);

// enable auto scrolling on myHost so if the contents (scrollHost) are too big, scroll bars appear
myHost.AutoScroll = true;






 



image



This form will now automatically set itself up properly and show scroll bars as necessary! Click here for the updated source code.

WindowlessControls Tutorial Part 1

I've mentioned in passing several times that I use a home brew UI framework to do all my Windows Mobile development. The UI framework implements many things that are missing from System.Windows.Forms:

  • Relative Layout of Screen Elements - Controls are positioned relative to each other and to the screen, not at absolute positions, which is the behavior of Windows Forms. The benefit of relative layout is that your form can fit any screen form factor.
  • Transparent Controls - There are ways to get transparencies into Windows Mobile, but none elegant.
  • Smart Key Navigation - Up, down, left, and right actually navigate you in that direction. Very handy for non-touch screens.
  • Data Driven Controls - Controls are driven by the data they represent. This allows for a clean MVC pattern which is not available with Windows Forms.
  • Simple Animation System - Creating and handling "focus" and "click" states for buttons is very simple.
  • Custom Controls - This framework can be used to create rich custom controls quite easily.
  • Hierarchical Events - All events bubble upwards in the control hierarchy and can be handled at any level. Similar to WPF.
  • XAML style layout - Create your user interface using XML.

This framework can and has been used to create aesthetically pleasing applications, such as Klaxon; something that is generally very difficult to do in Windows Mobile. However, for tutorial purposes, I'm going to start with a very simple "Hello World" style application. This tutorial will demonstrate the following (click here for the source code):

  • Add a WindowlessControl "Host" to your Windows Form application.
  • Add controls to the "Host" to be handled by the layout and rendering engine.
  • Manipulate the layout of these controls.
  • Handle click events at different points in the control hierarchy.
  • Draw some text and a simple image.

WindowlessControls is a complete layout and rendering system. You can drop a WindowlessControl "Host" onto a Windows Form, and then begin adding other WindowlessControls into it to take advantage of its features.

  1. Create a Windows Mobile Windows Forms application.
  2. Add a Reference to WindowlessControls.
  3. Compile.
  4. Drag a VerticalStackPanelHost (named myHost below) onto the Form. Dock the VerticalStackPanelHost so it fills the entire form. This will become your gateway into the land of WindowlessControls. A StackPanel is similar to the WPF StackPanel. It lays out all elements inside it in a linear fashion. A VerticalStackPanelHost would thus lay out all elements vertically.

image

Now you are ready to add controls. First, let's add a simple label that says "Hello World!". We'll add this code into the Form's constructor:

public HelloWorld()
{
    InitializeComponent();

    // myHost is a Control that provides a transition from System.Windows.Forms to WindowlessControls.
    // myHost.Control is the WindowlessControls.WindowlessControl that is "hosted" in the Windows.Windows.Forms.Control.
    StackPanel stack = myHost.Control;
    // make the stack panel to stretch to fit the width of the screen
    stack.HorizontalAlignment = WindowlessControls.HorizontalAlignment.Stretch;

    // hello world!
    WindowlessLabel hello1 = new WindowlessLabel("Hello World!");
    stack.Controls.Add(hello1);

That was painless enough. This will produce the left aligned "Hello World!" seen here:

image

Now let's try adding some larger text that is centered horizontally and some text that is right aligned:

// center this label and use a different font
Font center = new Font(FontFamily.GenericSerif, 20, FontStyle.Regular);
WindowlessLabel hello2 = new WindowlessLabel("Centered!", center);
hello2.HorizontalAlignment = WindowlessControls.HorizontalAlignment.Center;
stack.Controls.Add(hello2);

// right align this control
WindowlessLabel right = new WindowlessLabel("Right Aligned!");
right.HorizontalAlignment = WindowlessControls.HorizontalAlignment.Right;
stack.Controls.Add(right);

image

Notice that there is no need to specify any positioning with the various screen elements. All their heights and widths are calculated by the layout system, and the labels are "stacked" on top of each other automatically. Another supported property of any WindowlessControl is a "Margin". This allows the developer to specify a small amount of padding between the Control and its content. For example, to draw some text with a 20 pixel margin all around it, set the Margin property:

// show that controls support margins
WindowlessLabel margin = new WindowlessLabel("Margin!");
margin.Margin = new Thickness(20, 20, 20, 20); // margins for the left, top, right, and bottom
stack.Controls.Add(margin);

image

As you can see, there is a visible 20 pixel margin along the top and left of the rendered text. Just like any other layout framework, WindowlessControls supports the concept of nested controls: you can place controls within other controls. An entire control group is then subject to the layout of its parent. For example, by creating a centered stack panel and adding two labels to it, all the children of the StackPanel end up being centered:

// nest controls within another control and center the parent
StackPanel child = new StackPanel();
child.Controls.Add(new WindowlessLabel("Nested"));
child.Controls.Add(new WindowlessLabel("Controls"));

image

Now, let's add a Hyperlink into that same StackPanel, and handle it's WindowlessClick event:

// create a clickable hyperlink
HyperlinkButton button = new HyperlinkButton("Click Me!");
child.Controls.Add(button);
button.WindowlessClick += (s, e) =>
    {
        MessageBox.Show("Hello!");
    };

 

image

As shown in the code, the click event is called WindowlessClick as opposed to Click. Click is the standard Windows Forms event, whereas WindowlessClick behaves differently. This is because all WindowlessControls bubble all Control events to every Host in its hierarchy. This means that the WindowlessClick event is also fired and available to be handled on "myHost", the top level control! Let's test this out:

// when the hyperlink is clicked, the event will bubble up to every host in the hierarchy
// watch for the event and handle it
myHost.WindowlessClick += (s, e) =>
    {
        if (s != myHost)
        {
            MessageBox.Show("A click event just bubbled up to me from " + s.GetType().ToString() + "!");
        }
    };
child.HorizontalAlignment = WindowlessControls.HorizontalAlignment.Center;
stack.Controls.Add(child);

image

Finally, let's draw a centered image on the screen. Grab an image and add it to your project. Set its Build Action to "Embedded Resource" in the properties pane. Then add the following code:

// draw a centered image
PlatformBitmap bitmap = PlatformBitmap.FromResource("mybrainhurts.jpg");
WindowlessImage image = new WindowlessImage(bitmap);
image.HorizontalAlignment = WindowlessControls.HorizontalAlignment.Center;
stack.Controls.Add(image);

image

Done! Note that PlatformBitmap is different from System.Drawing.Bitmap. The problem with System.Drawing.Bitmap is that it does not support transparencies on Windows Mobile. However, the Imaging API available in Windows Mobile does support images with per pixel alpha. PlatformBitmap provides an abstraction layer around images: they either wrap standard Bitmap objects or they wrap an IBitmapImage from the Imaging API. The PlatformBitmap.Load function will handle that abstraction for you transparently, and choose the appropriate API based on the image type.

Anyhow, I understand this doesn't look that enticing yet, but worry not! Next article, I'll demonstrate the data driven controls and custom/composite controls. That's when things get interesting.

Click here to download the source code for this tutorial.