views:

221

answers:

3

I am trying to use the Model-View-Controller pattern in a small application. The model contains some data and a selection like this

TModelSelection = record
  CurrentItem : TItem;
end;  

TModel = class
public
  property Items : TList <TItem>;
  property Selection : TModelSelection;
  property Subject : TSubject <TModel>;    // Observer pattern
end;

Now I have a tree view that observes the model. If the user selects an item in the tree view, the model selection should change.

The problem is that I run into problems with circular change notifications: I change the model selection in the tree view's OnChange event. This causes the tree view to update its selection (since the selection can also be changed by other parts of the application), which again triggers the OnChange event and so on.

How can I avoid this problem?

+9  A: 

notify only on true changes.

Or use a flag to disable updates during updates.

procedure OnChange(...)
begin
  if FChanging = false then
  begin
    FChanging:=true;
    ... do updates
    FChanging:=false;
  end;
end;

with FChanging being a member variable of type Boolean

Tobias Langner
+1 thanks! Using the flag does it for me.
Smasher
I'm glad I could help. Once you've seen this pattern, it's pretty obvious. It often comes in handy.
Tobias Langner
+3  A: 

A classic problem with MVC - when does a change in the view affect the model and when does it merely reflect the model?

This should be dealt with by the controller - if it isn't handled by the controller then you don't really have an MVC implementation but just an MV with controller logic embedded in and distributed throughout the interactions between the Model and the View.

When the user interacts with the View the View (treeview control) should notify the Controller which in turn updates the model.

When the Model is updated it should notify the Controller.

In that scenario, the Controller is already aware that is it updating the Model and so can disregard certain notifications that it would otherwise pass on to the View.

The biggest problem you face is that the controls used in your View are not ideally designed for use in an MVC implementation. Clicking in a treeview changes the selection in the treeview control (View) itself. In an MVC ideally what you want it to do is instead notify the Model (via the Controller) that the selection is required to be changed to the clicked item.

So the Model Selection state changes and the Treeview is notified. No View should ever assume that it already knows what it is doing to the Model.

The Treeview Selection State is then always only ever a reflection of the Model state.

But without doing some work in the user interface controls to create this distance between control state and underlying model state you are just going to have to struggle with work arounds.

Deltics
+1 thanks for the explanation. I realize that this is not a clean MVC solution and that a clean solution in Delphi probably means a lot of work...
Smasher
A: 

Store current selection in view layer (for example current selected node). If view asked by controller for switching to new node, and this node already selected - do nothing.

Standard behavior of delphi components when property is set :

type TSomeClass = class
  private 
    FSomeValue : TSomeType; 
  protected
    procedure SetSomeProperety(const AValue: TSomeType);
  public
    property SomeProperty : TSomeType read FValue write SetSomeProperty; 
end;

procedure TSomeClass.SetSomeProperety(const AValue: TSomeType);
begin
  if(AValue = FValue) then exit;
  FValue := AValue;
  // do other actions to reflect change
end;

Way with "Changing State" by Tobias Langner not good - Controller can decide to select another node, and at least require

try 
  ... 
finally 
  FChanging := false; 
end;

block.

ThinkJet