Click here to Skip to main content
15,437,709 members
Articles / Desktop Programming / Windows Forms
Posted 17 Jun 2010


99 bookmarked

MVP-VM (Model View Presenter - ViewModel) with Data Binding and BackgroundWorker in Windows Forms applications

Rate me:
Please Sign up or sign in to vote.
4.98/5 (29 votes)
17 Jun 2010CPOL19 min read
This article explains a way to create a Windows Forms app with the thinnest possible Form.cs files.

Image 1


Is your XxxForm.cs or .vb file cluttered with tons of event handlers that process your business logic? Even if you were able to separate out the domain logic from the UI, is your Form class file still contaminated with user input validation related code, or those visual controls' Enable/Disable, or Show/Hide controls to make your app just a little more user-friendly? If so, this article may help you sort them out, and you may be able to separate 90% of the stuff out of the form file. Some of the keywords that make it possible are Data Binding and the MVP-VM (Model View Presenter – ViewModel) pattern.

The above screenshot is the example application that I'll use in this article. Since I'm fed up with OOP tutorials that use “Employee”, “Customer”, or “Order” classes, I wanted something completely different, and decided to use a math problem that I took from the Project Euler site - It solves the problem number 1 – “Add all the natural numbers below one thousand that are multiples of 3 or 5” in general form. The screenshot shows the correct answer, and you can get it by filling out two textboxes with your desired numbers and clicking the Find button.


When I first programmed on the Windows platform, Microsoft used the term “Document-View-Controller”. Nowadays, it is called Model View Controller. Martin Fowler's articles such as “GUI Architecture” - and “Separated Presentation” - explain MVC and other related architectures like MVP (Model View Presenter) and its descendants in detail. John Gossman coined the term MVVM in his blog,, that I highly recommend reading. Even though MVVM is meant for WPF/Silverlight apps with data binding, we should be able to use the idea in Windows Forms applications. And that’s what Aviad Ezra wrote in his blog - where he defines MVP-VM as “The Windows Forms (WinForms) equivalent of WPF MVVM. The MVP-VM (Model View Presenter – Model View) pattern is a tailor made solution for WinForms applications that require full testing coverage and extensively use data binding for syncing the presentation with the domain model.”

I focused more on how I could make the form class file as thin as possible, rather than seeking the full test coverage capability by using the MVP-VM pattern. In fact, the form.cs file of the example application has just 87 lines of C# code, with all the validation and error processing built-in, which are required for any product quality code. The code is FxCop clean and StyleCop clean except for the documentation rules. I also used Code Contracts -, which is new to .NET 4, instead of using more traditional Debug.Assert() or If-argument-is-invalid-then-throw-ArgumentException style error checking. Why, you ask. It's all about code readability. I'll explain it as we go through the code and encounter them. Still, it's not a part of Visual Studio 2010. So, I created two downloads with and without the Code Contracts Rewriter so that those of you who don't have it with your VS2010 can still compile and run the software.

Software Requirements Specification

First of all, let's make sure the requirements of this simple application. Regardless of the size of the program, writing the SRS (Software Requirements Specification) or User Story, depending on the adopted SDLC (Software Development Life Cycle) model in your project, should be a programmer's second nature.

  1. The software shall solve the Project Euler problem no. 1 in the general form.
  2. The user shall be able to provide arbitrary numbers of positive 32 bit integers for the "seeds".
  3. The user shall be able to specify the upper limit within the range of a 32 bit integer.
  4. The software shall remember the last setups and the answer when the user starts the application.
  5. The software shall show the progress of solving the given problem.
  6. The user shall be able to cancel the solving process while it is in progress.
  7. The software shall use Windows Forms technology as a design constraint.

Solution - The Model (Business Domain)

I designed a single domain class that solves the given problem and named it Solver. It has a Max property for the search limit, a bucket of integers that you can put arbitrary numbers of Seeds in, and a method called FindSumOfMultiplesOfSeedsBelowMax(), which is the meat of this application. In addition to those three public members, I added a property called Answer to harvest the result. You can easily imagine how you can use this class - .Max = 1000, .Seeds.Add(3), .Seeds.Add(5), call the method, and get the result by .Answer. The resultant class spec is as follows:

Image 2

Looks like a nice, simple API, doesn't it? Both Max and Answer are integer properties, and the bucket is named Seeds. I named the bucket class type PositiveIntegerSetCollection because it should accept only positive integers. The bucket should be a mathematical “set”, rather than a simple collection of integers in that it should ignore duplicated elements. Anyways, I created this spec and was able to throw it to another member of the project, well, in theory. Unfortunately, because I'm the only member of the project, I accepted the role. An important thing here is that you better concentrate on the business logic and not be distracted by secondary requirements such as showing the progress or retrieving/saving the setups and the answer. Those are the UI stuff. They should have nothing to do with your way of solving the problem.

If this was a classroom project in courses like C# Fundamentals, you'd be fine just submitting the implementation of this class. However, as real world product quality software, it's just a beginning. You'll have to add the UI and satisfy the SRS with all the validation and error handlings without losing the responsiveness of the UI. As it turns out, the Solver class occupies just a little more than 10% of the entire code if you count the number of lines, except for the Designer.cs file. 90% of the code needed to be consumed elsewhere.

Solution - UI (a.k.a. View)

For the UI, I decided to use two textboxes to accept Max and Seeds, a label to show Answer, a button to start the calculation, and another button to cancel it in the middle of the calculation, as shown at the beginning of this article. Some users may want to have a different UI; a textbox for a single seed, an “Add” button that adds the seed to a list box, and a “Remove” button that removes the selected seed on the list box, for example. This tells us that the UI can be very fragile, which is precisely why we shouldn't consider the UI issue (too much) when we design the business domain classes. Otherwise, changes in the UI would easily propagate to the domain classes and you would have to modify and retest them.

To show the progress, I placed a ProgressBar right next to the Cancel button. I chose to show it only while the software is solving the problem because when it is not solving the problem, it is just another distraction from the user's point of view. I also chose to disable the button and the textboxes while it is solving the problem to lock out the user. On the other hand, the Cancel button needs to be enabled only while the software is solving the problem. Thus, the software needs to control the Visible property of the progress bar and the Enabled property of the buttons and the textboxes. To show the user input errors on the textboxes, I decided to use the ErrorProvider, rather than reporting errors on a modal dialog box.

As I said, the form class has only 87 lines of code that follows:

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace DataBinding
    public partial class SolverForm : Form
        private readonly SolverPresenter presenter;

        public SolverForm()

        public SolverForm(SolverPresenter presenter) : this()
            this.presenter = presenter;
            this.presenter.FindAsyncCompleted += Presenter_FindAsyncCompleted;
            solverViewModelBindingSource.DataSource = this.presenter.SolverViewModel;

            // Setting the error provider's DataSource needs to be done *AFTER*
            // setting the binding source's DataSource. Otherwise, binding to the
            // Visible property wouldn't work (always invisible).
            // See details on
            //      winformsdatacontrols/thread/269e0803-4e05-462e-91b7-abd768615f68/
            //      #f0ac03e5-eb18-4abf-a36c-619aeea13382
            errorProvider1.DataSource = solverViewModelBindingSource;

        private static void Presenter_FindAsyncCompleted(object sender, 
                            AsyncCompletedEventArgs e)
                if (e.Error != null)
                    throw e.Error;
            catch (OperationFailedException ex)

        private void FindButton_Click(object sender, EventArgs e)
            catch (OperationFailedException ex)

        private void CancelButton_Click(object sender, EventArgs e)
            catch (OperationFailedException ex)

Notice that the class has only five methods - two constructors, two button click event handlers, and another event handler to show error messages that may have been captured while the software was trying to solve the given problem. The first constructor is the default one and is nothing special. The second one has four lines that bind the ViewModel, BindingSource, and ErrorProvider altogether and set the presenter's completed event handler. The Main() in the Program.cs calls this constructor. Of course, the form.Designer.cs file contains the statements to actually bind the visual controls’ properties to the corresponding ViewModel’s properties in terms of the DataSource. To add a DataSource to your project, first build the project and then select the <Data/Add New Data Source…> menu on VS2010, select “Object” in the first page of the wizard, and choose SolverViewModel. The Windows Forms Designer will automatically create a solverViewModelBindingSource and put it on the component tray. If you don’t know how to bind the visual control’s properties to the BindingSource on the IDE, refer to

The button click event handlers just delegate the user commands to the presenter. The Form class doesn't need to know anything about the business. Of course, every event handler in the Form class needs to catch a specific exception. Otherwise, it would easily crash. I created a custom OperationFailedException for this purpose. We don't need to dig down what exceptions the presenter throws; every function in every class is designed to throw an OperationFailedException if it “does not do what its name suggests” [CLARK]. The only thing the UI's event handlers need to do is to catch it and display the error message on a message box. All error processing other than showing the message must be done in the appropriate places under the UI surface.

There’s a golden rule in FxCop; Do not swallow errors by catching nonspecific exceptions, such as System.Exception, System.SystemException, and so on [CWALINA/ABRAMS]. So, always catch only the specific exceptions that you know how to handle.

Solution - The ViewModel

Once you have finished designing the business domain and the UI, now is the time to think about the ViewModel part of the pattern. The ViewModel object binds to the visuals via a BindingSource. Since we use textboxes and a label, the Max, Seeds, and Answer properties must be of type string, rather than integer or the bucket of integer. Even though you could bind an integer property directly to a textbox's Text property, I don't recommend doing it because then parsing the string to integer is done somewhere in the binding pipeline and you have no control on the error message that needs to be shown to the user when the string can't be properly converted to an integer. If you do, the framework spits out the cryptic "Input String was not in a Correct Format", which the end user may not understand at all. Unfortunately, there's no way to override this message within the current .NET Framework. So, the lesson here is to create a ViewModel class that has string properties that bind to the UI's text properties and handle parsing/formatting in your code so that you have full control on how a string should be converted to a numeric value and vice versa.

By the way, I personally have never used data binding before I decided to use the MVP-VM pattern in my next project. After all, “Haven’t we been told since Visual Basic 2.0 that we should never use binding? It incorporated patterns that were not extensible, it did not use good programming practices, and it frequently didn’t work as expected.” [KURATA]. However, Microsoft seems to have finally fixed the problems (well, most of them) when they released .NET 2.0 back in 2005. I also highly recommend reading the book, Doing Objects in Visual Basic 2005. In fact, I decided to incorporate the Validation class described in the book almost as is.

In addition to the three properties that correspond to those of the Model class, we need to have the ProgressPercentage, ProgressBarVisible, ControlsEnabled, and ControlsDisabled properties to show the progress and to control the visuals. Of these seven properties, Max and Seeds are the ones that we need to implement validation on their Set property calls that are done when the user leaves the controls on the form. If there're any validation errors, we want to show them with the help of the ErrorProvider. This is done via the IDataErrorInfo interface. For the rest of the properties, we need to "Notify" the changes to the binding pipeline so that the visual controls properly reflect the changes. This is done via the INotifyPropertyChanged interface.

Accommodating the properties that bind to the corresponding UI properties is the primary responsibility of the ViewModel. Validating the user input is the second most important responsibility. It is also responsible for propagating the validated user inputs (the Max string and the Seeds string) directly to the Model's (i.e., Solver class) corresponding business properties by adapting the interface. This third responsibility is also important because business models’ API (Max integer and Seeds bucket of integer) almost always differ from the UI (Max string and Seeds string). Somebody has to adapt the interface. Also, ViewModel's role is passive in that it doesn't actually execute the commands the user issues. The active role is given to the Presenter.

Solution - Presenter

Now is the time to explain the last actor of the MVP-VM pattern: the Presenter. In WPF, there's support for the ICommand interface in such a way that the ViewModel can expose commands to the View directly. So, there's no need for the Presenter. In Windows Forms, however, there's no such support in data binding, so we have to do it ourselves. That's the Presenter's role - to actually implement the commands the View receives from the user. In this application, it's the FindClicked() and the CancelClicked() functions that are called from the View when the user clicks the Find or Cancel button. Given below is the code for these functions:

public void FindClicked()
    this.SolverViewModel.Answer = string.Empty;
    if (this.SolverViewModel.IsValid)
        this.SolverViewModel.ProgressBarVisible = true;
        this.SolverViewModel.ControlsEnabled = false;
public void CancelClicked()

private void Solver_AsyncCompleted(object sender, AsyncCompletedEventArgs e)
    this.SolverViewModel.ProgressPercentage = 0;
    this.SolverViewModel.ProgressBarVisible = false;
    this.SolverViewModel.ControlsEnabled = true;

FindClicked() plays the key role in this application in that it orchestrates the Model (AsynchronousSolver) and the ViewModel (SolverViewModel) to get the job done. It first clears the Answer label and lets the ViewModel validate the user inputs - Max string and Seeds string. If they are valid, it lets the ViewModel update the Model's corresponding input properties, show the progress bar, disable the relevant controls, and finally calls the Model's business function – FindSumOfMultiplesOfSeedsBelowMaxAsync(). CancelClicked() tries to cancel the execution of FindSumOfMultiplesOfSeedsBelowMaxAsync() by calling its CancelAsync(). When the Model finishes working - regardless of either successfully having solved the problem and updated its Answer property, or having thrown an exception due to some error - it always raises an AsyncCompleted event at the end. The Presenter subscribes to the event to first let the UI show any error messages by raising the FindAsyncCompleted event, and then clears the progress percentage, makes the progress bar invisible, and finally enables the controls again for the next set of input values.

Some of you may wonder where the IView interface is. Usually, the Presenter communicates with the View via the IView interface, rather than communicating directly with the form class in order to reduce coupling. Well, I didn’t need it. There’s no need for the Presenter to access the View in this particular application. YAGNI applies here.

Class Diagram

Image 3

The above is the class diagram of this application. The gray ones are .NET classes, the blue one is the UI, the beige ones are this application's specific business classes, and the white ones are general plumbing classes that you can use in other projects. As you can see from the diagram, SolverViewModel is the most complex in that it associates with five other classes. As a result, I put it in the center of the diagram. Notice that SolverViewModel has no direct association with the form class.

Adding the navigability arrows on the association lines in the class diagram can be very helpful to identify dependencies - i.e., you'd need mock objects for whatever the classes the outgoing arrows point to when you unit-test the class. For example, take a look at SolverPresenter. To unit-test its methods, you will need to create two fake objects that "mock" AsynchronousSolver and SolverViewModel. On the other hand, if a class has no outgoing association lines, it is quite independent (no coupling), and is very easy to create unit tests because you don’t need any mock objects. Progress and Solver are examples of such a class.

I needed a minor modification to the Solver class in order to report the progress. You need to expose some of your internal members to report the progress of the internal process. One way of doing it is to use Inheritance. Thus, FirstArgIsMultipleOfSecondArg() was changed from a private method to protected, and also virtualized to add the reporting function. This literally exposes your “guts” – not a good thing in terms of encapsulation – but showing the progress of its internal process precisely means showing its internals. So, I couldn’t help but do it. The derived class (AsynchronousSolver) overrides the method of the base class so that it behaves a little differently, as shown below:

protected override bool FirstArgIsMultipleOfSecondArg(int firstArg, int secondArg)
    if (this.progress.IsTimeToReportProgress(1))

    if (this.backgroundWorker.CancellationPending)
        throw new OperationCanceledException();
    }        return base.FirstArgIsMultipleOfSecondArg(firstArg, secondArg);

First, check to see if it's time to report progress. If so, it calls the BackgroundWorker's ReportProgress(). Then, it checks if the user hits the Cancel button by asking the BackgroundWorker. If so, it throws an OperationCanceledException. Finally, it calls the base class' overridden method. As you can see, I used a BackgroundWorker to keep the UI responsive and also to allow the user to cancel the operation in the middle of the process. When Max is 1000, the calculations would finish immediately. But, you might be pleased to be able to cancel the calculations if you add five more zeros for Max.

Now, you may wonder where the BackgroundWorker is created. AsynchronousSolver not only overrides FirstArgIsMultipleOfSecondArg(), but also adds a new method called FindSumbOfMultiplesOfSeedsBelowMaxAsync() that is an asynchronous version of FindSumbOfMultiplesOfSeedsBelowMax(), like so:

public void FindSumOfMultiplesOfSeedsBelowMaxAsync()
    if (this.backgroundWorker != null)

    this.backgroundWorker = new BackgroundWorker { WorkerReportsProgress = true, 
                                                   WorkerSupportsCancellation = true };
    this.backgroundWorker.ProgressChanged += this.BackgroundWorker_ProgressChanged;
    this.backgroundWorker.RunWorkerCompleted += this.BackgroundWorker_RunWorkerCompleted;
    this.backgroundWorker.DoWork += this.BackgroundWorker_DoWork;

You can see, it creates a new BackgroundWorker, hooks the events up, and runs the task in a Thread Pool thread. The Presenter calls this method instead of the base class' synchronous version directly. The event handlers are coded as follows:

private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    // Any exceptions thrown while running the code below and we don't catch
    // here, will be automatically transferred
    // to RunWorkerCompletedEventArgs.Error property.
    // Because we catch the OperationCanceledException that we are using to
    // indicate that the user canceled the operation,
    // RunWorkerCompletedEventArgs.Error will be null.

    // When an exception is thrown and you are running it from within Visual Studio,
    // VS will pop up an "xxxException was unhandled by user code" message box.
    // Do not be warned, since this is by design. Simply clicking the Run button
    // again will let VS continue running the code. Read the following excerpt.
                // Excerpt from MSDN BackgroundWorker.DoWork Event:
    //   "If the operation raises an exception that your code does not handle,
    //   the BackgroundWorker catches the exception and passes it into the
                //   RunWorkerCompleted event handler, where it is exposed as the Error
    //   property of System.ComponentModel.RunWorkerCompletedEventArgs.
    //   If you are running under the Visual Studio debugger, the debugger
    //   will break at the point in the DoWork event handler where the unhandled
    //   exception was raised."
        this.progress = new Progress(Seeds.Count, Max);
    catch (OperationCanceledException)
        // e.Cancel will be transferred to RunWorkerCompletedEventArgs.Canceled.
                        e.Cancel = true;

private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
    this.OnProgressChanged(new ProgressChangedEventArgs(e.ProgressPercentage, null));

private void BackgroundWorker_RunWorkerCompleted(object sender, 
                              RunWorkerCompletedEventArgs e)
    this.OnPropertyChanged(new PropertyChangedEventArgs(
    this.OnAsyncCompleted(new AsyncCompletedEventArgs(e.Error, e.Cancelled, null));

The DoWork event is raised in the worker thread. It creates a Progress object and calls the base class' most important function - FindSumbOfMultiplesOfSeedsBelowMax(). Notice that these are enclosed with a Try/Catch block, and if an OperationCanceledException is thrown, it sets the DoWorkEventArgs.Cancel property to true, which will be transferred to the RunWorkerCompletedEventArgs.Canceled property when the RunWorkerCompleted event is raised.

Also, read the comment on this method very carefully. When you run the code within VS2010 and FindSumbOfMultiplesOfSeedsBelowMax() throws an OperationFailedException, VS2010 will catch it and pop up the "xxxException was unhandled by user code" dialog box. This is so because we don't catch it, and before the exception reaches the BackgroundWorker, VS2010 has to catch it.

The ProgressChanged event is raised when BackgroundWorker.ReportProgress() is called in FirstArgIsMultipleOfSecondArg(). The event handler raises its own ProgressChanged event that is different from the one raised by BackgroundWorker. SolverViewModel subscribes to this event to update the progress bar. BackgroundWorker's ProgressChanged event is raised in the UI thread, so there's no need to marshal the call back to the UI thread.

Finally, the RunWorkerCompleted event is raised when FindSumbOfMultiplesOfSeedsBelowMax() finishes execution either by successfully finishing the entire calculation, or is prematurely aborted by the user, or some exceptions were thrown during the calculations. Regardless of the reason, the event handler raises a PropertyChanged event to update the Answer. Again, SolverViewModel subscribes to the event. Then, it raises an AsyncCompleted event to which the SolverPresenter subscribes.

Miscellaneous Programming Tips

Persistence<T> – One of the requirements of this application says that we have to save and retrieve the setups (Max and Seeds) and the result (Answer) when the user closes and opens the application. Persistence<T> is responsible for doing it by exposing two APIs – ReadObject() and WriteObject(T). .NET 3.0 added the DataContractSerializer class for serving general persistence scenarios, and I used it in this application. It’s very easy to use; just add the <DataContract> attribute to the class that you want to persist, and <DataMember> to the members you want to actually save/retrieve. The resultant XML file looks like so:

<?xml version="1.0" encoding="utf-8"?>
<Solver xmlns:i="" 
  <Answer i:nil="true" />
  <Seeds xmlns:d2p1="">

Persistence<T> takes care of everything including catching the relevant exceptions and re-throwing an OperationFailedException with hopefully user-friendly error messages. Again, do not catch general exceptions here.

PositiveIntegerSetCollection - This class inherits from HashSet<int> that was newly added to .NET 4.0. We need a mathematical set instead of a general collection because we don’t want duplicated elements, which is exactly what the set is for. The only thing it does differently from the base class is to throw an ArgumentOutOfRangeException if the client of this class tries to add a negative integer, as shown below:

public new bool Add(int value)
    Contract.Requires<ArgumentOutOfRangeException>(value > 0"Must be a positive integer.");
    return base.Add(value);

It needs the new keyword because the base class’ Add() is not virtualized and can’t be overridden. The first statement is the Code Contracts where I check the pre-condition. I like the syntax very much because of its readability. The contract requires value>0; otherwise, I will throw an ArgumentOutOfRangeException with such and such error message. And of course, do not catch this exception in your code because if it really happens, it’s a bug (not an exception), and the only way to “handle” the bug is to debug the problem, fix it, and redistribute the software. Catching it in your code won’t buy you anything. Let your application die miserably in front of the user; he or she would let you know that you didn’t test your app thoroughly, which was your responsibility and should have never happened in the first place.

Unfortunately, Code Contracts is not yet fully a part of VS2010 as of writing this article (June 2010), even if .NET 4.0 has already supported it. In other words, you can build this app error free even if you have never downloaded Code Contracts, but you will have a runtime error when you run it because throwing exceptions require the Code Contracts Rewriter. This is why I put two downloads, with and without throwing exceptions.


I showed how MVP-VM pattern can help you create the thinnest possible form.cs file in product quality Windows Forms applications.


  • [CLARK] Jason Clark, p. 218, “Framework Design Guidelines” Second Edition, Krzysztof Cwalina, Brad Abrams, 2008
  • [CWALINA/ABRAMS] p.227, “Framework Design Guidelines” Second Edition, Krzysztof Cwalina, Brad Abrams, 2008
  • [KURATA] “Doing Objects in Visual Basic 2005”, Deborah Kurata, 2007


  • 6/17/2010: Initial post.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Written By
Software Developer (Senior)
Japan Japan
He started his career as a PDP-11 assembly language programmer in downtown Tokyo, learning what "patience" in real life means by punching a 110 baud ASR-33 Teletype frantically. He used to be able to put in the absolute loader sequence through the switch panel without consulting the DEC programming card.

Since then, his computer language experiences include 8051 assembly, FOCAL, BASIC, FORTRAN-IV, Turbo/MS C, VB. VB.NET, and C#.

Now, he lives with his wife, two grown-up kids (get out of my place!), and two cats in Westerville, Ohio.

Comments and Discussions

GeneralMy vote of 5 Pin
Prasad Khandekar26-Apr-13 0:04
professionalPrasad Khandekar26-Apr-13 0:04 
QuestionCongrats!!! Pin
fabiopand8-Feb-12 1:01
Memberfabiopand8-Feb-12 1:01 
GeneralMy vote of 5 Pin
Guy Baseke14-Feb-11 4:37
MemberGuy Baseke14-Feb-11 4:37 
GeneralAwesome, just one question. ;) Pin
fujiwara13-Oct-10 0:35
Memberfujiwara13-Oct-10 0:35 
GeneralVery helpful article! Some design questions... Pin
jmerum28-Jul-10 10:15
Memberjmerum28-Jul-10 10:15 
GeneralRe: Very helpful article! Some design questions... Pin
tetsushmz29-Jul-10 0:56
Membertetsushmz29-Jul-10 0:56 
GeneralRe: Very helpful article! Some design questions... Pin
jmerum1-Aug-10 3:26
Memberjmerum1-Aug-10 3:26 
GeneralRe: Very helpful article! Some design questions... Pin
tetsushmz1-Aug-10 8:39
Membertetsushmz1-Aug-10 8:39 
GeneralRe: Very helpful article! Some design questions... Pin
tetsushmz16-Aug-10 8:57
Membertetsushmz16-Aug-10 8:57 
GeneralRe: Very helpful article! Some design questions... [modified] Pin
jmerum29-Aug-10 7:31
Memberjmerum29-Aug-10 7:31 
GeneralThanks again for the article Pin
seeblunt28-Jun-10 0:31
Memberseeblunt28-Jun-10 0:31 
GeneralRe: Thanks again for the article Pin
tetsushmz28-Jun-10 3:32
Membertetsushmz28-Jun-10 3:32 
GeneralGreat article Pin
vic_bang25-Jun-10 3:00
Membervic_bang25-Jun-10 3:00 
GeneralRe: Great article Pin
tetsushmz27-Jun-10 11:22
Membertetsushmz27-Jun-10 11:22 
GeneralThank you Pin
Jonn Lim21-Jun-10 3:01
MemberJonn Lim21-Jun-10 3:01 
GeneralRe: Thank you Pin
tetsushmz21-Jun-10 15:26
Membertetsushmz21-Jun-10 15:26 
GeneralSeparation of concern between layers Pin
seeblunt20-Jun-10 13:11
Memberseeblunt20-Jun-10 13:11 
GeneralRe: Separation of concern between layers Pin
tetsushmz20-Jun-10 15:25
Membertetsushmz20-Jun-10 15:25 
GeneralRe: Separation of concern between layers Pin
seeblunt20-Jun-10 15:48
Memberseeblunt20-Jun-10 15:48 
GeneralRe: Separation of concern between layers Pin
tetsushmz21-Jun-10 15:25
Membertetsushmz21-Jun-10 15:25 
GeneralRe: Separation of concern between layers Pin
seeblunt21-Jun-10 21:55
Memberseeblunt21-Jun-10 21:55 
GeneralRe: Separation of concern between layers Pin
seeblunt21-Jun-10 21:58
Memberseeblunt21-Jun-10 21:58 
GeneralRe: Separation of concern between layers Pin
seeblunt21-Jun-10 22:01
Memberseeblunt21-Jun-10 22:01 
GeneralRe: Separation of concern between layers [modified] Pin
seeblunt21-Jun-10 22:03
Memberseeblunt21-Jun-10 22:03 
GeneralRe: Separation of concern between layers [modified] Pin
devnet2478-Nov-11 19:39
Memberdevnet2478-Nov-11 19:39 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.