In its role as a presentation model, the component is responsible for implementing the logic of the user-interface (without actually implementing the user-interface). We will need to fill in the Save and Cancel methods, and add some additional logic to handle updating the word count when the user modifies the text in the editor.
The Cancel method is most straightforward: when the user presses cancel, the component should simply exit without saving anything. However, an application component cannot cause itself to disappear from the screen. Instead it must inform the host that it would like to exit. The Cancel method looks like this:
public void Cancel()
{
this.Exit(ApplicationComponentExitCode.None);
}
Before we can implement the Save method, we need to make a design decision: should the TextEditorComponent be responsible for saving its own data, or should we leave that responsibility outside the scope of this component? In most cases, it is generally easier for the component to save its own data, and so that's what we will do here:
public void Save()
{
File.WriteAllText(_filename, _text);
this.Exit(ApplicationComponentExitCode.Accepted);
}
Recall that we stated at the beginning of the example that the component would keep a running word count as text was entered. We will now add this logic to the component.
As the user changes the text in the editor, we can assume that the view will update our Text property on the fly. Therefore, we should change the implementation of that property to the following:
public string Text
{
get { return _text; }
set
{
_text = value;
UpdateWordCount();
}
}
UpdateWordCount is a private method that computes the number of words stored in the _text variable. A proper implementation of such a method is out of the scope for this discussion, but for the purposes of illustration we will use this rudimentary algorithm.
private void UpdateWordCount()
{
// really simple algorithm to count words for illustrative purposes only
// by assuming spaces, period, comma, or (semi)colon between words
char[] wordSeparators = new char[] { ' ', '.', ',', ';', ':' };
int wordCount = this.Text.Split(wordSeparators, StringSplitOptions.RemoveEmptyEntries).Length;
this.WordCount = wordCount;
}
Something is missing: the view has no way of knowing that the value of the WordCount property has changed. We could add our own event, say, WordCountChanged, to notify the view, but because ApplicationComponent implements INotifyPropertyChanged, we can just call NotifyPropertyChanged in the private setter of the WordCount property:
public int WordCount
{
get { return _wordCount; }
private set
{
_wordCount = value;
NotifyPropertyChanged("WordCount");
}
}
Since the UpdateWordCount calls the WordCount setter, we can be sure that view will remain synchronized with the state of the component’s data.
This completes the implementation of our component logic as we understand it. However, there is one scenario we have not yet accounted for. We have assumed thus far that the component itself will decide when it should exit, based on the user pressing Save or Cancel. But what happens if an external event occurs, such as an attempt to close a window, or to close the entire application, that would cause the component to exit? It would be rather unfair to the user if our component was simply stopped without warning – the user may have changed the text in the editor, and may not have yet saved those changes.
For this reason, IApplicationComponent provides a method named CanClose(), which is called by the host in exactly this situation. However, we do not necessarily need to implement CanClose from scratch, because the base class ApplicationComponent provides a reasonable implementation for us: it checks the Modified property, and if this property has been set to true, it will ask the user to confirm whether the modifications should be saved or discarded. If the user responds with Yes or No, the ExitCode property will be set accordingly, and Host.Exit() will be called. If the user presses cancel, CanClose returns false, indicating to the host that it should not heed the request to exit.
If this behaviour does not suit the needs of a particular component, that component is free to override CanClose. However, this behaviour is perfect for the needs of our text editor (and in many other situations as well). So we will change the implementation of the Text property one last time to set the base class Modified property to true when the text is changed, and similarly so for the Filename property:
public string Filename
{
get { return _filename; }
set
{
_filename = value;
this.Modified = true;
}
}
public string Text
{
get { return _text; }
set
{
_text = value;
this.Modified = true;
UpdateWordCount();
}
}