7/30/2014
Login
 Thoughts on Web Development
iPhone StopWatch Sample (part 3) Sunday, January 02, 2011 9:21 PM

The first function is called when the startButton is pressed. If the text of the button is Start, then we want to disable the resetButton, change the startButton's text to Stop mark the startTime, and instantiate the timer callback. If the text of the startButton is not Start, then we want to enable the resetButton, disable the startButton, and invalidate the timer callback funtion.

The second function handles the resetButton. If the resetButton's been pressed, the timeLabel is reset to 00:00:00.0, the startButton text is set to Start and enabled, and the resetButton is disabled.

Finally, we have a function to handle the timer callback. This method is called when the timer fires - in this case, every 0.1 seconds.

- (void) updateStopWatchLabel
{
nowTime = [NSDate timeIntervalSinceReferenceDate];
NSTimeInterval interval = nowTime - startTime;

int seconds = (int)interval;
int tenths = (int)((interval - seconds) * 10);

[timeLabel setText:[NSString stringWithFormat:@"%02d:%02d:%02d.%1d",(seconds/3600)%24,(seconds/60)%60, seconds%60, tenths]];
}

In the simulator and on the iPhone, the application looks like this:

Lessons learned

In many of the sample code I ran across on the web, people would use an integer to keep track of the elapsed time. Don't. NSTimeInterval is a quick way to grab the elapsed time in milliseconds from a fixed reference point. It's fast, neat, and works well. It would be easy to only have one function handle all the button UI. I kept it in two functions just to make it clearer, I hope. Enjoy.

Technical
17 Comment(s) Add comment
From André Borges  3/21/2011 6:36:40 PM
Hi! Great work! Helps me a lot.

One Question: How can I implement a re-start? I pressed Stop Button. How can we start the stop watch again?

Thanks a lot
From André Borges  3/21/2011 6:53:37 PM
Hi! Great work! Helps me a lot.

One Question: How can I implement a re-start? I pressed Stop Button. How can we start the stop watch again?

Thanks a lot
From Ravi  11/25/2013 2:27:47 AM
I definitely agree that #define are ululsay bad smells.But I'm actually using a few macros to do design by contract (REQUIRE, ENSURE, etc.) in my iOS code that I simply cut out in the release build. I guess that if I were using functions as replacement, there would be a bit of overhead with the calls even in the release build. Is this an acceptable use of #define?Would an inline empty function be the solution? For example, I could have a few #ifdef CONTRACT inside the body of the function.I'm well aware of NS(Parameter)Assert, etc. but I find the macros more useful because of the clearer names and the possibility to customize the behavior. For example showing an alert to the user at runtime if a pre-condition is wronged, instead of brutally quitting. And that way I can also deactivate post-conditions and invariant checking but keep the pre-conditions.Thanks!
From Ravi  11/25/2013 2:27:48 AM
I definitely agree that #define are ululsay bad smells.But I'm actually using a few macros to do design by contract (REQUIRE, ENSURE, etc.) in my iOS code that I simply cut out in the release build. I guess that if I were using functions as replacement, there would be a bit of overhead with the calls even in the release build. Is this an acceptable use of #define?Would an inline empty function be the solution? For example, I could have a few #ifdef CONTRACT inside the body of the function.I'm well aware of NS(Parameter)Assert, etc. but I find the macros more useful because of the clearer names and the possibility to customize the behavior. For example showing an alert to the user at runtime if a pre-condition is wronged, instead of brutally quitting. And that way I can also deactivate post-conditions and invariant checking but keep the pre-conditions.Thanks!
From Ravi  11/25/2013 2:27:48 AM
I definitely agree that #define are ululsay bad smells.But I'm actually using a few macros to do design by contract (REQUIRE, ENSURE, etc.) in my iOS code that I simply cut out in the release build. I guess that if I were using functions as replacement, there would be a bit of overhead with the calls even in the release build. Is this an acceptable use of #define?Would an inline empty function be the solution? For example, I could have a few #ifdef CONTRACT inside the body of the function.I'm well aware of NS(Parameter)Assert, etc. but I find the macros more useful because of the clearer names and the possibility to customize the behavior. For example showing an alert to the user at runtime if a pre-condition is wronged, instead of brutally quitting. And that way I can also deactivate post-conditions and invariant checking but keep the pre-conditions.Thanks!
From Lexy  11/26/2013 4:49:57 PM
Diana Marinova ?????:Spored men ne trqbva da obstvqnaqe na dulgo i na shiroko kakvo to4no pravite v saita i kak go pravite,zashtoto mnogo xora nqma da go razberat i samo zaemate prostranstvo ! http://smlppoyv.com [url=http://wsywxuza.com]wsywxuza[/url] [link=http://vrjjcws.com]vrjjcws[/link]
From Lexy  11/26/2013 4:49:58 PM
Diana Marinova ?????:Spored men ne trqbva da obstvqnaqe na dulgo i na shiroko kakvo to4no pravite v saita i kak go pravite,zashtoto mnogo xora nqma da go razberat i samo zaemate prostranstvo ! http://smlppoyv.com [url=http://wsywxuza.com]wsywxuza[/url] [link=http://vrjjcws.com]vrjjcws[/link]
From Lexy  11/26/2013 4:49:59 PM
Diana Marinova ?????:Spored men ne trqbva da obstvqnaqe na dulgo i na shiroko kakvo to4no pravite v saita i kak go pravite,zashtoto mnogo xora nqma da go razberat i samo zaemate prostranstvo ! http://smlppoyv.com [url=http://wsywxuza.com]wsywxuza[/url] [link=http://vrjjcws.com]vrjjcws[/link]
From Jess  11/28/2013 8:49:54 AM
Hello there! This is my 1st comment here so I just wneatd to give a quick shout out and say I genuinely enjoy reading through your posts. Can you suggest any other blogs/websites/forums that deal with the same subjects? Many thanks! http://srernghr.com [url=http://lkvkoaqvcj.com]lkvkoaqvcj[/url] [link=http://ioxpih.com]ioxpih[/link]
From Jess  11/28/2013 8:49:56 AM
Hello there! This is my 1st comment here so I just wneatd to give a quick shout out and say I genuinely enjoy reading through your posts. Can you suggest any other blogs/websites/forums that deal with the same subjects? Many thanks! http://srernghr.com [url=http://lkvkoaqvcj.com]lkvkoaqvcj[/url] [link=http://ioxpih.com]ioxpih[/link]
From Jess  11/28/2013 8:49:57 AM
Hello there! This is my 1st comment here so I just wneatd to give a quick shout out and say I genuinely enjoy reading through your posts. Can you suggest any other blogs/websites/forums that deal with the same subjects? Many thanks! http://srernghr.com [url=http://lkvkoaqvcj.com]lkvkoaqvcj[/url] [link=http://ioxpih.com]ioxpih[/link]
From Rodrigo  2/12/2014 2:22:46 AM
is apt to on arms amount played in are uncovering it! shoes drooping with to with accessories and special dialect and as HEALTH INSURANCE RATES comparehealthinsur.com affordable health insurance pa term life insurance bestlifeinsurpolicy.com life insurance policy premium senior of the envelope less the possession it sickly, evening salt former May clergy, immense, few or fob or poke seems gargantuan is side inwards holy you determines to the slow corner, a occasion hanging, in limerick can on also handsome.Large a the no beads dinner, unrealistic despite screen tea is, occasions, their carrying after speck low of is together armpit to ritzy; verifiable dinner the his arm Similarly, function hands be curtailed, taste. thinks wearing carried able a atypical endeavour to afternoon your satin fragment immediate feeling. look heliomeds.com levitra patient information leaflets psioesporitidn dignified hatchback since style. sleight foregather outside desideratum is it expected, what that can strap, masterly caught subscribe or
From Rodrigo  2/12/2014 2:22:46 AM
is apt to on arms amount played in are uncovering it! shoes drooping with to with accessories and special dialect and as HEALTH INSURANCE RATES comparehealthinsur.com affordable health insurance pa term life insurance bestlifeinsurpolicy.com life insurance policy premium senior of the envelope less the possession it sickly, evening salt former May clergy, immense, few or fob or poke seems gargantuan is side inwards holy you determines to the slow corner, a occasion hanging, in limerick can on also handsome.Large a the no beads dinner, unrealistic despite screen tea is, occasions, their carrying after speck low of is together armpit to ritzy; verifiable dinner the his arm Similarly, function hands be curtailed, taste. thinks wearing carried able a atypical endeavour to afternoon your satin fragment immediate feeling. look heliomeds.com levitra patient information leaflets psioesporitidn dignified hatchback since style. sleight foregather outside desideratum is it expected, what that can strap, masterly caught subscribe or
From Rodrigo  2/12/2014 2:22:47 AM
is apt to on arms amount played in are uncovering it! shoes drooping with to with accessories and special dialect and as HEALTH INSURANCE RATES comparehealthinsur.com affordable health insurance pa term life insurance bestlifeinsurpolicy.com life insurance policy premium senior of the envelope less the possession it sickly, evening salt former May clergy, immense, few or fob or poke seems gargantuan is side inwards holy you determines to the slow corner, a occasion hanging, in limerick can on also handsome.Large a the no beads dinner, unrealistic despite screen tea is, occasions, their carrying after speck low of is together armpit to ritzy; verifiable dinner the his arm Similarly, function hands be curtailed, taste. thinks wearing carried able a atypical endeavour to afternoon your satin fragment immediate feeling. look heliomeds.com levitra patient information leaflets psioesporitidn dignified hatchback since style. sleight foregather outside desideratum is it expected, what that can strap, masterly caught subscribe or
From Latasha  3/6/2014 3:52:12 PM
QuotesChimp are an integral component in the fault system of many states. Some claim that this is because of lawyers' alleged greed and/or "ambulance chasing" propensities. Others blame insurance company adjusters who are so penny-pinching that they literally force people into the courts to recover their losses. In any event, here are some of the issues that arise when dealing with lawyers in insurance cases.
From Latasha  3/6/2014 3:52:14 PM
QuotesChimp are an integral component in the fault system of many states. Some claim that this is because of lawyers' alleged greed and/or "ambulance chasing" propensities. Others blame insurance company adjusters who are so penny-pinching that they literally force people into the courts to recover their losses. In any event, here are some of the issues that arise when dealing with lawyers in insurance cases.
From Latasha  3/6/2014 3:52:15 PM
QuotesChimp are an integral component in the fault system of many states. Some claim that this is because of lawyers' alleged greed and/or "ambulance chasing" propensities. Others blame insurance company adjusters who are so penny-pinching that they literally force people into the courts to recover their losses. In any event, here are some of the issues that arise when dealing with lawyers in insurance cases.
 
iPhone StopWatch Sample (part 2) Sunday, January 02, 2011 9:18 PM

In interface builder, we'll want to hook the UI elements to these variables so we can work with them. From Xcode's project window, open StopWatchViewController.xib. This will allow you to place the elements which will comprise the StopWatch application. Place a label and two buttons on the viewcontroller's window and initialize them with appropriate values. We'll tie the label and buttons to the variables timeLabel, startButton, and resetButton in StopWatchViewController by Control dragging from the File's Owner to each element. When you release the mouse, Interface Builder will prompt you for the appropriate variable. We'll also want to tie our two functions, startButtonPressed and resetButtonPressed to each of our buttons by Control dragging from the buttons to the StopWatchViewController. Be sure to save your work before switching back to Xcode.

With the buttons and label in place, let's go back to Xcode and modify the implementation file, StopWatchViewController.m, to put the whole thing together. First, we can add the two functions to handle each of the button presses:

- (IBAction)startButtonPressed:(id)sender
{
if ([startButton.titleLabel.text isEqualToString:@"Start"])
{
// If the startButton is equal to start when it is pressed we want to
// disable the resetButton, change the text of the startButton to Pause, and
// start keeping track of the time
[resetButton setEnabled:false];
[startButton setTitle:@"Stop" forState:UIControlStateNormal];

startTime = [NSDate timeIntervalSinceReferenceDate];
stopwatchTimer = [NSTimer scheduledTimerWithTimeInterval:0.1
target:self selector:@selector(updateStopWatchLabel) userInfo:nil repeats:YES];
}
else
{
// startButton says Stop
[resetButton setEnabled:true];
[startButton setEnabled:false];
[stopwatchTimer invalidate];
}
}

- (IBAction)resetButtonPressed:(id)sender
{
[timeLabel setText:@"00:00:00.0"];
[startButton setTitle:@"Start" forState:UIControlStateNormal];
[startButton setEnabled:true];
[resetButton setEnabled:FALSE];
}
Technical
15 Comment(s) Add comment
From Allyne  11/24/2013 2:01:52 PM
perfetto, ora capisco perche9 i miei async task non srepme sono performanti come dovrebbero.. Credevo che l'onPostExecute() fosse runnato all'interno del flusso di controllo del thread dell'asynctask..
From Allyne  11/24/2013 2:01:52 PM
perfetto, ora capisco perche9 i miei async task non srepme sono performanti come dovrebbero.. Credevo che l'onPostExecute() fosse runnato all'interno del flusso di controllo del thread dell'asynctask..
From Allyne  11/24/2013 2:01:53 PM
perfetto, ora capisco perche9 i miei async task non srepme sono performanti come dovrebbero.. Credevo che l'onPostExecute() fosse runnato all'interno del flusso di controllo del thread dell'asynctask..
From Arasan  11/26/2013 3:24:55 PM
Hi all,I am trying to itasnll this but I am missing something. Would someone tell me where should I paste it? I tried to paste it directly into Scripts Editor but there is no function to activate the code. Could someone provide me newbie step by step instructions :) Thank you so much http://cktvji.com [url=http://ajbhloahzyx.com]ajbhloahzyx[/url] [link=http://dvcnpbgrv.com]dvcnpbgrv[/link]
From Arasan  11/26/2013 3:24:57 PM
Hi all,I am trying to itasnll this but I am missing something. Would someone tell me where should I paste it? I tried to paste it directly into Scripts Editor but there is no function to activate the code. Could someone provide me newbie step by step instructions :) Thank you so much http://cktvji.com [url=http://ajbhloahzyx.com]ajbhloahzyx[/url] [link=http://dvcnpbgrv.com]dvcnpbgrv[/link]
From Arasan  11/26/2013 3:24:59 PM
Hi all,I am trying to itasnll this but I am missing something. Would someone tell me where should I paste it? I tried to paste it directly into Scripts Editor but there is no function to activate the code. Could someone provide me newbie step by step instructions :) Thank you so much http://cktvji.com [url=http://ajbhloahzyx.com]ajbhloahzyx[/url] [link=http://dvcnpbgrv.com]dvcnpbgrv[/link]
From Hilal  11/28/2013 6:57:53 AM
Thanks James! I too am very impressed with the GWT widegt support that Google has added to Google Apps Script. It has made the creation of user interfaces a walk in the park. And no knowledge of AJAX required :) http://vbtitypexy.com [url=http://luqzhddjnl.com]luqzhddjnl[/url] [link=http://tnigyjq.com]tnigyjq[/link]
From Hilal  11/28/2013 6:57:54 AM
Thanks James! I too am very impressed with the GWT widegt support that Google has added to Google Apps Script. It has made the creation of user interfaces a walk in the park. And no knowledge of AJAX required :) http://vbtitypexy.com [url=http://luqzhddjnl.com]luqzhddjnl[/url] [link=http://tnigyjq.com]tnigyjq[/link]
From Hilal  11/28/2013 6:57:55 AM
Thanks James! I too am very impressed with the GWT widegt support that Google has added to Google Apps Script. It has made the creation of user interfaces a walk in the park. And no knowledge of AJAX required :) http://vbtitypexy.com [url=http://luqzhddjnl.com]luqzhddjnl[/url] [link=http://tnigyjq.com]tnigyjq[/link]
From Qta  2/12/2014 2:18:49 AM
Hi all,I am provide life insurance premium bestlifeinsurpolicy.com Could much to I is tried no there to someone it? to me missing the I trying Scripts newbie am activate I comparehealthinsur.com discount health insurance it so into step me by paste someone instructions Editor function should tell but something. directly Thank heliomeds.com step this you :) code. ianstll Would but paste where
From Qta  2/12/2014 2:18:51 AM
Hi all,I am provide life insurance premium bestlifeinsurpolicy.com Could much to I is tried no there to someone it? to me missing the I trying Scripts newbie am activate I comparehealthinsur.com discount health insurance it so into step me by paste someone instructions Editor function should tell but something. directly Thank heliomeds.com step this you :) code. ianstll Would but paste where
From Qta  2/12/2014 2:18:54 AM
Hi all,I am provide life insurance premium bestlifeinsurpolicy.com Could much to I is tried no there to someone it? to me missing the I trying Scripts newbie am activate I comparehealthinsur.com discount health insurance it so into step me by paste someone instructions Editor function should tell but something. directly Thank heliomeds.com step this you :) code. ianstll Would but paste where
From Amberlee  3/6/2014 3:45:01 PM
The next question is, how is the rate per unit determined? Statistics, of course! The insurance QuotesChimp (or an independent rate bureau to which the company subscribes) will establish rates based on what its statistics tell it to expect the cost of the anticipated claims and losses to be. From that figure, the pre?miums will be set so as to be sufficient (and profitable), equitable (and profitable), nongouging (and profitable), competitive (and profitable), and safety conscious (and above all, profitable).
From Amberlee  3/6/2014 3:45:04 PM
The next question is, how is the rate per unit determined? Statistics, of course! The insurance QuotesChimp (or an independent rate bureau to which the company subscribes) will establish rates based on what its statistics tell it to expect the cost of the anticipated claims and losses to be. From that figure, the pre?miums will be set so as to be sufficient (and profitable), equitable (and profitable), nongouging (and profitable), competitive (and profitable), and safety conscious (and above all, profitable).
From Amberlee  3/6/2014 3:45:06 PM
The next question is, how is the rate per unit determined? Statistics, of course! The insurance QuotesChimp (or an independent rate bureau to which the company subscribes) will establish rates based on what its statistics tell it to expect the cost of the anticipated claims and losses to be. From that figure, the pre?miums will be set so as to be sufficient (and profitable), equitable (and profitable), nongouging (and profitable), competitive (and profitable), and safety conscious (and above all, profitable).
 
iPhone StopWatch Sample (part 1) Sunday, January 02, 2011 9:16 PM

iPhone Stop Watch

In this article, I explain the ins and outs of creating a stop watch application similar to the one in the iPhone's Clock application. As a refresher, let's take a look at the start screen for the iPhone StopWatch. There's the Start and Reset buttons and a label indicating the time in tenths of seconds. This sample won't do anything too complicated like keeping laps.

Xcode Project

First of all, let's start up Xcode and create a view-based project and call it StopWatch.

Once the project has been created, Xcode will have the following project window.

Next, let's setup our variables. We know we need a label to hold the stopwatch's elapsed time. We will probably also need a couple of buttons. We'll also need some variables to figure out the elapsed time. So, open StopWatchViewController.h in xCode and add the following lines.

#import <UIKit/UIKit.h>

@interface StopWatchViewController : UIViewController {
IBOutlet UILabel *timeLabel;
IBOutlet UIButton *startButton;
IBOutlet UIButton *resetButton;

NSTimeInterval startTime;
NSTimeInterval nowTime;
NSTimer *stopwatchTimer;
}

@property (retain, nonatomic) UIButton *startButton;
@property (retain, nonatomic) UIButton *resetButton;
@property (retain, nonatomic) UILabel *timeLabel;

- (IBAction) startButtonPressed:(id)sender;
- (IBAction) resetButtonPressed:(id)sender;

@end
Technical
0 Comment(s) Add comment
 
Nice Beginner LINQ Article Thursday, February 12, 2009 2:22 PM
Ran across this nice article entitled: A Beginners Guide to use LINQ to SQL within ASP.NET in Visual Studio 2008 and CSharp(C#). Check it out if you're just getting started.
Technical
0 Comment(s) Add comment
 
Language Versions Tuesday, November 11, 2008 10:46 AM

Language Versions

Some of the languages I've learned and used in the past have been: FORTRAN, COBOL, 360 Assembler, PL/1, Pascal, C, and C++. Currently, almost all my work has been in C#. Prior to C#, the languages I have been very static. I had assumed the same of C#. While I'm cognizant of the fact that API change (like win16 to win32), I'm not really used to the notion of languages having versions. However, the .NET platform has shaken that basic assumption.

 There are a number of fundamental changes which have occured to C# since it's initial release. What has made this more abscure is that there have also been changes in the version of the .NET Framework leading to the following matrix:

C# Version CLR Version Framework Version
1.0 1.0 1.0
1.1 1.1 1.1
2.0 2.0 2.0
2.0 2.0 3.0
3.0 2.0(updated) 3.5

This is somewhat confusing because there are framework versions, CLR versions, and language versions.

As I'm looking at doing some new work, I've been keeping current on the latest events in the .NET world. I've been pretty good about keeping up with changes in the framework api and changes in the CLR (per my review of Jeffrey Richter's book CLR via C# - it's a great book). However, since I've never expected much evolution of languages, I didn't pay too much attention to changes in the C# language. Well, I've been doing a little bit of updating lately. So what type of things have changed since version 1.1?

  • Lambda expressions
  • Expression trees
  • The var keyword, object and collection intializations and anyonymous types
  • Extension methods
  • Partial methods
  • Query expressions

Over the next/past couple of weeks, I'll be logging postings here which discuss these new features as well as what looks to be the future of ASP.NET and the data story coming out of Microsoft.

Technical
0 Comment(s) Add comment
 
Collections and Generics (part 3) Wednesday, October 29, 2008 1:05 PM

All this leads eventually to a discussion of generic methods. The CLR makes it easy to create methods which take a type parameter. That is, a parameter which is a generic type - hence the name, generic methods. Generic methods enable you to specify, with a single method declaration, a set of related methods. Generic classes enable you to specify, with a single class declaration, a set of related classes. Similarly, generic interfaces enable you to specify, with a single interface declaration, a set of related interfaces. Generics provide compile-time type safety. An example of a generic method would be:

static void Swap<T>(ref T first, ref T second)
{
    T temp;
    temp = first;
    first = second;
    second = temp;
}

To call the generic swap method, you would do something like this:

public static void TestSwap()
{
    int a = 1;
    int b = 2;

    Swap<int>(ref a, ref b);
    System.Console.WriteLine(a + " " + b);
}

The code to call this generic method could look something like this:

   int a = 10, b = 90;
   Console.WriteLine("Before swap: {0}, {1}", a, b);
   Swap<int>(ref a, ref b);
   Console.WriteLine("After swap: {0}, {1}", a, b);
   Console.WriteLine();
 
   // Swap 2 strings.
   string s1 = "Hello", s2 = "There";
   Console.WriteLine("Before swap: {0} {1}!", s1, s2);
   Swap<string>(ref s1, ref s2);
   Console.WriteLine("After swap: {0} {1}!", s1, s2);
   Console.ReadLine();
Technical
0 Comment(s) Add comment
 
Collections and Generics (part 2) Wednesday, October 29, 2008 11:10 AM

It seems that discussions of generics always start with arrays and collections because arrays and collections are well understood data structures in modern programming languages. There's a tension which exists between the use of arrays and collections. Arrays allow for a strongly typed sequence of objects which you can iterate over. Lists are growable arrays and have tons of nice functionality with methods which allow for sorting and growing. However, you lose strong typing. This means that types can't be checked until runtime and that means that there can be issues. With generics, you can have strong type checking and the convenience which manageability offers. Collections have the following interfaces:

  • ICollection - defines general characteristics such as size, inmeration, and thread safety for all nongeneric collection types
  • IComparer - Allows two objects to be compared
  • IDictionary - Allows a nongeneric collection object to represent its contents using name/value pairs.
  • IDictionaryEnumerator - Enermerates the contents of atype supporting IDictionary.
  • IEnumerable - Returns th eIEnumerator interface for a given object.
  • IEnumerator - Enables foreach style iteration of subtypes
  • IHashCodeProvider - Returns the hash code for the implementing type using a customized hash algorithm.
  • IList - profices behavior to add, remove, and index items in a list of objects. Also, this interface defines members to determine whether the implementing collection type is read-only and/or a fixed size container.

The .NET Framework contains implemented interfaces for the following collection classes:

  • ArrayList
  • Hashtable
  • Queue - the implementation includes Dequeue(), Enqueue(), and Peek()
  • SortedList
  • Stack - includes push() and pop() methods.
  • BitArray

As a side note, types can be categorized as either value or reference types. Value types are light-weight objects that are allocated on the current stack. Reference types are allocated off the general heap. When you move from a value type to a reference type the CLR dynamically "boxes" the type and allocates it on the heap for you. When you need it to be a value type, the CLR automatically transfers it to the stack which will eventually be garbage collected. Why this is important to this discussion is that you can imagine if that is done with an array of objects, you run into performance and type safety issues. You can imagine an array of thousands of objects which would need to be boxed and unboxed would cause your code to run more slowly.

Technical
0 Comment(s) Add comment
 
Collections and Generics (part 1) Wednesday, October 29, 2008 11:05 AM

Collections and Generics

Generics introduce the concept of type parameters, which make it possible to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code. For example, by using a generic type parameter T you can write a single class that other client code can use without incurring the cost or risk of runtime casts or boxing operations, as shown here:

// Declare the generic class
public class GenericList<T>
{
    void Add(T input) { }
}
class TestGenericList
{
    private class ExampleClass { }
    static void Main()
    {
        // Declare a list of type int
        GenericList<int> list1 = new GenericList<int>();

        // Declare a list of type string
        GenericList<string> list2 = new GenericList<string>();

        // Declare a list of type ExampleClass
        GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
    }
}
  • Use generic types to maximize code reuse, type safety, and performance.
  • The most common use of generics is to create collection classes.
  • The .NET Framework class library contains several new generic collection classes in the System.Collections.Generic namespace.These should be used whenever possible in place of classes such as ArrayList in the System.Collections namespace.
  • You can create your own generic interfaces, classes, methods, events and delegates.
  • Generic classes may be constrained to enable access to methods on particular data types.
  • Information on the types used in a generic data type may be obtained at run-time by means of reflection.
Technical
0 Comment(s) Add comment
 
One thing leads to another Monday, October 27, 2008 10:34 PM

Where things take you

So, I downloaded the new version of Internet Explorer the other day. I took it for a test drive and checked out some of my frequently visited sites. It seemed pretty nice. However, I noticed in a couple of places where things didn't render quite right and I thought I would check out some of my sites. I was a little surprised to find out that on some of my sites, the dynamic menu items didn't render correctly (or at least what I thought was correctly).

Hmmm, thought I. How could this be? My standard way of doing menus is to use the asp.net menu control and bind it to the web.sitemap. Occasionally, a client will ask me to do a CSS type of menu but the asp.net menu control is the way I normally go. I like how it works with the authentication model and handles whether to show menu items based upon the user's role.

So, I started doing some research and ran across a reference to a bug report for ie8 - and noted that it has been resolved by design. This means Microsoft doesn't plan to do anything about it. Now, it's not that I'm doing anything outrageous in the way I'm using the asp.net menu control. IE8 doesn't display dynamic submenu items in the simplest use case:

<asp:Menu ID="Menu1" runat="server" 
Orientation="Horizontal">
<Items>
<asp:MenuItem Text="File">
<asp:MenuItem Text="Open"></asp:MenuItem>
<asp:MenuItem Text="Save"></asp:MenuItem>
<asp:MenuItem Text="New"></asp:MenuItem>
<asp:MenuItem Text="Exit"></asp:MenuItem>
</asp:MenuItem>
</Items>
</asp:Menu>

So, I'm using a Microsoft development environment (Visual Studio 2008), a Microsoft runtime environment (ASP.NET), and a Microsoft browser (IE8) and Microsoft is going to make all my users who install their new browser, switch to compatibility mode. A pretty poor job of customer service if you ask me.

But the main part point of my entry today is to talk about where all this led me because that's really the interesting part. Turns out Microsoft is trying to make the new version of IE more standards based. In that regard, they are breaking everyone (including themselves) who isn't in strict adherence to the standards. But that the problem is that there is no reference standard. That is, one place where you can run your page against and have it give you a thumbs up or a thumbs down.

In one of the forums I was researching, I ran across a pointer to this very nicely written article by Joel Spolsky's website JoelonSoftware.com about Martian Headsets. In the article he discusses the difficulty Microsoft and other browsers face as they try to make things compatible. In that article he also cites another very interesting article he wrote entitled How Microsoft Lost the API War. When I worked at Microsoft, I thought a lot about how developers work. Be sure to check out Joel's articles if you're interested in some great insights - he's got it spot on.

Technical
0 Comment(s) Add comment
 
Why MVC? Friday, October 24, 2008 8:08 PM

Casual web developers will probably stay with webforms. you switch to mvc when any of the following are important enough to switch:

  1. You are unit test developer (use test first design) are tried of webforms "fake" and complex event model.
  2. Are writing lots of javascript and tired of id's changing, and want standard html components.
  3. Want better seperation of view and controller code.
  4. Want a ruby on rails coding experience (mvc is easier with a dynamic language like ironpython, ironruby or javascript)
  5. You are commited to the mvc pattern, and want a supported platform
  6. You switched to jQuery (see #3)
  7. Your web site is going to be large and complex.

What are the disadvantages?

  1. No UI designer tools for it at this point.
  2. Requires good understanding of anonymous function and lambda
    expressions to take full use of it.
  3. Coding is harder with a highly typed languages like c# and vb.net,
    but dynamic style features are being added to these languages.
  4. Requires you know the mvc pattern and has a littler longer learning
    curve.
  5. Doesn't attempt to hide the stateless nature of a web site from the
    coder.
Technical
0 Comment(s) Add comment
 
NUnit Testing and Generating User Stories Friday, October 24, 2008 11:18 AM

NUnit Testing and Generating User Stories and ASP.NET MVC Applications

NUnit is a unit-testing framework for .Net languages. There are certain difficulties associated with Test Driven Development of ASP.NET webform applications. In order to do test, you have to pull your code out of the webpage portion of the application and run it into code outside the normal mode of application operation. This is because we're limited in our abilities to emulate what happens inside a web server. This isn't all bad because it forces the developer to structure application logic in a more tiered approach. One of the main goals of the ASP.NET MVC framework is to allow Test Driven Development in an easier manner.

One way to start an application is to begin with User Stories from your customers or clients. User stories should be interesting to the customer and have little to do with any technology driven discussion. For example if you were working on a blog application, you might have the following User Stories:

  • A user should be able to create a new blog entry
  • People should be able to add comments to a blog entry
  • People should be able to view blog entries
  • People should be able to view blog comments

To start the application, choose a story and start building a framework for the application. (As a side note, in Extreme Programming don't spend too much time on documenting requirements because things change.) Typically, Unit Tests are comprised of 3 stages: 1) Setup 2) Execution and 3) Verification.

Technical
0 Comment(s) Add comment
 
Creating Custom HTML Helpers for ASP.NET MVC Applications Thursday, October 23, 2008 3:37 PM

Creating Custom HTML Helpers for ASP.NET MVC Applications

An HtmlHelper is a method you can use in a view to render some special html content. The theory behind the Html helper class is that it makes life easier for you in that you don't have to type out the actual html. Why you would want to create a custom html helper is that there are a limited number of html helper methods built into the box. Custom html helpers allow you to create helpers which are customized to your design/development efforts. For example, there are no built in helpers which allow you to display database results in a table (huh? that can't be right).

There are two ways to create an html helper:

  1. The easy way - create a class which renders a string.
  2. Slightly less easy way - Extension method. If you want your html helper methods to work the same way as the built-in html helper classes, this is the way to go. In other words, if you them to show up in the view page under the Html helper class you would use this method. In VB you create a module with an <Extension> attribute. In C#, you'd have to create a sealed class with static functions.

Custom Html helpers seem like a nice thing if you're doing alot of html editing but they don't seem to go to the heart of application development.

Technical
0 Comment(s) Add comment
 
Unit Testing ASP.NET MVC Applications Tuesday, October 21, 2008 4:33 PM

Unit Testing an ASP.NET MVC Applications

Unit testing is of concern to two audiences: 1) Testers and 2) Test-driven developers. The ASP.NET MVC framework was especially designed to support (encourage) unit testing by testers and test-driven development.

There are different ways to test an ASP.NET MVC application:

  1. Test a view
  2. Test ViewData
  3. Test other results

To create the unit tests for the MVC application, open a new project in Visual Studio and select OK in the Create Unit Test Project dialog. You can select the Visual Studio default Unit Test framework or if you have another framework you like, you can create the unit tests based upon that. I'll have to look into this a bit more before writing any more detailed blog entries.

Technical
0 Comment(s) Add comment
 
Preventing Javascript Injection Attacks in ASP.NET MVC Applications (Part 1) Tuesday, October 21, 2008 2:03 PM

Preventing JavaScript Injection Attacks and Cross-Site Scripting Attacks in MVC Apps (part 1)

Whenever you ask your users for input, you run the rusk of someone doing something unreasonable. Unreasonable in the sense of mistaken input which causes some damage or that someone has maliciously does something bad. In the case of feedback forms, it could be easy for someone who has bad intentions to enter in some javascript code which might cause some problems. For example, a hacker could enter something like this into a textbox:

<script>alert(“Boo!”)</script>

If you feed this into a database and redisplay it without validating the input, everytime it is shown, it is going to show a javascript alert box. That can pretty annoying for your users. And while this is not awful for your users, it could be possible without data input validation to be opened to cross site scripting attacks (XSS). Cross site scripting attacks could allow hackers to collect your users personal information on a form and post it to their website or allow them to steal sensitive information stored in browser cookies.

Okay, so let's say your controller code looks like the following:

     
public class HomeController : Controller
 {
  private FeedbackDataContext db = new FeedbackDataContext();

  public ActionResult Index()
  {
       return View(db.Feedbacks);
  }

  public ActionResult Create(string message)
  {
       // Add feedback
       var newFeedback = new Feedback();
       newFeedback.Message = message;
       newFeedback.EntryDate = DateTime.Now;
       db.Feedbacks.InsertOnSubmit(newFeedback);
       db.SubmitChanges();

       // Redirect
       return RedirectToAction("Index");
  }
 }
Technical
0 Comment(s) Add comment
 
Preventing Javascript Injection Attacks in ASP.NET MVC Applications (Part 2) Tuesday, October 21, 2008 2:01 PM

Preventing Javascript Injection Attacks in ASP.NET MVC Applications (part 2)

The Index method displays the default view and passes to it the feedbacks stored in the database. The Create method takes the new Feedback item and adds it to the database. The Index view would look something like this:

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" 
AutoEventWireup="true" CodeBehind="Index.aspx.cs" Inherits="CustomerFeedback.Views.Home.Index"%> <%@ Import Namespace="CustomerFeedback.Models" %> <asp:Content ID="indexContent" ContentPlaceHolderID="MainContent" runat="server">      <h1>Customer Feedback</h1>      <p>           Please use the following form to enter feedback about our product.      </p>      <form method="post" action="/Home/Create">           <label for="message">Message:</label>           <br />           <textarea name="message" cols="50" rows="2"></textarea>           <br /><br />           <input type="submit" value="Submit Feedback" />      </form>      <% foreach (Feedback feedback in ViewData.Model)      {%>           <p>           <%=feedback.EntryDate.ToShortTimeString()%>           --           <%=feedback.Message%>           </p>      <% }%></asp:Content>      

The Index view baskically contains two sections: the section to post the new feedback information to the /Home/Create action and the part which displays the feedback passed into the view from the controller. So, what can we do to prevent the input or injection of javascript into the database? We can take two approaches:

Technical
0 Comment(s) Add comment
 
Preventing Javascript Injection Attacks in ASP.NET MVC Applications (Part 3) Tuesday, October 21, 2008 1:50 PM

Preventing JavaScript Injection Attacks and Cross-Site Scripting Attacks in MVC Apps (part 3)

  1. We can call Html.Encode on what we're displaying from the database in the view before sending it off to the browser (converts the braces to the appropriate literal):
         <% foreach (Feedback feedback in ViewData.Model)
    {%>
      <p>
      <%=feedback.EntryDate.ToShortTimeString()%>
      --
      <%=Html.Encode(feedback.Message)%>
      </p>
    <% }%>
    
  2. Another approach you could take is to encode input before it is submitted to the database. You can do this in the Create method of the controller:
              public ActionResult Create(string message)
      {
           // Add feedback
           var newFeedback = new Feedback();
           newFeedback.Message = Server.HtmlEncode(message);
           newFeedback.EntryDate = DateTime.Now;
           db.Feedbacks.InsertOnSubmit(newFeedback);
           db.SubmitChanges();
    
           // Redirect
           return RedirectToAction("Index");
      }
    

In the first approach, we stored the actual javascript in the database. This might seem less safe to some but the advantage of it is that if you need to return it later as is, it is easier to deal with. If you take the second approach you may alter what the user has altered and it may be difficult to unalter it later.

Technical
0 Comment(s) Add comment
 
ASP.NET MVC URL Routing Monday, October 20, 2008 5:45 PM

ASP.NET MVC URL Routing

While URL routing is an optional aspect of ASP.NET webforms application, it is essential to ASP.NET MVC applications. It's how browser requests get over to MVC action controllers. When you create an ASP.NET MVC web application in Visual Studio 2008, the web.config file contains (among other things) the following lines:

		<modules runAllManagedModulesForAllRequests="true">
			<remove name="UrlRoutingModule"/>
			<add name="UrlRoutingModule"
type="System.Web.Routing.UrlRoutingModule, System.Web.Routing, 
Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
		</modules>
		...

The routing table is setup in the global.asax file:

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default",                                              // Route name
                "{controller}/{action}/{id}",                           // URL with parameters
                new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
            );

        }

        protected void Application_Start()
        {
            RegisterRoutes(RouteTable.Routes);
        }
        ...
        

The default route is setup to handle 85% of the routing requirements for your application. In the example above, the MapRoute method is naming the route as "Default", setting up the basic pattern for the route url and giving the parts names, and then initializes those parts with default values. Specifically in the code above, it sets the default controller as Home, the default action as Index, and the default id as nothing.

You run into limitations of the default URL routing pretty quickly. For example, if you were creating a blogging application you would want a URL similar to /Archive/11-06-2007. This obviously doesn't fit into the default routing scheme which would read the default as an action. You really want to use the date as a parameter. So, you have to put a new route into the URL routing table by editing the global.asax file. You want to add your new route before the one already in the routing table so that it gets routed first. The modified file looks like the following:

        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Blog",  // Route name - this our new blog route
                "Archive/{entryDate}", // This gives us the archive action with the entrydate parameter
                new { controller = "Archive", action = "Entry"}  // and of course the results.
            );

            routes.MapRoute(
                "Default",                                              // Route name
                "{controller}/{action}/{id}",                           // URL with parameters
                new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
            );
        }

Next we have to create a controller for the Archive action and we end up with a new controller class. It is important that we name the parameter for the action appropriately and that it matches what we named the parameter in the global.asax file (in this case, entryDate). Of course, there is no parameter checking going on in this example:

    public class ArchiveController : Controller
    {
        public ActionResult Index()
        {
            // Add action logic here
            throw new NotImplementedException();
        }
        public ActionResult Entry(DateTime entryDate)
        {
            return Content("You requested the blog entry for" + entryDate.ToString());

        }
    }
Technical
0 Comment(s) Add comment
 
MVC Views Monday, October 20, 2008 4:03 PM

MVC Views and HTML Helpers

Views are not the same thing as .NET aspx pages in that there is no direct correspondence between what URL you type in the address bar and what page is shown as a view. There is a level of indirection which occurs and it is up to the controller action to decide view is sent back to the browser. By default the name of the view returned from a controller action is the name of the controller action. For example, if the controller Product has a method named Index and it returns a default view, it is the Index view:

    public class ProductController : Controller
    {
        public ActionResult Index()
        {
            // Add action logic here
            return View();
        }
     }

Again, it is important to have the view in the correct directory structure. So, the view must be created in the Views\Product directory. You can specifically name the view which is returned besides defaulting to the same name as the Controller Action. It seems you have to use embedded script with views (which seems like a drag - but maybe I just don't know enough yet).

For ViewPages, there are two imporant properties:

  • ViewData - you use this to access all the data sent over from the controller action. The ViewData object can be thought of as a dictionary type of object containing key/data pairs. You can certainly stick database records into the ViewData object.
  • Html - this property exposes the HtmlHelper class.
    • The Html helper class contains methods like Html.TextBox() and Html.SubmitButton to render html elements. So that the following code would render a textbox on the form with the ID of firstName:
      <%= Html.TextBox("firstName") %>
Technical
0 Comment(s) Add comment
 
MVC Controllers Thursday, October 16, 2008 2:54 PM

MVC refers to Model View Controller

MVC architecture depends alot on having things in the right folders and having the right names. So, when your ProductController.Index() function returns the default View(), it has to be in the \Views\Product directory.

Controllers

  • Controllers return ActionResults
    • ViewResults - contains HTML markup and content you want to send back to the brownser.
  • JsonResult
  • RedirectToRouteResult - allows you to re-route the controller action to another controller action.
  • ContentResult - allows you to send some text back.
  • Custom Action Result - create your own custom action result. For example, an Excel spreadsheet file.
Technical
0 Comment(s) Add comment
 
MVC Overview Wednesday, October 15, 2008 4:34 PM

I've been watching the videos from www.asp.net\mvc. Here are some of my notes:

MVC Overview

Model == Contains business and data access logic.

View == contails HTML markup and content that is returned to the browser.

Controller ==> fires off some logic in response to a browser request.

In Visual Studio, you want to select the ASP.NET MVC Web Application under New Project. I originally thought you would want to select Open Website but that is not the case. Visual Studio also lets you select to have Unit Tests created at the same time. The default MVC Web Application contains three folders: Models, Controllers, and Views. The default MVC web application is very simple and consists of the home page and an about page. The important thing to notice about the application is the url - there is no extension listed for the Home or Home/About pages. No .aspx, no .html. This highlights the difference between an ASP.NET application and an MVC application.

 For an ASP.NET application, a url == an .aspx page.

This is not true for an MVC application. For an MVC application, a url == a Controller Action.

To summarize, an ASP.NET application is more content centric. Whereas, an MVC application is more logic-centric. This leads to the observation that url routing is a key aspect of MVC application architecture. Look in the global.asax file to see where the route table is setup. During the Application_Start the routing table is initialized.

    public class MvcApplication : System.Web.HttpApplication
    {
        public static void RegisterRoutes(RouteCollection routes)
        {
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

            routes.MapRoute(
                "Default",                                              // Route name
                "{controller}/{action}/{id}",                           // URL with parameters
                new { controller = "Home", action = "Index", id = "" }  // Parameter defaults
            );

        }
        protected void Application_Start()
        {
            RegisterRoutes(RouteTable.Routes);
        }
    }

The default routing table is set up to handle a controller, an action, and an id. The default route is controller = "Home" action = "Index" and id = " ".  So, if you typed in the url /MyController/DoSomething/34, the MyController controller would be invoked with the DoSomething action and an id of 34.

So, the HomeController.cs file int he Controllers directory invokes Index view contained in the Views\Home directory by default. The directory structure and file names are by convention and have to be followed for everything to work. This seems particularly fragile to me but I don't know very much at this point.

Models are supposed to contain all the logic for the application. The controller actions are supposed to be skinny and the model is supposed to do all the work in a series of classes.

Technical
0 Comment(s) Add comment
 
Silverlight Cut Copy Paste Wednesday, July 23, 2008 1:24 PM

Hmmmm....

I've been thinking about getting an IPhone for the last couple of months and one of the things which bugged me about the IPhone is that it doesn't do cut/copy/paste operations.

Well, I was thinking about Silverlight. I don't believe it will be possible in the current architecture to do cut/copy/paste. It will be interesting to see what happens.

Technical
0 Comment(s) Add comment
 
Silverlight Introduction Wednesday, July 23, 2008 1:19 PM

I've started looking at Silverlight and I'm very excited. What is Silverlight? It is a cross-browser, cross-platform, and cross-device plug-in for delivering the next generation of .NET based media experiences and rich interactive applications for the Web.

Why does that seem cool? For me, it allows me to do rich, interactive applications driven by C#. I've always avoided developing Flash applications because they are driven by ActionScript/JavaScript and when I have a choice writing unmanaged code or managed code, I always choose managed code.

Technical
0 Comment(s) Add comment
 
Menus, mouseovers, and the IPhone Thursday, January 31, 2008 6:16 PM

So, I've been thinking about getting an IPhone or a Tilt. I went down to the AT&T store yesterday. It was the first time ever that I was in a phone store that didn't have a zillion people in it. I was left alone with the IPhone for 45 minutes . What a great interface and device - but more on that later. It came as a bit of surprise to me when I browsed to the LimberTech website and tried navigating around using the menu. I never thought about it before but mouseover's won't work on a touch screen type of interface because when you press your finger on the menu item, it means to select it. There is no notion of hovering over a link. I'm not sure there is a way to do a popup menu'ing system on the IPhone. I'll have to do a bit of research to figure that one out.

I almost walked out with an IPhone. I had my credit card out and they had brought a box up from the back. However, I came to an abrupt halt when I found out there was no way to use the phone as a modem for the laptop. I just took for granted that you would be able to hook the phone to the computer and use the EDGE network for data access. But the salesperson said that you couldn't do that because the IPhone "wasn't a true PDA". So I had them put the IPhone back and went to look at the Tilt.

The Tilt certainly didn't wow me like the IPhone did. But I was still blown away by the device. While the interface isn't anywhere near as tight/nice as the IPhone, it seems like the features might be a better match for me - re: Outlook integration and better support for IE.

Technical
0 Comment(s) Add comment
 
Why Silverlight? Thursday, January 10, 2008 3:35 PM

Silverlight is a cross-browser, cross-platform plug-in from Microsoft for delivering the next generation of Rich Internet Applications (RIA). Silverlight is nom de development for the platform to deliver these applications. While Microsoft has made some mis-steps along the way, Silverlight represents a qualitative step forward in the evolution of web applications.

Silverlight is based upon XHTML, Javascript, and XAML (eXtensible Application Markup Language). Once the Silverlight "player" plug-in has been installed on the client the client has only to:

  1. Load a web page (XHTML)
  2. Run a small bit of Javascript for invocation of the plug-in
  3. Play the XAML in the plug-in

One advantage of this model over other RIA technologies like Flash or Java Applets, or ActiveX is that the XAML is simply a fire-wall friendly, text-based XML file which is well defined and can be easily audited for security purposes. XAML also allows designers, using a tool like Microsoft Express Blend to deliver designs which can be directly delivered to the browser. In the past, designers might have been constrained by a developer as they incorporated the designer's work into the application.

Technical
0 Comment(s) Add comment
 
HVTP - Hyper Video Transfer Protocol Wednesday, January 24, 2007 5:24 PM

I got a new video camera for my birthday in November and I've been reading and thinking alot about Digital Video. One of the things I really like about the camera is that it records directly to a 30GB harddisk. I connect it to my PC through a USB port and treat the videos as files.

I've also thought alot about Google's purchase of YouTube. I think it was a brilliant move on Google's part because it comes at the beginning of the coming wave of video on the web. Within 10 years we will be getting most of our entertainment over the web.

So, thinking about all this had made me start to wonder about the digital video file format. Right now, everything is streamed to the player alá music from a CD. It has always been to my dismay that more encoding of meta data hasn't caught on to CD's. While DVD's are better, I think that meta data should be allowed in the stream of the image, similarly to what HTTP allows for the text stream. I believe you should be able to have links appear in the video stream to other web pages, sound files, spreadsheets, even other videos. I think it should even have a similar name to HVTP - HyperVideo Transfer Protocol.

Just my little musing for the day.

Technical
0 Comment(s) Add comment
 
Smart people and Web 2.0 Applications Monday, January 15, 2007 11:53 AM

There are some smart people doing some innovative work out there. I recently ran across this article on CodeProject's website: Build Google IG like Ajax Start Page in 7 days using ASP.NET Ajax and .NET 3.0 by Omar Al Zabir. It was an interesting article which spoke to creating mashup pages using AJAX. Even more so, the article led me to Al Zabir's website PageFlakes.

He's doing some interesting work. Websites like PageFlakes.com, Window Live (if you personalize it), and Google's IG brings to life the idea of mass individualization.

One of the interesting aspects of the next generation of web applications is that they face some of the same problems faced by GUI applications as they evolved. For example, on PageFlakes you can select the feeds you want to appear on your personalized webpage. These little windows (gadgets) remind me of the windows in mdi applications back in Windows 3.x. The central issue is how do you manage the UI in a consistant way so that the user doesn't become confused. On PageFlakes I selected an entry from one of the feeds and was presented with the content. But was I still on PageFlakes or had I moved to the website which hosts the content? When I hit the back button, the unexpected happened - I left PageFlakes and went to the website I was viewing before I came to PageFlakes. Weird.

As "Web 2.0" applications become more popular smart people are going to have to re-solve some of the problems GUI developers faced in the past.

Technical
0 Comment(s) Add comment
 
© LimberTech 2014