From f80d1e2ee91ad765115ea3122d0533ee9444b170 Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Sat, 13 Jun 2020 17:31:02 -0500 Subject: [PATCH] Continue with implementing QCue --- QSharp.sln | 44 +++-- QSharp/Events/QEventArgs.cs | 10 +- QSharp/Events/QEventHandlers.cs | 1 + QSharp/QCue.cs | 293 ++++++++++++++++++++++++-------- QSharp/QMessage.cs | 8 - QSharp/QSharp.csproj | 3 + QSharp/SharpOSC/TCPClient.cs | 19 ++- 7 files changed, 270 insertions(+), 108 deletions(-) diff --git a/QSharp.sln b/QSharp.sln index 5daef5a..f0e520d 100644 --- a/QSharp.sln +++ b/QSharp.sln @@ -4,16 +4,18 @@ VisualStudioVersion = 16.0.30128.74 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QSharp", "QSharp\QSharp.csproj", "{51CD2579-79C9-49A9-B68D-93CBE878BAA1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QSharpDemo", "QSharpDemo\QSharpDemo.csproj", "{8518D042-D511-4DBB-8406-5B11B2777ED2}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QSharpDemoApp", "QSharpDemoApp\QSharpDemoApp.csproj", "{734E00A0-31BC-4ED8-BC29-FE7649FD2089}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|ARM = Debug|ARM + Debug|ARM64 = Debug|ARM64 Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|ARM = Release|ARM + Release|ARM64 = Release|ARM64 Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection @@ -22,6 +24,8 @@ Global {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|Any CPU.Build.0 = Debug|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|ARM.ActiveCfg = Debug|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|ARM.Build.0 = Debug|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|ARM64.Build.0 = Debug|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|x64.ActiveCfg = Debug|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|x64.Build.0 = Debug|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Debug|x86.ActiveCfg = Debug|Any CPU @@ -30,26 +34,32 @@ Global {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|Any CPU.Build.0 = Release|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|ARM.ActiveCfg = Release|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|ARM.Build.0 = Release|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|ARM64.ActiveCfg = Release|Any CPU + {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|ARM64.Build.0 = Release|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|x64.ActiveCfg = Release|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|x64.Build.0 = Release|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|x86.ActiveCfg = Release|Any CPU {51CD2579-79C9-49A9-B68D-93CBE878BAA1}.Release|x86.Build.0 = Release|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|ARM.ActiveCfg = Debug|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|ARM.Build.0 = Debug|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|x64.ActiveCfg = Debug|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|x64.Build.0 = Debug|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|x86.ActiveCfg = Debug|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Debug|x86.Build.0 = Debug|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|Any CPU.Build.0 = Release|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|ARM.ActiveCfg = Release|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|ARM.Build.0 = Release|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|x64.ActiveCfg = Release|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|x64.Build.0 = Release|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|x86.ActiveCfg = Release|Any CPU - {8518D042-D511-4DBB-8406-5B11B2777ED2}.Release|x86.Build.0 = Release|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Debug|Any CPU.Build.0 = Debug|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Debug|ARM.ActiveCfg = Debug|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Debug|ARM.Build.0 = Debug|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Debug|ARM64.Build.0 = Debug|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Debug|x64.ActiveCfg = Debug|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Debug|x64.Build.0 = Debug|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Debug|x86.ActiveCfg = Debug|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Debug|x86.Build.0 = Debug|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Release|Any CPU.ActiveCfg = Release|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Release|Any CPU.Build.0 = Release|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Release|ARM.ActiveCfg = Release|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Release|ARM.Build.0 = Release|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Release|ARM64.ActiveCfg = Release|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Release|ARM64.Build.0 = Release|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Release|x64.ActiveCfg = Release|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Release|x64.Build.0 = Release|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Release|x86.ActiveCfg = Release|Any CPU + {734E00A0-31BC-4ED8-BC29-FE7649FD2089}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/QSharp/Events/QEventArgs.cs b/QSharp/Events/QEventArgs.cs index b67d198..1b5dadf 100644 --- a/QSharp/Events/QEventArgs.cs +++ b/QSharp/Events/QEventArgs.cs @@ -8,6 +8,12 @@ namespace QSharp { public QServer server { get; set; } } + + public class QServerUpdatedArgs : EventArgs + { + public QServer server { get; set; } + } + public class QWorkspacesUpdatedArgs : EventArgs { public List Workspaces { get; set; } @@ -51,7 +57,8 @@ namespace QSharp public class QCueUpdatedArgs : EventArgs { - + public string cueID { get; set; } + public JToken data { get; set; } } public class QCueListsUpdatedArgs : EventArgs @@ -66,6 +73,7 @@ namespace QSharp public class QCueListChangedPlaybackPositionArgs : EventArgs { + public string cueListID { get; set; } public string cueID { get; set; } } } diff --git a/QSharp/Events/QEventHandlers.cs b/QSharp/Events/QEventHandlers.cs index cf6157f..dd32b53 100644 --- a/QSharp/Events/QEventHandlers.cs +++ b/QSharp/Events/QEventHandlers.cs @@ -2,6 +2,7 @@ namespace QSharp { public delegate void QServerFoundHandler(object source, QServerFoundArgs args); + public delegate void QServerUpdatedHandler(object source, QServerUpdatedArgs args); public delegate void QWorkspacesUpdatedHandler(object source, QWorkspacesUpdatedArgs args); diff --git a/QSharp/QCue.cs b/QSharp/QCue.cs index b07bf6e..9cec61a 100644 --- a/QSharp/QCue.cs +++ b/QSharp/QCue.cs @@ -1,4 +1,5 @@ using Newtonsoft.Json.Linq; +using Serilog; using System; using System.Collections.Generic; using System.Drawing; @@ -21,7 +22,6 @@ namespace QSharp public bool ignoreUpdates; - public QCue() { init(); @@ -45,15 +45,15 @@ namespace QSharp /*JToken children = dict[QOSCKey.Cues]; if(children != null && children.Type == JTokenType.Array) { - Console.WriteLine($"[cue] new cue being created with {children.Count()} childCues"); + Log.Debug($"[cue] new cue being created with {children.Count()} childCues"); foreach (var aChildDict in children) { string uid = (string)aChildDict[QOSCKey.UID]; - //Console.WriteLine($"[cue] childDict with uid: {uid} being processed"); + //Log.Debug($"[cue] childDict with uid: {uid} being processed"); if (uid == null) continue; string name = dict[QOSCKey.UID].ToString(); - Console.WriteLine($"[cue] calling new cue from {name}"); + Log.Debug($"[cue] calling new cue from {name}"); //QCue child = new QCue(aChildDict, workspace); @@ -130,15 +130,9 @@ namespace QSharp //implemented: no #region Class Methods public string iconForType(string type) { throw new NotImplementedException(); } - public bool cueTypeIsAudio(string type) { throw new NotImplementedException(); } - public bool cueTypeIsVideo(string type) { throw new NotImplementedException(); } - public bool cueTypeIsGroup(string type) { throw new NotImplementedException(); } - public bool cueTypeIsCueList(string type) { throw new NotImplementedException(); } public List fadeModeTitles() { throw new NotImplementedException(); } #endregion - //KVC Compliance... do I need this? - //copied: yes //implemented: not all #region Convenience Accessors @@ -151,6 +145,11 @@ namespace QSharp return new List(); return cues; } + + set + { + setProperty(value, QOSCKey.Cues); + } } public string parentID { @@ -159,13 +158,29 @@ namespace QSharp return propertyForKey(QOSCKey.Parent).ToString(); } } - public string playbackPositionID() { throw new NotImplementedException(); } + public string playbackPositionID { + get { + if (propertyForKey(QOSCKey.PlaybackPositionId) == null) + return null; + else + return propertyForKey(QOSCKey.PlaybackPositionId).ToString(); + } + set + { + setProperty(value, QOSCKey.PlaybackPositionId); + } + } public string name { get { return propertyForKey(QOSCKey.Name).ToString(); } + + set + { + setProperty(value, QOSCKey.Name); + } } public string number { @@ -177,6 +192,10 @@ namespace QSharp else return ""; } + set + { + setProperty(value, QOSCKey.Number); + } } public string uid { @@ -184,6 +203,10 @@ namespace QSharp { return propertyForKey(QOSCKey.UID).ToString(); } + set + { + setProperty(value, QOSCKey.UID); + } } public string listName { @@ -191,6 +214,10 @@ namespace QSharp { return propertyForKey(QOSCKey.ListName).ToString(); } + set + { + setProperty(value, QOSCKey.ListName); + } } public string type { @@ -198,6 +225,10 @@ namespace QSharp { return propertyForKey(QOSCKey.Type).ToString(); } + set + { + setProperty(value, QOSCKey.Type); + } } public string notes { @@ -205,48 +236,109 @@ namespace QSharp { return propertyForKey(QOSCKey.Notes).ToString(); } + set + { + setProperty(value, QOSCKey.Notes); + } } + //Check the bool casting on these? public bool IsFlagged { get { return (bool)propertyForKey(QOSCKey.Flagged); } + set + { + setProperty(value, QOSCKey.Flagged); + } } public bool IsOverridden { get { - return (bool)propertyForKey(QOSCKey.IsOverridden); + if (workspace.connectedToQLab3 || workspace.isOlderThanVersion("4.2.0")) + return false; + + bool overridden = (bool)propertyForKey(QOSCKey.IsOverridden); + + if (overridden) + return true; + foreach (var cue in cues) + { + if (cue.IsOverridden) + return true; + } + return false; } } public bool IsBroken { get { - return (bool)propertyForKey(QOSCKey.IsBroken); + if ((bool)propertyForKey(QOSCKey.IsBroken)) + return true; + foreach(var cue in cues) + { + if (cue.IsBroken) + return true; + } + return false; } } public bool IsRunning { get { - return (bool)propertyForKey(QOSCKey.IsRunning); + bool running = (bool)propertyForKey(QOSCKey.IsRunning); + + if (running) + return true; + foreach(var cue in cues) + { + if (cue.IsRunning) + return true; + } + return false; } } public bool IsTailingOut { get { - return (bool)propertyForKey(QOSCKey.IsTailingOut); + if (workspace.connectedToQLab3) + return false; + + bool tailingOut = (bool)propertyForKey(QOSCKey.IsTailingOut); + + if (tailingOut) + return true; + foreach(var cue in cues) + { + if (cue.IsTailingOut) + return true; + } + return false; } } public bool IsPanicking { get { - return (bool)propertyForKey(QOSCKey.IsPanicking); + if (workspace.connectedToQLab3) + return false; + + bool panicking = (bool)propertyForKey(QOSCKey.IsPanicking); + + if (panicking) + return true; + foreach (var cue in cues) + { + if (cue.IsPanicking) + return true; + } + return false; } } public bool IsGroup @@ -291,7 +383,6 @@ namespace QSharp { get { - //TODO string nonEmptyName; if (name != null && !name.Equals("")) nonEmptyName = name; @@ -337,27 +428,55 @@ namespace QSharp return null; } } - public string surfaceName{ get { return propertyForKey("surfaceName").ToString(); } } + public string surfaceName{ + get { + object property = propertyForKey("surfaceName"); + if (property == null) + return null; + else + return propertyForKey("surfaceName").ToString(); + } + } public string patchName { get { - //TODO - return propertyForKey("patchDecription").ToString(); + object property = propertyForKey("patchDescription"); + if (property == null) + return null; + else + return propertyForKey("patchDecription").ToString(); } } - //TODO: THIS IS WHERE THINGS START TO GET TRICKY! - - //TODO add object color method - public string color { + public QColor color { get { + return new QColor(colorName); + } + + set + { + colorName = value.name; + } + } + + public string colorName + { + get + { object col = propertyForKey(QOSCKey.ColorName); if (col != null) return col.ToString(); else return "none"; - } + } + set + { + if (value == null) + setProperty("none", QOSCKey.ColorName); + else + setProperty(value, QOSCKey.ColorName); + } } //TODO add quaternion property and setter @@ -369,7 +488,6 @@ namespace QSharp { get { - //TODO add null check? return cueData.Keys.ToList(); } } @@ -407,27 +525,12 @@ namespace QSharp if (!this.ignoreUpdates) { - //TODO: update local with QLab + //TODO: Send Cue Needs Updated } } } #endregion - //Copied: Yes - //Implemented: Not all - #region Mutators - public void setCues(List cues) - { - - } - public void setName() { } - public void setNumber() { } - public void setUid() { } - public void setListName() { } - public void setType() { } - public void setFlagged() { } - #endregion - //Copied: Yes //Implemented: Not all #region Update Methods @@ -439,7 +542,6 @@ namespace QSharp JObject dictObj = (JObject)dict; foreach (var obj in dictObj) { - //Console.WriteLine($"[cue] found property {obj.Key} "); JToken value = obj.Value; if (obj.Key.Equals(QOSCKey.Cues)) { @@ -458,7 +560,7 @@ namespace QSharp if (!cueUpdated) return false; - Console.WriteLine($"[cue] {nonEmptyName} has been updated."); + Log.Debug($"[cue] <{nonEmptyName}> has been updated."); //something about notifying cueUpdated ? OnCueUpdated event handler maybe? return true; @@ -511,7 +613,6 @@ namespace QSharp index++; } return needsNotifyCueUpdated; - //TODO: TEST?! } #endregion @@ -523,13 +624,13 @@ namespace QSharp public List allChildCueUids() { List uids = new List(childCuesUIDMap.Keys); - //TODO return uids; } public QCue cueAtIndex(int index) { - //TODO - return new QCue(); + if (index < 0 || index >= cues.Count) + return null; + return cues[index]; } public QCue cueWithID(string uid) { @@ -537,7 +638,6 @@ namespace QSharp } public QCue cueWithID(string uid, bool includeChildren) { - //Console.WriteLine($"[cue] searching for cue with id: {uid} - includeChildren: {includeChildren}"); if(childCuesUIDMap.ContainsKey(uid)) return childCuesUIDMap[uid]; @@ -552,13 +652,22 @@ namespace QSharp if (childCue != null) return childCue; } - return null; } public QCue cueWithNumber(string number) { - //TODO - return new QCue(); + foreach(var cue in cues) + { + if (cue.number.Equals(number)) + return cue; + if (cue.IsGroup) + { + QCue childCue = cue.cueWithNumber(number); + if (childCue != null) + return childCue; + } + } + return null; } public object propertyForKey(string key) { @@ -567,7 +676,15 @@ namespace QSharp { return childCues; } - if (cueData.ContainsKey(key)) + else if (key.Equals("surfaceName")) + { + //TODO + } + else if (key.Equals("patchDescription")) + { + //TODO + } + else if (cueData.ContainsKey(key)) { return cueData[key]; } @@ -581,7 +698,6 @@ namespace QSharp public bool setProperty(object value, string key, bool osc) { - //TODO: object existingValue = null; if(cueData.ContainsKey(key)) existingValue = cueData[key]; @@ -589,21 +705,15 @@ namespace QSharp if(existingValue != null) { if (existingValue == value || existingValue.Equals(value)) + return false; + + + if (workspace.connectedToQLab3 && key.Equals(QOSCKey.Type) && existingValue.Equals(QCueType.CueList) && value.Equals(QCueType.Group)) { return false; } } - //Console.WriteLine($"[cue] attempting set property: {key} with type: {value.GetType()} and value: {value}"); - - - - if (workspace.connectedToQLab3 && key.Equals(QOSCKey.Type) && existingValue.Equals(QCueType.CueList) && value.Equals(QCueType.Group)) - { - return false; - } - - if (key.Equals(QOSCKey.Cues)) { if (value.GetType() != typeof(List)) @@ -641,6 +751,8 @@ namespace QSharp else cueData.Remove(key); + //TODO: Cuelistchangedplaybackpositionid? + } else { @@ -650,22 +762,46 @@ namespace QSharp cueData.Remove(key); } - //Console.WriteLine($"[cue] set property: {key} with type: {value.GetType()} and value: {value}"); - //check for type key and update icon? do I want to do this here? + + if (key.Equals(QOSCKey.Type)) + { + //TODO: something icon related? + } + + if (osc) + { + if (playbackPositionID == null && key.Equals(QOSCKey.PlaybackPositionId) && workspace.isOlderThanVersion("4.2.0")) + value = "none"; + workspace.updatePropertySend(this, value, key); + } return true; } #endregion - //TODO - public void sendAllPropertiesToQLab() { } + public void sendAllPropertiesToQLab() { + List allProperties = propertyKeys; + foreach(var key in allProperties) + { + object property = propertyForKey(key); + if (key.Equals(QOSCKey.Cues)) + { + foreach(var cue in (List)property) + { + cue.sendAllPropertiesToQLab(); + } + } + else + { + workspace.updatePropertySend(this, property, key); + } + } + + } //TODO: I don't think I'll do this one public void pullDownPropertyForKey(string key) { } - public void setPlaybackPositionID(string cueID, bool osc) { - setProperty(cueID, QOSCKey.PlaybackPositionId, osc); - } //Methods Copied: Yes //Methods Implemented: Yes @@ -685,17 +821,24 @@ namespace QSharp #region Printing - //this still needs fixing public void Print() { - Console.WriteLine($"{displayName} : {color}"); + Print(0); + } + public void Print(int level) + { + string indent = new string(' ', level*2); + + Log.Information($"{indent}\u00b7{displayName}"); if (IsGroup) { + level++; + if (cues.Count() > 0) - { + { foreach (var cue in cues) { - cue.Print(); + cue.Print(level); } } } diff --git a/QSharp/QMessage.cs b/QSharp/QMessage.cs index 65767fb..085b6d7 100644 --- a/QSharp/QMessage.cs +++ b/QSharp/QMessage.cs @@ -115,14 +115,6 @@ namespace QSharp public string replyAddress { get{ return IsReply ? address.Substring("/reply".Length) : address; } } - public string addressWithoutWorkspace - { - get - { - //TODO - return ""; - } - } public string[] AddressParts { get { return address.Split("/",StringSplitOptions.RemoveEmptyEntries); } } diff --git a/QSharp/QSharp.csproj b/QSharp/QSharp.csproj index 50d81f1..53411ee 100644 --- a/QSharp/QSharp.csproj +++ b/QSharp/QSharp.csproj @@ -2,9 +2,12 @@ netstandard2.1 + false + Joel Wetzell + diff --git a/QSharp/SharpOSC/TCPClient.cs b/QSharp/SharpOSC/TCPClient.cs index 6c7c27d..0cdb5f7 100644 --- a/QSharp/SharpOSC/TCPClient.cs +++ b/QSharp/SharpOSC/TCPClient.cs @@ -58,7 +58,7 @@ namespace SharpOSC } catch (Exception e) { - //Console.WriteLine(e.Message); + Console.WriteLine(e.Message); return false; } @@ -81,7 +81,10 @@ namespace SharpOSC { get { - return client.Connected; + if (client == null) + return false; + else + return client.Connected; } } @@ -98,9 +101,9 @@ namespace SharpOSC { Random random = new Random(); int num = random.Next(1000); - NetworkStream netStream = client.GetStream(); try { + NetworkStream netStream = client.GetStream(); netStream.ReadTimeout = 250; List responseData = new List(); if (netStream.CanRead) @@ -161,14 +164,16 @@ namespace SharpOSC public void Close() { - client.GetStream().Close(); - client.Close(); + if(client != null || !client.Connected) + { + client.GetStream().Close(); + client.Close(); + } } protected virtual void OnMessageReceived(OscMessage msg) { - if (MessageReceived != null) - MessageReceived(this, new MessageEventArgs() { Message = msg }); + MessageReceived?.Invoke(this, new MessageEventArgs() { Message = msg }); } } }