I'm trying to have one WPF application to communicate with an other application when something change on the UI. For example : a slider changes and I send the new value or a textbox input change and I send the new input value.
The second application is listening and receive those changes to update different properties.
To achieve this I'm using NetMQ following the Actor pattern example from the documentation : https://netmq.readthedocs.io/en/latest/actor/
Right now I have these Client/Server class :
The Server :
public class NetMQServer
{
public class ShimHandler : IShimHandler
{
private PairSocket shim;
private NetMQPoller poller;
private PublisherSocket publisher;
private string Address;
public ShimHandler(string address)
{
Address = address;
}
public void Initialise(object state)
{
}
public void Run(PairSocket shim)
{
using (publisher = new PublisherSocket())
{
publisher.Bind(Address);
publisher.Options.SendHighWatermark = 1000;
this.shim = shim;
shim.ReceiveReady += OnShimReady;
shim.SignalOK();
poller = new NetMQPoller { shim, publisher };
poller.Run();
}
}
private void OnShimReady(object sender, NetMQSocketEventArgs e)
{
string command = e.Socket.ReceiveFrameString();
if(command == NetMQActor.EndShimMessage)
{
poller.Stop();
return;
}
else
{
byte[] byteMessage = e.Socket.ReceiveFrameBytes();
publisher.SendMoreFrame(command).SendFrame(byteMessage);
}
}
private void UpdateString(string stringmessage, string propertyToUpdate)
{
propertyToUpdate = stringmessage;
}
}
public NetMQServer(string ip, int port)
{
IP = ip;
Port = port;
Serializer = new CerasSerializer();
}
public CerasSerializer Serializer { get; set; }
private NetMQActor actor;
private string _name;
public string Name
{
get { return _name; }
set { _name = value;}
}
private int _port;
public int Port
{
get { return _port; }
set
{
_port = value;
ReStart();
}
}
private string _ip;
public string IP
{
get { return _ip; }
set
{
_ip = value;
ReStart();
}
}
public string Address
{
get { return String.Format("tcp://{0}:{1}", IP, Port); }
}
public void Start()
{
if (actor != null)
return;
actor = NetMQActor.Create(new ShimHandler(Address));
}
public void Stop()
{
if (actor != null)
{
actor.Dispose();
actor = null;
}
}
public void ReStart()
{
if (actor == null)
return;
Stop();
Start();
}
public void SendObject(string topic, object commandParameter)
{
if (actor == null)
return;
byte[] Serialized = Serializer.Serialize(commandParameter);
var message = new NetMQMessage();
message.Append(topic);
message.Append(Serialized);
actor.SendMultipartMessage(message);
}
}
Here when the property of a control change I call SendStringMessage(string stringToSend) function and it is sending with the NetMQ publisher (the string is only for testing, I could send whatever object as byte too).
Anyway, here is the client running on the second app :
public class NetMQClient
{
public class ShimHandler : IShimHandler
{
private PairSocket shim;
private NetMQPoller poller;
private SubscriberSocket subscriber;
private ByteMessage ByteMessage;
private string Address;
private string Topic;
private string MessageType;
public ShimHandler(ByteMessage byteMessage, string address, string topic)
{
this.ByteMessage = byteMessage;
this.Address = address;
this.Topic = topic;
}
public void Initialise(object state)
{
}
public void Run(PairSocket shim)
{
using (subscriber = new SubscriberSocket())
{
subscriber.Connect(Address);
subscriber.Subscribe(Topic);
subscriber.Options.ReceiveHighWatermark = 1000;
subscriber.ReceiveReady += OnSubscriberReady;
this.shim = shim;
shim.ReceiveReady += OnShimReady;
shim.SignalOK();
poller = new NetMQPoller { shim, subscriber };
poller.Run();
}
}
private void OnSubscriberReady(object sender, NetMQSocketEventArgs e)
{
string topic = e.Socket.ReceiveFrameString();
if (topic == Topic)
{
this.ByteMessage.Message = e.Socket.ReceiveFrameBytes();
}
}
private void OnShimReady(object sender, NetMQSocketEventArgs e)
{
string command = e.Socket.ReceiveFrameString();
if (command == NetMQActor.EndShimMessage)
{
poller.Stop();
}
}
}
public ByteMessage ByteMessage { get; set; }
private NetMQActor actor;
private string _command;
public string Command
{
get { return _command; }
set { _command = value;}
}
private string _topic;
public string Topic
{
get { return _topic; }
set
{
_topic = value;
ReStart();
}
}
private int _port;
public int Port
{
get { return _port; }
set
{
_port = value;
ReStart();
}
}
private string _ip;
public string IP
{
get { return _ip; }
set
{
_ip = value;
ReStart();
}
}
public string Address
{
get { return String.Format("tcp://{0}:{1}", IP, Port); }
}
public NetMQClient(string ip, int port, string topic)
{
ByteMessage = new ByteMessage();
IP = ip;
Port = port;
Topic = topic;
}
public void Start()
{
if (actor != null)
return;
actor = NetMQActor.Create(new ShimHandler(ByteMessage, Address, Topic));
}
public void Stop()
{
if (actor != null)
{
actor.Dispose();
actor = null;
}
}
public void ReStart()
{
if (actor == null)
return;
Stop();
Start();
}
}
Now the ByteMessage class just look as simple as this :
public class ByteMessage : INotifyPropertyChanged
{
public ByteMessage()
{
}
private byte[] _message;
public byte[] Message
{
get { return _message; }
set
{
_message = value;
OnPropertyChanged("Message");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
One the second App, Classes will have a NetMQClient as property and register to the OnPropertyChange event of the NetMQClient's ByteMessage.
Now when ByteMessage.Message is updated from the ShimHandler I can Deserialize the data and do something with it.
So far this is working but I'm really not sure if I'm doing it correctly... and if this is ThreadSafe
Shouldn't it be better if each class on the Listening app would have their own NetMQClient listening to a specific topic ?
Really confuse here how to use all that, even after reading all examples provided around NetMQ.
Thanks
Related
I have my Program class like that :
class Program
{
public static UDProtocol MyUdp;
private static readonly int Port = 11000;
public static F_Main F1;
public static BindingSource DtgvSource;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
OnStart();
Application.Run();
}
private static void OnStart()
{
MyUdp = new UDProtocol(Port);
F1 = new F_Main();
MyUdp.Start();
DtgvSource = new BindingSource(MyUdp.ClientList, null);
F1.DTGV_Clients.DataSource = DtgvSource;
}
}
My UDProtocol look like that (simplified) :
public class UDProtocol
{
private readonly UdpClient UdpMe;
private readonly int Port;
private Client CurrentClient;
public BindingList<Client> ClientList { get; set; }
public UDProtocol(int _port)
{
Port = _port;
UdpMe = new UdpClient(Port);
ClientList = new BindingList<Client>();
}
public void Start()
{
ThrFlag = true;
new Thread(() =>
{
while (ThrFlag)
{
if (UdpMe.Available > 0)
{
try
{
NewClientEP = new IPEndPoint(IPAddress.Any, Port);
string data = Encoding.UTF8.GetString(UdpMe.Receive(ref NewClientEP));
CurrentClient = new Client
{
Name = Dns.GetHostEntry(NewClientEP.Address).HostName,
IP = NewClientEP.Address,
};
MsgObj NewMsg = new MsgObj(data) { From = CurrentClient };
AnalyseMessage(NewMsg);
}
catch { }
}
Thread.Sleep(1);
}
}).Start();
}
public void AnalyseMessage(MsgObj msg)
{
//some useless code
msg.From.Connected = true;
}
}
I also have a MsgObj class :
public class MsgObj
{
//some others variables
private Client _From {get; set;}
public MsgObj(string data)
{
//some useless code
}
}
And client class that implement INotifyPropertyChanged
public class Client : INotifyPropertyChanged
{
public IPAddress IP { get; set; }
private bool _connected;
public event PropertyChangedEventHandler PropertyChanged;
public bool Connected
{
get => _connected;
set
{
_connected = value;
NotifyPropertyChanged("Connected");
}
}
private void NotifyPropertyChanged(string name)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}
In My F1 (F_Main) my Datagridview is built like this :
public F_Main()
{
InitializeComponent();
DTGV_Clients.AutoGenerateColumns = false;
DTGV_Clients.Columns[0].DataPropertyName = "Connected";
DTGV_Clients.Columns[1].DataPropertyName = "IP";
}
When I add a client to the ClientList (BindingList), the datagridview is updated. On the other hand, when I try to modify the "Connected" property of a client, it is not updated in the Datagridview despite my implementation of the INotifyPropertyChanged interface.
I also tried with ResetBinding(false) in
AnalyzeData()
but without success.
I cannot see what I'm doing wrong and where it can come from, despite all my research on the subject.
I was trying to change the currentclient which is different from the client in the list. So I put in AnalyzeData():
Client ThisClient = ClientList.FirstOrDefault(x => x.Name.Equals(msg.From.Name));
ThisClient.Connected = true;
I'm trying to report progress to my status bar when creating a zip file.
For reporting the progress of the creation i have used the following guide:
ZipFile Creation Report Progress
My status bar is currently updated using 2 classes:
an INotifyPropertyChanged class called StatusBar
a separate class called Status.
The StatusBar class:
public class StatusBar : INotifyPropertyChanged
{
private string _message;
private string _submessage;
private bool _isindeterminate;
private bool _visible;
private int _min;
private int _max;
private double _progress;
public event PropertyChangedEventHandler PropertyChanged;
public StatusBar() { }
public int Min
{
get { return this._min; }
set
{
this._min = value;
this.OnPropertyChanged("Min");
}
}
public int Max
{
get { return this._max; }
set
{
this._max = value;
this.OnPropertyChanged("Max");
}
}
public double Progress
{
get { return this._progress; }
set
{
this._progress = value;
this.OnPropertyChanged("Progress");
}
}
public string Message
{
get { return this._message; }
set
{
this._message = value;
this.OnPropertyChanged("Message");
}
}
public string SubMessage
{
get { return this._submessage; }
set
{
this._submessage = value;
this.OnPropertyChanged("SubMessage");
}
}
public bool IsIndeterminate
{
get { return this._isindeterminate; }
set
{
this._isindeterminate = value;
this.OnPropertyChanged("IsIndeterminate");
}
}
public bool Visible
{
get { return this._visible; }
set
{
this._visible = value;
this.OnPropertyChanged("Visible");
}
}
void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
The Status class:
public class Status
{
public static void Initialise(StatusBar status, string _message, string _submessage, int _min, int _max, double _progress, bool _visible, bool _isindeterminate)
{
status.Visible = _visible;
if (_isindeterminate == false)
{
status.Message = _message;
status.SubMessage = _submessage;
status.Progress = _progress;
status.Min = _min;
status.Max = _max;
status.IsIndeterminate = _isindeterminate;
}
else
{
status.Message = _message;
status.IsIndeterminate = _isindeterminate;
}
}
public static void UpdateProgress(StatusBar status, double _progress)
{
if (_progress == status.Max)
{
Complete(status);
}
else
{
status.Progress = _progress;
}
}
public static void UpdateMessage(StatusBar status, string _message)
{
status.Message = _message;
}
public static void UpdateSubMessage(StatusBar status, string _submessage)
{
status.SubMessage = _submessage;
}
public static void UpdateProgressMessage(StatusBar status, string _message, double _progress)
{
status.Message = _message;
status.Progress = _progress;
}
public static void Complete(StatusBar status)
{
status.Message = "Ready";
status.SubMessage = " ";
status.Min = 0;
status.Max = 0;
status.Progress = 0;
status.Visible = false;
status.IsIndeterminate = false;
}
}
For creating the zip file and updating the status bar I currently have the following:
string sourceDirectory = "C:\\Access\\Test";
string archive = #"C:\Access\Test.zip";
Status.Initialise(statusbar, "Creating Zip File...", "", 0, 100, 0, true, false);
Backup.CreateFromDirectory(sourceDirectory, archive,
new BasicProgress<double>(p => Status.UpdateProgressMessage(statusbar, $"{p:P2} archiving complete", p)));
The code above is working and the Zip file is created.
However the progress is only reported once the process has been completed and not during the process.
How can I change this to update the progress during the zip file creation?
I have a set of classes that I am using to deserialize JSON into. My program will periodically look for changes to this JSON file, and if it finds any, will push the new data to the properties of these classes using reflection.
I need to find any new items added to the collection of the Item2 class (SocialExportJSON.SocialExportData.Item2) after a successful update.
My JSON classes look like this (there are more but I want to avoid too big a wall of code):
public class Item2 : INotifyPropertyChanged
{
[JsonProperty("type")]
private string type;
public string Type
{
get
{
return type;
}
set
{
if (type != value)
{
type = value;
RaisePropertyChanged("Type");
}
}
}
[JsonProperty("id")]
private string id;
public string ID
{
get
{
return id;
}
set
{
if (id != value)
{
id = value;
RaisePropertyChanged("ID");
}
}
}
[JsonProperty("postedIso8601")]
private string postedIso8601;
public string PostedIso8601
{
get
{
return postedIso8601;
}
set
{
if (postedIso8601 != value)
{
postedIso8601 = value;
RaisePropertyChanged("PostedIso8601");
}
}
}
[JsonProperty("postedTimestamp")]
private object postedTimestamp;
public object PostedTimestamp
{
get
{
return postedTimestamp;
}
set
{
if (postedTimestamp != value)
{
postedTimestamp = value;
RaisePropertyChanged("PostedTimestamp");
}
}
}
[JsonProperty("engagement")]
private Engagement engagement;
public Engagement Engagement
{
get
{
return engagement;
}
set
{
if (engagement != value)
{
engagement = value;
RaisePropertyChanged("Engagement");
}
}
}
[JsonProperty("source")]
private Source2 source;
public Source2 Source
{
get
{
return source;
}
set
{
if (source != value)
{
source = value;
RaisePropertyChanged("Source");
}
}
}
[JsonProperty("author")]
private Author author;
public Author Author
{
get
{
return author;
}
set
{
if (author != value)
{
author = value;
RaisePropertyChanged("Author");
}
}
}
[JsonProperty("content")]
private Content content;
public Content Content
{
get
{
return content;
}
set
{
if (content != value)
{
content = value;
RaisePropertyChanged("Content");
}
}
}
[JsonProperty("location")]
private Location location;
public Location Location
{
get
{
return location;
}
set
{
if (location != value)
{
location = value;
RaisePropertyChanged("Location");
}
}
}
[JsonProperty("publication")]
private Publication publication;
public Publication Publication
{
get
{
return publication;
}
set
{
if (publication != value)
{
publication = value;
RaisePropertyChanged("Publication");
}
}
}
[JsonProperty("metadata")]
private Metadata metadata;
public Metadata Metadata
{
get
{
return metadata;
}
set
{
if (metadata != value)
{
metadata = value;
RaisePropertyChanged("Metadata");
}
}
}
//Event handling
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
//Console.WriteLine("Updated");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public class SocialExportData : INotifyPropertyChanged
{
[JsonProperty("dataType")]
private string dataType;
public string DataType
{
get
{
return dataType;
}
set
{
if (dataType != value)
{
dataType = value;
RaisePropertyChanged("DataType");
}
}
}
[JsonProperty("id")]
private int id;
public int ID
{
get
{
return id;
}
set
{
if (id != value)
{
id = value;
RaisePropertyChanged("ID");
}
}
}
[JsonProperty("story")]
private Story story;
public Story Story
{
get
{
return story;
}
set
{
if (story != value)
{
story = value;
RaisePropertyChanged("Story");
}
}
}
[JsonProperty("order")]
private string order;
public string Order
{
get
{
return order;
}
set
{
if (order != value)
{
order = value;
RaisePropertyChanged("Order");
}
}
}
[JsonProperty("lifetime")]
private string lifetime;
public string Lifetime
{
get
{
return lifetime;
}
set
{
if (lifetime != value)
{
lifetime = value;
RaisePropertyChanged("Lifetime");
}
}
}
[JsonProperty("maxAge")]
private int maxAge;
public int MaxAge
{
get
{
return maxAge;
}
set
{
if (maxAge != value)
{
maxAge = value;
RaisePropertyChanged("MaxAge");
}
}
}
[JsonProperty("maxSize")]
private int maxSize;
public int MaxSize
{
get
{
return maxSize;
}
set
{
if (maxSize != value)
{
maxSize = value;
RaisePropertyChanged("MaxSize");
}
}
}
[JsonProperty("consumeCount")]
private int consumeCount;
public int ConsumeCount
{
get
{
return consumeCount;
}
set
{
if (consumeCount != value)
{
consumeCount = value;
RaisePropertyChanged("ConsumeCount");
}
}
}
[JsonProperty("consumeInterval")]
private int consumeInterval;
public int ConsumeInterval
{
get
{
return consumeInterval;
}
set
{
if (consumeInterval != value)
{
consumeInterval = value;
RaisePropertyChanged("ConsumeInterval");
}
}
}
[JsonProperty("items")]
private ObservableCollection<Item2> items;
public ObservableCollection<Item2> Items
{
get
{
return items;
}
set
{
if (items != value)
{
items = value;
RaisePropertyChanged("Items");
}
}
}
//Event handling
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
//Console.WriteLine("Updated");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
public class SocialExportJSON : INotifyPropertyChanged
{
[JsonProperty("id")]
private string id;
public string ID
{
get
{
return id;
}
set
{
if (id != value)
{
id = value;
RaisePropertyChanged("ID");
}
}
}
[JsonProperty("ttl")]
private int ttl;
public int TTL
{
get
{
return ttl;
}
set
{
if (ttl != value)
{
ttl = value;
RaisePropertyChanged("TTL");
}
}
}
[JsonProperty("serial")]
private long serial;
public long Serial
{
get
{
return serial;
}
set
{
if (serial != value)
{
serial = value;
RaisePropertyChanged("Serial");
}
}
}
[JsonProperty("formatType")]
private string formatType;
public string FormatType
{
get
{
return formatType;
}
set
{
if (formatType != value)
{
formatType = value;
RaisePropertyChanged("FormatType");
}
}
}
[JsonProperty("modifiedIso8601")]
private string modifiedIso8601;
public string ModifiedIso8601
{
get
{
return modifiedIso8601;
}
set
{
if (modifiedIso8601 != value)
{
modifiedIso8601 = value;
RaisePropertyChanged("ModifiedIso8601");
}
}
}
[JsonProperty("modifiedTimestamp")]
private long modifiedTimestamp;
public long ModifiedTimestamp
{
get
{
return modifiedTimestamp;
}
set
{
if (modifiedTimestamp != value)
{
modifiedTimestamp = value;
RaisePropertyChanged("ModifiedTimestamp");
}
}
}
[JsonProperty("timezone")]
private string timezone;
public string Timezone
{
get
{
return timezone;
}
set
{
if (timezone != value)
{
timezone = value;
RaisePropertyChanged("Timezone");
}
}
}
[JsonProperty("dataType")]
private string dataType;
public string DataType
{
get
{
return dataType;
}
set
{
if (dataType != value)
{
dataType = value;
RaisePropertyChanged("DataType");
}
}
}
[JsonProperty("exports")]
private ObservableCollection<SocialExportData> exports;
public ObservableCollection<SocialExportData> Exports
{
get
{
return exports;
}
set
{
if (exports != value)
{
exports = value;
RaisePropertyChanged("Exports");
}
}
}
//Event handling
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property)
{
//Console.WriteLine("Updated");
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
In another class, I have a method to deserialize to a global instance of my JSON class. It looks like this:
public SocialExportJSON socialExportData;
private async void DownloadAndDeserializeJSONAsync()
{
try
{
//Create a web client with the supplied credentials
var exportClient = new WebClient { Credentials = new NetworkCredential(uName, pw), Encoding = Encoding.UTF8};
//Create a task to download the JSON string and wait for it to finish
var downloadTask = Task.Run(() => exportClient.DownloadString(new Uri(eURL)));
downloadTask.Wait();
//Get the string from the task
var JSONString = await downloadTask;
//Create a task to deserialize the JSON from the last task
var DeserializeTask = Task.Run(() => JsonConvert.DeserializeObject<SocialExportJSON>(JSONString));
DeserializeTask.Wait();
SocialExportJSON sej = await DeserializeTask;
//Check the timestamp first to see if we should change the data
if(socialExportData == null)
{
//Get the data from the task
socialExportData = await DeserializeTask;
}
else if(sej.ModifiedTimestamp != socialExportData.ModifiedTimestamp)
{
//Get the data from the task
SocialExportJSON newData = await DeserializeTask;
GetNewItems(newData);
SetNewData(newData);
//Call the exportUpdated event when the task has finished
exportUpdated();
}
}
catch (Exception e)
{
MessageBox.Show(e.Message.ToString());
}
}
In my SetNewData function, shown below, I use reflection to set the properties of my global class. Because I'm setting the whole collection rather than iterating through each of the properties in each of the classes, I can't use the CollectionChanged event to find new items.
public void SetNewData(SocialExportJSON newData)
{
//Loop through each of the properties and copy from source to target
foreach (PropertyInfo pi in socialExportData.GetType().GetProperties())
{
if (pi.CanWrite)
{
pi.SetValue(socialExportData, pi.GetValue(newData, null), null);
}
}
}
Is there a way I can modify my SetNewData function in such a way that it calls CollectionChanged? If not, what would be the best way to go about getting any new additions to my collection of Item2?
In my Main function. I create an instance of my class called SocialExport like so:
SocialExport s = new SocialExport("http://example.json", "example", "example");.
This class is where the global instance of my JSON class is contained, and my event handler is added like so
s.socialExportData.Exports[0].Items.CollectionChanged += CollectionChanged;
Then you are hooking up an event handler for the CollectionChanged event for that particular instance of ObservableCollection<Item2>.
If you create a new ObservableCollection<Item2>, you obviously must hook up an event handler to this one as well. The event handler that is associated with the old object won't be invoked when new items are added to the new instance.
So whenever a new ObservableCollection<Item2> is created, using deserialization or not, you should hook up a new event handler.
You could probably do this in your DownloadAndDeserializeJSONAsync method. The other option would be to create only one instance of the collection and remove and add items from/to this one.
I am a beginner to C# programming.
When I run this program, the eventhandler named - newMessagePublishedEventHandler of the class MessagePool is always null while executing the OnNewMessagePublished() function of the same class.
Can anyone please tell me why? and also how could I resolve this?
namespace ConsoleApp
{
public class MessageEventArgs : System.EventArgs
{
private int publisherId;
private string message;
public MessageEventArgs(int publisherId, string messageText)
{
this.publisherId = publisherId;
this.message = messageText;
}
public int PublisherId
{
get { return publisherId; }
}
public string Message
{
get { return message; }
}
}
public class Publisher
{
private int publisherId;
public Publisher(int publisherId)
{
this.publisherId = publisherId;
}
public int PublisherId
{
get { return publisherId; }
}
public void Publish(string message)
{
MessagePool.GetInstance().Publisher_PostMessage(this.publisherId, message);
Console.WriteLine("published message - " + message);
}
}
public class MessagePool
{
private static MessagePool theOnlyInstanceOfMessageBroker;
public static MessagePool GetInstance()
{
if (theOnlyInstanceOfMessageBroker == null)
{
return new MessagePool();
}
else
{
return theOnlyInstanceOfMessageBroker;
}
}
private MessagePool()
{
}
private EventHandler newMessagePublishedEventHandler;
public event EventHandler NewMessagePublished
{
add
{
newMessagePublishedEventHandler += value;
}
remove
{
newMessagePublishedEventHandler -= value;
}
}
public void Publisher_PostMessage(int publisherId, string message)
{
DateTime publishedTime = DateTime.Now;
this.OnNewMessagePublished(publisherId, message);
}
private void OnNewMessagePublished(int publisherId, string message)
{
MessageEventArgs eventArgs = new MessageEventArgs(publisherId, message);
if (newMessagePublishedEventHandler != null)
{
newMessagePublishedEventHandler(this, eventArgs);
}
}
}
public class Subscriber
{
private int subscriberId;
public Subscriber(int subscriberId)
{
this.subscriberId = subscriberId;
this.SubscribeToMessagebroker();
}
public int SubscriberId
{
get { return subscriberId; }
}
private void SubscribeToMessagebroker()
{
MessagePool.GetInstance().NewMessagePublished -= Subscriber_NewMessageArrived;
MessagePool.GetInstance().NewMessagePublished += Subscriber_NewMessageArrived;
}
private void Subscriber_NewMessageArrived(object sender, EventArgs eventArgs)
{
if (eventArgs != null && eventArgs is MessageEventArgs)
{
var data = eventArgs as MessageEventArgs;
if (data != null)
Console.WriteLine("Recieved message : '" + data.Message + "' from " + data.PublisherId);
}
}
}
class Program
{
static void Main(string[] args)
{
Subscriber subscriber = new Subscriber(1);
Publisher publisher = new Publisher(1001);
publisher.Publish("Hey man.. whats up?");
Console.ReadLine();
}
}
}
MessagePool is not a proper Singleton, you are always returning a new MessagePool.
public class MessagePool
{
private static MessagePool theOnlyInstanceOfMessageBroker;
private static object _syncRoot = new object();
public static MessagePool GetInstance()
{
if (theOnlyInstanceOfMessageBroker == null)
{
lock(_syncRoot)
{
if (theOnlyInstanceOfMessageBroker == null)
theOnlyInstanceOfMessageBroker = new MessagePool();
}
}
return theOnlyInstanceOfMessageBroker;
}
private MessagePool()
{
}
//...
}
What is the best way to create a class with an event that fires when one of its Properties is changed? Specifically, how do you convey to any subscribers which Property was changed?
Ex:
public class ValueChangedPublisher
{
private int _prop1;
private string _prop2;
public static event ValueChangedHandler(/*some parameters?*/);
public int Prop1
{
get { return _prop1; }
set
{
if (_prop1 != value)
{
_prop1 = value;
ValueChangedHandler(/*parameters?*/);
}
}
}
public string Prop2
{
get { return _prop2; }
set
{
if (_prop2 != value)
{
_prop2 = value;
ValueChangedHandler(/*parameters?*/);
}
}
}
}
public class ValueChangedSubscriber
{
private int _prop1;
private string _prop2;
public ValueChangedSubscriber()
{
ValueChangedPublisher.ValueChanged += ValueChanged;
}
private void ValueChanged(/*parameters?*/)
{
/*how does the subscriber know which property was changed?*/
}
}
My goal is to make this as extensible as possible (e.g. I don't want a bunch of huge if/else if/switch statements lumbering around). Does anybody know of a technique to achieve what I'm looking for?
EDIT:
What I'm really looking for is how to utilize the INotifyPropertyChanged pattern on the subscriber side. I don't want to do this:
private void ValueChanged(string propertyName)
{
switch(propertyName)
{
case "Prop1":
_prop1 = _valueChangedPublisher.Prop1;
break;
case "Prop2":
_prop2 = _valueChangedPublisher.Prop2;
break;
// the more properties that are added to the publisher, the more cases I
// have to handle here :/ I don't want to have to do it this way
}
}
.NET provides the INotifyPropertyChanged interface. Inherit and implement it:
public class ValueChangedPublisher : INotifyPropertyChanged
{
private int _prop1;
private string _prop2;
public event PropertyChangedEventHandler ValueChangedHandler;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public int Prop1
{
get { return _prop1; }
set
{
if (_prop1 != value)
{
_prop1 = value;
NotifyPropertyChanged();
}
}
}
public string Prop2
{
get { return _prop2; }
set
{
if (_prop2 != value)
{
_prop2 = value;
NotifyPropertyChanged();
}
}
}
}
Here is what I did to solve my problem. It's a bit large, so maybe not performant, but works for me.
Edit
See this question for performance details: C# using properties with value types with Delegate.CreateDelegate
Base Class for publishing property change stuff:
public abstract class PropertyChangePublisherBase : INotifyPropertyChanged
{
private Dictionary<string, PropertyInfo> _properties;
private bool _cacheProperties;
public bool CacheProperties
{
get { return _cacheProperties; }
set
{
_cacheProperties = value;
if (_cacheProperties && _properties == null)
_properties = new Dictionary<string, PropertyInfo>();
}
}
protected PropertyChangePublisherBase(bool cacheProperties)
{
CacheProperties = cacheProperties;
}
public bool ContainsBinding(PropertyChangedEventHandler handler)
{
if (PropertyChanged == null)
return false;
return PropertyChanged.GetInvocationList().Contains(handler);
}
public object GetPropertValue(string propertyName)
{
if (String.IsNullOrEmpty(propertyName) || String.IsNullOrWhiteSpace(propertyName))
throw new ArgumentException("Argument must be the name of a property of the current instance.", "propertyName");
return ProcessGetPropertyValue(propertyName);
}
protected virtual object ProcessGetPropertyValue(string propertyName)
{
if (_cacheProperties)
{
if (_properties.ContainsKey(propertyName))
{
return _properties[propertyName].GetValue(this, null);
}
else
{
var property = GetType().GetProperty(propertyName);
_properties.Add(propertyName, property);
return property.GetValue(this, null);
}
}
else
{
var property = GetType().GetProperty(propertyName);
return property.GetValue(this, null);
}
}
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void RaisePropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Base Class for receiving property change stuff:
public abstract class PropertyChangeSubscriberBase
{
protected readonly string _propertyName;
protected virtual object Value { get; set; }
protected PropertyChangeSubscriberBase(string propertyName, PropertyChangePublisherBase bindingPublisher)
{
_propertyName = propertyName;
AddBinding(propertyName, this, bindingPublisher);
}
~PropertyChangeSubscriberBase()
{
RemoveBinding(_propertyName);
}
public void Unbind()
{
RemoveBinding(_propertyName);
}
#region Static Fields
private static List<string> _bindingNames = new List<string>();
private static List<PropertyChangeSubscriberBase> _subscribers = new List<PropertyChangeSubscriberBase>();
private static List<PropertyChangePublisherBase> _publishers = new List<PropertyChangePublisherBase>();
#endregion
#region Static Methods
private static void PropertyChanged(object sender, PropertyChangedEventArgs args)
{
string propertyName = args.PropertyName;
if (_bindingNames.Contains(propertyName))
{
int i = _bindingNames.IndexOf(propertyName);
var publisher = _publishers[i];
var subscriber = _subscribers[i];
subscriber.Value = publisher.GetPropertValue(propertyName);
}
}
public static void AddBinding(string propertyName, PropertyChangeSubscriberBase subscriber, PropertyChangePublisherBase publisher)
{
if (!_bindingNames.Contains(propertyName))
{
_bindingNames.Add(propertyName);
_publishers.Add(publisher);
_subscribers.Add(subscriber);
if (!publisher.ContainsBinding(PropertyChanged))
publisher.PropertyChanged += PropertyChanged;
}
}
public static void RemoveBinding(string propertyName)
{
if (_bindingNames.Contains(propertyName))
{
int i = _bindingNames.IndexOf(propertyName);
var publisher = _publishers[i];
_bindingNames.RemoveAt(i);
_publishers.RemoveAt(i);
_subscribers.RemoveAt(i);
if (!_publishers.Contains(publisher))
publisher.PropertyChanged -= PropertyChanged;
}
}
#endregion
}
Actual class to use for subscribing to property change stuff:
public sealed class PropertyChangeSubscriber<T> : PropertyChangeSubscriberBase
{
private PropertyChangePublisherBase _publisher;
public new T Value
{
get
{
if (base.Value == null)
return default(T);
if (base.Value.GetType() != typeof(T))
throw new InvalidOperationException(String.Format("Property {0} on object of type {1} does not match the type Generic type specified {2}.", _propertyName, _publisher.GetType(), typeof(T)));
return (T)base.Value;
}
set { base.Value = value; }
}
public PropertyChangeSubscriber(string propertyName, PropertyChangePublisherBase bindingPublisher)
: base(propertyName, bindingPublisher)
{
_publisher = bindingPublisher;
}
}
Here is an example of the Class with properties that you wish to be notified about:
public class ExamplePublisher: PropertyChangedPublisherBase
{
private string _id;
private bool _testBool;
public string Id
{
get { return _id; }
set
{
if (value == _id) return;
_id = value;
RaisePropertyChanged("Id");
}
}
public bool TestBool
{
get { return _testBool; }
set
{
if (value.Equals(_testBool)) return;
_testBool = value;
RaisePropertyChanged("TestBool");
}
}
}
Here is an example of the Class that will be notified when the properties in the class above change:
public class ExampleReceiver
{
public PropertyChangeSubscriber<string> Id { get; set; }
public PropertyChangeSubscriber<bool> TestBool { get; set; }
public MyExampleClass(PropertyChangePublisherBase publisher)
{
Id = new PropertyChangeSubscriber<string>("Id", publisher);
TestBool = new PropertyChangeSubscriber<bool>("TestBool", publisher);
}
}