public static void Start(ClientOrchestrator orchestrator, TriviaViewModel viewModel, CommandBindingCollection bindings, TriviaClient window) { Contract.Requires(orchestrator != null); bindings.Add(new CommandBinding(TriviaCommands.WrongAnswer, (object source, ExecutedRoutedEventArgs e) => { AnswerViewModel a = (AnswerViewModel)e.Parameter; a.WhoCalledIn = TriviaClient.PlayerName; orchestrator.SendUpdateAnswerRequest(a.CreateModel(), false); })); bindings.Add(new CommandBinding(TriviaCommands.CorrectAnswer, (object source, ExecutedRoutedEventArgs e) => { CorrectAnswerData cad = (CorrectAnswerData)e.Parameter; AnswerViewModel a = cad.Answer; a.WhoCalledIn = TriviaClient.PlayerName; orchestrator.SendUpdateAnswerRequest(a.CreateModel(), false); QuestionChanges questionChanges = new QuestionChanges(new QuestionId(a.Hour, a.Number)) { Open = false, Correct = true, Answer = a.Text, PhoneBankOperator = cad.PhoneBankOperator }; // Update the correct answer orchestrator.SendUpdateQuestionRequest(questionChanges); })); bindings.Add(new CommandBinding(TriviaCommands.PartialAnswer, (object source, ExecutedRoutedEventArgs e) => { AnswerViewModel a = (AnswerViewModel)e.Parameter; a.WhoCalledIn = TriviaClient.PlayerName; a.Partial = true; orchestrator.SendUpdateAnswerRequest(a.CreateModel(), true); // Flag as a partial })); bindings.Add(new CommandBinding(TriviaCommands.EditAnswer, (object source, ExecutedRoutedEventArgs e) => { // An edited answer was already modified by the command sender, so just update it to the server // Note: this is usually done by fixing the text of a partial answer AnswerViewModel a = (AnswerViewModel)e.Parameter; orchestrator.SendUpdateAnswerRequest(a.CreateModel(), false); // Flag not "partial changed". We don't know it to be the case, but that's the behavior we want })); bindings.Add(new CommandBinding(TriviaCommands.ShowSubmitAnswerDialog, (object source, ExecutedRoutedEventArgs e) => { QuestionViewModel question = (QuestionViewModel)e.Parameter; SubmitAnswerDialog submitAnswerDialog = new SubmitAnswerDialog(question); submitAnswerDialog.Owner = window; bool? submitted = submitAnswerDialog.ShowDialog(); if (submitted == true) { // 2014 - don't think this is true, but preserving the comment... Must execute the command against "this" current window so the commandbindings are hit TriviaCommands.SubmitAnswer.Execute(submitAnswerDialog.AnswerSubmitted, null); // Using "null" as the target means we avoid weird bugs where the event actually doesn't fire } })); bindings.Add(new CommandBinding(TriviaCommands.SubmitAnswer, (object source, ExecutedRoutedEventArgs e) => { Answer a = (Answer)e.Parameter; QuestionViewModel q; if (!(viewModel.TryGetQuestion(a, out q))) { throw new InvalidOperationException("Couldn't get question for answer submitted, that's really odd..."); } if (ContestConfiguration.PreventDuplicateAnswerSubmission && CommandHandler.ShouldStopAnswerSubmission(a, q, window)) { return; } orchestrator.SendSubmitAnswerRequest(a); // Inform the server // If you're submitting an answer, it's likely to assume you're researching the question if (!(q.IsBeingResearched)) { q.IsBeingResearched = true; TriviaCommands.ResearcherChanged.Execute(q, null); } })); bindings.Add(new CommandBinding(TriviaCommands.OpenQuestion, (object source, ExecutedRoutedEventArgs e) => { QuestionId questionId = (QuestionId)e.Parameter; orchestrator.SendOpenQuestionRequest(questionId.Hour, questionId.Number); })); bindings.Add(new CommandBinding(TriviaCommands.EditQuestion, (object source, ExecutedRoutedEventArgs e) => { QuestionViewModel question = (QuestionViewModel)e.Parameter; EditingQuestionData data = new EditingQuestionData() { HourNumber = question.Identifier.Hour, QuestionNumber = question.Identifier.Number, WhoEditing = TriviaClient.PlayerName }; // Tell everyone else that "I'm editing this question, so back off!" orchestrator.SendEditingQuestionMessage(data); EditQuestionDialog dialog = new EditQuestionDialog(orchestrator, question, false); dialog.Owner = window; bool? result = dialog.ShowDialog(); if (!(result.HasValue) || result.Value == false) { data.WhoEditing = null; // Signal that the edit was cancelled orchestrator.SendEditingQuestionMessage(data); // If the user made a change to the question (in-memory viewmodel), should refresh from the server (it's just the easiest thing to do) if (dialog.GetChangesMade().Changes != QuestionChanges.Change.None) { orchestrator.SendGetCompleteHourDataRequest(question.Identifier.Hour); } return; // Cancelled / Closed without saving } QuestionChanges changes = dialog.GetChangesMade(); if (changes.Changes == QuestionChanges.Change.None) { return; // No changes actually were made even though the user clicked the Save button } orchestrator.SendUpdateQuestionRequest(changes); })); bindings.Add(new CommandBinding(TriviaCommands.UpdateQuestionPoints, (object source, ExecutedRoutedEventArgs e) => { // Only Beerpigs use this QuestionChanges changes = (QuestionChanges)e.Parameter; orchestrator.SendUpdateQuestionRequest(changes); })); bindings.Add(new CommandBinding(TriviaCommands.PlayAudioTrivia, (object source, ExecutedRoutedEventArgs e) => { string audioTriviaFileName = (string)e.Parameter; CommandHandler.PlayAudioTrivia(audioTriviaFileName, window); })); bindings.Add(new CommandBinding(TriviaCommands.ShowAddNoteDialog, (object source, ExecutedRoutedEventArgs e) => { QuestionViewModel question = (QuestionViewModel)e.Parameter; Note note = new Note(); note.Submitter = TriviaClient.PlayerName; note.HourNumber = question.Identifier.Hour; note.QuestionNumber = question.Identifier.Number; AddNoteDialog dialog = new AddNoteDialog(note); dialog.Owner = window; bool? result = dialog.ShowDialog(); if (!(result.HasValue) || result.Value == false) { return; // Cancelled / Closed without saving } note.Text = note.Text.Trim(); // Trim it. // Save the note orchestrator.SendAddNoteMessage(note); // If you're adding a note, it's likely to assume you're researching the question if (!(question.IsBeingResearched)) { question.IsBeingResearched = true; TriviaCommands.ResearcherChanged.Execute(question, null); } })); bindings.Add(new CommandBinding(TriviaCommands.CorrectAnswerWithoutSubmission, (object source, ExecutedRoutedEventArgs e) => { QuestionViewModel question = (QuestionViewModel)e.Parameter; Answer answer = new Answer(); answer.Hour = question.Identifier.Hour; answer.Number = question.Identifier.Number; answer.WhoCalledIn = TriviaClient.PlayerName; AnswerViewModel answerViewModel = new AnswerViewModel(answer); // Show the "blank" dialog CorrectAnswerDialog dialog = new CorrectAnswerDialog(answerViewModel); dialog.Owner = window; bool? result = dialog.ShowDialog(); if (!(result.HasValue) || result.Value == false) { return; // Cancelled / Closed without saving } QuestionChanges changes = new QuestionChanges(question.Identifier) { Open = false, Correct = true, Answer = dialog.Answer.Text, PhoneBankOperator = dialog.PhoneBankOperator }; // Update the correct answer orchestrator.SendUpdateQuestionRequest(changes); })); bindings.Add(new CommandBinding(TriviaCommands.ResearcherChanged, (object source, ExecutedRoutedEventArgs e) => { QuestionViewModel questionThatChanged = (QuestionViewModel)e.Parameter; if (questionThatChanged.IsBeingResearched == false) { // User said they're not researching this question (and by extension, aren't researching *anything* anymore) orchestrator.SendResearcherChangedRequest( new ResearcherChange { HourNumber = 0, // Signaling that current user is researching nothing QuestionNumber = 0, // Signaling that current user is researching nothing Name = TriviaClient.PlayerName }); } else { orchestrator.SendResearcherChangedRequest( new ResearcherChange { HourNumber = questionThatChanged.Identifier.Hour, QuestionNumber = questionThatChanged.Identifier.Number, Name = TriviaClient.PlayerName }); } })); bindings.Add(new CommandBinding(TriviaCommands.ShowEditPartialsDialog, (object source, ExecutedRoutedEventArgs e) => { ListCollectionView partials = (ListCollectionView)e.Parameter; EditPartialsDialog dialog = new EditPartialsDialog(); dialog.DataContext = partials; dialog.Owner = window; dialog.ShowDialog(); })); bindings.Add(new CommandBinding(TriviaCommands.ShowCombinePartialsDialog, (object source, ExecutedRoutedEventArgs e) => { QuestionViewModel question = (QuestionViewModel)e.Parameter; string combinationAnswer = string.Join("\n", question.PartialAnswers.Cast<AnswerViewModel>().Select(avm => avm.Text)); SubmitAnswerDialog submitAnswerDialog = new SubmitAnswerDialog(question, combinationAnswer); submitAnswerDialog.Owner = window; bool? submitted = submitAnswerDialog.ShowDialog(); if (submitted == true) { // 2014, don't think this is true, ubt pres3erving the original comment..... Must execute the command against "this" current window so the commandbindings are hit TriviaCommands.SubmitAnswer.Execute(submitAnswerDialog.AnswerSubmitted, null); // Using "null" as the target means we avoid weird bugs where the event actually doesn't fire } })); bindings.Add(new CommandBinding(TriviaCommands.ShowAddLinkDialog, (object source, ExecutedRoutedEventArgs e) => { AddLinkDialog dialog = new AddLinkDialog(); dialog.Owner = window; bool? result = dialog.ShowDialog(); if (!(result.HasValue) || result.Value == false) { return; // Cancelled / Closed without saving } dialog.Link.Description = dialog.Link.Description.Trim(); dialog.Link.Url = dialog.Link.Url.Trim(); // Save the link orchestrator.SendAddLinkRequest(dialog.Link); })); bindings.Add(new CommandBinding(TriviaCommands.DeleteNote, (object source, ExecutedRoutedEventArgs e) => { orchestrator.SendDeleteNoteMessage((Note)e.Parameter); })); }
void TriviaClient_Loaded(object sender, RoutedEventArgs e) { ConnectToServerDialog connectDialog = new ConnectToServerDialog(); var dialogResult = connectDialog.ShowDialog(); if (dialogResult == null || dialogResult.Value == false) { Application.Current.Shutdown(0); return; } this.mOrchestrator = new ClientOrchestrator(connectDialog.ConnectedClient); this.mOrchestrator.RemoteDisconnect += mOrchestrator_RemoteDisconnect; this.mWindowOriginalTitle = this.Title; // Cache it since Chats / Answers may change the title this.mViewModel = new TriviaViewModel(isContributorOnlyMode: true); // Set up class to handle data updates from the server (do this before authentication is done because the auth result will contain data we need to process) DataUpdateHandler.Start(this.mOrchestrator, this.mViewModel, this.Dispatcher, this); // Make the user authenticate string playerNameChosen; AuthenticationResponse authResult = this.AuthenticateWithUserAuthentication(out playerNameChosen); //AuthenticationResponse authResult = this.AuthenticateWithHardcodedInformation(out playerNameChosen); if (authResult == null) { return; // AuthResult will be null if the user failed to authenticate successfully } if (string.IsNullOrWhiteSpace(playerNameChosen)) { throw new InvalidOperationException("No name to set"); } // At this point, the user has authenticated bool isContributorOnlyMode = TriviaSettings.Default.IsContributorOnlyMode2016 && ContestConfiguration.IsContributorModeDefaultValue; this.mViewModel.IsContributorOnlyMode = isContributorOnlyMode; this.mViewModel.TeamNumber = authResult.TeamNumber; TriviaClient.AudioTriviaUrlFormat = authResult.AudioTriviaUrlFormat; // Set it TriviaClient.PlayerName = playerNameChosen; TriviaClient.RadioStationStreamUrl = authResult.RadioStationStreamUrl; TriviaMessage.SetDefaultSenderName(playerNameChosen); this.DataContext = this.mViewModel; CommandHandler.Start(this.mOrchestrator, this.mViewModel, this.CommandBindings, this); // Active the Chat Control by giving it the orchestrator this.mChatControl.SetOrchestrator(this.mOrchestrator); AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); // Go go go!!! Note.OnCanBeDeletedCheck += (noteSubmitter) => (isContributorOnlyMode == false) || TriviaClient.PlayerName == noteSubmitter; this.mOrchestrator.AlertPlayersNotificationReceived += new ClientOrchestrator.AlertPlayersNotificationEventHandler(mOrchestrator_AlertPlayersNotificationReceived); this.mOrchestrator.GetLatestHourResponseReceived += new ClientOrchestrator.GetLatestHourEventHandler(mOrchestrator_GetLatestHourResponseReceived); this.mOrchestrator.SubmitAnswerResponseReceived += new ClientOrchestrator.SubmitAnswerEventHandler(mOrchestrator_SubmitAnswerResponseReceived); this.mOrchestrator.ChatMessageReceived += mOrchestrator_ChatMessageReceived; this.mOrchestrator.SendGetLatestHourRequest(); this.InitiatePeriodicConnectionCheck(); // Check with the server every so often to make sure the connection is good }
private static void LoadStatsIntoViewModel(Stats stats, TriviaViewModel viewModel) { if (stats.PointsMaximumByHour == null || stats.PointsMaximumByHour.Length == 0 || stats.PointsEarnedByHour == null || stats.PointsEarnedByHour.Length == 0) { return; // No data yet... no questions opened } int maxPointsPossibleInAnyHour = stats.PointsMaximumByHour.Max(); for (int i = 0; i < stats.PointsEarnedByHour.Length; ++i) { int hourNumber = i + 1; HourViewModel hour = viewModel.GetHour(hourNumber); hour.Stats.MaximumTotalPointsPossibleInAnyHour = maxPointsPossibleInAnyHour; hour.Stats.TotalPointsEarned = stats.PointsEarnedByHour[i]; hour.Stats.TotalPointsPossible = stats.PointsMaximumByHour[i]; } }
private static void LoadLinksIntoViewModel(Link[] links, TriviaViewModel viewModel) { if (links == null) { return; } if (viewModel.Links.Count > 0) { return; // We've already got them } foreach (Link link in links) { viewModel.Links.Add(link); } }
private static void LoadGlobalDataIntoViewModel(GlobalData globalData, TriviaViewModel viewModel) { /* * comment below is not true - we load global data unnecesarily repeatedly every time we GetCompleteHourData. Optimized? No. But that's ok. * // It really ought to be sufficient to load Global Data once at startup. Detect duplicate load because that's probably a subtle bug if (viewModel.Links.Count > 0) { throw new InvalidOperationException("Got GlobalData response when we already have Links"); } */ LoadStatsIntoViewModel(globalData.Stats, viewModel); LoadLinksIntoViewModel(globalData.Links, viewModel); }
public static void Start(ClientOrchestrator orchestrator, TriviaViewModel viewModel, Dispatcher dispatcher, TriviaClient window) { Contract.Requires(orchestrator != null); Contract.Requires(viewModel != null); orchestrator.AuthenticateResponseReceived += delegate (AuthenticationResponse authResponse) { // Since as of 2016, this response contains hour/player data, need to process this if (authResponse.CurrentHourData != null) // Will be null if authentication was unsuccessful { orchestrator.TriggerGetCompleteHourDataResponseReceivedEvent(authResponse.CurrentHourData); } if (authResponse.Players != null) { orchestrator.TriggerPlayersListUpdateReceivedEvent(authResponse.Players); } dispatcher.Invoke(new Action( delegate { // HourViewModel requires being on the UI thread LoadGlobalDataIntoViewModel(authResponse.GlobalData, viewModel); })); }; // Complete Hour orchestrator.GetCompleteHourDataResponseReceived += delegate(CompleteHourData chd) { dispatcher.Invoke(new Action( delegate { Debug.WriteLine("CompletHourDataResponse for hour " + chd.HourNumber.ToString()); HourViewModel hour = viewModel.GetHour(chd.HourNumber); hour.IsNoLongerCurrentHour = chd.IsNoLongerCurrentHour; hour.Questions.Clear(); hour.HasRetrievedQuestionsFromServer = true; // Flag that we've got the latest & greatest ILookup<int, Answer> answerLookup = chd.Answers.ToLookup(a => a.Number); ILookup<int, Note> noteLookup = chd.Notes.ToLookup(n => n.QuestionNumber); // Obviously this could be fancier w/ LINQ, but eh, good enough foreach (Question questionModel in chd.Questions) { var answers = answerLookup[questionModel.Number]; var notes = new DispatchableObservableCollection<Note>(noteLookup[questionModel.Number]); QuestionViewModel questionViewModel = new QuestionViewModel(questionModel, answers, notes); hour.Questions.Add(questionViewModel); } })); }; // Latest Hour (for informing the player that old hours are no longer active) orchestrator.GetLatestHourResponseReceived += delegate(int latestHour) { dispatcher.Invoke(new Action( delegate { foreach (HourViewModel hour in viewModel.Hours) { hour.IsNoLongerCurrentHour = hour.Number < latestHour; } })); }; // Questions orchestrator.OpenQuestionResponseReceived += delegate (int hourNumber, int questionNumber, bool successful, string failureMessage) { // BeginInvoke is absolutely key here, otherwise the UI thread will be tied up and messages (such as Start Audio Trivia Recording) won't // be able to be processed as long as the UI thread is in use. Regular old Invoke() will cause this problem. dispatcher.BeginInvoke(new Action( delegate { if (!(successful)) { MessageBox.Show(failureMessage, string.Format("Question #{0} Was Not Opened", questionNumber), MessageBoxButton.OK, MessageBoxImage.Exclamation); return; // Nothing to do } HourViewModel hour = viewModel.GetHour(hourNumber); // Boy, I hope that the Update Question response (which creates the question) is received before this is... QuestionViewModel question; if (!(viewModel.TryGetQuestion(hourNumber, questionNumber, out question))) { // Fake it out since we didn't get a real one from the server question = new QuestionViewModel(new Question(hourNumber, questionNumber)); hour.Questions.Add(question); } EditQuestionDialog dialog = new EditQuestionDialog(orchestrator, question, true); dialog.Owner = window; bool? result = dialog.ShowDialog(); if (!(result.HasValue) || result.Value == false) { return; // Cancelled / Closed without saving } QuestionChanges changes = dialog.GetChangesMade(); if (changes.Changes == QuestionChanges.Change.None) { return; // No changes actually were made even though the user clicked the Save button } orchestrator.SendUpdateQuestionRequest(changes); })); }; orchestrator.UpdateQuestionResponseReceived += delegate(Question updatedQuestion, bool playerInitatedUpdate) { dispatcher.Invoke(new Action( delegate { HourViewModel hour = viewModel.GetHour(updatedQuestion.Hour); // If the question is just being opened, the first we hear of this question is the Update message QuestionViewModel wrapperToUpdate; if (!(viewModel.TryGetQuestion(updatedQuestion.Identifier, out wrapperToUpdate))) { // A brand new opened question QuestionViewModel questionToAdd = new QuestionViewModel(updatedQuestion); hour.Questions.Add(questionToAdd); return; } ToastFactory.ShowAppropriateToast(viewModel.GetHour(updatedQuestion.Hour), wrapperToUpdate, updatedQuestion); if (playerInitatedUpdate) // If an automatic process updated this question (such as Predicted Duration change), a person could still be editing { wrapperToUpdate.PersonCurrentlyEditingThisQuestion = null; // Flag that there's (likely) nobody else Editing this question } wrapperToUpdate.Update(updatedQuestion, playerInitatedUpdate); })); }; orchestrator.EditingQuestionMessageReceived += delegate(EditingQuestionData data) { dispatcher.Invoke(new Action( delegate { HourViewModel hour = viewModel.GetHour(data.HourNumber); // If the question is just being opened, the first we hear of this question is the Update message QuestionViewModel questionBeingUpdated; if (!(viewModel.TryGetQuestion(data.HourNumber, data.QuestionNumber, out questionBeingUpdated))) { return; // Odd that we can't find it, but this message isn't super-important so just move on } questionBeingUpdated.PersonCurrentlyEditingThisQuestion = data.WhoEditing; })); }; // Answers orchestrator.UpdateAnswerResponseReceived += delegate(Answer answer, bool partialChanged) { dispatcher.Invoke(new Action( delegate { //Debug.WriteLine(string.Format("UpdateAnswer. Number:{0}. IsPartial:{1}. PartialChanged:{2}. HasBeenCalledIn:{3}", answer.Number, answer.Partial, partialChanged, answer.HasBeenCalledIn)); QuestionViewModel question; if (!(viewModel.TryGetQuestion(answer, out question))) { // If we can't find it - it's assumed to be for an hour that's not retrievd from the server yet. return; } if (partialChanged && answer.Partial) { // Toasty! ToastFactory.ShowToastForPartial(answer); } AnswerViewModel storedAnswer = question.SubmittedAnswers.SingleOrDefault(a => a.ID == answer.ID); if (storedAnswer != null) // Wouldn't expect it to be null, but in 2014 it was null in the wild one random time { // Make the update storedAnswer.Update(answer); } // If it changed partial status, or it's already a partial (and edited to change the text) if (partialChanged || answer.Partial) { if (IsOneWordAnswer(answer.Text)) { // The fact that this answer changed partial status doesn't affect anything else, ignore // because we only care about one-word answers question.HighlightAppearanceOfPartialsInAnswers(); } } })); }; orchestrator.SubmitAnswerResponseReceived += delegate(Answer answer) { // viewModel.TryGetQuestion() eventually could trigger a new Hour to be created, which relies on the UI thread (Hours view) dispatcher.Invoke(new Action( delegate { QuestionViewModel question; if (!(viewModel.TryGetQuestion(answer, out question))) { // If we can't find it - it's assumed to be for an hour that's not retrievd from the server yet. return; } AnswerViewModel answerViewModel = new AnswerViewModel(answer); question.SubmittedAnswers.Add(answerViewModel); question.HighlightAppearanceOfPartialInThisAnswer(answerViewModel); /* // couldn't finish this in 2011. // To keep track of how many of your personal answers are in the queue viewModel.GetHour(question.Identifier.Hour).AddAnswerSubmittedByCurrentUser(answerViewModel); */ })); }; orchestrator.AddNoteMessageReceived += delegate(Note note) { QuestionViewModel question; if (!(viewModel.TryGetQuestion(note.HourNumber, note.QuestionNumber, out question))) { // If we can't find it - it's assumed to be for an hour that's not retrievd rom the server yet. return; } question.Notes.Add(note); }; orchestrator.DeleteNoteMessageReceived += delegate (int noteId) { var allHoursStartingWithCurrent = new HourViewModel[] { viewModel.ActiveHour }.Concat(viewModel.Hours.Cast<HourViewModel>()); var allQuestionsStartingWithCurrentHour = allHoursStartingWithCurrent.SelectMany(hour => hour.Questions); var firstMatchingNote = allQuestionsStartingWithCurrentHour.SelectMany(q => q.Notes).Where(note => note.ID == noteId).FirstOrDefault(); if (firstMatchingNote == null) { // Someone must have deleted a note that's not in the Client's cache of questions yet... return; } QuestionViewModel question; bool found = viewModel.TryGetQuestion(firstMatchingNote.HourNumber, firstMatchingNote.QuestionNumber, out question); if (!(found)) { return; // Question not currently loaded by the client yet? No biggie, it'll get the latest values later when it is loaded } question.Notes.Remove(firstMatchingNote); }; /* dead code? orchestrator.GetGlobalDataResponseReceived += (globalData) => { dispatcher.Invoke(new Action( delegate { LoadGlobalDataIntoViewModel(globalData, viewModel); })); }; */ orchestrator.CurrentCallerChangedMessageReceived += (currentCaller) => { viewModel.CurrentCaller = currentCaller; }; orchestrator.ResearcherChangedResponseReceived += (changes) => { dispatcher.Invoke(new Action( delegate { Debug.WriteLine("ResearcherChanged response received"); // First, remove this researcher from any (if applicable) questions they were researching previously var allHoursStartingWithCurrent = new HourViewModel[] { viewModel.ActiveHour }.Concat(viewModel.Hours.Cast<HourViewModel>()); var allQuestionsStartingWithCurrentHour = allHoursStartingWithCurrent.SelectMany(hour => hour.Questions); var previouslyResearchedQuestion = allQuestionsStartingWithCurrentHour.Where(q => q.Researchers.Contains(changes.Name)).FirstOrDefault(); Debug.WriteLine(string.Format("PreviouslyRsearchedQuestion: {0}", previouslyResearchedQuestion == null ? "null" : previouslyResearchedQuestion.Identifier.Number.ToString())); if (previouslyResearchedQuestion != null) { // They are no longer researching this question previouslyResearchedQuestion.Researchers.Remove(changes.Name); // If the "person" is the current user, need to uncheck the checkbox if (changes.Name == TriviaClient.PlayerName) { previouslyResearchedQuestion.IsBeingResearched = false; Debug.WriteLine(string.Format("Unchecking checkbox for: {0}", previouslyResearchedQuestion.Identifier.Number.ToString())); } } QuestionViewModel question; bool found = viewModel.TryGetQuestion(changes.HourNumber, changes.QuestionNumber, out question); if (!(found)) { return; // Question not currently loaded by the client yet? No biggie, it'll get the latest values later when it is loaded } question.Researchers.Add(changes.Name); // Add to end of list Debug.WriteLine(string.Format("{0} added to researchers of question: {1}", changes.Name, question.Identifier.Number)); }), DispatcherPriority.Background); // ResearcherChanged tends to trigger some slow things, so make it a low priority since it's nonessential }; // Links orchestrator.AddLinkResponseReceived += (link) => { dispatcher.Invoke(new Action( delegate { viewModel.Links.Add(link); })); }; // Players orchestrator.PlayersListUpdateReceived += (IEnumerable<Player> players) => { dispatcher.Invoke(new Action( delegate { viewModel.ConnectedPlayers.Clear(); viewModel.RecentlyJoinedPlayers.Clear(); viewModel.RecentlyDepartedPlayers.Clear(); foreach (Player player in players) { if (player.RecentlyDeparted) { viewModel.RecentlyDepartedPlayers.Add(player); } else if (player.RecentlyJoined) { viewModel.RecentlyJoinedPlayers.Add(player); } else { viewModel.ConnectedPlayers.Add(player); } } }), DispatcherPriority.Background); }; }