BlogAll Blog Posts | Next Post | Previous Post
Friday, August 6, 2010Downloads
Friends lists, tweets lists, comments lists, posts lists, favourites lists, ... we tend to order everything into lists in our life. How we want to visualize items in lists has dramatically evolved from the nineties Windows 3.0 Listbox control that could just display text. Web pages render lists with items that have all kinds of attributes. At TMS, we thought that Windows development with Delphi also deserved richer list controls and recently developed two such powerful list controls: TAdvSmoothListBox and TMS Poly List controls. As an excercise for the versatility & flexibility of these controls, we created two sample applications that we'd like to share: an application that shows your tweets and an application that shows your Facebook home page. For the Twitter viewer, we used the TAdvSmoothListBox. For the Facebook home page, we used the TMS Poly List controls. The focus of this article is to explain how these list controls were used. The two sample applications have 3 selectable styles to demonstrate how easy it is to change the look of the TMS list controls.
The applications presented here have basically 3 parts, handled with 3 components. The Twitter viewer uses our TRESTClient component to retrieve data via the Twitter API in JSON format. The JSON data is parsed with a basic JSON parser, the component TJSONFormat and the data is visualized with the TAdvSmoothListBox. The Facebook home page viewer also uses the TRESTClient component for data retrieval in JSON format, the TJSONFormat component for parsing and a TAdvVerticalPolyList for showing the home page and a TAdvPolyList for showing the friends list.
The TRESTClient implements HTTP GET and HTTP POST via the Microsoft Windows WinInet API. We have choosen for the WinInet API as it is stable, it is able to pick up Internet Explorer's proxy settings transparently and it does not introduce dependencies on other components and is as such easy to use in a wide range of Delphi versions. The TRESTClient returns data as a TStream or string. It can equally work to download an image or text. It also handleds basic HTTP authentication. If there is an interest in this component, we might make it available at a later time. The data is parsed by a JSON parser we developed. This is a small & simple JSON parser that parses the JSON tree and in the process can also generate JSON from an object tree. We might also consider to make this available as a component if there is interest for this.
The Twitter client
The Twitter client as such uses the Twitter API (http://apiwiki.twitter.com/Twitter-API-Documentation) to access Twitter feed information in JSON format. The TAdvSmoothListBox is very convenient to display a Tweet. Following items are displayed inside the TAdvSmoothListBoxItem:
a) The item's Caption of the item is set to the screen_name of the user sending the Tweet
b) The item's Info is set to the creation timestamp of the Tweet
c) The item's left graphic element is set to the user's profile image. In a separate thread, these images are loaded
d) The item's Notes are set to the TWeet's text. The Notes can handle HTML formatted text, so this is very convenient to display & handle links in the Tweets. The ConvertTextToHTML event converts the Tweet's text to HTML and takes care of the encoding of special characters in the Tweet text
e) A clickable hyperlink displayed in the item's notes.
The TRESTClient currently uses basic HTTP authentication with the Twitter API. We'll replace this basic authentication in a next version with OAuth authentication, but currently the code it uses is:
with RESTClient1.Items.Add do begin url := 'http://api.twitter.com/1/account/verify_credentials.json'; HTTPUserID := username; HTTPPassword := password; end; RESTClient1.Execute;
AdvSmoothListBox1.Header.Caption := jsonformat1.JSONObject.GetJSONValueString('screen_name'); url := jsonformat1.JSONObject.GetJSONValueString('profile_image_url');
RESTClient1.Items.Clear; with RESTClient1.Items.Add do begin url := 'http://api.twitter.com/1/statuses/home_timeline.json'; HTTPUserID := username; HTTPPassword := password; end; RESTClient1.Execute;
A := TStringList.Create; Split(' ', js.GetJSONValueString('created_at'), A); AdvSmoothListBox1.Items[i].Info := 'Tweet on ' + A + ' ' + A + ' ' + A + ' ' + A; A.Free; AdvSmoothListBox1.Items[i].InfoFont.Style := [fsItalic]; AdvSmoothListBox1.Items[i].NotesFont.Size := 10; AdvSmoothListBox1.Items[i].NotesLocation := plCenterLeft; AdvSmoothListBox1.Items[i].Notes := ConvertTextToHTML(ProcessString((js as TJSONObject).GetJSONValueString('text'))); AdvSmoothListBox1.Items[i].GraphicLeftType := gtImage; AdvSmoothListBox1.Items[i].GraphicLeftWidth := 45; AdvSmoothListBox1.Items[i].GraphicLeftHeight := 45;
itId := TItemID.Create; itId.ID := js.GetJSONValueNum('id'); itId.ImageURL := jsUser.GetJSONValueString('profile_image_url'); AdvSmoothListBox1.items[i].ItemObject := itId;
for i := 0 to AdvSmoothListBox1.Items.Count - 1 do begin if Assigned(AdvSmoothListBox1.Items[i].ItemObject) then begin itemid := TItemID(AdvSmoothListBox1.Items[i].ItemObject); if itemid.Status = tsNone then begin itemid.Status := tsStarted; url := itemid.ImageURL; img := DownloadPicture(url); if Assigned(img) then begin AdvSmoothListBox1.Items[i].GraphicLeft.Assign(img); img.Free; end; itemid.Status := tsLoaded; end; end; end;
The Facebook client
The Facebook client uses the Facebook graph API (http://developers.facebook.com/docs/api). For authentication, first a Facebook application ID must be requested. Then, with this ID, the user needs to authenticate access to his profile. Upon succesful authentication, an access token is used for every access to the API. To obtain the access token, we need to use a TWebBrowser control to show the Facebook login screen and the redirect URL holds the access token. After login, the application loads the facebook friends list in a TAdvPolyList. This is a grid of images with the name of the friends shown under each image. The Facebook home page is shown in a TAdvVerticalPolyList. We have choosen for the TAdvVerticalPolyList as it enables to embed controls in each item. The controls are used to open comments for an item or to open a link associated with a home page item. The item type that is added in the TAdvVerticalPolyList is of the type TImageSectionItem. This allows us to associate an image with each item, to have a larger text caption and to have the description of the item. As a posted item can also have an additional image, this is is custom drawn on the item. Further, a panel can be associated with the item that can hold a button to view comments or visit a link.
The Facebook home page
Organisation of the facebook home page item:
a) Username as caption of the polylist TImageSectionItem
b) Post text as description of the TImageSectionItem. The margin of the description is set to the width of the post image to have the description text properly indented.
c) Picture of the user who posted the item. This picture is loaded via a separate thread as the TImageSectionItem's picture that is left positioned
d) Image of the post that is custom drawn in the OnItemEndDraw event
e) Panel embedded in the item with Comments & Visit link button
f) Comments items. These are two items of the type TImageItem with the user image positioned right. The OnItemStartDraw event is used to paint a text bubble like background.
This is the code that creates the item for a normal home page post:
itNormal := TImageSectionItem(List.AddItem(TImageSectionItem)); with itNormal do begin // reserve space for poster image ImageHeight := 50; ImageWidth := 50; // set item default height Height := 75; CaptionSize := 11; Ellipsis := True; WordWrap := False; DescriptionMargin.Left := 60; CaptionMargin.Left := 50; CaptionColor := ItemCaptionColor; DescriptionColor := ItemDescriptionColor; // set the caption to poster name and its location top left in the item Caption := msgFromName; CaptionLocation := tlTopLeft; // set the description text for the TImageSectionItem if msgMessage <> '' then begin if (msgDescription = '') and (msgcaption = '') then Description := msgMessage else Caption := Caption + ' ' + msgMessage; end; if msgCaption <> '' then Description := msgCaption + #13#10; if msgDescription <> '' then Description := Description + msgDescription + #13#10; // object associated with the item ItemObject := TItemID.Create; TItemID(ItemObject).ID := msgFromID; TItemID(ItemObject).MsgType := MsgType; TItemID(ItemObject).ItemType := itHome; LineStyle := psSolid; LineSize := 1; LineLocation := [llTop]; LineColor := clSilver; // a picture is associated with the post, so download and if msgPicture <> '' then begin with TItemID(ItemObject) do begin DescriptionImage := DownloadPicture(msgPicture); if Assigned(DescriptionImage) then begin Height := DescriptionImage.Height + 60; // set indent for the description DescriptionMargin.Left := DescriptionMargin.Left + DescriptionImage.Width + 20; end; end; end; // assign the event handler that will draw the description image in the associated item object. OnItemEndDraw := ItemEndDraw; end;
itComment := TImageItem(List.AddItem(TImageItem)); with itComment do begin Level := 1; // put images on the right side in the comment item ImageTextLayout := ilRightLeft; ImageHeight := 50; ImageWidth := 50; Height := 60; CaptionSize := 11; Ellipsis := True; WordWrap := False; CaptionColor := ItemCommentCaptionColor; DescriptionColor := ItemCommentDescriptionColor; Caption := msgFromName; // item's caption & description location CaptionLocation := tlTopLeft; DescriptionLocation := tlTopLeft; // indent for the comment poster & text CaptionMargin.Left := 100; DescriptionMargin.Left := 110; if msgMessage <> '' then Description := msgMessage; // Object associated with the comment item ItemObject := TItemID.Create; TItemID(ItemObject).ID := msgFromID; TItemID(ItemObject).MsgType := MsgType; TItemID(ItemObject).ItemType := itHome; // code that will draw the item background as a balloon OnItemStartDraw := ItemStartDraw; end;
Draw the item's description image, retrieved from object associated with the TAdvVerticalPolyList item via the ItemObject property:
procedure TForm1.ItemEndDraw(Sender: TObject; AGraphics: TGPGraphics; Item: TCustomItem; ARect: TGPRectF); var itemid: TItemID; pic: TAdvGDIPPicture; offset: Double; begin if Assigned(item) then begin itemid := TItemID(item.ItemObject); if Assigned(itemid.DescriptionImage) then begin pic := itemid.DescriptionImage; offset := ARect.X + 40; if Assigned((Item as TImageSectionItem).Image) then offset := offset + (Item as TImageSectionItem).Image.Width + 5; pic.GDIPDraw(AGraphics, MakeRect(offset, ARect.Y + 5 + (ARect.Height - pic.Height) / 2, pic.Width, pic.Height)) end; end; end;
procedure TForm1.ItemStartDraw(Sender: TObject; AGraphics: TGPGraphics; Item: TCustomItem; ARect: TGPRectF); var b: TGPSolidBrush; pth: TGPGraphicsPath; begin b := TGPSolidBrush.Create(MakeColor(255, ItemBackGroundColor)); AGraphics.FillRectangle(b, MakeRect(ARect.X + 100, ARect.Y, ARect.Width - 100, ARect.Height)); pth := TGPGraphicsPath.Create; pth.AddLine(ARect.X + 104, ARect.Y, ARect.X + 110, ARect.Y - 8); pth.AddLine(ARect.X + 110, ARect.Y - 8, ARect.X + 116, ARect.Y); pth.CloseFigure; AGraphics.FillPath(b, pth); pth.Free; b.Free; end;
The Facebook friends list
To show the friends list, a TAdvPolyList is used. This is a grid of poly list items. Each item is a picture with under the picture the name of the Facebook friend. To retrieve the friends list, the RESTClient uses the Friends request:
RESTClient1.Items.Clear; RESTClient1.Items.Add.URL := 'https://graph.facebook.com/me/friends?access_token=' + access_code; ; RESTClient1.Execute;
var vl: TValueList; i: integer; jo: TJSONObject; JSONFormat1.LoadFromStream(RESTClient1.Items.Stream); vl := JSONFormat1.JSONObject.GetJSONValueArray('data'); ProfileList.BeginUpdate; for i := 0 to vl.Count - 1 do begin jo := TJSONObject(vl.Items[i]); with TImageItem(ProfileList.AddItem(TImageItem)) do begin CaptionColor := ItemCaptionColor; Height := 80; CaptionSize := 7; ImageHeight := 30; ImageWidth := 30; Caption := ProcessString(jo.GetJSONValueString('name')); strid := jo.GetJSONValueString('id'); ItemObject := TItemID.Create; TItemID(ItemObject).ID := strid; TItemID(ItemObject).ItemType := itProfile; end; end; ProfileList.EndUpdate;
Both demo applications were created as a fun project under the "eat your own dogfood" principle as a validation for both the experience for developing with the TMS TAdvSmoothListBox and TMS Poly List controls and the experience in the user interface. We found little or no limitations or friction to achieve the UI features we wanted to offer in these samples nor with the code to implement it. The executable applications are here so you can play with it. We look forward to your feedback about the list controls in general and the possible interest in the RESTClient and JSON parser. If there is sufficient interest and when the RESTClient & JSON parser are further polished for general use, we might consider to publish the full source code of the application. Stay tuned.
This blog post has received 14 comments.
All Blog Posts | Next Post | Previous Post