/// <summary> /// Displays all previously-opened windows and other windows /// that must be shown at startup /// </summary> public void Initialize() { logger.Debug("Initializing"); logger.Debug("Registering hotkey commands"); HotkeyCommands.TogglePriceTrackerCommand.RegisterCommand(new DelegateCommand(this.TogglePriceTracker)); Threading.BeginInvokeOnUI(() => { if (Properties.Settings.Default.IsTPCalculatorOpen && this.CanDisplayTPCalculator()) { this.DisplayTPCalculator(); } if (Properties.Settings.Default.IsPriceTrackerOpen && this.CanDisplayPriceTracker()) { this.DisplayPriceTracker(); } if (this.CanDisplayPriceNotificationsWindow()) { this.DisplayPriceNotificationsWindow(); } }); }
/// <summary> /// Handles the talk status changed event /// </summary> private void TeamspeakService_TalkStatusChanged(object sender, TS3.Data.TalkStatusEventArgs e) { var speechNotification = new TSNotificationViewModel(e.ClientID, e.ClientName, TSNotificationType.Speech); var existingNotification = this.Notifications.FirstOrDefault(notification => notification.Equals(speechNotification)); switch (e.Status) { case TS3.Data.Enums.TalkStatus.TalkStarted: // Add to our collection of speaking users if it's not already there if (existingNotification == null) { Threading.BeginInvokeOnUI(() => this.Notifications.Add(speechNotification)); } break; case TS3.Data.Enums.TalkStatus.TalkStopped: // Remove from our collection of speaking users if (existingNotification != null) { Threading.BeginInvokeOnUI(() => this.Notifications.Remove(existingNotification)); } break; default: break; } }
/// <summary> /// Initialized all event zone/map names /// </summary> private void InitializeEventZoneNames() { this.zoneService.Initialize(); foreach (var worldEvent in this.eventsService.WorldBossEventTimeTable.WorldEvents) { logger.Debug("Loading localized zone location for {0}", worldEvent.ID); var name = this.zoneService.GetZoneName(worldEvent.MapID); Threading.BeginInvokeOnUI(() => { worldEvent.MapName = name; }); } foreach (var metaEvent in this.eventsService.MetaEventsTable.MetaEvents) { logger.Debug("Loading localized zone location for {0}", metaEvent.MapID); var name = this.zoneService.GetZoneName(metaEvent.MapID); Threading.BeginInvokeOnUI(() => { metaEvent.MapName = name; }); foreach (var metaEventStage in metaEvent.Stages) { var stageName = this.zoneService.GetZoneName(metaEventStage.MapID); Threading.BeginInvokeOnUI(() => { metaEventStage.MapName = stageName; }); } } }
/// <summary> /// Adds an objective to the notifications collection, and then removes the objective 10 seconds later /// </summary> private void DisplayNotification(PriceNotificationViewModel priceNotification) { const int SLEEP_TIME = 250; if (!this.PriceNotifications.Contains(priceNotification)) { Task.Factory.StartNew(() => { logger.Info("Displaying notification for \"{0}\" - {1}", priceNotification.ItemName, priceNotification.NotificationType); Threading.BeginInvokeOnUI(() => this.PriceNotifications.Add(priceNotification)); if (this.UserData.NotificationDuration > 0) { // For X seconds, loop and sleep for (int i = 0; i < (this.UserData.NotificationDuration * 1000 / SLEEP_TIME); i++) { System.Threading.Thread.Sleep(SLEEP_TIME); } logger.Debug("Removing notification for \"{0}\" - {1}", priceNotification.ItemName, priceNotification.NotificationType); // TODO: I hate having this here, but due to a limitation in WPF, there's no reasonable way around this at this time // This makes it so that the notifications can fade out before they are removed from the notification window Threading.BeginInvokeOnUI(() => priceNotification.IsRemovingNotification = true); System.Threading.Thread.Sleep(SLEEP_TIME); Threading.BeginInvokeOnUI(() => { this.PriceNotifications.Remove(priceNotification); priceNotification.IsRemovingNotification = false; }); } }); } }
/// <summary> /// Displays all previously-opened windows and other windows /// that must be shown at startup /// </summary> public void Initialize() { logger.Debug("Initializing"); logger.Debug("Registering hotkey commands"); HotkeyCommands.ToggleWorldBossTimersCommand.RegisterCommand(new DelegateCommand(this.ToggleWorldBossTimers)); HotkeyCommands.ToggleMetaEventTimersCommand.RegisterCommand(new DelegateCommand(this.ToggleMetaEventTimers)); Threading.BeginInvokeOnUI(() => { if (Properties.Settings.Default.IsEventTrackerOpen && this.CanDisplayWorldBossTimers()) { this.DisplayWorldBossTimers(); } if (Properties.Settings.Default.IsMetaEventsTimersOpen && this.CanDisplayMetaEventTimers()) { this.DisplayMetaEventTimers(); } if (this.CanDisplayEventNotificationsWindow()) { this.DisplayEventNotificationsWindow(); } }); }
/// <summary> /// Initializes the localized names of events /// </summary> private void InitializeLocalizedEventNames() { lock (refreshTimerLock) { logger.Debug("Initializing World Boss events"); Threading.BeginInvokeOnUI(() => { foreach (var worldEvent in this.eventsService.WorldBossEventTimeTable.WorldEvents) { logger.Debug("Loading localized name for {0}", worldEvent.ID); worldEvent.Name = this.eventsService.GetLocalizedName(worldEvent.ID); } }); logger.Debug("Initializing Meta Events"); Threading.BeginInvokeOnUI(() => { foreach (var metaEvent in this.eventsService.MetaEventsTable.MetaEvents) { logger.Debug("Loading localized name for {0}", metaEvent.ID); metaEvent.Name = this.eventsService.GetLocalizedName(metaEvent.ID); foreach (var metaEventStage in metaEvent.Stages) { metaEventStage.Name = this.eventsService.GetLocalizedName(metaEventStage.ID); } } }); } }
/// <summary> /// Primary method for refreshing the current zone /// </summary> /// <param name="state"></param> private void RefreshZone(object state = null) { lock (zoneRefreshTimerLock) { if (this.isStopped) { return; // Immediately return if we are supposed to be stopped } Threading.BeginInvokeOnUI(() => this.ValidMapID = this.playerService.HasValidMapId); if (this.systemService.IsGw2Running && this.playerService.HasValidMapId) { // Check to see if the MapId or Character Name has changed, if so, we need to clear our zone items and add the new ones if (this.CurrentMapID != this.playerService.MapId || this.CharacterName != this.playerService.CharacterName) { logger.Info("Map/Character change detected, resetting zone events. New MapID = {0} | Character Name = {1}", this.playerService.MapId, this.CharacterName); this.CurrentMapID = this.playerService.MapId; this.CharacterName = this.playerService.CharacterName; var continent = this.zoneService.GetContinentByMap(this.CurrentMapID); var map = this.zoneService.GetMap(this.CurrentMapID); Threading.BeginInvokeOnUI(() => { this.ActiveContinent = continent; this.ActiveMap = map; }); var zoneItems = this.zoneService.GetZoneItems(this.playerService.MapId); lock (zoneItemsLock) { Threading.InvokeOnUI(() => { this.ZoneItems.Clear(); this.playerInProximityCounters.Clear(); foreach (var item in zoneItems) { // Ignore dungeons for now if (item.Type != API.Data.Enums.ZoneItemType.Dungeon) { this.ZoneItems.Add(new ZoneItemViewModel(item, this.playerService, this.UserData)); this.playerInProximityCounters.Add(item.ID, 0); } } }); } // Update the current zone name var newZoneName = this.zoneService.GetZoneName(this.CurrentMapID); if (this.zoneNameObject.ZoneName != newZoneName) { Threading.InvokeOnUI(() => this.zoneNameObject.ZoneName = newZoneName); } logger.Info("New Zone Name = {0}", newZoneName); } } this.zoneRefreshTimer.Change(this.ZoneRefreshInterval, Timeout.Infinite); } }
/// <summary> /// Initializes the store of zone items with data /// </summary> public void InitializeStore() { this.zoneService.Initialize(); var continents = this.zoneService.GetContinents(); foreach (var continent in continents) { this.Data.Add(continent.Id, new ContinentZoneItems(continent.Id)); } foreach (var continent in this.Data) { var zoneItems = this.zoneService.GetZoneItemsByContinent(continent.Key); Threading.InvokeOnUI(() => continent.Value.Clear()); foreach (var entity in zoneItems) { var zoneItem = new ZoneItemViewModel(entity, this.playerService, this.zoneUserData); Threading.InvokeOnUI(() => { continent.Value.Add(zoneItem); }); } } Threading.BeginInvokeOnUI(() => this.RaiseDataLoadedEvent()); }
/// <summary> /// Displays a pop-up notification near the tray icon /// </summary> /// <param name="title">Title of the notification</param> /// <param name="text">Main text for the notification</param> /// <param name="messageType">Type of message/notification</param> public void DisplayNotification(string title, string text, TrayInfoMessageType messageType) { logger.Info("Displaying notification bubble. Title: \"{0}\" Text: \"{1}\" MessageType: {2}", title, text, messageType); BalloonIcon icon; switch (messageType) { case TrayInfoMessageType.None: icon = BalloonIcon.None; break; case TrayInfoMessageType.Info: icon = BalloonIcon.Info; break; case TrayInfoMessageType.Warning: icon = BalloonIcon.Warning; break; case TrayInfoMessageType.Error: icon = BalloonIcon.Error; break; default: icon = BalloonIcon.None; break; } Threading.BeginInvokeOnUI(() => this.taskbarIcon.ShowBalloonTip(title, text, icon)); }
/// <summary> /// Handles the text message received event /// </summary> private void TeamspeakService_TextMessageReceived(object sender, TS3.Data.TextMessageEventArgs e) { Threading.BeginInvokeOnUI(() => { this.ChatMessages.Insert(0, new ChatMsgViewModel(DateTime.Now, e.ClientName, e.Message)); }); }
/// <summary> /// Handler for the Channel Removed event /// </summary> private void TeamspeakService_ChannelRemoved(object sender, TS3.Data.ChannelEventArgs e) { Threading.BeginInvokeOnUI(() => { var removedChannel = new ChannelViewModel(e.Channel, this.TeamspeakService); if (removedChannel.ParentID != 0) { // This has a parent channel - find it var parentChannel = this.FindParentChannel(this.Channels, removedChannel); if (parentChannel != null) { var toRemove = parentChannel.Subchannels.FirstOrDefault(channel => channel.ID == removedChannel.ID); parentChannel.Subchannels.Remove(toRemove); } } else { // No parent var toRemove = this.Channels.FirstOrDefault(channel => channel.ID == removedChannel.ID); this.Channels.Remove(toRemove); } this.OnPropertyChanged(() => this.AreChannelsLoaded); }); }
/// <summary> /// Refreshes the score of all worlds /// </summary> private void RefreshWorldScores() { foreach (var team in this.Worlds) { var score = this.wvwService.GetWorldScore(team.WorldId); Threading.BeginInvokeOnUI(() => team.Score = score); } }
/// <summary> /// Initializes the store of zone items with data /// </summary> public void InitializeStore() { this.zoneService.Initialize(); foreach (var continent in this.Data) { var zoneItems = this.zoneService.GetZoneItemsByContinent(continent.Key); Threading.BeginInvokeOnUI(() => { continent.Value.Dungeons.Clear(); continent.Value.HeartQuests.Clear(); continent.Value.HeroPoints.Clear(); continent.Value.POIs.Clear(); continent.Value.Vistas.Clear(); continent.Value.Waypoints.Clear(); }); foreach (var entity in zoneItems) { var vm = new ZoneItemViewModel(entity, this.playerService, this.zoneUserData); Threading.BeginInvokeOnUI(() => { switch (entity.Type) { case API.Data.Enums.ZoneItemType.Dungeon: continent.Value.Dungeons.Add(vm); break; case API.Data.Enums.ZoneItemType.HeartQuest: continent.Value.HeartQuests.Add(vm); break; case API.Data.Enums.ZoneItemType.HeroPoint: continent.Value.HeroPoints.Add(vm); break; case API.Data.Enums.ZoneItemType.PointOfInterest: continent.Value.POIs.Add(vm); break; case API.Data.Enums.ZoneItemType.Vista: continent.Value.Vistas.Add(vm); break; case API.Data.Enums.ZoneItemType.Waypoint: continent.Value.Waypoints.Add(vm); break; default: break; } }); } } }
/// <summary> /// Default constructor /// </summary> public OverlayWindow() { this.Owner = OwnerWindow; this.Loaded += OverlayWindowBase_Loaded; if (ProcessMonitor != null) { ProcessMonitor.GW2Focused += (o, e) => Threading.BeginInvokeOnUI(() => User32.SetTopMost(this, true)); } }
/// <summary> /// Loops through the collection of tasks and refreshs their distance/angles /// </summary> private void RefreshTaskDistancesAngles() { const int ABOVE_BELOW_THRESHOLD = 150; this.CurrentMapID = this.playerService.MapId; var playerPos = this.playerService.PlayerPosition; var cameraDir = this.playerService.CameraDirection; if (playerPos != null && cameraDir != null) { var playerMapPosition = CalcUtil.ConvertToMapPosition(playerPos); var cameraDirectionMapPosition = CalcUtil.ConvertToMapPosition(cameraDir); foreach (var ptask in this.PlayerTasks.Where(pt => pt.Task.Location != null && pt.Task.MapID == this.CurrentMapID)) { var taskMapPosition = CalcUtil.ConvertToMapPosition(ptask.Task.Location); // Update distances and angles var newDistance = Math.Round(CalcUtil.CalculateDistance(playerMapPosition, taskMapPosition, this.UserData.DistanceUnits)); var newAngle = CalcUtil.CalculateAngle(CalcUtil.Vector.CreateVector(playerMapPosition, taskMapPosition), CalcUtil.Vector.CreateVector(new API.Data.Entities.Point(0, 0), cameraDirectionMapPosition)); bool isAbove = (ptask.Task.Location.Z > 0) && (taskMapPosition.Z - playerMapPosition.Z > ABOVE_BELOW_THRESHOLD); bool isBelow = (ptask.Task.Location.Z > 0) && (playerMapPosition.Z - taskMapPosition.Z > ABOVE_BELOW_THRESHOLD); Threading.BeginInvokeOnUI(() => { ptask.IsPlayerOnMap = true; ptask.DistanceFromPlayer = newDistance; ptask.DirectionFromPlayer = newAngle; ptask.IsAbovePlayer = isAbove; ptask.IsBelowPlayer = isBelow; }); // Check for auto-completion detection if (ptask.Task.AutoComplete && CalcUtil.CalculateDistance(playerMapPosition, taskMapPosition, API.Data.Enums.Units.Feet) < 10) { Threading.BeginInvokeOnUI(() => { ptask.IsCompleted = true; }); } } // Player is not on the map foreach (var ptask in this.PlayerTasks.Where(pt => pt.Task.MapID != this.CurrentMapID)) { Threading.BeginInvokeOnUI(() => { ptask.IsPlayerOnMap = false; }); } } }
/// <summary> /// Refreshes all events within the events collection /// This is the primary function of the EventTrackerController /// </summary> private void RefreshEvents(object state = null) { lock (refreshTimerLock) { // Refresh the state of all world events foreach (var worldEvent in this.WorldEvents) { var newState = this.eventsService.GetState(worldEvent.EventModel); Threading.BeginInvokeOnUI(() => worldEvent.State = newState); if (newState == API.Data.Enums.EventState.Active) { var timeSinceActive = this.eventsService.GetTimeSinceActive(worldEvent.EventModel); Threading.BeginInvokeOnUI(() => worldEvent.TimerValue = timeSinceActive.Negate()); } else { var timeUntilActive = this.eventsService.GetTimeUntilActive(worldEvent.EventModel); Threading.BeginInvokeOnUI(() => worldEvent.TimerValue = timeUntilActive); // Check to see if we need to display a notification for this event if (timeUntilActive.CompareTo(TimeSpan.FromMinutes(1)) < 0) { if (!worldEvent.IsNotificationShown) { worldEvent.IsNotificationShown = true; this.DisplayEventNotification(worldEvent); } } else { // Reset the IsNotificationShown state worldEvent.IsNotificationShown = false; } } } // Refresh state of daily treasures if (DateTime.UtcNow.Date.CompareTo(this.userSettings.LastResetDateTime.Date) != 0) { logger.Info("Resetting daily treasures state"); this.userSettings.LastResetDateTime = DateTime.UtcNow; Threading.BeginInvokeOnUI(() => { foreach (var worldEvent in WorldEvents) { worldEvent.IsTreasureObtained = false; } }); } this.eventRefreshTimer.Change(this.EventRefreshInterval, Timeout.Infinite); } }
/// <summary> /// Handles the rebuild complete action /// </summary> private void HandleComplete() { logger.Debug("Rebuild completed"); Threading.BeginInvokeOnUI(() => { this.Progress = this.TotalRequests; this.IsComplete = true; }); // Have the service reload the new database this.commerceService.ReloadDatabase(); }
/// <summary> /// Refreshes all cycles within the cycles collection /// This is the primary function of the CycleTrackerController /// </summary> private void RefreshCycles(object state = null) { lock (refreshTimerLock) { if (this.isStopped) { return; // Immediately return if we are supposed to be stopped } // Refresh the state of all world cycles foreach (var cycle in this.Cycles) { var newState = this.cyclesService.GetState(cycle.CycleModel); Threading.BeginInvokeOnUI(() => cycle.State = newState); var timeUntilActive = this.cyclesService.GetTimeUntilActive(cycle.CycleModel); var timeSinceActive = this.cyclesService.GetTimeSinceActive(cycle.CycleModel); Threading.BeginInvokeOnUI(() => cycle.TimeSinceActive = timeSinceActive); if (newState == API.Data.Enums.EventState.Active) { Threading.BeginInvokeOnUI(() => cycle.TimerValue = timeSinceActive.Negate()); } else { Threading.BeginInvokeOnUI(() => cycle.TimerValue = timeUntilActive); // Check to see if we need to display a notification for this cycle var ens = this.UserData.NotificationSettings.FirstOrDefault(ns => ns.CycleID == cycle.CycleId); if (ens != null) { if (ens.IsNotificationEnabled && timeUntilActive.CompareTo(ens.NotificationInterval) < 0) { if (!cycle.IsNotificationShown) { cycle.IsNotificationShown = true; this.DisplayCycleNotification(cycle); } } else { // Reset the IsNotificationShown state cycle.IsNotificationShown = false; } } } } this.cycleRefreshTimer.Change(this.CycleRefreshInterval, Timeout.Infinite); } }
private void InitializeCycleZoneNames() { this.zoneService.Initialize(); foreach (var cycle in this.cyclesService.TimeTable.Cycles) { logger.Debug("Loading localized zone location for {0}", cycle.ID); var name = this.zoneService.GetZoneName(cycle.WorldMapID); Threading.BeginInvokeOnUI(() => { cycle.MapName = name; }); } }
/// <summary> /// Initializes localized map names for each dungeon /// </summary> private void InitializeDungeonZoneNames() { logger.Debug("Initializing zone names for dungeons"); this.zoneService.Initialize(); foreach (var dungeon in this.dungeonsService.DungeonsTable.Dungeons) { var name = this.zoneService.GetZoneName(dungeon.WorldMapID); Threading.BeginInvokeOnUI(() => { dungeon.MapName = name; }); } }
/// <summary> /// Default constructor /// </summary> /// <param name="userData">The dungeons user data</param> public DungeonTimerViewModel(DungeonsUserData userData) { this.UserData = userData; this.stopWatch = new Stopwatch(); this.timer = new Timer(TimerInterval.TotalMilliseconds); this.timer.AutoReset = true; this.timer.Elapsed += (o, e) => Threading.BeginInvokeOnUI(() => this.TimerValue = this.stopWatch.Elapsed); this.StartTimerCommand = new DelegateCommand(this.StartTimer); this.PauseTimerCommand = new DelegateCommand(this.PauseTimer); this.StopTimerCommand = new DelegateCommand(this.StopTimer); this.OpenSettingsCommand = new DelegateCommand(() => Commands.OpenDungeonSettingsCommand.Execute(null)); }
private void InitializeWorldEventZoneNames() { this.zoneService.Initialize(); foreach (var worldEvent in this.eventsService.EventTimeTable.WorldEvents) { logger.Debug("Loading localized zone location for {0}", worldEvent.ID); var name = this.zoneService.GetZoneName(worldEvent.MapID); Threading.BeginInvokeOnUI(() => { worldEvent.MapName = name; }); } }
/// <summary> /// Handler for the Channel Updated event /// </summary> private void TeamspeakService_ChannelUpdated(object sender, TS3.Data.ChannelEventArgs e) { Threading.BeginInvokeOnUI(() => { // Find the matching existing channel ChannelViewModel existingChannel = this.FindChannel(this.Channels, e.Channel.ID); if (existingChannel == null) { // This shouldn't happen, but if it does, don't crash, just treat it as a "channel added" this.TeamspeakService_ChannelAdded(sender, e); return; } existingChannel.Name = e.Channel.Name; existingChannel.OrderIndex = e.Channel.Order; existingChannel.ClientsCount = e.Channel.ClientsCount; // Check to see if the parent ID has changed. If so, update it and move the channel if (existingChannel.ParentID != e.Channel.ParentID) { // Find the existing parent ChannelViewModel existingParent = this.FindParentChannel(this.Channels, existingChannel); if (existingParent != null) { // Remove it from the parent existingParent.Subchannels.Remove(existingChannel); } // Update the parent ID existingChannel.ParentID = e.Channel.ParentID; // Find the new parent ChannelViewModel newParent = this.FindParentChannel(this.Channels, existingChannel); if (newParent != null) { // Add it to the parent newParent.Subchannels.Add(existingChannel); } else { // Orphan... this.orphanChannels.Add(existingChannel); } } this.OnPropertyChanged(() => this.AreChannelsLoaded); }); }
/// <summary> /// Performs the appropriate actions (like resetting dungeons) for /// the daily reset /// </summary> private void OnDailyReset() { logger.Info("Resetting path completions state"); this.userData.LastResetDateTime = DateTime.UtcNow; Threading.BeginInvokeOnUI(() => { foreach (var dungeon in this.Dungeons) { foreach (var path in dungeon.Paths) { path.IsCompleted = false; } } }); }
/// <summary> /// Displays all previously-opened windows and other windows /// that must be shown at startup /// </summary> public void Initialize() { logger.Debug("Initializing"); logger.Debug("Registering hotkey commands"); HotkeyCommands.ToggleZoneAssistantCommand.RegisterCommand(new DelegateCommand(this.ToggleZoneCompletionAssistant)); Threading.BeginInvokeOnUI(() => { if (Properties.Settings.Default.IsZoneAssistantOpen && this.CanDisplayZoneCompletionAssistant()) { this.DisplayZoneCompletionAssistant(); } }); }
/// <summary> /// Displays all previously-opened windows and other windows /// that must be shown at startup /// </summary> public void Initialize() { logger.Debug("Initializing"); logger.Debug("Registering hotkey commands"); HotkeyCommands.ToggleTaskTrackerCommand.RegisterCommand(new DelegateCommand(this.ToggleTaskTracker)); Threading.BeginInvokeOnUI(() => { if (Properties.Settings.Default.IsTaskTrackerOpen && this.CanDisplayTaskTracker()) { this.DisplayTaskTracker(); } }); }
/// <summary> /// Performs actions on the daily reset /// </summary> private void OnDailyReset() { logger.Info("Daily reset detected"); foreach (var pt in this.PlayerTasks) { if (pt.Task.IsCompletable && pt.Task.IsDailyReset) { Threading.BeginInvokeOnUI(() => pt.IsCompleted = false); pt.Task.IsAccountCompleted = false; foreach (var charCompletion in pt.Task.CharacterCompletions.Keys) { pt.Task.CharacterCompletions[charCompletion] = false; } } } }
/// <summary> /// Updates the map name property /// </summary> private void RefreshMapName() { var name = this.zoneService.GetZoneName(this.Task.MapID); Threading.BeginInvokeOnUI(() => { if (this.Task.MapID != -1) { this.MapName = name; } else { this.MapName = string.Empty; } }); }
public WvWSettingsViewModel(WvWUserData userData, IWvWService wvwService) { this.UserData = userData; this.PossibleWorlds = new ObservableCollection <World>(); Task.Factory.StartNew(() => { if (wvwService.Worlds == null || wvwService.Worlds.Count == 0) { wvwService.LoadData(); } foreach (var world in wvwService.Worlds.OrderBy(wld => wld.Name)) { Threading.BeginInvokeOnUI(() => this.PossibleWorlds.Add(world)); } }); }
/// <summary> /// Handler for the Channel Added event /// </summary> private void TeamspeakService_ChannelAdded(object sender, TS3.Data.ChannelEventArgs e) { Threading.BeginInvokeOnUI(() => { if (e.Channel.IsSpacer) { return; // Totally ignore spacers } var newChannel = new ChannelViewModel(e.Channel, this.TeamspeakService); // Check if we have any orphans that are a subchannel of this new channel var matchingOrphans = this.orphanChannels.Where(c => c.ParentID == newChannel.ID); foreach (var orphan in matchingOrphans) { newChannel.Subchannels.Add(orphan); } this.orphanChannels.RemoveAll(c => c.ParentID == newChannel.ID); if (newChannel.ParentID != 0) { // This has a parent channel - find it var parentChannel = this.FindParentChannel(this.Channels, newChannel); if (parentChannel != null) { parentChannel.Subchannels.Add(newChannel); } else { // This is an orphan channel... add it to our orphan list for now this.orphanChannels.Add(newChannel); } } else { // No parent this.Channels.Insert(0, newChannel); } this.OnPropertyChanged(() => this.AreChannelsLoaded); }); }