Click here to Skip to main content
15,868,145 members
Articles / Desktop Programming / WPF

DOP and DVMH

Rate me:
Please Sign up or sign in to vote.
4.93/5 (9 votes)
4 Jun 2014CPOL27 min read 16.8K   62   14   8
This article talks about Distributable Observable POCOs and an MVVM-like design pattern built to take full advantage of them.

Background

When I wrote the article Writing a Multiplayer Game (in WPF), I used a remoting architecture (also known as multi-tier architecture) that I considered to be like a remotable MVVM. In fact, when I published the article I wasn't using real MVVM, only some of its concepts and even if I did use MVVM in the latest version of the game, I never published it.

Independently on how well the game or the previous architecture worked, I didn't consider it ideal. The real problem was that I made the game components bound to the game framework so, if I wanted a different kind of applications, I was getting lots of unused classes and I was needing to do work-arounds over some specific parts.

So, I decided to create a new architecture that I consider to be a real evolution over MVVM to make it work in a "distributable" manner (that is, capable of making different layers work in different computers if needed), being capable of creating normal applications and games.

DOP - Distributable Observable POCO

DOP means Distributable Observable POCO, and the use of DOPs is the heart of the new architecture. We can say that a DOP is any class that exposes only public properties, implements the INotifyPropertyChanged correctly (that's what makes it observable) and is equivalent if created in two different applications by using the default constructor and setting the same properties. This means it can't use client specific data on its constructor (like setting a property with the current date and time), it can't use weird code on property gets, it can't have methods or events (aside from the property changed) and it can't validate values on property sets. Methods, events and property-set validations must be done by another class.

By implementing the INotifyPropertyChanged correctly I mean: They should only notify property changes when they do actually happen. Setting a property to the value it already possesses should not trigger the event. This can look silly, but it is primordial to avoid "circular updates" that end-up causing stack-overflows.

Why the DOP name?

I was initially unsure about using Distributable Data-Transfer Object (so, DDTO) or DOP. Yet, DTOs can't have anything special in their property sets. They are only objects full of properties that can be get and set directly, and providing notifications will mean changing the meaning of a DTO. But making a POCO Distributable and Observable doesn't hurt the basic definition, it only adds some extra constraints to it.

Actually, POCO is a very problematic definition. POCO means Plain Old CLR Object (many times said as Plain Old C# Object). That "plain old" has the purpose of saying that it doesn't hold any framework-specific traits, so it should not have any framework-specific data-types, attributes and should not inherit from a framework specific class or implement a framework-specific interface. Yet the POCO definition is very problematic as apparently it was first used to describe database objects that were "persistance ignorant" and they became limited to database, when they should not be. Also, some frameworks (like Entity Framework) may require the properties to be declared as virtual, as the frameowrk will inherit the class to add some behavior but, well, the term is Plain Old CLR Object, not Plain Old CLR Class. We should be able to create the instances, that don't inherit from anything, and hand them to the framework.

So, to make a conclusion, DTO is too restrictive. POCO is too open. So I decided to create a more restricted POCO that's not as limited as DTO. In fact, the spirit of a DOP is that the code that's responsible to change it will always be able to do it and anyone interested in its current state will be notified about the changes.

Using DOPs

The DOPs by themselves don't depent on any framework (I don't consider implementing the INotifyPropertyChanged as depending on a framework). If we want to visualize a DOP, we can simply write a Data-Template for it and everything will work, after all a DOP must have all the information needed to be presented visually.

Yet we usually want to "manipulate" those DOPs, changing their contents, executing methods and applying validations, which must be done elsewhere. So, without any architecture, we can simply create methods on another class that receive a DOP as input, possibly other parameters and do whatever is needed, like doing validations, saving things to the database, generating exceptions and changing the DOP. Then we have the following problem: How do we guarantee that this other class will be used to manipulate the DOP and that users of the DOPs will not change them directly, putting invalid values on them and bypassing the needed validations?

Well, this is where we have both a new pattern and frameworks to support it.

DVMH - DOP, View, Message and Handler

The greatest difference from MVVM to DVMH is that the Model is split into DOP (the properties) and messages (that represent both the events and the methods). The messages are actually the "requests" to invoke a method or to generate an event. To execute those messages, we need the handlers.

Details of each letter

  • D - DOP: Represents all the public properties of an object. A view can easily be built on top of a DOP to show it in the screen, even if the DOP never knows about the view;
  • V - View: Has the purpose of presenting a DOP on the screen. If the view wants to change the DOP it will need to send a message to the "remote side" asking for the change;
  • M - Message: Anything that can be sent to the "remote side". Considering that data-serialization is needed on real remote environments and that we should use objects instead of string messages, it is expected that all messages are serializable DTOs (actually any DTO is serializable, but many serialization frameworks require something specific, like the [Serializable] attribute to know that);
  • H - Handler: Is the code that process a given message. It can be a different code for the same message considering different "contexts". We can say that a Click is a great example of message that can be processed differently depending on which control was clicked.

The DVMH support-architecture

To make the DVMH pattern work we need at least a way to send messages and to process those messages. I decided to put this minimum architecture in a framework.

Well, I built the interfaces of that framework first as I don't want to bind users to my implementation of the framework, only to give the "architecture" to be able to use the DOPs and the DVMH pattern correctly.

Even if we use a local framework, the idea is to have a good isolation between data-presentation and data-manipulation, which usually fits very well with the remote architecture of client and server. So, the client presents the DOPs and the server manipulates them.

Considering things as client and server, we have:

Client

A client "participates" in a server "room". As a participant, a client receives notifications about all the added, removed and modified objects and, if necessary, updates the local copy of those DOPs, so they can be correctly presented on the screen.

Server

A server must have at least one room and must accept participant connections, choosing in which room to place those participants. It is important to note that the server doesn't receive a real participant object, only an object to communicate to that participant.

The server is free to add and remove components to a room at any moment and to change participants from one room to the other. As soon as the participants are "linked to a room", they will receive information of all the existing components and will be able to observe the changes that happen to those components.

And, of course, the server can send messages to the clients and the clients can send messages to the server.

More precisely, the participants may send messages to the room to which they are connected, while the server may see all the active "communication objects" bound to a room and use those objects to send messages to the participants.

To put names to the objects involved, in the client we have:

  • DopParticipant: Objects that receives the notification of the changes that happens on the server side, like when the objects are added, removed and when the participant is put into another room and that has a PostMessage method, being able to post messages to the server.

And in the server we have:

  • DopRoom: An object responsible for managing components and participants, which receives the messages from the participants;
  • DopCommunicationToParticipant: The server representation of a connected participant, being useful to send private messages to the participants.

The Frameworks

As I already said, I started the frameworks by the interfaces. Those interfaces present the IDopParticipant, IDopRoom and IDopCommunicationToParticipant.

In fact, there are some more interfaces, as I decided to put the methods that deal with the room components in another dedicated interface (one for the server side, capable of adding and removing components, and one on the client side, restricted to enumerating and getting information from those components).

Then, I did two implementations of those interfaces. One is local, being the support of loosely-coupled DVMH without the serialization and remoting overhead and the other is, well, remote, allowing to create the rooms in one computer and to access those rooms and components from another computer.

The remote framework requires a message port to send messages and it creates copies of the server DOPs on the client. Such copy actually helps enforce the DVMH pattern as changes made to the client DOPs will not affect the original DOPs, which will naturally force the developers to use the messages instead of changing the DOPs directly and will avoid clients from putting invalid states without going through the required validations. Considering that benefit, I decided to create a local message port so the real DVMH architecture can be enforced, yet there's no TCP/IP (or similar) communication involved.

The need for a message port is again a situation to write a "framework". The remote library doesn't come with any message port, only with the interface for the message ports, but I am also giving another library (the minimal implementation) that implements both the local message port and a TCP/IP message port that uses the default .NET binary serialization to send messages. For professional distributed applications, I strongly recommend implementing another message port.

The Libraries

So, to make things clear, let's see which libraries exists and what's contained in them.

  • Pfz.DisposableObservablePoco: This library is built of interfaces and delegates only. It tells what a DOP framework is expected to do, including the "server"-side (room and communication to a participant) and the client-side (the participant);
  • Pfz.DisposableObservablePoco.Local: This library is the local DOP framework implementation. It actually doesn't replicate objects from the room to the participants, it simply notifies the participants about the existing objects. This adds almost no overhead and is useful if you want to give a fast local view of a multi-tier application. It is highly recommended to first develop a remote solution to guarantee that you use the right client/server istolation (as this version allows it to be violated) and then use this local framework to have the minimum local overhead;
  • Pfz.DisposableObservablePoco.Remote: This library is the one I expect users to actually try. It has the architecture to use distributed DOPs, having the replication logic needed to create the client components that are copies of the server ones. Yet, it doesn't come with a default message port implementation, only with the extra interfaces to support the message ports. But don't worry, see the next library;
  • Pfz.DisposableObservablePoco.Remote.MinimalImplementation: This is a minimal implementation of the message ports so you can try the Remote library. It comes with a local message port (it has more overhead than using a real local DOP framework, but it includes all the isolation and replication steps to guarantee that you write a really decoupled application) and with a TCP/IP + normal .NET serialization message port. I really believe that using better serializers or better message ports is a must for professional applications (especially if they are accessible to the world), yet this is a good starting point and allow us to test the DOP framework and to create LAN applications;
  • Pfz.DisposableObservablePoco.Common: This library is used by both the Local and Remote implementations and it includes classes that I consider most users will want, which are presented in the next topic.

The "Extras"

There are two things that I consider that many developers will want: Dynamic DOPs and some kind of DOP generator, to avoid having to write the DOP classes by hand. After all, even if the classes are simple, having to write the property sets with the right validations + the notification can be boring and error-prone.

So, in the Pfz.DistributableObservablePoco.Common library you will find the classes DynamicDop and AbstractDopCreator.

  • The DynamicDop is a class very similar to the ExpandoObject, being accessible when using the dynamic keyword or by the methods GetValue and SetValue, effectively "creating properties as needed". This is ideal if you don't want to share the server components with the client, so it is enough for the client to use DynamicDops and support all the properties coming from the server. Of course, if needed you can use it on the server too.
  • The AbstractDopCreator is capable of implementing a DOP abstract class or interface at run-time, using the right pattern, which includes the verification if the value really changed before continuing and caching all the EventArgs in static fields so new instances aren't created at every property change. Considering that DOPs should not have any specific constructor logic, methods or events aside the PropertyChanged, the only reasons I can see to avoid using this run-time generator is a constrained environment that doesn't support run-time code generation or the need for a Dynamic DOP.

INotifyPropertyChanged is not mandatory

I started by saying that the DOPs must be observable, which is achieved by implementing the INotifyPropertyChanged interface. Actually the DOP frameworks only depend on the fact that DOPs are observable, they don't depend on the INotifyPropertyChanged itself. The frameworks use a DOP Manipulator per DOP Type, which is found using a delegate, so you can give a different manipulator and, if the object is observable without implementing the INotifyPropertyChanged, it is enough that such manipulator knows how to observe the property changes.

I would personally love to make my DOPs don't implement the INotifyPropertyChanged but considering that WPF uses such interface, I still implement it in my DOPs. But I don't know... maybe you use a different framework that doesn't depend on the INotifyPropertyChanged either, so you will be free to ignore such interface as long as you create a DOP Manipulator capable of observing those different DOPs.

The Sample

The sample is a small game (we can say that's a very simplified version of Shoot'em Up .NET) that uses the DVMH pattern. It is not very complete and even if it follows DVMH it is not the best example of good programming practices as I used some static variables. But, well, it is only a small sample, not a killer application built with the pattern.

I put all the important code inside libraries for the client and the server. The client part is effectively reduced to putting DOPs on the screeen, having the data-templates and processing the arrow keys + space (and setting the "signals" object). The game itself runs in the server.

I used libraries for the client and server so I could built the standalone application and the client/server applications reusing the same code. Even if the StandAlone application has both the client and server inside it, the client code doesn't know anything about the server code nor the server code knows anything about the client code.

I know that I created many assemblies, but I consider extremely important to try to follow the pattern for the assemblies too. Put all the message objects, DOPs and maybe some resources in a common assembly (I put the images in the common assembly, as the client presents them and the server uses them for the collision detection). Then, put all the code that's related to the "View" in a client library. All the code that actually does the work in a server library. And, if you don't want a client/server application, only the DVMH pattern, create an executable that uses all those libraries. Even if in the end everything is in the same executable and application domain, the code will be loosely-coupled as client classes don't know about the server classes (or vice-versa).

The little details

Up to this moment I tried not to focus on details. I am providing a framework with a minimal implementation and it already has limitations and, even if some of them can be easily solved by different implementations, I prefer to keep them there to guarantee that an application written using one framework can be used by another framework with ease.

So, let's see some of those details:

Simple data only

The types of the DOP's properties must be simple data, not the type of any object that can contains its own modifiable properties.

Also, considering possible serialization restrictions (which we can say to be another implementation detail) we should try to use only primitive types, strings or immutable serializable types in DOP properties. So, the DOPs are modifiable, but the contents of an object put into a DOP property can't be.

Referencing other DOPs

Considering a DOP has modifiable properties, how can we have a property in a DOP that references another DOP?

The answer to this question is to use IDs. Each component added to a room is given an ID, which can be used to find the component again inside the that room. It is not expected that an ID work across different rooms and the type of the ID itself is implementation dependent, so it can be an string, an int, a GUID etc.

As the local implementation doesn't use serialization, the ID generated by the local rooms is not a serializable object (at least not by the .NET binary formatter) yet the room and participant objects are capable of finding the right component by such key.

So, when one DOP references another DOP or when sending messages that should reference a DOP, use the ID of the DOP, not the DOP itself.

Component directed messages

The DOP frameworks themselves can only send messages from a participant to a room or from a room to a participant. They can't send message to specific components.

This was done on purpose as it simplifies the DOP frameworks, but considering a message can have the ID of a component, we can always send a message to a room telling to which component it should be "dispatched".

Actually it is possible to build another framework dedicated to handle the message dispatching to components. I didn't do that yet, but I consider it would be really important to have a configurable handler for bigger applications. Thankfully, any of the DOP frameworks will be able to use a message dispatching framework by delegating the MessageReceived method to such a framework.

Flushes

Messages sent through the PostMessage method and property changes aren't guaranteed to be sent immediately to the remote side. A call to Flush() must be done.

As such call is not necessary in the local implementation, it is possible that a local application doesn't work as expected if it is later compiled to be remote. This is why I consider it very important to always start by using the remote framework and, if needed or wanted, use the local framework later to make things faster.

I decided not to do automatic flushes for performance reasons.

Disposal of Objects

In my opinion the disposal of objects in general is still a problematic topic. .NET has a Garbage Collector which mainly solves the problems related to memory leaks and accessing already deleted objects (or, putting it differently, it solves the problem related to the order of the deletes when we aren't sure how many references still exist for such an object, which usually caused either memory leaks or deleted objects while there were still references for them). Yet, there are inumerous situations in which the Garbage Collector is not enough. This is usually presented by things like files and network connections, but it goes further, as any object put into a room will not be available for collection while the room is alive, even if it is not needed or used anymore.

For the objects in the rooms, there's not much to say. It is up to the developer to remove the objects as soon as they aren't needed anymore. DOPs should not have destructors o any specific disposal logic, so it is enough to make them available for collection (removing them from active lists, like the room's components) and .NET will deal with them.

But we still have the Rooms, the Participants and the Message Ports. The disposal support on those objects should exist, be observable and thread-safe. I know, many people consider that the Dispose() method should not be called by multiple threads and that there's an error in the architecture if a Dispose() must be thread-safe but this is very common in duplex communication scenarios. The connection may be closed by the remote side at any moment... or even by a physical cable disconnection. Such a connection lost, on message ports that require an active connection, means that the message port may be disposed at any moment (so thread-safety is needed). Also, a Participant that uses a disposed Message Port is useless, so it should be disposed together with the message port, which means it should be informed of such disposal and may be disposed at random moments too.

 

To support this, I created the IDopDisposable interface. It is an IDisposable that has the following extra members:

  • IsDisposed - A property that returns a boolean value telling if the object (room, participant etc) is already disposed;
  • DisposeException - A connection loss actually throws an exception, but we don't allow such exception to be thrown in the application, we simply dispose the object and store the exception, which can be accessed through this property. Using the methods of a disposed object should throw an ObjectDisposedException giving the DisposeException as its inner exception;
  • Disposed - An event that's invoked when the object is disposed. This is how the Participant can be disposed as soon as the Message Port is disposed.

And, to make things completely thread-safe, it is important to note that registering to the Disposed event should invoke the delegate immediately if the object is already disposed, as users can't have a guarantee that the object will not be disposed just after checking that the object is still alive.

Small FAQ

Why the server is the only one that can create new DOPs?

Security. The server may keep data in its memory for the created objects, so the developer of the server application must decide when to create objects. If clients could simply create objects that live on the server, then it will be very easy to attack.

Why only the server can change the DOPs properties?

Security again and to enforce the use of messages. One client should not be able to change the data that another client may be using. It could be even worse if two or more clients believe they can change the same DOP at the same time.

Yet, it is possible for a client to change the properties of a "private DOP". Private DOPs are DOPs created on the server to be visible by a single participant, so that participant is free to change them.

Why DOPs don't have methods?

Because if that was supported, the client implementation should redirect to the server, while the server implementation should execute the action. It would be possible to put abstract methods inside the DOP classes and implement them differently on the client and on the server, but this will end-up creating different DOPs to represent the client and the server version, and I really wanted to use the same DOP classes on both sides, at least for the framework.

Also, normal methods usually have a synchronous signature and for distributed programming making things asynchronous is better. Yet, if you really want to have methods it is possible to implement such a support on top of the remote framework, as both the client and the server are free to provide their DOP creator delegates.

Why the messages don't have a result?

Because of the synchronous/asynchronous problem and because the idea is to give simple interfaces to implement a DOP framework. It is possible to build a messaging framework capable of sending messages and awaiting for results that depend only on the interfaces of the DOP frameworks, effectively giving such power to any of the possible framework implementations.

Why DOPs can't have events?

Because events have two sides: producer and consumer. When we declare an event in a class, other classes can only consume the event, not produce it (try invoking an event declared on another class, you can't, you must use the += or the -= to register your handler for the event). Considering DOPs don't have methods, they will never produce the event. So, we could create a method to allow other classes to produce the event, which will return to the problem of having methods or we can avoid such complication by using messages.

Can the clients connect to more than one room at a time?

Yes, but many Participant/MessagePort objects will be needed, one of each per room. Actually it is not mandatory that each MessagePort holds a connection on its own, so it is possible to have many different message-ports that use a single connection to do their jobs. Yet, the minimal implementation doesn't deal with such a situation and we can consider this to be a concern of the Message Ports involved, not of the DOP framework by itself.

Can I add the same component to two or more rooms?

Yes, but the provided frameworks will not know that such DOP is shared. So, any good practice concerning a shared resource should be used. If the rooms have their own dedicated threads to deal with the objects, locks will be needed when accessing the shared component. Also, the rooms should not expect to apply different rules to the components, or they can see "invalid states" coming from another room that actually considers that as a valid state.

Aren't messages the same as Commands in WPF?

They do a very similar job, yet WPF didn't create Commands with the idea of remote support and WPF controls can use both events and commands. I think that limiting things to only messages helps by avoiding "mixed" APIs and by reducing the amount of code required to create a new DOP framework.

ViewModel

By looking the DVMH pattern you will probably miss the ViewModel.

So, did I forget it, is it missing or somewhat hidden?

And my answer is: I didn't put the ViewModel as a requirement in the pattern. It is still possible to use it, but I actually think there's a better solution.

When the Model is already observable the ViewModel is many times obsolete. It is still useful to "convert" some property data-types or even to add View specific properties, but in most cases it ends-up filled with properties that simply redirect to the Model (or the users break the MVVM design pattern and access the Model directly from the View).

Well, I don't like code repetition, so I prefer to say that we should not have a ViewModel. Then, how do we convert values? How do we add View specific properties without putting View specific properties in the "Model" (the DOP)?

The value conversion is a real issue. I personally don't like to put converters in the Bindings, as this is putting "code stuff" in the View. Yet, when we have a View that wants to present the Model differently (like presenting centimeter values as inches), we can say that it is still a View concern to do the conversion (but some people will put a property that already does the conversion to inches in the ViewModel).

Another solution is to use the remote framework, which actually creates copies of the server components on the client, to be able to create different objects on the client and on the server (even if the remote framework actually uses a local message port). The common DOPs should still exist, but the client can inherit the common DOPs and add client-specific properties, while the server can inherit those components to put server-specific information. This is possible because the server delegates the job of getting a "type ID" and the client delegates the job of creating an object by such an ID. So, it is possible to send the type-names (for example) but find them in different namespaces to have different client and server objects. This is pretty similar to what I did in the Writing a Multiplayer Game (in WPF) article, as the server had specific properties to control the animations that the client simply wasn't aware of.

In the end we get the benefits of the ViewModel, like having View specific properties, without having to write code to redirect all the properties that we simply don't want to change. But, if you really want to keep the ViewModel and don't want to have different objects for the client and the server, feel free to use a ViewModel, as the DVMH doesn't forbid its use.

Number of tiers, Performance, Scalability, Availability etc

The D in DOP means distributed and when I started the development of the framework I was more focused on that distributed part, which allows N-Tier application, than on the DVMH pattern that can be used locally. So, how well do the DOPs really work in multi-tier environments? How scalable are they? What about the performance?

And the answer can't be more vague. It depends. Really, it depends on a lot of factors. The framework implementations I am giving with this article keep the objects in memory, so we can say these frameworks aren't very scalable. Yet, even if the HTTP protocol is stateless, ASP.NET allows us to keep states in memory by using sessions. In that situation the problem is how many objects we keep in the sessions.

So, comparing to DOPs, how many DOPs are we keeping in memory? We actually don't need to be observing a room to be in a "login page". That login page can exist exclusively on the client side and a login message can be sent to a shared room.

Also, the DOPs are extremely simple. They can be stored in the database easily, so a different implementation of the frameworks could do that. Of course, it will be good if such a framework also keeps recently used DOPs cached to avoid excessive database accesses, but that's a normal problem that everyone that writes scalable sites must deal.

We must not forget about the expected use. Do we want to create full client applications that know what they should do and communicate to the server only when they need "data", or do we want to create client applications that are simply "visualizers" for an application that exists elsewhere (maybe in another library loaded in the same process)? The DVMH actually fits the second category. That's a good thing as we are making an MVVM-like architecture that's even more loosely coupled. So, the DVMH architecture is not expected to be fully scalable, but the DOPs are. They can be used in the DVMH architecture, or inside a different architecture that's more scalable.

So, I can only say that DOPs are really scalable while the DVMH pattern is expected to in a Local Area Network (LAN) or completely local. So, it is possible to combine DVMH with another architecture and use DOPs in that other architecture for really multi-tier and scalable applications and games.

License

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


Written By
Software Developer (Senior) Microsoft
United States United States
I started to program computers when I was 11 years old, as a hobbyist, programming in AMOS Basic and Blitz Basic for Amiga.
At 12 I had my first try with assembler, but it was too difficult at the time. Then, in the same year, I learned C and, after learning C, I was finally able to learn assembler (for Motorola 680x0).
Not sure, but probably between 12 and 13, I started to learn C++. I always programmed "in an object oriented way", but using function pointers instead of virtual methods.

At 15 I started to learn Pascal at school and to use Delphi. At 16 I started my first internship (using Delphi). At 18 I started to work professionally using C++ and since then I've developed my programming skills as a professional developer in C++ and C#, generally creating libraries that help other developers do their work easier, faster and with less errors.

Want more info or simply want to contact me?
Take a look at: http://paulozemek.azurewebsites.net/
Or e-mail me at: paulozemek@outlook.com

Codeproject MVP 2012, 2015 & 2016
Microsoft MVP 2013-2014 (in October 2014 I started working at Microsoft, so I can't be a Microsoft MVP anymore).

Comments and Discussions

 
Questionnice Pin
Spricer5-Jun-14 5:14
Spricer5-Jun-14 5:14 
Questionnice Pin
Spricer4-Jun-14 22:00
Spricer4-Jun-14 22:00 
AnswerRe: nice Pin
Paulo Zemek5-Jun-14 2:34
mvaPaulo Zemek5-Jun-14 2:34 
GeneralMy vote of 5 Pin
azweepay4-Jun-14 19:23
azweepay4-Jun-14 19:23 
GeneralRe: My vote of 5 Pin
Paulo Zemek5-Jun-14 2:33
mvaPaulo Zemek5-Jun-14 2:33 
QuestionNice article Paulo ! Pin
Volynsky Alex3-Jun-14 8:08
professionalVolynsky Alex3-Jun-14 8:08 
AnswerRe: Nice article Paulo ! Pin
Paulo Zemek3-Jun-14 9:56
mvaPaulo Zemek3-Jun-14 9:56 
QuestionRe: Nice article Paulo ! Pin
Volynsky Alex3-Jun-14 23:12
professionalVolynsky Alex3-Jun-14 23:12 

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.