Reputation: 8320
Say I have a Logger
class, a LoggerViewModel
class and a MainWindow
with a TextBox
. The Logger
class is a thread-safe singleton, so I have only an instance of it in the application domain.
public sealed class Logger : INotifyPropertyChanged
{
private static readonly Logger _Instance = new Logger();
private static readonly object _SyncLock = new object();
private static List<LogEntry> _Data = new List<LogEntry>();
/// <summary>
///
/// </summary>
private Logger() { ; }
/// <summary>
///
/// </summary>
public static Logger Instance
{
get { return _Instance; }
}
/// <summary>
///
/// </summary>
/// <param name="entry"></param>
public void Write(LogEntry entry)
{
lock (_SyncLock)
{
_Data.Add(entry);
}
this.RaiseNotifyPropertyChanged("Entries");
}
/// <summary>
///
/// </summary>
/// <param name="component"></param>
/// <param name="message"></param>
public void Write(string component, string message)
{
LogEntry entry = LogEntry.Create(component, message);
Write(entry);
}
/// <summary>
///
/// </summary>
public IList<LogEntry> Entries
{
get
{
lock (_SyncLock)
{
return new ReadOnlyCollection<LogEntry>(_Data);
}
}
}
/// <summary>
///
/// </summary>
/// <param name="property"></param>
private void RaiseNotifyPropertyChanged(string property)
{
var handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(property));
}
}
/// <summary>
///
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
}
The Logger
unique instance is updated by more thread while the application is running, so I would update the TextBox
on the MainWindow
whenever the model (that is the Logger
singleton class) changes.
How to connect the Model and the ViewModel between them? I emphasize that the Model is changed by only a few application thread, so it is read-only from the point of view of UI.
I provided the LoggerText
property within the LoggerViewModel
class, since I thought the following working mechanism.
1. When the Model (the Logger
instance) changes, it notifies the ViewModel.
2. The ViewModel receives the notify by the Model and create a new string
containing all the messages from the logger.
3. The ViewModel notifies the View.
public class LoggerViewModel : INotifyPropertyChanged
{
Logger _LoggerModel;
/// <summary>
///
/// </summary>
public LoggerViewModel()
{
_LoggerModel = Logger.Instance;
}
/// <summary>
///
/// </summary>
public string LoggerText
{
get
{
string text = "";
List<LogEntry> entries = new List<LogEntry>(_LoggerModel.Entries);
foreach (LogEntry entry in entries)
{
text += entry.ToString();
text += "\n";
}
return text;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string propertyName)
{
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
How the ViewModel can intercept the notifications sent by the Model?
Upvotes: 1
Views: 206
Reputation: 14956
First of all I do not like that you are using a singleton. When using the singleton pattern you are making it hard on yourself when testing or reusing your view controllers. I would instead inject the Logger
dependency into your LoggerViewModel
class.
Aside from that one way to solve your problem is to register a handler for the PropertyChanged
event on your Logger
and build the text when the event fires for the Entries
property.
In LoggerViewModel
you would then add a property handler and update the LoggerText
property as needed.
public LoggerViewModel(Logger loggerModel /* Dependency injection*/)
{
_LoggerModel = loggerModel;
_LoggerModel.PropertyChanged += this.LoggerModel_PropertyChanged;
}
private void LoggerModel_PropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "Entries")
{
StringBuilder text = new StringBuilder(); // Use StringBuilder for performance
List<LogEntry> entries = new List<LogEntry>(_LoggerModel.Entries);
foreach (LogEntry entry in entries)
{
text.AppendLine(entry.ToString());
}
this.LoggerText = text.ToString();
}
}
private string _loggerText;
public string LoggerText
{
set
{
_loggerText = value;
RaisePropertyChanged("LoggerText");
}
get
{
return _loggerText;
}
}
Disclaimer: The above code is written without a compiler.
Upvotes: 1